Fix an issue on run tests on Windows with Python 3.6 and later. Since Python 3.6 on Windows, io.IOBase object overlaid tty is incompatible with which of other file like objects. It makes impossible to redirect input or output between file and tty without changing file descriptor number by using os.dup2() without changing overlaid object[1]. This is a work around for it. [1] _io._WindowsConsoleIO breaks in the face of fd redirection https://bugs.python.org/issue30555 * build/run_tests.py (rebuild_textio): New. (TestHarness._run_py_test): Rebuild IO object after os.dup2() if it is needed. Reported by: jcorvel Index: build/run_tests.py =================================================================== --- build/run_tests.py (revision 1876712) +++ build/run_tests.py (working copy) @@ -48,6 +48,7 @@ ''' import os, sys, shutil, codecs +import io import re import logging import optparse, subprocess, threading, traceback @@ -141,6 +142,10 @@ else: return s.decode("latin-1") +def rebuild_textio(fd, mode): + '''Rebuild an io.BaseIO object from file descriptor for sys.std*''' + return open(fd, mode, encoding='utf-8', errors='replace', closefd=False) + class TestHarness: '''Test harness for Subversion tests. ''' @@ -844,6 +849,16 @@ # setup the output pipes if self.log: + # Since Python 3.6 on Windows, redirecting tty to file or file to tty + # by using os.dup2 breaks overlaid IO object. + # (See https://bugs.python.org/issue30555). + # To avoid this, we rebuild IO object after os.dup2 if it is needed. + need_rebuild_stdout = ( isinstance(sys.stdout, io.IOBase) + and not isinstance(sys.stdout.buffer.raw, + io.FileIO)) + need_rebuild_stderr = ( isinstance(sys.stderr, io.IOBase) + and not isinstance(sys.stderr.buffer.raw, + io.FileIO)) sys.stdout.flush() sys.stderr.flush() self.log.flush() @@ -851,6 +866,10 @@ old_stderr = os.dup(sys.stderr.fileno()) os.dup2(self.log.fileno(), sys.stdout.fileno()) os.dup2(self.log.fileno(), sys.stderr.fileno()) + if need_rebuild_stdout: + sys.stdout = rebuild_textio(sys.stdout.fileno(), 'w') + if need_rebuild_stderr: + sys.stderr = rebuild_textio(sys.stderr.fileno(), 'w') # These have to be class-scoped for use in the progress_func() self.dots_written = 0 @@ -897,6 +916,10 @@ os.dup2(old_stderr, sys.stderr.fileno()) os.close(old_stdout) os.close(old_stderr) + if need_rebuild_stdout: + sys.stdout = rebuild_textio(sys.stdout.fileno(), 'w') + if need_rebuild_stderr: + sys.stderr = rebuild_textio(sys.stderr.fileno(), 'w') return failed