Skip to content

Commit 6cfaa62

Browse files
committed
refactor(test): add new testing APIs and simple showcases
1 parent 7ba0abf commit 6cfaa62

File tree

7 files changed

+135
-48
lines changed

7 files changed

+135
-48
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ otel = [
3737
]
3838

3939
# Exports code dependent on private interfaces for the integration test suite
40-
test = ["dep:walkdir"]
40+
test = ["dep:snapbox", "dep:walkdir"]
4141

4242
# Sorted by alphabetic order
4343
[dependencies]
@@ -77,6 +77,7 @@ semver = "1.0"
7777
serde = { version = "1.0", features = ["derive"] }
7878
sha2 = "0.10"
7979
sharded-slab = "0.1.1"
80+
snapbox = { version = "0.6.21", optional = true }
8081
strsim = "0.11"
8182
tar = "0.4.26"
8283
tempfile = "3.8"

src/test/clitools.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::{
1818
};
1919

2020
use enum_map::{Enum, EnumMap, enum_map};
21+
use snapbox::{IntoData, RedactedValue, Redactions, assert_data_eq};
2122
use tempfile::TempDir;
2223
use url::Url;
2324

@@ -61,6 +62,60 @@ pub struct Config {
6162
pub test_root_dir: PathBuf,
6263
}
6364

