Files
pbakaus_impeccable/crates/html/tests/vectors.rs
T
Paul BakausandClaude Fable 5.1 0547ed6a63 reorg C: the open Rust runtime joins this repo as one Cargo workspace
The engine no longer lives in a separate repo. `crates/` is a snapshot of the
open crates (foundation, core, common, context, live, hook, skills, comp,
comp-verbs, html, browser, detect, cli) plus `Cargo.lock`, taken as a git
archive of the engine repo at the commit that finished the boundary split.
None of that repo's history comes with it, and none of it should: the closed
half stays private.

The closed half is the rule engine. It ships as a prebuilt native archive per
target, `libimpeccable_detector.a`, published as a `detector-v<X>` GitHub
Release on this repo. `crates/core/build.rs` resolves and links it three ways:
`IMPECCABLE_DETECTOR_LIB=<dir>` for a local detector build, else the
`~/.impeccable/detector/<version>/<target>/` cache, else a download verified
against its `.sha256` sidecar. `crates/core` is a thin shim over a three-symbol
C ABI; nothing above it knows the boundary exists.

What changed versus the engine repo copy:

- Every crate manifest moves from `license-file.workspace` to
  `license.workspace` (this workspace declares Apache-2.0), and the workspace
  gains the `postcard` dependency the boundary encoding needs.
- The launcher contract test reads `skill/scripts/impeccable{,.cmd}` instead of
  a sibling `launcher/` dir, and `engine_binary` downloads from
  `github.com/pbakaus/impeccable/releases/download/engine-v<version>/` instead
  of the retired dist repo. No oracle golden carried the old URL, so no
  re-recording was owed.
- The tests that hunted for a public repo through `IMPECCABLE_PUBLIC_REPO`,
  `../impeccable-second` or a hardcoded home directory now resolve the root as
  `CARGO_MANIFEST_DIR/../..`, because they are in it. The env var stays as an
  override for an out-of-tree checkout.
- The in-page bundle (`detect-antipatterns-browser.js`, 2 MB of generated wasm
  glue) is no longer tracked. `crates/core/build.rs` resolves it beside the
  archive, hands the path to `impeccable_core::browser::IN_PAGE_BUNDLE_JS`, and
  live mode serves that. `scripts/check-detector-release.mjs` now requires it
  and its `.sha256` in a detector release.
- The live crate embeds `skill/scripts/live-browser*.js` and
  `modern-screenshot.umd.js` directly rather than through vendored copies, so
  the binary and the installed skill cannot drift.
- `crates/browser/assets/` (an unused second copy of the bundle) is gone.
- `tests/lib/engine-bin.mjs` also accepts `target/release/impeccable`, so a
  plain `cargo build --release -p impeccable` is enough to run `bun run test`.

Verified with the archive from a local detector build: `cargo test --workspace`
267 pass, oracle 795 pass / 0 fail / 0 missing, `bun run build` clean, the
default suite green, and the launcher's `engine-probe` handshake answering
through `skill/scripts/impeccable`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
2026-09-01 15:31:26 -07:00

202 lines
7.2 KiB
Rust

