From cadcd5da5ea4649b978c247ca11f270ddf54fa04 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Sun, 26 Feb 2023 10:48:09 -0500 Subject: [PATCH] fs: add more tests for filesystem functionality (#5493) --- tokio/tests/fs_canonicalize_dir.rs | 22 ++++++ tokio/tests/fs_copy.rs | 2 +- tokio/tests/fs_dir.rs | 41 +++++++++- tokio/tests/fs_file.rs | 105 ++++++++++++++++++++++++- tokio/tests/fs_link.rs | 59 ++++++-------- tokio/tests/fs_open_options.rs | 80 +++++++++++++++++++ tokio/tests/fs_open_options_windows.rs | 51 ++++++++++++ tokio/tests/fs_remove_dir_all.rs | 31 ++++++++ tokio/tests/fs_remove_file.rs | 24 ++++++ tokio/tests/fs_rename.rs | 28 +++++++ tokio/tests/fs_symlink_dir_windows.rs | 31 ++++++++ tokio/tests/fs_symlink_file_windows.rs | 24 ++++++ tokio/tests/fs_try_exists.rs | 2 +- 13 files changed, 462 insertions(+), 38 deletions(-) create mode 100644 tokio/tests/fs_canonicalize_dir.rs create mode 100644 tokio/tests/fs_open_options.rs create mode 100644 tokio/tests/fs_open_options_windows.rs create mode 100644 tokio/tests/fs_remove_dir_all.rs create mode 100644 tokio/tests/fs_remove_file.rs create mode 100644 tokio/tests/fs_rename.rs create mode 100644 tokio/tests/fs_symlink_dir_windows.rs create mode 100644 tokio/tests/fs_symlink_file_windows.rs diff --git a/tokio/tests/fs_canonicalize_dir.rs b/tokio/tests/fs_canonicalize_dir.rs new file mode 100644 index 00000000000..83f0d813737 --- /dev/null +++ b/tokio/tests/fs_canonicalize_dir.rs @@ -0,0 +1,22 @@ +#![warn(rust_2018_idioms)] +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations + +use tokio::fs; + +#[tokio::test] +#[cfg(unix)] +async fn canonicalize_root_dir_unix() { + assert_eq!(fs::canonicalize("/.").await.unwrap().to_str().unwrap(), "/"); +} + +#[tokio::test] +#[cfg(windows)] +async fn canonicalize_root_dir_windows() { + // 2-step let bindings due to Rust memory semantics + let dir_path = fs::canonicalize("C:\\.\\").await.unwrap(); + + let dir_name = dir_path.to_str().unwrap(); + + assert!(dir_name.starts_with("\\\\")); + assert!(dir_name.ends_with("C:\\")); +} diff --git a/tokio/tests/fs_copy.rs b/tokio/tests/fs_copy.rs index 04678cf05b4..e6e5b666f4a 100644 --- a/tokio/tests/fs_copy.rs +++ b/tokio/tests/fs_copy.rs @@ -1,5 +1,5 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(tokio_wasi)))] // Wasi does not support file operations +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations use tempfile::tempdir; use tokio::fs; diff --git a/tokio/tests/fs_dir.rs b/tokio/tests/fs_dir.rs index f197a40ac95..5f653cb2135 100644 --- a/tokio/tests/fs_dir.rs +++ b/tokio/tests/fs_dir.rs @@ -1,5 +1,5 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(tokio_wasi)))] // Wasi does not support directory operations +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations use tokio::fs; use tokio_test::{assert_err, assert_ok}; @@ -45,6 +45,27 @@ async fn build_dir() { ); } +#[tokio::test] +#[cfg(unix)] +async fn build_dir_mode_read_only() { + let base_dir = tempdir().unwrap(); + let new_dir = base_dir.path().join("abc"); + + assert_ok!( + fs::DirBuilder::new() + .recursive(true) + .mode(0o444) + .create(&new_dir) + .await + ); + + assert!(fs::metadata(new_dir) + .await + .expect("metadata result") + .permissions() + .readonly()); +} + #[tokio::test] async fn remove() { let base_dir = tempdir().unwrap(); @@ -85,3 +106,21 @@ async fn read_inherent() { vec!["aa".to_string(), "bb".to_string(), "cc".to_string()] ); } + +#[tokio::test] +async fn read_dir_entry_info() { + let temp_dir = tempdir().unwrap(); + + let file_path = temp_dir.path().join("a.txt"); + + fs::write(&file_path, b"Hello File!").await.unwrap(); + + let mut dir = fs::read_dir(temp_dir.path()).await.unwrap(); + + let first_entry = dir.next_entry().await.unwrap().unwrap(); + + assert_eq!(first_entry.path(), file_path); + assert_eq!(first_entry.file_name(), "a.txt"); + assert!(first_entry.metadata().await.unwrap().is_file()); + assert!(first_entry.file_type().await.unwrap().is_file()); +} diff --git a/tokio/tests/fs_file.rs b/tokio/tests/fs_file.rs index 603ccad3802..dde09906246 100644 --- a/tokio/tests/fs_file.rs +++ b/tokio/tests/fs_file.rs @@ -1,5 +1,5 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(tokio_wasi)))] // Wasi does not support file operations +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations use std::io::prelude::*; use tempfile::NamedTempFile; @@ -87,13 +87,93 @@ async fn coop() { panic!("did not yield"); } +#[tokio::test] +async fn write_to_clone() { + let tempfile = tempfile(); + + let file = File::create(tempfile.path()).await.unwrap(); + let mut clone = file.try_clone().await.unwrap(); + + clone.write_all(HELLO).await.unwrap(); + clone.flush().await.unwrap(); + + let contents = std::fs::read(tempfile.path()).unwrap(); + assert_eq!(contents, HELLO); +} + +#[tokio::test] +async fn write_into_std() { + let tempfile = tempfile(); + + let file = File::create(tempfile.path()).await.unwrap(); + let mut std_file = file.into_std().await; + + std_file.write_all(HELLO).unwrap(); + + let contents = std::fs::read(tempfile.path()).unwrap(); + assert_eq!(contents, HELLO); +} + +#[tokio::test] +async fn write_into_std_immediate() { + let tempfile = tempfile(); + + let file = File::create(tempfile.path()).await.unwrap(); + let mut std_file = file.try_into_std().unwrap(); + + std_file.write_all(HELLO).unwrap(); + + let contents = std::fs::read(tempfile.path()).unwrap(); + assert_eq!(contents, HELLO); +} + +#[tokio::test] +async fn read_file_from_std() { + let mut tempfile = tempfile(); + tempfile.write_all(HELLO).unwrap(); + + let std_file = std::fs::File::open(tempfile.path()).unwrap(); + let mut file = File::from(std_file); + + let mut buf = [0; 1024]; + let n = file.read(&mut buf).await.unwrap(); + assert_eq!(n, HELLO.len()); + assert_eq!(&buf[..n], HELLO); +} + fn tempfile() -> NamedTempFile { NamedTempFile::new().unwrap() } #[tokio::test] #[cfg(unix)] -async fn unix_fd() { +async fn file_debug_fmt() { + let tempfile = tempfile(); + + let file = File::open(tempfile.path()).await.unwrap(); + + assert_eq!( + &format!("{:?}", file)[0..33], + "tokio::fs::File { std: File { fd:" + ); +} + +#[tokio::test] +#[cfg(windows)] +async fn file_debug_fmt() { + let tempfile = tempfile(); + + let file = File::open(tempfile.path()).await.unwrap(); + + assert_eq!( + &format!("{:?}", file)[0..37], + "tokio::fs::File { std: File { handle:" + ); +} + +#[tokio::test] +#[cfg(unix)] +async fn unix_fd_is_valid() { use std::os::unix::io::AsRawFd; let tempfile = tempfile(); @@ -101,6 +181,27 @@ async fn unix_fd() { assert!(file.as_raw_fd() as u64 > 0); } +#[tokio::test] +#[cfg(unix)] +async fn read_file_from_unix_fd() { + use std::os::unix::io::AsRawFd; + use std::os::unix::io::FromRawFd; + + let mut tempfile = tempfile(); + tempfile.write_all(HELLO).unwrap(); + + let file1 = File::open(tempfile.path()).await.unwrap(); + let raw_fd = file1.as_raw_fd(); + assert!(raw_fd > 0); + + let mut file2 = unsafe { File::from_raw_fd(raw_fd) }; + + let mut buf = [0; 1024]; + let n = file2.read(&mut buf).await.unwrap(); + assert_eq!(n, HELLO.len()); + assert_eq!(&buf[..n], HELLO); +} + #[tokio::test] #[cfg(windows)] async fn windows_handle() { diff --git a/tokio/tests/fs_link.rs b/tokio/tests/fs_link.rs index d198abc5182..681b566607e 100644 --- a/tokio/tests/fs_link.rs +++ b/tokio/tests/fs_link.rs @@ -1,10 +1,9 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(tokio_wasi)))] // Wasi does not support file operations +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations use tokio::fs; -use std::io::prelude::*; -use std::io::BufReader; +use std::io::Write; use tempfile::tempdir; #[tokio::test] @@ -13,24 +12,23 @@ async fn test_hard_link() { let src = dir.path().join("src.txt"); let dst = dir.path().join("dst.txt"); - { - let mut file = std::fs::File::create(&src).unwrap(); - file.write_all(b"hello").unwrap(); - } + std::fs::File::create(&src) + .unwrap() + .write_all(b"hello") + .unwrap(); - let dst_2 = dst.clone(); + fs::hard_link(&src, &dst).await.unwrap(); - assert!(fs::hard_link(src, dst_2.clone()).await.is_ok()); + std::fs::File::create(&src) + .unwrap() + .write_all(b"new-data") + .unwrap(); - let mut content = String::new(); + let content = fs::read(&dst).await.unwrap(); + assert_eq!(content, b"new-data"); - { - let file = std::fs::File::open(dst).unwrap(); - let mut reader = BufReader::new(file); - reader.read_to_string(&mut content).unwrap(); - } - - assert!(content == "hello"); + // test that this is not a symlink: + assert!(fs::read_link(&dst).await.is_err()); } #[cfg(unix)] @@ -40,25 +38,20 @@ async fn test_symlink() { let src = dir.path().join("src.txt"); let dst = dir.path().join("dst.txt"); - { - let mut file = std::fs::File::create(&src).unwrap(); - file.write_all(b"hello").unwrap(); - } - - let src_2 = src.clone(); - let dst_2 = dst.clone(); - - assert!(fs::symlink(src_2.clone(), dst_2.clone()).await.is_ok()); + std::fs::File::create(&src) + .unwrap() + .write_all(b"hello") + .unwrap(); - let mut content = String::new(); + fs::symlink(&src, &dst).await.unwrap(); - { - let file = std::fs::File::open(dst.clone()).unwrap(); - let mut reader = BufReader::new(file); - reader.read_to_string(&mut content).unwrap(); - } + std::fs::File::create(&src) + .unwrap() + .write_all(b"new-data") + .unwrap(); - assert!(content == "hello"); + let content = fs::read(&dst).await.unwrap(); + assert_eq!(content, b"new-data"); let read = fs::read_link(dst.clone()).await.unwrap(); assert!(read == src); diff --git a/tokio/tests/fs_open_options.rs b/tokio/tests/fs_open_options.rs new file mode 100644 index 00000000000..e8d8b51b39f --- /dev/null +++ b/tokio/tests/fs_open_options.rs @@ -0,0 +1,80 @@ +#![warn(rust_2018_idioms)] +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations + +use std::io::Write; +use tempfile::NamedTempFile; +use tokio::fs::OpenOptions; +use tokio::io::AsyncReadExt; + +const HELLO: &[u8] = b"hello world..."; + +#[tokio::test] +async fn open_with_open_options_and_read() { + let mut tempfile = NamedTempFile::new().unwrap(); + tempfile.write_all(HELLO).unwrap(); + + let mut file = OpenOptions::new().read(true).open(tempfile).await.unwrap(); + + let mut buf = [0; 1024]; + let n = file.read(&mut buf).await.unwrap(); + + assert_eq!(n, HELLO.len()); + assert_eq!(&buf[..n], HELLO); +} + +#[tokio::test] +async fn open_options_write() { + // TESTING HACK: use Debug output to check the stored data + assert!(format!("{:?}", OpenOptions::new().write(true)).contains("write: true")); +} + +#[tokio::test] +async fn open_options_append() { + // TESTING HACK: use Debug output to check the stored data + assert!(format!("{:?}", OpenOptions::new().append(true)).contains("append: true")); +} + +#[tokio::test] +async fn open_options_truncate() { + // TESTING HACK: use Debug output to check the stored data + assert!(format!("{:?}", OpenOptions::new().truncate(true)).contains("truncate: true")); +} + +#[tokio::test] +async fn open_options_create() { + // TESTING HACK: use Debug output to check the stored data + assert!(format!("{:?}", OpenOptions::new().create(true)).contains("create: true")); +} + +#[tokio::test] +async fn open_options_create_new() { + // TESTING HACK: use Debug output to check the stored data + assert!(format!("{:?}", OpenOptions::new().create_new(true)).contains("create_new: true")); +} + +#[tokio::test] +#[cfg(unix)] +async fn open_options_mode() { + // TESTING HACK: use Debug output to check the stored data + assert!(format!("{:?}", OpenOptions::new().mode(0o644)).contains("mode: 420 ")); +} + +#[tokio::test] +#[cfg(target_os = "linux")] +async fn open_options_custom_flags_linux() { + // TESTING HACK: use Debug output to check the stored data + assert!( + format!("{:?}", OpenOptions::new().custom_flags(libc::O_TRUNC)) + .contains("custom_flags: 512,") + ); +} + +#[tokio::test] +#[cfg(any(target_os = "freebsd", target_os = "macos"))] +async fn open_options_custom_flags_bsd_family() { + // TESTING HACK: use Debug output to check the stored data + assert!( + format!("{:?}", OpenOptions::new().custom_flags(libc::O_NOFOLLOW)) + .contains("custom_flags: 256,") + ); +} diff --git a/tokio/tests/fs_open_options_windows.rs b/tokio/tests/fs_open_options_windows.rs new file mode 100644 index 00000000000..398e6a12b36 --- /dev/null +++ b/tokio/tests/fs_open_options_windows.rs @@ -0,0 +1,51 @@ +#![warn(rust_2018_idioms)] +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations +#![cfg(windows)] + +use tokio::fs::OpenOptions; +use windows_sys::Win32::Storage::FileSystem; + +#[tokio::test] +#[cfg(windows)] +async fn open_options_windows_access_mode() { + // TESTING HACK: use Debug output to check the stored data + assert!(format!("{:?}", OpenOptions::new().access_mode(0)).contains("access_mode: Some(0)")); +} + +#[tokio::test] +#[cfg(windows)] +async fn open_options_windows_share_mode() { + // TESTING HACK: use Debug output to check the stored data + assert!(format!("{:?}", OpenOptions::new().share_mode(0)).contains("share_mode: 0,")); +} + +#[tokio::test] +#[cfg(windows)] +async fn open_options_windows_custom_flags() { + // TESTING HACK: use Debug output to check the stored data + assert!(format!( + "{:?}", + OpenOptions::new().custom_flags(FileSystem::FILE_FLAG_DELETE_ON_CLOSE) + ) + .contains("custom_flags: 67108864,")); +} + +#[tokio::test] +#[cfg(windows)] +async fn open_options_windows_attributes() { + assert!(format!( + "{:?}", + OpenOptions::new().attributes(FileSystem::FILE_ATTRIBUTE_HIDDEN) + ) + .contains("attributes: 2,")); +} + +#[tokio::test] +#[cfg(windows)] +async fn open_options_windows_security_qos_flags() { + assert!(format!( + "{:?}", + OpenOptions::new().security_qos_flags(FileSystem::SECURITY_IDENTIFICATION) + ) + .contains("security_qos_flags: 1114112,")); +} diff --git a/tokio/tests/fs_remove_dir_all.rs b/tokio/tests/fs_remove_dir_all.rs new file mode 100644 index 00000000000..53a2fd34f36 --- /dev/null +++ b/tokio/tests/fs_remove_dir_all.rs @@ -0,0 +1,31 @@ +#![warn(rust_2018_idioms)] +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations + +use tempfile::tempdir; +use tokio::fs; + +#[tokio::test] +async fn remove_dir_all() { + let temp_dir = tempdir().unwrap(); + + let test_dir = temp_dir.path().join("test"); + fs::create_dir(&test_dir).await.unwrap(); + + let file_path = test_dir.as_path().join("a.txt"); + + fs::write(&file_path, b"Hello File!").await.unwrap(); + + fs::remove_dir_all(test_dir.as_path()).await.unwrap(); + + // test dir should no longer exist + match fs::try_exists(test_dir).await { + Ok(exists) => assert!(!exists), + Err(_) => println!("ignored try_exists error after remove_dir_all"), + }; + + // contents should no longer exist + match fs::try_exists(file_path).await { + Ok(exists) => assert!(!exists), + Err(_) => println!("ignored try_exists error after remove_dir_all"), + }; +} diff --git a/tokio/tests/fs_remove_file.rs b/tokio/tests/fs_remove_file.rs new file mode 100644 index 00000000000..14fd680dabe --- /dev/null +++ b/tokio/tests/fs_remove_file.rs @@ -0,0 +1,24 @@ +#![warn(rust_2018_idioms)] +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations + +use tempfile::tempdir; +use tokio::fs; + +#[tokio::test] +async fn remove_file() { + let temp_dir = tempdir().unwrap(); + + let file_path = temp_dir.path().join("a.txt"); + + fs::write(&file_path, b"Hello File!").await.unwrap(); + + assert!(fs::try_exists(&file_path).await.unwrap()); + + fs::remove_file(&file_path).await.unwrap(); + + // should no longer exist + match fs::try_exists(file_path).await { + Ok(exists) => assert!(!exists), + Err(_) => println!("ignored try_exists error after remove_file"), + }; +} diff --git a/tokio/tests/fs_rename.rs b/tokio/tests/fs_rename.rs new file mode 100644 index 00000000000..382fea079bd --- /dev/null +++ b/tokio/tests/fs_rename.rs @@ -0,0 +1,28 @@ +#![warn(rust_2018_idioms)] +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations + +use tempfile::tempdir; +use tokio::fs; + +#[tokio::test] +async fn rename_file() { + let temp_dir = tempdir().unwrap(); + + let file_path = temp_dir.path().join("a.txt"); + + fs::write(&file_path, b"Hello File!").await.unwrap(); + + assert!(fs::try_exists(&file_path).await.unwrap()); + + let new_file_path = temp_dir.path().join("b.txt"); + + fs::rename(&file_path, &new_file_path).await.unwrap(); + + assert!(fs::try_exists(new_file_path).await.unwrap()); + + // original file should no longer exist + match fs::try_exists(file_path).await { + Ok(exists) => assert!(!exists), + Err(_) => println!("ignored try_exists error after rename"), + }; +} diff --git a/tokio/tests/fs_symlink_dir_windows.rs b/tokio/tests/fs_symlink_dir_windows.rs new file mode 100644 index 00000000000..d80354268a2 --- /dev/null +++ b/tokio/tests/fs_symlink_dir_windows.rs @@ -0,0 +1,31 @@ +#![warn(rust_2018_idioms)] +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations +#![cfg(windows)] + +use tempfile::tempdir; +use tokio::fs; + +#[tokio::test] +async fn symlink_file_windows() { + const FILE_NAME: &str = "abc.txt"; + + let temp_dir = tempdir().unwrap(); + + let dir1 = temp_dir.path().join("a"); + fs::create_dir(&dir1).await.unwrap(); + + let file1 = dir1.as_path().join(FILE_NAME); + fs::write(&file1, b"Hello File!").await.unwrap(); + + let dir2 = temp_dir.path().join("b"); + fs::symlink_dir(&dir1, &dir2).await.unwrap(); + + fs::write(&file1, b"new data!").await.unwrap(); + + let file2 = dir2.as_path().join(FILE_NAME); + + let from = fs::read(&file1).await.unwrap(); + let to = fs::read(&file2).await.unwrap(); + + assert_eq!(from, to); +} diff --git a/tokio/tests/fs_symlink_file_windows.rs b/tokio/tests/fs_symlink_file_windows.rs new file mode 100644 index 00000000000..50eaf8aab75 --- /dev/null +++ b/tokio/tests/fs_symlink_file_windows.rs @@ -0,0 +1,24 @@ +#![warn(rust_2018_idioms)] +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations +#![cfg(windows)] + +use tempfile::tempdir; +use tokio::fs; + +#[tokio::test] +async fn symlink_file_windows() { + let dir = tempdir().unwrap(); + + let source_path = dir.path().join("foo.txt"); + let dest_path = dir.path().join("bar.txt"); + + fs::write(&source_path, b"Hello File!").await.unwrap(); + fs::symlink_file(&source_path, &dest_path).await.unwrap(); + + fs::write(&source_path, b"new data!").await.unwrap(); + + let from = fs::read(&source_path).await.unwrap(); + let to = fs::read(&dest_path).await.unwrap(); + + assert_eq!(from, to); +} diff --git a/tokio/tests/fs_try_exists.rs b/tokio/tests/fs_try_exists.rs index d259b5caaf0..00d23650015 100644 --- a/tokio/tests/fs_try_exists.rs +++ b/tokio/tests/fs_try_exists.rs @@ -1,5 +1,5 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(tokio_wasi)))] // Wasi does not support file operations +#![cfg(all(feature = "full", not(tokio_wasi)))] // WASI does not support all fs operations use tempfile::tempdir; use tokio::fs;