use pedant_core::resolution::rust::{RustTargetSnapshot, SourceClosureFailureKind, TargetId};
#[cfg(feature = "resolution-test-support")]
use crate::resolution::closure_fixtures::ENTRY_ONLY_READS;
use crate::resolution::closure_fixtures::{
CLOSURE_CASES, EXCLUDED_FROM_LIBRARY_CLOSURE, EXPECTED_LIBRARY_CLOSURE,
EXPECTED_REPETITION_MODULES, EXPECTED_REPETITION_SOURCES, MISSING_MODULE, MODULE_REPETITION,
PATH_ESCAPE,
};
use crate::resolution::fixture;
#[cfg(feature = "resolution-test-support")]
use crate::resolution::views::observed_paths;
use crate::resolution::views::{
app_library, closure_kinds, reached_paths, root_modules, source_paths,
};
pub fn assert_library_closure(snapshot: &RustTargetSnapshot, library: TargetId) {
assert_eq!(
source_paths(snapshot),
EXPECTED_LIBRARY_CLOSURE,
"standard, nested, #[path], and inline-owned modules all belong to the closure"
);
assert_eq!(
snapshot.crate_root(),
"src/lib.rs",
"the snapshot records the target's entry point"
);
assert_eq!(snapshot.target(), library, "the snapshot keeps its target");
for excluded in EXCLUDED_FROM_LIBRARY_CLOSURE {
assert!(
snapshot.source(excluded).is_none(),
"{excluded} is not module-reachable from src/lib.rs"
);
}
}
pub fn assert_source_evidence(snapshot: &RustTargetSnapshot) {
let standard = snapshot
.source("src/standard.rs")
.expect("a reached source");
assert_eq!(
standard.text(),
"#[path = \"sibling.rs\"]\npub mod sibling;\n\npub fn standard() {}\n",
"the snapshot keeps the exact source text"
);
let names: Vec<&str> = standard
.ir()
.functions
.iter()
.map(|fact| &*fact.name)
.collect();
assert_eq!(names, ["standard"], "each source carries its one-pass IR");
let leaf = snapshot
.source("src/nested/leaf.rs")
.expect("a reached source");
assert_ne!(
standard.digest(),
leaf.digest(),
"distinct source bytes hash distinctly"
);
}
pub fn assert_module_repetition_is_bounded() {
let tmp = fixture::build_repository(MODULE_REPETITION, false);
let project = fixture::load_default(&tmp);
let library = app_library(&project);
let snapshot = project
.snapshot_target(library)
.expect("a self-including module chain ends");
assert_eq!(
source_paths(&snapshot),
EXPECTED_REPETITION_SOURCES,
"a repeated source is stored once however many positions hold it"
);
assert_eq!(
root_modules(&project, library),
EXPECTED_REPETITION_MODULES,
"ancestry ends the self-including chain and still instantiates the \
shared source, and its child, under both positions"
);
}
pub fn assert_closure_failure_families() {
for case in CLOSURE_CASES {
let tmp = fixture::build_repository(case.files, false);
let project = fixture::load_default(&tmp);
let error = project
.snapshot_target(app_library(&project))
.expect_err(case.label);
assert_eq!(
closure_kinds(&error),
[case.expected],
"{}: unexpected failures",
case.label
);
}
}
pub fn assert_invalid_utf8_is_partial_evidence() {
let tmp = fixture::build_repository(MISSING_MODULE, false);
fixture::write_file(tmp.path(), "repo/src/lib.rs", b"pub mod raw;\n");
fixture::write_file(tmp.path(), "repo/src/raw.rs", &[0xf0, 0x28, 0x8c, 0x28]);
let project = fixture::load_default(&tmp);
let error = project
.snapshot_target(app_library(&project))
.expect_err("a module source that is not UTF-8");
assert_eq!(
closure_kinds(&error),
[SourceClosureFailureKind::InvalidUtf8],
"non-UTF-8 source bytes have their own failure kind"
);
assert_eq!(
reached_paths(&error),
["src/lib.rs"],
"partial evidence names only the sources that succeeded"
);
}
#[cfg(feature = "resolution-test-support")]
struct ReadWatch(pedant_core::resolution::rust::ResolutionProbe);
#[cfg(not(feature = "resolution-test-support"))]
struct ReadWatch;
#[cfg(feature = "resolution-test-support")]
impl ReadWatch {
fn install() -> Self {
Self(pedant_core::resolution::rust::ResolutionProbe::install())
}
fn assert_read_only_the_entry(&self, label: &str) {
let reads = self.0.source_reads();
assert_eq!(
observed_paths(&reads),
ENTRY_ONLY_READS,
"{label}: the sentinel outside the root must never be read"
);
}
}
#[cfg(not(feature = "resolution-test-support"))]
impl ReadWatch {
fn install() -> Self {
Self
}
fn assert_read_only_the_entry(&self, label: &str) {
assert!(!label.is_empty(), "every confinement case names itself");
}
}
fn assert_escape_is_refused_without_reading(label: &str, tmp: &tempfile::TempDir) {
let watch = ReadWatch::install();
let project = fixture::load_default(tmp);
let error = project
.snapshot_target(app_library(&project))
.expect_err(label);
assert_eq!(
closure_kinds(&error),
[SourceClosureFailureKind::OutOfRoot],
"{label}: a source outside the root is refused, not read"
);
watch.assert_read_only_the_entry(label);
}
pub fn assert_root_confinement_never_reads_outside() {
assert_escape_is_refused_without_reading(
"a #[path] that leaves the repository root",
&fixture::build_repository(PATH_ESCAPE, false),
);
assert_symlink_escape_is_refused();
}
#[cfg(unix)]
fn assert_symlink_escape_is_refused() {
let tmp = fixture::build_repository(MISSING_MODULE, false);
fixture::write_file(tmp.path(), "repo/src/lib.rs", b"pub mod link;\n");
fixture::write_file(tmp.path(), "outside/secret.rs", b"pub fn secret() {}\n");
std::os::unix::fs::symlink(
tmp.path().join("outside/secret.rs"),
tmp.path().join("repo/src/link.rs"),
)
.unwrap();
assert_escape_is_refused_without_reading("a symlink out of the repository root", &tmp);
}
#[cfg(not(unix))]
fn assert_symlink_escape_is_refused() {}