/*! # RustyTorch++ Integration Test Framework Comprehensive end-to-end integration testing framework for the entire RustyTorch++ platform using strict TDD methodology. Tests real production workloads and scenarios without mocked dependencies. ## Test Categories 1. **Complete ML Pipeline Tests**: Data loading → Training → Evaluation → Deployment 2. **Cross-Component Integration**: Component interaction validation 3. **Production Deployment**: Containerized deployment with monitoring 4. **Performance Integration**: SLA validation and scaling tests 5. **Real-world Use Cases**: BERT, vision, GPT, CLIP validation 6. **Multi-tenant System**: Resource isolation and fair scheduling 7. **Disaster Recovery**: Fault tolerance and recovery testing 8. **Security Integration**: Authentication, authorization, audit logging ## TDD Approach All tests follow strict Test-Driven Development: 1. Write failing integration tests first 2. Full end-to-end execution (no mocking) 3. Production-realistic scenarios 4. Comprehensive error testing 5. Performance regression detection ## Test Environment Requirements - CUDA-capable GPU (recommended RTX 4090/5090) - Docker for containerization tests - Redis for caching tests - PostgreSQL for metadata storage tests - At least 32GB RAM for memory-intensive tests - Network connectivity for distributed tests ## Running Integration Tests ```bash # Run all integration tests cargo test --package rustytorch-integration-tests --features full-integration # Run specific test categories cargo run --bin ml_pipeline_tests cargo run --bin production_deployment_tests cargo run --bin performance_integration_tests # Run with specific backend RTX_BACKEND=cuda cargo test RTX_BACKEND=rocm cargo test # Performance benchmarks cargo bench --package rustytorch-integration-tests ``` ## Environment Variables - `RTX_BACKEND`: cuda|rocm|metal|cpu (default: cuda) - `RTX_DEVICE_COUNT`: Number of GPUs to use - `RTX_TEST_DATA_PATH`: Path to test datasets - `RTX_REDIS_URL`: Redis connection string - `RTX_POSTGRES_URL`: PostgreSQL connection string - `RTX_TEST_TIMEOUT`: Test timeout in seconds (default: 300) - `RTX_SKIP_GPU_TESTS`: Skip GPU-dependent tests - `RTX_PERFORMANCE_MODE`: Enable performance assertions - `RTX_CONTAINER_RUNTIME`: docker|podman (default: docker) */ #[cfg(feature = "integration-tests")] pub mod common; pub mod stubs; #[cfg(feature = "integration-tests")] pub mod pipeline; #[cfg(feature = "integration-tests")] pub mod cross_component; #[cfg(feature = "integration-tests")] pub mod production; #[cfg(feature = "integration-tests")] pub mod performance; #[cfg(feature = "integration-tests")] pub mod real_world; #[cfg(feature = "integration-tests")] pub mod multi_tenant; #[cfg(feature = "integration-tests")] pub mod disaster_recovery; #[cfg(feature = "integration-tests")] pub mod security; #[cfg(feature = "integration-tests")] pub mod chaos; // cuSOLVER integration tests - only available when CUDA feature is enabled #[cfg(all(feature = "integration-tests", feature = "cuda"))] pub mod cusolver_integration; // Re-export stubs for use in test files pub use stubs::*; // Re-export common utilities #[cfg(feature = "integration-tests")] pub use common::*; use anyhow::Result; use std::time::Duration; use tracing::{info, warn, error}; use sysinfo::SystemExt; /// Global integration test configuration #[derive(Debug, Clone)] pub struct IntegrationTestConfig { /// Backend to use for testing pub backend: Backend, /// Number of devices to use pub device_count: usize, /// Test timeout duration pub timeout: Duration, /// Path to test data pub test_data_path: std::path::PathBuf, /// Redis connection URL pub redis_url: Option, /// PostgreSQL connection URL pub postgres_url: Option, /// Skip GPU tests if true pub skip_gpu_tests: bool, /// Enable performance assertions pub performance_mode: bool, /// Container runtime (docker/podman) pub container_runtime: String, /// Maximum memory usage per test (MB) pub max_memory_mb: usize, /// Enable distributed testing pub enable_distributed: bool, } impl Default for IntegrationTestConfig { fn default() -> Self { Self { backend: Backend::from_env().unwrap_or(Backend::CUDA), device_count: std::env::var("RTX_DEVICE_COUNT") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(1), timeout: Duration::from_secs( std::env::var("RTX_TEST_TIMEOUT") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(300) ), test_data_path: std::env::var("RTX_TEST_DATA_PATH") .map(std::path::PathBuf::from) .unwrap_or_else(|_| std::path::PathBuf::from("./test_data")), redis_url: std::env::var("RTX_REDIS_URL").ok(), postgres_url: std::env::var("RTX_POSTGRES_URL").ok(), skip_gpu_tests: std::env::var("RTX_SKIP_GPU_TESTS").is_ok(), performance_mode: std::env::var("RTX_PERFORMANCE_MODE").is_ok(), container_runtime: std::env::var("RTX_CONTAINER_RUNTIME") .unwrap_or_else(|_| "docker".to_string()), max_memory_mb: std::env::var("RTX_MAX_MEMORY_MB") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(16384), // 16GB default enable_distributed: std::env::var("RTX_ENABLE_DISTRIBUTED").is_ok(), } } } /// Backend enumeration for testing #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Backend { CUDA, ROCm, Metal, CPU, } impl Backend { pub fn from_env() -> Result { match std::env::var("RTX_BACKEND").as_deref() { Ok("cuda") => Ok(Self::CUDA), Ok("rocm") => Ok(Self::ROCm), Ok("metal") => Ok(Self::Metal), Ok("cpu") => Ok(Self::CPU), Ok(other) => anyhow::bail!("Unknown backend: {other}"), Err(_) => Ok(Self::CUDA), // Default to CUDA } } pub fn as_str(&self) -> &'static str { match self { Self::CUDA => "cuda", Self::ROCm => "rocm", Self::Metal => "metal", Self::CPU => "cpu", } } pub fn supports_gpu(&self) -> bool { matches!(self, Self::CUDA | Self::ROCm | Self::Metal) } } impl std::fmt::Display for Backend { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.as_str()) } } /// Initialize integration test environment pub async fn initialize_test_environment() -> Result { // Initialize tracing tracing_subscriber::fmt() .with_env_filter("debug") .with_test_writer() .init(); let config = IntegrationTestConfig::default(); info!("Initializing integration test environment"); info!("Backend: {:?}", config.backend); info!("Device count: {}", config.device_count); info!("Timeout: {:?}", config.timeout); info!("Test data path: {:?}", config.test_data_path); info!("Skip GPU tests: {}", config.skip_gpu_tests); info!("Performance mode: {}", config.performance_mode); // Create test data directory if it doesn't exist if !config.test_data_path.exists() { std::fs::create_dir_all(&config.test_data_path)?; info!("Created test data directory: {:?}", config.test_data_path); } // Validate environment validate_test_environment(&config).await?; Ok(config) } /// Validate the test environment setup async fn validate_test_environment(config: &IntegrationTestConfig) -> Result<()> { info!("Validating test environment..."); // Check GPU availability if not skipping GPU tests if !config.skip_gpu_tests && config.backend.supports_gpu() { match config.backend { Backend::CUDA => { #[cfg(feature = "cuda")] { if let Ok(nvml) = nvml_wrapper::Nvml::init() { match nvml.device_count() { Ok(device_count) => { info!("CUDA devices available: {}", device_count); if device_count < config.device_count as u32 { warn!("Requested {} devices but only {} available", config.device_count, device_count); } } Err(e) => warn!("Could not get CUDA device count: {}", e), } } else { warn!("CUDA not available, falling back to CPU"); } } #[cfg(not(feature = "cuda"))] { warn!("CUDA feature not enabled, GPU tests will be limited"); } } Backend::ROCm => { // ROCm validation would go here warn!("ROCm validation not implemented yet"); } Backend::Metal => { // Metal validation would go here warn!("Metal validation not implemented yet"); } _ => {} } } // Check Docker availability for containerization tests if let Err(e) = tokio::process::Command::new(&config.container_runtime) .arg("--version") .output() .await { warn!("Container runtime '{}' not available: {}", config.container_runtime, e); } else { info!("Container runtime '{}' available", config.container_runtime); } // Check database connections if provided if let Some(redis_url) = &config.redis_url { match redis::Client::open(redis_url.clone()) { Ok(_) => info!("Redis connection validated"), Err(e) => warn!("Redis connection failed: {}", e), } } if let Some(postgres_url) = &config.postgres_url { match sqlx::PgPool::connect(postgres_url).await { Ok(_) => info!("PostgreSQL connection validated"), Err(e) => warn!("PostgreSQL connection failed: {}", e), } } // Check available system resources let sys = sysinfo::System::new_all(); let total_memory_mb = sys.total_memory() / 1024 / 1024; info!("System memory: {} MB", total_memory_mb); if total_memory_mb < config.max_memory_mb as u64 { warn!("System memory ({} MB) is less than requested max ({} MB)", total_memory_mb, config.max_memory_mb); } info!("Test environment validation completed"); Ok(()) } /// Test result tracking and reporting #[derive(Debug, Clone)] pub struct TestResults { pub passed: usize, pub failed: usize, pub skipped: usize, pub total_duration: Duration, pub failures: Vec, } #[derive(Debug, Clone)] pub struct TestFailure { pub test_name: String, pub error: String, pub duration: Duration, } impl TestResults { pub fn new() -> Self { Self { passed: 0, failed: 0, skipped: 0, total_duration: Duration::ZERO, failures: Vec::new(), } } pub fn add_pass(&mut self, duration: Duration) { self.passed += 1; self.total_duration += duration; } pub fn add_failure(&mut self, test_name: String, error: String, duration: Duration) { self.failed += 1; self.total_duration += duration; self.failures.push(TestFailure { test_name, error, duration }); } pub fn add_skip(&mut self, reason: &str) { self.skipped += 1; info!("Test skipped: {}", reason); } pub fn success_rate(&self) -> f64 { if self.passed + self.failed == 0 { 0.0 } else { self.passed as f64 / (self.passed + self.failed) as f64 } } pub fn print_summary(&self) { info!("=== Integration Test Results ==="); info!("Passed: {}", self.passed); info!("Failed: {}", self.failed); info!("Skipped: {}", self.skipped); info!("Success Rate: {:.2}%", self.success_rate() * 100.0); info!("Total Duration: {:?}", self.total_duration); if !self.failures.is_empty() { error!("=== Failures ==="); for failure in &self.failures { error!("FAILED: {} ({:?})", failure.test_name, failure.duration); error!(" Error: {}", failure.error); } } } } impl Default for TestResults { fn default() -> Self { Self::new() } } /// Macro for running integration tests with proper error handling and timing #[macro_export] macro_rules! integration_test { ($name:expr, $test_fn:expr, $results:expr) => { { use tracing::{info, error}; use std::time::Instant; info!("Running test: {}", $name); let start = Instant::now(); match $test_fn().await { Ok(_) => { let duration = start.elapsed(); info!("PASSED: {} ({:?})", $name, duration); $results.add_pass(duration); } Err(e) => { let duration = start.elapsed(); error!("FAILED: {} ({:?})", $name, duration); error!("Error: {}", e); $results.add_failure($name.to_string(), e.to_string(), duration); } } } }; } /// Utilities for test data generation and management pub mod test_data { use super::*; /// Generate synthetic training data for ML pipeline tests pub fn generate_classification_dataset( num_samples: usize, num_features: usize, num_classes: usize, seed: u64, ) -> Result<(Vec>, Vec)> { use rand::{SeedableRng, Rng}; use rand::rngs::StdRng; let mut rng = StdRng::seed_from_u64(seed); let mut features = Vec::with_capacity(num_samples); let mut labels = Vec::with_capacity(num_samples); for _ in 0..num_samples { let mut sample = Vec::with_capacity(num_features); for _ in 0..num_features { sample.push(rng.gen_range(-1.0..1.0)); } features.push(sample); labels.push(rng.gen_range(0..num_classes)); } Ok((features, labels)) } /// Generate synthetic text data for NLP tests pub fn generate_text_dataset( num_samples: usize, vocab_size: usize, sequence_length: usize, seed: u64, ) -> Result>> { use rand::{SeedableRng, Rng}; use rand::rngs::StdRng; let mut rng = StdRng::seed_from_u64(seed); let mut sequences = Vec::with_capacity(num_samples); for _ in 0..num_samples { let mut sequence = Vec::with_capacity(sequence_length); for _ in 0..sequence_length { sequence.push(rng.gen_range(0..vocab_size as u32)); } sequences.push(sequence); } Ok(sequences) } /// Generate synthetic image data for vision tests pub fn generate_image_dataset( num_samples: usize, height: u32, width: u32, channels: u32, seed: u64, ) -> Result>> { use rand::{SeedableRng, Rng}; use rand::rngs::StdRng; let mut rng = StdRng::seed_from_u64(seed); let pixels_per_image = (height * width * channels) as usize; let mut images = Vec::with_capacity(num_samples); for _ in 0..num_samples { let mut image = Vec::with_capacity(pixels_per_image); for _ in 0..pixels_per_image { image.push(rng.gen_range(0.0..1.0)); } images.push(image); } Ok(images) } }