use std::path::{Path, PathBuf};
use std::process::Command;
use crate::support_fixture_service::Fixture;
use crate::support_program_names_and_time_bound::WRAPPER;
use crate::support_resource_limits::ServiceLaunch;
pub const FIXTURE_ROOT_VARIABLE: &str = "CLI_GIT_TEST_ROOT";
pub fn fixture_root() -> PathBuf {
let root: PathBuf = std::env::var_os(FIXTURE_ROOT_VARIABLE)
.map_or_else(|| { return PathBuf::from(env!("CARGO_TARGET_TMPDIR")); }, PathBuf::from);
std::fs::create_dir_all(&root).expect("fixture root");
return root;
}
pub fn warm_wrapper(linked: &Path) {
static WARMED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
WARMED.get_or_init(|| {
use std::io::Read;
let mut file: std::fs::File = std::fs::File::open(linked).expect("open the fixture wrapper");
let mut chunk: Vec<u8> = vec![0; 1 << 20];
while file.read(chunk.as_mut_slice()).expect("read the built wrapper") > 0 {}
});
}
pub fn link_built_wrapper(built: &Path, to: &Path, store_root: &Path) {
if let Err(error) = std::fs::hard_link(built, to) {
let stored: std::io::Result<()> = if error.raw_os_error() == Some(libc::EXDEV) {
super::wrapper_store::link_stored(built, store_root, to, &mut |_| {})
} else {
Err(error)
};
if let Err(reason) = stored {
eprintln!("fixture wrapper: no hard link of {} at {} ({reason}); copying it instead", built.display(), to.display());
copy_executable(built, to);
}
}
warm_wrapper(to);
}
pub fn fixture(name: &str) -> Fixture {
return fixture_in(fixture_root().as_path(), name);
}
pub fn fixture_in(directory: &Path, name: &str) -> Fixture {
forbid_core_dumps();
std::fs::create_dir_all(directory).expect("fixture directory root");
let created: PathBuf = directory.join(format!("native-binary-{}-{name}", std::process::id()));
if created.exists() {
std::fs::remove_dir_all(&created).expect("remove stale fixture");
}
std::fs::create_dir(&created).expect("fresh fixture");
let root: PathBuf = std::fs::canonicalize(&created).expect("canonical fixture");
for subdirectory in ["wrapper", "bin", "home"] {
std::fs::create_dir(root.join(subdirectory)).expect("fixture directory");
}
let wrapper: PathBuf = root.join("wrapper/monochromatic-git-policy-cli");
link_built_wrapper(Path::new(WRAPPER), wrapper.as_path(), directory);
std::os::unix::fs::symlink(&wrapper, root.join("bin/git")).expect("git-named link");
return Fixture { root, wrapper, service: std::sync::Mutex::new(None), service_environment: std::sync::Mutex::new(Vec::new()), service_launch: std::sync::Mutex::new(ServiceLaunch::default()) };
}
pub const REMOVAL_TRIES: usize = 50;
pub const REMOVAL_PAUSE: std::time::Duration = std::time::Duration::from_millis(100);
pub fn remove_patiently(root: &Path, attempt: &dyn Fn(&Path) -> std::io::Result<()>, tries: usize, pause: std::time::Duration) -> std::io::Result<()> {
let mut last: std::io::Result<()> = Ok(());
for _ in 0..tries {
match attempt(root) {
Ok(()) => return Ok(()),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(error) if error.kind() == std::io::ErrorKind::DirectoryNotEmpty => {
last = Err(error);
std::thread::sleep(pause);
}
Err(error) => return Err(error),
}
}
return last;
}
pub fn remove(fixture: &Fixture) {
remove_patiently(&fixture.root, &|root| { return std::fs::remove_dir_all(root); }, REMOVAL_TRIES, REMOVAL_PAUSE).expect("remove only the fixture");
}
#[path = "../testing/fixture_executable.rs"]
mod fixture_executable;
pub use fixture_executable::executable;
pub fn copy_executable(from: &Path, to: &Path) {
let copied: bool = Command::new("cp")
.arg("--reflink=never")
.arg("--")
.arg(from)
.arg(to)
.status()
.expect("start fixture copy")
.success();
assert!(copied, "fixture copy failed: {from:?} to {to:?}");
}
pub fn forbid_core_dumps() {
static ONCE: std::sync::Once = std::sync::Once::new();
ONCE.call_once(|| {
let none: libc::rlimit = libc::rlimit { rlim_cur: 0, rlim_max: 0 };
let _set = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &none) };
});
}