use filetime::FileTime;
use std::cell::RefCell;
use std::env;
use std::fs;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use std::sync::OnceLock;
static CARGO_INTEGRATION_TEST_DIR: &str = "cit";
static GLOBAL_ROOT: OnceLock<Mutex<Option<PathBuf>>> = OnceLock::new();
fn global_root_legacy() -> PathBuf {
let mut path = t!(env::current_exe());
path.pop(); path.pop(); path.push("tmp");
path.mkdir_p();
path
}
fn set_global_root(tmp_dir: Option<&'static str>) {
let mut lock = GLOBAL_ROOT
.get_or_init(|| Default::default())
.lock()
.unwrap();
if lock.is_none() {
let mut root = match tmp_dir {
Some(tmp_dir) => PathBuf::from(tmp_dir),
None => global_root_legacy(),
};
root.push(CARGO_INTEGRATION_TEST_DIR);
*lock = Some(root);
}
}
pub fn global_root() -> PathBuf {
let lock = GLOBAL_ROOT
.get_or_init(|| Default::default())
.lock()
.unwrap();
match lock.as_ref() {
Some(p) => p.clone(),
None => unreachable!("GLOBAL_ROOT not set yet"),
}
}
thread_local! {
static TEST_ID: RefCell<Option<usize>> = RefCell::new(None);
}
pub struct TestIdGuard {
_private: (),
}
pub fn init_root(tmp_dir: Option<&'static str>) -> TestIdGuard {
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
TEST_ID.with(|n| *n.borrow_mut() = Some(id));
let guard = TestIdGuard { _private: () };
set_global_root(tmp_dir);
let r = root();
r.rm_rf();
r.mkdir_p();
guard
}
impl Drop for TestIdGuard {
fn drop(&mut self) {
TEST_ID.with(|n| *n.borrow_mut() = None);
}
}
pub fn root() -> PathBuf {
let id = TEST_ID.with(|n| {
n.borrow().expect(
"Tests must use the `#[cargo_test]` attribute in \
order to be able to use the crate root.",
)
});
let mut root = global_root();
root.push(&format!("t{}", id));
root
}
pub fn home() -> PathBuf {
let mut path = root();
path.push("home");
path.mkdir_p();
path
}
pub trait CargoPathExt {
fn rm_rf(&self);
fn mkdir_p(&self);
fn ls_r(&self) -> Vec<PathBuf>;
fn move_into_the_past(&self) {
self.move_in_time(|sec, nsec| (sec - 3600, nsec))
}
fn move_into_the_future(&self) {
self.move_in_time(|sec, nsec| (sec + 3600, nsec))
}
fn move_in_time<F>(&self, travel_amount: F)
where
F: Fn(i64, u32) -> (i64, u32);
}
impl CargoPathExt for Path {
fn rm_rf(&self) {
let meta = match self.symlink_metadata() {
Ok(meta) => meta,
Err(e) => {
if e.kind() == ErrorKind::NotFound {
return;
}
panic!("failed to remove {:?}, could not read: {:?}", self, e);
}
};
if meta.is_dir() {
if let Err(e) = fs::remove_dir_all(self) {
panic!("failed to remove {:?}: {:?}", self, e)
}
} else if let Err(e) = fs::remove_file(self) {
panic!("failed to remove {:?}: {:?}", self, e)
}
}
fn mkdir_p(&self) {
fs::create_dir_all(self)
.unwrap_or_else(|e| panic!("failed to mkdir_p {}: {}", self.display(), e))
}
fn ls_r(&self) -> Vec<PathBuf> {
walkdir::WalkDir::new(self)
.sort_by_file_name()
.into_iter()
.filter_map(|e| e.map(|e| e.path().to_owned()).ok())
.collect()
}
fn move_in_time<F>(&self, travel_amount: F)
where
F: Fn(i64, u32) -> (i64, u32),
{
if self.is_file() {
time_travel(self, &travel_amount);
} else {
recurse(self, &self.join("target"), &travel_amount);
}
fn recurse<F>(p: &Path, bad: &Path, travel_amount: &F)
where
F: Fn(i64, u32) -> (i64, u32),
{
if p.is_file() {
time_travel(p, travel_amount)
} else if !p.starts_with(bad) {
for f in t!(fs::read_dir(p)) {
let f = t!(f).path();
recurse(&f, bad, travel_amount);
}
}
}
fn time_travel<F>(path: &Path, travel_amount: &F)
where
F: Fn(i64, u32) -> (i64, u32),
{
let stat = t!(path.symlink_metadata());
let mtime = FileTime::from_last_modification_time(&stat);
let (sec, nsec) = travel_amount(mtime.unix_seconds(), mtime.nanoseconds());
let newtime = FileTime::from_unix_time(sec, nsec);
do_op(path, "set file times", |path| {
filetime::set_file_times(path, newtime, newtime)
});
}
}
}
fn do_op<F>(path: &Path, desc: &str, mut f: F)
where
F: FnMut(&Path) -> io::Result<()>,
{
match f(path) {
Ok(()) => {}
Err(ref e) if e.kind() == ErrorKind::PermissionDenied => {
let mut p = t!(path.metadata()).permissions();
p.set_readonly(false);
t!(fs::set_permissions(path, p));
let parent = path.parent().unwrap();
let mut p = t!(parent.metadata()).permissions();
p.set_readonly(false);
t!(fs::set_permissions(parent, p));
f(path).unwrap_or_else(|e| {
panic!("failed to {} {}: {}", desc, path.display(), e);
})
}
Err(e) => {
panic!("failed to {} {}: {}", desc, path.display(), e);
}
}
}
pub fn get_lib_filename(name: &str, kind: &str) -> String {
let prefix = get_lib_prefix(kind);
let extension = get_lib_extension(kind);
format!("{}{}.{}", prefix, name, extension)
}
pub fn get_lib_prefix(kind: &str) -> &str {
match kind {
"lib" | "rlib" => "lib",
"staticlib" | "dylib" | "proc-macro" => {
if cfg!(windows) {
""
} else {
"lib"
}
}
_ => unreachable!(),
}
}
pub fn get_lib_extension(kind: &str) -> &str {
match kind {
"lib" | "rlib" => "rlib",
"staticlib" => {
if cfg!(windows) {
"lib"
} else {
"a"
}
}
"dylib" | "proc-macro" => {
if cfg!(windows) {
"dll"
} else if cfg!(target_os = "macos") {
"dylib"
} else {
"so"
}
}
_ => unreachable!(),
}
}
pub fn sysroot() -> String {
let output = Command::new("rustc")
.arg("--print=sysroot")
.output()
.expect("rustc to run");
assert!(output.status.success());
let sysroot = String::from_utf8(output.stdout).unwrap();
sysroot.trim().to_string()
}
#[cfg(windows)]
pub fn windows_reserved_names_are_allowed() -> bool {
use cargo_util::is_ci;
if is_ci() {
return false;
}
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::ptr;
use windows_sys::Win32::Storage::FileSystem::GetFullPathNameW;
let test_file_name: Vec<_> = OsStr::new("aux.rs").encode_wide().collect();
let buffer_length =
unsafe { GetFullPathNameW(test_file_name.as_ptr(), 0, ptr::null_mut(), ptr::null_mut()) };
if buffer_length == 0 {
return false;
}
let mut buffer = vec![0u16; buffer_length as usize];
let result = unsafe {
GetFullPathNameW(
test_file_name.as_ptr(),
buffer_length,
buffer.as_mut_ptr(),
ptr::null_mut(),
)
};
if result == 0 {
return false;
}
let prefix: Vec<_> = OsStr::new("\\\\.\\").encode_wide().collect();
if buffer.starts_with(&prefix) {
false
} else {
true
}
}