GUI's M1 definition of done is "10 000 synthetic rows scroll at 60 fps with flat memory over 10 minutes (mockd --tasks 10000)". This flag was missing from the four unhappy-path flags that did land; the brief's own flag list omitted it, which is corrected here too. --tasks seeds a plausible population rather than N copies of one row: varied state, size (log-uniform 50 KB - 20 GB), category, queue position and description, drawn from the same category.list / queue.list fixtures the rest of mockd already serves so a synthetic task can never name a category or queue those methods don't also return. State distribution is roughly 55% complete / 8% failed / 4% cancelled / 6% paused / 2% retry_wait / 25% queued, using the new TaskErrorCode taxonomy for failures. "Progress advances across the whole set, not a handful of live rows" ruled out the obvious cheap answer. A bounded, rotating pool of concurrently-active downloads (--active-cap, default 24) is fed continuously from each queue's FIFO — with the rest of that queue's queuePosition renumbered on every promotion, as a real scheduler would — and a small fraction of active tasks hit a transient failure and cycle through retry_wait before rejoining, so the pool keeps rotating through new rows for the whole run instead of draining once. Verified over a 10000-task, 60-second run: 61.5 MB RSS flat, and the active pool's membership meaningfully different after 60s. tick() only ever walks the active pool plus due retry-wait entries, never the full task list, so its cost stays flat regardless of --tasks. A manual download.add is still admitted immediately regardless of --active-cap — a human driving the GUI by hand must never wait behind synthetic load. Fixed a latent double-push while building this: any task 'connecting' at the top of a tick was pushed to the progress batch once for the transition and again at the loop's unconditional final push, inflating event.task.progress payloads with a duplicate entry for that taskId. It predates this change (the original tick() had the same shape) but only became visible once several tasks are legitimately 'connecting' in the same tick, which --active-cap's continuous promotion now does routinely. --seed makes a run reproducible, which matters when a GUI bug only shows up at a particular row. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_012fgjnqFCS5h5L7gZTZo3rV
96 lines
5.1 KiB
Markdown
96 lines
5.1 KiB
Markdown
# tools/mockd — a fake `veloxd`
|
|
|
|
Serves `contracts/fixtures` over **both** transports, keeps just enough state that adding
|
|
and pausing a download does something visible, and fakes progress events at 4 Hz.
|
|
|
|
The GUI and extension lanes develop against this from day one and never wait for the real
|
|
daemon. Its unhappy-path flags exist so those lanes can test the cases that are hard to
|
|
arrange on purpose — a slow daemon, a flaky one, a dropped socket, a refused pairing.
|
|
|
|
```sh
|
|
cd tools/mockd
|
|
npm install
|
|
npm start -- --help
|
|
npm start # both transports, default paths
|
|
```
|
|
|
|
Defaults: `$XDG_RUNTIME_DIR/velox/velox.sock` and `ws://127.0.0.1:52000`.
|
|
|
|
## Flags
|
|
|
|
| Flag | Effect |
|
|
|---|---|
|
|
| `--uds <path>` / `--no-uds` | Unix socket path, or don't listen |
|
|
| `--ws-port <n>` / `--no-ws` | loopback WebSocket port, or don't listen |
|
|
| `--progress-hz <n>` | progress event rate (default 4, the contract's ceiling) |
|
|
| `--speed <bytes>` | synthetic per-task speed |
|
|
| `--tasks <n>` | seed `n` plausible synthetic tasks instead of the fixture's two — see below |
|
|
| `--active-cap <n>` | ceiling on concurrently-"downloading" synthetic tasks (default 24) |
|
|
| `--seed <n>` | PRNG seed for `--tasks`, so a run is exactly reproducible (default 1337) |
|
|
| `--slow <ms>` | delay every reply. **Past 750 ms `capture.offer` must fail open.** |
|
|
| `--flaky <0..1>` | answer this fraction of calls with `-32603` |
|
|
| `--drop-connection <s>` | terminate every connection every N seconds |
|
|
| `--refuse-pairing` | `session.pair` fails, as if the user clicked Deny |
|
|
| `--lockout` | `session.pair` answers `-32014`, as if the brute-force lockout tripped |
|
|
| `--allowed-root <dir>` | add a root that `download.add`'s `saveDir` may resolve inside |
|
|
| `--allow-any-origin` | skip the `moz-extension://` Origin check (debugging only) |
|
|
| `--no-validate` | stop validating params (to see what a client actually sends) |
|
|
|
|
## `--tasks` — load testing the GUI's table
|
|
|
|
GUI's M1 definition of done is "10 000 synthetic rows scroll at 60 fps with flat memory
|
|
over 10 minutes (`mockd --tasks 10000`)". That takes more than 10 000 identical rows:
|
|
|
|
```sh
|
|
npm start -- --tasks 10000
|
|
```
|
|
|
|
Seeds a plausible population — varied `state`, size, category, queue position and
|
|
description, drawn from the same `category.list` / `queue.list` fixtures the rest of
|
|
mockd serves, so nothing here can name a category or queue those methods don't also
|
|
return. Roughly 55% land `complete`, the rest split across `failed`, `cancelled`,
|
|
`paused`, `retry_wait` and `queued`, plus a bounded pool (`--active-cap`, default 24)
|
|
seeded straight into `downloading`.
|
|
|
|
That pool is **rotating**, not fixed: as an active task finishes, the next one is
|
|
promoted from its queue's FIFO — with the rest of that queue's `queuePosition` renumbered,
|
|
as a real scheduler would — and a small fraction of "finishing" active tasks fail instead
|
|
and cycle through `retry_wait` before rejoining. Over a ten-minute run this means hundreds
|
|
of distinct rows have shown live progress by the time it ends, not the same handful
|
|
forever, while at any instant the active count stays realistic. A manual `download.add`
|
|
is always admitted immediately regardless of `--active-cap` — a human driving the GUI by
|
|
hand is never made to wait behind synthetic load.
|
|
|
|
`tick()` only ever walks the active pool plus whatever retry-wait entries just came due,
|
|
never the full task list, so the per-tick cost stays flat regardless of `--tasks`.
|
|
|
|
`--seed` makes a run reproducible: the same seed always produces the same table, which
|
|
matters when a GUI bug only shows up at a particular row.
|
|
|
|
## What is real and what is faked
|
|
|
|
**Real**, because a client's correctness depends on it:
|
|
|
|
* transport and privilege rules, taken from the *generated* `METHODS` table — so a
|
|
privileged method is refused with `-32003` over the WebSocket exactly as `veloxd` must;
|
|
* param validation, through the *generated* validators, including range and length checks;
|
|
* `saveDir` canonicalization against the allowed roots, so `-32011` is reachable;
|
|
* the `capture.offer` decision — monitored types, minimum size, excluded hosts — so both
|
|
the take and the ignore paths get exercised;
|
|
* pairing: `session.hello` accepts only a token this process actually issued;
|
|
* task state, so add / pause / resume / cancel / remove do what a client expects to see.
|
|
|
|
**Faked**: bytes advance on a clock, not from a socket. There is no network, no disk, and
|
|
no engine. Anything not listed above is answered from its golden fixture.
|
|
|
|
## Why it imports the generated protocol code
|
|
|
|
`mockd` uses `extension/src/shared/protocol/` — the generated TypeScript — rather than
|
|
types of its own. A mock with hand-written types is a third source of truth, and it drifts.
|
|
This way a schema change that breaks a client breaks `mockd` in the same commit.
|
|
|
|
It imports the individual generated modules (`types.js`, `methods.js`, …) rather than
|
|
`index.js`, because the `extension/` tree has no `package.json` of its own for Node to
|
|
resolve a star re-export through. That is a quirk of running from outside that package, not
|
|
a problem with the generated code; the extension's own bundler is unaffected.
|