pmat 3.16.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
#![cfg_attr(coverage_nightly, coverage(off))]

#[cfg(test)]
mod property_tests {
    use crate::unified_quality::enhanced_parser::EnhancedParser;
    use proptest::prelude::*;
    use std::path::PathBuf;

    fn valid_rust_identifier() -> impl Strategy<Value = String> {
        prop::string::string_regex("[a-zA-Z_][a-zA-Z0-9_]*").unwrap()
    }

    fn simple_rust_function(name: String) -> String {
        format!("fn {}() {{ }}", name)
    }

    fn rust_function_with_if(name: String, condition: String) -> String {
        format!(
            r#"
            fn {}() {{
                if {} {{
                    return;
                }}
            }}
            "#,
            name, condition
        )
    }

    proptest! {
        #[test]
        #[ignore] // Fails on edge case: name = "_" - parser doesn't handle wildcard pattern
        fn parser_handles_valid_identifiers(name in valid_rust_identifier()) {
            let mut parser = EnhancedParser::new();
            let code = simple_rust_function(name);
            let path = PathBuf::from("test.rs");

            let result = parser.parse_incremental(&path, &code);
            prop_assert!(result.is_ok());

            let metrics = result.unwrap();
            prop_assert_eq!(metrics.functions, 1);
            prop_assert!(metrics.complexity >= 1); // Base complexity
        }

        #[test]
        #[ignore = "requires quality framework setup"]
        fn complexity_increases_with_control_flow(
            name in valid_rust_identifier(),
            condition in valid_rust_identifier()
        ) {
            let mut parser = EnhancedParser::new();
            let simple_code = simple_rust_function(name.clone());
            let complex_code = rust_function_with_if(name, condition);

            let path1 = PathBuf::from("simple.rs");
            let path2 = PathBuf::from("complex.rs");

            let simple_metrics = parser.parse_incremental(&path1, &simple_code).unwrap();
            let complex_metrics = parser.parse_incremental(&path2, &complex_code).unwrap();

            prop_assert!(complex_metrics.complexity > simple_metrics.complexity);
            prop_assert_eq!(simple_metrics.functions, complex_metrics.functions);
        }

        #[test]
        #[ignore = "requires quality framework setup"]
        fn cache_consistency(
            name in valid_rust_identifier(),
            _content_variations in prop::collection::vec(valid_rust_identifier(), 1..10)
        ) {
            let mut parser = EnhancedParser::new();
            let path = PathBuf::from("cache_test.rs");

            // Parse same content multiple times
            let base_code = simple_rust_function(name);

            let first_result = parser.parse_incremental(&path, &base_code).unwrap();
            let second_result = parser.parse_incremental(&path, &base_code).unwrap();

            // Results should be identical (cached)
            prop_assert_eq!(first_result.complexity, second_result.complexity);
            prop_assert_eq!(first_result.functions, second_result.functions);
            prop_assert_eq!(first_result.lines, second_result.lines);
        }

        #[test]
        fn hash_calculation_stable(content in "[a-zA-Z0-9\\s\\n{}();]{10,500}") {
            let parser = EnhancedParser::new();

            // Same content should produce same hash
            let hash1 = parser.calculate_hash(&content);
            let hash2 = parser.calculate_hash(&content);

            prop_assert_eq!(hash1, hash2);

            // Different content should produce different hash (with high probability)
            let modified_content = format!("{} // comment", content);
            let hash3 = parser.calculate_hash(&modified_content);
            prop_assert_ne!(hash1, hash3);
        }

        #[test]
        #[ignore = "requires quality framework setup"]
        fn satd_detection_accuracy(
            base_code in "[a-zA-Z0-9\\s\\n{}();]{50,200}",
            satd_count in 0usize..5
        ) {
            let mut parser = EnhancedParser::new();
            let path = PathBuf::from("satd_test.rs");

            // Add known SATD comments
            let satd_comments = ["TODO", "FIXME", "HACK", "XXX", "BUG"];
            let mut enhanced_code = base_code;

            for i in 0..satd_count {
                let comment_type = &satd_comments[i % satd_comments.len()];
                enhanced_code.push_str(&format!("\n// {}: test comment", comment_type));
            }

            let code = format!("fn test() {{\n{}\n}}", enhanced_code);
            let metrics = parser.parse_incremental(&path, &code).unwrap();

            prop_assert_eq!(metrics.satd_count, satd_count as u32);
        }

        #[test]
        #[ignore = "requires quality framework setup"]
        fn nesting_affects_cognitive_complexity(
            function_name in valid_rust_identifier(),
            nesting_levels in 1usize..5
        ) {
            let mut parser = EnhancedParser::new();
            let path = PathBuf::from("nesting_test.rs");

            // Create nested if statements
            let mut code = format!("fn {}() {{\n", function_name);

            for level in 0..nesting_levels {
                code.push_str(&"    ".repeat(level + 1));
                code.push_str(&format!("if condition_{} {{\n", level));
            }

            // Close all the braces
            for level in (0..nesting_levels).rev() {
                code.push_str(&"    ".repeat(level + 1));
                code.push_str("}\n");
            }
            code.push('}');

            let metrics = parser.parse_incremental(&path, &code).unwrap();

            // Cognitive complexity should be higher than cyclomatic for nested code
            prop_assert!(metrics.cognitive >= metrics.complexity);
            prop_assert!(metrics.complexity >= (nesting_levels as u32 + 1)); // +1 for base
        }

        #[test]
        fn line_counting_accuracy(
            line_count in 5usize..100,
            chars_per_line in 10usize..80
        ) {
            let mut parser = EnhancedParser::new();
            let path = PathBuf::from("lines_test.rs");

            // Generate code with known line count
            let mut code = String::new();
            for i in 0..line_count {
                let line_content = "a".repeat(chars_per_line % 50); // Keep reasonable
                code.push_str(&format!("// Line {}: {}\n", i, line_content));
            }
            code.push_str("fn test() {}"); // Add one more line

            let expected_lines = line_count + 1; // +1 for the function
            let metrics = parser.parse_incremental(&path, &code).unwrap();

            prop_assert_eq!(metrics.lines as usize, expected_lines);
        }

        #[test]
        #[ignore = "requires quality framework setup"]
        fn cache_invalidation_works(
            name in valid_rust_identifier(),
            content1 in "[a-zA-Z0-9]{10,100}",
            content2 in "[a-zA-Z0-9]{10,100}"
        ) {
            let mut parser = EnhancedParser::new();
            let path = PathBuf::from("invalidation_test.rs");

            let code1 = format!("fn {}() {{ /* {} */ }}", name, content1);
            let code2 = format!("fn {}() {{ /* {} */ }}", name, content2);

            // Parse first version
            let metrics1 = parser.parse_incremental(&path, &code1).unwrap();

            // Cache should have entry
            prop_assert!(parser.get_cached_metrics(&path).is_some());

            // Parse different content - should invalidate cache and reparse
            let metrics2 = parser.parse_incremental(&path, &code2).unwrap();

            // If content differs, metrics might differ (at least timestamps)
            if code1 != code2 {
                // At minimum, timestamps should be different
                prop_assert!(metrics1.timestamp <= metrics2.timestamp);
            }
        }

        #[test]
        #[ignore = "requires quality framework setup"]
        fn match_expression_complexity(
            function_name in valid_rust_identifier(),
            arm_count in 2usize..8
        ) {
            let mut parser = EnhancedParser::new();
            let path = PathBuf::from("match_test.rs");

            let mut code = format!("fn {}() {{\n    match x {{\n", function_name);

            for i in 0..arm_count {
                code.push_str(&format!("        {} => {},\n", i, i * 2));
            }

            code.push_str("    }\n}");

            let metrics = parser.parse_incremental(&path, &code).unwrap();

            // Match adds complexity for each arm
            prop_assert!(metrics.complexity >= (arm_count as u32 + 1)); // +1 for base
            prop_assert_eq!(metrics.functions, 1);
        }

        #[test]
        fn parser_memory_usage_bounded(
            file_count in 1usize..20,
            content_size in 100usize..1000
        ) {
            let mut parser = EnhancedParser::new();

            // Parse multiple files and check cache growth is reasonable
            for i in 0..file_count {
                let path = PathBuf::from(format!("file_{}.rs", i));
                let content = "a".repeat(content_size);
                let code = format!("fn test_{}() {{ /* {} */ }}", i, content);

                let _metrics = parser.parse_incremental(&path, &code).unwrap();
            }

            let stats = parser.cache_stats();
            prop_assert_eq!(stats.total_entries, file_count);

            // Memory usage should be reasonable (rough estimate)
            prop_assert!(stats.memory_usage_estimate > 0);
            prop_assert!(stats.memory_usage_estimate < file_count * 10000); // Upper bound check
        }
    }
}