Remove uses of os.dup2() from run_tests.py in order to adapt the new console
reader and writer on Windows since Python 3.6.

* build/run_tests.py
  (open_logfile): New function returning file-like object which is reassignable
  sys.stdout and sys.stderr.
  (TestHarness.run): Use open_logfile() instead of codecs.open().
  (TestHarness._open_log): Ditto.
  (TestHarness._run_py_test): Reassign sys.stdout and sys.stderr instead of
  uses of os.dup2().

* subversion/tests/cmdline/svntest/main.py
  (LoggingStdoutHandler): New function to use the value of sys.stdout at call
  time.
  (parse_options): Use LoggingStdoutHandler() instead of
  StreamHandler(sys.stdout).

Tested by: jcorvel
Reviewed by: jcorvel
             futatuki


git-svn-id: https://svn.apache.org/repos/asf/subversion/trunk@1883337 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/build/run_tests.py b/build/run_tests.py
index 5a661af..7b4eb15 100755
--- a/build/run_tests.py
+++ b/build/run_tests.py
@@ -47,7 +47,7 @@
 separated list of test numbers; the default is to run all the tests in it.
 '''
 
-import os, sys, shutil, codecs
+import os, sys, shutil
 import re
 import logging
 import optparse, subprocess, threading, traceback
@@ -141,6 +141,18 @@
   else:
     return s.decode("latin-1")
 
+def open_logfile(filename, mode, encoding='utf-8'):
+  if sys.version_info[0] != 2:
+    return open(filename, mode, encoding=encoding, errors='surrogateescape')
+  else:
+    class Wrapper(object):
+      def __init__(self, stream, encoding):
+        self._stream = stream
+        self.encoding = encoding
+      def __getattr__(self, name):
+        return getattr(self._stream, name)
+    return Wrapper(open(filename, mode), encoding)
+
 class TestHarness:
   '''Test harness for Subversion tests.
   '''
@@ -700,7 +712,7 @@
     # Copy the truly interesting verbose logs to a separate file, for easier
     # viewing.
     if xpassed or failed_list:
-      faillog = codecs.open(self.faillogfile, 'w', encoding="latin-1")
+      faillog = open_logfile(self.faillogfile, 'w')
       last_start_lineno = None
       last_start_re = re.compile('^(FAIL|SKIP|XFAIL|PASS|START|CLEANUP|END):')
       for lineno, line in enumerate(log_lines):
@@ -733,7 +745,7 @@
     'Open the log file with the required MODE.'
     if self.logfile:
       self._close_log()
-      self.log = codecs.open(self.logfile, mode, encoding="latin-1")
+      self.log = open_logfile(self.logfile, mode)
 
   def _close_log(self):
     'Close the log file.'
@@ -843,14 +855,13 @@
       sys.exit(1)
 
     # setup the output pipes
+    old_stdout = sys.stdout.fileno()
     if self.log:
       sys.stdout.flush()
       sys.stderr.flush()
       self.log.flush()
-      old_stdout = os.dup(sys.stdout.fileno())
-      old_stderr = os.dup(sys.stderr.fileno())
-      os.dup2(self.log.fileno(), sys.stdout.fileno())
-      os.dup2(self.log.fileno(), sys.stderr.fileno())
+      saved_stds = sys.stdout, sys.stderr
+      sys.stdout = sys.stderr = self.log
 
     # These have to be class-scoped for use in the progress_func()
     self.dots_written = 0
@@ -891,12 +902,8 @@
 
     # restore some values
     if self.log:
-      sys.stdout.flush()
-      sys.stderr.flush()
-      os.dup2(old_stdout, sys.stdout.fileno())
-      os.dup2(old_stderr, sys.stderr.fileno())
-      os.close(old_stdout)
-      os.close(old_stderr)
+      self.log.flush()
+      sys.stdout, sys.stderr = saved_stds
 
     return failed
 
diff --git a/subversion/tests/cmdline/svntest/main.py b/subversion/tests/cmdline/svntest/main.py
index af82ecc..fe54d38 100644
--- a/subversion/tests/cmdline/svntest/main.py
+++ b/subversion/tests/cmdline/svntest/main.py
@@ -2080,6 +2080,23 @@
     record.levelshort = self._level_short[record.levelno]
     return logging.Formatter.format(self, record)
 
+
+class LoggingStdoutHandler(logging.StreamHandler):
+  """
+  The handler is always writing using sys.stdout at call time rather than the
+  value of sys.stdout at construction time.
+
+  Inspired by logging._StderrHandler on Python 3.
+  """
+
+  def __init__(self, level=logging.NOTSET):
+    logging.Handler.__init__(self, level)
+
+  @property
+  def stream(self):
+    return sys.stdout
+
+
 def _create_parser(usage=None):
   """Return a parser for our test suite."""
 
@@ -2272,7 +2289,7 @@
                                        datefmt='%Y-%m-%d %H:%M:%S')
     else:
       formatter = AbbreviatedFormatter('%(levelshort)s: %(message)s')
-    handler = logging.StreamHandler(sys.stdout)
+    handler = LoggingStdoutHandler()
     handler.setFormatter(formatter)
     logger.addHandler(handler)