use std::fs::{Metadata, ReadDir};
use std::io;
use std::path::Path;
use std::process::{Command, ExitStatus, Output};
pub trait FileSystem {
fn copy(&self, from: &Path, to: &Path) -> io::Result<u64>;
fn create_dir_all(&self, path: &Path) -> io::Result<()>;
fn read_dir(&self, path: &Path) -> io::Result<ReadDir>;
fn metadata(&self, path: &Path) -> io::Result<Metadata>;
fn read_to_string(&self, path: &Path) -> io::Result<String>;
fn write(&self, path: &Path, contents: impl AsRef<[u8]>) -> io::Result<()>;
}
#[derive(Clone, Copy)]
pub struct RealFileSystem;
impl FileSystem for RealFileSystem {
fn copy(&self, from: &Path, to: &Path) -> io::Result<u64> {
std::fs::copy(from, to)
}
fn create_dir_all(&self, path: &Path) -> io::Result<()> {
std::fs::create_dir_all(path)
}
fn read_dir(&self, path: &Path) -> io::Result<ReadDir> {
std::fs::read_dir(path)
}
fn metadata(&self, path: &Path) -> io::Result<Metadata> {
std::fs::metadata(path)
}
fn read_to_string(&self, path: &Path) -> io::Result<String> {
std::fs::read_to_string(path)
}
fn write(&self, path: &Path, contents: impl AsRef<[u8]>) -> io::Result<()> {
std::fs::write(path, contents)
}
}
pub trait CommandExecutor {
fn status(&self, cmd: &mut Command) -> io::Result<ExitStatus>;
fn output(&self, cmd: &mut Command) -> io::Result<Output>;
fn execute<F>(&self, builder: F, program: &str) -> io::Result<Output>
where
F: FnOnce(&mut Command) -> &mut Command,
{
let mut cmd = Command::new(program);
builder(&mut cmd);
self.output(&mut cmd)
}
fn run<F>(&self, builder: F, program: &str) -> io::Result<ExitStatus>
where
F: FnOnce(&mut Command) -> &mut Command,
{
let mut cmd = Command::new(program);
builder(&mut cmd);
self.status(&mut cmd)
}
}
#[derive(Debug, Clone, Copy)]
pub struct RealCommandExecutor;
impl CommandExecutor for RealCommandExecutor {
fn status(&self, cmd: &mut Command) -> io::Result<ExitStatus> {
cmd.status()
}
fn output(&self, cmd: &mut Command) -> io::Result<Output> {
cmd.output()
}
}
#[cfg(all(test, unix))]
pub fn mock_exit_status(code: i32) -> ExitStatus {
use std::os::unix::process::ExitStatusExt;
ExitStatus::from_raw(code << 8) }
#[cfg(all(test, windows))]
pub fn mock_exit_status(code: i32) -> ExitStatus {
use std::os::windows::process::ExitStatusExt;
ExitStatus::from_raw(code as u32)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_real_filesystem_write_and_read() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let fs = RealFileSystem;
let content = b"Hello, World!";
fs.write(&file_path, content).unwrap();
let read_content = fs.read_to_string(&file_path).unwrap();
assert_eq!(read_content, "Hello, World!");
}
#[test]
fn test_real_filesystem_copy() {
let temp_dir = TempDir::new().unwrap();
let source = temp_dir.path().join("source.txt");
let dest = temp_dir.path().join("dest.txt");
let fs = RealFileSystem;
fs.write(&source, b"test content").unwrap();
let bytes_copied = fs.copy(&source, &dest).unwrap();
assert_eq!(bytes_copied, 12);
let dest_content = fs.read_to_string(&dest).unwrap();
assert_eq!(dest_content, "test content");
}
#[test]
fn test_real_filesystem_create_dir_all() {
let temp_dir = TempDir::new().unwrap();
let nested_path = temp_dir.path().join("a").join("b").join("c");
let fs = RealFileSystem;
fs.create_dir_all(&nested_path).unwrap();
assert!(nested_path.exists());
assert!(nested_path.is_dir());
}
#[test]
fn test_real_filesystem_metadata() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let fs = RealFileSystem;
fs.write(&file_path, b"content").unwrap();
let metadata = fs.metadata(&file_path).unwrap();
assert!(metadata.is_file());
assert_eq!(metadata.len(), 7); }
#[test]
fn test_real_filesystem_read_dir() {
let temp_dir = TempDir::new().unwrap();
let fs = RealFileSystem;
fs.write(&temp_dir.path().join("file1.txt"), b"test1")
.unwrap();
fs.write(&temp_dir.path().join("file2.txt"), b"test2")
.unwrap();
fs.write(&temp_dir.path().join("file3.txt"), b"test3")
.unwrap();
let entries: Vec<_> = fs
.read_dir(temp_dir.path())
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(entries.len(), 3);
}
#[test]
fn test_real_filesystem_read_nonexistent_file_returns_error() {
let fs = RealFileSystem;
let result = fs.read_to_string(Path::new("/nonexistent/file.txt"));
assert!(result.is_err());
}
#[test]
fn test_real_filesystem_copy_nonexistent_file_returns_error() {
let temp_dir = TempDir::new().unwrap();
let fs = RealFileSystem;
let result = fs.copy(
Path::new("/nonexistent.txt"),
&temp_dir.path().join("dest.txt"),
);
assert!(result.is_err());
}
#[test]
fn test_real_command_executor_status_success() {
let executor = RealCommandExecutor;
let mut cmd = Command::new("echo");
cmd.arg("test");
let status = executor.status(&mut cmd).unwrap();
assert!(status.success());
}
#[test]
fn test_real_command_executor_output_captures_stdout() {
let executor = RealCommandExecutor;
let mut cmd = Command::new("echo");
cmd.arg("hello");
let output = executor.output(&mut cmd).unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("hello"));
}
#[test]
fn test_real_command_executor_execute_with_builder() {
let executor = RealCommandExecutor;
let output = executor
.execute(|cmd| cmd.arg("test_output"), "echo")
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("test_output"));
}
#[test]
fn test_real_command_executor_run_with_builder() {
let executor = RealCommandExecutor;
let status = executor.run(|cmd| cmd.arg("test_arg"), "echo").unwrap();
assert!(status.success());
}
#[test]
fn test_real_command_executor_nonexistent_command_returns_error() {
let executor = RealCommandExecutor;
let mut cmd = Command::new("nonexistent_command_xyz_123");
let result = executor.output(&mut cmd);
assert!(result.is_err());
}
#[test]
fn test_real_command_executor_failed_command_returns_non_success() {
let executor = RealCommandExecutor;
let mut cmd = Command::new("cat");
cmd.arg("/nonexistent/file/that/does/not/exist.txt");
let output = executor.output(&mut cmd).unwrap();
assert!(!output.status.success());
}
#[test]
fn test_real_filesystem_clone() {
let fs1 = RealFileSystem;
let fs2 = fs1;
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().join("test.txt");
fs1.write(&path, b"test1").unwrap();
let content = fs2.read_to_string(&path).unwrap();
assert_eq!(content, "test1");
}
#[test]
fn test_real_command_executor_clone() {
let exec1 = RealCommandExecutor;
let exec2 = exec1;
let mut cmd = Command::new("echo");
cmd.arg("test");
let status1 = exec1.status(&mut cmd).unwrap();
assert!(status1.success());
let mut cmd2 = Command::new("echo");
cmd2.arg("test");
let status2 = exec2.status(&mut cmd2).unwrap();
assert!(status2.success());
}
}