Skip to content

Commit cb088dc

Browse files
committed
Auto merge of #14793 - epage:test-compare, r=weihanglo
docs(test): Document Execs assertions based on port effort ### What does this PR try to resolve? Closes #14039 A lot of this was pulled from #14039, common assertions people need to write, and from seeing people not noticing features that exist. I'm leaving behind the `contains` assertions, rather than finding a way to make them work with snapbox. Thankfully, snapbox is designed to let people build their own thing (see #14790). I considered reusing snapbox's `[..]` matches but the code here is pretty minimal, the logic is similar enough, and I don't have a great abstraction for snapbox for it. If there was more of a need, I'd make something work in snapbox. As such, the `contains` assertions are no longer deprecated. While doing this last pass through, I did some polish on the code as well. ### How should we test and review this PR? ### Additional information
2 parents 91a58e2 + af3cfd5 commit cb088dc

37 files changed

+187
-149
lines changed

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

+33-35
Original file line numberDiff line numberDiff line change
@@ -331,27 +331,6 @@ static E2E_LITERAL_REDACTIONS: &[(&str, &str)] = &[
331331
("[OPENING]", " Opening"),
332332
];
333333

334-
/// Normalizes the output so that it can be compared against the expected value.
335-
fn normalize_actual(content: &str, redactions: &snapbox::Redactions) -> String {
336-
use snapbox::filter::Filter as _;
337-
let content = snapbox::filter::FilterPaths.filter(content.into_data());
338-
let content = snapbox::filter::FilterNewlines.filter(content);
339-
let content = content.render().expect("came in as a String");
340-
let content = redactions.redact(&content);
341-
content
342-
}
343-
344-
/// Normalizes the expected string so that it can be compared against the actual output.
345-
fn normalize_expected(content: &str, redactions: &snapbox::Redactions) -> String {
346-
use snapbox::filter::Filter as _;
347-
let content = snapbox::filter::FilterPaths.filter(content.into_data());
348-
let content = snapbox::filter::FilterNewlines.filter(content);
349-
// Remove any conditionally absent redactions like `[EXE]`
350-
let content = content.render().expect("came in as a String");
351-
let content = redactions.clear_unused(&content);
352-
content.into_owned()
353-
}
354-
355334
/// Checks that the given string contains the given contiguous lines
356335
/// somewhere.
357336
///
@@ -364,12 +343,12 @@ pub(crate) fn match_contains(
364343
let expected = normalize_expected(expected, redactions);
365344
let actual = normalize_actual(actual, redactions);
366345
let e: Vec<_> = expected.lines().map(|line| WildStr::new(line)).collect();
367-
let a: Vec<_> = actual.lines().map(|line| WildStr::new(line)).collect();
346+
let a: Vec<_> = actual.lines().collect();
368347
if e.len() == 0 {
369348
bail!("expected length must not be zero");
370349
}
371350
for window in a.windows(e.len()) {
372-
if window == e {
351+
if e == window {
373352
return Ok(());
374353
}
375354
}
@@ -428,7 +407,6 @@ pub(crate) fn match_with_without(
428407

429408
let matches: Vec<_> = actual
430409
.lines()
431-
.map(WildStr::new)
432410
.filter(|line| with_wild.iter().all(|with| with == line))
433411
.filter(|line| !without_wild.iter().any(|without| without == line))
434412
.collect();
@@ -457,28 +435,48 @@ pub(crate) fn match_with_without(
457435
}
458436
}
459437

438+
/// Normalizes the output so that it can be compared against the expected value.
439+
fn normalize_actual(content: &str, redactions: &snapbox::Redactions) -> String {
440+
use snapbox::filter::Filter as _;
441+
let content = snapbox::filter::FilterPaths.filter(content.into_data());
442+
let content = snapbox::filter::FilterNewlines.filter(content);
443+
let content = content.render().expect("came in as a String");
444+
let content = redactions.redact(&content);
445+
content
446+
}
447+
448+
/// Normalizes the expected string so that it can be compared against the actual output.
449+
fn normalize_expected(content: &str, redactions: &snapbox::Redactions) -> String {
450+
use snapbox::filter::Filter as _;
451+
let content = snapbox::filter::FilterPaths.filter(content.into_data());
452+
let content = snapbox::filter::FilterNewlines.filter(content);
453+
// Remove any conditionally absent redactions like `[EXE]`
454+
let content = content.render().expect("came in as a String");
455+
let content = redactions.clear_unused(&content);
456+
content.into_owned()
457+
}
458+
460459
/// A single line string that supports `[..]` wildcard matching.
461-
pub(crate) struct WildStr<'a> {
460+
struct WildStr<'a> {
462461
has_meta: bool,
463462
line: &'a str,
464463
}
465464

466465
impl<'a> WildStr<'a> {
467-
pub fn new(line: &'a str) -> WildStr<'a> {
466+
fn new(line: &'a str) -> WildStr<'a> {
468467
WildStr {
469468
has_meta: line.contains("[..]"),
470469
line,
471470
}
472471
}
473472
}
474473

475-
impl<'a> PartialEq for WildStr<'a> {
476-
fn eq(&self, other: &Self) -> bool {
477-
match (self.has_meta, other.has_meta) {
478-
(false, false) => self.line == other.line,
479-
(true, false) => meta_cmp(self.line, other.line),
480-
(false, true) => meta_cmp(other.line, self.line),
481-
(true, true) => panic!("both lines cannot have [..]"),
474+
impl PartialEq<&str> for WildStr<'_> {
475+
fn eq(&self, other: &&str) -> bool {
476+
if self.has_meta {
477+
meta_cmp(self.line, other)
478+
} else {
479+
self.line == *other
482480
}
483481
}
484482
}
@@ -666,10 +664,10 @@ mod test {
666664
("[..]", "a b"),
667665
("[..]b", "a b"),
668666
] {
669-
assert_eq!(WildStr::new(a), WildStr::new(b));
667+
assert_eq!(WildStr::new(a), b);
670668
}
671669
for (a, b) in &[("[..]b", "c"), ("b", "c"), ("b", "cb")] {
672-
assert_ne!(WildStr::new(a), WildStr::new(b));
670+
assert_ne!(WildStr::new(a), b);
673671
}
674672
}
675673

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

+154-13
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,57 @@ impl Execs {
667667
/// Verifies that stdout is equal to the given lines.
668668
///
669669
/// See [`compare::assert_e2e`] for assertion details.
670+
///
671+
/// <div class="warning">
672+
///
673+
/// Prefer passing in [`str!`] for `expected` to get snapshot updating.
674+
///
675+
/// If `format!` is needed for content that changes from run to run that you don't care about,
676+
/// consider whether you could have [`compare::assert_e2e`] redact the content.
677+
/// If nothing else, a wildcard (`[..]`, `...`) may be useful.
678+
///
679+
/// However, `""` may be preferred for intentionally empty output so people don't accidentally
680+
/// bless a change.
681+
///
682+
/// </div>
683+
///
684+
/// # Examples
685+
///
686+
/// ```no_run
687+
/// use cargo_test_support::prelude::*;
688+
/// use cargo_test_support::str;
689+
/// use cargo_test_support::execs;
690+
///
691+
/// execs().with_stdout_data(str![r#"
692+
/// Hello world!
693+
/// "#]);
694+
/// ```
695+
///
696+
/// Non-deterministic compiler output
697+
/// ```no_run
698+
/// use cargo_test_support::prelude::*;
699+
/// use cargo_test_support::str;
700+
/// use cargo_test_support::execs;
701+
///
702+
/// execs().with_stdout_data(str![r#"
703+
/// [COMPILING] foo
704+
/// [COMPILING] bar
705+
/// "#].unordered());
706+
/// ```
707+
///
708+
/// jsonlines
709+
/// ```no_run
710+
/// use cargo_test_support::prelude::*;
711+
/// use cargo_test_support::str;
712+
/// use cargo_test_support::execs;
713+
///
714+
/// execs().with_stdout_data(str![r#"
715+
/// [
716+
/// {},
717+
/// {}
718+
/// ]
719+
/// "#].is_json().against_jsonlines());
720+
/// ```
670721
pub fn with_stdout_data(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
671722
self.expect_stdout_data = Some(expected.into_data());
672723
self
@@ -675,6 +726,57 @@ impl Execs {
675726
/// Verifies that stderr is equal to the given lines.
676727
///
677728
/// See [`compare::assert_e2e`] for assertion details.
729+
///
730+
/// <div class="warning">
731+
///
732+
/// Prefer passing in [`str!`] for `expected` to get snapshot updating.
733+
///
734+
/// If `format!` is needed for content that changes from run to run that you don't care about,
735+
/// consider whether you could have [`compare::assert_e2e`] redact the content.
736+
/// If nothing else, a wildcard (`[..]`, `...`) may be useful.
737+
///
738+
/// However, `""` may be preferred for intentionally empty output so people don't accidentally
739+
/// bless a change.
740+
///
741+
/// </div>
742+
///
743+
/// # Examples
744+
///
745+
/// ```no_run
746+
/// use cargo_test_support::prelude::*;
747+
/// use cargo_test_support::str;
748+
/// use cargo_test_support::execs;
749+
///
750+
/// execs().with_stderr_data(str![r#"
751+
/// Hello world!
752+
/// "#]);
753+
/// ```
754+
///
755+
/// Non-deterministic compiler output
756+
/// ```no_run
757+
/// use cargo_test_support::prelude::*;
758+
/// use cargo_test_support::str;
759+
/// use cargo_test_support::execs;
760+
///
761+
/// execs().with_stderr_data(str![r#"
762+
/// [COMPILING] foo
763+
/// [COMPILING] bar
764+
/// "#].unordered());
765+
/// ```
766+
///
767+
/// jsonlines
768+
/// ```no_run
769+
/// use cargo_test_support::prelude::*;
770+
/// use cargo_test_support::str;
771+
/// use cargo_test_support::execs;
772+
///
773+
/// execs().with_stderr_data(str![r#"
774+
/// [
775+
/// {},
776+
/// {}
777+
/// ]
778+
/// "#].is_json().against_jsonlines());
779+
/// ```
678780
pub fn with_stderr_data(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
679781
self.expect_stderr_data = Some(expected.into_data());
680782
self
@@ -706,7 +808,14 @@ impl Execs {
706808
/// its output.
707809
///
708810
/// See [`compare`] for supported patterns.
709-
#[deprecated(note = "replaced with `Execs::with_stdout_data(expected)`")]
811+
///
812+
/// <div class="warning">
813+
///
814+
/// Prefer [`Execs::with_stdout_data`] where possible.
815+
/// - `expected` cannot be snapshotted
816+
/// - `expected` can end up being ambiguous, causing the assertion to succeed when it should fail
817+
///
818+
/// </div>
710819
pub fn with_stdout_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
711820
self.expect_stdout_contains.push(expected.to_string());
712821
self
@@ -716,7 +825,14 @@ impl Execs {
716825
/// its output.
717826
///
718827
/// See [`compare`] for supported patterns.
719-
#[deprecated(note = "replaced with `Execs::with_stderr_data(expected)`")]
828+
///
829+
/// <div class="warning">
830+
///
831+
/// Prefer [`Execs::with_stderr_data`] where possible.
832+
/// - `expected` cannot be snapshotted
833+
/// - `expected` can end up being ambiguous, causing the assertion to succeed when it should fail
834+
///
835+
/// </div>
720836
pub fn with_stderr_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
721837
self.expect_stderr_contains.push(expected.to_string());
722838
self
@@ -727,7 +843,18 @@ impl Execs {
727843
/// See [`compare`] for supported patterns.
728844
///
729845
/// See note on [`Self::with_stderr_does_not_contain`].
730-
#[deprecated]
846+
///
847+
/// <div class="warning">
848+
///
849+
/// Prefer [`Execs::with_stdout_data`] where possible.
850+
/// - `expected` cannot be snapshotted
851+
/// - The absence of `expected` can either mean success or that the string being looked for
852+
/// changed.
853+
///
854+
/// To mitigate this, consider matching this up with
855+
/// [`Execs::with_stdout_contains`].
856+
///
857+
/// </div>
731858
pub fn with_stdout_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
732859
self.expect_stdout_not_contains.push(expected.to_string());
733860
self
@@ -737,12 +864,18 @@ impl Execs {
737864
///
738865
/// See [`compare`] for supported patterns.
739866
///
740-
/// Care should be taken when using this method because there is a
741-
/// limitless number of possible things that *won't* appear. A typo means
742-
/// your test will pass without verifying the correct behavior. If
743-
/// possible, write the test first so that it fails, and then implement
744-
/// your fix/feature to make it pass.
745-
#[deprecated]
867+
/// <div class="warning">
868+
///
869+
/// Prefer [`Execs::with_stdout_data`] where possible.
870+
/// - `expected` cannot be snapshotted
871+
/// - The absence of `expected` can either mean success or that the string being looked for
872+
/// changed.
873+
///
874+
/// To mitigate this, consider either matching this up with
875+
/// [`Execs::with_stdout_contains`] or replace it
876+
/// with [`Execs::with_stderr_line_without`].
877+
///
878+
/// </div>
746879
pub fn with_stderr_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
747880
self.expect_stderr_not_contains.push(expected.to_string());
748881
self
@@ -751,7 +884,18 @@ impl Execs {
751884
/// Verify that a particular line appears in stderr with and without the
752885
/// given substrings. Exactly one line must match.
753886
///
754-
/// The substrings are matched as `contains`. Example:
887+
/// The substrings are matched as `contains`.
888+
///
889+
/// <div class="warning">
890+
///
891+
/// Prefer [`Execs::with_stdout_data`] where possible.
892+
/// - `with` cannot be snapshotted
893+
/// - The absence of `without`` can either mean success or that the string being looked for
894+
/// changed.
895+
///
896+
/// </div>
897+
///
898+
/// # Example
755899
///
756900
/// ```no_run
757901
/// use cargo_test_support::execs;
@@ -768,9 +912,6 @@ impl Execs {
768912
/// This will check that a build line includes `-C opt-level=3` but does
769913
/// not contain `-C debuginfo` or `-C incremental`.
770914
///
771-
/// Be careful writing the `without` fragments, see note in
772-
/// `with_stderr_does_not_contain`.
773-
#[deprecated]
774915
pub fn with_stderr_line_without<S: ToString>(
775916
&mut self,
776917
with: &[S],

tests/testsuite/artifact_dep.rs

-5
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,6 @@ fn build_script_with_bin_artifact_and_lib_false() {
687687
)
688688
.build();
689689

690-
#[expect(deprecated)]
691690
p.cargo("build -Z bindeps")
692691
.masquerade_as_nightly_cargo(&["bindeps"])
693692
.with_status(101)
@@ -731,7 +730,6 @@ fn lib_with_bin_artifact_and_lib_false() {
731730
)
732731
.build();
733732

734-
#[expect(deprecated)]
735733
p.cargo("build -Z bindeps")
736734
.masquerade_as_nightly_cargo(&["bindeps"])
737735
.with_status(101)
@@ -1117,7 +1115,6 @@ fn build_script_deps_adopt_specified_target_unconditionally() {
11171115
.file("bar/src/lib.rs", "pub fn doit() {}")
11181116
.build();
11191117

1120-
#[expect(deprecated)]
11211118
p.cargo("check -v -Z bindeps")
11221119
.masquerade_as_nightly_cargo(&["bindeps"])
11231120
.with_stderr_does_not_contain(
@@ -1233,7 +1230,6 @@ fn non_build_script_deps_adopt_specified_target_unconditionally() {
12331230
.file("bar/src/lib.rs", "pub fn doit() {}")
12341231
.build();
12351232

1236-
#[expect(deprecated)]
12371233
p.cargo("check -v -Z bindeps")
12381234
.masquerade_as_nightly_cargo(&["bindeps"])
12391235
.with_stderr_contains(
@@ -1378,7 +1374,6 @@ fn build_script_deps_adopts_target_platform_if_target_equals_target() {
13781374
.build();
13791375

13801376
let alternate_target = cross_compile::alternate();
1381-
#[expect(deprecated)]
13821377
p.cargo("check -v -Z bindeps --target")
13831378
.arg(alternate_target)
13841379
.masquerade_as_nightly_cargo(&["bindeps"])

0 commit comments

Comments
 (0)