1use filetime::FileTime;
4use itertools::Itertools;
5use walkdir::WalkDir;
6
7use std::cell::RefCell;
8use std::fs;
9use std::io::{self, ErrorKind};
10use std::path::{Path, PathBuf};
11use std::process::Command;
12use std::sync::Mutex;
13use std::sync::OnceLock;
14use std::sync::atomic::{AtomicUsize, Ordering};
15
16use crate::compare::assert_e2e;
17use crate::compare::match_contains;
18
19static CARGO_INTEGRATION_TEST_DIR: &str = "cit";
20
21static GLOBAL_ROOT: OnceLock<Mutex<Option<PathBuf>>> = OnceLock::new();
22
23fn set_global_root(tmp_dir: &'static str) {
24 let mut lock = GLOBAL_ROOT
25 .get_or_init(|| Default::default())
26 .lock()
27 .unwrap();
28 if lock.is_none() {
29 let mut root = PathBuf::from(tmp_dir);
30 root.push(CARGO_INTEGRATION_TEST_DIR);
31 *lock = Some(root);
32 }
33}
34
35pub fn global_root() -> PathBuf {
39 let lock = GLOBAL_ROOT
40 .get_or_init(|| Default::default())
41 .lock()
42 .unwrap();
43 match lock.as_ref() {
44 Some(p) => p.clone(),
45 None => unreachable!("GLOBAL_ROOT not set yet"),
46 }
47}
48
49thread_local! {
54 static TEST_ID: RefCell<Option<usize>> = const { RefCell::new(None) };
55 static TEST_DIR: RefCell<Option<PathBuf>> = const { RefCell::new(None) };
56}
57
58pub struct TestIdGuard {
60 _private: (),
61}
62
63pub fn init_root(tmp_dir: &'static str, test_dir: PathBuf) -> TestIdGuard {
65 static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
66 let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
67 TEST_ID.with(|n| *n.borrow_mut() = Some(id));
68 if cfg!(windows) {
69 TEST_DIR.with(|n| *n.borrow_mut() = Some(PathBuf::from(format!("t{id}"))));
71 } else {
72 TEST_DIR.with(|n| *n.borrow_mut() = Some(test_dir));
73 }
74 let guard = TestIdGuard { _private: () };
75 set_global_root(tmp_dir);
76 let r = root();
77 r.rm_rf();
78 r.mkdir_p();
79 #[cfg(not(windows))]
80 if id == 0 {
81 use crate::SymlinkBuilder;
84 let mut alias = global_root();
85 alias.push("t0");
86 alias.rm_rf();
87 SymlinkBuilder::new_dir(r, alias).mk();
88 }
89 guard
90}
91
92impl Drop for TestIdGuard {
93 fn drop(&mut self) {
94 TEST_ID.with(|n| *n.borrow_mut() = None);
95 TEST_DIR.with(|n| *n.borrow_mut() = None);
96 }
97}
98
99pub fn root() -> PathBuf {
104 let test_dir = TEST_DIR.with(|n| {
105 n.borrow().clone().expect(
106 "Tests must use the `#[cargo_test]` attribute in \
107 order to be able to use the crate root.",
108 )
109 });
110 let mut root = global_root();
111 root.push(&test_dir);
112 root
113}
114pub fn home() -> PathBuf {
118 let mut path = root();
119 path.push("home");
120 path.mkdir_p();
121 path
122}
123
124pub fn cargo_home() -> PathBuf {
128 home().join(".cargo")
129}
130
131pub fn log_dir() -> PathBuf {
135 cargo_home().join("log")
136}
137
138pub fn log_file(idx: usize) -> PathBuf {
144 let log_dir = log_dir();
145
146 let entries = std::fs::read_dir(&log_dir).unwrap();
147 let mut log_files: Vec<_> = entries
148 .filter_map(Result::ok)
149 .filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("jsonl"))
150 .collect();
151
152 log_files.sort_unstable_by(|a, b| a.file_name().to_str().cmp(&b.file_name().to_str()));
154
155 assert_eq!(
156 idx + 1,
157 log_files.len(),
158 "unexpected number of log files: {}, expected {}",
159 log_files.len(),
160 idx + 1
161 );
162
163 log_files[idx].path()
164}
165
166pub trait CargoPathExt {
168 fn to_url(&self) -> url::Url;
169
170 fn rm_rf(&self);
171 fn mkdir_p(&self);
172
173 fn ls_r(&self) -> Vec<PathBuf>;
176
177 fn move_into_the_past(&self) {
178 self.move_in_time(|sec, nsec| (sec - 3600, nsec))
179 }
180
181 fn move_into_the_future(&self) {
182 self.move_in_time(|sec, nsec| (sec + 3600, nsec))
183 }
184
185 fn move_in_time<F>(&self, travel_amount: F)
186 where
187 F: Fn(i64, u32) -> (i64, u32);
188
189 fn assert_build_dir_layout(&self, expected: impl snapbox::IntoData);
190
191 fn assert_dir_layout(&self, expected: impl snapbox::IntoData, ignored_path_patterns: &[String]);
192}
193
194impl CargoPathExt for Path {
195 fn to_url(&self) -> url::Url {
196 url::Url::from_file_path(self).ok().unwrap()
197 }
198
199 fn rm_rf(&self) {
200 let meta = match self.symlink_metadata() {
201 Ok(meta) => meta,
202 Err(e) => {
203 if e.kind() == ErrorKind::NotFound {
204 return;
205 }
206 panic!("failed to remove {:?}, could not read: {:?}", self, e);
207 }
208 };
209 if meta.is_dir() {
213 if let Err(e) = fs::remove_dir_all(self) {
214 panic!("failed to remove {:?}: {:?}", self, e)
215 }
216 } else if let Err(e) = fs::remove_file(self) {
217 panic!("failed to remove {:?}: {:?}", self, e)
218 }
219 }
220
221 fn mkdir_p(&self) {
222 fs::create_dir_all(self)
223 .unwrap_or_else(|e| panic!("failed to mkdir_p {}: {}", self.display(), e))
224 }
225
226 fn ls_r(&self) -> Vec<PathBuf> {
227 walkdir::WalkDir::new(self)
228 .sort_by_file_name()
229 .into_iter()
230 .filter_map(|e| e.map(|e| e.path().to_owned()).ok())
231 .collect()
232 }
233
234 fn move_in_time<F>(&self, travel_amount: F)
235 where
236 F: Fn(i64, u32) -> (i64, u32),
237 {
238 if self.is_file() {
239 time_travel(self, &travel_amount);
240 } else {
241 recurse(self, &self.join("target"), &travel_amount);
242 }
243
244 fn recurse<F>(p: &Path, bad: &Path, travel_amount: &F)
245 where
246 F: Fn(i64, u32) -> (i64, u32),
247 {
248 if p.is_file() {
249 time_travel(p, travel_amount)
250 } else if !p.starts_with(bad) {
251 for f in t!(fs::read_dir(p)) {
252 let f = t!(f).path();
253 recurse(&f, bad, travel_amount);
254 }
255 }
256 }
257
258 fn time_travel<F>(path: &Path, travel_amount: &F)
259 where
260 F: Fn(i64, u32) -> (i64, u32),
261 {
262 let stat = t!(path.symlink_metadata());
263
264 let mtime = FileTime::from_last_modification_time(&stat);
265
266 let (sec, nsec) = travel_amount(mtime.unix_seconds(), mtime.nanoseconds());
267 let newtime = FileTime::from_unix_time(sec, nsec);
268
269 do_op(path, "set file times", |path| {
272 filetime::set_file_times(path, newtime, newtime)
273 });
274 }
275 }
276
277 #[track_caller]
278 fn assert_build_dir_layout(&self, expected: impl snapbox::IntoData) {
279 self.assert_dir_layout(expected.unordered(), &build_dir_ignored_path_patterns());
286 }
287
288 #[track_caller]
289 fn assert_dir_layout(
290 &self,
291 expected: impl snapbox::IntoData,
292 ignored_path_patterns: &[String],
293 ) {
294 let assert = assert_e2e();
295 let actual = WalkDir::new(self)
296 .sort_by_file_name()
297 .into_iter()
298 .filter_map(|e| e.ok())
299 .filter(|e| e.file_type().is_file())
300 .map(|e| e.path().to_string_lossy().into_owned())
301 .filter(|file| {
302 for ignored in ignored_path_patterns {
303 if match_contains(&ignored, file, &assert.redactions()).is_ok() {
304 return false;
305 }
306 }
307 return true;
308 })
309 .join("\n");
310
311 assert.eq(format!("{actual}\n"), expected);
312 }
313}
314
315impl CargoPathExt for PathBuf {
316 fn to_url(&self) -> url::Url {
317 self.as_path().to_url()
318 }
319
320 fn rm_rf(&self) {
321 self.as_path().rm_rf()
322 }
323 fn mkdir_p(&self) {
324 self.as_path().mkdir_p()
325 }
326
327 fn ls_r(&self) -> Vec<PathBuf> {
328 self.as_path().ls_r()
329 }
330
331 fn move_in_time<F>(&self, travel_amount: F)
332 where
333 F: Fn(i64, u32) -> (i64, u32),
334 {
335 self.as_path().move_in_time(travel_amount)
336 }
337
338 #[track_caller]
339 fn assert_build_dir_layout(&self, expected: impl snapbox::IntoData) {
340 self.as_path().assert_build_dir_layout(expected);
341 }
342
343 #[track_caller]
344 fn assert_dir_layout(
345 &self,
346 expected: impl snapbox::IntoData,
347 ignored_path_patterns: &[String],
348 ) {
349 self.as_path()
350 .assert_dir_layout(expected, ignored_path_patterns);
351 }
352}
353
354fn do_op<F>(path: &Path, desc: &str, mut f: F)
355where
356 F: FnMut(&Path) -> io::Result<()>,
357{
358 match f(path) {
359 Ok(()) => {}
360 Err(ref e) if e.kind() == ErrorKind::PermissionDenied => {
361 let mut p = t!(path.metadata()).permissions();
362 p.set_readonly(false);
363 t!(fs::set_permissions(path, p));
364
365 let parent = path.parent().unwrap();
368 let mut p = t!(parent.metadata()).permissions();
369 p.set_readonly(false);
370 t!(fs::set_permissions(parent, p));
371
372 f(path).unwrap_or_else(|e| {
373 panic!("failed to {} {}: {}", desc, path.display(), e);
374 })
375 }
376 Err(e) => {
377 panic!("failed to {} {}: {}", desc, path.display(), e);
378 }
379 }
380}
381
382fn build_dir_ignored_path_patterns() -> Vec<String> {
384 vec![
385 "[..].dSYM/[..]",
388 "[..].pdb",
390 ]
391 .into_iter()
392 .map(ToString::to_string)
393 .collect()
394}
395
396pub fn get_lib_filename(name: &str, kind: &str) -> String {
415 let prefix = get_lib_prefix(kind);
416 let extension = get_lib_extension(kind);
417 format!("{}{}.{}", prefix, name, extension)
418}
419
420pub fn get_lib_prefix(kind: &str) -> &str {
422 match kind {
423 "lib" | "rlib" => "lib",
424 "staticlib" | "dylib" | "proc-macro" => {
425 if cfg!(windows) {
426 ""
427 } else {
428 "lib"
429 }
430 }
431 _ => unreachable!(),
432 }
433}
434
435pub fn get_lib_extension(kind: &str) -> &str {
437 match kind {
438 "lib" | "rlib" => "rlib",
439 "staticlib" => {
440 if cfg!(windows) {
441 "lib"
442 } else {
443 "a"
444 }
445 }
446 "dylib" | "proc-macro" => {
447 if cfg!(windows) {
448 "dll"
449 } else if cfg!(target_os = "macos") {
450 "dylib"
451 } else {
452 "so"
453 }
454 }
455 _ => unreachable!(),
456 }
457}
458
459pub fn sysroot() -> String {
461 let output = Command::new("rustc")
462 .arg("--print=sysroot")
463 .output()
464 .expect("rustc to run");
465 assert!(output.status.success());
466 let sysroot = String::from_utf8(output.stdout).unwrap();
467 sysroot.trim().to_string()
468}
469
470#[cfg(windows)]
476pub fn windows_reserved_names_are_allowed() -> bool {
477 use std::ffi::OsStr;
478 use std::os::windows::ffi::OsStrExt;
479 use std::ptr;
480 use windows_sys::Win32::Storage::FileSystem::GetFullPathNameW;
481
482 let test_file_name: Vec<_> = OsStr::new("aux.rs").encode_wide().chain([0]).collect();
483
484 let buffer_length =
485 unsafe { GetFullPathNameW(test_file_name.as_ptr(), 0, ptr::null_mut(), ptr::null_mut()) };
486
487 if buffer_length == 0 {
488 return false;
490 }
491
492 let mut buffer = vec![0u16; buffer_length as usize];
493
494 let result = unsafe {
495 GetFullPathNameW(
496 test_file_name.as_ptr(),
497 buffer_length,
498 buffer.as_mut_ptr(),
499 ptr::null_mut(),
500 )
501 };
502
503 if result == 0 {
504 return false;
507 }
508
509 let prefix: Vec<_> = OsStr::new("\\\\.\\").encode_wide().collect();
514 if buffer.starts_with(&prefix) {
515 false
516 } else {
517 true
518 }
519}
520
521pub fn test_dir(path: &str, name: &str) -> std::path::PathBuf {
528 let test_dir: std::path::PathBuf = std::path::PathBuf::from(path)
529 .components()
530 .map(|c| c.as_os_str().to_str().unwrap().trim_end_matches(".rs"))
532 .skip_while(|c| c != &"tests" && c != &"src")
535 .filter(|c| c != &"tests" && c != &"mod")
540 .collect();
541 test_dir.join(name)
542}