Skip to content

test: extend the spawn-fallback contract to the importer sandbox helpers - #1217

Merged
mrbobbytables merged 2 commits into
mainfrom
quality/test-importer-sandbox-spawn-fallbacks
Oct 9, 2026
Merged

mrbobbytables merged 2 commits into
mainfrom
quality/test-importer-sandbox-spawn-fallbacks

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Test Improvement

Closes #1216.

tests/helpers-spawn-fallbacks.test.mjs pins one contract shared by every
spawn-based sandbox helper: the helper normalises the spawnSync result before
handing it to a test (status ?? 1, stdout ?? '', stderr ?? ''), so a child
that never started surfaces as a plain non-zero run with empty output rather
than leaking null/undefined into an assertion. spawnSync reports a failed
spawn as status: null with stdout/stderr undefined rather than throwing,
so that normalisation is the whole difference between "the importer failed" and
"the harness could not start it".

That file covers runScriptWithFixtures, runScriptWithFetchMock and
runWithGhStub. runImportArchitectures and runImportArchitectureIssue were
left out, and they back the only coverage the two importers have —
import-architectures.test.mjs, import-architectures-fallbacks.test.mjs,
import-architecture-issue.test.mjs and
import-architecture-issue-project-href.test.mjs.

Adds tests/helpers-import-sandbox-fallbacks.test.mjs, 4 tests:

  • runImportArchitectures degrades cleanly — status is 1,
    stdout/stderr are '', and the sandbox readers still work: the fixture
    the helper staged before the spawn is readable, while the record only the
    importer would have written is absent. That is what keeps a harness fault
    distinguishable from an importer that ran and wrote nothing.
  • runImportArchitectureIssue degrades cleanly — same contract;
    issue.json survives a spawn that never happened, catalog.json does not.
  • the mkfifo guard fails loudly — the helper shells out to mkfifo
    because Node has no binding for it and treats a non-zero exit as a broken
    fixture rather than a skip, but nothing checked that. A silent skip would let
    import-architectures-fallbacks.test.mjs's "a special file in images/ is
    dropped by the walk" pass against a tree that never contained a special file.
    The test asserts the failing path and mkfifo's own stderr reach the
    message, since without the latter it cannot say why.
  • the FIFO check collects the sandbox the throw orphaned — see below.

Two notes on how the failures are induced

The established withoutPath lever does not work here: both helpers spawn
process.execPath, an absolute path, so emptying PATH cannot stop the child
from starting. process.execPath is a writable, configurable property, so
pointing it at a path that does not exist is the equivalent lever, restored in a
finally. node --test runs the tests within a file sequentially, so no
sibling test observes the gap.

runImportArchitectures throws before it can return a cleanup() handle when a
FIFO fixture fails, so the temp directory it had already created would leak.
Both helpers place their sandbox with mkdtempSync(join(tmpdir(), …)) and
os.tmpdir() re-reads TMPDIR on every call, so the test redirects TMPDIR at
a directory it owns and removes it afterwards. The fourth test guards that
cleanup itself, through both restore paths — an absent TMPDIR has to come back
absent rather than as the string "undefined", which os.tmpdir() would then
treat as a relative directory name.

Measured

npm run test:unit:coverage, node v26.10.0, TZ=UTC, run locally at 593b9fe
(this branch's base):

lines regions
tests/helpers-import-sandbox.mjs before 100.00 86.21
tests/helpers-import-sandbox.mjs after 100.00 100.00
tests/helpers-import-issue-sandbox.mjs before 100.00 86.96
tests/helpers-import-issue-sandbox.mjs after 100.00 100.00

Both files reach 100/100, so the issue's three boxes are all ticked and nothing
is left for it to track. The new test file is itself 100.00 / 100.00. All-files
regions 95.20 → 95.29. Full unit suite: 2090 tests, 0 failures.
npm run test:unit:coverage:check exits 0; Prettier clean.

Each fallback was mutation-checked: dropping ?? 1 from
helpers-import-sandbox.mjs fails the first test, and nothing else in the suite
notices.

Scope / overlap

Adds one new file and touches nothing else — no production code, no
package.json, no existing test, no .github/workflows/. Checked against every
open hold-gated PR:


Filed by quality agent (hold-gated mode). Human review required.

— hive: agent=quality backend=copilot model=claude-opus-5 copilot=1.0.88

tests/helpers-spawn-fallbacks.test.mjs pins the spawnSync normalisation
contract for runScriptWithFixtures, runScriptWithFetchMock and runWithGhStub.
runImportArchitectures and runImportArchitectureIssue apply the same
status ?? 1 / stdout ?? '' / stderr ?? '' normalisation and were left out, so
all three fallbacks in each were uncovered.

Both helpers spawn process.execPath, an absolute path, so the established
withoutPath lever cannot stop the child from starting; process.execPath is a
writable property, so pointing it at a path that does not exist is the
equivalent and is restored in a finally.

Also covers the mkfifo guard in helpers-import-sandbox.mjs, which the helper
documents as a broken fixture rather than a skip but which nothing checked.

Closes #1216

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: quality <quality@hive.kubestellar.io>
@hivecommons-hive hivecommons-hive Bot added the hold label Oct 8, 2026
@hivecommons-hive

Copy link
Copy Markdown
Contributor Author

Important

Held for human review by the hive's ACMM level gate.

This PR was opened by the "quality" agent while Hive policy required a human checkpoint for that agent. Non-outreach agents are held at ACMM L3–L5; the outreach agent is always held because it publishes project-facing communication.

Hive will keep the hold label until a human removes it. Operators can make a deliberate one-off release during an ACMM level change with release_level_holds=true, but level changes never release this hold automatically.

Comment thread tests/helpers-import-sandbox-fallbacks.test.mjs Fixed
CodeQL flagged the `collision.replace(/\./g, '\\.')` escape as incomplete:
it escapes dots but leaves backslashes and every other regex metacharacter
intact. The assertion only ever needed to show that the failing path is named
in the message, so a plain substring check expresses that directly and removes
the partial-sanitisation pattern rather than extending it.

Signed-off-by: quality <quality@hive.kubestellar.io>
@mrbobbytables
mrbobbytables added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit a40670a Oct 9, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[quality] extend the spawn-fallback contract to the two importer sandbox helpers

2 participants