Skip to main content

cargo_test_support/
lib.rs

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