mirror of
https://github.com/rust-lang/cargo
synced 2024-08-25 02:25:46 +00:00
647 lines
22 KiB
Rust
647 lines
22 KiB
Rust
//! Tests for checking behavior of old cargos.
|
|
//!
|
|
//! These tests are ignored because it is intended to be run on a developer
|
|
//! system with a bunch of toolchains installed. This requires `rustup` to be
|
|
//! installed. It will iterate over installed toolchains, and run some tests
|
|
//! over each one, producing a report at the end. As of this writing, I have
|
|
//! tested 1.0 to 1.51. Run this with:
|
|
//!
|
|
//! ```console
|
|
//! cargo test --test testsuite -- old_cargos --nocapture --ignored
|
|
//! ```
|
|
|
|
use cargo::CargoResult;
|
|
use cargo_test_support::paths::CargoPathExt;
|
|
use cargo_test_support::registry::{self, Dependency, Package};
|
|
use cargo_test_support::{cargo_exe, execs, paths, process, project, rustc_host};
|
|
use cargo_util::{ProcessBuilder, ProcessError};
|
|
use semver::Version;
|
|
use std::fs;
|
|
|
|
fn tc_process(cmd: &str, toolchain: &str) -> ProcessBuilder {
|
|
let mut p = if toolchain == "this" {
|
|
if cmd == "cargo" {
|
|
process(&cargo_exe())
|
|
} else {
|
|
process(cmd)
|
|
}
|
|
} else {
|
|
let mut cmd = process(cmd);
|
|
cmd.arg(format!("+{}", toolchain));
|
|
cmd
|
|
};
|
|
// Reset PATH since `process` modifies it to remove rustup.
|
|
p.env("PATH", std::env::var_os("PATH").unwrap());
|
|
p
|
|
}
|
|
|
|
/// Returns a sorted list of all toolchains.
|
|
///
|
|
/// The returned value includes the parsed version, and the rustup toolchain
|
|
/// name as a string.
|
|
fn collect_all_toolchains() -> Vec<(Version, String)> {
|
|
let rustc_version = |tc| {
|
|
let mut cmd = tc_process("rustc", tc);
|
|
cmd.arg("-V");
|
|
let output = cmd.exec_with_output().expect("rustc installed");
|
|
let version = std::str::from_utf8(&output.stdout).unwrap();
|
|
let parts: Vec<_> = version.split_whitespace().collect();
|
|
assert_eq!(parts[0], "rustc");
|
|
assert!(parts[1].starts_with("1."));
|
|
Version::parse(parts[1]).expect("valid version")
|
|
};
|
|
|
|
// Provide a way to override the list.
|
|
if let Ok(tcs) = std::env::var("OLD_CARGO") {
|
|
return tcs
|
|
.split(',')
|
|
.map(|tc| (rustc_version(tc), tc.to_string()))
|
|
.collect();
|
|
}
|
|
|
|
let host = rustc_host();
|
|
// I tend to have lots of toolchains installed, but I don't want to test
|
|
// all of them (like dated nightlies, or toolchains for non-host targets).
|
|
let valid_names = &[
|
|
format!("stable-{}", host),
|
|
format!("beta-{}", host),
|
|
format!("nightly-{}", host),
|
|
];
|
|
|
|
let output = ProcessBuilder::new("rustup")
|
|
.args(&["toolchain", "list"])
|
|
.exec_with_output()
|
|
.expect("rustup should be installed");
|
|
let stdout = std::str::from_utf8(&output.stdout).unwrap();
|
|
let mut toolchains: Vec<_> = stdout
|
|
.lines()
|
|
.map(|line| {
|
|
// Some lines say things like (default), just get the version.
|
|
line.split_whitespace().next().expect("non-empty line")
|
|
})
|
|
.filter(|line| {
|
|
line.ends_with(&host)
|
|
&& (line.starts_with("1.") || valid_names.iter().any(|name| name == line))
|
|
})
|
|
.map(|line| (rustc_version(line), line.to_string()))
|
|
.collect();
|
|
|
|
// Also include *this* cargo.
|
|
toolchains.push((rustc_version("this"), "this".to_string()));
|
|
toolchains.sort_by(|a, b| a.0.cmp(&b.0));
|
|
toolchains
|
|
}
|
|
|
|
// This is a test for exercising the behavior of older versions of cargo with
|
|
// the new feature syntax.
|
|
//
|
|
// The test involves a few dependencies with different feature requirements:
|
|
//
|
|
// * `bar` 1.0.0 is the base version that does not use the new syntax.
|
|
// * `bar` 1.0.1 has a feature with the new syntax, but the feature is unused.
|
|
// The optional dependency `new-baz-dep` should not be activated.
|
|
// * `bar` 1.0.2 has a dependency on `baz` that *requires* the new feature
|
|
// syntax.
|
|
#[ignore]
|
|
#[cargo_test]
|
|
fn new_features() {
|
|
if std::process::Command::new("rustup").output().is_err() {
|
|
panic!("old_cargos requires rustup to be installed");
|
|
}
|
|
Package::new("new-baz-dep", "1.0.0").publish();
|
|
|
|
Package::new("baz", "1.0.0").publish();
|
|
let baz101_cksum = Package::new("baz", "1.0.1")
|
|
.add_dep(Dependency::new("new-baz-dep", "1.0").optional(true))
|
|
.feature("new-feat", &["dep:new-baz-dep"])
|
|
.publish();
|
|
|
|
let bar100_cksum = Package::new("bar", "1.0.0")
|
|
.add_dep(Dependency::new("baz", "1.0").optional(true))
|
|
.feature("feat", &["baz"])
|
|
.publish();
|
|
let bar101_cksum = Package::new("bar", "1.0.1")
|
|
.add_dep(Dependency::new("baz", "1.0").optional(true))
|
|
.feature("feat", &["dep:baz"])
|
|
.publish();
|
|
let bar102_cksum = Package::new("bar", "1.0.2")
|
|
.add_dep(Dependency::new("baz", "1.0").enable_features(&["new-feat"]))
|
|
.publish();
|
|
|
|
let p = project()
|
|
.file(
|
|
"Cargo.toml",
|
|
r#"
|
|
[package]
|
|
name = "foo"
|
|
version = "0.1.0"
|
|
|
|
[dependencies]
|
|
bar = "1.0"
|
|
"#,
|
|
)
|
|
.file("src/lib.rs", "")
|
|
.build();
|
|
|
|
let lock_bar_to = |toolchain_version: &Version, bar_version| {
|
|
let lock = if toolchain_version < &Version::new(1, 12, 0) {
|
|
let url = registry::registry_url();
|
|
match bar_version {
|
|
100 => format!(
|
|
r#"
|
|
[root]
|
|
name = "foo"
|
|
version = "0.1.0"
|
|
dependencies = [
|
|
"bar 1.0.0 (registry+{url})",
|
|
]
|
|
|
|
[[package]]
|
|
name = "bar"
|
|
version = "1.0.0"
|
|
source = "registry+{url}"
|
|
"#,
|
|
url = url
|
|
),
|
|
101 => format!(
|
|
r#"
|
|
[root]
|
|
name = "foo"
|
|
version = "0.1.0"
|
|
dependencies = [
|
|
"bar 1.0.1 (registry+{url})",
|
|
]
|
|
|
|
[[package]]
|
|
name = "bar"
|
|
version = "1.0.1"
|
|
source = "registry+{url}"
|
|
"#,
|
|
url = url
|
|
),
|
|
102 => format!(
|
|
r#"
|
|
[root]
|
|
name = "foo"
|
|
version = "0.1.0"
|
|
dependencies = [
|
|
"bar 1.0.2 (registry+{url})",
|
|
]
|
|
|
|
[[package]]
|
|
name = "bar"
|
|
version = "1.0.2"
|
|
source = "registry+{url}"
|
|
dependencies = [
|
|
"baz 1.0.1 (registry+{url})",
|
|
]
|
|
|
|
[[package]]
|
|
name = "baz"
|
|
version = "1.0.1"
|
|
source = "registry+{url}"
|
|
"#,
|
|
url = url
|
|
),
|
|
_ => panic!("unexpected version"),
|
|
}
|
|
} else {
|
|
match bar_version {
|
|
100 => format!(
|
|
r#"
|
|
[root]
|
|
name = "foo"
|
|
version = "0.1.0"
|
|
dependencies = [
|
|
"bar 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
]
|
|
|
|
[[package]]
|
|
name = "bar"
|
|
version = "1.0.0"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
[metadata]
|
|
"checksum bar 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "{}"
|
|
"#,
|
|
bar100_cksum
|
|
),
|
|
101 => format!(
|
|
r#"
|
|
[root]
|
|
name = "foo"
|
|
version = "0.1.0"
|
|
dependencies = [
|
|
"bar 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
]
|
|
|
|
[[package]]
|
|
name = "bar"
|
|
version = "1.0.1"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
[metadata]
|
|
"checksum bar 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "{}"
|
|
"#,
|
|
bar101_cksum
|
|
),
|
|
102 => format!(
|
|
r#"
|
|
[root]
|
|
name = "foo"
|
|
version = "0.1.0"
|
|
dependencies = [
|
|
"bar 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
]
|
|
|
|
[[package]]
|
|
name = "bar"
|
|
version = "1.0.2"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
dependencies = [
|
|
"baz 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
]
|
|
|
|
[[package]]
|
|
name = "baz"
|
|
version = "1.0.1"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
[metadata]
|
|
"checksum bar 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "{bar102_cksum}"
|
|
"checksum baz 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "{baz101_cksum}"
|
|
"#,
|
|
bar102_cksum = bar102_cksum,
|
|
baz101_cksum = baz101_cksum
|
|
),
|
|
_ => panic!("unexpected version"),
|
|
}
|
|
};
|
|
p.change_file("Cargo.lock", &lock);
|
|
};
|
|
|
|
let toolchains = collect_all_toolchains();
|
|
|
|
let config_path = paths::home().join(".cargo/config");
|
|
let lock_path = p.root().join("Cargo.lock");
|
|
|
|
struct ToolchainBehavior {
|
|
bar: Option<Version>,
|
|
baz: Option<Version>,
|
|
new_baz_dep: Option<Version>,
|
|
}
|
|
|
|
// Collect errors to print at the end. One entry per toolchain, a list of
|
|
// strings to print.
|
|
let mut unexpected_results: Vec<Vec<String>> = Vec::new();
|
|
|
|
for (version, toolchain) in &toolchains {
|
|
let mut tc_result = Vec::new();
|
|
// Write a config appropriate for this version.
|
|
if version < &Version::new(1, 12, 0) {
|
|
fs::write(
|
|
&config_path,
|
|
format!(
|
|
r#"
|
|
[registry]
|
|
index = "{}"
|
|
"#,
|
|
registry::registry_url()
|
|
),
|
|
)
|
|
.unwrap();
|
|
} else {
|
|
fs::write(
|
|
&config_path,
|
|
format!(
|
|
"
|
|
[source.crates-io]
|
|
registry = 'https://wut' # only needed by 1.12
|
|
replace-with = 'dummy-registry'
|
|
|
|
[source.dummy-registry]
|
|
registry = '{}'
|
|
",
|
|
registry::registry_url()
|
|
),
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
// Fetches the version of a package in the lock file.
|
|
let pkg_version = |pkg| -> Option<Version> {
|
|
let output = tc_process("cargo", toolchain)
|
|
.args(&["pkgid", pkg])
|
|
.cwd(p.root())
|
|
.exec_with_output()
|
|
.ok()?;
|
|
let stdout = std::str::from_utf8(&output.stdout).unwrap();
|
|
let version = stdout
|
|
.trim()
|
|
.rsplitn(2, ':')
|
|
.next()
|
|
.expect("version after colon");
|
|
Some(Version::parse(version).expect("parseable version"))
|
|
};
|
|
|
|
// Runs `cargo build` and returns the versions selected in the lock.
|
|
let run_cargo = || -> CargoResult<ToolchainBehavior> {
|
|
match tc_process("cargo", toolchain)
|
|
.args(&["build", "--verbose"])
|
|
.cwd(p.root())
|
|
.exec_with_output()
|
|
{
|
|
Ok(_output) => {
|
|
eprintln!("{} ok", toolchain);
|
|
let bar = pkg_version("bar");
|
|
let baz = pkg_version("baz");
|
|
let new_baz_dep = pkg_version("new-baz-dep");
|
|
Ok(ToolchainBehavior {
|
|
bar,
|
|
baz,
|
|
new_baz_dep,
|
|
})
|
|
}
|
|
Err(e) => {
|
|
eprintln!("{} err {}", toolchain, e);
|
|
Err(e)
|
|
}
|
|
}
|
|
};
|
|
|
|
macro_rules! check_lock {
|
|
($tc_result:ident, $pkg:expr, $which:expr, $actual:expr, None) => {
|
|
check_lock!(= $tc_result, $pkg, $which, $actual, None);
|
|
};
|
|
($tc_result:ident, $pkg:expr, $which:expr, $actual:expr, $expected:expr) => {
|
|
check_lock!(= $tc_result, $pkg, $which, $actual, Some(Version::parse($expected).unwrap()));
|
|
};
|
|
(= $tc_result:ident, $pkg:expr, $which:expr, $actual:expr, $expected:expr) => {
|
|
let exp: Option<Version> = $expected;
|
|
if $actual != $expected {
|
|
$tc_result.push(format!(
|
|
"{} for {} saw {:?} but expected {:?}",
|
|
$which, $pkg, $actual, exp
|
|
));
|
|
}
|
|
};
|
|
}
|
|
|
|
let check_err_contains = |tc_result: &mut Vec<_>, err: anyhow::Error, contents| {
|
|
if let Some(ProcessError {
|
|
stderr: Some(stderr),
|
|
..
|
|
}) = err.downcast_ref::<ProcessError>()
|
|
{
|
|
let stderr = std::str::from_utf8(stderr).unwrap();
|
|
if !stderr.contains(contents) {
|
|
tc_result.push(format!(
|
|
"{} expected to see error contents:\n{}\nbut saw:\n{}",
|
|
toolchain, contents, stderr
|
|
));
|
|
}
|
|
} else {
|
|
panic!("{} unexpected error {}", toolchain, err);
|
|
}
|
|
};
|
|
|
|
// Unlocked behavior.
|
|
let which = "unlocked";
|
|
lock_path.rm_rf();
|
|
p.build_dir().rm_rf();
|
|
match run_cargo() {
|
|
Ok(behavior) => {
|
|
// TODO: Switch to 51 after backport.
|
|
if version < &Version::new(1, 52, 0) && toolchain != "this" {
|
|
check_lock!(tc_result, "bar", which, behavior.bar, "1.0.2");
|
|
check_lock!(tc_result, "baz", which, behavior.baz, "1.0.1");
|
|
check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
|
|
} else {
|
|
check_lock!(tc_result, "bar", which, behavior.bar, "1.0.0");
|
|
check_lock!(tc_result, "baz", which, behavior.baz, None);
|
|
check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
tc_result.push(format!("unlocked build failed: {}", e));
|
|
}
|
|
}
|
|
|
|
let which = "locked bar 1.0.0";
|
|
lock_bar_to(version, 100);
|
|
match run_cargo() {
|
|
Ok(behavior) => {
|
|
check_lock!(tc_result, "bar", which, behavior.bar, "1.0.0");
|
|
check_lock!(tc_result, "baz", which, behavior.baz, None);
|
|
check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
|
|
}
|
|
Err(e) => {
|
|
tc_result.push(format!("bar 1.0.0 locked build failed: {}", e));
|
|
}
|
|
}
|
|
|
|
let which = "locked bar 1.0.1";
|
|
lock_bar_to(version, 101);
|
|
match run_cargo() {
|
|
Ok(behavior) => {
|
|
check_lock!(tc_result, "bar", which, behavior.bar, "1.0.1");
|
|
check_lock!(tc_result, "baz", which, behavior.baz, None);
|
|
check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
|
|
}
|
|
Err(e) => {
|
|
if toolchain == "this" {
|
|
// 1.0.1 can't be used without -Znamespaced-features
|
|
// It gets filtered out of the index.
|
|
check_err_contains(&mut tc_result, e,
|
|
"error: failed to select a version for the requirement `bar = \"=1.0.1\"`\n\
|
|
candidate versions found which didn't match: 1.0.2, 1.0.0"
|
|
);
|
|
} else {
|
|
tc_result.push(format!("bar 1.0.1 locked build failed: {}", e));
|
|
}
|
|
}
|
|
}
|
|
|
|
let which = "locked bar 1.0.2";
|
|
lock_bar_to(version, 102);
|
|
match run_cargo() {
|
|
Ok(behavior) => {
|
|
check_lock!(tc_result, "bar", which, behavior.bar, "1.0.2");
|
|
check_lock!(tc_result, "baz", which, behavior.baz, "1.0.1");
|
|
check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
|
|
}
|
|
Err(e) => {
|
|
if toolchain == "this" {
|
|
// baz can't lock to 1.0.1, it requires -Znamespaced-features
|
|
check_err_contains(&mut tc_result, e,
|
|
"error: failed to select a version for the requirement `baz = \"=1.0.1\"`\n\
|
|
candidate versions found which didn't match: 1.0.0"
|
|
);
|
|
} else {
|
|
tc_result.push(format!("bar 1.0.2 locked build failed: {}", e));
|
|
}
|
|
}
|
|
}
|
|
|
|
unexpected_results.push(tc_result);
|
|
}
|
|
|
|
// Generate a report.
|
|
let mut has_err = false;
|
|
for ((tc_vers, tc_name), errs) in toolchains.iter().zip(unexpected_results) {
|
|
if errs.is_empty() {
|
|
continue;
|
|
}
|
|
eprintln!("error: toolchain {} (version {}):", tc_name, tc_vers);
|
|
for err in errs {
|
|
eprintln!(" {}", err);
|
|
}
|
|
has_err = true;
|
|
}
|
|
if has_err {
|
|
panic!("at least one toolchain did not run as expected");
|
|
}
|
|
}
|
|
|
|
#[cargo_test]
|
|
#[ignore]
|
|
fn index_cache_rebuild() {
|
|
// Checks that the index cache gets rebuilt.
|
|
//
|
|
// 1.48 will not cache entries with features with the same name as a
|
|
// dependency. If the cache does not get rebuilt, then running with
|
|
// `-Znamespaced-features` would prevent the new cargo from seeing those
|
|
// entries. The index cache version was changed to prevent this from
|
|
// happening, and switching between versions should work correctly
|
|
// (although it will thrash the cash, that's better than not working
|
|
// correctly.
|
|
Package::new("baz", "1.0.0").publish();
|
|
Package::new("bar", "1.0.0").publish();
|
|
Package::new("bar", "1.0.1")
|
|
.add_dep(Dependency::new("baz", "1.0").optional(true))
|
|
.feature("baz", &["dep:baz"])
|
|
.publish();
|
|
|
|
let p = project()
|
|
.file(
|
|
"Cargo.toml",
|
|
r#"
|
|
[package]
|
|
name = "foo"
|
|
version = "0.1.0"
|
|
|
|
[dependencies]
|
|
bar = "1.0"
|
|
"#,
|
|
)
|
|
.file("src/lib.rs", "")
|
|
.build();
|
|
|
|
// This version of Cargo errors on index entries that have overlapping
|
|
// feature names, so 1.0.1 will be missing.
|
|
execs()
|
|
.with_process_builder(tc_process("cargo", "1.48.0"))
|
|
.arg("check")
|
|
.cwd(p.root())
|
|
.with_stderr(
|
|
"\
|
|
[UPDATING] [..]
|
|
[DOWNLOADING] crates ...
|
|
[DOWNLOADED] bar v1.0.0 [..]
|
|
[CHECKING] bar v1.0.0
|
|
[CHECKING] foo v0.1.0 [..]
|
|
[FINISHED] [..]
|
|
",
|
|
)
|
|
.run();
|
|
|
|
fs::remove_file(p.root().join("Cargo.lock")).unwrap();
|
|
|
|
// This should rebuild the cache and use 1.0.1.
|
|
p.cargo("check -Znamespaced-features")
|
|
.masquerade_as_nightly_cargo()
|
|
.with_stderr(
|
|
"\
|
|
[UPDATING] [..]
|
|
[DOWNLOADING] crates ...
|
|
[DOWNLOADED] bar v1.0.1 [..]
|
|
[CHECKING] bar v1.0.1
|
|
[CHECKING] foo v0.1.0 [..]
|
|
[FINISHED] [..]
|
|
",
|
|
)
|
|
.run();
|
|
|
|
fs::remove_file(p.root().join("Cargo.lock")).unwrap();
|
|
|
|
// Verify 1.48 can still resolve, and is at 1.0.0.
|
|
execs()
|
|
.with_process_builder(tc_process("cargo", "1.48.0"))
|
|
.arg("tree")
|
|
.cwd(p.root())
|
|
.with_stdout(
|
|
"\
|
|
foo v0.1.0 [..]
|
|
└── bar v1.0.0
|
|
",
|
|
)
|
|
.run();
|
|
}
|
|
|
|
#[cargo_test]
|
|
#[ignore]
|
|
fn avoids_split_debuginfo_collision() {
|
|
// Checks for a bug where .o files were being incorrectly shared between
|
|
// different toolchains using incremental and split-debuginfo on macOS.
|
|
let p = project()
|
|
.file(
|
|
"Cargo.toml",
|
|
r#"
|
|
[package]
|
|
name = "foo"
|
|
version = "0.1.0"
|
|
|
|
[profile.dev]
|
|
split-debuginfo = "unpacked"
|
|
"#,
|
|
)
|
|
.file("src/main.rs", "fn main() {}")
|
|
.build();
|
|
|
|
execs()
|
|
.with_process_builder(tc_process("cargo", "stable"))
|
|
.arg("build")
|
|
.env("CARGO_INCREMENTAL", "1")
|
|
.cwd(p.root())
|
|
.with_stderr(
|
|
"\
|
|
[COMPILING] foo v0.1.0 [..]
|
|
[FINISHED] [..]
|
|
",
|
|
)
|
|
.run();
|
|
|
|
p.cargo("build")
|
|
.env("CARGO_INCREMENTAL", "1")
|
|
.with_stderr(
|
|
"\
|
|
[COMPILING] foo v0.1.0 [..]
|
|
[FINISHED] [..]
|
|
",
|
|
)
|
|
.run();
|
|
|
|
execs()
|
|
.with_process_builder(tc_process("cargo", "stable"))
|
|
.arg("build")
|
|
.env("CARGO_INCREMENTAL", "1")
|
|
.cwd(p.root())
|
|
.with_stderr(
|
|
"\
|
|
[COMPILING] foo v0.1.0 [..]
|
|
[FINISHED] [..]
|
|
",
|
|
)
|
|
.run();
|
|
}
|