#![cfg_attr(coverage_nightly, coverage(off))]
use super::rust_tree_sitter_mutations::*;
use super::tree_sitter_operators::TreeSitterMutationOperator;
use super::types::{Mutant, MutantStatus, MutationOperatorType};
use anyhow::Result;
use sha2::{Digest, Sha256};
use std::path::PathBuf;
use tree_sitter::{Node, Parser, Tree};
pub struct RustMutationGenerator {
operators: Vec<Box<dyn TreeSitterMutationOperator>>,
}
impl RustMutationGenerator {
pub fn with_default_operators() -> Self {
Self {
operators: vec![
Box::new(RustBinaryOpMutation),
Box::new(RustRelationalOpMutation),
Box::new(RustLogicalOpMutation),
Box::new(RustBitwiseOpMutation),
Box::new(RustRangeOpMutation),
Box::new(RustPatternMutation),
Box::new(RustMethodChainMutation),
Box::new(RustBorrowMutation),
],
}
}
pub fn generate_mutants(&self, source: &str, file_path: &str) -> Result<Vec<Mutant>> {
let tree = self.parse_rust(source)?;
let mut mutants = Vec::new();
let root = tree.root_node();
self.visit_node(&root, source.as_bytes(), &mut mutants, file_path);
Ok(mutants)
}
fn parse_rust(&self, source: &str) -> Result<Tree> {
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_rust::LANGUAGE.into())
.map_err(|e| anyhow::anyhow!("Failed to set Rust language: {}", e))?;
parser
.parse(source, None)
.ok_or_else(|| anyhow::anyhow!("Failed to parse Rust source"))
}
fn visit_node(&self, node: &Node, source: &[u8], mutants: &mut Vec<Mutant>, file_path: &str) {
for operator in &self.operators {
if operator.can_mutate(node, source) {
let mutations = operator.mutate(node, source);
for (index, mutation) in mutations.iter().enumerate() {
let operator_name = operator.name();
let operator_type = map_operator_name_to_type(operator_name);
let mut hasher = Sha256::new();
hasher.update(mutation.source.as_bytes());
let hash = format!("{:x}", hasher.finalize());
let mutant = Mutant {
id: format!(
"{}_{}_{}_{}",
operator_name, mutation.location.line, mutation.location.column, index
),
original_file: PathBuf::from(file_path),
mutated_source: mutation.source.clone(),
location: mutation.location.clone(),
operator: operator_type,
hash,
status: MutantStatus::Pending,
};
mutants.push(mutant);
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.visit_node(&child, source, mutants, file_path);
}
}
}
fn map_operator_name_to_type(name: &str) -> MutationOperatorType {
match name {
"RustBinaryOp" => MutationOperatorType::ArithmeticReplacement,
"RustRelationalOp" => MutationOperatorType::RelationalReplacement,
"RustLogicalOp" => MutationOperatorType::ConditionalReplacement,
"RustBitwiseOp" => MutationOperatorType::BitwiseReplacement,
"RustRangeOp" => MutationOperatorType::RangeReplacement,
"RustPattern" => MutationOperatorType::PatternReplacement,
"RustMethodChain" => MutationOperatorType::MethodChainReplacement,
"RustBorrow" => MutationOperatorType::BorrowReplacement,
_ => MutationOperatorType::ArithmeticReplacement, }
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[ignore = "slow mutation test - run manually"]
#[test]
fn test_rust_generator_basic() {
let source = r#"
fn add(a: i32, b: i32) -> i32 {
a + b
}
"#;
let generator = RustMutationGenerator::with_default_operators();
let mutants = generator
.generate_mutants(source, "test.rs")
.expect("Should generate mutants");
assert!(
!mutants.is_empty(),
"Should generate at least one mutant for '+' operator"
);
let mutant = &mutants[0];
assert!(mutant.id.starts_with("RustBinaryOp"));
assert_eq!(mutant.original_file, PathBuf::from("test.rs"));
assert_ne!(mutant.mutated_source, source);
assert_eq!(mutant.status, MutantStatus::Pending);
}
#[ignore = "slow mutation test - run manually"]
#[test]
fn test_rust_generator_multiple_operators() {
let source = r#"
fn compare(a: i32, b: i32) -> bool {
a > b && a > 0
}
"#;
let generator = RustMutationGenerator::with_default_operators();
let mutants = generator
.generate_mutants(source, "test.rs")
.expect("Should generate mutants");
assert!(mutants.len() >= 6, "Should generate multiple mutants");
let has_relational = mutants.iter().any(|m| m.id.contains("RelationalOp"));
let has_logical = mutants.iter().any(|m| m.id.contains("LogicalOp"));
assert!(has_relational, "Should have relational operator mutants");
assert!(has_logical, "Should have logical operator mutants");
}
#[ignore = "slow mutation test - run manually"]
#[test]
fn test_rust_generator_range_operators() {
let source = r#"
fn range_sum(start: i32, end: i32) -> i32 {
(start..end).sum()
}
"#;
let generator = RustMutationGenerator::with_default_operators();
let mutants = generator
.generate_mutants(source, "test.rs")
.expect("Should generate mutants");
let has_range = mutants.iter().any(|m| m.id.contains("RangeOp"));
assert!(has_range, "Should have range operator mutants");
}
#[ignore = "slow mutation test - run manually"]
#[test]
fn test_rust_generator_pattern_matching() {
let source = r#"
fn unwrap(value: Option<i32>) -> i32 {
match value {
Some(x) => x,
None => 0,
}
}
"#;
let generator = RustMutationGenerator::with_default_operators();
let _mutants = generator
.generate_mutants(source, "test.rs")
.expect("Should generate mutants");
}
#[ignore = "slow mutation test - run manually"]
#[test]
fn test_rust_generator_method_chains() {
let source = r#"
fn process(values: Vec<i32>) -> Vec<i32> {
values.iter().map(|x| x * 2).collect()
}
"#;
let generator = RustMutationGenerator::with_default_operators();
let _mutants = generator
.generate_mutants(source, "test.rs")
.expect("Should generate mutants");
}
}