#![expect(clippy::expect_used, reason = "tests assert by panicking")]
#![expect(clippy::disallowed_types, reason = "fixtures use std strings")]
#[cfg(debug_assertions)]
use std::panic::{AssertUnwindSafe, catch_unwind};
use vize_l0::Allocator;
use vize_l0::dump::Dump;
use vize_l0::id::NodeId;
use vize_l0::pass::{
Fusability, Pair, PassDesc, PassEvent, PassKind, Pipeline, Preserved, run_pipeline,
};
use vize_l0::side_table::SideTable;
use vize_l2::dump::Page as L2Page;
#[cfg(debug_assertions)]
use vize_l2::verify::Rigor;
use vize_l2::verify::VerifyObserver;
const NORMALIZE: PassDesc = PassDesc::new(
"normalize",
PassKind::Optional,
Fusability::Fusable,
Preserved::ALL,
);
const DIAGNOSE: PassDesc = PassDesc::new(
"diagnose",
PassKind::MandatoryDiagnostic,
Fusability::Barrier,
Preserved::ALL,
);
const LOWER: PassDesc = PassDesc::new(
"lower",
PassKind::MandatoryLowering,
Fusability::Barrier,
Preserved::NONE,
);
const WITHOUT_LOWERING: &[PassDesc] = &[NORMALIZE, DIAGNOSE];
const WITH_LOWERING: &[PassDesc] = &[NORMALIZE, LOWER, NORMALIZE];
const LOWER_ONLY: &[PassDesc] = &[LOWER];
fn event_for(pipeline: &Pipeline, pass_index: usize) -> PassEvent<'_> {
let group_index = pipeline
.group_of_pass(pass_index)
.expect("the pass exists in the pipeline");
PassEvent {
pipeline,
group_index,
group: pipeline.group(group_index).expect("its group exists"),
pass_index,
}
}
#[cfg(debug_assertions)]
fn empty_if_page() -> L2Page {
L2Page::parse("[l2-dump-v2]\nops=1\n\n[l2-dump-v2.ops]\nui.if @0:10\n\n")
.expect("the page is grammar-valid")
}
#[cfg(debug_assertions)]
#[test]
fn rigor_escalates_at_the_lowering_pass_and_only_there() {
let mut verifier = VerifyObserver::new();
assert_eq!(verifier.rigor(), Rigor::Raw);
let pipeline = Pipeline::new("s2", WITHOUT_LOWERING);
run_pipeline(&pipeline, &mut verifier, |_| Ok(())).expect("no step fails");
assert_eq!(verifier.rigor(), Rigor::Raw);
let pipeline = Pipeline::new("s2", WITH_LOWERING);
run_pipeline(&pipeline, &mut verifier, |_| Ok(())).expect("no step fails");
assert_eq!(verifier.rigor(), Rigor::Canonical);
}
#[cfg(debug_assertions)]
#[test]
fn note_is_the_after_pass_hook_callable_from_a_step_body() {
let pipeline = Pipeline::new("s2", WITH_LOWERING);
let mut verifier = VerifyObserver::new();
verifier.note(&event_for(&pipeline, 0));
assert_eq!(verifier.rigor(), Rigor::Raw);
verifier.note(&event_for(&pipeline, 1));
assert_eq!(verifier.rigor(), Rigor::Canonical);
verifier.note(&event_for(&pipeline, 2));
assert_eq!(verifier.rigor(), Rigor::Canonical);
}
#[cfg(debug_assertions)]
#[test]
fn the_observer_composes_through_pair_and_still_tracks_rigor() {
let pipeline = Pipeline::new("s2", WITH_LOWERING);
let mut pair = Pair(VerifyObserver::new(), VerifyObserver::new());
run_pipeline(&pipeline, &mut pair, |_| Ok(())).expect("no step fails");
assert_eq!(pair.0.rigor(), Rigor::Canonical);
assert_eq!(pair.1.rigor(), Rigor::Canonical);
}
#[test]
fn a_holding_artifact_passes_every_check_without_panicking() {
let pipeline = Pipeline::new("s2", LOWER_ONLY);
let event = event_for(&pipeline, 0);
let text = std::fs::read_to_string(
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join("reference.folio"),
)
.expect("committed reference page reads");
let folio = L2Page::parse(&text).expect("the reference page parses");
let mut verifier = VerifyObserver::new();
verifier.note(&event);
verifier.check(&event, &folio);
let mut table = SideTable::new();
for index in 0..9u32 {
table.insert(
NodeId::from_index(index).expect("small indices have ids"),
index,
);
}
verifier.check_table(&event, &folio, &table);
let allocator = Allocator::default();
verifier.check_live(&event, &allocator, allocator.stamp());
}
#[cfg(debug_assertions)]
#[test]
fn check_panics_with_the_exact_report_naming_the_pass() {
let pipeline = Pipeline::new("s2", LOWER_ONLY);
let event = event_for(&pipeline, 0);
let folio = empty_if_page();
let mut verifier = VerifyObserver::new();
verifier.note(&event);
let payload = catch_unwind(AssertUnwindSafe(|| verifier.check(&event, &folio)))
.expect_err("a canonically invalid page must fail the check");
let message = payload
.downcast::<String>()
.expect("the report panics with a formatted string");
assert_eq!(
*message,
"L2 verifier: 1 violation(s) after `s2.lower`\n\
L2V004 @0:10 `ui.if` owns no branch"
);
}
#[cfg(debug_assertions)]
#[test]
fn check_at_raw_rigor_accepts_what_canonical_rigor_rejects() {
let pipeline = Pipeline::new("s2", WITHOUT_LOWERING);
let event = event_for(&pipeline, 0);
let folio = empty_if_page();
let verifier = VerifyObserver::new();
verifier.check(&event, &folio);
}
#[cfg(debug_assertions)]
#[test]
fn check_table_panics_with_the_exact_report_on_a_dangling_id() {
let pipeline = Pipeline::new("s2", &[DIAGNOSE]);
let event = event_for(&pipeline, 0);
let folio = L2Page::parse(
"[l2-dump-v2]\nops=2\n\n[l2-dump-v2.ops]\nui.element div @0:10\n ui.text \"x\" @2:8\n\n",
)
.expect("the page is grammar-valid");
let mut table = SideTable::new();
table.insert(
NodeId::from_index(1).expect("index 1 has an id"),
"resolves",
);
table.insert(
NodeId::from_index(99).expect("index 99 has an id"),
"dangles",
);
let verifier = VerifyObserver::new();
let payload = catch_unwind(AssertUnwindSafe(|| {
verifier.check_table(&event, &folio, &table)
}))
.expect_err("a dangling reference must fail the check");
let message = payload
.downcast::<String>()
.expect("the report panics with a formatted string");
assert_eq!(
*message,
"L2 verifier: 1 violation(s) after `s2.diagnose`\n\
L2V003 @0:0 side table references %99, but the artifact numbers 2 ops"
);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "arena-backed value outlived its compile")]
fn check_live_panics_on_a_stamp_from_before_the_reset() {
let pipeline = Pipeline::new("s2", LOWER_ONLY);
let mut allocator = Allocator::default();
let stamp = allocator.stamp();
allocator.reset();
VerifyObserver::new().check_live(&event_for(&pipeline, 0), &allocator, stamp);
}
#[test]
fn the_observer_has_the_documented_cfg_shape() {
#[cfg(debug_assertions)]
assert_eq!(size_of::<VerifyObserver>(), 1);
#[cfg(not(debug_assertions))]
assert_eq!(size_of::<VerifyObserver>(), 0);
}