1#![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#[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#[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#[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
226pub struct Project {
230 root: PathBuf,
231}
232
233#[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 pub fn root(&self) -> PathBuf {
255 self.root.root()
256 }
257
258 pub fn target_debug_dir(&self) -> PathBuf {
262 self.root.target_debug_dir()
263 }
264
265 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 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 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 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 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 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 pub fn build(mut self) -> Project {
328 self.rm_root();
330
331 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
346 for file in self.files.iter_mut() {
347 file.mk();
348 if is_coarse_mtime() {
349 filetime::set_file_times(&file.path, ftime, ftime).unwrap();
355 }
356 }
357
358 for symlink in self.symlinks.iter_mut() {
359 symlink.mk();
360 }
361
362 let ProjectBuilder { root, .. } = self;
363 root
364 }
365
366 fn rm_root(&self) {
367 self.root.root().rm_rf()
368 }
369}
370
371impl Project {
372 pub fn from_template(template_path: impl AsRef<Path>) -> Self {
374 let root = paths::root();
375 let project_root = root.join("case");
376 snapbox::dir::copy_template(template_path.as_ref(), &project_root).unwrap();
377 Self { root: project_root }
378 }
379
380 pub fn root(&self) -> PathBuf {
384 self.root.clone()
385 }
386
387 pub fn build_dir(&self) -> PathBuf {
391 self.root().join("target")
392 }
393
394 pub fn target_debug_dir(&self) -> PathBuf {
398 self.build_dir().join("debug")
399 }
400
401 pub fn url(&self) -> Url {
405 use paths::CargoPathExt;
406 self.root().to_url()
407 }
408
409 pub fn example_lib(&self, name: &str, kind: &str) -> PathBuf {
415 self.target_debug_dir()
416 .join("examples")
417 .join(paths::get_lib_filename(name, kind))
418 }
419
420 pub fn dylib(&self, name: &str) -> PathBuf {
423 self.target_debug_dir().join(format!(
424 "{}{name}{}",
425 env::consts::DLL_PREFIX,
426 env::consts::DLL_SUFFIX
427 ))
428 }
429
430 pub fn bin(&self, b: &str) -> PathBuf {
434 self.build_dir()
435 .join("debug")
436 .join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
437 }
438
439 pub fn release_bin(&self, b: &str) -> PathBuf {
443 self.build_dir()
444 .join("release")
445 .join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
446 }
447
448 pub fn target_bin(&self, target: &str, b: &str) -> PathBuf {
452 self.build_dir().join(target).join("debug").join(&format!(
453 "{}{}",
454 b,
455 env::consts::EXE_SUFFIX
456 ))
457 }
458
459 pub fn glob<P: AsRef<Path>>(&self, pattern: P) -> glob::Paths {
461 let pattern = self.root().join(pattern);
462 glob::glob(pattern.to_str().expect("failed to convert pattern to str"))
463 .expect("failed to glob")
464 }
465
466 pub fn change_file(&self, path: impl AsRef<Path>, body: &str) {
475 FileBuilder::new(self.root().join(path), body, false).mk()
476 }
477
478 pub fn process<T: AsRef<OsStr>>(&self, program: T) -> Execs {
491 let mut p = process(program);
492 p.cwd(self.root());
493 execs().with_process_builder(p)
494 }
495
496 pub fn rename_run(&self, src: &str, dst: &str) -> Execs {
510 let src = self.bin(src);
511 let dst = self.bin(dst);
512 fs::rename(&src, &dst)
513 .unwrap_or_else(|e| panic!("Failed to rename `{:?}` to `{:?}`: {}", src, dst, e));
514 self.process(dst)
515 }
516
517 pub fn read_lockfile(&self) -> String {
519 self.read_file("Cargo.lock")
520 }
521
522 pub fn read_file(&self, path: impl AsRef<Path>) -> String {
524 let full = self.root().join(path);
525 fs::read_to_string(&full)
526 .unwrap_or_else(|e| panic!("could not read file {}: {}", full.display(), e))
527 }
528
529 pub fn uncomment_root_manifest(&self) {
531 let contents = self.read_file("Cargo.toml").replace("#", "");
532 fs::write(self.root().join("Cargo.toml"), contents).unwrap();
533 }
534
535 pub fn symlink(&self, src: impl AsRef<Path>, dst: impl AsRef<Path>) {
536 let src = self.root().join(src.as_ref());
537 let dst = self.root().join(dst.as_ref());
538 #[cfg(unix)]
539 {
540 if let Err(e) = os::unix::fs::symlink(&src, &dst) {
541 panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
542 }
543 }
544 #[cfg(windows)]
545 {
546 if src.is_dir() {
547 if let Err(e) = os::windows::fs::symlink_dir(&src, &dst) {
548 panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
549 }
550 } else {
551 if let Err(e) = os::windows::fs::symlink_file(&src, &dst) {
552 panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
553 }
554 }
555 }
556 }
557}
558
559pub fn project() -> ProjectBuilder {
561 ProjectBuilder::new(paths::root().join("foo"))
562}
563
564pub fn project_in(dir: impl AsRef<Path>) -> ProjectBuilder {
566 ProjectBuilder::new(paths::root().join(dir).join("foo"))
567}
568
569pub fn project_in_home(name: impl AsRef<Path>) -> ProjectBuilder {
571 ProjectBuilder::new(paths::home().join(name))
572}
573
574pub fn main_file(println: &str, externed_deps: &[&str]) -> String {
591 let mut buf = String::new();
592
593 for dep in externed_deps.iter() {
594 buf.push_str(&format!("extern crate {};\n", dep));
595 }
596
597 buf.push_str("fn main() { println!(");
598 buf.push_str(println);
599 buf.push_str("); }\n");
600
601 buf
602}
603
604pub struct RawOutput {
612 pub code: Option<i32>,
613 pub stdout: Vec<u8>,
614 pub stderr: Vec<u8>,
615}
616
617#[must_use]
624#[derive(Clone)]
625pub struct Execs {
626 ran: bool,
627 process_builder: Option<ProcessBuilder>,
628 expect_stdin: Option<String>,
629 expect_exit_code: Option<i32>,
630 expect_stdout_data: Option<snapbox::Data>,
631 expect_stderr_data: Option<snapbox::Data>,
632 expect_stdout_contains: Vec<String>,
633 expect_stderr_contains: Vec<String>,
634 expect_stdout_not_contains: Vec<String>,
635 expect_stderr_not_contains: Vec<String>,
636 expect_stderr_with_without: Vec<(Vec<String>, Vec<String>)>,
637 stream_output: bool,
638 assert: snapbox::Assert,
639}
640
641impl Execs {
642 pub fn with_process_builder(mut self, p: ProcessBuilder) -> Execs {
643 self.process_builder = Some(p);
644 self
645 }
646}
647
648impl Execs {
650 pub fn with_stdout_data(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
705 self.expect_stdout_data = Some(expected.into_data());
706 self
707 }
708
709 pub fn with_stderr_data(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
764 self.expect_stderr_data = Some(expected.into_data());
765 self
766 }
767
768 pub fn with_stdin<S: ToString>(&mut self, expected: S) -> &mut Self {
770 self.expect_stdin = Some(expected.to_string());
771 self
772 }
773
774 pub fn with_status(&mut self, expected: i32) -> &mut Self {
778 self.expect_exit_code = Some(expected);
779 self
780 }
781
782 pub fn without_status(&mut self) -> &mut Self {
786 self.expect_exit_code = None;
787 self
788 }
789
790 pub fn with_stdout_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
803 self.expect_stdout_contains.push(expected.to_string());
804 self
805 }
806
807 pub fn with_stderr_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
820 self.expect_stderr_contains.push(expected.to_string());
821 self
822 }
823
824 pub fn with_stdout_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
842 self.expect_stdout_not_contains.push(expected.to_string());
843 self
844 }
845
846 pub fn with_stderr_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
863 self.expect_stderr_not_contains.push(expected.to_string());
864 self
865 }
866
867 pub fn with_stderr_line_without<S: ToString>(
899 &mut self,
900 with: &[S],
901 without: &[S],
902 ) -> &mut Self {
903 let with = with.iter().map(|s| s.to_string()).collect();
904 let without = without.iter().map(|s| s.to_string()).collect();
905 self.expect_stderr_with_without.push((with, without));
906 self
907 }
908}
909
910impl Execs {
912 #[allow(unused)]
916 pub fn stream(&mut self) -> &mut Self {
917 self.stream_output = true;
918 self
919 }
920
921 pub fn arg<T: AsRef<OsStr>>(&mut self, arg: T) -> &mut Self {
922 if let Some(ref mut p) = self.process_builder {
923 p.arg(arg);
924 }
925 self
926 }
927
928 pub fn args<T: AsRef<OsStr>>(&mut self, args: &[T]) -> &mut Self {
929 if let Some(ref mut p) = self.process_builder {
930 p.args(args);
931 }
932 self
933 }
934
935 pub fn cwd<T: AsRef<OsStr>>(&mut self, path: T) -> &mut Self {
936 if let Some(ref mut p) = self.process_builder {
937 if let Some(cwd) = p.get_cwd() {
938 let new_path = cwd.join(path.as_ref());
939 p.cwd(new_path);
940 } else {
941 p.cwd(path);
942 }
943 }
944 self
945 }
946
947 pub fn env<T: AsRef<OsStr>>(&mut self, key: &str, val: T) -> &mut Self {
948 if let Some(ref mut p) = self.process_builder {
949 p.env(key, val);
950 }
951 self
952 }
953
954 pub fn env_remove(&mut self, key: &str) -> &mut Self {
955 if let Some(ref mut p) = self.process_builder {
956 p.env_remove(key);
957 }
958 self
959 }
960
961 pub fn masquerade_as_nightly_cargo(&mut self, reasons: &[&str]) -> &mut Self {
967 if let Some(ref mut p) = self.process_builder {
968 p.masquerade_as_nightly_cargo(reasons);
969 }
970 self
971 }
972
973 pub fn replace_crates_io(&mut self, url: &Url) -> &mut Self {
978 if let Some(ref mut p) = self.process_builder {
979 p.env("__CARGO_TEST_CRATES_IO_URL_DO_NOT_USE_THIS", url.as_str());
980 }
981 self
982 }
983
984 pub fn overlay_registry(&mut self, url: &Url, path: &str) -> &mut Self {
985 if let Some(ref mut p) = self.process_builder {
986 let env_value = format!("{}={}", url, path);
987 p.env(
988 "__CARGO_TEST_DEPENDENCY_CONFUSION_VULNERABILITY_DO_NOT_USE_THIS",
989 env_value,
990 );
991 }
992 self
993 }
994
995 pub fn enable_split_debuginfo_packed(&mut self) -> &mut Self {
996 self.env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "packed")
997 .env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "packed")
998 .env("CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO", "packed")
999 .env("CARGO_PROFILE_BENCH_SPLIT_DEBUGINFO", "packed");
1000 self
1001 }
1002
1003 pub fn enable_mac_dsym(&mut self) -> &mut Self {
1004 if cfg!(target_os = "macos") {
1005 return self.enable_split_debuginfo_packed();
1006 }
1007 self
1008 }
1009}
1010
1011impl Execs {
1013 pub fn exec_with_output(&mut self) -> Result<Output> {
1014 self.ran = true;
1015 let p = (&self.process_builder).clone().unwrap();
1017 p.exec_with_output()
1018 }
1019
1020 pub fn build_command(&mut self) -> Command {
1021 self.ran = true;
1022 let p = (&self.process_builder).clone().unwrap();
1024 p.build_command()
1025 }
1026
1027 #[track_caller]
1028 pub fn run(&mut self) -> RawOutput {
1029 self.ran = true;
1030 let mut p = (&self.process_builder).clone().unwrap();
1031 if let Some(stdin) = self.expect_stdin.take() {
1032 p.stdin(stdin);
1033 }
1034
1035 match self.match_process(&p) {
1036 Err(e) => panic_error(&format!("test failed running {}", p), e),
1037 Ok(output) => output,
1038 }
1039 }
1040
1041 #[track_caller]
1044 pub fn run_json(&mut self) -> serde_json::Value {
1045 let output = self.run();
1046 serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {
1047 panic!(
1048 "\nfailed to parse JSON: {}\n\
1049 output was:\n{}\n",
1050 e,
1051 String::from_utf8_lossy(&output.stdout)
1052 );
1053 })
1054 }
1055
1056 #[track_caller]
1057 pub fn run_output(&mut self, output: &Output) {
1058 self.ran = true;
1059 if let Err(e) = self.match_output(output.status.code(), &output.stdout, &output.stderr) {
1060 panic_error("process did not return the expected result", e)
1061 }
1062 }
1063
1064 #[track_caller]
1065 fn verify_checks_output(&self, stdout: &[u8], stderr: &[u8]) {
1066 if self.expect_exit_code.unwrap_or(0) != 0
1067 && self.expect_stdin.is_none()
1068 && self.expect_stdout_data.is_none()
1069 && self.expect_stderr_data.is_none()
1070 && self.expect_stdout_contains.is_empty()
1071 && self.expect_stderr_contains.is_empty()
1072 && self.expect_stdout_not_contains.is_empty()
1073 && self.expect_stderr_not_contains.is_empty()
1074 && self.expect_stderr_with_without.is_empty()
1075 {
1076 panic!(
1077 "`with_status()` is used, but no output is checked.\n\
1078 The test must check the output to ensure the correct error is triggered.\n\
1079 --- stdout\n{}\n--- stderr\n{}",
1080 String::from_utf8_lossy(stdout),
1081 String::from_utf8_lossy(stderr),
1082 );
1083 }
1084 }
1085
1086 #[track_caller]
1087 fn match_process(&self, process: &ProcessBuilder) -> Result<RawOutput> {
1088 println!("running {}", process);
1089 let res = if self.stream_output {
1090 if is_ci() {
1091 panic!("`.stream()` is for local debugging")
1092 }
1093 process
1094 .exec_with_streaming(
1095 &mut |out| {
1096 println!("{}", out);
1097 Ok(())
1098 },
1099 &mut |err| {
1100 eprintln!("{}", err);
1101 Ok(())
1102 },
1103 true,
1104 false,
1105 )
1106 .1
1107 } else {
1108 process.exec_with_output()
1109 };
1110
1111 match res {
1112 Ok(out) => {
1113 self.match_output(out.status.code(), &out.stdout, &out.stderr)?;
1114 return Ok(RawOutput {
1115 stdout: out.stdout,
1116 stderr: out.stderr,
1117 code: out.status.code(),
1118 });
1119 }
1120 Err(e) => {
1121 if let Some(ProcessError {
1122 stdout: Some(stdout),
1123 stderr: Some(stderr),
1124 code,
1125 ..
1126 }) = e.downcast_ref::<ProcessError>()
1127 {
1128 self.match_output(*code, stdout, stderr)?;
1129 return Ok(RawOutput {
1130 stdout: stdout.to_vec(),
1131 stderr: stderr.to_vec(),
1132 code: *code,
1133 });
1134 }
1135 bail!("could not exec process {}: {:?}", process, e)
1136 }
1137 }
1138 }
1139
1140 #[track_caller]
1141 fn match_output(&self, code: Option<i32>, stdout: &[u8], stderr: &[u8]) -> Result<()> {
1142 self.verify_checks_output(stdout, stderr);
1143 let stdout = std::str::from_utf8(stdout).expect("stdout is not utf8");
1144 let stderr = std::str::from_utf8(stderr).expect("stderr is not utf8");
1145
1146 match self.expect_exit_code {
1147 None => {}
1148 Some(expected) if code == Some(expected) => {}
1149 Some(expected) => bail!(
1150 "process exited with code {} (expected {})\n--- stdout\n{}\n--- stderr\n{}",
1151 code.unwrap_or(-1),
1152 expected,
1153 stdout,
1154 stderr
1155 ),
1156 }
1157
1158 if let Some(expect_stdout_data) = &self.expect_stdout_data {
1159 if let Err(err) = self.assert.try_eq(
1160 Some(&"stdout"),
1161 stdout.into_data(),
1162 expect_stdout_data.clone(),
1163 ) {
1164 panic!("{err}")
1165 }
1166 }
1167 if let Some(expect_stderr_data) = &self.expect_stderr_data {
1168 if let Err(err) = self.assert.try_eq(
1169 Some(&"stderr"),
1170 stderr.into_data(),
1171 expect_stderr_data.clone(),
1172 ) {
1173 panic!("{err}")
1174 }
1175 }
1176 for expect in self.expect_stdout_contains.iter() {
1177 compare::match_contains(expect, stdout, self.assert.redactions())?;
1178 }
1179 for expect in self.expect_stderr_contains.iter() {
1180 compare::match_contains(expect, stderr, self.assert.redactions())?;
1181 }
1182 for expect in self.expect_stdout_not_contains.iter() {
1183 compare::match_does_not_contain(expect, stdout, self.assert.redactions())?;
1184 }
1185 for expect in self.expect_stderr_not_contains.iter() {
1186 compare::match_does_not_contain(expect, stderr, self.assert.redactions())?;
1187 }
1188 for (with, without) in self.expect_stderr_with_without.iter() {
1189 compare::match_with_without(stderr, with, without, self.assert.redactions())?;
1190 }
1191 Ok(())
1192 }
1193}
1194
1195impl Drop for Execs {
1196 fn drop(&mut self) {
1197 if !self.ran && !std::thread::panicking() {
1198 panic!("forgot to run this command");
1199 }
1200 }
1201}
1202
1203pub fn execs() -> Execs {
1205 Execs {
1206 ran: false,
1207 process_builder: None,
1208 expect_stdin: None,
1209 expect_exit_code: Some(0),
1210 expect_stdout_data: None,
1211 expect_stderr_data: None,
1212 expect_stdout_contains: Vec::new(),
1213 expect_stderr_contains: Vec::new(),
1214 expect_stdout_not_contains: Vec::new(),
1215 expect_stderr_not_contains: Vec::new(),
1216 expect_stderr_with_without: Vec::new(),
1217 stream_output: false,
1218 assert: compare::assert_e2e(),
1219 }
1220}
1221
1222pub fn basic_manifest(name: &str, version: &str) -> String {
1224 format!(
1225 r#"
1226 [package]
1227 name = "{}"
1228 version = "{}"
1229 authors = []
1230 edition = "2015"
1231 "#,
1232 name, version
1233 )
1234}
1235
1236pub fn basic_bin_manifest(name: &str) -> String {
1238 format!(
1239 r#"
1240 [package]
1241
1242 name = "{}"
1243 version = "0.5.0"
1244 authors = ["wycats@example.com"]
1245 edition = "2015"
1246
1247 [[bin]]
1248
1249 name = "{}"
1250 "#,
1251 name, name
1252 )
1253}
1254
1255pub fn basic_lib_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 [lib]
1267
1268 name = "{}"
1269 "#,
1270 name, name
1271 )
1272}
1273
1274pub fn target_spec_json() -> &'static str {
1279 static TARGET_SPEC_JSON: LazyLock<String> = LazyLock::new(|| {
1280 let json = std::process::Command::new("rustc")
1281 .env("RUSTC_BOOTSTRAP", "1")
1282 .arg("--print")
1283 .arg("target-spec-json")
1284 .arg("-Zunstable-options")
1285 .arg("--target")
1286 .arg("x86_64-unknown-none")
1287 .output()
1288 .expect("rustc --print target-spec-json")
1289 .stdout;
1290 String::from_utf8(json).expect("utf8 target spec json")
1291 });
1292
1293 TARGET_SPEC_JSON.as_str()
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
1326pub fn rustc_host() -> &'static str {
1328 &rustc_info().host
1329}
1330
1331pub 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 env::var("CARGO_TEST_DISABLE_NIGHTLY").is_err()
1343 && (vv.contains("-nightly") || vv.contains("-dev"))
1344}
1345
1346pub 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
1361pub trait ChannelChangerCommandExt {
1363 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
1381pub trait TestEnvCommandExt: Sized {
1383 fn test_env(mut self) -> Self {
1384 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 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 .env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "stable")
1428 .env("__CARGO_TEST_DISABLE_GLOBAL_KNOWN_HOST", "1")
1430 .env("__CARGO_TEST_FIXED_RETRY_SLEEP_MS", "1")
1432 .env("__CARGO_TEST_TTY_WIDTH_DO_NOT_USE_THIS", "400")
1439 .env("CARGO_INCREMENTAL", "0")
1443 .env("GIT_CONFIG_NOSYSTEM", "1")
1445 .env_remove("CI")
1446 .env_remove("__CARGO_DEFAULT_LIB_METADATA")
1447 .env_remove("ALL_PROXY")
1448 .env_remove("EMAIL")
1449 .env_remove("GIT_AUTHOR_EMAIL")
1450 .env_remove("GIT_AUTHOR_NAME")
1451 .env_remove("GIT_COMMITTER_EMAIL")
1452 .env_remove("GIT_COMMITTER_NAME")
1453 .env_remove("http_proxy")
1454 .env_remove("HTTPS_PROXY")
1455 .env_remove("https_proxy")
1456 .env_remove("MAKEFLAGS")
1457 .env_remove("MFLAGS")
1458 .env_remove("MSYSTEM") .env_remove("MANPAGER")
1460 .env_remove("PAGER")
1461 .env_remove("LESS")
1462 .env_remove("RUSTC")
1463 .env_remove("RUST_BACKTRACE")
1464 .env_remove("RUSTC_WORKSPACE_WRAPPER")
1465 .env_remove("RUSTC_WRAPPER")
1466 .env_remove("RUSTDOC")
1467 .env_remove("RUSTDOCFLAGS")
1468 .env_remove("RUSTFLAGS")
1469 .env_remove("RUSTUP_TOOLCHAIN_SOURCE")
1470 .env_remove("SSH_AUTH_SOCK") .env_remove("USER") .env_remove("XDG_CONFIG_HOME") .env_remove("OUT_DIR"); if cfg!(windows) {
1475 self = self.env("USERPROFILE", paths::home());
1476 }
1477 self
1478 }
1479
1480 fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self;
1481 fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self;
1482 fn env_remove(self, key: &str) -> Self;
1483}
1484
1485impl TestEnvCommandExt for &mut ProcessBuilder {
1486 fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self {
1487 let path = path.as_ref();
1488 self.cwd(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
1498impl TestEnvCommandExt for snapbox::cmd::Command {
1499 fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self {
1500 self.current_dir(path)
1501 }
1502 fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self {
1503 self.env(key, value)
1504 }
1505 fn env_remove(self, key: &str) -> Self {
1506 self.env_remove(key)
1507 }
1508}
1509
1510pub trait ArgLineCommandExt: Sized {
1512 fn arg_line(mut self, s: &str) -> Self {
1513 for mut arg in s.split_whitespace() {
1514 if (arg.starts_with('"') && arg.ends_with('"'))
1515 || (arg.starts_with('\'') && arg.ends_with('\''))
1516 {
1517 arg = &arg[1..(arg.len() - 1).max(1)];
1518 } else if arg.contains(&['"', '\''][..]) {
1519 panic!("shell-style argument parsing is not supported")
1520 }
1521 self = self.arg(arg);
1522 }
1523 self
1524 }
1525
1526 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self;
1527}
1528
1529impl ArgLineCommandExt for &mut ProcessBuilder {
1530 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
1531 self.arg(s)
1532 }
1533}
1534
1535impl ArgLineCommandExt for &mut Execs {
1536 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
1537 self.arg(s)
1538 }
1539}
1540
1541impl ArgLineCommandExt for snapbox::cmd::Command {
1542 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
1543 self.arg(s)
1544 }
1545}
1546
1547pub fn git_process(arg_line: &str) -> ProcessBuilder {
1549 let mut p = process("git");
1550 p.arg_line(arg_line);
1551 p
1552}
1553
1554pub fn sleep_ms(ms: u64) {
1555 ::std::thread::sleep(Duration::from_millis(ms));
1556}
1557
1558pub fn is_coarse_mtime() -> bool {
1560 cfg!(emulate_second_only_system) ||
1563 cfg!(target_os = "macos") && is_ci()
1567}
1568
1569pub fn slow_cpu_multiplier(main: u64) -> Duration {
1574 static SLOW_CPU_MULTIPLIER: OnceLock<u64> = OnceLock::new();
1575 let slow_cpu_multiplier = SLOW_CPU_MULTIPLIER.get_or_init(|| {
1576 env::var("CARGO_TEST_SLOW_CPU_MULTIPLIER")
1577 .ok()
1578 .and_then(|m| m.parse().ok())
1579 .unwrap_or(1)
1580 });
1581 Duration::from_secs(slow_cpu_multiplier * main)
1582}
1583
1584#[cfg(windows)]
1585pub fn symlink_supported() -> bool {
1586 if is_ci() {
1587 return true;
1589 }
1590 let src = paths::root().join("symlink_src");
1591 fs::write(&src, "").unwrap();
1592 let dst = paths::root().join("symlink_dst");
1593 let result = match os::windows::fs::symlink_file(&src, &dst) {
1594 Ok(_) => {
1595 fs::remove_file(&dst).unwrap();
1596 true
1597 }
1598 Err(e) => {
1599 eprintln!(
1600 "symlinks not supported: {:?}\n\
1601 Windows 10 users should enable developer mode.",
1602 e
1603 );
1604 false
1605 }
1606 };
1607 fs::remove_file(&src).unwrap();
1608 return result;
1609}
1610
1611#[cfg(not(windows))]
1612pub fn symlink_supported() -> bool {
1613 true
1614}
1615
1616pub fn no_such_file_err_msg() -> String {
1618 std::io::Error::from_raw_os_error(2).to_string()
1619}
1620
1621#[track_caller]
1625pub fn retry<F, R>(n: u32, mut f: F) -> R
1626where
1627 F: FnMut() -> Option<R>,
1628{
1629 let mut count = 0;
1630 let start = std::time::Instant::now();
1631 loop {
1632 if let Some(r) = f() {
1633 return r;
1634 }
1635 count += 1;
1636 if count > n {
1637 panic!(
1638 "test did not finish within {n} attempts ({:?} total)",
1639 start.elapsed()
1640 );
1641 }
1642 sleep_ms(100);
1643 }
1644}
1645
1646#[test]
1647#[should_panic(expected = "test did not finish")]
1648fn retry_fails() {
1649 retry(2, || None::<()>);
1650}
1651
1652#[track_caller]
1654pub fn thread_wait_timeout<T>(n: u32, thread: JoinHandle<T>) -> T {
1655 retry(n, || thread.is_finished().then_some(()));
1656 thread.join().unwrap()
1657}
1658
1659#[track_caller]
1662pub fn threaded_timeout<F, R>(n: u32, f: F) -> R
1663where
1664 F: FnOnce() -> R + Send + 'static,
1665 R: Send + 'static,
1666{
1667 let thread = std::thread::spawn(|| f());
1668 thread_wait_timeout(n, thread)
1669}
1670
1671#[track_caller]
1673pub fn assert_deps(project: &Project, fingerprint: &str, test_cb: impl Fn(&Path, &[(u8, &str)])) {
1674 let mut files = project
1675 .glob(fingerprint)
1676 .map(|f| f.expect("unwrap glob result"))
1677 .filter(|f| f.extension().is_none());
1679 let info_path = files
1680 .next()
1681 .unwrap_or_else(|| panic!("expected 1 dep-info file at {}, found 0", fingerprint));
1682 assert!(files.next().is_none(), "expected only 1 dep-info file");
1683 let dep_info = fs::read(&info_path).unwrap();
1684 let dep_info = &mut &dep_info[..];
1685
1686 read_usize(dep_info);
1688 read_u8(dep_info);
1689 read_u8(dep_info);
1690
1691 let deps = (0..read_usize(dep_info))
1692 .map(|_| {
1693 let ty = read_u8(dep_info);
1694 let path = std::str::from_utf8(read_bytes(dep_info)).unwrap();
1695 let checksum_present = read_bool(dep_info);
1696 if checksum_present {
1697 let _file_len = read_u64(dep_info);
1699 let _checksum = read_bytes(dep_info);
1700 }
1701 (ty, path)
1702 })
1703 .collect::<Vec<_>>();
1704 test_cb(&info_path, &deps);
1705
1706 fn read_usize(bytes: &mut &[u8]) -> usize {
1707 let ret = &bytes[..4];
1708 *bytes = &bytes[4..];
1709
1710 u32::from_le_bytes(ret.try_into().unwrap()) as usize
1711 }
1712
1713 fn read_u8(bytes: &mut &[u8]) -> u8 {
1714 let ret = bytes[0];
1715 *bytes = &bytes[1..];
1716 ret
1717 }
1718
1719 fn read_bool(bytes: &mut &[u8]) -> bool {
1720 read_u8(bytes) != 0
1721 }
1722
1723 fn read_u64(bytes: &mut &[u8]) -> u64 {
1724 let ret = &bytes[..8];
1725 *bytes = &bytes[8..];
1726
1727 u64::from_le_bytes(ret.try_into().unwrap())
1728 }
1729
1730 fn read_bytes<'a>(bytes: &mut &'a [u8]) -> &'a [u8] {
1731 let n = read_usize(bytes);
1732 let ret = &bytes[..n];
1733 *bytes = &bytes[n..];
1734 ret
1735 }
1736}
1737
1738#[track_caller]
1739pub fn assert_deps_contains(project: &Project, fingerprint: &str, expected: &[(u8, &str)]) {
1740 assert_deps(project, fingerprint, |info_path, entries| {
1741 for (e_kind, e_path) in expected {
1742 let pattern = glob::Pattern::new(e_path).unwrap();
1743 let count = entries
1744 .iter()
1745 .filter(|(kind, path)| kind == e_kind && pattern.matches(path))
1746 .count();
1747 if count != 1 {
1748 panic!(
1749 "Expected 1 match of {} {} in {:?}, got {}:\n{:#?}",
1750 e_kind, e_path, info_path, count, entries
1751 );
1752 }
1753 }
1754 })
1755}
1756
1757#[track_caller]
1758pub fn assert_deterministic_mtime(path: impl AsRef<Path>) {
1759 const DETERMINISTIC_TIMESTAMP: u64 = 1153704088;
1762
1763 let path = path.as_ref();
1764 let mtime = path.metadata().unwrap().modified().unwrap();
1765 let timestamp = mtime
1766 .duration_since(std::time::UNIX_EPOCH)
1767 .unwrap()
1768 .as_secs();
1769 assert_eq!(
1770 timestamp, DETERMINISTIC_TIMESTAMP,
1771 "expected deterministic mtime for {path:?}, got {timestamp}"
1772 );
1773}