Testing
Fairyfly has no external test framework. Put files ending in .test.js inside test/ and run them with ff test (one process, all files) or test/run.sh (one process per file).
Quick look
Create test/smoke.test.js:
Run it:
The check / done pattern
test/lib.mjs is tiny on purpose — read it once, then copy the pattern:
Three rules:
- Every test file imports from
./lib.mjs— relative import with the.mjsextension. - Every test file ends with
done("<name>"). Without it the process may idle instead of exiting. done()callsprocess.exit(), which also stops the event loop. Server-fixture tests (which keep a listener alive) terminate cleanly because of this.
Real example from the repo (test/url.test.js):
Running tests: ff test
Behavior:
- Opens
test/— a missing directory printsNo test/ directory found.and exits. Run from the project root. - Runs only files ending in
.test.js. Anything else is ignored (e.g.test/url.test.ffbcnever runs underff test). - Each file is evaluated as a module, then microtasks and the event loop are pumped — top-level
awaitworks. - Prints
OK test/<name>orFAIL test/<name>per file, then a{passed} passed, {failed} failedsummary. - Exit code is
1when any file fails. - Files larger than 10MB are not loaded (
FAIL … (read error)).
Running tests: test/run.sh
Same job, one process per file:
Green means every file exited 0 and printed no FAIL lines. make test
runs build + C fixtures, zig build test, then test/run.sh. make ci adds
zig fmt --check and a fuzz smoke run — the same gates as GitHub Actions.
Caution:
run.shgreps for the wordFAIL, so anyconsole.log("FAIL…")of your own marks the file failed. Onlycheck()should ever print it.
Fuzz runner
Run zig build fuzz -Doptimize=ReleaseSafe -- <target|smoke> [seed] [--events-max=N]. Missing or unknown targets and invalid arguments print usage and exit with status 2.
Async and server tests
Top-level await works, and the runner pumps the loop after each file:
For a server fixture, serve on a fixed test port, hit it, then done() (which exits and closes the listener):
Practical example: testing a JSON endpoint
Troubleshooting
No test/ directory found. — run from the project root, not from inside test/.
Hanging test run — you forgot done(name), or a setInterval / listener keeps the loop alive with no exit path. End every file with done().
FAIL test/x.test.js (read error) — the file exceeds the 10MB per-file cap or is unreadable.
Passes under ff test, fails under run.sh — something printed FAIL without failing check(). Search the output for stray FAIL text.
