Skip to content

Commit 0eed0e1

Browse files
committed
auto merge of #302 : alexcrichton/cargo/doc-test, r=wycats
Whenever `cargo test` is run and a testable library target is available, the doc tests will be run. This can be opted out of with `test = false` as usual. This is currently not super useful due to rust-lang/rust#16157, but I expect that to be merged soon. In the meantime examples will need to `extern crate foo` explicitly. Closes #334
2 parents 823b5a3 + 35ac31e commit 0eed0e1

File tree

9 files changed

+332
-132
lines changed

9 files changed

+332
-132
lines changed

src/bin/cargo-test.rs

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ extern crate docopt;
88
use std::io::process::ExitStatus;
99

1010
use cargo::ops;
11-
use cargo::{execute_main_without_stdin};
12-
use cargo::core::{MultiShell};
13-
use cargo::util;
11+
use cargo::execute_main_without_stdin;
12+
use cargo::core::MultiShell;
1413
use cargo::util::{CliResult, CliError, CargoError};
1514
use cargo::util::important_paths::{find_root_manifest_for_cwd};
1615

@@ -48,24 +47,18 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
4847
target: None,
4948
};
5049

51-
let test_executables = try!(ops::compile(&root,
52-
&mut compile_opts).map_err(|err| {
50+
let err = try!(ops::run_tests(&root, &mut compile_opts,
51+
options.arg_args.as_slice()).map_err(|err| {
5352
CliError::from_boxed(err, 101)
5453
}));
55-
56-
let test_dir = root.dir_path().join("target").join("test");
57-
58-
for file in test_executables.iter() {
59-
try!(util::process(test_dir.join(file.as_slice()))
60-
.args(options.arg_args.as_slice())
61-
.exec().map_err(|e| {
62-
let exit_status = match e.exit {
54+
match err {
55+
None => Ok(None),
56+
Some(err) => {
57+
let status = match err.exit {
6358
Some(ExitStatus(i)) => i as uint,
64-
_ => 1,
59+
_ => 101,
6560
};
66-
CliError::from_boxed(e.mark_human(), exit_status)
67-
}));
61+
Err(CliError::from_boxed(err.mark_human(), status))
62+
}
6863
}
69-
70-
Ok(None)
7164
}

src/cargo/ops/cargo_compile.rs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ pub struct CompileOptions<'a> {
4444
}
4545

4646
pub fn compile(manifest_path: &Path,
47-
options: &mut CompileOptions) -> CargoResult<Vec<String>> {
47+
options: &mut CompileOptions)
48+
-> CargoResult<HashMap<PackageId, Vec<Path>>> {
4849
let CompileOptions { update, env, ref mut shell, jobs, target } = *options;
4950
let target = target.map(|s| s.to_string());
5051

@@ -114,31 +115,20 @@ pub fn compile(manifest_path: &Path,
114115
}
115116
}).collect::<Vec<&Target>>();
116117

117-
{
118+
let ret = {
118119
let _p = profile::start("compiling");
119120
let mut config = try!(Config::new(*shell, update, jobs, target));
120121
try!(scrape_target_config(&mut config, &user_configs));
121122

122123
try!(ops::compile_targets(env.as_slice(), targets.as_slice(), &package,
123124
&PackageSet::new(packages.as_slice()),
124125
&resolve_with_overrides, &sources,
125-
&mut config));
126-
}
126+
&mut config))
127+
};
127128

128129
try!(ops::write_resolve(&package, &resolve));
129130

130-
let test_executables: Vec<String> = targets.iter()
131-
.filter_map(|target| {
132-
if target.get_profile().is_test() {
133-
debug!("Run Target: {}", target.get_name());
134-
Some(target.file_stem())
135-
} else {
136-
debug!("Skip Target: {}", target.get_name());
137-
None
138-
}
139-
}).collect();
140-
141-
Ok(test_executables)
131+
return Ok(ret);
142132
}
143133

