pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
//! Equivalent Mutant Detector Tests - Phase 4.2
//!
//! EXTREME TDD: RED PHASE - These tests MUST fail until implementation is complete

#[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();

        // Replace 'a + 0' with 'a' is equivalent
        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); // High confidence for trivial cases
    }

    #[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();

        // Multiplication by 1 is neutral
        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();

        // 'x || true' is always true
        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);

        // Must extract structural similarity
        assert!(features.ast_similarity >= 0.0);
        assert!(features.ast_similarity <= 1.0);

        // Must detect operator patterns
        assert!(!features.operator_patterns.is_empty());

        // edit_distance is unsigned type, validated by type system
        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();

        // Double negation
        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();

        // a + b == b + a
        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();

        // (a + b) + c == a + (b + c)
        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());

        // Should make same predictions
        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 }"), // Equivalent
            create_mutant_with_source("fn g(x: i32) -> i32 { x }"), // Equivalent
            create_mutant_with_source("fn h(a: i32, b: i32) -> i32 { a - b }"), // Not equivalent
        ];

        let filtered = detector.filter_equivalents(&mutants, &original_sources);
        assert!(filtered.is_ok());

        let non_equiv = filtered.unwrap();
        assert_eq!(non_equiv.len(), 1); // Only one non-equivalent
        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();

        // Add new equivalence patterns
        let new_patterns = vec![
            create_equivalence_sample("x - 0", "x", true),
            create_equivalence_sample("x / 1", "x", true),
        ];

        detector.update(&new_patterns).unwrap();

        // Should still be trained
        assert!(detector.is_trained());

        // Can detect new patterns
        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);
    }

    // Helper functions

    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), // De Morgan's law
            create_equivalence_sample("!(x || y)", "!x && !y", true), // De Morgan's law
        ]
    }

    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), // Not commutative
            create_equivalence_sample("a / b", "b / a", false), // Not commutative
        ]
    }

    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),
        ]
    }
}