use pedant_graph::{CachedCodeGraph, CodeGraph, GraphCache, GraphCacheStats, GraphNodeKind};
use super::cache_fixture::{Pair, admitted};
pub const SOURCE_PROJECTION_HITS: usize = 0;
pub const SOURCE_PROJECTION_MISSES: usize = 1;
pub const SOURCE_PROJECTION_EVICTIONS: usize = 2;
pub const EXACT_GRAPH_HITS: usize = 3;
pub const EXACT_GRAPH_MISSES: usize = 4;
pub const EXACT_GRAPH_EVICTIONS: usize = 5;
pub const SELECTED_INDEX_HITS: usize = 6;
pub const SELECTED_INDEX_MISSES: usize = 7;
pub const SELECTED_INDEX_EVICTIONS: usize = 8;
pub const DERIVED_PRODUCT_HITS: usize = 9;
pub const DERIVED_PRODUCT_MISSES: usize = 10;
pub const DERIVED_PRODUCT_EVICTIONS: usize = 11;
pub const COUNTERS: usize = 12;
pub const COUNTER_NAMES: [&str; COUNTERS] = [
"source_projection_hits",
"source_projection_misses",
"source_projection_evictions",
"exact_graph_hits",
"exact_graph_misses",
"exact_graph_evictions",
"selected_index_hits",
"selected_index_misses",
"selected_index_evictions",
"derived_product_hits",
"derived_product_misses",
"derived_product_evictions",
];
pub fn fragments_of(graph: &CodeGraph) -> u64 {
let counted = graph
.nodes()
.iter()
.filter(|node| *node.kind() == GraphNodeKind::File)
.count();
u64::try_from(counted).expect("a graph states fewer file nodes than u64 can count")
}
pub fn counters(stats: GraphCacheStats) -> [u64; COUNTERS] {
[
stats.source_projection_hits(),
stats.source_projection_misses(),
stats.source_projection_evictions(),
stats.exact_graph_hits(),
stats.exact_graph_misses(),
stats.exact_graph_evictions(),
stats.selected_index_hits(),
stats.selected_index_misses(),
stats.selected_index_evictions(),
stats.derived_product_hits(),
stats.derived_product_misses(),
stats.derived_product_evictions(),
]
}
pub fn assert_delta(
before: GraphCacheStats,
after: GraphCacheStats,
stated: &[(usize, u64)],
subject: &str,
) {
let mut named = [None; COUNTERS];
for (counter, amount) in stated {
assert!(
named[*counter].replace(*amount).is_none(),
"{subject}: {} is stated twice",
COUNTER_NAMES[*counter]
);
}
let expected = named.map(|amount| amount.unwrap_or(0));
let (before, after) = (counters(before), counters(after));
let observed: [u64; COUNTERS] = std::array::from_fn(|at| after[at].saturating_sub(before[at]));
assert_eq!(
observed, expected,
"{subject}: counters must read {expected:?}, over {COUNTER_NAMES:?}"
);
assert!(
(0..COUNTERS).all(|at| after[at] >= before[at]),
"{subject}: no counter may fall"
);
}
pub fn assert_miss_delta(
before: GraphCacheStats,
after: GraphCacheStats,
stated: &[(usize, u64)],
built: &CodeGraph,
subject: &str,
) {
let mut expected = stated.to_vec();
expected.push((EXACT_GRAPH_MISSES, 1));
expected.push((SOURCE_PROJECTION_MISSES, fragments_of(built)));
assert_delta(before, after, &expected, subject);
}
#[derive(Clone, Copy)]
pub struct Classification {
hits: u64,
misses: u64,
evictions: u64,
}
impl Classification {
pub fn retained(hits: u64, misses: u64) -> Self {
Self {
hits,
misses,
evictions: 0,
}
}
pub fn bounded(hits: u64, misses: u64, evictions: u64) -> Self {
Self {
hits,
misses,
evictions,
}
}
fn examined(self) -> u64 {
self.hits.saturating_add(self.misses)
}
}
pub fn assert_build_classifies(
cache: &mut GraphCache,
stated: Pair<'_>,
expected: Classification,
subject: &str,
) -> CachedCodeGraph {
let before = cache.stats();
let built = admitted(cache, stated, subject);
assert_delta(
before,
cache.stats(),
&[
(EXACT_GRAPH_MISSES, 1),
(SOURCE_PROJECTION_HITS, expected.hits),
(SOURCE_PROJECTION_MISSES, expected.misses),
(SOURCE_PROJECTION_EVICTIONS, expected.evictions),
],
subject,
);
assert_eq!(
expected.examined(),
fragments_of(built.graph()),
"{subject}: every current fragment is classified exactly once"
);
built
}
pub fn assert_build_examines_every_fragment(
cache: &mut GraphCache,
stated: Pair<'_>,
subject: &str,
) -> CachedCodeGraph {
let before = counters(cache.stats());
let built = admitted(cache, stated, subject);
let after = counters(cache.stats());
let examined = after[SOURCE_PROJECTION_HITS].saturating_sub(before[SOURCE_PROJECTION_HITS])
+ after[SOURCE_PROJECTION_MISSES].saturating_sub(before[SOURCE_PROJECTION_MISSES]);
assert_eq!(
after[EXACT_GRAPH_MISSES].saturating_sub(before[EXACT_GRAPH_MISSES]),
1,
"{subject}: a zero exact-graph ceiling builds every graph"
);
let fragments = fragments_of(built.graph());
assert!(
fragments > 0,
"{subject}: the graph states at least one fragment to classify"
);
assert_eq!(
examined, fragments,
"{subject}: every current fragment is classified exactly once"
);
built
}