use std::error::Error;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use std::sync::atomic::{AtomicU64, Ordering};
use rustix::fs::{FlockOperation, flock};
use rustix::io::Errno;
pub type FixtureResult<T> = Result<T, Box<dyn Error>>;
const CEILINGS: &str = include_str!("../tests/ceilings/test-cost.toml");
static FIXTURE_OPENS: AtomicU64 = AtomicU64::new(0);
fn binary_key() -> FixtureResult<(PathBuf, String)> {
static KEY: OnceLock<String> = OnceLock::new();
let exe = std::env::current_exe()?;
let name = exe
.file_name()
.and_then(|name| name.to_str())
.ok_or("the test binary has no file name")?
.to_owned();
let profile = exe
.parent()
.and_then(Path::parent)
.ok_or("the test binary has no target directory above it")?;
let binary_dir = profile.join("test-fixtures").join(name);
if let Some(key) = KEY.get() {
return Ok((binary_dir, key.clone()));
}
let hashed = content_hash(&exe)?;
Ok((binary_dir, KEY.get_or_init(|| hashed).clone()))
}
pub fn content_hash(path: &Path) -> FixtureResult<String> {
let mut hasher = blake3::Hasher::new();
hasher.update_reader(File::open(path)?)?;
Ok(hasher.finalize().to_hex().to_string())
}
fn lock_file(path: &Path, operation: FlockOperation) -> FixtureResult<File> {
let file = OpenOptions::new()
.create(true)
.truncate(false)
.write(true)
.open(path)?;
flock(&file, operation)?;
Ok(file)
}
fn hold_key(binary_dir: &Path, key: &str) -> FixtureResult<File> {
fs::create_dir_all(binary_dir)?;
let registry = lock_file(
&binary_dir.join("registry.lock"),
FlockOperation::LockShared,
)?;
let held = (|| {
let dir = binary_dir.join(key);
fs::create_dir_all(&dir)?;
lock_file(&dir.join("key.lock"), FlockOperation::LockShared)
})();
flock(®istry, FlockOperation::Unlock)?;
held
}
fn remove_stale_keys(binary_dir: &Path, keep: &str) -> FixtureResult<()> {
let registry = lock_file(
&binary_dir.join("registry.lock"),
FlockOperation::LockExclusive,
)?;
let removed = (|| -> FixtureResult<()> {
for entry in fs::read_dir(binary_dir)? {
let entry = entry?;
if !entry.file_type()?.is_dir() || entry.file_name() == keep {
continue;
}
let path = entry.path();
let lock = OpenOptions::new()
.create(true)
.truncate(false)
.write(true)
.open(path.join("key.lock"))?;
match flock(&lock, FlockOperation::NonBlockingLockExclusive) {
Ok(()) => fs::remove_dir_all(&path)?,
Err(error) if error == Errno::WOULDBLOCK => {}
Err(error) => return Err(error.into()),
}
}
Ok(())
})();
flock(®istry, FlockOperation::Unlock)?;
removed
}
pub fn fixture_builds(name: &str) -> FixtureResult<u64> {
let (binary_dir, key) = binary_key()?;
let held = hold_key(&binary_dir, &key)?;
let builds = read_builds(&binary_dir.join(&key).join(name));
flock(&held, FlockOperation::Unlock)?;
builds
}
fn read_builds(root: &Path) -> FixtureResult<u64> {
let record = root.join("builds");
if !record.exists() {
return Ok(0);
}
let mut text = String::new();
File::open(record)?.read_to_string(&mut text)?;
Ok(text.trim().parse()?)
}
fn with_fixture_in<T>(
binary_dir: &Path,
key: &str,
name: &str,
build: impl FnOnce(&Path) -> FixtureResult<()>,
read: impl FnOnce(&Path) -> FixtureResult<T>,
) -> FixtureResult<T> {
let held = hold_key(binary_dir, key)?;
let result = (|| {
let root = binary_dir.join(key).join(name);
fs::create_dir_all(&root)?;
let lock = lock_file(&root.join("build.lock"), FlockOperation::LockExclusive)?;
let build_outcome = build_once(binary_dir, key, &root, build);
flock(&lock, FlockOperation::Unlock)?;
build_outcome?;
read(&root.join("data"))
})();
flock(&held, FlockOperation::Unlock)?;
result
}
fn build_once(
binary_dir: &Path,
key: &str,
root: &Path,
build: impl FnOnce(&Path) -> FixtureResult<()>,
) -> FixtureResult<()> {
if root.join("complete").exists() {
return Ok(());
}
remove_stale_keys(binary_dir, key)?;
let data = root.join("data");
if data.exists() {
fs::remove_dir_all(&data)?;
}
fs::create_dir_all(&data)?;
let opened_before = liminal::durability::stores_opened();
build(&data)?;
FIXTURE_OPENS.fetch_add(
liminal::durability::stores_opened().saturating_sub(opened_before),
Ordering::Relaxed,
);
let builds = read_builds(root)? + 1;
File::create(root.join("builds"))?.write_all(builds.to_string().as_bytes())?;
File::create(root.join("complete"))?;
Ok(())
}
pub fn fixture_copy(
name: &str,
build: impl FnOnce(&Path) -> FixtureResult<()>,
) -> FixtureResult<tempfile::TempDir> {
let (binary_dir, key) = binary_key()?;
with_fixture_in(&binary_dir, &key, name, build, copy_out)
}
fn copy_out(source: &Path) -> FixtureResult<tempfile::TempDir> {
let copy = tempfile::tempdir()?;
copy_tree(source, copy.path())?;
Ok(copy)
}
fn copy_tree(from: &Path, to: &Path) -> FixtureResult<()> {
fs::create_dir_all(to)?;
for entry in fs::read_dir(from)? {
let entry = entry?;
let target = to.join(entry.file_name());
if entry.file_type()?.is_dir() {
copy_tree(&entry.path(), &target)?;
} else {
fs::copy(entry.path(), &target)?;
}
}
Ok(())
}
pub fn pin_store_opens(module: &str, test: &str) -> FixtureResult<()> {
let test = format!("{module}::{test}");
let opens =
liminal::durability::stores_opened().saturating_sub(FIXTURE_OPENS.load(Ordering::Relaxed));
append_ledger(&test, opens)?;
for line in check_store_opens(CEILINGS, &test, opens)? {
println!("{line}");
}
Ok(())
}
fn append_ledger(test: &str, opens: u64) -> FixtureResult<()> {
let (binary_dir, key) = binary_key()?;
let held = hold_key(&binary_dir, &key)?;
let written = (|| -> FixtureResult<()> {
let dir = binary_dir.join(&key);
let lock = lock_file(&dir.join("ledger.lock"), FlockOperation::LockExclusive)?;
let appended = OpenOptions::new()
.create(true)
.append(true)
.open(dir.join("store-opens.ledger"))
.and_then(|mut ledger| writeln!(ledger, "{test} {opens}"));
flock(&lock, FlockOperation::Unlock)?;
appended?;
Ok(())
})();
flock(&held, FlockOperation::Unlock)?;
written
}
fn check_store_opens(ceilings: &str, test: &str, opens: u64) -> FixtureResult<Vec<String>> {
let table: toml::Table = ceilings.parse()?;
let ceiling = table
.get("store_opens")
.and_then(toml::Value::as_table)
.and_then(|opens| opens.get(test))
.and_then(toml::Value::as_integer)
.ok_or_else(|| {
format!("the ceiling file has no store_opens ceiling for {test}; measured {opens}")
})?;
let ceiling = u64::try_from(ceiling)?;
if opens > ceiling {
return Err(format!("{test} opened {opens} stores, over its ceiling of {ceiling}").into());
}
let mut lines = vec![format!("{test} store_opens {opens} (ceiling {ceiling})")];
if opens.saturating_mul(10) < ceiling.saturating_mul(9) {
lines.push(format!(
"ceiling can drop: {test} store_opens {opens}, ceiling {ceiling}"
));
}
Ok(lines)
}
#[cfg(test)]
mod tests {
use std::fs;
use std::path::Path;
use super::{FixtureResult, check_store_opens, fixture_builds, fixture_copy, with_fixture_in};
const CEILINGS: &str = "[store_opens]\n\"a\" = 10\n";
#[test]
fn store_opens_fail_over_their_ceiling_and_say_when_it_can_drop() -> FixtureResult<()> {
assert!(check_store_opens(CEILINGS, "a", 11).is_err());
assert!(check_store_opens(CEILINGS, "missing", 0).is_err());
let at = check_store_opens(CEILINGS, "a", 10)?;
assert!(!at.iter().any(|line| line.starts_with("ceiling can drop")));
let nine = check_store_opens(CEILINGS, "a", 9)?;
assert!(!nine.iter().any(|line| line.starts_with("ceiling can drop")));
let eight = check_store_opens(CEILINGS, "a", 8)?;
assert!(
eight
.iter()
.any(|line| line.starts_with("ceiling can drop"))
);
Ok(())
}
#[test]
fn a_fixture_is_built_once_and_copied_whole() -> FixtureResult<()> {
let build = |dir: &Path| -> FixtureResult<()> {
fs::create_dir_all(dir.join("nested"))?;
fs::write(dir.join("nested").join("row"), b"one")?;
Ok(())
};
drop(fixture_copy("fixture-self-test", build)?);
let copy = fixture_copy("fixture-self-test", |_| {
Err("a built fixture is never built again".into())
})?;
assert_eq!(fs::read(copy.path().join("nested").join("row"))?, b"one");
assert_eq!(fixture_builds("fixture-self-test")?, 1);
Ok(())
}
fn seed(row: &'static [u8]) -> impl FnOnce(&Path) -> FixtureResult<()> {
move |data: &Path| Ok(fs::write(data.join("row"), row)?)
}
#[test]
fn a_key_being_read_survives_another_builds_cleanup() -> FixtureResult<()> {
let binary = tempfile::tempdir()?;
let dir = binary.path();
with_fixture_in(dir, "build-a", "seed", seed(b"a"), |_| Ok(()))?;
let read_during_cleanup = with_fixture_in(
dir,
"build-a",
"seed",
|_| Err("a built fixture is never built again".into()),
|data| {
with_fixture_in(dir, "build-b", "seed", seed(b"b"), |_| Ok(()))?;
Ok(fs::read(data.join("row"))?)
},
)?;
assert_eq!(read_during_cleanup, b"a");
assert!(dir.join("build-a").join("seed").join("complete").exists());
with_fixture_in(dir, "build-c", "seed", seed(b"c"), |_| Ok(()))?;
assert!(!dir.join("build-a").exists());
assert!(!dir.join("build-b").exists());
assert!(dir.join("build-c").join("seed").join("complete").exists());
Ok(())
}
}