diff --git a/Cargo.lock b/Cargo.lock index 3c11aa8c63..1a97bc1e76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,22 @@ dependencies = [ "blake2", ] +[[package]] +name = "assert_cmd" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "base64ct" version = "1.8.0" @@ -112,6 +128,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -168,6 +195,12 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.10.7" @@ -179,6 +212,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "endian-num" version = "0.1.2" @@ -379,6 +418,33 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -446,6 +512,7 @@ version = "0.7.1" dependencies = [ "aes", "argon2", + "assert_cmd", "base64ct", "endian-num", "env_logger", @@ -555,6 +622,12 @@ dependencies = [ "redox_termios", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "time" version = "0.3.41" @@ -607,6 +680,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 91415b1ee5..6201d3b5fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ std = [ "time", "uuid/v4", "redox_syscall/std", - "redox-scheme" + "redox-scheme", ] [target.'cfg(not(target_os = "redox"))'.dependencies] @@ -72,3 +72,6 @@ time = { version = "0.3", optional = true } [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } + +[dev-dependencies] +assert_cmd = "2.0.17" diff --git a/src/bin/mount.rs b/src/bin/mount.rs index 6da747bd0a..ede7a3c766 100644 --- a/src/bin/mount.rs +++ b/src/bin/mount.rs @@ -128,7 +128,7 @@ fn print_usage_exit() -> ! { } fn usage() { - println!("redoxfs --no-daemon [-d] [--uuid] [disk or uuid] [mountpoint] [block in hex]"); + println!("redoxfs [--no-daemon|-d] [--uuid] [disk or uuid] [mountpoint] [block in hex]"); } enum DiskId { diff --git a/src/dir.rs b/src/dir.rs index b5be384226..b8e8b095b2 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -126,7 +126,7 @@ impl DirList { self.count == 0 } - pub fn entries(&self) -> DirEntryIterator { + pub fn entries(&self) -> DirEntryIterator<'_> { DirEntryIterator { dir_list: self, emit_count: 0, diff --git a/src/mount/redox/mod.rs b/src/mount/redox/mod.rs index d165c05660..d72997d8f1 100644 --- a/src/mount/redox/mod.rs +++ b/src/mount/redox/mod.rs @@ -10,6 +10,9 @@ use self::scheme::FileScheme; pub mod resource; pub mod scheme; +//FIXME: mut callback is not mut +#[allow(unused_mut)] + pub fn mount(filesystem: FileSystem, mountpoint: P, mut callback: F) -> io::Result where D: Disk, diff --git a/src/mount/redox/resource.rs b/src/mount/redox/resource.rs index fc205cc1ed..75096aabb8 100644 --- a/src/mount/redox/resource.rs +++ b/src/mount/redox/resource.rs @@ -518,8 +518,8 @@ impl Resource for FileResource { if let Some(mut fmap) = v_opt { fmap.rc += 1; fmap.flags |= flags; - - fmap_info + //FIXME: Use result? + let _ = fmap_info .ranges .insert(range.start, range.end - range.start, fmap); } else { @@ -533,7 +533,8 @@ impl Resource for FileResource { tx, )? }; - fmap_info.ranges.insert(offset, aligned_size as u64, map); + //FIXME: Use result? + let _ = fmap_info.ranges.insert(offset, aligned_size as u64, map); } } //dbg!(&self.fmaps); @@ -572,7 +573,8 @@ impl Resource for FileResource { } if fmap.rc > 0 { - fmap_info + //FIXME: Use result? + let _ = fmap_info .ranges .insert(range.start, range.end - range.start, fmap); } diff --git a/src/mount/redox/scheme.rs b/src/mount/redox/scheme.rs index 4d9db062b6..c7f0a976b3 100644 --- a/src/mount/redox/scheme.rs +++ b/src/mount/redox/scheme.rs @@ -652,7 +652,7 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> { let scheme_name = &self.name; self.fs.tx(|tx| { - let orig_parent_ptr = match file.parent_ptr_opt() { + let _orig_parent_ptr = match file.parent_ptr_opt() { Some(some) => some, None => { // println!("orig is root"); @@ -1043,7 +1043,7 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> { id: usize, payload: &mut [u8], metadata: &[u64], - ctx: &CallerCtx, + _ctx: &CallerCtx, ) -> Result { let Some(verb) = FsCall::try_from_raw(metadata[0] as usize) else { return Err(Error::new(EINVAL)); diff --git a/src/tests.rs b/src/tests.rs index 2000d70ae6..2640038a41 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,17 +1,11 @@ use crate::htree::{HTreeHash, HTreeNode, HTreePtr, HTREE_IDX_ENTRIES}; use crate::{ - unmount_path, BlockAddr, BlockData, BlockLevel, BlockPtr, DirEntry, DirList, DiskMemory, - DiskSparse, FileSystem, Node, TreePtr, ALLOC_GC_THRESHOLD, BLOCK_SIZE, + BlockAddr, BlockData, BlockLevel, BlockPtr, DirEntry, DirList, DiskMemory, DiskSparse, + FileSystem, Node, TreePtr, ALLOC_GC_THRESHOLD, BLOCK_SIZE, }; -use core::panic::AssertUnwindSafe; -use std::panic::catch_unwind; -use std::path::Path; -use std::process::Command; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering::Relaxed; -use std::thread::sleep; -use std::time::Duration; -use std::{fs, thread, time}; +use std::{fs, time}; static IMAGE_SEQ: AtomicUsize = AtomicUsize::new(0); @@ -36,226 +30,6 @@ where res } -fn with_mounted(callback: F) -> T -where - T: Send + Sync + 'static, - F: FnOnce(&Path) -> T + Send + Sync + 'static, -{ - let mount_path_o = format!("image{}", IMAGE_SEQ.fetch_add(1, Relaxed)); - let mount_path = mount_path_o.clone(); - - let res = with_redoxfs(move |fs| { - if cfg!(not(target_os = "redox")) { - if !Path::new(&mount_path).exists() { - dbg!(fs::create_dir(dbg!(&mount_path))).unwrap(); - } - } - let join_handle = crate::mount(fs, dbg!(mount_path), move |real_path| { - let real_path = real_path.to_owned(); - thread::spawn(move || { - let res = catch_unwind(AssertUnwindSafe(|| callback(&real_path))); - - let real_path = real_path.to_str().unwrap(); - - if cfg!(target_os = "redox") { - dbg!(fs::remove_file(dbg!(format!(":{}", real_path)))).unwrap(); - } else { - if !dbg!(Command::new("sync").status()).unwrap().success() { - panic!("sync failed"); - } - - if !unmount_path(real_path).is_ok() { - // There seems to be a race condition where the device can be busy when trying to unmount. - // So, we pause for a moment and retry. There will still be an error output to the logs - // for the first failed attempt. - sleep(Duration::from_millis(200)); - if !unmount_path(real_path).is_ok() { - panic!("umount failed"); - } - } - } - - res.unwrap() - }) - }) - .unwrap(); - - join_handle.join().unwrap() - }); - - if cfg!(not(target_os = "redox")) { - dbg!(fs::remove_dir(dbg!(mount_path_o))).unwrap(); - } - - res -} - -#[test] -fn simple() { - with_mounted(|path| { - dbg!(fs::create_dir(&path.join("test"))).unwrap(); - }) -} - -#[test] -fn create_and_remove_file() { - with_mounted(|path| { - let file_name = "test_file.txt"; - let file_path = path.join(file_name); - - // Create the file - fs::write(&file_path, "Hello, world!").unwrap(); - assert!(fs::exists(&file_path).unwrap()); - - // Read the file - let contents = fs::read_to_string(&file_path).unwrap(); - assert_eq!(contents, "Hello, world!"); - - // Remove the file - fs::remove_file(&file_path).unwrap(); - assert!(!fs::exists(&file_path).unwrap()); - }); -} - -#[test] -fn create_and_remove_directory() { - with_mounted(|path| { - let dir_name = "test_dir"; - let dir_path = path.join(dir_name); - - // Create the directory - fs::create_dir(&dir_path).unwrap(); - assert!(fs::exists(&dir_path).unwrap()); - - // Check that the directory is empty - let entries: Vec<_> = fs::read_dir(&dir_path) - .unwrap() - .map(|e| e.unwrap().file_name()) - .collect(); - assert!(entries.is_empty()); - - // Add a file to the directory - let file_name = "test_file.txt"; - let file_path = dir_path.join(file_name); - fs::write(&file_path, "Hello, world!").unwrap(); - - // Check that the dir cannot be removed when not empty - let error = fs::remove_dir(&dir_path); - assert!(error.is_err()); - assert_eq!( - error.unwrap_err().kind(), - std::io::ErrorKind::DirectoryNotEmpty - ); - - // Remove the file - fs::remove_file(&file_path).unwrap(); - - // Remove the directory - fs::remove_dir(&dir_path).unwrap(); - assert!(!fs::exists(&dir_path).unwrap()); - }); -} - -#[test] -fn create_and_remove_symlink() { - with_mounted(|path| { - let real_file = "real_file.txt"; - let real_path = path.join(real_file); - let symlink_file = "symlink_to_real_file.txt"; - let symlink_path = path.join(symlink_file); - - // Create the real file - fs::write(&real_path, "Hello, world!").unwrap(); - - // Create the symmlink according to the platform - #[cfg(unix)] - std::os::unix::fs::symlink(&real_file, &symlink_path).unwrap(); - - #[cfg(windows)] - std::os::windows::fs::symlink_file(&real_file, &symlink_path).unwrap(); - - // Check that the symlink exists and points to the correct target - let exists = fs::exists(&symlink_path); - assert!( - exists.is_ok() && exists.unwrap(), - "Symlink should exist but was: {:?}", - fs::exists(&symlink_path) - ); - let symlink_metadata = fs::symlink_metadata(&symlink_path).unwrap(); - assert!(symlink_metadata.file_type().is_symlink()); - let target = fs::read_link(&symlink_path).unwrap(); - assert_eq!(target.to_str().unwrap(), real_file); - assert_eq!(fs::read(&symlink_path).unwrap(), b"Hello, world!"); - - // Confirm the symlink cannot be removed as a directory - let error = fs::remove_dir(&symlink_path); - assert!(error.is_err()); - assert_eq!(error.unwrap_err().kind(), std::io::ErrorKind::NotADirectory); - - // Remove the symlink - fs::remove_file(&symlink_path).unwrap(); - assert!(!fs::exists(&symlink_path).unwrap()); - }); -} - -#[cfg(target_os = "redox")] -#[test] -fn mmap() { - use syscall; - - //TODO - with_mounted(|path| { - use std::slice; - - let path = dbg!(path.join("test")); - - let mmap_inner = |write: bool| { - let fd = dbg!(libredox::call::open( - path.to_str().unwrap(), - libredox::flag::O_CREAT | libredox::flag::O_RDWR | libredox::flag::O_CLOEXEC, - 0, - )) - .unwrap(); - - let map = unsafe { - slice::from_raw_parts_mut( - dbg!(libredox::call::mmap(libredox::call::MmapArgs { - fd, - offset: 0, - length: 128, - prot: libredox::flag::PROT_READ | libredox::flag::PROT_WRITE, - flags: libredox::flag::MAP_SHARED, - addr: core::ptr::null_mut(), - })) - .unwrap() as *mut u8, - 128, - ) - }; - - // Maps should be available after closing - assert_eq!(dbg!(libredox::call::close(fd)), Ok(())); - - for i in 0..128 { - if write { - map[i as usize] = i; - } - assert_eq!(map[i as usize], i); - } - - //TODO: add msync - unsafe { - assert_eq!( - dbg!(libredox::call::munmap(map.as_mut_ptr().cast(), map.len())), - Ok(()) - ); - } - }; - - mmap_inner(true); - mmap_inner(false); - }) -} - #[test] fn many_create_remove_should_not_increase_size() { with_redoxfs(|mut fs| { @@ -466,35 +240,6 @@ fn many_create_write_list_find_read_delete() { } } -#[test] -fn many_write_read_delete_mounted() { - with_mounted(|path| { - let total_count = 500; - - for i in 0..total_count { - fs::write( - &path.join(format!("file{}", i)), - format!("Hello, number {i}!"), - ) - .unwrap(); - } - - // Confirm each of the created files can be found and read - for i in 0..total_count { - let contents = fs::read_to_string(&path.join(format!("file{}", i))).unwrap(); - assert_eq!(contents, format!("Hello, number {i}!")); - } - - // Remove all the files - for i in 0..total_count { - let file_path = path.join(format!("file{i}")); - assert!(fs::exists(&file_path).unwrap()); - fs::remove_file(&file_path).unwrap(); - assert!(!fs::exists(&file_path).unwrap()); - } - }); -} - // // MARK: H-Tree tests // diff --git a/src/unmount.rs b/src/unmount.rs index 368fd5910f..2b07d68f84 100644 --- a/src/unmount.rs +++ b/src/unmount.rs @@ -33,7 +33,7 @@ fn unmount_linux_path(mount_path: &str) -> io::Result { pub fn unmount_path(mount_path: &str) -> Result<(), io::Error> { if cfg!(target_os = "redox") { - fs::remove_file(format!(":{}", mount_path))? + fs::remove_file(format!("{}", mount_path))? } else { let status_res = if cfg!(target_os = "linux") { unmount_linux_path(mount_path) diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 0000000000..2cb3ae279e --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,392 @@ +use assert_cmd::cargo::CommandCargoExt; + +use core::panic::AssertUnwindSafe; +use redoxfs::{unmount_path, DirEntry, DiskMemory, DiskSparse, FileSystem, Node, TreePtr}; + +use std::panic::catch_unwind; +use std::path::Path; +use std::process::Command; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering::Relaxed; +use std::thread::sleep; +use std::time::Duration; +use std::{fs, time}; + +static IMAGE_SEQ: AtomicUsize = AtomicUsize::new(0); + +fn with_redoxfs(callback: F) -> T +where + T: Send + Sync + 'static, + F: FnOnce(&str) -> T + Send + Sync + 'static, +{ + let disk_path = format!("image{}.bin", IMAGE_SEQ.fetch_add(1, Relaxed)); + + { + let disk = DiskSparse::create(dbg!(&disk_path), 1024 * 1024 * 1024).unwrap(); + let ctime = dbg!(time::SystemTime::now().duration_since(time::UNIX_EPOCH)).unwrap(); + FileSystem::create(disk, None, ctime.as_secs(), ctime.subsec_nanos()).unwrap(); + } + let res = callback(&disk_path); + + dbg!(fs::remove_file(dbg!(disk_path))).unwrap(); + + res +} + +fn with_mounted(callback: F) -> T +where + T: Send + Sync + 'static, + F: FnOnce(&Path) -> T + Send + Sync + 'static, +{ + let mount_path_o = format!("image{}", IMAGE_SEQ.fetch_add(1, Relaxed)); + let mount_path = mount_path_o.clone(); + + let res = with_redoxfs(move |fs| { + // At redox, we mount on /scheme/ path, no need an empty dir + if cfg!(not(target_os = "redox")) { + if !Path::new(&mount_path).exists() { + dbg!(fs::create_dir(dbg!(&mount_path))).unwrap(); + } + } + let mut mount_cmd = Command::cargo_bin("redoxfs").expect("unable to find mount bin"); + mount_cmd.arg("-d").arg(dbg!(&fs)).arg(dbg!(&mount_path)); + let mut child = mount_cmd.spawn().expect("mount failed to run"); + + sleep(Duration::from_millis(200)); + + let real_path = Path::new(&mount_path); + let res = catch_unwind(AssertUnwindSafe(|| callback(real_path))); + + sleep(Duration::from_millis(200)); + + child.kill().expect("Can't kill"); + + if cfg!(target_os = "redox") { + unmount_path(&mount_path).unwrap(); + } else { + if !dbg!(Command::new("sync").status()).unwrap().success() { + panic!("sync failed"); + } + + if !unmount_path(&mount_path).is_ok() { + // There seems to be a race condition where the device can be busy when trying to unmount. + // So, we pause for a moment and retry. There will still be an error output to the logs + // for the first failed attempt. + sleep(Duration::from_millis(200)); + if !unmount_path(&mount_path).is_ok() { + panic!("umount failed"); + } + } + } + + res.expect("Test failed") + }); + + if cfg!(not(target_os = "redox")) { + dbg!(fs::remove_dir(dbg!(mount_path_o))).unwrap(); + } + + res +} + +#[test] +fn simple() { + with_mounted(|path| { + dbg!(fs::create_dir(&path.join("test"))).unwrap(); + }) +} + +#[test] +fn create_and_remove_file() { + with_mounted(|path| { + let file_name = "test_file.txt"; + let file_path = path.join(file_name); + + // Create the file + fs::write(&file_path, "Hello, world!").unwrap(); + assert!(fs::exists(&file_path).unwrap()); + + // Read the file + let contents = fs::read_to_string(&file_path).unwrap(); + assert_eq!(contents, "Hello, world!"); + + // Remove the file + fs::remove_file(&file_path).unwrap(); + assert!(!fs::exists(&file_path).unwrap()); + }); +} + +#[test] +fn create_and_remove_directory() { + with_mounted(|path| { + let dir_name = "test_dir"; + let dir_path = path.join(dir_name); + + // Create the directory + fs::create_dir(&dir_path).unwrap(); + assert!(fs::exists(&dir_path).unwrap()); + + // Check that the directory is empty + let entries: Vec<_> = fs::read_dir(&dir_path) + .unwrap() + .map(|e| e.unwrap().file_name()) + .collect(); + assert!(entries.is_empty()); + + // Add a file to the directory + let file_name = "test_file.txt"; + let file_path = dir_path.join(file_name); + fs::write(&file_path, "Hello, world!").unwrap(); + + // Check that the dir cannot be removed when not empty + let error = fs::remove_dir(&dir_path); + assert!(error.is_err()); + assert_eq!( + error.unwrap_err().kind(), + std::io::ErrorKind::DirectoryNotEmpty + ); + + // Remove the file + fs::remove_file(&file_path).unwrap(); + + // Remove the directory + fs::remove_dir(&dir_path).unwrap(); + assert!(!fs::exists(&dir_path).unwrap()); + }); +} + +#[test] +fn create_and_remove_symlink() { + with_mounted(|path| { + let real_file = "real_file.txt"; + let real_path = path.join(real_file); + let symlink_file = "symlink_to_real_file.txt"; + let symlink_path = path.join(symlink_file); + + // Create the real file + fs::write(&real_path, "Hello, world!").unwrap(); + + // Create the symmlink according to the platform + #[cfg(unix)] + std::os::unix::fs::symlink(&real_file, &symlink_path).unwrap(); + + #[cfg(windows)] + std::os::windows::fs::symlink_file(&real_file, &symlink_path).unwrap(); + + // Check that the symlink exists and points to the correct target + let exists = fs::exists(&symlink_path); + assert!( + exists.is_ok() && exists.unwrap(), + "Symlink should exist but was: {:?}", + fs::exists(&symlink_path) + ); + let symlink_metadata = fs::symlink_metadata(&symlink_path).unwrap(); + assert!(symlink_metadata.file_type().is_symlink()); + let target = fs::read_link(&symlink_path).unwrap(); + assert_eq!(target.to_str().unwrap(), real_file); + assert_eq!(fs::read(&symlink_path).unwrap(), b"Hello, world!"); + + // Confirm the symlink cannot be removed as a directory + let error = fs::remove_dir(&symlink_path); + assert!(error.is_err()); + assert_eq!(error.unwrap_err().kind(), std::io::ErrorKind::NotADirectory); + + // Remove the symlink + fs::remove_file(&symlink_path).unwrap(); + assert!(!fs::exists(&symlink_path).unwrap()); + }); +} + +#[cfg(target_os = "redox")] +#[test] +fn mmap() { + //TODO + with_mounted(|path| { + use std::slice; + + let path = dbg!(path.join("test")); + + let mmap_inner = |write: bool| { + let fd = dbg!(libredox::call::open( + path.to_str().unwrap(), + libredox::flag::O_CREAT | libredox::flag::O_RDWR | libredox::flag::O_CLOEXEC, + 0, + )) + .unwrap(); + + let map = unsafe { + slice::from_raw_parts_mut( + dbg!(libredox::call::mmap(libredox::call::MmapArgs { + fd, + offset: 0, + length: 128, + prot: libredox::flag::PROT_READ | libredox::flag::PROT_WRITE, + flags: libredox::flag::MAP_SHARED, + addr: core::ptr::null_mut(), + })) + .unwrap() as *mut u8, + 128, + ) + }; + + // Maps should be available after closing + assert_eq!(dbg!(libredox::call::close(fd)), Ok(())); + + for i in 0..128 { + if write { + map[i as usize] = i; + } + assert_eq!(map[i as usize], i); + } + + //TODO: add msync + unsafe { + assert_eq!( + dbg!(libredox::call::munmap(map.as_mut_ptr().cast(), map.len())), + Ok(()) + ); + } + }; + + mmap_inner(true); + mmap_inner(false); + }) +} + +// TODO: When increasing the total_count to 8000, the Allocator's deallocate() function surfaces as "slow" according to flamegraph. This +// appears to be the result of bulk deleting in this test, but I would bet that any filesystem that has lived for a long time would +// start to see degraded performance due to this. +#[test] +fn many_create_write_list_find_read_delete() { + let disk = DiskMemory::new(1024 * 1024 * 1024); + let ctime = time::SystemTime::now() + .duration_since(time::UNIX_EPOCH) + .unwrap(); + let mut fs = FileSystem::create(disk, None, ctime.as_secs(), ctime.subsec_nanos()).unwrap(); + let tree_ptr = TreePtr::::root(); + let total_count = 3000; + + // Create a bunch of files + for i in 0..total_count { + let result = fs.tx(|tx| { + tx.create_node( + tree_ptr, + &format!("file{i:05}"), + Node::MODE_FILE | 0644, + 1, + 0, + ) + }); + if result.is_err() { + println!("Failure on create iteration {i}"); + } + + let file_node = result.unwrap(); + let result = fs.tx(|tx| { + tx.write_node( + file_node.ptr(), + 0, + format!("Hello World! #{i}").as_bytes(), + ctime.as_secs(), + ctime.subsec_nanos(), + ) + }); + if result.is_err() { + println!("Failure on write iteration {i}"); + } + assert!(result.unwrap() > 0) + } + + // Confirm that they can be listed + { + let mut children = Vec::::with_capacity(total_count); + let _ = fs.tx(|tx| tx.child_nodes(tree_ptr, &mut children)).unwrap(); + assert_eq!( + children.len(), + total_count, + "The list of children should match the number of files created." + ); + let mut children: Vec = children + .iter() + .map(|entry| entry.name().unwrap_or_default().to_string()) + .collect(); + children.sort(); + + for i in 0..total_count { + let expected = format!("file{i:05}"); + let idx = children.binary_search(&expected); + assert!(idx.is_ok(), "Children did not contain '{}'", expected); + } + } + + // Find and read the files + for i in 0..total_count { + let result = fs.tx(|tx| tx.find_node(tree_ptr, &format!("file{i:05}"))); + if result.is_err() { + println!("Failure on find node iteration {i}"); + } + + let file_node = result.unwrap(); + let offset = 0; + let mut buf = [0_u8; 32]; + let result = fs.tx(|tx| { + tx.read_node( + file_node.ptr(), + offset, + &mut buf, + ctime.as_secs(), + ctime.subsec_nanos(), + ) + }); + if result.is_err() { + println!("Failure on read iteration {i}"); + } + let size = result.unwrap(); + let body = std::str::from_utf8(&buf[..size]).unwrap(); + assert_eq!(body, format!("Hello World! #{i}")); + } + + // Delete all the files + for i in 0..total_count { + let file_name = format!("file{i:05}"); + let result = fs.tx(|tx| tx.remove_node(tree_ptr, &file_name, Node::MODE_FILE)); + if result.is_err() { + println!("Failure on delete iteration {i}"); + result.unwrap(); + } + let result = fs.tx(|tx| tx.find_node(tree_ptr, &file_name)); + if !result.is_err() || result.err().unwrap().errno != syscall::error::ENOENT { + println!("Failure on delete verification iteration {i}"); + assert!(false, "Deletion appears to ahve failred"); + } + } +} + +#[test] +fn many_write_read_delete_mounted() { + with_mounted(|path| { + let total_count = 500; + + for i in 0..total_count { + fs::write( + &path.join(format!("file{}", i)), + format!("Hello, number {i}!"), + ) + .unwrap(); + } + + // Confirm each of the created files can be found and read + for i in 0..total_count { + let contents = fs::read_to_string(&path.join(format!("file{}", i))).unwrap(); + assert_eq!(contents, format!("Hello, number {i}!")); + } + + // Remove all the files + for i in 0..total_count { + let file_path = path.join(format!("file{i}")); + assert!(fs::exists(&file_path).unwrap()); + fs::remove_file(&file_path).unwrap(); + assert!(!fs::exists(&file_path).unwrap()); + } + }); +}