Events
The events that a test process sends to the runner.
The events are the wire format (D4). The exact fields are in
src/events.ts, with a comment on each one. That file is the
definition. This document gives the model and the rules.
1. Model#
A test process runs one test file and writes a file stream:
file-start
coverage (code that ran while the file loaded)
test-start
output*
test-end
coverage (code that ran in that test)
...
late-failure*
file-end
The runner role puts the file streams into one run stream:
run-start
<file stream> exit
<file stream> exit
...
hang ...
type-error ...
run-end
The runner role is the ubq runner when it starts child processes.
In a plain program (node test/foo.ts), the test process takes the
runner role for itself. Sinks always read a run stream, so they
have one code path.
Events of different files can mix in a run stream, because files run in parallel. Events of one file keep their order.
2. Events#
| Event | Written by | Meaning |
|---|---|---|
run-start |
runner role | the command, the runtime, the platform |
file-start |
test process | a test file started to load |
test-start |
test process | a test started to run |
test-end |
test process | status, duration, assertion count, and the failure if there is one |
late-failure |
test process | a failure for a test that already ended |
output |
test process, runner role | text for stdout or stderr, with the test that wrote it. The runner role writes it for text that did not go through the capture in the test process. |
coverage |
test process | ranges that ran since the last coverage event of that process. tfile names the test file, because events of different files mix in a run stream. |
file-error |
test process | a failure that belongs to no test, such as an error while the file loads |
file-end |
test process | the file has no more tests |
exit |
runner role | exit code and signal of the test process |
hang |
runner role | the result of a hang rerun: the places that opened what a timed-out test left open |
type-error |
runner role | one error from the type checker of the project, after all file streams |
run-end |
runner role | totals and the exit code of the run |
hang (build step 4) is not defined yet.
3. Rules#
One JSON object for each line. No line breaks in an object.
typenames the event. Field names are full words, so a person can read a dump.The version is on the first event of each stream (
run-start.v,file-start.v), not on each event. The version changes only when an event loses a field or a field changes meaning. A new optional field does not change it.A test has exactly one
test-startand onetest-end. A skipped test has both. A test that the selection left out has none, andfile-end.unselectedcounts it.A passing assertion has no event.
test-end.assertshas the count. This keeps the stream small.A test has at most one failure in
test-end, because the first failed assertion stops the test. A later problem (an assertion after the end, an unhandled rejection) is alate-failure. A consumer must change the status of that test tofail.The test process formats values.
failure.actual,failure.expected, andfailure.diffare text. Values such as aMapor a class instance do not survive JSON, and the diff needs the real values.Paths are relative to the project root, with
/separators. The project root is the folder of the nearestpackage.jsonabove the working directory.Stacks hold user frames only. Frames from the tool, the runtime, and
node_modulesare removed in the test process.A stream that ends with no
file-endmeans that the process died. Theexitevent from the runner role has the code and the signal.A run with
--repeathas onetest-endfor each test also. The runner makes it from the results of the repeats: the events of the first failure, or of the last repeat if the test did not fail.test-end.repeathas the count of the repeats and the count of the failures. A sink does not see the events of the other repeats.
4. Test IDs#
<file> > <group> > <name>, for example
test/parse.ts > parse > circle.
- The separator is
>. A test name or a group name that contains>is an error. - A name must be unique in its group (
05-api.md, section 3).
5. In-process delivery#
In a test process, the core library publishes each event on the
diagnostics_channel channel ubq.event. A sink is a function
that takes one event (Sink in src/events.ts).
6. Example#
A file with one passing test and one failing test, as a plain program:
{"type":"run-start","v":1,"time":1794440000000,"command":["node","test/parse.ts"],"cwd":"/work/app","runtime":{"name":"node","version":"26.5.0"},"platform":"darwin","arch":"arm64","ci":false}
{"type":"file-start","v":1,"file":"test/parse.ts","pid":4242,"coverage":"partial"}
{"type":"test-start","id":"test/parse.ts > parse > circle","timeout":5000,"at":{"file":"test/parse.ts","line":5,"column":3}}
{"type":"test-end","id":"test/parse.ts > parse > circle","status":"pass","ms":0.21,"asserts":3}
{"type":"test-start","id":"test/parse.ts > parse > square","timeout":5000,"at":{"file":"test/parse.ts","line":12,"column":3}}
{"type":"test-end","id":"test/parse.ts > parse > square","status":"fail","ms":0.4,"asserts":1,"failure":{"kind":"assert","assertion":"equal","message":"expected values to be equal","at":{"file":"test/parse.ts","line":14,"column":12},"actual":"3","expected":"2","diff":"- 2\n+ 3"}}
{"type":"file-end","file":"test/parse.ts","unselected":0}
{"type":"exit","file":"test/parse.ts","code":1,"signal":null}
{"type":"run-end","ms":41.2,"pass":1,"fail":1,"errors":0,"skip":0,"unselected":0,"code":1}