diff --git a/docs/plans/2026-05-13-iter-form-a.1.md b/docs/plans/2026-05-13-iter-form-a.1.md index 5b0bb3d..4cbfb28 100644 --- a/docs/plans/2026-05-13-iter-form-a.1.md +++ b/docs/plans/2026-05-13-iter-form-a.1.md @@ -659,12 +659,118 @@ Expected: PASS, 561 tests green. --- -### Task 7: Re-author the e2e.rs synthesised-diff test +### Task 7: Re-author e2e.rs raw-JSON-inspect tests (5 tests) **Files:** -- Modify: `crates/ail/tests/e2e.rs:695-735` +- Modify: `crates/ail/tests/e2e.rs` — 5 tests total per iter form-a.1 (T1-5) journal Known-debt section -The test loads `sum.ail.json` raw bytes, mutates the JSON to produce `sum_v2.ail.json`, and runs `ail diff` on the pair. After Task 8 deletes `sum.ail.json`, the test breaks. Re-author to derive `sum.ail.json` on-the-fly via `ail parse`. +The iter form-a.1 (T1-5) journal surfaced that 5 tests in +`crates/ail/tests/e2e.rs` raw-read `.ail.json` bytes for substring +asserts / mutation / byte-roundtrip comparison. They pass today +(`.ail.json` files still present pre-T8) but break at T8 when the +non-carve-out `.ail.json` siblings are deleted. The five tests are: + +1. `diff_detects_changed_def` — loads `sum.ail.json`, mutates the + JSON to produce `sum_v2.ail.json`, runs `ail diff` on the pair. + (This was T7's originally-named test.) +2. `borrow_own_demo_modes_are_metadata_only` — raw-reads `borrow_own_demo.ail.json` for substring assertions about mode annotations. +3. `reuse_as_demo_under_rc_uses_inplace_rewrite` — raw-reads `reuse_as_demo.ail.json` for substring assertions about reuse-as encoding. +4. `render_parse_round_trip_canonical` — byte-roundtrips a chosen `.ail.json` through `ail render | ail parse` for byte-equal assertion. (Note: `cli_parse_then_render_then_parse_is_idempotent` from T1 already pins this property across the whole `.ail` corpus; this test may simply be retired.) +5. `ail_run_accepts_ail_source_with_same_stdout_as_ail_json` — dual-form smoke: runs a fixture as both `.ail` and `.ail.json` and asserts identical stdout. Structurally requires BOTH forms to exist; post-T8 only the eight carve-outs have `.ail.json`. The seven §C4(a) carve-outs are negative-test fixtures (parse-rejected or load-rejected), so they cannot serve as the dual-form smoke fixture either. `prelude.ail.json` is dual-form post-iter (per §C4 (b)), but prelude is not user-runnable directly. + +- [ ] **Step 1: Inspect the five test bodies** + +Read the five tests in `crates/ail/tests/e2e.rs`. For each, note: +- the exact `.ail.json` filename read. +- the assertion shape (substring / byte-equal / stdout-match). +- whether the test's subject is JSON-AST byte mechanics (which require the JSON form) or program behaviour (which is form-agnostic). + +- [ ] **Step 2: Re-author `diff_detects_changed_def` (test 1)** + +This is the test originally named in the plan. The subject is JSON-byte mutation for `ail diff`. Replace the read-from-disk with a parse-derive: + +```rust +// before — reads JSON bytes directly: +let json = std::fs::read(workspace.join("examples").join("sum.ail.json")) + .expect("read sum.ail.json"); +let sum_v1 = String::from_utf8(json).unwrap(); +// after — derive the canonical JSON from sum.ail via `ail parse`: +let tmpdir = tempfile::tempdir().unwrap(); +let v1_path = tmpdir.path().join("sum_v1.ail.json"); +std::process::Command::new(ail_bin()) + .args([ + "parse", + workspace.join("examples").join("sum.ail").to_str().unwrap(), + "-o", + v1_path.to_str().unwrap(), + ]) + .status() + .expect("ail parse") + .success(); +let sum_v1 = std::fs::read_to_string(&v1_path).expect("read v1"); +``` + +The downstream mutation logic (creating `sum_v2.ail.json` with byte mutation) and `ail diff` invocation continue against tempfile paths. + +- [ ] **Step 3: Re-author `borrow_own_demo_modes_are_metadata_only` (test 2) and `reuse_as_demo_under_rc_uses_inplace_rewrite` (test 3)** + +Both raw-read `.ail.json` for substring assertions about mode/reuse-as encoding. The substring is in the JSON bytes; we derive the JSON via `ail parse` on the `.ail` source, then assert against the temp-file content. Same pattern as Step 2. + +For each test, find the `std::fs::read*("examples/.ail.json")` line and replace with a `ail parse examples/.ail -o ` + read of the tempfile. + +- [ ] **Step 4: Retire `render_parse_round_trip_canonical` (test 4)** + +This test's property (CLI render→parse byte-roundtrip) is already pinned across the entire `.ail` corpus by `cli_parse_then_render_then_parse_is_idempotent` (added in T1). Delete the redundant test body — the new test is strictly stronger (whole-corpus vs. one-fixture). + +Verify by inspection that the new T1 test covers the same property; if so, `git rm`-equivalent inline delete (remove the `#[test] fn render_parse_round_trip_canonical(...)` block from `e2e.rs`). + +- [ ] **Step 5: Re-author `ail_run_accepts_ail_source_with_same_stdout_as_ail_json` (test 5)** + +This test originally proved that the CLI accepts both extensions for a single program. Post-iter form-a.1, only carve-outs have `.ail.json` form, and carve-outs are negative-test fixtures (load/parse rejection — they don't have stdout to compare). + +Re-author into a structurally equivalent test that proves the same property without needing a dual-form fixture: render an existing `.ail` to a tempdir `.ail.json` via `ail parse`, run `ail run` against both the original `.ail` and the tempdir `.ail.json`, assert identical stdout. + +```rust +// derive a parallel .ail.json in a tempdir from the existing .ail +let tmpdir = tempfile::tempdir().unwrap(); +let json_path = tmpdir.path().join("dual_form.ail.json"); +std::process::Command::new(ail_bin()) + .args([ + "parse", + workspace.join("examples").join("hello.ail").to_str().unwrap(), + "-o", + json_path.to_str().unwrap(), + ]) + .status() + .expect("ail parse") + .success(); +// then run both forms and compare stdout +let ail_stdout = run_ail_run(workspace.join("examples").join("hello.ail")); +let json_stdout = run_ail_run(&json_path); +assert_eq!(ail_stdout, json_stdout, "dual-form stdout mismatch"); +``` + +Pick `hello.ail` (small, no I/O, predictable stdout) as the fixture. If the test originally used a different fixture, pick whichever has a stable single-line stdout. + +- [ ] **Step 6: Run e2e.rs** + +Run: + +``` +cargo test -p ail --test e2e +``` + +Expected: PASS. If a re-authored test still references a deleted carve-out path or a renamed helper, the implementer-phase repairs. + +- [ ] **Step 7: Final cargo test for Task 7** + +Run: + +``` +cargo test --workspace +``` + +Expected: PASS, 559 tests green. Task 7 net change: −1 test (`render_parse_round_trip_canonical` retired in Step 4). 560 − 1 = 559. - [ ] **Step 1: Inspect the current test body**