Skip to content

Add test coverage for defensive branches in JSON parser - #1536

Merged
sergey-shandar merged 5 commits into
mainfrom
claude/module-coverage-improvement-4ykmg5
Aug 13, 2026
Merged

Add test coverage for defensive branches in JSON parser#1536
sergey-shandar merged 5 commits into
mainfrom
claude/module-coverage-improvement-4ykmg5

Conversation

@sergey-shandar

Copy link
Copy Markdown
Contributor

This change adds test cases to cover defensive branches in the JSON parser module that are unreachable through normal parse execution but exist as safeguards.

Summary
Added three new test suites to the proof object that exercise defensive code paths in the JSON parser:

Key changes made

  • endArray tests: Added nullTop test case that directly invokes endArray with a null top state, which is normally set by startArray and should never be null during regular parsing
  • endObject tests: Added nonObjectTop test case that directly invokes endObject with a non-object top (an array instead), which is normally guaranteed to be an object during regular parsing
  • tokenToValue tests: Added nonValueToken test case that directly invokes tokenToValue with an EOF token, which should never occur since isValueToken guards against non-value tokens during normal parsing

Notable implementation details

  • Each test includes explanatory comments documenting why the branch is unreachable through normal parse execution
  • Tests verify that these defensive branches return appropriate null/result values
  • All tests use direct function invocation to bypass the normal parsing flow and reach these otherwise inaccessible code paths

https://claude.ai/code/session_01BmWCv5YGRXToq26xPoSXjX

endArray/endObject's null/non-object top guards and tokenToValue's
default arm are unreachable through parse() itself (the state machine
guarantees the invariants), so exercise them with direct calls,
matching the existing pushKey.nonObjectTop pattern. Brings
media/json/parser/module.f.mjs to 100% line/branch/function coverage.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
functionalscript a2a2375 Commit Preview URL

Branch Preview URL
Aug 13 2026, 10:34 PM

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving.

