//! Tests for when multiple artifacts have the same output filename.
//! See <https://github.com/rust-lang/cargo/issues/6313> for more details.
//! Ideally these should never happen, but I don't think we'll ever be able to
//! prevent all collisions.

use crate::prelude::*;
use crate::utils::cross_compile::disabled as cross_compile_disabled;
use cargo_test_support::registry::Package;
use cargo_test_support::str;
use cargo_test_support::{basic_manifest, cross_compile, project};
use std::env;

#[cargo_test]
fn collision_dylib() {
    // Path dependencies don't include metadata hash in filename for dylibs.
    let p = project()
        .file(
            "Cargo.toml",
            r#"
            [workspace]
            members = ["a", "b"]
            "#,
        )
        .file(
            "a/Cargo.toml",
            r#"
            [package]
            name = "a"
            version = "1.0.0"
            edition = "2015"

            [lib]
            crate-type = ["dylib"]
            "#,
        )
        .file("a/src/lib.rs", "")
        .file(
            "b/Cargo.toml",
            r#"
            [package]
            name = "b"
            version = "1.0.0"
            edition = "2015"

            [lib]
            crate-type = ["dylib"]
            name = "a"
            "#,
        )
        .file("b/src/lib.rs", "")
        .build();

    // `j=1` is required because on Windows you'll get an error due to
    // two processes writing to the file at the same time.
    p.cargo("build -j=1")
        .with_stderr_data(&format!("\
...
[WARNING] output filename collision at [ROOT]/foo/target/debug/deps/{}a{}
  |
  = [NOTE] the lib target `a` in package `b v1.0.0 ([ROOT]/foo/b)` has the same output filename as the lib target `a` in package `a v1.0.0 ([ROOT]/foo/a)`
  = [NOTE] this may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>
  = [HELP] consider changing their names to be unique or compiling them separately
...
", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX))
        .run();
}

#[cargo_test]
fn collision_example() {
    // Examples in a workspace can easily collide.
    let p = project()
        .file(
            "Cargo.toml",
            r#"
            [workspace]
            members = ["a", "b"]
            "#,
        )
        .file("a/Cargo.toml", &basic_manifest("a", "1.0.0"))
        .file("a/examples/ex1.rs", "fn main() {}")
        .file("b/Cargo.toml", &basic_manifest("b", "1.0.0"))
        .file("b/examples/ex1.rs", "fn main() {}")
        .build();

    // `j=1` is required because on Windows you'll get an error due to
    // two processes writing to the file at the same time.
    p.cargo("build --examples -j=1")
        .with_stderr_data(str![[r#"
...
[WARNING] output filename collision at [ROOT]/foo/target/debug/examples/ex1[EXE]
  |
  = [NOTE] the example target `ex1` in package `b v1.0.0 ([ROOT]/foo/b)` has the same output filename as the example target `ex1` in package `a v1.0.0 ([ROOT]/foo/a)`
  = [NOTE] this may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>
  = [HELP] consider changing their names to be unique or compiling them separately
...

"#]])
        .run();
}

#[cargo_test]
// See https://github.com/rust-lang/cargo/issues/7493
#[cfg_attr(
    any(target_env = "msvc", target_vendor = "apple"),
    ignore = "--artifact-dir and examples are currently broken on MSVC and apple"
)]
fn collision_export() {
    // `--artifact-dir` combines some things which can cause conflicts.
    let p = project()
        .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
        .file("examples/foo.rs", "fn main() {}")
        .file("src/main.rs", "fn main() {}")
        .build();

    // -j1 to avoid issues with two processes writing to the same file at the
    // same time.
    p.cargo("build -j1 --artifact-dir=out -Z unstable-options --bins --examples")
        .masquerade_as_nightly_cargo(&["artifact-dir"])
        .with_stderr_data(str![[r#"
[WARNING] `--artifact-dir` filename collision at [ROOT]/foo/out/foo[EXE]
  |
  = [NOTE] the example target `foo` in package `foo v1.0.0 ([ROOT]/foo)` has the same output filename as the bin target `foo` in package `foo v1.0.0 ([ROOT]/foo)`
  = [NOTE] this may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>
  = [HELP] consider changing their names to be unique or compiling them separately
...

"#]])
        .run();
}

#[cargo_test]
fn collision_doc() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
            [package]
            name = "foo"
            version = "0.1.0"
            edition = "2015"

            [dependencies]
            foo2 = { path = "foo2" }
            "#,
        )
        .file("src/lib.rs", "")
        .file(
            "foo2/Cargo.toml",
            r#"
            [package]
            name = "foo2"
            version = "0.1.0"
            edition = "2015"

            [lib]
            name = "foo"
            "#,
        )
        .file("foo2/src/lib.rs", "")
        .build();

    p.cargo("doc -j=1")
        .with_stderr_data(str![[r#"
...
[WARNING] output filename collision at [ROOT]/foo/target/doc/foo/index.html
  |
  = [NOTE] the lib target `foo` in package `foo2 v0.1.0 ([ROOT]/foo/foo2)` has the same output filename as the lib target `foo` in package `foo v0.1.0 ([ROOT]/foo)`
  = [NOTE] 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>
...

"#]])
        .run();
}

#[cargo_test]
fn collision_doc_multiple_versions() {
    // Multiple versions of the same package.
    Package::new("old-dep", "1.0.0").publish();
    Package::new("bar", "1.0.0").dep("old-dep", "1.0").publish();
    // Note that this removes "old-dep". Just checking what happens when there
    // are orphans.
    Package::new("bar", "2.0.0").publish();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                edition = "2015"

                [dependencies]
                bar = "1.0"
                bar2 = { package="bar", version="2.0" }
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    // Should only document bar 2.0, should not document old-dep.
    p.cargo("doc")
        .with_stderr_data(
            str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 3 packages to latest compatible versions
[ADDING] bar v1.0.0 (available: v2.0.0)
[DOWNLOADING] crates ...
[DOWNLOADED] bar v2.0.0 (registry `dummy-registry`)
[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)
[DOWNLOADED] old-dep v1.0.0 (registry `dummy-registry`)
[CHECKING] old-dep v1.0.0
[CHECKING] bar v2.0.0
[CHECKING] bar v1.0.0
[DOCUMENTING] bar v2.0.0
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
[DOCUMENTING] foo v0.1.0 ([ROOT]/foo)
[GENERATED] [ROOT]/foo/target/doc/foo/index.html

"#]]
            .unordered(),
        )
        .run();
}

#[cargo_test]
fn collision_doc_host_target_feature_split() {
    // Same dependency built twice due to different features.
    //
    // foo v0.1.0
    // ├── common v1.0.0
    // │   └── common-dep v1.0.0
    // └── pm v0.1.0 (proc-macro)
    //     └── common v1.0.0
    //         └── common-dep v1.0.0
    // [build-dependencies]
    // └── common-dep v1.0.0
    //
    // Here `common` and `common-dep` are built twice. `common-dep` has
    // different features for host versus target.
    Package::new("common-dep", "1.0.0")
        .feature("bdep-feat", &[])
        .file(
            "src/lib.rs",
            r#"
                /// Some doc
                pub fn f() {}

                /// Another doc
                #[cfg(feature = "bdep-feat")]
                pub fn bdep_func() {}
            "#,
        )
        .publish();
    Package::new("common", "1.0.0")
        .dep("common-dep", "1.0")
        .file(
            "src/lib.rs",
            r#"
                /// Some doc
                pub fn f() {}
            "#,
        )
        .publish();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                edition = "2015"
                resolver = "2"

                [dependencies]
                pm = { path = "pm" }
                common = "1.0"

                [build-dependencies]
                common-dep = { version = "1.0", features = ["bdep-feat"] }
            "#,
        )
        .file(
            "src/lib.rs",
            r#"
                /// Some doc
                pub fn f() {}
            "#,
        )
        .file("build.rs", "fn main() {}")
        .file(
            "pm/Cargo.toml",
            r#"
                [package]
                name = "pm"
                version = "0.1.0"
                edition = "2018"

                [lib]
                proc-macro = true

                [dependencies]
                common = "1.0"
            "#,
        )
        .file(
            "pm/src/lib.rs",
            r#"
                use proc_macro::TokenStream;

                /// Some doc
                #[proc_macro]
                pub fn pm(_input: TokenStream) -> TokenStream {
                    "".parse().unwrap()
                }
            "#,
        )
        .build();

    // No warnings, no duplicates, common and common-dep only documented once.
    p.cargo("doc")
        // Cannot check full output due to https://github.com/rust-lang/cargo/issues/9076
        .with_stderr_does_not_contain("[WARNING][..]")
        .run();

    assert!(p.build_dir().join("doc/common_dep/fn.f.html").exists());
    assert!(
        !p.build_dir()
            .join("doc/common_dep/fn.bdep_func.html")
            .exists()
    );
    assert!(p.build_dir().join("doc/common/fn.f.html").exists());
    assert!(p.build_dir().join("doc/pm/macro.pm.html").exists());
    assert!(p.build_dir().join("doc/foo/fn.f.html").exists());
}

#[cargo_test]
fn collision_doc_profile_split() {
    // Same dependency built twice due to different profile settings.
    Package::new("common", "1.0.0").publish();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                edition = "2015"

                [dependencies]
                pm = { path = "pm" }
                common = "1.0"

                [profile.dev]
                opt-level = 2
            "#,
        )
        .file("src/lib.rs", "")
        .file(
            "pm/Cargo.toml",
            r#"
                [package]
                name = "pm"
                version = "0.1.0"
                edition = "2015"

                [dependencies]
                common = "1.0"

                [lib]
                proc-macro = true
            "#,
        )
        .file("pm/src/lib.rs", "")
        .build();

    // Just to verify that common is normally built twice.
    // This is unordered because in rare cases `pm` may start
    // building in-between the two `common`.
    p.cargo("build -v")
        .with_stderr_data(
            str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] common v1.0.0 (registry `dummy-registry`)
[COMPILING] common v1.0.0
[RUNNING] `rustc --crate-name common [..]
[RUNNING] `rustc --crate-name common [..]
[COMPILING] pm v0.1.0 ([ROOT]/foo/pm)
[RUNNING] `rustc --crate-name pm [..]
[COMPILING] foo v0.1.0 ([ROOT]/foo)
[RUNNING] `rustc --crate-name foo [..]
[FINISHED] `dev` profile [optimized + debuginfo] target(s) in [ELAPSED]s

"#]]
            .unordered(),
        )
        .run();

    // Should only document common once, no warnings.
    p.cargo("doc")
        .with_stderr_data(
            str![[r#"
[CHECKING] common v1.0.0
[DOCUMENTING] common v1.0.0
[DOCUMENTING] pm v0.1.0 ([ROOT]/foo/pm)
[DOCUMENTING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `dev` profile [optimized + debuginfo] target(s) in [ELAPSED]s
[GENERATED] [ROOT]/foo/target/doc/foo/index.html

"#]]
            .unordered(),
        )
        .run();
}

#[cargo_test]
fn collision_doc_sources() {
    // Different sources with the same package.
    Package::new("bar", "1.0.0").publish();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                edition = "2015"

                [dependencies]
                bar = "1.0"
                bar2 = { path = "bar", package = "bar" }
            "#,
        )
        .file("src/lib.rs", "")
        .file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0"))
        .file("bar/src/lib.rs", "")
        .build();

    p.cargo("doc -j=1")
        .with_stderr_data(
            str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)
[WARNING] output filename collision at [ROOT]/foo/target/doc/bar/index.html
  |
  = [NOTE] the lib target `bar` in package `bar v1.0.0` has the same output filename as the lib target `bar` in package `bar v1.0.0 ([ROOT]/foo/bar)`
  = [NOTE] 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] bar v1.0.0 ([ROOT]/foo/bar)
[DOCUMENTING] bar v1.0.0 ([ROOT]/foo/bar)
[DOCUMENTING] bar v1.0.0
[CHECKING] bar v1.0.0
[DOCUMENTING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
[GENERATED] [ROOT]/foo/target/doc/foo/index.html

"#]]
            .unordered(),
        )
        .run();
}

#[cargo_test]
fn collision_doc_target() {
    // collision in doc with --target, doesn't fail due to orphans
    if cross_compile_disabled() {
        return;
    }

    Package::new("orphaned", "1.0.0").publish();
    Package::new("bar", "1.0.0")
        .dep("orphaned", "1.0")
        .publish();
    Package::new("bar", "2.0.0").publish();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                edition = "2015"

                [dependencies]
                bar2 = { version = "2.0", package="bar" }
                bar = "1.0"
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("doc --target")
        .arg(cross_compile::alternate())
        .with_stderr_data(
            str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 3 packages to latest compatible versions
[ADDING] bar v1.0.0 (available: v2.0.0)
[DOWNLOADING] crates ...
[DOWNLOADED] orphaned v1.0.0 (registry `dummy-registry`)
[DOWNLOADED] bar v2.0.0 (registry `dummy-registry`)
[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)
[CHECKING] orphaned v1.0.0
[DOCUMENTING] bar v2.0.0
[CHECKING] bar v2.0.0
[CHECKING] bar v1.0.0
[DOCUMENTING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
[GENERATED] [ROOT]/foo/target/[ALT_TARGET]/doc/foo/index.html

"#]]
            .unordered(),
        )
        .run();
}

#[cargo_test]
fn collision_with_root() {
    // Check for a doc collision between a root package and a dependency.
    // In this case, `foo-macro` comes from both the workspace and crates.io.
    // This checks that the duplicate correction code doesn't choke on this
    // by removing the root unit.
    Package::new("foo-macro", "1.0.0").publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [workspace]
                members = ["abc", "foo-macro"]
            "#,
        )
        .file(
            "abc/Cargo.toml",
            r#"
                [package]
                name = "abc"
                version = "1.0.0"
                edition = "2015"

                [dependencies]
                foo-macro = "1.0"
            "#,
        )
        .file("abc/src/lib.rs", "")
        .file(
            "foo-macro/Cargo.toml",
            r#"
                [package]
                name = "foo-macro"
                version = "1.0.0"
                edition = "2015"

                [lib]
                proc-macro = true

                [dependencies]
                abc = {path="../abc"}
            "#,
        )
        .file("foo-macro/src/lib.rs", "")
        .build();

    p.cargo("doc -j=1")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] foo-macro v1.0.0 (registry `dummy-registry`)
[WARNING] output filename collision at [ROOT]/foo/target/doc/foo_macro/index.html
  |
  = [NOTE] the lib target `foo_macro` in package `foo-macro v1.0.0` has the same output filename as the lib target `foo_macro` in package `foo-macro v1.0.0 ([ROOT]/foo/foo-macro)`
  = [NOTE] 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-macro v1.0.0
[DOCUMENTING] foo-macro v1.0.0
[CHECKING] abc v1.0.0 ([ROOT]/foo/abc)
[DOCUMENTING] foo-macro v1.0.0 ([ROOT]/foo/foo-macro)
[DOCUMENTING] abc v1.0.0 ([ROOT]/foo/abc)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
[GENERATED] [ROOT]/foo/target/doc/abc/index.html and 1 other file

"#]].unordered())
        .run();
}
