From 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 14:41:41 +0200 Subject: Merging upstream version 1.70.0+dfsg2. Signed-off-by: Daniel Baumann --- src/tools/cargo/tests/testsuite/doc.rs | 2503 ++++++++++++++++++++++++++++++++ 1 file changed, 2503 insertions(+) create mode 100644 src/tools/cargo/tests/testsuite/doc.rs (limited to 'src/tools/cargo/tests/testsuite/doc.rs') diff --git a/src/tools/cargo/tests/testsuite/doc.rs b/src/tools/cargo/tests/testsuite/doc.rs new file mode 100644 index 000000000..739bcf376 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/doc.rs @@ -0,0 +1,2503 @@ +//! Tests for the `cargo doc` command. + +use cargo::core::compiler::RustDocFingerprint; +use cargo_test_support::paths::CargoPathExt; +use cargo_test_support::registry::Package; +use cargo_test_support::{basic_lib_manifest, basic_manifest, git, project}; +use cargo_test_support::{rustc_host, symlink_supported, tools}; +use std::fs; +use std::str; + +#[cargo_test] +fn simple() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + build = "build.rs" + "#, + ) + .file("build.rs", "fn main() {}") + .file("src/lib.rs", "pub fn foo() {}") + .build(); + + p.cargo("doc") + .with_stderr( + "\ +[..] foo v0.0.1 ([CWD]) +[..] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + assert!(p.root().join("target/doc").is_dir()); + assert!(p.root().join("target/doc/foo/index.html").is_file()); +} + +#[cargo_test] +fn doc_no_libs() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [[bin]] + name = "foo" + doc = false + "#, + ) + .file("src/main.rs", "bad code") + .build(); + + p.cargo("doc").run(); +} + +#[cargo_test] +fn doc_twice() { + let p = project().file("src/lib.rs", "pub fn foo() {}").build(); + + p.cargo("doc") + .with_stderr( + "\ +[DOCUMENTING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + + p.cargo("doc").with_stdout("").run(); +} + +#[cargo_test] +fn doc_deps() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + "#, + ) + .file("src/lib.rs", "extern crate bar; pub fn foo() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .build(); + + p.cargo("doc") + .with_stderr( + "\ +[..] bar v0.0.1 ([CWD]/bar) +[..] bar v0.0.1 ([CWD]/bar) +[DOCUMENTING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + + assert!(p.root().join("target/doc").is_dir()); + assert!(p.root().join("target/doc/foo/index.html").is_file()); + assert!(p.root().join("target/doc/bar/index.html").is_file()); + + // Verify that it only emits rmeta for the dependency. + assert_eq!(p.glob("target/debug/**/*.rlib").count(), 0); + assert_eq!(p.glob("target/debug/deps/libbar-*.rmeta").count(), 1); + + p.cargo("doc") + .env("CARGO_LOG", "cargo::ops::cargo_rustc::fingerprint") + .with_stdout("") + .run(); + + assert!(p.root().join("target/doc").is_dir()); + assert!(p.root().join("target/doc/foo/index.html").is_file()); + assert!(p.root().join("target/doc/bar/index.html").is_file()); +} + +#[cargo_test] +fn doc_no_deps() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + "#, + ) + .file("src/lib.rs", "extern crate bar; pub fn foo() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .build(); + + p.cargo("doc --no-deps") + .with_stderr( + "\ +[CHECKING] bar v0.0.1 ([CWD]/bar) +[DOCUMENTING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + + assert!(p.root().join("target/doc").is_dir()); + assert!(p.root().join("target/doc/foo/index.html").is_file()); + assert!(!p.root().join("target/doc/bar/index.html").is_file()); +} + +#[cargo_test] +fn doc_only_bin() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + "#, + ) + .file("src/main.rs", "extern crate bar; pub fn foo() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .build(); + + p.cargo("doc -v").run(); + + assert!(p.root().join("target/doc").is_dir()); + assert!(p.root().join("target/doc/bar/index.html").is_file()); + assert!(p.root().join("target/doc/foo/index.html").is_file()); +} + +#[cargo_test] +fn doc_multiple_targets_same_name_lib() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["foo", "bar"] + "#, + ) + .file( + "foo/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + [lib] + name = "foo_lib" + "#, + ) + .file("foo/src/lib.rs", "") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.1.0" + [lib] + name = "foo_lib" + "#, + ) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("doc --workspace") + .with_status(101) + .with_stderr( + "\ +error: document output filename collision +The lib `foo_lib` in package `foo v0.1.0 ([ROOT]/foo/foo)` has the same name as \ +the lib `foo_lib` in package `bar v0.1.0 ([ROOT]/foo/bar)`. +Only one may be documented at once since they output to the same path. +Consider documenting only one, renaming one, or marking one with `doc = false` in Cargo.toml. +", + ) + .run(); +} + +#[cargo_test] +fn doc_multiple_targets_same_name() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["foo", "bar"] + "#, + ) + .file( + "foo/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + [[bin]] + name = "foo_lib" + path = "src/foo_lib.rs" + "#, + ) + .file("foo/src/foo_lib.rs", "") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.1.0" + [lib] + name = "foo_lib" + "#, + ) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("doc --workspace") + .with_stderr_unordered( + "\ +warning: output filename collision. +The bin target `foo_lib` in package `foo v0.1.0 ([ROOT]/foo/foo)` \ +has the same output filename as the lib target `foo_lib` in package \ +`bar v0.1.0 ([ROOT]/foo/bar)`. +Colliding filename is: [ROOT]/foo/target/doc/foo_lib/index.html +The targets should have unique names. +This is a known bug where multiple crates with the same name use +the same path; see . +[DOCUMENTING] bar v0.1.0 ([ROOT]/foo/bar) +[DOCUMENTING] foo v0.1.0 ([ROOT]/foo/foo) +[FINISHED] [..] +", + ) + .run(); +} + +#[cargo_test] +fn doc_multiple_targets_same_name_bin() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["foo", "bar"] + "#, + ) + .file( + "foo/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + "#, + ) + .file("foo/src/bin/foo-cli.rs", "") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.1.0" + "#, + ) + .file("bar/src/bin/foo-cli.rs", "") + .build(); + + p.cargo("doc --workspace") + .with_status(101) + .with_stderr( + "\ +error: document output filename collision +The bin `foo-cli` in package `foo v0.1.0 ([ROOT]/foo/foo)` has the same name as \ +the bin `foo-cli` in package `bar v0.1.0 ([ROOT]/foo/bar)`. +Only one may be documented at once since they output to the same path. +Consider documenting only one, renaming one, or marking one with `doc = false` in Cargo.toml. +", + ) + .run(); +} + +#[cargo_test] +fn doc_multiple_targets_same_name_undoced() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["foo", "bar"] + "#, + ) + .file( + "foo/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + [[bin]] + name = "foo-cli" + "#, + ) + .file("foo/src/foo-cli.rs", "") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.1.0" + [[bin]] + name = "foo-cli" + doc = false + "#, + ) + .file("bar/src/foo-cli.rs", "") + .build(); + + p.cargo("doc --workspace").run(); +} + +#[cargo_test] +fn doc_lib_bin_same_name_documents_lib() { + let p = project() + .file( + "src/main.rs", + r#" + //! Binary documentation + extern crate foo; + fn main() { + foo::foo(); + } + "#, + ) + .file( + "src/lib.rs", + r#" + //! Library documentation + pub fn foo() {} + "#, + ) + .build(); + + p.cargo("doc") + .with_stderr( + "\ +[DOCUMENTING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + let doc_html = p.read_file("target/doc/foo/index.html"); + assert!(doc_html.contains("Library")); + assert!(!doc_html.contains("Binary")); +} + +#[cargo_test] +fn doc_lib_bin_same_name_documents_lib_when_requested() { + let p = project() + .file( + "src/main.rs", + r#" + //! Binary documentation + extern crate foo; + fn main() { + foo::foo(); + } + "#, + ) + .file( + "src/lib.rs", + r#" + //! Library documentation + pub fn foo() {} + "#, + ) + .build(); + + p.cargo("doc --lib") + .with_stderr( + "\ +[DOCUMENTING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + let doc_html = p.read_file("target/doc/foo/index.html"); + assert!(doc_html.contains("Library")); + assert!(!doc_html.contains("Binary")); +} + +#[cargo_test] +fn doc_lib_bin_same_name_documents_named_bin_when_requested() { + let p = project() + .file( + "src/main.rs", + r#" + //! Binary documentation + extern crate foo; + fn main() { + foo::foo(); + } + "#, + ) + .file( + "src/lib.rs", + r#" + //! Library documentation + pub fn foo() {} + "#, + ) + .build(); + + p.cargo("doc --bin foo") + // The checking/documenting lines are sometimes swapped since they run + // concurrently. + .with_stderr_unordered( + "\ +warning: output filename collision. +The bin target `foo` in package `foo v0.0.1 ([ROOT]/foo)` \ +has the same output filename as the lib target `foo` in package `foo v0.0.1 ([ROOT]/foo)`. +Colliding filename is: [ROOT]/foo/target/doc/foo/index.html +The targets should have unique names. +This is a known bug where multiple crates with the same name use +the same path; see . +[CHECKING] foo v0.0.1 ([CWD]) +[DOCUMENTING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + let doc_html = p.read_file("target/doc/foo/index.html"); + assert!(!doc_html.contains("Library")); + assert!(doc_html.contains("Binary")); +} + +#[cargo_test] +fn doc_lib_bin_same_name_documents_bins_when_requested() { + let p = project() + .file( + "src/main.rs", + r#" + //! Binary documentation + extern crate foo; + fn main() { + foo::foo(); + } + "#, + ) + .file( + "src/lib.rs", + r#" + //! Library documentation + pub fn foo() {} + "#, + ) + .build(); + + p.cargo("doc --bins") + // The checking/documenting lines are sometimes swapped since they run + // concurrently. + .with_stderr_unordered( + "\ +warning: output filename collision. +The bin target `foo` in package `foo v0.0.1 ([ROOT]/foo)` \ +has the same output filename as the lib target `foo` in package `foo v0.0.1 ([ROOT]/foo)`. +Colliding filename is: [ROOT]/foo/target/doc/foo/index.html +The targets should have unique names. +This is a known bug where multiple crates with the same name use +the same path; see . +[CHECKING] foo v0.0.1 ([CWD]) +[DOCUMENTING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + let doc_html = p.read_file("target/doc/foo/index.html"); + assert!(!doc_html.contains("Library")); + assert!(doc_html.contains("Binary")); +} + +#[cargo_test] +fn doc_lib_bin_example_same_name_documents_named_example_when_requested() { + let p = project() + .file( + "src/main.rs", + r#" + //! Binary documentation + extern crate foo; + fn main() { + foo::foo(); + } + "#, + ) + .file( + "src/lib.rs", + r#" + //! Library documentation + pub fn foo() {} + "#, + ) + .file( + "examples/ex1.rs", + r#" + //! Example1 documentation + pub fn x() { f(); } + "#, + ) + .build(); + + p.cargo("doc --example ex1") + // The checking/documenting lines are sometimes swapped since they run + // concurrently. + .with_stderr_unordered( + "\ +[CHECKING] foo v0.0.1 ([CWD]) +[DOCUMENTING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", + ) + .run(); + + let doc_html = p.read_file("target/doc/ex1/index.html"); + assert!(!doc_html.contains("Library")); + assert!(!doc_html.contains("Binary")); + assert!(doc_html.contains("Example1")); +} + +#[cargo_test] +fn doc_lib_bin_example_same_name_documents_examples_when_requested() { + let p = project() + .file( + "src/main.rs", + r#" + //! Binary documentation + extern crate foo; + fn main() { + foo::foo(); + } + "#, + ) + .file( + "src/lib.rs", + r#" + //! Library documentation + pub fn foo() {} + "#, + ) + .file( + "examples/ex1.rs", + r#" + //! Example1 documentation + pub fn example1() { f(); } + "#, + ) + .file( + "examples/ex2.rs", + r#" + //! Example2 documentation + pub fn example2() { f(); } + "#, + ) + .build(); + + p.cargo("doc --examples") + // The checking/documenting lines are sometimes swapped since they run + // concurrently. + .with_stderr_unordered( + "\ +[CHECKING] foo v0.0.1 ([CWD]) +[DOCUMENTING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", + ) + .run(); + + let example_doc_html_1 = p.read_file("target/doc/ex1/index.html"); + let example_doc_html_2 = p.read_file("target/doc/ex2/index.html"); + + assert!(!example_doc_html_1.contains("Library")); + assert!(!example_doc_html_1.contains("Binary")); + + assert!(!example_doc_html_2.contains("Library")); + assert!(!example_doc_html_2.contains("Binary")); + + assert!(example_doc_html_1.contains("Example1")); + assert!(example_doc_html_2.contains("Example2")); +} + +#[cargo_test] +fn doc_dash_p() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.a] + path = "a" + "#, + ) + .file("src/lib.rs", "extern crate a;") + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.0.1" + authors = [] + + [dependencies.b] + path = "../b" + "#, + ) + .file("a/src/lib.rs", "extern crate b;") + .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) + .file("b/src/lib.rs", "") + .build(); + + p.cargo("doc -p a") + .with_stderr( + "\ +[..] b v0.0.1 ([CWD]/b) +[..] b v0.0.1 ([CWD]/b) +[DOCUMENTING] a v0.0.1 ([CWD]/a) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn doc_all_exclude() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar", "baz"] + "#, + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) + .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }") + .build(); + + p.cargo("doc --workspace --exclude baz") + .with_stderr_does_not_contain("[DOCUMENTING] baz v0.1.0 [..]") + .with_stderr( + "\ +[DOCUMENTING] bar v0.1.0 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn doc_all_exclude_glob() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar", "baz"] + "#, + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) + .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }") + .build(); + + p.cargo("doc --workspace --exclude '*z'") + .with_stderr_does_not_contain("[DOCUMENTING] baz v0.1.0 [..]") + .with_stderr( + "\ +[DOCUMENTING] bar v0.1.0 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn doc_same_name() { + let p = project() + .file("src/lib.rs", "") + .file("src/bin/main.rs", "fn main() {}") + .file("examples/main.rs", "fn main() {}") + .file("tests/main.rs", "fn main() {}") + .build(); + + p.cargo("doc").run(); +} + +#[cargo_test(nightly, reason = "no_core, lang_items requires nightly")] +fn doc_target() { + const TARGET: &str = "arm-unknown-linux-gnueabihf"; + + let p = project() + .file( + "src/lib.rs", + r#" + #![feature(no_core, lang_items)] + #![no_core] + + #[lang = "sized"] + trait Sized {} + + extern { + pub static A: u32; + } + "#, + ) + .build(); + + p.cargo("doc --verbose --target").arg(TARGET).run(); + assert!(p.root().join(&format!("target/{}/doc", TARGET)).is_dir()); + assert!(p + .root() + .join(&format!("target/{}/doc/foo/index.html", TARGET)) + .is_file()); +} + +#[cargo_test] +fn target_specific_not_documented() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [target.foo.dependencies] + a = { path = "a" } + "#, + ) + .file("src/lib.rs", "") + .file("a/Cargo.toml", &basic_manifest("a", "0.0.1")) + .file("a/src/lib.rs", "not rust") + .build(); + + p.cargo("doc").run(); +} + +#[cargo_test] +fn output_not_captured() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + a = { path = "a" } + "#, + ) + .file("src/lib.rs", "") + .file("a/Cargo.toml", &basic_manifest("a", "0.0.1")) + .file( + "a/src/lib.rs", + " + /// ``` + /// ` + /// ``` + pub fn foo() {} + ", + ) + .build(); + + p.cargo("doc") + .with_stderr_contains("[..]unknown start of token: `") + .run(); +} + +#[cargo_test] +fn target_specific_documented() { + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [target.foo.dependencies] + a = {{ path = "a" }} + [target.{}.dependencies] + a = {{ path = "a" }} + "#, + rustc_host() + ), + ) + .file( + "src/lib.rs", + " + extern crate a; + + /// test + pub fn foo() {} + ", + ) + .file("a/Cargo.toml", &basic_manifest("a", "0.0.1")) + .file( + "a/src/lib.rs", + " + /// test + pub fn foo() {} + ", + ) + .build(); + + p.cargo("doc").run(); +} + +#[cargo_test] +fn no_document_build_deps() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [build-dependencies] + a = { path = "a" } + "#, + ) + .file("src/lib.rs", "pub fn foo() {}") + .file("a/Cargo.toml", &basic_manifest("a", "0.0.1")) + .file( + "a/src/lib.rs", + " + /// ``` + /// ☃ + /// ``` + pub fn foo() {} + ", + ) + .build(); + + p.cargo("doc").run(); +} + +#[cargo_test] +fn doc_release() { + let p = project().file("src/lib.rs", "").build(); + + p.cargo("check --release").run(); + p.cargo("doc --release -v") + .with_stderr( + "\ +[DOCUMENTING] foo v0.0.1 ([..]) +[RUNNING] `rustdoc [..] src/lib.rs [..]` +[FINISHED] release [optimized] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn doc_multiple_deps() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + + [dependencies.baz] + path = "baz" + "#, + ) + .file("src/lib.rs", "extern crate bar; pub fn foo() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1")) + .file("baz/src/lib.rs", "pub fn baz() {}") + .build(); + + p.cargo("doc -p bar -p baz -v").run(); + + assert!(p.root().join("target/doc").is_dir()); + assert!(p.root().join("target/doc/bar/index.html").is_file()); + assert!(p.root().join("target/doc/baz/index.html").is_file()); +} + +#[cargo_test] +fn features() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + + [features] + foo = ["bar/bar"] + "#, + ) + .file("src/lib.rs", r#"#[cfg(feature = "foo")] pub fn foo() {}"#) + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + authors = [] + + [features] + bar = [] + "#, + ) + .file( + "bar/build.rs", + r#" + fn main() { + println!("cargo:rustc-cfg=bar"); + } + "#, + ) + .file( + "bar/src/lib.rs", + r#"#[cfg(feature = "bar")] pub fn bar() {}"#, + ) + .build(); + p.cargo("doc --features foo") + .with_stderr( + "\ +[COMPILING] bar v0.0.1 [..] +[DOCUMENTING] bar v0.0.1 [..] +[DOCUMENTING] foo v0.0.1 [..] +[FINISHED] [..] +", + ) + .run(); + assert!(p.root().join("target/doc").is_dir()); + assert!(p.root().join("target/doc/foo/fn.foo.html").is_file()); + assert!(p.root().join("target/doc/bar/fn.bar.html").is_file()); + // Check that turning the feature off will remove the files. + p.cargo("doc") + .with_stderr( + "\ +[COMPILING] bar v0.0.1 [..] +[DOCUMENTING] bar v0.0.1 [..] +[DOCUMENTING] foo v0.0.1 [..] +[FINISHED] [..] +", + ) + .run(); + assert!(!p.root().join("target/doc/foo/fn.foo.html").is_file()); + assert!(!p.root().join("target/doc/bar/fn.bar.html").is_file()); + // And switching back will rebuild and bring them back. + p.cargo("doc --features foo") + .with_stderr( + "\ +[DOCUMENTING] bar v0.0.1 [..] +[DOCUMENTING] foo v0.0.1 [..] +[FINISHED] [..] +", + ) + .run(); + assert!(p.root().join("target/doc/foo/fn.foo.html").is_file()); + assert!(p.root().join("target/doc/bar/fn.bar.html").is_file()); +} + +#[cargo_test] +fn rerun_when_dir_removed() { + let p = project() + .file( + "src/lib.rs", + r#" + /// dox + pub fn foo() {} + "#, + ) + .build(); + + p.cargo("doc").run(); + assert!(p.root().join("target/doc/foo/index.html").is_file()); + + fs::remove_dir_all(p.root().join("target/doc/foo")).unwrap(); + + p.cargo("doc").run(); + assert!(p.root().join("target/doc/foo/index.html").is_file()); +} + +#[cargo_test] +fn document_only_lib() { + let p = project() + .file( + "src/lib.rs", + r#" + /// dox + pub fn foo() {} + "#, + ) + .file( + "src/bin/bar.rs", + r#" + /// ``` + /// ☃ + /// ``` + pub fn foo() {} + fn main() { foo(); } + "#, + ) + .build(); + p.cargo("doc --lib").run(); + assert!(p.root().join("target/doc/foo/index.html").is_file()); +} + +#[cargo_test] +fn plugins_no_use_target() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [lib] + proc-macro = true + "#, + ) + .file("src/lib.rs", "") + .build(); + p.cargo("doc --target=x86_64-unknown-openbsd -v").run(); +} + +#[cargo_test] +fn doc_all_workspace() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = { path = "bar" } + + [workspace] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .build(); + + // The order in which bar is compiled or documented is not deterministic + p.cargo("doc --workspace") + .with_stderr_contains("[..] Documenting bar v0.1.0 ([..])") + .with_stderr_contains("[..] Checking bar v0.1.0 ([..])") + .with_stderr_contains("[..] Documenting foo v0.1.0 ([..])") + .run(); +} + +#[cargo_test] +fn doc_all_virtual_manifest() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar", "baz"] + "#, + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) + .file("baz/src/lib.rs", "pub fn baz() {}") + .build(); + + // The order in which bar and baz are documented is not guaranteed + p.cargo("doc --workspace") + .with_stderr_contains("[..] Documenting baz v0.1.0 ([..])") + .with_stderr_contains("[..] Documenting bar v0.1.0 ([..])") + .run(); +} + +#[cargo_test] +fn doc_virtual_manifest_all_implied() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar", "baz"] + "#, + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) + .file("baz/src/lib.rs", "pub fn baz() {}") + .build(); + + // The order in which bar and baz are documented is not guaranteed + p.cargo("doc") + .with_stderr_contains("[..] Documenting baz v0.1.0 ([..])") + .with_stderr_contains("[..] Documenting bar v0.1.0 ([..])") + .run(); +} + +#[cargo_test] +fn doc_virtual_manifest_one_project() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar", "baz"] + "#, + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) + .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }") + .build(); + + p.cargo("doc -p bar") + .with_stderr_does_not_contain("[DOCUMENTING] baz v0.1.0 [..]") + .with_stderr( + "\ +[DOCUMENTING] bar v0.1.0 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn doc_virtual_manifest_glob() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar", "baz"] + "#, + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "pub fn bar() { break_the_build(); }") + .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) + .file("baz/src/lib.rs", "pub fn baz() {}") + .build(); + + p.cargo("doc -p '*z'") + .with_stderr_does_not_contain("[DOCUMENTING] bar v0.1.0 [..]") + .with_stderr( + "\ +[DOCUMENTING] baz v0.1.0 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn doc_all_member_dependency_same_name() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar"] + "#, + ) + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.1.0" + + [dependencies] + bar = "0.1.0" + "#, + ) + .file("bar/src/lib.rs", "pub fn bar() {}") + .build(); + + Package::new("bar", "0.1.0").publish(); + + p.cargo("doc --workspace") + .with_stderr_unordered( + "\ +[UPDATING] [..] +[DOWNLOADING] crates ... +[DOWNLOADED] bar v0.1.0 (registry `dummy-registry`) +warning: output filename collision. +The lib target `bar` in package `bar v0.1.0` has the same output filename as \ +the lib target `bar` in package `bar v0.1.0 ([ROOT]/foo/bar)`. +Colliding filename is: [ROOT]/foo/target/doc/bar/index.html +The targets should have unique names. +This is a known bug where multiple crates with the same name use +the same path; see . +[DOCUMENTING] bar v0.1.0 +[CHECKING] bar v0.1.0 +[DOCUMENTING] bar v0.1.0 [..] +[FINISHED] [..] +", + ) + .run(); +} + +#[cargo_test] +fn doc_workspace_open_help_message() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["foo", "bar"] + "#, + ) + .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) + .file("foo/src/lib.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "") + .build(); + + // The order in which bar is compiled or documented is not deterministic + p.cargo("doc --workspace --open") + .env("BROWSER", tools::echo()) + .with_stderr_contains("[..] Documenting bar v0.1.0 ([..])") + .with_stderr_contains("[..] Documenting foo v0.1.0 ([..])") + .with_stderr_contains("[..] Opening [..]/bar/index.html") + .run(); +} + +#[cargo_test(nightly, reason = "-Zextern-html-root-url is unstable")] +fn doc_extern_map_local() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + "#, + ) + .file("src/lib.rs", "") + .file(".cargo/config.toml", "doc.extern-map.std = 'local'") + .build(); + + p.cargo("doc -v --no-deps -Zrustdoc-map --open") + .env("BROWSER", tools::echo()) + .masquerade_as_nightly_cargo(&["rustdoc-map"]) + .with_stderr( + "\ +[DOCUMENTING] foo v0.1.0 [..] +[RUNNING] `rustdoc --crate-type lib --crate-name foo src/lib.rs [..]--crate-version 0.1.0` +[FINISHED] [..] + Opening [CWD]/target/doc/foo/index.html +", + ) + .run(); +} + +#[cargo_test] +fn open_no_doc_crate() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "a" + version = "0.0.1" + authors = [] + + [lib] + doc = false + "#, + ) + .file("src/lib.rs", "#[cfg(feature)] pub fn f();") + .build(); + + p.cargo("doc --open") + .env("BROWSER", "do_not_run_me") + .with_status(101) + .with_stderr_contains("error: no crates with documentation") + .run(); +} + +#[cargo_test] +fn doc_workspace_open_different_library_and_package_names() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["foo"] + "#, + ) + .file( + "foo/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + [lib] + name = "foolib" + "#, + ) + .file("foo/src/lib.rs", "") + .build(); + + p.cargo("doc --open") + .env("BROWSER", tools::echo()) + .with_stderr_contains("[..] Documenting foo v0.1.0 ([..])") + .with_stderr_contains("[..] [CWD]/target/doc/foolib/index.html") + .with_stdout_contains("[CWD]/target/doc/foolib/index.html") + .run(); + + p.change_file( + ".cargo/config.toml", + &format!( + r#" + [doc] + browser = ["{}", "a"] + "#, + tools::echo().display().to_string().replace('\\', "\\\\") + ), + ); + + // check that the cargo config overrides the browser env var + p.cargo("doc --open") + .env("BROWSER", "do_not_run_me") + .with_stdout_contains("a [CWD]/target/doc/foolib/index.html") + .run(); +} + +#[cargo_test] +fn doc_workspace_open_binary() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["foo"] + "#, + ) + .file( + "foo/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + [[bin]] + name = "foobin" + path = "src/main.rs" + "#, + ) + .file("foo/src/main.rs", "") + .build(); + + p.cargo("doc --open") + .env("BROWSER", tools::echo()) + .with_stderr_contains("[..] Documenting foo v0.1.0 ([..])") + .with_stderr_contains("[..] Opening [CWD]/target/doc/foobin/index.html") + .run(); +} + +#[cargo_test] +fn doc_workspace_open_binary_and_library() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["foo"] + "#, + ) + .file( + "foo/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + [lib] + name = "foolib" + [[bin]] + name = "foobin" + path = "src/main.rs" + "#, + ) + .file("foo/src/lib.rs", "") + .file("foo/src/main.rs", "") + .build(); + + p.cargo("doc --open") + .env("BROWSER", tools::echo()) + .with_stderr_contains("[..] Documenting foo v0.1.0 ([..])") + .with_stderr_contains("[..] Opening [CWD]/target/doc/foolib/index.html") + .run(); +} + +#[cargo_test] +fn doc_edition() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + edition = "2018" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("doc -v") + .with_stderr_contains("[RUNNING] `rustdoc [..]--edition=2018[..]") + .run(); + + p.cargo("test -v") + .with_stderr_contains("[RUNNING] `rustdoc [..]--edition=2018[..]") + .run(); +} + +#[cargo_test] +fn doc_target_edition() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [lib] + edition = "2018" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("doc -v") + .with_stderr_contains("[RUNNING] `rustdoc [..]--edition=2018[..]") + .run(); + + p.cargo("test -v") + .with_stderr_contains("[RUNNING] `rustdoc [..]--edition=2018[..]") + .run(); +} + +// Tests an issue where depending on different versions of the same crate depending on `cfg`s +// caused `cargo doc` to fail. +#[cargo_test] +fn issue_5345() { + let foo = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [target.'cfg(all(windows, target_arch = "x86"))'.dependencies] + bar = "0.1" + + [target.'cfg(not(all(windows, target_arch = "x86")))'.dependencies] + bar = "0.2" + "#, + ) + .file("src/lib.rs", "extern crate bar;") + .build(); + Package::new("bar", "0.1.0").publish(); + Package::new("bar", "0.2.0").publish(); + + foo.cargo("check").run(); + foo.cargo("doc").run(); +} + +#[cargo_test] +fn doc_private_items() { + let foo = project() + .file("src/lib.rs", "mod private { fn private_item() {} }") + .build(); + foo.cargo("doc --document-private-items").run(); + + assert!(foo.root().join("target/doc").is_dir()); + assert!(foo + .root() + .join("target/doc/foo/private/index.html") + .is_file()); +} + +#[cargo_test] +fn doc_private_ws() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["a", "b"] + "#, + ) + .file("a/Cargo.toml", &basic_manifest("a", "0.0.1")) + .file("a/src/lib.rs", "fn p() {}") + .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) + .file("b/src/lib.rs", "fn p2() {}") + .file("b/src/bin/b-cli.rs", "fn main() {}") + .build(); + p.cargo("doc --workspace --bins --lib --document-private-items -v") + .with_stderr_contains( + "[RUNNING] `rustdoc [..] a/src/lib.rs [..]--document-private-items[..]", + ) + .with_stderr_contains( + "[RUNNING] `rustdoc [..] b/src/lib.rs [..]--document-private-items[..]", + ) + .with_stderr_contains( + "[RUNNING] `rustdoc [..] b/src/bin/b-cli.rs [..]--document-private-items[..]", + ) + .run(); +} + +const BAD_INTRA_LINK_LIB: &str = r#" +#![deny(broken_intra_doc_links)] + +/// [bad_link] +pub fn foo() {} +"#; + +#[cargo_test] +fn doc_cap_lints() { + let a = git::new("a", |p| { + p.file("Cargo.toml", &basic_lib_manifest("a")) + .file("src/lib.rs", BAD_INTRA_LINK_LIB) + }); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + a = {{ git = '{}' }} + "#, + a.url() + ), + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("doc") + .with_stderr_unordered( + "\ +[UPDATING] git repository `[..]` +[DOCUMENTING] a v0.5.0 ([..]) +[CHECKING] a v0.5.0 ([..]) +[DOCUMENTING] foo v0.0.1 ([..]) +[FINISHED] dev [..] +", + ) + .run(); + + p.root().join("target").rm_rf(); + + p.cargo("doc -vv") + .with_stderr_contains("[WARNING] [..]`bad_link`[..]") + .run(); +} + +#[cargo_test] +fn doc_message_format() { + let p = project().file("src/lib.rs", BAD_INTRA_LINK_LIB).build(); + + p.cargo("doc --message-format=json") + .with_status(101) + .with_json_contains_unordered( + r#" + { + "message": { + "children": "{...}", + "code": "{...}", + "level": "error", + "message": "{...}", + "rendered": "{...}", + "spans": "{...}" + }, + "package_id": "foo [..]", + "manifest_path": "[..]", + "reason": "compiler-message", + "target": "{...}" + } + "#, + ) + .run(); +} + +#[cargo_test] +fn doc_json_artifacts() { + // Checks the output of json artifact messages. + let p = project() + .file("src/lib.rs", "") + .file("src/bin/somebin.rs", "fn main() {}") + .build(); + + p.cargo("doc --message-format=json") + .with_json_contains_unordered( + r#" +{ + "reason": "compiler-artifact", + "package_id": "foo 0.0.1 [..]", + "manifest_path": "[ROOT]/foo/Cargo.toml", + "target": + { + "kind": ["lib"], + "crate_types": ["lib"], + "name": "foo", + "src_path": "[ROOT]/foo/src/lib.rs", + "edition": "2015", + "doc": true, + "doctest": true, + "test": true + }, + "profile": "{...}", + "features": [], + "filenames": ["[ROOT]/foo/target/debug/deps/libfoo-[..].rmeta"], + "executable": null, + "fresh": false +} + +{ + "reason": "compiler-artifact", + "package_id": "foo 0.0.1 [..]", + "manifest_path": "[ROOT]/foo/Cargo.toml", + "target": + { + "kind": ["lib"], + "crate_types": ["lib"], + "name": "foo", + "src_path": "[ROOT]/foo/src/lib.rs", + "edition": "2015", + "doc": true, + "doctest": true, + "test": true + }, + "profile": "{...}", + "features": [], + "filenames": ["[ROOT]/foo/target/doc/foo/index.html"], + "executable": null, + "fresh": false +} + +{ + "reason": "compiler-artifact", + "package_id": "foo 0.0.1 [..]", + "manifest_path": "[ROOT]/foo/Cargo.toml", + "target": + { + "kind": ["bin"], + "crate_types": ["bin"], + "name": "somebin", + "src_path": "[ROOT]/foo/src/bin/somebin.rs", + "edition": "2015", + "doc": true, + "doctest": false, + "test": true + }, + "profile": "{...}", + "features": [], + "filenames": ["[ROOT]/foo/target/doc/somebin/index.html"], + "executable": null, + "fresh": false +} + +{"reason":"build-finished","success":true} +"#, + ) + .run(); +} + +#[cargo_test] +fn short_message_format() { + let p = project().file("src/lib.rs", BAD_INTRA_LINK_LIB).build(); + p.cargo("doc --message-format=short") + .with_status(101) + .with_stderr_contains("src/lib.rs:4:6: error: [..]`bad_link`[..]") + .run(); +} + +#[cargo_test] +fn doc_example() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + + [[example]] + crate-type = ["lib"] + name = "ex1" + doc = true + "#, + ) + .file("src/lib.rs", "pub fn f() {}") + .file( + "examples/ex1.rs", + r#" + use foo::f; + + /// Example + pub fn x() { f(); } + "#, + ) + .build(); + + p.cargo("doc").run(); + assert!(p + .build_dir() + .join("doc") + .join("ex1") + .join("fn.x.html") + .exists()); +} + +#[cargo_test] +fn doc_example_with_deps() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + + [[example]] + crate-type = ["lib"] + name = "ex" + doc = true + + [dev-dependencies] + a = {path = "a"} + b = {path = "b"} + "#, + ) + .file("src/lib.rs", "") + .file( + "examples/ex.rs", + r#" + use a::fun; + + /// Example + pub fn x() { fun(); } + "#, + ) + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.0.1" + + [dependencies] + b = {path = "../b"} + "#, + ) + .file("a/src/fun.rs", "pub fn fun() {}") + .file("a/src/lib.rs", "pub mod fun;") + .file( + "b/Cargo.toml", + r#" + [package] + name = "b" + version = "0.0.1" + "#, + ) + .file("b/src/lib.rs", "") + .build(); + + p.cargo("doc --examples").run(); + assert!(p + .build_dir() + .join("doc") + .join("ex") + .join("fn.x.html") + .exists()); +} + +#[cargo_test] +fn bin_private_items() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + "#, + ) + .file( + "src/main.rs", + " + pub fn foo_pub() {} + fn foo_priv() {} + struct FooStruct; + enum FooEnum {} + trait FooTrait {} + type FooType = u32; + mod foo_mod {} + + ", + ) + .build(); + + p.cargo("doc") + .with_stderr( + "\ +[DOCUMENTING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + + assert!(p.root().join("target/doc/foo/index.html").is_file()); + assert!(p.root().join("target/doc/foo/fn.foo_pub.html").is_file()); + assert!(p.root().join("target/doc/foo/fn.foo_priv.html").is_file()); + assert!(p + .root() + .join("target/doc/foo/struct.FooStruct.html") + .is_file()); + assert!(p.root().join("target/doc/foo/enum.FooEnum.html").is_file()); + assert!(p + .root() + .join("target/doc/foo/trait.FooTrait.html") + .is_file()); + assert!(p.root().join("target/doc/foo/type.FooType.html").is_file()); + assert!(p.root().join("target/doc/foo/foo_mod/index.html").is_file()); +} + +#[cargo_test] +fn bin_private_items_deps() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + "#, + ) + .file( + "src/main.rs", + " + fn foo_priv() {} + pub fn foo_pub() {} + ", + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file( + "bar/src/lib.rs", + " + #[allow(dead_code)] + fn bar_priv() {} + pub fn bar_pub() {} + ", + ) + .build(); + + p.cargo("doc") + .with_stderr_unordered( + "\ +[DOCUMENTING] bar v0.0.1 ([..]) +[CHECKING] bar v0.0.1 ([..]) +[DOCUMENTING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + + assert!(p.root().join("target/doc/foo/index.html").is_file()); + assert!(p.root().join("target/doc/foo/fn.foo_pub.html").is_file()); + assert!(p.root().join("target/doc/foo/fn.foo_priv.html").is_file()); + + assert!(p.root().join("target/doc/bar/index.html").is_file()); + assert!(p.root().join("target/doc/bar/fn.bar_pub.html").is_file()); + assert!(!p.root().join("target/doc/bar/fn.bar_priv.html").exists()); +} + +#[cargo_test] +fn crate_versions() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.2.4" + authors = [] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("doc -v") + .with_stderr( + "\ +[DOCUMENTING] foo v1.2.4 [..] +[RUNNING] `rustdoc --crate-type lib --crate-name foo src/lib.rs [..]--crate-version 1.2.4` +[FINISHED] [..] +", + ) + .run(); + + let output_path = p.root().join("target/doc/foo/index.html"); + let output_documentation = fs::read_to_string(&output_path).unwrap(); + + assert!(output_documentation.contains("Version 1.2.4")); +} + +#[cargo_test] +fn crate_versions_flag_is_overridden() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.2.4" + authors = [] + "#, + ) + .file("src/lib.rs", "") + .build(); + + let output_documentation = || { + let output_path = p.root().join("target/doc/foo/index.html"); + fs::read_to_string(&output_path).unwrap() + }; + let asserts = |html: String| { + assert!(!html.contains("1.2.4")); + assert!(html.contains("Version 2.0.3")); + }; + + p.cargo("doc") + .env("RUSTDOCFLAGS", "--crate-version 2.0.3") + .run(); + asserts(output_documentation()); + + p.build_dir().rm_rf(); + + p.cargo("rustdoc -- --crate-version 2.0.3").run(); + asserts(output_documentation()); +} + +#[cargo_test(nightly, reason = "-Zdoctest-in-workspace is unstable")] +fn doc_test_in_workspace() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = [ + "crate-a", + "crate-b", + ] + "#, + ) + .file( + "crate-a/Cargo.toml", + r#" + [package] + name = "crate-a" + version = "0.1.0" + "#, + ) + .file( + "crate-a/src/lib.rs", + "\ + //! ``` + //! assert_eq!(1, 1); + //! ``` + ", + ) + .file( + "crate-b/Cargo.toml", + r#" + [package] + name = "crate-b" + version = "0.1.0" + "#, + ) + .file( + "crate-b/src/lib.rs", + "\ + //! ``` + //! assert_eq!(1, 1); + //! ``` + ", + ) + .build(); + p.cargo("test -Zdoctest-in-workspace --doc -vv") + .masquerade_as_nightly_cargo(&["doctest-in-workspace"]) + .with_stderr_contains("[DOCTEST] crate-a") + .with_stdout_contains( + " +running 1 test +test crate-a/src/lib.rs - (line 1) ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..] + +", + ) + .with_stderr_contains("[DOCTEST] crate-b") + .with_stdout_contains( + " +running 1 test +test crate-b/src/lib.rs - (line 1) ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..] + +", + ) + .run(); +} + +#[cargo_test] +fn doc_fingerprint_is_versioning_consistent() { + // Random rustc verbose version + let old_rustc_verbose_version = format!( + "\ +rustc 1.41.1 (f3e1a954d 2020-02-24) +binary: rustc +commit-hash: f3e1a954d2ead4e2fc197c7da7d71e6c61bad196 +commit-date: 2020-02-24 +host: {} +release: 1.41.1 +LLVM version: 9.0 +", + rustc_host() + ); + + // Create the dummy project. + let dummy_project = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.2.4" + authors = [] + "#, + ) + .file("src/lib.rs", "//! These are the docs!") + .build(); + + dummy_project.cargo("doc").run(); + + let fingerprint: RustDocFingerprint = + serde_json::from_str(&dummy_project.read_file("target/.rustdoc_fingerprint.json")) + .expect("JSON Serde fail"); + + // Check that the fingerprint contains the actual rustc version + // which has been used to compile the docs. + let output = std::process::Command::new("rustc") + .arg("-vV") + .output() + .expect("Failed to get actual rustc verbose version"); + assert_eq!( + fingerprint.rustc_vv, + (String::from_utf8_lossy(&output.stdout).as_ref()) + ); + + // As the test shows above. Now we have generated the `doc/` folder and inside + // the rustdoc fingerprint file is located with the correct rustc version. + // So we will remove it and create a new fingerprint with an old rustc version + // inside it. We will also place a bogus file inside of the `doc/` folder to ensure + // it gets removed as we expect on the next doc compilation. + dummy_project.change_file( + "target/.rustdoc_fingerprint.json", + &old_rustc_verbose_version, + ); + + fs::write( + dummy_project.build_dir().join("doc/bogus_file"), + String::from("This is a bogus file and should be removed!"), + ) + .expect("Error writing test bogus file"); + + // Now if we trigger another compilation, since the fingerprint contains an old version + // of rustc, cargo should remove the entire `/doc` folder (including the fingerprint) + // and generating another one with the actual version. + // It should also remove the bogus file we created above. + dummy_project.cargo("doc").run(); + + assert!(!dummy_project.build_dir().join("doc/bogus_file").exists()); + + let fingerprint: RustDocFingerprint = + serde_json::from_str(&dummy_project.read_file("target/.rustdoc_fingerprint.json")) + .expect("JSON Serde fail"); + + // Check that the fingerprint contains the actual rustc version + // which has been used to compile the docs. + assert_eq!( + fingerprint.rustc_vv, + (String::from_utf8_lossy(&output.stdout).as_ref()) + ); +} + +#[cargo_test] +fn doc_fingerprint_respects_target_paths() { + // Random rustc verbose version + let old_rustc_verbose_version = format!( + "\ +rustc 1.41.1 (f3e1a954d 2020-02-24) +binary: rustc +commit-hash: f3e1a954d2ead4e2fc197c7da7d71e6c61bad196 +commit-date: 2020-02-24 +host: {} +release: 1.41.1 +LLVM version: 9.0 +", + rustc_host() + ); + + // Create the dummy project. + let dummy_project = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.2.4" + authors = [] + "#, + ) + .file("src/lib.rs", "//! These are the docs!") + .build(); + + dummy_project.cargo("doc --target").arg(rustc_host()).run(); + + let fingerprint: RustDocFingerprint = + serde_json::from_str(&dummy_project.read_file("target/.rustdoc_fingerprint.json")) + .expect("JSON Serde fail"); + + // Check that the fingerprint contains the actual rustc version + // which has been used to compile the docs. + let output = std::process::Command::new("rustc") + .arg("-vV") + .output() + .expect("Failed to get actual rustc verbose version"); + assert_eq!( + fingerprint.rustc_vv, + (String::from_utf8_lossy(&output.stdout).as_ref()) + ); + + // As the test shows above. Now we have generated the `doc/` folder and inside + // the rustdoc fingerprint file is located with the correct rustc version. + // So we will remove it and create a new fingerprint with an old rustc version + // inside it. We will also place a bogus file inside of the `doc/` folder to ensure + // it gets removed as we expect on the next doc compilation. + dummy_project.change_file( + "target/.rustdoc_fingerprint.json", + &old_rustc_verbose_version, + ); + + fs::write( + dummy_project + .build_dir() + .join(rustc_host()) + .join("doc/bogus_file"), + String::from("This is a bogus file and should be removed!"), + ) + .expect("Error writing test bogus file"); + + // Now if we trigger another compilation, since the fingerprint contains an old version + // of rustc, cargo should remove the entire `/doc` folder (including the fingerprint) + // and generating another one with the actual version. + // It should also remove the bogus file we created above. + dummy_project.cargo("doc --target").arg(rustc_host()).run(); + + assert!(!dummy_project + .build_dir() + .join(rustc_host()) + .join("doc/bogus_file") + .exists()); + + let fingerprint: RustDocFingerprint = + serde_json::from_str(&dummy_project.read_file("target/.rustdoc_fingerprint.json")) + .expect("JSON Serde fail"); + + // Check that the fingerprint contains the actual rustc version + // which has been used to compile the docs. + assert_eq!( + fingerprint.rustc_vv, + (String::from_utf8_lossy(&output.stdout).as_ref()) + ); +} + +#[cargo_test] +fn doc_fingerprint_unusual_behavior() { + // Checks for some unusual circumstances with clearing the doc directory. + if !symlink_supported() { + return; + } + let p = project().file("src/lib.rs", "").build(); + p.build_dir().mkdir_p(); + let real_doc = p.root().join("doc"); + real_doc.mkdir_p(); + let build_doc = p.build_dir().join("doc"); + p.symlink(&real_doc, &build_doc); + fs::write(real_doc.join("somefile"), "test").unwrap(); + fs::write(real_doc.join(".hidden"), "test").unwrap(); + p.cargo("doc").run(); + // Make sure for the first run, it does not delete any files and does not + // break the symlink. + assert!(build_doc.join("somefile").exists()); + assert!(real_doc.join("somefile").exists()); + assert!(real_doc.join(".hidden").exists()); + assert!(real_doc.join("foo/index.html").exists()); + // Pretend that the last build was generated by an older version. + p.change_file( + "target/.rustdoc_fingerprint.json", + "{\"rustc_vv\": \"I am old\"}", + ); + // Change file to trigger a new build. + p.change_file("src/lib.rs", "// changed"); + p.cargo("doc") + .with_stderr( + "[DOCUMENTING] foo [..]\n\ + [FINISHED] [..]", + ) + .run(); + // This will delete somefile, but not .hidden. + assert!(!real_doc.join("somefile").exists()); + assert!(real_doc.join(".hidden").exists()); + assert!(real_doc.join("foo/index.html").exists()); + // And also check the -Z flag behavior. + p.change_file( + "target/.rustdoc_fingerprint.json", + "{\"rustc_vv\": \"I am old\"}", + ); + // Change file to trigger a new build. + p.change_file("src/lib.rs", "// changed2"); + fs::write(real_doc.join("somefile"), "test").unwrap(); + p.cargo("doc -Z skip-rustdoc-fingerprint") + .masquerade_as_nightly_cargo(&["skip-rustdoc-fingerprint"]) + .with_stderr( + "[DOCUMENTING] foo [..]\n\ + [FINISHED] [..]", + ) + .run(); + // Should not have deleted anything. + assert!(build_doc.join("somefile").exists()); + assert!(real_doc.join("somefile").exists()); +} + +#[cargo_test] +fn lib_before_bin() { + // Checks that the library is documented before the binary. + // Previously they were built concurrently, which can cause issues + // if the bin has intra-doc links to the lib. + let p = project() + .file( + "src/lib.rs", + r#" + /// Hi + pub fn abc() {} + "#, + ) + .file( + "src/bin/somebin.rs", + r#" + //! See [`foo::abc`] + fn main() {} + "#, + ) + .build(); + + // Run check first. This just helps ensure that the test clearly shows the + // order of the rustdoc commands. + p.cargo("check").run(); + + // The order of output here should be deterministic. + p.cargo("doc -v") + .with_stderr( + "\ +[DOCUMENTING] foo [..] +[RUNNING] `rustdoc --crate-type lib --crate-name foo src/lib.rs [..] +[RUNNING] `rustdoc --crate-type bin --crate-name somebin src/bin/somebin.rs [..] +[FINISHED] [..] +", + ) + .run(); + + // And the link should exist. + let bin_html = p.read_file("target/doc/somebin/index.html"); + assert!(bin_html.contains("../foo/fn.abc.html")); +} + +#[cargo_test] +fn doc_lib_false() { + // doc = false for a library + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [lib] + doc = false + + [dependencies] + bar = {path = "bar"} + "#, + ) + .file("src/lib.rs", "extern crate bar;") + .file("src/bin/some-bin.rs", "fn main() {}") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.1.0" + + [lib] + doc = false + "#, + ) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("doc") + .with_stderr( + "\ +[CHECKING] bar v0.1.0 [..] +[CHECKING] foo v0.1.0 [..] +[DOCUMENTING] foo v0.1.0 [..] +[FINISHED] [..] +", + ) + .run(); + + assert!(!p.build_dir().join("doc/foo").exists()); + assert!(!p.build_dir().join("doc/bar").exists()); + assert!(p.build_dir().join("doc/some_bin").exists()); +} + +#[cargo_test] +fn doc_lib_false_dep() { + // doc = false for a dependency + // Ensures that the rmeta gets produced + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = { path = "bar" } + "#, + ) + .file("src/lib.rs", "extern crate bar;") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.1.0" + + [lib] + doc = false + "#, + ) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("doc") + .with_stderr( + "\ +[CHECKING] bar v0.1.0 [..] +[DOCUMENTING] foo v0.1.0 [..] +[FINISHED] [..] +", + ) + .run(); + + assert!(p.build_dir().join("doc/foo").exists()); + assert!(!p.build_dir().join("doc/bar").exists()); +} + +#[cargo_test] +fn link_to_private_item() { + let main = r#" + //! [bar] + #[allow(dead_code)] + fn bar() {} + "#; + let p = project().file("src/lib.rs", main).build(); + p.cargo("doc") + .with_stderr_contains("[..] documentation for `foo` links to private item `bar`") + .run(); + // Check that binaries don't emit a private_intra_doc_links warning. + fs::rename(p.root().join("src/lib.rs"), p.root().join("src/main.rs")).unwrap(); + p.cargo("doc") + .with_stderr( + "[DOCUMENTING] foo [..]\n\ + [FINISHED] [..]", + ) + .run(); +} -- cgit v1.2.3