Contributing and testing
The repository holds two implementations of the compiler that must agree byte for byte, the book, and this website:
| directory | what it is |
|---|---|
engine/ts | the TypeScript reference engine (npm workspaces: core, the JavaScript and Python plugins, the CLI, the book’s Playground bundle, benchmarks) |
engine/rust | the Rust port (a cargo workspace: vir, core, frontend-js, frontend-python, cli, wasm), and packages/wasm, the @viscosity/wasm package |
book | the book, which teaches how Viscosity works |
website | these docs |
Two house rules: every transform needs a differential test (the original
and the compiled program must behave the same), and every performance
claim needs measured numbers committed with it. See CONTRIBUTING.md in
the repository for the book’s own rules.
The TypeScript engine
Section titled “The TypeScript engine”cd engine/tsnpm cinpx tsc -p tsconfig.json --noEmit # typechecknpx vitest run # unit, corpus and differential testsThe Python plugin’s differential tests run programs under python3.
The Rust engine
Section titled “The Rust engine”cd engine/rustcargo fmt --all --checkcargo test --workspacecargo clippy --workspace --all-targets -- -D warningsThe test suite runs the TypeScript engine as its reference, so install
engine/ts first (npm ci). Tests that need node or python3 are
skipped, with a message, when they are not installed. Beyond unit tests it
checks:
- Parity: every test program, compiled with default options and with
--bounds assume, gives exactly the TypeScript engine’s code andexplaintext (committed inengine/rust/tests/expected), as do the--framesand--profilefixtures. After changing the TypeScript engine, regenerate them withnode scripts/gen_expected.mjs. - Differential: each original and compiled program is run with
node(orpython3) and must print the same output and throw the same errors. - Early errors: programs with an ECMAScript early error, which both CLIs must reject, and valid programs close to one, which both must accept, with the same error positions where the parsers agree.
- Multi-file CLI, profiles and non-UTF-8 file names.
- Fuzzing: a fixed-seed run of the fuzzer, below.
The fuzzer
Section titled “The fuzzer”engine/rust/scripts/fuzz.mjs is a differential fuzzer for both engines.
It generates seeded random programs in the subset Viscosity cares about
(arrays of record literals, field reads and writes in loops of every shape,
escapes, out-of-bounds accesses, annotations, and now and then an early
error) and checks each one two ways:
- Behaviour: the original and the compiled program print the same
output and throw the same error, with
--bounds checkand, when no access is out of bounds,--bounds assume. - Parity: the TypeScript and the Rust engine give the same code, the
same
explaintext and the sameexplain --framestext, also with a random profile. Programs with an early error must be rejected by both.
cd engine/rustcargo build -p viscosity-clinode scripts/fuzz.mjs # 300 programs, seed 1 (a few seconds)node scripts/fuzz.mjs --count 20000 --seed 7 # a long campaignnode scripts/fuzz.mjs --seed 7 --only 1234 --print # show one programnode scripts/fuzz.mjs --count 5000 --reduce # shrink failing programscargo test runs 200 programs with seed 1. The bugs the fuzzer has found
are listed, with the corpus program that covers each, in
engine/rust/README.md.
Packaging
Section titled “Packaging”Nothing is published yet, but the packages can be built:
- the CLI:
npm packinengine/ts/packages/cli(see Install); @viscosity/wasm:node packages/wasm/build.mjsinengine/rust, andnode packages/wasm/smoke.mjs, which packs and installs it and compares every corpus program with the TypeScript CLI;- the crates:
cargo package --workspace --allow-dirtyinengine/rustpacks and builds all six in dependency order (removetarget/packageafterwards).
The website
Section titled “The website”cd websitenpm cinpm run gen # regenerate the CLI, benchmarks and credits pagesnpm run buildnode scripts/check_links.mjs # every internal link resolvesThe CLI reference is generated from the CLI’s own --help text; don’t edit
the generated pages by hand.
GitHub Actions (.github/workflows/ci.yml) runs on every push and pull
request:
| job | what it checks |
|---|---|
| engine (TypeScript) | typecheck, vitest, then packs the CLI, installs it into an empty directory and runs it |
| engine (Rust) | cargo fmt --check, cargo test --workspace (with Node, Python and the TypeScript engine installed, so parity, differential, fuzz and multi-file tests all run), clippy -D warnings, and a WebAssembly build |
| drip (book companion) | the book companion’s TypeScript and Rust tests, and clippy |
| book | the chapter checker, tests, build, link check and a browser smoke test |
| website | build and link check |