test(conformance): the standing wave-2 conformance battery

- strict-mode matrix: missing variables (top-level, in {{#each}},
  in {{#if}} then and else branches) fail typed through both
  sinks, never silent; the write sink's no-atomicity contract
  pinned by its row
- error-surface completeness: six failure paths each mapping to
  exactly one typed variant; registrations never half-survive;
  hostile context values render clean through both sinks and both
  escape policies with no panic paths
- two-sink correctness: byte-identical output over the ssr-shaped
  and code-shaped fixture postures, re-exercised over the proof
  fixture files (goldens stay pinned by the proof binaries)
- AGENTS.md: the wasm release build becomes standing and
  unconditional; the feature-matrix coincide-today posture and the
  handlebars feature-inspection posture documented honestly
  (ADR-003, ADR-009)

Verification: cargo test (all green), cargo test --all-features
(identical), clippy -D warnings clean, fmt clean, doc clean, wasm
release build clean, handlebars tree features default-only,
taskgraph validate clean
This commit is contained in:
glm-5.3-flash committed 2026-11-06 20:44:52 +00:00
1 parent 3b0483e50f
commit 952f208e27
3 files changed
+639 -4

No files matched your search

+29 -1
View File
@@ -145,13 +145,41 @@ Run these before committing. All must pass.
```bash
cargo test # full suite
cargo test --all-features # feature matrix (see below)
cargo clippy --all-targets -- -D warnings
cargo fmt --check
cargo doc --no-deps # if docs changed
cargo build --target wasm32-unknown-unknown --release # if deps/wasm-relevant code changed
cargo build --target wasm32-unknown-unknown --release # standing, unconditional (see below)
cargo publish --dry-run --allow-dirty # before a release
grep -rn '—' src/ docs/ README.md AGENTS.md .opencode/agents/ tests/ \
Cargo.toml # must be zero hits; see convention 12
taskgraph validate # after touching anything under tasks/
```
Two postures are standing and unconditional rather than condition-run:
- **The wasm build** (ADR-003, made standing by the wave-2
conformance battery): `cargo test` may pass everywhere but wasm is
where platform-only dependencies betray themselves, and this crate
promises wasm-clean always. Build the release target on every
change that lands, not only when deps seem affected. The
`grep -rn ... | grep handlebars | head` style feature inspection is
not needed by hand; the posture to preserve is: no handlebars
features (`dir_source`, `script_helper`) may end up enabled in the
final dependency set.
- **The feature matrix** (ADR-009), documented honestly per the
wave-2 battery: `cargo test` with `default` features and with
`--all-features` both pass, and they coincide today because
`bast-codegen` gates nothing yet. The coincide-today statement
holds until the first feature starts gating something; when one
does, this list grows the distinct postures that feature needs
rather than the matrix staying implicit.
```bash
# feature inspection posture (ADR-002):
cargo tree --all-targets -f "{p} {f}" | grep handlebars
# expected: handlebars features shown are only the substrate defaults
# this crate builds with (no dir_source, no script_helper)
```
## Task files (taskgraph)
+97 -3
View File
@@ -1,7 +1,7 @@
---
id: w2-conformance-battery
name: "Wave 2: the conformance battery (strict-mode matrix, error surface, two sinks, wasm, feature matrix)"
status: pending
status: completed
depends_on: [w2-ssr-proof, w2-code-proof]
scope: moderate
risk: low
@@ -101,8 +101,102 @@ workaround.
## Notes
> To be filled by implementation agent
Implementation landed as `tests/conformance.rs`, a standalone test
binary (13 rows) in the wave-1/2 convention of one fixture module per
test binary. The battery reuses the proof fixture files
(`tests/fixtures/ssr_proof/`, `tests/fixtures/code_proof/`) rather
than restating them, and does not restate the proof binaries' goldens;
its rows assert the obligations themselves against fresh public-API
renders.
What landed, per the acceptance criteria:
- **Strict-mode matrix**: five rows. Top-level missing variable,
missing variable inside an `{{#each}}` body, missing variable inside
a taken `{{#if}}` branch, missing variable inside a taken
`{{else}}` branch: each fails typed (`Error::Render`) through both
sinks, with the detail strings naming the missing variables, and
the present-variable case still rendering. Plus the write-sink
on-failure row: a template that streams a prefix before failing
leaves those bytes in the target (`"prefix-ok\n"` pinned) and
returns the typed error; the row states the no-atomicity contract
(consumers needing atomicity buffer first), which is what wave 1
pinned and nothing more.
- **Error-surface completeness**: six rows, each mapping to exactly
one variant of the two (the enum has no catch-all by construction,
so variant assignment is asserted): malformed `register_template`
and `register_partial` text resolves to `Error::Registration` and
the name never survives half-registered, including the failed-
overwrite case where the previous registration and its kind stay
intact (re-exercised from wave 1's unit rows); unknown template
name (including the empty name) resolves to `Error::Render` through
both sinks; partial-as-render-target resolves to `Error::Render`
through both sinks; strict missing variable resolves to
`Error::Render` through both sinks; unknown helper resolves to
`Error::Render` naming the helper, through both sinks; hostile
context values (script tags, quotes, braces, entity-lookalikes,
emoji, NUL/tab/newline control bytes) render typed-clean through
both sinks under both escape policies with two byte-pinned
expectations. No input the battery sends reaches a panic path.
- **Two-sink correctness**: two rows, one per fixture posture. The
ssr-shaped posture (ssr-proof page + fragments + caller-supplied
card text, HTML escaper) and the code-shaped posture (code-proof
writer + validator + the composed field-write-line partial, no-op
escaper, document-ordered fields) each produce byte-identical
output through `render` and `render_to_write`, with posture-
identity assertions (trusted raw slot / verbatim literals and
document order) proving the exercised rows are the real postures.
- **AGENTS.md edits (both recorded here as required)**: (1) the
wasm release build is now a standing, unconditional line in the
verification command list (comment changed from "if deps/wasm-
relevant code changed" to "standing, unconditional", ADR-003), with
a paragraph explaining the posture and the feature-inspection
posture (no `dir_source` / `script_helper` in the final dep set)
made explicit; (2) `cargo test --all-features` joined the command
list and the feature-matrix posture is documented honestly: default
and `--all-features` coincide today because `bast-codegen` gates
nothing yet, and the list grows the distinct postures when the
first feature starts gating something (ADR-009).
Verification run on the final tree:
- `cargo test`: 19 lib + 13 conformance + 5 code_fixtures + 7
code_proof + 3 ssr + 4 ssr_proof, all pass.
- `cargo test --all-features`: identical (the coincide holds).
- `cargo clippy --all-targets -- -D warnings`: clean (after
switching the battery to `expect_err` and a shared
`assert_both_sinks` helper so clippy's `err_expect` and
`single_element_loop` lints stay silent).
- `cargo fmt --check`, `cargo doc --no-deps`: clean.
- `cargo build --target wasm32-unknown-unknown --release`: passes.
- Tree inspection: `cargo tree --all-targets -f "{p} {f}" | grep
handlebars` shows only `handlebars v6.4.4 default` (no
`dir_source`, no `script_helper`).
- `taskgraph validate`: 18 tasks valid.
- The standing em-dash grep: zero hits outside the grep command
itself pre-existing in AGENTS.md (convention 12).
Stop-and-book: no core change was needed; every row passed against
the shipped wave-2 tree. One substrate observation surfaced while
authoring the hostile-values row and is dispositioned as data, not a
gap: handlebars treats the expression name `raw` as keyword-shaped
and never resolves it as a context variable (`{{& raw}}` renders
empty even in strict mode). The battery avoids the name and documents
why in a doc comment; no consumer behavior change is implied and no
core change is booked for v0.1.0.
## Summary
> To be filled on completion
The wave-2 conformance battery landed: `tests/conformance.rs` stands
as the standing verification home with 13 rows across the strict-mode
matrix (top-level, `{{#each}}`, `{{#if}}` both branches, write-sink
no-atomicity contract), error-surface completeness (six failure paths
each mapping to exactly one typed variant, no half registrations, no
panic paths, hostile data through both sinks and both escape
policies), and two-sink byte-equality over the ssr-shaped and code-
shaped fixture postures, re-exercised over the proof fixture files.
The wasm release build became a standing unconditional line in
AGENTS.md's verification list together with the feature-matrix
coincide-today posture (ADR-009) and the explicit handlebars feature-
inspection posture. Full verification set green, including wasm
release and `--all-features`. `review-wave-2` is unblocked.
+513
View File
@@ -0,0 +1,513 @@
//! The standing conformance battery: the verification rows the core's
//! consumers and every later wave lean on, in one home so future waves
//! grow rows here rather than scattering them. Rows the wave-1 unit
//! tests and the wave-2 proof binaries already pin golden output are
//! re-exercised here, not restated: the battery reuses the proof
//! fixture files under `tests/fixtures/ssr_proof/` and
//! `tests/fixtures/code_proof/` and asserts the obligations themselves
//! (strict-mode matrix, error-surface completeness, two-sink equality)
//! against fresh renders through the public API.
use alkgen::{Error, EscapePolicy, TemplateSet};
use serde_json::json;
/// Register a well-formed fixture template, unwrapping registration
/// only for fixtures the battery authors itself in-file; rows under
/// test always go through the typed error surface.
fn fixture_template(set: &mut TemplateSet, name: &str, text: &str) {
set.register_template(name, text)
.expect("battery fixture template should be well-formed");
}
fn fixture_partial(set: &mut TemplateSet, name: &str, text: &str) {
set.register_partial(name, text)
.expect("battery fixture partial should be well-formed");
}
/// The shared two-sink row helper: the same context through both
/// sinks, with the sink outputs asserted byte-identical.
fn assert_both_sinks<T: serde::Serialize>(set: &TemplateSet, name: &str, context: &T) {
let string_sink = set
.render(name, context)
.unwrap_or_else(|_| panic!("{name} should render by the string sink"));
let mut byte_sink = Vec::new();
set.render_to_write(name, context, &mut byte_sink)
.unwrap_or_else(|_| panic!("{name} should render by the write sink"));
assert_eq!(
&byte_sink[..],
string_sink.as_bytes(),
"both sinks must agree byte for byte on {name}"
);
}
/// The context shape the hostile-values row renders over. The key
/// `raw` is avoided on purpose: handlebars treats that name as a
/// keyword-shaped expression and never resolves it, and the row
/// should stay inside ordinary data.
const HOSTILE_TEMPLATE: &str = "a{{value}}b{{& trusted}}c";
// ------------------------------------------------------------------
// Strict-mode behavior matrix (ADR-002 decision 2, core.md)
//
// Every row: a missing context variable is a typed `Error::Render`
// through both sinks, never silent output. Top-level variables and
// variables nested inside `{{#each}}` and `{{#if}}` branches are each
// covered, and the write sink's on-failure behavior is stated by its
// row: a typed error, with the partial bytes already streamed left in
// place (no atomicity; consumers that need it buffer first).
// ------------------------------------------------------------------
#[test]
fn strict_matrix_missing_top_level_variable_fails_typed_through_both_sinks() {
let mut set = TemplateSet::new(EscapePolicy::NoOp);
fixture_template(&mut set, "tpl", "hello {{name}}");
let context = json!({});
let string_err = set
.render("tpl", &context)
.expect_err("missing variable must fail the string sink, never render silently");
assert!(
matches!(string_err, Error::Render(_)),
"the string sink's failure must be a typed Error::Render, got: {string_err:?}"
);
let mut sink = Vec::new();
let write_err = set
.render_to_write("tpl", &context, &mut sink)
.expect_err("missing variable must fail the write sink, never render silently");
assert!(
matches!(write_err, Error::Render(_)),
"the write sink's failure must be a typed Error::Render, got: {write_err:?}"
);
assert_eq!(
String::from_utf8_lossy(&sink),
"hello ",
"the write sink streams incrementally: bytes rendered before the failure \
(the literal text ahead of the variable) stay in the target; consumers \
needing atomicity buffer first (the wave-1 contract)"
);
let present = set
.render("tpl", &json!({ "name": "world" }))
.expect("present variables still render");
assert_eq!(present, "hello world");
}
#[test]
fn strict_matrix_missing_variable_inside_each_branch_fails_typed_through_both_sinks() {
let mut set = TemplateSet::new(EscapePolicy::NoOp);
fixture_template(
&mut set,
"cells",
"{{#each items}}<td>{{label}}</td>{{/each}}",
);
let context = json!({ "items": [ { "label": "fine" }, {} ] });
let string_err = set
.render("cells", &context)
.expect_err("a missing variable inside an {{#each}} body must fail the string sink");
assert!(matches!(string_err, Error::Render(_)));
assert!(
format!("{string_err}").contains("label"),
"the strict-mode detail names the missing variable"
);
let mut sink = Vec::new();
let write_err = set
.render_to_write("cells", &context, &mut sink)
.expect_err("a missing variable inside an {{#each}} body must fail the write sink");
assert!(matches!(write_err, Error::Render(_)));
}
#[test]
fn strict_matrix_missing_variable_in_if_then_branch_fails_typed_through_both_sinks() {
let mut set = TemplateSet::new(EscapePolicy::NoOp);
fixture_template(
&mut set,
"banner",
"{{#if flag}}<div>{{banner_text}}</div>{{/if}}",
);
let then_context = json!({ "flag": true });
let string_err = set
.render("banner", &then_context)
.expect_err("a missing variable in a taken {{#if}} branch must fail the string sink");
assert!(matches!(string_err, Error::Render(_)));
assert!(format!("{string_err}").contains("banner_text"));
let mut sink = Vec::new();
let write_err = set
.render_to_write("banner", &then_context, &mut sink)
.expect_err("a missing variable in a taken {{#if}} branch must fail the write sink");
assert!(matches!(write_err, Error::Render(_)));
}
#[test]
fn strict_matrix_missing_variable_in_if_else_branch_fails_typed_through_both_sinks() {
let mut set = TemplateSet::new(EscapePolicy::NoOp);
fixture_template(
&mut set,
"banner",
"{{#if flag}}<div>{{banner_text}}</div>{{else}}<p>{{fallback}}</p>{{/if}}",
);
let else_context = json!({ "flag": false });
let string_err = set
.render("banner", &else_context)
.expect_err("a missing variable in a taken {{else}} branch must fail the string sink");
assert!(matches!(string_err, Error::Render(_)));
assert!(format!("{string_err}").contains("fallback"));
let mut sink = Vec::new();
let write_err = set
.render_to_write("banner", &else_context, &mut sink)
.expect_err("a missing variable in a taken {{else}} branch must fail the write sink");
assert!(matches!(write_err, Error::Render(_)));
}
#[test]
fn write_sink_failure_is_typed_and_carries_the_no_atomicity_contract() {
let mut set = TemplateSet::new(EscapePolicy::NoOp);
fixture_template(&mut set, "report", "prefix-ok\n{{missing}}\ntrailing");
let context = json!({});
let mut sink = Vec::new();
let write_err = set
.render_to_write("report", &context, &mut sink)
.expect_err("a strict failure past already-streamed text must still be a typed error");
assert!(matches!(write_err, Error::Render(_)));
assert_eq!(
String::from_utf8_lossy(&sink),
"prefix-ok\n",
"the write sink streams incrementally: bytes rendered before a failure stay \
in the target, so consumers needing atomicity buffer first (the wave-1 contract)"
);
}
// ------------------------------------------------------------------
// Error-surface completeness (core.md error model, convention 2)
//
// Every engine-owned failure path maps to exactly one typed variant
// with no catch-all, nothing survives a failed registration as a
// half-registered name, and no input the API accepts reaches a panic.
// ------------------------------------------------------------------
#[test]
fn error_surface_malformed_template_text_maps_to_registration_and_never_half_registers() {
let mut set = TemplateSet::new(EscapePolicy::NoOp);
fixture_template(&mut set, "keep", "ok");
let template_err = set
.register_template("broken", "{{#if name")
.expect_err("malformed template text must fail registration");
assert!(matches!(template_err, Error::Registration(_)));
assert!(
!set.is_template("broken"),
"a failed first registration must not survive as a half-registered name"
);
let partial_err = set
.register_partial("broken-partial", "{{#each items")
.expect_err("malformed partial text must fail registration");
assert!(matches!(partial_err, Error::Registration(_)));
assert!(!set.is_template("broken-partial"));
let overwrite_err = set
.register_template("keep", "{{#if name")
.expect_err("malformed overwrite text must fail registration");
assert!(matches!(overwrite_err, Error::Registration(_)));
assert!(
set.is_template("keep"),
"a failed overwrite leaves the previous registration and its kind intact"
);
}
#[test]
fn error_surface_unknown_template_name_maps_to_render_through_both_sinks() {
let mut set = TemplateSet::new(EscapePolicy::NoOp);
fixture_template(&mut set, "present", "ok");
let context = json!({});
let string_err = set
.render("no-such-template", &context)
.expect_err("an unknown template name must fail the string sink typed");
assert!(matches!(string_err, Error::Render(_)));
let mut sink = Vec::new();
let write_err = set
.render_to_write("no-such-template", &context, &mut sink)
.expect_err("an unknown template name must fail the write sink typed");
assert!(matches!(write_err, Error::Render(_)));
assert!(
sink.is_empty(),
"nothing leaks into the target when the name never resolves"
);
let present_err = set
.render("", &context)
.expect_err("an empty name is an unknown name too");
assert!(matches!(present_err, Error::Render(_)));
}
#[test]
fn error_surface_partial_as_render_target_maps_to_render_through_both_sinks() {
let mut set = TemplateSet::new(EscapePolicy::NoOp);
fixture_template(&mut set, "body", "{{> chunk}}");
fixture_partial(&mut set, "chunk", "chunk: {{value}}");
let context = json!({ "value": 1 });
let string_err = set
.render("chunk", &context)
.expect_err("a partial's name must be rejected as a render target");
assert!(matches!(string_err, Error::Render(_)));
let mut sink = Vec::new();
let write_err = set
.render_to_write("chunk", &context, &mut sink)
.expect_err("the write sink must reject a partial's name too");
assert!(matches!(write_err, Error::Render(_)));
}
#[test]
fn error_surface_strict_missing_variable_maps_to_render_through_both_sinks() {
let mut set = TemplateSet::new(EscapePolicy::NoOp);
fixture_template(&mut set, "tpl", "value: {{value}}");
let context = json!({});
let string_err = set
.render("tpl", &context)
.expect_err("the strict missing variable must fail the string sink typed");
assert!(matches!(string_err, Error::Render(_)));
let mut sink = Vec::new();
let write_err = set
.render_to_write("tpl", &context, &mut sink)
.expect_err("the strict missing variable must fail the write sink typed");
assert!(matches!(write_err, Error::Render(_)));
}
#[test]
fn error_surface_unknown_helper_maps_to_render_through_both_sinks() {
let mut set = TemplateSet::new(EscapePolicy::NoOp);
fixture_template(&mut set, "loud", "{{shout word}}");
let context = json!({ "word": "echo" });
let string_err = set
.render("loud", &context)
.expect_err("an unregistered helper must fail the string sink typed");
assert!(matches!(string_err, Error::Render(_)));
assert!(
format!("{string_err}").contains("shout"),
"the failure names the helper that was never registered"
);
let mut sink = Vec::new();
let write_err = set
.render_to_write("loud", &context, &mut sink)
.expect_err("an unregistered helper must fail the write sink typed");
assert!(matches!(write_err, Error::Render(_)));
}
#[test]
fn error_surface_hostile_context_values_render_clean_or_typed_never_panic() {
let hostile_values = [
"<script>alert(1)</script>",
"\"quoted\" & 'single' = equal",
"backslash \\ backtick ` brace }",
"&#x27;&#x3D;&amp;",
"emoji \u{1F6A0} unicode e-acute \u{00E9}",
"control \u{0000} byte and tab \t newline \n",
];
let mut html_set = TemplateSet::new(EscapePolicy::Html);
fixture_template(&mut html_set, "page", HOSTILE_TEMPLATE);
for (i, hostile) in hostile_values.iter().enumerate() {
let context = json!({ "value": hostile, "trusted": "<b>ok</b>" });
let rendered = html_set
.render("page", &context)
.expect("hostile interpolated values are data: they render typed-clean, never panic");
assert!(
!rendered.contains("<script>"),
"the html policy must not pass interpolated markup raw (case {i})"
);
assert!(
rendered.contains("<b>ok</b>"),
"the trusted slot stays deliberately raw (case {i})"
);
let mut sink = Vec::new();
html_set
.render_to_write("page", &context, &mut sink)
.expect("the write sink handles the same hostile values the same way");
assert_eq!(
&sink[..],
rendered.as_bytes(),
"both sinks agree on hostile values too (case {i})"
);
}
let exact = html_set
.render(
"page",
&json!({ "value": "<script>alert(1)</script>", "trusted": "<b>" }),
)
.expect("the pinned hostile value should render");
assert_eq!(
exact, "a&lt;script&gt;alert(1)&lt;/script&gt;b<b>c",
"html-policy escaping of the hostile value is entity-escaping, byte-pinned"
);
let mut noop_set = TemplateSet::new(EscapePolicy::NoOp);
fixture_template(&mut noop_set, "plain", HOSTILE_TEMPLATE);
for (i, hostile) in hostile_values.iter().enumerate() {
let context = json!({ "value": hostile, "trusted": hostile });
let rendered = noop_set
.render("plain", &context)
.expect("the no-op policy passes data through verbatim, never panicking");
assert_eq!(
rendered,
format!("a{hostile}b{hostile}c"),
"the no-op policy output is the input verbatim (case {i})"
);
let mut sink = Vec::new();
noop_set
.render_to_write("plain", &context, &mut sink)
.expect("the write sink handles the same hostile values the same way");
assert_eq!(
&sink[..],
rendered.as_bytes(),
"both sinks agree on hostile values too (case {i})"
);
}
}
// ------------------------------------------------------------------
// Two-sink correctness rows (core.md render entry point)
//
// For each fixture posture, `render` and `render_to_write` must
// produce byte-identical output on the same context. Both postures
// re-exercise the wave-2 proof fixture files; their goldens stay
// pinned by the proof binaries.
// ------------------------------------------------------------------
/// The ssr-shaped fixture posture: the wave-2 ssr proof's page and
/// fragment partials under the HTML escaper, with the caller-supplied
/// card text registering through the same API (ADR-008 machinery
/// equality).
fn ssr_shaped_set() -> TemplateSet {
const PAGE: &str = include_str!("fixtures/ssr_proof/page.hbs");
const HEADER: &str = include_str!("fixtures/ssr_proof/header.hbs");
const NAV: &str = include_str!("fixtures/ssr_proof/nav.hbs");
const FOOTER: &str = include_str!("fixtures/ssr_proof/footer.hbs");
const CARD_TEXT: &str =
"<article class=\"card\" data-kind=\"{{kind}}\"><h3>{{title}}</h3><p>{{body}}</p></article>";
let mut set = TemplateSet::new(EscapePolicy::Html);
fixture_template(&mut set, "page", PAGE);
fixture_template(&mut set, "card", CARD_TEXT);
fixture_partial(&mut set, "header", HEADER);
fixture_partial(&mut set, "nav", NAV);
fixture_partial(&mut set, "footer", FOOTER);
set
}
fn ssr_shaped_context() -> serde_json::Value {
json!({
"site_name": "Alk Forge",
"tagline": "Your repositories",
"nav_links": [
{ "href": "/", "label": "Home" },
{ "href": "/search?q=<script>&lang=en", "label": "<script>alert('x')</script>" }
],
"cards": [
{ "kind": "repo", "title": "Repo \"settings\" & <config>", "body": "if (a < b) { alert(\"<b>hi</b>\") }" },
{ "kind": "link", "title": "Plain card", "body": "A plain body." }
],
"show_banner": true,
"banner_text": "<em>scheduled</em> maintenance",
"trusted_embed": "<aside id=\"notice\"><b>system notice</b></aside>",
"year": 2026
})
}
#[test]
fn two_sinks_agree_byte_for_byte_on_the_ssr_shaped_fixture_posture() {
let set = ssr_shaped_set();
let context = ssr_shaped_context();
assert_both_sinks(&set, "page", &context);
let card_context = &context["cards"][0];
assert_both_sinks(&set, "card", card_context);
let rendered = set
.render("page", &context)
.expect("the ssr-shaped posture should render");
assert!(
rendered.contains(r#"<aside id="notice"><b>system notice</b></aside>"#)
&& rendered.contains(r#"&lt;script&gt;alert(&#x27;x&#x27;)&lt;/script&gt;"#),
"the exercised row is the real ssr posture: hostile data escaped, trusted slot raw"
);
}
/// The code-shaped fixture posture: the wave-2 code proof's writer and
/// validator templates over the composed field-write-line partial
/// under the no-op escaper, with document-ordered fields and
/// builder-decided call names carried straight into the context.
fn code_shaped_set() -> TemplateSet {
const WRITER: &str = include_str!("fixtures/code_proof/writer.hbs");
const VALIDATOR: &str = include_str!("fixtures/code_proof/validator.hbs");
const FIELD_WRITE_LINE: &str = include_str!("fixtures/code_proof/field_write_line.hbs");
let mut set = TemplateSet::new(EscapePolicy::NoOp);
fixture_template(&mut set, "writer", WRITER);
fixture_template(&mut set, "validator", VALIDATOR);
fixture_partial(&mut set, "field-write-line", FIELD_WRITE_LINE);
set
}
#[test]
fn two_sinks_agree_byte_for_byte_on_the_code_shaped_fixture_posture() {
let set = code_shaped_set();
let context = json!({
"struct_name": "PacketHeader",
"fields": [
{ "name": "zeta", "writer_call": "write_str", "check_call": "check_str_len", "is_string": true, "max_len_const": "ZETA_MAX" },
{ "name": "magic", "writer_call": "write_u64", "check_call": "check_u64", "is_string": false, "max_len_const": "MAGIC_MAX" },
{ "name": "alpha", "writer_call": "write_bool", "check_call": "check_bool", "is_string": false, "max_len_const": "ALPHA_MAX" },
{ "name": "length", "writer_call": "write_str", "check_call": "check_str_len", "is_string": true, "max_len_const": "LENGTH_MAX" }
],
"default_consts": [
{ "const_name": "ZETA_DEFAULT", "literal": "\"v<1>&\\\"raw\\\"\"" }
]
});
assert_both_sinks(&set, "writer", &context);
assert_both_sinks(&set, "validator", &context);
let rendered = set
.render("writer", &context)
.expect("the code-shaped posture should render");
assert!(
rendered.contains(r#"out.write_str(&self.zeta)?;"#)
&& rendered.contains(r#"pub const ZETA_DEFAULT: &str = "v<1>&\"raw\"";"#),
"the exercised row is the real code posture: document order and verbatim literals"
);
let validator = set
.render("validator", &context)
.expect("the validator should render");
assert!(
validator.contains("check_str_len(&self.zeta, ZETA_MAX)?;")
&& validator.contains("check_u64(&self.magic)?;"),
"the validator half rides the same document-ordered context"
);
}