pmat 3.15.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
#![cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod metric_accuracy_tests {
    use tempfile::TempDir;

    fn calculate_variance(values: &[f64]) -> f64 {
        if values.is_empty() {
            return 0.0;
        }

        let mean = values.iter().sum::<f64>() / values.len() as f64;
        let sum_squared_diff: f64 = values.iter().map(|v| (*v - mean).powi(2)).sum();

        sum_squared_diff / values.len() as f64
    }

    #[tokio::test]
    async fn test_tdg_variance() {
        let temp_dir = TempDir::new().unwrap();

        // Create test files with different complexities
        let simple_file = temp_dir.path().join("simple.rs");
        tokio::fs::write(
            &simple_file,
            r#"
                fn simple_function() -> i32 {
                    42
                }
            "#,
        )
        .await
        .unwrap();

        let complex_file = temp_dir.path().join("complex.rs");
        tokio::fs::write(
            &complex_file,
            r#"
                fn complex_function(items: &[i32]) -> i32 {
                    let mut sum = 0;
                    for i in 0..items.len() {
                        if items[i] > 0 {
                            for j in 0..items[i] {
                                if j % 2 == 0 {
                                    sum += j;
                                } else {
                                    sum -= j;
                                }
                            }
                        }
                    }
                    sum
                }
                
                fn another_complex(x: i32) -> i32 {
                    match x {
                        0 => 1,
                        1 => 1,
                        n => another_complex(n - 1) + another_complex(n - 2)
                    }
                }
            "#,
        )
        .await
        .unwrap();

        let medium_file = temp_dir.path().join("medium.rs");
        tokio::fs::write(
            &medium_file,
            r#"
                fn medium_complexity(items: &[String]) -> Option<String> {
                    if items.is_empty() {
                        return None;
                    }
                    
                    let mut longest = &items[0];
                    for item in items.iter() {
                        if item.len() > longest.len() {
                            longest = item;
                        }
                    }
                    
                    Some(longest.clone())
                }
            "#,
        )
        .await
        .unwrap();

        // Read file sizes as a proxy for complexity
        let simple_size = tokio::fs::metadata(&simple_file).await.unwrap().len() as f64;
        let complex_size = tokio::fs::metadata(&complex_file).await.unwrap().len() as f64;
        let medium_size = tokio::fs::metadata(&medium_file).await.unwrap().len() as f64;

        let values = vec![simple_size, complex_size, medium_size];
        let variance = calculate_variance(&values);

        // File sizes should vary
        assert!(
            variance > 1000.0,
            "File size variance {variance:.3} too low - test files too similar in size"
        );
    }

    #[test]
    fn test_cognitive_bounds() {
        // Test is implemented in verified_complexity.rs
        // This is a placeholder to ensure the test suite structure is correct
        // Test passes if compilation succeeds
    }

    #[tokio::test]
    async fn test_ffi_not_dead() {
        use crate::services::dead_code_prover::{DeadCodeProofType, DeadCodeProver};

        let temp_dir = TempDir::new().unwrap();
        let ffi_file = temp_dir.path().join("ffi_export.rs");

        let content = r#"
                #[no_mangle]
                pub extern "C" fn exported_function() -> i32 {
                    42
                }
                
                #[no_mangle]
                pub static EXPORTED_STATIC: i32 = 100;
                
                #[export_name = "custom_name"]
                /// Renamed export.
                pub fn renamed_export() -> i32 {
                    200
                }
                
                fn internal_helper() -> i32 {
                    123
                }
            "#;

        tokio::fs::write(&ffi_file, content).await.unwrap();

        let mut prover = DeadCodeProver::new();
        let proofs = prover.analyze_file(&ffi_file, content);

        // Should detect functions in the file
        assert!(!proofs.is_empty(), "Should find some function proofs");

        // Check that at least one function is marked as externally visible
        let live_proofs = proofs
            .iter()
            .filter(|p| matches!(p.proof_type, DeadCodeProofType::ProvenLive))
            .count();

        // Since we have FFI exports, at least one should be marked as live
        assert!(
            live_proofs > 0,
            "Should find at least one live function due to FFI"
        );

        // Verify FFI tracker works
        assert!(
            prover.ffi_tracker().ffi_export_count() > 0,
            "Should detect FFI exports"
        );
    }

    #[tokio::test]
    async fn test_complexity_detection() {
        let temp_dir = TempDir::new().unwrap();
        let test_file = temp_dir.path().join("known_complexity.rs");

        tokio::fs::write(
            &test_file,
            r#"
                // Expected CC=1 (single path)
                fn simple() -> i32 {
                    42
                }
                
                // Expected CC=3 (if + 2 conditions)
                fn branching(x: i32, y: i32) -> i32 {
                    if x > 0 && y > 0 {
                        x + y
                    } else {
                        0
                    }
                }
                
                // Expected CC>20 (nested loops with conditions)
                fn very_complex(matrix: &[Vec<i32>]) -> i32 {
                    let mut result = 0;
                    for (i, row) in matrix.iter().enumerate() {
                        for (j, &cell) in row.iter().enumerate() {
                            if i % 2 == 0 {
                                if j % 2 == 0 {
                                    if cell > 0 {
                                        result += cell;
                                    } else if cell < -10 {
                                        result -= cell * 2;
                                    }
                                } else if j % 3 == 0 {
                                    result *= 2;
                                }
                            } else if i % 3 == 0 {
                                for k in 0..cell.abs() {
                                    if k % 2 == 0 {
                                        result += k;
                                    }
                                }
                            }
                        }
                    }
                    result
                }
            "#,
        )
        .await
        .unwrap();

        // Complex file should be larger
        let file_size = tokio::fs::metadata(&test_file).await.unwrap().len();
        assert!(
            file_size > 500,
            "Complex file should be > 500 bytes, got {file_size}"
        );
    }
}