//! Tests for normal registry dependencies.

use std::fmt::Write;
use std::fs::{self, File};
use std::path::Path;
use std::sync::Arc;
use std::sync::Mutex;

use cargo::core::SourceId;
use cargo_test_support::cargo_process;
use cargo_test_support::paths;
use cargo_test_support::prelude::*;
use cargo_test_support::registry::{
    self, registry_path, Dependency, Package, RegistryBuilder, Response, TestRegistry,
};
use cargo_test_support::{basic_manifest, project, str};
use cargo_test_support::{git, t};
use cargo_util::paths::remove_dir_all;

fn setup_http() -> TestRegistry {
    RegistryBuilder::new().http_index().build()
}

#[cargo_test]
fn test_server_stops() {
    let server = setup_http();
    server.join(); // ensure the server fully shuts down
}

#[cargo_test]
fn simple_http() {
    let _server = setup_http();
    simple(
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

#[cargo_test]
fn simple_git() {
    simple(
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

fn simple(pre_clean_expected: impl IntoData, post_clean_expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("bar", "0.0.1").publish();

    p.cargo("check").with_stderr_data(pre_clean_expected).run();

    p.cargo("clean").run();

    assert!(paths::home().join(".cargo/registry/CACHEDIR.TAG").is_file());

    // Don't download a second time
    p.cargo("check").with_stderr_data(post_clean_expected).run();
}

#[cargo_test]
fn deps_http() {
    let _server = setup_http();
    deps(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`)
[DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`)
[CHECKING] baz v0.0.1
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

#[cargo_test]
fn deps_git() {
    deps(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`)
[DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`)
[CHECKING] baz v0.0.1
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

fn deps(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("baz", "0.0.1").publish();
    Package::new("bar", "0.0.1").dep("baz", "*").publish();

    p.cargo("check").with_stderr_data(expected).run();

    assert!(paths::home().join(".cargo/registry/CACHEDIR.TAG").is_file());
}

#[cargo_test]
fn nonexistent_http() {
    let _server = setup_http();
    nonexistent(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `nonexistent` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]]);
}

#[cargo_test]
fn nonexistent_git() {
    nonexistent(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `nonexistent` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]]);
}

fn nonexistent(expected: impl IntoData) {
    Package::new("init", "0.0.1").publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                nonexistent = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("check")
        .with_status(101)
        .with_stderr_data(expected)
        .run();
}

#[cargo_test]
fn wrong_case_http() {
    let _server = setup_http();
    wrong_case(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package found
searched package name: `Init`
perhaps you meant:      init
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]]);
}

#[cargo_test]
fn wrong_case_git() {
    wrong_case(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package found
searched package name: `Init`
perhaps you meant:      init
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]]);
}

fn wrong_case(expected: impl IntoData) {
    Package::new("init", "0.0.1").publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                Init = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    // #5678 to make this work
    p.cargo("check")
        .with_status(101)
        .with_stderr_data(expected)
        .run();
}

#[cargo_test]
fn mis_hyphenated_http() {
    let _server = setup_http();
    mis_hyphenated(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package found
searched package name: `mis_hyphenated`
perhaps you meant:      mis-hyphenated
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]]);
}

#[cargo_test]
fn mis_hyphenated_git() {
    mis_hyphenated(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package found
searched package name: `mis_hyphenated`
perhaps you meant:      mis-hyphenated
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]]);
}

fn mis_hyphenated(expected: impl IntoData) {
    Package::new("mis-hyphenated", "0.0.1").publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                mis_hyphenated = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    // #2775 to make this work
    p.cargo("check")
        .with_status(101)
        .with_stderr_data(expected)
        .run();
}

#[cargo_test]
fn wrong_version_http() {
    let _server = setup_http();
    wrong_version(
        str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] failed to select a version for the requirement `foo = ">=1.0.0"`
candidate versions found which didn't match: 0.0.2, 0.0.1
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`
perhaps a crate was updated and forgotten to be re-vendored?

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] failed to select a version for the requirement `foo = ">=1.0.0"`
candidate versions found which didn't match: 0.0.4, 0.0.3, 0.0.2, ...
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`
perhaps a crate was updated and forgotten to be re-vendored?

"#]],
    );
}

#[cargo_test]
fn wrong_version_git() {
    wrong_version(
        str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] failed to select a version for the requirement `foo = ">=1.0.0"`
candidate versions found which didn't match: 0.0.2, 0.0.1
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`
perhaps a crate was updated and forgotten to be re-vendored?

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] failed to select a version for the requirement `foo = ">=1.0.0"`
candidate versions found which didn't match: 0.0.4, 0.0.3, 0.0.2, ...
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`
perhaps a crate was updated and forgotten to be re-vendored?

"#]],
    );
}

fn wrong_version(pre_publish_expected: impl IntoData, post_publish_expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                foo = ">= 1.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("foo", "0.0.1").publish();
    Package::new("foo", "0.0.2").publish();

    p.cargo("check")
        .with_status(101)
        .with_stderr_data(pre_publish_expected)
        .run();

    Package::new("foo", "0.0.3").publish();
    Package::new("foo", "0.0.4").publish();

    p.cargo("check")
        .with_status(101)
        .with_stderr_data(post_publish_expected)
        .run();
}

