Skip to content

Commit

Permalink
fix(test): Make redactions consistent with snapbox
Browse files Browse the repository at this point in the history
I'm unsure how we should be replacing these use cases, so I'm exploring
keeping them but making them use snapbox under the hood.
Part of the intent of snapbox is that it provides you the building
blocks to make what you need.
  • Loading branch information
epage committed Nov 7, 2024
1 parent be87c96 commit a03adcf
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 277 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ sha2 = "0.10.8"
shell-escape = "0.1.5"
similar = "2.6.0"
supports-hyperlinks = "3.0.0"
snapbox = { version = "0.6.18", features = ["diff", "dir", "term-svg", "regex", "json"] }
snapbox = { version = "0.6.20", features = ["diff", "dir", "term-svg", "regex", "json"] }
tar = { version = "0.4.42", default-features = false }
tempfile = "3.10.1"
thiserror = "1.0.63"
Expand Down
240 changes: 27 additions & 213 deletions crates/cargo-test-support/src/compare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ use std::fmt;
use std::path::Path;
use std::path::PathBuf;
use std::str;
use url::Url;

/// This makes it easier to write regex replacements that are guaranteed to only
/// get compiled once
Expand Down Expand Up @@ -333,111 +332,37 @@ static E2E_LITERAL_REDACTIONS: &[(&str, &str)] = &[
];

