#![cfg(feature = "mutation-testing")]
use pmat::services::mutation::{MutantExecutor, MutationConfig, MutationEngine, RustAdapter};
use std::io::Write;
use std::sync::Arc;
use tempfile::NamedTempFile;
#[tokio::test]
async fn test_unary_mutant_compiles() {
let source = r#"
fn validate(x: bool) -> bool {
!x
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate() {
assert_eq!(validate(true), false);
assert_eq!(validate(false), true);
}
}
"#;
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(source.as_bytes()).unwrap();
let temp_path = temp_file.path().to_path_buf();
let adapter = Arc::new(RustAdapter::new());
let config = MutationConfig::default();
let engine = MutationEngine::new(adapter, config);
let mutants = engine.generate_mutants_from_file(&temp_path).await.unwrap();
assert!(!mutants.is_empty(), "Should generate at least 1 mutant");
let first_mutant = &mutants[0];
println!("Mutated source:\n{}", first_mutant.mutated_source);
assert!(
first_mutant.mutated_source.contains("fn validate"),
"Mutated source should contain function signature, got: {}",
first_mutant.mutated_source
);
let parse_result = syn::parse_file(&first_mutant.mutated_source);
assert!(
parse_result.is_ok(),
"Mutated source should parse as valid Rust file, got error: {:?}",
parse_result.err()
);
}
#[tokio::test]
async fn test_statement_deletion_compiles() {
let source = r#"
fn process(x: i32) -> i32 {
validate(x);
compute(x)
}
fn validate(x: i32) {
println!("validating {}", x);
}
fn compute(x: i32) -> i32 {
x * 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process() {
assert_eq!(process(5), 10);
}
}
"#;
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(source.as_bytes()).unwrap();
let temp_path = temp_file.path().to_path_buf();
let adapter = Arc::new(RustAdapter::new());
let config = MutationConfig::default();
let engine = MutationEngine::new(adapter, config);
let mutants = engine.generate_mutants_from_file(&temp_path).await.unwrap();
let sdl_mutant = mutants.iter().find(|m| {
matches!(
m.operator,
pmat::services::mutation::MutationOperatorType::StatementDeletion
)
});
assert!(sdl_mutant.is_some(), "Should generate SDL mutant");
let mutant = sdl_mutant.unwrap();
println!("SDL Mutated source:\n{}", mutant.mutated_source);
assert!(
mutant.mutated_source.contains("fn process"),
"Should contain function signature"
);
assert!(
!mutant.mutated_source.contains("() ;"),
"SDL should delete statement, not replace with '() ;'. Got: {}",
mutant.mutated_source
);
let parse_result = syn::parse_file(&mutant.mutated_source);
assert!(
parse_result.is_ok(),
"SDL mutated source should parse as valid Rust: {:?}",
parse_result.err()
);
}
#[tokio::test]
async fn test_sdl_does_not_delete_expressions_in_conditions() {
let source = r#"
fn validate() -> Result<(), String> {
let mut set = std::collections::HashSet::new();
let name = "test";
if !set.insert(name) {
return Err("duplicate".to_string());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate() {
assert!(validate().is_ok());
}
}
"#;
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(source.as_bytes()).unwrap();
let temp_path = temp_file.path().to_path_buf();
let adapter = Arc::new(RustAdapter::new());
let config = MutationConfig::default();
let engine = MutationEngine::new(adapter, config);
let mutants = engine.generate_mutants_from_file(&temp_path).await.unwrap();
let sdl_mutants: Vec<_> = mutants
.iter()
.filter(|m| {
matches!(
m.operator,
pmat::services::mutation::MutationOperatorType::StatementDeletion
)
})
.collect();
println!("Found {} SDL mutants", sdl_mutants.len());
for mutant in &sdl_mutants {
println!("Testing SDL mutant: {}", mutant.id);
println!("Mutated source:\n{}", mutant.mutated_source);
let parse_result = syn::parse_file(&mutant.mutated_source);
assert!(
parse_result.is_ok(),
"SDL mutant should parse: {:?}\nMutated source: {}",
parse_result.err(),
mutant.mutated_source
);
if source.contains("Ok(())") {
assert!(
mutant.mutated_source.contains("Ok"),
"SDL should not delete function return value Ok(()). Mutant: {}",
mutant.mutated_source
);
}
}
}
#[tokio::test]
#[ignore] async fn test_mutant_compiles_with_cargo() {
let source = r#"
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
}
"#;
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(source.as_bytes()).unwrap();
let temp_path = temp_file.path().to_path_buf();
let adapter = Arc::new(RustAdapter::new());
let config = MutationConfig::default();
let engine = MutationEngine::new(adapter, config);
let mutants = engine.generate_mutants_from_file(&temp_path).await.unwrap();
assert!(!mutants.is_empty());
let work_dir = temp_path.parent().unwrap().to_path_buf();
let executor = MutantExecutor::new(work_dir);
let result = executor.execute_mutant(&mutants[0]).await;
assert!(
result.is_ok(),
"Mutant execution should succeed (even if tests fail)"
);
let mutation_result = result.unwrap();
assert_ne!(
mutation_result.status,
pmat::services::mutation::MutantStatus::CompileError,
"Mutant should compile successfully. Error: {:?}",
mutation_result.error_message
);
}
#[tokio::test]
async fn test_simple_expression_mutation_compiles() {
let source = r#"fn negate(x: i32) -> i32 { -x }"#;
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(source.as_bytes()).unwrap();
let temp_path = temp_file.path().to_path_buf();
let adapter = Arc::new(RustAdapter::new());
let config = MutationConfig::default();
let engine = MutationEngine::new(adapter, config);
let mutants = engine.generate_mutants_from_file(&temp_path).await.unwrap();
if mutants.is_empty() {
panic!("No mutants generated");
}
let mutant = &mutants[0];
println!("Original: {}", source);
println!("Mutated: {}", mutant.mutated_source);
assert!(
mutant.mutated_source.contains("fn negate"),
"Should contain function name"
);
assert!(
mutant.mutated_source.contains("-> i32"),
"Should contain return type"
);
let parse_result = syn::parse_file(&mutant.mutated_source);
assert!(
parse_result.is_ok(),
"Should parse as valid Rust: {:?}",
parse_result.err()
);
}