use crate::adapters::analyzers::coupling::{CouplingAnalysis, CouplingMetrics};
use crate::adapters::analyzers::dry::fragments::{FragmentEntry, FragmentGroup};
use crate::adapters::analyzers::dry::match_patterns::{RepeatedMatchEntry, RepeatedMatchGroup};
use crate::adapters::analyzers::structural::{
StructuralAnalysis, StructuralWarning, StructuralWarningKind,
};
use crate::app::projection::{project_coupling, project_data, project_dry, project_srp};
use crate::app::secondary::SecondaryResults;
use crate::domain::findings::{CouplingFindingKind, DryFindingKind, SrpFindingKind};
use crate::findings::Dimension;
fn metric(name: &str, warning: bool, suppressed: bool) -> CouplingMetrics {
CouplingMetrics {
module_name: name.into(),
afferent: 1,
efferent: 9,
instability: 0.9,
incoming: vec![],
outgoing: vec![],
suppressed,
warning,
}
}
fn coupling_with(metrics: Vec<CouplingMetrics>) -> CouplingAnalysis {
CouplingAnalysis {
metrics,
cycles: vec![],
sdp_violations: vec![],
graph: Default::default(),
}
}
fn structural_warning(dim: Dimension, kind: StructuralWarningKind) -> StructuralAnalysis {
StructuralAnalysis {
warnings: vec![StructuralWarning {
file: "src/x.rs".into(),
line: 10,
name: "Foo".into(),
kind,
dimension: dim,
suppressed: false,
}],
}
}
#[test]
fn project_coupling_threshold_filter_is_warning_and_not_suppressed() {
let coupling = coupling_with(vec![
metric("live", true, false),
metric("quiet", false, false),
]);
let breaches = project_coupling(Some(&coupling), None)
.into_iter()
.filter(|f| matches!(f.kind, CouplingFindingKind::ThresholdExceeded))
.count();
assert_eq!(
breaches, 1,
"only the warning, unsuppressed metric breaches"
);
}
#[test]
fn project_coupling_takes_only_coupling_dimension_structural_warnings() {
let structural = structural_warning(
Dimension::Coupling,
StructuralWarningKind::DowncastEscapeHatch,
);
let findings = project_coupling(None, Some(&structural));
let s = findings
.iter()
.find(|f| matches!(f.kind, CouplingFindingKind::Structural))
.expect("structural coupling finding");
assert!(
s.common.rule_id.starts_with("coupling/structural/"),
"rule_id keyed by the Coupling arm, got {}",
s.common.rule_id
);
}
#[test]
fn project_srp_takes_only_srp_dimension_structural_warnings() {
let structural = structural_warning(Dimension::Srp, StructuralWarningKind::SelflessMethod);
let findings = project_srp(None, Some(&structural));
let s = findings
.iter()
.find(|f| matches!(f.kind, SrpFindingKind::Structural))
.expect("structural srp finding");
assert!(
s.common.rule_id.starts_with("srp/structural/"),
"rule_id keyed by the Srp arm, got {}",
s.common.rule_id
);
}
#[test]
fn project_data_emits_one_module_record_per_metric() {
let coupling = coupling_with(vec![metric("m", true, false)]);
let data = project_data(&[], Some(&coupling));
assert_eq!(data.modules.len(), 1, "one ModuleCouplingRecord per metric");
}
fn empty_secondary() -> SecondaryResults {
SecondaryResults {
coupling: None,
duplicates: vec![],
dead_code: vec![],
fragments: vec![],
boilerplate: vec![],
wildcard_warnings: vec![],
repeated_matches: vec![],
srp: None,
tq: None,
structural: None,
}
}
#[test]
fn project_dry_emits_fragment_and_repeated_match_findings() {
let mut secondary = empty_secondary();
secondary.fragments = vec![FragmentGroup {
entries: vec![
FragmentEntry {
function_name: "a".into(),
qualified_name: "m::a".into(),
file: "src/x.rs".into(),
start_line: 1,
end_line: 8,
},
FragmentEntry {
function_name: "b".into(),
qualified_name: "m::b".into(),
file: "src/x.rs".into(),
start_line: 20,
end_line: 27,
},
],
statement_count: 6,
suppressed: false,
}];
secondary.repeated_matches = vec![RepeatedMatchGroup {
enum_name: "E".into(),
entries: vec![
RepeatedMatchEntry {
file: "src/x.rs".into(),
line: 3,
function_name: "f".into(),
arm_count: 4,
},
RepeatedMatchEntry {
file: "src/x.rs".into(),
line: 40,
function_name: "g".into(),
arm_count: 4,
},
],
suppressed: false,
}];
let findings = project_dry(&secondary);
let fragments = findings
.iter()
.filter(|f| matches!(f.kind, DryFindingKind::Fragment))
.count();
let repeated = findings
.iter()
.filter(|f| matches!(f.kind, DryFindingKind::RepeatedMatch))
.count();
assert_eq!(fragments, 2, "one fragment finding per entry");
assert_eq!(repeated, 2, "one repeated-match finding per entry");
}