plan: form-a.1 T7 expanded to handle 5 raw-JSON-inspect tests

T1-5 dispatch journal surfaced 4 additional e2e.rs tests beyond
the originally-named diff_detects_changed_def that raw-read
.ail.json bytes and will break at T8 deletion:

- borrow_own_demo_modes_are_metadata_only (substring assertions)
- reuse_as_demo_under_rc_uses_inplace_rewrite (substring assertions)
- render_parse_round_trip_canonical (whole-corpus property already
  pinned by T1's cli_parse_then_render_then_parse_is_idempotent —
  retire as redundant)
- ail_run_accepts_ail_source_with_same_stdout_as_ail_json (dual-form
  smoke — re-author to derive the .ail.json on-the-fly via ail parse
  on a working fixture like hello.ail)

T7 grows from 4 steps to 7 steps; net test-count change −1 (retire
render_parse_round_trip_canonical). T7 close: 559 tests green.
This commit is contained in:
2026-05-13 11:13:33 +02:00
parent 77b28ad64d
commit 9332d1e155
+109 -3
View File
@@ -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/<name>.ail.json")` line and replace with a `ail parse examples/<name>.ail -o <tempfile>` + 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**