1use decy_hir::{HirFunction, HirType};
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct TestGenConfig {
34 pub unit_tests_per_function: usize,
36
37 pub property_tests_per_function: usize,
39
40 pub property_test_cases: usize,
42
43 pub generate_doc_tests: bool,
45
46 pub generate_mutation_config: bool,
48
49 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#[derive(Debug)]
71pub struct TestGenerator {
72 config: TestGenConfig,
73}
74
75impl TestGenerator {
76 pub fn new(config: TestGenConfig) -> Self {
87 Self { config }
88 }
89
90 pub fn config(&self) -> &TestGenConfig {
92 &self.config
93 }
94
95 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 unit_tests.extend(self.generate_unit_tests(hir_func));
133
134 property_tests.extend(self.generate_property_tests(hir_func));
136
137 if self.config.generate_doc_tests {
139 doc_tests.extend(self.generate_doc_tests(hir_func));
140 }
141
142 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 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 tests.push(self.generate_happy_path_test(hir_func));
165
166 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 tests.extend(self.generate_edge_case_tests(hir_func));
175
176 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 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 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 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 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 fn generate_property_tests(&self, hir_func: &HirFunction) -> Vec<String> {
270 let mut tests = Vec::new();
271
272 tests.push(self.generate_determinism_property(hir_func));
274
275 tests.push(self.generate_no_panic_property(hir_func));
277
278 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 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 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 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 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 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 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::Float => "3.14".to_string(),
401 HirType::Double => "2.718".to_string(),
402 HirType::Char => "b'A'".to_string(),
403 HirType::Pointer(_) => "std::ptr::null_mut()".to_string(),
404 HirType::Box(inner) => {
405 format!("Box::new({})", Self::default_test_value(inner))
406 }
407 HirType::Vec(_) => "Vec::new()".to_string(),
408 HirType::Option(inner) => {
409 format!("Some({})", Self::default_test_value(inner))
411 }
412 HirType::Reference { inner, mutable: _ } => {
413 format!("&{}", Self::default_test_value(inner))
416 }
417 HirType::Struct(name) => {
418 format!("{}::default()", name)
419 }
420 HirType::Enum(name) => {
421 format!("{}::default()", name)
423 }
424 HirType::Array { element_type, size } => {
425 if let Some(n) = size {
426 format!("[{}; {}]", Self::default_test_value(element_type), n)
427 } else {
428 "&[]".to_string()
430 }
431 }
432 HirType::FunctionPointer { .. } => {
433 "todo!(\"Provide function pointer\")".to_string()
436 }
437 HirType::StringLiteral => r#""test string""#.to_string(),
438 HirType::OwnedString => r#"String::from("test string")"#.to_string(),
439 HirType::StringReference => r#""test string""#.to_string(),
440 }
441 }
442}
443
444#[derive(Debug, Clone, PartialEq, Eq)]
446pub struct GeneratedTests {
447 pub unit_tests: Vec<String>,
449
450 pub property_tests: Vec<String>,
452
453 pub doc_tests: Vec<String>,
455
456 pub mutation_config: Option<String>,
458
459 pub equivalence_tests: Vec<String>,
461}
462
463#[cfg(test)]
464#[path = "test_generator_tests.rs"]
465mod test_generator_tests;