use std::collections::{BTreeMap, BTreeSet};
use super::analysis_ownership::ANALYSIS_SOURCES;
use super::cache_source_boundary::CACHE_PREDICATES;
use super::inventory::{MANIFEST, SOURCES, TEST_ROOT, TEST_SUPPORT_SOURCES};
use super::scan::{self, discovered_sources, discovered_test_sources, parsed};
use super::surface::declared_items;
pub const ANALYSIS_PREDICATES: &[&str] = &[
"graph_analysis_limit_checks_dominate_allocations",
"graph_analysis_limits_refuse_before_work",
"graph_analysis_public_boundary_is_exact",
"graph_analysis_results_are_deterministic",
"graph_analysis_selection_is_explicit_and_shared",
"graph_analysis_source_boundary_is_exact",
"graph_betweenness_refuses_work_before_source_state",
"graph_components_and_condensation_are_exact",
"graph_declared_partition_follows_nearest_container",
"graph_degree_and_betweenness_match_directed_oracles",
"graph_derived_consumers_reuse_analysis_results",
"graph_divergence_and_layout_are_deterministic",
"graph_divergence_matches_declared_partition_arithmetic",
"graph_divergence_selection_reuses_analysis_indexes",
"graph_layout_assist_is_complete_and_coordinate_free",
"graph_metric_selection_reuses_analysis_indexes",
"graph_metrics_and_components_consume_shared_indexes",
"graph_neighbors_are_bounded_directed_and_ordered",
"graph_path_is_shortest_directed_and_deterministic",
"graph_subgraph_is_exact_induced_selection",
"graph_traversal_and_components_are_iterative",
"graph_traversal_consumes_shared_analysis_indexes",
];
pub const VERSION_ONE_PREDICATES: &[&str] = &[
"graph_defensive_error_paths_are_complete_and_wired",
"graph_defensive_malformed_inputs_return_exact_errors",
"graph_id_capacity_uses_one_checked_insertion_owner",
"graph_identity_checks_dominate_projection_state_construction",
"graph_ids_languages_and_limits_are_dense_checked_and_atomic",
"graph_projection_uses_only_supplied_resolution_facts",
"graph_public_identity_defaults_and_schema_are_exact",
"graph_public_lifecycle_surface_is_closed",
"graph_public_reading_surface_is_complete",
"rust_graph_builds_and_serializes_after_fixture_teardown",
"rust_graph_containment_is_a_unit_local_forest",
"rust_graph_edges_retain_candidate_certainty_and_evidence",
"rust_graph_json_v1_covers_every_graph_owned_variant",
"rust_graph_json_v1_is_exact_compact_and_deterministic",
"rust_graph_maps_every_rust_reference_kind_exactly",
"rust_graph_maps_every_rust_symbol_kind_exactly_once",
"rust_graph_preserves_cargo_dependency_evidence",
"rust_graph_qualifies_unit_roots_and_shared_source_files",
"rust_graph_rejects_identity_mismatch_before_capacity_checks",
"rust_graph_retains_enclosed_top_level_and_candidate_free_references",
"rust_graph_separates_logical_parentage_from_source_location",
"rust_graph_tiers_change_only_resolution_evidence",
];
const MANIFEST_DEPENDENCIES: &[&str] = &["pedant-core", "pedant-types", "serde", "thiserror"];
const MANIFEST_DEV_DEPENDENCIES: &[&str] =
&["pedant-core", "serde_json", "quote", "syn", "tempfile"];
pub fn assert_analysis_source_boundary_is_exact() {
assert_manifest_states_no_delta();
assert_analysis_modules_are_discovered();
assert_every_predicate_is_registered_once();
assert_every_registered_wrapper_is_modelled();
assert_no_test_lives_outside_the_root();
}
pub fn assert_manifest_states_no_delta() {
assert_eq!(
manifest_table("dependencies"),
MANIFEST_DEPENDENCIES,
"the library closure states exactly its modelled dependencies"
);
assert_eq!(
manifest_table("dev-dependencies"),
MANIFEST_DEV_DEPENDENCIES,
"the harness closure states exactly its modelled dependencies"
);
assert!(
!MANIFEST.contains("[features]"),
"this crate states no feature of its own"
);
assert_eq!(
MANIFEST.matches("pedant-core = {").count(),
MANIFEST.matches("default-features = false").count(),
"every pedant-core edge disables its default features"
);
assert_eq!(
MANIFEST.matches("\"resolution-test-support\"").count(),
1,
"the dev edge selects the one proof feature and nothing else"
);
}
fn manifest_table(name: &str) -> Vec<String> {
let header = format!("[{name}]");
MANIFEST
.lines()
.skip_while(|line| line.trim() != header)
.skip(1)
.take_while(|line| !line.trim_start().starts_with('['))
.filter_map(declared_key)
.collect()
}
fn declared_key(line: &str) -> Option<String> {
let trimmed = line.trim_start();
match trimmed.starts_with('#') {
true => None,
false => trimmed
.split_once('=')
.map(|(key, _)| key.trim().to_owned()),
}
}
fn assert_analysis_modules_are_discovered() {
let discovered: BTreeSet<String> = discovered_sources()
.into_iter()
.filter(|path| path.starts_with("src/analysis/"))
.collect();
assert_eq!(
discovered,
ANALYSIS_SOURCES
.iter()
.map(|path| (*path).to_owned())
.collect::<BTreeSet<String>>(),
"every analysis module is modelled and every modelled module exists"
);
}
fn assert_every_predicate_is_registered_once() {
for predicate in ANALYSIS_PREDICATES {
assert_eq!(
TEST_ROOT
.matches(&format!("#[test]\nfn {predicate}()"))
.count(),
1,
"{predicate} is registered exactly once, with its bare identity"
);
}
}
pub fn assert_every_registered_wrapper_is_modelled() {
let listed: Vec<&str> = [
CACHE_PREDICATES,
ANALYSIS_PREDICATES,
VERSION_ONE_PREDICATES,
]
.into_iter()
.flatten()
.copied()
.collect();
let modelled: BTreeSet<String> = listed.iter().map(|name| (*name).to_owned()).collect();
assert_eq!(
modelled.len(),
listed.len(),
"no predicate is modelled by two families"
);
assert_eq!(
registered_wrappers(),
modelled,
"the root registers exactly the predicates the family inventories name"
);
}
fn registered_wrappers() -> BTreeSet<String> {
TEST_ROOT
.lines()
.zip(TEST_ROOT.lines().skip(1))
.filter(|(attribute, _)| attribute.trim() == "#[test]")
.map(|(_, declaration)| registered_name(declaration))
.collect()
}
fn registered_name(line: &str) -> String {
let declared = line.trim();
let named = declared
.strip_prefix("fn ")
.unwrap_or_else(|| panic!("{declared} follows the attribute and must declare a test"));
named
.split_once("() {")
.map(|(name, _)| name.to_owned())
.unwrap_or_else(|| panic!("{declared} must be a bare wrapper taking no argument"))
}
pub fn assert_no_test_lives_outside_the_root() {
let modelled: BTreeSet<String> = TEST_SUPPORT_SOURCES
.iter()
.map(|module| module.path.to_owned())
.collect();
let discovered: BTreeSet<String> = discovered_test_sources()
.into_iter()
.filter(|path| path != "tests/graph.rs")
.collect();
assert_eq!(
discovered, modelled,
"every support module is scanned and every scanned module exists"
);
for module in TEST_SUPPORT_SOURCES {
assert!(
!declares_a_test(module.text),
"{}: every registered test lives at the root",
module.path
);
}
for entry in SOURCES {
assert!(
!declares_a_test(entry.text),
"{}: production source states no inline test",
entry.path
);
}
}
fn declares_a_test(text: &str) -> bool {
text.lines()
.map(str::trim)
.any(|line| line == "#[test]" || line.starts_with("#[cfg(test)"))
}
pub fn assert_functions_are_declared_once(sources: &[&str], floor: usize, subject: &str) {
let mut declared: BTreeMap<String, Vec<String>> = BTreeMap::new();
for path in sources {
for (name, definition) in declared_definitions(path) {
declared
.entry(definition)
.or_default()
.push(format!("{path}::{name}"));
}
}
let repeated: Vec<String> = declared
.values()
.filter(|sites| sites.len() > 1)
.map(|sites| sites.join(" == "))
.collect();
assert!(
repeated.is_empty(),
"one function is declared once; these sites declare the same one: {repeated:?}"
);
assert!(
declared.len() > floor,
"{subject} declares the functions this case reads: {}",
declared.len()
);
}
fn declared_definitions(path: &str) -> Vec<(String, String)> {
let file = parsed(path);
let mut declared = Vec::new();
for item in declared_items(&file.items) {
match item {
syn::Item::Fn(function) => declared.push((
function.sig.ident.to_string(),
normalized_definition(&function.sig, &function.block),
)),
syn::Item::Impl(block) if block.trait_.is_none() => {
declared.extend(impl_definitions(
&scan::token_text(&block.self_ty),
&block.items,
));
}
_ => (),
}
}
declared
}
fn impl_definitions(subject: &str, members: &[syn::ImplItem]) -> Vec<(String, String)> {
members
.iter()
.filter_map(|member| match member {
syn::ImplItem::Fn(method) if states_logic(&method.block) => Some((
format!("{subject}::{}", method.sig.ident),
normalized_definition(&method.sig, &method.block),
)),
_ => None,
})
.collect()
}
fn states_logic(body: &syn::Block) -> bool {
body.stmts.len() > 1
}
fn normalized_definition(signature: &syn::Signature, body: &syn::Block) -> String {
let stated = format!(
"{}{}{}",
scan::token_text(&signature.inputs),
scan::token_text(&signature.output),
scan::token_text(body)
);
stated.split_whitespace().collect()
}