Skip to content

Commit cafbc12

Browse files
committed
Auto merge of #13467 - ehuss:global-tracker-old-cargo, r=weihanglo
Add global_cache_tracker stability tests. This adds some tests to ensure that the database used in the global cache tracker stays compatible across versions. These tests work by using rustup to run both the current cargo and the stable cargo, and verifying that when switching versions, everything works as expected. These tests will be ignored on developer environments if they don't have rustup, or don't have the stable toolchain installed. It does assume that "stable" is at least 1.76. It is required for the tests to run in CI, but will be disabled in rust-lang/rust since it does not have rustup. I'm not expecting too much trouble with these tests, but if they become too fiddly or broken, they can always be changed or removed. The support code for running "cargo +stable" is very basic right now. If we expand to add similar tests in the future, then I think we could consider adding support functions (such as [`tc_process`](https://github.com/rust-lang/cargo/blob/64ccff290fe20e2aa7c04b9c71460a7fd962ea61/tests/testsuite/old_cargos.rs#L21-L36)) to make it easier or more reliable.
2 parents d325f9b + a82794e commit cafbc12

File tree

5 files changed

+215
-13
lines changed

5 files changed

+215
-13
lines changed

.github/workflows/main.yml

+6-1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ jobs:
116116
CARGO_PROFILE_TEST_DEBUG: 1
117117
CARGO_INCREMENTAL: 0
118118
CARGO_PUBLIC_NETWORK_TESTS: 1
119+
# Workaround for https://github.com/rust-lang/rustup/issues/3036
120+
RUSTUP_WINDOWS_PATH_ADD_BIN: 0
119121
strategy:
120122
matrix:
121123
include:
@@ -156,6 +158,10 @@ jobs:
156158
- uses: actions/checkout@v4
157159
- name: Dump Environment
158160
run: ci/dump-environment.sh
161+
# Some tests require stable. Make sure it is set to the most recent stable
162+
# so that we can predictably handle updates if necessary (and not randomly
163+
# when GitHub updates its image).
164+
- run: rustup update --no-self-update stable
159165
- run: rustup update --no-self-update ${{ matrix.rust }} && rustup default ${{ matrix.rust }}
160166
- run: rustup target add ${{ matrix.other }}
161167
- run: rustup component add rustc-dev llvm-tools-preview rust-docs
@@ -166,7 +172,6 @@ jobs:
166172
- name: Configure extra test environment
167173
run: echo CARGO_CONTAINER_TESTS=1 >> $GITHUB_ENV
168174
if: matrix.os == 'ubuntu-latest'
169-
170175
- run: cargo test -p cargo
171176
- name: Clear intermediate test output
172177
run: ci/clean-test-output.sh

crates/cargo-test-macro/src/lib.rs

+43-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use proc_macro::*;
2+
use std::path::Path;
23
use std::process::Command;
34
use std::sync::Once;
45