144134
fn source_ids_from_config(configs: &HashMap<String, config::ConfigValue>,

src/cargo/ops/cargo_rustc/mod.rs

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::HashSet;
1+
use std::collections::{HashSet, HashMap};
22
use std::dynamic_lib::DynamicLibrary;
33
use std::io::{fs, UserRWX};
44
use std::os;
@@ -41,11 +41,15 @@ fn uniq_target_dest<'a>(targets: &[&'a Target]) -> Option<&'a str> {
4141
curr.unwrap()
4242
}
4343

44+
// Returns a mapping of the root package plus its immediate dependencies to
45+
// where the compiled libraries are all located.
4446
pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package,
45-
deps: &PackageSet, resolve: &'a Resolve, sources: &'a SourceMap,
46-
config: &'a mut Config<'a>) -> CargoResult<()> {
47+
deps: &PackageSet, resolve: &'a Resolve,
48+
sources: &'a SourceMap,
49+
config: &'a mut Config<'a>)
50+
-> CargoResult<HashMap<PackageId, Vec<Path>>> {
4751
if targets.is_empty() {
48-
return Ok(());
52+
return Ok(HashMap::new());
4953
}
5054

5155
debug!("compile_targets; targets={}; pkg={}; deps={}", targets, pkg, deps);
@@ -82,8 +86,12 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package,
8286
cx.primary();
8387
try!(compile(targets, pkg, &mut cx, &mut queue));
8488

89+
let ret = build_return_map(&cx, pkg, deps);
90+
8591
// Now that we've figured out everything that we're going to do, do it!
86-
queue.execute(cx.config)
92+
try!(queue.execute(cx.config));
93+
94+
Ok(ret)
8795
}
8896

8997
fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package,
@@ -447,3 +455,36 @@ fn pre_version_component(v: &Version) -> Option<String> {
447455

448456
Some(ret)
449457
}
458+
459+
fn build_return_map(cx: &Context, root: &Package, deps: &PackageSet)
460+
-> HashMap<PackageId, Vec<Path>> {
461+
let mut ret = HashMap::new();
462+
match cx.resolve.deps(root.get_package_id()) {
463+
Some(mut my_deps) => {
464+
for dep in my_deps {
465+
let pkg = deps.iter().find(|p| p.get_package_id() == dep).unwrap();
466+
ret.insert(dep.clone(), build_paths(cx, pkg, false));
467+
}
468+
}
469+
None => {}
470+
}
471+
ret.insert(root.get_package_id().clone(), build_paths(cx, root, true));
472+
return ret;
473+
474+
fn build_paths(cx: &Context, pkg: &Package, root: bool) -> Vec<Path> {
475+
pkg.get_targets().iter().filter(|target| {
476+
target.get_profile().is_compile() && target.is_lib()
477+
}).flat_map(|target| {
478+
let kind = if target.get_profile().is_plugin() {
479+
KindPlugin
480+
} else {
481+
KindTarget
482+
};
483+
let layout = cx.layout(kind);
484+
cx.target_filenames(target).move_iter().map(|filename| {
485+
let root = if root {layout.root()} else {layout.deps()};
486+
root.join(filename)
487+
}).collect::<Vec<Path>>().move_iter()
488+
}).collect()
489+
}
490+
}

