@@ -422,7 +422,7 @@ def check_executed_tests(self, output, tests, skipped=(), failed=(),
422422 env_changed = (), omitted = (),
423423 rerun = {}, no_test_ran = (),
424424 randomize = False , interrupted = False ,
425- fail_env_changed = False ):
425+ fail_env_changed = False , executed = None , good = None ):
426426 if isinstance (tests , str ):
427427 tests = [tests ]
428428 if isinstance (skipped , str ):
@@ -435,12 +435,14 @@ def check_executed_tests(self, output, tests, skipped=(), failed=(),
435435 omitted = [omitted ]
436436 if isinstance (no_test_ran , str ):
437437 no_test_ran = [no_test_ran ]
438+ if executed is None :
439+ executed = tests
438440
439- executed = self .parse_executed_tests (output )
441+ get_executed = self .parse_executed_tests (output )
440442 if randomize :
441- self .assertEqual (set (executed ), set (tests ), output )
443+ self .assertEqual (set (get_executed ), set (executed ), output )
442444 else :
443- self .assertEqual (executed , tests , output )
445+ self .assertEqual (get_executed , executed , output )
444446
445447 def plural (count ):
446448 return 's' if count != 1 else ''
@@ -482,8 +484,9 @@ def list_regex(line_format, tests):
482484 regex = list_regex ('%s test%s run no tests' , no_test_ran )
483485 self .check_line (output , regex )
484486
485- good = (len (tests ) - len (skipped ) - len (failed )
486- - len (omitted ) - len (env_changed ) - len (no_test_ran ))
487+ if good is None :
488+ good = (len (tests ) - len (skipped ) - len (failed )
489+ - len (omitted ) - len (env_changed ) - len (no_test_ran ))
487490 if good :
488491 regex = r'%s test%s OK\.$' % (good , plural (good ))
489492 if not skipped and not failed and good > 1 :
@@ -1551,6 +1554,40 @@ def test_leak_tmp_file(self):
15511554 f"files (1): mytmpfile" ,
15521555 output )
15531556
1557+ def test_mp_decode_error (self ):
1558+ # gh-101634: If a worker stdout cannot be decoded, report a failed test
1559+ # and a non-zero exit code.
1560+ if sys .platform == 'win32' :
1561+ encoding = locale .getencoding ()
1562+ else :
1563+ encoding = sys .stdout .encoding
1564+
1565+ nonascii = b"byte:\xa0 \xa9 \xff \n "
1566+ try :
1567+ nonascii .decode (encoding )
1568+ except UnicodeDecodeError :
1569+ pass
1570+ else :
1571+ self .skipTest (f"{ encoding } can decode non-ASCII bytes { nonascii !a} " )
1572+
1573+ code = textwrap .dedent (fr"""
1574+ import sys
1575+ # bytes which cannot be decoded from UTF-8
1576+ nonascii = { nonascii !a}
1577+ sys.stdout.buffer.write(nonascii)
1578+ sys.stdout.buffer.flush()
1579+ """ )
1580+ testname = self .create_test (code = code )
1581+
1582+ output = self .run_tests ("--fail-env-changed" , "-v" , "-j1" , testname ,
1583+ exitcode = EXITCODE_BAD_TEST )
1584+ self .check_executed_tests (output , [testname ],
1585+ executed = [],
1586+ good = 0 ,
1587+ failed = "<regrtest worker>" ,
1588+ omitted = [testname ],
1589+ randomize = True )
1590+
15541591
15551592class TestUtils (unittest .TestCase ):
15561593 def test_format_duration (self ):
0 commit comments