Skip to content

Commit ab7555d

Browse files
committed
test(cli): cover stage reuse, reclamation, and stray update hashes
Interrupted installs are asserted to reuse one deterministic staging path; a stale stage left by a dead process is reclaimed without leaking its contents into the published toolchain; and a stray update hash is ignored during a staged install and refreshed after publication.
1 parent b6d9d4e commit ab7555d

2 files changed

Lines changed: 75 additions & 25 deletions

File tree

‎tests/suite/cli_crash.rs‎

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use std::{fs, path::PathBuf, process::Command, time::Duration};
22

3+
use rustup::dist::manifestation::CHECKPOINT_UPDATE_BEFORE_METADATA;
4+
use rustup::install::{
5+
CHECKPOINT_INSTALL_AFTER_PUBLISH, CHECKPOINT_INSTALL_BEFORE_PUBLISH, STAGING_DIR_PREFIX,
6+
};
37
use rustup::test::{CliTestContext, Scenario, this_host_tuple};
48
use wait_timeout::ChildExt;
59

@@ -30,6 +34,13 @@ fn nightly_update_hash_path(cx: &CliTestContext) -> PathBuf {
3034
.join(format!("nightly-{}", this_host_tuple()))
3135
}
3236

37+
fn nightly_staging_root(cx: &CliTestContext) -> PathBuf {
38+
cx.config
39+
.rustupdir
40+
.join("toolchains")
41+
.join(format!("{STAGING_DIR_PREFIX}nightly-{}", this_host_tuple()))
42+
}
43+
3344
fn staging_paths(cx: &CliTestContext) -> Vec<PathBuf> {
3445
fs::read_dir(cx.config.rustupdir.join("toolchains"))
3546
.expect("failed to read toolchains directory")
@@ -66,15 +77,13 @@ rustc-[HOST_TUPLE]
6677

6778
async fn assert_unpublished_install_can_be_retried(checkpoint: &str) {
6879
let cx = CliTestContext::new(Scenario::SimpleV2).await;
69-
let command = cx.config.cmd("rustup", ["toolchain", "install", "nightly"]);
70-
71-
let status = cx.kill_at_checkpoint(command, checkpoint);
80+
let status = cx.kill_at(checkpoint, ["rustup", "toolchain", "install", "nightly"]);
7281
assert!(!status.success());
7382
assert!(
7483
!nightly_path(&cx).exists(),
7584
"an interrupted staging operation published the toolchain"
7685
);
77-
assert_eq!(staging_paths(&cx).len(), 1);
86+
assert_eq!(staging_paths(&cx), vec![nightly_staging_root(&cx)]);
7887
cx.config
7988
.expect(["rustup", "toolchain", "list"])
8089
.await
@@ -89,12 +98,50 @@ async fn assert_unpublished_install_can_be_retried(checkpoint: &str) {
8998

9099
#[tokio::test]
91100
async fn interrupted_install_can_be_retried() {
92-
assert_unpublished_install_can_be_retried(BEFORE_METADATA).await;
101+
assert_unpublished_install_can_be_retried(CHECKPOINT_UPDATE_BEFORE_METADATA).await;
93102
}
94103

95104
#[tokio::test]
96105
async fn interrupted_install_before_publication_can_be_retried() {
97-
assert_unpublished_install_can_be_retried(BEFORE_PUBLISH).await;
106+
assert_unpublished_install_can_be_retried(CHECKPOINT_INSTALL_BEFORE_PUBLISH).await;
107+
}
108+
109+
#[tokio::test]
110+
async fn interrupted_installs_do_not_accumulate_stages() {
111+
let cx = CliTestContext::new(Scenario::SimpleV2).await;
112+
113+
for _ in 0..2 {
114+
let status = cx.kill_at(
115+
CHECKPOINT_INSTALL_BEFORE_PUBLISH,
116+
["rustup", "toolchain", "install", "nightly"],
117+
);
118+
assert!(!status.success());
119+
assert_eq!(staging_paths(&cx), vec![nightly_staging_root(&cx)]);
120+
}
121+
122+
assert_completes_successfully(cx.config.cmd("rustup", ["toolchain", "install", "nightly"]));
123+
assert!(staging_paths(&cx).is_empty());
124+
assert_nightly_is_complete(&cx).await;
125+
}
126+
127+
#[tokio::test]
128+
async fn stale_stage_is_reclaimed_by_the_next_install() {
129+
let cx = CliTestContext::new(Scenario::SimpleV2).await;
130+
let junk = nightly_staging_root(&cx)
131+
.join("toolchain")
132+
.join("bin")
133+
.join("stale-junk");
134+
fs::create_dir_all(junk.parent().unwrap()).unwrap();
135+
fs::write(&junk, "junk").unwrap();
136+
fs::write(nightly_staging_root(&cx).join("update-hash"), "stale-hash").unwrap();
137+
138+
assert_completes_successfully(cx.config.cmd("rustup", ["toolchain", "install", "nightly"]));
139+
assert!(staging_paths(&cx).is_empty());
140+
assert!(
141+
!nightly_path(&cx).join("bin").join("stale-junk").exists(),
142+
"contents of a stale stage leaked into the published toolchain"
143+
);
144+
assert_nightly_is_complete(&cx).await;
98145
}
99146

100147
#[tokio::test]
@@ -104,8 +151,10 @@ async fn unpublished_install_does_not_change_update_hash() {
104151
fs::create_dir_all(update_hash.parent().unwrap()).unwrap();
105152
fs::write(&update_hash, "stale-hash").unwrap();
106153

107-
let command = cx.config.cmd("rustup", ["toolchain", "install", "nightly"]);
108-
let status = cx.kill_at_checkpoint(command, BEFORE_PUBLISH);
154+
let status = cx.kill_at(
155+
CHECKPOINT_INSTALL_BEFORE_PUBLISH,
156+
["rustup", "toolchain", "install", "nightly"],
157+
);
109158

110159
assert!(!status.success());
111160
assert_eq!(fs::read_to_string(update_hash).unwrap(), "stale-hash");
@@ -115,9 +164,10 @@ async fn unpublished_install_does_not_change_update_hash() {
115164
#[tokio::test]
116165
async fn interrupted_install_after_publication_is_complete() {
117166
let cx = CliTestContext::new(Scenario::SimpleV2).await;
118-
let command = cx.config.cmd("rustup", ["toolchain", "install", "nightly"]);
119-
120-
let status = cx.kill_at_checkpoint(command, AFTER_PUBLISH);
167+
let status = cx.kill_at(
168+
CHECKPOINT_INSTALL_AFTER_PUBLISH,
169+
["rustup", "toolchain", "install", "nightly"],
170+
);
121171
assert!(!status.success());
122172
assert!(nightly_path(&cx).is_dir());
123173
assert!(staging_paths(&cx).is_empty());
@@ -163,16 +213,13 @@ async fn interrupted_update_can_be_retried() {
163213
.is_ok();
164214

165215
cx.config.set_current_dist_date("2015-01-02");
166-
let command = cx.config.cmd("rustup", ["update", "nightly"]);
167-
let status = cx.kill_at_checkpoint(command, BEFORE_METADATA);
216+
let status = cx.kill_at(
217+
CHECKPOINT_UPDATE_BEFORE_METADATA,
218+
["rustup", "update", "nightly"],
219+
);
168220
assert!(!status.success());
169221

170222
assert_completes_successfully(cx.config.cmd("rustup", ["update", "nightly"]));
171223

172224
assert_nightly_is_complete(&cx).await;
173225
}
174-
175-
const BEFORE_METADATA: &str = "manifestation-update-before-metadata";
176-
const BEFORE_PUBLISH: &str = "install-before-publish";
177-
const AFTER_PUBLISH: &str = "install-after-publish";
178-
const STAGING_DIR_PREFIX: &str = "+rustup-staging-";

‎tests/suite/cli_v2.rs‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1949,9 +1949,11 @@ async fn remove_target_missing_update_hash() {
19491949
.is_ok();
19501950
}
19511951

1952-
// Issue #1777
1952+
// Issue #1777: a stray hash file must not prevent installation. Staged
1953+
// installs never consult the alias-scoped hash, so it is silently refreshed
1954+
// after publication rather than removed up front.
19531955
#[tokio::test]
1954-
async fn warn_about_and_remove_stray_hash() {
1956+
async fn stray_hash_is_ignored_and_refreshed() {
19551957
let mut cx = CliTestContext::new(Scenario::None).await;
19561958
let mut hash_path = cx.config.rustupdir.join("update-hashes");
19571959
fs::create_dir_all(&hash_path).expect("Unable to make the update-hashes directory");
@@ -1965,12 +1967,13 @@ async fn warn_about_and_remove_stray_hash() {
19651967
cx.config
19661968
.expect(["rustup", "toolchain", "install", "nightly"])
19671969
.await
1968-
.with_stderr(snapbox::str![[r#"
1969-
...
1970-
warn: removing stray hash file in order to continue: [..]/update-hashes/nightly-[HOST_TUPLE]
1971-
...
1972-
"#]])
19731970
.is_ok();
1971+
1972+
let refreshed = fs::read_to_string(&hash_path).expect("Unable to read update-hash file");
1973+
assert_ne!(
1974+
refreshed, "LEGITHASH",
1975+
"stray hash file was not refreshed after installation"
1976+
);
19741977
}
19751978

19761979
fn make_component_unavailable(config: &Config, name: &str, target: String) {

0 commit comments

Comments
 (0)