scirs2_core/testing/
fuzzing.rs

1//! # Fuzzing Framework
2//!
3//! This module provides fuzzing capabilities for discovering edge cases and potential
4//! vulnerabilities in `SciRS2` Core functions. It includes:
5//! - Random input generation with configurable constraints
6//! - Edge case exploration
7//! - Boundary condition testing
8//! - Input validation fuzzing
9
10use 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
20/// Fuzzing input generator for different data types
21pub trait FuzzingGenerator<T> {
22    /// Generate a random input value
23    fn generate(&mut self) -> T;
24
25    /// Generate an edge case input
26    fn generate_edge_case(&mut self) -> T;
27
28    /// Generate a boundary condition input
29    fn generate_boundary(&mut self) -> T;
30}
31
32/// Configuration for fuzzing tests
33#[derive(Debug, Clone)]
34pub struct FuzzingConfig {
35    /// Number of random test cases to generate
36    pub random_cases: usize,
37    /// Number of edge cases to test
38    pub edge_cases: usize,
39    /// Number of boundary conditions to test
40    pub boundary_cases: usize,
41    /// Maximum input size for collections
42    pub max_input_size: usize,
43    /// Minimum input size for collections
44    pub min_input_size: usize,
45    /// Random seed for reproducible fuzzing
46    pub seed: Option<u64>,
47    /// Enable shrinking of failing test cases
48    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    /// Create a new fuzzing configuration
67    pub fn new() -> Self {
68        Self::default()
69    }
70
71    /// Set the number of random test cases
72    pub fn with_random_cases(mut self, cases: usize) -> Self {
73        self.random_cases = cases;
74        self
75    }
76
77    /// Set the number of edge cases
78    pub fn with_edge_cases(mut self, cases: usize) -> Self {
79        self.edge_cases = cases;
80        self
81    }
82
83    /// Set the number of boundary cases
84    pub fn with_boundary_cases(mut self, cases: usize) -> Self {
85        self.boundary_cases = cases;
86        self
87    }
88
89    /// Set the maximum input size
90    pub fn with_max_input_size(mut self, size: usize) -> Self {
91        self.max_input_size = size;
92        self
93    }
94
95    /// Set the minimum input size
96    pub fn with_min_input_size(mut self, size: usize) -> Self {
97        self.min_input_size = size;
98        self
99    }
100
101    /// Set the random seed
102    pub fn with_seed(mut self, seed: u64) -> Self {
103        self.seed = Some(seed);
104        self
105    }
106
107    /// Enable or disable shrinking
108    pub fn with_shrinking(mut self, enable: bool) -> Self {
109        self.enable_shrinking = enable;
110        self
111    }
112}
113
114/// Fuzzing result with detailed information about failures
115#[derive(Debug, Clone)]
116pub struct FuzzingResult {
117    /// Total test cases executed
118    pub total_cases: usize,
119    /// Number of failed cases
120    pub failed_cases: usize,
121    /// Execution time
122    pub duration: Duration,
123    /// Failed test cases with inputs and errors
124    pub failures: Vec<FuzzingFailure>,
125}
126
127/// Information about a fuzzing failure
128#[derive(Debug, Clone)]
129pub struct FuzzingFailure {
130    /// Test case number
131    pub case_number: usize,
132    /// Input that caused the failure (serialized as string)
133    pub input: String,
134    /// Error that occurred
135    pub error: String,
136    /// Type of test case (random, edge, boundary)
137    pub case_type: String,
138}
139
140/// Fuzzing engine that coordinates test generation and execution
141pub struct FuzzingEngine {
142    config: FuzzingConfig,
143    #[cfg(feature = "random")]
144    #[allow(dead_code)]
145    rng: StdRng,
146}
147
148impl FuzzingEngine {
149    /// Create a new fuzzing engine
150    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    /// Run fuzzing tests on a function with a generator
166    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        // Test random cases
181        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        // Test edge cases
195        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        // Test boundary cases
209        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
231/// Floating-point number fuzzing generator
232pub struct FloatFuzzingGenerator {
233    #[cfg(feature = "random")]
234    rng: StdRng,
235    min_value: f64,
236    max_value: f64,
237}
238
239impl FloatFuzzingGenerator {
240    /// Create a new float fuzzing generator
241    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    /// Create a generator with seed
251    #[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            // Fallback implementation without random feature
272            (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            // Return a meaningful edge case
309            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
334/// Vector fuzzing generator
335pub struct VectorFuzzingGenerator {
336    #[cfg(feature = "random")]
337    rng: StdRng,
338    minsize: usize,
339    maxsize: usize,
340    element_generator: FloatFuzzingGenerator,
341}
342
343impl VectorFuzzingGenerator {
344    /// Create a new vector fuzzing generator
345    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![],                                            // Empty vector
374                1 => vec![self.element_generator.generate_edge_case()], // Single element
375                2 => {
376                    // All same values
377                    let value = self.element_generator.generate_edge_case();
378                    let size = self.rng.gen_range(2..=10);
379                    vec![value; size]
380                }
381                _ => {
382                    // Mixed edge cases
383                    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![] // Empty vector as edge case
393        }
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                    // Minimum size
402                    let size = self.minsize;
403                    (0..size)
404                        .map(|_| self.element_generator.generate_boundary())
405                        .collect()
406                }
407                1 => {
408                    // Maximum size
409                    let size = self.maxsize;
410                    (0..size)
411                        .map(|_| self.element_generator.generate_boundary())
412                        .collect()
413                }
414                _ => {
415                    // Size near boundaries
416                    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
434/// High-level fuzzing utilities
435pub struct FuzzingUtils;
436
437impl FuzzingUtils {
438    /// Fuzz a numerical function with floating-point inputs
439    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    /// Fuzz a vector function
455    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    /// Create a comprehensive fuzzing test suite
474    pub fn create_fuzzing_suite(name: &str, config: TestConfig) -> crate::testing::TestSuite {
475        let mut suite = crate::testing::TestSuite::new(name, config);
476
477        // Add standard fuzzing tests
478        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        // Test normal generation
555        for _ in 0..100 {
556            let value = generator.generate();
557            assert!((-10.0..=10.0).contains(&value));
558        }
559
560        // Test edge case generation
561        let edge_case = generator.generate_edge_case();
562        assert!(edge_case.is_finite());
563
564        // Test boundary generation
565        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        // Test normal generation
574        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        // Test edge case generation
581        let edge_vector = generator.generate_edge_case();
582        // Edge cases might generate empty vectors or vectors with special values
583
584        // Test boundary generation
585        let boundary_vector = generator.generate_boundary();
586        // Boundary cases test size limits
587    }
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}