use super::support::{Fixture, WRAPPER, fixture, fixture_in};
use super::wrapper_store::{
IDLE_BUILD_AGE, PARTIAL_PREFIX, STORE_DIRECTORY, content_name, link_stored, publish, remove_idle_builds, shared_copy,
};
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::time::Duration;
fn identity(path: &Path) -> (u64, u64) {
let metadata: std::fs::Metadata = std::fs::metadata(path).expect("metadata");
return (metadata.dev(), metadata.ino());
}
fn names_in(directory: &Path) -> Vec<String> {
let mut names: Vec<String> = std::fs::read_dir(directory)
.expect("directory")
.map(|entry| { return entry.expect("entry").file_name().to_string_lossy().into_owned(); })
.collect();
names.sort();
return names;
}
#[test]
fn the_name_of_a_build_is_its_content() {
let created: Fixture = fixture("store-name");
let (first, second, third): (PathBuf, PathBuf, PathBuf) =
(created.root.join("first"), created.root.join("second"), created.root.join("third"));
std::fs::write(&first, b"build one").expect("first");
std::fs::write(&second, b"build two").expect("second");
std::fs::write(&third, b"build one").expect("third");
let stamp: std::time::SystemTime = std::time::UNIX_EPOCH + Duration::from_secs(1_000_000_000);
for (path, modified) in [(&first, stamp), (&second, stamp), (&third, stamp + Duration::from_secs(5))] {
std::fs::File::open(path).expect("open").set_modified(modified).expect("set the time");
}
assert_eq!(std::fs::metadata(&first).expect("first").len(), std::fs::metadata(&second).expect("second").len());
let (name_first, name_second, name_third) =
(content_name(&first).expect("first"), content_name(&second).expect("second"), content_name(&third).expect("third"));
assert_ne!(name_first, name_second, "equal length and time, different bytes");
assert_eq!(name_first, name_third, "equal bytes, different time");
}
#[test]
fn concurrent_callers_end_with_links_to_one_final_file_of_the_build() {
let created: Fixture = fixture("store-once");
let built: &Path = Path::new(WRAPPER);
let store: PathBuf = created.root.join(STORE_DIRECTORY);
std::fs::create_dir_all(&store).expect("store");
let other_build: String = String::from("0000000000000000-1-cli-git-native");
std::fs::write(store.join(&other_build), b"another build").expect("another build's copy");
let callers: usize = std::thread::available_parallelism().map_or(4, std::num::NonZeroUsize::get).max(4);
let links: Vec<PathBuf> = std::thread::scope(|scope| {
let handles: Vec<_> = (0..callers)
.map(|caller| {
let (root, link): (&Path, PathBuf) = (created.root.as_path(), created.root.join(format!("link-{caller}")));
return scope.spawn(move || {
let stored: PathBuf = shared_copy(built, root).expect("stored copy");
std::fs::hard_link(&stored, &link).expect("a fixture links the stored copy");
return link;
});
})
.collect();
return handles.into_iter().map(|handle| { return handle.join().expect("caller finished"); }).collect();
});
let first_identity: (u64, u64) = identity(&links[0]);
assert!(links.iter().all(|link| { return identity(link) == first_identity; }), "the links are not one file");
let final_name: String = content_name(built).expect("the build's name");
let mut expected: Vec<String> = vec![final_name, other_build];
expected.sort();
assert_eq!(names_in(&store), expected, "only the final file and the recent copy of the other build");
assert_eq!(std::fs::read(&links[0]).expect("stored bytes"), std::fs::read(built).expect("built bytes"));
let later: PathBuf = shared_copy(built, created.root.as_path()).expect("later ask");
assert_eq!(identity(&later), first_identity, "a later ask replaced the final file");
}
#[test]
fn a_late_publisher_leaves_the_winners_file_in_place() {
let created: Fixture = fixture("store-publish");
let stored: PathBuf = created.root.join("stored");
let (winner, loser): (PathBuf, PathBuf) = (created.root.join("winner"), created.root.join("loser"));
std::fs::write(&winner, b"winner bytes").expect("winner");
std::fs::write(&loser, b"loser bytes").expect("loser");
publish(winner.as_path(), stored.as_path()).expect("the first publisher");
let winner_inode: u64 = identity(&stored).1;
assert!(!winner.exists(), "the first publisher removes its partial file");
publish(loser.as_path(), stored.as_path()).expect("a late publisher succeeds");
assert!(!loser.exists(), "the late publisher removes its partial file");
assert_eq!(std::fs::read(&stored).expect("stored bytes"), b"winner bytes");
assert_eq!(identity(&stored).1, winner_inode, "the stored file was replaced");
}
#[test]
fn only_idle_copies_of_other_builds_leave_the_store() {
let created: Fixture = fixture("store-idle");
let store: PathBuf = created.root.join(STORE_DIRECTORY);
std::fs::create_dir_all(&store).expect("store");
let (keep, other, partial): (PathBuf, PathBuf, PathBuf) = (
store.join("keep"),
store.join("other"),
store.join(format!("{PARTIAL_PREFIX}1-0")),
);
for path in [&keep, &other, &partial] {
std::fs::write(path, b"bytes").expect("file");
}
remove_idle_builds(store.as_path(), keep.as_path(), IDLE_BUILD_AGE);
assert_eq!(names_in(&store).len(), 3, "nothing is idle yet: {:?}", names_in(&store));
remove_idle_builds(store.as_path(), keep.as_path(), Duration::ZERO);
assert_eq!(names_in(&store), vec![String::from("keep")], "everything but the current build's copy was idle");
}
#[test]
fn a_stored_copy_that_vanishes_before_the_link_is_made_again() {
let created: Fixture = fixture("store-vanish");
let built: &Path = Path::new(WRAPPER);
let to: PathBuf = created.root.join("linked");
let mut removals: usize = 0;
link_stored(built, created.root.as_path(), to.as_path(), &mut |stored| {
if removals == 0 {
std::fs::remove_file(stored).expect("the prune removes the copy");
}
removals += 1;
})
.expect("the link after the copy was made again");
assert!(removals >= 2, "the stored copy was asked for again: {removals}");
let stored: PathBuf = created.root.join(STORE_DIRECTORY).join(content_name(built).expect("name"));
assert_eq!(identity(&to), identity(&stored), "the link is not the stored file");
assert_eq!(std::fs::metadata(&to).expect("linked").nlink(), 2, "the link is a copy of its own");
assert_eq!(std::fs::read(&to).expect("linked bytes"), std::fs::read(built).expect("built bytes"));
}
#[test]
fn a_copy_that_does_not_have_the_builds_name_is_not_published() {
let created: Fixture = fixture("store-changed");
shared_copy(Path::new(WRAPPER), created.root.as_path()).expect("the build's name is read and its copy made");
let changed: PathBuf = created.root.join("changed-build");
std::fs::write(&changed, b"bytes the name was not read from").expect("changed build");
let empty_root: PathBuf = created.root.join("empty-root");
let refused = shared_copy(changed.as_path(), empty_root.as_path());
assert!(refused.is_err(), "a copy that is not the named build was published");
assert_eq!(names_in(&empty_root.join(STORE_DIRECTORY)), Vec::<String>::new(), "a partial file or a copy stayed");
}
#[test]
fn fixtures_share_one_wrapper_file_with_the_built_executables_bytes() {
let (first, second): (Fixture, Fixture) = (fixture("share-first"), fixture("share-second"));
assert_eq!(identity(&first.wrapper), identity(&second.wrapper), "separate files in the default root");
assert_eq!(
std::fs::read(&first.wrapper).expect("fixture bytes"),
std::fs::read(WRAPPER).expect("built bytes")
);
let elsewhere: PathBuf = PathBuf::from("/dev/shm").join(format!("native-binary-root-{}", std::process::id()));
std::fs::create_dir_all(&elsewhere).expect("a root in shared memory");
let probe: PathBuf = elsewhere.join("probe");
let crosses: bool = std::fs::hard_link(WRAPPER, &probe)
.is_err_and(|error| { return error.raw_os_error() == Some(libc::EXDEV); });
if !crosses {
std::fs::remove_dir_all(&elsewhere).expect("remove the root");
eprintln!("/dev/shm is not another filesystem than the build here; the relocated root is not exercised");
return;
}
let (moved_first, moved_second): (Fixture, Fixture) =
(fixture_in(elsewhere.as_path(), "share-first"), fixture_in(elsewhere.as_path(), "share-second"));
assert_eq!(identity(&moved_first.wrapper), identity(&moved_second.wrapper), "separate files in a relocated root");
assert_ne!(identity(&moved_first.wrapper), identity(Path::new(WRAPPER)), "the build cannot be that file");
assert_eq!(
std::fs::read(&moved_first.wrapper).expect("moved bytes"),
std::fs::read(WRAPPER).expect("built bytes")
);
drop((moved_first, moved_second));
std::fs::remove_dir_all(&elsewhere).expect("remove the root");
}