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