Head note: the head moved while I was reviewing, from d92da09b to
b9ae16f3. I re-verified everything below at b9ae16f3. The move was a merge
of main, and main had advanced to fddc5dca (#1535). Against that new
baseline the PR's entire delta is still exactly the 33-line proof addition to
fjs/media/json/parser/module.f.mjsgit diff $(git merge-base origin/main b9ae16f3) b9ae16f3 is that one file — so the merge brought in nothing of its
own. Baseline for every number below is origin/main = fddc5dca.

Are these branches actually unreachable?

Each case documents its branch as unreachable through parse. I tested that
rather than taking it: I replaced all four defensive arms (the three new ones
plus the pre-existing pushKey one) with throw, then ran parse over every
token sequence up to length 6 over the full 13-kind JsonToken alphabet —
5,229,043 sequences
. Zero reached any of the four.

Negative control: instrumenting the live arm of endArray instead fired 22
times within depth 3 alone, so the harness does detect reachability.

The comments are therefore accurate, and calling the internals directly is the
right way in — same pattern as btree/remove's proof.throw group. No branch
turned out to be reachable, so there is no more interesting finding hiding here.

Two-sided mutation testing

Six mutations, each applied to the PR tree and then identically to
origin/main:

mutation kills on PR kills on main@fddc5dca
endArray: drop the top === null arm endArray.nullTop (TypeError) nothing — 2549/2549 pass
endArray: defensive arm yields [] not null endArray.nullTop nothing — 2549/2549
endObject: drop the non-object arm endObject.nonObjectTop nothing — 2549/2549
endObject: defensive arm yields {} not null endObject.nonObjectTop nothing — 2549/2549
tokenToValue: delete the default arm tokenToValue.nonValueToken nothing — 2549/2549
tokenToValue: default yields false not null tokenToValue.nonValueToken nothing — 2549/2549

Every mutation kills exactly one case, and it is the case that targets it — 50
of 51 parser proofs still pass in each run. So each case is doing work no
existing test was doing.

The two weaknesses this PR family keeps hitting

  • #1518, message-pinning. Not present. None of the three new cases asserts
    on an error message; endArray/endObject assert status === 'result' and
    tokenToValue asserts the returned value directly.

  • #1525/#1528, pinning one field while a mutant clobbers another. Also not
    present, and this is the part I most expected to find. Both endArray and
    endObject assert status and value. Rows 2 and 4 above are exactly the
    "right tag, wrong payload" mutant: the arm still returns status: 'result'
    but yields [] / {} instead of null, and both are killed. assertEq is
    === (fjs/asserts/module.f.mjs:31-34), so the reference comparison is what
    catches them. The 'result' state carries no third field to clobber.

    This is a different situation from #1535, where proof.throw asserting only
    that a call throws was correctly judged fine: here the cases return normally,
    so the value assertion is the only thing standing between a mutant and a pass,
    and it is present.

Battery

  • npx tsc --noEmit → 0. npm run prepack from a clean tree → 0, both trees.
  • npm test: 2552 / 0 fail vs 2549 on main. +3 = the three new cases.
  • Coverage delta (npm run cov, real numbers — the tool is not vacuous in
    this environment, it runs the full suite):
    fjs/media/json/parser/module.f.mjs branches 97.22% → 100.00%; lines and
    functions were already 100% and stay there. All files: branches
    98.27% → 98.35%, lines 99.94% unchanged.
  • Public surface after prepack: 527 type aliases and 889 consts, identical
    on both trees
    (46,199 / 64,198 bytes, non-empty). No unprefixed type added,
    nothing widened. The parser's emitted .d.mts keeps its @module header.
  • linkcheck: 159 on both trees, identical sets.
  • CHANGELOG (§8.3): none needed and none added — this is proof-only, touching
    nothing but the proof block, with an unchanged public surface. Correct.

No findings.

@sergey-shandar
sergey-shandar marked this pull request as ready for review August 13, 2026 22:22

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12c87f705f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/media/json/parser/module.f.mjs Outdated
Comment on lines +220 to +223
// `endArray` is only ever invoked while `state.top` is the array
// being closed (set by `startArray`), so `top === null` is a
// defensive branch unreachable through `parse`. Call it directly.
nullTop: () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove unreachable branches instead of testing invalid states

These comments establish that top === null is unreachable through parse, yet the test bypasses the parser to preserve and cover that dead branch. This makes the coverage result depend on behavior for an impossible internal state rather than simplifying the implementation; remove the branch and its direct test, and apply the same correction to the newly tested unreachable branches in endObject and tokenToValue.

AGENTS.md reference: AGENTS.md:L150-L158

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bb79b67: restructured endArray/endObject to type-assert state.top instead of branching on it (the state machine already guarantees the container type there), and narrowed tokenToValue's parameter to a new _ValueToken type derived from isValueToken's type guard, so its switch is exhaustive with no defensive default arm. All three unreachable branches from this PR are gone, along with their direct-invocation tests; coverage stays at 100% line/branch/function for the file.


Generated by Claude Code

@sergey-shandar
sergey-shandar added this pull request to the merge queue Aug 13, 2026
@sergey-shandar
sergey-shandar removed this pull request from the merge queue due to a manual request Aug 13, 2026
Per AGENTS.md §3.2, genuinely unreachable branches should be
eliminated rather than covered by direct-invocation tests. Address
review feedback on PR #1536 (chatgpt-codex-connector) by:

- endArray/endObject: drop the top===null / non-object guards and
  type-assert state.top instead, since the state machine already
  guarantees the container type at those call sites.
- tokenToValue: narrow its parameter to a new _ValueToken type (the
  union isValueToken already establishes as a type guard) so the
  switch is exhaustive without a defensive default arm.

This removes the three direct-call proof cases added for those
branches; the pre-existing pushKey.nonObjectTop case is untouched.
media/json/parser/module.f.mjs stays at 100% line/branch/function
coverage.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb79b674d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

*/
const endArray = state => {
const array = state.top !== null ? toArray(state.top.values) : null
const array = toArray(/** @type {_JsonArray} */ (state.top).values)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the required CHANGELOG entry

This PR changes authored parser code, but the ## Unreleased section of CHANGELOG.md has no entry for PR #1536. Add the required short entry with the real PR number before merging; the repository workflow explicitly requires a CHANGELOG entry for every code-changing PR.

AGENTS.md reference: AGENTS.md:L133-L135

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in a2a2375.


Generated by Claude Code

@sergey-shandar
sergey-shandar added this pull request to the merge queue Aug 13, 2026
@sergey-shandar
sergey-shandar removed this pull request from the merge queue due to a manual request Aug 13, 2026
@sergey-shandar
sergey-shandar added this pull request to the merge queue Aug 13, 2026
Merged via the queue into main with commit e7e1881 Aug 13, 2026
19 checks passed
@sergey-shandar
sergey-shandar deleted the claude/module-coverage-improvement-4ykmg5 branch August 14, 2026 06:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants