use crate::cli::analysis_utilities::{calculate_provability_score, check_dead_code, check_entropy};
use proptest::prelude::*;
use std::path::Path;
proptest! {
#![proptest_config(ProptestConfig::with_cases(10))]
#[test]
#[ignore = "SLOW: scans entire codebase per iteration, >120s on CI"]
fn test_dead_code_threshold_property(max_percentage in 0.0..100.0) {
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(async {
check_dead_code(Path::new("."), max_percentage).await
});
match result {
Ok(violations) => {
for violation in violations {
prop_assert_eq!(violation.check_type, "dead_code");
prop_assert!(violation.severity == "error" || violation.severity == "warning");
prop_assert!(!violation.message.is_empty());
prop_assert!(!violation.file.is_empty());
}
}
Err(_) => {
}
}
}
#[test]
#[ignore] fn test_entropy_threshold_property(min_entropy in 0.0..1.0) {
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(async {
check_entropy(Path::new("."), min_entropy).await
});
match result {
Ok(violations) => {
for violation in violations {
prop_assert_eq!(violation.check_type, "entropy");
prop_assert!(violation.severity == "warning" || violation.severity == "error");
prop_assert!(violation.message.contains("entropy"));
if min_entropy >= 0.01 {
prop_assert!(violation.message.contains(&min_entropy.to_string()[..3]));
}
}
}
Err(_) => {
}
}
}
#[test]
#[ignore] fn test_entropy_monotonicity(
low_threshold in 0.1..0.5,
high_threshold in 0.5..0.9,
) {
let rt = tokio::runtime::Runtime::new().unwrap();
let (low_violations, high_violations) = rt.block_on(async {
let low = check_entropy(Path::new("."), low_threshold).await.unwrap_or_default();
let high = check_entropy(Path::new("."), high_threshold).await.unwrap_or_default();
(low, high)
});
prop_assert!(high_violations.len() >= low_violations.len(),
"Higher threshold ({}) found {} violations, but lower threshold ({}) found {}",
high_threshold, high_violations.len(), low_threshold, low_violations.len()
);
}
#[test]
fn test_provability_score_bounds(test_runs in 1usize..5) {
let rt = tokio::runtime::Runtime::new().unwrap();
for _ in 0..test_runs {
let result = rt.block_on(async {
calculate_provability_score(Path::new(".")).await
});
match result {
Ok(score) => {
prop_assert!(score >= 0.0, "Score {} should not be negative", score);
prop_assert!(score <= 1.0, "Score {} should not exceed 1.0", score);
}
Err(_) => {
}
}
}
}
#[test]
#[ignore = "SLOW: scans entire codebase per iteration, >120s on CI"]
fn test_dead_code_percentage_invariants(
threshold1 in 10.0..50.0,
threshold2 in 50.0..90.0,
) {
let rt = tokio::runtime::Runtime::new().unwrap();
let (violations1, violations2) = rt.block_on(async {
let v1 = check_dead_code(Path::new("."), threshold1).await.unwrap_or_default();
let v2 = check_dead_code(Path::new("."), threshold2).await.unwrap_or_default();
(v1, v2)
});
prop_assert!(violations1.len() >= violations2.len(),
"Threshold {} found {} violations, but threshold {} found {}",
threshold1, violations1.len(), threshold2, violations2.len()
);
}
#[test]
#[ignore] fn test_violation_message_quality(check_type in prop::sample::select(vec!["dead_code", "entropy"])) {
let rt = tokio::runtime::Runtime::new().unwrap();
let violations = rt.block_on(async {
match check_type {
"dead_code" => check_dead_code(Path::new("."), 5.0).await.unwrap_or_default(),
"entropy" => check_entropy(Path::new("."), 0.9).await.unwrap_or_default(),
_ => vec![],
}
});
for violation in violations {
prop_assert!(!violation.message.is_empty());
prop_assert!(violation.message.len() > 10, "Message too short: {}", violation.message);
prop_assert!(violation.message.len() < 500, "Message too long: {}", violation.message);
match check_type {
"dead_code" => prop_assert!(violation.message.to_lowercase().contains("dead")),
"entropy" => prop_assert!(violation.message.to_lowercase().contains("entropy") ||
violation.message.to_lowercase().contains("diversity")),
_ => {}
}
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod integration_tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[tokio::test]
async fn test_dead_code_with_test_project() {
let temp_dir = TempDir::new().unwrap();
let src_dir = temp_dir.path().join("src");
fs::create_dir_all(&src_dir).unwrap();
fs::write(
temp_dir.path().join("Cargo.toml"),
r#"[package]
name = "test-dead-code"
version = "0.1.0"
edition = "2021"
"#,
)
.unwrap();
fs::write(
src_dir.join("lib.rs"),
r#"
/// Used function.
pub fn used_function() {
println!("I am used");
}
fn unused_function() {
println!("I am never called");
}
fn another_unused() {
let _x = 42;
println!("Also unused");
}
"#,
)
.unwrap();
let violations_low = check_dead_code(temp_dir.path(), 5.0).await.unwrap();
let violations_high = check_dead_code(temp_dir.path(), 90.0).await.unwrap();
assert!(
!violations_low.is_empty(),
"Should find dead code with 5% threshold"
);
assert!(violations_high.len() <= violations_low.len());
}
#[tokio::test]
async fn test_entropy_with_repetitive_code() {
let temp_dir = TempDir::new().unwrap();
let src_dir = temp_dir.path().join("src");
fs::create_dir_all(&src_dir).unwrap();
fs::write(
src_dir.join("repetitive.rs"),
r#"
fn func1() { let x = 1; let y = 1; let z = 1; }
fn func2() { let x = 1; let y = 1; let z = 1; }
fn func3() { let x = 1; let y = 1; let z = 1; }
fn func4() { let x = 1; let y = 1; let z = 1; }
fn func5() { let x = 1; let y = 1; let z = 1; }
"#,
)
.unwrap();
fs::write(
src_dir.join("diverse.rs"),
r#"
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
/// Configuration for config.
pub struct Config {
name: String,
value: i32,
enabled: bool,
}
impl Config {
/// Create a new instance.
pub fn new(name: String) -> Self {
Self { name, value: 42, enabled: true }
}
}
"#,
)
.unwrap();
let violations = check_entropy(temp_dir.path(), 0.7).await.unwrap();
assert!(
violations.is_empty() || !violations.is_empty(),
"Entropy analyzer should complete analysis without error"
);
}
}