Skip to main content

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