Files
surma 151550242b sausage-vfs: review fixes (fallible growth, symlink flags, stream cancel, sized reads, timestamps, error kinds, linker shadowing)
API: Layer::stat(path, follow), Layer::readlink, Open::follow,
Stat::symlink and Entry::symlink. Process::idle documents the shutdown
order: abort, await the task, await idle, release locks.
2026-10-23 00:49:46 +01:00

93 lines
3.2 KiB
Rust

//! Regression tests for review findings about memory files and process shutdown.
use sausage_vfs::*;
use std::{
io::ErrorKind,
time::{Duration, SystemTime},
};
fn rw() -> Open {
Open {
write: true,
create: true,
..Open::default()
}
}
/// A capacity overflow returns an error without poisoning the shared memory tree.
#[tokio::test]
async fn memory_capacity_failure() {
let memory = Memory::default();
let file = memory.open("/file", rw()).await.unwrap();
let copy = file.clone();
let result =
tokio::spawn(async move { copy.write(i64::MAX as u64 - 10, vec![0; 32]).await }).await;
assert_eq!(result.unwrap().unwrap_err().kind(), ErrorKind::FileTooLarge);
assert_eq!(
file.write(1u64 << 62, vec![1]).await.unwrap_err().kind(),
ErrorKind::FileTooLarge
);
assert_eq!(
file.set_size(1u64 << 62).await.unwrap_err().kind(),
ErrorKind::FileTooLarge
);
file.write(0, b"usable".to_vec()).await.unwrap();
assert_eq!(memory.stat("/file", true).await.unwrap().size, 6);
memory.mkdir("/still-usable").await.unwrap();
}
/// Memory metadata distinguishes a missing parent from a regular-file parent.
#[tokio::test]
async fn memory_parent_errors() {
let memory = Memory::default();
memory.open("/file", rw()).await.unwrap();
assert_eq!(
memory.stat("/file/child", true).await.unwrap_err().kind(),
ErrorKind::NotADirectory
);
assert_eq!(
memory
.open("/file/child", Open::default())
.await
.err()
.unwrap()
.kind(),
ErrorKind::NotADirectory
);
}
/// Memory writes update modification time and read handles can set timestamps.
#[tokio::test]
async fn memory_timestamps() {
let memory = Memory::default();
let file = memory.open("/file", rw()).await.unwrap();
file.write(0, vec![1]).await.unwrap();
assert!(file.stat().await.unwrap().modified.is_some());
let read = memory.open("/file", Open::default()).await.unwrap();
let time = SystemTime::UNIX_EPOCH + Duration::from_secs(123);
read.set_times(None, Some(time)).await.unwrap();
assert_eq!(read.stat().await.unwrap().modified, Some(time));
}
/// Awaiting an aborted writer before idle prevents writes after lock release.
#[tokio::test(flavor = "multi_thread")]
async fn abort_join_then_idle() {
let temp = tempfile::tempdir().unwrap();
let process = Process::default();
let host = Host::new(temp.path(), process.clone()).unwrap();
let file = host.open("/file", rw()).await.unwrap();
let (started_tx, started_rx) = tokio::sync::oneshot::channel();
let copy = file.clone();
let task = tokio::spawn(async move {
copy.append(vec![1]).await.unwrap();
started_tx.send(()).unwrap();
loop {
copy.append(vec![1]).await.unwrap();
}
});
started_rx.await.unwrap();
task.abort();
assert!(task.await.unwrap_err().is_cancelled());
process.idle().await;
let before = file.stat().await.unwrap().size;
assert!(before >= 1);
for _ in 0..100 {
tokio::task::yield_now().await;
assert_eq!(file.stat().await.unwrap().size, before);
}
}