test
js/cli/test.md
fino test runs tests written for Fino's built-in test framework. Use it for
runtime integration tests, module-level regression tests, and API behavior that
needs to execute inside Fino instead of Node or another JavaScript host:
fino test tests/app.test.ts
fino test tests/net
fino test 'tests/**/*.test.ts'
fino test --parallel tests
fino test --coverage tests/app.test.ts
Quote glob arguments so Fino performs recursive expansion itself. In shells
where globstar is disabled, an unquoted tests/**.test.ts is expanded before
Fino starts and may select only files exactly one directory below tests/.
Direct file inputs are imported as given. Directory inputs expand to descendant
.test.ts files. Glob inputs are resolved from the current working directory.
If expansion includes .test.ts files, non-test helper modules are ignored, so
fino test tests/net imports test files without also importing helpers that sit
beside them. Results are emitted as TAP-13, which is readable in a terminal and
can be consumed by TAP tooling.
Command Reference
| Name | Value | Description |
|---|---|---|
files... |
strings | Required. Test files, directories, or glob patterns to import and run. |
--filter |
string | Run only registered tests whose full path contains the filter text. |
--timeout |
number | Per-test deadline in milliseconds. Defaults to 60000; 0 waits forever. |
--show-output |
failures, always, or never |
Control captured console output. Defaults to failures. |
--durations |
boolean | Add TAP duration metadata to result lines. |
--parallel |
boolean | Run each test file in an isolated Realm, with bounded top-level group concurrency. |
--ordered |
boolean | Emit parallel groups in deterministic registration order instead of completion order. |
--coverage[=<path>] |
path | Collect native V8 coverage. A bare flag writes coverage/coverage.json; a custom path must use =. |
Console output is captured by default and printed for failures. Use
--show-output=always for live debugging output or --show-output=never to
suppress captured output in failure details.
Deadlines
Every test body runs under a deadline. A test that exceeds it fails with a
TestTimeoutError naming the limit, so a hang is reported as one failing test
rather than a run that never finishes. The default is 60 seconds; --timeout
changes it for the whole run and --timeout 0 disables it.
A test that is legitimately slower than the run-wide limit can raise its own without loosening the default for everything else:
it('replays the full cassette', { timeout: 300_000 }, async (t) => {
// ...
});
Under --parallel the coordinator derives its own deadlines from this value.
Each test or lifecycle-hook transition renews the worker deadline. It uses the
current test’s timeout (including an override or 0) plus 30 seconds of slack;
a group of healthy tests can therefore run longer than one test’s limit. Hooks
use the run default. Console output and unrelated timers do not renew it. A
worker that stops reporting progress, or fails to exit within the stall threshold,
is reported as a named lifecycle failure.
Parallel files
--parallel runs every matched test file in its own Realm. The command defaults
to ten executing top-level test groups per configured reactor thread. Set
FINO_TEST_CONCURRENCY to a positive integer to change that per-reactor amount,
or FINO_REACTOR_THREADS to control the underlying reactor pool. The reactor
pool defaults to one fewer than the host's online processor count, reserving a
processor for main-thread coordination while retaining at least two reactors on
multi-processor hosts. At most one group executes in a given file Realm so its
shared module state retains serial semantics.
Files enter a rolling live-Realm window in discovery order. Each module's top
level registers its groups, then their closures wait for group admission. When
all groups in a file settle and its Realm exits, the next file enters the
window. This bounds retained Realm state by the concurrency setting rather than
the total file count. Groups from different file Realms overlap; groups from
the same file remain sequential. Results are returned to the parent as
structured data, then merged into the ordinary top-level TAP stream in
completion order by default. Each completed group emits as one atomic block, so
output stays responsive without interleaving. Pass --ordered to hold completed
groups until every earlier registered group has settled and emit in
deterministic registration order. The aggregate 1..N plan is emitted at the
end after every rolling registration is known, as permitted by TAP 13. File
names are not added as wrapper subtests. Failure details are held until the
final aggregate summary. In parallel mode, --show-output=always includes a
group's console output as TAP comments when its result is emitted; it is not
live. Raw stdout and stderr from test Realms and their child processes are
captured at the process boundary and suppressed so they cannot corrupt the TAP
stream.
A top-level group that measures process-global state or strict latency can pass
{ exclusive: true } to test, suite, describe, or a nested it. The
containing root group waits for all admitted work, reserves the channel's full
capacity while it runs, and releases ordinary parallel admission immediately
when it settles.
Use --filter when a suite is large but the registered test path has a stable
name:
fino test --filter websocket tests/net
The command throws when no files are supplied or expansion finds no test files.
It is not a Node node:test compatibility command.
Coverage
Coverage is collected from V8 in every participating Realm and remapped to
original TypeScript locations with the source maps already produced by Fino's
loader. The TAP stream ends with one grouped # coverage comment block. The
canonical JSON artifact preserves aggregate totals and the Realm ids which
covered each line, function, and branch.
Use fino coverage to inspect that artifact, apply thresholds,
or export LCOV.