use pedant_core::resolution::rust::{
CargoTargetKind, RustResolutionSnapshot, RustTargetResolution,
};
use pedant_graph::{
CachedCodeGraph, CodeGraph, GraphBuildError, GraphCache, GraphCacheLimits, GraphLimits,
build_rust_graph, build_rust_graph_with_limits,
};
use super::cache_counting::{Classification, assert_build_classifies, assert_delta, fragments_of};
use super::corpus::TARGET_KIND_CORPUS;
use super::fixture::{self, Fixture, Resolved};
use super::promotion::{self, Restatement};
pub fn generous() -> GraphCacheLimits {
GraphCacheLimits::new(64, 64, 64, 64)
}
pub fn exact_only(max_exact_graphs: u32) -> GraphCacheLimits {
GraphCacheLimits::new(0, max_exact_graphs, 0, 0)
}
pub fn derived_only(max_selected_indexes: u32, max_products: u32) -> GraphCacheLimits {
GraphCacheLimits::new(0, 1, max_selected_indexes, max_products)
}
pub fn projection_only(max_source_projections: u32) -> GraphCacheLimits {
GraphCacheLimits::new(max_source_projections, 0, 0, 0)
}
pub type Pair<'a> = (&'a RustResolutionSnapshot, &'a RustTargetResolution);
pub fn pair(resolved: &Resolved) -> Pair<'_> {
(&resolved.snapshot, &resolved.resolution)
}
pub fn build(
cache: &mut GraphCache,
stated: Pair<'_>,
limits: GraphLimits,
) -> Result<CachedCodeGraph, GraphBuildError> {
let (snapshot, resolution) = stated;
cache.build_rust_graph(snapshot, resolution, limits)
}
pub fn admitted(cache: &mut GraphCache, stated: Pair<'_>, subject: &str) -> CachedCodeGraph {
bounded(cache, stated, GraphLimits::default(), subject)
}
pub fn bounded(
cache: &mut GraphCache,
stated: Pair<'_>,
limits: GraphLimits,
subject: &str,
) -> CachedCodeGraph {
build(cache, stated, limits)
.unwrap_or_else(|error| panic!("{subject} should build through the cache: {error}"))
}
pub fn direct(stated: Pair<'_>, limits: GraphLimits, subject: &str) -> CodeGraph {
let (snapshot, resolution) = stated;
build_rust_graph_with_limits(snapshot, resolution, limits)
.unwrap_or_else(|error| panic!("{subject} should build directly: {error}"))
}
pub fn assert_matches_direct(cached: &CodeGraph, stated: Pair<'_>, subject: &str) {
assert_equals_direct(
cached,
&direct(stated, GraphLimits::default(), subject),
subject,
);
}
pub fn assert_equals_direct(cached: &CodeGraph, minted: &CodeGraph, subject: &str) {
assert_eq!(cached, minted, "{subject}: cached and direct graphs differ");
assert_eq!(
json(cached),
json(minted),
"{subject}: cached and direct version-1 bytes differ"
);
assert_eq!(
cached.schema_version(),
CodeGraph::SCHEMA_VERSION,
"{subject}: the schema version is unchanged"
);
}
pub fn assert_direct_builders_agree(stated: Pair<'_>, subject: &str) {
let (snapshot, resolution) = stated;
let unbounded = build_rust_graph(snapshot, resolution).unwrap_or_else(|error| {
panic!("{subject} should build under the default ceilings: {error}")
});
assert_eq!(
&unbounded,
&direct(stated, GraphLimits::default(), subject),
"{subject}: the two direct builders disagree"
);
}
pub fn assert_refused_without_observation(
cache: &mut GraphCache,
stated: Pair<'_>,
expected: GraphBuildError,
subject: &str,
) {
let (snapshot, resolution) = stated;
let before = cache.stats();
let refused = build(cache, stated, GraphLimits::default())
.err()
.unwrap_or_else(|| panic!("{subject}: an invalid pairing must be refused"));
let directly = build_rust_graph_with_limits(snapshot, resolution, GraphLimits::default())
.expect_err("the direct builder refuses the same pairing");
assert_eq!(
format!("{refused:?}"),
format!("{expected:?}"),
"{subject}: the cache returns the existing refusal"
);
assert_eq!(
format!("{refused:?}"),
format!("{directly:?}"),
"{subject}: the cached and direct refusals agree"
);
assert_delta(before, cache.stats(), &[], subject);
}
pub fn json(graph: &CodeGraph) -> String {
serde_json::to_string(graph).expect("a built graph serializes")
}
pub fn restated(resolved: &Resolved, restatement: Restatement) -> RustTargetResolution {
promotion::restate(
&resolved.snapshot,
resolved.resolution.report(),
restatement,
)
}
pub fn cached_workspace(
files: &[(&str, &str)],
limits: GraphCacheLimits,
) -> (Resolved, GraphCache, CachedCodeGraph) {
let (directory, resolved) = fixture::resolve_target(files, fixture::CORPUS_LIBRARY);
let (cache, held) = cached_from(&resolved, limits);
drop(directory);
(resolved, cache, held)
}
pub fn cached_from(resolved: &Resolved, limits: GraphCacheLimits) -> (GraphCache, CachedCodeGraph) {
let mut cache = GraphCache::new(limits);
let held = admitted(&mut cache, pair(resolved), "the cached workspace");
(cache, held)
}
pub fn assert_clear_keeps_the_held_graph(
cache: &mut GraphCache,
base: &Resolved,
held: &CachedCodeGraph,
subject: &str,
) {
let before = cache.stats();
cache.clear();
assert_delta(
before,
cache.stats(),
&[],
&format!("{subject}: clearing the cache"),
);
assert_matches_direct(held.graph(), pair(base), subject);
let rebuilt = assert_build_classifies(
cache,
pair(base),
Classification::retained(0, fragments_of(held.graph())),
&format!("{subject}: a cleared store derives every fragment again"),
);
assert!(
!std::ptr::eq(held.graph(), rebuilt.graph()),
"{subject}: a cleared store answers from a fresh allocation"
);
assert_eq!(
held.graph(),
rebuilt.graph(),
"{subject}: the held graph equals the one the cleared store rebuilt"
);
assert_eq!(
json(held.graph()),
json(rebuilt.graph()),
"{subject}: the held and rebuilt graphs state the same version-1 bytes"
);
}
pub fn two_root_targets() -> (Fixture, Resolved, Resolved) {
let corpus = Fixture::build(TARGET_KIND_CORPUS);
let library = corpus.resolve(corpus.sole_library());
let example = corpus.resolve(corpus.target("app", CargoTargetKind::Example, "demo"));
(corpus, library, example)
}