@@ -1641,8 +1641,8 @@ def test_env_changed(self):
16411641
16421642 def test_github_actions (self ):
16431643 # Failures are annotated in the GitHub Actions job log at the failing
1644- # line ("# fail") of the test file, and listed in the job summary
1645- code = textwrap .dedent ("""
1644+ # line of the test file, and listed in the job summary
1645+ cases = self . create_test ( 'github_cases' , textwrap .dedent ("""
16461646 import doctest, sys, unittest
16471647
16481648 def load_tests(loader, tests, pattern):
@@ -1651,16 +1651,15 @@ def load_tests(loader, tests, pattern):
16511651
16521652 def doctest_fail():
16531653 '''
1654- >>> 1 + 1 # fail
1654+ >>> 1 + 1
16551655 3
16561656 '''
16571657
16581658 class Tests(unittest.TestCase):
16591659 def test_subtest(self):
16601660 with self.subTest(x=1):
1661- self.fail() # fail
1662- """ )
1663- cases = self .create_test ('github_cases' , code )
1661+ self.fail()
1662+ """ ))
16641663 crash = self .create_test ('github_crash' , textwrap .dedent ("""
16651664 import os, unittest
16661665
@@ -1671,18 +1670,40 @@ def test_crash(self):
16711670 filename = os .path .join (self .tmptestdir , 'github_step_summary.md' )
16721671 env = dict (os .environ , GITHUB_STEP_SUMMARY = filename )
16731672 env .pop ('SOURCE_DATE_EPOCH' , None )
1674- output = self .run_tests ('--fast-ci' , '-j2' , cases , crash ,
1673+ # A single worker without --randomize runs the tests in order
1674+ output = self .run_tests ('-j1' , '--rerun' , cases , crash ,
16751675 env = env , exitcode = EXITCODE_BAD_TEST )
1676- # One annotation per failure of the re-run, not of the first run:
1677- # one per "# fail" line, and the crash (without a line)
1678- fail_lines = [(f'{ cases } .py' , str (lineno ))
1679- for lineno , line in enumerate (code .splitlines (), 1 )
1680- if line .endswith ('# fail' )]
1681- self .assertCountEqual (
1682- re .findall (r'^::error file=[^,]*?([^/,]+?)(?:,line=(\d+))?,title=' ,
1683- output , re .MULTILINE ),
1684- fail_lines + [(f'{ crash } .py' , '' )],
1685- output )
1676+
1677+ def unescape (text ):
1678+ for char in '\r \n :,%' :
1679+ text = text .replace (f'%{ ord (char ):02X} ' , char )
1680+ return text
1681+
1682+ annotations = []
1683+ for props , message in re .findall (r'^::error (.*?)::(.*)$' , output ,
1684+ re .MULTILINE ):
1685+ props = dict (prop .split ('=' , 1 ) for prop in props .split (',' ))
1686+ annotations .append (({key : unescape (value )
1687+ for key , value in props .items ()},
1688+ unescape (message )))
1689+ # One annotation per failure of the re-run, not of the first run
1690+ cases_file = os .path .join (self .tmptestdir , f'{ cases } .py' )
1691+ crash_file = os .path .join (self .tmptestdir , f'{ crash } .py' )
1692+ self .assertEqual (annotations , [
1693+ ({'file' : cases_file , 'line' : '17' ,
1694+ 'title' : f'test_subtest ({ cases } .Tests.test_subtest) (x=1)' },
1695+ 'AssertionError: None' ),
1696+ ({'file' : cases_file , 'line' : '10' ,
1697+ 'title' : f'doctest_fail ({ cases } ) [0]' },
1698+ 'AssertionError: Failed example:\n '
1699+ ' 1 + 1\n '
1700+ 'Expected:\n '
1701+ ' 3\n '
1702+ 'Got:\n '
1703+ ' 2' ),
1704+ ({'file' : crash_file , 'title' : crash },
1705+ f'{ crash } worker non-zero exit code' ),
1706+ ], output )
16861707 with open (filename , encoding = 'utf-8' ) as fp :
16871708 summary = fp .read ()
16881709 self .assertIn ('## FAILURE: 2 test files and 2 test cases failed' ,
0 commit comments