use std::time::Duration;
use turmoil::fs::shim::tokio::fs as tokio_fs;
use turmoil::{Builder, Result};
#[test]
fn cache_hit_reduces_latency() -> Result {
let mut builder = Builder::new();
builder
.fs()
.io_latency()
.min_latency(Duration::from_millis(10))
.max_latency(Duration::from_millis(10));
builder.fs().page_cache();
let mut sim = builder.build();
sim.client("test", async {
tokio_fs::create_dir("/data").await?;
let file = tokio_fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("/data/file.txt")
.await?;
file.write_at(b"hello world", 0).await?;
let start = tokio::time::Instant::now();
let mut buf = [0u8; 11];
file.read_at(&mut buf, 0).await?;
let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_millis(5),
"cache hit should be fast, got {:?}",
elapsed
);
assert_eq!(&buf, b"hello world");
Ok(())
});
sim.run()
}
#[test]
fn no_cache_full_latency() -> Result {
let mut builder = Builder::new();
builder
.fs()
.io_latency()
.min_latency(Duration::from_millis(10))
.max_latency(Duration::from_millis(10));
let mut sim = builder.build();
sim.client("test", async {
tokio_fs::create_dir("/data").await?;
let file = tokio_fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("/data/file.txt")
.await?;
file.write_at(b"hello", 0).await?;
let start = tokio::time::Instant::now();
let mut buf = [0u8; 5];
file.read_at(&mut buf, 0).await?;
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_millis(10),
"without cache, read should have full latency, got {:?}",
elapsed
);
Ok(())
});
sim.run()
}
#[test]
fn random_eviction_chaos() -> Result {
let mut builder = Builder::new();
builder
.fs()
.io_latency()
.min_latency(Duration::from_millis(10))
.max_latency(Duration::from_millis(10));
builder.fs().page_cache().random_eviction_probability(1.0);
let mut sim = builder.build();
sim.client("test", async {
tokio_fs::create_dir("/data").await?;
let file = tokio_fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("/data/file.txt")
.await?;
file.write_at(b"hello", 0).await?;
let start = tokio::time::Instant::now();
let mut buf = [0u8; 5];
file.read_at(&mut buf, 0).await?;
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_millis(10),
"with 100% eviction, read should have full latency, got {:?}",
elapsed
);
Ok(())
});
sim.run()
}
#[test]
fn direct_io_bypasses_cache() -> Result {
let mut builder = Builder::new();
builder
.fs()
.io_latency()
.min_latency(Duration::from_millis(10))
.max_latency(Duration::from_millis(10));
builder.fs().page_cache();
let mut sim = builder.build();
sim.client("test", async {
tokio_fs::create_dir("/data").await?;
let normal_file = tokio_fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("/data/file.txt")
.await?;
let mut write_data = vec![0u8; 512];
write_data[..5].copy_from_slice(b"hello");
normal_file.write_at(&write_data, 0).await?;
let direct_file = tokio_fs::OpenOptions::new()
.read(true)
.direct_io(true)
.open("/data/file.txt")
.await?;
let layout = std::alloc::Layout::from_size_align(512, 512).unwrap();
let ptr = unsafe { std::alloc::alloc_zeroed(layout) };
let start = tokio::time::Instant::now();
let buf = unsafe { std::slice::from_raw_parts_mut(ptr, 512) };
direct_file.read_at(buf, 0).await?;
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_millis(10),
"direct_io should bypass cache and have full latency, got {:?}",
elapsed
);
assert_eq!(&buf[..5], b"hello");
unsafe { std::alloc::dealloc(ptr, layout) };
Ok(())
});
sim.run()
}
#[test]
fn lru_eviction() -> Result {
let mut builder = Builder::new();
builder
.fs()
.io_latency()
.min_latency(Duration::from_millis(10))
.max_latency(Duration::from_millis(10));
builder.fs().page_cache().max_pages(2);
let mut sim = builder.build();
sim.client("test", async {
tokio_fs::create_dir("/data").await?;
let file = tokio_fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("/data/file.txt")
.await?;
file.write_at(b"page0", 0).await?; file.write_at(b"page1", 4096).await?; file.write_at(b"page2", 8192).await?;
let start = tokio::time::Instant::now();
let mut buf = [0u8; 5];
file.read_at(&mut buf, 0).await?;
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_millis(10),
"evicted page should have full latency, got {:?}",
elapsed
);
let start = tokio::time::Instant::now();
file.read_at(&mut buf, 8192).await?;
let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_millis(5),
"cached page should be fast, got {:?}",
elapsed
);
Ok(())
});
sim.run()
}
#[test]
fn cache_multiple_files() -> Result {
let mut builder = Builder::new();
builder
.fs()
.io_latency()
.min_latency(Duration::from_millis(10))
.max_latency(Duration::from_millis(10));
builder.fs().page_cache();
let mut sim = builder.build();
sim.client("test", async {
tokio_fs::create_dir("/data").await?;
let file1 = tokio_fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("/data/file1.txt")
.await?;
let file2 = tokio_fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("/data/file2.txt")
.await?;
file1.write_at(b"file1", 0).await?;
file2.write_at(b"file2", 0).await?;
let start = tokio::time::Instant::now();
let mut buf = [0u8; 5];
file1.read_at(&mut buf, 0).await?;
file2.read_at(&mut buf, 0).await?;
let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_millis(5),
"both files should be cached, got {:?}",
elapsed
);
Ok(())
});
sim.run()
}