cargo_test_support/
lib.rs

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