/// Normalizes the output so that it can be compared against the expected value.
fn normalize_actual(actual: &str, cwd: Option<&Path>) -> String {
// It's easier to read tabs in outputs if they don't show up as literal
// hidden characters
let actual = actual.replace('\t', "<tab>");
if cfg!(windows) {
// Let's not deal with \r\n vs \n on windows...
let actual = actual.replace('\r', "");
normalize_windows(&actual, cwd)
} else {
actual
}
fn normalize_actual(content: &str, redactions: &snapbox::Redactions) -> String {
use snapbox::filter::Filter as _;
let content = snapbox::filter::FilterPaths.filter(content.into_data());
let content = snapbox::filter::FilterNewlines.filter(content);
let content = content.render().expect("came in as a String");
let content = redactions.redact(&content);
content
}

/// Normalizes the expected string so that it can be compared against the actual output.
fn normalize_expected(expected: &str, cwd: Option<&Path>) -> String {
let expected = replace_dirty_msvc(expected);
let expected = substitute_macros(&expected);

if cfg!(windows) {
normalize_windows(&expected, cwd)
} else {
let expected = match cwd {
None => expected,
Some(cwd) => expected.replace("[CWD]", &cwd.display().to_string()),
};
let expected = expected.replace("[ROOT]", &paths::root().display().to_string());
expected
}
}

fn replace_dirty_msvc_impl(s: &str, is_msvc: bool) -> String {
if is_msvc {
s.replace("[DIRTY-MSVC]", "[DIRTY]")
} else {
use itertools::Itertools;

let mut new = s
.lines()
.filter(|it| !it.starts_with("[DIRTY-MSVC]"))
.join("\n");

if s.ends_with("\n") {
new.push_str("\n");
}

new
}
}

fn replace_dirty_msvc(s: &str) -> String {
replace_dirty_msvc_impl(s, cfg!(target_env = "msvc"))
}

/// Normalizes text for both actual and expected strings on Windows.
fn normalize_windows(text: &str, cwd: Option<&Path>) -> String {
// Let's not deal with / vs \ (windows...)
let text = text.replace('\\', "/");

// Weirdness for paths on Windows extends beyond `/` vs `\` apparently.
// Namely paths like `c:\` and `C:\` are equivalent and that can cause
// issues. The return value of `env::current_dir()` may return a
// lowercase drive name, but we round-trip a lot of values through `Url`
// which will auto-uppercase the drive name. To just ignore this
// distinction we try to canonicalize as much as possible, taking all
// forms of a path and canonicalizing them to one.
let replace_path = |s: &str, path: &Path, with: &str| {
let path_through_url = Url::from_file_path(path).unwrap().to_file_path().unwrap();
let path1 = path.display().to_string().replace('\\', "/");
let path2 = path_through_url.display().to_string().replace('\\', "/");
s.replace(&path1, with)
.replace(&path2, with)
.replace(with, &path1)
};

let text = match cwd {
None => text,
Some(p) => replace_path(&text, p, "[CWD]"),
};

// Similar to cwd above, perform similar treatment to the root path
// which in theory all of our paths should otherwise get rooted at.
let root = paths::root();
let text = replace_path(&text, &root, "[ROOT]");

text
}

fn substitute_macros(input: &str) -> String {
let mut result = input.to_owned();
for &(pat, subst) in MIN_LITERAL_REDACTIONS {
result = result.replace(pat, subst)
}
for &(pat, subst) in E2E_LITERAL_REDACTIONS {
result = result.replace(pat, subst)
}
result
fn normalize_expected(content: &str, redactions: &snapbox::Redactions) -> String {
use snapbox::filter::Filter as _;
let content = snapbox::filter::FilterPaths.filter(content.into_data());
let content = snapbox::filter::FilterNewlines.filter(content);
// Remove any conditionally absent redactions like `[EXE]`
let content = content.render().expect("came in as a String");
let content = redactions.clear_unused(&content);
content.into_owned()
}

/// Checks that the given string contains the given contiguous lines
/// somewhere.
///
/// See [Patterns](index.html#patterns) for more information on pattern matching.
pub(crate) fn match_contains(expected: &str, actual: &str, cwd: Option<&Path>) -> Result<()> {
let expected = normalize_expected(expected, cwd);
let actual = normalize_actual(actual, cwd);
pub(crate) fn match_contains(
expected: &str,
actual: &str,
redactions: &snapbox::Redactions,
) -> Result<()> {
let expected = normalize_expected(expected, redactions);
let actual = normalize_actual(actual, redactions);
let e: Vec<_> = expected.lines().map(|line| WildStr::new(line)).collect();
let a: Vec<_> = actual.lines().map(|line| WildStr::new(line)).collect();
if e.len() == 0 {
Expand Down Expand Up @@ -465,9 +390,9 @@ pub(crate) fn match_contains(expected: &str, actual: &str, cwd: Option<&Path>) -
pub(crate) fn match_does_not_contain(
expected: &str,
actual: &str,
cwd: Option<&Path>,
redactions: &snapbox::Redactions,
) -> Result<()> {
if match_contains(expected, actual, cwd).is_ok() {
if match_contains(expected, actual, redactions).is_ok() {
bail!(
"expected not to find:\n\
{}\n\n\
Expand All @@ -492,10 +417,10 @@ pub(crate) fn match_with_without(
actual: &str,
with: &[String],
without: &[String],
cwd: Option<&Path>,
redactions: &snapbox::Redactions,
) -> Result<()> {
let actual = normalize_actual(actual, cwd);
let norm = |s: &String| format!("[..]{}[..]", normalize_expected(s, cwd));
let actual = normalize_actual(actual, redactions);
let norm = |s: &String| format!("[..]{}[..]", normalize_expected(s, redactions));
let with: Vec<_> = with.iter().map(norm).collect();
let without: Vec<_> = without.iter().map(norm).collect();
let with_wild: Vec<_> = with.iter().map(|w| WildStr::new(w)).collect();
Expand Down Expand Up @@ -748,117 +673,6 @@ mod test {
}
}

#[test]
fn dirty_msvc() {
let case = |expected: &str, wild: &str, msvc: bool| {
assert_eq!(expected, &replace_dirty_msvc_impl(wild, msvc));
};

// no replacements
case("aa", "aa", false);
case("aa", "aa", true);

// with replacements
case(
"\
[DIRTY] a",
"\
[DIRTY-MSVC] a",
true,
);
case(
"",
"\
[DIRTY-MSVC] a",
false,
);
case(
"\
[DIRTY] a
[COMPILING] a",
"\
[DIRTY-MSVC] a
[COMPILING] a",
true,
);
case(
"\
[COMPILING] a",
"\
[DIRTY-MSVC] a
[COMPILING] a",
false,
);

// test trailing newline behavior
case(
"\
A
B
", "\
A
B
", true,
);

case(
"\
A
B
", "\
A
B
", false,
);

case(
"\
A
B", "\
A
B", true,
);

case(
"\
A
B", "\
A
B", false,
);

case(
"\
[DIRTY] a
",
"\
[DIRTY-MSVC] a
",
true,
);
case(
"\n",
"\
[DIRTY-MSVC] a
",
false,
);

case(
"\
[DIRTY] a",
"\
[DIRTY-MSVC] a",
true,
);
case(
"",
"\
[DIRTY-MSVC] a",
false,
);
}

#[test]
fn redact_elapsed_time() {
let mut subs = snapbox::Redactions::new();
Expand Down
15 changes: 5 additions & 10 deletions crates/cargo-test-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,10 +820,6 @@ impl Execs {
self
}

fn get_cwd(&self) -> Option<&Path> {
self.process_builder.as_ref().and_then(|p| p.get_cwd())
}

pub fn env<T: AsRef<OsStr>>(&mut self, key: &str, val: T) -> &mut Self {
if let Some(ref mut p) = self.process_builder {
p.env(key, val);
Expand Down Expand Up @@ -1021,7 +1017,6 @@ impl Execs {
self.verify_checks_output(stdout, stderr);
let stdout = std::str::from_utf8(stdout).expect("stdout is not utf8");
let stderr = std::str::from_utf8(stderr).expect("stderr is not utf8");
let cwd = self.get_cwd();

match self.expect_exit_code {
None => {}
Expand Down Expand Up @@ -1054,19 +1049,19 @@ impl Execs {
}
}
for expect in self.expect_stdout_contains.iter() {
compare::match_contains(expect, stdout, cwd)?;
compare::match_contains(expect, stdout, self.assert.redactions())?;
}
for expect in self.expect_stderr_contains.iter() {
compare::match_contains(expect, stderr, cwd)?;
compare::match_contains(expect, stderr, self.assert.redactions())?;
}
for expect in self.expect_stdout_not_contains.iter() {
compare::match_does_not_contain(expect, stdout, cwd)?;
compare::match_does_not_contain(expect, stdout, self.assert.redactions())?;
}
for expect in self.expect_stderr_not_contains.iter() {
compare::match_does_not_contain(expect, stderr, cwd)?;
compare::match_does_not_contain(expect, stderr, self.assert.redactions())?;
}
for (with, without) in self.expect_stderr_with_without.iter() {
compare::match_with_without(stderr, with, without, cwd)?;
compare::match_with_without(stderr, with, without, self.assert.redactions())?;
}
Ok(())
}
Expand Down
Loading

0 comments on commit a03adcf

Please sign in to comment.