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 {
150            unit_tests,
151            property_tests,
152            doc_tests,
153            mutation_config,
154            equivalence_tests,
155        }
156    }
157
158    /// Generate unit tests for a function.
159    fn generate_unit_tests(&self, hir_func: &HirFunction) -> Vec<String> {
160        let mut tests = Vec::new();
161        let func_name = hir_func.name();
162
163        // Generate happy path test
164        tests.push(self.generate_happy_path_test(hir_func));
165
166        // Generate null/None tests for pointer parameters
167        for param in hir_func.parameters() {
168            if matches!(param.param_type(), HirType::Pointer(_) | HirType::Box(_)) {
169                tests.push(self.generate_null_parameter_test(hir_func, param.name()));
170            }
171        }
172
173        // Generate edge case tests based on parameter types
174        tests.extend(self.generate_edge_case_tests(hir_func));
175
176        // Ensure we have at least the configured number of tests
177        while tests.len() < self.config.unit_tests_per_function {
178            tests.push(format!(
179                r#"#[test]
180fn test_{}_case_{}() {{
181    // Additional test case {}
182    let result = {}();
183    // Add assertions here
184}}"#,
185                func_name,
186                tests.len(),
187                tests.len(),
188                func_name
189            ));
190        }
191
192        tests
193    }
194
195    /// Generate happy path test.
196    fn generate_happy_path_test(&self, hir_func: &HirFunction) -> String {
197        let func_name = hir_func.name();
198        let params = hir_func.parameters();
199
200        let param_setup = if params.is_empty() {
201            String::new()
202        } else {
203            let setups: Vec<String> = params
204                .iter()
205                .map(|p| {
206                    let default_val = Self::default_test_value(p.param_type());
207                    format!("    let {} = {};", p.name(), default_val)
208                })
209                .collect();
210            format!("{}\n", setups.join("\n"))
211        };
212
213        let call_args: Vec<String> = params.iter().map(|p| p.name().to_string()).collect();
214        let call_expr = if call_args.is_empty() {
215            format!("{}()", func_name)
216        } else {
217            format!("{}({})", func_name, call_args.join(", "))
218        };
219
220        format!(
221            r#"#[test]
222fn test_{}_happy_path() {{
223{}    let result = {};
224    // Verify expected behavior
225}}"#,
226            func_name, param_setup, call_expr
227        )
228    }
229
230    /// Generate null parameter test.
231    fn generate_null_parameter_test(&self, hir_func: &HirFunction, param_name: &str) -> String {
232        let func_name = hir_func.name();
233
234        format!(
235            r#"#[test]
236fn test_{}_null_{} () {{
237    // Test with null/None for {}
238    // Should handle gracefully
239}}"#,
240            func_name, param_name, param_name
241        )
242    }
243
244    /// Generate edge case tests.
245    fn generate_edge_case_tests(&self, hir_func: &HirFunction) -> Vec<String> {
246        let mut tests = Vec::new();
247        let func_name = hir_func.name();
248
249        // Generate boundary value tests for integer parameters
250        let has_int_params = hir_func
251            .parameters()
252            .iter()
253            .any(|p| matches!(p.param_type(), HirType::Int));
254
255        if has_int_params {
256            tests.push(format!(
257                r#"#[test]
258fn test_{}_boundary_values() {{
259    // Test with boundary values (0, MAX, MIN)
260}}"#,
261                func_name
262            ));
263        }
264
265        tests
266    }
267
268    /// Generate property tests for a function.
269    fn generate_property_tests(&self, hir_func: &HirFunction) -> Vec<String> {
270        let mut tests = Vec::new();
271
272        // Generate determinism property
273        tests.push(self.generate_determinism_property(hir_func));
274
275        // Generate no-panic property
276        tests.push(self.generate_no_panic_property(hir_func));
277
278        // Generate additional properties to meet minimum count
279        while tests.len() < self.config.property_tests_per_function {
280            tests.push(self.generate_generic_property(hir_func, tests.len()));
281        }
282
283        tests
284    }
285
286    /// Generate determinism property test.
287    fn generate_determinism_property(&self, hir_func: &HirFunction) -> String {
288        let func_name = hir_func.name();
289
290        format!(
291            r#"proptest! {{
292    #[test]
293    fn prop_{}_deterministic(/* inputs here */) {{
294        // Same inputs should produce same outputs
295        let result1 = {}(/* args */);
296        let result2 = {}(/* args */);
297        prop_assert_eq!(result1, result2);
298    }}
299}}"#,
300            func_name, func_name, func_name
301        )
302    }
303
304    /// Generate no-panic property test.
305    fn generate_no_panic_property(&self, hir_func: &HirFunction) -> String {
306        let func_name = hir_func.name();
307
308        format!(
309            r#"proptest! {{
310    #[test]
311    fn prop_{}_never_panics(/* inputs here */) {{
312        // Should never panic, even with invalid inputs
313        let _ = {}(/* args */);
314    }}
315}}"#,
316            func_name, func_name
317        )
318    }
319
320    /// Generate a generic property test.
321    fn generate_generic_property(&self, hir_func: &HirFunction, index: usize) -> String {
322        let func_name = hir_func.name();
323
324        format!(
325            r#"proptest! {{
326    #[test]
327    fn prop_{}_invariant_{}(/* inputs here */) {{
328        // Test invariant {}
329        let result = {}(/* args */);
330        // Add property assertions here
331    }}
332}}"#,
333            func_name, index, index, func_name
334        )
335    }
336
337    /// Generate doc tests for a function.
338    fn generate_doc_tests(&self, hir_func: &HirFunction) -> Vec<String> {
339        let mut tests = Vec::new();
340        let func_name = hir_func.name();
341
342        let params = hir_func.parameters();
343        let param_setup: Vec<String> = params
344            .iter()
345            .map(|p| {
346                let default_val = Self::default_test_value(p.param_type());
347                format!("let {} = {};", p.name(), default_val)
348            })
349            .collect();
350
351        let call_args: Vec<String> = params.iter().map(|p| p.name().to_string()).collect();
352        let call_expr = if call_args.is_empty() {
353            format!("{}()", func_name)
354        } else {
355            format!("{}({})", func_name, call_args.join(", "))
356        };
357
358        let doc_test = format!(
359            r#"/// Function: {}
360///
361/// # Examples
362///
363/// ```
364/// {}
365/// let result = {};
366/// // Verify result
367/// ```"#,
368            func_name,
369            param_setup.join("\n/// "),
370            call_expr
371        );
372
373        tests.push(doc_test);
374        tests
375    }
376
377    /// Generate mutation test configuration.
378    fn generate_mutation_config(&self, hir_func: &HirFunction) -> String {
379        let func_name = hir_func.name();
380
381        format!(
382            r#"[[mutant]]
383function = "{}"
384mutations = [
385    "replace_return_values",
386    "flip_boolean_conditions",
387    "replace_arithmetic_operators",
388]
389expected_kill_rate = 0.90
390"#,
391            func_name
392        )
393    }
394
395    /// Get default test value for a type.
396    fn default_test_value(hir_type: &HirType) -> String {
397        match hir_type {
398            HirType::Void => "()".to_string(),
399            HirType::Int => "42".to_string(),
400            HirType::UnsignedInt => "42u32".to_string(), // DECY-158
401            HirType::Float => "3.14".to_string(),
402            HirType::Double => "2.718".to_string(),
403            HirType::Char => "b'A'".to_string(),
404            HirType::Pointer(_) => "std::ptr::null_mut()".to_string(),
405            HirType::Box(inner) => {
406                format!("Box::new({})", Self::default_test_value(inner))
407            }
408            HirType::Vec(_) => "Vec::new()".to_string(),
409            HirType::Option(inner) => {
410                // Option types can be Some or None - use Some for test values
411                format!("Some({})", Self::default_test_value(inner))
412            }
413            HirType::Reference { inner, mutable: _ } => {
414                // References need to borrow from a variable
415                // Generate a reference to a default value
416                format!("&{}", Self::default_test_value(inner))
417            }
418            HirType::Struct(name) => {
419                format!("{}::default()", name)
420            }
421            HirType::Enum(name) => {
422                // Use first variant (placeholder - could be enhanced)
423                format!("{}::default()", name)
424            }
425            HirType::Array { element_type, size } => {
426                if let Some(n) = size {
427                    format!("[{}; {}]", Self::default_test_value(element_type), n)
428                } else {
429                    // Unsized array - needs a slice reference
430                    "&[]".to_string()
431                }
432            }
433            HirType::FunctionPointer { .. } => {
434                // Function pointers need concrete function values
435                // Return a placeholder for test generation
436                "todo!(\"Provide function pointer\")".to_string()
437            }
438            HirType::StringLiteral => r#""test string""#.to_string(),
439            HirType::OwnedString => r#"String::from("test string")"#.to_string(),
440            HirType::StringReference => r#""test string""#.to_string(),
441            HirType::Union(_) => {
442                // Unions will be transformed to enums, default to first variant
443                "todo!(\"Union default value\")".to_string()
444            }
445            // DECY-172: Type aliases use 0 as default (for size_t/ssize_t/ptrdiff_t)
446            HirType::TypeAlias(_) => "0".to_string(),
447        }
448    }
449}
450
451/// Generated test suite for a transpiled function.
452#[derive(Debug, Clone, PartialEq, Eq)]
453pub struct GeneratedTests {
454    /// Unit tests (happy path, error cases, edge cases)
455    pub unit_tests: Vec<String>,
456
457    /// Property tests (determinism, no panics, invariants)
458    pub property_tests: Vec<String>,
459
460    /// Doc tests (usage examples)
461    pub doc_tests: Vec<String>,
462
463    /// Mutation test configuration (TOML format)
464    pub mutation_config: Option<String>,
465
466    /// Behavior equivalence tests (Rust vs C)
467    pub equivalence_tests: Vec<String>,
468}
469
470#[cfg(test)]
471#[path = "test_generator_tests.rs"]
472mod test_generator_tests;