Skip to content

fix(test-harness): run the built CLI, not a packaged binary - #48

Merged
kinlane merged 1 commit into
mainfrom
fix/harness-without-binary
Aug 3, 2026
Merged

fix(test-harness): run the built CLI, not a packaged binary#48
kinlane merged 1 commit into
mainfrom
fix/harness-without-binary

Conversation

@kinlane

@kinlane kinlane commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Closes #35 — option 3, per your decision. Refs #3, #10, #11, #29.

You asked me to verify rather than assume. Here is what I actually ran.

First: the diagnosis in #35 was too generous to the toolchain

It is not that pkg lacks a Windows target. The two branches of build-binary disagree about the version format.

# Unix — normalizes to the major version
NODE_VERSION=$(echo "18.20.8" | awk -F "." '{ print $1 }')   # -> node18-linux
# Windows — interpolates raw
--targets node${{ inputs.node-version }}-windows              # -> node18.20.8-windows

pkg publishes bases per major version, hence No available node version satisfies 'v18.20.8'. Note it complained about the version, not the platform. Inherited and long invisible, because test-windows only ran on direct develop pushes upstream with its harness step gated on a matrix key that did not exist.

What I verified locally

Install and build both succeed on Node 25.2.1 — itself a data point for the open criterion on #3.

Then, pointing the harness at dist/index.js:

Attempt Result
bin: 'node <dist>' 66/66 fail/bin/sh: node: command not found
bin: '"<process.execPath>" "<dist>"' 65/66 pass

That first result is the finding worth keeping: scenarios are spawned with a scrubbed environment that has no PATH — which is precisely why an absolute, self-contained binary worked here before. Anything relying on PATH silently fails everywhere at once.

The one remaining failure is help-no-document, and it is not this change. Its own faketty.sh calls script -qfec, util-linux syntax that BSD script rejects:

$ bash faketty.sh echo hello
script: illegal option -- f

It fails identically with a trivial command and no {bin} involved. macOS-local artifact.

What I changed

  • generate-tests.tsbin is now process.execPath plus the built entry point, overridable via SPECTRAL_BIN so the real binary can still be exercised at release time. Decoupling the per-commit job from packaging should not make the packaged artifact untestable.
  • win.ts — PowerShell parses a statement beginning with a quoted string as a string literal, so it would echo the path rather than run it. Added the call operator when {bin} is a quoted interpreter path.
  • Both harness actions — the build-binary step is removed. yarn build already runs in every job that reaches the harness.

Lint caught a prettier error in my first draft before merge, which is #33 and #37 doing their job — the first time today that happened in the right order.

What I have not verified, and cannot

Windows. The PowerShell call-operator fix is reasoned, not tested — I have no Windows machine. CI is the only proof, and given the day's record I would rather say that plainly than imply confidence I do not have. If test-windows fails on this PR, the fix is wrong and I will say so.

This is also why the PR matters beyond #35: test-windows currently reddens every pull request, which blocks turning on required status checks in #10.

Option 3 from #35. The harness spawned packages/cli/binaries/spectral — the
standalone binary produced by @yao-pkg/pkg — so a per-commit test job was gated
on producing a distributable artifact. When packaging broke, the whole harness
was unrunnable, and it read as a test failure rather than a packaging one.

That is exactly what happened on Windows, and the root cause is narrower than
#35 first described. The two branches of build-binary disagree about the version
format. The Unix branch normalizes to the major version:

    NODE_VERSION=$(echo "18.20.8" | awk -F "." '{ print $1 }')   # node18-linux

The Windows branch interpolates the input raw:

    --targets node${{ inputs.node-version }}-windows             # node18.20.8-windows

pkg publishes bases per major version, so it reported "No available node version
satisfies 'v18.20.8'". Note it complained about the version, not the platform.
Inherited, and long invisible: test-windows only ran on direct develop pushes
upstream, with its harness step gated on a matrix key that did not exist.

The harness now runs the built CLI. Two things this required, both verified
locally rather than assumed:

  - process.execPath, not a bare `node`. Scenarios are spawned with a scrubbed
    environment containing no PATH — which is precisely why an absolute,
    self-contained binary worked here before. A bare `node` gives
    "/bin/sh: node: command not found" on all 66 suites.

  - A PowerShell call operator on Windows. A statement beginning with a quoted
    string is parsed as a string literal, not a command, so {bin} as a quoted
    interpreter path plus script path would be echoed instead of executed.

