diff options
Diffstat (limited to 'tests/testsuite/old_cargos.rs')
-rw-r--r-- | tests/testsuite/old_cargos.rs | 679 |
1 files changed, 679 insertions, 0 deletions
diff --git a/tests/testsuite/old_cargos.rs b/tests/testsuite/old_cargos.rs new file mode 100644 index 0000000..a85e13d --- /dev/null +++ b/tests/testsuite/old_cargos.rs @@ -0,0 +1,679 @@ +//! 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(); + + toolchains.sort_by(|a, b| a.0.cmp(&b.0)); + toolchains +} + +/// Returns whether the default toolchain is the stable version. +fn default_toolchain_is_stable() -> bool { + let default = tc_process("rustc", "this").arg("-V").exec_with_output(); + let stable = tc_process("rustc", "stable").arg("-V").exec_with_output(); + match (default, stable) { + (Ok(d), Ok(s)) => d.stdout == s.stdout, + _ => false, + } +} + +// 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 = "must be run manually, requires old cargo installations"] +#[cargo_test] +fn new_features() { + let registry = registry::init(); + 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.index_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.index_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.index_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) => { + if version < &Version::new(1, 51, 0) { + 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 if version >= &Version::new(1, 51, 0) && version <= &Version::new(1, 59, 0) { + 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); + } + // Starting with 1.60, namespaced-features has been stabilized. + else { + 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, + "1.0.0" + ); + } + } + 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) => { + // When version >= 1.51 and <= 1.59, + // 1.0.1 can't be used without -Znamespaced-features + // It gets filtered out of the index. + check_err_contains( + &mut tc_result, + e, + "candidate versions found which didn't match: 1.0.2, 1.0.0", + ); + } + } + + let which = "locked bar 1.0.2"; + lock_bar_to(version, 102); + match run_cargo() { + Ok(behavior) => { + if version <= &Version::new(1, 59, 0) { + 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); + } + // Starting with 1.60, namespaced-features has been stabilized. + else { + 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, + "1.0.0" + ); + } + } + Err(e) => { + // When version >= 1.51 and <= 1.59, + // baz can't lock to 1.0.1, it requires -Znamespaced-features + check_err_contains( + &mut tc_result, + e, + "candidate versions found which didn't match: 1.0.0", + ); + } + } + + 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 = "must be run manually, requires old cargo installations"] +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") + .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 = "must be run manually, requires old cargo installations"] +fn avoids_split_debuginfo_collision() { + // Test needs two different toolchains. + // If the default toolchain is stable, then it won't work. + if default_toolchain_is_stable() { + return; + } + // 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( + "\ +[FINISHED] [..] +", + ) + .run(); +} |