#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod ml_predictor_red_tests {
use crate::services::mutation::{
Mutant, MutantFeatures, MutantStatus, MutationOperatorType, SourceLocation,
SurvivabilityPredictor, TrainingData,
};
#[test]
fn red_mutant_features_must_extract_from_mutant() {
let mutant = Mutant {
id: "test_mutant".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: 10,
column: 30,
end_line: 10,
end_column: 40,
},
operator: MutationOperatorType::ArithmeticReplacement,
hash: "abc123".to_string(),
status: MutantStatus::Pending,
};
let features = MutantFeatures::from_mutant(&mutant);
assert_eq!(
features.operator_type,
MutationOperatorType::ArithmeticReplacement
);
assert!(features.cyclomatic_complexity > 0);
assert_eq!(features.source_line, 10);
}
#[test]
fn red_features_must_include_code_patterns() {
let mutant = create_nested_mutant();
let features = MutantFeatures::from_mutant(&mutant);
let _ = features.nesting_depth;
let _ = features.control_flow_count;
let _ = features.has_loops; }
#[test]
fn red_predictor_must_be_trainable() {
let mut predictor = SurvivabilityPredictor::new();
let training_data = vec![
create_training_sample(MutationOperatorType::ArithmeticReplacement, true),
create_training_sample(MutationOperatorType::ArithmeticReplacement, false),
create_training_sample(MutationOperatorType::RelationalReplacement, true),
];
let result = predictor.train(&training_data);
assert!(result.is_ok());
assert!(predictor.is_trained());
}
#[test]
fn red_predictor_must_predict_kill_probability() {
let mut predictor = SurvivabilityPredictor::new();
let training_data = create_minimal_training_data();
predictor.train(&training_data).unwrap();
let mutant = create_test_mutant();
let prediction = predictor.predict(&mutant);
assert!(prediction.is_ok());
let result = prediction.unwrap();
assert!(result.kill_probability >= 0.0);
assert!(result.kill_probability <= 1.0);
assert!(result.confidence >= 0.0);
assert!(result.confidence <= 1.0);
}
#[test]
fn red_predictor_must_prioritize_high_kill_probability_mutants() {
let mut predictor = SurvivabilityPredictor::new();
predictor
.train(&create_training_data_with_patterns())
.unwrap();
let mutants = vec![
create_arithmetic_mutant(), create_relational_mutant(), create_conditional_mutant(), ];
let prioritized = predictor.prioritize_mutants(&mutants);
assert!(prioritized.is_ok());
let ranked = prioritized.unwrap();
assert_eq!(ranked.len(), 3);
for i in 1..ranked.len() {
assert!(ranked[i - 1].1.kill_probability >= ranked[i].1.kill_probability);
}
}
#[test]
fn red_predictor_must_handle_unseen_operator_types() {
let mut predictor = SurvivabilityPredictor::new();
let training_data = vec![create_training_sample(
MutationOperatorType::ArithmeticReplacement,
true,
)];
predictor.train(&training_data).unwrap();
let mutant = Mutant {
operator: MutationOperatorType::ConstantReplacement,
..create_test_mutant()
};
let prediction = predictor.predict(&mutant);
assert!(prediction.is_ok());
let result = prediction.unwrap();
assert!(result.confidence < 0.9); }
#[test]
fn red_predictor_must_calibrate_probabilities() {
let mut predictor = SurvivabilityPredictor::new();
predictor.train(&create_calibration_data()).unwrap();
let test_mutants = create_test_set();
let mut predictions = Vec::new();
for mutant in &test_mutants {
predictions.push(predictor.predict(mutant).unwrap());
}
let avg_predicted =
predictions.iter().map(|p| p.kill_probability).sum::<f64>() / predictions.len() as f64;
assert!(avg_predicted > 0.1);
assert!(avg_predicted < 0.9);
}
#[test]
fn red_predictor_must_save_and_load_model() {
let mut predictor = SurvivabilityPredictor::new();
predictor.train(&create_minimal_training_data()).unwrap();
let model_path = std::path::PathBuf::from("/tmp/test_model.bin");
let save_result = predictor.save(&model_path);
assert!(save_result.is_ok());
let loaded = SurvivabilityPredictor::load(&model_path);
assert!(loaded.is_ok());
let loaded_predictor = loaded.unwrap();
assert!(loaded_predictor.is_trained());
let mutant = create_test_mutant();
let original_pred = predictor.predict(&mutant).unwrap();
let loaded_pred = loaded_predictor.predict(&mutant).unwrap();
assert!(original_pred.kill_probability >= 0.0 && original_pred.kill_probability <= 1.0);
assert!(loaded_pred.kill_probability >= 0.0 && loaded_pred.kill_probability <= 1.0);
assert!(!loaded_predictor.feature_importance().unwrap().is_empty());
}
#[test]
fn red_predictor_must_provide_feature_importance() {
let mut predictor = SurvivabilityPredictor::new();
predictor
.train(&create_training_data_with_patterns())
.unwrap();
let importance = predictor.feature_importance();
assert!(importance.is_ok());
let features = importance.unwrap();
assert!(!features.is_empty());
let total: f64 = features.values().sum();
assert!((total - 1.0).abs() < 0.1);
let max_importance = features.values().cloned().fold(0.0, f64::max);
assert!(max_importance > 0.0);
}
#[test]
fn red_predictor_must_handle_missing_features() {
let mut predictor = SurvivabilityPredictor::new();
predictor.train(&create_minimal_training_data()).unwrap();
let sparse_mutant = Mutant {
id: "sparse".to_string(),
original_file: std::path::PathBuf::from("test.rs"),
mutated_source: "".to_string(), location: SourceLocation {
line: 1,
column: 1,
end_line: 1,
end_column: 1,
},
operator: MutationOperatorType::ArithmeticReplacement,
hash: "".to_string(),
status: MutantStatus::Pending,
};
let prediction = predictor.predict(&sparse_mutant);
assert!(prediction.is_ok() || prediction.is_err());
}
#[test]
fn red_prediction_result_must_include_explanation() {
let mut predictor = SurvivabilityPredictor::new();
predictor
.train(&create_training_data_with_patterns())
.unwrap();
let mutant = create_test_mutant();
let result = predictor.predict_with_explanation(&mutant);
assert!(result.is_ok());
let (prediction, explanation) = result.unwrap();
assert!(!explanation.is_empty());
assert!(explanation.contains("probability") || explanation.contains("likely"));
assert!(prediction.kill_probability >= 0.0);
}
#[test]
fn red_predictor_must_support_incremental_learning() {
let mut predictor = SurvivabilityPredictor::new();
predictor.train(&create_minimal_training_data()).unwrap();
let _initial_prediction = predictor.predict(&create_test_mutant()).unwrap();
let new_data = vec![create_training_sample(
MutationOperatorType::ConditionalReplacement,
true,
)];
let update_result = predictor.update(&new_data);
assert!(update_result.is_ok());
assert!(predictor.is_trained());
let updated_prediction = predictor.predict(&create_test_mutant()).unwrap();
assert!(updated_prediction.kill_probability >= 0.0);
}
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_nested_mutant() -> Mutant {
Mutant {
mutated_source: r#"
fn complex() {
if x > 0 {
for i in 0..10 {
while y < 5 {
// deeply nested
}
}
}
}
"#
.to_string(),
..create_test_mutant()
}
}
fn create_arithmetic_mutant() -> Mutant {
Mutant {
operator: MutationOperatorType::ArithmeticReplacement,
..create_test_mutant()
}
}
fn create_relational_mutant() -> Mutant {
Mutant {
operator: MutationOperatorType::RelationalReplacement,
mutated_source: "fn test(a: i32) -> bool { a >= 5 }".to_string(),
..create_test_mutant()
}
}
fn create_conditional_mutant() -> Mutant {
Mutant {
operator: MutationOperatorType::ConditionalReplacement,
mutated_source: "fn test(a: bool, b: bool) -> bool { a || b }".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_minimal_training_data() -> Vec<TrainingData> {
vec![
create_training_sample(MutationOperatorType::ArithmeticReplacement, true),
create_training_sample(MutationOperatorType::ArithmeticReplacement, false),
create_training_sample(MutationOperatorType::RelationalReplacement, true),
create_training_sample(MutationOperatorType::RelationalReplacement, true),
]
}
fn create_training_data_with_patterns() -> 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_calibration_data() -> Vec<TrainingData> {
create_training_data_with_patterns()
}
fn create_test_set() -> Vec<Mutant> {
vec![
create_arithmetic_mutant(),
create_relational_mutant(),
create_conditional_mutant(),
]
}
}