1 #![allow(unknown_lints)]
2
3 use std::cell::Cell;
4 use std::env;
5 use std::ffi::OsStr;
6 use std::fs;
7 use std::io::{self, ErrorKind};
8 use std::path::{Path, PathBuf};
9 use std::sync::atomic::{AtomicUsize, Ordering};
10 use std::sync::Once;
11
12 static RLS_INTEGRATION_TEST_DIR: &str = "rlsit";
13 static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
14
15 thread_local!(static TASK_ID: usize = NEXT_ID.fetch_add(1, Ordering::SeqCst));
16
init()17 fn init() {
18 static GLOBAL_INIT: Once = Once::new();
19 thread_local!(static LOCAL_INIT: Cell<bool> = Cell::new(false));
20 GLOBAL_INIT.call_once(|| {
21 global_root().mkdir_p();
22 });
23 LOCAL_INIT.with(|i| {
24 if i.get() {
25 return;
26 }
27 i.set(true);
28 root().rm_rf();
29 })
30 }
31
global_root() -> PathBuf32 fn global_root() -> PathBuf {
33 let mut path = env::current_exe().unwrap();
34 path.pop(); // chop off exe name
35 path.pop(); // chop off 'debug'
36
37 // If `cargo test` is run manually then our path looks like
38 // `target/debug/foo`, in which case our `path` is already pointing at
39 // `target`. If, however, `cargo test --target $target` is used then the
40 // output is `target/$target/debug/foo`, so our path is pointing at
41 // `target/$target`. Here we conditionally pop the `$target` name.
42 if path.file_name().and_then(OsStr::to_str) != Some("target") {
43 path.pop();
44 }
45
46 path.join(RLS_INTEGRATION_TEST_DIR)
47 }
48
root() -> PathBuf49 pub fn root() -> PathBuf {
50 init();
51 global_root().join(&TASK_ID.with(|my_id| format!("t{}", my_id)))
52 }
53
54 pub trait TestPathExt {
rm_rf(&self)55 fn rm_rf(&self);
mkdir_p(&self)56 fn mkdir_p(&self);
57 }
58
59 #[allow(clippy::redundant_closure)] // &Path is not AsRef<Path>
60 impl TestPathExt for Path {
61 /* Technically there is a potential race condition, but we don't
62 * care all that much for our tests
63 */
rm_rf(&self)64 fn rm_rf(&self) {
65 if !self.exists() {
66 return;
67 }
68
69 for file in fs::read_dir(self).unwrap() {
70 let file = file.unwrap().path();
71
72 if file.is_dir() {
73 file.rm_rf();
74 } else {
75 // On windows we can't remove a readonly file, and git will
76 // often clone files as readonly. As a result, we have some
77 // special logic to remove readonly files on windows.
78 do_op(&file, "remove file", |p| fs::remove_file(p));
79 }
80 }
81 do_op(self, "remove dir", |p| fs::remove_dir(p));
82 }
83
mkdir_p(&self)84 fn mkdir_p(&self) {
85 fs::create_dir_all(self)
86 .unwrap_or_else(|e| panic!("failed to mkdir_p {}: {}", self.display(), e))
87 }
88 }
89
do_op<F>(path: &Path, desc: &str, mut f: F) where F: FnMut(&Path) -> io::Result<()>,90 fn do_op<F>(path: &Path, desc: &str, mut f: F)
91 where
92 F: FnMut(&Path) -> io::Result<()>,
93 {
94 match f(path) {
95 Ok(()) => {}
96 Err(ref e) if cfg!(windows) && e.kind() == ErrorKind::PermissionDenied => {
97 let mut p = path.metadata().unwrap().permissions();
98 p.set_readonly(false);
99 fs::set_permissions(path, p).unwrap();
100 f(path).unwrap_or_else(|e| {
101 panic!("failed to {} {}: {}", desc, path.display(), e);
102 })
103 }
104 Err(e) => {
105 panic!("failed to {} {}: {}", desc, path.display(), e);
106 }
107 }
108 }
109