On this page
Bare-run refusal for Node suites: why the threshold is a function the consumer calls rather than a runner hook, and where the honest call sites are.
#typescript/src/sandbox
#typescript/src/sandbox
Bare-run refusal: a suite that is supposed to run inside the sandbox refuses to run outside it.
#Why this is a helper the consumer calls, and not a hook
The Python plugin refuses at pytest_collection_modifyitems: pytest hands it the whole selected test list before a single test body runs, so the plugin can count the run and abort it before anything happens.
Node's built-in runner offers no equivalent, and the gap is structural rather than a missing API. node --test runs each test FILE in its own child process; a module loaded with --import is loaded in those children, never in the parent that owns the run (NODE_TEST_CONTEXT=child-v8 is set in every one of them). A setup module therefore sees exactly one file and can never learn how many tests the whole run selected. Custom reporters do run in the parent, but they are fed events as tests complete -- far too late to refuse a run, and with no way to abort one.
So the count has to come from wherever the consumer genuinely knows it, and this module is a plain function the consumer calls there. The two honest call sites:
- A programmatic runner entry.
node:test'srun({ files })is called
with the file list, so the entry point knows the size of the run before it starts. Use policy: "threshold" there:
```ts import { run } from "node:test"; import { glob } from "node:fs/promises"; import { requireSandbox } from "stricttest";
const files = await Array.fromAsync(glob("dist-test/tests/ *\/.test.js")); requireSandbox({ policy: "threshold", threshold: 10, count: files.length }); run({ files }).pipe(process.stdout); ```
- **A
--imported setup module**, for suites that run through plain
node --test. A child cannot count the run, so the only honest stance there is policy: "always" -- every bare run is refused, including the small targeted one. That is strictly stronger than the threshold and costs the fast bare inner loop; a suite that wants that loop back needs the runner entry above.
There is deliberately no third shape that guesses. A cross-process counter that refused halfway through a run would have already executed tests bare, which is the thing being prevented.
#DEFAULT_SANDBOX_ENV
export const DEFAULT_SANDBOX_ENV = "STRICTTEST_SANDBOX" The environment variable the sandbox runner sets to "1".
#DEFAULT_RUNNER_COMMAND
export const DEFAULT_RUNNER_COMMAND = "scripts/test.sh"The command shown in the refusal message.
#SandboxRequiredError
export class SandboxRequiredError extends ErrorThrown by {@link requireSandbox} when a run is refused.
#SandboxPolicy
export type SandboxPolicy =How much of a bare run to refuse. The choice is required: a suite declares where it stands rather than inheriting a default.
"always": refuse every run outside the sandbox."threshold": refuse a run of more thanthresholditems outside the
sandbox, where count is how many this run has. Smaller runs stay bare for iteration speed.
#RequireSandboxOptions
export type RequireSandboxOptions = SandboxPolicy &#insideSandbox
export function insideSandbox(sandboxEnv = DEFAULT_SANDBOX_ENV): booleanWhether this process is running inside the sandbox runner.
#requireSandbox
export function requireSandbox(options: RequireSandboxOptions): voidRefuse this run if it is happening outside the sandbox, per the declared policy. Throws {@link SandboxRequiredError}; returns silently otherwise.