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