Expand description
Cross-backend conformance tests for Output, Cursor, and Input (retroglyph#763).
Each of the five backends in this workspace answers the same handful of obligations
(Output::clear/Output::resize resetting internal state, out-of-range DrawCell
positions, cursor tracking staying in sync with external writes, Input::push_event
coalescing consecutive Mouse(Moved) events) independently. This module is what holds
those independent answers to a single agreed contract: assert_output_contract,
assert_cursor_contract, and assert_input_contract each drive a backend through one
facet’s obligations and panic on the first violation, so a backend crate wires one of them
into a #[test] and gets every future regression in that facet for free.
§Why not B: Backend
GlRenderer deliberately implements neither Input nor Cursor (a GPU/pixel surface has
no text cursor and never receives external input): a single B: Backend bound would make the
harness itself impossible to use, since a bound including Input + Cursor could never be
satisfied by every backend that wants only assert_output_contract. The three entry points
stay separate so a backend opts into exactly the facets it implements.
§The Observable hook, and why it must be a delta
Output/Cursor have no shared way to read back “what would actually appear”: a terminal
backend has emitted bytes, a pixel backend has a framebuffer, Headless
has a Grid. Observable::snapshot is the one method a backend
implements to bridge that gap, and every assertion below only ever compares two calls to it
for equality, never interpreting the u64 any other way.
That equality only means what it should if snapshot returns what changed since the
previous call, not the backend’s whole history or its whole current state. The assertions
compare two independently-built action sequences that a real user could not tell apart from
this point forward; if snapshot hashed everything ever produced (a terminal backend’s whole
emitted byte log, say), two sequences of different lengths could never compare equal even when
both are correct, and the assertions would fail on every backend, always, for a reason that has
nothing to do with the obligation under test. A backend whose only observable output is an
appended log implements this by hashing the slice appended since the last call (and advancing
a remembered offset past it). A framebuffer-shaped backend implements it by hashing the
positions that differ from the previous call’s content (and remembering the new content for
next time) rather than the whole buffer. Either way, snapshot needs its own “since last
call” bookkeeping the production backend has no other reason to carry, which is usually
easiest to add via a small test-only wrapper around the real backend rather than on the
backend type itself; see the tests module below for a worked example over
Headless.
§What this does not cover
Output::needs_full_frame only takes effect through
Terminal::present when a backend also returns true from
Output::composites_layers (see that method’s docs); a bare Output impl has no diffing of
its own to exercise, so that combination is instead pinned by a Terminal-level test rather
than by this module.
Traits§
- Observable
- A backend that can report a digest of what changed since the last call.
Functions§
- assert_
cursor_ contract - Drives
BthroughCursor’s tracked-cursor obligation. - assert_
cursor_ style_ contract - Drives
BthroughCursor::set_cursor_style’s obligation: eachCursorStylevariant must have its own distinct, observable effect (retroglyph#920). - assert_
input_ contract - Drives
BthroughInput’s coalescing obligation. - assert_
output_ contract - Drives
BthroughOutput’s obligations:makemust return a fresh backend sized to the requestedSize, with no cells drawn yet. - fnv1a
- Hashes
byteswith FNV-1a (64-bit).