4.2 KiB
4.2 KiB
Quality gates — MPRIS
Criteria an implementation of architecture/mpris.md must satisfy.
Automated coverage lives in cbd-tui/src/mpris.rs (the mapping, as unit
tests) and cbd-tui/tests/mpris_bus.rs (the round trip over a real
session bus, skipped when there is none).
Hard rules (highest priority)
- G1 — No panic, whatever the bus does. A missing session bus, a
bus that never answers, a refused name, a failed property emission and a
dropped connection are all logged and survived (D13). The connection
attempt carries a timeout, as an external call must. (A missing bus is
exercised by every test run:
startreturnsNoneand the client runs on.) - G2 — No secret and no credential-bearing URL reaches the bus.
Published metadata is title, artist, album, length and a synthetic
trackid — no
xesam:url, nompris:artUrl, no library path (D8). Every peer on a session bus can read properties, so this is a redaction boundary, not a matter of taste. (tests:metadata_names_the_track_and_never_a_url, and the bus test asserts the library path is absent from what a client actually receives.) - G3 — The state feed is bounded. 64 slots,
try_send, adebug!on overflow; the orchestrator never awaits the bus (D12). (test:a_full_feed_drops_instead_of_blocking.) - G4 — No lock is held across an
await. The published mirror sits behind astd::sync::Mutexwhose guard never crosses a suspension point; a poisoned lock is recovered rather than propagated as a panic. - G5 — Nothing is published that the server did not say. No optimistic local write on a method call, no interpolated position, no cached duration carried across a track change (D2).
Behaviour
- G6 — A pause key cannot start playback.
Play,Pauseand the modifier setters consult the published state and send nothing when the target is already the current value; onlyPlayPausetoggles unconditionally (D3). (tests:pause_never_starts_playback_and_play_never_stops_it,play_pause_always_toggles,the_modifiers_toggle_only_when_they_differ.) - G7 — Mute round-trips. Volume 0 mutes rather than turning the
level down, so unmuting restores it; a non-zero target unmutes and then
moves by the delta (D4). (tests:
volume_is_muted_as_zero_and_restored_by_unmuting,setting_a_volume_sends_the_delta_to_it.) - G8 — Unrepresentable requests are refused, not approximated.
LoopStatus::Track, a playback rate other than 1.0,OpenUri,Quit,RaiseandSetFullscreenanswerNotSupportedand change nothing (D6, D14). (test:a_single_track_loop_is_refused_not_faked.) - G9 — An unknown duration is omitted, not zero. A zero
mpris:lengthdraws a full progress bar in consumers (D9). (tests:an_unknown_duration_is_omitted_not_zero,the_live_duration_wins_over_the_tracks_own.) - G10 —
Seekedfires on a discontinuity and not on the clock or a track change (D10). (test:a_position_jump_is_a_seek_but_a_new_track_is_not.) - G11 — Only real changes are announced. A
PropertiesChangedfor a value that did not change wakes every listener for nothing. (test:only_real_changes_are_announced.) - G12 — A stale
SetPositionis ignored. The spec's trackid argument exists so a seek aimed at a track that has since changed does not move the one now playing. (test:set_position_becomes_a_delta_and_ignores_a_stale_track.)
Build and shape
- G13 — The feature carries its own weight and nothing else's.
mprisadds one crate and no system library;--no-default-featurescompiles, tests and clippies clean through a stub of the same shape, and the matrix incheck-featurescoversmprisalone,notificationsalone, and neither. - G14 — The server is untouched. No proto change, no server change; the feature is a client-side translation or it is misplaced.
- G15 — Verified against a real client, not only a test double.
playerctlmust list the player, read its metadata, and drive play-pause, seek and volume. Re-check on ampris-server/zbusbump.