crabidy/quality/mpris.md

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: start returns None and 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, no mpris: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, a debug! 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 a std::sync::Mutex whose 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, Pause and the modifier setters consult the published state and send nothing when the target is already the current value; only PlayPause toggles 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, Raise and SetFullscreen answer NotSupported and change nothing (D6, D14). (test: a_single_track_loop_is_refused_not_faked.)
  • G9 — An unknown duration is omitted, not zero. A zero mpris:length draws a full progress bar in consumers (D9). (tests: an_unknown_duration_is_omitted_not_zero, the_live_duration_wins_over_the_tracks_own.)
  • G10 — Seeked fires 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 PropertiesChanged for a value that did not change wakes every listener for nothing. (test: only_real_changes_are_announced.)
  • G12 — A stale SetPosition is 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. mpris adds one crate and no system library; --no-default-features compiles, tests and clippies clean through a stub of the same shape, and the matrix in check-features covers mpris alone, notifications alone, 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. playerctl must list the player, read its metadata, and drive play-pause, seek and volume. Re-check on a mpris-server/zbus bump.