use std::sync::Once;
pub mod helpers;
#[cfg(test)]
mod test_config {
use chrono::Duration;
pub struct TestConfig {
pub use_external_services: bool,
pub redis_host: String,
pub redis_port: u16,
pub minio_endpoint: String,
pub minio_bucket: String,
pub minio_access_key: String,
pub minio_secret_key: String,
pub request_timeout: Duration,
}
impl TestConfig {
pub fn from_env() -> Self {
Self {
use_external_services: std::env::var("USE_EXTERNAL_SERVICES")
.unwrap_or_else(|_| "false".to_string())
== "true",
redis_host: std::env::var("REDIS_HOST").unwrap_or_else(|_| "localhost".to_string()),
redis_port: std::env::var("REDIS_PORT")
.unwrap_or_else(|_| "6379".to_string())
.parse()
.unwrap_or(6379),
minio_endpoint: std::env::var("MINIO_ENDPOINT")
.unwrap_or_else(|_| "localhost:9000".to_string()),
minio_bucket: std::env::var("MINIO_BUCKET")
.unwrap_or_else(|_| "test-bucket".to_string()),
minio_access_key: std::env::var("MINIO_ACCESS_KEY")
.unwrap_or_else(|_| "minioadmin".to_string()),
minio_secret_key: std::env::var("MINIO_SECRET_KEY")
.unwrap_or_else(|_| "minioadmin".to_string()),
request_timeout: Duration::seconds(30),
}
}
}
}
#[cfg(test)]
pub use test_config::TestConfig;
pub mod integration;
pub mod unit;
#[cfg(feature = "cli")]
pub mod cli;
static INIT: Once = Once::new();
pub fn init_test_env() {
INIT.call_once(|| {
if std::env::var("TEST_LOG").is_ok() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug"))
.init();
}
unsafe {
std::env::set_var("RUST_BACKTRACE", "1");
std::env::set_var("ENVIRONMENT", "test");
}
});
}
pub type TestResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
pub mod timing {
use std::time::{Duration, Instant};
use tokio::time::sleep;
pub async fn wait_for_condition<F, Fut>(
mut condition: F,
timeout: Duration,
check_interval: Duration,
) -> bool
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = bool>,
{
let start = Instant::now();
while start.elapsed() < timeout {
if condition().await {
return true;
}
sleep(check_interval).await;
}
false
}
pub const SERVICE_STARTUP_TIMEOUT: Duration = Duration::from_secs(60);
pub const HEALTH_CHECK_TIMEOUT: Duration = Duration::from_secs(30);
pub const TEST_OPERATION_TIMEOUT: Duration = Duration::from_secs(10);
pub const CHECK_INTERVAL: Duration = Duration::from_millis(500);
}
pub mod generators {
use std::path::PathBuf;
pub fn test_file_path(name: &str) -> PathBuf {
PathBuf::from(format!("test-{}", name))
}
pub fn test_content(size: usize) -> String {
format!(
"Test content with {} characters: {}",
size,
"x".repeat(size.saturating_sub(40))
)
}
pub fn test_binary_data(size: usize) -> Vec<u8> {
(0..size).map(|i| (i % 256) as u8).collect()
}
}
pub mod assertions {
use crate::TestResult;
pub async fn assert_timeout<F, T>(
future: F,
timeout: std::time::Duration,
message: &str,
) -> TestResult
where
F: std::future::Future<Output = T>,
{
match tokio::time::timeout(timeout, future).await {
Ok(_) => Ok(()),
Err(_) => Err(format!("Timeout: {}", message).into()),
}
}
pub async fn assert_services_healthy(
redis_host: &str,
redis_port: u16,
minio_endpoint: &str,
) -> TestResult {
if !crate::integration::TestEnvironment::check_service_availability(redis_host, redis_port)
{
return Err(format!("Redis not available at {}:{}", redis_host, redis_port).into());
}
let (minio_host, minio_port) =
crate::integration::TestEnvironment::parse_endpoint(minio_endpoint);
if !crate::integration::TestEnvironment::check_service_availability(&minio_host, minio_port)
{
return Err(format!("MinIO not available at {}", minio_endpoint).into());
}
Ok(())
}
}
pub mod parallel {
use crate::TestResult;
use futures::future::try_join_all;
pub async fn run_parallel_tests<F, Fut>(
tests: Vec<(&str, F)>,
max_concurrency: usize,
) -> TestResult
where
F: FnOnce() -> Fut + Send + 'static,
Fut: std::future::Future<Output = TestResult> + Send,
{
let semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new(max_concurrency));
let tasks: Vec<_> = tests
.into_iter()
.map(|(name, test)| {
let semaphore = semaphore.clone();
let name = name.to_string();
tokio::spawn(async move {
let _permit = semaphore.acquire().await.unwrap();
println!("๐งช Running test: {}", name);
match test().await {
Ok(_) => {
println!("โ
Test passed: {}", name);
Ok(())
}
Err(e) => {
println!("โ Test failed: {} - {}", name, e);
Err(e)
}
}
})
})
.collect();
let results: Result<Vec<_>, _> = try_join_all(tasks).await;
match results {
Ok(test_results) => {
for result in test_results {
result?;
}
Ok(())
}
Err(e) => Err(format!("Parallel test execution failed: {}", e).into()),
}
}
}
pub mod performance {
use std::time::{Duration, Instant};
pub struct PerfMeasurement {
start: Instant,
operation: String,
}
impl PerfMeasurement {
pub fn start(operation: &str) -> Self {
Self {
start: Instant::now(),
operation: operation.to_string(),
}
}
pub fn finish(self) -> Duration {
let duration = self.start.elapsed();
println!("โฑ๏ธ {}: {:?}", self.operation, duration);
duration
}
}
pub async fn benchmark<F, Fut, T>(
name: &str,
iterations: usize,
mut operation: F,
) -> (Duration, Vec<T>)
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = T>,
{
let start = Instant::now();
let mut results = Vec::with_capacity(iterations);
for _ in 0..iterations {
results.push(operation().await);
}
let total_duration = start.elapsed();
let avg_duration = total_duration / iterations as u32;
println!(
"๐ Benchmark {}: {} iterations, total: {:?}, avg: {:?}",
name, iterations, total_duration, avg_duration
);
(total_duration, results)
}
}