1#![allow(clippy::match_same_arms)]
9#![allow(clippy::self_only_used_in_recursion)]
10
11use crate::generator::{BinaryOp, CompareOp, PythonNode, UnaryOp};
12
13use super::MutationOperator;
14
15#[derive(Debug, Clone)]
17pub struct AstMutation {
18 pub operator: MutationOperator,
20 pub path: Vec<usize>,
22 pub description: String,
24 pub mutated_ast: PythonNode,
26}
27
28#[derive(Debug, Default)]
30pub struct AstMutator {
31 enabled_operators: Vec<MutationOperator>,
33}
34
35impl AstMutator {
36 #[must_use]
38 pub fn new() -> Self {
39 Self {
40 enabled_operators: MutationOperator::all(),
41 }
42 }
43
44 #[must_use]
46 pub fn with_operators(operators: Vec<MutationOperator>) -> Self {
47 Self {
48 enabled_operators: operators,
49 }
50 }
51
52 #[must_use]
54 pub fn mutate(&self, ast: &PythonNode) -> Vec<AstMutation> {
55 let mut mutations = Vec::new();
56
57 for operator in &self.enabled_operators {
58 let op_mutations = match operator {
59 MutationOperator::Aor => self.apply_aor(ast, vec![]),
60 MutationOperator::Ror => self.apply_ror(ast, vec![]),
61 MutationOperator::Lor => self.apply_lor(ast, vec![]),
62 MutationOperator::Uoi => self.apply_uoi(ast, vec![]),
63 MutationOperator::Abs => self.apply_abs(ast, vec![]),
64 MutationOperator::Sdl => self.apply_sdl(ast, vec![]),
65 MutationOperator::Svr => self.apply_svr(ast, vec![]),
66 MutationOperator::Bsr => self.apply_bsr(ast, vec![]),
67 };
68 mutations.extend(op_mutations);
69 }
70
71 mutations
72 }
73
74 fn apply_aor(&self, ast: &PythonNode, path: Vec<usize>) -> Vec<AstMutation> {
77 let mut mutations = Vec::new();
78
79 match ast {
80 PythonNode::BinOp { left, op, right } => {
81 let replacements = arithmetic_replacements(*op);
83 for new_op in replacements {
84 let mutated = PythonNode::BinOp {
85 left: left.clone(),
86 op: new_op,
87 right: right.clone(),
88 };
89 mutations.push(AstMutation {
90 operator: MutationOperator::Aor,
91 path: path.clone(),
92 description: format!("Replace {} with {}", op.to_str(), new_op.to_str()),
93 mutated_ast: mutated,
94 });
95 }
96
97 let mut left_path = path.clone();
99 left_path.push(0);
100 mutations.extend(self.apply_aor(left, left_path));
101
102 let mut right_path = path;
103 right_path.push(1);
104 mutations.extend(self.apply_aor(right, right_path));
105 }
106 PythonNode::Module(stmts) => {
107 for (i, stmt) in stmts.iter().enumerate() {
108 let mut child_path = path.clone();
109 child_path.push(i);
110 mutations.extend(self.apply_aor(stmt, child_path));
111 }
112 }
113 PythonNode::Assign { value, .. } => {
114 let mut child_path = path;
115 child_path.push(0);
116 mutations.extend(self.apply_aor(value, child_path));
117 }
118 PythonNode::If { test, body, orelse } => {
119 let mut test_path = path.clone();
120 test_path.push(0);
121 mutations.extend(self.apply_aor(test, test_path));
122
123 for (i, stmt) in body.iter().enumerate() {
124 let mut child_path = path.clone();
125 child_path.push(1 + i);
126 mutations.extend(self.apply_aor(stmt, child_path));
127 }
128
129 for (i, stmt) in orelse.iter().enumerate() {
130 let mut child_path = path.clone();
131 child_path.push(1 + body.len() + i);
132 mutations.extend(self.apply_aor(stmt, child_path));
133 }
134 }
135 PythonNode::Return(Some(value)) => {
136 let mut child_path = path;
137 child_path.push(0);
138 mutations.extend(self.apply_aor(value, child_path));
139 }
140 PythonNode::UnaryOp { operand, .. } => {
141 let mut child_path = path;
142 child_path.push(0);
143 mutations.extend(self.apply_aor(operand, child_path));
144 }
145 PythonNode::Compare { left, right, .. } => {
146 let mut left_path = path.clone();
147 left_path.push(0);
148 mutations.extend(self.apply_aor(left, left_path));
149
150 let mut right_path = path;
151 right_path.push(1);
152 mutations.extend(self.apply_aor(right, right_path));
153 }
154 _ => {}
155 }
156
157 mutations
158 }
159
160 fn apply_ror(&self, ast: &PythonNode, path: Vec<usize>) -> Vec<AstMutation> {
163 let mut mutations = Vec::new();
164
165 match ast {
166 PythonNode::Compare { left, op, right } => {
167 let replacements = relational_replacements(*op);
169 for new_op in replacements {
170 let mutated = PythonNode::Compare {
171 left: left.clone(),
172 op: new_op,
173 right: right.clone(),
174 };
175 mutations.push(AstMutation {
176 operator: MutationOperator::Ror,
177 path: path.clone(),
178 description: format!("Replace {} with {}", op.to_str(), new_op.to_str()),
179 mutated_ast: mutated,
180 });
181 }
182
183 let mut left_path = path.clone();
185 left_path.push(0);
186 mutations.extend(self.apply_ror(left, left_path));
187
188 let mut right_path = path;
189 right_path.push(1);
190 mutations.extend(self.apply_ror(right, right_path));
191 }
192 PythonNode::Module(stmts) => {
193 for (i, stmt) in stmts.iter().enumerate() {
194 let mut child_path = path.clone();
195 child_path.push(i);
196 mutations.extend(self.apply_ror(stmt, child_path));
197 }
198 }
199 PythonNode::If { test, body, orelse } => {
200 let mut test_path = path.clone();
201 test_path.push(0);
202 mutations.extend(self.apply_ror(test, test_path));
203
204 for (i, stmt) in body.iter().enumerate() {
205 let mut child_path = path.clone();
206 child_path.push(1 + i);
207 mutations.extend(self.apply_ror(stmt, child_path));
208 }
209
210 for (i, stmt) in orelse.iter().enumerate() {
211 let mut child_path = path.clone();
212 child_path.push(1 + body.len() + i);
213 mutations.extend(self.apply_ror(stmt, child_path));
214 }
215 }
216 PythonNode::While { test, body } => {
217 let mut test_path = path.clone();
218 test_path.push(0);
219 mutations.extend(self.apply_ror(test, test_path));
220
221 for (i, stmt) in body.iter().enumerate() {
222 let mut child_path = path.clone();
223 child_path.push(1 + i);
224 mutations.extend(self.apply_ror(stmt, child_path));
225 }
226 }
227 _ => {}
228 }
229
230 mutations
231 }
232
233 fn apply_lor(&self, ast: &PythonNode, path: Vec<usize>) -> Vec<AstMutation> {
236 let mut mutations = Vec::new();
237
238 match ast {
239 PythonNode::BinOp { left, op, right } => {
240 let replacements = logical_replacements(*op);
242 for new_op in replacements {
243 let mutated = PythonNode::BinOp {
244 left: left.clone(),
245 op: new_op,
246 right: right.clone(),
247 };
248 mutations.push(AstMutation {
249 operator: MutationOperator::Lor,
250 path: path.clone(),
251 description: format!("Replace {} with {}", op.to_str(), new_op.to_str()),
252 mutated_ast: mutated,
253 });
254 }
255
256 let mut left_path = path.clone();
258 left_path.push(0);
259 mutations.extend(self.apply_lor(left, left_path));
260
261 let mut right_path = path;
262 right_path.push(1);
263 mutations.extend(self.apply_lor(right, right_path));
264 }
265 PythonNode::Module(stmts) => {
266 for (i, stmt) in stmts.iter().enumerate() {
267 let mut child_path = path.clone();
268 child_path.push(i);
269 mutations.extend(self.apply_lor(stmt, child_path));
270 }
271 }
272 PythonNode::If { test, body, orelse } => {
273 let mut test_path = path.clone();
274 test_path.push(0);
275 mutations.extend(self.apply_lor(test, test_path));
276
277 for (i, stmt) in body.iter().enumerate() {
278 let mut child_path = path.clone();
279 child_path.push(1 + i);
280 mutations.extend(self.apply_lor(stmt, child_path));
281 }
282
283 for (i, stmt) in orelse.iter().enumerate() {
284 let mut child_path = path.clone();
285 child_path.push(1 + body.len() + i);
286 mutations.extend(self.apply_lor(stmt, child_path));
287 }
288 }
289 _ => {}
290 }
291
292 mutations
293 }
294
295 fn apply_uoi(&self, ast: &PythonNode, path: Vec<usize>) -> Vec<AstMutation> {
298 let mut mutations = Vec::new();
299
300 match ast {
301 PythonNode::Name(name) => {
302 let neg_mutated = PythonNode::UnaryOp {
304 op: UnaryOp::Neg,
305 operand: Box::new(ast.clone()),
306 };
307 mutations.push(AstMutation {
308 operator: MutationOperator::Uoi,
309 path: path.clone(),
310 description: format!("Insert negation: {name} → -{name}"),
311 mutated_ast: neg_mutated,
312 });
313
314 let not_mutated = PythonNode::UnaryOp {
316 op: UnaryOp::Not,
317 operand: Box::new(ast.clone()),
318 };
319 mutations.push(AstMutation {
320 operator: MutationOperator::Uoi,
321 path,
322 description: format!("Insert not: {name} → not {name}"),
323 mutated_ast: not_mutated,
324 });
325 }
326 PythonNode::IntLit(n) => {
327 let neg_mutated = PythonNode::UnaryOp {
329 op: UnaryOp::Neg,
330 operand: Box::new(ast.clone()),
331 };
332 mutations.push(AstMutation {
333 operator: MutationOperator::Uoi,
334 path,
335 description: format!("Insert negation: {n} → -{n}"),
336 mutated_ast: neg_mutated,
337 });
338 }
339 PythonNode::Module(stmts) => {
340 for (i, stmt) in stmts.iter().enumerate() {
341 let mut child_path = path.clone();
342 child_path.push(i);
343 mutations.extend(self.apply_uoi(stmt, child_path));
344 }
345 }
346 PythonNode::Assign { value, .. } => {
347 let mut child_path = path;
348 child_path.push(0);
349 mutations.extend(self.apply_uoi(value, child_path));
350 }
351 PythonNode::BinOp { left, right, .. } => {
352 let mut left_path = path.clone();
353 left_path.push(0);
354 mutations.extend(self.apply_uoi(left, left_path));
355
356 let mut right_path = path;
357 right_path.push(1);
358 mutations.extend(self.apply_uoi(right, right_path));
359 }
360 _ => {}
361 }
362
363 mutations
364 }
365
366 fn apply_abs(&self, ast: &PythonNode, path: Vec<usize>) -> Vec<AstMutation> {
369 let mut mutations = Vec::new();
370
371 match ast {
372 PythonNode::Name(name) => {
373 let abs_mutated = PythonNode::Call {
374 func: "abs".to_string(),
375 args: vec![ast.clone()],
376 };
377 mutations.push(AstMutation {
378 operator: MutationOperator::Abs,
379 path,
380 description: format!("Insert abs: {name} → abs({name})"),
381 mutated_ast: abs_mutated,
382 });
383 }
384 PythonNode::IntLit(n) => {
385 let abs_mutated = PythonNode::Call {
386 func: "abs".to_string(),
387 args: vec![ast.clone()],
388 };
389 mutations.push(AstMutation {
390 operator: MutationOperator::Abs,
391 path,
392 description: format!("Insert abs: {n} → abs({n})"),
393 mutated_ast: abs_mutated,
394 });
395 }
396 PythonNode::Module(stmts) => {
397 for (i, stmt) in stmts.iter().enumerate() {
398 let mut child_path = path.clone();
399 child_path.push(i);
400 mutations.extend(self.apply_abs(stmt, child_path));
401 }
402 }
403 PythonNode::Assign { value, .. } => {
404 let mut child_path = path;
405 child_path.push(0);
406 mutations.extend(self.apply_abs(value, child_path));
407 }
408 _ => {}
409 }
410
411 mutations
412 }
413
414 fn apply_sdl(&self, ast: &PythonNode, path: Vec<usize>) -> Vec<AstMutation> {
417 let mut mutations = Vec::new();
418
419 if let PythonNode::Module(stmts) = ast {
420 for i in 0..stmts.len() {
422 if stmts.len() > 1 {
423 let mut new_stmts = stmts.clone();
425 new_stmts.remove(i);
426
427 let mutated = PythonNode::Module(new_stmts);
428 let mut del_path = path.clone();
429 del_path.push(i);
430
431 mutations.push(AstMutation {
432 operator: MutationOperator::Sdl,
433 path: del_path,
434 description: format!("Delete statement {}", i + 1),
435 mutated_ast: mutated,
436 });
437 }
438 }
439
440 for (i, stmt) in stmts.iter().enumerate() {
442 let mut child_path = path.clone();
443 child_path.push(i);
444 mutations.extend(self.apply_sdl_compound(stmt, child_path));
445 }
446 }
447
448 mutations
449 }
450
451 fn apply_sdl_compound(&self, ast: &PythonNode, path: Vec<usize>) -> Vec<AstMutation> {
453 let mut mutations = Vec::new();
454
455 match ast {
456 PythonNode::If { test, body, orelse } => {
457 if body.len() > 1 {
459 for i in 0..body.len() {
460 let mut new_body = body.clone();
461 new_body.remove(i);
462
463 let mutated = PythonNode::If {
464 test: test.clone(),
465 body: new_body,
466 orelse: orelse.clone(),
467 };
468
469 let mut del_path = path.clone();
470 del_path.push(i);
471
472 mutations.push(AstMutation {
473 operator: MutationOperator::Sdl,
474 path: del_path,
475 description: format!("Delete if-body statement {}", i + 1),
476 mutated_ast: mutated,
477 });
478 }
479 }
480
481 if orelse.len() > 1 {
483 for i in 0..orelse.len() {
484 let mut new_orelse = orelse.clone();
485 new_orelse.remove(i);
486
487 let mutated = PythonNode::If {
488 test: test.clone(),
489 body: body.clone(),
490 orelse: new_orelse,
491 };
492
493 let mut del_path = path.clone();
494 del_path.push(body.len() + i);
495
496 mutations.push(AstMutation {
497 operator: MutationOperator::Sdl,
498 path: del_path,
499 description: format!("Delete else-body statement {}", i + 1),
500 mutated_ast: mutated,
501 });
502 }
503 }
504 }
505 PythonNode::FuncDef { name, args, body } => {
506 if body.len() > 1 {
507 for i in 0..body.len() {
508 let mut new_body = body.clone();
509 new_body.remove(i);
510
511 let mutated = PythonNode::FuncDef {
512 name: name.clone(),
513 args: args.clone(),
514 body: new_body,
515 };
516
517 let mut del_path = path.clone();
518 del_path.push(i);
519
520 mutations.push(AstMutation {
521 operator: MutationOperator::Sdl,
522 path: del_path,
523 description: format!("Delete function body statement {}", i + 1),
524 mutated_ast: mutated,
525 });
526 }
527 }
528 }
529 _ => {}
530 }
531
532 mutations
533 }
534
535 fn apply_svr(&self, ast: &PythonNode, path: Vec<usize>) -> Vec<AstMutation> {
538 let mut mutations = Vec::new();
539
540 let var_names = collect_variable_names(ast);
542
543 if var_names.len() < 2 {
544 return mutations; }
546
547 self.apply_svr_recursive(ast, path, &var_names, &mut mutations);
548 mutations
549 }
550
551 fn apply_svr_recursive(
552 &self,
553 ast: &PythonNode,
554 path: Vec<usize>,
555 var_names: &[String],
556 mutations: &mut Vec<AstMutation>,
557 ) {
558 match ast {
559 PythonNode::Name(name) => {
560 for other_var in var_names {
562 if other_var != name {
563 let mutated = PythonNode::Name(other_var.clone());
564 mutations.push(AstMutation {
565 operator: MutationOperator::Svr,
566 path: path.clone(),
567 description: format!("Replace {name} with {other_var}"),
568 mutated_ast: mutated,
569 });
570 }
571 }
572 }
573 PythonNode::Module(stmts) => {
574 for (i, stmt) in stmts.iter().enumerate() {
575 let mut child_path = path.clone();
576 child_path.push(i);
577 self.apply_svr_recursive(stmt, child_path, var_names, mutations);
578 }
579 }
580 PythonNode::Assign { value, .. } => {
581 let mut child_path = path;
582 child_path.push(0);
583 self.apply_svr_recursive(value, child_path, var_names, mutations);
584 }
585 PythonNode::BinOp { left, right, .. } => {
586 let mut left_path = path.clone();
587 left_path.push(0);
588 self.apply_svr_recursive(left, left_path, var_names, mutations);
589
590 let mut right_path = path;
591 right_path.push(1);
592 self.apply_svr_recursive(right, right_path, var_names, mutations);
593 }
594 _ => {}
595 }
596 }
597
598 fn apply_bsr(&self, ast: &PythonNode, path: Vec<usize>) -> Vec<AstMutation> {
601 let mut mutations = Vec::new();
602
603 match ast {
604 PythonNode::IntLit(n) => {
605 let boundaries = boundary_int_values(*n);
606 for new_val in boundaries {
607 let mutated = PythonNode::IntLit(new_val);
608 mutations.push(AstMutation {
609 operator: MutationOperator::Bsr,
610 path: path.clone(),
611 description: format!("Boundary: {n} → {new_val}"),
612 mutated_ast: mutated,
613 });
614 }
615 }
616 PythonNode::StrLit(s) => {
617 let boundaries = boundary_str_values(s);
618 for new_val in boundaries {
619 let mutated = PythonNode::StrLit(new_val.clone());
620 mutations.push(AstMutation {
621 operator: MutationOperator::Bsr,
622 path: path.clone(),
623 description: format!("Boundary: \"{s}\" → \"{new_val}\""),
624 mutated_ast: mutated,
625 });
626 }
627 }
628 PythonNode::BoolLit(b) => {
629 let mutated = PythonNode::BoolLit(!b);
630 mutations.push(AstMutation {
631 operator: MutationOperator::Bsr,
632 path,
633 description: format!("Boundary: {b} → {}", !b),
634 mutated_ast: mutated,
635 });
636 }
637 PythonNode::Module(stmts) => {
638 for (i, stmt) in stmts.iter().enumerate() {
639 let mut child_path = path.clone();
640 child_path.push(i);
641 mutations.extend(self.apply_bsr(stmt, child_path));
642 }
643 }
644 PythonNode::Assign { value, .. } => {
645 let mut child_path = path;
646 child_path.push(0);
647 mutations.extend(self.apply_bsr(value, child_path));
648 }
649 PythonNode::BinOp { left, right, .. } => {
650 let mut left_path = path.clone();
651 left_path.push(0);
652 mutations.extend(self.apply_bsr(left, left_path));
653
654 let mut right_path = path;
655 right_path.push(1);
656 mutations.extend(self.apply_bsr(right, right_path));
657 }
658 _ => {}
659 }
660
661 mutations
662 }
663}
664
665fn arithmetic_replacements(op: BinaryOp) -> Vec<BinaryOp> {
667 match op {
668 BinaryOp::Add => vec![BinaryOp::Sub, BinaryOp::Mult],
669 BinaryOp::Sub => vec![BinaryOp::Add, BinaryOp::Mult],
670 BinaryOp::Mult => vec![BinaryOp::Add, BinaryOp::Div],
671 BinaryOp::Div => vec![BinaryOp::Mult, BinaryOp::FloorDiv],
672 BinaryOp::Mod => vec![BinaryOp::Div, BinaryOp::FloorDiv],
673 BinaryOp::FloorDiv => vec![BinaryOp::Div, BinaryOp::Mod],
674 BinaryOp::Pow => vec![BinaryOp::Mult],
675 BinaryOp::And | BinaryOp::Or => vec![], }
677}
678
679fn relational_replacements(op: CompareOp) -> Vec<CompareOp> {
681 match op {
682 CompareOp::Eq => vec![CompareOp::NotEq],
683 CompareOp::NotEq => vec![CompareOp::Eq],
684 CompareOp::Lt => vec![CompareOp::LtE, CompareOp::Gt],
685 CompareOp::LtE => vec![CompareOp::Lt, CompareOp::GtE],
686 CompareOp::Gt => vec![CompareOp::GtE, CompareOp::Lt],
687 CompareOp::GtE => vec![CompareOp::Gt, CompareOp::LtE],
688 }
689}
690
691fn logical_replacements(op: BinaryOp) -> Vec<BinaryOp> {
693 match op {
694 BinaryOp::And => vec![BinaryOp::Or],
695 BinaryOp::Or => vec![BinaryOp::And],
696 _ => vec![], }
698}
699
700fn collect_variable_names(ast: &PythonNode) -> Vec<String> {
702 let mut names = Vec::new();
703 collect_names_recursive(ast, &mut names);
704 names.sort();
705 names.dedup();
706 names
707}
708
709fn collect_names_recursive(ast: &PythonNode, names: &mut Vec<String>) {
710 match ast {
711 PythonNode::Name(name) => names.push(name.clone()),
712 PythonNode::Assign { target, value } => {
713 names.push(target.clone());
714 collect_names_recursive(value, names);
715 }
716 PythonNode::Module(stmts) => {
717 for stmt in stmts {
718 collect_names_recursive(stmt, names);
719 }
720 }
721 PythonNode::BinOp { left, right, .. } | PythonNode::Compare { left, right, .. } => {
722 collect_names_recursive(left, names);
723 collect_names_recursive(right, names);
724 }
725 PythonNode::UnaryOp { operand, .. } => {
726 collect_names_recursive(operand, names);
727 }
728 PythonNode::If { test, body, orelse } => {
729 collect_names_recursive(test, names);
730 for stmt in body {
731 collect_names_recursive(stmt, names);
732 }
733 for stmt in orelse {
734 collect_names_recursive(stmt, names);
735 }
736 }
737 PythonNode::FuncDef { args, body, .. } => {
738 for arg in args {
739 names.push(arg.clone());
740 }
741 for stmt in body {
742 collect_names_recursive(stmt, names);
743 }
744 }
745 PythonNode::Return(Some(value)) => collect_names_recursive(value, names),
746 _ => {}
747 }
748}
749
750fn boundary_int_values(n: i64) -> Vec<i64> {
752 match n {
753 0 => vec![-1, 1],
754 1 => vec![0, 2],
755 -1 => vec![0, -2],
756 _ if n > 0 => vec![n - 1, n + 1, 0],
757 _ => vec![n - 1, n + 1, 0],
758 }
759}
760
761fn boundary_str_values(s: &str) -> Vec<String> {
763 if s.is_empty() {
764 vec![" ".to_string(), "a".to_string()]
765 } else {
766 vec![String::new(), format!("{s} ")]
767 }
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773
774 #[test]
775 fn test_aor_basic() {
776 let ast = PythonNode::BinOp {
777 left: Box::new(PythonNode::Name("a".to_string())),
778 op: BinaryOp::Add,
779 right: Box::new(PythonNode::Name("b".to_string())),
780 };
781
782 let mutator = AstMutator::with_operators(vec![MutationOperator::Aor]);
783 let mutations = mutator.mutate(&ast);
784
785 assert!(!mutations.is_empty());
786 assert!(mutations
788 .iter()
789 .any(|m| m.description.contains("Replace + with -")));
790 }
791
792 #[test]
793 fn test_ror_basic() {
794 let ast = PythonNode::Compare {
795 left: Box::new(PythonNode::Name("x".to_string())),
796 op: CompareOp::Lt,
797 right: Box::new(PythonNode::IntLit(10)),
798 };
799
800 let mutator = AstMutator::with_operators(vec![MutationOperator::Ror]);
801 let mutations = mutator.mutate(&ast);
802
803 assert!(!mutations.is_empty());
804 assert!(mutations
805 .iter()
806 .any(|m| m.description.contains("Replace < with <=")));
807 }
808
809 #[test]
810 fn test_lor_basic() {
811 let ast = PythonNode::BinOp {
812 left: Box::new(PythonNode::Name("a".to_string())),
813 op: BinaryOp::And,
814 right: Box::new(PythonNode::Name("b".to_string())),
815 };
816
817 let mutator = AstMutator::with_operators(vec![MutationOperator::Lor]);
818 let mutations = mutator.mutate(&ast);
819
820 assert!(!mutations.is_empty());
821 assert!(mutations
822 .iter()
823 .any(|m| m.description.contains("Replace and with or")));
824 }
825
826 #[test]
827 fn test_uoi_basic() {
828 let ast = PythonNode::Assign {
829 target: "x".to_string(),
830 value: Box::new(PythonNode::Name("y".to_string())),
831 };
832
833 let mutator = AstMutator::with_operators(vec![MutationOperator::Uoi]);
834 let mutations = mutator.mutate(&ast);
835
836 assert!(!mutations.is_empty());
837 assert!(mutations
838 .iter()
839 .any(|m| m.description.contains("Insert negation")));
840 }
841
842 #[test]
843 fn test_abs_basic() {
844 let ast = PythonNode::Assign {
845 target: "x".to_string(),
846 value: Box::new(PythonNode::Name("y".to_string())),
847 };
848
849 let mutator = AstMutator::with_operators(vec![MutationOperator::Abs]);
850 let mutations = mutator.mutate(&ast);
851
852 assert!(!mutations.is_empty());
853 assert!(mutations
854 .iter()
855 .any(|m| m.description.contains("Insert abs")));
856 }
857
858 #[test]
859 fn test_sdl_basic() {
860 let ast = PythonNode::Module(vec![
861 PythonNode::Assign {
862 target: "x".to_string(),
863 value: Box::new(PythonNode::IntLit(1)),
864 },
865 PythonNode::Assign {
866 target: "y".to_string(),
867 value: Box::new(PythonNode::IntLit(2)),
868 },
869 ]);
870
871 let mutator = AstMutator::with_operators(vec![MutationOperator::Sdl]);
872 let mutations = mutator.mutate(&ast);
873
874 assert_eq!(mutations.len(), 2); }
876
877 #[test]
878 fn test_svr_basic() {
879 let ast = PythonNode::Module(vec![
880 PythonNode::Assign {
881 target: "x".to_string(),
882 value: Box::new(PythonNode::IntLit(1)),
883 },
884 PythonNode::Assign {
885 target: "y".to_string(),
886 value: Box::new(PythonNode::Name("x".to_string())),
887 },
888 ]);
889
890 let mutator = AstMutator::with_operators(vec![MutationOperator::Svr]);
891 let mutations = mutator.mutate(&ast);
892
893 assert!(!mutations.is_empty());
894 assert!(mutations
896 .iter()
897 .any(|m| m.description.contains("Replace x with y")));
898 }
899
900 #[test]
901 fn test_bsr_basic() {
902 let ast = PythonNode::Assign {
903 target: "x".to_string(),
904 value: Box::new(PythonNode::IntLit(0)),
905 };
906
907 let mutator = AstMutator::with_operators(vec![MutationOperator::Bsr]);
908 let mutations = mutator.mutate(&ast);
909
910 assert!(!mutations.is_empty());
911 assert!(mutations.iter().any(|m| m.description.contains("0 → -1")));
913 assert!(mutations.iter().any(|m| m.description.contains("0 → 1")));
914 }
915
916 #[test]
917 fn test_all_operators() {
918 let ast = PythonNode::Module(vec![
919 PythonNode::Assign {
920 target: "x".to_string(),
921 value: Box::new(PythonNode::BinOp {
922 left: Box::new(PythonNode::IntLit(1)),
923 op: BinaryOp::Add,
924 right: Box::new(PythonNode::IntLit(2)),
925 }),
926 },
927 PythonNode::If {
928 test: Box::new(PythonNode::Compare {
929 left: Box::new(PythonNode::Name("x".to_string())),
930 op: CompareOp::Lt,
931 right: Box::new(PythonNode::IntLit(10)),
932 }),
933 body: vec![PythonNode::Pass],
934 orelse: vec![],
935 },
936 ]);
937
938 let mutator = AstMutator::new();
939 let mutations = mutator.mutate(&ast);
940
941 assert!(mutations.len() > 5);
943
944 let operators: std::collections::HashSet<_> =
946 mutations.iter().map(|m| m.operator).collect();
947 assert!(operators.len() >= 3);
948 }
949
950 #[test]
951 fn test_mutation_produces_valid_ast() {
952 let ast = PythonNode::BinOp {
953 left: Box::new(PythonNode::IntLit(1)),
954 op: BinaryOp::Add,
955 right: Box::new(PythonNode::IntLit(2)),
956 };
957
958 let mutator = AstMutator::new();
959 let mutations = mutator.mutate(&ast);
960
961 for mutation in &mutations {
963 let code = mutation.mutated_ast.to_code(0);
964 assert!(!code.is_empty());
965 }
966 }
967
968 #[test]
969 fn test_ast_mutator_default() {
970 let mutator = AstMutator::default();
971 assert!(mutator.enabled_operators.is_empty());
972 }
973
974 #[test]
975 fn test_ast_mutator_debug() {
976 let mutator = AstMutator::new();
977 let debug = format!("{:?}", mutator);
978 assert!(debug.contains("AstMutator"));
979 }
980
981 #[test]
982 fn test_ast_mutation_debug() {
983 let mutation = AstMutation {
984 operator: MutationOperator::Aor,
985 path: vec![0, 1],
986 description: "Test mutation".to_string(),
987 mutated_ast: PythonNode::IntLit(1),
988 };
989 let debug = format!("{:?}", mutation);
990 assert!(debug.contains("AstMutation"));
991 }
992
993 #[test]
994 fn test_ast_mutation_clone() {
995 let mutation = AstMutation {
996 operator: MutationOperator::Aor,
997 path: vec![0, 1],
998 description: "Test mutation".to_string(),
999 mutated_ast: PythonNode::IntLit(1),
1000 };
1001 let cloned = mutation.clone();
1002 assert_eq!(cloned.operator, mutation.operator);
1003 assert_eq!(cloned.path, mutation.path);
1004 }
1005
1006 #[test]
1007 fn test_aor_in_if_body() {
1008 let ast = PythonNode::If {
1009 test: Box::new(PythonNode::BoolLit(true)),
1010 body: vec![PythonNode::Assign {
1011 target: "x".to_string(),
1012 value: Box::new(PythonNode::BinOp {
1013 left: Box::new(PythonNode::IntLit(1)),
1014 op: BinaryOp::Add,
1015 right: Box::new(PythonNode::IntLit(2)),
1016 }),
1017 }],
1018 orelse: vec![PythonNode::Assign {
1019 target: "y".to_string(),
1020 value: Box::new(PythonNode::BinOp {
1021 left: Box::new(PythonNode::IntLit(3)),
1022 op: BinaryOp::Sub,
1023 right: Box::new(PythonNode::IntLit(4)),
1024 }),
1025 }],
1026 };
1027
1028 let mutator = AstMutator::with_operators(vec![MutationOperator::Aor]);
1029 let mutations = mutator.mutate(&ast);
1030 assert!(!mutations.is_empty());
1031 }
1032
1033 #[test]
1034 fn test_aor_in_return() {
1035 let ast = PythonNode::Return(Some(Box::new(PythonNode::BinOp {
1036 left: Box::new(PythonNode::IntLit(1)),
1037 op: BinaryOp::Mult,
1038 right: Box::new(PythonNode::IntLit(2)),
1039 })));
1040
1041 let mutator = AstMutator::with_operators(vec![MutationOperator::Aor]);
1042 let mutations = mutator.mutate(&ast);
1043 assert!(!mutations.is_empty());
1044 }
1045
1046 #[test]
1047 fn test_aor_in_unary_op() {
1048 let ast = PythonNode::UnaryOp {
1049 op: UnaryOp::Neg,
1050 operand: Box::new(PythonNode::BinOp {
1051 left: Box::new(PythonNode::IntLit(1)),
1052 op: BinaryOp::Add,
1053 right: Box::new(PythonNode::IntLit(2)),
1054 }),
1055 };
1056
1057 let mutator = AstMutator::with_operators(vec![MutationOperator::Aor]);
1058 let mutations = mutator.mutate(&ast);
1059 assert!(!mutations.is_empty());
1060 }
1061
1062 #[test]
1063 fn test_aor_in_compare() {
1064 let ast = PythonNode::Compare {
1065 left: Box::new(PythonNode::BinOp {
1066 left: Box::new(PythonNode::IntLit(1)),
1067 op: BinaryOp::Add,
1068 right: Box::new(PythonNode::IntLit(2)),
1069 }),
1070 op: CompareOp::Lt,
1071 right: Box::new(PythonNode::IntLit(10)),
1072 };
1073
1074 let mutator = AstMutator::with_operators(vec![MutationOperator::Aor]);
1075 let mutations = mutator.mutate(&ast);
1076 assert!(!mutations.is_empty());
1077 }
1078
1079 #[test]
1080 fn test_ror_in_while() {
1081 let ast = PythonNode::While {
1082 test: Box::new(PythonNode::Compare {
1083 left: Box::new(PythonNode::Name("x".to_string())),
1084 op: CompareOp::Lt,
1085 right: Box::new(PythonNode::IntLit(10)),
1086 }),
1087 body: vec![PythonNode::Assign {
1088 target: "x".to_string(),
1089 value: Box::new(PythonNode::BinOp {
1090 left: Box::new(PythonNode::Name("x".to_string())),
1091 op: BinaryOp::Add,
1092 right: Box::new(PythonNode::IntLit(1)),
1093 }),
1094 }],
1095 };
1096
1097 let mutator = AstMutator::with_operators(vec![MutationOperator::Ror]);
1098 let mutations = mutator.mutate(&ast);
1099 assert!(!mutations.is_empty());
1100 }
1101
1102 #[test]
1103 fn test_ror_in_if_orelse() {
1104 let ast = PythonNode::If {
1105 test: Box::new(PythonNode::Compare {
1106 left: Box::new(PythonNode::Name("x".to_string())),
1107 op: CompareOp::Gt,
1108 right: Box::new(PythonNode::IntLit(0)),
1109 }),
1110 body: vec![PythonNode::Pass],
1111 orelse: vec![PythonNode::If {
1112 test: Box::new(PythonNode::Compare {
1113 left: Box::new(PythonNode::Name("x".to_string())),
1114 op: CompareOp::Eq,
1115 right: Box::new(PythonNode::IntLit(0)),
1116 }),
1117 body: vec![PythonNode::Pass],
1118 orelse: vec![],
1119 }],
1120 };
1121
1122 let mutator = AstMutator::with_operators(vec![MutationOperator::Ror]);
1123 let mutations = mutator.mutate(&ast);
1124 assert!(!mutations.is_empty());
1125 }
1126
1127 #[test]
1128 fn test_lor_in_if() {
1129 let ast = PythonNode::If {
1130 test: Box::new(PythonNode::BinOp {
1131 left: Box::new(PythonNode::Name("a".to_string())),
1132 op: BinaryOp::And,
1133 right: Box::new(PythonNode::Name("b".to_string())),
1134 }),
1135 body: vec![PythonNode::Pass],
1136 orelse: vec![PythonNode::Pass],
1137 };
1138
1139 let mutator = AstMutator::with_operators(vec![MutationOperator::Lor]);
1140 let mutations = mutator.mutate(&ast);
1141 assert!(!mutations.is_empty());
1142 }
1143
1144 #[test]
1145 fn test_bsr_in_binop() {
1146 let ast = PythonNode::BinOp {
1147 left: Box::new(PythonNode::IntLit(0)),
1148 op: BinaryOp::Add,
1149 right: Box::new(PythonNode::IntLit(1)),
1150 };
1151
1152 let mutator = AstMutator::with_operators(vec![MutationOperator::Bsr]);
1153 let mutations = mutator.mutate(&ast);
1154 assert!(!mutations.is_empty());
1155 }
1156
1157 #[test]
1158 fn test_bsr_string() {
1159 let ast = PythonNode::Assign {
1160 target: "s".to_string(),
1161 value: Box::new(PythonNode::StrLit("hello".to_string())),
1162 };
1163
1164 let mutator = AstMutator::with_operators(vec![MutationOperator::Bsr]);
1165 let mutations = mutator.mutate(&ast);
1166 assert!(!mutations.is_empty());
1167 }
1168
1169 #[test]
1170 fn test_bsr_empty_string() {
1171 let ast = PythonNode::Assign {
1172 target: "s".to_string(),
1173 value: Box::new(PythonNode::StrLit(String::new())),
1174 };
1175
1176 let mutator = AstMutator::with_operators(vec![MutationOperator::Bsr]);
1177 let mutations = mutator.mutate(&ast);
1178 assert!(!mutations.is_empty());
1179 }
1180
1181 #[test]
1182 fn test_arithmetic_replacements() {
1183 assert!(!arithmetic_replacements(BinaryOp::Add).is_empty());
1184 assert!(!arithmetic_replacements(BinaryOp::Sub).is_empty());
1185 assert!(!arithmetic_replacements(BinaryOp::Mult).is_empty());
1186 assert!(!arithmetic_replacements(BinaryOp::Div).is_empty());
1187 assert!(!arithmetic_replacements(BinaryOp::Mod).is_empty());
1188 assert!(!arithmetic_replacements(BinaryOp::FloorDiv).is_empty());
1189 assert!(!arithmetic_replacements(BinaryOp::Pow).is_empty());
1190 assert!(arithmetic_replacements(BinaryOp::And).is_empty()); }
1192
1193 #[test]
1194 fn test_relational_replacements() {
1195 assert!(!relational_replacements(CompareOp::Eq).is_empty());
1196 assert!(!relational_replacements(CompareOp::NotEq).is_empty());
1197 assert!(!relational_replacements(CompareOp::Lt).is_empty());
1198 assert!(!relational_replacements(CompareOp::LtE).is_empty());
1199 assert!(!relational_replacements(CompareOp::Gt).is_empty());
1200 assert!(!relational_replacements(CompareOp::GtE).is_empty());
1201 }
1202
1203 #[test]
1204 fn test_logical_replacements() {
1205 assert!(!logical_replacements(BinaryOp::And).is_empty());
1206 assert!(!logical_replacements(BinaryOp::Or).is_empty());
1207 assert!(logical_replacements(BinaryOp::Add).is_empty()); }
1209
1210 #[test]
1211 fn test_collect_variable_names() {
1212 let ast = PythonNode::Module(vec![
1213 PythonNode::Assign {
1214 target: "x".to_string(),
1215 value: Box::new(PythonNode::IntLit(1)),
1216 },
1217 PythonNode::Assign {
1218 target: "y".to_string(),
1219 value: Box::new(PythonNode::Name("x".to_string())),
1220 },
1221 ]);
1222
1223 let names = collect_variable_names(&ast);
1224 assert!(names.contains(&"x".to_string()));
1225 assert!(names.contains(&"y".to_string()));
1226 }
1227
1228 #[test]
1229 fn test_collect_variable_names_in_binop() {
1230 let ast = PythonNode::BinOp {
1231 left: Box::new(PythonNode::Name("a".to_string())),
1232 op: BinaryOp::Add,
1233 right: Box::new(PythonNode::Name("b".to_string())),
1234 };
1235
1236 let names = collect_variable_names(&ast);
1237 assert!(names.contains(&"a".to_string()));
1238 assert!(names.contains(&"b".to_string()));
1239 }
1240
1241 #[test]
1242 fn test_collect_variable_names_in_unaryop() {
1243 let ast = PythonNode::UnaryOp {
1244 op: UnaryOp::Neg,
1245 operand: Box::new(PythonNode::Name("x".to_string())),
1246 };
1247
1248 let names = collect_variable_names(&ast);
1249 assert!(names.contains(&"x".to_string()));
1250 }
1251
1252 #[test]
1253 fn test_collect_variable_names_in_if() {
1254 let ast = PythonNode::If {
1255 test: Box::new(PythonNode::Name("cond".to_string())),
1256 body: vec![PythonNode::Assign {
1257 target: "a".to_string(),
1258 value: Box::new(PythonNode::IntLit(1)),
1259 }],
1260 orelse: vec![PythonNode::Assign {
1261 target: "b".to_string(),
1262 value: Box::new(PythonNode::IntLit(2)),
1263 }],
1264 };
1265
1266 let names = collect_variable_names(&ast);
1267 assert!(names.contains(&"cond".to_string()));
1268 assert!(names.contains(&"a".to_string()));
1269 assert!(names.contains(&"b".to_string()));
1270 }
1271
1272 #[test]
1273 fn test_collect_variable_names_in_funcdef() {
1274 let ast = PythonNode::FuncDef {
1275 name: "foo".to_string(),
1276 args: vec!["x".to_string(), "y".to_string()],
1277 body: vec![PythonNode::Return(Some(Box::new(PythonNode::Name(
1278 "x".to_string(),
1279 ))))],
1280 };
1281
1282 let names = collect_variable_names(&ast);
1283 assert!(names.contains(&"x".to_string()));
1284 assert!(names.contains(&"y".to_string()));
1285 }
1286
1287 #[test]
1288 fn test_boundary_int_values() {
1289 let vals_0 = boundary_int_values(0);
1290 assert!(vals_0.contains(&-1));
1291 assert!(vals_0.contains(&1));
1292
1293 let vals_1 = boundary_int_values(1);
1294 assert!(vals_1.contains(&0));
1295 assert!(vals_1.contains(&2));
1296
1297 let vals_neg1 = boundary_int_values(-1);
1298 assert!(vals_neg1.contains(&0));
1299 assert!(vals_neg1.contains(&-2));
1300
1301 let vals_5 = boundary_int_values(5);
1302 assert!(vals_5.contains(&4));
1303 assert!(vals_5.contains(&6));
1304 assert!(vals_5.contains(&0));
1305
1306 let vals_neg5 = boundary_int_values(-5);
1307 assert!(vals_neg5.contains(&-6));
1308 assert!(vals_neg5.contains(&-4));
1309 }
1310
1311 #[test]
1312 fn test_boundary_str_values() {
1313 let vals_empty = boundary_str_values("");
1314 assert!(vals_empty.contains(&" ".to_string()));
1315 assert!(vals_empty.contains(&"a".to_string()));
1316
1317 let vals_nonempty = boundary_str_values("hello");
1318 assert!(vals_nonempty.contains(&String::new()));
1319 assert!(vals_nonempty.contains(&"hello ".to_string()));
1320 }
1321
1322 #[test]
1323 fn test_abs_with_intlit() {
1324 let ast = PythonNode::Assign {
1326 target: "x".to_string(),
1327 value: Box::new(PythonNode::IntLit(42)),
1328 };
1329
1330 let mutator = AstMutator::with_operators(vec![MutationOperator::Abs]);
1331 let mutations = mutator.mutate(&ast);
1332
1333 assert!(!mutations.is_empty());
1334 assert!(mutations.iter().any(|m| m.description.contains("abs(42)")));
1335 }
1336
1337 #[test]
1338 fn test_sdl_if_with_multiple_body_statements() {
1339 let ast = PythonNode::Module(vec![PythonNode::If {
1341 test: Box::new(PythonNode::BoolLit(true)),
1342 body: vec![
1343 PythonNode::Assign {
1344 target: "x".to_string(),
1345 value: Box::new(PythonNode::IntLit(1)),
1346 },
1347 PythonNode::Assign {
1348 target: "y".to_string(),
1349 value: Box::new(PythonNode::IntLit(2)),
1350 },
1351 ],
1352 orelse: vec![],
1353 }]);
1354
1355 let mutator = AstMutator::with_operators(vec![MutationOperator::Sdl]);
1356 let mutations = mutator.mutate(&ast);
1357
1358 assert!(!mutations.is_empty());
1359 assert!(mutations.iter().any(|m| m.description.contains("if-body")));
1360 }
1361
1362 #[test]
1363 fn test_sdl_if_with_multiple_orelse_statements() {
1364 let ast = PythonNode::Module(vec![PythonNode::If {
1366 test: Box::new(PythonNode::BoolLit(true)),
1367 body: vec![PythonNode::Pass],
1368 orelse: vec![
1369 PythonNode::Assign {
1370 target: "x".to_string(),
1371 value: Box::new(PythonNode::IntLit(1)),
1372 },
1373 PythonNode::Assign {
1374 target: "y".to_string(),
1375 value: Box::new(PythonNode::IntLit(2)),
1376 },
1377 ],
1378 }]);
1379
1380 let mutator = AstMutator::with_operators(vec![MutationOperator::Sdl]);
1381 let mutations = mutator.mutate(&ast);
1382
1383 assert!(!mutations.is_empty());
1384 assert!(mutations
1385 .iter()
1386 .any(|m| m.description.contains("else-body")));
1387 }
1388
1389 #[test]
1390 fn test_sdl_funcdef_with_multiple_body_statements() {
1391 let ast = PythonNode::Module(vec![PythonNode::FuncDef {
1393 name: "foo".to_string(),
1394 args: vec![],
1395 body: vec![
1396 PythonNode::Assign {
1397 target: "x".to_string(),
1398 value: Box::new(PythonNode::IntLit(1)),
1399 },
1400 PythonNode::Return(Some(Box::new(PythonNode::Name("x".to_string())))),
1401 ],
1402 }]);
1403
1404 let mutator = AstMutator::with_operators(vec![MutationOperator::Sdl]);
1405 let mutations = mutator.mutate(&ast);
1406
1407 assert!(!mutations.is_empty());
1408 assert!(mutations
1409 .iter()
1410 .any(|m| m.description.contains("function body")));
1411 }
1412
1413 #[test]
1414 fn test_svr_in_binop() {
1415 let ast = PythonNode::Module(vec![
1417 PythonNode::Assign {
1418 target: "x".to_string(),
1419 value: Box::new(PythonNode::IntLit(1)),
1420 },
1421 PythonNode::Assign {
1422 target: "y".to_string(),
1423 value: Box::new(PythonNode::IntLit(2)),
1424 },
1425 PythonNode::Assign {
1426 target: "z".to_string(),
1427 value: Box::new(PythonNode::BinOp {
1428 left: Box::new(PythonNode::Name("x".to_string())),
1429 op: BinaryOp::Add,
1430 right: Box::new(PythonNode::Name("y".to_string())),
1431 }),
1432 },
1433 ]);
1434
1435 let mutator = AstMutator::with_operators(vec![MutationOperator::Svr]);
1436 let mutations = mutator.mutate(&ast);
1437
1438 assert!(!mutations.is_empty());
1439 assert!(mutations.iter().any(|m| m.description.contains("Replace")));
1441 }
1442
1443 #[test]
1444 fn test_bsr_boolean() {
1445 let ast = PythonNode::Assign {
1447 target: "flag".to_string(),
1448 value: Box::new(PythonNode::BoolLit(true)),
1449 };
1450
1451 let mutator = AstMutator::with_operators(vec![MutationOperator::Bsr]);
1452 let mutations = mutator.mutate(&ast);
1453
1454 assert!(!mutations.is_empty());
1455 assert!(mutations
1456 .iter()
1457 .any(|m| m.description.contains("true → false")));
1458 }
1459
1460 #[test]
1461 fn test_bsr_boolean_false() {
1462 let ast = PythonNode::Assign {
1464 target: "flag".to_string(),
1465 value: Box::new(PythonNode::BoolLit(false)),
1466 };
1467
1468 let mutator = AstMutator::with_operators(vec![MutationOperator::Bsr]);
1469 let mutations = mutator.mutate(&ast);
1470
1471 assert!(!mutations.is_empty());
1472 assert!(mutations
1473 .iter()
1474 .any(|m| m.description.contains("false → true")));
1475 }
1476}