bpo-30283: Backport regrtest features from master to 2.7 (#1516)

* regrtest: add --slowest alias to --slow

* make buildbottest: add --slowest option

* regrtest: add "- " prefix to --slowest output

* regrtest: Fix an outdated comment

* regrtest: replace PermissionError

Replace PermissionError with OSError and check on exc.errno.
PermissionError was added to Python 3.3.

* regrtest: add -3 -tt options to run Python scripts

* regrtest: backport --list-tests option

* regrtest: backport "Tests result: xxx" summary

* regrtest: backport total duration

* regrtest: add timestamp to the progress

* regrtest: describe previous test state

* Add the state of the test: passed, failed, etc.
* If a test took longer than 30 seconds, log its execution time

* regrtest: -jN logs running workers

* regrtest: mention if tests are run in parallel

* regrtest: parallel mode is more verbose during wait

Display running tests every 30 seconds if no test completed in the
meanwhile.

* test_regrtest: fix typo in SubprocessRun
diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py
index 9f68d6b..211d1b5 100644
--- a/Lib/test/test_regrtest.py
+++ b/Lib/test/test_regrtest.py
@@ -6,6 +6,7 @@
 from __future__ import print_function
 
 import collections
+import errno
 import os.path
 import platform
 import re
@@ -33,7 +34,8 @@
     """)
 
 
-SubprocssRun = collections.namedtuple('SubprocssRun', 'returncode stdout, stderr')
+SubprocessRun = collections.namedtuple('SubprocessRun',
+                                       'returncode stdout stderr')
 
 
 class BaseTestCase(unittest.TestCase):
@@ -58,13 +60,15 @@
         path = os.path.join(self.tmptestdir, name + '.py')
 
         self.addCleanup(support.unlink, path)
-        # Use 'x' mode to ensure that we do not override existing tests
+        # Use O_EXCL to ensure that we do not override existing tests
         try:
             fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL)
-        except PermissionError as exc:
-            if not sysconfig.is_python_build():
+        except OSError as exc:
+            if (exc.errno in (errno.EACCES, errno.EPERM)
+               and not sysconfig.is_python_build()):
                 self.skipTest("cannot write %s: %s" % (path, exc))
-            raise
+            else:
+                raise
         else:
             with os.fdopen(fd, 'w') as fp:
                 fp.write(code)
@@ -81,7 +85,7 @@
         self.assertRegexpMatches(output, regex)
 
     def parse_executed_tests(self, output):
-        regex = (r'^\[ *[0-9]+(?:/ *[0-9]+)*\] (%s)'
+        regex = (r'^[0-9]+:[0-9]+:[0-9]+ \[ *[0-9]+(?:/ *[0-9]+)*\] (%s)'
                  % self.TESTNAME_REGEX)
         parser = re.finditer(regex, output, re.MULTILINE)
         return list(match.group(1) for match in parser)
@@ -139,6 +143,14 @@
         if interrupted:
             self.check_line(output, 'Test suite interrupted by signal SIGINT.')
 
+        if nfailed:
+            result = 'FAILURE'
+        elif interrupted:
+            result = 'INTERRUPTED'
+        else:
+            result = 'SUCCESS'
+        self.check_line(output, 'Tests result: %s' % result)
+
     def parse_random_seed(self, output):
         match = self.regex_search(r'Using random seed ([0-9]+)', output)
         randseed = int(match.group(1))
@@ -171,7 +183,7 @@
                         "---\n"
                         % stderr)
             self.fail(msg)
-        return SubprocssRun(proc.returncode, stdout, stderr)
+        return SubprocessRun(proc.returncode, stdout, stderr)
 
     def run_python(self, args, **kw):
         args = [sys.executable] + list(args)
@@ -193,7 +205,7 @@
         # Create NTEST tests doing nothing
         self.tests = [self.create_test() for index in range(self.NTEST)]
 
-        self.python_args = ['-Wd', '-E', '-bb']
+        self.python_args = ['-Wd', '-3', '-E', '-bb', '-tt']
         self.regrtest_args = ['-uall', '-rwW',
                               '--testdir=%s' % self.tmptestdir]
 
@@ -370,13 +382,13 @@
         self.check_executed_tests(output, test, omitted=test,
                                   interrupted=True)
 
-    def test_slow(self):
+    def test_slowest(self):
         # test --slow
         tests = [self.create_test() for index in range(3)]
-        output = self.run_tests("--slow", *tests)
+        output = self.run_tests("--slowest", *tests)
         self.check_executed_tests(output, tests)
         regex = ('10 slowest tests:\n'
-                 '(?:%s: .*\n){%s}'
+                 '(?:- %s: .*\n){%s}'
                  % (self.TESTNAME_REGEX, len(tests)))
         self.check_line(output, regex)
 
@@ -405,6 +417,13 @@
         output = self.run_tests('--forever', test, exitcode=1)
         self.check_executed_tests(output, [test]*3, failed=test)
 
+    def test_list_tests(self):
+        # test --list-tests
+        tests = [self.create_test() for i in range(5)]
+        output = self.run_tests('--list-tests', *tests)
+        self.assertEqual(output.rstrip().splitlines(),
+                         tests)
+
     def test_crashed(self):
         # Any code which causes a crash
         code = 'import ctypes; ctypes.string_at(0)'