Skip to main content

decy_codegen/
test_generator.rs

1//! Test generator for transpiled Rust code.
2//!
3//! This module implements the test generation system described in the specification
4//! (Section 9: Test Generation - EXTREME TDD Output).
5//!
6//! For each transpiled function, the generator creates:
7//! - Unit tests (≥5 per function): Happy path, error cases, edge cases
8//! - Property tests (≥5 per function): Determinism, no panics, invariants
9//! - Doc tests: Usage examples in documentation
10//! - Mutation test config: Test quality verification
11//! - Behavior equivalence tests: Rust vs C comparison (optional)
12//!
13//! # Examples
14//!
15//! ```
16//! use decy_codegen::test_generator::{TestGenerator, TestGenConfig};
17//! use decy_hir::{HirFunction, HirType};
18//!
19//! let config = TestGenConfig::default();
20//! let generator = TestGenerator::new(config);
21//!
22//! let func = HirFunction::new("test".to_string(), HirType::Void, vec![]);
23//! let tests = generator.generate_tests(&func);
24//!
25//! assert!(tests.unit_tests.len() >= 5);
26//! assert!(tests.property_tests.len() >= 5);
27//! ```
28
29use decy_hir::{HirFunction, HirType};
30
31/// Configuration for test generation.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct TestGenConfig {
34    /// Number of unit tests to generate per function (default: 5)
35    pub unit_tests_per_function: usize,
36
37    /// Number of property tests to generate per function (default: 5)
38    pub property_tests_per_function: usize,
39
40    /// Number of test cases for each property test (default: 1000)
41    pub property_test_cases: usize,
42
43    /// Whether to generate doc tests (default: true)
44    pub generate_doc_tests: bool,
45
46    /// Whether to generate mutation test configuration (default: true)
47    pub generate_mutation_config: bool,
48
49    /// Whether to generate behavior equivalence tests (default: true if C available)
50    pub behavior_equivalence_tests: bool,
51}
52
53impl Default for TestGenConfig {
54    fn default() -> Self {
55        Self {
56            unit_tests_per_function: 5,
57            property_tests_per_function: 5,
58            property_test_cases: 1000,
59            generate_doc_tests: true,
60            generate_mutation_config: true,
61            behavior_equivalence_tests: true,
62        }
63    }
64}
65
66/// Test generator for transpiled Rust functions.
67///
68/// Generates comprehensive test suites including unit tests, property tests,
69/// doc tests, and mutation test configurations.
70#[derive(Debug)]
71pub struct TestGenerator {
72    config: TestGenConfig,
73}
74
75impl TestGenerator {
76    /// Create a new test generator with the given configuration.
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// use decy_codegen::test_generator::{TestGenerator, TestGenConfig};
82    ///
83    /// let config = TestGenConfig::default();
84    /// let generator = TestGenerator::new(config);
85    /// ```
86    pub fn new(config: TestGenConfig) -> Self {
87        Self { config }
88    }
89
90    /// Get the test generator configuration.
91    pub fn config(&self) -> &TestGenConfig {
92        &self.config
93    }
94
95    /// Generate a complete test suite for a HIR function.
96    ///
97    /// # Arguments
98    ///
99    /// * `hir_func` - The HIR function to generate tests for
100    ///
101    /// # Returns
102    ///
103    /// A `GeneratedTests` struct containing all generated test code.
104    ///
105    /// # Examples
106    ///
107    /// ```
108    /// use decy_codegen::test_generator::{TestGenerator, TestGenConfig};
109    /// use decy_hir::{HirFunction, HirType, HirParameter};
110    ///
111    /// let generator = TestGenerator::new(TestGenConfig::default());
112    ///
113    /// let func = HirFunction::new(
114    ///     "add".to_string(),
115    ///     HirType::Int,
116    ///     vec![
117    ///         HirParameter::new("a".to_string(), HirType::Int),
118    ///         HirParameter::new("b".to_string(), HirType::Int),
119    ///     ],
120    /// );
121    ///
122    /// let tests = generator.generate_tests(&func);
123    /// assert!(tests.unit_tests.len() >= 5);
124    /// ```
125    pub fn generate_tests(&self, hir_func: &HirFunction) -> GeneratedTests {
126        let mut unit_tests = Vec::new();
127        let mut property_tests = Vec::new();
128        let mut doc_tests = Vec::new();
129        let equivalence_tests = Vec::new();
130
131        // Generate unit tests
132        unit_tests.extend(self.generate_unit_tests(hir_func));
133
134        // Generate property tests
135        property_tests.extend(self.generate_property_tests(hir_func));
136
137        // Generate doc tests if enabled
138        if self.config.generate_doc_tests {
139            doc_tests.extend(self.generate_doc_tests(hir_func));
140        }
141
142        // Generate mutation config if enabled
143        let mutation_config = if self.config.generate_mutation_config {
144            Some(self.generate_mutation_config(hir_func))
145        } else {
146            None
147        };
148
149        GeneratedTests { unit_tests, property_tests, doc_tests, mutation_config, equivalence_tests }
150    }
151
152    /// Generate unit tests for a function.
153    fn generate_unit_tests(&self, hir_func: &HirFunction) -> Vec<String> {
154        let mut tests = Vec::new();
155        let func_name = hir_func.name();
156
157        // Generate happy path test
158        tests.push(self.generate_happy_path_test(hir_func));
159
160        // Generate null/None tests for pointer parameters
161        for param in hir_func.parameters() {
162            if matches!(param.param_type(), HirType::Pointer(_) | HirType::Box(_)) {
163                tests.push(self.generate_null_parameter_test(hir_func, param.name()));
164            }
165        }
166
167        // Generate edge case tests based on parameter types
168        tests.extend(self.generate_edge_case_tests(hir_func));
169
170        // Ensure we have at least the configured number of tests
171        while tests.len() < self.config.unit_tests_per_function {
172            tests.push(format!(
173                r#"#[test]
174fn test_{}_case_{}() {{
175    // Additional test case {}
176    let result = {}();
177    // Add assertions here
178}}"#,
179                func_name,
180                tests.len(),
181                tests.len(),
182                func_name
183            ));
184        }
185
186        tests
187    }
188
189    /// Generate happy path test.
190    fn generate_happy_path_test(&self, hir_func: &HirFunction) -> String {
191        let func_name = hir_func.name();
192        let params = hir_func.parameters();
193
194        let param_setup = if params.is_empty() {
195            String::new()
196        } else {
197            let setups: Vec<String> = params
198                .iter()
199                .map(|p| {
200                    let default_val = Self::default_test_value(p.param_type());
201                    format!("    let {} = {};", p.name(), default_val)
202                })
203                .collect();
204            format!("{}\n", setups.join("\n"))
205        };
206
207        let call_args: Vec<String> = params.iter().map(|p| p.name().to_string()).collect();
208        let call_expr = if call_args.is_empty() {
209            format!("{}()", func_name)
210        } else {
211            format!("{}({})", func_name, call_args.join(", "))
212        };
213
214        format!(
215            r#"#[test]
216fn test_{}_happy_path() {{
217{}    let result = {};
218    // Verify expected behavior
219}}"#,
220            func_name, param_setup, call_expr
221        )
222    }
223
224    /// Generate null parameter test.
225    fn generate_null_parameter_test(&self, hir_func: &HirFunction, param_name: &str) -> String {
226        let func_name = hir_func.name();
227
228        format!(
229            r#"#[test]
230fn test_{}_null_{} () {{
231    // Test with null/None for {}
232    // Should handle gracefully
233}}"#,
234            func_name, param_name, param_name
235        )
236    }
237
238    /// Generate edge case tests.
239    fn generate_edge_case_tests(&self, hir_func: &HirFunction) -> Vec<String> {
240        let mut tests = Vec::new();
241        let func_name = hir_func.name();
242
243        // Generate boundary value tests for integer parameters
244        let has_int_params =
245            hir_func.parameters().iter().any(|p| matches!(p.param_type(), HirType::Int));
246
247        if has_int_params {
248            tests.push(format!(
249                r#"#[test]
250fn test_{}_boundary_values() {{
251    // Test with boundary values (0, MAX, MIN)
252}}"#,
253                func_name
254            ));
255        }
256
257        tests
258    }
259
260    /// Generate property tests for a function.
261    fn generate_property_tests(&self, hir_func: &HirFunction) -> Vec<String> {
262        let mut tests = Vec::new();
263
264        // Generate determinism property
265        tests.push(self.generate_determinism_property(hir_func));
266
267        // Generate no-panic property
268        tests.push(self.generate_no_panic_property(hir_func));
269
270        // Generate additional properties to meet minimum count
271        while tests.len() < self.config.property_tests_per_function {
272            tests.push(self.generate_generic_property(hir_func, tests.len()));
273        }
274
275        tests
276    }
277
278    /// Generate determinism property test.
279    fn generate_determinism_property(&self, hir_func: &HirFunction) -> String {
280        let func_name = hir_func.name();
281
282        format!(
283            r#"proptest! {{
284    #[test]
285    fn prop_{}_deterministic(/* inputs here */) {{
286        // Same inputs should produce same outputs
287        let result1 = {}(/* args */);
288        let result2 = {}(/* args */);
289        prop_assert_eq!(result1, result2);
290    }}
291}}"#,
292            func_name, func_name, func_name
293        )
294    }
295
296    /// Generate no-panic property test.
297    fn generate_no_panic_property(&self, hir_func: &HirFunction) -> String {
298        let func_name = hir_func.name();
299
300        format!(
301            r#"proptest! {{
302    #[test]
303    fn prop_{}_never_panics(/* inputs here */) {{
304        // Should never panic, even with invalid inputs
305        let _ = {}(/* args */);
306    }}
307}}"#,
308            func_name, func_name
309        )
310    }
311
312    /// Generate a generic property test.
313    fn generate_generic_property(&self, hir_func: &HirFunction, index: usize) -> String {
314        let func_name = hir_func.name();
315
316        format!(
317            r#"proptest! {{
318    #[test]
319    fn prop_{}_invariant_{}(/* inputs here */) {{
320        // Test invariant {}
321        let result = {}(/* args */);
322        // Add property assertions here
323    }}
324}}"#,
325            func_name, index, index, func_name
326        )
327    }
328
329    /// Generate doc tests for a function.
330    fn generate_doc_tests(&self, hir_func: &HirFunction) -> Vec<String> {
331        let mut tests = Vec::new();
332        let func_name = hir_func.name();
333
334        let params = hir_func.parameters();
335        let param_setup: Vec<String> = params
336            .iter()
337            .map(|p| {
338                let default_val = Self::default_test_value(p.param_type());
339                format!("let {} = {};", p.name(), default_val)
340            })
341            .collect();
342
343        let call_args: Vec<String> = params.iter().map(|p| p.name().to_string()).collect();
344        let call_expr = if call_args.is_empty() {
345            format!("{}()", func_name)
346        } else {
347            format!("{}({})", func_name, call_args.join(", "))
348        };
349
350        let doc_test = format!(
351            r#"/// Function: {}
352///
353/// # Examples
354///
355/// ```
356/// {}
357/// let result = {};
358/// // Verify result
359/// ```"#,
360            func_name,
361            param_setup.join("\n/// "),
362            call_expr
363        );
364
365        tests.push(doc_test);
366        tests
367    }
368
369    /// Generate mutation test configuration.
370    fn generate_mutation_config(&self, hir_func: &HirFunction) -> String {
371        let func_name = hir_func.name();
372
373        format!(
374            r#"[[mutant]]
375function = "{}"
376mutations = [
377    "replace_return_values",
378    "flip_boolean_conditions",
379    "replace_arithmetic_operators",
380]
381expected_kill_rate = 0.90
382"#,
383            func_name
384        )
385    }
386
387    /// Get default test value for a type.
388    fn default_test_value(hir_type: &HirType) -> String {
389        match hir_type {
390            HirType::Void => "()".to_string(),
391            HirType::Bool => "true".to_string(),
392            HirType::Int => "42".to_string(),
393            HirType::UnsignedInt => "42u32".to_string(), // DECY-158
394            HirType::Float => "3.14".to_string(),
395            HirType::Double => "2.718".to_string(),
396            HirType::Char => "b'A'".to_string(),
397            HirType::SignedChar => "65i8".to_string(), // DECY-250: 'A' as i8
398            HirType::Pointer(_) => "std::ptr::null_mut()".to_string(),
399            HirType::Box(inner) => {
400                format!("Box::new({})", Self::default_test_value(inner))
401            }
402            HirType::Vec(_) => "Vec::new()".to_string(),
403            HirType::Option(inner) => {
404                // Option types can be Some or None - use Some for test values
405                format!("Some({})", Self::default_test_value(inner))
406            }
407            HirType::Reference { inner, mutable: _ } => {
408                // References need to borrow from a variable
409                // Generate a reference to a default value
410                format!("&{}", Self::default_test_value(inner))
411            }
412            HirType::Struct(name) => {
413                format!("{}::default()", name)
414            }
415            HirType::Enum(name) => {
416                // Use first variant (placeholder - could be enhanced)
417                format!("{}::default()", name)
418            }
419            HirType::Array { element_type, size } => {
420                if let Some(n) = size {
421                    format!("[{}; {}]", Self::default_test_value(element_type), n)
422                } else {
423                    // Unsized array - needs a slice reference
424                    "&[]".to_string()
425                }
426            }
427            HirType::FunctionPointer { .. } => {
428                // Function pointers need concrete function values
429                // Return a placeholder for test generation
430                "todo!(\"Provide function pointer\")".to_string()
431            }
432            HirType::StringLiteral => r#""test string""#.to_string(),
433            HirType::OwnedString => r#"String::from("test string")"#.to_string(),
434            HirType::StringReference => r#""test string""#.to_string(),
435            HirType::Union(_) => {
436                // Unions will be transformed to enums, default to first variant
437                "todo!(\"Union default value\")".to_string()
438            }
439            // DECY-172: Type aliases use 0 as default (for size_t/ssize_t/ptrdiff_t)
440            HirType::TypeAlias(_) => "0".to_string(),
441        }
442    }
443}
444
445/// Generated test suite for a transpiled function.
446#[derive(Debug, Clone, PartialEq, Eq)]
447pub struct GeneratedTests {
448    /// Unit tests (happy path, error cases, edge cases)
449    pub unit_tests: Vec<String>,
450
451    /// Property tests (determinism, no panics, invariants)
452    pub property_tests: Vec<String>,
453
454    /// Doc tests (usage examples)
455    pub doc_tests: Vec<String>,
456
457    /// Mutation test configuration (TOML format)
458    pub mutation_config: Option<String>,
459
460    /// Behavior equivalence tests (Rust vs C)
461    pub equivalence_tests: Vec<String>,
462}
463
464#[cfg(test)]
465#[path = "test_generator_tests.rs"]
466mod test_generator_tests;