640 lines
21 KiB
Rust
640 lines
21 KiB
Rust
use core::panic::AssertUnwindSafe;
|
|
use redoxfs::{unmount_path, DirEntry, DiskMemory, DiskSparse, FileSystem, Node, TreePtr};
|
|
|
|
use std::io::{Read, Seek, SeekFrom, Write};
|
|
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::{env, 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();
|
|
}
|
|
} else {
|
|
//FIXME: cargo_bin is broken when cross compiling. This is redoxer specific workaround
|
|
env::set_var(
|
|
"CARGO_BIN_EXE_redoxfs",
|
|
"/root/target/x86_64-unknown-redox/debug/redoxfs",
|
|
);
|
|
}
|
|
let mut mount_cmd = Command::new(assert_cmd::cargo_bin!("redoxfs"));
|
|
mount_cmd.arg("-d").arg(dbg!(&fs)).arg(dbg!(&mount_path));
|
|
let mut child = mount_cmd.spawn().expect("mount failed to run");
|
|
|
|
let real_path = if cfg!(target_os = "redox") {
|
|
let real_path = dbg!(Path::new("/scheme").join(&mount_path));
|
|
let mut tries = 0;
|
|
loop {
|
|
if real_path.exists() {
|
|
break;
|
|
}
|
|
tries += 1;
|
|
if tries == 10 {
|
|
panic!("Fail to wait for mount")
|
|
}
|
|
println!("{tries}");
|
|
sleep(Duration::from_millis(500));
|
|
}
|
|
real_path
|
|
} else {
|
|
sleep(Duration::from_millis(200));
|
|
let r = Path::new(".").join(&mount_path);
|
|
r
|
|
};
|
|
|
|
let res = catch_unwind(AssertUnwindSafe(|| callback(&real_path)));
|
|
|
|
sleep(Duration::from_millis(200));
|
|
|
|
child.kill().expect("Can't kill");
|
|
let _ = child.wait();
|
|
|
|
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_err() {
|
|
// 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_err() {
|
|
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_or_else(|_| panic!("cannot create dir {}", &dir_path.display()));
|
|
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 | 0o644,
|
|
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);
|
|
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}");
|
|
if let Err(e) = fs.tx(|tx| tx.remove_node(tree_ptr, &file_name, Node::MODE_FILE)) {
|
|
println!("Failure on delete iteration {i}");
|
|
panic!("{e}");
|
|
}
|
|
|
|
let result = fs.tx(|tx| tx.find_node(tree_ptr, &file_name));
|
|
if result.is_ok() || result.unwrap_err().errno != syscall::error::ENOENT {
|
|
println!("Failure on delete verification iteration {i}");
|
|
panic!("Deletion appears to have failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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());
|
|
}
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn rename_no_replace() {
|
|
let disk = DiskMemory::new(1024 * 1024 * 1024);
|
|
let mut fs = FileSystem::create(disk, None, 0, 0)
|
|
.expect("Creating in memory file system should succeed");
|
|
|
|
let root = TreePtr::root();
|
|
let dir = fs
|
|
.tx(|tx| tx.create_node(root, "dir", Node::MODE_DIR, 0, 0))
|
|
.expect("Creating a directory should succeed");
|
|
let source_file = fs
|
|
.tx(|tx| tx.create_node(root, "source", Node::MODE_FILE, 0, 0))
|
|
.expect("Creating source file to copy should succeed");
|
|
let no_clobber_file = fs
|
|
.tx(|tx| tx.create_node(root, "no_clobber", Node::MODE_FILE, 0, 0))
|
|
.expect("Creating second file to not clobber should succeed");
|
|
|
|
// Rename /source to /target
|
|
fs.tx(|tx| tx.rename_node_no_replace(root, "source", root, "target"))
|
|
.expect("Renaming existing 'source' to non-existing 'target' should succeed");
|
|
let target_file = fs
|
|
.tx(|tx| tx.find_node(root, "target"))
|
|
.expect("'target' should exist because we just renamed 'source' to 'target'");
|
|
assert_eq!(
|
|
source_file.id(),
|
|
target_file.id(),
|
|
"source and target are most definitely the same file"
|
|
);
|
|
|
|
// Don't rename /target to /no_clobber
|
|
let err = fs
|
|
.tx(|tx| tx.rename_node_no_replace(root, "target", root, "no_clobber"))
|
|
.expect_err("Renaming 'target' to existing 'no_clobber' should fail");
|
|
assert_eq!(
|
|
syscall::EEXIST,
|
|
err.errno,
|
|
"Renaming to existing file should fail with EEXIST"
|
|
);
|
|
assert_ne!(
|
|
no_clobber_file.id(),
|
|
target_file.id(),
|
|
"'target' and 'no_clobber' should be distinct files"
|
|
);
|
|
|
|
// Don't rename /target to /dir
|
|
let err = fs
|
|
.tx(|tx| tx.rename_node_no_replace(root, "target", root, "dir"))
|
|
.expect_err("Renaming 'target' to existing directory 'dir' should fail");
|
|
assert_eq!(
|
|
syscall::EEXIST,
|
|
err.errno,
|
|
"Renaming to existing file should fail with EEXIST"
|
|
);
|
|
assert_ne!(
|
|
dir.id(),
|
|
target_file.id(),
|
|
"'target' and 'dir' should be distinct nodes"
|
|
);
|
|
|
|
// Don't rename /dir to /target
|
|
let err = fs
|
|
.tx(|tx| tx.rename_node_no_replace(root, "dir", root, "target"))
|
|
.expect_err("Renaming 'dir' to existing file 'target' should fail");
|
|
assert_eq!(
|
|
syscall::EEXIST,
|
|
err.errno,
|
|
"Renaming to existing file should fail with EEXIST"
|
|
);
|
|
assert_ne!(
|
|
target_file.id(),
|
|
dir.id(),
|
|
"'dir' and 'target' should be distinct nodes"
|
|
);
|
|
|
|
// Don't rename /target to /target
|
|
let err = fs
|
|
.tx(|tx| tx.rename_node_no_replace(root, "target", root, "target"))
|
|
.expect_err("Renaming 'target' to itself should fail");
|
|
assert_eq!(
|
|
syscall::EEXIST,
|
|
err.errno,
|
|
"Renaming file to itself should fail with EEXIST"
|
|
);
|
|
|
|
// Rename /target to /dir/target
|
|
fs.tx(|tx| tx.rename_node_no_replace(root, "target", dir.ptr(), "target"))
|
|
.expect("Renaming /target to /dir/target should succeed");
|
|
let moved_target = fs
|
|
.tx(|tx| tx.find_node(dir.ptr(), "target"))
|
|
.expect("'target' should have moved to /dir/target");
|
|
assert_eq!(target_file.id(), moved_target.id());
|
|
|
|
// Rename /dir to /newdir
|
|
fs.tx(|tx| tx.rename_node_no_replace(root, "dir", root, "newdir"))
|
|
.expect("Renaming 'dir' to 'newdir' should succeed");
|
|
}
|
|
|
|
#[test]
|
|
fn rename_works() {
|
|
let disk = DiskMemory::new(1024 * 1024 * 1024);
|
|
let mut fs = FileSystem::create(disk, None, 0, 0)
|
|
.expect("Creating in memory file system should succeed");
|
|
|
|
let root = TreePtr::root();
|
|
let dir = fs
|
|
.tx(|tx| tx.create_node(root, "dir", Node::MODE_DIR, 0, 0))
|
|
.expect("Creating a directory should succeed");
|
|
let source_file = fs
|
|
.tx(|tx| tx.create_node(root, "source", Node::MODE_FILE, 0, 0))
|
|
.expect("Creating source file should succeed");
|
|
let target_file_orig = fs
|
|
.tx(|tx| tx.create_node(root, "target", Node::MODE_FILE, 0, 0))
|
|
.expect("Creating target file should succeed");
|
|
|
|
// Rename /source to /source2
|
|
fs.tx(|tx| tx.rename_node(root, "source", root, "source2"))
|
|
.expect("Renaming existing 'source' to non-existing 'source2' should succeed");
|
|
let source2_file = fs
|
|
.tx(|tx| tx.find_node(root, "source2"))
|
|
.expect("'source2' should exist because we just renamed 'source' to 'source2'");
|
|
assert_eq!(source_file.id(), source2_file.id());
|
|
let err = fs
|
|
.tx(|tx| tx.find_node(root, "source"))
|
|
.expect_err("'source' should not exist because it was moved");
|
|
assert_eq!(syscall::ENOENT, err.errno);
|
|
|
|
// Rename /source2 to /target
|
|
fs.tx(|tx| tx.rename_node(root, "source2", root, "target"))
|
|
.expect("Renaming existing 'source2' to existing 'target' should succeed");
|
|
let target_file_mv = fs
|
|
.tx(|tx| tx.find_node(root, "target"))
|
|
.expect("'target' should exist because the rename succeeded");
|
|
assert_ne!(
|
|
target_file_orig.id(),
|
|
target_file_mv.id(),
|
|
"Move failed because 'target' is still the same"
|
|
);
|
|
assert_eq!(
|
|
source2_file.id(),
|
|
target_file_mv.id(),
|
|
"Move failed because 'source2' != 'target'"
|
|
);
|
|
|
|
// Don't rename /target to /dir
|
|
// XXX: A similar test fails on Linux using rename(). Not sure if the discrepancy matters.
|
|
// let err = fs
|
|
// .tx(|tx| tx.rename_node(root, "target", root, "dir"))
|
|
// .expect_err("Renaming 'target' to existing directory 'dir' should fail");
|
|
// assert_eq!(
|
|
// syscall::EEXIST,
|
|
// err.errno,
|
|
// "Renaming to existing file should fail with EEXIST"
|
|
// );
|
|
// assert_ne!(
|
|
// dir.id(),
|
|
// target_file_mv.id(),
|
|
// "'target' and 'dir' should be distinct nodes"
|
|
// );
|
|
|
|
// Don't rename /dir to /target
|
|
// XXX: A similar test fails on Linux using rename().
|
|
// let err = fs
|
|
// .tx(|tx| tx.rename_node(root, "dir", root, "target"))
|
|
// .expect_err("Renaming 'dir' to existing file 'target' should fail");
|
|
// assert_eq!(
|
|
// syscall::EEXIST,
|
|
// err.errno,
|
|
// "Renaming to existing file should fail with EEXIST"
|
|
// );
|
|
// assert_ne!(
|
|
// target_file_mv.id(),
|
|
// dir.id(),
|
|
// "'dir' and 'target' should be distinct nodes"
|
|
// );
|
|
|
|
// Rename /target to /target
|
|
fs.tx(|tx| tx.rename_node(root, "target", root, "target"))
|
|
.expect("Renaming 'target' to itself should succeed");
|
|
let target_self_mv = fs
|
|
.tx(|tx| tx.find_node(root, "target"))
|
|
.expect("'target' should exist because rename succeeded");
|
|
assert_eq!(
|
|
target_file_mv.id(),
|
|
target_self_mv.id(),
|
|
"'target' shouldn't have changed during a move to self"
|
|
);
|
|
|
|
// Rename /target to /dir/target
|
|
fs.tx(|tx| tx.rename_node(root, "target", dir.ptr(), "target"))
|
|
.expect("Renaming /target to /dir/target should succeed");
|
|
let moved_target = fs
|
|
.tx(|tx| tx.find_node(dir.ptr(), "target"))
|
|
.expect("'target' should have moved to /dir/target");
|
|
assert_eq!(target_file_mv.id(), moved_target.id());
|
|
|
|
// Rename /dir to /newdir
|
|
fs.tx(|tx| tx.rename_node(root, "dir", root, "newdir"))
|
|
.expect("Renaming 'dir' to 'newdir' should succeed");
|
|
}
|
|
|
|
#[test]
|
|
fn temporary_file() {
|
|
with_mounted(|path| {
|
|
let file_path = path.join("temp");
|
|
let mut file = fs::File::create_new(&file_path).expect("failed to create temp file");
|
|
|
|
fs::remove_file(&file_path).expect("failed to unlink temp file");
|
|
|
|
let write_data = "Test\n";
|
|
file.write_all(write_data.as_bytes())
|
|
.expect("failed to write temp file");
|
|
|
|
let mut read_data = String::new();
|
|
file.seek(SeekFrom::Start(0))
|
|
.expect("failed to seek temp file");
|
|
file.read_to_string(&mut read_data)
|
|
.expect("failed to read temp file");
|
|
|
|
assert_eq!(read_data, write_data);
|
|
});
|
|
}
|