Raxol. Harness. Surface. Golden
(Raxol v2.6.1)
View Source
Byte-golden snapshot matrix for the harness degradation ladder: renders
each fixed fixture session, end-to-end, through Raxol.Harness.Surface
in each of the three render modes (:inline_log, :tmux_conservative,
:flat) at fixed geometry, and blesses/checks the raw emitted byte
stream against a checked-in golden file per fixture x mode pair.
Mirrors the RATE --gen / Raxol.Harness.Fixture.Bless precedent one
layer further down the stack: RATE hashes rendered frames, Fixture.Bless
snapshots the journal-fold projection, and this module snapshots the raw
bytes a real terminal would receive -- the level below both, where a
degradation-tier regression (a stray \e[2J, a footer bleed, a changed
cursor dance) shows up as a literal byte diff rather than a semantic one.
Determinism audit
Every render in this matrix must be byte-identical run over run, VM over VM, machine over machine -- otherwise "golden" is a lie. Five potential sources of nondeterminism, and why each is closed:
Time.
render/2drivesSurface.advance/2in a loop and NEVER passes anow(seerender/2below -- the loop always callsSurface.advance(model), arity 1, defaultingnowtonil). The status strip's elapsed-ticker values derive solely from fixture eventtsfields, never a live clock:Raxol.Harness.StatusStrip's own moduledoc ("The elapsed ticker and R11 (no wall-clock in the default suite)") documents thatrender/2never callsSystem.monotonic_time/1or any wall-clock function -- elapsed is a pure subtraction of two caller-supplied integers.StallDetector's notion of time is likewise caller-owned and is never fed into this render path at all (this module never touchesStallDetector).Environment. An explicit
:modeoption toSurface.new/2bypassesModeSelect.select_with_reason/3entirely (see that function's own "test seam" doc) -- noSystem.get_env/0snapshot, no tty/CI detection, and no startup mode notice varies run to run.render/2always passes an explicit:modeandenv: %{}anyway, as hygiene: even though the explicit mode makesenvdead for mode-pick purposes, passing a fixed empty map rather thanSystem.get_env/0means no accidental future code path inSurface.new/2could reach into a live, machine-dependent environment map.Capabilities.
render/2always passescapabilities: nilexplicitly.nilmeans neitherRaxol.Terminal.Capabilities.cached/0(apersistent_term-backed, machine-dependent probe) nor any$TERMsniffing ever reaches the byte stream -- every render in this matrix is built from the same, fully-specified capability record (none).nilis also exactly the conservative clamp:tmux_conservativeitself assumes (seeModeSelect's moduledoc: the capability ladder clamps for a detected multiplexer before the record ever reachesInlineAuthority.new/5), so:tmux_conservative's golden renders with precisely the capabilities the real ladder would hand it for an un-probed/conservative terminal.Geometry. Fixed at
[width: 60, rows: 20, footer_rows: 6]for every fixture x mode pair -- the exact geometrytest/harness/t13a_surface_test.exsalready uses for its own end-to-end assertions, so this matrix's byte streams are directly comparable to that suite's documented behavior.Map iteration order. This class is closed by the runtime's own determinism, not by any tripwire in this module: identical Erlang/ Elixir maps iterate in identical order within a VM -- map iteration order is a deterministic, platform-stable function of the keys alone (sorted order for small maps, a fixed-hash HAMT layout above 32 entries) -- so two renders of the same fixture in the same process can never diverge on map-iteration order by itself. What the DETERMINISM test in
test/harness/golden_snapshot_test.exs(rendering the same fixture x mode pair TWICE in one test and asserting byte equality) actually catches is unseeded randomness, process-dictionary orpersistent_termstate leaking between calls, and clock leakage -- anything whose value can differ between two calls in the SAME VM run. The cross-machine golden comparison (the checked-in golden file, compared byte-for-byte across CI runs on different machines/VM instances) is the backstop for anything environment-shaped that a single-VM determinism test cannot see at all.
:tmux_conservative vs :inline_log
Per ModeSelect's own moduledoc, there is no separate
TmuxConservativeAuthority -- :tmux_conservative routes through the
exact same InlineAuthority as :inline_log (the reflow seam is
detection-only today). So the :tmux_conservative golden for a given
fixture MAY be byte-identical to that fixture's :inline_log golden,
today. Each tier still gets its own pinned golden file rather than being
aliased or skipped, so that the day a real rendering difference gates on
tier (a transient-region algorithm, a capability clamp that changes
emitted bytes), the two tiers can diverge independently without this
matrix needing to be restructured.
Bless status conventions
run/1's bless path (check: false) deliberately extends
Raxol.Harness.Fixture.Bless's :written/:current/:drift/:skipped
status convention rather than reusing it verbatim: :written is split
into :created (no golden existed on disk yet) and :overwritten (a
golden existed and its bytes changed). A byte-golden clobber overwrites
the ONLY reviewable record of what a real terminal would receive for
that fixture x mode pair, so it must be loud at bless time, not just
visible later in a PR diff of an opaque binary file -- :overwritten
results carry a GoldenDiff report of old-vs-new bytes in :diff and
the old size in :old_bytes, and the mix task prints both immediately.
Next to every <fixture>.<mode>.golden, bless also (re)writes
<fixture>.<mode>.golden.txt: a line-oriented, inspect/1-escaped
textual rendering of the same bytes (see escape_lines/1) that IS
reviewable in a PR diff, even though the .golden file itself is
marked binary in .gitattributes (deliberately -- its \r\n bytes
are sealed-line protocol bytes, not line endings, and eol-normalizing
them would corrupt the golden). A missing or stale sidecar is :drift
in --check mode, exactly like a missing or stale golden.
Summary
Functions
Pure, line-oriented, inspect/1-escaped textual rendering of a raw
golden byte stream -- the PR-reviewable form bless writes to
<golden path>.txt next to every byte-opaque .golden file.
The fixture base names this matrix covers (see the moduledoc).
The on-disk path for a fixture x mode pair's golden file, e.g.
test/fixtures/harness/goldens/simple-chat.inline_log.golden. Always
resolved against goldens_dir/0 -- run/1's internal :dir option
(mirroring Fixture.Bless.run/1's own :dir convention) does not
affect this public helper, which existing tests use to read the
checked-in goldens directly.
Directory holding the checked-in golden byte files.
Step budget for drive_to_completion/3, given the number of fixture
events a render will replay.
The degradation-ladder tiers this matrix covers (see the moduledoc).
Renders session_or_name through the assembled Raxol.Harness.Surface
in mode, at this matrix's fixed geometry, and returns the raw emitted
byte stream. session_or_name is either an already-loaded
%Raxol.Harness.Fixture.Session{} or a fixture base name (loaded from
test/fixtures/harness/sessions/<name>.jsonl).
Runs the full fixtures x modes matrix.
Types
@type result() :: %{ name: String.t(), path: Path.t(), bytes: non_neg_integer(), status: status(), diff: String.t() | nil, old_bytes: non_neg_integer() | nil }
@type status() :: :created | :overwritten | :current | :drift
Functions
Pure, line-oriented, inspect/1-escaped textual rendering of a raw
golden byte stream -- the PR-reviewable form bless writes to
<golden path>.txt next to every byte-opaque .golden file.
raw is split into chunks at each \n byte (the newline stays
attached to the chunk it ends -- a chunk NEVER starts with a leftover
fragment of the previous line), each chunk is rendered via
inspect(chunk, limit: :infinity, printable_limit: :infinity) (a
literal Elixir term -- a quoted string or a <<...>> binary literal --
that reproduces the chunk exactly, unabridged), one per line, joined by
a real \n, with one trailing \n.
Concatenating the unescaped form of every emitted line (each parsed
back, e.g. via Code.eval_string/1) reproduces raw byte-for-byte --
see the round-trip property test in
test/harness/golden_snapshot_test.exs. An empty raw produces zero
escaped lines: just the single trailing \n.
@spec fixtures() :: [String.t()]
The fixture base names this matrix covers (see the moduledoc).
@spec golden_path(String.t(), Raxol.Harness.Surface.mode()) :: Path.t()
The on-disk path for a fixture x mode pair's golden file, e.g.
test/fixtures/harness/goldens/simple-chat.inline_log.golden. Always
resolved against goldens_dir/0 -- run/1's internal :dir option
(mirroring Fixture.Bless.run/1's own :dir convention) does not
affect this public helper, which existing tests use to read the
checked-in goldens directly.
@spec goldens_dir() :: Path.t()
Directory holding the checked-in golden byte files.
@spec max_steps(non_neg_integer()) :: pos_integer()
Step budget for drive_to_completion/3, given the number of fixture
events a render will replay.
Derivation: Surface.advance/2 reveals at most one additional event per
call (revealed = min(revealed + 1, length(events))), so at most
event_count advances are ever needed to reveal every event. A render
only reaches :done once revealed events AND painted blocks both catch
up (painted_count trails revealed by at most one flushed block per
advance), so the budget doubles event_count to also cover those
painting-catchup advances, plus a flat 32-step buffer for advances
that do not correspond to a revealed event at all (the startup footer
paint, the mode-notice paint, and any other one-shot bookkeeping step).
@spec modes() :: [Raxol.Harness.Surface.mode()]
The degradation-ladder tiers this matrix covers (see the moduledoc).
@spec render( Raxol.Harness.Fixture.Session.t() | String.t(), Raxol.Harness.Surface.mode() ) :: binary()
Renders session_or_name through the assembled Raxol.Harness.Surface
in mode, at this matrix's fixed geometry, and returns the raw emitted
byte stream. session_or_name is either an already-loaded
%Raxol.Harness.Fixture.Session{} or a fixture base name (loaded from
test/fixtures/harness/sessions/<name>.jsonl).
See the moduledoc's "Determinism audit" for why this is safe to compare
byte-for-byte across runs/machines: no wall clock, no live environment,
no capability probe, fixed geometry. The replay loop is bounded (see
drive_to_completion/3 / max_steps/1) rather than an unbounded
recursion, so a non-converging Surface.advance/2 raises loudly instead
of hanging.
Runs the full fixtures x modes matrix.
Options:
:check(defaultfalse) -- whentrue, writes nothing: compares each fresh render against its on-disk golden AND its on-disk escaped textual sidecar (<path>.txt, seeescape_lines/1), reporting:driftfor either one missing or stale.:dir(defaultgoldens_dir/0) -- directory holding the golden (+ sidecar) files, mirroringFixture.Bless.run/1's own:dirconvention. Exists mainly so error-handling behavior (Fix 4: a directory occupying a golden's path) is unit-testable against a scratch directory rather than the checked-in one.
When :check is false (the bless path): writes a golden that doesn't
exist yet (status :created), overwrites one whose bytes changed
(status :overwritten -- see the moduledoc's "Bless status
conventions"), or reports :current when the freshly rendered bytes
already match what's on disk (a stale/missing sidecar is silently
repaired in that case; the pair's status stays :current since the
GOLDEN itself did not change). Sidecar (re)writes always accompany a
golden create/overwrite.
Returns {:ok, [result()]} when nothing drifted (or, in bless mode,
always -- writing resolves drift by construction), or {:error, {:drift, names}} in check mode when one or more fixture x mode pairs drifted,
where names are "<fixture>.<mode>" strings.