#
cargo_test_support/
lib.rs

    1//! # Cargo test support.
    2//!
    3//! See <https://rust-lang.github.io/cargo/contrib/> for a guide on writing tests.
    4//!
    5//! There are two places you can find API documentation
    6//!
    7//! - <https://docs.rs/cargo-test-support>:
    8//!   targeted at external tool developers testing cargo-related code
    9//!   - Released with every rustc release
    10//! - <https://doc.rust-lang.org/nightly/nightly-rustc/cargo_test_support>:
    11//!   targeted at cargo contributors
    12//!   - Updated on each update of the `cargo` submodule in `rust-lang/rust`
    13//!
    14//! > This crate is maintained by the Cargo team, primarily for use by Cargo
    15//! > and not intended for external use. This
    16//! > crate may make major changes to its APIs or be deprecated without warning.
    17//!
    18//! # Example
    19//!
    20//! ```rust,no_run
    21//! use cargo_test_support::prelude::*;
    22//! use cargo_test_support::str;
    23//! use cargo_test_support::project;
    24//!
    25//! #[cargo_test]
    26//! fn some_test() {
    27//!     let p = project()
    28//!         .file("src/main.rs", r#"fn main() { println!("hi!"); }"#)
    29//!         .build();
    30//!
    31//!     p.cargo("run --bin foo")
    32//!         .with_stderr_data(str![[r#"
    33//! [COMPILING] foo [..]
    34//! [FINISHED] [..]
    35//! [RUNNING] `target/debug/foo`
    36//! "#]])
    37//!         .with_stdout_data(str![["hi!"]])
    38//!         .run();
    39//! }
    40//! ```
    41
    42#![allow(clippy::disallowed_methods)]
    43#![allow(clippy::print_stderr)]
    44#![allow(clippy::print_stdout)]
    45
    46use std::env;
    47use std::ffi::OsStr;
    48use std::fmt::Write;
    49use std::fs;
    50use std::os;
    51use std::path::{Path, PathBuf};
    52use std::process::{Command, Output};
    53use std::sync::LazyLock;
    54use std::sync::OnceLock;
    55use std::thread::JoinHandle;
    56use std::time::{self, Duration};
    57
    58use anyhow::{Result, bail};
    59use cargo_util::{ProcessError, is_ci};
    60use snapbox::IntoData as _;
    61use url::Url;
    62
    63use self::paths::CargoPathExt;
    64
    65/// Unwrap a `Result` with a useful panic message
    66///
    67/// # Example
    68///
    69/// ```rust
    70/// use cargo_test_support::t;
    71/// t!(std::fs::read_to_string("Cargo.toml"));
    72/// ```
    73#[macro_export]
    74macro_rules! t {
    75    ($e:expr) => {
    76        match $e {
    77            Ok(e) => e,
    78            Err(e) => $crate::panic_error(&format!("failed running {}", stringify!($e)), e),
    79        }
    80    };
    81}
    82
    83pub use cargo_util::ProcessBuilder;
    84pub use snapbox::file;
    85pub use snapbox::str;
    86pub use snapbox::utils::current_dir;
    87
    88/// `panic!`, reporting the specified error , see also [`t!`]
    89#[track_caller]
    90pub fn panic_error(what: &str, err: impl Into<anyhow::Error>) -> ! {
    91    let err = err.into();
    92    pe(what, err);
    93    #[track_caller]
    94    fn pe(what: &str, err: anyhow::Error) -> ! {
    95        let mut result = format!("{}\nerror: {}", what, err);
    96        for cause in err.chain().skip(1) {
    97            let _ = writeln!(result, "\nCaused by:");
    98            let _ = write!(result, "{}", cause);
    99        }
    100        panic!("\n{}", result);
    101    }
    102}
    103
    104pub use cargo_test_macro::cargo_test;
    105
    106pub mod compare;
    107pub mod containers;
    108pub mod cross_compile;
    109pub mod git;
    110pub mod install;
    111pub mod paths;
    112pub mod publish;
    113pub mod registry;
    114
    115pub mod prelude {
    116    pub use crate::ArgLineCommandExt;
    117    pub use crate::ChannelChangerCommandExt;
    118    pub use crate::TestEnvCommandExt;
    119    pub use crate::cargo_test;
    120    pub use crate::paths::CargoPathExt;
    121    pub use snapbox::IntoData;
    122}
    123
    124/*
    125 *
    126 * ===== Builders =====
    127 *
    128 */
    129
    130#[derive(PartialEq, Clone)]
    131struct FileBuilder {
    132    path: PathBuf,
    133    body: String,
    134    executable: bool,
    135}
    136
    137impl FileBuilder {
    138    pub fn new(path: PathBuf, body: &str, executable: bool) -> FileBuilder {
    139        FileBuilder {
    140            path,
    141            body: body.to_string(),
    142            executable: executable,
    143        }
    144    }
    145
    146    fn mk(&mut self) {
    147        if self.executable {
    148            let mut path = self.path.clone().into_os_string();
    149            write!(path, "{}", env::consts::EXE_SUFFIX).unwrap();
    150            self.path = path.into();
    151        }
    152
    153        self.dirname().mkdir_p();
    154        fs::write(&self.path, &self.body)
    155            .unwrap_or_else(|e| panic!("could not create file {}: {}", self.path.display(), e));
    156
    157        #[cfg(unix)]
    158        if self.executable {
    159            use std::os::unix::fs::PermissionsExt;
    160
    161            let mut perms = fs::metadata(&self.path).unwrap().permissions();
    162            let mode = perms.mode();
    163            perms.set_mode(mode | 0o111);
    164            fs::set_permissions(&self.path, perms).unwrap();
    165        }
    166    }
    167
    168    fn dirname(&self) -> &Path {
    169        self.path.parent().unwrap()
    170    }
    171}
    172
    173#[derive(PartialEq, Clone)]
    174struct SymlinkBuilder {
    175    dst: PathBuf,
    176    src: PathBuf,
    177    src_is_dir: bool,
    178}
    179
    180impl SymlinkBuilder {
    181    pub fn new(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
    182        SymlinkBuilder {
    183            dst,
    184            src,
    185            src_is_dir: false,
    186        }
    187    }
    188
    189    pub fn new_dir(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
    190        SymlinkBuilder {
    191            dst,
    192            src,
    193            src_is_dir: true,
    194        }
    195    }
    196
    197    #[cfg(unix)]
    198    fn mk(&self) {
    199        self.dirname().mkdir_p();
    200        t!(os::unix::fs::symlink(&self.dst, &self.src));
    201    }
    202
    203    #[cfg(windows)]
    204    fn mk(&mut self) {
    205        self.dirname().mkdir_p();
    206        if self.src_is_dir {
    207            t!(os::windows::fs::symlink_dir(&self.dst, &self.src));
    208        } else {
    209            if let Some(ext) = self.dst.extension() {
    210                if ext == env::consts::EXE_EXTENSION {
    211                    self.src.set_extension(ext);
    212                }
    213            }
    214            t!(os::windows::fs::symlink_file(&self.dst, &self.src));
    215        }
    216    }
    217
    218    fn dirname(&self) -> &Path {
    219        self.src.parent().unwrap()
    220    }
    221}
    222
    223/// A cargo project to run tests against.
    224///
    225/// See [`ProjectBuilder`] or [`Project::from_template`] to get started.
    226pub struct Project {
    227    root: PathBuf,
    228}
    229
    230/// Create a project to run tests against
    231///
    232/// - Creates a [`basic_manifest`] if one isn't supplied
    233///
    234/// To get started, see:
    235/// - [`project`]
    236/// - [`project_in`]
    237/// - [`project_in_home`]
    238/// - [`Project::from_template`]
    239#[must_use]
    240pub struct ProjectBuilder {
    241    root: Project,
    242    files: Vec<FileBuilder>,
    243    symlinks: Vec<SymlinkBuilder>,
    244    no_manifest: bool,
    245}
    246
    247impl ProjectBuilder {
    248    /// Root of the project
    249    ///
    250    /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo`
    251    pub fn root(&self) -> PathBuf {
    252        self.root.root()
    253    }
    254
    255    /// Project's debug dir
    256    ///
    257    /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug`
    258    pub fn target_debug_dir(&self) -> PathBuf {
    259        self.root.target_debug_dir()
    260    }
    261
    262    /// Create project in `root`
    263    pub fn new(root: PathBuf) -> ProjectBuilder {
    264        ProjectBuilder {
    265            root: Project { root },
    266            files: vec![],
    267            symlinks: vec![],
    268            no_manifest: false,
    269        }
    270    }
    271
    272    /// Create project, relative to [`paths::root`]
    273    pub fn at<P: AsRef<Path>>(mut self, path: P) -> Self {
    274        self.root = Project {
    275            root: paths::root().join(path),
    276        };
    277        self
    278    }
    279
    280    /// Adds a file to the project.
    281    pub fn file<B: AsRef<Path>>(mut self, path: B, body: &str) -> Self {
    282        self._file(path.as_ref(), body, false);
    283        self
    284    }
    285
    286    /// Adds an executable file to the project.
    287    pub fn executable<B: AsRef<Path>>(mut self, path: B, body: &str) -> Self {
    288        self._file(path.as_ref(), body, true);
    289        self
    290    }
    291
    292    fn _file(&mut self, path: &Path, body: &str, executable: bool) {
    293        self.files.push(FileBuilder::new(
    294            self.root.root().join(path),
    295            body,
    296            executable,
    297        ));
    298    }
    299
    300    /// Adds a symlink to a file to the project.
    301    pub fn symlink(mut self, dst: impl AsRef<Path>, src: impl AsRef<Path>) -> Self {
    302        self.symlinks.push(SymlinkBuilder::new(
    303            self.root.root().join(dst),
    304            self.root.root().join(src),
    305        ));
    306        self
    307    }
    308
    309    /// Create a symlink to a directory
    310    pub fn symlink_dir(mut self, dst: impl AsRef<Path>, src: impl AsRef<Path>) -> Self {
    311        self.symlinks.push(SymlinkBuilder::new_dir(
    312            self.root.root().join(dst),
    313            self.root.root().join(src),
    314        ));
    315        self
    316    }
    317
    318    pub fn no_manifest(mut self) -> Self {
    319        self.no_manifest = true;
    320        self
    321    }
    322
    323    /// Creates the project.
    324    pub fn build(mut self) -> Project {
    325        // First, clean the directory if it already exists
    326        self.rm_root();
    327
    328        // Create the empty directory
    329        self.root.root().mkdir_p();
    330
    331        let manifest_path = self.root.root().join("Cargo.toml");
    332        if !self.no_manifest && self.files.iter().all(|fb| fb.path != manifest_path) {
    333            self._file(
    334                Path::new("Cargo.toml"),
    335                &basic_manifest("foo", "0.0.1"),
    336                false,
    337            )
    338        }
    339
    340        let past = time::SystemTime::now() - Duration::new(1, 0);
    341        let ftime = filetime::FileTime::from_system_time(past);
    342
    343        for file in self.files.iter_mut() {
    344            file.mk();
    345            if is_coarse_mtime() {
    346                // Place the entire project 1 second in the past to ensure
    347                // that if cargo is called multiple times, the 2nd call will
    348                // see targets as "fresh". Without this, if cargo finishes in
    349                // under 1 second, the second call will see the mtime of
    350                // source == mtime of output and consider it dirty.
    351                filetime::set_file_times(&file.path, ftime, ftime).unwrap();
    352            }
    353        }
    354
    355        for symlink in self.symlinks.iter_mut() {
    356            symlink.mk();
    357        }
    358
    359        let ProjectBuilder { root, .. } = self;
    360        root
    361    }
    362
    363    fn rm_root(&self) {
    364        self.root.root().rm_rf()
    365    }
    366}
    367
    368impl Project {
    369    /// Copy the test project from a fixed state
    370    pub fn from_template(template_path: impl AsRef<Path>) -> Self {
    371        let root = paths::root();
    372        let project_root = root.join("case");
    373        snapbox::dir::copy_template(template_path.as_ref(), &project_root).unwrap();
    374        Self { root: project_root }
    375    }
    376
    377    /// Root of the project
    378    ///
    379    /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo`
    380    pub fn root(&self) -> PathBuf {
    381        self.root.clone()
    382    }
    383
    384    /// Project's target dir
    385    ///
    386    /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target`
    387    pub fn build_dir(&self) -> PathBuf {
    388        self.root().join("target")
    389    }
    390
    391    /// Project's debug dir
    392    ///
    393    /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug`
    394    pub fn target_debug_dir(&self) -> PathBuf {
    395        self.build_dir().join("debug")
    396    }
    397
    398    /// File url for root
    399    ///
    400    /// ex: `file://$CARGO_TARGET_TMPDIR/cit/t0/foo`
    401    pub fn url(&self) -> Url {
    402        use paths::CargoPathExt;
    403        self.root().to_url()
    404    }
    405
    406    /// Path to an example built as a library.
    407    ///
    408    /// `kind` should be one of: "lib", "rlib", "staticlib", "dylib", "proc-macro"
    409    ///
    410    /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug/examples/libex.rlib`
    411    pub fn example_lib(&self, name: &str, kind: &str) -> PathBuf {
    412        self.target_debug_dir()
    413            .join("examples")
    414            .join(paths::get_lib_filename(name, kind))
    415    }
    416
    417    /// Path to a dynamic library.
    418    /// ex: `/path/to/cargo/target/cit/t0/foo/target/debug/examples/libex.dylib`
    419    pub fn dylib(&self, name: &str) -> PathBuf {
    420        self.target_debug_dir().join(format!(
    421            "{}{name}{}",
    422            env::consts::DLL_PREFIX,
    423            env::consts::DLL_SUFFIX
    424        ))
    425    }
    426
    427    /// Path to a debug binary.
    428    ///
    429    /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug/foo`
    430    pub fn bin(&self, b: &str) -> PathBuf {
    431        self.build_dir()
    432            .join("debug")
    433            .join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
    434    }
    435
    436    /// Path to a release binary.
    437    ///
    438    /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/release/foo`
    439    pub fn release_bin(&self, b: &str) -> PathBuf {
    440        self.build_dir()
    441            .join("release")
    442            .join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
    443    }
    444
    445    /// Path to a debug binary for a specific target triple.
    446    ///
    447    /// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/i686-apple-darwin/debug/foo`
    448    pub fn target_bin(&self, target: &str, b: &str) -> PathBuf {
    449        self.build_dir().join(target).join("debug").join(&format!(
    450            "{}{}",
    451            b,
    452            env::consts::EXE_SUFFIX
    453        ))
    454    }
    455
    456    /// Returns an iterator of paths within [`Project::root`] matching the glob pattern
    457    pub fn glob<P: AsRef<Path>>(&self, pattern: P) -> glob::Paths {
    458        let pattern = self.root().join(pattern);
    459        glob::glob(pattern.to_str().expect("failed to convert pattern to str"))
    460            .expect("failed to glob")
    461    }
    462
    463    /// Overwrite a file with new content
    464    ///
    465    // # Example:
    466    ///
    467    /// ```no_run
    468    /// # let p = cargo_test_support::project().build();
    469    /// p.change_file("src/lib.rs", "fn new_fn() {}");
    470    /// ```
    471    pub fn change_file(&self, path: impl AsRef<Path>, body: &str) {
    472        FileBuilder::new(self.root().join(path), body, false).mk()
    473    }
    474
    475    /// Creates a `ProcessBuilder` to run a program in the project
    476    /// and wrap it in an Execs to assert on the execution.
    477    ///
    478    /// # Example:
    479    ///
    480    /// ```no_run
    481    /// # use cargo_test_support::str;
    482    /// # let p = cargo_test_support::project().build();
    483    /// p.process(&p.bin("foo"))
    484    ///     .with_stdout_data(str!["bar\n"])
    485    ///     .run();
    486    /// ```
    487    pub fn process<T: AsRef<OsStr>>(&self, program: T) -> Execs {
    488        let mut p = process(program);
    489        p.cwd(self.root());
    490        execs().with_process_builder(p)
    491    }
    492
    493    /// Safely run a process after `cargo build`.
    494    ///
    495    /// Windows has a problem where a process cannot be reliably
    496    /// be replaced, removed, or renamed immediately after executing it.
    497    /// The action may fail (with errors like Access is denied), or
    498    /// it may succeed, but future attempts to use the same filename
    499    /// will fail with "Already Exists".
    500    ///
    501    /// If you have a test that needs to do `cargo run` multiple
    502    /// times, you should instead use `cargo build` and use this
    503    /// method to run the executable. Each time you call this,
    504    /// use a new name for `dst`.
    505    /// See rust-lang/cargo#5481.
    506    pub fn rename_run(&self, src: &str, dst: &str) -> Execs {
    507        let src = self.bin(src);
    508        let dst = self.bin(dst);
    509        fs::rename(&src, &dst)
    510            .unwrap_or_else(|e| panic!("Failed to rename `{:?}` to `{:?}`: {}", src, dst, e));
    511        self.process(dst)
    512    }
    513
    514    /// Returns the contents of `Cargo.lock`.
    515    pub fn read_lockfile(&self) -> String {
    516        self.read_file("Cargo.lock")
    517    }
    518
    519    /// Returns the contents of a path in the project root
    520    pub fn read_file(&self, path: impl AsRef<Path>) -> String {
    521        let full = self.root().join(path);
    522        fs::read_to_string(&full)
    523            .unwrap_or_else(|e| panic!("could not read file {}: {}", full.display(), e))
    524    }
    525
    526    /// Modifies `Cargo.toml` to remove all commented lines.
    527    pub fn uncomment_root_manifest(&self) {
    528        let contents = self.read_file("Cargo.toml").replace("#", "");
    529        fs::write(self.root().join("Cargo.toml"), contents).unwrap();
    530    }
    531
    532    pub fn symlink(&self, src: impl AsRef<Path>, dst: impl AsRef<Path>) {
    533        let src = self.root().join(src.as_ref());
    534        let dst = self.root().join(dst.as_ref());
    535        #[cfg(unix)]
    536        {
    537            if let Err(e) = os::unix::fs::symlink(&src, &dst) {
    538                panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
    539            }
    540        }
    541        #[cfg(windows)]
    542        {
    543            if src.is_dir() {
    544                if let Err(e) = os::windows::fs::symlink_dir(&src, &dst) {
    545                    panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
    546                }
    547            } else {
    548                if let Err(e) = os::windows::fs::symlink_file(&src, &dst) {
    549                    panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
    550                }
    551            }
    552        }
    553    }
    554}
    555
    556/// Generates a project layout, see [`ProjectBuilder`]
    557pub fn project() -> ProjectBuilder {
    558    ProjectBuilder::new(paths::root().join("foo"))
    559}
    560
    561/// Generates a project layout in given directory, see [`ProjectBuilder`]
    562pub fn project_in(dir: impl AsRef<Path>) -> ProjectBuilder {
    563    ProjectBuilder::new(paths::root().join(dir).join("foo"))
    564}
    565
    566/// Generates a project layout inside our fake home dir, see [`ProjectBuilder`]
    567pub fn project_in_home(name: impl AsRef<Path>) -> ProjectBuilder {
    568    ProjectBuilder::new(paths::home().join(name))
    569}
    570
    571// === Helpers ===
    572
    573/// Generate a `main.rs` printing the specified text
    574///
    575/// ```rust
    576/// # use cargo_test_support::main_file;
    577/// # mod dep {
    578/// #     fn bar() -> &'static str {
    579/// #         "world"
    580/// #     }
    581/// # }
    582/// main_file(
    583///     r#""hello {}", dep::bar()"#,
    584///     &[]
    585/// );
    586/// ```
    587pub fn main_file(println: &str, externed_deps: &[&str]) -> String {
    588    let mut buf = String::new();
    589
    590    for dep in externed_deps.iter() {
    591        buf.push_str(&format!("extern crate {};\n", dep));
    592    }
    593
    594    buf.push_str("fn main() { println!(");
    595    buf.push_str(println);
    596    buf.push_str("); }\n");
    597
    598    buf
    599}
    600
    601/// This is the raw output from the process.
    602///
    603/// This is similar to `std::process::Output`, however the `status` is
    604/// translated to the raw `code`. This is necessary because `ProcessError`
    605/// does not have access to the raw `ExitStatus` because `ProcessError` needs
    606/// to be serializable (for the Rustc cache), and `ExitStatus` does not
    607/// provide a constructor.
    608pub struct RawOutput {
    609    pub code: Option<i32>,
    610    pub stdout: Vec<u8>,
    611    pub stderr: Vec<u8>,
    612}
    613
    614/// Run and verify a [`ProcessBuilder`]
    615///
    616/// Construct with
    617/// - [`execs`]
    618/// - [`Project`] methods
    619/// - `cargo_process` in testsuite
    620#[must_use]
    621#[derive(Clone)]
    622pub struct Execs {
    623    ran: bool,
    624    process_builder: Option<ProcessBuilder>,
    625    expect_stdin: Option<String>,
    626    expect_exit_code: Option<i32>,
    627    expect_stdout_data: Option<snapbox::Data>,
    628    expect_stderr_data: Option<snapbox::Data>,
    629    expect_stdout_contains: Vec<String>,
    630    expect_stderr_contains: Vec<String>,
    631    expect_stdout_not_contains: Vec<String>,
    632    expect_stderr_not_contains: Vec<String>,
    633    expect_stderr_with_without: Vec<(Vec<String>, Vec<String>)>,
    634    stream_output: bool,
    635    assert: snapbox::Assert,
    636}
    637
    638impl Execs {
    639    pub fn with_process_builder(mut self, p: ProcessBuilder) -> Execs {
    640        self.process_builder = Some(p);
    641        self
    642    }
    643}
    644
    645/// # Configure assertions
    646impl Execs {
    647    /// Verifies that stdout is equal to the given lines.
    648    ///
    649    /// See [`compare::assert_e2e`] for assertion details.
    650    ///
    651    /// <div class="warning">
    652    ///
    653    /// Prefer passing in [`str!`] for `expected` to get snapshot updating.
    654    ///
    655    /// If `format!` is needed for content that changes from run to run that you don't care about,
    656    /// consider whether you could have [`compare::assert_e2e`] redact the content.
    657    /// If nothing else, a wildcard (`[..]`, `...`) may be useful.
    658    ///
    659    /// However, `""` may be preferred for intentionally empty output so people don't accidentally
    660    /// bless a change.
    661    ///
    662    /// </div>
    663    ///
    664    /// # Examples
    665    ///
    666    /// ```no_run
    667    /// use cargo_test_support::prelude::*;
    668    /// use cargo_test_support::str;
    669    /// use cargo_test_support::execs;
    670    ///
    671    /// execs().with_stdout_data(str![r#"
    672    /// Hello world!
    673    /// "#]);
    674    /// ```
    675    ///
    676    /// Non-deterministic compiler output
    677    /// ```no_run
    678    /// use cargo_test_support::prelude::*;
    679    /// use cargo_test_support::str;
    680    /// use cargo_test_support::execs;
    681    ///
    682    /// execs().with_stdout_data(str![r#"
    683    /// [COMPILING] foo
    684    /// [COMPILING] bar
    685    /// "#].unordered());
    686    /// ```
    687    ///
    688    /// jsonlines
    689    /// ```no_run
    690    /// use cargo_test_support::prelude::*;
    691    /// use cargo_test_support::str;
    692    /// use cargo_test_support::execs;
    693    ///
    694    /// execs().with_stdout_data(str![r#"
    695    /// [
    696    ///   {},
    697    ///   {}
    698    /// ]
    699    /// "#].is_json().against_jsonlines());
    700    /// ```
    701    pub fn with_stdout_data(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
    702        self.expect_stdout_data = Some(expected.into_data());
    703        self
    704    }
    705
    706    /// Verifies that stderr is equal to the given lines.
    707    ///
    708    /// See [`compare::assert_e2e`] for assertion details.
    709    ///
    710    /// <div class="warning">
    711    ///
    712    /// Prefer passing in [`str!`] for `expected` to get snapshot updating.
    713    ///
    714    /// If `format!` is needed for content that changes from run to run that you don't care about,
    715    /// consider whether you could have [`compare::assert_e2e`] redact the content.
    716    /// If nothing else, a wildcard (`[..]`, `...`) may be useful.
    717    ///
    718    /// However, `""` may be preferred for intentionally empty output so people don't accidentally
    719    /// bless a change.
    720    ///
    721    /// </div>
    722    ///
    723    /// # Examples
    724    ///
    725    /// ```no_run
    726    /// use cargo_test_support::prelude::*;
    727    /// use cargo_test_support::str;
    728    /// use cargo_test_support::execs;
    729    ///
    730    /// execs().with_stderr_data(str![r#"
    731    /// Hello world!
    732    /// "#]);
    733    /// ```
    734    ///
    735    /// Non-deterministic compiler output
    736    /// ```no_run
    737    /// use cargo_test_support::prelude::*;
    738    /// use cargo_test_support::str;
    739    /// use cargo_test_support::execs;
    740    ///
    741    /// execs().with_stderr_data(str![r#"
    742    /// [COMPILING] foo
    743    /// [COMPILING] bar
    744    /// "#].unordered());
    745    /// ```
    746    ///
    747    /// jsonlines
    748    /// ```no_run
    749    /// use cargo_test_support::prelude::*;
    750    /// use cargo_test_support::str;
    751    /// use cargo_test_support::execs;
    752    ///
    753    /// execs().with_stderr_data(str![r#"
    754    /// [
    755    ///   {},
    756    ///   {}
    757    /// ]
    758    /// "#].is_json().against_jsonlines());
    759    /// ```
    760    pub fn with_stderr_data(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
    761        self.expect_stderr_data = Some(expected.into_data());
    762        self
    763    }
    764
    765    /// Writes the given lines to stdin.
    766    pub fn with_stdin<S: ToString>(&mut self, expected: S) -> &mut Self {
    767        self.expect_stdin = Some(expected.to_string());
    768        self
    769    }
    770
    771    /// Verifies the exit code from the process.
    772    ///
    773    /// This is not necessary if the expected exit code is `0`.
    774    pub fn with_status(&mut self, expected: i32) -> &mut Self {
    775        self.expect_exit_code = Some(expected);
    776        self
    777    }
    778
    779    /// Removes exit code check for the process.
    780    ///
    781    /// By default, the expected exit code is `0`.
    782    pub fn without_status(&mut self) -> &mut Self {
    783        self.expect_exit_code = None;
    784        self
    785    }
    786
    787    /// Verifies that stdout contains the given contiguous lines somewhere in
    788    /// its output.
    789    ///
    790    /// See [`compare`] for supported patterns.
    791    ///
    792    /// <div class="warning">
    793    ///
    794    /// Prefer [`Execs::with_stdout_data`] where possible.
    795    /// - `expected` cannot be snapshotted
    796    /// - `expected` can end up being ambiguous, causing the assertion to succeed when it should fail
    797    ///
    798    /// </div>
    799    pub fn with_stdout_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
    800        self.expect_stdout_contains.push(expected.to_string());
    801        self
    802    }
    803
    804    /// Verifies that stderr contains the given contiguous lines somewhere in
    805    /// its output.
    806    ///
    807    /// See [`compare`] for supported patterns.
    808    ///
    809    /// <div class="warning">
    810    ///
    811    /// Prefer [`Execs::with_stderr_data`] where possible.
    812    /// - `expected` cannot be snapshotted
    813    /// - `expected` can end up being ambiguous, causing the assertion to succeed when it should fail
    814    ///
    815    /// </div>
    816    pub fn with_stderr_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
    817        self.expect_stderr_contains.push(expected.to_string());
    818        self
    819    }
    820
    821    /// Verifies that stdout does not contain the given contiguous lines.
    822    ///
    823    /// See [`compare`] for supported patterns.
    824    ///
    825    /// See note on [`Self::with_stderr_does_not_contain`].
    826    ///
    827    /// <div class="warning">
    828    ///
    829    /// Prefer [`Execs::with_stdout_data`] where possible.
    830    /// - `expected` cannot be snapshotted
    831    /// - The absence of `expected` can either mean success or that the string being looked for
    832    ///   changed.
    833    ///
    834    /// To mitigate this, consider matching this up with
    835    /// [`Execs::with_stdout_contains`].
    836    ///
    837    /// </div>
    838    pub fn with_stdout_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
    839        self.expect_stdout_not_contains.push(expected.to_string());
    840        self
    841    }
    842
    843    /// Verifies that stderr does not contain the given contiguous lines.
    844    ///
    845    /// See [`compare`] for supported patterns.
    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 either matching this up with
    855    /// [`Execs::with_stdout_contains`] or replace it
    856    /// with [`Execs::with_stderr_line_without`].
    857    ///
    858    /// </div>
    859    pub fn with_stderr_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
    860        self.expect_stderr_not_contains.push(expected.to_string());
    861        self
    862    }
    863
    864    /// Verify that a particular line appears in stderr with and without the
    865    /// given substrings. Exactly one line must match.
    866    ///
    867    /// The substrings are matched as `contains`.
    868    ///
    869    /// <div class="warning">
    870    ///
    871    /// Prefer [`Execs::with_stdout_data`] where possible.
    872    /// - `with` cannot be snapshotted
    873    /// - The absence of `without` can either mean success or that the string being looked for
    874    ///   changed.
    875    ///
    876    /// </div>
    877    ///
    878    /// # Example
    879    ///
    880    /// ```no_run
    881    /// use cargo_test_support::execs;
    882    ///
    883    /// execs().with_stderr_line_without(
    884    ///     &[
    885    ///         "[RUNNING] `rustc --crate-name build_script_build",
    886    ///         "-C opt-level=3",
    887    ///     ],
    888    ///     &["-C debuginfo", "-C incremental"],
    889    /// );
    890    /// ```
    891    ///
    892    /// This will check that a build line includes `-C opt-level=3` but does
    893    /// not contain `-C debuginfo` or `-C incremental`.
    894    ///
    895    pub fn with_stderr_line_without<S: ToString>(
    896        &mut self,
    897        with: &[S],
    898        without: &[S],
    899    ) -> &mut Self {
    900        let with = with.iter().map(|s| s.to_string()).collect();
    901        let without = without.iter().map(|s| s.to_string()).collect();
    902        self.expect_stderr_with_without.push((with, without));
    903        self
    904    }
    905}
    906
    907/// # Configure the process
    908impl Execs {
    909    /// Forward subordinate process stdout/stderr to the terminal.
    910    /// Useful for printf debugging of the tests.
    911    /// CAUTION: CI will fail if you leave this in your test!
    912    #[allow(unused)]
    913    pub fn stream(&mut self) -> &mut Self {
    914        self.stream_output = true;
    915        self
    916    }
    917
    918    pub fn arg<T: AsRef<OsStr>>(&mut self, arg: T) -> &mut Self {
    919        if let Some(ref mut p) = self.process_builder {
    920            p.arg(arg);
    921        }
    922        self
    923    }
    924
    925    pub fn args<T: AsRef<OsStr>>(&mut self, args: &[T]) -> &mut Self {
    926        if let Some(ref mut p) = self.process_builder {
    927            p.args(args);
    928        }
    929        self
    930    }
    931
    932    pub fn cwd<T: AsRef<OsStr>>(&mut self, path: T) -> &mut Self {
    933        if let Some(ref mut p) = self.process_builder {
    934            if let Some(cwd) = p.get_cwd() {
    935                let new_path = cwd.join(path.as_ref());
    936                p.cwd(new_path);
    937            } else {
    938                p.cwd(path);
    939            }
    940        }
    941        self
    942    }
    943
    944    pub fn env<T: AsRef<OsStr>>(&mut self, key: &str, val: T) -> &mut Self {
    945        if let Some(ref mut p) = self.process_builder {
    946            p.env(key, val);
    947        }
    948        self
    949    }
    950
    951    pub fn env_remove(&mut self, key: &str) -> &mut Self {
    952        if let Some(ref mut p) = self.process_builder {
    953            p.env_remove(key);
    954        }
    955        self
    956    }
    957
    958    /// Enables nightly features for testing
    959    ///
    960    /// The list of reasons should be why nightly cargo is needed. If it is
    961    /// because of an unstable feature put the name of the feature as the reason,
    962    /// e.g. `&["print-im-a-teapot"]`
    963    pub fn masquerade_as_nightly_cargo(&mut self, reasons: &[&str]) -> &mut Self {
    964        if let Some(ref mut p) = self.process_builder {
    965            p.masquerade_as_nightly_cargo(reasons);
    966        }
    967        self
    968    }
    969
    970    /// Overrides the crates.io URL for testing.
    971    ///
    972    /// Can be used for testing crates-io functionality where alt registries
    973    /// cannot be used.
    974    pub fn replace_crates_io(&mut self, url: &Url) -> &mut Self {
    975        if let Some(ref mut p) = self.process_builder {
    976            p.env("__CARGO_TEST_CRATES_IO_URL_DO_NOT_USE_THIS", url.as_str());
    977        }
    978        self
    979    }
    980
    981    pub fn overlay_registry(&mut self, url: &Url, path: &str) -> &mut Self {
    982        if let Some(ref mut p) = self.process_builder {
    983            let env_value = format!("{}={}", url, path);
    984            p.env(
    985                "__CARGO_TEST_DEPENDENCY_CONFUSION_VULNERABILITY_DO_NOT_USE_THIS",
    986                env_value,
    987            );
    988        }
    989        self
    990    }
    991
    992    pub fn enable_split_debuginfo_packed(&mut self) -> &mut Self {
    993        self.env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "packed")
    994            .env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "packed")
    995            .env("CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO", "packed")
    996            .env("CARGO_PROFILE_BENCH_SPLIT_DEBUGINFO", "packed");
    997        self
    998    }
    999
    1000    pub fn enable_mac_dsym(&mut self) -> &mut Self {
    1001        if cfg!(target_os = "macos") {
    1002            return self.enable_split_debuginfo_packed();
    1003        }
    1004        self
    1005    }
    1006}
    1007
    1008/// # Run and verify the process
    1009impl Execs {
    1010    pub fn exec_with_output(&mut self) -> Result<Output> {
    1011        self.ran = true;
    1012        // TODO avoid unwrap
    1013        let p = (&self.process_builder).clone().unwrap();
    1014        p.exec_with_output()
    1015    }
    1016
    1017    pub fn build_command(&mut self) -> Command {
    1018        self.ran = true;
    1019        // TODO avoid unwrap
    1020        let p = (&self.process_builder).clone().unwrap();
    1021        p.build_command()
    1022    }
    1023
    1024    #[track_caller]
    1025    pub fn run(&mut self) -> RawOutput {
    1026        self.ran = true;
    1027        let mut p = (&self.process_builder).clone().unwrap();
    1028        if let Some(stdin) = self.expect_stdin.take() {
    1029            p.stdin(stdin);
    1030        }
    1031
    1032        match self.match_process(&p) {
    1033            Err(e) => panic_error(&format!("test failed running {}", p), e),
    1034            Ok(output) => output,
    1035        }
    1036    }
    1037
    1038    /// Runs the process, checks the expected output, and returns the first
    1039    /// JSON object on stdout.
    1040    #[track_caller]
    1041    pub fn run_json(&mut self) -> serde_json::Value {
    1042        let output = self.run();
    1043        serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {
    1044            panic!(
    1045                "\nfailed to parse JSON: {}\n\
    1046                     output was:\n{}\n",
    1047                e,
    1048                String::from_utf8_lossy(&output.stdout)
    1049            );
    1050        })
    1051    }
    1052
    1053    #[track_caller]
    1054    pub fn run_output(&mut self, output: &Output) {
    1055        self.ran = true;
    1056        if let Err(e) = self.match_output(output.status.code(), &output.stdout, &output.stderr) {
    1057            panic_error("process did not return the expected result", e)
    1058        }
    1059    }
    1060
    1061    #[track_caller]
    1062    fn verify_checks_output(&self, stdout: &[u8], stderr: &[u8]) {
    1063        if self.expect_exit_code.unwrap_or(0) != 0
    1064            && self.expect_stdin.is_none()
    1065            && self.expect_stdout_data.is_none()
    1066            && self.expect_stderr_data.is_none()
    1067            && self.expect_stdout_contains.is_empty()
    1068            && self.expect_stderr_contains.is_empty()
    1069            && self.expect_stdout_not_contains.is_empty()
    1070            && self.expect_stderr_not_contains.is_empty()
    1071            && self.expect_stderr_with_without.is_empty()
    1072        {
    1073            panic!(
    1074                "`with_status()` is used, but no output is checked.\n\
    1075                 The test must check the output to ensure the correct error is triggered.\n\
    1076                 --- stdout\n{}\n--- stderr\n{}",
    1077                String::from_utf8_lossy(stdout),
    1078                String::from_utf8_lossy(stderr),
    1079            );
    1080        }
    1081    }
    1082
    1083    #[track_caller]
    1084    fn match_process(&self, process: &ProcessBuilder) -> Result<RawOutput> {
    1085        println!("running {}", process);
    1086        let res = if self.stream_output {
    1087            if is_ci() {
    1088                panic!("`.stream()` is for local debugging")
    1089            }
    1090            process.exec_with_streaming(
    1091                &mut |out| {
    1092                    println!("{}", out);
    1093                    Ok(())
    1094                },
    1095                &mut |err| {
    1096                    eprintln!("{}", err);
    1097                    Ok(())
    1098                },
    1099                true,
    1100            )
    1101        } else {
    1102            process.exec_with_output()
    1103        };
    1104
    1105        match res {
    1106            Ok(out) => {
    1107                self.match_output(out.status.code(), &out.stdout, &out.stderr)?;
    1108                return Ok(RawOutput {
    1109                    stdout: out.stdout,
    1110                    stderr: out.stderr,
    1111                    code: out.status.code(),
    1112                });
    1113            }
    1114            Err(e) => {
    1115                if let Some(ProcessError {
    1116                    stdout: Some(stdout),
    1117                    stderr: Some(stderr),
    1118                    code,
    1119                    ..
    1120                }) = e.downcast_ref::<ProcessError>()
    1121                {
    1122                    self.match_output(*code, stdout, stderr)?;
    1123                    return Ok(RawOutput {
    1124                        stdout: stdout.to_vec(),
    1125                        stderr: stderr.to_vec(),
    1126                        code: *code,
    1127                    });
    1128                }
    1129                bail!("could not exec process {}: {:?}", process, e)
    1130            }
    1131        }
    1132    }
    1133
    1134    #[track_caller]
    1135    fn match_output(&self, code: Option<i32>, stdout: &[u8], stderr: &[u8]) -> Result<()> {
    1136        self.verify_checks_output(stdout, stderr);
    1137        let stdout = std::str::from_utf8(stdout).expect("stdout is not utf8");
    1138        let stderr = std::str::from_utf8(stderr).expect("stderr is not utf8");
    1139
    1140        match self.expect_exit_code {
    1141            None => {}
    1142            Some(expected) if code == Some(expected) => {}
    1143            Some(expected) => bail!(
    1144                "process exited with code {} (expected {})\n--- stdout\n{}\n--- stderr\n{}",
    1145                code.unwrap_or(-1),
    1146                expected,
    1147                stdout,
    1148                stderr
    1149            ),
    1150        }
    1151
    1152        if let Some(expect_stdout_data) = &self.expect_stdout_data {
    1153            if let Err(err) = self.assert.try_eq(
    1154                Some(&"stdout"),
    1155                stdout.into_data(),
    1156                expect_stdout_data.clone(),
    1157            ) {
    1158                panic!("{err}")
    1159            }
    1160        }
    1161        if let Some(expect_stderr_data) = &self.expect_stderr_data {
    1162            if let Err(err) = self.assert.try_eq(
    1163                Some(&"stderr"),
    1164                stderr.into_data(),
    1165                expect_stderr_data.clone(),
    1166            ) {
    1167                panic!("{err}")
    1168            }
    1169        }
    1170        for expect in self.expect_stdout_contains.iter() {
    1171            compare::match_contains(expect, stdout, self.assert.redactions())?;
    1172        }
    1173        for expect in self.expect_stderr_contains.iter() {
    1174            compare::match_contains(expect, stderr, self.assert.redactions())?;
    1175        }
    1176        for expect in self.expect_stdout_not_contains.iter() {
    1177            compare::match_does_not_contain(expect, stdout, self.assert.redactions())?;
    1178        }
    1179        for expect in self.expect_stderr_not_contains.iter() {
    1180            compare::match_does_not_contain(expect, stderr, self.assert.redactions())?;
    1181        }
    1182        for (with, without) in self.expect_stderr_with_without.iter() {
    1183            compare::match_with_without(stderr, with, without, self.assert.redactions())?;
    1184        }
    1185        Ok(())
    1186    }
    1187}
    1188
    1189impl Drop for Execs {
    1190    fn drop(&mut self) {
    1191        if !self.ran && !std::thread::panicking() {
    1192            panic!("forgot to run this command");
    1193        }
    1194    }
    1195}
    1196
    1197/// Run and verify a process, see [`Execs`]
    1198pub fn execs() -> Execs {
    1199    Execs {
    1200        ran: false,
    1201        process_builder: None,
    1202        expect_stdin: None,
    1203        expect_exit_code: Some(0),
    1204        expect_stdout_data: None,
    1205        expect_stderr_data: None,
    1206        expect_stdout_contains: Vec::new(),
    1207        expect_stderr_contains: Vec::new(),
    1208        expect_stdout_not_contains: Vec::new(),
    1209        expect_stderr_not_contains: Vec::new(),
    1210        expect_stderr_with_without: Vec::new(),
    1211        stream_output: false,
    1212        assert: compare::assert_e2e(),
    1213    }
    1214}
    1215
    1216/// Generate a basic `Cargo.toml`
    1217pub fn basic_manifest(name: &str, version: &str) -> String {
    1218    format!(
    1219        r#"
    1220        [package]
    1221        name = "{}"
    1222        version = "{}"
    1223        authors = []
    1224        edition = "2015"
    1225    "#,
    1226        name, version
    1227    )
    1228}
    1229
    1230/// Generate a `Cargo.toml` with the specified `bin.name`
    1231pub fn basic_bin_manifest(name: &str) -> String {
    1232    format!(
    1233        r#"
    1234        [package]
    1235
    1236        name = "{}"
    1237        version = "0.5.0"
    1238        authors = ["wycats@example.com"]
    1239        edition = "2015"
    1240
    1241        [[bin]]
    1242
    1243        name = "{}"
    1244    "#,
    1245        name, name
    1246    )
    1247}
    1248
    1249/// Generate a `Cargo.toml` with the specified `lib.name`
    1250pub fn basic_lib_manifest(name: &str) -> String {
    1251    format!(
    1252        r#"
    1253        [package]
    1254
    1255        name = "{}"
    1256        version = "0.5.0"
    1257        authors = ["wycats@example.com"]
    1258        edition = "2015"
    1259
    1260        [lib]
    1261
    1262        name = "{}"
    1263    "#,
    1264        name, name
    1265    )
    1266}
    1267
    1268/// Gets a valid target spec JSON from rustc.
    1269///
    1270/// To avoid any hardcoded value, this fetches `x86_64-unknown-none` target
    1271/// spec JSON directly from `rustc`, as Cargo shouldn't know the JSON schema.
    1272pub fn target_spec_json() -> &'static str {
    1273    static TARGET_SPEC_JSON: LazyLock<String> = LazyLock::new(|| {
    1274        let json = std::process::Command::new("rustc")
    1275            .env("RUSTC_BOOTSTRAP", "1")
    1276            .arg("--print")
    1277            .arg("target-spec-json")
    1278            .arg("-Zunstable-options")
    1279            .arg("--target")
    1280            .arg("x86_64-unknown-none")
    1281            .output()
    1282            .expect("rustc --print target-spec-json")
    1283            .stdout;
    1284        String::from_utf8(json).expect("utf8 target spec json")
    1285    });
    1286
    1287    TARGET_SPEC_JSON.as_str()
    1288}
    1289
    1290struct RustcInfo {
    1291    verbose_version: String,
    1292    host: String,
    1293}
    1294
    1295impl RustcInfo {
    1296    fn new() -> RustcInfo {
    1297        let output = ProcessBuilder::new("rustc")
    1298            .arg("-vV")
    1299            .exec_with_output()
    1300            .expect("rustc should exec");
    1301        let verbose_version = String::from_utf8(output.stdout).expect("utf8 output");
    1302        let host = verbose_version
    1303            .lines()
    1304            .filter_map(|line| line.strip_prefix("host: "))
    1305            .next()
    1306            .expect("verbose version has host: field")
    1307            .to_string();
    1308        RustcInfo {
    1309            verbose_version,
    1310            host,
    1311        }
    1312    }
    1313}
    1314
    1315fn rustc_info() -> &'static RustcInfo {
    1316    static RUSTC_INFO: OnceLock<RustcInfo> = OnceLock::new();
    1317    RUSTC_INFO.get_or_init(RustcInfo::new)
    1318}
    1319
    1320/// The rustc host such as `x86_64-unknown-linux-gnu`.
    1321pub fn rustc_host() -> &'static str {
    1322    &rustc_info().host
    1323}
    1324
    1325/// The host triple suitable for use in a cargo environment variable (uppercased).
    1326pub fn rustc_host_env() -> String {
    1327    rustc_host().to_uppercase().replace('-', "_")
    1328}
    1329
    1330pub fn is_nightly() -> bool {
    1331    let vv = &rustc_info().verbose_version;
    1332    // CARGO_TEST_DISABLE_NIGHTLY is set in rust-lang/rust's CI so that all
    1333    // nightly-only tests are disabled there. Otherwise, it could make it
    1334    // difficult to land changes which would need to be made simultaneously in
    1335    // rust-lang/cargo and rust-lan/rust, which isn't possible.
    1336    env::var("CARGO_TEST_DISABLE_NIGHTLY").is_err()
    1337        && (vv.contains("-nightly") || vv.contains("-dev"))
    1338}
    1339
    1340/// Run `$bin` in the test's environment, see [`ProcessBuilder`]
    1341///
    1342/// For more on the test environment, see
    1343/// - [`paths::root`]
    1344/// - [`TestEnvCommandExt`]
    1345pub fn process<T: AsRef<OsStr>>(bin: T) -> ProcessBuilder {
    1346    _process(bin.as_ref())
    1347}
    1348
    1349fn _process(t: &OsStr) -> ProcessBuilder {
    1350    let mut p = ProcessBuilder::new(t);
    1351    p.cwd(&paths::root()).test_env();
    1352    p
    1353}
    1354
    1355/// Enable nightly features for testing
    1356pub trait ChannelChangerCommandExt {
    1357    /// The list of reasons should be why nightly cargo is needed. If it is
    1358    /// because of an unstable feature put the name of the feature as the reason,
    1359    /// e.g. `&["print-im-a-teapot"]`.
    1360    fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self;
    1361}
    1362
    1363impl ChannelChangerCommandExt for &mut ProcessBuilder {
    1364    fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self {
    1365        self.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly")
    1366    }
    1367}
    1368
    1369impl ChannelChangerCommandExt for snapbox::cmd::Command {
    1370    fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self {
    1371        self.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly")
    1372    }
    1373}
    1374
    1375/// Establish a process's test environment
    1376pub trait TestEnvCommandExt: Sized {
    1377    fn test_env(mut self) -> Self {
    1378        // In general just clear out all cargo-specific configuration already in the
    1379        // environment. Our tests all assume a "default configuration" unless
    1380        // specified otherwise.
    1381        for (k, _v) in env::vars() {
    1382            if k.starts_with("CARGO_") {
    1383                self = self.env_remove(&k);
    1384            }
    1385        }
    1386        if env::var_os("RUSTUP_TOOLCHAIN").is_some() {
    1387            // Override the PATH to avoid executing the rustup wrapper thousands
    1388            // of times. This makes the testsuite run substantially faster.
    1389            static RUSTC_DIR: OnceLock<PathBuf> = OnceLock::new();
    1390            let rustc_dir = RUSTC_DIR.get_or_init(|| {
    1391                match ProcessBuilder::new("rustup")
    1392                    .args(&["which", "rustc"])
    1393                    .exec_with_output()
    1394                {
    1395                    Ok(output) => {
    1396                        let s = std::str::from_utf8(&output.stdout).expect("utf8").trim();
    1397                        let mut p = PathBuf::from(s);
    1398                        p.pop();
    1399                        p
    1400                    }
    1401                    Err(e) => {
    1402                        panic!("RUSTUP_TOOLCHAIN was set, but could not run rustup: {}", e);
    1403                    }
    1404                }
    1405            });
    1406            let path = env::var_os("PATH").unwrap_or_default();
    1407            let paths = env::split_paths(&path);
    1408            let new_path =
    1409                env::join_paths(std::iter::once(rustc_dir.clone()).chain(paths)).unwrap();
    1410            self = self.env("PATH", new_path);
    1411        }
    1412
    1413        self = self
    1414            .current_dir(&paths::root())
    1415            .env("HOME", paths::home())
    1416            .env("CARGO_HOME", paths::cargo_home())
    1417            .env("__CARGO_TEST_ROOT", paths::global_root())
    1418            // Force Cargo to think it's on the stable channel for all tests, this
    1419            // should hopefully not surprise us as we add cargo features over time and
    1420            // cargo rides the trains.
    1421            .env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "stable")
    1422            // Keeps cargo within its sandbox.
    1423            .env("__CARGO_TEST_DISABLE_GLOBAL_KNOWN_HOST", "1")
    1424            // Set retry sleep to 1 millisecond.
    1425            .env("__CARGO_TEST_FIXED_RETRY_SLEEP_MS", "1")
    1426            // Incremental generates a huge amount of data per test, which we
    1427            // don't particularly need. Tests that specifically need to check
    1428            // the incremental behavior should turn this back on.
    1429            .env("CARGO_INCREMENTAL", "0")
    1430            // Don't read the system git config which is out of our control.
    1431            .env("GIT_CONFIG_NOSYSTEM", "1")
    1432            .env_remove("__CARGO_DEFAULT_LIB_METADATA")
    1433            .env_remove("ALL_PROXY")
    1434            .env_remove("EMAIL")
    1435            .env_remove("GIT_AUTHOR_EMAIL")
    1436            .env_remove("GIT_AUTHOR_NAME")
    1437            .env_remove("GIT_COMMITTER_EMAIL")
    1438            .env_remove("GIT_COMMITTER_NAME")
    1439            .env_remove("http_proxy")
    1440            .env_remove("HTTPS_PROXY")
    1441            .env_remove("https_proxy")
    1442            .env_remove("MAKEFLAGS")
    1443            .env_remove("MFLAGS")
    1444            .env_remove("MSYSTEM") // assume cmd.exe everywhere on windows
    1445            .env_remove("RUSTC")
    1446            .env_remove("RUST_BACKTRACE")
    1447            .env_remove("RUSTC_WORKSPACE_WRAPPER")
    1448            .env_remove("RUSTC_WRAPPER")
    1449            .env_remove("RUSTDOC")
    1450            .env_remove("RUSTDOCFLAGS")
    1451            .env_remove("RUSTFLAGS")
    1452            .env_remove("SSH_AUTH_SOCK") // ensure an outer agent is never contacted
    1453            .env_remove("USER") // not set on some rust-lang docker images
    1454            .env_remove("XDG_CONFIG_HOME") // see #2345
    1455            .env_remove("OUT_DIR"); // see #13204
    1456        if cfg!(windows) {
    1457            self = self.env("USERPROFILE", paths::home());
    1458        }
    1459        self
    1460    }
    1461
    1462    fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self;
    1463    fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self;
    1464    fn env_remove(self, key: &str) -> Self;
    1465}
    1466
    1467impl TestEnvCommandExt for &mut ProcessBuilder {
    1468    fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self {
    1469        let path = path.as_ref();
    1470        self.cwd(path)
    1471    }
    1472    fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self {
    1473        self.env(key, value)
    1474    }
    1475    fn env_remove(self, key: &str) -> Self {
    1476        self.env_remove(key)
    1477    }
    1478}
    1479
    1480impl TestEnvCommandExt for snapbox::cmd::Command {
    1481    fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self {
    1482        self.current_dir(path)
    1483    }
    1484    fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self {
    1485        self.env(key, value)
    1486    }
    1487    fn env_remove(self, key: &str) -> Self {
    1488        self.env_remove(key)
    1489    }
    1490}
    1491
    1492/// Add a list of arguments as a line
    1493pub trait ArgLineCommandExt: Sized {
    1494    fn arg_line(mut self, s: &str) -> Self {
    1495        for mut arg in s.split_whitespace() {
    1496            if (arg.starts_with('"') && arg.ends_with('"'))
    1497                || (arg.starts_with('\'') && arg.ends_with('\''))
    1498            {
    1499                arg = &arg[1..(arg.len() - 1).max(1)];
    1500            } else if arg.contains(&['"', '\''][..]) {
    1501                panic!("shell-style argument parsing is not supported")
    1502            }
    1503            self = self.arg(arg);
    1504        }
    1505        self
    1506    }
    1507
    1508    fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self;
    1509}
    1510
    1511impl ArgLineCommandExt for &mut ProcessBuilder {
    1512    fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
    1513        self.arg(s)
    1514    }
    1515}
    1516
    1517impl ArgLineCommandExt for &mut Execs {
    1518    fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
    1519        self.arg(s)
    1520    }
    1521}
    1522
    1523impl ArgLineCommandExt for snapbox::cmd::Command {
    1524    fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
    1525        self.arg(s)
    1526    }
    1527}
    1528
    1529/// Run `git $arg_line`, see [`ProcessBuilder`]
    1530pub fn git_process(arg_line: &str) -> ProcessBuilder {
    1531    let mut p = process("git");
    1532    p.arg_line(arg_line);
    1533    p
    1534}
    1535
    1536pub fn sleep_ms(ms: u64) {
    1537    ::std::thread::sleep(Duration::from_millis(ms));
    1538}
    1539
    1540/// Returns `true` if the local filesystem has low-resolution mtimes.
    1541pub fn is_coarse_mtime() -> bool {
    1542    // If the filetime crate is being used to emulate HFS then
    1543    // return `true`, without looking at the actual hardware.
    1544    cfg!(emulate_second_only_system) ||
    1545    // This should actually be a test that `$CARGO_TARGET_DIR` is on an HFS
    1546    // filesystem, (or any filesystem with low-resolution mtimes). However,
    1547    // that's tricky to detect, so for now just deal with CI.
    1548    cfg!(target_os = "macos") && is_ci()
    1549}
    1550
    1551/// A way for to increase the cut off for all the time based test.
    1552///
    1553/// Some CI setups are much slower then the equipment used by Cargo itself.
    1554/// Architectures that do not have a modern processor, hardware emulation, etc.
    1555pub fn slow_cpu_multiplier(main: u64) -> Duration {
    1556    static SLOW_CPU_MULTIPLIER: OnceLock<u64> = OnceLock::new();
    1557    let slow_cpu_multiplier = SLOW_CPU_MULTIPLIER.get_or_init(|| {
    1558        env::var("CARGO_TEST_SLOW_CPU_MULTIPLIER")
    1559            .ok()
    1560            .and_then(|m| m.parse().ok())
    1561            .unwrap_or(1)
    1562    });
    1563    Duration::from_secs(slow_cpu_multiplier * main)
    1564}
    1565
    1566#[cfg(windows)]
    1567pub fn symlink_supported() -> bool {
    1568    if is_ci() {
    1569        // We want to be absolutely sure this runs on CI.
    1570        return true;
    1571    }
    1572    let src = paths::root().join("symlink_src");
    1573    fs::write(&src, "").unwrap();
    1574    let dst = paths::root().join("symlink_dst");
    1575    let result = match os::windows::fs::symlink_file(&src, &dst) {
    1576        Ok(_) => {
    1577            fs::remove_file(&dst).unwrap();
    1578            true
    1579        }
    1580        Err(e) => {
    1581            eprintln!(
    1582                "symlinks not supported: {:?}\n\
    1583                 Windows 10 users should enable developer mode.",
    1584                e
    1585            );
    1586            false
    1587        }
    1588    };
    1589    fs::remove_file(&src).unwrap();
    1590    return result;
    1591}
    1592
    1593#[cfg(not(windows))]
    1594pub fn symlink_supported() -> bool {
    1595    true
    1596}
    1597
    1598/// The error message for ENOENT.
    1599pub fn no_such_file_err_msg() -> String {
    1600    std::io::Error::from_raw_os_error(2).to_string()
    1601}
    1602
    1603/// Helper to retry a function `n` times.
    1604///
    1605/// The function should return `Some` when it is ready.
    1606pub fn retry<F, R>(n: u32, mut f: F) -> R
    1607where
    1608    F: FnMut() -> Option<R>,
    1609{
    1610    let mut count = 0;
    1611    let start = std::time::Instant::now();
    1612    loop {
    1613        if let Some(r) = f() {
    1614            return r;
    1615        }
    1616        count += 1;
    1617        if count > n {
    1618            panic!(
    1619                "test did not finish within {n} attempts ({:?} total)",
    1620                start.elapsed()
    1621            );
    1622        }
    1623        sleep_ms(100);
    1624    }
    1625}
    1626
    1627#[test]
    1628#[should_panic(expected = "test did not finish")]
    1629fn retry_fails() {
    1630    retry(2, || None::<()>);
    1631}
    1632
    1633/// Helper that waits for a thread to finish, up to `n` tenths of a second.
    1634pub fn thread_wait_timeout<T>(n: u32, thread: JoinHandle<T>) -> T {
    1635    retry(n, || thread.is_finished().then_some(()));
    1636    thread.join().unwrap()
    1637}
    1638
    1639/// Helper that runs some function, and waits up to `n` tenths of a second for
    1640/// it to finish.
    1641pub fn threaded_timeout<F, R>(n: u32, f: F) -> R
    1642where
    1643    F: FnOnce() -> R + Send + 'static,
    1644    R: Send + 'static,
    1645{
    1646    let thread = std::thread::spawn(|| f());
    1647    thread_wait_timeout(n, thread)
    1648}
    1649
    1650// Helper for testing dep-info files in the fingerprint dir.
    1651#[track_caller]
    1652pub fn assert_deps(project: &Project, fingerprint: &str, test_cb: impl Fn(&Path, &[(u8, &str)])) {
    1653    let mut files = project
    1654        .glob(fingerprint)
    1655        .map(|f| f.expect("unwrap glob result"))
    1656        // Filter out `.json` entries.
    1657        .filter(|f| f.extension().is_none());
    1658    let info_path = files
    1659        .next()
    1660        .unwrap_or_else(|| panic!("expected 1 dep-info file at {}, found 0", fingerprint));
    1661    assert!(files.next().is_none(), "expected only 1 dep-info file");
    1662    let dep_info = fs::read(&info_path).unwrap();
    1663    let dep_info = &mut &dep_info[..];
    1664
    1665    // Consume the magic marker and version. Here they don't really matter.
    1666    read_usize(dep_info);
    1667    read_u8(dep_info);
    1668    read_u8(dep_info);
    1669
    1670    let deps = (0..read_usize(dep_info))
    1671        .map(|_| {
    1672            let ty = read_u8(dep_info);
    1673            let path = std::str::from_utf8(read_bytes(dep_info)).unwrap();
    1674            let checksum_present = read_bool(dep_info);
    1675            if checksum_present {
    1676                // Read out the checksum info without using it
    1677                let _file_len = read_u64(dep_info);
    1678                let _checksum = read_bytes(dep_info);
    1679            }
    1680            (ty, path)
    1681        })
    1682        .collect::<Vec<_>>();
    1683    test_cb(&info_path, &deps);
    1684
    1685    fn read_usize(bytes: &mut &[u8]) -> usize {
    1686        let ret = &bytes[..4];
    1687        *bytes = &bytes[4..];
    1688
    1689        u32::from_le_bytes(ret.try_into().unwrap()) as usize
    1690    }
    1691
    1692    fn read_u8(bytes: &mut &[u8]) -> u8 {
    1693        let ret = bytes[0];
    1694        *bytes = &bytes[1..];
    1695        ret
    1696    }
    1697
    1698    fn read_bool(bytes: &mut &[u8]) -> bool {
    1699        read_u8(bytes) != 0
    1700    }
    1701
    1702    fn read_u64(bytes: &mut &[u8]) -> u64 {
    1703        let ret = &bytes[..8];
    1704        *bytes = &bytes[8..];
    1705
    1706        u64::from_le_bytes(ret.try_into().unwrap())
    1707    }
    1708
    1709    fn read_bytes<'a>(bytes: &mut &'a [u8]) -> &'a [u8] {
    1710        let n = read_usize(bytes);
    1711        let ret = &bytes[..n];
    1712        *bytes = &bytes[n..];
    1713        ret
    1714    }
    1715}
    1716
    1717pub fn assert_deps_contains(project: &Project, fingerprint: &str, expected: &[(u8, &str)]) {
    1718    assert_deps(project, fingerprint, |info_path, entries| {
    1719        for (e_kind, e_path) in expected {
    1720            let pattern = glob::Pattern::new(e_path).unwrap();
    1721            let count = entries
    1722                .iter()
    1723                .filter(|(kind, path)| kind == e_kind && pattern.matches(path))
    1724                .count();
    1725            if count != 1 {
    1726                panic!(
    1727                    "Expected 1 match of {} {} in {:?}, got {}:\n{:#?}",
    1728                    e_kind, e_path, info_path, count, entries
    1729                );
    1730            }
    1731        }
    1732    })
    1733}