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