diff --git a/docs/changelog/2999.bugfix.rst b/docs/changelog/2999.bugfix.rst new file mode 100644 index 0000000000..7e4e336979 --- /dev/null +++ b/docs/changelog/2999.bugfix.rst @@ -0,0 +1 @@ +Fix terminal size of tox subcommands (fixes ipython, ipdb, prompt_toolkit, ...). diff --git a/src/tox/execute/local_sub_process/__init__.py b/src/tox/execute/local_sub_process/__init__.py index efd9c9c788..d79788515c 100644 --- a/src/tox/execute/local_sub_process/__init__.py +++ b/src/tox/execute/local_sub_process/__init__.py @@ -314,7 +314,7 @@ def _pty(key: str) -> tuple[int, int] | None: # adjust sub-process terminal size columns, lines = shutil.get_terminal_size(fallback=(-1, -1)) if columns != -1 and lines != -1: - size = struct.pack("HHHH", columns, lines, 0, 0) + size = struct.pack("HHHH", lines, columns, 0, 0) fcntl.ioctl(child, termios.TIOCSWINSZ, size) return main, child diff --git a/tests/execute/local_subprocess/test_local_subprocess.py b/tests/execute/local_subprocess/test_local_subprocess.py index 22c854d73b..4c7184bf6a 100644 --- a/tests/execute/local_subprocess/test_local_subprocess.py +++ b/tests/execute/local_subprocess/test_local_subprocess.py @@ -3,6 +3,7 @@ import json import logging import os +import pty import shutil import stat import subprocess @@ -19,6 +20,7 @@ from tox.execute.api import ExecuteOptions, Outcome from tox.execute.local_sub_process import SIG_INTERRUPT, LocalSubProcessExecuteInstance, LocalSubProcessExecutor +from tox.execute.local_sub_process.read_via_thread_unix import ReadViaThreadUnix from tox.execute.request import ExecuteRequest, StdinSource from tox.execute.stream import SyncWrite from tox.report import NamedBytesIO @@ -140,6 +142,42 @@ def test_local_execute_write_a_lot(os_env: dict[str, str]) -> None: assert outcome.err == expected_err, expected_err[len(outcome.err) :] +@pytest.mark.skipif(sys.platform == "win32", reason="Unix terminal size test") +def test_local_execute_terminal_size(os_env: dict[str, str], monkeypatch: MonkeyPatch) -> None: + """Regression test for #2999 - check terminal size is set correctly in tox subprocess.""" + terminal_size = os.terminal_size((84, 42)) + main, child = pty.openpty() + # Use ReadViaThreadUnix to help with debugging the test itself. + with ( + ReadViaThreadUnix(main, sys.stdout.buffer.write, name="testout", drain=True), + monkeypatch.context() as monkey, + open(child, "w") as stdout_mock, # noqa: PTH123 + ): + # Switch stdout with test pty + monkey.setattr(sys, "stdout", stdout_mock) + monkey.setenv("COLUMNS", "84") + monkey.setenv("LINES", "42") + + executor = LocalSubProcessExecutor(colored=False) + request = ExecuteRequest( + cmd=[sys.executable, "-c", "import os; print(os.get_terminal_size())"], + cwd=Path(), + env=os_env, + stdin=StdinSource.OFF, + run_id="", + ) + out_err = FakeOutErr() + with executor.call(request, show=False, out_err=out_err.out_err, env=MagicMock()) as status: + while status.exit_code is None: # pragma: no branch + status.wait() + outcome = status.outcome + assert outcome is not None + assert bool(outcome), outcome + expected_out = f"{terminal_size!r}\r\n" + assert outcome.out == expected_out, expected_out[len(outcome.out) :] + assert not outcome.err + + def test_local_execute_basic_fail(capsys: CaptureFixture, caplog: LogCaptureFixture, monkeypatch: MonkeyPatch) -> None: monkeypatch.chdir(Path(__file__).parents[3]) caplog.set_level(logging.NOTSET)