@@ -74,6 +75,12 @@ pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
7475
requires_reason = true;
7576
set_ignore!(is_not_nightly, "requires nightly");
7677
}
78+
"requires_rustup_stable" => {
79+
set_ignore!(
80+
!has_rustup_stable(),
81+
"rustup or stable toolchain not installed"
82+
);
83+
}
7784
s if s.starts_with("requires_") => {
7885
let command = &s[9..];
7986
set_ignore!(!has_command(command), "{command} not installed");
@@ -204,29 +211,28 @@ fn version() -> (u32, bool) {
204211
unsafe { VERSION }
205212
}
206213

207-
fn has_command(command: &str) -> bool {
208-
let output = match Command::new(command).arg("--version").output() {
214+
fn check_command(command_path: &Path, args: &[&str]) -> bool {
215+
let mut command = Command::new(command_path);
216+
let command_name = command.get_program().to_str().unwrap().to_owned();
217+
command.args(args);
218+
let output = match command.output() {
209219
Ok(output) => output,
210220
Err(e) => {
211221
// * hg is not installed on GitHub macOS or certain constrained
212222
// environments like Docker. Consider installing it if Cargo
213223
// gains more hg support, but otherwise it isn't critical.
214224
// * lldb is not pre-installed on Ubuntu and Windows, so skip.
215-
if is_ci() && !["hg", "lldb"].contains(&command) {
216-
panic!(
217-
"expected command `{}` to be somewhere in PATH: {}",
218-
command, e
219-
);
225+
if is_ci() && !matches!(command_name.as_str(), "hg" | "lldb") {
226+
panic!("expected command `{command_name}` to be somewhere in PATH: {e}",);
220227
}
221228
return false;
222229
}
223230
};
224231
if !output.status.success() {
225232
panic!(
226-
"expected command `{}` to be runnable, got error {}:\n\
233+
"expected command `{command_name}` to be runnable, got error {}:\n\
227234
stderr:{}\n\
228235
stdout:{}\n",
229-
command,
230236
output.status,
231237
String::from_utf8_lossy(&output.stderr),
232238
String::from_utf8_lossy(&output.stdout)
@@ -235,6 +241,34 @@ fn has_command(command: &str) -> bool {
235241
true
236242
}
237243

244+
fn has_command(command: &str) -> bool {
245+
check_command(Path::new(command), &["--version"])
246+
}
247+
248+
fn has_rustup_stable() -> bool {
249+
if option_env!("CARGO_TEST_DISABLE_NIGHTLY").is_some() {
250+
// This cannot run on rust-lang/rust CI due to the lack of rustup.
251+
return false;
252+
}
253+
if cfg!(windows) && !is_ci() && option_env!("RUSTUP_WINDOWS_PATH_ADD_BIN").is_none() {
254+
// There is an issue with rustup that doesn't allow recursive cargo
255+
// invocations. Disable this on developer machines if the environment
256+
// variable is not enabled. This can be removed once
257+
// https://github.com/rust-lang/rustup/issues/3036 is resolved.
258+
return false;
259+
}
260+
// Cargo mucks with PATH on Windows, adding sysroot host libdir, which is
261+
// "bin", which circumvents the rustup wrapper. Use the path directly from
262+
// CARGO_HOME.
263+
let home = match option_env!("CARGO_HOME") {
264+
Some(home) => home,
265+
None if is_ci() => panic!("expected to run under rustup"),
266+
None => return false,
267+
};
268+
let cargo = Path::new(home).join("bin/cargo");
269+
check_command(&cargo, &["+stable", "--version"])
270+
}
271+
238272
/// Whether or not this running in a Continuous Integration environment.
239273
fn is_ci() -> bool {
240274
// Consider using `tracked_env` instead of option_env! when it is stabilized.

crates/cargo-test-support/src/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,13 @@ impl Execs {
764764
self
765765
}
766766

767+
pub fn args<T: AsRef<OsStr>>(&mut self, args: &[T]) -> &mut Self {
768+
if let Some(ref mut p) = self.process_builder {
769+
p.args(args);
770+
}
771+
self
772+
}
773+
767774
pub fn cwd<T: AsRef<OsStr>>(&mut self, path: T) -> &mut Self {
768775
if let Some(ref mut p) = self.process_builder {
769776
if let Some(cwd) = p.get_cwd() {

src/cargo/core/global_cache_tracker.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ impl GlobalCacheTracker {
499499
let mut stmt = self.conn.prepare_cached(
500500
"SELECT git_db.name, git_checkout.name, git_checkout.size, git_checkout.timestamp
501501
FROM git_db, git_checkout
502-
WHERE git_checkout.registry_id = git_db.id",
502+
WHERE git_checkout.git_id = git_db.id",
503503
)?;
504504
let rows = stmt
505505
.query_map([], |row| {

tests/testsuite/global_cache_tracker.rs

+158-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ use cargo::GlobalContext;
1414
use cargo_test_support::paths::{self, CargoPathExt};
1515
use cargo_test_support::registry::{Package, RegistryBuilder};
1616
use cargo_test_support::{
17-
basic_manifest, cargo_process, execs, git, project, retry, sleep_ms, thread_wait_timeout,
18-
Project,
17+
basic_manifest, cargo_process, execs, git, process, project, retry, sleep_ms,
18+
thread_wait_timeout, Execs, Project,
1919
};
2020
use itertools::Itertools;
2121
use std::fmt::Write;
22+
use std::path::Path;
2223
use std::path::PathBuf;
2324
use std::process::Stdio;
2425
use std::time::{Duration, SystemTime};
@@ -153,6 +154,14 @@ fn populate_cache(
153154
(cache_dir, src_dir)
154155
}
155156

157+
fn rustup_cargo() -> Execs {
158+
// Get the path to the rustup cargo wrapper. This is necessary because
159+
// cargo adds the "deps" directory into PATH on Windows, which points to
160+
// the wrong cargo.
161+
let rustup_cargo = Path::new(&std::env::var_os("CARGO_HOME").unwrap()).join("bin/cargo");
162+
execs().with_process_builder(process(rustup_cargo))
163+
}
164+
156165
#[cargo_test]
157166
fn auto_gc_gated() {
158167
// Requires -Zgc to both track last-use data and to run auto-gc.
@@ -1863,3 +1872,150 @@ fn clean_gc_quiet_is_quiet() {
18631872
)
18641873
.run();
18651874
}
1875+
1876+
#[cargo_test(requires_rustup_stable)]
1877+
fn compatible_with_older_cargo() {
1878+
// Ensures that db stays backwards compatible across versions.
1879+
1880+
// T-4 months: Current version, build the database.
1881+
Package::new("old", "1.0.0").publish();
1882+
Package::new("middle", "1.0.0").publish();
1883+
Package::new("new", "1.0.0").publish();
1884+
let p = project()
1885+
.file(
1886+
"Cargo.toml",
1887+
r#"
1888+
[package]
1889+
name = "foo"
1890+
version = "0.1.0"
1891+
1892+
[dependencies]
1893+
old = "1.0"
1894+
middle = "1.0"
1895+
new = "1.0"
1896+
"#,
1897+
)
1898+
.file("src/lib.rs", "")
1899+
.build();
1900+
// Populate the last-use data.
1901+
p.cargo("check -Zgc")
1902+
.masquerade_as_nightly_cargo(&["gc"])
1903+
.env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4))
1904+
.run();
1905+
assert_eq!(
1906+
get_registry_names("src"),
1907+
["middle-1.0.0", "new-1.0.0", "old-1.0.0"]
1908+
);
1909+
assert_eq!(
1910+
get_registry_names("cache"),
1911+
["middle-1.0.0.crate", "new-1.0.0.crate", "old-1.0.0.crate"]
1912+
);
1913+
1914+
// T-2 months: Stable version, make sure it reads and deletes old src.
1915+
p.change_file(
1916+
"Cargo.toml",
1917+
r#"
1918+
[package]
1919+
name = "foo"
1920+
version = "0.1.0"
1921+
1922+
[dependencies]
1923+
new = "1.0"
1924+
middle = "1.0"
1925+
"#,
1926+
);
1927+
rustup_cargo()
1928+
.args(&["+stable", "check", "-Zgc"])
1929+
.cwd(p.root())
1930+
.masquerade_as_nightly_cargo(&["gc"])
1931+
.env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(2))
1932+
.run();
1933+
assert_eq!(get_registry_names("src"), ["middle-1.0.0", "new-1.0.0"]);
1934+
assert_eq!(
1935+
get_registry_names("cache"),
1936+
["middle-1.0.0.crate", "new-1.0.0.crate", "old-1.0.0.crate"]
1937+
);
1938+
1939+
// T-0 months: Current version, make sure it can read data from stable,
1940+
// deletes old crate and middle src.
1941+
p.change_file(
1942+
"Cargo.toml",
1943+
r#"
1944+
[package]
1945+
name = "foo"
1946+
version = "0.1.0"
1947+
1948+
[dependencies]
1949+
new = "1.0"
1950+
"#,
1951+
);
1952+
p.cargo("check -Zgc")
1953+
.masquerade_as_nightly_cargo(&["gc"])
1954+
.run();
1955+
assert_eq!(get_registry_names("src"), ["new-1.0.0"]);
1956+
assert_eq!(
1957+
get_registry_names("cache"),
1958+
["middle-1.0.0.crate", "new-1.0.0.crate"]
1959+
);
1960+
}
1961+
1962+
#[cargo_test(requires_rustup_stable)]
1963+
fn forward_compatible() {
1964+
// Checks that db created in an older version can be read in a newer version.
1965+
Package::new("bar", "1.0.0").publish();
1966+
let git_project = git::new("from_git", |p| {
1967+
p.file("Cargo.toml", &basic_manifest("from_git", "1.0.0"))
1968+
.file("src/lib.rs", "")
1969+
});
1970+
1971+
let p = project()
1972+
.file(
1973+
"Cargo.toml",
1974+
&format!(
1975+
r#"
1976+
[package]
1977+
name = "foo"
1978+
1979+
[dependencies]
1980+
bar = "1.0.0"
1981+
from_git = {{ git = '{}' }}
1982+
"#,
1983+
git_project.url()
1984+
),
1985+
)
1986+
.file("src/lib.rs", "")
1987+
.build();
1988+
1989+
rustup_cargo()
1990+
.args(&["+stable", "check", "-Zgc"])
1991+
.cwd(p.root())
1992+
.masquerade_as_nightly_cargo(&["gc"])
1993+
.run();
1994+
1995+
let config = GlobalContextBuilder::new().unstable_flag("gc").build();
1996+
let lock = config
1997+
.acquire_package_cache_lock(CacheLockMode::MutateExclusive)
1998+
.unwrap();
1999+
let tracker = GlobalCacheTracker::new(&config).unwrap();
2000+
// Don't want to check the actual index name here, since although the
2001+
// names are semi-stable, they might change over long periods of time.
2002+
let indexes = tracker.registry_index_all().unwrap();
2003+
assert_eq!(indexes.len(), 1);
2004+
let crates = tracker.registry_crate_all().unwrap();
2005+
let names: Vec<_> = crates
2006+
.iter()
2007+
.map(|(krate, _timestamp)| krate.crate_filename)
2008+
.collect();
2009+
assert_eq!(names, &["bar-1.0.0.crate"]);
2010+
let srcs = tracker.registry_src_all().unwrap();
2011+
let names: Vec<_> = srcs
2012+
.iter()
2013+
.map(|(src, _timestamp)| src.package_dir)
2014+
.collect();
2015+
assert_eq!(names, &["bar-1.0.0"]);
2016+
let dbs: Vec<_> = tracker.git_db_all().unwrap();
2017+
assert_eq!(dbs.len(), 1);
2018+
let cos: Vec<_> = tracker.git_checkout_all().unwrap();
2019+
assert_eq!(cos.len(), 1);
2020+
drop(lock);
2021+
}

0 commit comments

Comments
 (0)