9.3 KiB
Testing
Offline
Everything in pstr-core is offline and pure. That is deliberate: the filename
parser, the config writer and the catalog migrations are exactly the things whose
edge cases are painful to reproduce against a live share, so they are pinned by
table-driven tests instead.
cargo test --workspace --locked
cargo test -p pstr-core naming:: # just the parser
What is pinned today:
naming— the fansub, scene and cross conventions; dashes inside titles; version suffixes; a bracketed resolution that must not read as a year; a word starting withsthat must not start a season; dotted titles vs. extensions; and that an unrecognisable name still yields a usable title rather than being dropped.config— round trip, missing-is-None, unparseable-is-an-error (the one that protects the share list from a read-modify-write wiping it), and no temp file left behind.catalog— schema version, refuse-to-open on a newer schema, share replace semantics (drops removed rows, leaves other shares alone), and that a recrawl preserves watch state.
pstr-stream is offline too, and for the same reason: it runs against a
BlockSource fake, so the caching, deduplication and read-ahead behaviour that
is nearly impossible to provoke deliberately over a network is pinned exactly.
block— byte range → block mapping, including non-uniform sizes, a range ending exactly on a boundary (which must not pull the next block), and splicing into the right part of the output buffer.ring— never exceeds its byte budget, evicts least-recently-used, a containment check records no traffic and no recency, a block bigger than the budget is not stored, reinserting does not double-count.disk— round trip, a block disagreeing with its sidecar is rejected and removed, a block with no sidecar is a miss, eviction deletes the files rather than merely forgetting them, a reopened cache adopts the previous run's bytes, and cache paths carry no names.single_flight— N callers run the work once, a waiter sees the leader's failure, a failed call leaves the key retryable, and a cancelled leader does not wedge the key.stream— reads across block boundaries, a seek fetches only the block it lands in, a demand read joins an in-flight prefetch instead of refetching, a failing prefetch never fails the read, a seek cancels the read-ahead it invalidates while sequential playback cancels none, and streaming a file far larger than the ring stays inside the budget.
pstr-player is offline too, everywhere it can be. mpv itself needs a window and
a codec, but nothing mpv's stream callbacks decide lives in the callbacks —
they hand straight to StreamCursor, a plain safe type over a BlockSource
fake, which is what the tests drive.
cursor— sequential reads walk the file and stop at EOF, a seek moves where the next read starts, a read crossing a block boundary is allowed to come up short but returns the right bytes, seeking exactly to EOF is allowed while past it is an error, mpv's post-open seekability probe to 0 succeeds (fail it and every seek bar is gone), and a cancelled stream fails its reads and stays failed.registry— a published stream is reachable through its URL, dropping the handle revokes it, and a token is never reused after the stream behind it is gone, so a stale URL cannot start playing whatever took the number over. Only well-formedpstr://URLs parse.player— every mpv end-file reason maps to something the UI can act on (Eofmeans "next episode"; nothing else does), and an observed property arriving in an unexpected format is not read as a position of zero.
Against a real share
Needs a Proton Drive public link. Use a share you can afford to have indexed; nothing here writes, but the crawl reads every node's metadata.
cargo run -p pstr-cli -- add --name anime 'https://drive.proton.me/urls/TOKEN#fragment'
cargo run -p pstr-cli -- add --name anime 'https://…' --custom-password # prompts
cargo run -p pstr-cli -- crawl
cargo run -p pstr-cli -- list
Check: node count matches the folder, titles and episode numbers look right, and the crawl time is seconds rather than minutes (if it is minutes, the SDK's node-key cache is not doing its job).
The reference run, against a real library: 354 nodes crawled, 310 playable, 22
titles, ~25s. Eleven files end up with no episode number and that is correct —
they are films and OP/ED extras. Anything else unnumbered is a parser gap; add
the filename to naming's tests before fixing it.
Useful for spotting gaps:
sqlite3 ~/.local/share/proton-stream/catalog.db \
"SELECT title, name FROM nodes WHERE is_folder=0 AND episode IS NULL;"
Benchmarking playback, without a player
pstr bench reads a catalogued file the way a demuxer would — 64 KiB at a time —
and reports the three numbers that decide whether the app is usable: cold seek
latency, sustained throughput, and peak resident memory.
cargo run -p pstr-cli -- bench --file Frieren --verify
cargo run -p pstr-cli -- bench --file Frieren --readahead 0 # isolate read-ahead
cargo run -p pstr-cli -- bench --file Frieren --disk-cache # run twice
--verify cross-checks a mid-file read against the SDK's own download_range.
Run it whenever anything in the block-mapping or caching path changes; it is the
only check that catches bytes served from the wrong offset, which otherwise
surfaces as video that decodes into garbage rather than as an error.
Reference run, 761 MiB episode, 191 blocks, over a real public link:
| cold seek (worst) | sustained | peak resident | |
|---|---|---|---|
| read-ahead off | 572 ms | 7.2 MiB/s | 60 MiB |
| read-ahead 6, no seek cancellation | 2895 ms | 5.0 MiB/s | 128 MiB |
| read-ahead 12, seek cancellation | 715 ms | 8.7 MiB/s | 128 MiB |
| read-ahead 32 | 716 ms | 7.8 MiB/s | 128 MiB (evicting) |
| warm disk cache | 14 ms | 1039 MiB/s | 76 MiB |
Two findings worth not rediscovering. Read-ahead left running from the position
the viewer just left holds exactly the bandwidth the seek needs — 5x on
worst-case latency. And a read-ahead window deeper than half the ring evicts its
own blocks before the player reaches them, so throughput falls. Both are fixed
in pstr-stream::stream; both are pinned by offline tests.
Expect run-to-run spread of 15–20% on the throughput number. Treat a change under that as noise.
Note that bench's tuned read-ahead default (12) is not the right one for
mpv, which brings its own — see docs/BUGS.md B8. A change that improves bench
has not thereby improved playback; measure both.
Playing, with a player
cargo run --release -p pstr-cli -- play --file Frieren
cargo run --release -p pstr-cli -- play --file Frieren --start 900 # cold mid-file seek
cargo run --release -p pstr-cli -- play --file Frieren --disk-cache # run twice
cargo run --release -p pstr-cli -- play --file Frieren --headless --for-seconds 60
Use --release. A debug build is watchable but the numbers below are not
reproducible from one.
--headless decodes with no window and no sound, so the whole chain still runs
somewhere there is no display; --for-seconds ends an unattended run without a
window to close. Together they are the smoke test. Every run prints time to first
frame, seek-resume latency, and what the block layer actually fetched.
Reference run, same 761 MiB / 1560 s episode over a real public link, defaults:
| cold | warm disk cache | |
|---|---|---|
| time to first frame | 2.3 s | 0.3 s |
| mid-file seek resumed in | 1.0–1.6 s | — |
| blocks fetched for 38 s of playback | 16 (61 MiB) | 0 |
| ring evictions | 0 | 0 |
Sustained playback holds exactly realtime: 178 s of media in 180 s of wall clock, the 2 s deficit being the time to first frame, with no drift over the run. Check that: a deficit that grows is the block layer failing to keep up, and it is the one failure the smoke test can see but a short run cannot.
Cannot load libcuda.so.1 on stderr is mpv's hwdec=auto-safe probe declining a
backend. Not an error.
SDK-side
The streaming surface this app depends on is tested in ../proton-sdk-rs:
cd ../proton-sdk-rs
PROTON_TOTP_SECRET=... cargo test -p proton-drive-rs --test live_sharing \
-- --ignored --nocapture --test-threads=1
a_visitor_can_seek_within_a_shared_multi_block_file is the load-bearing one —
it verifies that the drive/unauth/ revision endpoint returns a usable XAttr,
without which block boundaries cannot be placed and seeking is impossible.
Live runs there must stay --test-threads=1: Proton throttles repeated logins
and each fresh one burns a TOTP window.
Acceptance, once playback lands
- Add the share; the catalog populates with correct titles and seasons.
- Play a 1080p HEVC episode — first frame in a couple of seconds, not a full download.
- Drag the seek bar to ~75%; playback resumes there within a block fetch.
- Close and reopen; the resume position is honoured.
- Leave it paused past the anonymous session's lifetime, then seek — the session renews rather than erroring.
- Watch the same episode again; it comes from the disk block cache.
- Repeat on Windows with the bundled
libmpv-2.dll.