SPECTRAL_BIN overrides the default, so the real binary can still be exercised at
release time by pointing it at packages/cli/binaries/spectral. Decoupling the
per-commit job from packaging does not mean the packaged artifact stops being
testable.

The build-binary step is removed from both harness actions, since yarn build has
already produced dist in every job that runs them.

Verified locally on macOS, Node 25.2.1: 65 of 66 harness suites pass. The 66th,
help-no-document, fails because its own faketty.sh helper calls `script -qfec`,
which is util-linux syntax that BSD script does not accept — it fails identically
with a trivial command and is unrelated to this change.

Windows correctness is NOT verified locally and cannot be. CI is the proof.

Closes #35
Refs #3, #10, #11, #29

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@kinlane kinlane left a comment

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.

Not 100% sure, but going for it.

@kinlane
kinlane merged commit 2a10c58 into main Aug 3, 2026
4 of 6 checks passed
kinlane added a commit that referenced this pull request Aug 3, 2026
…ges (#49)

* fix(test-harness): invalidate generated tests when the generator changes

main is red. #48 passed locally and failed in CI, and this is why.

The harness generates a .test.js per scenario, baking the {bin} command into the
generated file at generation time. Generation is incremental: fileEntryCache
tracks the scenario files and the generated outputs, and only regenerates
scenarios whose inputs changed. Nothing tracked the generator itself.

So #48 changed which command the tests run, no scenario file moved, and CI
restored a previously generated tests/ directory together with the cache that
declared it current. Result: the suites kept executing the old packaged-binary
path, which no longer exists:

    /bin/sh: 1: /__w/.../packages/cli/binaries/spectral: not found

Two layers, because either alone leaves a hole.

generate-tests.ts now tracks __filename alongside the scenarios. If the generator
changed, every scenario is regenerated rather than only those with changed
inputs.

The CI cache key now includes hashFiles('test-harness/**') — and, critically, so
does the restore-keys PREFIX. The prefix was the actual hole: an exact-key miss
fell back to test-harness-<os>-<node>-, which happily restored tests generated by
an older generator.

Verified by reproducing the failure locally before fixing it. The subtlety is
that it only appears at steady state: after a run with an empty tests/ directory,
the next run sees the generated files as newly tracked and regenerates anyway. It
takes three runs to reach the state CI restores into.

  control, no fix, steady-state cache, generator changed  ->  dist/index.js  (stale)
  with fix,          same cache,       generator changed  ->  dist/FIXED.js  (correct)

Harness still passes 65/66 locally. The 66th is help-no-document, whose own
faketty.sh uses util-linux `script -qfec` syntax that BSD script rejects; it
fails identically with a trivial command and is unrelated.

Refs #35, #29, #33

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(test-harness): quote the bin command for PowerShell on Windows

Second Windows attempt, and the first one was wrong for a reason worth recording.

win.ts wraps every command in:

    powershell -Command "& { cd '<cwd>';<command>;echo LASTEXITCODE=$LASTEXITCODE }"

The whole thing is already inside double quotes. So emitting {bin} as

    "C:\Program Files\nodejs\node.exe" "...\dist\index.js"

terminated that wrapper, and PowerShell saw an unquoted path that split on its
space:

    & : The term 'C:\Program' is not recognized as the name of a cmdlet...

The call operator I added was firing correctly; the quoting around it was not
surviving. This never came up before because the old {bin} was a single path with
no spaces (binaries/spectral.exe), so it needed no quoting at all.

Quoting is now platform-aware: PowerShell single quotes are literal and pass
through the wrapper intact, while POSIX shells keep double quotes. win.ts
recognises either quote character when deciding to prefix the call operator.

Also fixes the prettier error the lint job caught on the previous commit.

Local state unchanged: 65/66 on macOS, the 66th being help-no-document's BSD
`script` incompatibility.

Windows remains unverified locally and cannot be verified locally. CI is the only
proof, and this is the second attempt at it — stated plainly rather than dressed
up as confidence.

Refs #35, #29

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

Windows: the test suite passes, but the binary build has no Node 18.20.8 target

1 participant