#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod ml_integration_tests {
use crate::services::mutation::{
EquivalenceTrainingData, EquivalentMutantDetector, Mutant, MutantStatus,
MutationOperatorType, SourceLocation, SurvivabilityPredictor, TrainingData,
};
#[test]
fn test_end_to_end_ml_mutation_pipeline() {
let survivability_training = create_historical_mutation_data();
let equivalence_training = create_historical_equivalence_data();
let mut predictor = SurvivabilityPredictor::new();
predictor.train(&survivability_training).unwrap();
assert!(predictor.is_trained());
let mut detector = EquivalentMutantDetector::new();
detector.train(&equivalence_training).unwrap();
assert!(detector.is_trained());
let mutants = create_test_mutant_set();
let original_sources = create_original_sources();
let (non_equiv, _filtered_count) =
filter_equivalent_mutants(&detector, &mutants, &original_sources);
assert!(
non_equiv.len() <= mutants.len(),
"Should have same or fewer mutants after filtering"
);
let prioritized = predictor.prioritize_mutants(&non_equiv).unwrap();
assert_eq!(prioritized.len(), non_equiv.len());
for i in 1..prioritized.len() {
assert!(
prioritized[i - 1].1.kill_probability >= prioritized[i].1.kill_probability,
"Mutants should be sorted by kill probability"
);
}
let top_n = if non_equiv.len() >= 3 {
3
} else {
non_equiv.len()
};
let smart_sample: Vec<_> = prioritized.iter().take(top_n).collect();
assert_eq!(smart_sample.len(), top_n);
for (_, prediction) in &smart_sample {
assert!(prediction.kill_probability >= 0.0);
assert!(prediction.kill_probability <= 1.0);
}
assert!(!prioritized.is_empty(), "Should have mutants to test");
}
#[test]
fn test_ml_model_persistence() {
let mut predictor = SurvivabilityPredictor::new();
predictor.train(&create_historical_mutation_data()).unwrap();
let mut detector = EquivalentMutantDetector::new();
detector
.train(&create_historical_equivalence_data())
.unwrap();
let pred_path = std::path::PathBuf::from("/tmp/test_predictor.bin");
let det_path = std::path::PathBuf::from("/tmp/test_detector.bin");
predictor.save(&pred_path).unwrap();
detector.save(&det_path).unwrap();
let loaded_pred = SurvivabilityPredictor::load(&pred_path).unwrap();
let loaded_det = EquivalentMutantDetector::load(&det_path).unwrap();
assert!(loaded_pred.is_trained());
assert!(loaded_det.is_trained());
let mutant = create_test_mutant();
let original = "fn test(a: i32) -> i32 { a + b }";
let pred1 = predictor.predict(&mutant).unwrap();
let pred2 = loaded_pred.predict(&mutant).unwrap();
assert!(pred1.kill_probability >= 0.0 && pred1.kill_probability <= 1.0);
assert!(pred2.kill_probability >= 0.0 && pred2.kill_probability <= 1.0);
let det1 = detector.detect_equivalent(&mutant, original).unwrap();
let det2 = loaded_det.detect_equivalent(&mutant, original).unwrap();
assert_eq!(det1.is_equivalent, det2.is_equivalent);
}
#[test]
fn test_incremental_learning_pipeline() {
let mut predictor = SurvivabilityPredictor::new();
let initial_data = create_historical_mutation_data();
predictor.train(&initial_data).unwrap();
let test_mutant = create_arithmetic_mutant();
let _initial_prediction = predictor.predict(&test_mutant).unwrap();
let new_results = vec![
create_training_sample(MutationOperatorType::ArithmeticReplacement, true),
create_training_sample(MutationOperatorType::ArithmeticReplacement, true),
create_training_sample(MutationOperatorType::ArithmeticReplacement, false),
];
predictor.update(&new_results).unwrap();
let updated_prediction = predictor.predict(&test_mutant).unwrap();
assert!(updated_prediction.kill_probability >= 0.0);
assert!(updated_prediction.kill_probability <= 1.0);
assert!(predictor.is_trained());
}
#[test]
fn test_combined_ml_effectiveness() {
let mut predictor = SurvivabilityPredictor::new();
predictor.train(&create_diverse_training_data()).unwrap();
let mut detector = EquivalentMutantDetector::new();
detector.train(&create_diverse_equivalence_data()).unwrap();
let all_mutants = vec![
create_arithmetic_mutant(), create_relational_mutant(), create_conditional_mutant(), create_identity_mutant(), create_tautology_mutant(), ];
let original_sources = vec![
("test1.rs", "fn add(a: i32, b: i32) -> i32 { a + b }"),
("test2.rs", "fn compare(a: i32) -> bool { a > 5 }"),
("test3.rs", "fn check(x: bool) -> bool { x && true }"),
("test4.rs", "fn identity(x: i32) -> i32 { x + 0 }"),
("test5.rs", "fn always(x: bool) -> bool { x || true }"),
];
let non_equiv: Vec<Mutant> = all_mutants
.iter()
.zip(&original_sources)
.filter_map(|(m, (_, orig))| {
if !detector.detect_equivalent(m, orig).unwrap().is_equivalent {
Some(m.clone())
} else {
None
}
})
.collect();
assert!(
non_equiv.len() <= all_mutants.len(),
"Should filter or keep same count"
);
let prioritized = predictor.prioritize_mutants(&non_equiv).unwrap();
assert!(!prioritized.is_empty(), "Should have mutants to prioritize");
for i in 1..prioritized.len() {
assert!(
prioritized[i - 1].1.kill_probability >= prioritized[i].1.kill_probability,
"Should be sorted by kill probability"
);
}
assert!(
!prioritized.is_empty(),
"ML pipeline completes successfully"
);
}
#[test]
fn test_prediction_confidence_calibration() {
let mut predictor = SurvivabilityPredictor::new();
predictor.train(&create_calibrated_training_data()).unwrap();
let high_kill_mutant = create_arithmetic_mutant(); let low_kill_mutant = create_conditional_mutant();
let high_pred = predictor.predict(&high_kill_mutant).unwrap();
let low_pred = predictor.predict(&low_kill_mutant).unwrap();
assert!(high_pred.kill_probability > low_pred.kill_probability);
assert!(high_pred.confidence >= 0.5);
assert!(low_pred.confidence >= 0.5);
}
fn create_test_mutant() -> Mutant {
Mutant {
id: "test_1".to_string(),
original_file: std::path::PathBuf::from("test.rs"),
mutated_source: "fn add(a: i32, b: i32) -> i32 { a - b }".to_string(),
location: SourceLocation {
line: 5,
column: 10,
end_line: 5,
end_column: 20,
},
operator: MutationOperatorType::ArithmeticReplacement,
hash: "hash1".to_string(),
status: MutantStatus::Pending,
}
}
fn create_arithmetic_mutant() -> Mutant {
Mutant {
operator: MutationOperatorType::ArithmeticReplacement,
mutated_source: "fn add(a: i32, b: i32) -> i32 { a - b }".to_string(),
..create_test_mutant()
}
}
fn create_relational_mutant() -> Mutant {
Mutant {
operator: MutationOperatorType::RelationalReplacement,
mutated_source: "fn compare(a: i32) -> bool { a >= 5 }".to_string(),
..create_test_mutant()
}
}
fn create_conditional_mutant() -> Mutant {
Mutant {
operator: MutationOperatorType::ConditionalReplacement,
mutated_source: "fn check(x: bool) -> bool { x || true }".to_string(),
..create_test_mutant()
}
}
fn create_identity_mutant() -> Mutant {
Mutant {
operator: MutationOperatorType::ArithmeticReplacement,
mutated_source: "fn identity(x: i32) -> i32 { x }".to_string(),
..create_test_mutant()
}
}
fn create_tautology_mutant() -> Mutant {
Mutant {
operator: MutationOperatorType::ConditionalReplacement,
mutated_source: "fn always(x: bool) -> bool { true }".to_string(),
..create_test_mutant()
}
}
fn create_training_sample(operator: MutationOperatorType, was_killed: bool) -> TrainingData {
TrainingData {
mutant: Mutant {
operator,
status: if was_killed {
MutantStatus::Killed
} else {
MutantStatus::Survived
},
..create_test_mutant()
},
was_killed,
test_failures: if was_killed {
vec!["test1".to_string()]
} else {
vec![]
},
execution_time_ms: 100,
}
}
fn create_historical_mutation_data() -> Vec<TrainingData> {
vec![
create_training_sample(MutationOperatorType::ArithmeticReplacement, true),
create_training_sample(MutationOperatorType::ArithmeticReplacement, true),
create_training_sample(MutationOperatorType::ArithmeticReplacement, false),
create_training_sample(MutationOperatorType::RelationalReplacement, true),
create_training_sample(MutationOperatorType::RelationalReplacement, false),
create_training_sample(MutationOperatorType::ConditionalReplacement, false),
]
}
fn create_historical_equivalence_data() -> Vec<EquivalenceTrainingData> {
vec![
create_equivalence_sample("a + 0", "a", true),
create_equivalence_sample("a * 1", "a", true),
create_equivalence_sample("x || true", "true", true),
create_equivalence_sample("x && true", "x", true),
create_equivalence_sample("a + b", "a - b", false),
]
}
fn create_equivalence_sample(
original: &str,
mutated: &str,
is_equivalent: bool,
) -> EquivalenceTrainingData {
EquivalenceTrainingData {
mutant: Mutant {
mutated_source: mutated.to_string(),
..create_test_mutant()
},
original_source: original.to_string(),
is_equivalent,
verified_manually: true,
detection_method: "test".to_string(),
}
}
fn create_test_mutant_set() -> Vec<Mutant> {
vec![
create_arithmetic_mutant(),
create_relational_mutant(),
create_conditional_mutant(),
create_identity_mutant(),
create_tautology_mutant(),
]
}
fn create_original_sources() -> Vec<(&'static str, &'static str)> {
vec![
("test1.rs", "fn add(a: i32, b: i32) -> i32 { a + b }"),
("test2.rs", "fn compare(a: i32) -> bool { a > 5 }"),
("test3.rs", "fn check(x: bool) -> bool { x && true }"),
("test4.rs", "fn identity(x: i32) -> i32 { x + 0 }"),
("test5.rs", "fn always(x: bool) -> bool { x || true }"),
]
}
fn filter_equivalent_mutants(
detector: &EquivalentMutantDetector,
mutants: &[Mutant],
original_sources: &[(&str, &str)],
) -> (Vec<Mutant>, usize) {
let mut non_equiv = Vec::new();
let mut filtered = 0;
for (i, mutant) in mutants.iter().enumerate() {
let original = if i < original_sources.len() {
original_sources[i].1
} else {
""
};
if !detector
.detect_equivalent(mutant, original)
.unwrap()
.is_equivalent
{
non_equiv.push(mutant.clone());
} else {
filtered += 1;
}
}
(non_equiv, filtered)
}
fn create_diverse_training_data() -> Vec<TrainingData> {
let mut data = Vec::new();
for i in 0..10 {
data.push(create_training_sample(
MutationOperatorType::ArithmeticReplacement,
i < 8,
));
}
for i in 0..10 {
data.push(create_training_sample(
MutationOperatorType::RelationalReplacement,
i < 5,
));
}
for i in 0..10 {
data.push(create_training_sample(
MutationOperatorType::ConditionalReplacement,
i < 2,
));
}
data
}
fn create_diverse_equivalence_data() -> Vec<EquivalenceTrainingData> {
vec![
create_equivalence_sample("a + 0", "a", true),
create_equivalence_sample("a * 1", "a", true),
create_equivalence_sample("a - 0", "a", true),
create_equivalence_sample("x || true", "true", true),
create_equivalence_sample("x && true", "x", true),
create_equivalence_sample("a + b", "b + a", true),
create_equivalence_sample("a + b", "a - b", false),
create_equivalence_sample("a * b", "a / b", false),
]
}
fn create_calibrated_training_data() -> Vec<TrainingData> {
create_diverse_training_data()
}
}