Reopen sys.__std*__ on Windows consoles so they follow fd capture - #14736
Reopen sys.__std*__ on Windows consoles so they follow fd capture#14736Abhishek-kroy wants to merge 1 commit into
Conversation
On Windows, when attached to a console, the sys.__std{in,out,err}__
streams are backed by _WindowsConsoleIO, which writes through the
console handle rather than the file descriptor and whose isatty()
unconditionally returns True. As a result, while FDCapture had
redirected the fds, sys.__stdout__.isatty() kept returning True (unlike
on POSIX, where it returns False), and writes to sys.__stdout__ either
escaped capturing and appeared on the terminal or failed with
"OSError: [WinError 6] The handle is invalid" once dup2 had closed the
console handle.
Extend _windowsconsoleio_workaround to also reopen the dunder streams,
backed by their original file descriptors. The raw stream is built
explicitly with io.FileIO because open() on a console fd would produce
another _WindowsConsoleIO.
Closes pytest-dev#12349
Co-authored-by: Claude <noreply@anthropic.com>
|
closing as unattended ai contribution - the errors are clear and easy to fix yet nothing was touched after initial drop in |
|
Thanks for the detailed writeup — the analysis of We deliberately do not touch the dunder streams. The motivating breakage no longer applies. The failing writes and What remains is only What would change our minds is a concrete example. Real code that breaks under pytest on a supported Python — not a synthetic If you hit an actual failure in real code, please do open an issue with it and we'll take another look. |
Closes #12349
The problem
On Windows, when attached to a console,
sys.__stdin__/sys.__stdout__/sys.__stderr__are backed by_io._WindowsConsoleIO, which writes through the console handle rather than the file descriptor, and whoseisatty()unconditionally returnsTrue.So while
FDCapturehasdup2-ed the fds away (--capture=fd, the default):sys.__stdout__.isatty()keeps returningTrue, whileos.isatty(1)correctly returnsFalse— unlike POSIX, where both returnFalse(this is the exact assertion failure reported in sys.__stdout__.isatty() returns incorrect value for Windows 11 #12349, reproduced on Windows 11);sys.__stdout__either escape capturing and are printed straight to the terminal, or fail withOSError: [WinError 6] The handle is invalidoncedup2has closed the console handle (the very handle instability already described in_windowsconsoleio_workaround's docstring).The fix
Extend
_windowsconsoleio_workaroundto also reopen the dunder streams, backed by their original file descriptors (unlikesys.std*, which keep being reopened on duplicated fds for handle isolation). The raw stream is constructed explicitly withio.FileIO, becauseopen()on a console fd produces another_WindowsConsoleIOand would change nothing.After the fix the dunder streams follow fd redirection exactly like on POSIX:
isatty()reflects the actual state of the fd, writes during capture land in the capture file, and writes outside capture reach the console.sys.__stdin__.encodingandsys.__stderr__.fileno()(used by the faulthandler plugin) are preserved.Verification on Windows 11
Running the reproducer from #12349 under a real console (ConPTY), before the fix:
and a write probe showed
OSError(9, 'The handle is invalid')plus output leaking to the terminal despite capture being active.After the fix, both asserts from the issue pass, and writes to
sys.__stdout__are captured.testing/test_capture.py(131 tests),testing/test_terminal.pyandtesting/test_faulthandler.pyall pass both piped and under a real console on Windows 11.The new regression test fakes the console environment (monkeypatched
sys.platformandio._WindowsConsoleIO), so it exercises this code path on all platforms/CI runners, where no real console is available.closes #XYZWto the PR description and/or commits (whereXYZWis the issue number).Co-authored-bycommit trailers.changelogdirectory (changelog/12349.bugfix.rst).AUTHORSin alphabetical order.Disclosure per the AI/LLM-Assisted Contributions Policy: this change was developed with AI assistance (Claude Code), credited in the commit trailer. I take responsibility for the change and will respond to review feedback myself.