1use filetime::FileTime;
4use itertools::Itertools;
5use walkdir::WalkDir;
6
7use std::cell::RefCell;
8use std::env;
9use std::fs;
10use std::io::{self, ErrorKind};
11use std::path::{Path, PathBuf};
12use std::process::Command;
13use std::sync::Mutex;
14use std::sync::OnceLock;
15use std::sync::atomic::{AtomicUsize, Ordering};
16
17use crate::compare::assert_e2e;
18use crate::compare::match_contains;
19
20static CARGO_INTEGRATION_TEST_DIR: &str = "cit";
21
22static GLOBAL_ROOT: OnceLock<Mutex<Option<PathBuf>>> = OnceLock::new();
23
24fn global_root_legacy() -> PathBuf {
27 let mut path = t!(env::current_exe());
28 path.pop(); path.pop(); path.push("tmp");
31 path.mkdir_p();
32 path
33}
34
35fn set_global_root(tmp_dir: Option<&'static str>) {
36 let mut lock = GLOBAL_ROOT
37 .get_or_init(|| Default::default())
38 .lock()
39 .unwrap();
40 if lock.is_none() {
41 let mut root = match tmp_dir {
42 Some(tmp_dir) => PathBuf::from(tmp_dir),
43 None => global_root_legacy(),
44 };
45
46 root.push(CARGO_INTEGRATION_TEST_DIR);
47 *lock = Some(root);
48 }
49}
50
51pub fn global_root() -> PathBuf {
55 let lock = GLOBAL_ROOT
56 .get_or_init(|| Default::default())
57 .lock()
58 .unwrap();
59 match lock.as_ref() {
60 Some(p) => p.clone(),
61 None => unreachable!("GLOBAL_ROOT not set yet"),
62 }
63}
64
65thread_local! {
74 static TEST_ID: RefCell<Option<usize>> = const { RefCell::new(None) };
75}
76
77pub struct TestIdGuard {
79 _private: (),
80}
81
82pub fn init_root(tmp_dir: Option<&'static str>) -> TestIdGuard {
84 static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
85
86 let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
87 TEST_ID.with(|n| *n.borrow_mut() = Some(id));
88
89 let guard = TestIdGuard { _private: () };
90
91 set_global_root(tmp_dir);
92 let r = root();
93 r.rm_rf();
94 r.mkdir_p();
95
96 guard
97}
98
99impl Drop for TestIdGuard {
100 fn drop(&mut self) {
101 TEST_ID.with(|n| *n.borrow_mut() = None);
102 }
103}
104
105pub fn root() -> PathBuf {
109 let id = TEST_ID.with(|n| {
110 n.borrow().expect(
111 "Tests must use the `#[cargo_test]` attribute in \
112 order to be able to use the crate root.",
113 )
114 });
115
116 let mut root = global_root();
117 root.push(&format!("t{}", id));
118 root
119}
120
121pub fn home() -> PathBuf {
125 let mut path = root();
126 path.push("home");
127 path.mkdir_p();
128 path
129}
130
131pub fn cargo_home() -> PathBuf {
135 home().join(".cargo")
136}
137
138pub trait CargoPathExt {
140 fn to_url(&self) -> url::Url;
141
142 fn rm_rf(&self);
143 fn mkdir_p(&self);
144
145 fn ls_r(&self) -> Vec<PathBuf>;
148
149 fn move_into_the_past(&self) {
150 self.move_in_time(|sec, nsec| (sec - 3600, nsec))
151 }
152
153 fn move_into_the_future(&self) {
154 self.move_in_time(|sec, nsec| (sec + 3600, nsec))
155 }
156
157 fn move_in_time<F>(&self, travel_amount: F)
158 where
159 F: Fn(i64, u32) -> (i64, u32);
160
161 fn assert_build_dir_layout(&self, expected: impl snapbox::IntoData);
162
163 fn assert_dir_layout(&self, expected: impl snapbox::IntoData, ignored_path_patterns: &[String]);
164}
165
166impl CargoPathExt for Path {
167 fn to_url(&self) -> url::Url {
168 url::Url::from_file_path(self).ok().unwrap()
169 }
170
171 fn rm_rf(&self) {
172 let meta = match self.symlink_metadata() {
173 Ok(meta) => meta,
174 Err(e) => {
175 if e.kind() == ErrorKind::NotFound {
176 return;
177 }
178 panic!("failed to remove {:?}, could not read: {:?}", self, e);
179 }
180 };
181 if meta.is_dir() {
185 if let Err(e) = fs::remove_dir_all(self) {
186 panic!("failed to remove {:?}: {:?}", self, e)
187 }
188 } else if let Err(e) = fs::remove_file(self) {
189 panic!("failed to remove {:?}: {:?}", self, e)
190 }
191 }
192
193 fn mkdir_p(&self) {
194 fs::create_dir_all(self)
195 .unwrap_or_else(|e| panic!("failed to mkdir_p {}: {}", self.display(), e))
196 }
197
198 fn ls_r(&self) -> Vec<PathBuf> {
199 walkdir::WalkDir::new(self)
200 .sort_by_file_name()
201 .into_iter()
202 .filter_map(|e| e.map(|e| e.path().to_owned()).ok())
203 .collect()
204 }
205
206 fn move_in_time<F>(&self, travel_amount: F)
207 where
208 F: Fn(i64, u32) -> (i64, u32),
209 {
210 if self.is_file() {
211 time_travel(self, &travel_amount);
212 } else {
213 recurse(self, &self.join("target"), &travel_amount);
214 }
215
216 fn recurse<F>(p: &Path, bad: &Path, travel_amount: &F)
217 where
218 F: Fn(i64, u32) -> (i64, u32),
219 {
220 if p.is_file() {
221 time_travel(p, travel_amount)
222 } else if !p.starts_with(bad) {
223 for f in t!(fs::read_dir(p)) {
224 let f = t!(f).path();
225 recurse(&f, bad, travel_amount);
226 }
227 }
228 }
229
230 fn time_travel<F>(path: &Path, travel_amount: &F)
231 where
232 F: Fn(i64, u32) -> (i64, u32),
233 {
234 let stat = t!(path.symlink_metadata());
235
236 let mtime = FileTime::from_last_modification_time(&stat);
237
238 let (sec, nsec) = travel_amount(mtime.unix_seconds(), mtime.nanoseconds());
239 let newtime = FileTime::from_unix_time(sec, nsec);
240
241 do_op(path, "set file times", |path| {
244 filetime::set_file_times(path, newtime, newtime)
245 });
246 }
247 }
248
249 #[track_caller]
250 fn assert_build_dir_layout(&self, expected: impl snapbox::IntoData) {
251 self.assert_dir_layout(expected.unordered(), &build_dir_ignored_path_patterns());
258 }
259
260 #[track_caller]
261 fn assert_dir_layout(
262 &self,
263 expected: impl snapbox::IntoData,
264 ignored_path_patterns: &[String],
265 ) {
266 let assert = assert_e2e();
267 let actual = WalkDir::new(self)
268 .sort_by_file_name()
269 .into_iter()
270 .filter_map(|e| e.ok())
271 .filter(|e| e.file_type().is_file())
272 .map(|e| e.path().to_string_lossy().into_owned())
273 .filter(|file| {
274 for ignored in ignored_path_patterns {
275 if match_contains(&ignored, file, &assert.redactions()).is_ok() {
276 return false;
277 }
278 }
279 return true;
280 })
281 .join("\n");
282
283 assert.eq(format!("{actual}\n"), expected);
284 }
285}
286
287impl CargoPathExt for PathBuf {
288 fn to_url(&self) -> url::Url {
289 self.as_path().to_url()
290 }
291
292 fn rm_rf(&self) {
293 self.as_path().rm_rf()
294 }
295 fn mkdir_p(&self) {
296 self.as_path().mkdir_p()
297 }
298
299 fn ls_r(&self) -> Vec<PathBuf> {
300 self.as_path().ls_r()
301 }
302
303 fn move_in_time<F>(&self, travel_amount: F)
304 where
305 F: Fn(i64, u32) -> (i64, u32),
306 {
307 self.as_path().move_in_time(travel_amount)
308 }
309
310 #[track_caller]
311 fn assert_build_dir_layout(&self, expected: impl snapbox::IntoData) {
312 self.as_path().assert_build_dir_layout(expected);
313 }
314
315 #[track_caller]
316 fn assert_dir_layout(
317 &self,
318 expected: impl snapbox::IntoData,
319 ignored_path_patterns: &[String],
320 ) {
321 self.as_path()
322 .assert_dir_layout(expected, ignored_path_patterns);
323 }
324}
325
326fn do_op<F>(path: &Path, desc: &str, mut f: F)
327where
328 F: FnMut(&Path) -> io::Result<()>,
329{
330 match f(path) {
331 Ok(()) => {}
332 Err(ref e) if e.kind() == ErrorKind::PermissionDenied => {
333 let mut p = t!(path.metadata()).permissions();
334 p.set_readonly(false);
335 t!(fs::set_permissions(path, p));
336
337 let parent = path.parent().unwrap();
340 let mut p = t!(parent.metadata()).permissions();
341 p.set_readonly(false);
342 t!(fs::set_permissions(parent, p));
343
344 f(path).unwrap_or_else(|e| {
345 panic!("failed to {} {}: {}", desc, path.display(), e);
346 })
347 }
348 Err(e) => {
349 panic!("failed to {} {}: {}", desc, path.display(), e);
350 }
351 }
352}
353
354fn build_dir_ignored_path_patterns() -> Vec<String> {
356 vec![
357 "[..].dSYM/[..]",
360 "[..].pdb",
362 ]
363 .into_iter()
364 .map(ToString::to_string)
365 .collect()
366}
367
368pub fn get_lib_filename(name: &str, kind: &str) -> String {
387 let prefix = get_lib_prefix(kind);
388 let extension = get_lib_extension(kind);
389 format!("{}{}.{}", prefix, name, extension)
390}
391
392pub fn get_lib_prefix(kind: &str) -> &str {
394 match kind {
395 "lib" | "rlib" => "lib",
396 "staticlib" | "dylib" | "proc-macro" => {
397 if cfg!(windows) {
398 ""
399 } else {
400 "lib"
401 }
402 }
403 _ => unreachable!(),
404 }
405}
406
407pub fn get_lib_extension(kind: &str) -> &str {
409 match kind {
410 "lib" | "rlib" => "rlib",
411 "staticlib" => {
412 if cfg!(windows) {
413 "lib"
414 } else {
415 "a"
416 }
417 }
418 "dylib" | "proc-macro" => {
419 if cfg!(windows) {
420 "dll"
421 } else if cfg!(target_os = "macos") {
422 "dylib"
423 } else {
424 "so"
425 }
426 }
427 _ => unreachable!(),
428 }
429}
430
431pub fn sysroot() -> String {
433 let output = Command::new("rustc")
434 .arg("--print=sysroot")
435 .output()
436 .expect("rustc to run");
437 assert!(output.status.success());
438 let sysroot = String::from_utf8(output.stdout).unwrap();
439 sysroot.trim().to_string()
440}
441
442#[cfg(windows)]
448pub fn windows_reserved_names_are_allowed() -> bool {
449 use std::ffi::OsStr;
450 use std::os::windows::ffi::OsStrExt;
451 use std::ptr;
452 use windows_sys::Win32::Storage::FileSystem::GetFullPathNameW;
453
454 let test_file_name: Vec<_> = OsStr::new("aux.rs").encode_wide().chain([0]).collect();
455
456 let buffer_length =
457 unsafe { GetFullPathNameW(test_file_name.as_ptr(), 0, ptr::null_mut(), ptr::null_mut()) };
458
459 if buffer_length == 0 {
460 return false;
462 }
463
464 let mut buffer = vec![0u16; buffer_length as usize];
465
466 let result = unsafe {
467 GetFullPathNameW(
468 test_file_name.as_ptr(),
469 buffer_length,
470 buffer.as_mut_ptr(),
471 ptr::null_mut(),
472 )
473 };
474
475 if result == 0 {
476 return false;
479 }
480
481 let prefix: Vec<_> = OsStr::new("\\\\.\\").encode_wide().collect();
486 if buffer.starts_with(&prefix) {
487 false
488 } else {
489 true
490 }
491}