use super::metrics_types::ComplexityMetrics;
use super::thresholds::{ComplexityThresholds, GodObjectThresholds};
pub fn calculate_god_object_score(
method_count: usize,
field_count: usize,
responsibility_count: usize,
lines_of_code: usize,
thresholds: &GodObjectThresholds,
) -> f64 {
let method_factor = (method_count as f64 / thresholds.max_methods as f64).min(3.0);
let field_factor = (field_count as f64 / thresholds.max_fields as f64).min(3.0);
let responsibility_factor = (responsibility_count as f64 / 3.0).min(3.0);
let size_factor = (lines_of_code as f64 / thresholds.max_lines as f64).min(3.0);
let mut violation_count = 0;
if method_count > thresholds.max_methods {
violation_count += 1;
}
if field_count > thresholds.max_fields {
violation_count += 1;
}
if responsibility_count > thresholds.max_traits {
violation_count += 1;
}
if lines_of_code > thresholds.max_lines {
violation_count += 1;
}
let base_score = method_factor * field_factor * responsibility_factor * size_factor;
if violation_count > 0 {
let base_min_score = match violation_count {
1 => 30.0, 2 => 50.0, _ => 70.0, };
let score = base_score * 20.0 * (violation_count as f64);
score.max(base_min_score)
} else {
base_score * 10.0
}
}
pub fn calculate_god_object_score_weighted(
weighted_method_count: f64,
field_count: usize,
responsibility_count: usize,
lines_of_code: usize,
avg_complexity: f64,
thresholds: &GodObjectThresholds,
) -> f64 {
let method_factor = (weighted_method_count / thresholds.max_methods as f64).min(3.0);
let field_factor = (field_count as f64 / thresholds.max_fields as f64).min(3.0);
let responsibility_factor = (responsibility_count as f64 / 3.0).min(3.0);
let size_factor = (lines_of_code as f64 / thresholds.max_lines as f64).min(3.0);
let complexity_factor = if avg_complexity < 3.0 {
0.7 } else if avg_complexity > 10.0 {
1.5 } else {
1.0
};
let mut violation_count = 0;
if weighted_method_count > thresholds.max_methods as f64 {
violation_count += 1;
}
if field_count > thresholds.max_fields {
violation_count += 1;
}
if responsibility_count > thresholds.max_traits {
violation_count += 1;
}
if lines_of_code > thresholds.max_lines {
violation_count += 1;
}
let base_score = method_factor * field_factor * responsibility_factor * size_factor;
if violation_count > 0 {
let base_min_score = match violation_count {
1 => 30.0, 2 => 50.0, _ => 70.0, };
let score = base_score * 20.0 * complexity_factor * (violation_count as f64);
score.max(base_min_score)
} else {
base_score * 10.0 * complexity_factor
}
}
pub fn calculate_complexity_factor(
metrics: &ComplexityMetrics,
thresholds: &ComplexityThresholds,
) -> f64 {
if metrics.total_cyclomatic == 0 {
return 1.0; }
let avg_factor = (metrics.avg_cyclomatic / thresholds.target_avg_complexity).clamp(0.5, 2.0);
let max_factor =
(metrics.max_cyclomatic as f64 / thresholds.max_method_complexity as f64).clamp(0.5, 2.5);
let total_factor =
(metrics.total_cyclomatic as f64 / thresholds.target_total_complexity).clamp(0.5, 2.0);
let variance_factor = (metrics.complexity_variance / 5.0).clamp(0.8, 1.5);
let combined = avg_factor * 0.4 + max_factor * 0.3 + total_factor * 0.2 + variance_factor * 0.1;
combined.clamp(0.5, 3.0)
}
#[allow(clippy::too_many_arguments)]
pub fn calculate_god_object_score_with_complexity(
weighted_method_count: f64,
field_count: usize,
responsibility_count: usize,
lines_of_code: usize,
complexity_metrics: &ComplexityMetrics,
thresholds: &GodObjectThresholds,
complexity_thresholds: &ComplexityThresholds,
) -> f64 {
let method_factor = (weighted_method_count / thresholds.max_methods as f64).min(3.0);
let field_factor = (field_count as f64 / thresholds.max_fields as f64).min(3.0);
let responsibility_factor = (responsibility_count as f64 / 3.0).min(3.0);
let size_factor = (lines_of_code as f64 / thresholds.max_lines as f64).min(3.0);
let complexity_factor = calculate_complexity_factor(complexity_metrics, complexity_thresholds);
let adjusted_method_factor = method_factor * complexity_factor.sqrt();
let violation_count = count_violations_with_complexity(
weighted_method_count,
field_count,
responsibility_count,
lines_of_code,
complexity_metrics,
thresholds,
);
let base_score = adjusted_method_factor * field_factor * responsibility_factor * size_factor;
if violation_count > 0 {
let base_min_score = match violation_count {
1 => 30.0,
2 => 50.0,
_ => 70.0,
};
let score = base_score * 20.0 * (violation_count as f64);
score.max(base_min_score)
} else {
base_score * 10.0
}
}
fn count_violations_with_complexity(
weighted_method_count: f64,
field_count: usize,
responsibility_count: usize,
lines_of_code: usize,
complexity_metrics: &ComplexityMetrics,
thresholds: &GodObjectThresholds,
) -> usize {
let mut violations = 0;
if weighted_method_count > thresholds.max_methods as f64 {
violations += 1;
}
if field_count > thresholds.max_fields {
violations += 1;
}
if responsibility_count > thresholds.max_traits {
violations += 1;
}
if lines_of_code > thresholds.max_lines {
violations += 1;
}
if complexity_metrics.total_cyclomatic > thresholds.max_complexity {
violations += 1;
}
if complexity_metrics.max_cyclomatic > 25 {
violations += 1;
}
if complexity_metrics.avg_cyclomatic > 10.0 {
violations += 1;
}
violations
}
use super::classification_types::MethodSelfUsageBreakdown;
#[allow(clippy::too_many_arguments)]
pub fn calculate_god_object_score_with_self_usage(
method_breakdown: &MethodSelfUsageBreakdown,
field_count: usize,
responsibility_count: usize,
lines_of_code: usize,
complexity_metrics: &ComplexityMetrics,
thresholds: &GodObjectThresholds,
complexity_thresholds: &ComplexityThresholds,
) -> f64 {
let weighted_method_count = method_breakdown.weighted_count();
let base_score = calculate_god_object_score_with_complexity(
weighted_method_count,
field_count,
responsibility_count,
lines_of_code,
complexity_metrics,
thresholds,
complexity_thresholds,
);
let functional_factor = if method_breakdown.is_highly_pure() {
0.5 } else if method_breakdown.is_mostly_pure() {
0.7 } else {
1.0 };
base_score * functional_factor
}
pub fn calculate_effective_method_count(
method_breakdown: &MethodSelfUsageBreakdown,
avg_complexity: f64,
) -> f64 {
let complexity_factor = if avg_complexity < 3.0 {
0.8 } else if avg_complexity > 10.0 {
1.5 } else {
1.0
};
method_breakdown.weighted_count() * complexity_factor
}
use super::classification_types::FunctionalDecompositionMetrics;
#[allow(clippy::too_many_arguments)]
pub fn calculate_god_object_score_with_functional_bonus(
method_breakdown: &MethodSelfUsageBreakdown,
field_count: usize,
responsibility_count: usize,
lines_of_code: usize,
complexity_metrics: &ComplexityMetrics,
functional_metrics: &FunctionalDecompositionMetrics,
thresholds: &GodObjectThresholds,
complexity_thresholds: &ComplexityThresholds,
) -> f64 {
let base_score = calculate_god_object_score_with_self_usage(
method_breakdown,
field_count,
responsibility_count,
lines_of_code,
complexity_metrics,
thresholds,
complexity_thresholds,
);
base_score * functional_metrics.score_multiplier()
}
pub fn apply_functional_bonus(
base_score: f64,
functional_metrics: &FunctionalDecompositionMetrics,
) -> f64 {
base_score * functional_metrics.score_multiplier()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scoring_deterministic() {
let thresholds = GodObjectThresholds::default();
let score1 = calculate_god_object_score(20, 15, 5, 500, &thresholds);
let score2 = calculate_god_object_score(20, 15, 5, 500, &thresholds);
assert_eq!(score1, score2);
}
#[test]
fn test_weighted_scoring_deterministic() {
let thresholds = GodObjectThresholds::default();
let score1 = calculate_god_object_score_weighted(25.0, 15, 5, 500, 5.0, &thresholds);
let score2 = calculate_god_object_score_weighted(25.0, 15, 5, 500, 5.0, &thresholds);
assert_eq!(score1, score2);
}
#[test]
fn test_scoring_zero_methods() {
let thresholds = GodObjectThresholds::default();
let score = calculate_god_object_score(0, 0, 0, 0, &thresholds);
assert_eq!(score, 0.0);
}
#[test]
fn test_scoring_zero_responsibilities() {
let thresholds = GodObjectThresholds::default();
let score = calculate_god_object_score(10, 5, 0, 100, &thresholds);
assert_eq!(score, 0.0);
}
#[test]
fn test_scoring_threshold_boundary() {
let thresholds = GodObjectThresholds::default();
let score = calculate_god_object_score(
thresholds.max_methods,
thresholds.max_fields,
thresholds.max_traits,
thresholds.max_lines,
&thresholds,
);
assert!(score < 30.0); }
#[test]
fn test_scoring_single_violation() {
let thresholds = GodObjectThresholds::default();
let score = calculate_god_object_score(
thresholds.max_methods + 1,
thresholds.max_fields,
thresholds.max_traits,
thresholds.max_lines,
&thresholds,
);
assert!(score >= 30.0);
}
#[test]
fn test_scoring_multiple_violations() {
let thresholds = GodObjectThresholds::default();
let score = calculate_god_object_score(
thresholds.max_methods + 10,
thresholds.max_fields + 10,
thresholds.max_traits + 1,
thresholds.max_lines,
&thresholds,
);
assert!(score >= 70.0);
}
#[test]
fn test_weighted_vs_unweighted_consistency() {
let thresholds = GodObjectThresholds::default();
let method_count = 20;
let field_count = 15;
let resp_count = 5;
let loc = 500;
let unweighted =
calculate_god_object_score(method_count, field_count, resp_count, loc, &thresholds);
let weighted = calculate_god_object_score_weighted(
method_count as f64,
field_count,
resp_count,
loc,
5.0, &thresholds,
);
assert_eq!(unweighted, weighted);
}
#[test]
fn test_weighted_low_complexity_bonus() {
let thresholds = GodObjectThresholds::default();
let normal = calculate_god_object_score_weighted(20.0, 15, 5, 500, 5.0, &thresholds);
let low_complexity =
calculate_god_object_score_weighted(20.0, 15, 5, 500, 2.0, &thresholds);
assert!(low_complexity < normal);
}
#[test]
fn test_weighted_high_complexity_penalty() {
let thresholds = GodObjectThresholds::default();
let normal = calculate_god_object_score_weighted(20.0, 15, 5, 500, 5.0, &thresholds);
let high_complexity =
calculate_god_object_score_weighted(20.0, 15, 5, 500, 15.0, &thresholds);
assert!(high_complexity > normal);
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn score_never_negative(
method_count in 0..1000usize,
field_count in 0..200usize,
resp_count in 0..100usize,
loc in 0..10000usize
) {
let thresholds = GodObjectThresholds::default();
let score = calculate_god_object_score(
method_count,
field_count,
resp_count,
loc,
&thresholds
);
prop_assert!(score >= 0.0);
}
#[test]
fn weighted_score_never_negative(
weighted_count in 0.0..1000.0f64,
field_count in 0..200usize,
resp_count in 0..100usize,
loc in 0..10000usize,
avg_complexity in 1.0..30.0f64
) {
let thresholds = GodObjectThresholds::default();
let score = calculate_god_object_score_weighted(
weighted_count,
field_count,
resp_count,
loc,
avg_complexity,
&thresholds
);
prop_assert!(score >= 0.0);
}
#[test]
fn score_monotonic_in_methods(
base in 10..100usize,
delta in 1..50usize,
field_count in 5..50usize,
resp_count in 1..10usize,
loc in 100..1000usize
) {
let thresholds = GodObjectThresholds::default();
let score1 = calculate_god_object_score(base, field_count, resp_count, loc, &thresholds);
let score2 = calculate_god_object_score(base + delta, field_count, resp_count, loc, &thresholds);
prop_assert!(score2 >= score1);
}
#[test]
fn score_monotonic_in_fields(
method_count in 10..100usize,
base in 5..50usize,
delta in 1..20usize,
resp_count in 1..10usize,
loc in 100..1000usize
) {
let thresholds = GodObjectThresholds::default();
let score1 = calculate_god_object_score(method_count, base, resp_count, loc, &thresholds);
let score2 = calculate_god_object_score(method_count, base + delta, resp_count, loc, &thresholds);
prop_assert!(score2 >= score1);
}
#[test]
fn score_monotonic_in_responsibilities(
method_count in 10..100usize,
field_count in 5..50usize,
base in 1..10usize,
delta in 1..5usize,
loc in 100..1000usize
) {
let thresholds = GodObjectThresholds::default();
let score1 = calculate_god_object_score(method_count, field_count, base, loc, &thresholds);
let score2 = calculate_god_object_score(method_count, field_count, base + delta, loc, &thresholds);
prop_assert!(score2 >= score1);
}
#[test]
fn weighted_score_reasonable_bounds(
weighted_count in 1.0..500.0f64,
field_count in 1..100usize,
resp_count in 1..20usize,
loc in 100..5000usize,
avg_complexity in 1.0..20.0f64
) {
let thresholds = GodObjectThresholds::default();
let score = calculate_god_object_score_weighted(
weighted_count,
field_count,
resp_count,
loc,
avg_complexity,
&thresholds
);
prop_assert!(score.is_finite());
prop_assert!(score >= 0.0);
}
#[test]
fn complexity_factor_affects_score(
weighted_count in 20.0..100.0f64,
field_count in 10..50usize,
resp_count in 3..10usize,
loc in 500..2000usize
) {
let thresholds = GodObjectThresholds::default();
let low_complexity = calculate_god_object_score_weighted(
weighted_count,
field_count,
resp_count,
loc,
2.0, &thresholds
);
let high_complexity = calculate_god_object_score_weighted(
weighted_count,
field_count,
resp_count,
loc,
15.0, &thresholds
);
prop_assert!(high_complexity >= low_complexity,
"High complexity ({}) should be >= low complexity ({})",
high_complexity, low_complexity);
}
}
}
#[cfg(test)]
mod spec_211_tests {
use super::*;
#[test]
fn test_complexity_factor_low() {
let metrics = ComplexityMetrics {
avg_cyclomatic: 2.0,
max_cyclomatic: 5,
total_cyclomatic: 20,
complexity_variance: 1.0,
..Default::default()
};
let thresholds = ComplexityThresholds::default();
let factor = calculate_complexity_factor(&metrics, &thresholds);
assert!(
factor < 1.0,
"Low complexity should produce factor < 1.0, got {}",
factor
);
}
#[test]
fn test_complexity_factor_high() {
let metrics = ComplexityMetrics {
avg_cyclomatic: 20.0, max_cyclomatic: 50, total_cyclomatic: 300, complexity_variance: 15.0, ..Default::default()
};
let thresholds = ComplexityThresholds::default();
let factor = calculate_complexity_factor(&metrics, &thresholds);
assert!(
factor > 2.0,
"High complexity should produce factor > 2.0, got {}",
factor
);
}
#[test]
fn test_complexity_factor_neutral() {
let metrics = ComplexityMetrics {
avg_cyclomatic: 5.0, max_cyclomatic: 15, total_cyclomatic: 75, complexity_variance: 5.0,
..Default::default()
};
let thresholds = ComplexityThresholds::default();
let factor = calculate_complexity_factor(&metrics, &thresholds);
assert!(
(factor - 1.0).abs() < 0.15,
"Neutral complexity should produce factor ≈ 1.0, got {}",
factor
);
}
#[test]
fn test_complexity_factor_empty_metrics() {
let metrics = ComplexityMetrics::default();
let thresholds = ComplexityThresholds::default();
let factor = calculate_complexity_factor(&metrics, &thresholds);
assert!(
(factor - 1.0).abs() < f64::EPSILON,
"Empty metrics should produce factor = 1.0, got {}",
factor
);
}
#[test]
fn test_complexity_factor_clamped_low() {
let metrics = ComplexityMetrics {
avg_cyclomatic: 0.5,
max_cyclomatic: 1,
total_cyclomatic: 2,
complexity_variance: 0.1,
..Default::default()
};
let thresholds = ComplexityThresholds::default();
let factor = calculate_complexity_factor(&metrics, &thresholds);
assert!(
factor >= 0.5,
"Factor should be clamped to >= 0.5, got {}",
factor
);
}
#[test]
fn test_complexity_factor_clamped_high() {
let metrics = ComplexityMetrics {
avg_cyclomatic: 50.0,
max_cyclomatic: 100,
total_cyclomatic: 1000,
complexity_variance: 50.0,
..Default::default()
};
let thresholds = ComplexityThresholds::default();
let factor = calculate_complexity_factor(&metrics, &thresholds);
assert!(
factor <= 3.0,
"Factor should be clamped to <= 3.0, got {}",
factor
);
}
#[test]
fn test_complexity_factor_deterministic() {
let metrics = ComplexityMetrics {
avg_cyclomatic: 7.5,
max_cyclomatic: 20,
total_cyclomatic: 100,
complexity_variance: 4.0,
..Default::default()
};
let thresholds = ComplexityThresholds::default();
let factor1 = calculate_complexity_factor(&metrics, &thresholds);
let factor2 = calculate_complexity_factor(&metrics, &thresholds);
assert_eq!(factor1, factor2);
}
#[test]
fn test_score_with_complexity_simple_struct() {
let metrics = ComplexityMetrics {
avg_cyclomatic: 1.5,
max_cyclomatic: 3,
total_cyclomatic: 15,
..Default::default()
};
let score = calculate_god_object_score_with_complexity(
15.0,
5,
3,
200,
&metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert!(
score.is_finite() && score >= 0.0,
"Score should be valid, got {}",
score
);
}
#[test]
fn test_score_with_complexity_complex_struct() {
let metrics = ComplexityMetrics {
avg_cyclomatic: 12.0,
max_cyclomatic: 25,
total_cyclomatic: 180,
..Default::default()
};
let score = calculate_god_object_score_with_complexity(
15.0,
5,
3,
200,
&metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert!(
score.is_finite() && score >= 0.0,
"Score should be valid, got {}",
score
);
}
#[test]
fn test_complex_scores_higher_than_simple() {
let simple_metrics = ComplexityMetrics {
avg_cyclomatic: 1.5,
max_cyclomatic: 3,
total_cyclomatic: 15,
complexity_variance: 0.5,
..Default::default()
};
let complex_metrics = ComplexityMetrics {
avg_cyclomatic: 12.0,
max_cyclomatic: 25,
total_cyclomatic: 180,
complexity_variance: 8.0,
..Default::default()
};
let simple_score = calculate_god_object_score_with_complexity(
15.0,
10,
4,
500,
&simple_metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
let complex_score = calculate_god_object_score_with_complexity(
15.0,
10,
4,
500,
&complex_metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert!(
complex_score > simple_score,
"Complex struct ({}) should score higher than simple struct ({})",
complex_score,
simple_score
);
}
#[test]
fn test_one_complex_method_scores_higher_than_many_simple() {
let one_complex = ComplexityMetrics {
avg_cyclomatic: 50.0,
max_cyclomatic: 50,
total_cyclomatic: 50,
complexity_variance: 0.0,
..Default::default()
};
let many_simple = ComplexityMetrics {
avg_cyclomatic: 5.0,
max_cyclomatic: 5,
total_cyclomatic: 50, complexity_variance: 0.0,
..Default::default()
};
let one_complex_score = calculate_god_object_score_with_complexity(
1.0, 5,
2,
100,
&one_complex,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
let many_simple_score = calculate_god_object_score_with_complexity(
10.0, 5,
2,
100,
&many_simple,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert!(
one_complex_score > many_simple_score,
"One 50-complexity method ({}) should score higher than 10 5-complexity methods ({})",
one_complex_score,
many_simple_score
);
}
#[test]
fn test_high_max_triggers_violation() {
let metrics = ComplexityMetrics {
avg_cyclomatic: 5.0,
max_cyclomatic: 30, total_cyclomatic: 50,
..Default::default()
};
let thresholds = GodObjectThresholds::default();
let violations = count_violations_with_complexity(
10.0, 5, 3, 500, &metrics,
&thresholds,
);
assert!(
violations >= 1,
"max_cyclomatic > 25 should trigger a violation, got {} violations",
violations
);
}
#[test]
fn test_high_avg_triggers_violation() {
let metrics = ComplexityMetrics {
avg_cyclomatic: 12.0, max_cyclomatic: 15,
total_cyclomatic: 60,
..Default::default()
};
let thresholds = GodObjectThresholds::default();
let violations = count_violations_with_complexity(10.0, 5, 3, 500, &metrics, &thresholds);
assert!(
violations >= 1,
"avg_cyclomatic > 10.0 should trigger a violation, got {} violations",
violations
);
}
#[test]
fn test_score_deterministic() {
let metrics = ComplexityMetrics {
avg_cyclomatic: 8.0,
max_cyclomatic: 15,
total_cyclomatic: 120,
complexity_variance: 4.0,
..Default::default()
};
let score1 = calculate_god_object_score_with_complexity(
20.0,
12,
5,
800,
&metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
let score2 = calculate_god_object_score_with_complexity(
20.0,
12,
5,
800,
&metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert_eq!(score1, score2);
}
use proptest::prelude::*;
proptest! {
#[test]
fn complexity_factor_in_range(
avg in 0.5f64..30.0,
max in 1u32..100,
total in 1u32..500,
variance in 0.0f64..20.0
) {
let metrics = ComplexityMetrics {
avg_cyclomatic: avg,
max_cyclomatic: max,
total_cyclomatic: total,
complexity_variance: variance,
..Default::default()
};
let thresholds = ComplexityThresholds::default();
let factor = calculate_complexity_factor(&metrics, &thresholds);
prop_assert!((0.5..=3.0).contains(&factor),
"Factor {} out of range [0.5, 3.0]", factor);
}
#[test]
fn score_with_complexity_non_negative(
weighted_count in 1.0..100.0f64,
field_count in 1..50usize,
resp_count in 1..10usize,
loc in 100..2000usize,
avg in 1.0f64..20.0,
max in 1u32..50,
total in 1u32..300
) {
let metrics = ComplexityMetrics {
avg_cyclomatic: avg,
max_cyclomatic: max,
total_cyclomatic: total,
..Default::default()
};
let score = calculate_god_object_score_with_complexity(
weighted_count,
field_count,
resp_count,
loc,
&metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
prop_assert!(score >= 0.0 && score.is_finite(),
"Score {} is invalid", score);
}
#[test]
fn higher_complexity_means_higher_or_equal_score(
weighted_count in 10.0..50.0f64,
field_count in 5..20usize,
resp_count in 2..6usize,
loc in 300..1000usize,
low_avg in 1.0f64..5.0,
high_avg in 10.0f64..20.0
) {
let low_metrics = ComplexityMetrics {
avg_cyclomatic: low_avg,
max_cyclomatic: 5,
total_cyclomatic: 50,
..Default::default()
};
let high_metrics = ComplexityMetrics {
avg_cyclomatic: high_avg,
max_cyclomatic: 25,
total_cyclomatic: 200,
..Default::default()
};
let low_score = calculate_god_object_score_with_complexity(
weighted_count,
field_count,
resp_count,
loc,
&low_metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
let high_score = calculate_god_object_score_with_complexity(
weighted_count,
field_count,
resp_count,
loc,
&high_metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
prop_assert!(high_score >= low_score,
"High complexity score ({}) should be >= low complexity score ({})",
high_score, low_score);
}
}
}
#[cfg(test)]
mod spec_213_tests {
use super::*;
#[test]
fn test_method_self_usage_breakdown_from_classifications() {
use super::super::classification_types::MethodSelfUsage;
let classifications = vec![
MethodSelfUsage::InstanceMethod,
MethodSelfUsage::InstanceMethod,
MethodSelfUsage::InstanceMethod,
MethodSelfUsage::PureAssociated,
MethodSelfUsage::PureAssociated,
MethodSelfUsage::PureAssociated,
MethodSelfUsage::PureAssociated,
MethodSelfUsage::PureAssociated,
MethodSelfUsage::UnusedSelf,
];
let breakdown = MethodSelfUsageBreakdown::from_classifications(&classifications);
assert_eq!(breakdown.total_methods, 9);
assert_eq!(breakdown.instance_methods, 3);
assert_eq!(breakdown.pure_associated, 5);
assert_eq!(breakdown.unused_self, 1);
}
#[test]
fn test_method_self_usage_breakdown_weighted_count() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 24,
instance_methods: 3,
pure_associated: 20,
unused_self: 1,
};
let weighted = breakdown.weighted_count();
assert!(
(weighted - 7.3).abs() < 0.01,
"Expected 7.3, got {}",
weighted
);
}
#[test]
fn test_method_self_usage_breakdown_pure_helper_count() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 24,
instance_methods: 3,
pure_associated: 20,
unused_self: 1,
};
assert_eq!(breakdown.pure_helper_count(), 21);
}
#[test]
fn test_method_self_usage_breakdown_pure_ratio() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 24,
instance_methods: 3,
pure_associated: 20,
unused_self: 1,
};
let ratio = breakdown.pure_ratio();
assert!(
(ratio - 0.875).abs() < 0.001,
"Expected 0.875, got {}",
ratio
);
}
#[test]
fn test_method_self_usage_breakdown_is_mostly_pure() {
let mostly_pure = MethodSelfUsageBreakdown {
total_methods: 10,
instance_methods: 4,
pure_associated: 6,
unused_self: 0,
};
assert!(mostly_pure.is_mostly_pure());
let not_mostly_pure = MethodSelfUsageBreakdown {
total_methods: 10,
instance_methods: 6,
pure_associated: 4,
unused_self: 0,
};
assert!(!not_mostly_pure.is_mostly_pure());
}
#[test]
fn test_method_self_usage_breakdown_is_highly_pure() {
let highly_pure = MethodSelfUsageBreakdown {
total_methods: 10,
instance_methods: 2,
pure_associated: 8,
unused_self: 0,
};
assert!(highly_pure.is_highly_pure());
let not_highly_pure = MethodSelfUsageBreakdown {
total_methods: 10,
instance_methods: 4,
pure_associated: 6,
unused_self: 0,
};
assert!(!not_highly_pure.is_highly_pure());
assert!(not_highly_pure.is_mostly_pure()); }
#[test]
fn test_method_self_usage_breakdown_display() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 24,
instance_methods: 3,
pure_associated: 20,
unused_self: 1,
};
let display = format!("{}", breakdown);
assert_eq!(display, "24 (3 instance, 21 pure helpers)");
}
#[test]
fn test_method_self_usage_breakdown_empty() {
let breakdown = MethodSelfUsageBreakdown::default();
assert_eq!(breakdown.total_methods, 0);
assert_eq!(breakdown.weighted_count(), 0.0);
assert_eq!(breakdown.pure_ratio(), 0.0);
assert!(!breakdown.is_mostly_pure());
}
#[test]
fn test_calculate_god_object_score_with_self_usage_highly_pure() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 24,
instance_methods: 3,
pure_associated: 21,
unused_self: 0,
};
let metrics = ComplexityMetrics::default();
let score = calculate_god_object_score_with_self_usage(
&breakdown,
3, 1, 200, &metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert!(
score < 50.0,
"Highly pure struct should score < 50 (CRITICAL threshold), got {}",
score
);
}
#[test]
fn test_calculate_god_object_score_with_self_usage_mostly_pure() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 20,
instance_methods: 8,
pure_associated: 12,
unused_self: 0,
};
let metrics = ComplexityMetrics {
avg_cyclomatic: 5.0,
max_cyclomatic: 10,
total_cyclomatic: 100,
..Default::default()
};
let score = calculate_god_object_score_with_self_usage(
&breakdown,
5, 2, 300, &metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert!(
score.is_finite() && score >= 0.0,
"Score should be valid, got {}",
score
);
}
#[test]
fn test_calculate_god_object_score_with_self_usage_instance_heavy() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 20,
instance_methods: 18,
pure_associated: 2,
unused_self: 0,
};
let metrics = ComplexityMetrics {
avg_cyclomatic: 8.0,
max_cyclomatic: 15,
total_cyclomatic: 160,
..Default::default()
};
let score = calculate_god_object_score_with_self_usage(
&breakdown,
10, 5, 500, &metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert!(
score.is_finite() && score > 0.0,
"Score should be valid and positive, got {}",
score
);
}
#[test]
fn test_highly_pure_scores_lower_than_instance_heavy() {
let metrics = ComplexityMetrics::default();
let highly_pure = MethodSelfUsageBreakdown {
total_methods: 20,
instance_methods: 3,
pure_associated: 17,
unused_self: 0,
};
let instance_heavy = MethodSelfUsageBreakdown {
total_methods: 20,
instance_methods: 17,
pure_associated: 3,
unused_self: 0,
};
let pure_score = calculate_god_object_score_with_self_usage(
&highly_pure,
5,
3,
300,
&metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
let instance_score = calculate_god_object_score_with_self_usage(
&instance_heavy,
5,
3,
300,
&metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert!(
pure_score < instance_score,
"Highly pure struct ({}) should score LOWER than instance heavy ({})",
pure_score,
instance_score
);
}
#[test]
fn test_calculate_effective_method_count_low_complexity() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 10,
instance_methods: 5,
pure_associated: 5,
unused_self: 0,
};
let effective = calculate_effective_method_count(&breakdown, 2.0);
assert!(
(effective - 4.8).abs() < 0.01,
"Expected 4.8, got {}",
effective
);
}
#[test]
fn test_calculate_effective_method_count_high_complexity() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 10,
instance_methods: 5,
pure_associated: 5,
unused_self: 0,
};
let effective = calculate_effective_method_count(&breakdown, 15.0);
assert!(
(effective - 9.0).abs() < 0.01,
"Expected 9.0, got {}",
effective
);
}
#[test]
fn test_calculate_effective_method_count_normal_complexity() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 10,
instance_methods: 5,
pure_associated: 5,
unused_self: 0,
};
let effective = calculate_effective_method_count(&breakdown, 5.0);
assert!(
(effective - 6.0).abs() < 0.01,
"Expected 6.0, got {}",
effective
);
}
#[test]
fn test_score_with_self_usage_deterministic() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 15,
instance_methods: 5,
pure_associated: 10,
unused_self: 0,
};
let metrics = ComplexityMetrics {
avg_cyclomatic: 6.0,
max_cyclomatic: 12,
total_cyclomatic: 90,
..Default::default()
};
let score1 = calculate_god_object_score_with_self_usage(
&breakdown,
8,
3,
400,
&metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
let score2 = calculate_god_object_score_with_self_usage(
&breakdown,
8,
3,
400,
&metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert_eq!(score1, score2);
}
use proptest::prelude::*;
proptest! {
#[test]
fn prop_breakdown_weighted_count_non_negative(
instance in 0usize..50,
pure in 0usize..50,
unused in 0usize..20
) {
let breakdown = MethodSelfUsageBreakdown {
total_methods: instance + pure + unused,
instance_methods: instance,
pure_associated: pure,
unused_self: unused,
};
prop_assert!(breakdown.weighted_count() >= 0.0);
}
#[test]
fn prop_pure_ratio_bounded(
instance in 0usize..50,
pure in 0usize..50,
unused in 0usize..20
) {
let breakdown = MethodSelfUsageBreakdown {
total_methods: instance + pure + unused,
instance_methods: instance,
pure_associated: pure,
unused_self: unused,
};
let ratio = breakdown.pure_ratio();
prop_assert!((0.0..=1.0).contains(&ratio),
"Pure ratio {} out of bounds", ratio);
}
#[test]
fn prop_effective_count_non_negative(
instance in 0usize..50,
pure in 0usize..50,
unused in 0usize..20,
complexity in 1.0f64..25.0
) {
let breakdown = MethodSelfUsageBreakdown {
total_methods: instance + pure + unused,
instance_methods: instance,
pure_associated: pure,
unused_self: unused,
};
let effective = calculate_effective_method_count(&breakdown, complexity);
prop_assert!(effective >= 0.0 && effective.is_finite(),
"Effective count {} invalid", effective);
}
#[test]
fn prop_score_with_self_usage_finite(
instance in 1usize..30,
pure in 0usize..30,
fields in 1usize..20,
resp in 1usize..10,
loc in 50usize..1000
) {
let breakdown = MethodSelfUsageBreakdown {
total_methods: instance + pure,
instance_methods: instance,
pure_associated: pure,
unused_self: 0,
};
let metrics = ComplexityMetrics::default();
let score = calculate_god_object_score_with_self_usage(
&breakdown,
fields,
resp,
loc,
&metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
prop_assert!(score.is_finite() && score >= 0.0,
"Score {} invalid", score);
}
}
}
#[cfg(test)]
mod spec_215_tests {
use super::*;
#[test]
fn test_functional_bonus_strong_functional_design() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 24,
instance_methods: 3,
pure_associated: 21,
unused_self: 0,
};
let functional_metrics = FunctionalDecompositionMetrics {
pure_method_ratio: 0.875,
orchestrator_count: 3,
pure_helper_count: 21,
avg_pure_method_loc: 8.0,
composition_patterns: vec![],
functional_score: 0.80, };
let metrics = ComplexityMetrics::default();
let score = calculate_god_object_score_with_functional_bonus(
&breakdown,
3, 1, 200, &metrics,
&functional_metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert!(
score < 30.0,
"Strong functional design should score low, got {}",
score
);
}
#[test]
fn test_functional_bonus_moderate_functional_style() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 20,
instance_methods: 8,
pure_associated: 12,
unused_self: 0,
};
let functional_metrics = FunctionalDecompositionMetrics {
pure_method_ratio: 0.60,
orchestrator_count: 5,
pure_helper_count: 12,
avg_pure_method_loc: 10.0,
composition_patterns: vec![],
functional_score: 0.55, };
let metrics = ComplexityMetrics::default();
let score = calculate_god_object_score_with_functional_bonus(
&breakdown,
5,
2,
300,
&metrics,
&functional_metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert!(score.is_finite() && score >= 0.0);
}
#[test]
fn test_functional_bonus_no_functional_pattern() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 20,
instance_methods: 18,
pure_associated: 2,
unused_self: 0,
};
let functional_metrics = FunctionalDecompositionMetrics {
pure_method_ratio: 0.10,
orchestrator_count: 10,
pure_helper_count: 2,
avg_pure_method_loc: 20.0,
composition_patterns: vec![],
functional_score: 0.20, };
let metrics = ComplexityMetrics {
avg_cyclomatic: 8.0,
max_cyclomatic: 15,
total_cyclomatic: 160,
..Default::default()
};
let score_with_bonus = calculate_god_object_score_with_functional_bonus(
&breakdown,
10,
5,
500,
&metrics,
&functional_metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
let score_base = calculate_god_object_score_with_self_usage(
&breakdown,
10,
5,
500,
&metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert!(
(score_with_bonus - score_base).abs() < 0.01,
"No functional pattern should not affect score. Base: {}, With bonus: {}",
score_base,
score_with_bonus
);
}
#[test]
fn test_functional_bonus_deterministic() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 15,
instance_methods: 5,
pure_associated: 10,
unused_self: 0,
};
let functional_metrics = FunctionalDecompositionMetrics {
pure_method_ratio: 0.67,
orchestrator_count: 3,
pure_helper_count: 10,
avg_pure_method_loc: 6.0,
composition_patterns: vec![],
functional_score: 0.72,
};
let metrics = ComplexityMetrics::default();
let score1 = calculate_god_object_score_with_functional_bonus(
&breakdown,
5,
2,
200,
&metrics,
&functional_metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
let score2 = calculate_god_object_score_with_functional_bonus(
&breakdown,
5,
2,
200,
&metrics,
&functional_metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert_eq!(score1, score2, "Scoring should be deterministic");
}
#[test]
fn test_apply_functional_bonus_simple() {
let high_functional = FunctionalDecompositionMetrics {
functional_score: 0.80,
..Default::default()
};
let moderate_functional = FunctionalDecompositionMetrics {
functional_score: 0.55,
..Default::default()
};
let weak_functional = FunctionalDecompositionMetrics {
functional_score: 0.35,
..Default::default()
};
let no_functional = FunctionalDecompositionMetrics {
functional_score: 0.20,
..Default::default()
};
let base_score = 100.0;
let high_result = apply_functional_bonus(base_score, &high_functional);
assert!(
(high_result - 30.0).abs() < 0.01,
"High functional should be 30.0, got {}",
high_result
);
let moderate_result = apply_functional_bonus(base_score, &moderate_functional);
assert!(
(moderate_result - 50.0).abs() < 0.01,
"Moderate functional should be 50.0, got {}",
moderate_result
);
let weak_result = apply_functional_bonus(base_score, &weak_functional);
assert!(
(weak_result - 75.0).abs() < 0.01,
"Weak functional should be 75.0, got {}",
weak_result
);
let no_result = apply_functional_bonus(base_score, &no_functional);
assert!(
(no_result - 100.0).abs() < 0.01,
"No functional should be 100.0, got {}",
no_result
);
}
#[test]
fn test_functional_bonus_reduces_high_scores() {
let breakdown = MethodSelfUsageBreakdown {
total_methods: 30,
instance_methods: 5,
pure_associated: 25,
unused_self: 0,
};
let functional_metrics = FunctionalDecompositionMetrics {
pure_method_ratio: 0.833,
orchestrator_count: 2,
pure_helper_count: 25,
avg_pure_method_loc: 5.0,
composition_patterns: vec![],
functional_score: 0.85, };
let metrics = ComplexityMetrics {
avg_cyclomatic: 3.0,
max_cyclomatic: 8,
total_cyclomatic: 90,
..Default::default()
};
let score = calculate_god_object_score_with_functional_bonus(
&breakdown,
5, 3, 400, &metrics,
&functional_metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
assert!(
score < 50.0,
"Strong functional code should not be CRITICAL, got {}",
score
);
}
use proptest::prelude::*;
proptest! {
#[test]
fn prop_functional_bonus_never_increases_score(
instance in 1usize..20,
pure in 0usize..30,
fields in 1usize..20,
resp in 1usize..10,
loc in 50usize..1000,
functional_score in 0.0f64..1.0
) {
let breakdown = MethodSelfUsageBreakdown {
total_methods: instance + pure,
instance_methods: instance,
pure_associated: pure,
unused_self: 0,
};
let functional_metrics = FunctionalDecompositionMetrics {
functional_score,
..Default::default()
};
let metrics = ComplexityMetrics::default();
let score_with_bonus = calculate_god_object_score_with_functional_bonus(
&breakdown,
fields,
resp,
loc,
&metrics,
&functional_metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
let score_base = calculate_god_object_score_with_self_usage(
&breakdown,
fields,
resp,
loc,
&metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
prop_assert!(score_with_bonus <= score_base + 0.001,
"Functional bonus should not increase score. Base: {}, With bonus: {}",
score_base, score_with_bonus);
}
#[test]
fn prop_functional_bonus_finite(
instance in 1usize..20,
pure in 0usize..30,
fields in 1usize..20,
resp in 1usize..10,
loc in 50usize..1000,
functional_score in 0.0f64..1.0
) {
let breakdown = MethodSelfUsageBreakdown {
total_methods: instance + pure,
instance_methods: instance,
pure_associated: pure,
unused_self: 0,
};
let functional_metrics = FunctionalDecompositionMetrics {
functional_score,
..Default::default()
};
let metrics = ComplexityMetrics::default();
let score = calculate_god_object_score_with_functional_bonus(
&breakdown,
fields,
resp,
loc,
&metrics,
&functional_metrics,
&GodObjectThresholds::default(),
&ComplexityThresholds::default(),
);
prop_assert!(score.is_finite() && score >= 0.0,
"Score {} is invalid", score);
}
}
}