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 { unit_tests, property_tests, doc_tests, mutation_config, equivalence_tests }
150 }
151
152 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 tests.push(self.generate_happy_path_test(hir_func));
159
160 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 tests.extend(self.generate_edge_case_tests(hir_func));
169
170 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 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 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 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 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 fn generate_property_tests(&self, hir_func: &HirFunction) -> Vec<String> {
262 let mut tests = Vec::new();
263
264 tests.push(self.generate_determinism_property(hir_func));
266
267 tests.push(self.generate_no_panic_property(hir_func));
269
270 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 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 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 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 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 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 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(), 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(), 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 format!("Some({})", Self::default_test_value(inner))
406 }
407 HirType::Reference { inner, mutable: _ } => {
408 format!("&{}", Self::default_test_value(inner))
411 }
412 HirType::Struct(name) => {
413 format!("{}::default()", name)
414 }
415 HirType::Enum(name) => {
416 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 "&[]".to_string()
425 }
426 }
427 HirType::FunctionPointer { .. } => {
428 "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 "todo!(\"Union default value\")".to_string()
438 }
439 HirType::TypeAlias(_) => "0".to_string(),
441 }
442 }
443}
444
445#[derive(Debug, Clone, PartialEq, Eq)]
447pub struct GeneratedTests {
448 pub unit_tests: Vec<String>,
450
451 pub property_tests: Vec<String>,
453
454 pub doc_tests: Vec<String>,
456
457 pub mutation_config: Option<String>,
459
460 pub equivalence_tests: Vec<String>,
462}
463
464#[cfg(test)]
465#[path = "test_generator_tests.rs"]
466mod test_generator_tests;