Skip to content

uutests: open simulated terminals close-on-exec - #15217

Merged
sylvestre merged 2 commits into
uutils:mainfrom
abendrothj:uutests-pty-cloexec
Oct 12, 2026
Merged

sylvestre merged 2 commits into
uutils:mainfrom
abendrothj:uutests-pty-cloexec

Conversation

@abendrothj

@abendrothj abendrothj commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

The PTYs behind terminal simulation weren't close-on-exec, so commands other tests started in the meantime inherited them. That made the descriptor-limit tests flaky, such as sort's on #15135.

On Linux, Android, FreeBSD and NetBSD the PTY is now opened close-on-exec through rustix. Elsewhere close-on-exec is set right after openpty, which leaves a short window. If the platform split isn't wanted, the openpty + fcntl path alone would do, with that window everywhere.

Removing the leak breaks the harness's stdin terminal: it only stayed open because the child inherited a copy of its controller end, so "closing" stdin now hangs it up (on Linux, 6 of the 10 terminal-simulation tests in test_env.rs fail). A thread now reads that PTY's echo, as a terminal would, which keeps it open until the child closes it. That also avoids a ~0.6s wait per test on macOS, where exit waits for the unread echo.

test_simulation_of_terminal_not_inherited_by_other_commands fails on main and passes here.

test_more now kills more after each pty test: some options don't quit on q (see #8429), and once the terminal stops leaking, a leftover more spins and slows the tail tests.

Closes #15216

Copilot AI balanced review requested due to automatic review settings October 10, 2026 04:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread tests/by-util/test_env.rs
#[cfg(target_os = "linux")]
#[test]
fn test_simulation_of_terminal_not_inherited_by_other_commands() {
let scene = TestScenario::new("util");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sleep 1 would be enough, no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same cfg list twice, could it be a single cfg_if! or an else branch?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no cfg_if in the tree, and cfg_select! isn't available at our 1.88 MSRV. A cfg!() if/else would need both branches to compile everywhere, but OpenptFlags::CLOEXEC only exists on Linux, Android, FreeBSD and NetBSD. I can name the list once with a cfg_aliases build.rs in uutests, if you prefer.

openpty doesn't set close-on-exec, so every command that another test
started while a terminal was simulated inherited its PTYs, up to six
descriptors. Tests that run under a low descriptor limit then failed.

Open the PTY close-on-exec through rustix where the OS supports it
(Linux, Android, FreeBSD, NetBSD), and set it right after openpty
elsewhere, which leaves a short window.

The stdin PTY stayed open only through the copy the child inherited.
Read its echo in a thread, as a terminal would, which keeps it open until
the child closes it; macOS also waits at exit for an unread echo.
Copilot AI balanced review requested due to automatic review settings October 11, 2026 02:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown

GNU testsuite comparison:

Skipping an intermittent issue tests/date/resolution (passes in this run but fails in the 'main' branch)

Copilot AI balanced review requested due to automatic review settings October 12, 2026 04:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@sylvestre
sylvestre merged commit 2b04160 into uutils:main Oct 12, 2026
89 checks passed
@sylvestre

Copy link
Copy Markdown
Contributor

Thanks for your PR

/// Open a PTY whose two ends are close-on-exec, so that commands started by
/// other tests in the meantime don't inherit them.
#[cfg(all(unix, not(target_os = "redox")))]
fn open_pty(size: Option<&libc::winsize>) -> OpenptyResult {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails to compile on NetBSD targets:

$ RUSTC_BOOTSTRAP=1 cargo clippy -q -Zbuild-std -p uutests --target i686-unknown-netbsd
error[E0432]: unresolved import `rustix::pty::ptsname`
    --> tests/uutests/src/lib/util.rs:3057:57
     |
3057 |         use rustix::pty::{OpenptFlags, grantpt, openpt, ptsname, unlockpt};
     |                                                         ^^^^^^^ no `ptsname` in `pty`
     |
note: found an item that was configured out
    --> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.5/src/pty.rs:135:8
     |
 124 |       any(
     |  ________-
 125 | |         apple,
 126 | |         linux_like,
 127 | |         target_os = "freebsd",
 128 | |         target_os = "fuchsia",
 129 | |         target_os = "illumos"
 130 | |     )
     | |_____- the item is gated here
...
 135 |   pub fn ptsname<Fd: AsFd, B: Into<Vec<u8>>>(fd: Fd, reuse: B) -> io::Result<CString> {
     |          ^^^^^^^

For more information about this error, try `rustc --explain E0432`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will address in #14252

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I missed that rustix has no ptsname on NetBSD.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tests: simulated terminals leak into commands that other tests start

4 participants