From 264049eacb26559ff877ae257c259e3e14b387c6 Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 17 Nov 2024 18:28:52 +0100 Subject: [PATCH] fix: integration tests for windows --- .github/workflows/windows.yml | 48 +++---- README.md | 12 +- remotefs-fuse-cli/src/main.rs | 1 + remotefs-fuse/Cargo.toml | 2 + remotefs-fuse/src/driver/windows.rs | 156 ++++++++++++++++++---- remotefs-fuse/tests/dokany/mod.rs | 157 +++++++++++++++++++++++ remotefs-fuse/tests/driver/mod.rs | 152 ++++++++++++---------- remotefs-fuse/tests/integration_tests.rs | 13 +- 8 files changed, 421 insertions(+), 120 deletions(-) create mode 100644 remotefs-fuse/tests/dokany/mod.rs diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index f6b5545..2fbfc25 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,23 +1,25 @@ -name: windows - -on: [push, pull_request] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - runs-on: windows-2019 - - steps: - - uses: actions/checkout@v2 - - name: Build - run: cargo build --all-features - - name: Format - run: cargo fmt --all -- --check - - name: Clippy - run: cargo clippy -- -Dwarnings - - name: Run tests - run: cargo test --no-fail-fast - env: - RUST_LOG: trace +name: windows + +on: [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + - name: install dokany + run: choco install dokany + - name: Build + run: cargo build --all-features + - name: Format + run: cargo fmt --all -- --check + - name: Clippy + run: cargo clippy -- -Dwarnings + - name: Run tests + run: cargo test --no-fail-fast --features integration-tests + env: + RUST_LOG: trace diff --git a/README.md b/README.md index 33b6b51..2f2cebf 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

~ A FUSE Driver for remotefs-rs ~

-

Developed by @veeso

+

Developed by @veeso

Current version: 0.1.0

@@ -162,6 +162,8 @@ All the features are enabled by default; so if you want to build it with only ce remotefs-fuse-cli -o opt1 -o opt2=abc --to /mnt/to --volume [protocol-options...] ``` +On Windows the mountpoint can be specified simply using the drive letter `--to M` will mount the FS to `M:\` + where protocol options are - aws-s3 @@ -210,6 +212,8 @@ Mount options can be viewed in the docs at ❗ This doesn't apply to Windows. + The possibility to override UID and GID is used because sometimes this scenario can happen: 1. my UID is `1000` @@ -221,7 +225,11 @@ That's why I've added `Uid` and `Gid` into the `MountOption` variant. Setting the `Uid` option to `1002` you'll be able to operate on the File system as it should. -> ❗ This doesn't apply to Windows. +## Project stability + +Please consider this is an early-stage project and I haven't heavily tested it, in particular the Windows version. + +I suggest you to first test it on test filesystems to see whether the library behaves correctly with your system. ## Changelog ⏳ diff --git a/remotefs-fuse-cli/src/main.rs b/remotefs-fuse-cli/src/main.rs index 46a494f..1568273 100644 --- a/remotefs-fuse-cli/src/main.rs +++ b/remotefs-fuse-cli/src/main.rs @@ -44,6 +44,7 @@ fn main() -> anyhow::Result<()> { log::info!("Mounting remote fs at {}", mount_path.display()); // create the mount point if it does not exist + #[cfg(unix)] if !mount_path.exists() { log::info!("creating mount point at {}", mount_path.display()); std::fs::create_dir_all(&mount_path)?; diff --git a/remotefs-fuse/Cargo.toml b/remotefs-fuse/Cargo.toml index 0114721..dd09508 100644 --- a/remotefs-fuse/Cargo.toml +++ b/remotefs-fuse/Cargo.toml @@ -33,6 +33,7 @@ nix = { version = "0.29", features = ["fs"] } dashmap = "6" dokan = "0.3.1" dokan-sys = "0.3.1" +path-slash = "0.2" widestring = "0.4.3" winapi = "0.3.9" @@ -40,6 +41,7 @@ winapi = "0.3.9" env_logger = "^0.11" pretty_assertions = "^1" remotefs-memory = "0.1" +serial_test = "^3" [target.'cfg(unix)'.dev-dependencies] nix = { version = "0.29", features = ["user"] } diff --git a/remotefs-fuse/src/driver/windows.rs b/remotefs-fuse/src/driver/windows.rs index efd773a..96ab109 100644 --- a/remotefs-fuse/src/driver/windows.rs +++ b/remotefs-fuse/src/driver/windows.rs @@ -11,8 +11,8 @@ use std::time::UNIX_EPOCH; use dashmap::mapref::one::Ref; use dokan::{ - CreateFileInfo, FileInfo, FileSystemHandler, FileTimeOperation, FillDataError, FillDataResult, - FindData, FindStreamData, OperationInfo, OperationResult, VolumeInfo, + CreateFileInfo, DiskSpaceInfo, FileInfo, FileSystemHandler, FileTimeOperation, FillDataError, + FillDataResult, FindData, FindStreamData, OperationInfo, OperationResult, VolumeInfo, }; use dokan_sys::win32::{ FILE_CREATE, FILE_DELETE_ON_CLOSE, FILE_DIRECTORY_FILE, FILE_MAXIMUM_DISPOSITION, @@ -20,6 +20,7 @@ use dokan_sys::win32::{ FILE_SUPERSEDE, }; use entry::{EntryName, StatHandle}; +use path_slash::PathBufExt; use remotefs::fs::{Metadata, UnixPex}; use remotefs::{File, RemoteError, RemoteErrorType, RemoteFs, RemoteResult}; use widestring::{U16CStr, U16CString, U16Str, U16String}; @@ -144,8 +145,12 @@ where .map(|p| p.to_path_buf()) .unwrap_or_else(|| PathBuf::from("/")); + // convert to `/` path + let slash_path = PathBuf::from(p.to_slash_lossy().to_string()); + debug!("PathInfo: '{p:?}' -> '{slash_path:?}'"); + PathInfo { - path: p, + path: slash_path, parent, file_name: file_name.to_ucstring(), } @@ -379,10 +384,10 @@ where where F: FnMut(&FindData) -> FillDataResult, { + debug!("find_files({ctx:?}, {pattern:?})"); if ctx.is_file() { return Err(STATUS_NOT_A_DIRECTORY); } - debug!("find_files({ctx:?}, {pattern:?})"); // list directory let entries = match self.remote(|remote| remote.list_dir(ctx.path())) { @@ -401,21 +406,21 @@ where .map(|pattern| dokan::is_name_in_expression(pattern, &file_name, false)) .unwrap_or(true) { - (fill)(&Self::find_data(&child)).or_else(Self::ignore_name_too_long)?; + (fill)(&Self::find_data(&child, file_name)).or_else(Self::ignore_name_too_long)?; } } Ok(()) } - fn find_data(file: &File) -> FindData { + fn find_data(file: &File, file_name: U16CString) -> FindData { FindData { attributes: Self::attributes_from_file(file), creation_time: file.metadata().created.unwrap_or(UNIX_EPOCH), last_access_time: file.metadata().accessed.unwrap_or(UNIX_EPOCH), last_write_time: file.metadata().modified.unwrap_or(UNIX_EPOCH), file_size: file.metadata().size, - file_name: Self::file_name(file.path()), + file_name, } } @@ -447,6 +452,18 @@ where where F: FnOnce(&mut AltStream) -> OperationResult, { + // check if alt stream is requested; must contain ':" in the name + let use_alt_stream = match context.stat.read() { + Ok(stat) => stat.file.path.to_string_lossy().contains(':'), + Err(_) => { + error!("mutex poisoned"); + return Some(Err(STATUS_INVALID_DEVICE_REQUEST)); + } + }; + if !use_alt_stream { + return None; + } + let alt_stream = match context.alt_stream.read() { Err(_) => { error!("mutex poisoned"); @@ -528,7 +545,8 @@ where create_options: u32, _info: &mut OperationInfo<'c, 'h, Self>, ) -> OperationResult> { - info!("create_file({file_name:?}, {desired_access:?}, {file_attributes:?}, {share_access:?}, {create_disposition:?}, {create_options:?})"); + let file_name_path = Self::path_info(file_name).path; + info!("create_file({file_name_path:?}, {desired_access:?}, {file_attributes:?}, {share_access:?}, {create_disposition:?}, {create_options:?})"); let stat = self.stat(file_name).ok(); @@ -569,7 +587,7 @@ where error!("delete on close: {file_name:?}"); return Err(STATUS_CANNOT_DELETE); } - std::mem::drop(read); + drop(read); let stream_name = EntryName(file_name.to_ustring()); let ret = { @@ -600,15 +618,18 @@ where return Err(STATUS_ACCESS_DENIED); } } - FILE_CREATE => return Err(ntstatus::STATUS_OBJECT_NAME_COLLISION), + FILE_CREATE => { + error!("alt stream already exists: {file_name:?}"); + return Err(ntstatus::STATUS_OBJECT_NAME_COLLISION); + } _ => (), } Some((stream, false)) } else { - if create_disposition == FILE_OPEN || create_disposition == FILE_OVERWRITE { - error!("alt stream not found: {file_name:?}"); - return Err(STATUS_OBJECT_NAME_NOT_FOUND); - } + //if create_disposition == FILE_OPEN || create_disposition == FILE_OVERWRITE { + // error!("alt stream not found: {file_name:?}"); + // return Err(STATUS_OBJECT_NAME_NOT_FOUND); + //} if is_readonly { error!("file {file_name:?} is readonly"); return Err(STATUS_ACCESS_DENIED); @@ -637,6 +658,8 @@ where .ok() .map(|r| r.file.is_file()) .unwrap_or_default(); + + // check if file or directory match is_file { true => { if create_options & FILE_DIRECTORY_FILE > 0 { @@ -650,7 +673,10 @@ where return Err(STATUS_ACCESS_DENIED); } } - FILE_CREATE => return Err(STATUS_OBJECT_NAME_COLLISION), + FILE_CREATE => { + error!("file already exists: {file_name:?}"); + return Err(STATUS_OBJECT_NAME_COLLISION); + } _ => (), } debug!("open file: {file_name:?}"); @@ -664,9 +690,11 @@ where is_dir: false, new_file_created: false, }) - } + } // end is file false => { + // is directory if create_options & FILE_NON_DIRECTORY_FILE > 0 { + error!("file is a directory: {file_name:?}"); return Err(STATUS_FILE_IS_A_DIRECTORY); } match create_disposition { @@ -683,13 +711,23 @@ where new_file_created: false, }) } - FILE_CREATE => Err(STATUS_OBJECT_NAME_COLLISION), - _ => Err(STATUS_INVALID_PARAMETER), + FILE_CREATE => { + error!("directory already exists: {file_name:?}"); + Err(STATUS_OBJECT_NAME_COLLISION) + } + _ => { + error!("invalid create disposition: {create_disposition}"); + Err(STATUS_INVALID_PARAMETER) + } } } } - } else if create_disposition == FILE_OPEN || create_disposition == FILE_OPEN_IF { + } + // END IF FILE EXISTS + else if create_disposition == FILE_CREATE || create_disposition == FILE_OPEN_IF { + // FILE DOES NOT EXIST if create_options & FILE_NON_DIRECTORY_FILE > 0 { + // create file debug!("create file: {file_name:?}"); let path_info = Self::path_info(file_name); if let Err(err) = self.write( @@ -725,9 +763,9 @@ where }) } else { // create directory - debug!("create directory: {file_name:?}"); let stat = { let path_info = Self::path_info(file_name); + debug!("create directory: {}", path_info.path.display()); if let Err(err) = self .remote(|remote| remote.create_dir(&path_info.path, UnixPex::from(0o755))) @@ -756,7 +794,11 @@ where new_file_created: true, }) } + } else if create_disposition == FILE_OPEN { + error!("tried to open non existing file: {file_name:?}"); + Err(STATUS_OBJECT_NAME_NOT_FOUND) } else { + error!("invalid create disposition: {create_disposition}"); Err(STATUS_INVALID_PARAMETER) } } @@ -798,13 +840,29 @@ where .unwrap_or_default() .unwrap_or_default(); + if alt_stream_delete { + let mut alt_stream = match context.alt_stream.write() { + Ok(alt_stream) => alt_stream, + Err(_) => { + error!("mutex poisoned"); + return; + } + }; + alt_stream.take(); + return; + } + if context.delete_on_close || stat.delete_on_close || stat.delete_pending || info.delete_on_close() - || alt_stream_delete { - debug!("removing file: {file_name:?}"); + info!("removing file: {}; delete_on_close: {}; stat.delete_on_close: {}; delete_pending: {}", + stat.file.path().display(), + context.delete_on_close, + stat.delete_on_close, + stat.delete_pending + ); if let Err(err) = self.remote(|remote| { if stat.file.is_dir() { remote.remove_dir(&stat.file.path) @@ -913,6 +971,7 @@ where // check alt stream if let Some(res) = Self::try_alt_stream(context, |alt_stream| { + debug!("write alt stream: {file_name:?}"); let offset = if info.write_to_eof() { alt_stream.data.len() } else { @@ -930,8 +989,10 @@ where } if info.write_to_eof() { + debug!("append file: {file_name:?}"); self.append(&file, buffer) } else { + debug!("write file: {file_name:?}"); self.write(&file, buffer, offset as u64) } .map_err(|err| { @@ -1015,6 +1076,7 @@ where Ok(stream) => stream.clone(), }; if alt_stream.is_some() { + error!("alt stream found"); return Err(STATUS_INVALID_DEVICE_REQUEST); } drop(alt_stream); @@ -1054,6 +1116,7 @@ where ) -> OperationResult<()> { info!("find_files_with_pattern({file_name:?}, {pattern:?}, {context:?})"); + /* let alt_stream = match context.alt_stream.read() { Err(_) => { error!("mutex poisoned"); @@ -1062,9 +1125,11 @@ where Ok(stream) => stream.clone(), }; if alt_stream.is_some() { + error!("alt stream found"); return Err(STATUS_INVALID_DEVICE_REQUEST); } drop(alt_stream); + */ let file = match context.stat.read() { Err(_) => { @@ -1330,7 +1395,10 @@ where Ok(()) }) - .unwrap_or(Err(STATUS_NOT_IMPLEMENTED)) + .unwrap_or({ + debug!("cant set end of file: not implemented"); + Ok(()) + }) } /// Sets allocation size of the file. @@ -1355,7 +1423,10 @@ where Ok(()) }) - .unwrap_or(Err(STATUS_NOT_IMPLEMENTED)) + .unwrap_or({ + debug!("cant set allocation size: not implemented"); + Ok(()) + }) } /// Gets security information of a file. @@ -1487,4 +1558,41 @@ where fs_name: U16CString::from_str("DOKANY").expect("failed to create U16CString"), }) } + + fn lock_file( + &'h self, + _file_name: &U16CStr, + _offset: i64, + _length: i64, + _info: &OperationInfo<'c, 'h, Self>, + _context: &'c Self::Context, + ) -> OperationResult<()> { + error!("lock_file not implemented"); + Err(STATUS_NOT_IMPLEMENTED) + } + + fn unlock_file( + &'h self, + _file_name: &U16CStr, + _offset: i64, + _length: i64, + _info: &OperationInfo<'c, 'h, Self>, + _context: &'c Self::Context, + ) -> OperationResult<()> { + error!("unlock_file not implemented"); + Err(STATUS_NOT_IMPLEMENTED) + } + + fn get_disk_free_space( + &'h self, + _info: &OperationInfo<'c, 'h, Self>, + ) -> OperationResult { + const DEFAULT_SIZE: u64 = 1024 * 1024 * 1024 * 128; // 128GB + info!("get_disk_free_space()"); + Ok(DiskSpaceInfo { + free_byte_count: DEFAULT_SIZE, + byte_count: DEFAULT_SIZE, + available_byte_count: DEFAULT_SIZE, + }) + } } diff --git a/remotefs-fuse/tests/dokany/mod.rs b/remotefs-fuse/tests/dokany/mod.rs new file mode 100644 index 0000000..ce8c098 --- /dev/null +++ b/remotefs-fuse/tests/dokany/mod.rs @@ -0,0 +1,157 @@ +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, AtomicUsize}; +use std::sync::{Arc, Mutex}; +use std::thread::JoinHandle; +use std::time::Duration; + +use remotefs_fuse::{Mount, Umount}; +use serial_test::serial; + +use crate::driver::mounted_file_path; + +pub type UmountLock = Arc>>; + +static AVAILABLE_DRIVES: &[&str] = &["Z", "Y", "X", "W", "V", "U", "T", "S", "R", "Q"]; +static CURRENT_DRIVE: AtomicUsize = AtomicUsize::new(0); + +/// Mounts the filesystem in a separate thread. +/// +/// The filesystem must be unmounted manually and then the thread must be joined. +fn mount(p: &Path) -> (UmountLock, JoinHandle<()>) { + let mountpoint = p.to_path_buf(); + + let error_flag = Arc::new(AtomicBool::new(false)); + let error_flag_t = error_flag.clone(); + + let umount = Arc::new(Mutex::new(None)); + let umount_t = umount.clone(); + + let join = std::thread::spawn(move || { + let mut mount = + Mount::mount(crate::driver::setup_driver(), &mountpoint, &[]).expect("failed to mount"); + + let umount = mount.unmounter(); + *umount_t.lock().unwrap() = Some(umount); + + mount.run().expect("failed to run filesystem event loop"); + + // set the error flag if the filesystem was unmounted + error_flag_t.store(true, std::sync::atomic::Ordering::Relaxed); + }); + + // wait for the filesystem to be mounted + std::thread::sleep(Duration::from_secs(1)); + if error_flag.load(std::sync::atomic::Ordering::Relaxed) { + panic!("Failed to mount filesystem"); + } + + (umount, join) +} + +fn umount(umount: UmountLock) { + umount + .lock() + .unwrap() + .as_mut() + .unwrap() + .umount() + .expect("Failed to unmount"); +} + +fn next_driver() -> PathBuf { + let current = CURRENT_DRIVE.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let drive = AVAILABLE_DRIVES[current % AVAILABLE_DRIVES.len()]; + PathBuf::from(drive) +} + +fn path_to_drive(mnt: &Path, path: &Path) -> PathBuf { + let mut drive_path = PathBuf::from(format!("{}:\\", mnt.display())); + drive_path.push(path); + + drive_path +} + +/// Mounts the filesystem and calls the provided closure with the mountpoint. +fn with_mounted_drive(f: F) +where + F: FnOnce(&Path), +{ + let _ = env_logger::Builder::new() + .is_test(true) + .filter_level(log::LevelFilter::Debug) + .try_init(); + let mnt = next_driver(); + // mount + let (umounter, join) = mount(mnt.as_path()); + f(mnt.as_path()); + // unmount + umount(umounter); + join.join().expect("Failed to join thread"); + + // wait for the filesystem to be unmounted + std::thread::sleep(Duration::from_secs(3)); +} + +#[test] +#[serial] +fn test_should_mount_fs() { + with_mounted_drive(|mnt| { + let mounted_file_path = PathBuf::from(format!( + "{}:\\{}", + mnt.display(), + mounted_file_path().display() + )); + println!("Mounted file path: {:?}", mounted_file_path); + assert!(mounted_file_path.exists()); + }); +} + +#[test] +#[serial] +fn test_should_create_file() { + with_mounted_drive(|mnt| { + let file_path = PathBuf::from("test.txt"); + let file_path = path_to_drive(mnt, &file_path); + let file_content = "Hello, World!"; + std::fs::write(&file_path, file_content).expect("Failed to write to file"); + + // read from file + let read_content = std::fs::read_to_string(&file_path).expect("Failed to read from file"); + assert_eq!(file_content, read_content); + }); +} + +#[test] +#[serial] +fn test_should_unlink_file() { + with_mounted_drive(|mnt| { + let file_path = PathBuf::from("test.txt"); + let file_path = path_to_drive(mnt, &file_path); + let file_content = "Hello, World!"; + std::fs::write(&file_path, file_content).expect("Failed to write to file"); + + // unlink file + std::fs::remove_file(&file_path).expect("Failed to unlink file"); + assert!(!file_path.exists()); + }); +} + +#[test] +#[serial] +#[ignore = "Strange behavior when removing the directory"] +fn test_should_make_and_remove_directory() { + with_mounted_drive(|mnt| { + let dir_path = PathBuf::from("test"); + let dir_path = path_to_drive(mnt, &dir_path); + std::fs::create_dir(&dir_path).expect("Failed to create directory"); + assert!(dir_path.exists()); + + // wait for the filesystem to cleanup + std::thread::sleep(Duration::from_secs(1)); + + // remove directory + println!("Removing directory: {:?}", dir_path); + std::fs::remove_dir_all(&dir_path).expect("Failed to remove directory"); + assert!(!dir_path.exists()); + }); +} diff --git a/remotefs-fuse/tests/driver/mod.rs b/remotefs-fuse/tests/driver/mod.rs index ad09c0e..6d51787 100644 --- a/remotefs-fuse/tests/driver/mod.rs +++ b/remotefs-fuse/tests/driver/mod.rs @@ -1,66 +1,86 @@ -use std::path::{Path, PathBuf}; - -use remotefs::fs::{Metadata, UnixPex}; -use remotefs::{RemoteError, RemoteErrorType, RemoteFs}; -use remotefs_memory::{node, Inode, MemoryFs, Node, Tree}; - -pub fn mounted_file_path() -> &'static Path { - Path::new("/tmp/mounted.txt") -} - -pub fn setup_driver() -> MemoryFs { - let gid = nix::unistd::getgid().as_raw(); - let uid = nix::unistd::getuid().as_raw(); - - let tree = Tree::new(node!( - PathBuf::from("/"), - Inode::dir(uid, gid, UnixPex::from(0o755)), - )); - - let mut fs = MemoryFs::new(tree) - .with_get_gid(|| nix::unistd::getgid().as_raw()) - .with_get_uid(|| nix::unistd::getuid().as_raw()); - - fs.connect().expect("Failed to connect to fs"); - - make_file_at(&mut fs, mounted_file_path(), b"Hello, world!"); - - fs -} - -/// Make file on the remote fs at `path` with `content` -/// -/// If the stems in the path do not exist, they will be created. -fn make_file_at(remote: &mut MemoryFs, path: &Path, content: &[u8]) { - let parent_dir = path.parent().expect("Path has no parent"); - make_dir_at(remote, parent_dir); - - let reader = std::io::Cursor::new(content.to_vec()); - - remote - .create_file( - path, - &Metadata::default().size(content.len() as u64), - Box::new(reader), - ) - .expect("Failed to create file"); -} - -/// Make directory on the remote fs at `path` -/// -/// All the stems in the path will be created if they do not exist. -fn make_dir_at(remote: &mut MemoryFs, path: &Path) { - let mut abs_path = Path::new("/").to_path_buf(); - for stem in path.iter() { - abs_path.push(stem); - println!("Creating directory: {abs_path:?}"); - match remote.create_dir(&abs_path, UnixPex::from(0o755)) { - Ok(_) - | Err(RemoteError { - kind: RemoteErrorType::DirectoryAlreadyExists, - .. - }) => {} - Err(err) => panic!("Failed to create directory: {err}"), - } - } -} +use std::path::{Path, PathBuf}; + +use path_slash::PathBufExt; +use remotefs::fs::{Metadata, UnixPex}; +use remotefs::{RemoteError, RemoteErrorType, RemoteFs}; +use remotefs_memory::{node, Inode, MemoryFs, Node, Tree}; + +pub fn mounted_file_path() -> &'static Path { + Path::new("/tmp/mounted.txt") +} + +#[cfg(unix)] +pub fn setup_driver() -> MemoryFs { + let gid = nix::unistd::getgid().as_raw(); + let uid = nix::unistd::getuid().as_raw(); + + let tree = Tree::new(node!( + PathBuf::from("/"), + Inode::dir(uid, gid, UnixPex::from(0o755)), + )); + + let mut fs = MemoryFs::new(tree) + .with_get_gid(|| nix::unistd::getgid().as_raw()) + .with_get_uid(|| nix::unistd::getuid().as_raw()); + + fs.connect().expect("Failed to connect to fs"); + + make_file_at(&mut fs, mounted_file_path(), b"Hello, world!"); + + fs +} + +#[cfg(windows)] +pub fn setup_driver() -> MemoryFs { + let tree = Tree::new(node!( + PathBuf::from("/"), + Inode::dir(0, 0, UnixPex::from(0o755)), + )); + + let mut fs = MemoryFs::new(tree); + + fs.connect().expect("Failed to connect to fs"); + + make_file_at(&mut fs, mounted_file_path(), b"Hello, world!"); + + fs +} + +/// Make file on the remote fs at `path` with `content` +/// +/// If the stems in the path do not exist, they will be created. +fn make_file_at(remote: &mut MemoryFs, path: &Path, content: &[u8]) { + let parent_dir = path.parent().expect("Path has no parent"); + make_dir_at(remote, parent_dir); + + let reader = std::io::Cursor::new(content.to_vec()); + + remote + .create_file( + path, + &Metadata::default().size(content.len() as u64), + Box::new(reader), + ) + .expect("Failed to create file"); +} + +/// Make directory on the remote fs at `path` +/// +/// All the stems in the path will be created if they do not exist. +fn make_dir_at(remote: &mut MemoryFs, path: &Path) { + let mut abs_path = Path::new("/").to_path_buf(); + for stem in path.iter() { + abs_path.push(stem); + // convert to slash + let abs_path = PathBuf::from(abs_path.to_slash_lossy().to_string()); + println!("Creating directory: {abs_path:?}"); + match remote.create_dir(&abs_path, UnixPex::from(0o755)) { + Ok(_) + | Err(RemoteError { + kind: RemoteErrorType::DirectoryAlreadyExists, + .. + }) => {} + Err(err) => panic!("Failed to create directory: {err}"), + } + } +} diff --git a/remotefs-fuse/tests/integration_tests.rs b/remotefs-fuse/tests/integration_tests.rs index b47cba4..336eba1 100644 --- a/remotefs-fuse/tests/integration_tests.rs +++ b/remotefs-fuse/tests/integration_tests.rs @@ -1,5 +1,8 @@ -#[cfg(feature = "integration-tests")] -pub mod driver; -#[cfg(unix)] -#[cfg(feature = "integration-tests")] -mod fuse; +#[cfg(windows)] +#[cfg(feature = "integration-tests")] +mod dokany; +#[cfg(feature = "integration-tests")] +pub mod driver; +#[cfg(unix)] +#[cfg(feature = "integration-tests")] +mod fuse;