cargo_test_support/
paths.rs
1use filetime::FileTime;
4
5use std::cell::RefCell;
6use std::env;
7use std::fs;
8use std::io::{self, ErrorKind};
9use std::path::{Path, PathBuf};
10use std::process::Command;
11use std::sync::atomic::{AtomicUsize, Ordering};
12use std::sync::Mutex;
13use std::sync::OnceLock;
14
15static CARGO_INTEGRATION_TEST_DIR: &str = "cit";
16
17static GLOBAL_ROOT: OnceLock<Mutex<Option<PathBuf>>> = OnceLock::new();
18
19fn global_root_legacy() -> PathBuf {
22 let mut path = t!(env::current_exe());
23 path.pop(); path.pop(); path.push("tmp");
26 path.mkdir_p();
27 path
28}
29
30fn set_global_root(tmp_dir: Option<&'static str>) {
31 let mut lock = GLOBAL_ROOT
32 .get_or_init(|| Default::default())
33 .lock()
34 .unwrap();
35 if lock.is_none() {
36 let mut root = match tmp_dir {
37 Some(tmp_dir) => PathBuf::from(tmp_dir),
38 None => global_root_legacy(),
39 };
40
41 root.push(CARGO_INTEGRATION_TEST_DIR);
42 *lock = Some(root);
43 }
44}
45
46pub fn global_root() -> PathBuf {
50 let lock = GLOBAL_ROOT
51 .get_or_init(|| Default::default())
52 .lock()
53 .unwrap();
54 match lock.as_ref() {
55 Some(p) => p.clone(),
56 None => unreachable!("GLOBAL_ROOT not set yet"),
57 }
58}
59
60thread_local! {
69 static TEST_ID: RefCell<Option<usize>> = RefCell::new(None);
70}
71
72pub struct TestIdGuard {
74 _private: (),
75}
76
77pub fn init_root(tmp_dir: Option<&'static str>) -> TestIdGuard {
79 static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
80
81 let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
82 TEST_ID.with(|n| *n.borrow_mut() = Some(id));
83
84 let guard = TestIdGuard { _private: () };
85
86 set_global_root(tmp_dir);
87 let r = root();
88 r.rm_rf();
89 r.mkdir_p();
90
91 guard
92}
93
94impl Drop for TestIdGuard {
95 fn drop(&mut self) {
96 TEST_ID.with(|n| *n.borrow_mut() = None);
97 }
98}
99
100pub fn root() -> PathBuf {
104 let id = TEST_ID.with(|n| {
105 n.borrow().expect(
106 "Tests must use the `#[cargo_test]` attribute in \
107 order to be able to use the crate root.",
108 )
109 });
110
111 let mut root = global_root();
112 root.push(&format!("t{}", id));
113 root
114}
115
116pub fn home() -> PathBuf {
120 let mut path = root();
121 path.push("home");
122 path.mkdir_p();
123 path
124}
125
126pub fn cargo_home() -> PathBuf {
130 home().join(".cargo")
131}
132
133pub trait CargoPathExt {
135 fn to_url(&self) -> url::Url;
136
137 fn rm_rf(&self);
138 fn mkdir_p(&self);
139
140 fn ls_r(&self) -> Vec<PathBuf>;
143
144 fn move_into_the_past(&self) {
145 self.move_in_time(|sec, nsec| (sec - 3600, nsec))
146 }
147
148 fn move_into_the_future(&self) {
149 self.move_in_time(|sec, nsec| (sec + 3600, nsec))
150 }
151
152 fn move_in_time<F>(&self, travel_amount: F)
153 where
154 F: Fn(i64, u32) -> (i64, u32);
155}
156
157impl CargoPathExt for Path {
158 fn to_url(&self) -> url::Url {
159 url::Url::from_file_path(self).ok().unwrap()
160 }
161
162 fn rm_rf(&self) {
163 let meta = match self.symlink_metadata() {
164 Ok(meta) => meta,
165 Err(e) => {
166 if e.kind() == ErrorKind::NotFound {
167 return;
168 }
169 panic!("failed to remove {:?}, could not read: {:?}", self, e);
170 }
171 };
172 if meta.is_dir() {
176 if let Err(e) = fs::remove_dir_all(self) {
177 panic!("failed to remove {:?}: {:?}", self, e)
178 }
179 } else if let Err(e) = fs::remove_file(self) {
180 panic!("failed to remove {:?}: {:?}", self, e)
181 }
182 }
183
184 fn mkdir_p(&self) {
185 fs::create_dir_all(self)
186 .unwrap_or_else(|e| panic!("failed to mkdir_p {}: {}", self.display(), e))
187 }
188
189 fn ls_r(&self) -> Vec<PathBuf> {
190 walkdir::WalkDir::new(self)
191 .sort_by_file_name()
192 .into_iter()
193 .filter_map(|e| e.map(|e| e.path().to_owned()).ok())
194 .collect()
195 }
196
197 fn move_in_time<F>(&self, travel_amount: F)
198 where
199 F: Fn(i64, u32) -> (i64, u32),
200 {
201 if self.is_file() {
202 time_travel(self, &travel_amount);
203 } else {
204 recurse(self, &self.join("target"), &travel_amount);
205 }
206
207 fn recurse<F>(p: &Path, bad: &Path, travel_amount: &F)
208 where
209 F: Fn(i64, u32) -> (i64, u32),
210 {
211 if p.is_file() {
212 time_travel(p, travel_amount)
213 } else if !p.starts_with(bad) {
214 for f in t!(fs::read_dir(p)) {
215 let f = t!(f).path();
216 recurse(&f, bad, travel_amount);
217 }
218 }
219 }
220
221 fn time_travel<F>(path: &Path, travel_amount: &F)
222 where
223 F: Fn(i64, u32) -> (i64, u32),
224 {
225 let stat = t!(path.symlink_metadata());
226
227 let mtime = FileTime::from_last_modification_time(&stat);
228
229 let (sec, nsec) = travel_amount(mtime.unix_seconds(), mtime.nanoseconds());
230 let newtime = FileTime::from_unix_time(sec, nsec);
231
232 do_op(path, "set file times", |path| {
235 filetime::set_file_times(path, newtime, newtime)
236 });
237 }
238 }
239}
240
241impl CargoPathExt for PathBuf {
242 fn to_url(&self) -> url::Url {
243 self.as_path().to_url()
244 }
245
246 fn rm_rf(&self) {
247 self.as_path().rm_rf()
248 }
249 fn mkdir_p(&self) {
250 self.as_path().mkdir_p()
251 }
252
253 fn ls_r(&self) -> Vec<PathBuf> {
254 self.as_path().ls_r()
255 }
256
257 fn move_in_time<F>(&self, travel_amount: F)
258 where
259 F: Fn(i64, u32) -> (i64, u32),
260 {
261 self.as_path().move_in_time(travel_amount)
262 }
263}
264
265fn do_op<F>(path: &Path, desc: &str, mut f: F)
266where
267 F: FnMut(&Path) -> io::Result<()>,
268{
269 match f(path) {
270 Ok(()) => {}
271 Err(ref e) if e.kind() == ErrorKind::PermissionDenied => {
272 let mut p = t!(path.metadata()).permissions();
273 p.set_readonly(false);
274 t!(fs::set_permissions(path, p));
275
276 let parent = path.parent().unwrap();
279 let mut p = t!(parent.metadata()).permissions();
280 p.set_readonly(false);
281 t!(fs::set_permissions(parent, p));
282
283 f(path).unwrap_or_else(|e| {
284 panic!("failed to {} {}: {}", desc, path.display(), e);
285 })
286 }
287 Err(e) => {
288 panic!("failed to {} {}: {}", desc, path.display(), e);
289 }
290 }
291}
292
293pub fn get_lib_filename(name: &str, kind: &str) -> String {
312 let prefix = get_lib_prefix(kind);
313 let extension = get_lib_extension(kind);
314 format!("{}{}.{}", prefix, name, extension)
315}
316
317pub fn get_lib_prefix(kind: &str) -> &str {
319 match kind {
320 "lib" | "rlib" => "lib",
321 "staticlib" | "dylib" | "proc-macro" => {
322 if cfg!(windows) {
323 ""
324 } else {
325 "lib"
326 }
327 }
328 _ => unreachable!(),
329 }
330}
331
332pub fn get_lib_extension(kind: &str) -> &str {
334 match kind {
335 "lib" | "rlib" => "rlib",
336 "staticlib" => {
337 if cfg!(windows) {
338 "lib"
339 } else {
340 "a"
341 }
342 }
343 "dylib" | "proc-macro" => {
344 if cfg!(windows) {
345 "dll"
346 } else if cfg!(target_os = "macos") {
347 "dylib"
348 } else {
349 "so"
350 }
351 }
352 _ => unreachable!(),
353 }
354}
355
356pub fn sysroot() -> String {
358 let output = Command::new("rustc")
359 .arg("--print=sysroot")
360 .output()
361 .expect("rustc to run");
362 assert!(output.status.success());
363 let sysroot = String::from_utf8(output.stdout).unwrap();
364 sysroot.trim().to_string()
365}
366
367#[cfg(windows)]
373pub fn windows_reserved_names_are_allowed() -> bool {
374 use std::ffi::OsStr;
375 use std::os::windows::ffi::OsStrExt;
376 use std::ptr;
377 use windows_sys::Win32::Storage::FileSystem::GetFullPathNameW;
378
379 let test_file_name: Vec<_> = OsStr::new("aux.rs").encode_wide().collect();
380
381 let buffer_length =
382 unsafe { GetFullPathNameW(test_file_name.as_ptr(), 0, ptr::null_mut(), ptr::null_mut()) };
383
384 if buffer_length == 0 {
385 return false;
387 }
388
389 let mut buffer = vec![0u16; buffer_length as usize];
390
391 let result = unsafe {
392 GetFullPathNameW(
393 test_file_name.as_ptr(),
394 buffer_length,
395 buffer.as_mut_ptr(),
396 ptr::null_mut(),
397 )
398 };
399
400 if result == 0 {
401 return false;
404 }
405
406 let prefix: Vec<_> = OsStr::new("\\\\.\\").encode_wide().collect();
411 if buffer.starts_with(&prefix) {
412 false
413 } else {
414 true
415 }
416}