use pedant_graph::{
CachedCodeGraph, GraphAnalysis, GraphAnalysisLimits, GraphDirection, GraphEdgeSelection,
GraphNodeId,
};
use super::analysis_fixture::{foreign_node, generous_limits, metric_selections};
use super::cache_analysis_reading::{AnalysisReading, Asked, Walk, first_node, rows};
use super::cache_counting::{SELECTED_INDEX_HITS, assert_delta};
use super::cache_fixture::{cached_workspace, generous};
use super::corpus::GRAPH_CORPUS;
use super::corpus_analysis::ANALYSIS_TRAVERSAL_CORPUS;
pub fn assert_cached_analysis_matches_every_direct_operation() {
for (label, files) in [
("the mixed-edge corpus", GRAPH_CORPUS),
("the traversal corpus", ANALYSIS_TRAVERSAL_CORPUS),
] {
let (_resolved, _cache, held) = cached_workspace(files, generous());
let asked = Asked::over(held.graph());
for (selection_label, selection) in metric_selections() {
let subject = format!("{label} at {selection_label}");
assert_selection_matches(&held, selection, &asked, &subject);
}
}
assert_refusals_match_direct();
}
fn assert_selection_matches(
held: &CachedCodeGraph,
selection: GraphEdgeSelection,
asked: &Asked,
subject: &str,
) {
let limits = generous_limits();
let direct = GraphAnalysis::new(held.graph(), selection, limits)
.unwrap_or_else(|error| panic!("{subject}: the direct analysis should build: {error}"));
let cached = held
.analyze(selection, limits)
.unwrap_or_else(|error| panic!("{subject}: the cached analysis should build: {error}"));
assert_eq!(
(cached.selection(), cached.limits()),
(selection, limits),
"{subject}: the cached analysis states the arguments it was built with"
);
assert_eq!(
(direct.selection(), direct.limits()),
(selection, limits),
"{subject}: the direct analysis states the arguments it was built with"
);
assert!(
std::ptr::eq(cached.graph(), held.graph()),
"{subject}: a cached analysis reads the graph its handle holds"
);
let expected = rows(&direct, asked);
let observed = rows(&cached, asked);
for (stated, answered) in expected.iter().zip(&observed) {
assert_eq!(
stated, answered,
"{subject}: the cached and direct answers differ"
);
}
}
fn assert_refusals_match_direct() {
let (_resolved, _cache, held) = cached_workspace(ANALYSIS_TRAVERSAL_CORPUS, generous());
let selection = GraphEdgeSelection::code_relations();
let unknown = foreign_node(held.graph());
let shallow = GraphAnalysisLimits::new(u32::MAX, u32::MAX, 1, u64::MAX);
for (label, limits, node, depth) in [
("an unknown node", generous_limits(), unknown, 1_u32),
(
"a depth above the ceiling",
shallow,
first_node(held.graph()),
4,
),
("an unknown node above the ceiling", shallow, unknown, 4),
] {
let direct = GraphAnalysis::new(held.graph(), selection, limits)
.unwrap_or_else(|error| panic!("{label}: the direct analysis should build: {error}"));
let cached = held
.analyze(selection, limits)
.unwrap_or_else(|error| panic!("{label}: the cached analysis should build: {error}"));
let asked = Asked::one(
Walk::new(node, depth, GraphDirection::Outgoing),
(node, node),
);
assert_eq!(
rows(&cached, &asked),
rows(&direct, &asked),
"{label}: both analyses refuse identically"
);
}
assert_refusal_parity(&held, selection);
}
fn assert_refusal_parity(held: &CachedCodeGraph, selection: GraphEdgeSelection) {
for (label, limits) in [
(
"a node ceiling of zero",
GraphAnalysisLimits::new(0, u32::MAX, u32::MAX, u64::MAX),
),
(
"a node ceiling below the graph",
GraphAnalysisLimits::new(1, u32::MAX, u32::MAX, u64::MAX),
),
(
"a selected-edge ceiling below the selection",
GraphAnalysisLimits::new(u32::MAX, 0, u32::MAX, u64::MAX),
),
] {
let before = held.stats();
let refused = held
.analyze(selection, limits)
.err()
.unwrap_or_else(|| panic!("{label} must refuse"));
let directly = GraphAnalysis::new(held.graph(), selection, limits)
.err()
.unwrap_or_else(|| panic!("{label} must refuse directly"));
assert_eq!(
format!("{refused:?}"),
format!("{directly:?}"),
"{label}: the cached refusal is the direct refusal"
);
assert_delta(before, held.stats(), &[], label);
}
}
pub fn assert_cached_analysis_rechecks_every_limit_before_hits() {
let (_resolved, _cache, held) = cached_workspace(GRAPH_CORPUS, generous());
let selection = GraphEdgeSelection::all();
let seed = first_node(held.graph());
warm(&held, selection, seed);
assert_refusal_parity(&held, selection);
assert_query_ceilings_dominate(&held, selection, seed);
}
fn warm(held: &CachedCodeGraph, selection: GraphEdgeSelection, seed: GraphNodeId) {
let analysis = held
.analyze(selection, generous_limits())
.expect("permissive ceilings admit the analysis");
analysis
.neighbors(seed, 3, GraphDirection::Both)
.expect("a warm neighborhood");
analysis
.subgraph(seed, 3, GraphDirection::Both)
.expect("a warm subgraph");
analysis.betweenness_centrality().expect("warm centrality");
analysis.layout_assist().expect("warm layout metadata");
}
fn assert_query_ceilings_dominate(
held: &CachedCodeGraph,
selection: GraphEdgeSelection,
seed: GraphNodeId,
) {
let before = held.stats();
assert_depth_ceiling_dominates(held, selection, seed);
assert_work_ceiling_dominates(held, selection);
assert_delta(
before,
held.stats(),
&[(SELECTED_INDEX_HITS, 2)],
"a refused query examines no product",
);
}
fn assert_depth_ceiling_dominates(
held: &CachedCodeGraph,
selection: GraphEdgeSelection,
seed: GraphNodeId,
) {
let limits = GraphAnalysisLimits::new(u32::MAX, u32::MAX, 1, u64::MAX);
let cached = held
.analyze(selection, limits)
.expect("a lower depth ceiling still admits the analysis");
let directly = GraphAnalysis::new(held.graph(), selection, limits)
.expect("the direct analysis admits the same ceilings");
let walk = Walk::new(seed, 3, GraphDirection::Both);
assert_refuses_identically(
"a warm neighborhood above the depth ceiling",
&cached.neighbor_row(walk),
&directly.neighbor_row(walk),
);
assert_refuses_identically(
"a warm subgraph above the depth ceiling",
&cached.subgraph_row(walk),
&directly.subgraph_row(walk),
);
}
fn assert_work_ceiling_dominates(held: &CachedCodeGraph, selection: GraphEdgeSelection) {
let limits = GraphAnalysisLimits::new(u32::MAX, u32::MAX, u32::MAX, 1);
let cached = held
.analyze(selection, limits)
.expect("a lower work ceiling still admits the analysis");
let directly = GraphAnalysis::new(held.graph(), selection, limits)
.expect("the direct analysis admits the same ceilings");
assert_refuses_identically(
"warm centrality above the work ceiling",
&cached.betweenness_row(),
&directly.betweenness_row(),
);
assert_refuses_identically(
"warm layout metadata above the work ceiling",
&cached.layout_row(),
&directly.layout_row(),
);
}
fn assert_refuses_identically(label: &str, cached: &str, directly: &str) {
assert_eq!(
cached, directly,
"{label}: both analyses refuse identically"
);
assert!(
cached.starts_with("refused|"),
"{label}: a retained answer cannot bypass the current ceiling"
);
}