Skip to content

Consolidate test/*.sh's duplicated ok()/no() PASS/FAIL helpers into one sourced file? #305

Description

@s0undt3ch

Context

While closing out the review round on #293, two new gates (test/hooksembedrecursivecheck.sh,
test/shimselfunverifiedcheck.sh) landed with an ok() missing the house ; return 0; tail —
test/gateexitcheck.sh's own (G1) check caught it, because that omission makes a failed write
inside ok() report the ARM as a shell-level failure to any cmd && ok ... || no ... chain instead
of going through the fail= accumulator.

That's the second time this exact shape of bug has been introduced by hand-copying ok()/no()
into a new gate file. A quick audit turned up two pre-existing instances of the same bug already
live on main, in test/pargatescheck.sh and test/type3clonecheck.sh — unrelated to #293, not
touched here, flagging for visibility.

What we found

ok()/no() are defined inline, per-file, in 624 of test/*.sh. The landscape:

  • 591/624 share one byte-identical dominant shape:
    ok(){ printf ' PASS %s\n' "$*" || { fail=1; printf ' FAIL could not write the PASS line for: %s\n' "$*"; }; return 0; }
    no(){ printf ' FAIL %s\n' "$*"; fail=1; }
  • 27 use echo+$1 instead of printf+$* — spot-checked a sample; every call site passes a
    single quoted string, so $1$* there. Looks safe to normalize.
  • 2 (test/jsoncheck.sh, test/jsonparitycheck.sh) redirect to stderr deliberately — several
    arms capture a verb's raw --json payload from stdout, and PASS/FAIL text there would corrupt it.
    A real, intentional variant, not something to collapse away.
  • 1 (test/lb3namecheck.sh) uses FAIL (uppercase) as its accumulator, self-consistently
    throughout that file. Also a deliberate override, not a bug.
  • 2 (test/pargatescheck.sh, test/type3clonecheck.sh) already carry the missing-return 0
    bug today, live on main.

Why we stopped short of just fixing it

Two existing anti-regression checks hard-assume ok()/no() are defined inline, in the same
file
, not sourced from a shared helper:

  1. test/gateexitcheck.sh's (G1) check globs test/*.sh (non-recursive) for literal ok(){...}
    definitions and attributes each one's own behavioral contract back to the file containing it. If
    the dominant shape moved out of 591 files into one sourced helper, the set of inline definitions
    it walks would collapse toward empty — tripping the gate's own "no ok() definition found
    anywhere — the sweep is inert" guard. Not a false positive: a real "this scan can no longer see
    anything" condition.
  2. test/manifestcheck.sh's (I2) check independently scans every test/*.sh for a call to
    ok/no/pass/warn and requires a same-file definition, specifically to catch "calls a
    helper it never defines → silent no-op, exits 0 anyway." Every migrated file would trip this too.

Both checks have their own history/comment explaining a real bug they once caught, so weakening
them to special-case a sourcing line is itself a design decision with a real chance of quietly
reopening what they guard against — and neither is a small patch; both need new logic to resolve
. ".../gateresult.sh" and correctly attribute coverage across ~600 files from one shared
definition.

Question for the maintainers

Is this worth doing, and if so, in what shape? Options as we see them:

  • (a) Teach gateexitcheck.sh and manifestcheck.sh to recognize a sourced
    test/lib/gateresult.sh (or similar — test/lib/ already holds headbinlib.sh,
    unset-agent-env-variables.sh) as equivalent coverage, verified on a couple of hand-migrated
    gates first, then do the ~624-file mechanical migration as a second, separate change.
  • (b) Leave it as-is — 624 files of controlled duplication with two live static checks against
    drift is arguably an acceptable, if unusual, tradeoff, and the actual bug rate (2 known instances
    in the project's whole history) may not justify the harness-logic risk.
  • (c) Something else — happy to take direction on the shape you'd want if this is worth doing
    at all.

Happy to do the work either way once there's a steer; didn't want to force a specific migration
design without checking first, given it touches nearly every test file in the tree.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions