pub mod fixtures {
use std::path::{Path, PathBuf};
use tempfile::TempDir;
pub struct TestDir {
pub dir: TempDir,
}
impl TestDir {
pub fn new() -> std::io::Result<Self> {
let dir = TempDir::new()?;
Ok(Self { dir })
}
pub fn path(&self) -> &Path {
self.dir.path()
}
pub fn add_file(&self, name: &str, content: &str) -> std::io::Result<PathBuf> {
let path = self.dir.path().join(name);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&path, content)?;
Ok(path)
}
}
pub const PYTHON_SIMPLE_FUNCTION: &str = r#"
def simple():
return 42
"#;
pub const PYTHON_MODERATE_COMPLEXITY: &str = r#"
def moderate(a, b, c, d):
if a > 0:
return 1
elif b > 0:
return 2
elif c > 0:
return 3
elif d > 0:
return 4
else:
return 0
"#;
pub const PYTHON_HIGH_COMPLEXITY: &str = r#"
def complex_function(a, b, c, d, e, f):
result = 0
if a > 0:
if b > 0:
result += 1
elif c > 0:
result += 2
else:
result += 3
elif d > 0:
if e > 0:
result += 4
elif f > 0:
result += 5
else:
result += 6
else:
if a < -10:
result -= 1
elif b < -10:
result -= 2
else:
result -= 3
for i in range(10):
if i % 2 == 0:
result += i
return result
"#;
pub const PYTHON_MULTIPLE_FUNCTIONS: &str = r#"
def func_cc1():
return 1
def func_cc2(a):
if a:
return 1
return 0
def func_cc3(a, b):
if a:
return 1
elif b:
return 2
return 0
def func_cc4(a, b, c):
if a:
return 1
elif b:
return 2
elif c:
return 3
return 0
"#;
pub const PYTHON_SINGLE_METHOD_CLASS: &str = r#"
class SingleMethod:
def __init__(self):
self.value = 0
def get_value(self):
return self.value
"#;
pub const PYTHON_COHESIVE_CLASS: &str = r#"
class CohesiveClass:
def __init__(self):
self.value = 0
def get_value(self):
return self.value
def set_value(self, v):
self.value = v
def increment(self):
self.value += 1
def decrement(self):
self.value -= 1
"#;
pub const PYTHON_DISCONNECTED_CLASS: &str = r#"
class DisconnectedClass:
def method_a(self):
self.field_a = 1
return self.field_a
def method_b(self):
self.field_b = 2
return self.field_b
def method_c(self):
self.field_c = 3
return self.field_c
def method_d(self):
self.field_d = 4
return self.field_d
"#;
pub const PYTHON_LOW_COHESION_CLASS: &str = r#"
class LowCohesionClass:
# Group 1: handles user data
def get_user_name(self):
return self.user_name
def set_user_name(self, name):
self.user_name = name
# Group 2: handles product data (disconnected from user)
def get_product_id(self):
return self.product_id
def set_product_id(self, id):
self.product_id = id
# Group 3: handles order data (disconnected from both)
def get_order_total(self):
return self.order_total
def calculate_total(self, items):
self.order_total = sum(items)
"#;
pub const PYTHON_UNREACHABLE_FUNCTION: &str = r#"
def main():
helper()
return 0
def helper():
return 42
def _unused_private():
"""This private function is never called."""
pass
def another_unused():
"""Public but unreachable."""
pass
"#;
pub const PYTHON_ENTRY_POINTS: &str = r#"
def main():
"""Entry point - should not be flagged."""
return run()
def run():
"""Called by main."""
return start()
def start():
"""Called by run."""
return 0
def test_something():
"""Test function - should not be flagged."""
assert True
def setup():
"""Setup function - should not be flagged."""
pass
def teardown():
"""Teardown function - should not be flagged."""
pass
"#;
pub const PYTHON_DUNDER_METHODS: &str = r#"
class MyClass:
def __init__(self):
self.value = 0
def __str__(self):
return str(self.value)
def __repr__(self):
return f"MyClass({self.value})"
def __eq__(self, other):
return self.value == other.value
def public_method(self):
return self.value
"#;
pub const PYTHON_CONCRETE_PACKAGE: &str = r#"
class ConcreteClass:
def method(self):
return 42
class AnotherConcrete:
def another_method(self):
return 24
"#;
pub const PYTHON_ABSTRACT_PACKAGE: &str = r#"
from abc import ABC, abstractmethod
class AbstractBase(ABC):
@abstractmethod
def abstract_method(self):
pass
class ConcreteImpl(AbstractBase):
def abstract_method(self):
return 42
"#;
pub const PYTHON_HIGH_EFFERENT: &str = r#"
import os
import sys
import json
import logging
import pathlib
from typing import List, Dict
def process():
pass
"#;
pub const PYTHON_ISOLATED_PACKAGE: &str = r#"
def standalone_function():
return 42
class IsolatedClass:
def method(self):
return 1
"#;
pub const PYTHON_MODULE_A: &str = r#"
from module_b import func_b1, func_b2, func_b3
def func_a1():
return func_b1()
def func_a2():
return func_b2() + func_b1()
def func_a3():
return func_b3() + func_b2() + func_b1()
"#;
pub const PYTHON_MODULE_B: &str = r#"
from module_a import func_a1, func_a2
def func_b1():
return 1
def func_b2():
return func_a1() + 2
def func_b3():
return func_a2() + 3
"#;
pub const PYTHON_LOOSELY_COUPLED: &str = r#"
def independent_func():
return 42
def another_independent():
return 24
"#;
pub const PYTHON_CLONES: &str = r#"
def clone_a(x, y, z):
result = 0
for i in range(x):
if i % 2 == 0:
result += y
else:
result += z
return result
def clone_b(a, b, c):
result = 0
for i in range(a):
if i % 2 == 0:
result += b
else:
result += c
return result
"#;
pub const PYTHON_DIFFERENT_FUNCTIONS: &str = r#"
def function_one():
return 1
def function_two(a, b, c, d, e):
for x in range(100):
for y in range(100):
if a > b:
if c > d:
return e
return 0
"#;
pub const TYPESCRIPT_SIMPLE: &str = r#"
function simpleFunction(): number {
return 42;
}
class SimpleClass {
private value: number;
constructor() {
this.value = 0;
}
getValue(): number {
return this.value;
}
}
"#;
pub const GO_SIMPLE: &str = r#"
package main
func simpleFunction() int {
return 42
}
type SimpleStruct struct {
value int
}
func (s *SimpleStruct) GetValue() int {
return s.value
}
"#;
pub const RUST_SIMPLE: &str = r#"
fn simple_function() -> i32 {
42
}
struct SimpleStruct {
value: i32,
}
impl SimpleStruct {
fn get_value(&self) -> i32 {
self.value
}
}
"#;
pub const PYTHON_SYNTAX_ERROR: &str = r#"
def broken_function(
# Missing closing parenthesis
return 42
"#;
pub const EMPTY_FILE: &str = "";
pub const COMMENTS_ONLY: &str = r#"
# This file has only comments
# No actual code here
# Just comments
"#;
}
#[cfg(test)]
mod core_type_tests {
#[test]
#[ignore = "health module not yet implemented"]
fn test_health_report_structure() {
todo!("Implement HealthReport struct per spec section 2.2");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_health_summary_aggregation() {
todo!("Implement HealthSummary aggregation");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_severity_ordering() {
todo!("Implement Severity enum with correct ordering");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_sub_analysis_result_structure() {
todo!("Implement SubAnalysisResult struct");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_health_report_to_dict() {
todo!("Implement HealthReport.to_dict()");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_health_report_detail_method() {
todo!("Implement HealthReport.detail() method");
}
}
#[cfg(test)]
mod complexity_tests {
use super::fixtures::*;
#[test]
#[ignore = "health module not yet implemented"]
fn test_complexity_hotspot_detection() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("high_complexity.py", PYTHON_HIGH_COMPLEXITY)
.unwrap();
todo!("Implement complexity hotspot detection");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_complexity_average_calculation() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("multiple.py", PYTHON_MULTIPLE_FUNCTIONS)
.unwrap();
todo!("Implement complexity average calculation");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_complexity_empty_file() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("empty.py", EMPTY_FILE).unwrap();
todo!("Implement complexity analysis for empty files");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_complexity_per_function_data() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("test.py", PYTHON_MODERATE_COMPLEXITY)
.unwrap();
todo!("Implement per-function complexity data");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_complexity_sorted_descending() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("multiple.py", PYTHON_MULTIPLE_FUNCTIONS)
.unwrap();
todo!("Implement complexity sorting");
}
}
#[cfg(test)]
mod cohesion_tests {
use super::fixtures::*;
use crate::quality::cohesion::{analyze_cohesion, CohesionVerdict};
#[test]
fn test_lcom4_single_method_class() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("single.py", PYTHON_SINGLE_METHOD_CLASS)
.unwrap();
let report = analyze_cohesion(test_dir.path(), None, 2).unwrap();
assert_eq!(report.classes_analyzed, 1);
let class = &report.classes[0];
assert_eq!(class.name, "SingleMethod");
assert_eq!(class.lcom4, 1);
assert_eq!(class.method_count, 1);
assert_eq!(class.verdict, CohesionVerdict::Cohesive);
}
#[test]
fn test_lcom4_disconnected_methods() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("disconnected.py", PYTHON_DISCONNECTED_CLASS)
.unwrap();
let report = analyze_cohesion(test_dir.path(), None, 2).unwrap();
assert_eq!(report.classes_analyzed, 1);
let class = &report.classes[0];
assert_eq!(class.name, "DisconnectedClass");
assert_eq!(class.method_count, 4);
assert_eq!(class.lcom4, 4);
assert_eq!(class.verdict, CohesionVerdict::SplitCandidate);
}
#[test]
fn test_lcom4_connected_methods() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("cohesive.py", PYTHON_COHESIVE_CLASS)
.unwrap();
let report = analyze_cohesion(test_dir.path(), None, 2).unwrap();
assert_eq!(report.classes_analyzed, 1);
let class = &report.classes[0];
assert_eq!(class.name, "CohesiveClass");
assert_eq!(class.method_count, 4);
assert_eq!(class.lcom4, 1);
assert_eq!(class.verdict, CohesionVerdict::Cohesive);
}
#[test]
fn test_cohesion_low_detection() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("low_cohesion.py", PYTHON_LOW_COHESION_CLASS)
.unwrap();
let report = analyze_cohesion(test_dir.path(), None, 2).unwrap();
assert_eq!(report.classes_analyzed, 1);
let class = &report.classes[0];
assert_eq!(class.name, "LowCohesionClass");
assert!(class.lcom4 > 2, "LCOM4 should be > 2, got {}", class.lcom4);
assert_eq!(class.verdict, CohesionVerdict::SplitCandidate);
assert_eq!(report.low_cohesion_count, 1);
}
#[test]
fn test_cohesion_excludes_dunder() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("dunder.py", PYTHON_DUNDER_METHODS)
.unwrap();
let report = analyze_cohesion(test_dir.path(), None, 2).unwrap();
assert_eq!(report.classes_analyzed, 1);
let class = &report.classes[0];
assert_eq!(class.name, "MyClass");
assert_eq!(class.method_count, 1);
assert_eq!(class.lcom4, 1);
}
#[test]
fn test_cohesion_verdict_values() {
let cohesive = CohesionVerdict::Cohesive;
let split = CohesionVerdict::SplitCandidate;
assert_eq!(serde_json::to_string(&cohesive).unwrap(), "\"cohesive\"");
assert_eq!(
serde_json::to_string(&split).unwrap(),
"\"split_candidate\""
);
}
#[test]
fn test_cohesion_component_info() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("disconnected.py", PYTHON_DISCONNECTED_CLASS)
.unwrap();
let report = analyze_cohesion(test_dir.path(), None, 2).unwrap();
let class = &report.classes[0];
assert_eq!(class.components.len(), 4);
for component in &class.components {
assert_eq!(component.methods.len(), 1);
assert!(!component.fields.is_empty());
}
}
#[test]
fn test_cohesion_typescript_class() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("simple.ts", TYPESCRIPT_SIMPLE).unwrap();
let report = analyze_cohesion(test_dir.path(), None, 2).unwrap();
assert!(report.classes_analyzed >= 1);
let class = report.classes.iter().find(|c| c.name == "SimpleClass");
assert!(class.is_some(), "SimpleClass should be found");
}
#[test]
fn test_cohesion_go_struct_methods() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("simple.go", GO_SIMPLE).unwrap();
let report = analyze_cohesion(test_dir.path(), None, 2).unwrap();
assert!(report.classes_analyzed >= 1);
let class = report.classes.iter().find(|c| c.name == "SimpleStruct");
assert!(class.is_some(), "SimpleStruct should be found");
}
#[test]
fn test_cohesion_rust_impl_blocks() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("simple.rs", RUST_SIMPLE).unwrap();
let report = analyze_cohesion(test_dir.path(), None, 2).unwrap();
assert!(report.classes_analyzed >= 1);
let class = report.classes.iter().find(|c| c.name == "SimpleStruct");
assert!(class.is_some(), "SimpleStruct should be found");
}
}
#[cfg(test)]
mod dead_code_tests {
use super::fixtures::*;
#[test]
#[ignore = "health module not yet implemented"]
fn test_dead_code_unreachable_function() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("unreachable.py", PYTHON_UNREACHABLE_FUNCTION)
.unwrap();
todo!("Implement unreachable function detection");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_dead_code_exclude_entry_points() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("entry_points.py", PYTHON_ENTRY_POINTS)
.unwrap();
todo!("Implement entry point exclusion");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_dead_code_exclude_dunders() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("dunder.py", PYTHON_DUNDER_METHODS)
.unwrap();
todo!("Implement dunder method exclusion");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_dead_function_structure() {
todo!("Implement DeadFunction struct");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_dead_code_summary() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("unreachable.py", PYTHON_UNREACHABLE_FUNCTION)
.unwrap();
todo!("Implement DeadCodeSummary");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_dead_code_by_file() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("a.py", PYTHON_UNREACHABLE_FUNCTION)
.unwrap();
test_dir.add_file("b.py", PYTHON_ENTRY_POINTS).unwrap();
todo!("Implement by_file grouping");
}
}
#[cfg(test)]
mod martin_metrics_tests {
use super::fixtures::*;
#[test]
#[ignore = "health module not yet implemented"]
fn test_martin_abstractness() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("concrete.py", PYTHON_CONCRETE_PACKAGE)
.unwrap();
test_dir
.add_file("abstract.py", PYTHON_ABSTRACT_PACKAGE)
.unwrap();
todo!("Implement abstractness calculation");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_martin_instability() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("high_efferent.py", PYTHON_HIGH_EFFERENT)
.unwrap();
todo!("Implement instability calculation");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_martin_distance() {
todo!("Implement distance calculation");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_martin_zone_detection() {
todo!("Implement zone detection");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_metrics_health_values() {
todo!("Implement MetricsHealth enum");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_martin_isolated_package() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("isolated.py", PYTHON_ISOLATED_PACKAGE)
.unwrap();
todo!("Implement isolated package detection");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_package_metrics_structure() {
todo!("Implement PackageMetrics struct");
}
}
#[cfg(test)]
mod coupling_tests {
use super::fixtures::*;
use crate::quality::coupling::{analyze_coupling, CouplingVerdict};
use crate::types::Language;
#[test]
fn test_coupling_import_detection() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("module_a.py", PYTHON_MODULE_A).unwrap();
test_dir.add_file("module_b.py", PYTHON_MODULE_B).unwrap();
let report = analyze_coupling(test_dir.path(), Some(Language::Python), Some(10)).unwrap();
assert!(report.modules_analyzed >= 2);
}
#[test]
fn test_coupling_score_calculation() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("module_a.py", PYTHON_MODULE_A).unwrap();
test_dir.add_file("module_b.py", PYTHON_MODULE_B).unwrap();
let report = analyze_coupling(test_dir.path(), Some(Language::Python), Some(10)).unwrap();
for pair in &report.top_pairs {
assert!(
pair.score >= 0.0 && pair.score <= 1.0,
"Score {} out of range",
pair.score
);
}
}
#[test]
fn test_coupling_tight_threshold() {
assert_eq!(CouplingVerdict::from_score(0.0), CouplingVerdict::Loose);
assert_eq!(CouplingVerdict::from_score(0.29), CouplingVerdict::Loose);
assert_eq!(CouplingVerdict::from_score(0.3), CouplingVerdict::Moderate);
assert_eq!(CouplingVerdict::from_score(0.59), CouplingVerdict::Moderate);
assert_eq!(CouplingVerdict::from_score(0.6), CouplingVerdict::Tight);
assert_eq!(CouplingVerdict::from_score(1.0), CouplingVerdict::Tight);
}
#[test]
fn test_coupling_verdict_values() {
assert_eq!(
serde_json::to_string(&CouplingVerdict::Loose).unwrap(),
"\"loose\""
);
assert_eq!(
serde_json::to_string(&CouplingVerdict::Moderate).unwrap(),
"\"moderate\""
);
assert_eq!(
serde_json::to_string(&CouplingVerdict::Tight).unwrap(),
"\"tight\""
);
}
#[test]
fn test_module_coupling_structure() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("a.py", PYTHON_SIMPLE_FUNCTION).unwrap();
test_dir.add_file("b.py", PYTHON_SIMPLE_FUNCTION).unwrap();
let report = analyze_coupling(test_dir.path(), Some(Language::Python), Some(10)).unwrap();
let json = serde_json::to_value(&report).unwrap();
assert!(json.get("modules_analyzed").is_some());
assert!(json.get("pairs_analyzed").is_some());
assert!(json.get("top_pairs").is_some());
}
#[test]
fn test_coupling_loose() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("independent.py", PYTHON_LOOSELY_COUPLED)
.unwrap();
test_dir
.add_file("another.py", PYTHON_ISOLATED_PACKAGE)
.unwrap();
let report = analyze_coupling(test_dir.path(), Some(Language::Python), Some(10)).unwrap();
for pair in &report.top_pairs {
assert!(
pair.score < 0.6,
"Expected loose coupling, got score {}",
pair.score
);
}
}
}
#[cfg(test)]
mod similarity_tests {
use super::fixtures::*;
use crate::quality::similarity::{find_similar, SimilarityReason};
use crate::types::Language;
#[test]
fn test_similar_identical_functions() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("clones.py", PYTHON_CLONES).unwrap();
let report = find_similar(test_dir.path(), Some(Language::Python), 0.5, Some(100)).unwrap();
assert!(report.functions_analyzed >= 2);
}
#[test]
fn test_similar_threshold() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("clones.py", PYTHON_CLONES).unwrap();
test_dir
.add_file("different.py", PYTHON_DIFFERENT_FUNCTIONS)
.unwrap();
let threshold = 0.7;
let report = find_similar(
test_dir.path(),
Some(Language::Python),
threshold,
Some(100),
)
.unwrap();
for pair in &report.similar_pairs {
assert!(
pair.score >= threshold,
"Pair {} <-> {} has score {} below threshold {}",
pair.func_a.name,
pair.func_b.name,
pair.score,
threshold
);
}
}
#[test]
fn test_similar_different_functions() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("different.py", PYTHON_DIFFERENT_FUNCTIONS)
.unwrap();
let report = find_similar(test_dir.path(), Some(Language::Python), 0.1, Some(100)).unwrap();
for pair in &report.similar_pairs {
assert!(
pair.score < 0.95,
"Different functions {} <-> {} scored too high: {}",
pair.func_a.name,
pair.func_b.name,
pair.score
);
}
}
#[test]
fn test_similar_pair_structure() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("a.py", PYTHON_CLONES).unwrap();
let report = find_similar(test_dir.path(), Some(Language::Python), 0.5, Some(100)).unwrap();
let json = serde_json::to_value(&report).unwrap();
assert!(json.get("functions_analyzed").is_some());
assert!(json.get("pairs_compared").is_some());
assert!(json.get("similar_pairs").is_some());
assert!(json.get("threshold").is_some());
}
#[test]
fn test_similar_reasons() {
assert_eq!(
SimilarityReason::SameSignature.description(),
"same parameter count"
);
assert_eq!(
SimilarityReason::SimilarComplexity.description(),
"similar complexity"
);
assert_eq!(
SimilarityReason::SimilarCallPattern.description(),
"similar call pattern"
);
assert_eq!(
SimilarityReason::SimilarLoc.description(),
"similar lines of code"
);
}
#[test]
fn test_similar_deduplication() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("clones.py", PYTHON_CLONES).unwrap();
let report = find_similar(test_dir.path(), Some(Language::Python), 0.3, Some(100)).unwrap();
let mut seen_pairs: std::collections::HashSet<(String, String)> =
std::collections::HashSet::new();
for pair in &report.similar_pairs {
let (a, b) = if pair.func_a.name < pair.func_b.name {
(pair.func_a.name.clone(), pair.func_b.name.clone())
} else {
(pair.func_b.name.clone(), pair.func_a.name.clone())
};
assert!(
!seen_pairs.contains(&(a.clone(), b.clone())),
"Duplicate pair found: {} <-> {}",
a,
b
);
seen_pairs.insert((a, b));
}
}
#[test]
fn test_similar_max_functions() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("multi.py", PYTHON_MULTIPLE_FUNCTIONS)
.unwrap();
let report = find_similar(test_dir.path(), Some(Language::Python), 0.0, Some(2)).unwrap();
assert!(
report.functions_analyzed <= 2,
"Expected at most 2 functions, got {}",
report.functions_analyzed
);
assert!(
report.pairs_compared <= 1,
"Expected at most 1 pair, got {}",
report.pairs_compared
);
}
#[test]
fn test_similarity_weighted_components() {
}
}
#[cfg(test)]
mod integration_tests {
use super::fixtures::*;
use crate::quality::health::{run_health, HealthOptions};
#[test]
fn test_quick_mode_excludes_coupling_similar() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("main.py", PYTHON_MULTIPLE_FUNCTIONS)
.unwrap();
let options = HealthOptions {
quick: true,
..Default::default()
};
let report = run_health(test_dir.path(), None, options).unwrap();
assert!(report.quick_mode, "Report should indicate quick mode");
let coupling = report
.sub_results
.get("coupling")
.expect("coupling result missing");
assert!(
!coupling.success,
"coupling should be skipped in quick mode"
);
assert!(
coupling
.error
.as_ref()
.is_some_and(|e| e.contains("skipped")),
"coupling should have skipped error message"
);
let similar = report
.sub_results
.get("similar")
.expect("similar result missing");
assert!(!similar.success, "similar should be skipped in quick mode");
assert!(
similar
.error
.as_ref()
.is_some_and(|e| e.contains("skipped")),
"similar should have skipped error message"
);
assert!(
report
.sub_results
.get("complexity")
.expect("complexity missing")
.success
);
assert!(
report
.sub_results
.get("cohesion")
.expect("cohesion missing")
.success
);
assert!(report.sub_results.contains_key("dead"));
assert!(report.sub_results.contains_key("metrics"));
}
#[test]
fn test_full_mode_includes_all() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("main.py", PYTHON_MULTIPLE_FUNCTIONS)
.unwrap();
test_dir
.add_file("helper.py", PYTHON_COHESIVE_CLASS)
.unwrap();
let options = HealthOptions {
quick: false, ..Default::default()
};
let report = run_health(test_dir.path(), None, options).unwrap();
assert!(!report.quick_mode, "Report should indicate full mode");
assert!(
report.sub_results.contains_key("complexity"),
"complexity missing"
);
assert!(
report.sub_results.contains_key("cohesion"),
"cohesion missing"
);
assert!(report.sub_results.contains_key("dead"), "dead missing");
assert!(
report.sub_results.contains_key("metrics"),
"metrics missing"
);
assert!(
report.sub_results.contains_key("coupling"),
"coupling missing"
);
assert!(
report.sub_results.contains_key("similar"),
"similar missing"
);
assert!(
report.sub_results.get("complexity").unwrap().success,
"complexity should succeed"
);
assert!(
report.sub_results.get("cohesion").unwrap().success,
"cohesion should succeed"
);
let coupling = report.sub_results.get("coupling").unwrap();
let similar = report.sub_results.get("similar").unwrap();
assert!(
coupling
.error
.as_ref()
.is_none_or(|e| !e.contains("skipped")),
"coupling should not be skipped in full mode"
);
assert!(
similar
.error
.as_ref()
.is_none_or(|e| !e.contains("skipped")),
"similar should not be skipped in full mode"
);
}
#[test]
fn test_health_multi_language() {
use crate::quality::health::{run_health, HealthOptions};
use crate::types::Language;
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("code.py", PYTHON_SIMPLE_FUNCTION)
.unwrap();
test_dir.add_file("code.ts", TYPESCRIPT_SIMPLE).unwrap();
test_dir.add_file("code.go", GO_SIMPLE).unwrap();
test_dir.add_file("code.rs", RUST_SIMPLE).unwrap();
let options = HealthOptions::default();
let py_result = run_health(test_dir.path(), Some(Language::Python), options.clone());
assert!(py_result.is_ok(), "Python analysis should work");
let ts_result = run_health(test_dir.path(), Some(Language::TypeScript), options.clone());
assert!(ts_result.is_ok(), "TypeScript analysis should work");
let go_result = run_health(test_dir.path(), Some(Language::Go), options.clone());
assert!(go_result.is_ok(), "Go analysis should work");
let rs_result = run_health(test_dir.path(), Some(Language::Rust), options.clone());
assert!(rs_result.is_ok(), "Rust analysis should work");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_language_auto_detection() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("a.py", PYTHON_SIMPLE_FUNCTION).unwrap();
test_dir.add_file("b.py", PYTHON_COHESIVE_CLASS).unwrap();
test_dir
.add_file("c.py", PYTHON_MULTIPLE_FUNCTIONS)
.unwrap();
test_dir.add_file("d.ts", TYPESCRIPT_SIMPLE).unwrap();
todo!("Implement language auto-detection");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_single_file_input() {
let test_dir = TestDir::new().unwrap();
let _file_path = test_dir
.add_file("single.py", PYTHON_MULTIPLE_FUNCTIONS)
.unwrap();
todo!("Implement single file analysis");
}
}
#[cfg(test)]
mod output_format_tests {
use super::fixtures::*;
#[test]
#[ignore = "health module not yet implemented"]
fn test_json_output_schema() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("main.py", PYTHON_MULTIPLE_FUNCTIONS)
.unwrap();
todo!("Implement JSON output validation");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_text_output_dashboard() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("main.py", PYTHON_MULTIPLE_FUNCTIONS)
.unwrap();
todo!("Implement text output format");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_text_output_with_errors() {
todo!("Implement text output error display");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_detail_flag() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("main.py", PYTHON_MULTIPLE_FUNCTIONS)
.unwrap();
todo!("Implement --detail flag");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_invalid_detail_value() {
todo!("Implement --detail validation");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_skipped_sub_analysis_json() {
todo!("Implement skipped sub-analysis JSON format");
}
}
#[cfg(test)]
mod edge_case_tests {
use super::fixtures::*;
use crate::quality::health::{run_health, HealthOptions};
use crate::types::Language;
#[test]
fn test_health_empty_directory() {
let test_dir = TestDir::new().unwrap();
let options = HealthOptions::default();
let result = run_health(test_dir.path(), None, options);
assert!(result.is_err(), "Empty directory should return error");
}
#[test]
fn test_health_no_supported_files() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("readme.md", "# Readme").unwrap();
test_dir.add_file("config.json", "{}").unwrap();
let options = HealthOptions::default();
let result = run_health(test_dir.path(), None, options);
assert!(result.is_err(), "No supported files should return error");
}
#[test]
fn test_health_graceful_parse_error() {
let test_dir = TestDir::new().unwrap();
test_dir
.add_file("good.py", PYTHON_SIMPLE_FUNCTION)
.unwrap();
test_dir.add_file("bad.py", PYTHON_SYNTAX_ERROR).unwrap();
let options = HealthOptions::default();
let result = run_health(test_dir.path(), Some(Language::Python), options);
assert!(result.is_ok(), "Should handle parse errors gracefully");
let report = result.unwrap();
assert!(report.sub_results.contains_key("complexity"));
}
#[test]
#[ignore = "all-bad files case needs deeper analysis"]
fn test_all_files_parse_error() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("bad1.py", PYTHON_SYNTAX_ERROR).unwrap();
test_dir.add_file("bad2.py", "def broken(\n").unwrap();
let options = HealthOptions::default();
let result = run_health(test_dir.path(), Some(Language::Python), options);
assert!(
result.is_ok(),
"Should return report even with all parse errors"
);
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_comments_only_file() {
let test_dir = TestDir::new().unwrap();
test_dir.add_file("comments.py", COMMENTS_ONLY).unwrap();
todo!("Implement comments-only file handling");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_large_file_performance() {
let mut content = String::new();
for i in 0..1000 {
content.push_str(&format!("def func_{}():\n return {}\n\n", i, i));
}
let test_dir = TestDir::new().unwrap();
test_dir.add_file("large.py", &content).unwrap();
todo!("Implement large file handling");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_unicode_content() {
let content = r#"
def greet(name):
"""Say hello in multiple languages."""
return f"Hello/Hola/こんにちは/Привет {name}"
class Émoji:
"""Class with unicode name."""
def method_with_emoji_🎉(self):
return "🎉"
"#;
let test_dir = TestDir::new().unwrap();
test_dir.add_file("unicode.py", content).unwrap();
todo!("Implement unicode handling");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_symlink_handling() {
todo!("Implement symlink handling");
}
#[test]
fn test_path_not_found() {
use crate::quality::health::{run_health, HealthOptions};
use std::path::Path;
let options = HealthOptions::default();
let result = run_health(
Path::new("/nonexistent/path/that/does/not/exist"),
None,
options,
);
assert!(result.is_err(), "Nonexistent path should return error");
}
}
#[cfg(test)]
mod error_type_tests {
#[test]
#[ignore = "health module not yet implemented"]
fn test_health_error_variants() {
todo!("Implement HealthError enum");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_analysis_error_variants() {
todo!("Implement AnalysisError enum");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_error_messages() {
todo!("Implement error Display");
}
}
#[cfg(test)]
mod options_tests {
#[test]
#[ignore = "health module not yet implemented"]
fn test_health_options_defaults() {
todo!("Implement HealthOptions defaults");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_preset_strict() {
todo!("Implement ThresholdPreset::Strict");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_preset_relaxed() {
todo!("Implement ThresholdPreset::Relaxed");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_complexity_options() {
todo!("Implement ComplexityOptions");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_cohesion_options() {
todo!("Implement CohesionOptions");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_similarity_options() {
todo!("Implement SimilarityOptions");
}
}
#[cfg(test)]
mod incremental_tests {
#[test]
#[ignore = "health module not yet implemented"]
fn test_incremental_options() {
todo!("Implement IncrementalOptions");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_incremental_changed_files_only() {
todo!("Implement incremental analysis");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_incremental_cache_reuse() {
todo!("Implement cache reuse");
}
}
#[cfg(test)]
mod health_finding_tests {
#[test]
#[ignore = "health module not yet implemented"]
fn test_health_finding_structure() {
todo!("Implement HealthFinding struct");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_severity_counts() {
todo!("Implement severity counts");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_severity_complexity() {
todo!("Implement complexity severity assignment");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_severity_cohesion() {
todo!("Implement cohesion severity assignment");
}
}
#[cfg(test)]
mod parallel_tests {
#[test]
#[ignore = "health module not yet implemented"]
fn test_parallel_independent_analyzers() {
todo!("Implement parallel analyzer execution");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_parallel_file_processing() {
todo!("Implement parallel file processing");
}
#[test]
#[ignore = "health module not yet implemented"]
fn test_parallel_timing() {
todo!("Verify parallel timing");
}
}