use super::*;
#[test]
fn raw_file_fsfile_round_trips() {
use std::io::{Read, Seek, Write};
let tmp = tempfile::tempdir().expect("tempdir");
let path = tmp.path().join("iou_rawfile.bin");
let cpath = std::ffi::CString::new(path.to_str().expect("utf8 path"))
.expect("path has no interior NUL");
let fd = open_raw(&cpath, O_CREAT | O_RDWR | O_TRUNC, 0o600).expect("openat should succeed");
let ring = Arc::new(Mutex::new(IoUringRaw::new(8).expect("ring setup")));
let mut file = IoUringRawFile::new(ring, fd, false);
let payload = b"raw io_uring file round-trip payload";
let written = Write::write(&mut file, payload).expect("write");
assert_eq!(written, payload.len(), "short write");
assert_eq!(
FsFile::metadata(&file).expect("metadata").len,
payload.len() as u64,
"metadata len must match what was written"
);
assert_eq!(
FsFile::hard_link_count(&file).expect("hard_link_count"),
1,
"a freshly created file has a single hard link"
);
let mut rb = vec![0u8; payload.len()];
let n = FsFile::read_at(&file, &mut rb, 0).expect("read_at");
assert_eq!(n, payload.len(), "short read_at");
assert_eq!(&rb, payload, "read_at bytes must match");
file.seek(std::io::SeekFrom::Start(4)).expect("seek");
let mut tail = vec![0u8; payload.len() - 4];
let mut got = 0;
while got < tail.len() {
let chunk = tail.get_mut(got..).expect("in-bounds");
let r = Read::read(&mut file, chunk).expect("read");
if r == 0 {
break;
}
got += r;
}
assert_eq!(
tail.get(..got).expect("in-bounds"),
payload.get(4..).expect("in-bounds"),
"seek+read bytes must match"
);
FsFile::sync_all(&file).expect("sync_all");
FsFile::sync_data(&file).expect("sync_data");
FsFile::set_len(&file, 4).expect("set_len");
assert_eq!(
FsFile::metadata(&file).expect("metadata after set_len").len,
4,
"set_len must shrink the file"
);
assert!(
FsFile::try_lock_exclusive(&file).expect("try_lock_exclusive"),
"exclusive lock on a fresh file must succeed"
);
drop(file); }
#[test]
fn raw_fs_directory_and_file_ops() {
use crate::fs::Fs;
use crate::path::Path;
use std::io::Write;
let tmp = tempfile::tempdir().expect("tempdir");
let base = tmp.path().join("rawfs");
let base_s = base.to_str().expect("utf8 temp path");
let fs = IoUringRawFs::new(8).expect("fs setup");
let dir = Path::new(base_s);
fs.create_dir_all(dir).expect("create_dir_all");
assert!(fs.exists(dir).expect("dir exists"));
assert!(fs.metadata(dir).expect("dir metadata").is_dir);
let fpath = dir.join("a.txt");
let mut file = fs
.open(&fpath, &FsOpenOptions::new().write(true).create(true))
.expect("open");
file.write_all(b"hello").expect("write");
file.sync_all().expect("sync");
drop(file);
assert_eq!(fs.metadata(&fpath).expect("file metadata").len, 5);
let entries = fs.read_dir(dir).expect("read_dir");
assert!(
entries.iter().any(|e| e.file_name == "a.txt" && !e.is_dir),
"read_dir must list the written file"
);
let fpath2 = dir.join("b.txt");
fs.rename(&fpath, &fpath2).expect("rename");
assert!(!fs.exists(&fpath).expect("old gone"));
assert!(fs.exists(&fpath2).expect("new present"));
fs.remove_file(&fpath2).expect("remove_file");
assert!(!fs.exists(&fpath2).expect("file gone"));
fs.remove_dir_all(dir).expect("remove_dir_all");
assert!(!fs.exists(dir).expect("dir gone"));
}
#[test]
fn raw_fs_same_file_answers_by_identity() {
use crate::fs::Fs;
let tmp = tempfile::tempdir().expect("tempdir");
let fs = IoUringRawFs::new(8).expect("fs setup");
let a = tmp.path().join("a");
let b = tmp.path().join("b");
let c = tmp.path().join("c");
std::fs::write(&a, b"payload").expect("write a");
std::fs::hard_link(&a, &b).expect("link b");
std::fs::write(&c, b"payload").expect("write c");
assert!(
fs.same_file(&a, &b).expect("probe"),
"two names of one inode"
);
assert!(!fs.same_file(&a, &c).expect("probe"), "two inodes");
let missing = fs
.same_file(&a, &tmp.path().join("missing"))
.expect_err("a missing name has no identity");
assert_eq!(missing.kind(), ErrorKind::NotFound);
}
#[test]
fn raw_fs_read_link_reports_only_symlinks() {
use crate::fs::Fs;
let tmp = tempfile::tempdir().expect("tempdir");
let fs = IoUringRawFs::new(8).expect("fs setup");
let file = tmp.path().join("file");
let link = tmp.path().join("link");
std::fs::write(&file, b"payload").expect("write");
std::os::unix::fs::symlink("dangling", &link).expect("symlink");
assert_eq!(
fs.read_link(&link).expect("read_link"),
Some(crate::path::PathBuf::from("dangling"))
);
assert_eq!(fs.read_link(&file).expect("read_link"), None);
let raw = tmp.path().join("raw");
{
use std::os::unix::ffi::OsStrExt as _;
std::os::unix::fs::symlink(std::ffi::OsStr::from_bytes(b"t\xff"), &raw).expect("symlink");
assert_eq!(
fs.read_link(&raw).expect("read_link"),
Some(std::path::PathBuf::from(std::ffi::OsStr::from_bytes(
b"t\xff"
)))
);
}
assert_eq!(
fs.read_link(&tmp.path().join("missing"))
.expect("read_link"),
None
);
}
#[test]
fn raw_fs_symlink_makes_a_link_read_link_reads_back() {
use crate::fs::Fs;
let tmp = tempfile::tempdir().expect("tempdir");
let fs = IoUringRawFs::new(8).expect("fs setup");
let link = tmp.path().join("link");
fs.symlink(std::path::Path::new("elsewhere"), &link, false)
.expect("symlink");
assert_eq!(
fs.read_link(&link).expect("read_link"),
Some(crate::path::PathBuf::from("elsewhere"))
);
}
#[test]
fn raw_file_append_writes_accumulate_at_eof() {
use crate::fs::Fs;
use crate::path::Path;
use std::io::Write;
let tmp = tempfile::tempdir().expect("tempdir");
let p = tmp.path().join("log.bin");
let ps = p.to_str().expect("utf8 temp path");
let fs = IoUringRawFs::new(8).expect("fs setup");
let path = Path::new(ps);
{
let mut f = fs
.open(path, &FsOpenOptions::new().append(true).create(true))
.expect("open append");
f.write_all(b"aaa").expect("first append");
f.sync_all().expect("sync");
}
{
let mut f = fs
.open(path, &FsOpenOptions::new().append(true).create(true))
.expect("reopen append");
f.write_all(b"bbb").expect("second append");
f.sync_all().expect("sync");
}
let f = fs
.open(path, &FsOpenOptions::new().read(true))
.expect("open read");
let mut buf = [0u8; 6];
let n = f.read_at(&mut buf, 0).expect("read_at");
assert_eq!(n, 6, "both appends must be present");
assert_eq!(&buf, b"aaabbb", "appends land in order at EOF");
}
#[test]
fn raw_fs_no_wait_refuses_a_pipe_with_no_reader_and_opens_a_file() {
use crate::fs::Fs;
use crate::path::Path;
use std::io::Write;
let tmp = tempfile::tempdir().expect("tempdir");
let pipe = tmp.path().join("pipe");
let file = tmp.path().join("file");
let name = std::ffi::CString::new(pipe.to_str().expect("utf8")).expect("no NUL");
assert_eq!(unsafe { libc::mkfifo(name.as_ptr(), 0o600) }, 0, "mkfifo");
std::fs::write(&file, b"").expect("file");
let fs = IoUringRawFs::new(8).expect("fs setup");
let opts = FsOpenOptions::new().write(true).no_wait(true);
assert!(
fs.open(Path::new(pipe.to_str().expect("utf8")), &opts)
.is_err(),
"a pipe with no reader is refused"
);
let mut opened = fs
.open(Path::new(file.to_str().expect("utf8")), &opts)
.expect("a regular file opens");
opened.write_all(b"a table").expect("write");
drop(opened);
assert_eq!(std::fs::read(&file).expect("read"), b"a table");
}
#[test]
fn raw_fs_no_follow_refuses_a_symlink_and_opens_a_file() {
use crate::fs::Fs;
use crate::path::Path;
let tmp = tempfile::tempdir().expect("tempdir");
let target = tmp.path().join("target");
let link = tmp.path().join("link");
std::fs::write(&target, b"").expect("target");
std::os::unix::fs::symlink(&target, &link).expect("symlink");
let fs = IoUringRawFs::new(8).expect("fs setup");
let opts = FsOpenOptions::new().write(true).no_follow(true);
assert!(
fs.open(Path::new(link.to_str().expect("utf8")), &opts)
.is_err(),
"a symlink is refused"
);
assert!(
fs.open(Path::new(target.to_str().expect("utf8")), &opts)
.is_ok(),
"a regular file opens"
);
}
#[test]
fn ring_setup_and_nop_round_trips() {
let mut ring = IoUringRaw::new(8).expect("io_uring_setup + mmap should succeed");
assert!(
ring.sq_entries() >= 8,
"kernel rounds entries up to >= request"
);
let res = ring
.nop(0xDEAD_BEEF)
.expect("NOP submit/complete should succeed");
assert_eq!(res, 0, "NOP res must be 0");
}
#[test]
fn multiple_nops_reuse_slots() {
let mut ring = IoUringRaw::new(4).expect("setup");
for i in 0..16u64 {
let res = ring.nop(i).expect("nop");
assert_eq!(res, 0);
}
}
#[test]
fn file_write_fsync_read_round_trips_through_the_ring() {
let path = std::env::temp_dir().join(format!(
"iou_raw_rt_{}_{}.bin",
std::process::id(),
line!()
));
let cpath = std::ffi::CString::new(path.to_str().expect("utf8 path"))
.expect("path has no interior NUL");
let fd = open_raw(&cpath, O_CREAT | O_RDWR | O_TRUNC, 0o600).expect("openat should succeed");
let mut ring = IoUringRaw::new(8).expect("ring setup");
let payload = b"io_uring raw round-trip payload";
let offset = 4096u64;
let written = ring
.write_at(fd, payload, offset)
.expect("write_at should succeed");
assert_eq!(written, payload.len(), "short write");
ring.fsync(fd, false).expect("fsync should succeed");
let mut readback = vec![0u8; payload.len()];
let read = ring
.read_at(fd, &mut readback, offset)
.expect("read_at should succeed");
assert_eq!(read, payload.len(), "short read");
assert_eq!(
&readback, payload,
"read-back bytes must match what we wrote"
);
let mut tail = [0u8; 8];
let eof = ring
.read_at(fd, &mut tail, offset + payload.len() as u64)
.expect("read at EOF should succeed");
assert_eq!(eof, 0, "read past EOF must return 0");
close_raw(fd).expect("close should succeed");
let _ = std::fs::remove_file(&path);
}
#[test]
fn errno_maps_to_expected_error_kinds() {
assert_eq!(errno_to_kind(1), ErrorKind::PermissionDenied); assert_eq!(errno_to_kind(2), ErrorKind::NotFound); assert_eq!(errno_to_kind(4), ErrorKind::Interrupted); assert_eq!(errno_to_kind(9), ErrorKind::InvalidInput); assert_eq!(errno_to_kind(11), ErrorKind::WouldBlock); assert_eq!(errno_to_kind(13), ErrorKind::PermissionDenied); assert_eq!(errno_to_kind(17), ErrorKind::AlreadyExists); assert_eq!(errno_to_kind(22), ErrorKind::InvalidInput); assert_eq!(errno_to_kind(libc::EOPNOTSUPP), ErrorKind::Unsupported);
assert_eq!(errno_to_kind(132), ErrorKind::Other); }
#[test]
fn environment_errnos_map_to_environmental_kinds() {
for (errno, kind) in [
(libc::ENOMEM, ErrorKind::OutOfMemory),
(libc::EMFILE, ErrorKind::TooManyOpenFiles),
(libc::ENFILE, ErrorKind::TooManyOpenFiles),
(libc::ENOSPC, ErrorKind::StorageFull),
(libc::EROFS, ErrorKind::ReadOnlyFilesystem),
(libc::EDQUOT, ErrorKind::QuotaExceeded),
(libc::EPIPE, ErrorKind::BrokenPipe),
(libc::ENETDOWN, ErrorKind::NetworkDown),
(libc::ENETUNREACH, ErrorKind::NetworkUnreachable),
(libc::ECONNABORTED, ErrorKind::ConnectionAborted),
(libc::ECONNRESET, ErrorKind::ConnectionReset),
(libc::ENOTCONN, ErrorKind::NotConnected),
(libc::ETIMEDOUT, ErrorKind::TimedOut),
(libc::ECONNREFUSED, ErrorKind::ConnectionRefused),
(libc::EHOSTUNREACH, ErrorKind::HostUnreachable),
(libc::ESTALE, ErrorKind::StaleNetworkFileHandle),
] {
assert_eq!(errno_to_kind(errno), kind, "errno {errno}");
assert!(kind.is_environmental(), "{kind:?} for errno {errno}");
}
assert_eq!(errno_to_kind(libc::EXDEV), ErrorKind::CrossesDevices);
}
#[test]
fn ring_setup_with_zero_entries_is_rejected() {
match IoUringRaw::new(0) {
Ok(_) => panic!("zero-entry ring must be rejected"),
Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
}
}
#[test]
fn open_raw_missing_file_returns_not_found() {
let cpath =
std::ffi::CString::new("/proc/does-not-exist/iou_raw_missing").expect("no interior NUL");
let err = open_raw(&cpath, O_RDONLY, 0).expect_err("missing file must fail to open");
assert_eq!(err.kind(), ErrorKind::NotFound);
}
#[test]
fn ring_op_on_bad_fd_surfaces_completion_error() {
let mut ring = IoUringRaw::new(4).expect("ring setup");
let err = ring
.write_at(1 << 30, b"x", 0)
.expect_err("write to a non-open fd must fail");
assert_eq!(err.kind(), ErrorKind::InvalidInput); }
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
#[test]
fn raw_available_space_reports_plausible_free_bytes() {
use crate::fs::Fs;
use crate::path::Path;
let tmp = tempfile::tempdir().expect("tempdir");
let fs = IoUringRawFs::new(8).expect("fs setup");
let free = fs
.available_space(Path::new(tmp.path().to_str().expect("utf8 path")))
.expect("statfs must succeed on a real filesystem");
assert!(
free > 0,
"a writable tempdir filesystem must report free space"
);
assert!(
free < u64::MAX,
"a real probe must not return the unbounded sentinel"
);
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
#[test]
fn raw_statfs_on_missing_path_errors() {
let cpath =
std::ffi::CString::new("/proc/does-not-exist/iou_raw_statfs").expect("no interior NUL");
assert!(statfs_available_raw(&cpath).is_err());
}