mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-17 10:17:55 +00:00
1eef0de003
* chore(ecstore): drop the disk dead_code blanket Removing the blanket exposes 36 items in the lowest storage layer: 7 deleted, 29 kept with reasoned item-level allows. That is the smallest deletion share of this burn-down, and the reason is a verification limit rather than a judgement call. disk/local.rs carries 141 `#[cfg(target_os = "linux")]` sites — the densest platform gating in the tree, because O_DIRECT and io_uring only exist there. The direct-I/O cluster (six ENV_RUSTFS_OBJECT_DIRECT_IO_* constants plus is_direct_io_read_enabled, is_direct_io_write_enabled, get_direct_io_read_threshold, direct_write_staging_capacity, direct_write_tail_split and DIRECT_WRITE_STAGING_BYTES) reads as dead on macOS purely because its production callers at local.rs:1766, 3114 and 4605 sit inside Linux-gated blocks. direct_write_staging_capacity even documents itself as "Platform-independent (no O_DIRECT), so it is unit-tested on any host". Deleting those would leave every local check green — 4096 tests pass, clippy is clean, make pre-commit exits 0 — and break the Linux build in CI, because all four local lanes compile for aarch64-apple-darwin. Cross-checking locally is not available either: cargo check --target x86_64-unknown-linux-gnu fails in the aws-lc-sys build script for want of a Linux C cross-compiler. Their allows name the platform reason so the next reader on a non-Linux host does not repeat the investigation. Deleted, all in files with no target_os gating at all (os.rs, disk_store.rs): - HealthDiskCtxKey and HealthDiskCtxValue with its private log_success. Note that DiskHealthTracker::log_success is a different method of the same name and is live from cluster/rpc/peer_s3_client.rs and remote_disk.rs — the two have to be told apart by type, not by name. - LocalDiskWrapper::new_with_health and check_id. - os.rs file_exists and lock_destination_directory_for_path_access. Kept with allows: DiskHealthTracker's set_faulty, mark_offline, waiting_count and last_success have test callers in remote_disk.rs, so they only look dead in the lib target. to_disk_error, remove_all and sync_dir_files are asserted by their own files' tests. The reclaim, mmap and path-cache field groups are written but never read back. Placement follows the same rule as the earlier roots: per-method allows inside impl DiskHealthTracker and impl LocalDisk, since both are mostly live and a block-level allow would be a smaller version of the blanket this issue removes. Struct-level allows are used only where the warning covers that struct's own fields. The three cached_read_env! functions take their allow inside the macro invocation, before the fn line, because the macro forwards $(#[$meta:meta])* onto the generated item. Verification, four lanes warning-free: default, --tests, --features rio-v2 --tests, --features test-util --tests. cargo nextest run -p rustfs-ecstore 4096 passed; clippy --lib --tests -D warnings clean; make pre-commit exit 0. The Linux lane is not covered locally and is left to CI. Ref rustfs/backlog#1823 (step 2). * chore(ecstore): correct two dead_code reasons in the disk root check_valid_path and reject_symlink_components have no caller at all - not even a test - so 'asserted by this file's tests' misreads them as covered. Both are method wrappers over live free functions; say that instead. Ref rustfs/backlog#1823.
597 lines
18 KiB
Rust
597 lines
18 KiB
Rust
// Copyright 2024 RustFS Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use std::{
|
|
fs::Metadata,
|
|
path::Path,
|
|
sync::{Arc, OnceLock},
|
|
};
|
|
use tokio::{
|
|
fs::{self, File},
|
|
io,
|
|
};
|
|
|
|
static READONLY_OPTIONS: OnceLock<Arc<fs::OpenOptions>> = OnceLock::new();
|
|
static WRITEONLY_OPTIONS: OnceLock<Arc<fs::OpenOptions>> = OnceLock::new();
|
|
static READWRITE_OPTIONS: OnceLock<Arc<fs::OpenOptions>> = OnceLock::new();
|
|
|
|
fn get_readonly_options() -> &'static Arc<fs::OpenOptions> {
|
|
READONLY_OPTIONS.get_or_init(|| {
|
|
let mut opts = fs::OpenOptions::new();
|
|
opts.read(true);
|
|
Arc::new(opts)
|
|
})
|
|
}
|
|
|
|
fn get_writeonly_options() -> &'static Arc<fs::OpenOptions> {
|
|
WRITEONLY_OPTIONS.get_or_init(|| {
|
|
let mut opts = fs::OpenOptions::new();
|
|
opts.write(true);
|
|
Arc::new(opts)
|
|
})
|
|
}
|
|
|
|
fn get_readwrite_options() -> &'static Arc<fs::OpenOptions> {
|
|
READWRITE_OPTIONS.get_or_init(|| {
|
|
let mut opts = fs::OpenOptions::new();
|
|
opts.read(true).write(true);
|
|
Arc::new(opts)
|
|
})
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
pub fn same_file(f1: &Metadata, f2: &Metadata) -> bool {
|
|
use std::os::unix::fs::MetadataExt;
|
|
|
|
if f1.dev() != f2.dev() {
|
|
return false;
|
|
}
|
|
|
|
if f1.ino() != f2.ino() {
|
|
return false;
|
|
}
|
|
|
|
if f1.size() != f2.size() {
|
|
return false;
|
|
}
|
|
if f1.permissions() != f2.permissions() {
|
|
return false;
|
|
}
|
|
|
|
if f1.mtime() != f2.mtime() {
|
|
return false;
|
|
}
|
|
|
|
true
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
pub fn same_file(f1: &Metadata, f2: &Metadata) -> bool {
|
|
if f1.permissions() != f2.permissions() {
|
|
return false;
|
|
}
|
|
|
|
if f1.file_type() != f2.file_type() {
|
|
return false;
|
|
}
|
|
|
|
if f1.len() != f2.len() {
|
|
return false;
|
|
}
|
|
true
|
|
}
|
|
|
|
type FileMode = usize;
|
|
|
|
pub const O_RDONLY: FileMode = 0x00000;
|
|
pub const O_WRONLY: FileMode = 0x00001;
|
|
pub const O_RDWR: FileMode = 0x00002;
|
|
pub const O_CREATE: FileMode = 0x00040;
|
|
// pub const O_EXCL: FileMode = 0x00080;
|
|
// pub const O_NOCTTY: FileMode = 0x00100;
|
|
pub const O_TRUNC: FileMode = 0x00200;
|
|
// pub const O_NONBLOCK: FileMode = 0x00800;
|
|
pub const O_APPEND: FileMode = 0x00400;
|
|
// pub const O_SYNC: FileMode = 0x01000;
|
|
// pub const O_ASYNC: FileMode = 0x02000;
|
|
// pub const O_CLOEXEC: FileMode = 0x80000;
|
|
|
|
// read: bool,
|
|
// write: bool,
|
|
// append: bool,
|
|
// truncate: bool,
|
|
// create: bool,
|
|
// create_new: bool,
|
|
|
|
pub async fn open_file(path: impl AsRef<Path>, mode: FileMode) -> io::Result<File> {
|
|
let base_opts = match mode & (O_RDONLY | O_WRONLY | O_RDWR) {
|
|
O_RDONLY => get_readonly_options(),
|
|
O_WRONLY => get_writeonly_options(),
|
|
O_RDWR => get_readwrite_options(),
|
|
_ => get_readonly_options(),
|
|
};
|
|
|
|
if (mode & (O_CREATE | O_APPEND | O_TRUNC)) != 0 {
|
|
let mut opts = (**base_opts).clone();
|
|
if mode & O_CREATE != 0 {
|
|
opts.create(true);
|
|
}
|
|
if mode & O_APPEND != 0 {
|
|
opts.append(true);
|
|
}
|
|
if mode & O_TRUNC != 0 {
|
|
opts.truncate(true);
|
|
}
|
|
opts.open(path.as_ref()).await
|
|
} else {
|
|
base_opts.open(path.as_ref()).await
|
|
}
|
|
}
|
|
|
|
pub async fn access(path: impl AsRef<Path>) -> io::Result<()> {
|
|
fs::metadata(path).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn access_std(path: impl AsRef<Path>) -> io::Result<()> {
|
|
std::fs::metadata(path)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn lstat(path: impl AsRef<Path>) -> io::Result<Metadata> {
|
|
fs::symlink_metadata(path).await
|
|
}
|
|
|
|
pub fn lstat_std(path: impl AsRef<Path>) -> io::Result<Metadata> {
|
|
std::fs::symlink_metadata(path)
|
|
}
|
|
|
|
pub async fn make_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
|
|
fs::create_dir_all(path.as_ref()).await
|
|
}
|
|
|
|
fn is_dir_error(e: &io::Error) -> bool {
|
|
e.raw_os_error() == Some(libc::EISDIR)
|
|
|| e.kind() == io::ErrorKind::IsADirectory
|
|
// macOS: remove_file on a directory returns EPERM
|
|
|| (cfg!(target_os = "macos") && e.raw_os_error() == Some(libc::EPERM))
|
|
}
|
|
|
|
#[tracing::instrument(level = "debug", skip_all)]
|
|
pub async fn remove(path: impl AsRef<Path>) -> io::Result<()> {
|
|
// Try remove_file first; fall back to remove_dir if it's a directory
|
|
match fs::remove_file(path.as_ref()).await {
|
|
Ok(()) => Ok(()),
|
|
Err(e) if is_dir_error(&e) => fs::remove_dir(path.as_ref()).await,
|
|
Err(e) => Err(e),
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code, reason = "asserted by this file's tests (backlog#1823)")]
|
|
pub async fn remove_all(path: impl AsRef<Path>) -> io::Result<()> {
|
|
// Try remove_file first; fall back to remove_dir_all if it's a directory
|
|
match fs::remove_file(path.as_ref()).await {
|
|
Ok(()) => Ok(()),
|
|
Err(e) if is_dir_error(&e) => fs::remove_dir_all(path.as_ref()).await,
|
|
Err(e) => Err(e),
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(level = "debug", skip_all)]
|
|
pub fn remove_std(path: impl AsRef<Path>) -> io::Result<()> {
|
|
// Try remove_file first; fall back to remove_dir if it's a directory
|
|
match std::fs::remove_file(path.as_ref()) {
|
|
Ok(()) => Ok(()),
|
|
Err(e) if is_dir_error(&e) => std::fs::remove_dir(path.as_ref()),
|
|
Err(e) => Err(e),
|
|
}
|
|
}
|
|
|
|
pub fn remove_all_std(path: impl AsRef<Path>) -> io::Result<()> {
|
|
let path = path.as_ref();
|
|
let meta = std::fs::metadata(path)?;
|
|
if meta.is_dir() {
|
|
std::fs::remove_dir_all(path)
|
|
} else {
|
|
std::fs::remove_file(path)
|
|
}
|
|
}
|
|
|
|
pub async fn mkdir(path: impl AsRef<Path>) -> io::Result<()> {
|
|
fs::create_dir(path.as_ref()).await
|
|
}
|
|
|
|
pub async fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
|
|
fs::rename(from, to).await
|
|
}
|
|
|
|
pub fn rename_std(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
|
|
std::fs::rename(from, to)
|
|
}
|
|
|
|
#[tracing::instrument(level = "debug", skip_all)]
|
|
pub async fn read_file(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
|
|
fs::read(path.as_ref()).await
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use tempfile::TempDir;
|
|
use tokio::io::AsyncWriteExt;
|
|
|
|
#[tokio::test]
|
|
async fn test_file_mode_constants() {
|
|
assert_eq!(O_RDONLY, 0x00000);
|
|
assert_eq!(O_WRONLY, 0x00001);
|
|
assert_eq!(O_RDWR, 0x00002);
|
|
assert_eq!(O_CREATE, 0x00040);
|
|
assert_eq!(O_TRUNC, 0x00200);
|
|
assert_eq!(O_APPEND, 0x00400);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_open_file_read_only() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_readonly.txt");
|
|
|
|
// Create a test file
|
|
tokio::fs::write(&file_path, b"test content").await.unwrap();
|
|
|
|
// Test opening in read-only mode
|
|
let file = open_file(&file_path, O_RDONLY).await;
|
|
assert!(file.is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_open_file_write_only() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_writeonly.txt");
|
|
|
|
// Test opening in write-only mode with create flag
|
|
let mut file = open_file(&file_path, O_WRONLY | O_CREATE).await.unwrap();
|
|
|
|
// Should be able to write
|
|
file.write_all(b"write test").await.unwrap();
|
|
file.flush().await.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_open_file_read_write() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_readwrite.txt");
|
|
|
|
// Test opening in read-write mode with create flag
|
|
let mut file = open_file(&file_path, O_RDWR | O_CREATE).await.unwrap();
|
|
|
|
// Should be able to write and read
|
|
file.write_all(b"read-write test").await.unwrap();
|
|
file.flush().await.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_open_file_append() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_append.txt");
|
|
|
|
// Create initial content
|
|
tokio::fs::write(&file_path, b"initial").await.unwrap();
|
|
|
|
// Open in append mode
|
|
let mut file = open_file(&file_path, O_WRONLY | O_APPEND).await.unwrap();
|
|
file.write_all(b" appended").await.unwrap();
|
|
file.flush().await.unwrap();
|
|
|
|
// Verify content
|
|
let content = tokio::fs::read_to_string(&file_path).await.unwrap();
|
|
assert_eq!(content, "initial appended");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_open_file_truncate() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_truncate.txt");
|
|
|
|
// Create initial content
|
|
tokio::fs::write(&file_path, b"initial content").await.unwrap();
|
|
|
|
// Open with truncate flag
|
|
let mut file = open_file(&file_path, O_WRONLY | O_TRUNC).await.unwrap();
|
|
file.write_all(b"new").await.unwrap();
|
|
file.flush().await.unwrap();
|
|
|
|
// Verify content was truncated
|
|
let content = tokio::fs::read_to_string(&file_path).await.unwrap();
|
|
assert_eq!(content, "new");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_access() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_access.txt");
|
|
|
|
// Should fail for non-existent file
|
|
assert!(access(&file_path).await.is_err());
|
|
|
|
// Create file and test again
|
|
tokio::fs::write(&file_path, b"test").await.unwrap();
|
|
assert!(access(&file_path).await.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_access_std() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_access_std.txt");
|
|
|
|
// Should fail for non-existent file
|
|
assert!(access_std(&file_path).is_err());
|
|
|
|
// Create file and test again
|
|
std::fs::write(&file_path, b"test").unwrap();
|
|
assert!(access_std(&file_path).is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_lstat() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_lstat.txt");
|
|
|
|
// Create test file
|
|
tokio::fs::write(&file_path, b"test content").await.unwrap();
|
|
|
|
// Test lstat
|
|
let metadata = lstat(&file_path).await.unwrap();
|
|
assert!(metadata.is_file());
|
|
assert_eq!(metadata.len(), 12); // "test content" is 12 bytes
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[tokio::test]
|
|
async fn test_lstat_preserves_symlink_metadata() {
|
|
use std::os::unix::fs::symlink;
|
|
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let target_path = temp_dir.path().join("target.txt");
|
|
let link_path = temp_dir.path().join("link.txt");
|
|
|
|
tokio::fs::write(&target_path, b"test content").await.unwrap();
|
|
symlink(&target_path, &link_path).unwrap();
|
|
|
|
let metadata = lstat(&link_path).await.unwrap();
|
|
assert!(metadata.file_type().is_symlink());
|
|
}
|
|
|
|
#[test]
|
|
fn test_lstat_std() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_lstat_std.txt");
|
|
|
|
// Create test file
|
|
std::fs::write(&file_path, b"test content").unwrap();
|
|
|
|
// Test lstat_std
|
|
let metadata = lstat_std(&file_path).unwrap();
|
|
assert!(metadata.is_file());
|
|
assert_eq!(metadata.len(), 12); // "test content" is 12 bytes
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
fn test_lstat_std_preserves_symlink_metadata() {
|
|
use std::os::unix::fs::symlink;
|
|
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let target_path = temp_dir.path().join("target-std.txt");
|
|
let link_path = temp_dir.path().join("link-std.txt");
|
|
|
|
std::fs::write(&target_path, b"test content").unwrap();
|
|
symlink(&target_path, &link_path).unwrap();
|
|
|
|
let metadata = lstat_std(&link_path).unwrap();
|
|
assert!(metadata.file_type().is_symlink());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_make_dir_all() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let nested_path = temp_dir.path().join("level1").join("level2").join("level3");
|
|
|
|
// Should create nested directories
|
|
assert!(make_dir_all(&nested_path).await.is_ok());
|
|
assert!(nested_path.exists());
|
|
assert!(nested_path.is_dir());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_remove_file() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_remove.txt");
|
|
|
|
// Create test file
|
|
tokio::fs::write(&file_path, b"test").await.unwrap();
|
|
assert!(file_path.exists());
|
|
|
|
// Remove file
|
|
assert!(remove(&file_path).await.is_ok());
|
|
assert!(!file_path.exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_remove_directory() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let dir_path = temp_dir.path().join("test_remove_dir");
|
|
|
|
// Create test directory
|
|
tokio::fs::create_dir(&dir_path).await.unwrap();
|
|
assert!(dir_path.exists());
|
|
|
|
// Remove directory
|
|
assert!(remove(&dir_path).await.is_ok());
|
|
assert!(!dir_path.exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_remove_all() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let dir_path = temp_dir.path().join("test_remove_all");
|
|
let file_path = dir_path.join("nested_file.txt");
|
|
|
|
// Create nested structure
|
|
tokio::fs::create_dir(&dir_path).await.unwrap();
|
|
tokio::fs::write(&file_path, b"nested content").await.unwrap();
|
|
|
|
// Remove all
|
|
assert!(remove_all(&dir_path).await.is_ok());
|
|
assert!(!dir_path.exists());
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_std() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_remove_std.txt");
|
|
|
|
// Create test file
|
|
std::fs::write(&file_path, b"test").unwrap();
|
|
assert!(file_path.exists());
|
|
|
|
// Remove file
|
|
assert!(remove_std(&file_path).is_ok());
|
|
assert!(!file_path.exists());
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_all_std() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let dir_path = temp_dir.path().join("test_remove_all_std");
|
|
let file_path = dir_path.join("nested_file.txt");
|
|
|
|
// Create nested structure
|
|
std::fs::create_dir(&dir_path).unwrap();
|
|
std::fs::write(&file_path, b"nested content").unwrap();
|
|
|
|
// Remove all
|
|
assert!(remove_all_std(&dir_path).is_ok());
|
|
assert!(!dir_path.exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_mkdir() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let dir_path = temp_dir.path().join("test_mkdir");
|
|
|
|
// Create directory
|
|
assert!(mkdir(&dir_path).await.is_ok());
|
|
assert!(dir_path.exists());
|
|
assert!(dir_path.is_dir());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_rename() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let old_path = temp_dir.path().join("old_name.txt");
|
|
let new_path = temp_dir.path().join("new_name.txt");
|
|
|
|
// Create test file
|
|
tokio::fs::write(&old_path, b"test content").await.unwrap();
|
|
assert!(old_path.exists());
|
|
assert!(!new_path.exists());
|
|
|
|
// Rename file
|
|
assert!(rename(&old_path, &new_path).await.is_ok());
|
|
assert!(!old_path.exists());
|
|
assert!(new_path.exists());
|
|
|
|
// Verify content preserved
|
|
let content = tokio::fs::read_to_string(&new_path).await.unwrap();
|
|
assert_eq!(content, "test content");
|
|
}
|
|
|
|
#[test]
|
|
fn test_rename_std() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let old_path = temp_dir.path().join("old_name_std.txt");
|
|
let new_path = temp_dir.path().join("new_name_std.txt");
|
|
|
|
// Create test file
|
|
std::fs::write(&old_path, b"test content").unwrap();
|
|
assert!(old_path.exists());
|
|
assert!(!new_path.exists());
|
|
|
|
// Rename file
|
|
assert!(rename_std(&old_path, &new_path).is_ok());
|
|
assert!(!old_path.exists());
|
|
assert!(new_path.exists());
|
|
|
|
// Verify content preserved
|
|
let content = std::fs::read_to_string(&new_path).unwrap();
|
|
assert_eq!(content, "test content");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_read_file() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_read.txt");
|
|
|
|
let test_content = b"This is test content for reading";
|
|
tokio::fs::write(&file_path, test_content).await.unwrap();
|
|
|
|
// Read file
|
|
let read_content = read_file(&file_path).await.unwrap();
|
|
assert_eq!(read_content, test_content);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_read_file_nonexistent() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("nonexistent.txt");
|
|
|
|
// Should fail for non-existent file
|
|
assert!(read_file(&file_path).await.is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_same_file() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file_path = temp_dir.path().join("test_same.txt");
|
|
|
|
// Create test file
|
|
tokio::fs::write(&file_path, b"test content").await.unwrap();
|
|
|
|
// Get metadata twice
|
|
let metadata1 = tokio::fs::metadata(&file_path).await.unwrap();
|
|
let metadata2 = tokio::fs::metadata(&file_path).await.unwrap();
|
|
|
|
// Should be the same file
|
|
assert!(same_file(&metadata1, &metadata2));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_different_files() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let file1_path = temp_dir.path().join("file1.txt");
|
|
let file2_path = temp_dir.path().join("file2.txt");
|
|
|
|
// Create two different files
|
|
tokio::fs::write(&file1_path, b"content1").await.unwrap();
|
|
tokio::fs::write(&file2_path, b"different content").await.unwrap();
|
|
|
|
// Get metadata
|
|
let metadata1 = tokio::fs::metadata(&file1_path).await.unwrap();
|
|
let metadata2 = tokio::fs::metadata(&file2_path).await.unwrap();
|
|
|
|
// Should be different files
|
|
assert!(!same_file(&metadata1, &metadata2));
|
|
}
|
|
}
|