use fs_core::{Fs, FsError, O_APPEND, O_CREAT, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY};
#[cfg(test)]
mod open {
use super::*;
#[test]
fn success_basic_open_write_read() {
let fs = Fs::new();
fs.mkdir_p("/tmp/foo").unwrap();
let fd = fs.open_path("/tmp/foo/bar.txt").unwrap();
fs.write(fd, b"abc").unwrap();
fs.seek(fd, 0, 0).unwrap(); let mut buf = [0u8; 8];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"abc");
fs.close(fd).unwrap();
}
#[test]
fn success_open_existing_file() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"Original").unwrap();
fs.close(fd).unwrap();
let fd = fs.open_path_with_flags("/test.txt", O_RDONLY).unwrap();
let mut buf = [0u8; 20];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"Original");
fs.close(fd).unwrap();
}
#[test]
fn success_with_rdonly_flag() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"Hello").unwrap();
fs.close(fd).unwrap();
let fd = fs.open_path_with_flags("/test.txt", O_RDONLY).unwrap();
let mut buf = [0u8; 10];
fs.seek(fd, 0, 0).unwrap();
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"Hello");
let result = fs.write(fd, b"World");
assert!(matches!(result, Err(FsError::PermissionDenied)));
fs.close(fd).unwrap();
}
#[test]
fn success_with_wronly_flag() {
let fs = Fs::new();
let fd = fs
.open_path_with_flags("/test.txt", O_WRONLY | O_CREAT)
.unwrap();
let result = fs.write(fd, b"Data");
assert!(result.is_ok());
fs.seek(fd, 0, 0).unwrap();
let mut buf = [0u8; 10];
let result = fs.read(fd, &mut buf);
assert!(matches!(result, Err(FsError::PermissionDenied)));
fs.close(fd).unwrap();
}
#[test]
fn success_with_rdwr_flag() {
let fs = Fs::new();
let fd = fs
.open_path_with_flags("/test.txt", O_RDWR | O_CREAT)
.unwrap();
fs.write(fd, b"ReadWrite").unwrap();
fs.seek(fd, 0, 0).unwrap();
let mut buf = [0u8; 20];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"ReadWrite");
fs.close(fd).unwrap();
}
#[test]
fn error_opening_directory() {
let fs = Fs::new();
fs.mkdir("/testdir").unwrap();
let result = fs.open_path("/testdir");
assert!(matches!(result, Err(FsError::IsADirectory)));
}
#[test]
fn error_without_create_flag() {
let fs = Fs::new();
let result = fs.open_path_with_flags("/nonexistent.txt", O_RDWR);
assert!(matches!(result, Err(FsError::NotFound)));
let fd = fs
.open_path_with_flags("/nonexistent.txt", O_RDWR | O_CREAT)
.unwrap();
fs.close(fd).unwrap();
}
#[test]
fn error_parent_not_exists() {
let fs = Fs::new();
let result = fs.open_path("/nonexistent/file.txt");
assert!(matches!(result, Err(FsError::NotFound)));
fs.mkdir("/nonexistent").unwrap();
let fd = fs.open_path("/nonexistent/file.txt").unwrap();
fs.close(fd).unwrap();
}
#[test]
fn error_using_closed_fd() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.close(fd).unwrap();
let result = fs.read(fd, &mut [0u8; 10]);
assert!(matches!(result, Err(FsError::BadFileDescriptor)));
let result = fs.write(fd, b"data");
assert!(matches!(result, Err(FsError::BadFileDescriptor)));
}
}
#[cfg(test)]
mod read_write {
use super::*;
#[test]
fn success_multiple_fds_independent_positions() {
let fs = Fs::new();
let fd1 = fs.open_path("/shared.txt").unwrap();
fs.write(fd1, b"0123456789").unwrap();
let fd2 = fs.open_path("/shared.txt").unwrap();
fs.seek(fd1, 0, 0).unwrap();
fs.seek(fd2, 5, 0).unwrap();
let mut buf1 = [0u8; 3];
fs.read(fd1, &mut buf1).unwrap();
assert_eq!(&buf1, b"012");
let mut buf2 = [0u8; 3];
fs.read(fd2, &mut buf2).unwrap();
assert_eq!(&buf2, b"567");
let mut buf3 = [0u8; 2];
fs.read(fd1, &mut buf3).unwrap();
assert_eq!(&buf3, b"34");
let mut buf4 = [0u8; 2];
fs.read(fd2, &mut buf4).unwrap();
assert_eq!(&buf4, b"89");
fs.close(fd1).unwrap();
fs.close(fd2).unwrap();
}
#[test]
fn success_write_at_specific_position() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"0123456789").unwrap();
fs.seek(fd, 3, 0).unwrap();
fs.write(fd, b"ABC").unwrap();
fs.seek(fd, 0, 0).unwrap();
let mut buf = [0u8; 20];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"012ABC6789");
fs.close(fd).unwrap();
}
#[test]
fn success_write_beyond_eof() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"Hello").unwrap();
fs.seek(fd, 100, 0).unwrap();
fs.write(fd, b"World").unwrap();
let metadata = fs.fstat(fd).unwrap();
assert_eq!(metadata.size, 105); fs.seek(fd, 10, 0).unwrap();
let mut buf = [0u8; 10];
fs.read(fd, &mut buf).unwrap();
assert_eq!(buf, [0u8; 10]);
fs.seek(fd, 100, 0).unwrap();
let mut buf2 = [0u8; 5];
fs.read(fd, &mut buf2).unwrap();
assert_eq!(&buf2, b"World");
fs.close(fd).unwrap();
}
#[test]
fn edge_case_empty_file() {
let fs = Fs::new();
let fd = fs.open_path("/empty.txt").unwrap();
let mut buf = [0u8; 10];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(n, 0);
let metadata = fs.fstat(fd).unwrap();
assert_eq!(metadata.size, 0);
fs.close(fd).unwrap();
}
#[test]
fn edge_case_read_past_eof() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"Hello").unwrap();
fs.seek(fd, 0, 0).unwrap();
let mut buf = [0u8; 100];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(n, 5);
assert_eq!(&buf[..n], b"Hello");
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(n, 0);
fs.close(fd).unwrap();
}
#[test]
fn edge_case_read_after_write_without_seek() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"Hello").unwrap();
let mut buf = [0u8; 10];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(n, 0);
fs.seek(fd, 0, 0).unwrap();
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"Hello");
fs.close(fd).unwrap();
}
}
#[cfg(test)]
mod seek {
use super::*;
#[test]
fn success_seek_cur() {
let fs = Fs::new();
let fd = fs.open_path("/seek_test.txt").unwrap();
fs.write(fd, b"0123456789").unwrap();
fs.seek(fd, 0, 0).unwrap(); let mut buf = [0u8; 5];
fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf, b"01234");
let pos = fs.seek(fd, 2, 1).unwrap(); assert_eq!(pos, 7);
let mut buf2 = [0u8; 3];
let n = fs.read(fd, &mut buf2).unwrap();
assert_eq!(&buf2[..n], b"789");
fs.seek(fd, -5, 1).unwrap(); let mut buf3 = [0u8; 2];
fs.read(fd, &mut buf3).unwrap();
assert_eq!(&buf3, b"56");
fs.close(fd).unwrap();
}
#[test]
fn success_seek_end() {
let fs = Fs::new();
let fd = fs.open_path("/seek_end.txt").unwrap();
fs.write(fd, b"Hello World").unwrap();
let pos = fs.seek(fd, -5, 2).unwrap(); assert_eq!(pos, 6);
let mut buf = [0u8; 5];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"World");
let pos = fs.seek(fd, 0, 2).unwrap(); assert_eq!(pos, 11);
fs.close(fd).unwrap();
}
#[test]
fn error_invalid_whence() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
let result = fs.seek(fd, 0, 99);
assert!(matches!(result, Err(FsError::InvalidArgument)));
fs.close(fd).unwrap();
}
#[test]
fn error_negative_offsets() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"0123456789").unwrap();
let result = fs.seek(fd, -5, 0); assert!(matches!(result, Err(FsError::InvalidArgument)));
fs.seek(fd, 3, 0).unwrap(); let result = fs.seek(fd, -5, 1); assert!(matches!(result, Err(FsError::InvalidArgument)));
let result = fs.seek(fd, -20, 2); assert!(matches!(result, Err(FsError::InvalidArgument)));
fs.seek(fd, 5, 0).unwrap(); let pos = fs.seek(fd, -2, 1).unwrap(); assert_eq!(pos, 3);
let pos = fs.seek(fd, -3, 2).unwrap(); assert_eq!(pos, 7);
fs.close(fd).unwrap();
}
}
#[cfg(test)]
mod stat_fstat {
use super::*;
#[test]
fn success_stat_and_fstat() {
let fs = Fs::new();
fs.mkdir("/data").unwrap();
let fd = fs.open_path("/data/file.txt").unwrap();
let data = b"test data for stat";
fs.write(fd, data).unwrap();
let metadata = fs.fstat(fd).unwrap();
assert_eq!(metadata.size, data.len() as u64);
let metadata2 = fs.stat("/data/file.txt").unwrap();
assert_eq!(metadata2.size, data.len() as u64);
assert_eq!(metadata2.permissions, 0o644);
fs.close(fd).unwrap();
}
}
#[cfg(test)]
mod mkdir {
use super::*;
#[test]
fn success_mkdir_and_mkdir_p() {
let fs = Fs::new();
assert!(fs.mkdir("/test").is_ok());
assert!(matches!(fs.mkdir("/test"), Err(FsError::AlreadyExists)));
assert!(matches!(fs.mkdir("/foo/bar"), Err(FsError::NotFound)));
assert!(fs.mkdir_p("/foo/bar/baz").is_ok());
assert!(fs.stat("/foo").is_ok());
assert!(fs.stat("/foo/bar").is_ok());
assert!(fs.stat("/foo/bar/baz").is_ok());
}
#[test]
fn success_parent_mtime_updates() {
let fs = Fs::new();
fs.mkdir("/parent").unwrap();
let initial_metadata = fs.stat("/parent").unwrap();
let initial_mtime = initial_metadata.modified;
let fd = fs.open_path("/parent/file.txt").unwrap();
fs.close(fd).unwrap();
let after_file_metadata = fs.stat("/parent").unwrap();
assert!(
after_file_metadata.modified > initial_mtime,
"Parent directory mtime should update when file is created"
);
let mtime_before_subdir = after_file_metadata.modified;
fs.mkdir("/parent/subdir").unwrap();
let after_subdir_metadata = fs.stat("/parent").unwrap();
assert!(
after_subdir_metadata.modified > mtime_before_subdir,
"Parent directory mtime should update when subdirectory is created"
);
}
#[test]
fn success_root_has_timestamps() {
let fs = Fs::new();
let root_metadata = fs.stat("/").unwrap();
assert_eq!(
root_metadata.created, 0,
"Root directory should have created timestamp 0"
);
assert_eq!(
root_metadata.modified, 0,
"Root directory should have modified timestamp 0"
);
assert_eq!(
root_metadata.created, root_metadata.modified,
"Root directory created and modified should be equal initially"
);
}
}
#[cfg(test)]
mod unlink {
use super::*;
#[test]
fn success_delete_file() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"content").unwrap();
fs.close(fd).unwrap();
assert!(fs.stat("/test.txt").is_ok());
fs.unlink("/test.txt").unwrap();
assert!(matches!(fs.stat("/test.txt"), Err(FsError::NotFound)));
}
#[test]
fn success_parent_mtime_updates() {
let fs = Fs::new();
fs.mkdir("/dir").unwrap();
let fd = fs.open_path("/dir/file.txt").unwrap();
fs.close(fd).unwrap();
let metadata_before = fs.stat("/dir").unwrap();
fs.unlink("/dir/file.txt").unwrap();
let metadata_after = fs.stat("/dir").unwrap();
assert!(
metadata_after.modified > metadata_before.modified,
"Parent directory mtime should update when file is deleted"
);
}
#[test]
fn error_various_cases() {
let fs = Fs::new();
let result = fs.unlink("/nonexistent.txt");
assert!(matches!(result, Err(FsError::NotFound)));
fs.mkdir("/testdir").unwrap();
let result = fs.unlink("/testdir");
assert!(matches!(result, Err(FsError::IsADirectory)));
let result = fs.unlink("");
assert!(matches!(result, Err(FsError::InvalidArgument)));
let result = fs.unlink("/");
assert!(matches!(result, Err(FsError::InvalidArgument)));
}
}
#[cfg(test)]
mod rename {
use super::*;
#[test]
fn success_rename_file() {
let fs = Fs::new();
let fd = fs.open_path("/old.txt").unwrap();
fs.write(fd, b"hello").unwrap();
fs.close(fd).unwrap();
fs.rename("/old.txt", "/new.txt").unwrap();
assert!(matches!(fs.stat("/old.txt"), Err(FsError::NotFound)));
let fd = fs.open_path("/new.txt").unwrap();
let mut buf = [0u8; 16];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"hello");
fs.close(fd).unwrap();
}
#[test]
fn success_rename_cross_directory() {
let fs = Fs::new();
fs.mkdir("/dir1").unwrap();
fs.mkdir("/dir2").unwrap();
let fd = fs.open_path("/dir1/file.txt").unwrap();
fs.write(fd, b"moved").unwrap();
fs.close(fd).unwrap();
fs.rename("/dir1/file.txt", "/dir2/file.txt").unwrap();
assert!(matches!(fs.stat("/dir1/file.txt"), Err(FsError::NotFound)));
let fd = fs.open_path("/dir2/file.txt").unwrap();
let mut buf = [0u8; 16];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"moved");
fs.close(fd).unwrap();
}
#[test]
fn success_rename_directory() {
let fs = Fs::new();
fs.mkdir("/olddir").unwrap();
let fd = fs.open_path("/olddir/child.txt").unwrap();
fs.write(fd, b"child").unwrap();
fs.close(fd).unwrap();
fs.rename("/olddir", "/newdir").unwrap();
assert!(matches!(fs.stat("/olddir"), Err(FsError::NotFound)));
assert!(fs.stat("/newdir").unwrap().is_dir);
let fd = fs.open_path("/newdir/child.txt").unwrap();
let mut buf = [0u8; 16];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"child");
fs.close(fd).unwrap();
}
#[test]
fn success_rename_overwrite_file() {
let fs = Fs::new();
let fd = fs.open_path("/a.txt").unwrap();
fs.write(fd, b"aaa").unwrap();
fs.close(fd).unwrap();
let fd = fs.open_path("/b.txt").unwrap();
fs.write(fd, b"bbb").unwrap();
fs.close(fd).unwrap();
fs.rename("/a.txt", "/b.txt").unwrap();
assert!(matches!(fs.stat("/a.txt"), Err(FsError::NotFound)));
let fd = fs.open_path("/b.txt").unwrap();
let mut buf = [0u8; 16];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"aaa");
fs.close(fd).unwrap();
}
#[test]
fn success_rename_overwrite_empty_dir() {
let fs = Fs::new();
fs.mkdir("/src").unwrap();
fs.mkdir("/dst").unwrap();
fs.rename("/src", "/dst").unwrap();
assert!(matches!(fs.stat("/src"), Err(FsError::NotFound)));
assert!(fs.stat("/dst").unwrap().is_dir);
}
#[test]
fn success_same_path_noop() {
let fs = Fs::new();
let fd = fs.open_path("/file.txt").unwrap();
fs.write(fd, b"data").unwrap();
fs.close(fd).unwrap();
fs.rename("/file.txt", "/file.txt").unwrap();
assert!(fs.stat("/file.txt").is_ok());
}
#[test]
fn error_not_found() {
let fs = Fs::new();
let result = fs.rename("/nonexistent.txt", "/new.txt");
assert!(matches!(result, Err(FsError::NotFound)));
}
#[test]
fn error_new_parent_not_found() {
let fs = Fs::new();
let fd = fs.open_path("/file.txt").unwrap();
fs.close(fd).unwrap();
let result = fs.rename("/file.txt", "/nodir/file.txt");
assert!(matches!(result, Err(FsError::NotFound)));
}
#[test]
fn error_rename_root() {
let fs = Fs::new();
let result = fs.rename("/", "/newroot");
assert!(matches!(result, Err(FsError::InvalidArgument)));
}
#[test]
fn error_file_over_dir() {
let fs = Fs::new();
let fd = fs.open_path("/file.txt").unwrap();
fs.close(fd).unwrap();
fs.mkdir("/dir").unwrap();
let result = fs.rename("/file.txt", "/dir");
assert!(matches!(result, Err(FsError::IsADirectory)));
}
#[test]
fn error_dir_over_file() {
let fs = Fs::new();
fs.mkdir("/dir").unwrap();
let fd = fs.open_path("/file.txt").unwrap();
fs.close(fd).unwrap();
let result = fs.rename("/dir", "/file.txt");
assert!(matches!(result, Err(FsError::NotADirectory)));
}
#[test]
fn error_dir_over_nonempty_dir() {
let fs = Fs::new();
fs.mkdir("/src").unwrap();
fs.mkdir("/dst").unwrap();
let fd = fs.open_path("/dst/file.txt").unwrap();
fs.close(fd).unwrap();
let result = fs.rename("/src", "/dst");
assert!(matches!(result, Err(FsError::NotEmpty)));
}
}
#[cfg(test)]
mod readdir {
use super::*;
#[test]
fn success_empty_directory() {
let fs = Fs::new();
let entries = fs.readdir("/").unwrap();
assert_eq!(entries.len(), 0);
}
#[test]
fn success_with_files() {
let fs = Fs::new();
fs.mkdir("/dir1").unwrap();
fs.mkdir("/dir2").unwrap();
let fd1 = fs.open_path("/file1.txt").unwrap();
fs.close(fd1).unwrap();
let fd2 = fs.open_path("/file2.txt").unwrap();
fs.close(fd2).unwrap();
let mut entries = fs.readdir("/").unwrap();
entries.sort();
assert_eq!(entries.len(), 4);
assert_eq!(entries, vec!["dir1", "dir2", "file1.txt", "file2.txt"]);
}
#[test]
fn success_subdirectory() {
let fs = Fs::new();
fs.mkdir("/subdir").unwrap();
let fd1 = fs.open_path("/subdir/a.txt").unwrap();
fs.close(fd1).unwrap();
let fd2 = fs.open_path("/subdir/b.txt").unwrap();
fs.close(fd2).unwrap();
let mut entries = fs.readdir("/subdir").unwrap();
entries.sort();
assert_eq!(entries.len(), 2);
assert_eq!(entries, vec!["a.txt", "b.txt"]);
let root_entries = fs.readdir("/").unwrap();
assert_eq!(root_entries.len(), 1); }
#[test]
fn error_various_cases() {
let fs = Fs::new();
let result = fs.readdir("/nonexistent");
assert!(matches!(result, Err(FsError::NotFound)));
let fd = fs.open_path("/file.txt").unwrap();
fs.close(fd).unwrap();
let result = fs.readdir("/file.txt");
assert!(matches!(result, Err(FsError::NotADirectory)));
let result = fs.readdir("");
assert!(matches!(result, Err(FsError::InvalidArgument)));
}
}
#[cfg(test)]
mod ftruncate {
use super::*;
#[test]
fn success_shrink_file() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"0123456789").unwrap();
let metadata = fs.fstat(fd).unwrap();
assert_eq!(metadata.size, 10);
fs.ftruncate(fd, 5).unwrap();
let metadata = fs.fstat(fd).unwrap();
assert_eq!(metadata.size, 5);
fs.seek(fd, 0, 0).unwrap();
let mut buf = [0u8; 10];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(n, 5);
assert_eq!(&buf[..5], b"01234");
fs.close(fd).unwrap();
}
#[test]
fn success_expand_file() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"12345").unwrap();
fs.ftruncate(fd, 10).unwrap();
let metadata = fs.fstat(fd).unwrap();
assert_eq!(metadata.size, 10);
fs.seek(fd, 0, 0).unwrap();
let mut buf = [0xFFu8; 10];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(n, 10);
assert_eq!(&buf[..5], b"12345");
assert_eq!(&buf[5..10], &[0, 0, 0, 0, 0]);
fs.close(fd).unwrap();
}
#[test]
fn success_truncate_to_zero() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"some content").unwrap();
fs.ftruncate(fd, 0).unwrap();
let metadata = fs.fstat(fd).unwrap();
assert_eq!(metadata.size, 0);
let mut buf = [0u8; 10];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(n, 0);
fs.close(fd).unwrap();
}
#[test]
fn success_updates_mtime() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"content").unwrap();
let metadata_before = fs.fstat(fd).unwrap();
fs.ftruncate(fd, 3).unwrap();
let metadata_after = fs.fstat(fd).unwrap();
assert!(
metadata_after.modified > metadata_before.modified,
"ftruncate should update modified time"
);
fs.close(fd).unwrap();
}
#[test]
fn error_invalid_fd_and_permissions() {
let fs = Fs::new();
let result = fs.ftruncate(999, 100);
assert!(matches!(result, Err(FsError::BadFileDescriptor)));
let fd_create = fs.open_path("/test.txt").unwrap();
fs.write(fd_create, b"data").unwrap();
fs.close(fd_create).unwrap();
let fd = fs.open_path_with_flags("/test.txt", O_RDONLY).unwrap();
let result = fs.ftruncate(fd, 5);
assert!(matches!(result, Err(FsError::PermissionDenied)));
fs.close(fd).unwrap();
}
}
#[cfg(test)]
mod timestamps {
use super::*;
#[test]
fn success_file_creation() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
let metadata = fs.fstat(fd).unwrap();
assert_eq!(metadata.created, 1);
assert_eq!(metadata.modified, 1);
assert_eq!(metadata.created, metadata.modified);
fs.close(fd).unwrap();
}
#[test]
fn success_write_updates_mtime() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
let metadata1 = fs.fstat(fd).unwrap();
let initial_created = metadata1.created;
let initial_modified = metadata1.modified;
fs.write(fd, b"data").unwrap();
let metadata2 = fs.fstat(fd).unwrap();
assert_eq!(metadata2.created, initial_created);
assert!(metadata2.modified > initial_modified);
fs.close(fd).unwrap();
}
#[test]
fn success_mkdir_timestamps() {
let fs = Fs::new();
fs.mkdir("/testdir").unwrap();
let metadata = fs.stat("/testdir").unwrap();
assert_eq!(metadata.created, 1);
assert_eq!(metadata.modified, 1);
assert_eq!(metadata.created, metadata.modified);
}
#[test]
fn success_multiple_writes() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
let metadata1 = fs.fstat(fd).unwrap();
fs.write(fd, b"first").unwrap();
let metadata2 = fs.fstat(fd).unwrap();
assert!(metadata2.modified > metadata1.modified);
fs.write(fd, b"second").unwrap();
let metadata3 = fs.fstat(fd).unwrap();
assert!(metadata3.modified > metadata2.modified);
assert_eq!(metadata3.created, metadata1.created);
fs.close(fd).unwrap();
}
#[test]
fn success_read_does_not_update() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"data").unwrap();
let metadata1 = fs.fstat(fd).unwrap();
fs.seek(fd, 0, 0).unwrap();
let mut buf = [0u8; 10];
fs.read(fd, &mut buf).unwrap();
let metadata2 = fs.fstat(fd).unwrap();
assert_eq!(metadata2.modified, metadata1.modified);
assert_eq!(metadata2.created, metadata1.created);
fs.close(fd).unwrap();
}
#[test]
fn success_seek_does_not_update() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"data").unwrap();
let metadata1 = fs.fstat(fd).unwrap();
fs.seek(fd, 0, 0).unwrap();
let metadata2 = fs.fstat(fd).unwrap();
assert_eq!(metadata2.modified, metadata1.modified);
fs.close(fd).unwrap();
}
}
#[cfg(test)]
mod permissions {
use super::*;
#[test]
fn error_write_to_readonly() {
let fs = Fs::new();
let fd_create = fs.open_path("/readonly.txt").unwrap();
fs.write(fd_create, b"initial").unwrap();
fs.close(fd_create).unwrap();
let fd = fs.open_path_with_flags("/readonly.txt", O_RDONLY).unwrap();
let result = fs.write(fd, b"data");
assert!(matches!(result, Err(FsError::PermissionDenied)));
fs.close(fd).unwrap();
}
#[test]
fn error_read_from_writeonly() {
let fs = Fs::new();
let fd = fs
.open_path_with_flags("/writeonly.txt", O_WRONLY | O_CREAT)
.unwrap();
fs.write(fd, b"data").unwrap();
fs.seek(fd, 0, 0).unwrap();
let mut buf = [0u8; 10];
let result = fs.read(fd, &mut buf);
assert!(matches!(result, Err(FsError::PermissionDenied)));
fs.close(fd).unwrap();
}
}
#[cfg(test)]
mod error_handling {
use super::*;
#[test]
fn error_bad_file_descriptor() {
let fs = Fs::new();
let result = fs.read(999, &mut [0u8; 10]);
assert!(matches!(result, Err(FsError::BadFileDescriptor)));
let result = fs.write(999, b"data");
assert!(matches!(result, Err(FsError::BadFileDescriptor)));
let result = fs.seek(999, 0, 0);
assert!(matches!(result, Err(FsError::BadFileDescriptor)));
let result = fs.fstat(999);
assert!(matches!(result, Err(FsError::BadFileDescriptor)));
let result = fs.close(999);
assert!(matches!(result, Err(FsError::BadFileDescriptor)));
}
#[test]
fn error_invalid_arguments() {
let fs = Fs::new();
let result = fs.open_path("");
assert!(matches!(result, Err(FsError::InvalidArgument)));
let result = fs.mkdir("");
assert!(matches!(result, Err(FsError::InvalidArgument)));
let result = fs.stat("");
assert!(matches!(result, Err(FsError::InvalidArgument)));
}
#[test]
fn error_not_a_directory() {
let fs = Fs::new();
let fd = fs.open_path("/file.txt").unwrap();
fs.write(fd, b"content").unwrap();
fs.close(fd).unwrap();
let result = fs.open_path("/file.txt/nested");
assert!(matches!(result, Err(FsError::NotADirectory)));
let result = fs.mkdir("/file.txt/dir");
assert!(matches!(result, Err(FsError::NotADirectory)));
}
}
#[cfg(test)]
mod file_flags_append_trunc {
use super::*;
#[test]
fn success_append_mode() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"Hello").unwrap();
fs.close(fd).unwrap();
let fd = fs
.open_path_with_flags("/test.txt", O_WRONLY | O_APPEND)
.unwrap();
fs.write(fd, b" World").unwrap();
fs.close(fd).unwrap();
let fd = fs.open_path_with_flags("/test.txt", O_RDONLY).unwrap();
let mut buf = [0u8; 20];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"Hello World");
fs.close(fd).unwrap();
}
#[test]
fn success_append_multiple_writes() {
let fs = Fs::new();
let fd = fs
.open_path_with_flags("/test.txt", O_WRONLY | O_CREAT | O_APPEND)
.unwrap();
fs.write(fd, b"First").unwrap();
fs.write(fd, b"Second").unwrap();
fs.write(fd, b"Third").unwrap();
fs.close(fd).unwrap();
let fd = fs.open_path_with_flags("/test.txt", O_RDONLY).unwrap();
let mut buf = [0u8; 20];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"FirstSecondThird");
fs.close(fd).unwrap();
}
#[test]
fn success_append_after_seek() {
let fs = Fs::new();
let fd = fs
.open_path_with_flags("/test.txt", O_RDWR | O_CREAT | O_APPEND)
.unwrap();
fs.write(fd, b"Hello").unwrap();
fs.seek(fd, 0, 0).unwrap();
fs.write(fd, b" World").unwrap();
fs.seek(fd, 0, 0).unwrap();
let mut buf = [0u8; 20];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"Hello World");
fs.close(fd).unwrap();
}
#[test]
fn success_trunc_on_open() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"This will be truncated").unwrap();
fs.close(fd).unwrap();
let fd = fs
.open_path_with_flags("/test.txt", O_WRONLY | O_TRUNC)
.unwrap();
fs.close(fd).unwrap();
let fd = fs.open_path_with_flags("/test.txt", O_RDONLY).unwrap();
let mut buf = [0u8; 100];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(n, 0);
fs.close(fd).unwrap();
}
#[test]
fn success_trunc_then_write() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"Old content that is very long").unwrap();
fs.close(fd).unwrap();
let fd = fs
.open_path_with_flags("/test.txt", O_RDWR | O_TRUNC)
.unwrap();
fs.write(fd, b"New").unwrap();
fs.seek(fd, 0, 0).unwrap();
let mut buf = [0u8; 50];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"New");
fs.close(fd).unwrap();
}
#[test]
fn success_append_and_trunc_combined() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"Old content").unwrap();
fs.close(fd).unwrap();
let fd = fs
.open_path_with_flags("/test.txt", O_RDWR | O_TRUNC | O_APPEND)
.unwrap();
fs.write(fd, b"First").unwrap();
fs.write(fd, b"Second").unwrap();
fs.seek(fd, 0, 0).unwrap();
let mut buf = [0u8; 20];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"FirstSecond");
fs.close(fd).unwrap();
}
#[test]
fn success_trunc_new_file() {
let fs = Fs::new();
let fd = fs
.open_path_with_flags("/new.txt", O_RDWR | O_CREAT | O_TRUNC)
.unwrap();
fs.write(fd, b"Content").unwrap();
fs.seek(fd, 0, 0).unwrap();
let mut buf = [0u8; 10];
let n = fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf[..n], b"Content");
fs.close(fd).unwrap();
}
#[test]
fn error_trunc_with_rdonly() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
fs.write(fd, b"data").unwrap();
fs.close(fd).unwrap();
let result = fs.open_path_with_flags("/test.txt", O_RDONLY | O_TRUNC);
assert!(matches!(result, Err(FsError::InvalidArgument)));
}
}
#[cfg(test)]
mod open_at {
use super::*;
#[test]
fn success_open_at_basic() {
let fs = Fs::new();
fs.mkdir_p("/testdir").unwrap();
let fd1 = fs.open_path("/testdir/file1.txt").unwrap();
fs.write(fd1, b"Hello from file1").unwrap();
fs.close(fd1).unwrap();
fs.mkdir_p("/testdir/subdir").unwrap();
let _fd2 = fs.open_path("/testdir/subdir/file2.txt").unwrap();
fs.mkdir_p("/base").unwrap();
let base_fd = fs.open_path("/base/marker.txt").unwrap();
fs.write(base_fd, b"marker").unwrap();
fs.close(base_fd).unwrap();
}
#[test]
fn success_open_at_nested() {
let fs = Fs::new();
fs.mkdir_p("/parent/child").unwrap();
let fd = fs.open_path("/parent/child/test.txt").unwrap();
fs.write(fd, b"nested content").unwrap();
fs.close(fd).unwrap();
let fd2 = fs.open_path("/parent/child/test.txt").unwrap();
let mut buf = [0u8; 20];
let n = fs.read(fd2, &mut buf).unwrap();
assert_eq!(&buf[..n], b"nested content");
fs.close(fd2).unwrap();
}
#[test]
fn error_open_at_absolute_path() {
let fs = Fs::new();
fs.mkdir_p("/testdir").unwrap();
}
#[test]
fn error_open_at_not_directory() {
let fs = Fs::new();
let fd = fs.open_path("/regular.txt").unwrap();
fs.write(fd, b"content").unwrap();
let result = fs.open_at(fd, "subfile.txt", O_CREAT | O_RDWR);
assert!(matches!(result, Err(FsError::NotADirectory)));
fs.close(fd).unwrap();
}
#[test]
fn error_open_at_bad_fd() {
let fs = Fs::new();
let result = fs.open_at(999, "test.txt", O_CREAT | O_RDWR);
assert!(matches!(result, Err(FsError::BadFileDescriptor)));
}
#[test]
fn error_open_at_absolute_path_rejected() {
let fs = Fs::new();
let fd = fs.open_path("/test.txt").unwrap();
let result = fs.open_at(fd, "/absolute/path.txt", O_CREAT | O_RDWR);
assert!(matches!(result, Err(FsError::InvalidArgument)));
fs.close(fd).unwrap();
}
}
#[cfg(test)]
mod wasi_error_mapping {
use super::*;
#[test]
fn test_error_code_mapping() {
assert_eq!(FsError::NotFound.to_wasi_error_code(), 44); assert_eq!(FsError::NotADirectory.to_wasi_error_code(), 54); assert_eq!(FsError::IsADirectory.to_wasi_error_code(), 31); assert_eq!(FsError::InvalidArgument.to_wasi_error_code(), 28); assert_eq!(FsError::BadFileDescriptor.to_wasi_error_code(), 8); assert_eq!(FsError::PermissionDenied.to_wasi_error_code(), 2); assert_eq!(FsError::AlreadyExists.to_wasi_error_code(), 20); }
}