use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
static C: AtomicU64 = AtomicU64::new(0);
fn tmp(suffix: &str) -> PathBuf {
let n = C.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!(
"fsys_batch_partial_{}_{}_{}",
std::process::id(),
n,
suffix
))
}
struct FileGuard(PathBuf);
impl Drop for FileGuard {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.0);
}
}
struct DirGuard(PathBuf);
impl Drop for DirGuard {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
#[test]
fn failure_reports_correct_failed_at_and_completed() {
let h = fsys::new().expect("handle");
let ok1 = tmp("ok_1");
let ok2 = tmp("ok_2");
let bad = tmp("bad_dir");
let never = tmp("never_attempted");
let _g1 = FileGuard(ok1.clone());
let _g2 = FileGuard(ok2.clone());
std::fs::create_dir_all(&bad).unwrap();
let _bg = DirGuard(bad.clone());
let _ng = FileGuard(never.clone());
let result = h.write_batch(&[
(ok1.as_path(), b"first".as_slice()),
(ok2.as_path(), b"second".as_slice()),
(bad.as_path(), b"this-will-fail".as_slice()), (never.as_path(), b"never-runs".as_slice()),
]);
let err = result.expect_err("expected failure");
assert_eq!(err.failed_at, 2, "third op (index 2) should fail");
assert_eq!(err.completed, 2, "first two ops should have succeeded");
}
#[test]
fn ops_before_failure_are_durable() {
let h = fsys::new().expect("handle");
let durable = tmp("durable_before_fail");
let bad = tmp("fail_target_dir");
let _g1 = FileGuard(durable.clone());
std::fs::create_dir_all(&bad).unwrap();
let _bg = DirGuard(bad.clone());
let _ = h.write_batch(&[
(durable.as_path(), b"persisted".as_slice()),
(bad.as_path(), b"fail".as_slice()),
]);
assert_eq!(std::fs::read(&durable).unwrap(), b"persisted");
}
#[test]
fn ops_after_failure_are_not_attempted() {
let h = fsys::new().expect("handle");
let ok = tmp("after_failure_ok");
let bad = tmp("middle_fail_dir");
let after = tmp("after_failure_never");
let _g1 = FileGuard(ok.clone());
let _g2 = FileGuard(after.clone());
std::fs::create_dir_all(&bad).unwrap();
let _bg = DirGuard(bad.clone());
let _ = h.write_batch(&[
(ok.as_path(), b"first".as_slice()),
(bad.as_path(), b"fails".as_slice()),
(after.as_path(), b"never-written".as_slice()),
]);
assert!(!after.exists(), "ops after the failing index must not run");
}
#[test]
fn no_rollback_of_successful_ops() {
let h = fsys::new().expect("handle");
let durable = tmp("no_rollback");
let bad = tmp("no_rollback_bad");
let _g1 = FileGuard(durable.clone());
std::fs::create_dir_all(&bad).unwrap();
let _bg = DirGuard(bad.clone());
let result = h.write_batch(&[
(durable.as_path(), b"survives".as_slice()),
(bad.as_path(), b"fails".as_slice()),
]);
assert!(result.is_err());
assert!(durable.exists(), "successful ops must NOT be rolled back");
assert_eq!(std::fs::read(&durable).unwrap(), b"survives");
}
#[test]
fn batch_error_inner_carries_underlying_error() {
let h = fsys::new().expect("handle");
let bad = tmp("inner_error_dir");
std::fs::create_dir_all(&bad).unwrap();
let _bg = DirGuard(bad.clone());
let result = h.write_batch(&[(bad.as_path(), b"x".as_slice())]);
let err = result.expect_err("expected failure");
match err.inner() {
fsys::Error::AtomicReplaceFailed { .. } => { }
fsys::Error::Io(_) => { }
other => panic!("unexpected inner error variant: {other:?}"),
}
}