diff --git a/Cargo.lock b/Cargo.lock
index 5ad0f4e0515..07f76e8ecd4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3209,9 +3209,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
 
 [[package]]
 name = "snapbox"
-version = "0.6.5"
+version = "0.6.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ad8c7be18cc9ec7f4d7948ad6b9df0e04fc649663e3c0ed59f304ed17ca69e9"
+checksum = "94204b12a4d3550420babdb4148c6639692e4e3e61060866929c5107f208aeb6"
 dependencies = [
  "anstream",
  "anstyle",
diff --git a/Cargo.toml b/Cargo.toml
index 36dbed8218a..b88e0b3fd9f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -91,7 +91,7 @@ sha1 = "0.10.6"
 sha2 = "0.10.8"
 shell-escape = "0.1.5"
 supports-hyperlinks = "3.0.0"
-snapbox = { version = "0.6.5", features = ["diff", "dir", "term-svg", "regex"] }
+snapbox = { version = "0.6.7", features = ["diff", "dir", "term-svg", "regex"] }
 tar = { version = "0.4.40", default-features = false }
 tempfile = "3.10.1"
 thiserror = "1.0.59"
diff --git a/crates/cargo-test-support/src/compare.rs b/crates/cargo-test-support/src/compare.rs
index 2d33c61109c..53b752621de 100644
--- a/crates/cargo-test-support/src/compare.rs
+++ b/crates/cargo-test-support/src/compare.rs
@@ -40,13 +40,14 @@ use crate::diff;
 use crate::paths;
 use anyhow::{bail, Context, Result};
 use serde_json::Value;
-use std::env;
 use std::fmt;
 use std::path::Path;
 use std::str;
 use url::Url;
 
-/// Default `snapbox` Assertions
+/// Assertion policy for UI tests
+///
+/// This emphasizes showing as much content as possible at the cost of more brittleness
 ///
 /// # Snapshots
 ///
@@ -82,7 +83,7 @@ pub fn assert_ui() -> snapbox::Assert {
     let root_url = url::Url::from_file_path(&root).unwrap().to_string();
 
     let mut subs = snapbox::Redactions::new();
-    subs.extend([("[EXE]", std::env::consts::EXE_SUFFIX)])
+    subs.extend(MIN_LITERAL_REDACTIONS.into_iter().cloned())
         .unwrap();
     subs.insert("[ROOT]", root).unwrap();
     subs.insert("[ROOTURL]", root_url).unwrap();
@@ -96,6 +97,121 @@ pub fn assert_ui() -> snapbox::Assert {
         .redact_with(subs)
 }
 
+/// Assertion policy for functional end-to-end tests
+///
+/// This emphasizes showing as much content as possible at the cost of more brittleness
+///
+/// # Snapshots
+///
+/// Updating of snapshots is controlled with the `SNAPSHOTS` environment variable:
+///
+/// - `skip`: do not run the tests
+/// - `ignore`: run the tests but ignore their failure
+/// - `verify`: run the tests
+/// - `overwrite`: update the snapshots based on the output of the tests
+///
+/// # Patterns
+///
+/// - `[..]` is a character wildcard, stopping at line breaks
+/// - `\n...\n` is a multi-line wildcard
+/// - `[EXE]` matches the exe suffix for the current platform
+/// - `[ROOT]` matches [`paths::root()`][crate::paths::root]
+/// - `[ROOTURL]` matches [`paths::root()`][crate::paths::root] as a URL
+///
+/// # Normalization
+///
+/// In addition to the patterns described above, text is normalized
+/// in such a way to avoid unwanted differences. The normalizations are:
+///
+/// - Backslashes are converted to forward slashes to deal with Windows paths.
+///   This helps so that all tests can be written assuming forward slashes.
+///   Other heuristics are applied to try to ensure Windows-style paths aren't
+///   a problem.
+/// - Carriage returns are removed, which can help when running on Windows.
+pub fn assert_e2e() -> snapbox::Assert {
+    let root = paths::root();
+    // Use `from_file_path` instead of `from_dir_path` so the trailing slash is
+    // put in the users output, rather than hidden in the variable
+    let root_url = url::Url::from_file_path(&root).unwrap().to_string();
+
+    let mut subs = snapbox::Redactions::new();
+    subs.extend(MIN_LITERAL_REDACTIONS.into_iter().cloned())
+        .unwrap();
+    subs.extend(E2E_LITERAL_REDACTIONS.into_iter().cloned())
+        .unwrap();
+    subs.insert("[ROOT]", root).unwrap();
+    subs.insert("[ROOTURL]", root_url).unwrap();
+    subs.insert(
+        "[ELAPSED]",
+        regex::Regex::new("[FINISHED].*in (?<redacted>[0-9]+(\\.[0-9]+))s").unwrap(),
+    )
+    .unwrap();
+    snapbox::Assert::new()
+        .action_env(snapbox::assert::DEFAULT_ACTION_ENV)
+        .redact_with(subs)
+}
+
+static MIN_LITERAL_REDACTIONS: &[(&str, &str)] = &[
+    ("[EXE]", std::env::consts::EXE_SUFFIX),
+    ("[BROKEN_PIPE]", "Broken pipe (os error 32)"),
+    ("[BROKEN_PIPE]", "The pipe is being closed. (os error 232)"),
+];
+static E2E_LITERAL_REDACTIONS: &[(&str, &str)] = &[
+    ("[RUNNING]", "     Running"),
+    ("[COMPILING]", "   Compiling"),
+    ("[CHECKING]", "    Checking"),
+    ("[COMPLETED]", "   Completed"),
+    ("[CREATED]", "     Created"),
+    ("[CREATING]", "    Creating"),
+    ("[CREDENTIAL]", "  Credential"),
+    ("[DOWNGRADING]", " Downgrading"),
+    ("[FINISHED]", "    Finished"),
+    ("[ERROR]", "error:"),
+    ("[WARNING]", "warning:"),
+    ("[NOTE]", "note:"),
+    ("[HELP]", "help:"),
+    ("[DOCUMENTING]", " Documenting"),
+    ("[SCRAPING]", "    Scraping"),
+    ("[FRESH]", "       Fresh"),
+    ("[DIRTY]", "       Dirty"),
+    ("[LOCKING]", "     Locking"),
+    ("[UPDATING]", "    Updating"),
+    ("[ADDING]", "      Adding"),
+    ("[REMOVING]", "    Removing"),
+    ("[REMOVED]", "     Removed"),
+    ("[UNCHANGED]", "   Unchanged"),
+    ("[DOCTEST]", "   Doc-tests"),
+    ("[PACKAGING]", "   Packaging"),
+    ("[PACKAGED]", "    Packaged"),
+    ("[DOWNLOADING]", " Downloading"),
+    ("[DOWNLOADED]", "  Downloaded"),
+    ("[UPLOADING]", "   Uploading"),
+    ("[UPLOADED]", "    Uploaded"),
+    ("[VERIFYING]", "   Verifying"),
+    ("[ARCHIVING]", "   Archiving"),
+    ("[INSTALLING]", "  Installing"),
+    ("[REPLACING]", "   Replacing"),
+    ("[UNPACKING]", "   Unpacking"),
+    ("[SUMMARY]", "     Summary"),
+    ("[FIXED]", "       Fixed"),
+    ("[FIXING]", "      Fixing"),
+    ("[IGNORED]", "     Ignored"),
+    ("[INSTALLED]", "   Installed"),
+    ("[REPLACED]", "    Replaced"),
+    ("[BUILDING]", "    Building"),
+    ("[LOGIN]", "       Login"),
+    ("[LOGOUT]", "      Logout"),
+    ("[YANK]", "        Yank"),
+    ("[OWNER]", "       Owner"),
+    ("[MIGRATING]", "   Migrating"),
+    ("[EXECUTABLE]", "  Executable"),
+    ("[SKIPPING]", "    Skipping"),
+    ("[WAITING]", "     Waiting"),
+    ("[PUBLISHED]", "   Published"),
+    ("[BLOCKING]", "    Blocking"),
+    ("[GENERATED]", "   Generated"),
+];
+
 /// 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
@@ -185,64 +301,11 @@ fn normalize_windows(text: &str, cwd: Option<&Path>) -> String {
 }
 
 fn substitute_macros(input: &str) -> String {
-    let macros = [
-        ("[RUNNING]", "     Running"),
-        ("[COMPILING]", "   Compiling"),
-        ("[CHECKING]", "    Checking"),
-        ("[COMPLETED]", "   Completed"),
-        ("[CREATED]", "     Created"),
-        ("[CREATING]", "    Creating"),
-        ("[CREDENTIAL]", "  Credential"),
-        ("[DOWNGRADING]", " Downgrading"),
-        ("[FINISHED]", "    Finished"),
-        ("[ERROR]", "error:"),
-        ("[WARNING]", "warning:"),
-        ("[NOTE]", "note:"),
-        ("[HELP]", "help:"),
-        ("[DOCUMENTING]", " Documenting"),
-        ("[SCRAPING]", "    Scraping"),
-        ("[FRESH]", "       Fresh"),
-        ("[DIRTY]", "       Dirty"),
-        ("[LOCKING]", "     Locking"),
-        ("[UPDATING]", "    Updating"),
-        ("[ADDING]", "      Adding"),
-        ("[REMOVING]", "    Removing"),
-        ("[REMOVED]", "     Removed"),
-        ("[UNCHANGED]", "   Unchanged"),
-        ("[DOCTEST]", "   Doc-tests"),
-        ("[PACKAGING]", "   Packaging"),
-        ("[PACKAGED]", "    Packaged"),
-        ("[DOWNLOADING]", " Downloading"),
-        ("[DOWNLOADED]", "  Downloaded"),
-        ("[UPLOADING]", "   Uploading"),
-        ("[UPLOADED]", "    Uploaded"),
-        ("[VERIFYING]", "   Verifying"),
-        ("[ARCHIVING]", "   Archiving"),
-        ("[INSTALLING]", "  Installing"),
-        ("[REPLACING]", "   Replacing"),
-        ("[UNPACKING]", "   Unpacking"),
-        ("[SUMMARY]", "     Summary"),
-        ("[FIXED]", "       Fixed"),
-        ("[FIXING]", "      Fixing"),
-        ("[EXE]", env::consts::EXE_SUFFIX),
-        ("[IGNORED]", "     Ignored"),
-        ("[INSTALLED]", "   Installed"),
-        ("[REPLACED]", "    Replaced"),
-        ("[BUILDING]", "    Building"),
-        ("[LOGIN]", "       Login"),
-        ("[LOGOUT]", "      Logout"),
-        ("[YANK]", "        Yank"),
-        ("[OWNER]", "       Owner"),
-        ("[MIGRATING]", "   Migrating"),
-        ("[EXECUTABLE]", "  Executable"),
-        ("[SKIPPING]", "    Skipping"),
-        ("[WAITING]", "     Waiting"),
-        ("[PUBLISHED]", "   Published"),
-        ("[BLOCKING]", "    Blocking"),
-        ("[GENERATED]", "   Generated"),
-    ];
     let mut result = input.to_owned();
-    for &(pat, subst) in &macros {
+    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
@@ -254,7 +317,7 @@ fn substitute_macros(input: &str) -> String {
 ///
 /// - `description` explains where the output is from (usually "stdout" or "stderr").
 /// - `other_output` is other output to display in the error (usually stdout or stderr).
-pub fn match_exact(
+pub(crate) fn match_exact(
     expected: &str,
     actual: &str,
     description: &str,
@@ -282,7 +345,7 @@ pub fn match_exact(
 
 /// Convenience wrapper around [`match_exact`] which will panic on error.
 #[track_caller]
-pub fn assert_match_exact(expected: &str, actual: &str) {
+pub(crate) fn assert_match_exact(expected: &str, actual: &str) {
     if let Err(e) = match_exact(expected, actual, "", "", None) {
         crate::panic_error("", e);
     }
@@ -292,7 +355,7 @@ pub fn assert_match_exact(expected: &str, actual: &str) {
 /// of the lines.
 ///
 /// See [Patterns](index.html#patterns) for more information on pattern matching.
-pub fn match_unordered(expected: &str, actual: &str, cwd: Option<&Path>) -> Result<()> {
+pub(crate) fn match_unordered(expected: &str, actual: &str, cwd: Option<&Path>) -> Result<()> {
     let expected = normalize_expected(expected, cwd);
     let actual = normalize_actual(actual, cwd);
     let e: Vec<_> = expected.lines().map(|line| WildStr::new(line)).collect();
@@ -342,7 +405,7 @@ pub fn match_unordered(expected: &str, actual: &str, cwd: Option<&Path>) -> Resu
 /// somewhere.
 ///
 /// See [Patterns](index.html#patterns) for more information on pattern matching.
-pub fn match_contains(expected: &str, actual: &str, cwd: Option<&Path>) -> Result<()> {
+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);
     let e: Vec<_> = expected.lines().map(|line| WildStr::new(line)).collect();
@@ -369,7 +432,11 @@ pub fn match_contains(expected: &str, actual: &str, cwd: Option<&Path>) -> Resul
 /// anywhere.
 ///
 /// See [Patterns](index.html#patterns) for more information on pattern matching.
-pub fn match_does_not_contain(expected: &str, actual: &str, cwd: Option<&Path>) -> Result<()> {
+pub(crate) fn match_does_not_contain(
+    expected: &str,
+    actual: &str,
+    cwd: Option<&Path>,
+) -> Result<()> {
     if match_contains(expected, actual, cwd).is_ok() {
         bail!(
             "expected not to find:\n\
@@ -388,7 +455,7 @@ pub fn match_does_not_contain(expected: &str, actual: &str, cwd: Option<&Path>)
 /// somewhere, and should be repeated `number` times.
 ///
 /// See [Patterns](index.html#patterns) for more information on pattern matching.
-pub fn match_contains_n(
+pub(crate) fn match_contains_n(
     expected: &str,
     number: usize,
     actual: &str,
@@ -425,7 +492,7 @@ pub fn match_contains_n(
 ///
 /// See [`crate::Execs::with_stderr_line_without`] for an example and cautions
 /// against using.
-pub fn match_with_without(
+pub(crate) fn match_with_without(
     actual: &str,
     with: &[String],
     without: &[String],
@@ -473,7 +540,7 @@ pub fn match_with_without(
 /// expected JSON objects.
 ///
 /// See [`crate::Execs::with_json`] for more details.
-pub fn match_json(expected: &str, actual: &str, cwd: Option<&Path>) -> Result<()> {
+pub(crate) fn match_json(expected: &str, actual: &str, cwd: Option<&Path>) -> Result<()> {
     let (exp_objs, act_objs) = collect_json_objects(expected, actual)?;
     if exp_objs.len() != act_objs.len() {
         bail!(
@@ -494,7 +561,7 @@ pub fn match_json(expected: &str, actual: &str, cwd: Option<&Path>) -> Result<()
 ///
 /// See [`crate::Execs::with_json_contains_unordered`] for more details and
 /// cautions when using.
-pub fn match_json_contains_unordered(
+pub(crate) fn match_json_contains_unordered(
     expected: &str,
     actual: &str,
     cwd: Option<&Path>,
@@ -552,7 +619,11 @@ fn collect_json_objects(
 /// as paths). You can use a `"{...}"` string literal as a wildcard for
 /// arbitrary nested JSON (useful for parts of object emitted by other programs
 /// (e.g., rustc) rather than Cargo itself).
-pub fn find_json_mismatch(expected: &Value, actual: &Value, cwd: Option<&Path>) -> Result<()> {
+pub(crate) fn find_json_mismatch(
+    expected: &Value,
+    actual: &Value,
+    cwd: Option<&Path>,
+) -> Result<()> {
     match find_json_mismatch_r(expected, actual, cwd) {
         Some((expected_part, actual_part)) => bail!(
             "JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
@@ -619,7 +690,7 @@ fn find_json_mismatch_r<'a>(
 }
 
 /// A single line string that supports `[..]` wildcard matching.
-pub struct WildStr<'a> {
+pub(crate) struct WildStr<'a> {
     has_meta: bool,
     line: &'a str,
 }
diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs
index 48391b400a8..5db67b8ea90 100644
--- a/crates/cargo-test-support/src/lib.rs
+++ b/crates/cargo-test-support/src/lib.rs
@@ -76,6 +76,7 @@ pub mod prelude {
     pub use crate::CargoCommand;
     pub use crate::ChannelChanger;
     pub use crate::TestEnv;
+    pub use snapbox::IntoData;
 }
 
 /*
diff --git a/tests/testsuite/alt_registry.rs b/tests/testsuite/alt_registry.rs
index 1fef04e3ae4..68466d0d005 100644
--- a/tests/testsuite/alt_registry.rs
+++ b/tests/testsuite/alt_registry.rs
@@ -1,8 +1,9 @@
 //! Tests for alternative registries.
 
-use cargo_test_support::compare::assert_match_exact;
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::publish::validate_alt_upload;
 use cargo_test_support::registry::{self, Package, RegistryBuilder};
+use cargo_test_support::str;
 use cargo_test_support::{basic_manifest, paths, project};
 use std::fs;
 
@@ -1476,9 +1477,10 @@ fn sparse_lockfile() {
         .build();
 
     p.cargo("generate-lockfile").run();
-    assert_match_exact(
+    assert_e2e().eq(
         &p.read_lockfile(),
-        r#"# This file is automatically @generated by Cargo.
+        str![[r##"
+# This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
 version = 3
 
@@ -1492,8 +1494,10 @@ dependencies = [
 [[package]]
 name = "foo"
 version = "0.1.0"
-source = "sparse+http://[..]/"
-checksum = "458c1addb23fde7dfbca0410afdbcc0086f96197281ec304d9e0e10def3cb899""#,
+source = "sparse+http://127.0.0.1:[..]/index/"
+checksum = "458c1addb23fde7dfbca0410afdbcc0086f96197281ec304d9e0e10def3cb899"
+
+"##]],
     );
 }
 
diff --git a/tests/testsuite/artifact_dep.rs b/tests/testsuite/artifact_dep.rs
index 7194b4a61f6..b8234140f7f 100644
--- a/tests/testsuite/artifact_dep.rs
+++ b/tests/testsuite/artifact_dep.rs
@@ -1,8 +1,9 @@
 //! Tests specific to artifact dependencies, designated using
 //! the new `dep = { artifact = "bin", … }` syntax in manifests.
 
-use cargo_test_support::compare;
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::registry::{Package, RegistryBuilder};
+use cargo_test_support::str;
 use cargo_test_support::{
     basic_bin_manifest, basic_manifest, cross_compile, project, publish, registry, rustc_host,
     Project,
@@ -595,25 +596,31 @@ fn build_script_with_bin_artifacts() {
     let build_script_output = build_script_output_string(&p, "foo");
     // we need the binary directory for this artifact along with all binary paths
     if cfg!(target_env = "msvc") {
-        compare::assert_match_exact(
-            "[..]/artifact/bar-[..]/bin/baz.exe\n\
-             [..]/artifact/bar-[..]/staticlib/bar-[..].lib\n\
-             [..]/artifact/bar-[..]/cdylib/bar.dll\n\
-             [..]/artifact/bar-[..]/bin\n\
-             [..]/artifact/bar-[..]/bin/bar.exe\n\
-             [..]/artifact/bar-[..]/bin/bar.exe",
+        assert_e2e().eq(
             &build_script_output,
-        )
+            str![[r#"
+[ROOT]/foo/target/debug/deps/artifact/bar-[..]/bin/baz[EXE]
+[ROOT]/foo/target/debug/deps/artifact/bar-[..]/staticlib/bar-[..].lib
+[ROOT]/foo/target/debug/deps/artifact/bar-[..]/cdylib/bar.dll
+[ROOT]/foo/target/debug/deps/artifact/bar-[..]/bin
+[ROOT]/foo/target/debug/deps/artifact/bar-[..]/bin/bar[EXE]
+[ROOT]/foo/target/debug/deps/artifact/bar-[..]/bin/bar[EXE]
+
+"#]],
+        );
     } else {
-        compare::assert_match_exact(
-            "[..]/artifact/bar-[..]/bin/baz-[..]\n\
-             [..]/artifact/bar-[..]/staticlib/libbar-[..].a\n\
-             [..]/artifact/bar-[..]/cdylib/[..]bar.[..]\n\
-             [..]/artifact/bar-[..]/bin\n\
-             [..]/artifact/bar-[..]/bin/bar-[..]\n\
-             [..]/artifact/bar-[..]/bin/bar-[..]",
+        assert_e2e().eq(
             &build_script_output,
-        )
+            str![[r#"
+[ROOT]/foo/target/debug/deps/artifact/bar-[..]/bin/baz-[..]
+[ROOT]/foo/target/debug/deps/artifact/bar-[..]/staticlib/libbar-[..].a
+[ROOT]/foo/target/debug/deps/artifact/bar-[..]/cdylib/[..]bar.[..]
+[ROOT]/foo/target/debug/deps/artifact/bar-[..]/bin
+[ROOT]/foo/target/debug/deps/artifact/bar-[..]/bin/bar-[..]
+[ROOT]/foo/target/debug/deps/artifact/bar-[..]/bin/bar-[..]
+
+"#]],
+        );
     }
 
     assert!(
@@ -777,19 +784,22 @@ fn build_script_with_selected_dashed_bin_artifact_and_lib_true() {
     let build_script_output = build_script_output_string(&p, "foo");
     // we need the binary directory for this artifact and the binary itself
     if cfg!(target_env = "msvc") {
-        compare::assert_match_exact(
-            &format!(
-                "[..]/artifact/bar-baz-[..]/bin\n\
-                 [..]/artifact/bar-baz-[..]/bin/baz_suffix{}",
-                std::env::consts::EXE_SUFFIX,
-            ),
+        assert_e2e().eq(
             &build_script_output,
+            str![[r#"
+[ROOT]/foo/target/debug/deps/artifact/bar-baz-[..]/bin
+[ROOT]/foo/target/debug/deps/artifact/bar-baz-[..]/bin/baz_suffix[EXE]
+
+"#]],
         );
     } else {
-        compare::assert_match_exact(
-            "[..]/artifact/bar-baz-[..]/bin\n\
-        [..]/artifact/bar-baz-[..]/bin/baz_suffix-[..]",
+        assert_e2e().eq(
             &build_script_output,
+            str![[r#"
+[ROOT]/foo/target/debug/deps/artifact/bar-baz-[..]/bin
+[ROOT]/foo/target/debug/deps/artifact/bar-baz-[..]/bin/baz_suffix-[..]
+
+"#]],
         );
     }
 
@@ -1740,7 +1750,14 @@ fn allow_dep_renames_with_multiple_versions() {
         .with_stderr_contains("[COMPILING] foo [..]")
         .run();
     let build_script_output = build_script_output_string(&p, "foo");
-    compare::assert_match_exact("0.5.0\n1.0.0", &build_script_output);
+    assert_e2e().eq(
+        &build_script_output,
+        str![[r#"
+0.5.0
+1.0.0
+
+"#]],
+    );
 }
 
 #[cargo_test]
@@ -3216,9 +3233,13 @@ fn build_only_specified_artifact_library() {
         .cargo("build -Z bindeps")
         .masquerade_as_nightly_cargo(&["bindeps"])
         .run();
-    compare::assert_match_exact(
-        "cdylib present: true\nstaticlib present: false",
+    assert_e2e().eq(
         &build_script_output_string(&cdylib, "foo"),
+        str![[r#"
+cdylib present: true
+staticlib present: false
+
+"#]],
     );
 
     let staticlib = create_project("staticlib");
@@ -3226,8 +3247,12 @@ fn build_only_specified_artifact_library() {
         .cargo("build -Z bindeps")
         .masquerade_as_nightly_cargo(&["bindeps"])
         .run();
-    compare::assert_match_exact(
-        "cdylib present: false\nstaticlib present: true",
+    assert_e2e().eq(
         &build_script_output_string(&staticlib, "foo"),
+        str![[r#"
+cdylib present: false
+staticlib present: true
+
+"#]],
     );
 }
diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs
index 32b5048d160..6fb2d416c75 100644
--- a/tests/testsuite/build.rs
+++ b/tests/testsuite/build.rs
@@ -6,10 +6,13 @@ use cargo::{
     ops::CompileOptions,
     GlobalContext,
 };
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::paths::{root, CargoPathExt};
+use cargo_test_support::prelude::*;
 use cargo_test_support::registry::Package;
+use cargo_test_support::str;
 use cargo_test_support::{
-    basic_bin_manifest, basic_lib_manifest, basic_manifest, cargo_exe, cargo_process, compare, git,
+    basic_bin_manifest, basic_lib_manifest, basic_manifest, cargo_exe, cargo_process, git,
     is_nightly, main_file, paths, process, project, rustc_host, sleep_ms, symlink_supported, t,
     tools, Execs, ProjectBuilder,
 };
@@ -6448,17 +6451,17 @@ fn close_output() {
     };
 
     let stderr = spawn(false);
-    compare::match_unordered(
-        "\
-[COMPILING] foo [..]
+    assert_e2e().eq(
+        &stderr,
+        str![[r#"
+[COMPILING] foo v0.1.0 ([ROOT]/foo)
 hello stderr!
-[ERROR] [..]
+[ERROR] [BROKEN_PIPE]
 [WARNING] build failed, waiting for other jobs to finish...
-",
-        &stderr,
-        None,
-    )
-    .unwrap();
+
+"#]]
+        .unordered(),
+    );
 
     // Try again with stderr.
     p.build_dir().rm_rf();
diff --git a/tests/testsuite/build_script.rs b/tests/testsuite/build_script.rs
index ef76309233b..1b770cdf1fa 100644
--- a/tests/testsuite/build_script.rs
+++ b/tests/testsuite/build_script.rs
@@ -1,9 +1,10 @@
 //! Tests for build.rs scripts.
 
-use cargo_test_support::compare::assert_match_exact;
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::install::cargo_home;
 use cargo_test_support::paths::CargoPathExt;
 use cargo_test_support::registry::Package;
+use cargo_test_support::str;
 use cargo_test_support::tools;
 use cargo_test_support::{
     basic_manifest, cargo_exe, cross_compile, is_coarse_mtime, project, project_in,
@@ -3398,9 +3399,12 @@ fn generate_good_d_files() {
 
     println!("*.d file content*: {}", &dot_d);
 
-    assert_match_exact(
-        "[..]/target/debug/meow[EXE]: [..]/awoo/barkbarkbark [..]/awoo/build.rs[..]",
+    assert_e2e().eq(
         &dot_d,
+        str![[r#"
+[ROOT]/foo/target/debug/meow[EXE]: [ROOT]/foo/awoo/barkbarkbark [ROOT]/foo/awoo/build.rs [ROOT]/foo/awoo/src/lib.rs [ROOT]/foo/src/main.rs
+
+"#]],
     );
 
     // paths relative to dependency roots should not be allowed
@@ -3421,9 +3425,12 @@ fn generate_good_d_files() {
 
     println!("*.d file content with dep-info-basedir*: {}", &dot_d);
 
-    assert_match_exact(
-        "target/debug/meow[EXE]: awoo/barkbarkbark awoo/build.rs[..]",
+    assert_e2e().eq(
         &dot_d,
+        str![[r#"
+target/debug/meow[EXE]: awoo/barkbarkbark awoo/build.rs awoo/src/lib.rs src/main.rs
+
+"#]],
     );
 
     // paths relative to dependency roots should not be allowed
@@ -3485,16 +3492,10 @@ fn generate_good_d_files_for_external_tools() {
 
     println!("*.d file content with dep-info-basedir*: {}", &dot_d);
 
-    assert_match_exact(
-        concat!(
-            "rust_things/foo/target/debug/meow[EXE]:",
-            " rust_things/foo/awoo/barkbarkbark",
-            " rust_things/foo/awoo/build.rs",
-            " rust_things/foo/awoo/src/lib.rs",
-            " rust_things/foo/src/main.rs",
-        ),
-        &dot_d,
-    );
+    assert_e2e().eq(&dot_d, str![[r#"
+rust_things/foo/target/debug/meow[EXE]: rust_things/foo/awoo/barkbarkbark rust_things/foo/awoo/build.rs rust_things/foo/awoo/src/lib.rs rust_things/foo/src/main.rs
+
+"#]]);
 }
 
 #[cargo_test]
diff --git a/tests/testsuite/check.rs b/tests/testsuite/check.rs
index da8239fd748..2bae136ac71 100644
--- a/tests/testsuite/check.rs
+++ b/tests/testsuite/check.rs
@@ -3,10 +3,11 @@
 use std::fmt::{self, Write};
 
 use crate::messages::raw_rustc_output;
-use cargo_test_support::compare;
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::install::exe;
 use cargo_test_support::paths::CargoPathExt;
 use cargo_test_support::registry::Package;
+use cargo_test_support::str;
 use cargo_test_support::tools;
 use cargo_test_support::{basic_bin_manifest, basic_manifest, git, project};
 
@@ -1553,7 +1554,10 @@ fn pkgid_querystring_works() {
     let output = p.cargo("pkgid").arg("gitdep").exec_with_output().unwrap();
     let gitdep_pkgid = String::from_utf8(output.stdout).unwrap();
     let gitdep_pkgid = gitdep_pkgid.trim();
-    compare::assert_match_exact("git+file://[..]/gitdep?branch=master#1.0.0", &gitdep_pkgid);
+    assert_e2e().eq(
+        gitdep_pkgid,
+        str!["git+[ROOTURL]/gitdep?branch=master#1.0.0"],
+    );
 
     p.cargo("build -p")
         .arg(gitdep_pkgid)
diff --git a/tests/testsuite/config.rs b/tests/testsuite/config.rs
index 6f0b262dbc5..8793700c413 100644
--- a/tests/testsuite/config.rs
+++ b/tests/testsuite/config.rs
@@ -5,7 +5,9 @@ use cargo::util::context::{
     self, Definition, GlobalContext, JobsConfig, SslVersionConfig, StringList,
 };
 use cargo::CargoResult;
-use cargo_test_support::compare;
+use cargo_test_support::compare::assert_e2e;
+use cargo_test_support::prelude::*;
+use cargo_test_support::str;
 use cargo_test_support::{paths, project, symlink_supported, t};
 use cargo_util_schemas::manifest::TomlTrimPaths;
 use cargo_util_schemas::manifest::TomlTrimPathsValue;
@@ -208,7 +210,7 @@ fn rename_config_toml_to_config_replacing_with_symlink() {
 }
 
 #[track_caller]
-pub fn assert_error<E: Borrow<anyhow::Error>>(error: E, msgs: &str) {
+pub fn assert_error<E: Borrow<anyhow::Error>>(error: E, msgs: impl IntoData) {
     let causes = error
         .borrow()
         .chain()
@@ -222,7 +224,7 @@ pub fn assert_error<E: Borrow<anyhow::Error>>(error: E, msgs: &str) {
         })
         .collect::<Vec<_>>()
         .join("\n\n");
-    compare::assert_match_exact(msgs, &causes);
+    assert_e2e().eq(&causes, msgs);
 }
 
 #[cargo_test]
@@ -287,10 +289,12 @@ f1 = 1
 
     // It should NOT have warned for the symlink.
     let output = read_output(gctx);
-    let expected = "\
+    let expected = str![[r#"
 [WARNING] `[ROOT]/.cargo/config` is deprecated in favor of `config.toml`
-[NOTE] if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`";
-    compare::assert_match_exact(expected, &output);
+[NOTE] if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
+
+"#]];
+    assert_e2e().eq(&output, expected);
 }
 
 #[cargo_test]
@@ -316,7 +320,7 @@ f1 = 1
 
     // It should NOT have warned for the symlink.
     let output = read_output(gctx);
-    compare::assert_match_exact("", &output);
+    assert_e2e().eq(&output, str![[""]]);
 }
 
 #[cargo_test]
@@ -342,7 +346,7 @@ f1 = 1
 
     // It should NOT have warned for the symlink.
     let output = read_output(gctx);
-    compare::assert_match_exact("", &output);
+    assert_e2e().eq(&output, str![[""]]);
 }
 
 #[cargo_test]
@@ -368,7 +372,7 @@ f1 = 1
 
     // It should NOT have warned for this situation.
     let output = read_output(gctx);
-    compare::assert_match_exact("", &output);
+    assert_e2e().eq(&output, str![[""]]);
 }
 
 #[cargo_test]
@@ -395,10 +399,11 @@ f1 = 2
 
     // But it also should have warned.
     let output = read_output(gctx);
-    let expected = "\
-[WARNING] both `[..]/.cargo/config` and `[..]/.cargo/config.toml` exist. Using `[..]/.cargo/config`
-";
-    compare::assert_match_exact(expected, &output);
+    let expected = str![[r#"
+[WARNING] both `[ROOT]/.cargo/config` and `[ROOT]/.cargo/config.toml` exist. Using `[ROOT]/.cargo/config`
+
+"#]];
+    assert_e2e().eq(&output, expected);
 }
 
 #[cargo_test]
@@ -429,10 +434,11 @@ unused = 456
 
     // Verify the warnings.
     let output = read_output(gctx);
-    let expected = "\
-warning: unused config key `S.unused` in `[..]/.cargo/config.toml`
-";
-    compare::assert_match_exact(expected, &output);
+    let expected = str![[r#"
+[WARNING] unused config key `S.unused` in `[ROOT]/.cargo/config.toml`
+
+"#]];
+    assert_e2e().eq(&output, expected);
 }
 
 #[cargo_test]
@@ -824,7 +830,8 @@ Caused by:
   |
 1 | asdf
   |     ^
-expected `.`, `=`",
+expected `.`, `=`
+",
     );
 }
 
@@ -1630,7 +1637,7 @@ fn all_profile_options() {
     let profile_toml = toml::to_string(&profile).unwrap();
     let roundtrip: cargo_toml::TomlProfile = toml::from_str(&profile_toml).unwrap();
     let roundtrip_toml = toml::to_string(&roundtrip).unwrap();
-    compare::assert_match_exact(&profile_toml, &roundtrip_toml);
+    assert_e2e().eq(&roundtrip_toml, &profile_toml);
 }
 
 #[cargo_test]
diff --git a/tests/testsuite/config_cli.rs b/tests/testsuite/config_cli.rs
index 4fb8e419b47..cf6a52fedf4 100644
--- a/tests/testsuite/config_cli.rs
+++ b/tests/testsuite/config_cli.rs
@@ -4,8 +4,9 @@ use super::config::{
     assert_error, read_output, write_config_at, write_config_toml, GlobalContextBuilder,
 };
 use cargo::util::context::Definition;
-use cargo_test_support::compare;
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::paths;
+use cargo_test_support::str;
 use std::{collections::HashMap, fs};
 
 #[cargo_test]
@@ -348,10 +349,11 @@ fn unused_key() {
 
     gctx.build_config().unwrap();
     let output = read_output(gctx);
-    let expected = "\
-warning: unused config key `build.unused` in `--config cli option`
-";
-    compare::assert_match_exact(expected, &output);
+    let expected = str![[r#"
+[WARNING] unused config key `build.unused` in `--config cli option`
+
+"#]];
+    assert_e2e().eq(&output, expected);
 }
 
 #[cargo_test]
diff --git a/tests/testsuite/config_include.rs b/tests/testsuite/config_include.rs
index 583fc0e942f..cc133ae587b 100644
--- a/tests/testsuite/config_include.rs
+++ b/tests/testsuite/config_include.rs
@@ -182,8 +182,7 @@ fn wrong_file_extension() {
 could not load Cargo configuration
 
 Caused by:
-  expected a config include path ending with `.toml`, but found `config.png` from `[..]/.cargo/config.toml`
-",
+  expected a config include path ending with `.toml`, but found `config.png` from `[ROOT]/.cargo/config.toml`",
     );
 }
 
diff --git a/tests/testsuite/dep_info.rs b/tests/testsuite/dep_info.rs
index f53f002ecb4..4aada7da7af 100644
--- a/tests/testsuite/dep_info.rs
+++ b/tests/testsuite/dep_info.rs
@@ -1,9 +1,10 @@
 //! Tests for dep-info files. This includes the dep-info file Cargo creates in
 //! the output directory, and the ones stored in the fingerprint.
 
-use cargo_test_support::compare::assert_match_exact;
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::paths::{self, CargoPathExt};
 use cargo_test_support::registry::Package;
+use cargo_test_support::str;
 use cargo_test_support::{
     basic_bin_manifest, basic_manifest, main_file, project, rustc_host, Project,
 };
@@ -593,8 +594,11 @@ fn non_local_build_script() {
 
     p.cargo("build").run();
     let contents = p.read_file("target/debug/foo.d");
-    assert_match_exact(
-        "[ROOT]/foo/target/debug/foo[EXE]: [ROOT]/foo/src/main.rs",
+    assert_e2e().eq(
         &contents,
+        str![[r#"
+[ROOT]/foo/target/debug/foo[EXE]: [ROOT]/foo/src/main.rs
+
+"#]],
     );
 }
diff --git a/tests/testsuite/fix.rs b/tests/testsuite/fix.rs
index 6ebf09e7be0..58c897df5d4 100644
--- a/tests/testsuite/fix.rs
+++ b/tests/testsuite/fix.rs
@@ -1,10 +1,11 @@
 //! Tests for the `cargo fix` command.
 
 use cargo::core::Edition;
-use cargo_test_support::compare::assert_match_exact;
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::git::{self, init};
 use cargo_test_support::paths::{self, CargoPathExt};
 use cargo_test_support::registry::{Dependency, Package};
+use cargo_test_support::str;
 use cargo_test_support::tools;
 use cargo_test_support::{basic_manifest, is_nightly, project};
 
@@ -1537,9 +1538,9 @@ fn fix_shared_cross_workspace() {
         )
         .run();
 
-    assert_match_exact(
-        "pub fn fixme(_x: Box<&dyn Fn() -> ()>) {}",
+    assert_e2e().eq(
         &p.read_file("foo/src/shared.rs"),
+        str!["pub fn fixme(_x: Box<&dyn Fn() -> ()>) {}"],
     );
 }
 
diff --git a/tests/testsuite/install.rs b/tests/testsuite/install.rs
index ef3f4f3ae60..a2f2a7b021a 100644
--- a/tests/testsuite/install.rs
+++ b/tests/testsuite/install.rs
@@ -5,10 +5,11 @@ use std::io::prelude::*;
 use std::path::Path;
 use std::thread;
 
-use cargo_test_support::compare;
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::cross_compile;
 use cargo_test_support::git;
 use cargo_test_support::registry::{self, registry_path, Package};
+use cargo_test_support::str;
 use cargo_test_support::{
     basic_manifest, cargo_process, no_such_file_err_msg, project, project_in, symlink_supported, t,
 };
@@ -2296,26 +2297,23 @@ fn failed_install_retains_temp_directory() {
     let err = cargo_process("install foo").exec_with_output().unwrap_err();
     let err = err.downcast::<ProcessError>().unwrap();
     let stderr = String::from_utf8(err.stderr.unwrap()).unwrap();
-    compare::match_contains(
-        "\
+    assert_e2e().eq(&stderr, str![[r#"
 [UPDATING] `dummy-registry` index
 [DOWNLOADING] crates ...
 [DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
 [INSTALLING] foo v0.0.1
 [COMPILING] foo v0.0.1
-",
-        &stderr,
-        None,
-    )
-    .unwrap();
-    compare::match_contains(
-        "error: failed to compile `foo v0.0.1`, intermediate artifacts can be found at \
-        `[..]`.\nTo reuse those artifacts with a future compilation, set the environment \
-        variable `CARGO_TARGET_DIR` to that path.",
-        &stderr,
-        None,
-    )
-    .unwrap();
+[ERROR] expected one of `!` or `::`, found `<eof>`
+ --> [ROOT]/home/.cargo/registry/src/-[..]/foo-0.0.1/src/main.rs:1:1
+  |
+1 | x
+  | ^ expected one of `!` or `::`
+
+[ERROR] could not compile `foo` (bin "foo") due to 1 previous error
+[ERROR] failed to compile `foo v0.0.1`, intermediate artifacts can be found at `[..]`.
+To reuse those artifacts with a future compilation, set the environment variable `CARGO_TARGET_DIR` to that path.
+
+"#]]);
 
     // Find the path in the output.
     let stderr = stderr.split_once("found at `").unwrap().1;
@@ -2354,39 +2352,39 @@ fn sparse_install() {
     assert_has_installed_exe(cargo_home(), "foo");
     let assert_v1 = |expected| {
         let v1 = fs::read_to_string(paths::home().join(".cargo/.crates.toml")).unwrap();
-        compare::assert_match_exact(expected, &v1);
+        assert_e2e().eq(&v1, expected);
     };
-    assert_v1(
-        r#"[v1]
+    assert_v1(str![[r#"
+[v1]
 "foo 0.0.1 (sparse+http://127.0.0.1:[..]/index/)" = ["foo[EXE]"]
-"#,
-    );
+
+"#]]);
     cargo_process("install bar").run();
     assert_has_installed_exe(cargo_home(), "bar");
-    assert_v1(
-        r#"[v1]
+    assert_v1(str![[r#"
+[v1]
 "bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = ["bar[EXE]"]
 "foo 0.0.1 (sparse+http://127.0.0.1:[..]/index/)" = ["foo[EXE]"]
-"#,
-    );
+
+"#]]);
 
     cargo_process("uninstall bar")
         .with_stderr("[REMOVING] [CWD]/home/.cargo/bin/bar[EXE]")
         .run();
     assert_has_not_installed_exe(cargo_home(), "bar");
-    assert_v1(
-        r#"[v1]
+    assert_v1(str![[r#"
+[v1]
 "foo 0.0.1 (sparse+http://127.0.0.1:[..]/index/)" = ["foo[EXE]"]
-"#,
-    );
+
+"#]]);
     cargo_process("uninstall foo")
         .with_stderr("[REMOVING] [CWD]/home/.cargo/bin/foo[EXE]")
         .run();
     assert_has_not_installed_exe(cargo_home(), "foo");
-    assert_v1(
-        r#"[v1]
-"#,
-    );
+    assert_v1(str![[r#"
+[v1]
+
+"#]]);
 }
 
 #[cargo_test]
diff --git a/tests/testsuite/lockfile_compat.rs b/tests/testsuite/lockfile_compat.rs
index bb724f94305..28bd2cb2b65 100644
--- a/tests/testsuite/lockfile_compat.rs
+++ b/tests/testsuite/lockfile_compat.rs
@@ -1,8 +1,9 @@
 //! Tests for supporting older versions of the Cargo.lock file format.
 
-use cargo_test_support::compare::assert_match_exact;
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::git;
 use cargo_test_support::registry::Package;
+use cargo_test_support::str;
 use cargo_test_support::{basic_lib_manifest, basic_manifest, project};
 
 #[cargo_test]
@@ -16,7 +17,8 @@ fn oldest_lockfile_still_works() {
 fn oldest_lockfile_still_works_with_command(cargo_command: &str) {
     Package::new("bar", "0.1.0").publish();
 
-    let expected_lockfile = r#"# This file is automatically @generated by Cargo.
+    let expected_lockfile = str![[r##"
+# This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
 version = 3
 
@@ -32,7 +34,8 @@ version = "0.0.1"
 dependencies = [
  "bar",
 ]
-"#;
+
+"##]];
 
     let old_lockfile = r#"
 [root]
@@ -69,7 +72,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
     p.cargo(cargo_command).run();
 
     let lock = p.read_lockfile();
-    assert_match_exact(expected_lockfile, &lock);
+    assert_e2e().eq(&lock, expected_lockfile);
 }
 
 #[cargo_test]
@@ -116,7 +119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
     p.cargo("check --locked").run();
 
     let lock = p.read_lockfile();
-    assert_match_exact(&old_lockfile, &lock);
+    assert_e2e().eq(&lock, &old_lockfile);
 }
 
 #[cargo_test]
@@ -164,8 +167,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
     p.cargo("check").run();
 
     let lock = p.read_lockfile();
-    assert_match_exact(
-        r#"# This file is automatically @generated by Cargo.
+    assert_e2e().eq(
+        &lock,
+        str![[r##"
+# This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
 version = 3
 
@@ -181,8 +186,8 @@ version = "0.0.1"
 dependencies = [
  "bar",
 ]
-"#,
-        &lock,
+
+"##]],
     );
 }
 
@@ -409,24 +414,26 @@ fn current_lockfile_format() {
 
     let actual = p.read_lockfile();
 
-    let expected = "\
-# This file is automatically @generated by Cargo.\n# It is not intended for manual editing.
+    let expected = str![[r##"
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
 version = 3
 
 [[package]]
-name = \"bar\"
-version = \"0.1.0\"
-source = \"registry+https://github.com/rust-lang/crates.io-index\"
-checksum = \"[..]\"
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "[..]"
 
 [[package]]
-name = \"foo\"
-version = \"0.0.1\"
+name = "foo"
+version = "0.0.1"
 dependencies = [
- \"bar\",
+ "bar",
 ]
-";
-    assert_match_exact(expected, &actual);
+
+"##]];
+    assert_e2e().eq(&actual, expected);
 }
 
 #[cargo_test]
@@ -471,9 +478,11 @@ dependencies = [
     p.cargo("check").run();
 
     let lock = p.read_lockfile();
-    assert_match_exact(
-        r#"# [..]
-# [..]
+    assert_e2e().eq(
+        &lock,
+        str![[r##"
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
 version = 3
 
 [[package]]
@@ -488,8 +497,8 @@ version = "0.0.1"
 dependencies = [
  "bar",
 ]
-"#,
-        &lock,
+
+"##]],
     );
 }
 
@@ -571,7 +580,7 @@ dependencies = [
     p.cargo("fetch").run();
 
     let lock = p.read_lockfile();
-    assert_match_exact(&lockfile, &lock);
+    assert_e2e().eq(&lock, &lockfile);
 }
 
 #[cargo_test]
@@ -644,7 +653,7 @@ dependencies = [
     p.cargo("fetch").run();
 
     let lock = p.read_lockfile();
-    assert_match_exact(&lockfile, &lock);
+    assert_e2e().eq(&lock, &lockfile);
 }
 
 #[cargo_test]
@@ -664,7 +673,7 @@ version = 3
 [[package]]
 name = "dep1"
 version = "0.5.0"
-source = "git+{}?branch=master#{}"
+source = "git+[ROOTURL]/dep1?branch=master#{}"
 
 [[package]]
 name = "foo"
@@ -673,7 +682,6 @@ dependencies = [
  "dep1",
 ]
 "#,
-        git_project.url(),
         head_id,
     );
 
@@ -701,7 +709,7 @@ dependencies = [
     p.cargo("fetch").run();
 
     let lock = p.read_lockfile();
-    assert_match_exact(&lockfile, &lock);
+    assert_e2e().eq(&lock, &lockfile);
 }
 
 #[cargo_test]
@@ -982,7 +990,7 @@ version = "0.0.1"
 "#;
 
     let lock = p.read_lockfile();
-    assert_match_exact(lockfile, &lock);
+    assert_e2e().eq(&lock, lockfile);
 }
 
 fn create_branch(repo: &git2::Repository, branch: &str, head_id: git2::Oid) {
@@ -1021,7 +1029,7 @@ version = 3
 [[package]]
 name = "dep1"
 version = "0.5.0"
-source = "git+{url}?{ref_kind}={git_ref}#{head_id}"
+source = "git+[ROOTURL]/dep1?{ref_kind}={git_ref}#{head_id}"
 
 [[package]]
 name = "foo"
@@ -1066,7 +1074,7 @@ dependencies = [
         .run();
 
     let lock = p.read_lockfile();
-    assert_match_exact(&lockfile, &lock);
+    assert_e2e().eq(&lock, &lockfile);
 
     // v3 doesn't URL-encode URL parameters, but `url` crate does decode as it
     // was URL-encoded. Therefore Cargo thinks they are from different source
@@ -1117,7 +1125,7 @@ version = 4
 [[package]]
 name = "dep1"
 version = "0.5.0"
-source = "git+{url}?{ref_kind}={encoded_ref}#{head_id}"
+source = "git+[ROOTURL]/dep1?{ref_kind}={encoded_ref}#{head_id}"
 
 [[package]]
 name = "foo"
@@ -1162,7 +1170,7 @@ dependencies = [
         .run();
 
     let lock = p.read_lockfile();
-    assert_match_exact(&lockfile, &lock);
+    assert_e2e().eq(&lock, &lockfile);
 
     // Unlike v3_and_git_url_encoded, v4 encodes URL parameters so no git
     // repository re-clone happen.
diff --git a/tests/testsuite/pkgid.rs b/tests/testsuite/pkgid.rs
index 05bf9e66fa0..8bbaa03383e 100644
--- a/tests/testsuite/pkgid.rs
+++ b/tests/testsuite/pkgid.rs
@@ -1,10 +1,11 @@
 //! Tests for the `cargo pkgid` command.
 
 use cargo_test_support::basic_lib_manifest;
-use cargo_test_support::compare;
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::git;
 use cargo_test_support::project;
 use cargo_test_support::registry::Package;
+use cargo_test_support::str;
 
 #[cargo_test]
 fn local() {
@@ -305,7 +306,7 @@ fn pkgid_json_message_metadata_consistency() {
     let output = p.cargo("pkgid").arg("foo").exec_with_output().unwrap();
     let pkgid = String::from_utf8(output.stdout).unwrap();
     let pkgid = pkgid.trim();
-    compare::assert_match_exact("path+file://[..]/foo#0.5.0", &pkgid);
+    assert_e2e().eq(pkgid, str!["path+[ROOTURL]/foo#0.5.0"]);
 
     p.cargo("check --message-format=json")
         .with_json(
diff --git a/tests/testsuite/profile_trim_paths.rs b/tests/testsuite/profile_trim_paths.rs
index fbdec2a6919..58a3fd15d5d 100644
--- a/tests/testsuite/profile_trim_paths.rs
+++ b/tests/testsuite/profile_trim_paths.rs
@@ -1,10 +1,12 @@
 //! Tests for `-Ztrim-paths`.
 
 use cargo_test_support::basic_manifest;
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::git;
 use cargo_test_support::paths;
 use cargo_test_support::project;
 use cargo_test_support::registry::Package;
+use cargo_test_support::str;
 
 #[cargo_test]
 fn gated_manifest() {
@@ -711,7 +713,6 @@ fn custom_build_env_var_trim_paths() {
 #[cfg(unix)]
 #[cargo_test(requires_lldb, nightly, reason = "-Zremap-path-scope is unstable")]
 fn lldb_works_after_trimmed() {
-    use cargo_test_support::compare::match_contains;
     use cargo_util::is_ci;
 
     if !is_ci() {
@@ -770,14 +771,17 @@ fn lldb_works_after_trimmed() {
     let bin_path = p.bin("foo");
     assert!(bin_path.is_file());
     let stdout = String::from_utf8(run_lldb(bin_path).stdout).unwrap();
-    match_contains("[..]stopped[..]", &stdout, None).unwrap();
-    match_contains("[..]stop reason = breakpoint[..]", &stdout, None).unwrap();
-    match_contains(
-        "\
-(lldb) continue
-Hello, Ferris!",
+    assert_e2e().eq(
         &stdout,
-        None,
-    )
-    .unwrap();
+        str![[r#"
+...
+[..]stopped[..]
+[..]stop reason = breakpoint 1.1[..]
+...
+(lldb) continue
+Hello, Ferris!
+...
+
+"#]],
+    );
 }
diff --git a/tests/testsuite/registry_auth.rs b/tests/testsuite/registry_auth.rs
index 1ac1ba28444..12c00998134 100644
--- a/tests/testsuite/registry_auth.rs
+++ b/tests/testsuite/registry_auth.rs
@@ -1,7 +1,8 @@
 //! Tests for registry authentication.
 
-use cargo_test_support::compare::match_contains;
+use cargo_test_support::compare::assert_e2e;
 use cargo_test_support::registry::{Package, RegistryBuilder, Token};
+use cargo_test_support::str;
 use cargo_test_support::{project, Execs, Project};
 
 fn cargo(p: &Project, s: &str) -> Execs {
@@ -473,23 +474,14 @@ fn token_not_logged() {
         .exec_with_output()
         .unwrap();
     let log = String::from_utf8(output.stderr).unwrap();
-    let lines = "\
-[UPDATING] crates.io index
-[PACKAGING] foo v0.1.0 [..]
-[VERIFYING] foo v0.1.0 [..]
-[DOWNLOADING] crates ...
-[DOWNLOADED] bar v1.0.0
-[COMPILING] bar v1.0.0
-[COMPILING] foo v0.1.0 [..]
-[FINISHED] [..]
-[PACKAGED] 3 files[..]
-[UPLOADING] foo v0.1.0[..]
-[UPLOADED] foo v0.1.0 to registry `crates-io`
-[NOTE] waiting [..]
-";
-    for line in lines.lines() {
-        match_contains(line, &log, None).unwrap();
-    }
+    assert_e2e().eq(
+        &log,
+        str![[r#"
+...
+[PUBLISHED] foo v0.1.0 at registry `crates-io`
+
+"#]],
+    );
     let authorizations: Vec<_> = log
         .lines()
         .filter(|line| {