use crate::cli::QualityCheckType;
use proptest::prelude::*;
use tempfile::TempDir;
use tokio::fs;
prop_compose! {
fn arb_quality_checks()(
include_complexity in any::<bool>(),
include_dead_code in any::<bool>(),
include_satd in any::<bool>(),
include_security in any::<bool>(),
include_entropy in any::<bool>(),
include_duplicates in any::<bool>(),
include_coverage in any::<bool>(),
) -> Vec<QualityCheckType> {
let mut checks = Vec::new();
if include_complexity { checks.push(QualityCheckType::Complexity); }
if include_dead_code { checks.push(QualityCheckType::DeadCode); }
if include_satd { checks.push(QualityCheckType::Satd); }
if include_security { checks.push(QualityCheckType::Security); }
if include_entropy { checks.push(QualityCheckType::Entropy); }
if include_duplicates { checks.push(QualityCheckType::Duplicates); }
if include_coverage { checks.push(QualityCheckType::Coverage); }
if checks.is_empty() {
vec![QualityCheckType::All]
} else {
checks
}
}
}
prop_compose! {
fn arb_thresholds()(
max_dead_code in 0.0..1.0f64,
min_entropy in 0.0..10.0f64,
max_complexity_p99 in 1..100u32,
) -> (f64, f64, u32) {
(max_dead_code, min_entropy, max_complexity_p99)
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10))]
#[test]
fn prop_quality_gate_shows_checks(
checks in arb_quality_checks(),
(_max_dead_code, _min_entropy, _max_complexity_p99) in arb_thresholds(),
_fail_on_violation in any::<bool>(),
_include_provability in any::<bool>(),
perf in any::<bool>(),
) {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let temp_dir = TempDir::new().unwrap();
let src_dir = temp_dir.path().join("src");
fs::create_dir_all(&src_dir).await.unwrap();
let test_file = src_dir.join("lib.rs");
fs::write(&test_file, r#"
fn simple_function() {
println!("Hello, world!");
}
fn complex_function(a: i32, b: i32, c: i32, d: i32) {
if a > 0 {
if b > 0 {
if c > 0 {
println!("Complex");
}
}
}
}
"#).await.unwrap();
let original_output = std::sync::Arc::new(std::sync::Mutex::new(Vec::<String>::new()));
let _output_clone = original_output.clone();
let checks_displayed = verify_checks_displayed(&checks, perf).await;
prop_assert!(checks_displayed, "Quality gate must show which checks are being run");
if perf {
let perf_metrics_shown = verify_perf_metrics_displayed().await;
prop_assert!(perf_metrics_shown, "Performance metrics must be shown when --perf flag is used");
}
Ok(())
})?;
}
#[test]
fn prop_quality_gate_exit_behavior(
fail_on_violation in any::<bool>(),
has_violations in any::<bool>(),
) {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let should_exit_with_error = fail_on_violation && has_violations;
let exit_status = simulate_quality_gate_exit(fail_on_violation, has_violations).await;
if should_exit_with_error {
prop_assert_eq!(exit_status, 1, "Should exit with code 1 when fail_on_violation=true and violations exist");
} else {
prop_assert_eq!(exit_status, 0, "Should exit with code 0 when no failures required or no violations");
}
Ok(())
})?;
}
#[test]
fn prop_check_combinations_valid(
checks in arb_quality_checks(),
) {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let check_names = get_check_names(&checks);
prop_assert!(!check_names.is_empty(), "At least one check must be specified");
for check_name in &check_names {
prop_assert!(is_valid_check_name(check_name), "Check name '{}' must be valid", check_name);
}
if checks.contains(&QualityCheckType::All) {
let all_check_names = get_all_check_names();
prop_assert!(check_names.len() >= all_check_names.len() || checks.len() == 1,
"All check type should include all available checks");
}
Ok(())
})?;
}
}
async fn verify_checks_displayed(checks: &[QualityCheckType], perf: bool) -> bool {
let check_names = get_check_names(checks);
if check_names.is_empty() {
return false;
}
for check_name in &check_names {
if check_name.trim().is_empty() {
return false;
}
}
if perf {
return true;
}
true
}
async fn verify_perf_metrics_displayed() -> bool {
true
}
async fn simulate_quality_gate_exit(fail_on_violation: bool, has_violations: bool) -> i32 {
if fail_on_violation && has_violations {
1 } else {
0 }
}
fn get_check_names(checks: &[QualityCheckType]) -> Vec<String> {
let mut names = Vec::new();
for check in checks {
match check {
QualityCheckType::All => {
names.extend(get_all_check_names());
}
QualityCheckType::Complexity => names.push("Complexity analysis".to_string()),
QualityCheckType::DeadCode => names.push("Dead code detection".to_string()),
QualityCheckType::Satd => names.push("Self-admitted technical debt (SATD)".to_string()),
QualityCheckType::Security => names.push("Security vulnerabilities".to_string()),
QualityCheckType::Entropy => names.push("Code entropy".to_string()),
QualityCheckType::Duplicates => names.push("Duplicate code".to_string()),
QualityCheckType::Coverage => names.push("Test coverage".to_string()),
QualityCheckType::Sections => names.push("Documentation sections".to_string()),
QualityCheckType::Provability => names.push("Provability".to_string()),
}
}
names.sort();
names.dedup();
names
}
fn get_all_check_names() -> Vec<String> {
vec![
"Complexity analysis".to_string(),
"Dead code detection".to_string(),
"Self-admitted technical debt (SATD)".to_string(),
"Security vulnerabilities".to_string(),
"Code entropy".to_string(),
"Duplicate code".to_string(),
"Test coverage".to_string(),
"Documentation sections".to_string(),
"Provability".to_string(),
]
}
fn is_valid_check_name(name: &str) -> bool {
let valid_names = get_all_check_names();
valid_names.contains(&name.to_string())
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_check_name_generation() {
let checks = vec![QualityCheckType::Complexity, QualityCheckType::DeadCode];
let names = get_check_names(&checks);
assert_eq!(names.len(), 2);
assert!(names.contains(&"Complexity analysis".to_string()));
assert!(names.contains(&"Dead code detection".to_string()));
}
#[test]
fn test_all_check_expansion() {
let checks = vec![QualityCheckType::All];
let names = get_check_names(&checks);
let all_names = get_all_check_names();
assert_eq!(names.len(), all_names.len());
for name in &all_names {
assert!(names.contains(name));
}
}
#[test]
fn test_valid_check_names() {
let valid_names = get_all_check_names();
for name in &valid_names {
assert!(is_valid_check_name(name));
}
assert!(!is_valid_check_name("Invalid Check"));
}
#[test]
fn test_exit_behavior_simulation() {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
assert_eq!(simulate_quality_gate_exit(false, false).await, 0);
assert_eq!(simulate_quality_gate_exit(false, true).await, 0);
assert_eq!(simulate_quality_gate_exit(true, false).await, 0);
assert_eq!(simulate_quality_gate_exit(true, true).await, 1);
});
}
}