use super::*;
#[test]
fn empty_contains_nothing() {
let set = MetricSet::empty();
assert!(!set.contains(Metric::Loc));
assert!(!set.contains(Metric::Halstead));
assert!(!set.contains(Metric::Mi));
}
#[test]
fn all_contains_every_variant() {
let set = MetricSet::all();
for m in [
Metric::Cognitive,
Metric::Cyclomatic,
Metric::Halstead,
Metric::Loc,
Metric::Nom,
Metric::Tokens,
Metric::Nargs,
Metric::Nexits,
Metric::Abc,
Metric::Npm,
Metric::Npa,
Metric::Mi,
Metric::Wmc,
] {
assert!(set.contains(m), "MetricSet::all() must contain {m}");
}
}
#[test]
fn with_dependencies_pulls_in_mi_inputs() {
let set = MetricSet::from_slice_with_deps(&[Metric::Mi]);
assert!(set.contains(Metric::Mi));
assert!(set.contains(Metric::Loc), "Mi depends on Loc");
assert!(set.contains(Metric::Cyclomatic), "Mi depends on Cyclomatic");
assert!(set.contains(Metric::Halstead), "Mi depends on Halstead");
assert!(!set.contains(Metric::Abc));
assert!(!set.contains(Metric::Tokens));
}
#[test]
fn with_dependencies_pulls_in_wmc_inputs() {
let set = MetricSet::from_slice_with_deps(&[Metric::Wmc]);
assert!(set.contains(Metric::Wmc));
assert!(
set.contains(Metric::Cyclomatic),
"Wmc depends on Cyclomatic"
);
assert!(set.contains(Metric::Nom), "Wmc depends on Nom");
}
#[test]
fn with_dependencies_pulls_in_nom_for_averaging_metrics() {
for m in [Metric::Cognitive, Metric::Nexits, Metric::Nargs] {
let set = MetricSet::from_slice_with_deps(&[m]);
assert!(set.contains(m));
assert!(
set.contains(Metric::Nom),
"{m:?} depends on Nom for its per-function average divisor (#428)"
);
}
}
#[test]
fn closure_is_idempotent_for_mixed_input() {
let a = MetricSet::from_slice_with_deps(&[Metric::Mi, Metric::Loc]);
let b = MetricSet::from_slice_with_deps(&[Metric::Mi]);
assert_eq!(a, b);
}
#[test]
fn closure_handles_duplicate_input() {
let set = MetricSet::from_slice_with_deps(&[Metric::Mi, Metric::Mi, Metric::Mi]);
assert_eq!(set, MetricSet::from_slice_with_deps(&[Metric::Mi]));
}
#[test]
fn empty_slice_yields_empty_set() {
assert_eq!(MetricSet::from_slice_with_deps(&[]), MetricSet::empty());
}
#[test]
fn resolved_closes_unresolved_set() {
let unresolved = MetricSet::empty().with(Metric::Mi);
assert!(unresolved.contains(Metric::Mi));
assert!(
!unresolved.contains(Metric::Loc),
"test premise: the verbatim set must be missing Mi's deps"
);
let resolved = unresolved.resolved();
assert!(resolved.contains(Metric::Mi));
assert!(resolved.contains(Metric::Loc), "Mi depends on Loc");
assert!(
resolved.contains(Metric::Cyclomatic),
"Mi depends on Cyclomatic"
);
assert!(
resolved.contains(Metric::Halstead),
"Mi depends on Halstead"
);
assert!(!resolved.contains(Metric::Abc));
assert!(!resolved.contains(Metric::Tokens));
assert_eq!(resolved, MetricSet::from_slice_with_deps(&[Metric::Mi]));
}
#[test]
fn resolved_is_idempotent() {
let once = MetricSet::empty().with(Metric::Mi).resolved();
assert_eq!(once, once.resolved(), "resolved() must be idempotent");
assert_eq!(MetricSet::all().resolved(), MetricSet::all());
assert_eq!(MetricSet::empty().resolved(), MetricSet::empty());
}
const ALL_VARIANTS: &[Metric] = &[
Metric::Cognitive,
Metric::Cyclomatic,
Metric::Halstead,
Metric::Loc,
Metric::Nom,
Metric::Tokens,
Metric::Nargs,
Metric::Nexits,
Metric::Abc,
Metric::Npm,
Metric::Npa,
Metric::Mi,
Metric::Wmc,
];
#[allow(dead_code)]
fn _all_variants_exhaustive_guard(m: Metric) {
match m {
Metric::Cognitive
| Metric::Cyclomatic
| Metric::Halstead
| Metric::Loc
| Metric::Nom
| Metric::Tokens
| Metric::Nargs
| Metric::Nexits
| Metric::Abc
| Metric::Npm
| Metric::Npa
| Metric::Mi
| Metric::Wmc => (),
}
}
#[test]
fn from_str_round_trips_every_variant_display_name() {
for &m in ALL_VARIANTS {
let parsed: Metric = m
.to_string()
.parse()
.unwrap_or_else(|e| panic!("Display->FromStr round-trip failed for {m}: {e}"));
assert_eq!(parsed, m, "round-trip mismatch for {m}");
}
}
#[test]
fn from_str_rejects_retired_exit_alias() {
assert_eq!("nexits".parse::<Metric>().unwrap(), Metric::Nexits);
assert!("exit".parse::<Metric>().is_err());
}
#[test]
fn nexits_canonical_spelling_is_consistent() {
assert_eq!(Metric::Nexits.to_string(), "nexits");
assert!(Metric::NAMES.contains(&"nexits"));
assert!(!Metric::NAMES.contains(&"exit"));
}
#[test]
fn serde_uses_canonical_display_spelling() {
assert_eq!(serde_json::to_string(&Metric::Nargs).unwrap(), "\"nargs\"",);
assert_eq!(
serde_json::to_string(&Metric::Nexits).unwrap(),
"\"nexits\"",
);
assert_eq!(
serde_json::from_str::<Metric>("\"nargs\"").unwrap(),
Metric::Nargs,
);
assert_eq!(
serde_json::from_str::<Metric>("\"nexits\"").unwrap(),
Metric::Nexits,
);
}
#[test]
fn serde_round_trips_every_variant() {
for &m in ALL_VARIANTS {
let json = serde_json::to_string(&m).unwrap();
let back: Metric = serde_json::from_str(&json).unwrap();
assert_eq!(back, m, "serde round-trip mismatch for {m}");
}
}
#[test]
fn serde_round_trips_every_variant_through_non_borrowing_format() {
for &m in ALL_VARIANTS {
let mut buf = Vec::new();
ciborium::into_writer(&m, &mut buf).unwrap();
let back: Metric = ciborium::from_reader(buf.as_slice()).unwrap();
assert_eq!(back, m, "CBOR round-trip mismatch for {m}");
}
}
#[test]
fn deserialize_rejects_retired_exit_alias() {
assert!(serde_json::from_str::<Metric>("\"exit\"").is_err());
}
#[test]
fn deserialize_rejects_unknown_naming_the_input() {
let err = serde_json::from_str::<Metric>("\"no_such_metric\"").unwrap_err();
assert!(
err.to_string().contains("no_such_metric"),
"serde error must name the offending input; got: {err}",
);
}
#[test]
fn btree_set_iteration_is_declaration_order() {
use std::collections::BTreeSet;
let set: BTreeSet<Metric> =
BTreeSet::from([Metric::Wmc, Metric::Cognitive, Metric::Nexits, Metric::Abc]);
let ordered: Vec<Metric> = set.into_iter().collect();
assert_eq!(
ordered,
vec![Metric::Cognitive, Metric::Nexits, Metric::Abc, Metric::Wmc],
);
}
#[test]
fn suppressible_excludes_tokens_only() {
let suppressible: Vec<Metric> = Metric::suppressible().collect();
assert!(
!suppressible.contains(&Metric::Tokens),
"tokens has no threshold and must not be suppressible",
);
for &m in ALL_VARIANTS {
if m == Metric::Tokens {
continue;
}
assert!(suppressible.contains(&m), "{m} must be suppressible");
}
assert_eq!(suppressible.len(), ALL_VARIANTS.len() - 1);
}
#[test]
fn from_str_rejects_uppercase() {
let err = "Loc".parse::<Metric>().unwrap_err();
assert_eq!(err.to_string(), "unknown metric: Loc");
}
#[test]
fn names_table_parses_to_every_variant() {
use std::collections::HashSet;
let mut seen: HashSet<Metric> = HashSet::new();
for name in Metric::NAMES {
let parsed = name
.parse::<Metric>()
.unwrap_or_else(|_| panic!("Metric::NAMES contains {name:?} but FromStr rejects it"));
seen.insert(parsed);
}
for &m in ALL_VARIANTS {
assert!(
seen.contains(&m),
"Metric::{m:?} is not represented in Metric::NAMES; \
add the canonical spelling to the table",
);
}
}
#[test]
fn names_table_is_alphabetised() {
let mut sorted: Vec<&str> = Metric::NAMES.to_vec();
sorted.sort_unstable();
assert_eq!(
Metric::NAMES,
sorted.as_slice(),
"Metric::NAMES must stay alphabetised",
);
}
#[test]
fn with_primitive_does_not_resolve_dependencies() {
let resolved = MetricSet::from_slice_with_deps(&[Metric::Mi]);
assert!(resolved.contains(Metric::Mi));
assert!(resolved.contains(Metric::Loc));
assert!(resolved.contains(Metric::Cyclomatic));
assert!(resolved.contains(Metric::Halstead));
let bare = MetricSet::empty().with(Metric::Mi);
assert!(bare.contains(Metric::Mi));
assert!(!bare.contains(Metric::Loc), "with(Mi) must NOT pull Loc");
assert!(
!bare.contains(Metric::Cyclomatic),
"with(Mi) must NOT pull Cyclomatic",
);
assert!(
!bare.contains(Metric::Halstead),
"with(Mi) must NOT pull Halstead",
);
}
#[test]
fn with_metric_set_resolves_dependencies() {
let unresolved = MetricSet::empty().with(Metric::Mi);
assert!(
!unresolved.contains(Metric::Loc),
"test premise: the verbatim set must omit Mi's deps",
);
let opts = crate::MetricsOptions::default().with_metric_set(unresolved);
assert!(opts.metrics.contains(Metric::Mi));
assert!(
opts.metrics.contains(Metric::Loc),
"with_metric_set must pull Loc (Mi dependency)",
);
assert!(
opts.metrics.contains(Metric::Cyclomatic),
"with_metric_set must pull Cyclomatic (Mi dependency)",
);
assert!(
opts.metrics.contains(Metric::Halstead),
"with_metric_set must pull Halstead (Mi dependency)",
);
}
#[test]
fn from_str_rejects_unknown_name() {
let err = "bogus".parse::<Metric>().unwrap_err();
assert_eq!(err.to_string(), "unknown metric: bogus");
assert_eq!(err.input(), "bogus");
}
#[test]
fn distinct_bits_per_variant() {
let mut seen: u32 = 0;
for &m in ALL_VARIANTS {
let bit = m.bit();
assert_ne!(bit, 0, "bit() must be non-zero for {m}");
assert_eq!(seen & bit, 0, "duplicate bit for {m}: {bit:#b}");
seen |= bit;
}
assert_eq!(seen, MetricSet::ALL_BITS);
}
#[test]
fn all_variants_round_trip_through_all_contains() {
let set = MetricSet::all();
for &m in ALL_VARIANTS {
assert!(
set.contains(m),
"MetricSet::all() must contain {m}; \
did a new variant land without updating ALL_BITS?",
);
}
}
#[test]
fn storage_width_covers_every_variant() {
const STORAGE_BITS: usize = u32::BITS as usize;
assert!(
ALL_VARIANTS.len() <= STORAGE_BITS,
"MetricSet storage exhausted: {} variants > {STORAGE_BITS}-bit storage; widen MetricSet",
ALL_VARIANTS.len(),
);
}