Move tests with mount as integration tests
This commit is contained in:
Generated
+82
@@ -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"
|
||||
|
||||
+4
-1
@@ -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"
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
@@ -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<D, P, T, F>(filesystem: FileSystem<D>, mountpoint: P, mut callback: F) -> io::Result<T>
|
||||
where
|
||||
D: Disk,
|
||||
|
||||
@@ -518,8 +518,8 @@ impl<D: Disk> Resource<D> 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<D: Disk> Resource<D> 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<D: Disk> Resource<D> for FileResource {
|
||||
}
|
||||
|
||||
if fmap.rc > 0 {
|
||||
fmap_info
|
||||
//FIXME: Use result?
|
||||
let _ = fmap_info
|
||||
.ranges
|
||||
.insert(range.start, range.end - range.start, fmap);
|
||||
}
|
||||
|
||||
@@ -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<usize> {
|
||||
let Some(verb) = FsCall::try_from_raw(metadata[0] as usize) else {
|
||||
return Err(Error::new(EINVAL));
|
||||
|
||||
+3
-258
@@ -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<T, F>(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
|
||||
//
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ fn unmount_linux_path(mount_path: &str) -> io::Result<ExitStatus> {
|
||||
|
||||
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)
|
||||
|
||||
+392
@@ -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<T, F>(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<T, F>(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::<Node>::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::<DirEntry>::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<String> = 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());
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user