#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod equivalent_detector_red_tests {
use crate::services::mutation::{
EquivalenceFeatures, EquivalenceTrainingData, EquivalentMutantDetector, Mutant,
MutantStatus, MutationOperatorType, SourceLocation,
};
#[test]
fn red_detector_must_identify_trivial_equivalents() {
let mut detector = EquivalentMutantDetector::new();
detector.train(&create_basic_training_data()).unwrap();
let original = "fn test(a: i32) -> i32 { a + 0 }";
let mutant = create_mutant_with_source("fn test(a: i32) -> i32 { a }");
let result = detector.detect_equivalent(&mutant, original);
assert!(result.is_ok());
let equivalence = result.unwrap();
assert!(equivalence.is_equivalent);
assert!(equivalence.confidence > 0.7); }
#[test]
fn red_detector_must_reject_non_equivalents() {
let mut detector = EquivalentMutantDetector::new();
detector.train(&create_basic_training_data()).unwrap();
let original = "fn add(a: i32, b: i32) -> i32 { a + b }";
let mutant = create_mutant_with_source("fn add(a: i32, b: i32) -> i32 { a - b }");
let result = detector.detect_equivalent(&mutant, original);
assert!(result.is_ok());
let equivalence = result.unwrap();
assert!(!equivalence.is_equivalent);
}
#[test]
fn red_detector_must_handle_operator_neutrality() {
let mut detector = EquivalentMutantDetector::new();
detector.train(&create_operator_patterns()).unwrap();
let original = "fn calc(x: i32) -> i32 { x * 1 }";
let mutant = create_mutant_with_source("fn calc(x: i32) -> i32 { x }");
let equivalence = detector.detect_equivalent(&mutant, original).unwrap();
assert!(equivalence.is_equivalent);
assert!(equivalence.reason.contains("neutral") || equivalence.reason.contains("identity"));
}
#[test]
fn red_detector_must_detect_boolean_tautologies() {
let mut detector = EquivalentMutantDetector::new();
detector.train(&create_boolean_patterns()).unwrap();
let original = "fn check(x: bool) -> bool { x || true }";
let mutant = create_mutant_with_source("fn check(x: bool) -> bool { true }");
let equivalence = detector.detect_equivalent(&mutant, original).unwrap();
assert!(equivalence.is_equivalent);
assert!(equivalence.confidence > 0.6);
}
#[test]
fn red_detector_must_extract_equivalence_features() {
let original = "fn test(a: i32) -> i32 { a + 0 }";
let mutant = create_mutant_with_source("fn test(a: i32) -> i32 { a }");
let features = EquivalenceFeatures::from_mutant_pair(&mutant, original);
assert!(features.ast_similarity >= 0.0);
assert!(features.ast_similarity <= 1.0);
assert!(!features.operator_patterns.is_empty());
let _ = features.edit_distance;
}
#[test]
fn red_detector_must_be_trainable() {
let mut detector = EquivalentMutantDetector::new();
let training_data = vec![
create_equivalence_sample("a + 0", "a", true),
create_equivalence_sample("a * 1", "a", true),
create_equivalence_sample("a + b", "a - b", false),
create_equivalence_sample("x || true", "true", true),
];
let result = detector.train(&training_data);
assert!(result.is_ok());
assert!(detector.is_trained());
}
#[test]
fn red_detector_must_provide_explanation() {
let mut detector = EquivalentMutantDetector::new();
detector.train(&create_operator_patterns()).unwrap();
let original = "fn test(n: i32) -> i32 { n * 1 }";
let mutant = create_mutant_with_source("fn test(n: i32) -> i32 { n }");
let (result, explanation) = detector.detect_with_explanation(&mutant, original).unwrap();
assert!(!explanation.is_empty());
assert!(explanation.contains("equivalent") || explanation.contains("identity"));
assert!(result.is_equivalent);
}
#[test]
fn red_detector_must_handle_complex_expressions() {
let mut detector = EquivalentMutantDetector::new();
detector.train(&create_complex_patterns()).unwrap();
let original = "fn check(x: bool) -> bool { !!x }";
let mutant = create_mutant_with_source("fn check(x: bool) -> bool { x }");
let equivalence = detector.detect_equivalent(&mutant, original).unwrap();
assert!(equivalence.is_equivalent);
}
#[test]
fn red_detector_must_detect_commutative_operations() {
let mut detector = EquivalentMutantDetector::new();
detector.train(&create_commutative_patterns()).unwrap();
let original = "fn add(a: i32, b: i32) -> i32 { a + b }";
let mutant = create_mutant_with_source("fn add(a: i32, b: i32) -> i32 { b + a }");
let equivalence = detector.detect_equivalent(&mutant, original).unwrap();
assert!(equivalence.is_equivalent);
assert!(equivalence.reason.contains("commutative"));
}
#[test]
fn red_detector_must_identify_associative_patterns() {
let mut detector = EquivalentMutantDetector::new();
detector.train(&create_associative_patterns()).unwrap();
let original = "fn calc(a: i32, b: i32, c: i32) -> i32 { (a + b) + c }";
let mutant =
create_mutant_with_source("fn calc(a: i32, b: i32, c: i32) -> i32 { a + (b + c) }");
let equivalence = detector.detect_equivalent(&mutant, original).unwrap();
assert!(equivalence.is_equivalent);
}
#[test]
fn red_detector_must_save_and_load() {
let mut detector = EquivalentMutantDetector::new();
detector.train(&create_basic_training_data()).unwrap();
let model_path = std::path::PathBuf::from("/tmp/equiv_detector.bin");
detector.save(&model_path).unwrap();
let loaded = EquivalentMutantDetector::load(&model_path).unwrap();
assert!(loaded.is_trained());
let original = "fn test(a: i32) -> i32 { a + 0 }";
let mutant = create_mutant_with_source("fn test(a: i32) -> i32 { a }");
let original_result = detector.detect_equivalent(&mutant, original).unwrap();
let loaded_result = loaded.detect_equivalent(&mutant, original).unwrap();
assert_eq!(original_result.is_equivalent, loaded_result.is_equivalent);
}
#[test]
fn red_detector_must_filter_equivalent_mutants() {
let mut detector = EquivalentMutantDetector::new();
detector.train(&create_operator_patterns()).unwrap();
let original_sources = vec![
("test1.rs", "fn f(x: i32) -> i32 { x + 0 }"),
("test2.rs", "fn g(x: i32) -> i32 { x * 1 }"),
("test3.rs", "fn h(a: i32, b: i32) -> i32 { a + b }"),
];
let mutants = vec![
create_mutant_with_source("fn f(x: i32) -> i32 { x }"), create_mutant_with_source("fn g(x: i32) -> i32 { x }"), create_mutant_with_source("fn h(a: i32, b: i32) -> i32 { a - b }"), ];
let filtered = detector.filter_equivalents(&mutants, &original_sources);
assert!(filtered.is_ok());
let non_equiv = filtered.unwrap();
assert_eq!(non_equiv.len(), 1); assert!(non_equiv[0].0.mutated_source.contains("a - b"));
}
#[test]
fn red_detector_must_update_with_new_patterns() {
let mut detector = EquivalentMutantDetector::new();
detector.train(&create_basic_training_data()).unwrap();
let _initial_accuracy = detector.get_accuracy_estimate();
let new_patterns = vec![
create_equivalence_sample("x - 0", "x", true),
create_equivalence_sample("x / 1", "x", true),
];
detector.update(&new_patterns).unwrap();
assert!(detector.is_trained());
let original = "fn test(x: i32) -> i32 { x - 0 }";
let mutant = create_mutant_with_source("fn test(x: i32) -> i32 { x }");
let equivalence = detector.detect_equivalent(&mutant, original).unwrap();
assert!(equivalence.is_equivalent);
}
fn create_mutant_with_source(source: &str) -> Mutant {
Mutant {
id: "test_mutant".to_string(),
original_file: std::path::PathBuf::from("test.rs"),
mutated_source: source.to_string(),
location: SourceLocation {
line: 1,
column: 1,
end_line: 1,
end_column: 50,
},
operator: MutationOperatorType::ArithmeticReplacement,
hash: "test_hash".to_string(),
status: MutantStatus::Pending,
}
}
fn create_equivalence_sample(
original: &str,
mutated: &str,
is_equivalent: bool,
) -> EquivalenceTrainingData {
EquivalenceTrainingData {
mutant: create_mutant_with_source(mutated),
original_source: original.to_string(),
is_equivalent,
verified_manually: true,
detection_method: if is_equivalent {
"manual_review".to_string()
} else {
"test_execution".to_string()
},
}
}
fn create_basic_training_data() -> Vec<EquivalenceTrainingData> {
vec![
create_equivalence_sample("a + 0", "a", true),
create_equivalence_sample("a * 1", "a", true),
create_equivalence_sample("a + b", "a - b", false),
create_equivalence_sample("a * b", "a / b", false),
create_equivalence_sample("true || x", "true", true),
create_equivalence_sample("false && x", "false", true),
]
}
fn create_operator_patterns() -> Vec<EquivalenceTrainingData> {
let mut patterns = create_basic_training_data();
patterns.extend(vec![
create_equivalence_sample("x - 0", "x", true),
create_equivalence_sample("x / 1", "x", true),
create_equivalence_sample("x * 0", "0", true),
create_equivalence_sample("0 * x", "0", true),
]);
patterns
}
fn create_boolean_patterns() -> Vec<EquivalenceTrainingData> {
vec![
create_equivalence_sample("x || true", "true", true),
create_equivalence_sample("x && false", "false", true),
create_equivalence_sample("x || false", "x", true),
create_equivalence_sample("x && true", "x", true),
create_equivalence_sample("!(!x)", "x", true),
]
}
fn create_complex_patterns() -> Vec<EquivalenceTrainingData> {
vec![
create_equivalence_sample("!!x", "x", true),
create_equivalence_sample("!(x && y)", "!x || !y", true), create_equivalence_sample("!(x || y)", "!x && !y", true), ]
}
fn create_commutative_patterns() -> Vec<EquivalenceTrainingData> {
vec![
create_equivalence_sample("a + b", "b + a", true),
create_equivalence_sample("a * b", "b * a", true),
create_equivalence_sample("a && b", "b && a", true),
create_equivalence_sample("a || b", "b || a", true),
create_equivalence_sample("a - b", "b - a", false), create_equivalence_sample("a / b", "b / a", false), ]
}
fn create_associative_patterns() -> Vec<EquivalenceTrainingData> {
vec![
create_equivalence_sample("(a + b) + c", "a + (b + c)", true),
create_equivalence_sample("(a * b) * c", "a * (b * c)", true),
create_equivalence_sample("(a && b) && c", "a && (b && c)", true),
create_equivalence_sample("(a || b) || c", "a || (b || c)", true),
]
}
}