1# Library regression lanes
2
3## The CI gate
4
5`python3 tools/ci.py` gates `main` (or `--rev REV`, or `--working-copy` for this
6checkout's `@`) in the jj workspace `workspaces/ci`: it points that
7workspace's own commit, a child of `main`, at the revision's files, so other
8checkouts' edits in progress never reach the result, and a working copy is
9frozen as it was when the run started. Its build cache is
10`workspaces/ci/target`, which can link to a shared `target/`. Runs wait for one
11another, and the exit status is the result.
12
13Cargo normally checks source mtimes, so a shared cache can reuse another
14checkout's artifacts when the current sources are older. CI and release/build
15entry points clean local packages from `cargo metadata` before each build epoch,
16scoped to its target and release profile where applicable. This removes only
17workspace and other path package artifacts, including vendored patches;
18registry and Git dependencies stay cached.
19All CI cleanup precedes concurrent lanes. Direct Cargo runs after switching
20checkouts need the same cleanup; `python3 tools/cargo_fresh.py` accepts Cargo's
21`--target`, `--target-dir`, and `--release` options. Shared-cache builds and
22cleanup must run serially across checkouts.
23
24| Lane | Runs |
25| --- | --- |
26| `fmt` | `cargo fmt --all --check` |
27| `clippy` | Clippy `-D warnings` on the workspace (all targets and features), and `snowbound` without default features |
28| `test` | builds every test target, runs the executables four at a time (`--test-jobs`) from their package folders, slowest last time first, and the doctests |
29| `python` | builds the examples the suite runs, then `tools/test_*.py` under `uv` with Pillow and pdfplumber |
30| `windows-x86_64` | `platform/windows/cargo.sh` build of `snowbound` (nightly's win7 target) |
31| `windows-aarch64`, `linux-*` | Clippy `-D warnings` on what ships (libraries and binaries but `mobile`), then the `snowbound` build; Linux through `platform/linux/cargo.sh`, which links with zig against glibc 2.17 |
32| `ios` | `xcodebuild` of the simulator app, unsigned |
33| `android` | Clippy `-D warnings` on `mobile` through `platform/android/cargo.sh`, then `apps/android/build.py`'s APK; skipped, saying why, without the NDK, SDK, JDK or the `aarch64-linux-android` target |
34| `web` | Clippy `-D warnings` on `snowbound` for `wasm32-unknown-unknown`, SQLite built by nixpkgs' clang; `release_web.py` links, optimizes and deploys the static folder |
35| `web-js` | Node boundary tests for Live Share browser sockets and storage |
36| `macos-10.6` | `platform/snow-leopard/cargo.sh` build of `snowbound`; skipped, saying why, without the SDK or nightly `rust-src` |
37
38```sh
39python3 tools/ci.py # every lane, on main
40python3 tools/ci.py --working-copy --changed # the lanes and test packages @'s changes from main reach
41python3 tools/ci.py --rev xyz --lanes test windows # `windows` names both windows-* lanes
42```
43
44Lanes run four at a time (`--jobs`), each under its own time limit
45(`--timeout MINUTES` overrides them all). The table it prints names each
46failure's first errors with their files and lines, to tell whose edit broke
47it; `workspaces/ci/target/ci/runs/TIME/` keeps every lane's log and
48`summary.json` (status, seconds, errors with files, each test executable's
49time), for the last 20 runs. After a run over `--budget` (40 GB), it deletes
50the build units this run didn't use, least recently used first, which keeps
51`deps/` small for the font tests that scan it. Windows needs llvm-mingw from
52`platform/windows/toolchain.sh` in the main checkout's `target/windows`, or
53`LLVM_MINGW`; Linux needs `zig`. `release.py` runs the gate on the commit it
54publishes.
55
56The workspace is made on first use; to drop it, `jj workspace forget ci` and
57delete `workspaces/ci`.
58
59## Public fixtures
60
61From a checkout with Rust and [uv](https://docs.astral.sh/uv/), which provides
62Python 3.12 with Pillow and pdfplumber without installing anything system-wide:
63
64```sh
65uv run --no-project --python 3.12 --with pillow --with pdfplumber \
66 python tools/check_public.py /absolute/path/to/new-results
67```
68
69The command runs formatting, all workspace features/targets, doctests, Clippy,
70diagnostic/example builds, and Python regression tests. Each stage has its own
71log; `results.json` records executed commands, elapsed times and exit statuses.
72Only a completed run receives `status: passed`. Existing result directories are
73refused. This lane clears inherited `ONESTORE_*` and `SNOWBOUND_*` overrides and
74uses this checkout's binaries so personal captures or external exporters cannot
75silently replace public inputs.
76
77Repeat the same command in a fresh checkout containing only versioned files to
78verify clean-checkout compatibility. Fixture symlinks must remain symlinks; their
79targets are versioned in this repository. Neither `corpus/private`, ignored
80`evidence`, existing `target` outputs, credentials nor running virtual machines
81are required. Cargo's normal dependency cache may be reused.
82
83For the Python tests alone, end the same `uv run` with
84`python -m unittest discover -s tools -p 'test_*.py'`. Without pdfplumber they
85skip the PDF oracles (`test_pdf_format`, part of `test_document_oracle`) and
86say so; `check_public.py` refuses to start without it.
87
88Rust explicitly reports ignored lab tests and fixture generators. Those cases
89are **not** part of a successful public run. The Python suite tests native/lab
90harness logic using retained synthetic captures and mocks; it does not claim a
91new execution of OneNote or a real server interruption.
92
93## Sweeps and soak
94
95Seeded sweeps (random edit walks, multi-client schedules, differentials across
96corpus sections) run a smoke slice by default. `SNOWBOUND_SWEEP=0` runs them in
97full with the seeds as written; any other number shifts every sweep's seeds, and
98a failure replays under the same value.
99
100`tools/soak.sh [seconds per fuzz target]` soaks a dedicated machine until
101stopped: each round runs the full sweeps under a random shift, in release with
102debug assertions, then every `fuzz/` target for the time limit. A failing round
103leaves `soak/*.log` ending in the command that replays it; crash inputs stay in
104`fuzz/artifacts/<target>/`. On a Linux VM, install rustup's stable and nightly
105toolchains, `cargo install cargo-fuzz`, and `build-essential pkg-config
106libfontconfig-dev`; copy the checkout and run the script under `tmux`.
107
108## Motion capture
109
110`SNOWBOUND_FRAMES=DIRECTORY` writes every frame the app draws to
111`DIRECTORY/MILLISECONDS.png`, timed from when the window opened. With a
112hidden-window replay (`SNOWBOUND_REPLAY` plus `--screenshot`, see
113`tools/canvas/README.md`) this records an animation at its real pace without
114touching the screen: waits tick at 60 Hz, and the app draws as fast as it can
115while it animates. Point it at a copy of a notebook, end the script with a
116short `wait` so the last frames finish writing, and assemble strips or GIFs
117with `ffmpeg`.
118
119## Private and native verification
120
121Private notebooks stay outside versioned fixtures. Materialize a copy before
122editing and retain source hashes; never point an authoring harness at an original
123notebook. Live acceptance uses explicitly owned disposable targets and preserves
124the run's commands, inputs, outputs and teardown evidence.
125
126| Boundary | Entry point | Acceptance evidence |
127| --- | --- | --- |
128| Native authoring and cold reopen | `native_runner.py --help` | Independent OneNote capture; exact expected page count when known; owned clone teardown |
129| Password-protected sections | `native_protected.py NOTEBOOK PASSWORDS OUTPUT [SECTION]` | A fresh clone cold-opens Snowbound's protected sections, unlocks each in OneNote's dialog, types into one through COM and reads every page with the notebook it leaves (`corpus/protected-sections`) |
130| Mixed native/Rust/offline writers | `native_collaboration.py --help` | Recorded intents, durable receipts, independent server state and cold native comparison |
131| SMB directory pagination | `test_smb_directory.py --help` | Caller-owned Linux VM, native filesystem oracle, interrupted-page rejection |
132| SMB publication and payload interruptions | Ignored tests in `notebook` (feature `smb`) | Explicit `ONESTORE_SMB_*` lab inputs, retained protocol traces and independent recovery checks |
133| Application session on a share | `session_acceptance.py --help` | Page saves through `notebook::session` on the mounted Samba share, relaunch between launches, cold native reopen of the edited pages, owned VM and clone teardown |
134
135To compare an additional **already captured** notebook hierarchy without running
136OneNote:
137
138```sh
139PYTHONPATH=tools ONESTORE_NOTEBOOK_NATIVE=/absolute/path/to/capture \
140 python3 -m unittest test_notebook_discovery
141```
142
143The capture contains `notebook/` and `read/hierarchy.xml`. This comparison is a
144separate private lane and must retain its own log. A missing capture, unavailable
145lab, compilation-only iOS result or ignored test never establishes live native
146compatibility. Process exit, server/VM interruption and physical storage loss
147remain distinct fault models.