bpo-30258: regrtest handles child process crash (#1431)

Backport the CHILD_ERROR status from master: a test is considered as
failed if a worker process running a test exited with a code
different than zero.

Change also the output: write stdout and stderr of the child process
after the test name, instead of writing it before.

accumulate_result(): don't use time of CHILD_ERROR or INTERRUPTED
results.
diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py
index bc3fa71..e49bcb9 100755
--- a/Lib/test/regrtest.py
+++ b/Lib/test/regrtest.py
@@ -220,6 +220,7 @@
 SKIPPED = -2
 RESOURCE_DENIED = -3
 INTERRUPTED = -4
+CHILD_ERROR = -5   # error in a child process
 
 from test import test_support
 
@@ -466,7 +467,8 @@
 
     def accumulate_result(test, result):
         ok, test_time = result
-        test_times.append((test_time, test))
+        if ok not in (CHILD_ERROR, INTERRUPTED):
+            test_times.append((test_time, test))
         if ok == PASSED:
             good.append(test)
         elif ok == FAILED:
@@ -478,6 +480,9 @@
         elif ok == RESOURCE_DENIED:
             skipped.append(test)
             resource_denieds.append(test)
+        else:
+            # CHILD_ERROR
+            bad.append(test)
 
     if forever:
         def test_forever(tests=list(selected)):
@@ -533,9 +538,17 @@
                                    universal_newlines=True,
                                    close_fds=(os.name != 'nt'))
                     stdout, stderr = popen.communicate()
+                    retcode = popen.wait()
+
                     # Strip last refcount output line if it exists, since it
                     # comes from the shutdown of the interpreter in the subcommand.
                     stderr = debug_output_pat.sub("", stderr)
+
+                    if retcode != 0:
+                        result = (CHILD_ERROR, "Exit code %s" % retcode)
+                        output.put((test, stdout.rstrip(), stderr.rstrip(),
+                                    result))
+
                     stdout, _, result = stdout.strip().rpartition("\n")
                     if not result:
                         output.put((None, None, None, None))
@@ -545,9 +558,11 @@
             except BaseException:
                 output.put((None, None, None, None))
                 raise
+
         workers = [Thread(target=work) for i in range(use_mp)]
         for worker in workers:
             worker.start()
+
         finished = 0
         test_index = 1
         try:
@@ -556,21 +571,24 @@
                 if test is None:
                     finished += 1
                     continue
-                if stdout:
-                    print stdout
-                if stderr and not pgo:
-                    print >>sys.stderr, stderr
-                sys.stdout.flush()
-                sys.stderr.flush()
-                if result[0] == INTERRUPTED:
-                    assert result[1] == 'KeyboardInterrupt'
-                    raise KeyboardInterrupt   # What else?
                 accumulate_result(test, result)
                 if not quiet:
                     fmt = "[{1:{0}}{2}/{3}] {4}" if bad else "[{1:{0}}{2}] {4}"
                     print(fmt.format(
                         test_count_width, test_index, test_count,
                         len(bad), test))
+
+                if stdout:
+                    print stdout
+                sys.stdout.flush()
+                if stderr and not pgo:
+                    print >>sys.stderr, stderr
+                sys.stderr.flush()
+
+                if result[0] == INTERRUPTED:
+                    assert result[1] == 'KeyboardInterrupt'
+                    raise KeyboardInterrupt   # What else?
+
                 test_index += 1
         except KeyboardInterrupt:
             interrupted = True
@@ -738,6 +756,7 @@
            for Profile Guided Optimization build
 
     Returns one of the test result constants:
+        CHILD_ERROR      Child process crashed
         INTERRUPTED      KeyboardInterrupt when run under -j
         RESOURCE_DENIED  test skipped because resource denied
         SKIPPED          test skipped for some other reason