use std::fs;
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime};
use anyhow::{Context, Result};
use tempfile::TempDir;
pub struct FileTestUtils;
impl FileTestUtils {
pub fn create_test_file(content: &str) -> Result<(TempDir, PathBuf)> {
let temp_dir = TempDir::new().context("Failed to create temporary directory")?;
let file_path = temp_dir.path().join("test_file.txt");
fs::write(&file_path, content)
.context("Failed to write test file")?;
Ok((temp_dir, file_path))
}
pub fn create_binary_test_file(data: &[u8]) -> Result<(TempDir, PathBuf)> {
let temp_dir = TempDir::new().context("Failed to create temporary directory")?;
let file_path = temp_dir.path().join("test_binary.bin");
fs::write(&file_path, data)
.context("Failed to write binary test file")?;
Ok((temp_dir, file_path))
}
pub fn create_large_test_file(size_mb: usize) -> Result<(TempDir, PathBuf)> {
let temp_dir = TempDir::new().context("Failed to create temporary directory")?;
let file_path = temp_dir.path().join("large_test_file.dat");
let chunk_size = 1024 * 1024; let chunk = vec![0u8; chunk_size];
let mut file = fs::File::create(&file_path)
.context("Failed to create large test file")?;
use std::io::Write;
for _ in 0..size_mb {
file.write_all(&chunk)
.context("Failed to write chunk to large test file")?;
}
Ok((temp_dir, file_path))
}
pub fn verify_file_content(path: &Path, expected: &str) -> Result<bool> {
let content = fs::read_to_string(path)
.context("Failed to read file for verification")?;
Ok(content == expected)
}
pub fn verify_binary_file_content(path: &Path, expected: &[u8]) -> Result<bool> {
let content = fs::read(path)
.context("Failed to read binary file for verification")?;
Ok(content == expected)
}
pub fn get_file_size(path: &Path) -> Result<u64> {
let metadata = fs::metadata(path)
.context("Failed to get file metadata")?;
Ok(metadata.len())
}
pub fn file_exists(path: &Path) -> bool {
path.exists() && path.is_file()
}
}
pub struct PerformanceUtils;
impl PerformanceUtils {
pub async fn measure_async<F, T>(operation: F) -> (T, Duration)
where
F: std::future::Future<Output = T>,
{
let start = SystemTime::now();
let result = operation.await;
let duration = start.elapsed().unwrap_or(Duration::ZERO);
(result, duration)
}
pub fn measure_sync<F, T>(operation: F) -> (T, Duration)
where
F: FnOnce() -> T,
{
let start = SystemTime::now();
let result = operation();
let duration = start.elapsed().unwrap_or(Duration::ZERO);
(result, duration)
}
pub fn calculate_throughput_mbps(bytes: u64, duration: Duration) -> f64 {
if duration.is_zero() {
return 0.0;
}
let mb = bytes as f64 / (1024.0 * 1024.0);
let seconds = duration.as_secs_f64();
mb / seconds
}
pub fn format_duration(duration: Duration) -> String {
let millis = duration.as_millis();
if millis < 1000 {
format!("{}ms", millis)
} else {
format!("{:.2}s", duration.as_secs_f64())
}
}
}
pub struct TestAssertions;
impl TestAssertions {
pub fn assert_command_success(output: &crate::integration::CommandOutput, context: &str) -> Result<()> {
if !output.success() {
anyhow::bail!(
"{} failed with exit code {}\nstdout: {}\nstderr: {}",
context,
output.exit_code,
output.stdout,
output.stderr
);
}
Ok(())
}
pub fn assert_ssh_success(output: &crate::integration::SshCommandOutput, context: &str) -> Result<()> {
if !output.success() {
anyhow::bail!(
"SSH {} failed with exit code {}\nstdout: {}\nstderr: {}",
context,
output.exit_code,
output.stdout,
output.stderr
);
}
Ok(())
}
pub fn assert_output_contains(output: &str, expected: &str, context: &str) -> Result<()> {
if !output.contains(expected) {
anyhow::bail!(
"{}: Expected output to contain '{}', but got: {}",
context,
expected,
output
);
}
Ok(())
}
pub fn assert_performance_threshold(
actual: Duration,
threshold: Duration,
operation: &str,
) -> Result<()> {
if actual > threshold {
anyhow::bail!(
"{} took {} but threshold is {}",
operation,
PerformanceUtils::format_duration(actual),
PerformanceUtils::format_duration(threshold)
);
}
Ok(())
}
pub fn assert_throughput_threshold(
actual_mbps: f64,
min_mbps: f64,
operation: &str,
) -> Result<()> {
if actual_mbps < min_mbps {
anyhow::bail!(
"{} achieved {:.2} MB/s but minimum is {:.2} MB/s",
operation,
actual_mbps,
min_mbps
);
}
Ok(())
}
}
pub struct EnvUtils;
impl EnvUtils {
pub fn check_docker_available() -> Result<()> {
use std::process::Command;
let output = Command::new("docker")
.args(&["--version"])
.output()
.context("Failed to check Docker availability")?;
if !output.status.success() {
anyhow::bail!("Docker is not available or not running");
}
Ok(())
}
pub fn check_docker_compose_available() -> Result<()> {
use std::process::Command;
let output = Command::new("docker-compose")
.args(&["--version"])
.output()
.context("Failed to check docker-compose availability")?;
if !output.status.success() {
anyhow::bail!("docker-compose is not available");
}
Ok(())
}
pub fn check_ssh_keys_exist(key_path: &str) -> Result<()> {
let private_key = Path::new(key_path);
let public_key_path = format!("{}.pub", key_path);
let public_key = Path::new(&public_key_path);
if !private_key.exists() {
anyhow::bail!("SSH private key not found: {}", key_path);
}
if !public_key.exists() {
anyhow::bail!("SSH public key not found: {}.pub", key_path);
}
Ok(())
}
pub fn setup_test_environment() -> Result<()> {
println!("Checking test environment prerequisites...");
Self::check_docker_available()
.context("Docker check failed")?;
Self::check_docker_compose_available()
.context("Docker Compose check failed")?;
Self::check_ssh_keys_exist("docker/ssh_keys/test_key")
.context("SSH keys check failed")?;
println!("✅ Test environment prerequisites satisfied");
Ok(())
}
}
pub struct TestDataGenerator;
impl TestDataGenerator {
pub fn random_text(size: usize) -> String {
use rand::Rng;
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789\n\t ";
let mut rng = rand::thread_rng();
(0..size)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect()
}
pub fn random_binary(size: usize) -> Vec<u8> {
use rand::Rng;
let mut rng = rand::thread_rng();
(0..size).map(|_| rng.gen()).collect()
}
pub fn structured_json_data(records: usize) -> String {
let mut data = String::from("[\n");
for i in 0..records {
if i > 0 {
data.push_str(",\n");
}
data.push_str(&format!(
r#" {{"id": {}, "name": "test_record_{}", "value": {}}}"#,
i, i, i * 42
));
}
data.push_str("\n]");
data
}
}