Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,32 @@ reads that marker.
which moved to the next row because it did not fit in an empty last
column is not recorded as a wrap, so `logical_text()` splits the line
there (#585).
- Dropping a `Terminal` no longer holds the process-wide PTY lock while
the child stops (#589). It sent SIGHUP and polled in portable-pty's fixed
50 ms steps under the lock, so a drop took 50 ms (200 ms when the child
ignores SIGHUP), and every spawn in the test binary waited it out. The
child is now stopped first, polled from 1 ms, and the lock is taken only
to close the descriptors: measured, 1 ms per drop and no wait for a
concurrent spawn. The responder threads' copies of the master now close
under the lock too.
- `Error::Write` at the deadline says what happens to the bytes (#590): the
write is not withdrawn, the rest of it reaches the application if it
reads again, and later input queues behind it. The message and the docs
said the write "gave up", so a retry typed its input twice.
- A query's reply is queued before the screen the query is drawn on can be
seen (#591). A test that waited for that screen and typed could have its
keystroke reach the application ahead of the reply: 11 runs in 9,600 at
32 threads, and none since.
- On Unix, a program named with a directory part, such as
`target/debug/app`, is found from the working directory, as
`std::process::Command` finds it (#592). Only `PATH` was searched, so the
spawn failed with "No viable candidates found in PATH", or under
`env_clear()` could not resolve `PATH` at all. `PATH` is still searched
after.
- On Windows, a reaped child is the terminal closing 500 ms after the reap,
however short the waits that follow (#593). Each wait restarted that
grace, so waits shorter than it never saw EOF, and a send to the dead
child was not refused.
- `termlens` no longer panics with exit code 101 (#594). A closed or full
stderr (`2>&1 | true`, `2>/dev/full`) made every diagnostic and the
`inspect` trailer panic; the write is now checked, and a trailer that cannot
Expand Down
7 changes: 7 additions & 0 deletions crates/termlens/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ pub enum Error {
/// means the bytes could not be handed over at all, which is a fact
/// about the child rather than about the test, so the screen at the
/// moment of the failure is embedded the way a timeout's is.
///
/// A write that gave up at the deadline is **not withdrawn**: the bytes
/// not yet written stay queued and reach the application, in order, if
/// it ever reads again, and input sent afterwards waits behind them —
/// failing the same way while the queue does not move. So a retry types
/// its input twice, and "the application never got these bytes" is not
/// what this error says (#590).
#[error("failed to send {what}\n--- screen at the failed write ---\n{screen}")]
Write {
/// What was being sent, which command it was going to, and why the
Expand Down
21 changes: 16 additions & 5 deletions crates/termlens/src/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,14 @@ mod unix {
})
}

/// The file the PTY layer would execute for `program` run in `dir`,
/// chosen by its rules, or `None` for the PTY layer to say why there is
/// none.
/// The file to execute for `program` run in `dir`, or `None` for the PTY
/// layer to say why there is none.
///
/// The PTY layer's rules, with one change: a name with a directory part
/// is a path from `dir` first, as `execvp` and `std::process::Command`
/// take it. The PTY layer searched PATH for `target/debug/app`, never
/// the directory the test runs in (#592); PATH is still searched after,
/// so nothing it found stops being found.
fn resolve(program: &OsStr, dir: &Path, path: Option<&OsStr>) -> Option<PathBuf> {
let program = Path::new(program);
let runnable = |file: &Path| !file.is_dir() && executable(file);
Expand All @@ -219,8 +224,14 @@ mod unix {
let file = dir.join(program);
return runnable(&file).then_some(file);
}
// Anything else, `sub/x` included, along PATH, each entry taken
// relative to `dir`.
// `sub/x` in `dir`, then along PATH; a bare name along PATH only.
if program.components().count() > 1 {
let file = dir.join(program);
if runnable(&file) {
return Some(file);
}
}
// Each PATH entry taken relative to `dir`.
std::env::split_paths(path?)
.map(|entry| dir.join(entry).join(program))
.find(|file| runnable(file))
Expand Down
Loading
Loading