src/cargo/ops/cargo_test.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use std::os;
2+
3+
use core::Source;
4+
use sources::PathSource;
5+
use ops;
6+
use util::{process, CargoResult, ProcessError};
7+
8+
pub fn run_tests(manifest_path: &Path,
9+
options: &mut ops::CompileOptions,
10+
args: &[String]) -> CargoResult<Option<ProcessError>> {
11+
let mut source = PathSource::for_path(&manifest_path.dir_path());
12+
try!(source.update());
13+
let package = try!(source.get_root_package());
14+
15+
let compiled_libs = try!(ops::compile(manifest_path, options));
16+
17+
let mut exes: Vec<Path> = package.get_targets().iter().filter_map(|target| {
18+
if !target.get_profile().is_test() { return None }
19+
let root = package.get_root().join("target");
20+
let root = match target.get_profile().get_dest() {
21+
Some(dest) => root.join(dest),
22+
None => root,
23+
};
24+
Some(root.join(target.file_stem()))
25+
}).collect();
26+
exes.sort();
27+
28+
let cwd = os::getcwd();
29+
for exe in exes.iter() {
30+
let to_display = match exe.path_relative_from(&cwd) {
31+
Some(path) => path,
32+
None => exe.clone(),
33+
};
34+
try!(options.shell.status("Running", to_display.display()));
35+
match process(exe).args(args).exec() {
36+
Ok(()) => {}
37+
Err(e) => return Ok(Some(e))
38+
}
39+
}
40+
41+
let mut libs = package.get_targets().iter().filter_map(|target| {
42+
if !target.get_profile().is_test() || !target.is_lib() {
43+
return None
44+
}
45+
Some((target.get_src_path(), target.get_name()))
46+
});
47+
48+
for (lib, name) in libs {
49+
try!(options.shell.status("Doc-tests", name));
50+
let mut p = process("rustdoc").arg("--test").arg(lib)
51+
.arg("--crate-name").arg(name)
52+
.arg("-L").arg("target/test")
53+
.arg("-L").arg("target/test/deps")
54+
.cwd(package.get_root());
55+
56+
// FIXME(rust-lang/rust#16272): this should just always be passed.
57+
if args.len() > 0 {
58+
p = p.arg("--test-args").arg(args.connect(" "));
59+
}
60+
61+
for (pkg, libs) in compiled_libs.iter() {
62+
for lib in libs.iter() {
63+
let mut arg = pkg.get_name().as_bytes().to_vec();
64+
arg.push(b'=');
65+
arg.push_all(lib.as_vec());
66+
p = p.arg("--extern").arg(arg.as_slice());
67+
}
68+
}
69+
70+
match p.exec() {
71+
Ok(()) => {}
72+
Err(e) => return Ok(Some(e)),
73+
}
74+
}
75+
76+
Ok(None)
77+
}

src/cargo/ops/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub use self::cargo_new::{new, NewOptions};
77
pub use self::cargo_doc::{doc, DocOptions};
88
pub use self::cargo_generate_lockfile::{generate_lockfile, write_resolve};
99
pub use self::cargo_generate_lockfile::{update_lockfile, load_lockfile};
10+
pub use self::cargo_test::run_tests;
1011

1112
mod cargo_clean;
1213
mod cargo_compile;
@@ -16,3 +17,4 @@ mod cargo_run;
1617
mod cargo_new;
1718
mod cargo_doc;
1819
mod cargo_generate_lockfile;
20+
mod cargo_test;

src/cargo/util/toml.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ fn normalize(libs: &[TomlLibTarget],
626626
|bin| format!("src/bin/{}.rs", bin.name));
627627
},
628628
([_, ..], []) => {
629-
lib_targets(&mut ret, libs, test_dep, metadata);
629+
lib_targets(&mut ret, libs, Needed, metadata);
630630
},
631631
([], [_, ..]) => {
632632
bin_targets(&mut ret, bins, test_dep, metadata,

tests/support/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,3 +499,4 @@ pub static RUNNING: &'static str = " Running";
499499
pub static COMPILING: &'static str = " Compiling";
500500
pub static FRESH: &'static str = " Fresh";
501501
pub static UPDATING: &'static str = " Updating";
502+
pub static DOCTEST: &'static str = " Doc-tests";

tests/test_cargo_freshness.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ test!(modify_only_some_files {
7070

7171
let lib = p.root().join("src/lib.rs");
7272
let bin = p.root().join("src/b.rs");
73-
let test = p.root().join("tests/test.rs");
7473

7574
File::create(&lib).write_str("invalid rust code").assert();
7675
lib.move_into_the_past().assert();
@@ -85,9 +84,4 @@ test!(modify_only_some_files {
8584
{compiling} foo v0.0.1 (file:{dir})
8685
", compiling = COMPILING, dir = p.root().display())));
8786
assert_that(&p.bin("foo"), existing_file());
88-
89-
// Make sure the tests don't recompile the lib
90-
File::create(&test).write_str("fn foo() {}").assert();
91-
assert_that(p.process(cargo_dir().join("cargo-test")),
92-
execs().with_status(0));
9387
})

0 commit comments

Comments
 (0)