1pub 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#[derive(Debug, Clone)]
38pub struct TestConfig {
39 pub timeout: Duration,
41 pub iterations: usize,
43 pub memory_limit: usize,
45 pub verbose: bool,
47 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, verbose: false,
58 seed: None,
59 }
60 }
61}
62
63impl TestConfig {
64 pub fn new() -> Self {
66 Self::default()
67 }
68
69 pub fn with_timeout(mut self, timeout: Duration) -> Self {
71 self.timeout = timeout;
72 self
73 }
74
75 pub fn with_iterations(mut self, iterations: usize) -> Self {
77 self.iterations = iterations;
78 self
79 }
80
81 pub fn with_memory_limit(mut self, limit: usize) -> Self {
83 self.memory_limit = limit;
84 self
85 }
86
87 pub fn with_verbose(mut self, verbose: bool) -> Self {
89 self.verbose = verbose;
90 self
91 }
92
93 pub fn with_seed(mut self, seed: u64) -> Self {
95 self.seed = Some(seed);
96 self
97 }
98}
99
100#[derive(Debug, Clone)]
102pub struct TestResult {
103 pub passed: bool,
105 pub duration: Duration,
107 pub cases_executed: usize,
109 pub memory_used: usize,
111 pub error: Option<String>,
113 pub metadata: std::collections::HashMap<String, String>,
115}
116
117impl TestResult {
118 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 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 pub fn with_memory_usage(mut self, memory: usize) -> Self {
144 self.memory_used = memory;
145 self
146 }
147
148 pub fn with_metadata(mut self, key: String, value: String) -> Self {
150 self.metadata.insert(key, value);
151 self
152 }
153}
154
155pub struct TestRunner {
157 config: TestConfig,
158}
159
160impl TestRunner {
161 pub fn new(config: TestConfig) -> Self {
163 Self { config }
164 }
165
166 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 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 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 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 match testfn(i) {
243 Ok(()) => {
244 cases_executed += 1;
245
246 #[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 #[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); }
310 }
311 }
312
313 Err(CoreError::ComputationError(
314 crate::error::ErrorContext::new("Could not find VmRSS in /proc/self/status"),
315 ))
316 }
317
318 #[cfg(not(target_os = "linux"))]
320 #[allow(dead_code)]
321 fn get_memory_usage(&self) -> CoreResult<usize> {
322 Ok(0)
324 }
325}
326
327type TestFn = Box<dyn Fn(&TestRunner) -> CoreResult<TestResult> + Send + Sync>;
329
330pub struct TestSuite {
332 name: String,
333 tests: Vec<TestFn>,
334 config: TestConfig,
335}
336
337impl TestSuite {
338 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 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 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 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}