//! Replays the recorded JS call vectors for the css-cascade module through
//! the Rust port and requires every one to match (mirrors
//! `crates/core/tests/vectors.rs`). Vectors live in the public repo at
//! `tests/oracle/vectors/calls/<module>/<fn>.jsonl` in this repo;
//! `IMPECCABLE_PUBLIC_REPO` overrides the root for an out-of-tree checkout.
use impeccable_html::cascade::vectors::{call, KNOWN};
use serde_json::Value;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
const MODULES: &[&str] = &["engines.static-html.css-cascade"];
/// The repo root: this workspace is the public repo. `IMPECCABLE_PUBLIC_REPO`
/// overrides it for an out-of-tree checkout.
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("..").join("..")
}
fn vectors_dir() -> Option<PathBuf> {
let mut candidates: Vec<PathBuf> = Vec::new();
if let Ok(p) = std::env::var("IMPECCABLE_PUBLIC_REPO") {
candidates.push(PathBuf::from(p));
}
candidates.push(repo_root());
candidates
.into_iter()
.map(|repo| {
repo.join("tests")
.join("oracle")
.join("vectors")
.join("calls")
})
.find(|dir| dir.is_dir())
}
/// Canonical form for comparison: numbers by f64 value, objects key-order
/// insensitive, `{"$undef":true}` object values dropped (JSON.stringify would
/// omit them).
#[derive(Debug, PartialEq)]
enum Canon {
Null,
Bool(bool),
Num(f64),
Str(String),
Arr(Vec<Canon>),
Obj(BTreeMap<String, Canon>),
Undef,
}
fn is_undef(v: &Value) -> bool {
matches!(v, Value::Object(m) if m.len() == 1 && m.get("$undef").is_some())
}
fn canon(v: &Value) -> Canon {
match v {
Value::Null => Canon::Null,
Value::Bool(b) => Canon::Bool(*b),
Value::Number(n) => Canon::Num(n.as_f64().unwrap_or(f64::NAN)),
Value::String(s) => Canon::Str(s.clone()),
Value::Array(a) => Canon::Arr(a.iter().map(canon).collect()),
Value::Object(m) => {
if is_undef(v) {
return Canon::Undef;
}
if m.len() == 1 {
if let Some(n) = m.get("$negzero") {
if n.as_bool() == Some(true) {
return Canon::Num(-0.0);
}
}
}
Canon::Obj(
m.iter()
.filter(|(_, v)| !is_undef(v))
.map(|(k, v)| (k.clone(), canon(v)))
.collect(),
)
}
}
}
fn same(a: &Canon, b: &Canon) -> bool {
match (a, b) {
(Canon::Num(x), Canon::Num(y)) => {
(x.is_nan() && y.is_nan()) || (x == y && x.is_sign_negative() == y.is_sign_negative())
}
(Canon::Arr(x), Canon::Arr(y)) => {
x.len() == y.len() && x.iter().zip(y).all(|(p, q)| same(p, q))
}
(Canon::Obj(x), Canon::Obj(y)) => {
x.len() == y.len()
&& x.iter()
.all(|(k, v)| y.get(k).map(|w| same(v, w)).unwrap_or(false))
}
_ => a == b,
}
}
#[test]
fn replay_recorded_vectors() {
let Some(dir) = vectors_dir() else {
panic!(
"vectors dir not found; set IMPECCABLE_PUBLIC_REPO to a checkout of the public repo that has \
tests/oracle/vectors/calls (generate with `node tests/oracle/vectors/record-calls.mjs`)"
);
};
let mut total_pass = 0usize;
let mut total_fail = 0usize;
let mut lines_out: Vec<String> = Vec::new();
for module in MODULES {
let mod_dir = dir.join(module);
let mut known: Vec<&str> = Vec::new();
for (m, fns) in KNOWN {
if m == module {
known.extend(fns.iter().copied());
}
}
let mut seen: Vec<String> = Vec::new();
let mut entries: Vec<PathBuf> = match std::fs::read_dir(&mod_dir) {
Ok(rd) => rd
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().map(|e| e == "jsonl").unwrap_or(false))
.collect(),
Err(_) => Vec::new(),
};
entries.sort();
for path in entries {
let fn_name = path.file_stem().unwrap().to_string_lossy().to_string();
seen.push(fn_name.clone());
if !known.iter().any(|k| *k == fn_name) {
lines_out.push(format!("SKIP {}/{}: not ported yet", module, fn_name));
continue;
}
let text = std::fs::read_to_string(&path).unwrap();
let mut pass = 0usize;
let mut fail = 0usize;
let mut first_failures: Vec<String> = Vec::new();
for (lineno, line) in text.lines().enumerate() {
if line.trim().is_empty() {
continue;
}
let rec: Value = serde_json::from_str(line)
.unwrap_or_else(|e| panic!("{}:{}: {e}", path.display(), lineno + 1));
let args = rec
.get("args")
.and_then(|a| a.as_array())
.cloned()
.unwrap_or_default();
let expected = rec.get("result").cloned().unwrap_or(Value::Null);
match call(module, &fn_name, &args) {
None => {
fail += 1;
if first_failures.len() < 3 {
first_failures.push(format!(
" line {}: dispatcher has no handler / rejected args {}",
lineno + 1,
Value::Array(args.clone())
));
}
}
Some(actual) => {
if same(&canon(&actual), &canon(&expected)) {
pass += 1;
} else {
fail += 1;
if first_failures.len() < 3 {
first_failures.push(format!(
" line {}: args {}\n expected {}\n actual {}",
lineno + 1,
Value::Array(args.clone()),
expected,
actual
));
}
}
}
}
}
total_pass += pass;
total_fail += fail;
lines_out.push(format!(
"{} {}/{}: {} pass, {} fail",
if fail == 0 { "PASS" } else { "FAIL" },
module,
fn_name,
pass,
fail
));
lines_out.extend(first_failures);
}
for f in &known {
if !seen.iter().any(|s| s == *f) {
lines_out.push(format!("SKIP {}/{}: no vectors recorded", module, f));
}
}
}
let summary = lines_out.join("\n");
println!("{summary}\nTOTAL: {total_pass} pass, {total_fail} fail");
assert_eq!(total_fail, 0, "vector mismatches:\n{summary}");
}