1use crate::error::{CoreError, CoreResult};
11use crate::testing::{TestConfig, TestResult};
12use std::fmt::Debug;
13use std::time::{Duration, Instant};
14
15#[cfg(feature = "random")]
16use rand::rngs::StdRng;
17#[cfg(feature = "random")]
18use rand::{Rng, SeedableRng};
19
20pub trait FuzzingGenerator<T> {
22 fn generate(&mut self) -> T;
24
25 fn generate_edge_case(&mut self) -> T;
27
28 fn generate_boundary(&mut self) -> T;
30}
31
32#[derive(Debug, Clone)]
34pub struct FuzzingConfig {
35 pub random_cases: usize,
37 pub edge_cases: usize,
39 pub boundary_cases: usize,
41 pub max_input_size: usize,
43 pub min_input_size: usize,
45 pub seed: Option<u64>,
47 pub enable_shrinking: bool,
49}
50
51impl Default for FuzzingConfig {
52 fn default() -> Self {
53 Self {
54 random_cases: 1000,
55 edge_cases: 100,
56 boundary_cases: 100,
57 max_input_size: 10000,
58 min_input_size: 0,
59 seed: None,
60 enable_shrinking: true,
61 }
62 }
63}
64
65impl FuzzingConfig {
66 pub fn new() -> Self {
68 Self::default()
69 }
70
71 pub fn with_random_cases(mut self, cases: usize) -> Self {
73 self.random_cases = cases;
74 self
75 }
76
77 pub fn with_edge_cases(mut self, cases: usize) -> Self {
79 self.edge_cases = cases;
80 self
81 }
82
83 pub fn with_boundary_cases(mut self, cases: usize) -> Self {
85 self.boundary_cases = cases;
86 self
87 }
88
89 pub fn with_max_input_size(mut self, size: usize) -> Self {
91 self.max_input_size = size;
92 self
93 }
94
95 pub fn with_min_input_size(mut self, size: usize) -> Self {
97 self.min_input_size = size;
98 self
99 }
100
101 pub fn with_seed(mut self, seed: u64) -> Self {
103 self.seed = Some(seed);
104 self
105 }
106
107 pub fn with_shrinking(mut self, enable: bool) -> Self {
109 self.enable_shrinking = enable;
110 self
111 }
112}
113
114#[derive(Debug, Clone)]
116pub struct FuzzingResult {
117 pub total_cases: usize,
119 pub failed_cases: usize,
121 pub duration: Duration,
123 pub failures: Vec<FuzzingFailure>,
125}
126
127#[derive(Debug, Clone)]
129pub struct FuzzingFailure {
130 pub case_number: usize,
132 pub input: String,
134 pub error: String,
136 pub case_type: String,
138}
139
140pub struct FuzzingEngine {
142 config: FuzzingConfig,
143 #[cfg(feature = "random")]
144 #[allow(dead_code)]
145 rng: StdRng,
146}
147
148impl FuzzingEngine {
149 pub fn new(config: FuzzingConfig) -> Self {
151 #[cfg(feature = "random")]
152 let rng = if let Some(seed) = config.seed {
153 StdRng::seed_from_u64(seed)
154 } else {
155 StdRng::seed_from_u64(Default::default())
156 };
157
158 Self {
159 config,
160 #[cfg(feature = "random")]
161 rng,
162 }
163 }
164
165 pub fn fuzz_function_with_generator<T, F, G>(
167 &mut self,
168 test_fn: F,
169 mut generator: G,
170 ) -> CoreResult<FuzzingResult>
171 where
172 T: Debug + Clone,
173 F: Fn(&T) -> CoreResult<()>,
174 G: FuzzingGenerator<T>,
175 {
176 let start_time = Instant::now();
177 let mut failures = Vec::new();
178 let mut case_number = 0;
179
180 for _ in 0..self.config.random_cases {
182 let input = generator.generate();
183 if let Err(error) = test_fn(&input) {
184 failures.push(FuzzingFailure {
185 case_number,
186 input: format!("{input:?}"),
187 error: format!("{error:?}"),
188 case_type: "random".to_string(),
189 });
190 }
191 case_number += 1;
192 }
193
194 for _ in 0..self.config.edge_cases {
196 let input = generator.generate_edge_case();
197 if let Err(error) = test_fn(&input) {
198 failures.push(FuzzingFailure {
199 case_number,
200 input: format!("{input:?}"),
201 error: format!("{error:?}"),
202 case_type: "edge".to_string(),
203 });
204 }
205 case_number += 1;
206 }
207
208 for _ in 0..self.config.boundary_cases {
210 let input = generator.generate_boundary();
211 if let Err(error) = test_fn(&input) {
212 failures.push(FuzzingFailure {
213 case_number,
214 input: format!("{input:?}"),
215 error: format!("{error:?}"),
216 case_type: "boundary".to_string(),
217 });
218 }
219 case_number += 1;
220 }
221
222 Ok(FuzzingResult {
223 total_cases: case_number,
224 failed_cases: failures.len(),
225 duration: start_time.elapsed(),
226 failures,
227 })
228 }
229}
230
231pub struct FloatFuzzingGenerator {
233 #[cfg(feature = "random")]
234 rng: StdRng,
235 min_value: f64,
236 max_value: f64,
237}
238
239impl FloatFuzzingGenerator {
240 pub fn new(min_val: f64, max_val: f64) -> Self {
242 Self {
243 #[cfg(feature = "random")]
244 rng: StdRng::seed_from_u64(Default::default()),
245 min_value: min_val,
246 max_value: max_val,
247 }
248 }
249
250 #[allow(unused_variables)]
252 pub fn with_seed(min_val: f64, max_val: f64, seed: u64) -> Self {
253 Self {
254 #[cfg(feature = "random")]
255 rng: StdRng::seed_from_u64(seed),
256 min_value: min_val,
257 max_value: max_val,
258 }
259 }
260}
261
262#[allow(deprecated)]
263impl FuzzingGenerator<f64> for FloatFuzzingGenerator {
264 fn generate(&mut self) -> f64 {
265 #[cfg(feature = "random")]
266 {
267 self.rng.gen_range(self.min_value..=self.max_value)
268 }
269 #[cfg(not(feature = "random"))]
270 {
271 (self.min_value + self.max_value) / 2.0
273 }
274 }
275
276 fn generate_edge_case(&mut self) -> f64 {
277 #[cfg(feature = "random")]
278 {
279 let edge_cases = vec![
280 0.0,
281 -0.0,
282 f64::INFINITY,
283 f64::NEG_INFINITY,
284 f64::NAN,
285 f64::MIN,
286 f64::MAX,
287 f64::MIN_POSITIVE,
288 f64::EPSILON,
289 -f64::EPSILON,
290 1.0,
291 -1.0,
292 ];
293
294 let valid_edges: Vec<f64> = edge_cases
295 .into_iter()
296 .filter(|&x| x >= self.min_value && x <= self.max_value && x.is_finite())
297 .collect();
298
299 if valid_edges.is_empty() {
300 self.generate()
301 } else {
302 let index = self.rng.gen_range(0..valid_edges.len());
303 valid_edges[index]
304 }
305 }
306 #[cfg(not(feature = "random"))]
307 {
308 if self.min_value <= 0.0 && self.max_value >= 0.0 {
310 0.0
311 } else {
312 self.min_value
313 }
314 }
315 }
316
317 fn generate_boundary(&mut self) -> f64 {
318 #[cfg(feature = "random")]
319 {
320 match self.rng.gen_range(0..4) {
321 0 => self.min_value,
322 1 => self.max_value,
323 2 => self.min_value + f64::EPSILON,
324 _ => self.max_value - f64::EPSILON,
325 }
326 }
327 #[cfg(not(feature = "random"))]
328 {
329 self.min_value
330 }
331 }
332}
333
334pub struct VectorFuzzingGenerator {
336 #[cfg(feature = "random")]
337 rng: StdRng,
338 minsize: usize,
339 maxsize: usize,
340 element_generator: FloatFuzzingGenerator,
341}
342
343impl VectorFuzzingGenerator {
344 pub fn new(min_size: usize, max_size: usize, min_value: f64, max_value: f64) -> Self {
346 Self {
347 #[cfg(feature = "random")]
348 rng: StdRng::seed_from_u64(Default::default()),
349 minsize: min_size,
350 maxsize: max_size,
351 element_generator: FloatFuzzingGenerator::new(min_value, max_value),
352 }
353 }
354}
355
356#[allow(deprecated)]
357impl FuzzingGenerator<Vec<f64>> for VectorFuzzingGenerator {
358 fn generate(&mut self) -> Vec<f64> {
359 #[cfg(feature = "random")]
360 let size = self.rng.gen_range(self.minsize..=self.maxsize);
361 #[cfg(not(feature = "random"))]
362 let size = (self.minsize + self.maxsize) / 2;
363
364 (0..size)
365 .map(|_| self.element_generator.generate())
366 .collect()
367 }
368
369 fn generate_edge_case(&mut self) -> Vec<f64> {
370 #[cfg(feature = "random")]
371 {
372 match self.rng.gen_range(0..4) {
373 0 => vec![], 1 => vec![self.element_generator.generate_edge_case()], 2 => {
376 let value = self.element_generator.generate_edge_case();
378 let size = self.rng.gen_range(2..=10);
379 vec![value; size]
380 }
381 _ => {
382 let size = self.rng.gen_range(2..=10);
384 (0..size)
385 .map(|_| self.element_generator.generate_edge_case())
386 .collect()
387 }
388 }
389 }
390 #[cfg(not(feature = "random"))]
391 {
392 vec![] }
394 }
395
396 fn generate_boundary(&mut self) -> Vec<f64> {
397 #[cfg(feature = "random")]
398 {
399 match self.rng.gen_range(0..3) {
400 0 => {
401 let size = self.minsize;
403 (0..size)
404 .map(|_| self.element_generator.generate_boundary())
405 .collect()
406 }
407 1 => {
408 let size = self.maxsize;
410 (0..size)
411 .map(|_| self.element_generator.generate_boundary())
412 .collect()
413 }
414 _ => {
415 let size = if self.minsize > 0 {
417 self.minsize - 1
418 } else {
419 self.maxsize + 1
420 };
421 (0..size)
422 .map(|_| self.element_generator.generate_boundary())
423 .collect()
424 }
425 }
426 }
427 #[cfg(not(feature = "random"))]
428 {
429 vec![self.element_generator.generate_boundary(); self.minsize]
430 }
431 }
432}
433
434pub struct FuzzingUtils;
436
437impl FuzzingUtils {
438 pub fn fuzz_numeric_function<F>(
440 function: F,
441 config: FuzzingConfig,
442 min_value: f64,
443 max_value: f64,
444 ) -> CoreResult<FuzzingResult>
445 where
446 F: Fn(f64) -> CoreResult<f64>,
447 {
448 let mut engine = FuzzingEngine::new(config);
449 let generator = FloatFuzzingGenerator::new(min_value, max_value);
450
451 engine.fuzz_function_with_generator(|input: &f64| function(*input).map(|_| ()), generator)
452 }
453
454 pub fn fuzz_vector_function<F>(
456 function: F,
457 config: FuzzingConfig,
458 minsize: usize,
459 maxsize: usize,
460 min_value: f64,
461 max_value: f64,
462 ) -> CoreResult<FuzzingResult>
463 where
464 F: Fn(&[f64]) -> CoreResult<Vec<f64>>,
465 {
466 let mut engine = FuzzingEngine::new(config);
467 let generator = VectorFuzzingGenerator::new(minsize, maxsize, min_value, max_value);
468
469 engine
470 .fuzz_function_with_generator(|input: &Vec<f64>| function(input).map(|_| ()), generator)
471 }
472
473 pub fn create_fuzzing_suite(name: &str, config: TestConfig) -> crate::testing::TestSuite {
475 let mut suite = crate::testing::TestSuite::new(name, config);
476
477 suite.add_test("numeric_edge_cases", |_runner| {
479 let fuzzing_config = FuzzingConfig::default().with_edge_cases(100);
480 let result = Self::fuzz_numeric_function(
481 |x| {
482 if x.is_finite() && x != 0.0 {
483 Ok(1.0 / x)
484 } else {
485 Err(CoreError::DomainError(crate::error::ErrorContext::new(
486 "Division by zero or infinite input",
487 )))
488 }
489 },
490 fuzzing_config,
491 -1000.0,
492 1000.0,
493 )?;
494
495 if result.failed_cases > 0 {
496 return Ok(TestResult::failure(
497 std::time::Duration::from_secs(1),
498 result.total_cases,
499 format!("Fuzzing found {} failures", result.failed_cases),
500 ));
501 }
502
503 Ok(TestResult::success(
504 std::time::Duration::from_secs(1),
505 result.total_cases,
506 ))
507 });
508
509 suite.add_test("vector_boundary_conditions", |_runner| {
510 let fuzzing_config = FuzzingConfig::default().with_boundary_cases(50);
511 let result = Self::fuzz_vector_function(
512 |v| {
513 if v.is_empty() {
514 Err(CoreError::ValidationError(crate::error::ErrorContext::new(
515 "Empty vector not allowed",
516 )))
517 } else {
518 Ok(v.iter().map(|&x| x * 2.0).collect())
519 }
520 },
521 fuzzing_config,
522 0,
523 1000,
524 -100.0,
525 100.0,
526 )?;
527
528 if result.failed_cases > 0 {
529 return Ok(TestResult::failure(
530 std::time::Duration::from_secs(1),
531 result.total_cases,
532 format!("Vector fuzzing found {} failures", result.failed_cases),
533 ));
534 }
535
536 Ok(TestResult::success(
537 std::time::Duration::from_secs(1),
538 result.total_cases,
539 ))
540 });
541
542 suite
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549
550 #[test]
551 fn test_float_fuzzing_generator() {
552 let mut generator = FloatFuzzingGenerator::new(-10.0, 10.0);
553
554 for _ in 0..100 {
556 let value = generator.generate();
557 assert!((-10.0..=10.0).contains(&value));
558 }
559
560 let edge_case = generator.generate_edge_case();
562 assert!(edge_case.is_finite());
563
564 let boundary = generator.generate_boundary();
566 assert!(boundary.is_finite());
567 }
568
569 #[test]
570 fn test_vector_fuzzing_generator() {
571 let mut generator = VectorFuzzingGenerator::new(1, 10, -5.0, 5.0);
572
573 let vector = generator.generate();
575 assert!(!vector.is_empty() && vector.len() <= 10);
576 for &value in &vector {
577 assert!((-5.0..=5.0).contains(&value));
578 }
579
580 let edge_vector = generator.generate_edge_case();
582 let boundary_vector = generator.generate_boundary();
586 }
588
589 #[test]
590 fn test_fuzzing_config() {
591 let config = FuzzingConfig::new()
592 .with_random_cases(500)
593 .with_edge_cases(50)
594 .with_boundary_cases(25)
595 .with_max_input_size(5000)
596 .with_seed(12345);
597
598 assert_eq!(config.random_cases, 500);
599 assert_eq!(config.edge_cases, 50);
600 assert_eq!(config.boundary_cases, 25);
601 assert_eq!(config.max_input_size, 5000);
602 assert_eq!(config.seed, Some(12345));
603 }
604}