#![allow(dead_code)]
use std::path::Path;
use std::time::SystemTime;
#[derive(Debug, Clone, PartialEq)]
pub struct FileMetadata {
pub modified: SystemTime,
pub size: u64,
}
impl FileMetadata {
pub fn new(modified: SystemTime, size: u64) -> Self {
Self { modified, size }
}
pub fn from_std_metadata(metadata: &std::fs::Metadata) -> Result<Self, std::io::Error> {
Ok(Self {
modified: metadata.modified()?,
size: metadata.len(),
})
}
}
#[cfg_attr(test, mockall::automock)]
pub trait FileSystemTrait: Clone + Send + Sync {
fn exists(&self, path: &Path) -> bool;
fn read(&self, path: &Path) -> Result<Vec<u8>, std::io::Error>;
fn metadata(&self, path: &Path) -> Result<FileMetadata, std::io::Error>;
fn read_dir(&self, path: &Path) -> Result<Vec<std::path::PathBuf>, std::io::Error>;
}
#[derive(Debug, Clone)]
pub struct RealFileSystem;
impl FileSystemTrait for RealFileSystem {
fn exists(&self, path: &Path) -> bool {
path.exists()
}
fn read(&self, path: &Path) -> Result<Vec<u8>, std::io::Error> {
std::fs::read(path)
}
fn metadata(&self, path: &Path) -> Result<FileMetadata, std::io::Error> {
let metadata = std::fs::metadata(path)?;
FileMetadata::from_std_metadata(&metadata)
}
fn read_dir(&self, path: &Path) -> Result<Vec<std::path::PathBuf>, std::io::Error> {
let entries = std::fs::read_dir(path)?;
let mut paths = Vec::new();
for entry in entries {
let entry = entry?;
paths.push(entry.path());
}
Ok(paths)
}
}
#[cfg(test)]
mod test_filesystem {
use super::*;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
type TestFileData = HashMap<PathBuf, (Vec<u8>, SystemTime)>;
#[derive(Clone)]
pub struct TestFileSystem {
state: Arc<Mutex<TestFileData>>,
}
impl TestFileSystem {
pub fn new() -> Self {
Self {
state: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn set_file_content<P: Into<PathBuf>>(
&self,
path: P,
content: &str,
modified: SystemTime,
) {
let mut state = self.state.lock().unwrap();
state.insert(path.into(), (content.as_bytes().to_vec(), modified));
}
pub fn update_file_content<P: AsRef<Path>>(
&self,
path: P,
content: &str,
modified: SystemTime,
) {
let mut state = self.state.lock().unwrap();
let path_buf = path.as_ref().to_path_buf();
state.insert(path_buf, (content.as_bytes().to_vec(), modified));
}
}
impl FileSystemTrait for TestFileSystem {
fn exists(&self, path: &Path) -> bool {
let state = self.state.lock().unwrap();
if state.contains_key(path) {
return true;
}
for file_path in state.keys() {
if let Some(parent) = file_path.parent()
&& parent == path
{
return true;
}
}
false
}
fn read(&self, path: &Path) -> Result<Vec<u8>, std::io::Error> {
let state = self.state.lock().unwrap();
state
.get(path)
.map(|(content, _)| content.clone())
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "File not found"))
}
fn metadata(&self, path: &Path) -> Result<FileMetadata, std::io::Error> {
let state = self.state.lock().unwrap();
state
.get(path)
.map(|(content, modified)| FileMetadata {
modified: *modified,
size: content.len() as u64,
})
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "File not found"))
}
fn read_dir(&self, path: &Path) -> Result<Vec<std::path::PathBuf>, std::io::Error> {
let state = self.state.lock().unwrap();
let mut entries = Vec::new();
for file_path in state.keys() {
if let Some(parent) = file_path.parent()
&& parent == path
{
entries.push(file_path.clone());
}
}
Ok(entries)
}
}
}
#[cfg(test)]
pub use test_filesystem::TestFileSystem;
#[cfg(test)]
impl Clone for MockFileSystemTrait {
fn clone(&self) -> Self {
MockFileSystemTrait::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use std::time::{Duration, UNIX_EPOCH};
#[test]
fn test_file_metadata_creation() {
let time = UNIX_EPOCH + Duration::from_secs(1000);
let metadata = FileMetadata::new(time, 42);
assert_eq!(metadata.modified, time);
assert_eq!(metadata.size, 42);
}
#[test]
fn test_test_filesystem_basic_operations() {
let fs = TestFileSystem::new();
let path = PathBuf::from("/test/file.txt");
let content = "Hello, world!";
let time = UNIX_EPOCH + Duration::from_secs(1000);
assert!(!fs.exists(&path));
fs.set_file_content(&path, content, time);
assert!(fs.exists(&path));
let read_content = fs.read(&path).unwrap();
assert_eq!(read_content, content.as_bytes());
let metadata = fs.metadata(&path).unwrap();
assert_eq!(metadata.modified, time);
assert_eq!(metadata.size, content.len() as u64);
}
#[test]
fn test_test_filesystem_update_content() {
let fs = TestFileSystem::new();
let path = PathBuf::from("/test/file.txt");
let time1 = UNIX_EPOCH + Duration::from_secs(1000);
let time2 = UNIX_EPOCH + Duration::from_secs(2000);
fs.set_file_content(&path, "Initial", time1);
let metadata1 = fs.metadata(&path).unwrap();
assert_eq!(metadata1.modified, time1);
assert_eq!(metadata1.size, 7);
fs.update_file_content(&path, "Updated content", time2);
let metadata2 = fs.metadata(&path).unwrap();
assert_eq!(metadata2.modified, time2);
assert_eq!(metadata2.size, 15);
let content = fs.read(&path).unwrap();
assert_eq!(content, b"Updated content");
}
#[test]
fn test_test_filesystem_cloning() {
let fs1 = TestFileSystem::new();
let path = PathBuf::from("/test/clone.txt");
let time = UNIX_EPOCH + Duration::from_secs(1000);
fs1.set_file_content(&path, "Shared content", time);
let fs2 = fs1.clone();
assert!(fs1.exists(&path));
assert!(fs2.exists(&path));
let content1 = fs1.read(&path).unwrap();
let content2 = fs2.read(&path).unwrap();
assert_eq!(content1, content2);
fs2.update_file_content(&path, "Modified", time);
let updated_content = fs1.read(&path).unwrap();
assert_eq!(updated_content, b"Modified");
}
#[test]
fn test_real_filesystem_trait_implementation() {
let fs = RealFileSystem;
let _cloned = fs.clone();
let non_existent = PathBuf::from("/definitely/does/not/exist");
assert!(!fs.exists(&non_existent));
let result = fs.read(&non_existent);
assert!(result.is_err());
let metadata_result = fs.metadata(&non_existent);
assert!(metadata_result.is_err());
}
}