From e49b73e665c00a511a4c8055318379bfc245b9fe Mon Sep 17 00:00:00 2001 From: Ana Date: Thu, 15 Oct 2026 17:40:00 +0200 Subject: [PATCH] fix: stop test scripts from inheriting the terminal Scripts inherited the TUI's terminal as stdin, so a tool like flutter could switch it back to line mode with echo on exit. Mouse events then printed as raw escape codes and shortcuts stopped working. --- render_machine/render_utils.py | 1 + tests/test_execute_script.py | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 tests/test_execute_script.py diff --git a/render_machine/render_utils.py b/render_machine/render_utils.py index 790ec61e..b6b83b02 100644 --- a/render_machine/render_utils.py +++ b/render_machine/render_utils.py @@ -109,6 +109,7 @@ def execute_script( # noqa: C901 start_time = time.time() proc = subprocess.Popen( cmd, + stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, diff --git a/tests/test_execute_script.py b/tests/test_execute_script.py new file mode 100644 index 00000000..1bc8d3cc --- /dev/null +++ b/tests/test_execute_script.py @@ -0,0 +1,44 @@ +"""Tests for running test scripts with render_utils.execute_script.""" + +import os +import stat +import sys +from pathlib import Path + +import pytest + +from render_machine.render_utils import execute_script + + +@pytest.fixture +def stdin_with_input(): + """Replace fd 0 with a pipe holding a line, as a terminal would after a keypress. + + A pipe stands in for the terminal: whether a script can reach codeplain's stdin + does not depend on what kind of file stdin is, and a pipe works in CI too. + """ + read_fd, write_fd = os.pipe() + os.write(write_fd, b"keypress\n") + os.close(write_fd) + # A child process inherits file descriptor 0, not sys.stdin, so fd 0 is what has to change. + saved_stdin = os.dup(0) + os.dup2(read_fd, 0) + os.close(read_fd) + yield + os.dup2(saved_stdin, 0) + os.close(saved_stdin) + + +@pytest.mark.skipif(sys.platform == "win32", reason="shell scripts are not supported on Windows") +def test_script_does_not_inherit_stdin(tmp_path: Path, stdin_with_input): + # A tool such as flutter resets the terminal it reads from, which breaks the TUI. + # The script reports what it finds on its stdin: the waiting line if it inherited + # codeplain's stdin, end-of-file if it was given an empty one. + script = tmp_path / "read_stdin.sh" + script.write_text('#!/bin/sh\nif read -r line; then echo "read:$line"; else echo "eof"; fi\n') + script.chmod(script.stat().st_mode | stat.S_IEXEC) + + returncode, output, _ = execute_script(str(script), [], "Unit Tests") + + assert returncode == 0 + assert output.strip() == "eof"