use super::support::{FIXTURE_ROOT_VARIABLE, Fixture, fixture, settle_fixture};
use std::path::{Path, PathBuf};
#[test]
fn a_fixture_is_removed_when_its_test_panics() {
let (sender, receiver) = std::sync::mpsc::channel::<PathBuf>();
let handle = std::thread::spawn(move || {
let created: Fixture = fixture("drop-on-panic");
std::fs::write(created.root.join("file"), "content").expect("file");
sender.send(created.root.clone()).expect("send the root");
panic!("the test fails before it removes its fixture");
});
assert!(handle.join().is_err(), "the thread panics on purpose");
let root: PathBuf = receiver.recv().expect("the root");
assert!(!root.exists(), "{} was left behind", root.display());
}
#[test]
fn a_fixture_removed_explicitly_is_dropped_quietly() {
let created: Fixture = fixture("drop-after-remove");
let root: PathBuf = created.root.clone();
super::support::remove(&created);
assert!(!root.exists());
drop(created);
}
#[test]
fn settling_keeps_removes_and_accepts_a_tree_that_is_gone() {
let created: Fixture = fixture("settle");
let root: &Path = created.root.as_path();
std::fs::write(root.join("file"), "content").expect("file");
settle_fixture(root, true);
assert!(root.join("file").exists(), "a kept fixture stays");
settle_fixture(root, false);
assert!(!root.exists(), "a settled fixture goes");
settle_fixture(root, false);
}
#[test]
fn fixtures_are_created_outside_the_system_temporary_directory() {
if std::env::var_os(FIXTURE_ROOT_VARIABLE).is_some() {
return;
}
let created: Fixture = fixture("location");
assert!(
created.root.starts_with(std::fs::canonicalize(env!("CARGO_TARGET_TMPDIR")).expect("target scratch")),
"{} is not under the target scratch directory",
created.root.display()
);
}
fn shared_extent_count(path: &Path) -> Option<usize> {
use std::os::fd::AsRawFd;
const FS_IOC_FIEMAP: u64 = 0xC020_660B;
const FIEMAP_EXTENT_SHARED: u32 = 0x2000;
const HEADER_BYTES: usize = 32;
const EXTENT_BYTES: usize = 56;
const EXTENT_SLOTS: usize = 256;
let file: std::fs::File = std::fs::File::open(path).expect("open the copy");
let mut buffer: Vec<u8> = vec![0; HEADER_BYTES + EXTENT_BYTES * EXTENT_SLOTS];
buffer[8..16].copy_from_slice(&u64::MAX.to_ne_bytes());
buffer[24..28].copy_from_slice(&u32::try_from(EXTENT_SLOTS).expect("slot count").to_ne_bytes());
let result: i32 = unsafe { libc::ioctl(file.as_raw_fd(), FS_IOC_FIEMAP as _, buffer.as_mut_ptr()) };
if result != 0 {
return None;
}
let mapped: usize = usize::try_from(u32::from_ne_bytes(buffer[20..24].try_into().expect("count bytes")))
.expect("mapped count");
let mut shared: usize = 0;
for index in 0..mapped.min(EXTENT_SLOTS) {
let flags_at: usize = HEADER_BYTES + EXTENT_BYTES * index + 40;
let flags: u32 = u32::from_ne_bytes(buffer[flags_at..flags_at + 4].try_into().expect("flag bytes"));
if flags & FIEMAP_EXTENT_SHARED != 0 {
shared += 1;
}
}
return Some(shared);
}
#[test]
fn a_byte_copy_of_the_wrapper_shares_no_storage_with_the_built_executable() {
let created: Fixture = fixture("copy-storage");
let copy: PathBuf = created.root.join("byte-copy");
super::support::copy_executable(Path::new(super::support::WRAPPER), copy.as_path());
match shared_extent_count(copy.as_path()) {
Some(shared) => assert_eq!(
shared,
0,
"{} shares {shared} extents with {}",
copy.display(),
super::support::WRAPPER
),
None => eprintln!("the filesystem under {} reports no extents; nothing to check", created.root.display()),
}
}
#[test]
fn a_fixture_service_outlives_the_thread_that_started_it() {
let created: Fixture = fixture("service-outlives-thread");
let git_dir: PathBuf = created.root.join("repo/.git");
std::fs::create_dir_all(&git_dir).expect("a Git directory to serve");
let service = super::support::start_fixture_service(&created);
std::thread::scope(|scope| {
return scope.spawn(|| { service.serve(&created, git_dir.as_path()); }).join().expect("the thread that starts the service ends");
});
let socket: PathBuf = git_policy_cli::service_instance::socket_for(service.runtime.as_path(), std::fs::canonicalize(&git_dir).expect("canonical").as_path());
std::thread::sleep(std::time::Duration::from_millis(500));
assert!(std::os::unix::net::UnixStream::connect(&socket).is_ok(), "the service stopped with the thread that started it");
}