summaryrefslogtreecommitdiffstats
path: root/src/tools/cargo/tests/testsuite/doc.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/cargo/tests/testsuite/doc.rs')
-rw-r--r--src/tools/cargo/tests/testsuite/doc.rs2503
1 files changed, 2503 insertions, 0 deletions
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 <https://github.com/rust-lang/cargo/issues/6313>.
+[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 <https://github.com/rust-lang/cargo/issues/6313>.
+[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 <https://github.com/rust-lang/cargo/issues/6313>.
+[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 <https://github.com/rust-lang/cargo/issues/6313>.
+[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();
+}