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 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 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 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 pub fn root(&self) -> PathBuf {
387 self.root.clone()
388 }
389
390 pub fn build_dir(&self) -> PathBuf {
394 self.root().join("target")
395 }
396
397 pub fn target_debug_dir(&self) -> PathBuf {
401 self.build_dir().join("debug")
402 }
403
404 pub fn url(&self) -> Url {
408 use paths::CargoPathExt;
409 self.root().to_url()
410 }
411
412 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 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 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 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 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 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 pub fn change_file(&self, path: impl AsRef<Path>, body: &str) {
478 FileBuilder::new(self.root().join(path), body, false).mk()
479 }
480
481 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 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 pub fn read_lockfile(&self) -> String {
522 self.read_file("Cargo.lock")
523 }
524
525 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 #[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 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
578pub fn project() -> ProjectBuilder {
580 ProjectBuilder::new(paths::root().join("foo"))
581}
582
583pub fn project_in(dir: impl AsRef<Path>) -> ProjectBuilder {
585 ProjectBuilder::new(paths::root().join(dir).join("foo"))
586}
587
588pub fn project_in_home(name: impl AsRef<Path>) -> ProjectBuilder {
590 ProjectBuilder::new(paths::home().join(name))
591}
592
593pub 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
623pub struct RawOutput {
631 pub code: Option<i32>,
632 pub stdout: Vec<u8>,
633 pub stderr: Vec<u8>,
634}
635
636#[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
667impl Execs {
669 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 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 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 pub fn with_status(&mut self, expected: i32) -> &mut Self {
797 self.expect_exit_code = Some(expected);
798 self
799 }
800
801 pub fn without_status(&mut self) -> &mut Self {
805 self.expect_exit_code = None;
806 self
807 }
808
809 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 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 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 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 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
929impl Execs {
931 #[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 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 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
1030impl Execs {
1032 pub fn exec_with_output(&mut self) -> Result<Output> {
1033 self.ran = true;
1034 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 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 #[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
1222pub 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
1241pub 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
1255pub 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
1274pub 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
1293pub 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
1345pub fn rustc_host() -> &'static str {
1347 &rustc_info().host
1348}
1349
1350pub 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 env::var("CARGO_TEST_DISABLE_NIGHTLY").is_err()
1362 && (vv.contains("-nightly") || vv.contains("-dev"))
1363}
1364
1365pub 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
1380pub trait ChannelChangerCommandExt {
1382 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
1400pub trait TestEnvCommandExt: Sized {
1402 fn test_env(mut self) -> Self {
1403 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 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 .env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "stable")
1447 .env("__CARGO_TEST_DISABLE_GLOBAL_KNOWN_HOST", "1")
1449 .env("__CARGO_TEST_FIXED_RETRY_SLEEP_MS", "1")
1451 .env("__CARGO_TEST_TTY_WIDTH_DO_NOT_USE_THIS", "400")
1458 .env("CARGO_INCREMENTAL", "0")
1462 .env("GIT_CONFIG_NOSYSTEM", "1")
1464 .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") .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") .env_remove("USER") .env_remove("XDG_CONFIG_HOME") .env_remove("OUT_DIR"); 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
1532pub 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
1569pub 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
1580pub fn is_coarse_mtime() -> bool {
1582 cfg!(emulate_second_only_system) ||
1585 cfg!(target_os = "macos") && is_ci()
1589}
1590
1591pub 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 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
1638pub fn no_such_file_err_msg() -> String {
1640 std::io::Error::from_raw_os_error(2).to_string()
1641}
1642
1643#[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#[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#[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#[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(|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 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 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 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}