cargo_test_support/
lib.rs

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