#![cfg(windows)]
#![allow(clippy::expect_used, clippy::panic)]
use std::io::Write as _;
use std::path::Path;
use tempfile::tempdir;
use zeppelin_embed::vfs::{StdVfs, SyncKind, Vfs};
fn code_of(error: &std::io::Error) -> Option<u32> {
error.raw_os_error().and_then(|raw| u32::try_from(raw).ok())
}
const ERROR_FILE_NOT_FOUND: u32 = 2;
const ERROR_PATH_NOT_FOUND: u32 = 3;
const ERROR_SHARING_VIOLATION: u32 = 32;
#[test]
fn std_vfs_sync_flushes_a_file_for_both_sync_kinds() {
let directory = tempdir().expect("scratch");
let path = directory.path().join("artifact.bin");
StdVfs.write(&path, b"durable-bytes").expect("write");
StdVfs
.sync(&path, SyncKind::Full)
.expect("full sync of a file");
StdVfs
.sync(&path, SyncKind::Barrier)
.expect("barrier sync of a file");
assert_eq!(StdVfs.read(&path).expect("read"), b"durable-bytes");
}
#[test]
fn std_vfs_sync_flushes_a_directory() {
let directory = tempdir().expect("scratch");
let nested = directory.path().join("namespace");
std::fs::create_dir_all(&nested).expect("create namespace");
StdVfs
.write(&nested.join("manifest.zman"), b"generation-1")
.expect("publish");
StdVfs
.sync(&nested, SyncKind::Full)
.expect("full sync of a directory");
StdVfs
.sync(&nested, SyncKind::Barrier)
.expect("barrier sync of a directory");
}
#[test]
fn std_vfs_sync_of_a_missing_path_fails_loudly() {
let directory = tempdir().expect("scratch");
let missing_file = directory.path().join("absent.bin");
let error = StdVfs
.sync(&missing_file, SyncKind::Full)
.expect_err("a missing file must not sync");
assert_eq!(code_of(&error), Some(ERROR_FILE_NOT_FOUND));
let missing_directory = directory.path().join("absent").join("deeper");
let error = StdVfs
.sync(&missing_directory, SyncKind::Full)
.expect_err("a missing directory must not sync");
assert_eq!(code_of(&error), Some(ERROR_PATH_NOT_FOUND));
}
#[test]
fn wal_appends_and_flushes_reach_one_retained_file_object() {
let directory = tempdir().expect("scratch");
let path = directory.path().join("wal.ze");
let mut handle = StdVfs.open_append(&path).expect("create the WAL");
handle.append(b"record-one\n").expect("first append");
handle.sync(SyncKind::Full).expect("flush an append handle");
handle.append(b"record-two\n").expect("second append");
handle
.sync(SyncKind::Barrier)
.expect("barrier flush an append handle");
drop(handle);
assert_eq!(
StdVfs.read(&path).expect("read the WAL"),
b"record-one\nrecord-two\n"
);
}
#[test]
fn publication_replaces_a_name_a_reader_still_holds_open() {
let directory = tempdir().expect("scratch");
let manifest = directory.path().join("manifest.zman");
let temporary = directory.path().join("manifest.zman.tmp");
StdVfs.write(&manifest, b"generation-1").expect("seed");
let reader = std::fs::File::open(&manifest).expect("hold the published manifest open");
StdVfs.write(&temporary, b"generation-2").expect("stage");
StdVfs.sync(&temporary, SyncKind::Full).expect("flush data");
StdVfs
.rename(&temporary, &manifest)
.expect("publish under a live reader");
StdVfs
.sync(directory.path(), SyncKind::Full)
.expect("flush the namespace");
assert_eq!(
StdVfs.read(&manifest).expect("read"),
b"generation-2",
"the published name must resolve to the new generation"
);
drop(reader);
}
#[test]
fn a_mapped_artifact_is_unlinked_at_once_and_its_reader_stays_valid() {
let directory = tempdir().expect("scratch");
let segment = directory.path().join("segment-0001.zseg");
let payload: Vec<u8> = (0..16_384_u32).map(|value| (value % 251) as u8).collect();
StdVfs.write(&segment, &payload).expect("seed");
let file = StdVfs.open_for_map(&segment).expect("open for map");
let mapping = zeppelin_embed::sys::windows::TestMapping::open(file, payload.len() as u64)
.expect("map the sealed segment");
StdVfs
.delete(&segment)
.expect("unlink under a live mapping");
assert!(
!segment.try_exists().expect("exists probe"),
"the path must be unlinked at once, not left delete-pending"
);
StdVfs
.sync(directory.path(), SyncKind::Full)
.expect("flush the namespace after retirement");
assert_eq!(
mapping.bytes(),
payload.as_slice(),
"a retained reader must keep the bytes it validated after the unlink"
);
let error = std::fs::File::open(&segment).expect_err("a retired path must not reopen");
assert_eq!(code_of(&error), Some(ERROR_FILE_NOT_FOUND));
drop(mapping);
}
#[test]
fn retirement_is_refused_by_a_reader_that_does_not_share_deletion() {
use std::os::windows::fs::OpenOptionsExt as _;
const FILE_SHARE_READ: u32 = 0x0000_0001;
let directory = tempdir().expect("scratch");
let segment = directory.path().join("segment-0002.zseg");
StdVfs.write(&segment, b"retired-but-held").expect("seed");
let exclusive = std::fs::OpenOptions::new()
.read(true)
.share_mode(FILE_SHARE_READ)
.open(&segment)
.expect("reader that does not share deletion");
let error = StdVfs
.delete(&segment)
.expect_err("a non-sharing reader must block unlinking");
assert_eq!(code_of(&error), Some(ERROR_SHARING_VIOLATION));
assert!(segment.try_exists().expect("exists"));
drop(exclusive);
StdVfs
.delete(&segment)
.expect("retirement succeeds once the reader is released");
assert!(!segment.try_exists().expect("exists after delete"));
}
#[test]
fn non_ascii_and_spaced_paths_round_trip_through_the_production_vfs() {
let directory = tempdir().expect("scratch");
let store = directory
.path()
.join("magasin de donn\u{e9}es \u{4e2d}\u{6587}");
StdVfs
.ensure_directory(&store, true)
.expect("create a non-ASCII store directory");
let manifest = store.join("manifest \u{2013} v1.zman");
StdVfs
.write(&manifest, b"unicode-generation")
.expect("write");
StdVfs.sync(&manifest, SyncKind::Full).expect("flush data");
StdVfs
.sync(&store, SyncKind::Full)
.expect("flush namespace");
assert_eq!(StdVfs.read(&manifest).expect("read"), b"unicode-generation");
assert_eq!(StdVfs.open(&manifest).expect("length"), 18);
}
#[test]
fn an_interior_nul_in_a_store_path_is_rejected() {
let error = StdVfs
.sync(Path::new("bad\u{0}path"), SyncKind::Full)
.expect_err("interior NUL must be rejected");
assert!(
matches!(
error.kind(),
std::io::ErrorKind::InvalidInput | std::io::ErrorKind::InvalidFilename
),
"expected a typed path rejection, observed {error:?}"
);
}
#[test]
fn a_relative_store_path_resolves_to_the_same_directory() {
let directory = tempdir().expect("scratch");
let nested = directory.path().join("store");
std::fs::create_dir_all(&nested).expect("create");
let mut file = std::fs::File::create(nested.join("marker")).expect("marker");
file.write_all(b"same-store").expect("write marker");
drop(file);
let mixed = nested.join(".").join("marker");
assert_eq!(
StdVfs.read(&mixed).expect("read through `.`"),
b"same-store"
);
let slashed =
std::path::PathBuf::from(nested.to_string_lossy().replace('\\', "/")).join("marker");
assert_eq!(
StdVfs.read(&slashed).expect("read through forward slashes"),
b"same-store"
);
}