mod support;
use self::support::{
assert_revision_tree_matches_git, make_executable, with_mounted_timefs, write_symlink,
RepositoryFixture,
};
use std::collections::HashSet;
use std::fs;
use std::os::unix::ffi::OsStringExt;
use std::path::Path;
#[test]
fn random_tree_shapes_match_git_ground_truth() {
for seed in 0_u64..6 {
let fixture = randomized_fixture(seed);
let (_value, mount) = with_mounted_timefs(fixture.path(), &[], &["-f"], |mounted| {
assert_revision_tree_matches_git(&fixture, &mounted.path().join("now"), "HEAD");
assert_revision_tree_matches_git(
&fixture,
&mounted.path().join("at").join("HEAD~1"),
"HEAD~1",
);
assert_revision_tree_matches_git(
&fixture,
&mounted.path().join("at").join("HEAD~2"),
"HEAD~2",
);
});
assert!(
mount.stderr.is_empty(),
"randomized fixture {seed} emitted unexpected stderr: {}",
mount.stderr
);
}
}
fn randomized_fixture(seed: u64) -> RepositoryFixture {
let fixture = RepositoryFixture::new("timefs-task8-randomized");
let mut rng = SimpleRng::new(seed ^ 0x5eed_fade_cafe_beef);
for generation in 0..3_u32 {
fixture.remove_worktree_entries();
populate_random_tree(&fixture, &mut rng, generation);
fixture.git(["add", "-A"]);
let message = format!("Random tree generation {generation}");
fixture.git(["commit", "-m", message.as_str()]);
}
fixture
}
fn populate_random_tree(fixture: &RepositoryFixture, rng: &mut SimpleRng, generation: u32) {
let entry_count = 12 + rng.range_usize(0, 8);
let mut seen = HashSet::new();
for index in 0..entry_count {
let relative = unique_random_path(rng, generation, index, &mut seen);
let full_path = fixture.path().join(std::ffi::OsString::from_vec(relative));
if let Some(parent) = full_path.parent() {
fs::create_dir_all(parent)
.expect("randomized fixture parent directory creation should succeed");
}
match rng.range_usize(0, 3) {
0 => {
let len = 1 + rng.range_usize(0, 48);
fs::write(&full_path, random_bytes(rng, len))
.expect("randomized blob write should succeed");
}
1 => {
let len = 1 + rng.range_usize(0, 48);
fs::write(&full_path, random_bytes(rng, len))
.expect("randomized executable write should succeed");
make_executable(&full_path);
}
_ => {
let target = format!("target-{}-{}", generation, rng.range_usize(0, 1_000));
write_symlink(&target, &full_path);
}
}
}
}
fn unique_random_path(
rng: &mut SimpleRng,
generation: u32,
index: usize,
seen: &mut HashSet<Vec<u8>>,
) -> Vec<u8> {
loop {
let depth = rng.range_usize(0, 3);
let mut path = Vec::new();
for level in 0..depth {
if !path.is_empty() {
path.push(b'/');
}
path.extend_from_slice(
format!("dir-{generation}-{level}-{}", rng.range_usize(0, 64)).as_bytes(),
);
}
if !path.is_empty() {
path.push(b'/');
}
path.extend_from_slice(
format!("leaf-{generation}-{index}-{}", rng.range_usize(0, 4_096)).as_bytes(),
);
if seen.insert(path.clone()) {
return path;
}
}
}
fn random_bytes(rng: &mut SimpleRng, len: usize) -> Vec<u8> {
(0..len)
.map(|_| u8::try_from(32 + rng.range_usize(0, 95)).expect("ASCII byte range fits in u8"))
.collect()
}
struct SimpleRng(u64);
impl SimpleRng {
fn new(seed: u64) -> Self {
Self(seed)
}
fn next_u64(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
self.0
}
fn range_usize(&mut self, start: usize, end: usize) -> usize {
assert!(start < end, "invalid random range");
let span = end - start;
start + (self.next_u64() as usize % span)
}
}
#[allow(dead_code)]
fn _assert_exists(path: &Path) {
assert!(path.exists(), "expected path to exist: {}", path.display());
}