#![expect(
clippy::expect_used,
reason = "test assertions over known-good fixtures; failure surfaces via panic"
)]
#![expect(
clippy::indexing_slicing,
reason = "test code indexes fixture buffers with known sizes"
)]
use super::*;
use std::io::{Read, Write};
use std::sync::Arc;
use test_log::test;
fn try_io_uring() -> Option<IoUringFs> {
if !is_io_uring_available() {
eprintln!("skipping: io_uring not supported by kernel");
return None;
}
Some(IoUringFs::new().expect("io_uring available but IoUringFs::new() failed"))
}
#[test]
fn probe_availability() {
let available = is_io_uring_available();
eprintln!("io_uring available: {available}");
}
#[test]
fn create_read_write() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let path = dir.path().join("test.txt");
let opts = FsOpenOptions::new().write(true).create(true);
let mut file = fs.open(&path, &opts)?;
file.write_all(b"hello world")?;
file.sync_all()?;
drop(file);
let opts = FsOpenOptions::new().read(true);
let mut file = fs.open(&path, &opts)?;
let mut buf = String::new();
file.read_to_string(&mut buf)?;
assert_eq!(buf, "hello world");
Ok(())
}
#[test]
fn read_at_pread_semantics() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let path = dir.path().join("pread.bin");
let opts = FsOpenOptions::new().write(true).create(true).read(true);
let mut file = fs.open(&path, &opts)?;
file.write_all(b"hello world")?;
file.sync_data()?;
let mut buf = [0u8; 5];
let n = file.read_at(&mut buf, 6)?;
assert_eq!(n, 5);
assert_eq!(&buf, b"world");
let n = file.read_at(&mut buf, 0)?;
assert_eq!(n, 5);
assert_eq!(&buf, b"hello");
Ok(())
}
#[test]
fn directory_operations() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let nested = dir.path().join("a").join("b").join("c");
fs.create_dir_all(&nested)?;
assert!(fs.exists(&nested)?);
let file_path = nested.join("data.bin");
let opts = FsOpenOptions::new().write(true).create_new(true);
let mut file = fs.open(&file_path, &opts)?;
file.write_all(b"data")?;
drop(file);
let entries: Vec<_> = fs.read_dir(&nested)?;
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].file_name, "data.bin");
let meta = fs.metadata(&file_path)?;
assert!(meta.is_file);
assert_eq!(meta.len, 4);
fs.remove_file(&file_path)?;
assert!(!fs.exists(&file_path)?);
let top = dir.path().join("a");
fs.remove_dir_all(&top)?;
assert!(!fs.exists(&top)?);
Ok(())
}
#[test]
fn rename() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let src = dir.path().join("src.txt");
let dst = dir.path().join("dst.txt");
let opts = FsOpenOptions::new().write(true).create(true);
let mut file = fs.open(&src, &opts)?;
file.write_all(b"content")?;
drop(file);
fs.rename(&src, &dst)?;
assert!(!fs.exists(&src)?);
assert!(fs.exists(&dst)?);
Ok(())
}
#[test]
fn sync_directory() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
fs.sync_directory(dir.path())?;
Ok(())
}
#[test]
fn file_metadata() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let path = dir.path().join("meta.bin");
let opts = FsOpenOptions::new().write(true).create(true).read(true);
let mut file = fs.open(&path, &opts)?;
file.write_all(b"12345")?;
let meta = file.metadata()?;
assert!(meta.is_file);
assert_eq!(meta.len, 5);
Ok(())
}
#[test]
fn file_set_len() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let path = dir.path().join("truncate.bin");
let opts = FsOpenOptions::new().write(true).create(true).read(true);
let mut file = fs.open(&path, &opts)?;
file.write_all(b"hello world")?;
file.set_len(5)?;
let meta = file.metadata()?;
assert_eq!(meta.len, 5);
Ok(())
}
#[test]
fn lock_exclusive() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let path = dir.path().join("lockfile");
let opts = FsOpenOptions::new().write(true).create(true);
let file = fs.open(&path, &opts)?;
file.lock_exclusive()?;
Ok(())
}
#[test]
fn truncate_and_append() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let path = dir.path().join("trunc.txt");
let opts = FsOpenOptions::new().write(true).create(true);
let mut file = fs.open(&path, &opts)?;
file.write_all(b"hello world")?;
drop(file);
let opts = FsOpenOptions::new().write(true).truncate(true);
let mut file = fs.open(&path, &opts)?;
file.write_all(b"hi")?;
drop(file);
let meta = fs.metadata(&path)?;
assert_eq!(meta.len, 2);
let opts = FsOpenOptions::new().write(true).append(true);
let mut file = fs.open(&path, &opts)?;
file.seek(SeekFrom::Start(0))?;
file.write_all(b"!")?;
drop(file);
let mut file = fs.open(&path, &FsOpenOptions::new().read(true))?;
let mut buf = String::new();
file.read_to_string(&mut buf)?;
assert_eq!(buf, "hi!");
assert_eq!(fs.metadata(&path)?.len, 3);
Ok(())
}
#[test]
fn seek_operations() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let path = dir.path().join("seek.bin");
let opts = FsOpenOptions::new().write(true).create(true).read(true);
let mut file = fs.open(&path, &opts)?;
file.write_all(b"hello world")?;
file.seek(SeekFrom::Start(0))?;
let mut buf = [0u8; 5];
file.read_exact(&mut buf)?;
assert_eq!(&buf, b"hello");
file.seek(SeekFrom::Current(1))?;
file.read_exact(&mut buf)?;
assert_eq!(&buf, b"world");
let pos = file.seek(SeekFrom::End(-5))?;
assert_eq!(pos, 6);
Ok(())
}
#[test]
fn concurrent_read_at() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let path = dir.path().join("concurrent.bin");
let opts = FsOpenOptions::new().write(true).create(true).read(true);
let mut file = fs.open(&path, &opts)?;
#[expect(clippy::cast_possible_truncation, reason = "% 256 guarantees 0..=255")]
let data: Vec<u8> = (0u32..1000).map(|i| (i % 256) as u8).collect();
file.write_all(&data)?;
file.sync_all()?;
let file = Arc::new(file);
let mut handles = Vec::new();
for chunk_start in (0..1000).step_by(100) {
let file = Arc::clone(&file);
handles.push(thread::spawn(move || -> io::Result<()> {
let mut buf = [0u8; 100];
let n = file.read_at(&mut buf, chunk_start as u64)?;
assert_eq!(n, 100);
for (i, &byte) in buf.iter().enumerate() {
#[expect(clippy::cast_possible_truncation, reason = "% 256 guarantees 0..=255")]
let expected = ((chunk_start + i) % 256) as u8;
assert_eq!(byte, expected);
}
Ok(())
}));
}
for h in handles {
match h.join() {
Ok(result) => result?,
Err(_) => return Err(io::Error::other("thread panicked")),
}
}
Ok(())
}
#[test]
fn metadata_directory() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let meta = fs.metadata(dir.path())?;
assert!(meta.is_dir);
assert!(!meta.is_file);
Ok(())
}
#[test]
fn object_safety() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let fs: Arc<dyn Fs> = Arc::new(fs);
let dir = tempfile::tempdir()?;
let bogus = dir.path().join("nonexistent");
assert!(!fs.exists(&bogus)?);
Ok(())
}
#[test]
fn empty_buffer_returns_zero() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let path = dir.path().join("empty_buf.bin");
let opts = FsOpenOptions::new().write(true).create(true).read(true);
let mut file = fs.open(&path, &opts)?;
file.write_all(b"data")?;
let n = file.read_at(&mut [], 0)?;
assert_eq!(n, 0);
let n = file.read(&mut [])?;
assert_eq!(n, 0);
let n = file.write(&[])?;
assert_eq!(n, 0);
file.flush()?;
Ok(())
}
#[test]
fn sync_directory_rejects_file() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let path = dir.path().join("not_a_dir.txt");
let opts = FsOpenOptions::new().write(true).create(true);
fs.open(&path, &opts)?;
match fs.sync_directory(&path) {
Ok(()) => panic!("sync_directory on a file should fail"),
Err(err) => assert_eq!(err.kind(), crate::io::ErrorKind::InvalidInput),
}
Ok(())
}
#[test]
fn seek_overflow_returns_error() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let path = dir.path().join("seek_overflow.bin");
let opts = FsOpenOptions::new().write(true).create(true).read(true);
let mut file = fs.open(&path, &opts)?;
file.write_all(b"data")?;
file.seek(SeekFrom::Start(u64::MAX - 1))?;
match file.seek(SeekFrom::Current(2)) {
Ok(_) => panic!("seek past u64::MAX should fail"),
Err(err) => assert_eq!(err.kind(), io::ErrorKind::InvalidInput),
}
file.seek(SeekFrom::Start(0))?;
match file.seek(SeekFrom::Current(-1)) {
Ok(_) => panic!("seek before zero should fail"),
Err(err) => assert_eq!(err.kind(), io::ErrorKind::InvalidInput),
}
match file.seek(SeekFrom::End(-100)) {
Ok(_) => panic!("seek before zero should fail"),
Err(err) => assert_eq!(err.kind(), io::ErrorKind::InvalidInput),
}
Ok(())
}
#[test]
fn debug_impl() {
let Some(fs) = try_io_uring() else {
return;
};
let debug = format!("{fs:?}");
assert!(debug.contains("IoUringFs"));
}
#[test]
fn with_ring_size() -> io::Result<()> {
if !is_io_uring_available() {
eprintln!("skipping: io_uring not supported by kernel");
return Ok(());
}
let fs =
IoUringFs::with_ring_size(64).expect("io_uring available but with_ring_size(64) failed");
let dir = tempfile::tempdir()?;
let path = dir.path().join("ring64.bin");
let opts = FsOpenOptions::new().write(true).create(true);
let mut file = fs.open(&path, &opts)?;
file.write_all(b"ok")?;
file.sync_all()?;
assert_eq!(fs.metadata(&path)?.len, 2);
Ok(())
}
#[test]
fn seek_negative_from_current() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let path = dir.path().join("seek_neg.bin");
let opts = FsOpenOptions::new().write(true).create(true).read(true);
let mut file = fs.open(&path, &opts)?;
file.write_all(b"abcdefghij")?;
file.seek(SeekFrom::Start(8))?;
let pos = file.seek(SeekFrom::Current(-3))?;
assert_eq!(pos, 5);
let mut buf = [0u8; 5];
file.read_exact(&mut buf)?;
assert_eq!(&buf, b"fghij");
Ok(())
}
#[test]
fn clone_shares_ring() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let fs2 = fs.clone();
let dir = tempfile::tempdir()?;
let p1 = dir.path().join("a.txt");
let p2 = dir.path().join("b.txt");
let opts = FsOpenOptions::new().write(true).create(true);
let mut f1 = fs.open(&p1, &opts)?;
let mut f2 = fs2.open(&p2, &opts)?;
f1.write_all(b"one")?;
f2.write_all(b"two")?;
f1.sync_all()?;
f2.sync_all()?;
assert_eq!(fs.metadata(&p1)?.len, 3);
assert_eq!(fs2.metadata(&p2)?.len, 3);
Ok(())
}
#[test]
fn available_space_reports_plausible_free_bytes() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
let free = fs.available_space(dir.path())?;
assert!(
free > 0,
"a writable tempdir filesystem must report free space"
);
assert!(
free < u64::MAX,
"a real probe must not return the unbounded sentinel"
);
Ok(())
}
#[test]
fn volume_id_matches_the_kernel_mount() -> io::Result<()> {
let Some(fs) = try_io_uring() else {
return Ok(());
};
let dir = tempfile::tempdir()?;
assert_eq!(
fs.volume_id(dir.path()),
crate::fs::StdFs.volume_id(dir.path()),
"uring and std agree on the mount backing a path"
);
assert!(fs.volume_id(dir.path()).is_some(), "a real mount has an id");
Ok(())
}