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;
113pub mod tools;
114
115pub mod prelude {
116 pub use crate::cargo_test;
117 pub use crate::paths::CargoPathExt;
118 pub use crate::ArgLineCommandExt;
119 pub use crate::CargoCommandExt;
120 pub use crate::ChannelChangerCommandExt;
121 pub use crate::TestEnvCommandExt;
122 pub use snapbox::IntoData;
123}
124
125#[derive(PartialEq, Clone)]
132struct FileBuilder {
133 path: PathBuf,
134 body: String,
135 executable: bool,
136}
137
138impl FileBuilder {
139 pub fn new(path: PathBuf, body: &str, executable: bool) -> FileBuilder {
140 FileBuilder {
141 path,
142 body: body.to_string(),
143 executable: executable,
144 }
145 }
146
147 fn mk(&mut self) {
148 if self.executable {
149 let mut path = self.path.clone().into_os_string();
150 write!(path, "{}", env::consts::EXE_SUFFIX).unwrap();
151 self.path = path.into();
152 }
153
154 self.dirname().mkdir_p();
155 fs::write(&self.path, &self.body)
156 .unwrap_or_else(|e| panic!("could not create file {}: {}", self.path.display(), e));
157
158 #[cfg(unix)]
159 if self.executable {
160 use std::os::unix::fs::PermissionsExt;
161
162 let mut perms = fs::metadata(&self.path).unwrap().permissions();
163 let mode = perms.mode();
164 perms.set_mode(mode | 0o111);
165 fs::set_permissions(&self.path, perms).unwrap();
166 }
167 }
168
169 fn dirname(&self) -> &Path {
170 self.path.parent().unwrap()
171 }
172}
173
174#[derive(PartialEq, Clone)]
175struct SymlinkBuilder {
176 dst: PathBuf,
177 src: PathBuf,
178 src_is_dir: bool,
179}
180
181impl SymlinkBuilder {
182 pub fn new(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
183 SymlinkBuilder {
184 dst,
185 src,
186 src_is_dir: false,
187 }
188 }
189
190 pub fn new_dir(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
191 SymlinkBuilder {
192 dst,
193 src,
194 src_is_dir: true,
195 }
196 }
197
198 #[cfg(unix)]
199 fn mk(&self) {
200 self.dirname().mkdir_p();
201 t!(os::unix::fs::symlink(&self.dst, &self.src));
202 }
203
204 #[cfg(windows)]
205 fn mk(&mut self) {
206 self.dirname().mkdir_p();
207 if self.src_is_dir {
208 t!(os::windows::fs::symlink_dir(&self.dst, &self.src));
209 } else {
210 if let Some(ext) = self.dst.extension() {
211 if ext == env::consts::EXE_EXTENSION {
212 self.src.set_extension(ext);
213 }
214 }
215 t!(os::windows::fs::symlink_file(&self.dst, &self.src));
216 }
217 }
218
219 fn dirname(&self) -> &Path {
220 self.src.parent().unwrap()
221 }
222}
223
224pub struct Project {
228 root: PathBuf,
229}
230
231#[must_use]
241pub struct ProjectBuilder {
242 root: Project,
243 files: Vec<FileBuilder>,
244 symlinks: Vec<SymlinkBuilder>,
245 no_manifest: bool,
246}
247
248impl ProjectBuilder {
249 pub fn root(&self) -> PathBuf {
253 self.root.root()
254 }
255
256 pub fn target_debug_dir(&self) -> PathBuf {
260 self.root.target_debug_dir()
261 }
262
263 pub fn new(root: PathBuf) -> ProjectBuilder {
265 ProjectBuilder {
266 root: Project { root },
267 files: vec![],
268 symlinks: vec![],
269 no_manifest: false,
270 }
271 }
272
273 pub fn at<P: AsRef<Path>>(mut self, path: P) -> Self {
275 self.root = Project {
276 root: paths::root().join(path),
277 };
278 self
279 }
280
281 pub fn file<B: AsRef<Path>>(mut self, path: B, body: &str) -> Self {
283 self._file(path.as_ref(), body, false);
284 self
285 }
286
287 pub fn executable<B: AsRef<Path>>(mut self, path: B, body: &str) -> Self {
289 self._file(path.as_ref(), body, true);
290 self
291 }
292
293 fn _file(&mut self, path: &Path, body: &str, executable: bool) {
294 self.files.push(FileBuilder::new(
295 self.root.root().join(path),
296 body,
297 executable,
298 ));
299 }
300
301 pub fn symlink(mut self, dst: impl AsRef<Path>, src: impl AsRef<Path>) -> Self {
303 self.symlinks.push(SymlinkBuilder::new(
304 self.root.root().join(dst),
305 self.root.root().join(src),
306 ));
307 self
308 }
309
310 pub fn symlink_dir(mut self, dst: impl AsRef<Path>, src: impl AsRef<Path>) -> Self {
312 self.symlinks.push(SymlinkBuilder::new_dir(
313 self.root.root().join(dst),
314 self.root.root().join(src),
315 ));
316 self
317 }
318
319 pub fn no_manifest(mut self) -> Self {
320 self.no_manifest = true;
321 self
322 }
323
324 pub fn build(mut self) -> Project {
326 self.rm_root();
328
329 self.root.root().mkdir_p();
331
332 let manifest_path = self.root.root().join("Cargo.toml");
333 if !self.no_manifest && self.files.iter().all(|fb| fb.path != manifest_path) {
334 self._file(
335 Path::new("Cargo.toml"),
336 &basic_manifest("foo", "0.0.1"),
337 false,
338 )
339 }
340
341 let past = time::SystemTime::now() - Duration::new(1, 0);
342 let ftime = filetime::FileTime::from_system_time(past);
343
344 for file in self.files.iter_mut() {
345 file.mk();
346 if is_coarse_mtime() {
347 filetime::set_file_times(&file.path, ftime, ftime).unwrap();
353 }
354 }
355
356 for symlink in self.symlinks.iter_mut() {
357 symlink.mk();
358 }
359
360 let ProjectBuilder { root, .. } = self;
361 root
362 }
363
364 fn rm_root(&self) {
365 self.root.root().rm_rf()
366 }
367}
368
369impl Project {
370 pub fn from_template(template_path: impl AsRef<Path>) -> Self {
372 let root = paths::root();
373 let project_root = root.join("case");
374 snapbox::dir::copy_template(template_path.as_ref(), &project_root).unwrap();
375 Self { root: project_root }
376 }
377
378 pub fn root(&self) -> PathBuf {
382 self.root.clone()
383 }
384
385 pub fn build_dir(&self) -> PathBuf {
389 self.root().join("target")
390 }
391
392 pub fn target_debug_dir(&self) -> PathBuf {
396 self.build_dir().join("debug")
397 }
398
399 pub fn url(&self) -> Url {
403 use paths::CargoPathExt;
404 self.root().to_url()
405 }
406
407 pub fn example_lib(&self, name: &str, kind: &str) -> PathBuf {
413 self.target_debug_dir()
414 .join("examples")
415 .join(paths::get_lib_filename(name, kind))
416 }
417
418 pub fn dylib(&self, name: &str) -> PathBuf {
421 self.target_debug_dir().join(format!(
422 "{}{name}{}",
423 env::consts::DLL_PREFIX,
424 env::consts::DLL_SUFFIX
425 ))
426 }
427
428 pub fn bin(&self, b: &str) -> PathBuf {
432 self.build_dir()
433 .join("debug")
434 .join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
435 }
436
437 pub fn release_bin(&self, b: &str) -> PathBuf {
441 self.build_dir()
442 .join("release")
443 .join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
444 }
445
446 pub fn target_bin(&self, target: &str, b: &str) -> PathBuf {
450 self.build_dir().join(target).join("debug").join(&format!(
451 "{}{}",
452 b,
453 env::consts::EXE_SUFFIX
454 ))
455 }
456
457 pub fn glob<P: AsRef<Path>>(&self, pattern: P) -> glob::Paths {
459 let pattern = self.root().join(pattern);
460 glob::glob(pattern.to_str().expect("failed to convert pattern to str"))
461 .expect("failed to glob")
462 }
463
464 pub fn change_file(&self, path: impl AsRef<Path>, body: &str) {
473 FileBuilder::new(self.root().join(path), body, false).mk()
474 }
475
476 pub fn process<T: AsRef<OsStr>>(&self, program: T) -> Execs {
489 let mut p = process(program);
490 p.cwd(self.root());
491 execs().with_process_builder(p)
492 }
493
494 pub fn cargo(&self, cmd: &str) -> Execs {
507 let cargo = cargo_exe();
508 let mut execs = self.process(&cargo);
509 if let Some(ref mut p) = execs.process_builder {
510 p.env("CARGO", cargo);
511 p.arg_line(cmd);
512 }
513 execs
514 }
515
516 pub fn rename_run(&self, src: &str, dst: &str) -> Execs {
530 let src = self.bin(src);
531 let dst = self.bin(dst);
532 fs::rename(&src, &dst)
533 .unwrap_or_else(|e| panic!("Failed to rename `{:?}` to `{:?}`: {}", src, dst, e));
534 self.process(dst)
535 }
536
537 pub fn read_lockfile(&self) -> String {
539 self.read_file("Cargo.lock")
540 }
541
542 pub fn read_file(&self, path: impl AsRef<Path>) -> String {
544 let full = self.root().join(path);
545 fs::read_to_string(&full)
546 .unwrap_or_else(|e| panic!("could not read file {}: {}", full.display(), e))
547 }
548
549 pub fn uncomment_root_manifest(&self) {
551 let contents = self.read_file("Cargo.toml").replace("#", "");
552 fs::write(self.root().join("Cargo.toml"), contents).unwrap();
553 }
554
555 pub fn symlink(&self, src: impl AsRef<Path>, dst: impl AsRef<Path>) {
556 let src = self.root().join(src.as_ref());
557 let dst = self.root().join(dst.as_ref());
558 #[cfg(unix)]
559 {
560 if let Err(e) = os::unix::fs::symlink(&src, &dst) {
561 panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
562 }
563 }
564 #[cfg(windows)]
565 {
566 if src.is_dir() {
567 if let Err(e) = os::windows::fs::symlink_dir(&src, &dst) {
568 panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
569 }
570 } else {
571 if let Err(e) = os::windows::fs::symlink_file(&src, &dst) {
572 panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
573 }
574 }
575 }
576 }
577}
578
579pub fn project() -> ProjectBuilder {
581 ProjectBuilder::new(paths::root().join("foo"))
582}
583
584pub fn project_in(dir: impl AsRef<Path>) -> ProjectBuilder {
586 ProjectBuilder::new(paths::root().join(dir).join("foo"))
587}
588
589pub fn project_in_home(name: impl AsRef<Path>) -> ProjectBuilder {
591 ProjectBuilder::new(paths::home().join(name))
592}
593
594pub fn main_file(println: &str, externed_deps: &[&str]) -> String {
611 let mut buf = String::new();
612
613 for dep in externed_deps.iter() {
614 buf.push_str(&format!("extern crate {};\n", dep));
615 }
616
617 buf.push_str("fn main() { println!(");
618 buf.push_str(println);
619 buf.push_str("); }\n");
620
621 buf
622}
623
624pub fn cargo_exe() -> PathBuf {
626 snapbox::cmd::cargo_bin("cargo")
627}
628
629pub struct RawOutput {
637 pub code: Option<i32>,
638 pub stdout: Vec<u8>,
639 pub stderr: Vec<u8>,
640}
641
642#[must_use]
649#[derive(Clone)]
650pub struct Execs {
651 ran: bool,
652 process_builder: Option<ProcessBuilder>,
653 expect_stdin: Option<String>,
654 expect_exit_code: Option<i32>,
655 expect_stdout_data: Option<snapbox::Data>,
656 expect_stderr_data: Option<snapbox::Data>,
657 expect_stdout_contains: Vec<String>,
658 expect_stderr_contains: Vec<String>,
659 expect_stdout_not_contains: Vec<String>,
660 expect_stderr_not_contains: Vec<String>,
661 expect_stderr_with_without: Vec<(Vec<String>, Vec<String>)>,
662 stream_output: bool,
663 assert: snapbox::Assert,
664}
665
666impl Execs {
667 pub fn with_process_builder(mut self, p: ProcessBuilder) -> Execs {
668 self.process_builder = Some(p);
669 self
670 }
671}
672
673impl Execs {
675 pub fn with_stdout_data(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
730 self.expect_stdout_data = Some(expected.into_data());
731 self
732 }
733
734 pub fn with_stderr_data(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
789 self.expect_stderr_data = Some(expected.into_data());
790 self
791 }
792
793 pub fn with_stdin<S: ToString>(&mut self, expected: S) -> &mut Self {
795 self.expect_stdin = Some(expected.to_string());
796 self
797 }
798
799 pub fn with_status(&mut self, expected: i32) -> &mut Self {
803 self.expect_exit_code = Some(expected);
804 self
805 }
806
807 pub fn without_status(&mut self) -> &mut Self {
811 self.expect_exit_code = None;
812 self
813 }
814
815 pub fn with_stdout_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
828 self.expect_stdout_contains.push(expected.to_string());
829 self
830 }
831
832 pub fn with_stderr_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
845 self.expect_stderr_contains.push(expected.to_string());
846 self
847 }
848
849 pub fn with_stdout_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
867 self.expect_stdout_not_contains.push(expected.to_string());
868 self
869 }
870
871 pub fn with_stderr_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
888 self.expect_stderr_not_contains.push(expected.to_string());
889 self
890 }
891
892 pub fn with_stderr_line_without<S: ToString>(
924 &mut self,
925 with: &[S],
926 without: &[S],
927 ) -> &mut Self {
928 let with = with.iter().map(|s| s.to_string()).collect();
929 let without = without.iter().map(|s| s.to_string()).collect();
930 self.expect_stderr_with_without.push((with, without));
931 self
932 }
933}
934
935impl Execs {
937 #[allow(unused)]
941 pub fn stream(&mut self) -> &mut Self {
942 self.stream_output = true;
943 self
944 }
945
946 pub fn arg<T: AsRef<OsStr>>(&mut self, arg: T) -> &mut Self {
947 if let Some(ref mut p) = self.process_builder {
948 p.arg(arg);
949 }
950 self
951 }
952
953 pub fn args<T: AsRef<OsStr>>(&mut self, args: &[T]) -> &mut Self {
954 if let Some(ref mut p) = self.process_builder {
955 p.args(args);
956 }
957 self
958 }
959
960 pub fn cwd<T: AsRef<OsStr>>(&mut self, path: T) -> &mut Self {
961 if let Some(ref mut p) = self.process_builder {
962 if let Some(cwd) = p.get_cwd() {
963 let new_path = cwd.join(path.as_ref());
964 p.cwd(new_path);
965 } else {
966 p.cwd(path);
967 }
968 }
969 self
970 }
971
972 pub fn env<T: AsRef<OsStr>>(&mut self, key: &str, val: T) -> &mut Self {
973 if let Some(ref mut p) = self.process_builder {
974 p.env(key, val);
975 }
976 self
977 }
978
979 pub fn env_remove(&mut self, key: &str) -> &mut Self {
980 if let Some(ref mut p) = self.process_builder {
981 p.env_remove(key);
982 }
983 self
984 }
985
986 pub fn masquerade_as_nightly_cargo(&mut self, reasons: &[&str]) -> &mut Self {
992 if let Some(ref mut p) = self.process_builder {
993 p.masquerade_as_nightly_cargo(reasons);
994 }
995 self
996 }
997
998 pub fn replace_crates_io(&mut self, url: &Url) -> &mut Self {
1003 if let Some(ref mut p) = self.process_builder {
1004 p.env("__CARGO_TEST_CRATES_IO_URL_DO_NOT_USE_THIS", url.as_str());
1005 }
1006 self
1007 }
1008
1009 pub fn overlay_registry(&mut self, url: &Url, path: &str) -> &mut Self {
1010 if let Some(ref mut p) = self.process_builder {
1011 let env_value = format!("{}={}", url, path);
1012 p.env(
1013 "__CARGO_TEST_DEPENDENCY_CONFUSION_VULNERABILITY_DO_NOT_USE_THIS",
1014 env_value,
1015 );
1016 }
1017 self
1018 }
1019
1020 pub fn enable_split_debuginfo_packed(&mut self) -> &mut Self {
1021 self.env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "packed")
1022 .env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "packed")
1023 .env("CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO", "packed")
1024 .env("CARGO_PROFILE_BENCH_SPLIT_DEBUGINFO", "packed");
1025 self
1026 }
1027
1028 pub fn enable_mac_dsym(&mut self) -> &mut Self {
1029 if cfg!(target_os = "macos") {
1030 return self.enable_split_debuginfo_packed();
1031 }
1032 self
1033 }
1034}
1035
1036impl Execs {
1038 pub fn exec_with_output(&mut self) -> Result<Output> {
1039 self.ran = true;
1040 let p = (&self.process_builder).clone().unwrap();
1042 p.exec_with_output()
1043 }
1044
1045 pub fn build_command(&mut self) -> Command {
1046 self.ran = true;
1047 let p = (&self.process_builder).clone().unwrap();
1049 p.build_command()
1050 }
1051
1052 #[track_caller]
1053 pub fn run(&mut self) -> RawOutput {
1054 self.ran = true;
1055 let mut p = (&self.process_builder).clone().unwrap();
1056 if let Some(stdin) = self.expect_stdin.take() {
1057 p.stdin(stdin);
1058 }
1059
1060 match self.match_process(&p) {
1061 Err(e) => panic_error(&format!("test failed running {}", p), e),
1062 Ok(output) => output,
1063 }
1064 }
1065
1066 #[track_caller]
1069 pub fn run_json(&mut self) -> serde_json::Value {
1070 let output = self.run();
1071 serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {
1072 panic!(
1073 "\nfailed to parse JSON: {}\n\
1074 output was:\n{}\n",
1075 e,
1076 String::from_utf8_lossy(&output.stdout)
1077 );
1078 })
1079 }
1080
1081 #[track_caller]
1082 pub fn run_output(&mut self, output: &Output) {
1083 self.ran = true;
1084 if let Err(e) = self.match_output(output.status.code(), &output.stdout, &output.stderr) {
1085 panic_error("process did not return the expected result", e)
1086 }
1087 }
1088
1089 #[track_caller]
1090 fn verify_checks_output(&self, stdout: &[u8], stderr: &[u8]) {
1091 if self.expect_exit_code.unwrap_or(0) != 0
1092 && self.expect_stdin.is_none()
1093 && self.expect_stdout_data.is_none()
1094 && self.expect_stderr_data.is_none()
1095 && self.expect_stdout_contains.is_empty()
1096 && self.expect_stderr_contains.is_empty()
1097 && self.expect_stdout_not_contains.is_empty()
1098 && self.expect_stderr_not_contains.is_empty()
1099 && self.expect_stderr_with_without.is_empty()
1100 {
1101 panic!(
1102 "`with_status()` is used, but no output is checked.\n\
1103 The test must check the output to ensure the correct error is triggered.\n\
1104 --- stdout\n{}\n--- stderr\n{}",
1105 String::from_utf8_lossy(stdout),
1106 String::from_utf8_lossy(stderr),
1107 );
1108 }
1109 }
1110
1111 #[track_caller]
1112 fn match_process(&self, process: &ProcessBuilder) -> Result<RawOutput> {
1113 println!("running {}", process);
1114 let res = if self.stream_output {
1115 if is_ci() {
1116 panic!("`.stream()` is for local debugging")
1117 }
1118 process.exec_with_streaming(
1119 &mut |out| {
1120 println!("{}", out);
1121 Ok(())
1122 },
1123 &mut |err| {
1124 eprintln!("{}", err);
1125 Ok(())
1126 },
1127 true,
1128 )
1129 } else {
1130 process.exec_with_output()
1131 };
1132
1133 match res {
1134 Ok(out) => {
1135 self.match_output(out.status.code(), &out.stdout, &out.stderr)?;
1136 return Ok(RawOutput {
1137 stdout: out.stdout,
1138 stderr: out.stderr,
1139 code: out.status.code(),
1140 });
1141 }
1142 Err(e) => {
1143 if let Some(ProcessError {
1144 stdout: Some(stdout),
1145 stderr: Some(stderr),
1146 code,
1147 ..
1148 }) = e.downcast_ref::<ProcessError>()
1149 {
1150 self.match_output(*code, stdout, stderr)?;
1151 return Ok(RawOutput {
1152 stdout: stdout.to_vec(),
1153 stderr: stderr.to_vec(),
1154 code: *code,
1155 });
1156 }
1157 bail!("could not exec process {}: {:?}", process, e)
1158 }
1159 }
1160 }
1161
1162 #[track_caller]
1163 fn match_output(&self, code: Option<i32>, stdout: &[u8], stderr: &[u8]) -> Result<()> {
1164 self.verify_checks_output(stdout, stderr);
1165 let stdout = std::str::from_utf8(stdout).expect("stdout is not utf8");
1166 let stderr = std::str::from_utf8(stderr).expect("stderr is not utf8");
1167
1168 match self.expect_exit_code {
1169 None => {}
1170 Some(expected) if code == Some(expected) => {}
1171 Some(expected) => bail!(
1172 "process exited with code {} (expected {})\n--- stdout\n{}\n--- stderr\n{}",
1173 code.unwrap_or(-1),
1174 expected,
1175 stdout,
1176 stderr
1177 ),
1178 }
1179
1180 if let Some(expect_stdout_data) = &self.expect_stdout_data {
1181 if let Err(err) = self.assert.try_eq(
1182 Some(&"stdout"),
1183 stdout.into_data(),
1184 expect_stdout_data.clone(),
1185 ) {
1186 panic!("{err}")
1187 }
1188 }
1189 if let Some(expect_stderr_data) = &self.expect_stderr_data {
1190 if let Err(err) = self.assert.try_eq(
1191 Some(&"stderr"),
1192 stderr.into_data(),
1193 expect_stderr_data.clone(),
1194 ) {
1195 panic!("{err}")
1196 }
1197 }
1198 for expect in self.expect_stdout_contains.iter() {
1199 compare::match_contains(expect, stdout, self.assert.redactions())?;
1200 }
1201 for expect in self.expect_stderr_contains.iter() {
1202 compare::match_contains(expect, stderr, self.assert.redactions())?;
1203 }
1204 for expect in self.expect_stdout_not_contains.iter() {
1205 compare::match_does_not_contain(expect, stdout, self.assert.redactions())?;
1206 }
1207 for expect in self.expect_stderr_not_contains.iter() {
1208 compare::match_does_not_contain(expect, stderr, self.assert.redactions())?;
1209 }
1210 for (with, without) in self.expect_stderr_with_without.iter() {
1211 compare::match_with_without(stderr, with, without, self.assert.redactions())?;
1212 }
1213 Ok(())
1214 }
1215}
1216
1217impl Drop for Execs {
1218 fn drop(&mut self) {
1219 if !self.ran && !std::thread::panicking() {
1220 panic!("forgot to run this command");
1221 }
1222 }
1223}
1224
1225pub fn execs() -> Execs {
1227 Execs {
1228 ran: false,
1229 process_builder: None,
1230 expect_stdin: None,
1231 expect_exit_code: Some(0),
1232 expect_stdout_data: None,
1233 expect_stderr_data: None,
1234 expect_stdout_contains: Vec::new(),
1235 expect_stderr_contains: Vec::new(),
1236 expect_stdout_not_contains: Vec::new(),
1237 expect_stderr_not_contains: Vec::new(),
1238 expect_stderr_with_without: Vec::new(),
1239 stream_output: false,
1240 assert: compare::assert_e2e(),
1241 }
1242}
1243
1244pub fn basic_manifest(name: &str, version: &str) -> String {
1246 format!(
1247 r#"
1248 [package]
1249 name = "{}"
1250 version = "{}"
1251 authors = []
1252 edition = "2015"
1253 "#,
1254 name, version
1255 )
1256}
1257
1258pub fn basic_bin_manifest(name: &str) -> String {
1260 format!(
1261 r#"
1262 [package]
1263
1264 name = "{}"
1265 version = "0.5.0"
1266 authors = ["wycats@example.com"]
1267 edition = "2015"
1268
1269 [[bin]]
1270
1271 name = "{}"
1272 "#,
1273 name, name
1274 )
1275}
1276
1277pub fn basic_lib_manifest(name: &str) -> String {
1279 format!(
1280 r#"
1281 [package]
1282
1283 name = "{}"
1284 version = "0.5.0"
1285 authors = ["wycats@example.com"]
1286 edition = "2015"
1287
1288 [lib]
1289
1290 name = "{}"
1291 "#,
1292 name, name
1293 )
1294}
1295
1296struct RustcInfo {
1297 verbose_version: String,
1298 host: String,
1299}
1300
1301impl RustcInfo {
1302 fn new() -> RustcInfo {
1303 let output = ProcessBuilder::new("rustc")
1304 .arg("-vV")
1305 .exec_with_output()
1306 .expect("rustc should exec");
1307 let verbose_version = String::from_utf8(output.stdout).expect("utf8 output");
1308 let host = verbose_version
1309 .lines()
1310 .filter_map(|line| line.strip_prefix("host: "))
1311 .next()
1312 .expect("verbose version has host: field")
1313 .to_string();
1314 RustcInfo {
1315 verbose_version,
1316 host,
1317 }
1318 }
1319}
1320
1321fn rustc_info() -> &'static RustcInfo {
1322 static RUSTC_INFO: OnceLock<RustcInfo> = OnceLock::new();
1323 RUSTC_INFO.get_or_init(RustcInfo::new)
1324}
1325
1326pub fn rustc_host() -> &'static str {
1328 &rustc_info().host
1329}
1330
1331pub fn rustc_host_env() -> String {
1333 rustc_host().to_uppercase().replace('-', "_")
1334}
1335
1336pub fn is_nightly() -> bool {
1337 let vv = &rustc_info().verbose_version;
1338 env::var("CARGO_TEST_DISABLE_NIGHTLY").is_err()
1343 && (vv.contains("-nightly") || vv.contains("-dev"))
1344}
1345
1346pub fn process<T: AsRef<OsStr>>(bin: T) -> ProcessBuilder {
1352 _process(bin.as_ref())
1353}
1354
1355fn _process(t: &OsStr) -> ProcessBuilder {
1356 let mut p = ProcessBuilder::new(t);
1357 p.cwd(&paths::root()).test_env();
1358 p
1359}
1360
1361pub trait ChannelChangerCommandExt {
1363 fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self;
1367}
1368
1369impl ChannelChangerCommandExt for &mut ProcessBuilder {
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
1375impl ChannelChangerCommandExt for snapbox::cmd::Command {
1376 fn masquerade_as_nightly_cargo(self, _reasons: &[&str]) -> Self {
1377 self.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly")
1378 }
1379}
1380
1381pub trait TestEnvCommandExt: Sized {
1383 fn test_env(mut self) -> Self {
1384 for (k, _v) in env::vars() {
1388 if k.starts_with("CARGO_") {
1389 self = self.env_remove(&k);
1390 }
1391 }
1392 if env::var_os("RUSTUP_TOOLCHAIN").is_some() {
1393 static RUSTC_DIR: OnceLock<PathBuf> = OnceLock::new();
1396 let rustc_dir = RUSTC_DIR.get_or_init(|| {
1397 match ProcessBuilder::new("rustup")
1398 .args(&["which", "rustc"])
1399 .exec_with_output()
1400 {
1401 Ok(output) => {
1402 let s = std::str::from_utf8(&output.stdout).expect("utf8").trim();
1403 let mut p = PathBuf::from(s);
1404 p.pop();
1405 p
1406 }
1407 Err(e) => {
1408 panic!("RUSTUP_TOOLCHAIN was set, but could not run rustup: {}", e);
1409 }
1410 }
1411 });
1412 let path = env::var_os("PATH").unwrap_or_default();
1413 let paths = env::split_paths(&path);
1414 let new_path =
1415 env::join_paths(std::iter::once(rustc_dir.clone()).chain(paths)).unwrap();
1416 self = self.env("PATH", new_path);
1417 }
1418
1419 self = self
1420 .current_dir(&paths::root())
1421 .env("HOME", paths::home())
1422 .env("CARGO_HOME", paths::cargo_home())
1423 .env("__CARGO_TEST_ROOT", paths::global_root())
1424 .env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "stable")
1428 .env("__CARGO_TEST_DISABLE_GLOBAL_KNOWN_HOST", "1")
1430 .env("__CARGO_TEST_FIXED_RETRY_SLEEP_MS", "1")
1432 .env("CARGO_INCREMENTAL", "0")
1436 .env("GIT_CONFIG_NOSYSTEM", "1")
1438 .env_remove("__CARGO_DEFAULT_LIB_METADATA")
1439 .env_remove("ALL_PROXY")
1440 .env_remove("EMAIL")
1441 .env_remove("GIT_AUTHOR_EMAIL")
1442 .env_remove("GIT_AUTHOR_NAME")
1443 .env_remove("GIT_COMMITTER_EMAIL")
1444 .env_remove("GIT_COMMITTER_NAME")
1445 .env_remove("http_proxy")
1446 .env_remove("HTTPS_PROXY")
1447 .env_remove("https_proxy")
1448 .env_remove("MAKEFLAGS")
1449 .env_remove("MFLAGS")
1450 .env_remove("MSYSTEM") .env_remove("RUSTC")
1452 .env_remove("RUST_BACKTRACE")
1453 .env_remove("RUSTC_WORKSPACE_WRAPPER")
1454 .env_remove("RUSTC_WRAPPER")
1455 .env_remove("RUSTDOC")
1456 .env_remove("RUSTDOCFLAGS")
1457 .env_remove("RUSTFLAGS")
1458 .env_remove("SSH_AUTH_SOCK") .env_remove("USER") .env_remove("XDG_CONFIG_HOME") .env_remove("OUT_DIR"); if cfg!(windows) {
1463 self = self.env("USERPROFILE", paths::home());
1464 }
1465 self
1466 }
1467
1468 fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self;
1469 fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self;
1470 fn env_remove(self, key: &str) -> Self;
1471}
1472
1473impl TestEnvCommandExt for &mut ProcessBuilder {
1474 fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self {
1475 let path = path.as_ref();
1476 self.cwd(path)
1477 }
1478 fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self {
1479 self.env(key, value)
1480 }
1481 fn env_remove(self, key: &str) -> Self {
1482 self.env_remove(key)
1483 }
1484}
1485
1486impl TestEnvCommandExt for snapbox::cmd::Command {
1487 fn current_dir<S: AsRef<std::path::Path>>(self, path: S) -> Self {
1488 self.current_dir(path)
1489 }
1490 fn env<S: AsRef<std::ffi::OsStr>>(self, key: &str, value: S) -> Self {
1491 self.env(key, value)
1492 }
1493 fn env_remove(self, key: &str) -> Self {
1494 self.env_remove(key)
1495 }
1496}
1497
1498pub trait CargoCommandExt {
1500 fn cargo_ui() -> Self;
1501}
1502
1503impl CargoCommandExt for snapbox::cmd::Command {
1504 fn cargo_ui() -> Self {
1505 Self::new(cargo_exe())
1506 .with_assert(compare::assert_ui())
1507 .env("CARGO_TERM_COLOR", "always")
1508 .test_env()
1509 }
1510}
1511
1512pub trait ArgLineCommandExt: Sized {
1514 fn arg_line(mut self, s: &str) -> Self {
1515 for mut arg in s.split_whitespace() {
1516 if (arg.starts_with('"') && arg.ends_with('"'))
1517 || (arg.starts_with('\'') && arg.ends_with('\''))
1518 {
1519 arg = &arg[1..(arg.len() - 1).max(1)];
1520 } else if arg.contains(&['"', '\''][..]) {
1521 panic!("shell-style argument parsing is not supported")
1522 }
1523 self = self.arg(arg);
1524 }
1525 self
1526 }
1527
1528 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self;
1529}
1530
1531impl ArgLineCommandExt for &mut ProcessBuilder {
1532 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
1533 self.arg(s)
1534 }
1535}
1536
1537impl ArgLineCommandExt for &mut Execs {
1538 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
1539 self.arg(s)
1540 }
1541}
1542
1543impl ArgLineCommandExt for snapbox::cmd::Command {
1544 fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
1545 self.arg(s)
1546 }
1547}
1548
1549pub fn cargo_process(arg_line: &str) -> Execs {
1551 let cargo = cargo_exe();
1552 let mut p = process(&cargo);
1553 p.env("CARGO", cargo);
1554 p.arg_line(arg_line);
1555 execs().with_process_builder(p)
1556}
1557
1558pub fn git_process(arg_line: &str) -> ProcessBuilder {
1560 let mut p = process("git");
1561 p.arg_line(arg_line);
1562 p
1563}
1564
1565pub fn sleep_ms(ms: u64) {
1566 ::std::thread::sleep(Duration::from_millis(ms));
1567}
1568
1569pub fn is_coarse_mtime() -> bool {
1571 cfg!(emulate_second_only_system) ||
1574 cfg!(target_os = "macos") && is_ci()
1578}
1579
1580pub fn slow_cpu_multiplier(main: u64) -> Duration {
1585 static SLOW_CPU_MULTIPLIER: OnceLock<u64> = OnceLock::new();
1586 let slow_cpu_multiplier = SLOW_CPU_MULTIPLIER.get_or_init(|| {
1587 env::var("CARGO_TEST_SLOW_CPU_MULTIPLIER")
1588 .ok()
1589 .and_then(|m| m.parse().ok())
1590 .unwrap_or(1)
1591 });
1592 Duration::from_secs(slow_cpu_multiplier * main)
1593}
1594
1595#[cfg(windows)]
1596pub fn symlink_supported() -> bool {
1597 if is_ci() {
1598 return true;
1600 }
1601 let src = paths::root().join("symlink_src");
1602 fs::write(&src, "").unwrap();
1603 let dst = paths::root().join("symlink_dst");
1604 let result = match os::windows::fs::symlink_file(&src, &dst) {
1605 Ok(_) => {
1606 fs::remove_file(&dst).unwrap();
1607 true
1608 }
1609 Err(e) => {
1610 eprintln!(
1611 "symlinks not supported: {:?}\n\
1612 Windows 10 users should enable developer mode.",
1613 e
1614 );
1615 false
1616 }
1617 };
1618 fs::remove_file(&src).unwrap();
1619 return result;
1620}
1621
1622#[cfg(not(windows))]
1623pub fn symlink_supported() -> bool {
1624 true
1625}
1626
1627pub fn no_such_file_err_msg() -> String {
1629 std::io::Error::from_raw_os_error(2).to_string()
1630}
1631
1632pub fn retry<F, R>(n: u32, mut f: F) -> R
1636where
1637 F: FnMut() -> Option<R>,
1638{
1639 let mut count = 0;
1640 let start = std::time::Instant::now();
1641 loop {
1642 if let Some(r) = f() {
1643 return r;
1644 }
1645 count += 1;
1646 if count > n {
1647 panic!(
1648 "test did not finish within {n} attempts ({:?} total)",
1649 start.elapsed()
1650 );
1651 }
1652 sleep_ms(100);
1653 }
1654}
1655
1656#[test]
1657#[should_panic(expected = "test did not finish")]
1658fn retry_fails() {
1659 retry(2, || None::<()>);
1660}
1661
1662pub fn thread_wait_timeout<T>(n: u32, thread: JoinHandle<T>) -> T {
1664 retry(n, || thread.is_finished().then_some(()));
1665 thread.join().unwrap()
1666}
1667
1668pub fn threaded_timeout<F, R>(n: u32, f: F) -> R
1671where
1672 F: FnOnce() -> R + Send + 'static,
1673 R: Send + 'static,
1674{
1675 let thread = std::thread::spawn(|| f());
1676 thread_wait_timeout(n, thread)
1677}
1678
1679#[track_caller]
1681pub fn assert_deps(project: &Project, fingerprint: &str, test_cb: impl Fn(&Path, &[(u8, &str)])) {
1682 let mut files = project
1683 .glob(fingerprint)
1684 .map(|f| f.expect("unwrap glob result"))
1685 .filter(|f| f.extension().is_none());
1687 let info_path = files
1688 .next()
1689 .unwrap_or_else(|| panic!("expected 1 dep-info file at {}, found 0", fingerprint));
1690 assert!(files.next().is_none(), "expected only 1 dep-info file");
1691 let dep_info = fs::read(&info_path).unwrap();
1692 let dep_info = &mut &dep_info[..];
1693
1694 read_usize(dep_info);
1696 read_u8(dep_info);
1697 read_u8(dep_info);
1698
1699 let deps = (0..read_usize(dep_info))
1700 .map(|_| {
1701 let ty = read_u8(dep_info);
1702 let path = std::str::from_utf8(read_bytes(dep_info)).unwrap();
1703 let checksum_present = read_bool(dep_info);
1704 if checksum_present {
1705 let _file_len = read_u64(dep_info);
1707 let _checksum = read_bytes(dep_info);
1708 }
1709 (ty, path)
1710 })
1711 .collect::<Vec<_>>();
1712 test_cb(&info_path, &deps);
1713
1714 fn read_usize(bytes: &mut &[u8]) -> usize {
1715 let ret = &bytes[..4];
1716 *bytes = &bytes[4..];
1717
1718 u32::from_le_bytes(ret.try_into().unwrap()) as usize
1719 }
1720
1721 fn read_u8(bytes: &mut &[u8]) -> u8 {
1722 let ret = bytes[0];
1723 *bytes = &bytes[1..];
1724 ret
1725 }
1726
1727 fn read_bool(bytes: &mut &[u8]) -> bool {
1728 read_u8(bytes) != 0
1729 }
1730
1731 fn read_u64(bytes: &mut &[u8]) -> u64 {
1732 let ret = &bytes[..8];
1733 *bytes = &bytes[8..];
1734
1735 u64::from_le_bytes(ret.try_into().unwrap())
1736 }
1737
1738 fn read_bytes<'a>(bytes: &mut &'a [u8]) -> &'a [u8] {
1739 let n = read_usize(bytes);
1740 let ret = &bytes[..n];
1741 *bytes = &bytes[n..];
1742 ret
1743 }
1744}
1745
1746pub fn assert_deps_contains(project: &Project, fingerprint: &str, expected: &[(u8, &str)]) {
1747 assert_deps(project, fingerprint, |info_path, entries| {
1748 for (e_kind, e_path) in expected {
1749 let pattern = glob::Pattern::new(e_path).unwrap();
1750 let count = entries
1751 .iter()
1752 .filter(|(kind, path)| kind == e_kind && pattern.matches(path))
1753 .count();
1754 if count != 1 {
1755 panic!(
1756 "Expected 1 match of {} {} in {:?}, got {}:\n{:#?}",
1757 e_kind, e_path, info_path, count, entries
1758 );
1759 }
1760 }
1761 })
1762}