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.exec_with_streaming(
1094 &mut |out| {
1095 println!("{}", out);
1096 Ok(())
1097 },
1098 &mut |err| {
1099 eprintln!("{}", err);
1100 Ok(())
1101 },
1102 true,
1103 )
1104 } else {
1105 process.exec_with_output()
1106 };
1107
1108 match res {
1109 Ok(out) => {
1110 self.match_output(out.status.code(), &out.stdout, &out.stderr)?;
1111 return Ok(RawOutput {
1112 stdout: out.stdout,
1113 stderr: out.stderr,
1114 code: out.status.code(),
1115 });
1116 }
1117 Err(e) => {
1118 if let Some(ProcessError {
1119 stdout: Some(stdout),
1120 stderr: Some(stderr),
1121 code,
1122 ..
1123 }) = e.downcast_ref::<ProcessError>()
1124 {
1125 self.match_output(*code, stdout, stderr)?;
1126 return Ok(RawOutput {
1127 stdout: stdout.to_vec(),
1128 stderr: stderr.to_vec(),
1129 code: *code,
1130 });
1131 }
1132 bail!("could not exec process {}: {:?}", process, e)
1133 }
1134 }
1135 }
1136
1137 #[track_caller]
1138 fn match_output(&self, code: Option<i32>, stdout: &[u8], stderr: &[u8]) -> Result<()> {
1139 self.verify_checks_output(stdout, stderr);
1140 let stdout = std::str::from_utf8(stdout).expect("stdout is not utf8");
1141 let stderr = std::str::from_utf8(stderr).expect("stderr is not utf8");
1142
1143 match self.expect_exit_code {
1144 None => {}
1145 Some(expected) if code == Some(expected) => {}
1146 Some(expected) => bail!(
1147 "process exited with code {} (expected {})\n--- stdout\n{}\n--- stderr\n{}",
1148 code.unwrap_or(-1),
1149 expected,
1150 stdout,
1151 stderr
1152 ),
1153 }
1154
1155 if let Some(expect_stdout_data) = &self.expect_stdout_data {
1156 if let Err(err) = self.assert.try_eq(
1157 Some(&"stdout"),
1158 stdout.into_data(),
1159 expect_stdout_data.clone(),
1160 ) {
1161 panic!("{err}")
1162 }
1163 }
1164 if let Some(expect_stderr_data) = &self.expect_stderr_data {
1165 if let Err(err) = self.assert.try_eq(
1166 Some(&"stderr"),
1167 stderr.into_data(),
1168 expect_stderr_data.clone(),
1169 ) {
1170 panic!("{err}")
1171 }
1172 }
1173 for expect in self.expect_stdout_contains.iter() {
1174 compare::match_contains(expect, stdout, self.assert.redactions())?;
1175 }
1176 for expect in self.expect_stderr_contains.iter() {
1177 compare::match_contains(expect, stderr, self.assert.redactions())?;
1178 }
1179 for expect in self.expect_stdout_not_contains.iter() {
1180 compare::match_does_not_contain(expect, stdout, self.assert.redactions())?;
1181 }
1182 for expect in self.expect_stderr_not_contains.iter() {
1183 compare::match_does_not_contain(expect, stderr, self.assert.redactions())?;
1184 }
1185 for (with, without) in self.expect_stderr_with_without.iter() {
1186 compare::match_with_without(stderr, with, without, self.assert.redactions())?;
1187 }
1188 Ok(())
1189 }
1190}
1191
1192impl Drop for Execs {
1193 fn drop(&mut self) {
1194 if !self.ran && !std::thread::panicking() {
1195 panic!("forgot to run this command");
1196 }
1197 }
1198}
1199
1200pub fn execs() -> Execs {
1202 Execs {
1203 ran: false,
1204 process_builder: None,
1205 expect_stdin: None,
1206 expect_exit_code: Some(0),
1207 expect_stdout_data: None,
1208 expect_stderr_data: None,
1209 expect_stdout_contains: Vec::new(),
1210 expect_stderr_contains: Vec::new(),
1211 expect_stdout_not_contains: Vec::new(),
1212 expect_stderr_not_contains: Vec::new(),
1213 expect_stderr_with_without: Vec::new(),
1214 stream_output: false,
1215 assert: compare::assert_e2e(),
1216 }
1217}
1218
1219pub fn basic_manifest(name: &str, version: &str) -> String {
1221 format!(
1222 r#"
1223 [package]
1224 name = "{}"
1225 version = "{}"
1226 authors = []
1227 edition = "2015"
1228 "#,
1229 name, version
1230 )
1231}
1232
1233pub fn basic_bin_manifest(name: &str) -> String {
1235 format!(
1236 r#"
1237 [package]
1238
1239 name = "{}"
1240 version = "0.5.0"
1241 authors = ["wycats@example.com"]
1242 edition = "2015"
1243
1244 [[bin]]
1245
1246 name = "{}"
1247 "#,
1248 name, name
1249 )
1250}
1251
1252pub fn basic_lib_manifest(name: &str) -> String {
1254 format!(
1255 r#"
1256 [package]
1257
1258 name = "{}"
1259 version = "0.5.0"
1260 authors = ["wycats@example.com"]
1261 edition = "2015"
1262
1263 [lib]
1264
1265 name = "{}"
1266 "#,
1267 name, name
1268 )
1269}
1270
1271pub fn target_spec_json() -> &'static str {
1276 static TARGET_SPEC_JSON: LazyLock<String> = LazyLock::new(|| {
1277 let json = std::process::Command::new("rustc")
1278 .env("RUSTC_BOOTSTRAP", "1")
1279 .arg("--print")
1280 .arg("target-spec-json")
1281 .arg("-Zunstable-options")
1282 .arg("--target")
1283 .arg("x86_64-unknown-none")
1284 .output()
1285 .expect("rustc --print target-spec-json")
1286 .stdout;
1287 String::from_utf8(json).expect("utf8 target spec json")
1288 });
1289
1290 TARGET_SPEC_JSON.as_str()
1291}
1292
1293struct RustcInfo {
1294 verbose_version: String,
1295 host: String,
1296}
1297
1298impl RustcInfo {
1299 fn new() -> RustcInfo {
1300 let output = ProcessBuilder::new("rustc")
1301 .arg("-vV")
1302 .exec_with_output()
1303 .expect("rustc should exec");
1304 let verbose_version = String::from_utf8(output.stdout).expect("utf8 output");
1305 let host = verbose_version
1306 .lines()
1307 .filter_map(|line| line.strip_prefix("host: "))
1308 .next()
1309 .expect("verbose version has host: field")
1310 .to_string();
1311 RustcInfo {
1312 verbose_version,
1313 host,
1314 }
1315 }
1316}
1317
1318fn rustc_info() -> &'static RustcInfo {
1319 static RUSTC_INFO: OnceLock<RustcInfo> = OnceLock::new();
1320 RUSTC_INFO.get_or_init(RustcInfo::new)
1321}
1322
1323pub fn rustc_host() -> &'static str {
1325 &rustc_info().host
1326}
1327
1328pub fn rustc_host_env() -> String {
1330 rustc_host().to_uppercase().replace('-', "_")
1331}
1332
1333pub fn is_nightly() -> bool {
1334 let vv = &rustc_info().verbose_version;
1335 env::var("CARGO_TEST_DISABLE_NIGHTLY").is_err()
1340 && (vv.contains("-nightly") || vv.contains("-dev"))
1341}
1342
1343pub fn process<T: AsRef<OsStr>>(bin: T) -> ProcessBuilder {
1349 _process(bin.as_ref())
1350}
1351
1352fn _process(t: &OsStr) -> ProcessBuilder {
1353 let mut p = ProcessBuilder::new(t);
1354 p.cwd(&paths::root()).test_env();
1355 p
1356}
1357
1358pub trait ChannelChangerCommandExt {
1360 fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self;
1364}
1365
1366impl ChannelChangerCommandExt for &mut ProcessBuilder {
1367 fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self {
1368 self.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly")
1369 }
1370}
1371
1372impl ChannelChangerCommandExt for snapbox::cmd::Command {
1373 fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self {
1374 self.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly")
1375 }
1376}
1377
1378pub trait TestEnvCommandExt: Sized {
1380 fn test_env(mut self) -> Self {
1381 for (k, _v) in env::vars() {
1385 if k.starts_with("CARGO_") {
1386 self = self.env_remove(&k);
1387 }
1388 }
1389 if env::var_os("RUSTUP_TOOLCHAIN").is_some() {
1390 static RUSTC_DIR: OnceLock<PathBuf> = OnceLock::new();
1393 let rustc_dir = RUSTC_DIR.get_or_init(|| {
1394 match ProcessBuilder::new("rustup")
1395 .args(&["which", "rustc"])
1396 .exec_with_output()
1397 {
1398 Ok(output) => {
1399 let s = std::str::from_utf8(&output.stdout).expect("utf8").trim();
1400 let mut p = PathBuf::from(s);
1401 p.pop();
1402 p
1403 }
1404 Err(e) => {
1405 panic!("RUSTUP_TOOLCHAIN was set, but could not run rustup: {}", e);
1406 }
1407 }
1408 });
1409 let path = env::var_os("PATH").unwrap_or_default();
1410 let paths = env::split_paths(&path);
1411 let new_path =
1412 env::join_paths(std::iter::once(rustc_dir.clone()).chain(paths)).unwrap();
1413 self = self.env("PATH", new_path);
1414 }
1415
1416 self = self
1417 .current_dir(&paths::root())
1418 .env("HOME", paths::home())
1419 .env("CARGO_HOME", paths::cargo_home())
1420 .env("__CARGO_TEST_ROOT", paths::global_root())
1421 .env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "stable")
1425 .env("__CARGO_TEST_DISABLE_GLOBAL_KNOWN_HOST", "1")
1427 .env("__CARGO_TEST_FIXED_RETRY_SLEEP_MS", "1")
1429 .env("__CARGO_TEST_TTY_WIDTH_DO_NOT_USE_THIS", "400")
1436 .env("CARGO_INCREMENTAL", "0")
1440 .env("GIT_CONFIG_NOSYSTEM", "1")
1442 .env_remove("__CARGO_DEFAULT_LIB_METADATA")
1443 .env_remove("ALL_PROXY")
1444 .env_remove("EMAIL")
1445 .env_remove("GIT_AUTHOR_EMAIL")
1446 .env_remove("GIT_AUTHOR_NAME")
1447 .env_remove("GIT_COMMITTER_EMAIL")
1448 .env_remove("GIT_COMMITTER_NAME")
1449 .env_remove("http_proxy")
1450 .env_remove("HTTPS_PROXY")
1451 .env_remove("https_proxy")
1452 .env_remove("MAKEFLAGS")
1453 .env_remove("MFLAGS")
1454 .env_remove("MSYSTEM") .env_remove("MANPAGER")
1456 .env_remove("PAGER")
1457 .env_remove("LESS")
1458 .env_remove("RUSTC")
1459 .env_remove("RUST_BACKTRACE")
1460 .env_remove("RUSTC_WORKSPACE_WRAPPER")
1461 .env_remove("RUSTC_WRAPPER")
1462 .env_remove("RUSTDOC")
1463 .env_remove("RUSTDOCFLAGS")
1464 .env_remove("RUSTFLAGS")
1465 .env_remove("RUSTUP_TOOLCHAIN_SOURCE")
1466 .env_remove("SSH_AUTH_SOCK") .env_remove("USER") .env_remove("XDG_CONFIG_HOME") .env_remove("OUT_DIR"); if cfg!(windows) {
1471 self = self.env("USERPROFILE", paths::home());
1472 }
1473 self
1474 }
1475
1476 fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self;
1477 fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self;
1478 fn env_remove(self, key: &str) -> Self;
1479}
1480
1481impl TestEnvCommandExt for &mut ProcessBuilder {
1482 fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self {
1483 let path = path.as_ref();
1484 self.cwd(path)
1485 }
1486 fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self {
1487 self.env(key, value)
1488 }
1489 fn env_remove(self, key: &str) -> Self {
1490 self.env_remove(key)
1491 }
1492}
1493
1494impl TestEnvCommandExt for snapbox::cmd::Command {
1495 fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self {
1496 self.current_dir(path)
1497 }
1498 fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self {
1499 self.env(key, value)
1500 }
1501 fn env_remove(self, key: &str) -> Self {
1502 self.env_remove(key)
1503 }
1504}
1505
1506pub trait ArgLineCommandExt: Sized {
1508 fn arg_line(mut self, s: &str) -> Self {
1509 for mut arg in s.split_whitespace() {
1510 if (arg.starts_with('"') && arg.ends_with('"'))
1511 || (arg.starts_with('\'') && arg.ends_with('\''))
1512 {
1513 arg = &arg[1..(arg.len() - 1).max(1)];
1514 } else if arg.contains(&['"', '\''][..]) {
1515 panic!("shell-style argument parsing is not supported")
1516 }
1517 self = self.arg(arg);
1518 }
1519 self
1520 }
1521
1522 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self;
1523}
1524
1525impl ArgLineCommandExt for &mut ProcessBuilder {
1526 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
1527 self.arg(s)
1528 }
1529}
1530
1531impl ArgLineCommandExt for &mut Execs {
1532 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
1533 self.arg(s)
1534 }
1535}
1536
1537impl ArgLineCommandExt for snapbox::cmd::Command {
1538 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
1539 self.arg(s)
1540 }
1541}
1542
1543pub fn git_process(arg_line: &str) -> ProcessBuilder {
1545 let mut p = process("git");
1546 p.arg_line(arg_line);
1547 p
1548}
1549
1550pub fn sleep_ms(ms: u64) {
1551 ::std::thread::sleep(Duration::from_millis(ms));
1552}
1553
1554pub fn is_coarse_mtime() -> bool {
1556 cfg!(emulate_second_only_system) ||
1559 cfg!(target_os = "macos") && is_ci()
1563}
1564
1565pub fn slow_cpu_multiplier(main: u64) -> Duration {
1570 static SLOW_CPU_MULTIPLIER: OnceLock<u64> = OnceLock::new();
1571 let slow_cpu_multiplier = SLOW_CPU_MULTIPLIER.get_or_init(|| {
1572 env::var("CARGO_TEST_SLOW_CPU_MULTIPLIER")
1573 .ok()
1574 .and_then(|m| m.parse().ok())
1575 .unwrap_or(1)
1576 });
1577 Duration::from_secs(slow_cpu_multiplier * main)
1578}
1579
1580#[cfg(windows)]
1581pub fn symlink_supported() -> bool {
1582 if is_ci() {
1583 return true;
1585 }
1586 let src = paths::root().join("symlink_src");
1587 fs::write(&src, "").unwrap();
1588 let dst = paths::root().join("symlink_dst");
1589 let result = match os::windows::fs::symlink_file(&src, &dst) {
1590 Ok(_) => {
1591 fs::remove_file(&dst).unwrap();
1592 true
1593 }
1594 Err(e) => {
1595 eprintln!(
1596 "symlinks not supported: {:?}\n\
1597 Windows 10 users should enable developer mode.",
1598 e
1599 );
1600 false
1601 }
1602 };
1603 fs::remove_file(&src).unwrap();
1604 return result;
1605}
1606
1607#[cfg(not(windows))]
1608pub fn symlink_supported() -> bool {
1609 true
1610}
1611
1612pub fn no_such_file_err_msg() -> String {
1614 std::io::Error::from_raw_os_error(2).to_string()
1615}
1616
1617#[track_caller]
1621pub fn retry<F, R>(n: u32, mut f: F) -> R
1622where
1623 F: FnMut() -> Option<R>,
1624{
1625 let mut count = 0;
1626 let start = std::time::Instant::now();
1627 loop {
1628 if let Some(r) = f() {
1629 return r;
1630 }
1631 count += 1;
1632 if count > n {
1633 panic!(
1634 "test did not finish within {n} attempts ({:?} total)",
1635 start.elapsed()
1636 );
1637 }
1638 sleep_ms(100);
1639 }
1640}
1641
1642#[test]
1643#[should_panic(expected = "test did not finish")]
1644fn retry_fails() {
1645 retry(2, || None::<()>);
1646}
1647
1648#[track_caller]
1650pub fn thread_wait_timeout<T>(n: u32, thread: JoinHandle<T>) -> T {
1651 retry(n, || thread.is_finished().then_some(()));
1652 thread.join().unwrap()
1653}
1654
1655#[track_caller]
1658pub fn threaded_timeout<F, R>(n: u32, f: F) -> R
1659where
1660 F: FnOnce() -> R + Send + 'static,
1661 R: Send + 'static,
1662{
1663 let thread = std::thread::spawn(|| f());
1664 thread_wait_timeout(n, thread)
1665}
1666
1667#[track_caller]
1669pub fn assert_deps(project: &Project, fingerprint: &str, test_cb: impl Fn(&Path, &[(u8, &str)])) {
1670 let mut files = project
1671 .glob(fingerprint)
1672 .map(|f| f.expect("unwrap glob result"))
1673 .filter(|f| f.extension().is_none());
1675 let info_path = files
1676 .next()
1677 .unwrap_or_else(|| panic!("expected 1 dep-info file at {}, found 0", fingerprint));
1678 assert!(files.next().is_none(), "expected only 1 dep-info file");
1679 let dep_info = fs::read(&info_path).unwrap();
1680 let dep_info = &mut &dep_info[..];
1681
1682 read_usize(dep_info);
1684 read_u8(dep_info);
1685 read_u8(dep_info);
1686
1687 let deps = (0..read_usize(dep_info))
1688 .map(|_| {
1689 let ty = read_u8(dep_info);
1690 let path = std::str::from_utf8(read_bytes(dep_info)).unwrap();
1691 let checksum_present = read_bool(dep_info);
1692 if checksum_present {
1693 let _file_len = read_u64(dep_info);
1695 let _checksum = read_bytes(dep_info);
1696 }
1697 (ty, path)
1698 })
1699 .collect::<Vec<_>>();
1700 test_cb(&info_path, &deps);
1701
1702 fn read_usize(bytes: &mut &[u8]) -> usize {
1703 let ret = &bytes[..4];
1704 *bytes = &bytes[4..];
1705
1706 u32::from_le_bytes(ret.try_into().unwrap()) as usize
1707 }
1708
1709 fn read_u8(bytes: &mut &[u8]) -> u8 {
1710 let ret = bytes[0];
1711 *bytes = &bytes[1..];
1712 ret
1713 }
1714
1715 fn read_bool(bytes: &mut &[u8]) -> bool {
1716 read_u8(bytes) != 0
1717 }
1718
1719 fn read_u64(bytes: &mut &[u8]) -> u64 {
1720 let ret = &bytes[..8];
1721 *bytes = &bytes[8..];
1722
1723 u64::from_le_bytes(ret.try_into().unwrap())
1724 }
1725
1726 fn read_bytes<'a>(bytes: &mut &'a [u8]) -> &'a [u8] {
1727 let n = read_usize(bytes);
1728 let ret = &bytes[..n];
1729 *bytes = &bytes[n..];
1730 ret
1731 }
1732}
1733
1734#[track_caller]
1735pub fn assert_deps_contains(project: &Project, fingerprint: &str, expected: &[(u8, &str)]) {
1736 assert_deps(project, fingerprint, |info_path, entries| {
1737 for (e_kind, e_path) in expected {
1738 let pattern = glob::Pattern::new(e_path).unwrap();
1739 let count = entries
1740 .iter()
1741 .filter(|(kind, path)| kind == e_kind && pattern.matches(path))
1742 .count();
1743 if count != 1 {
1744 panic!(
1745 "Expected 1 match of {} {} in {:?}, got {}:\n{:#?}",
1746 e_kind, e_path, info_path, count, entries
1747 );
1748 }
1749 }
1750 })
1751}
1752
1753#[track_caller]
1754pub fn assert_deterministic_mtime(path: impl AsRef<Path>) {
1755 const DETERMINISTIC_TIMESTAMP: u64 = 1153704088;
1758
1759 let path = path.as_ref();
1760 let mtime = path.metadata().unwrap().modified().unwrap();
1761 let timestamp = mtime
1762 .duration_since(std::time::UNIX_EPOCH)
1763 .unwrap()
1764 .as_secs();
1765 assert_eq!(
1766 timestamp, DETERMINISTIC_TIMESTAMP,
1767 "expected deterministic mtime for {path:?}, got {timestamp}"
1768 );
1769}