1#![allow(clippy::disallowed_methods)]
43#![allow(clippy::print_stderr)]
44#![allow(clippy::print_stdout)]
45
46use std::env;
47use std::ffi::OsStr;
48use std::fmt::Write;
49use std::fs;
50use std::os;
51use std::path::{Path, PathBuf};
52use std::process::{Command, Output};
53use std::sync::LazyLock;
54use std::sync::OnceLock;
55use std::thread::JoinHandle;
56use std::time::{self, Duration};
57
58use anyhow::{Result, bail};
59use cargo_util::{ProcessError, is_ci};
60use snapbox::IntoData as _;
61use url::Url;
62
63use self::paths::CargoPathExt;
64
65#[macro_export]
74macro_rules! t {
75 ($e:expr) => {
76 match $e {
77 Ok(e) => e,
78 Err(e) => $crate::panic_error(&format!("failed running {}", stringify!($e)), e),
79 }
80 };
81}
82
83pub use cargo_util::ProcessBuilder;
84#[doc(inline)]
85pub use snapbox;
86pub use snapbox::file;
87pub use snapbox::str;
88pub use snapbox::utils::current_dir;
89
90#[track_caller]
92pub fn panic_error(what: &str, err: impl Into<anyhow::Error>) -> ! {
93 let err = err.into();
94 pe(what, err);
95 #[track_caller]
96 fn pe(what: &str, err: anyhow::Error) -> ! {
97 let mut result = format!("{}\nerror: {}", what, err);
98 for cause in err.chain().skip(1) {
99 let _ = writeln!(result, "\nCaused by:");
100 let _ = write!(result, "{}", cause);
101 }
102 panic!("\n{}", result);
103 }
104}
105
106pub use cargo_test_macro::cargo_test;
107
108pub mod compare;
109pub mod containers;
110pub mod cross_compile;
111pub mod git;
112pub mod install;
113pub mod paths;
114pub mod publish;
115pub mod registry;
116
117pub mod prelude {
118 pub use crate::ArgLineCommandExt;
119 pub use crate::ChannelChangerCommandExt;
120 pub use crate::TestEnvCommandExt;
121 pub use crate::cargo_test;
122 pub use crate::paths::CargoPathExt;
123 pub use snapbox::IntoData;
124}
125
126#[derive(PartialEq, Clone)]
133struct FileBuilder {
134 path: PathBuf,
135 body: String,
136 executable: bool,
137}
138
139impl FileBuilder {
140 pub fn new(path: PathBuf, body: &str, executable: bool) -> FileBuilder {
141 FileBuilder {
142 path,
143 body: body.to_string(),
144 executable: executable,
145 }
146 }
147
148 fn mk(&mut self) {
149 if self.executable {
150 let mut path = self.path.clone().into_os_string();
151 write!(path, "{}", env::consts::EXE_SUFFIX).unwrap();
152 self.path = path.into();
153 }
154
155 self.dirname().mkdir_p();
156 fs::write(&self.path, &self.body)
157 .unwrap_or_else(|e| panic!("could not create file {}: {}", self.path.display(), e));
158
159 #[cfg(unix)]
160 if self.executable {
161 use std::os::unix::fs::PermissionsExt;
162
163 let mut perms = fs::metadata(&self.path).unwrap().permissions();
164 let mode = perms.mode();
165 perms.set_mode(mode | 0o111);
166 fs::set_permissions(&self.path, perms).unwrap();
167 }
168 }
169
170 fn dirname(&self) -> &Path {
171 self.path.parent().unwrap()
172 }
173}
174
175#[derive(PartialEq, Clone)]
176struct SymlinkBuilder {
177 dst: PathBuf,
178 src: PathBuf,
179 src_is_dir: bool,
180}
181
182impl SymlinkBuilder {
183 pub fn new(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
184 SymlinkBuilder {
185 dst,
186 src,
187 src_is_dir: false,
188 }
189 }
190
191 pub fn new_dir(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
192 SymlinkBuilder {
193 dst,
194 src,
195 src_is_dir: true,
196 }
197 }
198
199 #[cfg(unix)]
200 fn mk(&self) {
201 self.dirname().mkdir_p();
202 t!(os::unix::fs::symlink(&self.dst, &self.src));
203 }
204
205 #[cfg(windows)]
206 fn mk(&mut self) {
207 self.dirname().mkdir_p();
208 if self.src_is_dir {
209 t!(os::windows::fs::symlink_dir(&self.dst, &self.src));
210 } else {
211 if let Some(ext) = self.dst.extension() {
212 if ext == env::consts::EXE_EXTENSION {
213 self.src.set_extension(ext);
214 }
215 }
216 t!(os::windows::fs::symlink_file(&self.dst, &self.src));
217 }
218 }
219
220 fn dirname(&self) -> &Path {
221 self.src.parent().unwrap()
222 }
223}
224
225pub struct Project {
229 root: PathBuf,
230}
231
232#[must_use]
242pub struct ProjectBuilder {
243 root: Project,
244 files: Vec<FileBuilder>,
245 symlinks: Vec<SymlinkBuilder>,
246 no_manifest: bool,
247}
248
249impl ProjectBuilder {
250 pub fn root(&self) -> PathBuf {
254 self.root.root()
255 }
256
257 pub fn target_debug_dir(&self) -> PathBuf {
261 self.root.target_debug_dir()
262 }
263
264 pub fn new(root: PathBuf) -> ProjectBuilder {
266 ProjectBuilder {
267 root: Project { root },
268 files: vec![],
269 symlinks: vec![],
270 no_manifest: false,
271 }
272 }
273
274 pub fn at<P: AsRef<Path>>(mut self, path: P) -> Self {
276 self.root = Project {
277 root: paths::root().join(path),
278 };
279 self
280 }
281
282 pub fn file<B: AsRef<Path>>(mut self, path: B, body: &str) -> Self {
284 self._file(path.as_ref(), body, false);
285 self
286 }
287
288 pub fn executable<B: AsRef<Path>>(mut self, path: B, body: &str) -> Self {
290 self._file(path.as_ref(), body, true);
291 self
292 }
293
294 fn _file(&mut self, path: &Path, body: &str, executable: bool) {
295 self.files.push(FileBuilder::new(
296 self.root.root().join(path),
297 body,
298 executable,
299 ));
300 }
301
302 pub fn symlink(mut self, dst: impl AsRef<Path>, src: impl AsRef<Path>) -> Self {
304 self.symlinks.push(SymlinkBuilder::new(
305 self.root.root().join(dst),
306 self.root.root().join(src),
307 ));
308 self
309 }
310
311 pub fn symlink_dir(mut self, dst: impl AsRef<Path>, src: impl AsRef<Path>) -> Self {
313 self.symlinks.push(SymlinkBuilder::new_dir(
314 self.root.root().join(dst),
315 self.root.root().join(src),
316 ));
317 self
318 }
319
320 pub fn no_manifest(mut self) -> Self {
321 self.no_manifest = true;
322 self
323 }
324
325 pub fn build(mut self) -> Project {
327 self.rm_root();
329
330 self.root.root().mkdir_p();
332
333 let manifest_path = self.root.root().join("Cargo.toml");
334 if !self.no_manifest && self.files.iter().all(|fb| fb.path != manifest_path) {
335 self._file(
336 Path::new("Cargo.toml"),
337 &basic_manifest("foo", "0.0.1"),
338 false,
339 )
340 }
341
342 let past = time::SystemTime::now() - Duration::new(1, 0);
343 let ftime = filetime::FileTime::from_system_time(past);
344
345 for file in self.files.iter_mut() {
346 file.mk();
347 if is_coarse_mtime() {
348 filetime::set_file_times(&file.path, ftime, ftime).unwrap();
354 }
355 }
356
357 for symlink in self.symlinks.iter_mut() {
358 symlink.mk();
359 }
360
361 let ProjectBuilder { root, .. } = self;
362 root
363 }
364
365 fn rm_root(&self) {
366 self.root.root().rm_rf()
367 }
368}
369
370impl Project {
371 pub fn from_template(template_path: impl AsRef<Path>) -> Self {
373 let root = paths::root();
374 let project_root = root.join("case");
375 snapbox::dir::copy_template(template_path.as_ref(), &project_root).unwrap();
376 Self { root: project_root }
377 }
378
379 pub fn root(&self) -> PathBuf {
383 self.root.clone()
384 }
385
386 pub fn build_dir(&self) -> PathBuf {
390 self.root().join("target")
391 }
392
393 pub fn target_debug_dir(&self) -> PathBuf {
397 self.build_dir().join("debug")
398 }
399
400 pub fn url(&self) -> Url {
404 use paths::CargoPathExt;
405 self.root().to_url()
406 }
407
408 pub fn example_lib(&self, name: &str, kind: &str) -> PathBuf {
414 self.target_debug_dir()
415 .join("examples")
416 .join(paths::get_lib_filename(name, kind))
417 }
418
419 pub fn dylib(&self, name: &str) -> PathBuf {
422 self.target_debug_dir().join(format!(
423 "{}{name}{}",
424 env::consts::DLL_PREFIX,
425 env::consts::DLL_SUFFIX
426 ))
427 }
428
429 pub fn bin(&self, b: &str) -> PathBuf {
433 self.build_dir()
434 .join("debug")
435 .join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
436 }
437
438 pub fn release_bin(&self, b: &str) -> PathBuf {
442 self.build_dir()
443 .join("release")
444 .join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
445 }
446
447 pub fn target_bin(&self, target: &str, b: &str) -> PathBuf {
451 self.build_dir().join(target).join("debug").join(&format!(
452 "{}{}",
453 b,
454 env::consts::EXE_SUFFIX
455 ))
456 }
457
458 pub fn glob<P: AsRef<Path>>(&self, pattern: P) -> glob::Paths {
460 let pattern = self.root().join(pattern);
461 glob::glob(pattern.to_str().expect("failed to convert pattern to str"))
462 .expect("failed to glob")
463 }
464
465 pub fn change_file(&self, path: impl AsRef<Path>, body: &str) {
474 FileBuilder::new(self.root().join(path), body, false).mk()
475 }
476
477 pub fn process<T: AsRef<OsStr>>(&self, program: T) -> Execs {
490 let mut p = process(program);
491 p.cwd(self.root());
492 execs().with_process_builder(p)
493 }
494
495 pub fn rename_run(&self, src: &str, dst: &str) -> Execs {
509 let src = self.bin(src);
510 let dst = self.bin(dst);
511 fs::rename(&src, &dst)
512 .unwrap_or_else(|e| panic!("Failed to rename `{:?}` to `{:?}`: {}", src, dst, e));
513 self.process(dst)
514 }
515
516 pub fn read_lockfile(&self) -> String {
518 self.read_file("Cargo.lock")
519 }
520
521 pub fn read_file(&self, path: impl AsRef<Path>) -> String {
523 let full = self.root().join(path);
524 fs::read_to_string(&full)
525 .unwrap_or_else(|e| panic!("could not read file {}: {}", full.display(), e))
526 }
527
528 pub fn uncomment_root_manifest(&self) {
530 let contents = self.read_file("Cargo.toml").replace("#", "");
531 fs::write(self.root().join("Cargo.toml"), contents).unwrap();
532 }
533
534 pub fn symlink(&self, src: impl AsRef<Path>, dst: impl AsRef<Path>) {
535 let src = self.root().join(src.as_ref());
536 let dst = self.root().join(dst.as_ref());
537 #[cfg(unix)]
538 {
539 if let Err(e) = os::unix::fs::symlink(&src, &dst) {
540 panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
541 }
542 }
543 #[cfg(windows)]
544 {
545 if src.is_dir() {
546 if let Err(e) = os::windows::fs::symlink_dir(&src, &dst) {
547 panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
548 }
549 } else {
550 if let Err(e) = os::windows::fs::symlink_file(&src, &dst) {
551 panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
552 }
553 }
554 }
555 }
556}
557
558pub fn project() -> ProjectBuilder {
560 ProjectBuilder::new(paths::root().join("foo"))
561}
562
563pub fn project_in(dir: impl AsRef<Path>) -> ProjectBuilder {
565 ProjectBuilder::new(paths::root().join(dir).join("foo"))
566}
567
568pub fn project_in_home(name: impl AsRef<Path>) -> ProjectBuilder {
570 ProjectBuilder::new(paths::home().join(name))
571}
572
573pub fn main_file(println: &str, externed_deps: &[&str]) -> String {
590 let mut buf = String::new();
591
592 for dep in externed_deps.iter() {
593 buf.push_str(&format!("extern crate {};\n", dep));
594 }
595
596 buf.push_str("fn main() { println!(");
597 buf.push_str(println);
598 buf.push_str("); }\n");
599
600 buf
601}
602
603pub struct RawOutput {
611 pub code: Option<i32>,
612 pub stdout: Vec<u8>,
613 pub stderr: Vec<u8>,
614}
615
616#[must_use]
623#[derive(Clone)]
624pub struct Execs {
625 ran: bool,
626 process_builder: Option<ProcessBuilder>,
627 expect_stdin: Option<String>,
628 expect_exit_code: Option<i32>,
629 expect_stdout_data: Option<snapbox::Data>,
630 expect_stderr_data: Option<snapbox::Data>,
631 expect_stdout_contains: Vec<String>,
632 expect_stderr_contains: Vec<String>,
633 expect_stdout_not_contains: Vec<String>,
634 expect_stderr_not_contains: Vec<String>,
635 expect_stderr_with_without: Vec<(Vec<String>, Vec<String>)>,
636 stream_output: bool,
637 assert: snapbox::Assert,
638}
639
640impl Execs {
641 pub fn with_process_builder(mut self, p: ProcessBuilder) -> Execs {
642 self.process_builder = Some(p);
643 self
644 }
645}
646
647impl Execs {
649 pub fn with_stdout_data(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
704 self.expect_stdout_data = Some(expected.into_data());
705 self
706 }
707
708 pub fn with_stderr_data(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
763 self.expect_stderr_data = Some(expected.into_data());
764 self
765 }
766
767 pub fn with_stdin<S: ToString>(&mut self, expected: S) -> &mut Self {
769 self.expect_stdin = Some(expected.to_string());
770 self
771 }
772
773 pub fn with_status(&mut self, expected: i32) -> &mut Self {
777 self.expect_exit_code = Some(expected);
778 self
779 }
780
781 pub fn without_status(&mut self) -> &mut Self {
785 self.expect_exit_code = None;
786 self
787 }
788
789 pub fn with_stdout_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
802 self.expect_stdout_contains.push(expected.to_string());
803 self
804 }
805
806 pub fn with_stderr_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
819 self.expect_stderr_contains.push(expected.to_string());
820 self
821 }
822
823 pub fn with_stdout_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
841 self.expect_stdout_not_contains.push(expected.to_string());
842 self
843 }
844
845 pub fn with_stderr_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
862 self.expect_stderr_not_contains.push(expected.to_string());
863 self
864 }
865
866 pub fn with_stderr_line_without<S: ToString>(
898 &mut self,
899 with: &[S],
900 without: &[S],
901 ) -> &mut Self {
902 let with = with.iter().map(|s| s.to_string()).collect();
903 let without = without.iter().map(|s| s.to_string()).collect();
904 self.expect_stderr_with_without.push((with, without));
905 self
906 }
907}
908
909impl Execs {
911 #[allow(unused)]
915 pub fn stream(&mut self) -> &mut Self {
916 self.stream_output = true;
917 self
918 }
919
920 pub fn arg<T: AsRef<OsStr>>(&mut self, arg: T) -> &mut Self {
921 if let Some(ref mut p) = self.process_builder {
922 p.arg(arg);
923 }
924 self
925 }
926
927 pub fn args<T: AsRef<OsStr>>(&mut self, args: &[T]) -> &mut Self {
928 if let Some(ref mut p) = self.process_builder {
929 p.args(args);
930 }
931 self
932 }
933
934 pub fn cwd<T: AsRef<OsStr>>(&mut self, path: T) -> &mut Self {
935 if let Some(ref mut p) = self.process_builder {
936 if let Some(cwd) = p.get_cwd() {
937 let new_path = cwd.join(path.as_ref());
938 p.cwd(new_path);
939 } else {
940 p.cwd(path);
941 }
942 }
943 self
944 }
945
946 pub fn env<T: AsRef<OsStr>>(&mut self, key: &str, val: T) -> &mut Self {
947 if let Some(ref mut p) = self.process_builder {
948 p.env(key, val);
949 }
950 self
951 }
952
953 pub fn env_remove(&mut self, key: &str) -> &mut Self {
954 if let Some(ref mut p) = self.process_builder {
955 p.env_remove(key);
956 }
957 self
958 }
959
960 pub fn masquerade_as_nightly_cargo(&mut self, reasons: &[&str]) -> &mut Self {
966 if let Some(ref mut p) = self.process_builder {
967 p.masquerade_as_nightly_cargo(reasons);
968 }
969 self
970 }
971
972 pub fn replace_crates_io(&mut self, url: &Url) -> &mut Self {
977 if let Some(ref mut p) = self.process_builder {
978 p.env("__CARGO_TEST_CRATES_IO_URL_DO_NOT_USE_THIS", url.as_str());
979 }
980 self
981 }
982
983 pub fn overlay_registry(&mut self, url: &Url, path: &str) -> &mut Self {
984 if let Some(ref mut p) = self.process_builder {
985 let env_value = format!("{}={}", url, path);
986 p.env(
987 "__CARGO_TEST_DEPENDENCY_CONFUSION_VULNERABILITY_DO_NOT_USE_THIS",
988 env_value,
989 );
990 }
991 self
992 }
993
994 pub fn enable_split_debuginfo_packed(&mut self) -> &mut Self {
995 self.env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "packed")
996 .env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "packed")
997 .env("CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO", "packed")
998 .env("CARGO_PROFILE_BENCH_SPLIT_DEBUGINFO", "packed");
999 self
1000 }
1001
1002 pub fn enable_mac_dsym(&mut self) -> &mut Self {
1003 if cfg!(target_os = "macos") {
1004 return self.enable_split_debuginfo_packed();
1005 }
1006 self
1007 }
1008}
1009
1010impl Execs {
1012 pub fn exec_with_output(&mut self) -> Result<Output> {
1013 self.ran = true;
1014 let p = (&self.process_builder).clone().unwrap();
1016 p.exec_with_output()
1017 }
1018
1019 pub fn build_command(&mut self) -> Command {
1020 self.ran = true;
1021 let p = (&self.process_builder).clone().unwrap();
1023 p.build_command()
1024 }
1025
1026 #[track_caller]
1027 pub fn run(&mut self) -> RawOutput {
1028 self.ran = true;
1029 let mut p = (&self.process_builder).clone().unwrap();
1030 if let Some(stdin) = self.expect_stdin.take() {
1031 p.stdin(stdin);
1032 }
1033
1034 match self.match_process(&p) {
1035 Err(e) => panic_error(&format!("test failed running {}", p), e),
1036 Ok(output) => output,
1037 }
1038 }
1039
1040 #[track_caller]
1043 pub fn run_json(&mut self) -> serde_json::Value {
1044 let output = self.run();
1045 serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {
1046 panic!(
1047 "\nfailed to parse JSON: {}\n\
1048 output was:\n{}\n",
1049 e,
1050 String::from_utf8_lossy(&output.stdout)
1051 );
1052 })
1053 }
1054
1055 #[track_caller]
1056 pub fn run_output(&mut self, output: &Output) {
1057 self.ran = true;
1058 if let Err(e) = self.match_output(output.status.code(), &output.stdout, &output.stderr) {
1059 panic_error("process did not return the expected result", e)
1060 }
1061 }
1062
1063 #[track_caller]
1064 fn verify_checks_output(&self, stdout: &[u8], stderr: &[u8]) {
1065 if self.expect_exit_code.unwrap_or(0) != 0
1066 && self.expect_stdin.is_none()
1067 && self.expect_stdout_data.is_none()
1068 && self.expect_stderr_data.is_none()
1069 && self.expect_stdout_contains.is_empty()
1070 && self.expect_stderr_contains.is_empty()
1071 && self.expect_stdout_not_contains.is_empty()
1072 && self.expect_stderr_not_contains.is_empty()
1073 && self.expect_stderr_with_without.is_empty()
1074 {
1075 panic!(
1076 "`with_status()` is used, but no output is checked.\n\
1077 The test must check the output to ensure the correct error is triggered.\n\
1078 --- stdout\n{}\n--- stderr\n{}",
1079 String::from_utf8_lossy(stdout),
1080 String::from_utf8_lossy(stderr),
1081 );
1082 }
1083 }
1084
1085 #[track_caller]
1086 fn match_process(&self, process: &ProcessBuilder) -> Result<RawOutput> {
1087 println!("running {}", process);
1088 let res = if self.stream_output {
1089 if is_ci() {
1090 panic!("`.stream()` is for local debugging")
1091 }
1092 process.exec_with_streaming(
1093 &mut |out| {
1094 println!("{}", out);
1095 Ok(())
1096 },
1097 &mut |err| {
1098 eprintln!("{}", err);
1099 Ok(())
1100 },
1101 true,
1102 )
1103 } else {
1104 process.exec_with_output()
1105 };
1106
1107 match res {
1108 Ok(out) => {
1109 self.match_output(out.status.code(), &out.stdout, &out.stderr)?;
1110 return Ok(RawOutput {
1111 stdout: out.stdout,
1112 stderr: out.stderr,
1113 code: out.status.code(),
1114 });
1115 }
1116 Err(e) => {
1117 if let Some(ProcessError {
1118 stdout: Some(stdout),
1119 stderr: Some(stderr),
1120 code,
1121 ..
1122 }) = e.downcast_ref::<ProcessError>()
1123 {
1124 self.match_output(*code, stdout, stderr)?;
1125 return Ok(RawOutput {
1126 stdout: stdout.to_vec(),
1127 stderr: stderr.to_vec(),
1128 code: *code,
1129 });
1130 }
1131 bail!("could not exec process {}: {:?}", process, e)
1132 }
1133 }
1134 }
1135
1136 #[track_caller]
1137 fn match_output(&self, code: Option<i32>, stdout: &[u8], stderr: &[u8]) -> Result<()> {
1138 self.verify_checks_output(stdout, stderr);
1139 let stdout = std::str::from_utf8(stdout).expect("stdout is not utf8");
1140 let stderr = std::str::from_utf8(stderr).expect("stderr is not utf8");
1141
1142 match self.expect_exit_code {
1143 None => {}
1144 Some(expected) if code == Some(expected) => {}
1145 Some(expected) => bail!(
1146 "process exited with code {} (expected {})\n--- stdout\n{}\n--- stderr\n{}",
1147 code.unwrap_or(-1),
1148 expected,
1149 stdout,
1150 stderr
1151 ),
1152 }
1153
1154 if let Some(expect_stdout_data) = &self.expect_stdout_data {
1155 if let Err(err) = self.assert.try_eq(
1156 Some(&"stdout"),
1157 stdout.into_data(),
1158 expect_stdout_data.clone(),
1159 ) {
1160 panic!("{err}")
1161 }
1162 }
1163 if let Some(expect_stderr_data) = &self.expect_stderr_data {
1164 if let Err(err) = self.assert.try_eq(
1165 Some(&"stderr"),
1166 stderr.into_data(),
1167 expect_stderr_data.clone(),
1168 ) {
1169 panic!("{err}")
1170 }
1171 }
1172 for expect in self.expect_stdout_contains.iter() {
1173 compare::match_contains(expect, stdout, self.assert.redactions())?;
1174 }
1175 for expect in self.expect_stderr_contains.iter() {
1176 compare::match_contains(expect, stderr, self.assert.redactions())?;
1177 }
1178 for expect in self.expect_stdout_not_contains.iter() {
1179 compare::match_does_not_contain(expect, stdout, self.assert.redactions())?;
1180 }
1181 for expect in self.expect_stderr_not_contains.iter() {
1182 compare::match_does_not_contain(expect, stderr, self.assert.redactions())?;
1183 }
1184 for (with, without) in self.expect_stderr_with_without.iter() {
1185 compare::match_with_without(stderr, with, without, self.assert.redactions())?;
1186 }
1187 Ok(())
1188 }
1189}
1190
1191impl Drop for Execs {
1192 fn drop(&mut self) {
1193 if !self.ran && !std::thread::panicking() {
1194 panic!("forgot to run this command");
1195 }
1196 }
1197}
1198
1199pub fn execs() -> Execs {
1201 Execs {
1202 ran: false,
1203 process_builder: None,
1204 expect_stdin: None,
1205 expect_exit_code: Some(0),
1206 expect_stdout_data: None,
1207 expect_stderr_data: None,
1208 expect_stdout_contains: Vec::new(),
1209 expect_stderr_contains: Vec::new(),
1210 expect_stdout_not_contains: Vec::new(),
1211 expect_stderr_not_contains: Vec::new(),
1212 expect_stderr_with_without: Vec::new(),
1213 stream_output: false,
1214 assert: compare::assert_e2e(),
1215 }
1216}
1217
1218pub fn basic_manifest(name: &str, version: &str) -> String {
1220 format!(
1221 r#"
1222 [package]
1223 name = "{}"
1224 version = "{}"
1225 authors = []
1226 edition = "2015"
1227 "#,
1228 name, version
1229 )
1230}
1231
1232pub fn basic_bin_manifest(name: &str) -> String {
1234 format!(
1235 r#"
1236 [package]
1237
1238 name = "{}"
1239 version = "0.5.0"
1240 authors = ["wycats@example.com"]
1241 edition = "2015"
1242
1243 [[bin]]
1244
1245 name = "{}"
1246 "#,
1247 name, name
1248 )
1249}
1250
1251pub fn basic_lib_manifest(name: &str) -> String {
1253 format!(
1254 r#"
1255 [package]
1256
1257 name = "{}"
1258 version = "0.5.0"
1259 authors = ["wycats@example.com"]
1260 edition = "2015"
1261
1262 [lib]
1263
1264 name = "{}"
1265 "#,
1266 name, name
1267 )
1268}
1269
1270pub fn target_spec_json() -> &'static str {
1275 static TARGET_SPEC_JSON: LazyLock<String> = LazyLock::new(|| {
1276 let json = std::process::Command::new("rustc")
1277 .env("RUSTC_BOOTSTRAP", "1")
1278 .arg("--print")
1279 .arg("target-spec-json")
1280 .arg("-Zunstable-options")
1281 .arg("--target")
1282 .arg("x86_64-unknown-none")
1283 .output()
1284 .expect("rustc --print target-spec-json")
1285 .stdout;
1286 String::from_utf8(json).expect("utf8 target spec json")
1287 });
1288
1289 TARGET_SPEC_JSON.as_str()
1290}
1291
1292struct RustcInfo {
1293 verbose_version: String,
1294 host: String,
1295}
1296
1297impl RustcInfo {
1298 fn new() -> RustcInfo {
1299 let output = ProcessBuilder::new("rustc")
1300 .arg("-vV")
1301 .exec_with_output()
1302 .expect("rustc should exec");
1303 let verbose_version = String::from_utf8(output.stdout).expect("utf8 output");
1304 let host = verbose_version
1305 .lines()
1306 .filter_map(|line| line.strip_prefix("host: "))
1307 .next()
1308 .expect("verbose version has host: field")
1309 .to_string();
1310 RustcInfo {
1311 verbose_version,
1312 host,
1313 }
1314 }
1315}
1316
1317fn rustc_info() -> &'static RustcInfo {
1318 static RUSTC_INFO: OnceLock<RustcInfo> = OnceLock::new();
1319 RUSTC_INFO.get_or_init(RustcInfo::new)
1320}
1321
1322pub fn rustc_host() -> &'static str {
1324 &rustc_info().host
1325}
1326
1327pub fn rustc_host_env() -> String {
1329 rustc_host().to_uppercase().replace('-', "_")
1330}
1331
1332pub fn is_nightly() -> bool {
1333 let vv = &rustc_info().verbose_version;
1334 env::var("CARGO_TEST_DISABLE_NIGHTLY").is_err()
1339 && (vv.contains("-nightly") || vv.contains("-dev"))
1340}
1341
1342pub fn process<T: AsRef<OsStr>>(bin: T) -> ProcessBuilder {
1348 _process(bin.as_ref())
1349}
1350
1351fn _process(t: &OsStr) -> ProcessBuilder {
1352 let mut p = ProcessBuilder::new(t);
1353 p.cwd(&paths::root()).test_env();
1354 p
1355}
1356
1357pub trait ChannelChangerCommandExt {
1359 fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self;
1363}
1364
1365impl ChannelChangerCommandExt for &mut ProcessBuilder {
1366 fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self {
1367 self.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly")
1368 }
1369}
1370
1371impl ChannelChangerCommandExt for snapbox::cmd::Command {
1372 fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self {
1373 self.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly")
1374 }
1375}
1376
1377pub trait TestEnvCommandExt: Sized {
1379 fn test_env(mut self) -> Self {
1380 for (k, _v) in env::vars() {
1384 if k.starts_with("CARGO_") {
1385 self = self.env_remove(&k);
1386 }
1387 }
1388 if env::var_os("RUSTUP_TOOLCHAIN").is_some() {
1389 static RUSTC_DIR: OnceLock<PathBuf> = OnceLock::new();
1392 let rustc_dir = RUSTC_DIR.get_or_init(|| {
1393 match ProcessBuilder::new("rustup")
1394 .args(&["which", "rustc"])
1395 .exec_with_output()
1396 {
1397 Ok(output) => {
1398 let s = std::str::from_utf8(&output.stdout).expect("utf8").trim();
1399 let mut p = PathBuf::from(s);
1400 p.pop();
1401 p
1402 }
1403 Err(e) => {
1404 panic!("RUSTUP_TOOLCHAIN was set, but could not run rustup: {}", e);
1405 }
1406 }
1407 });
1408 let path = env::var_os("PATH").unwrap_or_default();
1409 let paths = env::split_paths(&path);
1410 let new_path =
1411 env::join_paths(std::iter::once(rustc_dir.clone()).chain(paths)).unwrap();
1412 self = self.env("PATH", new_path);
1413 }
1414
1415 self = self
1416 .current_dir(&paths::root())
1417 .env("HOME", paths::home())
1418 .env("CARGO_HOME", paths::cargo_home())
1419 .env("__CARGO_TEST_ROOT", paths::global_root())
1420 .env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "stable")
1424 .env("__CARGO_TEST_DISABLE_GLOBAL_KNOWN_HOST", "1")
1426 .env("__CARGO_TEST_FIXED_RETRY_SLEEP_MS", "1")
1428 .env("__CARGO_TEST_TTY_WIDTH_DO_NOT_USE_THIS", "400")
1435 .env("CARGO_INCREMENTAL", "0")
1439 .env("GIT_CONFIG_NOSYSTEM", "1")
1441 .env_remove("__CARGO_DEFAULT_LIB_METADATA")
1442 .env_remove("ALL_PROXY")
1443 .env_remove("EMAIL")
1444 .env_remove("GIT_AUTHOR_EMAIL")
1445 .env_remove("GIT_AUTHOR_NAME")
1446 .env_remove("GIT_COMMITTER_EMAIL")
1447 .env_remove("GIT_COMMITTER_NAME")
1448 .env_remove("http_proxy")
1449 .env_remove("HTTPS_PROXY")
1450 .env_remove("https_proxy")
1451 .env_remove("MAKEFLAGS")
1452 .env_remove("MFLAGS")
1453 .env_remove("MSYSTEM") .env_remove("RUSTC")
1455 .env_remove("RUST_BACKTRACE")
1456 .env_remove("RUSTC_WORKSPACE_WRAPPER")
1457 .env_remove("RUSTC_WRAPPER")
1458 .env_remove("RUSTDOC")
1459 .env_remove("RUSTDOCFLAGS")
1460 .env_remove("RUSTFLAGS")
1461 .env_remove("RUSTUP_TOOLCHAIN_SOURCE")
1462 .env_remove("SSH_AUTH_SOCK") .env_remove("USER") .env_remove("XDG_CONFIG_HOME") .env_remove("OUT_DIR"); if cfg!(windows) {
1467 self = self.env("USERPROFILE", paths::home());
1468 }
1469 self
1470 }
1471
1472 fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self;
1473 fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self;
1474 fn env_remove(self, key: &str) -> Self;
1475}
1476
1477impl TestEnvCommandExt for &mut ProcessBuilder {
1478 fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self {
1479 let path = path.as_ref();
1480 self.cwd(path)
1481 }
1482 fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self {
1483 self.env(key, value)
1484 }
1485 fn env_remove(self, key: &str) -> Self {
1486 self.env_remove(key)
1487 }
1488}
1489
1490impl TestEnvCommandExt for snapbox::cmd::Command {
1491 fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self {
1492 self.current_dir(path)
1493 }
1494 fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self {
1495 self.env(key, value)
1496 }
1497 fn env_remove(self, key: &str) -> Self {
1498 self.env_remove(key)
1499 }
1500}
1501
1502pub trait ArgLineCommandExt: Sized {
1504 fn arg_line(mut self, s: &str) -> Self {
1505 for mut arg in s.split_whitespace() {
1506 if (arg.starts_with('"') && arg.ends_with('"'))
1507 || (arg.starts_with('\'') && arg.ends_with('\''))
1508 {
1509 arg = &arg[1..(arg.len() - 1).max(1)];
1510 } else if arg.contains(&['"', '\''][..]) {
1511 panic!("shell-style argument parsing is not supported")
1512 }
1513 self = self.arg(arg);
1514 }
1515 self
1516 }
1517
1518 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self;
1519}
1520
1521impl ArgLineCommandExt for &mut ProcessBuilder {
1522 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
1523 self.arg(s)
1524 }
1525}
1526
1527impl ArgLineCommandExt for &mut Execs {
1528 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
1529 self.arg(s)
1530 }
1531}
1532
1533impl ArgLineCommandExt for snapbox::cmd::Command {
1534 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
1535 self.arg(s)
1536 }
1537}
1538
1539pub fn git_process(arg_line: &str) -> ProcessBuilder {
1541 let mut p = process("git");
1542 p.arg_line(arg_line);
1543 p
1544}
1545
1546pub fn sleep_ms(ms: u64) {
1547 ::std::thread::sleep(Duration::from_millis(ms));
1548}
1549
1550pub fn is_coarse_mtime() -> bool {
1552 cfg!(emulate_second_only_system) ||
1555 cfg!(target_os = "macos") && is_ci()
1559}
1560
1561pub fn slow_cpu_multiplier(main: u64) -> Duration {
1566 static SLOW_CPU_MULTIPLIER: OnceLock<u64> = OnceLock::new();
1567 let slow_cpu_multiplier = SLOW_CPU_MULTIPLIER.get_or_init(|| {
1568 env::var("CARGO_TEST_SLOW_CPU_MULTIPLIER")
1569 .ok()
1570 .and_then(|m| m.parse().ok())
1571 .unwrap_or(1)
1572 });
1573 Duration::from_secs(slow_cpu_multiplier * main)
1574}
1575
1576#[cfg(windows)]
1577pub fn symlink_supported() -> bool {
1578 if is_ci() {
1579 return true;
1581 }
1582 let src = paths::root().join("symlink_src");
1583 fs::write(&src, "").unwrap();
1584 let dst = paths::root().join("symlink_dst");
1585 let result = match os::windows::fs::symlink_file(&src, &dst) {
1586 Ok(_) => {
1587 fs::remove_file(&dst).unwrap();
1588 true
1589 }
1590 Err(e) => {
1591 eprintln!(
1592 "symlinks not supported: {:?}\n\
1593 Windows 10 users should enable developer mode.",
1594 e
1595 );
1596 false
1597 }
1598 };
1599 fs::remove_file(&src).unwrap();
1600 return result;
1601}
1602
1603#[cfg(not(windows))]
1604pub fn symlink_supported() -> bool {
1605 true
1606}
1607
1608pub fn no_such_file_err_msg() -> String {
1610 std::io::Error::from_raw_os_error(2).to_string()
1611}
1612
1613#[track_caller]
1617pub fn retry<F, R>(n: u32, mut f: F) -> R
1618where
1619 F: FnMut() -> Option<R>,
1620{
1621 let mut count = 0;
1622 let start = std::time::Instant::now();
1623 loop {
1624 if let Some(r) = f() {
1625 return r;
1626 }
1627 count += 1;
1628 if count > n {
1629 panic!(
1630 "test did not finish within {n} attempts ({:?} total)",
1631 start.elapsed()
1632 );
1633 }
1634 sleep_ms(100);
1635 }
1636}
1637
1638#[test]
1639#[should_panic(expected = "test did not finish")]
1640fn retry_fails() {
1641 retry(2, || None::<()>);
1642}
1643
1644#[track_caller]
1646pub fn thread_wait_timeout<T>(n: u32, thread: JoinHandle<T>) -> T {
1647 retry(n, || thread.is_finished().then_some(()));
1648 thread.join().unwrap()
1649}
1650
1651#[track_caller]
1654pub fn threaded_timeout<F, R>(n: u32, f: F) -> R
1655where
1656 F: FnOnce() -> R + Send + 'static,
1657 R: Send + 'static,
1658{
1659 let thread = std::thread::spawn(|| f());
1660 thread_wait_timeout(n, thread)
1661}
1662
1663#[track_caller]
1665pub fn assert_deps(project: &Project, fingerprint: &str, test_cb: impl Fn(&Path, &[(u8, &str)])) {
1666 let mut files = project
1667 .glob(fingerprint)
1668 .map(|f| f.expect("unwrap glob result"))
1669 .filter(|f| f.extension().is_none());
1671 let info_path = files
1672 .next()
1673 .unwrap_or_else(|| panic!("expected 1 dep-info file at {}, found 0", fingerprint));
1674 assert!(files.next().is_none(), "expected only 1 dep-info file");
1675 let dep_info = fs::read(&info_path).unwrap();
1676 let dep_info = &mut &dep_info[..];
1677
1678 read_usize(dep_info);
1680 read_u8(dep_info);
1681 read_u8(dep_info);
1682
1683 let deps = (0..read_usize(dep_info))
1684 .map(|_| {
1685 let ty = read_u8(dep_info);
1686 let path = std::str::from_utf8(read_bytes(dep_info)).unwrap();
1687 let checksum_present = read_bool(dep_info);
1688 if checksum_present {
1689 let _file_len = read_u64(dep_info);
1691 let _checksum = read_bytes(dep_info);
1692 }
1693 (ty, path)
1694 })
1695 .collect::<Vec<_>>();
1696 test_cb(&info_path, &deps);
1697
1698 fn read_usize(bytes: &mut &[u8]) -> usize {
1699 let ret = &bytes[..4];
1700 *bytes = &bytes[4..];
1701
1702 u32::from_le_bytes(ret.try_into().unwrap()) as usize
1703 }
1704
1705 fn read_u8(bytes: &mut &[u8]) -> u8 {
1706 let ret = bytes[0];
1707 *bytes = &bytes[1..];
1708 ret
1709 }
1710
1711 fn read_bool(bytes: &mut &[u8]) -> bool {
1712 read_u8(bytes) != 0
1713 }
1714
1715 fn read_u64(bytes: &mut &[u8]) -> u64 {
1716 let ret = &bytes[..8];
1717 *bytes = &bytes[8..];
1718
1719 u64::from_le_bytes(ret.try_into().unwrap())
1720 }
1721
1722 fn read_bytes<'a>(bytes: &mut &'a [u8]) -> &'a [u8] {
1723 let n = read_usize(bytes);
1724 let ret = &bytes[..n];
1725 *bytes = &bytes[n..];
1726 ret
1727 }
1728}
1729
1730#[track_caller]
1731pub fn assert_deps_contains(project: &Project, fingerprint: &str, expected: &[(u8, &str)]) {
1732 assert_deps(project, fingerprint, |info_path, entries| {
1733 for (e_kind, e_path) in expected {
1734 let pattern = glob::Pattern::new(e_path).unwrap();
1735 let count = entries
1736 .iter()
1737 .filter(|(kind, path)| kind == e_kind && pattern.matches(path))
1738 .count();
1739 if count != 1 {
1740 panic!(
1741 "Expected 1 match of {} {} in {:?}, got {}:\n{:#?}",
1742 e_kind, e_path, info_path, count, entries
1743 );
1744 }
1745 }
1746 })
1747}
1748
1749#[track_caller]
1750pub fn assert_deterministic_mtime(path: impl AsRef<Path>) {
1751 const DETERMINISTIC_TIMESTAMP: u64 = 1153704088;
1754
1755 let path = path.as_ref();
1756 let mtime = path.metadata().unwrap().modified().unwrap();
1757 let timestamp = mtime
1758 .duration_since(std::time::UNIX_EPOCH)
1759 .unwrap()
1760 .as_secs();
1761 assert_eq!(
1762 timestamp, DETERMINISTIC_TIMESTAMP,
1763 "expected deterministic mtime for {path:?}, got {timestamp}"
1764 );
1765}