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