Skip to main content

scirs2_core/testing/
mod.rs

1//! # Testing Framework for `SciRS2` Core
2//!
3//! This module provides comprehensive testing infrastructure including:
4//! - Property-based testing for mathematical properties
5//! - Fuzzing tests for edge case discovery
6//! - Stress testing for memory and performance limits
7//! - Large-scale dataset testing
8//! - Security audit utilities
9//! - Integration testing with dependent modules
10//! - Ecosystem integration testing for 1.0 release readiness
11//!
12//! ## Features
13//!
14//! - **Property-based testing**: Automatic generation of test cases to verify mathematical properties
15//! - **Fuzzing**: Random input generation to discover edge cases and potential vulnerabilities
16//! - **Stress testing**: Memory pressure and performance limit testing
17//! - **Large-scale testing**: Multi-GB dataset handling and processing
18//! - **Security auditing**: Input validation and bounds checking verification
19//! - **Integration testing**: Cross-module compatibility and communication validation
20//! - **Ecosystem integration**: Complete ecosystem validation for 1.0 release readiness
21
22pub mod ecosystem_integration;
23pub mod fuzzing;
24pub mod gpu_availability;
25pub mod integration;
26pub mod large_scale;
27pub mod propertybased;
28pub mod security;
29pub mod stress;
30
31use crate::error::CoreResult;
32#[cfg(target_os = "linux")]
33use crate::error::{CoreError, ErrorContext};
34use std::time::{Duration, Instant};
35
36/// Test execution configuration
37#[derive(Debug, Clone)]
38pub struct TestConfig {
39    /// Maximum execution time for a single test
40    pub timeout: Duration,
41    /// Number of iterations for property-based tests
42    pub iterations: usize,
43    /// Memory limit for stress tests (in bytes)
44    pub memory_limit: usize,
45    /// Enable verbose logging during tests
46    pub verbose: bool,
47    /// Random seed for reproducible test runs
48    pub seed: Option<u64>,
49}
50
51impl Default for TestConfig {
52    fn default() -> Self {
53        Self {
54            timeout: Duration::from_secs(30),
55            iterations: 1000,
56            memory_limit: 1024 * 1024 * 1024, // 1GB
57            verbose: false,
58            seed: None,
59        }
60    }
61}
62
63impl TestConfig {
64    /// Create a new test configuration
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    /// Set the timeout for test execution
70    pub fn with_timeout(mut self, timeout: Duration) -> Self {
71        self.timeout = timeout;
72        self
73    }
74
75    /// Set the number of iterations for property-based tests
76    pub fn with_iterations(mut self, iterations: usize) -> Self {
77        self.iterations = iterations;
78        self
79    }
80
81    /// Set the memory limit for stress tests
82    pub fn with_memory_limit(mut self, limit: usize) -> Self {
83        self.memory_limit = limit;
84        self
85    }
86
87    /// Enable verbose logging
88    pub fn with_verbose(mut self, verbose: bool) -> Self {
89        self.verbose = verbose;
90        self
91    }
92
93    /// Set a random seed for reproducible tests
94    pub fn with_seed(mut self, seed: u64) -> Self {
95        self.seed = Some(seed);
96        self
97    }
98}
99
100/// Test result with performance metrics
101#[derive(Debug, Clone)]
102pub struct TestResult {
103    /// Whether the test passed
104    pub passed: bool,
105    /// Test execution time
106    pub duration: Duration,
107    /// Number of test cases executed
108    pub cases_executed: usize,
109    /// Memory usage during test (in bytes)
110    pub memory_used: usize,
111    /// Error information if test failed
112    pub error: Option<String>,
113    /// Additional metadata
114    pub metadata: std::collections::HashMap<String, String>,
115}
116
117impl TestResult {
118    /// Create a successful test result
119    pub fn success(duration: Duration, cases: usize) -> Self {
120        Self {
121            passed: true,
122            duration,
123            cases_executed: cases,
124            memory_used: 0,
125            error: None,
126            metadata: std::collections::HashMap::new(),
127        }
128    }
129
130    /// Create a failed test result
131    pub fn failure(duration: Duration, cases: usize, error: String) -> Self {
132        Self {
133            passed: false,
134            duration,
135            cases_executed: cases,
136            memory_used: 0,
137            error: Some(error),
138            metadata: std::collections::HashMap::new(),
139        }
140    }
141
142    /// Add memory usage information
143    pub fn with_memory_usage(mut self, memory: usize) -> Self {
144        self.memory_used = memory;
145        self
146    }
147
148    /// Add metadata
149    pub fn with_metadata(mut self, key: String, value: String) -> Self {
150        self.metadata.insert(key, value);
151        self
152    }
153}
154
155/// Test runner that executes tests with timeout and resource monitoring
156pub struct TestRunner {
157    config: TestConfig,
158}
159
160impl TestRunner {
161    /// Create a new test runner with the given configuration
162    pub fn new(config: TestConfig) -> Self {
163        Self { config }
164    }
165
166    /// Execute a test function with timeout and monitoring
167    pub fn execute<F>(&self, test_name: &str, testfn: F) -> CoreResult<TestResult>
168    where
169        F: FnOnce() -> CoreResult<()>,
170    {
171        if self.config.verbose {
172            println!("Executing test: {}", test_name);
173        }
174
175        let start_time = Instant::now();
176
177        // Execute the test with timeout monitoring
178        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(testfn));
179
180        let duration = start_time.elapsed();
181
182        match result {
183            Ok(Ok(())) => {
184                if self.config.verbose {
185                    println!("Test {} passed in {:?}", test_name, duration);
186                }
187                Ok(TestResult::success(duration, 1))
188            }
189            Ok(Err(e)) => {
190                if self.config.verbose {
191                    println!("Test {} failed: {:?}", test_name, e);
192                }
193                Ok(TestResult::failure(duration, 1, format!("{e:?}")))
194            }
195            Err(panic) => {
196                let errormsg = if let Some(s) = panic.downcast_ref::<String>() {
197                    s.clone()
198                } else if let Some(s) = panic.downcast_ref::<&str>() {
199                    s.to_string()
200                } else {
201                    "Unknown panic".to_string()
202                };
203
204                if self.config.verbose {
205                    println!("Test {} panicked: {}", test_name, errormsg);
206                }
207                Ok(TestResult::failure(duration, 1, errormsg))
208            }
209        }
210    }
211
212    /// Execute multiple test iterations
213    pub fn execute_iterations<F>(&self, test_name: &str, testfn: F) -> CoreResult<TestResult>
214    where
215        F: Fn(usize) -> CoreResult<()>,
216    {
217        if self.config.verbose {
218            println!(
219                "Executing {} iterations of test: {}",
220                self.config.iterations, test_name
221            );
222        }
223
224        let start_time = Instant::now();
225        let mut cases_executed = 0;
226        #[cfg(target_os = "linux")]
227        let mut max_memory = 0;
228        #[cfg(not(target_os = "linux"))]
229        let max_memory = 0;
230
231        for i in 0..self.config.iterations {
232            // Check timeout
233            if start_time.elapsed() > self.config.timeout {
234                return Ok(TestResult::failure(
235                    start_time.elapsed(),
236                    cases_executed,
237                    format!("Test timed out after {} iterations", cases_executed),
238                ));
239            }
240
241            // Execute single iteration
242            match testfn(i) {
243                Ok(()) => {
244                    cases_executed += 1;
245
246                    // Monitor memory usage (simplified)
247                    #[cfg(target_os = "linux")]
248                    {
249                        if let Ok(memory) = self.get_memory_usage() {
250                            max_memory = max_memory.max(memory);
251
252                            if memory > self.config.memory_limit {
253                                return Ok(TestResult::failure(
254                                    start_time.elapsed(),
255                                    cases_executed,
256                                    format!("Memory limit exceeded: {} bytes", memory),
257                                )
258                                .with_memory_usage(memory));
259                            }
260                        }
261                    }
262                }
263                Err(e) => {
264                    return Ok(TestResult::failure(
265                        start_time.elapsed(),
266                        cases_executed,
267                        format!("Iteration {}: {:?}", i, e),
268                    )
269                    .with_memory_usage(max_memory));
270                }
271            }
272        }
273
274        let duration = start_time.elapsed();
275        if self.config.verbose {
276            println!(
277                "Test {} completed {} iterations in {:?}",
278                test_name, cases_executed, duration
279            );
280        }
281
282        Ok(TestResult::success(duration, cases_executed).with_memory_usage(max_memory))
283    }
284
285    /// Get current memory usage (Linux-specific implementation)
286    #[cfg(target_os = "linux")]
287    #[allow(dead_code)]
288    fn get_memory_usage(&self) -> CoreResult<usize> {
289        use std::fs;
290
291        let status = fs::read_to_string("/proc/self/status").map_err(|e| {
292            CoreError::IoError(ErrorContext::new(format!(
293                "Failed to read /proc/self/status: {}",
294                e
295            )))
296        })?;
297
298        for line in status.lines() {
299            if line.starts_with("VmRSS:") {
300                let parts: Vec<&str> = line.split_whitespace().collect();
301                if parts.len() >= 2 {
302                    let kb: usize = parts[1].parse().map_err(|e| {
303                        CoreError::ValidationError(crate::error::ErrorContext::new(format!(
304                            "Failed to parse memory: {}",
305                            e
306                        )))
307                    })?;
308                    return Ok(kb * 1024); // Convert KB to bytes
309                }
310            }
311        }
312
313        Err(CoreError::ComputationError(
314            crate::error::ErrorContext::new("Could not find VmRSS in /proc/self/status"),
315        ))
316    }
317
318    /// Get current memory usage (fallback implementation)
319    #[cfg(not(target_os = "linux"))]
320    #[allow(dead_code)]
321    fn get_memory_usage(&self) -> CoreResult<usize> {
322        // Fallback: return 0 (no monitoring on non-Linux systems)
323        Ok(0)
324    }
325}
326
327/// Type alias for test functions
328type TestFn = Box<dyn Fn(&TestRunner) -> CoreResult<TestResult> + Send + Sync>;
329
330/// Test suite for organizing and running multiple tests
331pub struct TestSuite {
332    name: String,
333    tests: Vec<TestFn>,
334    config: TestConfig,
335}
336
337impl TestSuite {
338    /// Create a new test suite
339    pub fn new(name: &str, config: TestConfig) -> Self {
340        Self {
341            name: name.to_string(),
342            tests: Vec::new(),
343            config,
344        }
345    }
346
347    /// Add a test to the suite
348    pub fn add_test<F>(&mut self, test_name: &str, testfn: F)
349    where
350        F: Fn(&TestRunner) -> CoreResult<TestResult> + Send + Sync + 'static,
351    {
352        let name = test_name.to_string();
353        self.tests.push(Box::new(move |runner| {
354            println!("Running test: {}", name);
355            testfn(runner)
356        }));
357    }
358
359    /// Run all tests in the suite
360    pub fn run(&self) -> CoreResult<Vec<TestResult>> {
361        println!("Running test suite: {}", self.name);
362
363        let runner = TestRunner::new(self.config.clone());
364        let mut results = Vec::new();
365
366        for (i, test) in self.tests.iter().enumerate() {
367            println!("Test {}/{}", i + 1, self.tests.len());
368            match test(&runner) {
369                Ok(result) => {
370                    results.push(result);
371                }
372                Err(e) => {
373                    results.push(TestResult::failure(
374                        Duration::from_secs(0),
375                        0,
376                        format!("{:?}", e),
377                    ));
378                }
379            }
380        }
381
382        // Print summary
383        let passed = results.iter().filter(|r| r.passed).count();
384        let total = results.len();
385        println!(
386            "Test suite {} completed: {}/{} tests passed",
387            self.name, passed, total
388        );
389
390        Ok(results)
391    }
392}