test(pty): Add a terminal harness for screen tests - #1100
Open
JeanMertz wants to merge 3 commits into
Open
Conversation
A test can now assert on what a terminal renders rather than on the bytes JP emitted. `jp_pty::Terminal` hands out a `Writer` for the code under test to draw into and answers through `Screen`: what row `N` holds, where the cursor is, whether a row wrapped. Scrolling, deferred wrap at the right margin, and resize only exist on the far side of that boundary, so none of them was reachable from a byte assertion. Two backends sit behind one screen API. `Terminal::pty` opens a real pty through `portable-pty`, so output crosses the kernel's line discipline, the writer is a tty, and a child can be spawned into it, typed at, and resized under it. `Terminal::modelled` feeds the `vt100` screen model directly, imitating a tty's `ONLCR`. `Terminal::open` takes the first where the platform allows and falls back to the second, which is what Windows gets: ConPTY is reachable only by a child process, so a pty-only harness would have left every in-process case unix-only. `expectrl` was the alternative considered and rejected: it matches a spawned child's output stream and cannot hand a tty to in-process code. `wait_for` blocks on a condvar woken by arriving bytes and returns the screen that satisfied its predicate, so the assertions after it are made against that snapshot rather than a later one; nothing sleeps for a fixed interval. A timeout renders the screen with the cursor marked, and `Error`'s `Debug` defers to `Display`, so an `unwrap` in a test prints it. Signed-off-by: Jean Mertz <git@jeanmertz.com>
The status region's multi-row draw and erase cases run against a terminal from `jp_pty`, a real pty where the platform has one, and assert on the rows and the cursor it renders. Three byte snapshots of the same behaviour go with them. A byte assertion cannot say whether a terminal scrolled, wrapped, or clamped the cursor, and that is where multi-row erasure goes wrong. `shrinking_below_the_block_spares_the_content_above_it` was asserting less than its name claimed: `cursor().0 < 4` holds whether or not the erase caps its walk, because a cursor-up clamps at row 0 either way. Which rows survive a shrink turns out to be the terminal's reflow policy, which the screen model does not imitate. The case is renamed to `an_erase_after_a_shrink_stops_at_the_top_of_the_viewport` and pins the answerable part: every reachable row is cleared and the walk stops at the top. It fails when the erase is removed. `examples/region_spike.rs` is deleted. It measured the cursor position around each draw against a live terminal for a human to read off, and the four cases it settled are assertions now. Signed-off-by: Jean Mertz <git@jeanmertz.com>
The status region's multi-row draw and erase now run against a real console. `region_probe` prints past the bottom of the screen, claims a region with a two-row window, and either holds the block or releases it and writes over where it was; `tests/region_pty.rs` spawns it and asserts on the rows and the cursor that come back. The in-process cases reach a pty on unix and the screen model everywhere else, because a pty's subsidiary end cannot be written to from this process on Windows. Running them there is regression coverage — a change to JP's own sequence still fails — but it is the same platform-independent Rust that runs on Linux, so it cannot answer what a console does with that sequence. That was the question RFD 091 phase 4's spike left open, and a spawned child is the only way into a ConPTY. `Terminal::pty` is used directly rather than `Terminal::open`, so a platform without one fails the case instead of quietly measuring the model. A pty echoes what is typed into it, and that moves the screen. The probe first took its step as a command on stdin, and the echoed line landed at the cursor — which sits inside the block — scrolling the screen by a row the region never accounted for, stranding its top row and putting every later erase one row low. It reproduced identically on every run and read exactly like a multi-row erase bug. `a_block_released_at_the_bottom_leaves_no_row_behind` covers the same sequence in process and passes, which is what placed the fault in the measurement rather than the code. The step is an argument now and nothing is typed at the probe. Signed-off-by: Jean Mertz <git@jeanmertz.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A test can now assert on what a terminal renders rather than on the
bytes JP emitted.
jp_pty::Terminalhands out aWriterfor the codeunder test to draw into and answers through
Screen: what rowNholds, where the cursor is, whether a row wrapped. Scrolling, deferred
wrap at the right margin, and resize only exist on the far side of that
boundary, so none of them was reachable from a byte assertion.
Two backends sit behind one screen API.
Terminal::ptyopens a real ptythrough
portable-pty, so output crosses the kernel's line discipline,the writer is a tty, and a child can be spawned into it, typed at, and
resized under it.
Terminal::modelledfeeds thevt100screen modeldirectly, imitating a tty's
ONLCR.Terminal::opentakes the firstwhere the platform allows and falls back to the second, which is what
Windows gets: ConPTY is reachable only by a child process, so a
pty-only harness would have left every in-process case unix-only.
expectrlwas the alternative considered and rejected: it matches aspawned child's output stream and cannot hand a tty to in-process code.
wait_forblocks on a condvar woken by arriving bytes and returns thescreen that satisfied its predicate, so the assertions after it are made
against that snapshot rather than a later one; nothing sleeps for a
fixed interval. A timeout renders the screen with the cursor marked, and
Error'sDebugdefers toDisplay, so anunwrapin a test printsit.