65+
#[derive(Clone)]
66+
pub struct Assert {
67+
output: SanitizedOutput,
68+
redactions: Redactions,
69+
}
70+
71+
impl Assert {
72+
/// Creates a new [`Assert`] object with the given command [`SanitizedOutput`].
73+
pub fn new(output: SanitizedOutput) -> Self {
74+
let mut redactions = Redactions::new();
75+
redactions
76+
.extend([("[HOST_TRIPLE]", this_host_triple())])
77+
.expect("invalid redactions detected");
78+
Self { output, redactions }
79+
}
80+
81+
/// Extend the redaction rules used in the currrent assertion with new values.
82+
pub fn extend_redactions(
83+
&mut self,
84+
vars: impl IntoIterator<Item = (&'static str, impl Into<RedactedValue>)>,
85+
) -> &mut Self {
86+
self.redactions
87+
.extend(vars)
88+
.expect("invalid redactions detected");
89+
self
90+
}
91+
92+
/// Asserts that the command exited with an ok status.
93+
pub fn is_ok(&self) -> &Self {
94+
assert!(self.output.ok);
95+
self
96+
}
97+
98+
/// Asserts that the command exited with an error.
99+
pub fn is_err(&self) -> &Self {
100+
assert!(!self.output.ok);
101+
self
102+
}
103+
104+
/// Asserts that the command exited with the given `expected` stdout pattern.
105+
pub fn with_stdout(&self, expected: impl IntoData) -> &Self {
106+
let stdout = self.redactions.redact(&self.output.stdout);
107+
assert_data_eq!(&stdout, expected);
108+
self
109+
}
110+
111+
/// Asserts that the command exited with the given `expected` stderr pattern.
112+
pub fn with_stderr(&self, expected: impl IntoData) -> &Self {
113+
let stderr = self.redactions.redact(&self.output.stderr);
114+
assert_data_eq!(&stderr, expected);
115+
self
116+
}
117+
}
118+
64119
impl Config {
65120
pub fn current_dir(&self) -> PathBuf {
66121
self.workdir.borrow().clone()
@@ -136,6 +191,26 @@ impl Config {
136191
}
137192
}
138193

194+
/// Returns an [`Assert`] object to check the output of running the command
195+
/// specified by `args` under the default environment.
196+
#[must_use]
197+
pub async fn expect(&self, args: impl AsRef<[&str]>) -> Assert {
198+
self.expect_with_env(args, &[]).await
199+
}
200+
201+
/// Returns an [`Assert`] object to check the output of running the command
202+
/// specified by `args` and under the environment specified by `env`.
203+
#[must_use]
204+
pub async fn expect_with_env(
205+
&self,
206+
args: impl AsRef<[&str]>,
207+
env: impl AsRef<[(&str, &str)]>,
208+
) -> Assert {
209+
let args = args.as_ref();
210+
let output = self.run(args[0], &args[1..], env.as_ref()).await;
211+
Assert::new(output)
212+
}
213+
139214
/// Expect an ok status
140215
pub async fn expect_ok(&mut self, args: &[&str]) {
141216
self.expect_ok_env(args, &[]).await
@@ -1035,7 +1110,7 @@ pub struct Output {
10351110
pub stderr: Vec<u8>,
10361111
}
10371112

1038-
#[derive(Debug)]
1113+
#[derive(Debug, Clone)]
10391114
pub struct SanitizedOutput {
10401115
pub ok: bool,
10411116
pub stdout: String,

tests/suite/cli_exact.rs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use rustup::test::{
66
CROSS_ARCH1, CROSS_ARCH2, CliTestContext, MULTI_ARCH1, Scenario, this_host_triple,
77
};
88
use rustup::utils::raw;
9+
use snapbox::str;
910

1011
#[tokio::test]
1112
async fn update_once() {
@@ -308,23 +309,21 @@ info: default toolchain set to 'nightly-{0}'
308309

309310
#[tokio::test]
310311
async fn override_again() {
311-
let mut cx = CliTestContext::new(Scenario::SimpleV2).await;
312-
let cwd = cx.config.current_dir();
312+
let cx = &CliTestContext::new(Scenario::SimpleV2).await;
313313
cx.config
314-
.expect_ok(&["rustup", "override", "add", "nightly"])
315-
.await;
314+
.expect(["rustup", "override", "add", "nightly"])
315+
.await
316+
.is_ok();
316317
cx.config
317-
.expect_ok_ex(
318-
&["rustup", "override", "add", "nightly"],
319-
"",
320-
&format!(
321-
r"info: override toolchain for '{}' set to 'nightly-{1}'
322-
",
323-
cwd.display(),
324-
&this_host_triple()
325-
),
326-
)
327-
.await;
318+
.expect(["rustup", "override", "add", "nightly"])
319+
.await
320+
.extend_redactions([("[CWD]", cx.config.current_dir().display().to_string())])
321+
.is_ok()
322+
.with_stdout("")
323+
.with_stderr(str![[r#"
324+
info: override toolchain for '[CWD]' set to 'nightly-[HOST_TRIPLE]'
325+
326+
"#]]);
328327
}
329328

330329
#[tokio::test]

tests/suite/cli_misc.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use rustup::test::{
1111
};
1212
use rustup::utils;
1313
use rustup::utils::raw::symlink_dir;
14+
use snapbox::str;
1415

1516
#[tokio::test]
1617
async fn smoke_test() {
@@ -113,16 +114,15 @@ async fn custom_invalid_names_with_archive_dates() {
113114
async fn update_all_no_update_whitespace() {
114115
let cx = CliTestContext::new(Scenario::SimpleV2).await;
115116
cx.config
116-
.expect_stdout_ok(
117-
&["rustup", "update", "nightly"],
118-
for_host!(
119-
r"
120-
nightly-{} installed - 1.3.0 (hash-nightly-2)
117+
.expect(["rustup", "update", "nightly"])
118+
.await
119+
.is_ok()
120+
.with_stdout(str![[r#"
121121
122-
"
123-
),
124-
)
125-
.await;
122+
nightly-[HOST_TRIPLE] installed - 1.3.0 (hash-nightly-2)
123+
124+
125+
"#]]);
126126
}
127127

128128
// Issue #145

tests/suite/cli_self_upd.rs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use rustup::test::{
2020
use rustup::test::{RegistryGuard, RegistryValueId, USER_PATH};
2121
use rustup::utils::{self, raw};
2222
use rustup::{DUP_TOOLS, TOOLS, for_host};
23+
use snapbox::str;
2324
#[cfg(windows)]
2425
use windows_registry::Value;
2526

@@ -387,26 +388,32 @@ info: downloading self-update
387388

388389
#[tokio::test]
389390
async fn update_precise() {
390-
let version = env!("CARGO_PKG_VERSION");
391-
let expected_output = format!(
392-
"info: checking for self-update
393-
info: `RUSTUP_VERSION` has been set to `{TEST_VERSION}`
394-
info: downloading self-update
395-
"
396-
);
397-
398-
let mut cx = SelfUpdateTestContext::new(TEST_VERSION).await;
391+
let cx = SelfUpdateTestContext::new(TEST_VERSION).await;
399392
cx.config
400-
.expect_ok(&["rustup-init", "-y", "--no-modify-path"])
401-
.await;
393+
.expect(["rustup-init", "-y", "--no-modify-path"])
394+
.await
395+
.is_ok();
402396
cx.config
403-
.expect_ok_ex_env(
404-
&["rustup", "self", "update"],
405-
&[("RUSTUP_VERSION", TEST_VERSION)],
406-
&format!(" rustup updated - {version} (from {version})\n\n",),
407-
&expected_output,
397+
.expect_with_env(
398+
["rustup", "self", "update"],
399+
[("RUSTUP_VERSION", TEST_VERSION)],
408400
)
409-
.await;
401+
.await
402+
.extend_redactions([
403+
("[TEST_VERSION]", TEST_VERSION),
404+
("[VERSION]", env!("CARGO_PKG_VERSION")),
405+
])
406+
.with_stdout(str![[r#"
407+
rustup updated - [VERSION] (from [VERSION])
408+
409+
410+
"#]])
411+
.with_stderr(str![[r#"
412+
info: checking for self-update
413+
info: `RUSTUP_VERSION` has been set to `[TEST_VERSION]`
414+
info: downloading self-update
415+
416+
"#]]);
410417
}
411418

412419
#[cfg(windows)]

tests/suite/cli_v1.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,20 @@ use std::fs;
55

66
use rustup::for_host;
77
use rustup::test::{CliTestContext, Scenario};
8+
use snapbox::str;
89

910
#[tokio::test]
1011
async fn rustc_no_default_toolchain() {
1112
let cx = CliTestContext::new(Scenario::SimpleV1).await;
1213
cx.config
13-
.expect_err(
14-
&["rustc"],
15-
"rustup could not choose a version of rustc to run",
16-
)
17-
.await;
14+
.expect(["rustc"])
15+
.await
16+
.is_err()
17+
.with_stderr(str![[r#"
18+
error: rustup could not choose a version of rustc to run, because one wasn't specified explicitly, and no default is configured.
19+
help: run 'rustup default stable' to download the latest stable release of Rust and set it as your default toolchain.
20+
21+
"#]]);
1822
}
1923

2024
#[tokio::test]

0 commit comments

Comments
 (0)