From 0484cd880eded66739792cc1bc8e393e9f108365 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 31 Jul 2014 20:21:13 -0700 Subject: [PATCH] Implement doc tests 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. --- src/cargo/ops/cargo_compile.rs | 11 +- src/cargo/ops/cargo_rustc/mod.rs | 51 +++++- src/cargo/ops/cargo_test.rs | 52 +++++- src/cargo/util/toml.rs | 2 +- tests/support/mod.rs | 1 + tests/test_cargo_freshness.rs | 6 - tests/test_cargo_test.rs | 274 +++++++++++++++++++++---------- 7 files changed, 290 insertions(+), 107 deletions(-) diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index dabbd8e969b..3b605e1a99e 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -44,7 +44,8 @@ pub struct CompileOptions<'a> { } pub fn compile(manifest_path: &Path, - options: &mut CompileOptions) -> CargoResult<()> { + options: &mut CompileOptions) + -> CargoResult>> { let CompileOptions { update, env, ref mut shell, jobs, target } = *options; let target = target.map(|s| s.to_string()); @@ -114,7 +115,7 @@ pub fn compile(manifest_path: &Path, } }).collect::>(); - { + let ret = { let _p = profile::start("compiling"); let mut config = try!(Config::new(*shell, update, jobs, target)); try!(scrape_target_config(&mut config, &user_configs)); @@ -122,12 +123,12 @@ pub fn compile(manifest_path: &Path, try!(ops::compile_targets(env.as_slice(), targets.as_slice(), &package, &PackageSet::new(packages.as_slice()), &resolve_with_overrides, &sources, - &mut config)); - } + &mut config)) + }; try!(ops::write_resolve(&package, &resolve)); - Ok(()) + return Ok(ret); } fn source_ids_from_config(configs: &HashMap, diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index 8e8e80482ad..f11b9dd7ddc 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::{HashSet, HashMap}; use std::dynamic_lib::DynamicLibrary; use std::io::{fs, UserRWX}; use std::os; @@ -41,11 +41,15 @@ fn uniq_target_dest<'a>(targets: &[&'a Target]) -> Option<&'a str> { curr.unwrap() } +// Returns a mapping of the root package plus its immediate dependencies to +// where the compiled libraries are all located. pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package, - deps: &PackageSet, resolve: &'a Resolve, sources: &'a SourceMap, - config: &'a mut Config<'a>) -> CargoResult<()> { + deps: &PackageSet, resolve: &'a Resolve, + sources: &'a SourceMap, + config: &'a mut Config<'a>) + -> CargoResult>> { if targets.is_empty() { - return Ok(()); + return Ok(HashMap::new()); } 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, cx.primary(); try!(compile(targets, pkg, &mut cx, &mut queue)); + let ret = build_return_map(&cx, pkg, deps); + // Now that we've figured out everything that we're going to do, do it! - queue.execute(cx.config) + try!(queue.execute(cx.config)); + + Ok(ret) } fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package, @@ -453,3 +461,36 @@ fn pre_version_component(v: &Version) -> Option { Some(ret) } + +fn build_return_map(cx: &Context, root: &Package, deps: &PackageSet) + -> HashMap> { + let mut ret = HashMap::new(); + match cx.resolve.deps(root.get_package_id()) { + Some(mut my_deps) => { + for dep in my_deps { + let pkg = deps.iter().find(|p| p.get_package_id() == dep).unwrap(); + ret.insert(dep.clone(), build_paths(cx, pkg, false)); + } + } + None => {} + } + ret.insert(root.get_package_id().clone(), build_paths(cx, root, true)); + return ret; + + fn build_paths(cx: &Context, pkg: &Package, root: bool) -> Vec { + pkg.get_targets().iter().filter(|target| { + target.get_profile().is_compile() && target.is_lib() + }).flat_map(|target| { + let kind = if target.get_profile().is_plugin() { + KindPlugin + } else { + KindTarget + }; + let layout = cx.layout(kind); + cx.target_filenames(target).move_iter().map(|filename| { + let root = if root {layout.root()} else {layout.deps()}; + root.join(filename) + }).collect::>().move_iter() + }).collect() + } +} diff --git a/src/cargo/ops/cargo_test.rs b/src/cargo/ops/cargo_test.rs index 6828235dd3a..60257142e3c 100644 --- a/src/cargo/ops/cargo_test.rs +++ b/src/cargo/ops/cargo_test.rs @@ -1,3 +1,5 @@ +use std::os; + use core::Source; use sources::PathSource; use ops; @@ -10,9 +12,9 @@ pub fn run_tests(manifest_path: &Path, try!(source.update()); let package = try!(source.get_root_package()); - try!(ops::compile(manifest_path, options)); + let compiled_libs = try!(ops::compile(manifest_path, options)); - let mut exes = package.get_targets().iter().filter_map(|target| { + let mut exes: Vec = package.get_targets().iter().filter_map(|target| { if !target.get_profile().is_test() { return None } let root = package.get_root().join("target"); let root = match target.get_profile().get_dest() { @@ -20,14 +22,56 @@ pub fn run_tests(manifest_path: &Path, None => root, }; Some(root.join(target.file_stem())) - }); + }).collect(); + exes.sort(); - for exe in exes { + let cwd = os::getcwd(); + for exe in exes.iter() { + let to_display = match exe.path_relative_from(&cwd) { + Some(path) => path, + None => exe.clone(), + }; + try!(options.shell.status("Running", to_display.display())); match process(exe).args(args).exec() { Ok(()) => {} Err(e) => return Ok(Some(e)) } } + let mut libs = package.get_targets().iter().filter_map(|target| { + if !target.get_profile().is_test() || !target.is_lib() { + return None + } + Some((target.get_src_path(), target.get_name())) + }); + + for (lib, name) in libs { + try!(options.shell.status("Doc-tests", name)); + let mut p = process("rustdoc").arg("--test").arg(lib) + .arg("--crate-name").arg(name) + .arg("-L").arg("target/test") + .arg("-L").arg("target/test/deps") + .cwd(package.get_root()); + + // FIXME(rust-lang/rust#16272): this should just always be passed. + if args.len() > 0 { + p = p.arg("--test-args").arg(args.connect(" ")); + } + + for (pkg, libs) in compiled_libs.iter() { + for lib in libs.iter() { + let mut arg = pkg.get_name().as_bytes().to_vec(); + arg.push(b'='); + arg.push_all(lib.as_vec()); + p = p.arg("--extern").arg(arg.as_slice()); + } + } + + match p.exec() { + Ok(()) => {} + Err(e) => return Ok(Some(e)), + } + } + Ok(None) } diff --git a/src/cargo/util/toml.rs b/src/cargo/util/toml.rs index 22196e6701b..b0bb5b4fd4d 100644 --- a/src/cargo/util/toml.rs +++ b/src/cargo/util/toml.rs @@ -623,7 +623,7 @@ fn normalize(libs: &[TomlLibTarget], |bin| format!("src/bin/{}.rs", bin.name)); }, ([_, ..], []) => { - lib_targets(&mut ret, libs, test_dep, metadata); + lib_targets(&mut ret, libs, Needed, metadata); }, ([], [_, ..]) => { bin_targets(&mut ret, bins, test_dep, metadata, diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 4de930804c9..080e4349ed3 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -499,3 +499,4 @@ pub static RUNNING: &'static str = " Running"; pub static COMPILING: &'static str = " Compiling"; pub static FRESH: &'static str = " Fresh"; pub static UPDATING: &'static str = " Updating"; +pub static DOCTEST: &'static str = " Doc-tests"; diff --git a/tests/test_cargo_freshness.rs b/tests/test_cargo_freshness.rs index 54d1490f16b..3b4a84e8a9d 100644 --- a/tests/test_cargo_freshness.rs +++ b/tests/test_cargo_freshness.rs @@ -70,7 +70,6 @@ test!(modify_only_some_files { let lib = p.root().join("src/lib.rs"); let bin = p.root().join("src/b.rs"); - let test = p.root().join("tests/test.rs"); File::create(&lib).write_str("invalid rust code").assert(); lib.move_into_the_past().assert(); @@ -85,9 +84,4 @@ test!(modify_only_some_files { {compiling} foo v0.0.1 (file:{dir}) ", compiling = COMPILING, dir = p.root().display()))); assert_that(&p.bin("foo"), existing_file()); - - // Make sure the tests don't recompile the lib - File::create(&test).write_str("fn foo() {}").assert(); - assert_that(p.process(cargo_dir().join("cargo-test")), - execs().with_status(0)); }) diff --git a/tests/test_cargo_test.rs b/tests/test_cargo_test.rs index 56878e55e08..15cb1e8e4da 100644 --- a/tests/test_cargo_test.rs +++ b/tests/test_cargo_test.rs @@ -2,7 +2,7 @@ use std::path; use std::str; use support::{project, execs, basic_bin_manifest, basic_lib_manifest}; -use support::{COMPILING, cargo_dir, ResultTest, FRESH}; +use support::{COMPILING, cargo_dir, ResultTest, FRESH, RUNNING, DOCTEST}; use support::paths::PathExt; use hamcrest::{assert_that, existing_file}; use cargo::util::process; @@ -34,11 +34,16 @@ test!(cargo_test_simple { execs().with_stdout("hello\n")); assert_that(p.process(cargo_dir().join("cargo-test")), - execs().with_stdout(format!("{} foo v0.5.0 (file:{})\n\n\ - running 1 test\n\ - test test_hello ... ok\n\n\ - test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured\n\n", - COMPILING, p.root().display()))); + execs().with_stdout(format!("\ +{} foo v0.5.0 (file:{}) +{} target[..]test[..]foo + +running 1 test +test test_hello ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured\n\n", + COMPILING, p.root().display(), + RUNNING))); }) test!(many_similar_names { @@ -95,19 +100,30 @@ test!(cargo_test_failing_test { execs().with_stdout("hello\n")); assert_that(p.process(cargo_dir().join("cargo-test")), - execs().with_stdout(format!("{} foo v0.5.0 (file:{})\n\n\ - running 1 test\n\ - test test_hello ... FAILED\n\n\ - failures:\n\n\ - ---- test_hello stdout ----\n\ - task 'test_hello' failed at 'assertion failed: \ - `(left == right) && (right == left)` (left: \ - `hello`, right: `nope`)', src{sep}foo.rs:12\n\n\n\n\ - failures:\n test_hello\n\n\ - test result: FAILED. 0 passed; 1 failed; \ - 0 ignored; 0 measured\n\n", - COMPILING, p.root().display(), - sep = path::SEP)) + execs().with_stdout(format!("\ +{} foo v0.5.0 (file:{}) +{} target[..]test[..]foo + +running 1 test +test test_hello ... FAILED + +failures: + +---- test_hello stdout ---- +task 'test_hello' failed at 'assertion failed: \ + `(left == right) && (right == left)` (left: \ + `hello`, right: `nope`)', src{sep}foo.rs:12 + + + +failures: + test_hello + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured + +", + COMPILING, p.root().display(), RUNNING, + sep = path::SEP)) .with_stderr(format!("\ task '
' failed at 'Some tests failed', [..] Could not execute process `{test}[..]` (status=101) @@ -127,10 +143,18 @@ test!(test_with_lib_dep { name = "baz" path = "src/main.rs" "#) - .file("src/lib.rs", " + .file("src/lib.rs", r#" + /// + /// ```rust + /// extern crate foo; + /// fn main() { + /// println!("{}", foo::foo()); + /// } + /// ``` + /// pub fn foo(){} #[test] fn lib_test() {} - ") + "#) .file("src/main.rs", " extern crate foo; @@ -140,26 +164,32 @@ test!(test_with_lib_dep { fn bin_test() {} "); - let output = p.cargo_process("cargo-test") - .exec_with_output().assert(); - let out = str::from_utf8(output.output.as_slice()).assert(); + assert_that(p.cargo_process("cargo-test"), + execs().with_stdout(format!("\ +{} foo v0.0.1 (file:{}) +{running} target[..]test[..]baz-[..] - let bin = "\ running 1 test test bin_test ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured"; - let lib = "\ +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + +{running} target[..]test[..]foo + running 1 test test lib_test ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured"; +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured - let head = format!("{compiling} foo v0.0.1 (file:{dir})", - compiling = COMPILING, dir = p.root().display()); +{doctest} foo - assert!(out == format!("{}\n\n{}\n\n\n{}\n\n", head, bin, lib).as_slice() || - out == format!("{}\n\n{}\n\n\n{}\n\n", head, lib, bin).as_slice()); +running 1 test +test foo_0 ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + +", + COMPILING, p.root().display(), running = RUNNING, doctest = DOCTEST))) }) test!(test_with_deep_lib_dep { @@ -200,13 +230,21 @@ test!(test_with_deep_lib_dep { .with_stdout(format!("\ {compiling} foo v0.0.1 (file:{dir}) {compiling} bar v0.0.1 (file:{dir}) +{running} target[..] running 1 test test bar_test ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured\n\n\ +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measure + +{doctest} bar + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured\n\n\ ", - compiling = COMPILING, + compiling = COMPILING, running = RUNNING, + doctest = DOCTEST, dir = p.root().display()).as_slice())); }) @@ -235,26 +273,31 @@ test!(external_test_explicit { fn external_test() { assert_eq!(foo::get_hello(), "Hello") } "#); - let output = p.cargo_process("cargo-test") - .exec_with_output().assert(); - let out = str::from_utf8(output.output.as_slice()).assert(); + assert_that(p.cargo_process("cargo-test"), + execs().with_stdout(format!("\ +{} foo v0.0.1 (file:{}) +{running} target[..]test[..]foo-[..] - let internal = "\ running 1 test test internal_test ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured"; - let external = "\ +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + +{running} target[..]test[..]test-[..] + running 1 test test external_test ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured"; +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured - let head = format!("{compiling} foo v0.0.1 (file:{dir})", - compiling = COMPILING, dir = p.root().display()); +{doctest} foo - assert!(out == format!("{}\n\n{}\n\n\n{}\n\n", head, internal, external).as_slice() || - out == format!("{}\n\n{}\n\n\n{}\n\n", head, external, internal).as_slice()); +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured + +", + COMPILING, p.root().display(), running = RUNNING, doctest = DOCTEST))) }) test!(external_test_implicit { @@ -278,26 +321,31 @@ test!(external_test_implicit { fn external_test() { assert_eq!(foo::get_hello(), "Hello") } "#); - let output = p.cargo_process("cargo-test") - .exec_with_output().assert(); - let out = str::from_utf8(output.output.as_slice()).assert(); + assert_that(p.cargo_process("cargo-test"), + execs().with_stdout(format!("\ +{} foo v0.0.1 (file:{}) +{running} target[..]test[..]external-[..] - let internal = "\ running 1 test -test internal_test ... ok +test external_test ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + +{running} target[..]test[..]foo-[..] -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured"; - let external = "\ running 1 test -test external_test ... ok +test internal_test ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + +{doctest} foo -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured"; +running 0 tests - let head = format!("{compiling} foo v0.0.1 (file:{dir})", - compiling = COMPILING, dir = p.root().display()); +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured - assert!(out == format!("{}\n\n{}\n\n\n{}\n\n", head, internal, external).as_slice() || - out == format!("{}\n\n{}\n\n\n{}\n\n", head, external, internal).as_slice()); +", + COMPILING, p.root().display(), running = RUNNING, doctest = DOCTEST))) }) test!(dont_run_examples { @@ -334,26 +382,42 @@ test!(pass_through_command_line { execs().with_status(0) .with_stdout(format!("\ {compiling} foo v0.0.1 (file:{dir}) +{running} target[..]test[..]foo running 1 test test bar ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured\n\n\ +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + +{doctest} foo + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured\n\n\ ", - compiling = COMPILING, + compiling = COMPILING, running = RUNNING, + doctest = DOCTEST, dir = p.root().display()).as_slice())); assert_that(p.cargo_process("cargo-test").arg("foo"), execs().with_status(0) .with_stdout(format!("\ {compiling} foo v0.0.1 (file:{dir}) +{running} target[..]test[..]foo running 1 test test foo ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured\n\n\ +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + +{doctest} foo + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured\n\n\ ", - compiling = COMPILING, + compiling = COMPILING, running = RUNNING, + doctest = DOCTEST, dir = p.root().display()).as_slice())); }) @@ -400,27 +464,31 @@ test!(lib_bin_same_name { fn bin_test() {} "); - let output = p.cargo_process("cargo-test") - .exec_with_output().assert(); - let out = str::from_utf8(output.output.as_slice()).assert(); + assert_that(p.cargo_process("cargo-test"), + execs().with_stdout(format!("\ +{} foo v0.0.1 (file:{}) +{running} target[..]test[..]foo-[..] - let bin = "\ running 1 test -test bin_test ... ok +test [..] ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + +{running} target[..]test[..]foo-[..] -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured"; - let lib = "\ running 1 test -test lib_test ... ok +test [..] ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured"; +{doctest} foo - let head = format!("{compiling} foo v0.0.1 (file:{dir})", - compiling = COMPILING, dir = p.root().display()); +running 0 tests - assert!(out == format!("{}\n\n{}\n\n\n{}\n\n", head, bin, lib).as_slice() || - out == format!("{}\n\n{}\n\n\n{}\n\n", head, lib, bin).as_slice(), - "bad output: {}", out); +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured + +", + COMPILING, p.root().display(), running = RUNNING, doctest = DOCTEST))) }) test!(lib_with_standard_name { @@ -449,13 +517,14 @@ test!(lib_with_standard_name { execs().with_status(0) .with_stdout(format!("\ {compiling} syntax v0.0.1 (file:{dir}) +{running} target[..]test[..]test-[..] running 1 test test test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured\n\n\ ", - compiling = COMPILING, + compiling = COMPILING, running = RUNNING, dir = p.root().display()).as_slice())); }) @@ -487,13 +556,14 @@ test!(lib_with_standard_name2 { execs().with_status(0) .with_stdout(format!("\ {compiling} syntax v0.0.1 (file:{dir}) +{running} target[..]test[..]syntax-[..] running 1 test test test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured\n\n\ ", - compiling = COMPILING, + compiling = COMPILING, running = RUNNING, dir = p.root().display()).as_slice())); }) @@ -545,26 +615,42 @@ test!(test_dylib { execs().with_status(0) .with_stdout(format!("\ {compiling} foo v0.0.1 (file:{dir}) +{running} target[..]test[..]foo-[..] running 1 test test foo ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured\n\n\ +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + +{doctest} foo + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured\n\n\ ", - compiling = COMPILING, + compiling = COMPILING, running = RUNNING, + doctest = DOCTEST, dir = p.root().display()).as_slice())); p.root().move_into_the_past().assert(); assert_that(p.process(cargo_dir().join("cargo-test")), execs().with_status(0) .with_stdout(format!("\ {fresh} foo v0.0.1 (file:{dir}) +{running} target[..]test[..]foo-[..] running 1 test test foo ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured\n\n\ +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + +{doctest} foo + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured\n\n\ ", - fresh = FRESH, + fresh = FRESH, running = RUNNING, + doctest = DOCTEST, dir = p.root().display()).as_slice())); }) @@ -586,25 +672,41 @@ test!(test_twice_with_build_cmd { execs().with_status(0) .with_stdout(format!("\ {compiling} foo v0.0.1 (file:{dir}) +{running} target[..]test[..]foo-[..] running 1 test test foo ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured\n\n\ +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + +{doctest} foo + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured\n\n\ ", - compiling = COMPILING, + compiling = COMPILING, running = RUNNING, + doctest = DOCTEST, dir = p.root().display()).as_slice())); assert_that(p.process(cargo_dir().join("cargo-test")), execs().with_status(0) .with_stdout(format!("\ {fresh} foo v0.0.1 (file:{dir}) +{running} target[..]test[..]foo-[..] running 1 test test foo ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured\n\n\ +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + +{doctest} foo + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured\n\n\ ", - fresh = FRESH, + fresh = FRESH, running = RUNNING, + doctest = DOCTEST, dir = p.root().display()).as_slice())); })