1pub 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#[derive(Debug, Clone)]
37pub struct TestConfig {
38 pub timeout: Duration,
40 pub iterations: usize,
42 pub memory_limit: usize,
44 pub verbose: bool,
46 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, verbose: false,
57 seed: None,
58 }
59 }
60}
61
62impl TestConfig {
63 pub fn new() -> Self {
65 Self::default()
66 }
67
68 pub fn with_timeout(mut self, timeout: Duration) -> Self {
70 self.timeout = timeout;
71 self
72 }
73
74 pub fn with_iterations(mut self, iterations: usize) -> Self {
76 self.iterations = iterations;
77 self
78 }
79
80 pub fn with_memory_limit(mut self, limit: usize) -> Self {
82 self.memory_limit = limit;
83 self
84 }
85
86 pub fn with_verbose(mut self, verbose: bool) -> Self {
88 self.verbose = verbose;
89 self
90 }
91
92 pub fn with_seed(mut self, seed: u64) -> Self {
94 self.seed = Some(seed);
95 self
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct TestResult {
102 pub passed: bool,
104 pub duration: Duration,
106 pub cases_executed: usize,
108 pub memory_used: usize,
110 pub error: Option<String>,
112 pub metadata: std::collections::HashMap<String, String>,
114}
115
116impl TestResult {
117 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 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 pub fn with_memory_usage(mut self, memory: usize) -> Self {
143 self.memory_used = memory;
144 self
145 }
146
147 pub fn with_metadata(mut self, key: String, value: String) -> Self {
149 self.metadata.insert(key, value);
150 self
151 }
152}
153
154pub struct TestRunner {
156 config: TestConfig,
157}
158
159impl TestRunner {
160 pub fn new(config: TestConfig) -> Self {
162 Self { config }
163 }
164
165 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 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 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 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 match testfn(i) {
242 Ok(()) => {
243 cases_executed += 1;
244
245 #[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 #[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); }
309 }
310 }
311
312 Err(CoreError::ComputationError(
313 crate::error::ErrorContext::new("Could not find VmRSS in /proc/self/status"),
314 ))
315 }
316
317 #[cfg(not(target_os = "linux"))]
319 #[allow(dead_code)]
320 fn get_memory_usage(&self) -> CoreResult<usize> {
321 Ok(0)
323 }
324}
325
326type TestFn = Box<dyn Fn(&TestRunner) -> CoreResult<TestResult> + Send + Sync>;
328
329pub struct TestSuite {
331 name: String,
332 tests: Vec<TestFn>,
333 config: TestConfig,
334}
335
336impl TestSuite {
337 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 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 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 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}