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