#[cargo_test]
fn bad_cksum_http() {
    let _server = setup_http();
    bad_cksum(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bad-cksum v0.0.1 (registry `dummy-registry`)
[ERROR] failed to download replaced source registry `crates-io`

Caused by:
  failed to verify the checksum of `bad-cksum v0.0.1 (registry `dummy-registry`)`

"#]]);
}

#[cargo_test]
fn bad_cksum_git() {
    bad_cksum(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bad-cksum v0.0.1 (registry `dummy-registry`)
[ERROR] failed to download replaced source registry `crates-io`

Caused by:
  failed to verify the checksum of `bad-cksum v0.0.1 (registry `dummy-registry`)`

"#]]);
}

fn bad_cksum(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bad-cksum = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    let pkg = Package::new("bad-cksum", "0.0.1");
    pkg.publish();
    t!(File::create(&pkg.archive_dst()));

    p.cargo("check -v")
        .with_status(101)
        .with_stderr_data(expected)
        .run();
}

#[cargo_test]
fn update_registry_http() {
    let _server = setup_http();
    update_registry(
        str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `notyet` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] notyet v0.0.1 (registry `dummy-registry`)
[CHECKING] notyet v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

#[cargo_test]
fn update_registry_git() {
    update_registry(
        str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `notyet` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] notyet v0.0.1 (registry `dummy-registry`)
[CHECKING] notyet v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

fn update_registry(pre_publish_expected: impl IntoData, post_publish_expected: impl IntoData) {
    Package::new("init", "0.0.1").publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                notyet = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("check")
        .with_status(101)
        .with_stderr_data(pre_publish_expected)
        .run();

    Package::new("notyet", "0.0.1").publish();

    p.cargo("check")
        .with_stderr_data(post_publish_expected)
        .run();
}

#[cargo_test]
fn package_with_path_deps_http() {
    let _server = setup_http();
    package_with_path_deps(
        str![[r#"
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
[UPDATING] `dummy-registry` index
[ERROR] failed to prepare local package for uploading

Caused by:
  no matching package named `notyet` found
  location searched: `dummy-registry` index (which is replacing registry `crates-io`)
  required by package `foo v0.0.1 ([ROOT]/foo)`

"#]],
        str![[r#"
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
[UPDATING] `dummy-registry` index
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
[VERIFYING] foo v0.0.1 ([ROOT]/foo)
[DOWNLOADING] crates ...
[DOWNLOADED] notyet v0.0.1 (registry `dummy-registry`)
[COMPILING] notyet v0.0.1
[COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

#[cargo_test]
fn package_with_path_deps_git() {
    package_with_path_deps(
        str![[r#"
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
[UPDATING] `dummy-registry` index
[ERROR] failed to prepare local package for uploading

Caused by:
  no matching package named `notyet` found
  location searched: `dummy-registry` index (which is replacing registry `crates-io`)
  required by package `foo v0.0.1 ([ROOT]/foo)`

"#]],
        str![[r#"
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
[UPDATING] `dummy-registry` index
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
[VERIFYING] foo v0.0.1 ([ROOT]/foo)
[DOWNLOADING] crates ...
[DOWNLOADED] notyet v0.0.1 (registry `dummy-registry`)
[COMPILING] notyet v0.0.1
[COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

fn package_with_path_deps(
    pre_publish_expected: impl IntoData,
    post_publish_expected: impl IntoData,
) {
    Package::new("init", "0.0.1").publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []
                license = "MIT"
                description = "foo"
                repository = "bar"

                [dependencies.notyet]
                version = "0.0.1"
                path = "notyet"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file("notyet/Cargo.toml", &basic_manifest("notyet", "0.0.1"))
        .file("notyet/src/lib.rs", "")
        .build();

    p.cargo("package")
        .with_status(101)
        .with_stderr_data(pre_publish_expected)
        .run();

    Package::new("notyet", "0.0.1").publish();

    p.cargo("package")
        .with_stderr_data(post_publish_expected)
        .run();
}

#[cargo_test]
fn lockfile_locks_http() {
    let _server = setup_http();
    lockfile_locks(
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

#[cargo_test]
fn lockfile_locks_git() {
    lockfile_locks(
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

fn lockfile_locks(pre_publish_expected: impl IntoData, post_publish_expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("bar", "0.0.1").publish();

    p.cargo("check")
        .with_stderr_data(pre_publish_expected)
        .run();

    p.root().move_into_the_past();
    Package::new("bar", "0.0.2").publish();

    p.cargo("check")
        .with_stderr_data(post_publish_expected)
        .run();
}

#[cargo_test]
fn lockfile_locks_transitively_http() {
    let _server = setup_http();
    lockfile_locks_transitively(
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`)
[DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`)
[CHECKING] baz v0.0.1
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

#[cargo_test]
fn lockfile_locks_transitively_git() {
    lockfile_locks_transitively(
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`)
[DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`)
[CHECKING] baz v0.0.1
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

fn lockfile_locks_transitively(
    pre_publish_expected: impl IntoData,
    post_publish_expected: impl IntoData,
) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("baz", "0.0.1").publish();
    Package::new("bar", "0.0.1").dep("baz", "*").publish();

    p.cargo("check")
        .with_stderr_data(pre_publish_expected)
        .run();

    p.root().move_into_the_past();
    Package::new("baz", "0.0.2").publish();
    Package::new("bar", "0.0.2").dep("baz", "*").publish();

    p.cargo("check")
        .with_stderr_data(post_publish_expected)
        .run();
}

#[cargo_test]
fn yanks_are_not_used_http() {
    let _server = setup_http();
    yanks_are_not_used(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`)
[DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`)
[CHECKING] baz v0.0.1
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

#[cargo_test]
fn yanks_are_not_used_git() {
    yanks_are_not_used(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`)
[DOWNLOADED] ba[..] v0.0.1 (registry `dummy-registry`)
[CHECKING] baz v0.0.1
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

fn yanks_are_not_used(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("baz", "0.0.1").publish();
    Package::new("baz", "0.0.2").yanked(true).publish();
    Package::new("bar", "0.0.1").dep("baz", "*").publish();
    Package::new("bar", "0.0.2")
        .dep("baz", "*")
        .yanked(true)
        .publish();

    p.cargo("check").with_stderr_data(expected).run();
}

#[cargo_test]
fn relying_on_a_yank_is_bad_http() {
    let _server = setup_http();
    relying_on_a_yank_is_bad(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] failed to select a version for the requirement `baz = "=0.0.2"`
candidate versions found which didn't match: 0.0.1
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `bar v0.0.1`
    ... which satisfies dependency `bar = "*"` of package `foo v0.0.1 ([ROOT]/foo)`
perhaps a crate was updated and forgotten to be re-vendored?

"#]]);
}

#[cargo_test]
fn relying_on_a_yank_is_bad_git() {
    relying_on_a_yank_is_bad(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] failed to select a version for the requirement `baz = "=0.0.2"`
candidate versions found which didn't match: 0.0.1
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `bar v0.0.1`
    ... which satisfies dependency `bar = "*"` of package `foo v0.0.1 ([ROOT]/foo)`
perhaps a crate was updated and forgotten to be re-vendored?

"#]]);
}

fn relying_on_a_yank_is_bad(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("baz", "0.0.1").publish();
    Package::new("baz", "0.0.2").yanked(true).publish();
    Package::new("bar", "0.0.1").dep("baz", "=0.0.2").publish();

    p.cargo("check")
        .with_status(101)
        .with_stderr_data(expected)
        .run();
}

#[cargo_test]
fn yanks_in_lockfiles_are_ok_http() {
    let _server = setup_http();
    yanks_in_lockfiles_are_ok(
        str![[r#"
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `bar` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]],
    );
}

#[cargo_test]
fn yanks_in_lockfiles_are_ok_git() {
    yanks_in_lockfiles_are_ok(
        str![[r#"
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `bar` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]],
    );
}

fn yanks_in_lockfiles_are_ok(expected_check: impl IntoData, expected_update: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("bar", "0.0.1").publish();

    p.cargo("check").run();

    registry_path().join("3").rm_rf();

    Package::new("bar", "0.0.1").yanked(true).publish();

    p.cargo("check").with_stderr_data(expected_check).run();

    p.cargo("update")
        .with_status(101)
        .with_stderr_data(expected_update)
        .run();
}

#[cargo_test]
fn yanks_in_lockfiles_are_ok_for_other_update_http() {
    let _server = setup_http();
    yanks_in_lockfiles_are_ok_for_other_update(
        str![[r#"
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `bar` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[UPDATING] baz v0.0.1 -> v0.0.2

"#]],
    );
}

#[cargo_test]
fn yanks_in_lockfiles_are_ok_for_other_update_git() {
    yanks_in_lockfiles_are_ok_for_other_update(
        str![[r#"
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `bar` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[UPDATING] baz v0.0.1 -> v0.0.2

"#]],
    );
}

fn yanks_in_lockfiles_are_ok_for_other_update(
    expected_check: impl IntoData,
    expected_update: impl IntoData,
    expected_other_update: impl IntoData,
) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "*"
                baz = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("bar", "0.0.1").publish();
    Package::new("baz", "0.0.1").publish();

    p.cargo("check").run();

    registry_path().join("3").rm_rf();

    Package::new("bar", "0.0.1").yanked(true).publish();
    Package::new("baz", "0.0.1").publish();

    p.cargo("check").with_stderr_data(expected_check).run();

    Package::new("baz", "0.0.2").publish();

    p.cargo("update")
        .with_status(101)
        .with_stderr_data(expected_update)
        .run();

    p.cargo("update baz")
        .with_stderr_data(expected_other_update)
        .run();
}

#[cargo_test]
fn yanks_in_lockfiles_are_ok_with_new_dep_http() {
    let _server = setup_http();
    yanks_in_lockfiles_are_ok_with_new_dep(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[ADDING] baz v0.0.1
[DOWNLOADING] crates ...
[DOWNLOADED] baz v0.0.1 (registry `dummy-registry`)
[CHECKING] baz v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

#[cargo_test]
fn yanks_in_lockfiles_are_ok_with_new_dep_git() {
    yanks_in_lockfiles_are_ok_with_new_dep(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[ADDING] baz v0.0.1
[DOWNLOADING] crates ...
[DOWNLOADED] baz v0.0.1 (registry `dummy-registry`)
[CHECKING] baz v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

fn yanks_in_lockfiles_are_ok_with_new_dep(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("bar", "0.0.1").publish();

    p.cargo("check").run();

    registry_path().join("3").rm_rf();

    Package::new("bar", "0.0.1").yanked(true).publish();
    Package::new("baz", "0.0.1").publish();

    p.change_file(
        "Cargo.toml",
        r#"
            [package]
            name = "foo"
            version = "0.0.1"
            edition = "2015"
            authors = []

            [dependencies]
            bar = "*"
            baz = "*"
        "#,
    );

    p.cargo("check").with_stderr_data(expected).run();
}

#[cargo_test]
fn update_with_lockfile_if_packages_missing_http() {
    let _server = setup_http();
    update_with_lockfile_if_packages_missing(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

#[cargo_test]
fn update_with_lockfile_if_packages_missing_git() {
    update_with_lockfile_if_packages_missing(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

fn update_with_lockfile_if_packages_missing(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("bar", "0.0.1").publish();
    p.cargo("check").run();
    p.root().move_into_the_past();

    paths::home().join(".cargo/registry").rm_rf();
    p.cargo("check").with_stderr_data(expected).run();
}

#[cargo_test]
fn update_lockfile_http() {
    let _server = setup_http();
    update_lockfile(
        str![[r#"
[UPDATING] `dummy-registry` index
[UPDATING] bar v0.0.1 -> v0.0.2

"#]],
        str![[r#"
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.2 (registry `dummy-registry`)
[CHECKING] bar v0.0.2
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[UPDATING] bar v0.0.2 -> v0.0.3

"#]],
        str![[r#"
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.3 (registry `dummy-registry`)
[CHECKING] bar v0.0.3
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[UPDATING] bar v0.0.3 -> v0.0.4
[ADDING] spam v0.2.5

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[UPDATING] bar v0.0.4 -> v0.0.5
[REMOVING] spam v0.2.5

"#]],
    );
}

#[cargo_test]
fn update_lockfile_git() {
    update_lockfile(
        str![[r#"
[UPDATING] `dummy-registry` index
[UPDATING] bar v0.0.1 -> v0.0.2

"#]],
        str![[r#"
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.2 (registry `dummy-registry`)
[CHECKING] bar v0.0.2
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[UPDATING] bar v0.0.2 -> v0.0.3

"#]],
        str![[r#"
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.3 (registry `dummy-registry`)
[CHECKING] bar v0.0.3
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[UPDATING] bar v0.0.3 -> v0.0.4
[ADDING] spam v0.2.5

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[UPDATING] bar v0.0.4 -> v0.0.5
[REMOVING] spam v0.2.5

"#]],
    );
}

fn update_lockfile(
    expected_update: impl IntoData,
    expected_check: impl IntoData,
    expected_other_update: impl IntoData,
    expected_other_check: impl IntoData,
    expected_new_update: impl IntoData,
    expected_new_check: impl IntoData,
) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    println!("0.0.1");
    Package::new("bar", "0.0.1").publish();
    p.cargo("check").run();

    Package::new("bar", "0.0.2").publish();
    Package::new("bar", "0.0.3").publish();
    paths::home().join(".cargo/registry").rm_rf();
    println!("0.0.2 update");
    p.cargo("update bar --precise 0.0.2")
        .with_stderr_data(expected_update)
        .run();

    println!("0.0.2 build");
    p.cargo("check").with_stderr_data(expected_check).run();

    println!("0.0.3 update");
    p.cargo("update bar")
        .with_stderr_data(expected_other_update)
        .run();

    println!("0.0.3 build");
    p.cargo("check")
        .with_stderr_data(expected_other_check)
        .run();

    println!("new dependencies update");
    Package::new("bar", "0.0.4").dep("spam", "0.2.5").publish();
    Package::new("spam", "0.2.5").publish();
    p.cargo("update bar")
        .with_stderr_data(expected_new_update)
        .run();

    println!("new dependencies update");
    Package::new("bar", "0.0.5").publish();
    p.cargo("update bar")
        .with_stderr_data(expected_new_check)
        .run();
}

#[cargo_test]
fn dev_dependency_not_used_http() {
    let _server = setup_http();
    dev_dependency_not_used(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

#[cargo_test]
fn dev_dependency_not_used_git() {
    dev_dependency_not_used(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

fn dev_dependency_not_used(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("baz", "0.0.1").publish();
    Package::new("bar", "0.0.1").dev_dep("baz", "*").publish();

    p.cargo("check").with_stderr_data(expected).run();
}

#[cargo_test]
fn bad_license_file_http() {
    let registry = setup_http();
    bad_license_file(
        &registry,
        str![[r#"
...
[ERROR] license-file `foo` does not appear to exist (relative to `[ROOT]/foo`).
...
"#]],
    );
}

#[cargo_test]
fn bad_license_file_git() {
    let registry = registry::init();
    bad_license_file(
        &registry,
        str![[r#"
...
[ERROR] license-file `foo` does not appear to exist (relative to `[ROOT]/foo`).
...
"#]],
    );
}

fn bad_license_file(registry: &TestRegistry, expected: impl IntoData) {
    Package::new("foo", "1.0.0").publish();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []
                license-file = "foo"
                description = "bar"
                repository = "baz"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();
    p.cargo("publish -v")
        .replace_crates_io(registry.index_url())
        .with_status(101)
        .with_stderr_data(expected)
        .run();
}

#[cargo_test]
fn updating_a_dep_http() {
    let _server = setup_http();
    updating_a_dep(
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[CHECKING] bar v0.0.1
[CHECKING] a v0.0.1 ([ROOT]/foo/a)
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[UPDATING] bar v0.0.1 -> v0.1.0
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.1.0 (registry `dummy-registry`)
[CHECKING] bar v0.1.0
[CHECKING] a v0.0.1 ([ROOT]/foo/a)
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

#[cargo_test]
fn updating_a_dep_git() {
    updating_a_dep(
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[CHECKING] bar v0.0.1
[CHECKING] a v0.0.1 ([ROOT]/foo/a)
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[UPDATING] bar v0.0.1 -> v0.1.0
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.1.0 (registry `dummy-registry`)
[CHECKING] bar v0.1.0
[CHECKING] a v0.0.1 ([ROOT]/foo/a)
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

fn updating_a_dep(pre_update_expected: impl IntoData, post_update_expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies.a]
                path = "a"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file(
            "a/Cargo.toml",
            r#"
                [package]
                name = "a"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "*"
            "#,
        )
        .file("a/src/lib.rs", "")
        .build();

    Package::new("bar", "0.0.1").publish();

    p.cargo("check").with_stderr_data(pre_update_expected).run();
    assert!(paths::home().join(".cargo/registry/CACHEDIR.TAG").is_file());

    // Now delete the CACHEDIR.TAG file: this is the situation we'll be in after
    // upgrading from a version of Cargo that doesn't mark this directory, to one that
    // does. It should be recreated.
    fs::remove_file(paths::home().join(".cargo/registry/CACHEDIR.TAG"))
        .expect("remove CACHEDIR.TAG");

    p.change_file(
        "a/Cargo.toml",
        r#"
        [package]
        name = "a"
        version = "0.0.1"
        edition = "2015"
        authors = []

        [dependencies]
        bar = "0.1.0"
        "#,
    );
    Package::new("bar", "0.1.0").publish();

    println!("second");
    p.cargo("check")
        .with_stderr_data(post_update_expected)
        .run();

    assert!(
        paths::home().join(".cargo/registry/CACHEDIR.TAG").is_file(),
        "CACHEDIR.TAG recreated in existing registry"
    );
}

#[cargo_test]
fn git_and_registry_dep_http() {
    let _server = setup_http();
    git_and_registry_dep(
        str![[r#"
[UPDATING] `dummy-registry` index
[UPDATING] git repository `[ROOTURL]/b`
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] a v0.0.1 (registry `dummy-registry`)
[CHECKING] a v0.0.1
[CHECKING] b v0.0.1 ([ROOTURL]/b#[..])
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

#[cargo_test]
fn git_and_registry_dep_git() {
    git_and_registry_dep(
        str![[r#"
[UPDATING] `dummy-registry` index
[UPDATING] git repository `[ROOTURL]/b`
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] a v0.0.1 (registry `dummy-registry`)
[CHECKING] a v0.0.1
[CHECKING] b v0.0.1 ([ROOTURL]/b#[..])
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
        str![[r#"
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

fn git_and_registry_dep(pre_move_expected: impl IntoData, post_move_expected: impl IntoData) {
    let b = git::repo(&paths::root().join("b"))
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "b"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                a = "0.0.1"
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    let p = project()
        .file(
            "Cargo.toml",
            &format!(
                r#"
                    [package]
                    name = "foo"
                    version = "0.0.1"
                    edition = "2015"
                    authors = []

                    [dependencies]
                    a = "0.0.1"

                    [dependencies.b]
                    git = '{}'
                "#,
                b.url()
            ),
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("a", "0.0.1").publish();

    p.root().move_into_the_past();
    p.cargo("check").with_stderr_data(pre_move_expected).run();
    p.root().move_into_the_past();

    println!("second");
    p.cargo("check").with_stderr_data(post_move_expected).run();
}

#[cargo_test]
fn update_publish_then_update_http() {
    let _server = setup_http();
    update_publish_then_update(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] a v0.1.1 (registry `dummy-registry`)
[COMPILING] a v0.1.1
[COMPILING] foo v0.5.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

#[cargo_test]
fn update_publish_then_update_git() {
    update_publish_then_update(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
[DOWNLOADED] a v0.1.1 (registry `dummy-registry`)
[COMPILING] a v0.1.1
[COMPILING] foo v0.5.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

fn update_publish_then_update(expected: impl IntoData) {
    // First generate a Cargo.lock and a clone of the registry index at the
    // "head" of the current registry.
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                a = "0.1.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();
    Package::new("a", "0.1.0").publish();
    p.cargo("build").run();

    // Next, publish a new package and back up the copy of the registry we just
    // created.
    Package::new("a", "0.1.1").publish();
    let registry = paths::home().join(".cargo/registry");
    let backup = paths::root().join("registry-backup");
    t!(fs::rename(&registry, &backup));

    // Generate a Cargo.lock with the newer version, and then move the old copy
    // of the registry back into place.
    let p2 = project()
        .at("foo2")
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                a = "0.1.1"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();
    p2.cargo("build").run();
    registry.rm_rf();
    t!(fs::rename(&backup, &registry));
    t!(fs::rename(
        p2.root().join("Cargo.lock"),
        p.root().join("Cargo.lock")
    ));

    // Finally, build the first project again (with our newer Cargo.lock) which
    // should force an update of the old registry, download the new crate, and
    // then build everything again.
    p.cargo("build").with_stderr_data(expected).run();
}

#[cargo_test]
fn fetch_downloads_http() {
    let _server = setup_http();
    fetch_downloads(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] a v0.1.0 (registry `dummy-registry`)

"#]]);
}

#[cargo_test]
fn fetch_downloads_git() {
    fetch_downloads(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] a v0.1.0 (registry `dummy-registry`)

"#]]);
}

fn fetch_downloads(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                a = "0.1.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("a", "0.1.0").publish();

    p.cargo("fetch").with_stderr_data(expected).run();
}

#[cargo_test]
fn update_transitive_dependency_http() {
    let _server = setup_http();
    update_transitive_dependency(
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[UPDATING] b v0.1.0 -> v0.1.1

"#]],
        str![[r#"
[DOWNLOADING] crates ...
[DOWNLOADED] b v0.1.1 (registry `dummy-registry`)
[CHECKING] b v0.1.1
[CHECKING] a v0.1.0
[CHECKING] foo v0.5.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

#[cargo_test]
fn update_transitive_dependency_git() {
    update_transitive_dependency(
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[UPDATING] b v0.1.0 -> v0.1.1

"#]],
        str![[r#"
[DOWNLOADING] crates ...
[DOWNLOADED] b v0.1.1 (registry `dummy-registry`)
[CHECKING] b v0.1.1
[CHECKING] a v0.1.0
[CHECKING] foo v0.5.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

fn update_transitive_dependency(expected_update: impl IntoData, expected_check: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                a = "0.1.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("a", "0.1.0").dep("b", "*").publish();
    Package::new("b", "0.1.0").publish();

    p.cargo("fetch").run();

    Package::new("b", "0.1.1").publish();

    p.cargo("update b").with_stderr_data(expected_update).run();

    p.cargo("check").with_stderr_data(expected_check).run();
}

#[cargo_test]
fn update_backtracking_ok_http() {
    let _server = setup_http();
    update_backtracking_ok(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[UPDATING] hyper v0.6.5 -> v0.6.6
[UPDATING] openssl v0.1.0 -> v0.1.1

"#]]);
}

#[cargo_test]
fn update_backtracking_ok_git() {
    update_backtracking_ok(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[UPDATING] hyper v0.6.5 -> v0.6.6
[UPDATING] openssl v0.1.0 -> v0.1.1

"#]]);
}

fn update_backtracking_ok(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                webdriver = "0.1"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("webdriver", "0.1.0")
        .dep("hyper", "0.6")
        .publish();
    Package::new("hyper", "0.6.5")
        .dep("openssl", "0.1")
        .dep("cookie", "0.1")
        .publish();
    Package::new("cookie", "0.1.0")
        .dep("openssl", "0.1")
        .publish();
    Package::new("openssl", "0.1.0").publish();

    p.cargo("generate-lockfile").run();

    Package::new("openssl", "0.1.1").publish();
    Package::new("hyper", "0.6.6")
        .dep("openssl", "0.1.1")
        .dep("cookie", "0.1.0")
        .publish();

    p.cargo("update hyper").with_stderr_data(expected).run();
}

#[cargo_test]
fn update_multiple_packages_http() {
    let _server = setup_http();
    update_multiple_packages(
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[UPDATING] a v0.1.0 -> v0.1.1
[UPDATING] b v0.1.0 -> v0.1.1
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[UPDATING] c v0.1.0 -> v0.1.1

"#]],
        str![[r#"
[DOWNLOADING] crates ...
[DOWNLOADED] a v0.1.1 (registry `dummy-registry`)
[DOWNLOADED] b v0.1.1 (registry `dummy-registry`)
[DOWNLOADED] c v0.1.1 (registry `dummy-registry`)
[CHECKING] a v0.1.1
[CHECKING] c v0.1.1
[CHECKING] b v0.1.1
[CHECKING] foo v0.5.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

#[cargo_test]
fn update_multiple_packages_git() {
    update_multiple_packages(
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[UPDATING] a v0.1.0 -> v0.1.1
[UPDATING] b v0.1.0 -> v0.1.1
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest

"#]],
        str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[UPDATING] c v0.1.0 -> v0.1.1

"#]],
        str![[r#"
[DOWNLOADING] crates ...
[DOWNLOADED] c v0.1.1 (registry `dummy-registry`)
[DOWNLOADED] b v0.1.1 (registry `dummy-registry`)
[DOWNLOADED] a v0.1.1 (registry `dummy-registry`)
[CHECKING] b v0.1.1
[CHECKING] a v0.1.1
[CHECKING] c v0.1.1
[CHECKING] foo v0.5.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]],
    );
}

fn update_multiple_packages(
    expected_update: impl IntoData,
    expected_other_update: impl IntoData,
    expected_check: impl IntoData,
) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                a = "*"
                b = "*"
                c = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("a", "0.1.0").publish();
    Package::new("b", "0.1.0").publish();
    Package::new("c", "0.1.0").publish();

    p.cargo("fetch").run();

    Package::new("a", "0.1.1").publish();
    Package::new("b", "0.1.1").publish();
    Package::new("c", "0.1.1").publish();

    p.cargo("update a b")
        .with_stderr_data(expected_update)
        .run();

    p.cargo("update b c")
        .with_stderr_data(expected_other_update)
        .run();

    p.cargo("check")
        .with_stderr_data(IntoData::unordered(expected_check))
        .run();
}

#[cargo_test]
fn bundled_crate_in_registry_http() {
    let _server = setup_http();
    bundled_crate_in_registry();
}

#[cargo_test]
fn bundled_crate_in_registry_git() {
    bundled_crate_in_registry();
}

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

                [dependencies]
                bar = "0.1"
                baz = "0.1"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("bar", "0.1.0").publish();
    Package::new("baz", "0.1.0")
        .dep("bar", "0.1.0")
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "baz"
                version = "0.1.0"
                edition = "2015"
                authors = []

                [dependencies]
                bar = { path = "bar", version = "0.1.0" }
            "#,
        )
        .file("src/lib.rs", "")
        .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
        .file("bar/src/lib.rs", "")
        .publish();

    p.cargo("run").run();
}

#[cargo_test]
fn update_same_prefix_oh_my_how_was_this_a_bug_http() {
    let _server = setup_http();
    update_same_prefix_oh_my_how_was_this_a_bug();
}

#[cargo_test]
fn update_same_prefix_oh_my_how_was_this_a_bug_git() {
    update_same_prefix_oh_my_how_was_this_a_bug();
}

fn update_same_prefix_oh_my_how_was_this_a_bug() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "ugh"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                foo = "0.1"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("foobar", "0.2.0").publish();
    Package::new("foo", "0.1.0")
        .dep("foobar", "0.2.0")
        .publish();

    p.cargo("generate-lockfile").run();
    p.cargo("update foobar --precise=0.2.0").run();
}

#[cargo_test]
fn use_semver_http() {
    let _server = setup_http();
    use_semver();
}

#[cargo_test]
fn use_semver_git() {
    use_semver();
}

fn use_semver() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                foo = "1.2.3-alpha.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("foo", "1.2.3-alpha.0").publish();

    p.cargo("check").run();
}

#[cargo_test]
fn use_semver_package_incorrectly_http() {
    let _server = setup_http();
    use_semver_package_incorrectly(str![[r#"
[ERROR] failed to select a version for the requirement `a = "^0.1"`
candidate versions found which didn't match: 0.1.1-alpha.0
location searched: [ROOT]/foo/a
required by package `b v0.1.0 ([ROOT]/foo/b)`
if you are looking for the prerelease package it needs to be specified explicitly
    a = { version = "0.1.1-alpha.0" }

"#]]);
}

#[cargo_test]
fn use_semver_package_incorrectly_git() {
    use_semver_package_incorrectly(str![[r#"
[ERROR] failed to select a version for the requirement `a = "^0.1"`
candidate versions found which didn't match: 0.1.1-alpha.0
location searched: [ROOT]/foo/a
required by package `b v0.1.0 ([ROOT]/foo/b)`
if you are looking for the prerelease package it needs to be specified explicitly
    a = { version = "0.1.1-alpha.0" }

"#]]);
}

fn use_semver_package_incorrectly(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
            [workspace]
            members = ["a", "b"]
            "#,
        )
        .file(
            "a/Cargo.toml",
            r#"
            [package]
            name = "a"
            version = "0.1.1-alpha.0"
            edition = "2015"
            authors = []
            "#,
        )
        .file(
            "b/Cargo.toml",
            r#"
            [package]
            name = "b"
            version = "0.1.0"
            edition = "2015"
            authors = []

            [dependencies]
            a = { version = "^0.1", path = "../a" }
            "#,
        )
        .file("a/src/main.rs", "fn main() {}")
        .file("b/src/main.rs", "fn main() {}")
        .build();

    p.cargo("check")
        .with_status(101)
        .with_stderr_data(expected)
        .run();
}

#[cargo_test]
fn only_download_relevant_http() {
    let _server = setup_http();
    only_download_relevant(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 3 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] baz v0.1.0 (registry `dummy-registry`)
[CHECKING] baz v0.1.0
[CHECKING] bar v0.5.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

#[cargo_test]
fn only_download_relevant_git() {
    only_download_relevant(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 3 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] baz v0.1.0 (registry `dummy-registry`)
[CHECKING] baz v0.1.0
[CHECKING] bar v0.5.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

fn only_download_relevant(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [target.foo.dependencies]
                foo = "*"
                [dev-dependencies]
                bar = "*"
                [dependencies]
                baz = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("foo", "0.1.0").publish();
    Package::new("bar", "0.1.0").publish();
    Package::new("baz", "0.1.0").publish();

    p.cargo("check").with_stderr_data(expected).run();
}

#[cargo_test]
fn resolve_and_backtracking_http() {
    let _server = setup_http();
    resolve_and_backtracking();
}

#[cargo_test]
fn resolve_and_backtracking_git() {
    resolve_and_backtracking();
}

fn resolve_and_backtracking() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                foo = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("foo", "0.1.1")
        .feature_dep("bar", "0.1", &["a", "b"])
        .publish();
    Package::new("foo", "0.1.0").publish();

    p.cargo("check").run();
}

#[cargo_test]
fn upstream_warnings_on_extra_verbose_http() {
    let _server = setup_http();
    upstream_warnings_on_extra_verbose(str![[r#"
...
[WARNING] function `unused` is never used
...
"#]]);
}

#[cargo_test]
fn upstream_warnings_on_extra_verbose_git() {
    upstream_warnings_on_extra_verbose(str![[r#"
...
[WARNING] function `unused` is never used
...
"#]]);
}

fn upstream_warnings_on_extra_verbose(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                foo = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("foo", "0.1.0")
        .file("src/lib.rs", "fn unused() {}")
        .publish();

    p.cargo("check -vv").with_stderr_data(expected).run();
}

#[cargo_test]
fn disallow_network_http() {
    let _server = setup_http();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                foo = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("check --frozen")
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] failed to get `foo` as a dependency of package `bar v0.5.0 ([ROOT]/foo)`

Caused by:
  failed to query replaced source registry `crates-io`

Caused by:
  attempting to make an HTTP request, but --frozen was specified

"#]])
        .run();
}

#[cargo_test]
fn disallow_network_git() {
    let _server = RegistryBuilder::new().build();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                foo = "*"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("check --frozen")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] failed to get `foo` as a dependency of package `bar v0.5.0 ([ROOT]/foo)`

Caused by:
  failed to load source for dependency `foo`

Caused by:
  Unable to update registry `crates-io`

Caused by:
  failed to update replaced source registry `crates-io`

Caused by:
  attempting to make an HTTP request, but --frozen was specified

"#]])
        .run();
}

#[cargo_test]
fn add_dep_dont_update_registry_http() {
    let _server = setup_http();
    add_dep_dont_update_registry(str![[r#"
[CHECKING] bar v0.5.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

#[cargo_test]
fn add_dep_dont_update_registry_git() {
    add_dep_dont_update_registry(str![[r#"
[CHECKING] bar v0.5.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

fn add_dep_dont_update_registry(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                baz = { path = "baz" }
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file(
            "baz/Cargo.toml",
            r#"
                [package]
                name = "baz"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                remote = "0.3"
            "#,
        )
        .file("baz/src/lib.rs", "")
        .build();

    Package::new("remote", "0.3.4").publish();

    p.cargo("check").run();

    p.change_file(
        "Cargo.toml",
        r#"
        [package]
        name = "bar"
        version = "0.5.0"
        edition = "2015"
        authors = []

        [dependencies]
        baz = { path = "baz" }
        remote = "0.3"
        "#,
    );

    p.cargo("check").with_stderr_data(expected).run();
}

#[cargo_test]
fn bump_version_dont_update_registry_http() {
    let _server = setup_http();
    bump_version_dont_update_registry(str![[r#"
[CHECKING] bar v0.6.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

#[cargo_test]
fn bump_version_dont_update_registry_git() {
    bump_version_dont_update_registry(str![[r#"
[CHECKING] bar v0.6.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]);
}

fn bump_version_dont_update_registry(expected: impl IntoData) {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                baz = { path = "baz" }
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .file(
            "baz/Cargo.toml",
            r#"
                [package]
                name = "baz"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                remote = "0.3"
            "#,
        )
        .file("baz/src/lib.rs", "")
        .build();

    Package::new("remote", "0.3.4").publish();

    p.cargo("check").run();

    p.change_file(
        "Cargo.toml",
        r#"
        [package]
        name = "bar"
        version = "0.6.0"
        edition = "2015"
        authors = []

        [dependencies]
        baz = { path = "baz" }
        "#,
    );

    p.cargo("check").with_stderr_data(expected).run();
}

#[cargo_test]
fn toml_lies_but_index_is_truth_http() {
    let _server = setup_http();
    toml_lies_but_index_is_truth();
}

#[cargo_test]
fn toml_lies_but_index_is_truth_git() {
    toml_lies_but_index_is_truth();
}

fn toml_lies_but_index_is_truth() {
    Package::new("foo", "0.2.0").publish();
    Package::new("bar", "0.3.0")
        .dep("foo", "0.2.0")
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.3.0"
                edition = "2015"
                authors = []

                [dependencies]
                foo = "0.1.0"
            "#,
        )
        .file("src/lib.rs", "extern crate foo;")
        .publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "bar"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "0.3"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("check -v").run();
}

#[cargo_test]
fn vv_prints_warnings_http() {
    let _server = setup_http();
    vv_prints_warnings();
}

#[cargo_test]
fn vv_prints_warnings_git() {
    vv_prints_warnings();
}

fn vv_prints_warnings() {
    Package::new("foo", "0.2.0")
        .file(
            "src/lib.rs",
            "#![deny(warnings)] fn foo() {} // unused function",
        )
        .publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "fo"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                foo = "0.2"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("check -vv").run();
}

#[cargo_test]
fn bad_and_or_malicious_packages_rejected_http() {
    let _server = setup_http();
    bad_and_or_malicious_packages_rejected(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.2.0 (registry `dummy-registry`)
[ERROR] failed to download replaced source registry `crates-io`

Caused by:
  failed to unpack package `foo v0.2.0 (registry `dummy-registry`)`

Caused by:
  invalid tarball downloaded, contains a file at "foo-0.1.0/src/lib.rs" which isn't under "foo-0.2.0"

"#]]);
}

#[cargo_test]
fn bad_and_or_malicious_packages_rejected_git() {
    bad_and_or_malicious_packages_rejected(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] foo v0.2.0 (registry `dummy-registry`)
[ERROR] failed to download replaced source registry `crates-io`

Caused by:
  failed to unpack package `foo v0.2.0 (registry `dummy-registry`)`

Caused by:
  invalid tarball downloaded, contains a file at "foo-0.1.0/src/lib.rs" which isn't under "foo-0.2.0"

"#]]);
}

fn bad_and_or_malicious_packages_rejected(expected: impl IntoData) {
    Package::new("foo", "0.2.0")
        .extra_file("foo-0.1.0/src/lib.rs", "")
        .publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "fo"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                foo = "0.2"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("check -vv")
        .with_status(101)
        .with_stderr_data(expected)
        .run();
}

#[cargo_test]
fn git_init_templatedir_missing_http() {
    let _server = setup_http();
    git_init_templatedir_missing();
}

#[cargo_test]
fn git_init_templatedir_missing_git() {
    git_init_templatedir_missing();
}

fn git_init_templatedir_missing() {
    Package::new("foo", "0.2.0").dep("bar", "*").publish();
    Package::new("bar", "0.2.0").publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "fo"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                foo = "0.2"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    p.cargo("check").run();

    remove_dir_all(paths::home().join(".cargo/registry")).unwrap();
    fs::write(
        paths::home().join(".gitconfig"),
        r#"
            [init]
            templatedir = nowhere
        "#,
    )
    .unwrap();

    p.cargo("check").run();
    p.cargo("check").run();
}

#[cargo_test]
fn rename_deps_and_features_http() {
    let _server = setup_http();
    rename_deps_and_features();
}

#[cargo_test]
fn rename_deps_and_features_git() {
    rename_deps_and_features();
}

fn rename_deps_and_features() {
    Package::new("foo", "0.1.0")
        .file("src/lib.rs", "pub fn f1() {}")
        .publish();
    Package::new("foo", "0.2.0")
        .file("src/lib.rs", "pub fn f2() {}")
        .publish();
    Package::new("bar", "0.2.0")
        .add_dep(
            Dependency::new("foo01", "0.1.0")
                .package("foo")
                .optional(true),
        )
        .add_dep(Dependency::new("foo02", "0.2.0").package("foo"))
        .feature("another", &["foo01"])
        .file(
            "src/lib.rs",
            r#"
                extern crate foo02;
                #[cfg(feature = "foo01")]
                extern crate foo01;

                pub fn foo() {
                    foo02::f2();
                    #[cfg(feature = "foo01")]
                    foo01::f1();
                }
            "#,
        )
        .publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "a"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                bar = "0.2"
            "#,
        )
        .file(
            "src/main.rs",
            "
                extern crate bar;
                fn main() { bar::foo(); }
            ",
        )
        .build();

    p.cargo("check").run();
    p.cargo("check --features bar/foo01").run();
    p.cargo("check --features bar/another").run();
}

#[cargo_test]
fn ignore_invalid_json_lines_http() {
    let _server = setup_http();
    ignore_invalid_json_lines();
}

#[cargo_test]
fn ignore_invalid_json_lines_git() {
    ignore_invalid_json_lines();
}

fn ignore_invalid_json_lines() {
    Package::new("foo", "0.1.0").publish();
    Package::new("foo", "0.1.1").invalid_json(true).publish();
    Package::new("foo", "0.2.0").publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "a"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                foo = '0.1.0'
                foo02 = { version = '0.2.0', package = 'foo' }
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("check").run();
}

#[cargo_test]
fn readonly_registry_still_works_http() {
    let _server = setup_http();
    readonly_registry_still_works();
}

#[cargo_test]
fn readonly_registry_still_works_git() {
    readonly_registry_still_works();
}

fn readonly_registry_still_works() {
    Package::new("foo", "0.1.0").publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "a"
                version = "0.5.0"
                edition = "2015"
                authors = []

                [dependencies]
                foo = '0.1.0'
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("generate-lockfile").run();
    p.cargo("fetch --locked").run();
    chmod_readonly(&paths::home(), true);
    p.cargo("check").run();
    // make sure we un-readonly the files afterwards so "cargo clean" can remove them (#6934)
    chmod_readonly(&paths::home(), false);

    fn chmod_readonly(path: &Path, readonly: bool) {
        for entry in t!(path.read_dir()) {
            let entry = t!(entry);
            let path = entry.path();
            if t!(entry.file_type()).is_dir() {
                chmod_readonly(&path, readonly);
            } else {
                set_readonly(&path, readonly);
            }
        }
        set_readonly(path, readonly);
    }

    fn set_readonly(path: &Path, readonly: bool) {
        let mut perms = t!(path.metadata()).permissions();
        perms.set_readonly(readonly);
        t!(fs::set_permissions(path, perms));
    }
}

#[cargo_test]
fn registry_index_rejected_http() {
    let _server = setup_http();
    registry_index_rejected(
        str![[r#"
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`

Caused by:
  the `registry.index` config value is no longer supported
  Use `[source]` replacement to alter the default index for crates.io.

"#]],
        str![[r#"
[ERROR] the `registry.index` config value is no longer supported
Use `[source]` replacement to alter the default index for crates.io.

"#]],
    );
}

#[cargo_test]
fn registry_index_rejected_git() {
    registry_index_rejected(
        str![[r#"
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`

Caused by:
  the `registry.index` config value is no longer supported
  Use `[source]` replacement to alter the default index for crates.io.

"#]],
        str![[r#"
[ERROR] the `registry.index` config value is no longer supported
Use `[source]` replacement to alter the default index for crates.io.

"#]],
    );
}

fn registry_index_rejected(expected_check: impl IntoData, expected_login: impl IntoData) {
    Package::new("dep", "0.1.0").publish();

    let p = project()
        .file(
            ".cargo/config.toml",
            r#"
            [registry]
            index = "https://example.com/"
            "#,
        )
        .file(
            "Cargo.toml",
            r#"
            [package]
            name = "foo"
            version = "0.1.0"
            edition = "2015"

            [dependencies]
            dep = "0.1"
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("check")
        .with_status(101)
        .with_stderr_data(expected_check)
        .run();

    p.cargo("login")
        .with_status(101)
        .with_stderr_data(expected_login)
        .run();
}

#[cargo_test]
fn package_lock_inside_package_is_overwritten() {
    let registry = registry::init();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("bar", "0.0.1")
        .file("src/lib.rs", "")
        .file(".cargo-ok", "")
        .publish();

    p.cargo("check").run();

    let id = SourceId::for_registry(registry.index_url()).unwrap();
    let hash = cargo::util::hex::short_hash(&id);
    let ok = paths::cargo_home()
        .join("registry")
        .join("src")
        .join(format!("-{}", hash))
        .join("bar-0.0.1")
        .join(".cargo-ok");

    assert_eq!(ok.metadata().unwrap().len(), 7);
}

#[cargo_test]
fn package_lock_as_a_symlink_inside_package_is_overwritten() {
    let registry = registry::init();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("bar", "0.0.1")
        .file("src/lib.rs", "pub fn f() {}")
        .symlink(".cargo-ok", "src/lib.rs")
        .publish();

    p.cargo("check").run();

    let id = SourceId::for_registry(registry.index_url()).unwrap();
    let hash = cargo::util::hex::short_hash(&id);
    let pkg_root = paths::cargo_home()
        .join("registry")
        .join("src")
        .join(format!("-{}", hash))
        .join("bar-0.0.1");
    let ok = pkg_root.join(".cargo-ok");
    let librs = pkg_root.join("src/lib.rs");

    // Is correctly overwritten and doesn't affect the file linked to
    assert_eq!(ok.metadata().unwrap().len(), 7);
    assert_eq!(fs::read_to_string(librs).unwrap(), "pub fn f() {}");
}

#[cargo_test]
fn ignores_unknown_index_version_http() {
    let _server = setup_http();
    ignores_unknown_index_version(str![[r#"
foo v0.1.0 ([ROOT]/foo)
└── bar v1.0.0

"#]]);
}

#[cargo_test]
fn ignores_unknown_index_version_git() {
    ignores_unknown_index_version(str![[r#"
foo v0.1.0 ([ROOT]/foo)
└── bar v1.0.0

"#]]);
}

fn ignores_unknown_index_version(expected: impl IntoData) {
    // If the version field is not understood, it is ignored.
    Package::new("bar", "1.0.0").publish();
    Package::new("bar", "1.0.1")
        .schema_version(u32::MAX)
        .publish();

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

                [dependencies]
                bar = "1.0"
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("tree").with_stdout_data(expected).run();
}

#[cargo_test]
fn unknown_index_version_error() {
    // If the version field is not understood, it is ignored.
    Package::new("bar", "1.0.1")
        .schema_version(u32::MAX)
        .publish();

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

                [dependencies]
                bar = "1.0"
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("generate-lockfile")
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `bar` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.1.0 ([ROOT]/foo)`

"#]])
        .run();
}

#[cargo_test]
fn protocol() {
    cargo_process("install bar")
        .with_status(101)
        .env("CARGO_REGISTRIES_CRATES_IO_PROTOCOL", "invalid")
        .with_stderr_data(str![[r#"
[ERROR] unsupported registry protocol `invalid` (defined in environment variable `CARGO_REGISTRIES_CRATES_IO_PROTOCOL`)

"#]])
        .run()
}

#[cargo_test]
fn http_requires_trailing_slash() {
    cargo_process("install bar --index sparse+https://invalid.crates.io/test")
        .with_status(101)
        .with_stderr_data(str![[r#"
[ERROR] sparse registry url must end in a slash `/`: sparse+https://invalid.crates.io/test

"#]])
        .run()
}

// Limit the test to debug builds so that `__CARGO_TEST_MAX_UNPACK_SIZE` will take affect.
#[cfg(debug_assertions)]
#[cargo_test]
fn reach_max_unpack_size() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"

                [dependencies]
                bar = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    // Size of bar.crate is around 180 bytes.
    Package::new("bar", "0.0.1").publish();

    p.cargo("check")
        .env("__CARGO_TEST_MAX_UNPACK_SIZE", "8") // hit 8 bytes limit and boom!
        .env("__CARGO_TEST_MAX_UNPACK_RATIO", "0")
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[ERROR] failed to download replaced source registry `crates-io`

Caused by:
  failed to unpack package `bar v0.0.1 (registry `dummy-registry`)`

Caused by:
  failed to iterate over archive

Caused by:
  maximum limit reached when reading

"#]])
        .run();

    // Restore to the default ratio and it should compile.
    p.cargo("check")
        .env("__CARGO_TEST_MAX_UNPACK_SIZE", "8")
        .with_stderr_data(str![[r#"
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]])
        .run();
}

#[cargo_test]
fn sparse_blocking_count() {
    let fail_count = Mutex::new(0);
    let _registry = RegistryBuilder::new()
        .http_index()
        .add_responder("/index/3/b/bar", move |req, server| {
            let mut fail_count = fail_count.lock().unwrap();
            if *fail_count < 1 {
                *fail_count += 1;
                server.internal_server_error(req)
            } else {
                server.index(req)
            }
        })
        .build();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("bar", "0.0.1").publish();

    // Ensure we have the expected number of `block_until_ready` calls.
    // The 1st (0 transfers pending), is the deliberate extra call in `ensure_loaded` for a source.
    // The 2nd (1 transfers pending), is the registry `config.json`.
    // the 3rd (1 transfers pending), is the package metadata for `bar`.

    p.cargo("check")
        .env("CARGO_LOG", "network::HttpRegistry::block_until_ready=trace")
        .with_stderr_data(str![[r#"
   [..] TRACE network::HttpRegistry::block_until_ready: 0 transfers pending
[UPDATING] `dummy-registry` index
   [..] TRACE network::HttpRegistry::block_until_ready: 1 transfers pending
   [..] TRACE network::HttpRegistry::block_until_ready: 1 transfers pending
[WARNING] spurious network error (3 tries remaining): failed to get successful HTTP response from `[..]/index/3/b/bar` ([..]), got 500
body:
internal server error
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]).run();
}

#[cargo_test]
fn sparse_retry_single() {
    let fail_count = Mutex::new(0);
    let _registry = RegistryBuilder::new()
        .http_index()
        .add_responder("/index/3/b/bar", move |req, server| {
            let mut fail_count = fail_count.lock().unwrap();
            if *fail_count < 2 {
                *fail_count += 1;
                server.internal_server_error(req)
            } else {
                server.index(req)
            }
        })
        .build();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                bar = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("bar", "0.0.1").publish();

    p.cargo("check").with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[WARNING] spurious network error (3 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 500
body:
internal server error
[WARNING] spurious network error (2 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 500
body:
internal server error
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[CHECKING] bar v0.0.1
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]).run();
}

#[cargo_test]
fn sparse_retry_multiple() {
    // Tests retry behavior of downloading lots of packages with various
    // failure rates accessing the sparse index.

    // The index is the number of retries, the value is the number of packages
    // that retry that number of times. Thus 50 packages succeed on first try,
    // 25 on second, etc.
    const RETRIES: &[u32] = &[50, 25, 12, 6];

    let pkgs: Vec<_> = RETRIES
        .iter()
        .enumerate()
        .flat_map(|(retries, num)| {
            (0..*num)
                .into_iter()
                .map(move |n| (retries as u32, format!("{}-{n}-{retries}", rand_prefix())))
        })
        .collect();

    let mut builder = RegistryBuilder::new().http_index();
    let fail_counts: Arc<Mutex<Vec<u32>>> = Arc::new(Mutex::new(vec![0; pkgs.len()]));
    let mut cargo_toml = r#"
        [package]
        name = "foo"
        version = "0.1.0"
        edition = "2015"

        [dependencies]
        "#
    .to_string();
    // The expected stderr output.
    let mut expected = "\
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
"
    .to_string();
    for (n, (retries, name)) in pkgs.iter().enumerate() {
        let count_clone = fail_counts.clone();
        let retries = *retries;
        let ab = &name[..2];
        let cd = &name[2..4];
        builder = builder.add_responder(format!("/index/{ab}/{cd}/{name}"), move |req, server| {
            let mut fail_counts = count_clone.lock().unwrap();
            if fail_counts[n] < retries {
                fail_counts[n] += 1;
                server.internal_server_error(req)
            } else {
                server.index(req)
            }
        });
        write!(&mut cargo_toml, "{name} = \"1.0.0\"\n").unwrap();
        for retry in 0..retries {
            let remain = 3 - retry;
            write!(
                &mut expected,
                "[WARNING] spurious network error ({remain} tries remaining): \
                failed to get successful HTTP response from \
                `http://127.0.0.1:[..]/{ab}/{cd}/{name}` (127.0.0.1), got 500\n\
                body:\n\
                internal server error\n"
            )
            .unwrap();
        }
        write!(
            &mut expected,
            "\
[DOWNLOADED] {name} v1.0.0 (registry `dummy-registry`)
"
        )
        .unwrap();
    }
    write!(
        &mut expected,
        "\
[LOCKING] 93 packages to latest compatible versions
"
    )
    .unwrap();
    let _server = builder.build();
    for (_, name) in &pkgs {
        Package::new(name, "1.0.0").publish();
    }
    let p = project()
        .file("Cargo.toml", &cargo_toml)
        .file("src/lib.rs", "")
        .build();
    p.cargo("fetch")
        .with_stderr_data(IntoData::unordered(expected))
        .run();
}

#[cargo_test]
fn dl_retry_single() {
    // Tests retry behavior of downloading a package.
    // This tests a single package which exercises the code path that causes
    // it to block.
    let fail_count = Mutex::new(0);
    let _server = RegistryBuilder::new()
        .http_index()
        .add_responder("/dl/bar/1.0.0/download", move |req, server| {
            let mut fail_count = fail_count.lock().unwrap();
            if *fail_count < 2 {
                *fail_count += 1;
                server.internal_server_error(req)
            } else {
                server.dl(req)
            }
        })
        .build();
    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"
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("fetch").with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[WARNING] spurious network error (3 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 500
body:
internal server error
[WARNING] spurious network error (2 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 500
body:
internal server error
[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)

"#]]).run();
}

/// Creates a random prefix to randomly spread out the package names
/// to somewhat evenly distribute the different failures at different
/// points.
fn rand_prefix() -> String {
    use rand::Rng;
    const CHARS: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
    let mut rng = rand::thread_rng();
    (0..5)
        .map(|_| CHARS[rng.gen_range(0..CHARS.len())] as char)
        .collect()
}

#[cargo_test]
fn dl_retry_multiple() {
    // Tests retry behavior of downloading lots of packages with various
    // failure rates.

    // The index is the number of retries, the value is the number of packages
    // that retry that number of times. Thus 50 packages succeed on first try,
    // 25 on second, etc.
    const RETRIES: &[u32] = &[50, 25, 12, 6];

    let pkgs: Vec<_> = RETRIES
        .iter()
        .enumerate()
        .flat_map(|(retries, num)| {
            (0..*num)
                .into_iter()
                .map(move |n| (retries as u32, format!("{}-{n}-{retries}", rand_prefix())))
        })
        .collect();

    let mut builder = RegistryBuilder::new().http_index();
    let fail_counts: Arc<Mutex<Vec<u32>>> = Arc::new(Mutex::new(vec![0; pkgs.len()]));
    let mut cargo_toml = r#"
        [package]
        name = "foo"
        version = "0.1.0"
        edition = "2015"

        [dependencies]
        "#
    .to_string();
    // The expected stderr output.
    let mut expected = "\
[UPDATING] `dummy-registry` index
[DOWNLOADING] crates ...
"
    .to_string();
    for (n, (retries, name)) in pkgs.iter().enumerate() {
        let count_clone = fail_counts.clone();
        let retries = *retries;
        builder =
            builder.add_responder(format!("/dl/{name}/1.0.0/download"), move |req, server| {
                let mut fail_counts = count_clone.lock().unwrap();
                if fail_counts[n] < retries {
                    fail_counts[n] += 1;
                    server.internal_server_error(req)
                } else {
                    server.dl(req)
                }
            });
        write!(&mut cargo_toml, "{name} = \"1.0.0\"\n").unwrap();
        for retry in 0..retries {
            let remain = 3 - retry;
            write!(
                &mut expected,
                "[WARNING] spurious network error ({remain} tries remaining): \
                failed to get successful HTTP response from \
                `http://127.0.0.1:[..]/dl/{name}/1.0.0/download` (127.0.0.1), got 500\n\
                body:\n\
                internal server error\n"
            )
            .unwrap();
        }
        write!(
            &mut expected,
            "[DOWNLOADED] {name} v1.0.0 (registry `dummy-registry`)\n"
        )
        .unwrap();
    }
    write!(
        &mut expected,
        "[LOCKING] 93 packages to latest compatible versions\n"
    )
    .unwrap();
    let _server = builder.build();
    for (_, name) in &pkgs {
        Package::new(name, "1.0.0").publish();
    }
    let p = project()
        .file("Cargo.toml", &cargo_toml)
        .file("src/lib.rs", "")
        .build();
    p.cargo("fetch")
        .with_stderr_data(IntoData::unordered(expected))
        .run();
}

#[cargo_test]
fn deleted_entry() {
    // Checks the behavior when a package is removed from the index.
    // This is done occasionally on crates.io to handle things like
    // copyright takedowns.
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                edition = "2015"

                [dependencies]
                bar = "0.1"
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    // First, test removing a single version, but leaving an older version.
    Package::new("bar", "0.1.0").publish();
    let bar_path = Path::new("3/b/bar");
    let bar_reg_path = registry_path().join(&bar_path);
    let old_index = fs::read_to_string(&bar_reg_path).unwrap();
    Package::new("bar", "0.1.1").publish();
    p.cargo("tree")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.1.1 (registry `dummy-registry`)

"#]])
        .with_stdout_data(str![[r#"
foo v0.1.0 ([ROOT]/foo)
└── bar v0.1.1

"#]])
        .run();

    // Remove 0.1.1
    fs::remove_file(paths::root().join("dl/bar/0.1.1/download")).unwrap();
    let repo = git2::Repository::open(registry_path()).unwrap();
    let mut index = repo.index().unwrap();
    fs::write(&bar_reg_path, &old_index).unwrap();
    index.add_path(&bar_path).unwrap();
    index.write().unwrap();
    git::commit(&repo);

    // With `Cargo.lock` unchanged, it shouldn't have an impact.
    p.cargo("tree")
        .with_stderr_data("")
        .with_stdout_data(str![[r#"
foo v0.1.0 ([ROOT]/foo)
└── bar v0.1.1

"#]])
        .run();

    // Regenerating Cargo.lock should switch to old version.
    fs::remove_file(p.root().join("Cargo.lock")).unwrap();
    p.cargo("tree")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.1.0 (registry `dummy-registry`)

"#]])
        .with_stdout_data(str![[r#"
foo v0.1.0 ([ROOT]/foo)
└── bar v0.1.0

"#]])
        .run();

    // Remove the package entirely.
    fs::remove_file(paths::root().join("dl/bar/0.1.0/download")).unwrap();
    let mut index = repo.index().unwrap();
    index.remove(&bar_path, 0).unwrap();
    index.write().unwrap();
    git::commit(&repo);
    fs::remove_file(&bar_reg_path).unwrap();

    // With `Cargo.lock` unchanged, it shouldn't have an impact.
    p.cargo("tree")
        .with_stderr_data("")
        .with_stdout_data(str![[r#"
foo v0.1.0 ([ROOT]/foo)
└── bar v0.1.0

"#]])
        .run();

    // Regenerating Cargo.lock should fail.
    fs::remove_file(p.root().join("Cargo.lock")).unwrap();
    p.cargo("tree")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `bar` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.1.0 ([ROOT]/foo)`

"#]])
        .with_status(101)
        .run();
}

#[cargo_test]
fn corrupted_ok_overwritten() {
    // Checks what happens if .cargo-ok gets truncated, such as if the file is
    // created, but the flush/close is interrupted.
    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"
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("fetch")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)

"#]])
        .run();
    let ok = glob::glob(
        paths::home()
            .join(".cargo/registry/src/*/bar-1.0.0/.cargo-ok")
            .to_str()
            .unwrap(),
    )
    .unwrap()
    .next()
    .unwrap()
    .unwrap();
    // Simulate cargo being interrupted, or filesystem corruption.
    fs::write(&ok, "").unwrap();
    assert_eq!(fs::read_to_string(&ok).unwrap(), "");
    p.cargo("fetch").with_stderr_data("").run();
    assert_eq!(fs::read_to_string(&ok).unwrap(), r#"{"v":1}"#);
}

#[cargo_test]
fn not_found_permutations() {
    // Test for querying permutations for a missing dependency.
    let misses = Arc::new(Mutex::new(Vec::new()));
    let misses2 = misses.clone();
    let _registry = RegistryBuilder::new()
        .http_index()
        .not_found_handler(move |req, _server| {
            let mut misses = misses2.lock().unwrap();
            misses.push(req.url.path().to_string());
            Response {
                code: 404,
                headers: vec![],
                body: b"not found".to_vec(),
            }
        })
        .build();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"
                authors = []

                [dependencies]
                a-b_c = "1.0"
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("check")
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `a-b_c` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]])
        .run();
    let mut misses = misses.lock().unwrap();
    misses.sort();
    assert_eq!(
        &*misses,
        &[
            "/index/a-/b-/a-b-c",
            "/index/a-/b_/a-b_c",
            "/index/a_/b_/a_b_c"
        ]
    );
}

#[cargo_test]
fn default_auth_error() {
    // Check for the error message for an authentication error when default is set.
    let crates_io = RegistryBuilder::new().http_api().build();
    let _alternative = RegistryBuilder::new().http_api().alternative().build();

    paths::home().join(".cargo/credentials.toml").rm_rf();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                edition = "2015"
                license = "MIT"
                description = "foo"
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    // Test output before setting the default.
    p.cargo("publish --no-verify")
        .replace_crates_io(crates_io.index_url())
        .with_stderr_data(str![[r#"
[UPDATING] crates.io index
[ERROR] no token found, please run `cargo login`
or use environment variable CARGO_REGISTRY_TOKEN

"#]])
        .with_status(101)
        .run();

    p.cargo("publish --no-verify --registry alternative")
        .replace_crates_io(crates_io.index_url())
        .with_stderr_data(str![[r#"
[UPDATING] `alternative` index
[ERROR] no token found for `alternative`, please run `cargo login --registry alternative`
or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN

"#]])
        .with_status(101)
        .run();

    // Test the output with the default.
    cargo_util::paths::append(
        &paths::cargo_home().join("config.toml"),
        br#"
            [registry]
            default = "alternative"
        "#,
    )
    .unwrap();

    p.cargo("publish --no-verify")
        .replace_crates_io(crates_io.index_url())
        .with_stderr_data(str![[r#"
[UPDATING] `alternative` index
[ERROR] no token found for `alternative`, please run `cargo login --registry alternative`
or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN

"#]])
        .with_status(101)
        .run();

    p.cargo("publish --no-verify --registry crates-io")
        .replace_crates_io(crates_io.index_url())
        .with_stderr_data(str![[r#"
[UPDATING] crates.io index
[ERROR] no token found, please run `cargo login --registry crates-io`
or use environment variable CARGO_REGISTRY_TOKEN

"#]])
        .with_status(101)
        .run();
}

const SAMPLE_HEADERS: &[&str] = &[
    "x-amz-cf-pop: SFO53-P2",
    "x-amz-cf-id: vEc3osJrCAXVaciNnF4Vev-hZFgnYwmNZtxMKRJ5bF6h9FTOtbTMnA==",
    "x-cache: Hit from cloudfront",
    "server: AmazonS3",
    "x-amz-version-id: pvsJYY_JGsWiSETZvLJKb7DeEW5wWq1W",
    "x-amz-server-side-encryption: AES256",
    "content-type: text/plain",
    "via: 1.1 bcbc5b46216015493e082cfbcf77ef10.cloudfront.net (CloudFront)",
];

#[cargo_test]
fn debug_header_message_index() {
    // The error message should include some headers for debugging purposes.
    let _server = RegistryBuilder::new()
        .http_index()
        .add_responder("/index/3/b/bar", |_, _| Response {
            code: 503,
            headers: SAMPLE_HEADERS.iter().map(|s| s.to_string()).collect(),
            body: b"Please slow down".to_vec(),
        })
        .build();
    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"
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("fetch")
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[WARNING] spurious network error (3 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503
body:
Please slow down
[WARNING] spurious network error (2 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503
body:
Please slow down
[WARNING] spurious network error (1 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503
body:
Please slow down
[ERROR] failed to get `bar` as a dependency of package `foo v0.1.0 ([ROOT]/foo)`

Caused by:
  failed to query replaced source registry `crates-io`

Caused by:
  download of 3/b/bar failed

Caused by:
  failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503
  debug headers:
  x-amz-cf-pop: SFO53-P2
  x-amz-cf-id: vEc3osJrCAXVaciNnF4Vev-hZFgnYwmNZtxMKRJ5bF6h9FTOtbTMnA==
  x-cache: Hit from cloudfront
  body:
  Please slow down

"#]])
        .run();
}

#[cargo_test]
fn debug_header_message_dl() {
    // Same as debug_header_message_index, but for the dl endpoint which goes
    // through a completely different code path.
    let _server = RegistryBuilder::new()
        .http_index()
        .add_responder("/dl/bar/1.0.0/download", |_, _| Response {
            code: 503,
            headers: SAMPLE_HEADERS.iter().map(|s| s.to_string()).collect(),
            body: b"Please slow down".to_vec(),
        })
        .build();
    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"
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("fetch")
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[WARNING] spurious network error (3 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503
body:
Please slow down
[WARNING] spurious network error (2 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503
body:
Please slow down
[WARNING] spurious network error (1 tries remaining): failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503
body:
Please slow down
[ERROR] failed to download from `http://127.0.0.1:[..]/dl/bar/1.0.0/download`

Caused by:
  failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503
  debug headers:
  x-amz-cf-pop: SFO53-P2
  x-amz-cf-id: vEc3osJrCAXVaciNnF4Vev-hZFgnYwmNZtxMKRJ5bF6h9FTOtbTMnA==
  x-cache: Hit from cloudfront
  body:
  Please slow down

"#]])
        .run();
}

#[cfg(unix)]
#[cargo_test]
fn set_mask_during_unpacking() {
    use std::os::unix::fs::MetadataExt;

    Package::new("bar", "1.0.0")
        .file_with_mode("example.sh", 0o777, "#!/bin/sh")
        .file_with_mode("src/lib.rs", 0o666, "")
        .publish();

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

                [dependencies]
                bar = "1.0"
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("fetch")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)

"#]])
        .run();
    let src_file_path = |path: &str| {
        glob::glob(
            paths::home()
                .join(".cargo/registry/src/*/bar-1.0.0/")
                .join(path)
                .to_str()
                .unwrap(),
        )
        .unwrap()
        .next()
        .unwrap()
        .unwrap()
    };

    let umask = cargo::util::get_umask();
    let metadata = fs::metadata(src_file_path("src/lib.rs")).unwrap();
    assert_eq!(metadata.mode() & 0o777, 0o666 & !umask);
    let metadata = fs::metadata(src_file_path("example.sh")).unwrap();
    assert_eq!(metadata.mode() & 0o777, 0o777 & !umask);
}

#[cargo_test]
fn unpack_again_when_cargo_ok_is_unrecognized() {
    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"
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("fetch")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)

"#]])
        .run();

    let src_file_path = |path: &str| {
        glob::glob(
            paths::home()
                .join(".cargo/registry/src/*/bar-1.0.0/")
                .join(path)
                .to_str()
                .unwrap(),
        )
        .unwrap()
        .next()
        .unwrap()
        .unwrap()
    };

    // Change permissions to simulate the old behavior not respecting umask.
    let lib_rs = src_file_path("src/lib.rs");
    let cargo_ok = src_file_path(".cargo-ok");
    let mut perms = fs::metadata(&lib_rs).unwrap().permissions();
    assert!(!perms.readonly());
    perms.set_readonly(true);
    fs::set_permissions(&lib_rs, perms).unwrap();
    let ok = fs::read_to_string(&cargo_ok).unwrap();
    assert_eq!(&ok, r#"{"v":1}"#);

    p.cargo("fetch").with_stderr_data("").run();

    // Without changing `.cargo-ok`, a unpack won't be triggered.
    let perms = fs::metadata(&lib_rs).unwrap().permissions();
    assert!(perms.readonly());

    // Write "ok" to simulate the old behavior and trigger the unpack again.
    fs::write(&cargo_ok, "ok").unwrap();

    p.cargo("fetch").with_stderr_data("").run();

    // Permission has been restored and `.cargo-ok` is in the new format.
    let perms = fs::metadata(lib_rs).unwrap().permissions();
    assert!(!perms.readonly());
    let ok = fs::read_to_string(&cargo_ok).unwrap();
    assert_eq!(&ok, r#"{"v":1}"#);
}

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

                [dependencies]
                baz = "=0.0.1"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("baz", "0.0.1+b").publish();
    Package::new("baz", "0.0.1+c").yanked(true).publish();

    p.cargo("check")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] baz v0.0.1+b (registry `dummy-registry`)
[CHECKING] baz v0.0.1+b
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]])
        .run();

    Package::new("baz", "0.0.1+d").publish();

    p.cargo("clean").run();
    p.cargo("check")
        .with_stderr_data(str![[r#"
[CHECKING] baz v0.0.1+b
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]])
        .run();
}

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

                [dependencies]
                baz = "=0.0.1"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    Package::new("baz", "0.0.1+a").publish();
    Package::new("baz", "0.0.1+b").publish();
    Package::new("baz", "0.0.1+c").publish();

    p.cargo("update --package baz --precise 0.0.1+b")
        .with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[UPDATING] baz v0.0.1+c -> v0.0.1+b

"#]])
        .run();

    p.cargo("check")
        .with_stderr_data(str![[r#"
[DOWNLOADING] crates ...
[DOWNLOADED] baz v0.0.1+b (registry `dummy-registry`)
[CHECKING] baz v0.0.1+b
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]])
        .run();
}

#[cargo_test]
fn builtin_source_replacement() {
    // errors for builtin source replacement of crates.io
    // should not include mention of source replacement in the error message.
    let server = RegistryBuilder::new().build();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2015"

                [dependencies]
                bad-cksum = ">= 0.0.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    let pkg = Package::new("bad-cksum", "0.0.1");
    pkg.publish();
    t!(File::create(&pkg.archive_dst()));

    p.cargo("check -v")
        .replace_crates_io(&server.index_url())
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] crates.io index
[LOCKING] 1 package to latest compatible version
[DOWNLOADING] crates ...
[DOWNLOADED] bad-cksum v0.0.1
[ERROR] failed to verify the checksum of `bad-cksum v0.0.1`

"#]])
        .run();
}

#[cargo_test]
fn builtin_source_replacement_no_vendor_error() {
    // errors for builtin source replacement of crates.io
    // should not mention outdated vendor dependencies
    let server = RegistryBuilder::new().build();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                edition = "2021"

                [dependencies]
                dep = "0.2.0"
            "#,
        )
        .file("src/main.rs", "fn main() {}")
        .build();

    let pkg = Package::new("dep", "0.1.0");
    pkg.publish();

    p.cargo("check -v")
        .replace_crates_io(&server.index_url())
        .with_status(101)
        .with_stderr_data(str![[r#"
[UPDATING] crates.io index
[ERROR] failed to select a version for the requirement `dep = "^0.2.0"`
candidate versions found which didn't match: 0.1.0
location searched: crates.io index
required by package `foo v0.0.1 ([ROOT]/foo)`

"#]])
        .run();
}
