Documentation
¶
Overview ¶
Package e2e drives the compiled binary inside a real terminal so that rendering, key handling and resize behaviour can be asserted on the frames a user would actually see.
The terminal itself is whatever the platform really offers. On Unix it is tmux, driven as a scriptable terminal; on Windows it is a ConPTY pseudoconsole owned by the test process, with the program's output parsed by a virtual terminal emulator. Both are real terminals in the sense that matters here: the program under test is talking to a pty or a console host, not to a pipe, so it turns colour on, enters the alternate screen, and is told its size. The platform files carry the facts each backend was built around, all of which were established from the platform's own documentation and behaviour rather than assumed.
This file holds everything that is the same on both: the API tests use, and the waits, which are where a terminal test is won or lost. Every wait polls for a condition rather than sleeping for a guessed duration, because a slow machine makes a sleep too short, never too long.
Index ¶
- Variables
- func Available() error
- type Options
- type Terminal
- func (tm *Terminal) Alive() bool
- func (tm *Terminal) AssertFits()
- func (tm *Terminal) Capture() string
- func (tm *Terminal) CaptureANSI() string
- func (tm *Terminal) Close()
- func (tm *Terminal) ExitStatus() (int, bool)
- func (tm *Terminal) Lines() []string
- func (tm *Terminal) MustWaitFor(substr string, timeout time.Duration) string
- func (tm *Terminal) Paste(text string)
- func (tm *Terminal) Resize(width, height int)
- func (tm *Terminal) ResizeAndWait(width, height int, timeout time.Duration) string
- func (tm *Terminal) SendKeys(keys ...string)
- func (tm *Terminal) SendText(text string)
- func (tm *Terminal) Size() (width, height int)
- func (tm *Terminal) WaitChanged(previous string, timeout time.Duration) string
- func (tm *Terminal) WaitExit(timeout time.Duration) (int, bool)
- func (tm *Terminal) WaitFor(substr string, timeout time.Duration) (string, error)
- func (tm *Terminal) WaitSettled(timeout time.Duration) string
Constants ¶
This section is empty.
Variables ¶
var ErrTimeout = errors.New("timed out waiting for the terminal")
ErrTimeout is returned when a wait gives up.
Functions ¶
Types ¶
type Options ¶
type Options struct {
// Width and Height are the initial terminal size in character cells.
Width, Height int
// Dir is the working directory for the command.
Dir string
// Env holds extra environment entries in KEY=VALUE form. TERM and the
// colour-related variables are set for you unless you override them.
Env []string
// Color leaves colour enabled. By default colour is switched off so that
// captured frames are stable text.
Color bool
}
Options configures a Terminal.
type Terminal ¶
type Terminal struct {
// contains filtered or unexported fields
}
Terminal is one command running in a real terminal.
Every method reports failure through the test rather than returning an error, except the waits that a test may legitimately expect to time out.
func Start ¶
Start launches command in a new terminal of the requested size and returns once the program has been started.
command is an argument vector, not a shell command line: command[0] is the executable and the rest are passed to it exactly as given, so a path or an argument containing spaces needs no quoting by the caller and no shell is involved. Pass an absolute path for command[0]: a relative one is resolved against a working directory that differs between the platforms, since one terminal starts the program itself and the other has the program started for it. The Terminal is closed automatically when the test finishes.
func (*Terminal) AssertFits ¶
func (tm *Terminal) AssertFits()
AssertFits checks the invariant every frame must satisfy: no line wider than the terminal and no more lines than it has rows. A frame that breaks this corrupts the display and is the most common resize bug.
func (*Terminal) Capture ¶
Capture returns the visible screen as plain text with trailing blanks trimmed from each line.
func (*Terminal) CaptureANSI ¶
CaptureANSI returns the visible screen including the escape sequences that style it, for assertions where styling is the thing under test.
func (*Terminal) Close ¶
func (tm *Terminal) Close()
Close tears down the terminal and everything running in it.
func (*Terminal) ExitStatus ¶
ExitStatus returns the program's exit status, and whether it has exited.
A terminal is dead the moment the program closes it; the exit status arrives when the program is reaped, which is a separate event and can come later. In that window a backend can see a dead terminal with no status yet, and reading that as zero made an immediate `exit 3` report as a clean exit on a loaded runner. A dead terminal with no status yet is therefore not "exited" here: the caller keeps polling, and the answer it gets is the program's own.
func (*Terminal) MustWaitFor ¶
MustWaitFor is WaitFor with the error turned into a test failure.
func (*Terminal) Paste ¶ added in v0.5.0
Paste pastes text the way a terminal does when the user presses the paste key: as one bracketed block, not as a run of keystrokes. Programs that read pastes as a unit take a different path for it, and a run of SendKeys would not exercise that path.
func (*Terminal) ResizeAndWait ¶
ResizeAndWait changes the size and waits for the program to redraw, which is the only safe way to assert on a frame after a resize.
func (*Terminal) SendKeys ¶
SendKeys sends key names to the program: "Enter", "Escape", "Space", "Tab", "BSpace", "Up", "Down", "Left", "Right", "Home", "End", "PageUp", "PageDown", "Insert", "Delete", "F1" to "F12", a single printable character, and those prefixed with "C-", "M-" or "S-" for control, alt and shift. Names are matched without regard to case.
A name that is not one of those is a test failure rather than something sent literally: a typo that silently typed its own letters into the program would be found as a mysterious assertion failure much later. Use SendText for literal text.
func (*Terminal) SendText ¶
SendText sends literal text, which is what to use for characters a key name would claim.
func (*Terminal) Size ¶
Size returns the size the program actually sees. It is not necessarily the size passed to Start or Resize, so assertions about wrapping should use this.
func (*Terminal) WaitChanged ¶
WaitChanged blocks until the screen differs from previous and has then stopped changing, and returns the new screen.
This is the wait to use after anything that should redraw. WaitSettled alone is not enough: it returns as soon as the screen has held still for a moment, and immediately after a resize the screen is still the old one and perfectly still, so a caller can be handed the frame from before the change and assert against it. That is not a hypothetical — it is how a genuine recovery from the too-small state looked like a failure to recover.
func (*Terminal) WaitExit ¶ added in v0.3.1
WaitExit blocks until the program has exited and its status is known, and returns that status. It is the wait to use before asserting on an exit code: Alive going false says the terminal closed, which is earlier than the status being known, so a caller that polls Alive and then reads ExitStatus once can read it in the gap.
A program that has demonstrably exited without its status ever becoming available is reported through t.Fatalf with the backend's own view of it, rather than as a timeout: that state has a cause, and the cause is in that report rather than in the program under test.
func (*Terminal) WaitFor ¶
WaitFor blocks until the screen contains substr. It returns the screen that matched.
func (*Terminal) WaitSettled ¶
WaitSettled blocks until the screen has stopped changing, and returns it. Polling for a stable frame is deterministic where sleeping for a guessed duration is not: a slow machine makes a sleep too short, never too long.