cargo_test_support/
lib.rs

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