Skip to main content

Module conformance

Module conformance 

Source
Expand description

Cross-backend conformance tests for Output, Cursor, and Input (retroglyph#763).

Each backend 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 slots that differ from the previous call’s content, which is what SlotDelta is for; see the tests module below for a worked example over Headless.

§Coverage

Not every backend wires every entry point: assert_output_contract/assert_cursor_contract need Observable, which some backends have no test-only reason to implement, and assert_input_contract binds only backends where Input::push_event’s coalescing obligation actually applies (see that method’s docs for when it doesn’t). As of retroglyph#997:

BackendOutputCursorInput
Headless (core)yesyesyes
Crossterm<W> (crossterm)yesyesexempt (see below)
SoftwareRenderer (software)yesyesyes
GlRenderer (gl)yesn/a (no Cursor impl)n/a (no Input impl)
WindowBackend<P> (window)not wirednot wiredyes
TerminalWasm (terminal-wasm)yesyesyes

Crossterm‘s pushed_events queue is deliberately exempt from the coalescing obligation assert_input_contract checks: unlike the windowed backends’ motion events, which can arrive at device-polling rate, pushed_events is only ever fed one event at a time by a test harness or a driver replaying an event it already read off the real terminal, so there is no unbounded-growth failure mode to guard against there (retroglyph#294/retroglyph#768 motivated the obligation itself). See Crossterm::push_event’s doc for the same note closer to the code.

§What this does not cover

Output::compositing’s PixelLayered { needs_full_frame } dispatch only takes effect through Terminal::present; a bare Output impl has no diffing of its own to exercise, so that is instead pinned by a Terminal-level test rather than by this module.

Structs§

SlotDelta
Per-slot “changed since last call” bookkeeping for a framebuffer-shaped Observable.

Traits§

Observable
A backend that can report a digest of what changed since the last call.

Functions§

assert_cursor_contract
Drives B through Cursor’s tracked-cursor obligation.
assert_cursor_style_contract
Drives B through Cursor::set_cursor_style’s obligation: each CursorStyle variant must have its own distinct, observable effect (retroglyph#920).
assert_input_contract
Drives B through Input’s coalescing obligation.
assert_output_contract
Drives B through Output’s obligations: make must return a fresh backend sized to the requested Size, with no cells drawn yet.
fnv1a
Hashes bytes with FNV-1a (64-bit).