One suite of guest JavaScript, run unchanged on every OpenWorkers runtime, to measure what each backend actually implements.
A low score is a result, not a failure: the suite is written against the WinterTC (TC55) Minimum Common API and the specs it points at, never against what a runtime happens to do. Guest assertions never set the exit code, so the run stays usable while the runtimes are incomplete.
tests/ holds plain classic scripts. Each declares its tests with
test('name', fn) at column 0, and asserts with the helpers the harness
injects (assert, assertEqual, assertType, assertArrayEqual,
assertThrows, assertRejects). Nothing in a test file is backend-specific.
| area | file | tests |
|---|---|---|
| crypto | js/crypto/crypto.js |
30 |
| encoding | js/encoding/base64.js |
20 |
| encoding | js/encoding/text.js |
35 |
| globals | js/globals/abort.js |
24 |
| globals | js/globals/blob.js |
26 |
| globals | js/globals/microtasks.js |
10 |
| globals | js/globals/misc.js |
16 |
| globals | js/globals/structured-clone.js |
17 |
| globals | js/globals/timers.js |
16 |
| headers | js/headers/headers.js |
28 |
| request | js/request/request.js |
29 |
| response | js/response/response.js |
38 |
| streams | js/streams/streams.js |
25 |
| url | js/url/search-params.js |
35 |
| url | js/url/url.js |
33 |
| wintercg | wintercg/minimum-common-api.js |
66 |
| total | 448 |
Every file runs in a fresh worker, and every test runs under a 2s deadline, so one missing global costs its own line and nothing else. The denominator comes from the source, not from the run: a backend that cannot parse a file still scores 0/N against the same N as the others.
cargo run --release --features v8 --bin conformance
cargo run --release --features boa --bin conformance -- --verbose
cargo run --release --features quickjs --bin conformance -- --filter url
cargo run --release --features jsc --bin conformance -- --jsonOne backend per build, like the rest of the workspace. --verbose lists every
failing test, --json emits the whole run, --filter selects by path.
Measured 2026-08-21, one commit per runtime, all against openworkers-core
v0.15.0.
| area | v8 | jsc | quickjs | boa |
|---|---|---|---|---|
| crypto (30) | 30 | 23 | 21 | 10 |
| encoding (55) | 45 | 33 | 44 | 47 |
| globals (109) | 86 | 37 | 43 | 80 |
| headers (28) | 23 | 24 | 24 | 24 |
| request (29) | 22 | 19 | 16 | 20 |
| response (38) | 28 | 27 | 21 | 30 |
| streams (25) | 13 | 12 | 11 | 13 |
| url (68) | 68 | 66 | 66 | 66 |
| wintercg (66) | 38 | 27 | 24 | 29 |
| total (448) | 353 | 268 | 270 | 319 |
| 78% | 59% | 60% | 71% |
Branch names lag the code: every one of these builds against core v0.15.0.
| backend | branch | commit |
|---|---|---|
| v8 | main |
fe3fb23 |
| jsc | feat/core-0.14 |
2df2560 |
| quickjs | feat/core-0.14 |
ed14afe |
| boa | feat/core-0.14 |
9dec1ee |
| wasm | feat/core-0.14-wasmtime-bump |
c97afa3 |
77 of the 448 tests fail on all four backends, so most of the gap is the platform's, not any one engine's.
- v8 is the only backend with a complete
URLand a completecrypto, 68/68 and 30/30. Itssubtlecovers the six digests, AES-GCM round trips, raw HMAC and AES key import and export, and ECDSA key generation. jsc and quickjs stop after digest and HMAC; boa has nosubtle.digestat all, which is most of the distance between its 10/30 and everyone else. - jsc and quickjs miss
URL.canParseandURL.parseand nothing else inurl.js; boa takesurl.jswhole and loses its two points onURLSearchParams. All three backURLwith theurlcrate. - boa is 34 behind v8 (319 against 353) on the strength of
boa_wintertc, while being the weakest on crypto by far. - No backend exposes
EventorEventTarget, so the DOM event core of the Minimum Common API is missing everywhere, and with itCustomEvent,ErrorEvent,MessageEvent,PromiseRejectionEventandAbortSignal.any.AbortControllerexists on v8 (8/8) and boa (6/8) only. - No backend sorts
Headersiteration by name, none rejects an invalid name or a value with a newline, and none trims whitespace around a value. v8 alone also fails to comma-joinSet-Cookieinget. - No backend sets
Content-Typefrom the body onRequestorResponse, so neither a string nor aURLSearchParamsarrives with its type, and none enforces the status rules (a body with 204 or 304, a status below 200 or above 599). - Streams stop at
ReadableStream:WritableStream,TransformStream, the queuing strategies,TextEncoderStream/TextDecoderStream,CompressionStream,pipeToandReadableStream.fromare missing on all four,teeexists on v8 and boa only, and no backend makes a stream async-iterable. structuredClone,BlobandFileexist on v8 and boa only, andmultipart/form-dataparses on v8 alone.performanceexists on v8 and quickjs, and only quickjs carriestimeOrigin;selfexists on v8 and boa.
The guest suite asks what a script can observe about itself. It cannot see a
body that does not arrive all at once, because only the host is on the other
end of the channel. src/streaming/ is that second measurement, and
STREAMING.md holds its matrix.
cargo run --release --features v8 --bin streaming
cargo run --release --features jsc --bin streaming -- --filter backpressure
cargo run --release --features wasm --bin streaming -- --json25 probes on the JavaScript backends, 4 on wasm, each in its own thread with
its own deadline, so a runtime that deadlocks mid-stream costs one line and
gets reported as hangs rather than taking the run with it.
One cargo feature per backend, mutually exclusive, the same pattern as
openworkers-task-executor.
wasm builds and reports a skip: that runtime executes wasm components, not
JavaScript, so this suite has nothing to say about it. Reporting 0/448 there
would be a lie, not a measurement.
nova has no feature at all. nova_vm 1.0 pulls temporal_rs 0.1.2, written
against icu_calendar 2.1, while v8 152 pulls temporal_capi 0.2.3+, which
wants 2.2. One lockfile cannot hold both, and this crate needs v8.
examples/ssr_oracle.rs and fixtures/sveltekit-app/ are a separate,
complementary measurement: 17 scenarios of a real SvelteKit app, recorded on v8
and diffed byte for byte by the other runtimes.
cargo run --release --features v8 --example ssr_oracle -- --check