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