1use depyler_hir::hir::{BinOp, HirExpr, HirFunction, HirStmt, Type};
7use anyhow::Result;
8use quote::quote;
9use syn;
10
11#[derive(Debug, Clone)]
13pub struct TestGenConfig {
14 pub generate_property_tests: bool,
16 pub generate_example_tests: bool,
18 pub max_test_cases: usize,
20 pub enable_shrinking: bool,
22}
23
24impl Default for TestGenConfig {
25 fn default() -> Self {
26 Self {
27 generate_property_tests: true,
28 generate_example_tests: true,
29 max_test_cases: 100,
30 enable_shrinking: true,
31 }
32 }
33}
34
35pub struct TestGenerator {
37 config: TestGenConfig,
38}
39
40impl TestGenerator {
41 pub fn new(config: TestGenConfig) -> Self {
42 Self { config }
43 }
44
45 pub fn generate_test_items_for_function(
50 &self,
51 func: &HirFunction,
52 ) -> Result<Vec<proc_macro2::TokenStream>> {
53 if !func.properties.is_pure {
55 return Ok(Vec::new());
56 }
57
58 let mut test_functions = Vec::new();
59
60 if self.config.generate_property_tests {
62 if let Some(prop_test) = self.generate_property_test(func)? {
63 test_functions.push(prop_test);
64 }
65 }
66
67 if self.config.generate_example_tests {
69 if let Some(example_test) = self.generate_example_test(func)? {
70 test_functions.push(example_test);
71 }
72 }
73
74 Ok(test_functions)
75 }
76
77 pub fn generate_tests_module(
82 &self,
83 functions: &[HirFunction],
84 ) -> Result<Option<proc_macro2::TokenStream>> {
85 let mut all_test_items = Vec::new();
86
87 for func in functions {
89 let test_items = self.generate_test_items_for_function(func)?;
90 all_test_items.extend(test_items);
91 }
92
93 if all_test_items.is_empty() {
95 return Ok(None);
96 }
97
98 Ok(Some(quote! {
100 #[cfg(test)]
101 mod tests {
102 use super::*;
103 use quickcheck::{quickcheck, TestResult};
104
105 #(#all_test_items)*
106 }
107 }))
108 }
109
110 #[deprecated(
115 since = "3.19.22",
116 note = "Use generate_tests_module() to avoid duplicate mod tests blocks (DEPYLER-0280)"
117 )]
118 pub fn generate_tests(&self, func: &HirFunction) -> Result<Option<proc_macro2::TokenStream>> {
119 let test_items = self.generate_test_items_for_function(func)?;
120
121 if test_items.is_empty() {
122 return Ok(None);
123 }
124
125 Ok(Some(quote! {
126 #[cfg(test)]
127 mod tests {
128 use super::*;
129 use quickcheck::{quickcheck, TestResult};
130
131 #(#test_items)*
132 }
133 }))
134 }
135
136 fn generate_property_test(
138 &self,
139 func: &HirFunction,
140 ) -> Result<Option<proc_macro2::TokenStream>> {
141 let func_name = syn::Ident::new(&func.name, proc_macro2::Span::call_site());
142 let test_name = syn::Ident::new(
143 &format!("quickcheck_{}", func.name),
144 proc_macro2::Span::call_site(),
145 );
146
147 let properties = self.analyze_function_properties(func);
149
150 if properties.is_empty() {
151 return Ok(None);
152 }
153
154 let param_types: Vec<_> = func
156 .params
157 .iter()
158 .map(|param| self.type_to_quickcheck_type(¶m.ty))
159 .collect();
160
161 let param_names: Vec<_> = func
162 .params
163 .iter()
164 .map(|param| syn::Ident::new(¶m.name, proc_macro2::Span::call_site()))
165 .collect();
166
167 let property_checks: Vec<_> = properties
169 .iter()
170 .map(|prop| self.property_to_assertion(prop, &func_name, ¶m_names, &func.params))
171 .collect();
172
173 Ok(Some(quote! {
174 #[test]
175 fn #test_name() {
176 fn prop(#(#param_names: #param_types),*) -> TestResult {
177 #(#property_checks)*
178 TestResult::passed()
179 }
180
181 quickcheck(prop as fn(#(#param_types),*) -> TestResult);
182 }
183 }))
184 }
185
186 fn generate_example_test(
188 &self,
189 func: &HirFunction,
190 ) -> Result<Option<proc_macro2::TokenStream>> {
191 let test_name = syn::Ident::new(
192 &format!("test_{}_examples", func.name),
193 proc_macro2::Span::call_site(),
194 );
195
196 let test_cases = self.generate_test_cases(func);
198
199 if test_cases.is_empty() {
200 return Ok(None);
201 }
202
203 Ok(Some(quote! {
204 #[test]
205 fn #test_name() {
206 #(#test_cases)*
207 }
208 }))
209 }
210
211 fn analyze_function_properties(&self, func: &HirFunction) -> Vec<TestProperty> {
213 let mut properties = Vec::new();
218
219 if self.is_identity_function(func) {
221 properties.push(TestProperty::Identity);
222 }
223
224 if self.is_commutative(func) {
225 properties.push(TestProperty::Commutative);
226 }
227
228 if self.is_associative(func) {
229 properties.push(TestProperty::Associative);
230 }
231
232 if self.returns_non_negative(func) {
233 properties.push(TestProperty::NonNegative);
234 }
235
236 if self.preserves_length(func) {
237 properties.push(TestProperty::LengthPreserving);
238 }
239
240 if self.is_idempotent(func) {
241 properties.push(TestProperty::Idempotent);
242 }
243
244 if self.is_sorting_function(func) {
245 properties.push(TestProperty::Sorted);
246 properties.push(TestProperty::SameElements);
247 }
248
249 properties
250 }
251
252 fn is_identity_function(&self, func: &HirFunction) -> bool {
254 if func.params.len() == 1 && func.body.len() == 1 {
256 if let HirStmt::Return(Some(HirExpr::Var(name))) = &func.body[0] {
257 return name == &func.params[0].name;
258 }
259 }
260 false
261 }
262
263 fn is_commutative(&self, func: &HirFunction) -> bool {
265 if func.params.len() == 2 && func.body.len() == 1 {
266 if let HirStmt::Return(Some(HirExpr::Binary { op, left, right })) = &func.body[0] {
267 let is_string_concat = matches!(op, BinOp::Add)
273 && (matches!(func.params[0].ty, Type::String)
274 || matches!(func.params[1].ty, Type::String));
275
276 if is_string_concat {
278 return false;
279 }
280
281 matches!(
283 op,
284 BinOp::Add | BinOp::Mul | BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor
285 ) && self.is_simple_param_reference(left, &func.params[0].name)
286 && self.is_simple_param_reference(right, &func.params[1].name)
287 } else {
288 false
289 }
290 } else {
291 false
292 }
293 }
294
295 fn is_simple_param_reference(&self, expr: &HirExpr, param_name: &str) -> bool {
297 matches!(expr, HirExpr::Var(name) if name == param_name)
298 }
299
300 fn is_associative(&self, _func: &HirFunction) -> bool {
302 false
305 }
306
307 fn returns_non_negative(&self, func: &HirFunction) -> bool {
309 func.name.contains("abs") || func.name.contains("magnitude")
311 }
312
313 fn preserves_length(&self, func: &HirFunction) -> bool {
315 if func.params.len() == 1 {
317 if let (Type::List(_), Type::List(_)) = (&func.params[0].ty, &func.ret_type) {
318 return func.name.contains("sort") || func.name.contains("map");
320 }
321 }
322 false
323 }
324
325 fn is_idempotent(&self, func: &HirFunction) -> bool {
327 func.name.contains("normalize") || func.name.contains("clean")
328 }
329
330 fn is_sorting_function(&self, func: &HirFunction) -> bool {
332 !func.params.is_empty() && func.name.contains("sort")
334 }
335
336 #[allow(clippy::only_used_in_recursion)]
338 fn type_to_quickcheck_type(&self, ty: &Type) -> proc_macro2::TokenStream {
339 match ty {
340 Type::Int => quote! { i32 },
341 Type::Float => quote! { f64 },
342 Type::String => quote! { String },
343 Type::Bool => quote! { bool },
344 Type::List(inner) => {
345 let inner_type = self.type_to_quickcheck_type(inner);
346 quote! { Vec<#inner_type> }
347 }
348 _ => quote! { () }, }
350 }
351
352 fn convert_arg_for_property_test(
358 &self,
359 ty: &Type,
360 arg_name: &syn::Ident,
361 ) -> proc_macro2::TokenStream {
362 match ty {
363 Type::String => {
364 quote! { (&*#arg_name).into() }
370 }
371 Type::List(_) => {
372 quote! { &#arg_name }
374 }
375 Type::Dict(_, _) => {
376 quote! { &#arg_name }
378 }
379 _ => {
380 quote! { #arg_name.clone() }
382 }
383 }
384 }
385
386 fn property_to_assertion(
391 &self,
392 prop: &TestProperty,
393 func_name: &syn::Ident,
394 params: &[syn::Ident],
395 func_params: &[depyler_hir::hir::HirParam],
396 ) -> proc_macro2::TokenStream {
397 match prop {
398 TestProperty::Identity => {
399 if params.is_empty() || func_params.is_empty() {
401 return quote! {};
402 }
403 let param = ¶ms[0];
404
405 let param_converted = self.convert_arg_for_property_test(&func_params[0].ty, param);
407
408 quote! {
409 let result = #func_name(#param_converted);
410 if result != #param {
411 return TestResult::failed();
412 }
413 }
414 }
415 TestProperty::Commutative => {
416 if params.len() < 2 || func_params.len() < 2 {
418 return quote! {};
419 }
420 let (a, b) = (¶ms[0], ¶ms[1]);
421
422 let a_converted = self.convert_arg_for_property_test(&func_params[0].ty, a);
426 let b_converted = self.convert_arg_for_property_test(&func_params[1].ty, b);
427
428 let special_value_check = if matches!(func_params[0].ty, Type::Int)
431 && matches!(func_params[1].ty, Type::Int)
432 {
433 quote! {
434 if (#a > 0 && #b > i32::MAX - #a) || (#a < 0 && #b < i32::MIN - #a) {
436 return TestResult::discard();
437 }
438 }
439 } else if matches!(func_params[0].ty, Type::Float)
440 && matches!(func_params[1].ty, Type::Float)
441 {
442 quote! {
443 if #a.is_nan() || #b.is_nan() || #a.is_infinite() || #b.is_infinite() {
445 return TestResult::discard();
446 }
447 }
448 } else {
449 quote! {}
450 };
451
452 quote! {
453 #special_value_check
454 let result1 = #func_name(#a_converted, #b_converted);
455 let result2 = #func_name(#b_converted, #a_converted);
456 if result1 != result2 {
457 return TestResult::failed();
458 }
459 }
460 }
461 TestProperty::NonNegative => {
462 let converted_args: Vec<_> = params
464 .iter()
465 .zip(func_params.iter())
466 .map(|(param, func_param)| {
467 self.convert_arg_for_property_test(&func_param.ty, param)
468 })
469 .collect();
470
471 quote! {
472 let result = #func_name(#(#converted_args),*);
473 if result < 0 {
474 return TestResult::failed();
475 }
476 }
477 }
478 TestProperty::LengthPreserving => {
479 if params.is_empty() || func_params.is_empty() {
481 return quote! {};
482 }
483 let param = ¶ms[0];
484
485 let param_converted = self.convert_arg_for_property_test(&func_params[0].ty, param);
487
488 quote! {
489 let input_len = #param.len();
490 let result = #func_name(#param_converted);
491 if result.len() != input_len {
492 return TestResult::failed();
493 }
494 }
495 }
496 TestProperty::Sorted => {
497 let converted_args: Vec<_> = params
499 .iter()
500 .zip(func_params.iter())
501 .map(|(param, func_param)| {
502 self.convert_arg_for_property_test(&func_param.ty, param)
503 })
504 .collect();
505
506 quote! {
507 let result = #func_name(#(#converted_args),*);
508 for i in 1..result.len() {
509 if result[i-1] > result[i] {
510 return TestResult::failed();
511 }
512 }
513 }
514 }
515 TestProperty::SameElements => {
516 if params.is_empty() || func_params.is_empty() {
518 return quote! {};
519 }
520 let param = ¶ms[0];
521
522 let param_converted = self.convert_arg_for_property_test(&func_params[0].ty, param);
524
525 quote! {
526 let mut input_sorted = #param.clone();
527 input_sorted.sort();
528 let mut result = #func_name(#param_converted);
529 result.sort();
530 if input_sorted != result {
531 return TestResult::failed();
532 }
533 }
534 }
535 TestProperty::Idempotent => {
536 let converted_args: Vec<_> = params
538 .iter()
539 .zip(func_params.iter())
540 .map(|(param, func_param)| {
541 self.convert_arg_for_property_test(&func_param.ty, param)
542 })
543 .collect();
544
545 quote! {
546 let once = #func_name(#(#converted_args),*);
547 let twice = #func_name(once.clone());
548 if once != twice {
549 return TestResult::failed();
550 }
551 }
552 }
553 _ => quote! {},
554 }
555 }
556
557 fn generate_test_cases(&self, func: &HirFunction) -> Vec<proc_macro2::TokenStream> {
559 let func_name = syn::Ident::new(&func.name, proc_macro2::Span::call_site());
560 let mut cases = Vec::new();
561
562 match (&func.ret_type, func.params.len()) {
564 (Type::Int, 0) => {
565 cases.push(quote! {
567 let _ = #func_name();
568 });
569 }
570 (Type::Int, 1) => {
571 let param_type = &func.params[0].ty;
573 match param_type {
574 Type::Int => {
575 if func.name.contains("abs") {
577 cases.push(quote! {
579 assert_eq!(#func_name(0), 0);
580 assert_eq!(#func_name(1), 1);
581 assert_eq!(#func_name(-1), 1);
582 assert_eq!(#func_name(i32::MIN + 1), i32::MAX);
583 });
584 } else {
585 cases.push(quote! {
587 assert_eq!(#func_name(0), 0);
588 assert_eq!(#func_name(1), 1);
589 assert_eq!(#func_name(-1), -1);
590 });
591 }
592 }
593 Type::List(_) => {
594 if func.name.contains("sum") {
597 cases.push(quote! {
599 assert_eq!(#func_name(&vec![]), 0);
600 assert_eq!(#func_name(&vec![1]), 1);
601 assert_eq!(#func_name(&vec![1, 2, 3]), 6); });
603 } else if func.name.contains("len")
604 || func.name.contains("count")
605 || func.name.contains("size")
606 {
607 cases.push(quote! {
609 assert_eq!(#func_name(&vec![]), 0);
610 assert_eq!(#func_name(&vec![1]), 1);
611 assert_eq!(#func_name(&vec![1, 2, 3]), 3);
612 });
613 } else {
614 cases.push(quote! {
616 assert_eq!(#func_name(&vec![]), 0);
617 assert_eq!(#func_name(&vec![1]), 1);
618 assert_eq!(#func_name(&vec![1, 2, 3]), 3);
619 });
620 }
621 }
622 Type::String => {
623 cases.push(quote! {
625 assert_eq!(#func_name(""), 0);
626 assert_eq!(#func_name("a"), 1);
627 assert_eq!(#func_name("abc"), 3);
628 });
629 }
630 _ => {
631 }
633 }
634 }
635 (Type::Int, 2)
636 if matches!(&func.params[0].ty, Type::Int)
637 && matches!(&func.params[1].ty, Type::Int) =>
638 {
639 cases.push(quote! {
641 assert_eq!(#func_name(0, 0), 0);
642 assert_eq!(#func_name(1, 2), 3);
643 assert_eq!(#func_name(-1, 1), 0);
644 });
645 }
646 (Type::Bool, _) => {
647 if func.params.len() == 1 {
649 cases.push(quote! {
650 let _ = #func_name(Default::default());
652 });
653 }
654 }
655 (Type::List(_), _) => {
656 if func.params.len() == 1 && matches!(&func.params[0].ty, Type::List(_)) {
658 cases.push(quote! {
659 assert_eq!(#func_name(vec![]), vec![]);
660 assert_eq!(#func_name(vec![1]), vec![1]);
661 });
662 }
663 }
664 _ => {}
665 }
666
667 cases
668 }
669}
670
671#[derive(Debug, Clone, PartialEq)]
673enum TestProperty {
674 Identity,
675 Commutative,
676 Associative,
677 NonNegative,
678 LengthPreserving,
679 Sorted,
680 SameElements,
681 Idempotent,
682 #[allow(dead_code)]
683 Distributive,
684 #[allow(dead_code)]
685 Monotonic,
686}
687
688#[cfg(test)]
689#[allow(clippy::field_reassign_with_default)]
690mod tests {
691 use super::*;
692 use depyler_hir::hir::FunctionProperties;
693 use depyler_annotations::TranspilationAnnotations;
694 use smallvec::smallvec;
695
696 fn make_pure_properties() -> FunctionProperties {
697 let mut props = FunctionProperties::default();
698 props.is_pure = true;
699 props
700 }
701
702 fn make_impure_properties() -> FunctionProperties {
703 let mut props = FunctionProperties::default();
704 props.is_pure = false;
705 props
706 }
707
708 fn make_param(name: &str, ty: Type) -> depyler_hir::hir::HirParam {
709 depyler_hir::hir::HirParam::new(name.to_string(), ty)
710 }
711
712 #[test]
714 fn test_testgen_config_default() {
715 let config = TestGenConfig::default();
716 assert!(config.generate_property_tests);
717 assert!(config.generate_example_tests);
718 assert_eq!(config.max_test_cases, 100);
719 assert!(config.enable_shrinking);
720 }
721
722 #[test]
723 fn test_testgen_config_custom() {
724 let config = TestGenConfig {
725 generate_property_tests: false,
726 generate_example_tests: true,
727 max_test_cases: 50,
728 enable_shrinking: false,
729 };
730 assert!(!config.generate_property_tests);
731 assert!(config.generate_example_tests);
732 assert_eq!(config.max_test_cases, 50);
733 assert!(!config.enable_shrinking);
734 }
735
736 #[test]
737 fn test_testgen_config_clone() {
738 let config = TestGenConfig::default();
739 let cloned = config.clone();
740 assert_eq!(config.max_test_cases, cloned.max_test_cases);
741 }
742
743 #[test]
745 fn test_test_generator_new() {
746 let config = TestGenConfig::default();
747 let _gen = TestGenerator::new(config);
748 }
749
750 #[test]
751 fn test_generate_test_items_for_impure_function() {
752 let gen = TestGenerator::new(TestGenConfig::default());
753 let func = HirFunction {
754 name: "side_effect".to_string(),
755 params: smallvec![],
756 ret_type: Type::Int,
757 body: vec![],
758 properties: make_impure_properties(),
759 annotations: TranspilationAnnotations::default(),
760 docstring: None,
761 };
762 let result = gen.generate_test_items_for_function(&func).unwrap();
763 assert!(
764 result.is_empty(),
765 "Impure functions should not generate tests"
766 );
767 }
768
769 #[test]
770 fn test_generate_tests_module_empty() {
771 let gen = TestGenerator::new(TestGenConfig::default());
772 let result = gen.generate_tests_module(&[]).unwrap();
773 assert!(result.is_none(), "Empty function list should return None");
774 }
775
776 #[test]
777 fn test_generate_tests_module_impure_only() {
778 let gen = TestGenerator::new(TestGenConfig::default());
779 let func = HirFunction {
780 name: "impure".to_string(),
781 params: smallvec![],
782 ret_type: Type::Int,
783 body: vec![],
784 properties: make_impure_properties(),
785 annotations: TranspilationAnnotations::default(),
786 docstring: None,
787 };
788 let result = gen.generate_tests_module(&[func]).unwrap();
789 assert!(result.is_none(), "Only impure functions should return None");
790 }
791
792 #[test]
794 fn test_is_identity_function_true() {
795 let gen = TestGenerator::new(TestGenConfig::default());
796 let func = HirFunction {
797 name: "identity".to_string(),
798 params: smallvec![make_param("x", Type::Int)],
799 ret_type: Type::Int,
800 body: vec![HirStmt::Return(Some(HirExpr::Var("x".to_string())))],
801 properties: make_pure_properties(),
802 annotations: TranspilationAnnotations::default(),
803 docstring: None,
804 };
805 assert!(gen.is_identity_function(&func));
806 }
807
808 #[test]
809 fn test_is_identity_function_false_different_return() {
810 let gen = TestGenerator::new(TestGenConfig::default());
811 let func = HirFunction {
812 name: "not_identity".to_string(),
813 params: smallvec![make_param("x", Type::Int)],
814 ret_type: Type::Int,
815 body: vec![HirStmt::Return(Some(HirExpr::Var("y".to_string())))],
816 properties: make_pure_properties(),
817 annotations: TranspilationAnnotations::default(),
818 docstring: None,
819 };
820 assert!(!gen.is_identity_function(&func));
821 }
822
823 #[test]
824 fn test_is_identity_function_false_multiple_params() {
825 let gen = TestGenerator::new(TestGenConfig::default());
826 let func = HirFunction {
827 name: "two_params".to_string(),
828 params: smallvec![make_param("x", Type::Int), make_param("y", Type::Int)],
829 ret_type: Type::Int,
830 body: vec![HirStmt::Return(Some(HirExpr::Var("x".to_string())))],
831 properties: make_pure_properties(),
832 annotations: TranspilationAnnotations::default(),
833 docstring: None,
834 };
835 assert!(!gen.is_identity_function(&func));
836 }
837
838 #[test]
839 fn test_is_identity_function_false_multiple_stmts() {
840 let gen = TestGenerator::new(TestGenConfig::default());
841 let func = HirFunction {
842 name: "multiple_stmts".to_string(),
843 params: smallvec![make_param("x", Type::Int)],
844 ret_type: Type::Int,
845 body: vec![
846 HirStmt::Expr(HirExpr::Var("x".to_string())),
847 HirStmt::Return(Some(HirExpr::Var("x".to_string()))),
848 ],
849 properties: make_pure_properties(),
850 annotations: TranspilationAnnotations::default(),
851 docstring: None,
852 };
853 assert!(!gen.is_identity_function(&func));
854 }
855
856 #[test]
858 fn test_is_commutative_add() {
859 let gen = TestGenerator::new(TestGenConfig::default());
860 let func = HirFunction {
861 name: "add".to_string(),
862 params: smallvec![make_param("a", Type::Int), make_param("b", Type::Int)],
863 ret_type: Type::Int,
864 body: vec![HirStmt::Return(Some(HirExpr::Binary {
865 op: BinOp::Add,
866 left: Box::new(HirExpr::Var("a".to_string())),
867 right: Box::new(HirExpr::Var("b".to_string())),
868 }))],
869 properties: make_pure_properties(),
870 annotations: TranspilationAnnotations::default(),
871 docstring: None,
872 };
873 assert!(gen.is_commutative(&func));
874 }
875
876 #[test]
877 fn test_is_commutative_mul() {
878 let gen = TestGenerator::new(TestGenConfig::default());
879 let func = HirFunction {
880 name: "mul".to_string(),
881 params: smallvec![make_param("a", Type::Int), make_param("b", Type::Int)],
882 ret_type: Type::Int,
883 body: vec![HirStmt::Return(Some(HirExpr::Binary {
884 op: BinOp::Mul,
885 left: Box::new(HirExpr::Var("a".to_string())),
886 right: Box::new(HirExpr::Var("b".to_string())),
887 }))],
888 properties: make_pure_properties(),
889 annotations: TranspilationAnnotations::default(),
890 docstring: None,
891 };
892 assert!(gen.is_commutative(&func));
893 }
894
895 #[test]
896 fn test_is_commutative_sub_false() {
897 let gen = TestGenerator::new(TestGenConfig::default());
898 let func = HirFunction {
899 name: "sub".to_string(),
900 params: smallvec![make_param("a", Type::Int), make_param("b", Type::Int)],
901 ret_type: Type::Int,
902 body: vec![HirStmt::Return(Some(HirExpr::Binary {
903 op: BinOp::Sub,
904 left: Box::new(HirExpr::Var("a".to_string())),
905 right: Box::new(HirExpr::Var("b".to_string())),
906 }))],
907 properties: make_pure_properties(),
908 annotations: TranspilationAnnotations::default(),
909 docstring: None,
910 };
911 assert!(!gen.is_commutative(&func));
912 }
913
914 #[test]
915 fn test_is_commutative_string_concat_false() {
916 let gen = TestGenerator::new(TestGenConfig::default());
918 let func = HirFunction {
919 name: "concat".to_string(),
920 params: smallvec![make_param("a", Type::String), make_param("b", Type::String)],
921 ret_type: Type::String,
922 body: vec![HirStmt::Return(Some(HirExpr::Binary {
923 op: BinOp::Add,
924 left: Box::new(HirExpr::Var("a".to_string())),
925 right: Box::new(HirExpr::Var("b".to_string())),
926 }))],
927 properties: make_pure_properties(),
928 annotations: TranspilationAnnotations::default(),
929 docstring: None,
930 };
931 assert!(!gen.is_commutative(&func));
932 }
933
934 #[test]
935 fn test_is_commutative_single_param_false() {
936 let gen = TestGenerator::new(TestGenConfig::default());
937 let func = HirFunction {
938 name: "single".to_string(),
939 params: smallvec![make_param("a", Type::Int)],
940 ret_type: Type::Int,
941 body: vec![HirStmt::Return(Some(HirExpr::Var("a".to_string())))],
942 properties: make_pure_properties(),
943 annotations: TranspilationAnnotations::default(),
944 docstring: None,
945 };
946 assert!(!gen.is_commutative(&func));
947 }
948
949 #[test]
951 fn test_is_simple_param_reference_true() {
952 let gen = TestGenerator::new(TestGenConfig::default());
953 let expr = HirExpr::Var("x".to_string());
954 assert!(gen.is_simple_param_reference(&expr, "x"));
955 }
956
957 #[test]
958 fn test_is_simple_param_reference_false_different_name() {
959 let gen = TestGenerator::new(TestGenConfig::default());
960 let expr = HirExpr::Var("x".to_string());
961 assert!(!gen.is_simple_param_reference(&expr, "y"));
962 }
963
964 #[test]
965 fn test_is_simple_param_reference_false_not_var() {
966 let gen = TestGenerator::new(TestGenConfig::default());
967 let expr = HirExpr::Literal(depyler_hir::hir::Literal::Int(1));
968 assert!(!gen.is_simple_param_reference(&expr, "x"));
969 }
970
971 #[test]
973 fn test_is_associative_always_false() {
974 let gen = TestGenerator::new(TestGenConfig::default());
975 let func = HirFunction {
976 name: "add".to_string(),
977 params: smallvec![],
978 ret_type: Type::Int,
979 body: vec![],
980 properties: make_pure_properties(),
981 annotations: TranspilationAnnotations::default(),
982 docstring: None,
983 };
984 assert!(!gen.is_associative(&func));
985 }
986
987 #[test]
989 fn test_returns_non_negative_abs() {
990 let gen = TestGenerator::new(TestGenConfig::default());
991 let func = HirFunction {
992 name: "my_abs".to_string(),
993 params: smallvec![],
994 ret_type: Type::Int,
995 body: vec![],
996 properties: make_pure_properties(),
997 annotations: TranspilationAnnotations::default(),
998 docstring: None,
999 };
1000 assert!(gen.returns_non_negative(&func));
1001 }
1002
1003 #[test]
1004 fn test_returns_non_negative_magnitude() {
1005 let gen = TestGenerator::new(TestGenConfig::default());
1006 let func = HirFunction {
1007 name: "magnitude".to_string(),
1008 params: smallvec![],
1009 ret_type: Type::Int,
1010 body: vec![],
1011 properties: make_pure_properties(),
1012 annotations: TranspilationAnnotations::default(),
1013 docstring: None,
1014 };
1015 assert!(gen.returns_non_negative(&func));
1016 }
1017
1018 #[test]
1019 fn test_returns_non_negative_false() {
1020 let gen = TestGenerator::new(TestGenConfig::default());
1021 let func = HirFunction {
1022 name: "subtract".to_string(),
1023 params: smallvec![],
1024 ret_type: Type::Int,
1025 body: vec![],
1026 properties: make_pure_properties(),
1027 annotations: TranspilationAnnotations::default(),
1028 docstring: None,
1029 };
1030 assert!(!gen.returns_non_negative(&func));
1031 }
1032
1033 #[test]
1035 fn test_preserves_length_sort() {
1036 let gen = TestGenerator::new(TestGenConfig::default());
1037 let func = HirFunction {
1038 name: "my_sort".to_string(),
1039 params: smallvec![make_param("arr", Type::List(Box::new(Type::Int)))],
1040 ret_type: Type::List(Box::new(Type::Int)),
1041 body: vec![],
1042 properties: make_pure_properties(),
1043 annotations: TranspilationAnnotations::default(),
1044 docstring: None,
1045 };
1046 assert!(gen.preserves_length(&func));
1047 }
1048
1049 #[test]
1050 fn test_preserves_length_map() {
1051 let gen = TestGenerator::new(TestGenConfig::default());
1052 let func = HirFunction {
1053 name: "double_map".to_string(),
1054 params: smallvec![make_param("arr", Type::List(Box::new(Type::Int)))],
1055 ret_type: Type::List(Box::new(Type::Int)),
1056 body: vec![],
1057 properties: make_pure_properties(),
1058 annotations: TranspilationAnnotations::default(),
1059 docstring: None,
1060 };
1061 assert!(gen.preserves_length(&func));
1062 }
1063
1064 #[test]
1065 fn test_preserves_length_false_no_list() {
1066 let gen = TestGenerator::new(TestGenConfig::default());
1067 let func = HirFunction {
1068 name: "my_sort".to_string(),
1069 params: smallvec![make_param("x", Type::Int)],
1070 ret_type: Type::Int,
1071 body: vec![],
1072 properties: make_pure_properties(),
1073 annotations: TranspilationAnnotations::default(),
1074 docstring: None,
1075 };
1076 assert!(!gen.preserves_length(&func));
1077 }
1078
1079 #[test]
1081 fn test_is_idempotent_normalize() {
1082 let gen = TestGenerator::new(TestGenConfig::default());
1083 let func = HirFunction {
1084 name: "normalize_path".to_string(),
1085 params: smallvec![],
1086 ret_type: Type::String,
1087 body: vec![],
1088 properties: make_pure_properties(),
1089 annotations: TranspilationAnnotations::default(),
1090 docstring: None,
1091 };
1092 assert!(gen.is_idempotent(&func));
1093 }
1094
1095 #[test]
1096 fn test_is_idempotent_clean() {
1097 let gen = TestGenerator::new(TestGenConfig::default());
1098 let func = HirFunction {
1099 name: "clean_text".to_string(),
1100 params: smallvec![],
1101 ret_type: Type::String,
1102 body: vec![],
1103 properties: make_pure_properties(),
1104 annotations: TranspilationAnnotations::default(),
1105 docstring: None,
1106 };
1107 assert!(gen.is_idempotent(&func));
1108 }
1109
1110 #[test]
1111 fn test_is_idempotent_false() {
1112 let gen = TestGenerator::new(TestGenConfig::default());
1113 let func = HirFunction {
1114 name: "increment".to_string(),
1115 params: smallvec![],
1116 ret_type: Type::Int,
1117 body: vec![],
1118 properties: make_pure_properties(),
1119 annotations: TranspilationAnnotations::default(),
1120 docstring: None,
1121 };
1122 assert!(!gen.is_idempotent(&func));
1123 }
1124
1125 #[test]
1127 fn test_is_sorting_function_true() {
1128 let gen = TestGenerator::new(TestGenConfig::default());
1129 let func = HirFunction {
1130 name: "bubble_sort".to_string(),
1131 params: smallvec![make_param("arr", Type::List(Box::new(Type::Int)))],
1132 ret_type: Type::List(Box::new(Type::Int)),
1133 body: vec![],
1134 properties: make_pure_properties(),
1135 annotations: TranspilationAnnotations::default(),
1136 docstring: None,
1137 };
1138 assert!(gen.is_sorting_function(&func));
1139 }
1140
1141 #[test]
1142 fn test_is_sorting_function_no_params_false() {
1143 let gen = TestGenerator::new(TestGenConfig::default());
1145 let func = HirFunction {
1146 name: "get_sorted".to_string(),
1147 params: smallvec![],
1148 ret_type: Type::List(Box::new(Type::Int)),
1149 body: vec![],
1150 properties: make_pure_properties(),
1151 annotations: TranspilationAnnotations::default(),
1152 docstring: None,
1153 };
1154 assert!(!gen.is_sorting_function(&func));
1155 }
1156
1157 #[test]
1158 fn test_is_sorting_function_no_sort_in_name() {
1159 let gen = TestGenerator::new(TestGenConfig::default());
1160 let func = HirFunction {
1161 name: "order_items".to_string(),
1162 params: smallvec![make_param("arr", Type::List(Box::new(Type::Int)))],
1163 ret_type: Type::List(Box::new(Type::Int)),
1164 body: vec![],
1165 properties: make_pure_properties(),
1166 annotations: TranspilationAnnotations::default(),
1167 docstring: None,
1168 };
1169 assert!(!gen.is_sorting_function(&func));
1170 }
1171
1172 #[test]
1174 fn test_type_to_quickcheck_type_int() {
1175 let gen = TestGenerator::new(TestGenConfig::default());
1176 let result = gen.type_to_quickcheck_type(&Type::Int);
1177 assert_eq!(result.to_string(), "i32");
1178 }
1179
1180 #[test]
1181 fn test_type_to_quickcheck_type_float() {
1182 let gen = TestGenerator::new(TestGenConfig::default());
1183 let result = gen.type_to_quickcheck_type(&Type::Float);
1184 assert_eq!(result.to_string(), "f64");
1185 }
1186
1187 #[test]
1188 fn test_type_to_quickcheck_type_string() {
1189 let gen = TestGenerator::new(TestGenConfig::default());
1190 let result = gen.type_to_quickcheck_type(&Type::String);
1191 assert_eq!(result.to_string(), "String");
1192 }
1193
1194 #[test]
1195 fn test_type_to_quickcheck_type_bool() {
1196 let gen = TestGenerator::new(TestGenConfig::default());
1197 let result = gen.type_to_quickcheck_type(&Type::Bool);
1198 assert_eq!(result.to_string(), "bool");
1199 }
1200
1201 #[test]
1202 fn test_type_to_quickcheck_type_list() {
1203 let gen = TestGenerator::new(TestGenConfig::default());
1204 let result = gen.type_to_quickcheck_type(&Type::List(Box::new(Type::Int)));
1205 assert_eq!(result.to_string(), "Vec < i32 >");
1206 }
1207
1208 #[test]
1209 fn test_type_to_quickcheck_type_unsupported() {
1210 let gen = TestGenerator::new(TestGenConfig::default());
1211 let result = gen.type_to_quickcheck_type(&Type::None);
1212 assert_eq!(result.to_string(), "()");
1213 }
1214
1215 #[test]
1217 fn test_analyze_function_properties_identity() {
1218 let gen = TestGenerator::new(TestGenConfig::default());
1219 let func = HirFunction {
1220 name: "identity".to_string(),
1221 params: smallvec![make_param("x", Type::Int)],
1222 ret_type: Type::Int,
1223 body: vec![HirStmt::Return(Some(HirExpr::Var("x".to_string())))],
1224 properties: make_pure_properties(),
1225 annotations: TranspilationAnnotations::default(),
1226 docstring: None,
1227 };
1228 let props = gen.analyze_function_properties(&func);
1229 assert!(props.contains(&TestProperty::Identity));
1230 }
1231
1232 #[test]
1233 fn test_analyze_function_properties_commutative() {
1234 let gen = TestGenerator::new(TestGenConfig::default());
1235 let func = HirFunction {
1236 name: "add".to_string(),
1237 params: smallvec![make_param("a", Type::Int), make_param("b", Type::Int)],
1238 ret_type: Type::Int,
1239 body: vec![HirStmt::Return(Some(HirExpr::Binary {
1240 op: BinOp::Add,
1241 left: Box::new(HirExpr::Var("a".to_string())),
1242 right: Box::new(HirExpr::Var("b".to_string())),
1243 }))],
1244 properties: make_pure_properties(),
1245 annotations: TranspilationAnnotations::default(),
1246 docstring: None,
1247 };
1248 let props = gen.analyze_function_properties(&func);
1249 assert!(props.contains(&TestProperty::Commutative));
1250 }
1251
1252 #[test]
1253 fn test_analyze_function_properties_sorting() {
1254 let gen = TestGenerator::new(TestGenConfig::default());
1255 let func = HirFunction {
1256 name: "my_sort".to_string(),
1257 params: smallvec![make_param("arr", Type::List(Box::new(Type::Int)))],
1258 ret_type: Type::List(Box::new(Type::Int)),
1259 body: vec![],
1260 properties: make_pure_properties(),
1261 annotations: TranspilationAnnotations::default(),
1262 docstring: None,
1263 };
1264 let props = gen.analyze_function_properties(&func);
1265 assert!(props.contains(&TestProperty::Sorted));
1266 assert!(props.contains(&TestProperty::SameElements));
1267 assert!(props.contains(&TestProperty::LengthPreserving));
1268 }
1269
1270 #[test]
1272 fn test_property_eq() {
1273 assert_eq!(TestProperty::Identity, TestProperty::Identity);
1274 assert_ne!(TestProperty::Identity, TestProperty::Commutative);
1275 }
1276
1277 #[test]
1278 fn test_property_clone() {
1279 let prop = TestProperty::NonNegative;
1280 let cloned = prop.clone();
1281 assert_eq!(prop, cloned);
1282 }
1283
1284 #[test]
1285 fn test_property_debug() {
1286 let prop = TestProperty::Idempotent;
1287 let debug_str = format!("{:?}", prop);
1288 assert_eq!(debug_str, "Idempotent");
1289 }
1290}