Skip to content

Commit b5247ed

Browse files
committed
Simplify test
1 parent fe08263 commit b5247ed

1 file changed

Lines changed: 71 additions & 136 deletions

File tree

‎Lib/test/test_regrtest.py‎

Lines changed: 71 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1642,197 +1642,132 @@ def test_env_changed(self):
16421642
success=True),
16431643
stats=2)
16441644

1645-
def create_marked_test(self, name, code):
1646-
# Create a test: return its name and the line number of each of its
1647-
# "# marker" comments
1648-
code = textwrap.dedent(code)
1649-
name = self.create_test(name, code)
1650-
markers = {match[1]: str(lineno)
1651-
for lineno, text in enumerate(code.splitlines(), 1)
1652-
if (match := re.search(r'# (\w+)$', text))}
1653-
return name, markers
1654-
16551645
def run_tests_github(self, *args, exitcode=0):
1656-
# Run tests as in GitHub Actions: return the output and the job
1657-
# summary (None if regrtest didn't write it)
1646+
# Run tests as in GitHub Actions: return the output, the annotations
1647+
# as (file, line, title) tuples and the job summary ('' if not written)
16581648
filename = os.path.join(self.tmptestdir, 'github_step_summary.md')
1659-
self.addCleanup(os_helper.unlink, filename)
16601649
os_helper.unlink(filename)
1661-
env = dict(os.environ)
1650+
env = dict(os.environ, GITHUB_STEP_SUMMARY=filename)
16621651
env.pop('SOURCE_DATE_EPOCH', None)
1663-
env['GITHUB_STEP_SUMMARY'] = filename
16641652
output = self.run_tests(*args, env=env, exitcode=exitcode)
1653+
annotations = re.findall(r'^::error file=[^,]*?([^/,]+)'
1654+
r'(?:,line=(\d+))?,title=([^:]*)::',
1655+
output, re.MULTILINE)
1656+
# Every annotation has a file and a title
1657+
self.assertEqual(len(annotations),
1658+
len(re.findall('^::error', output, re.MULTILINE)),
1659+
output)
16651660
try:
16661661
with open(filename, encoding='utf-8') as fp:
1667-
return output, fp.read()
1662+
return output, annotations, fp.read()
16681663
except FileNotFoundError:
1669-
return output, None
1664+
return output, annotations, ''
16701665

16711666
def test_github_annotations(self):
1672-
# Every test failure is annotated once in the GitHub Actions job log,
1673-
# located in the test file, at the failing line of the test file if
1674-
# there is a traceback. The job summary lists the failures.
1675-
cases, lines = self.create_marked_test('github_cases', """
1676-
import doctest, json, sys, unittest
1667+
# Every failure is annotated in the GitHub Actions job log, located
1668+
# in its test file, at the failing line ("# fail") for test cases.
1669+
# The job summary lists the failures.
1670+
code = textwrap.dedent("""
1671+
import doctest, sys, unittest
16771672
16781673
def load_tests(loader, tests, pattern):
16791674
tests.addTests(doctest.DocTestSuite(sys.modules[__name__]))
16801675
return tests
16811676
16821677
def doctest_fail():
16831678
'''
1684-
>>> 1 + 1 # doctest
1679+
>>> 1 + 1 # fail
16851680
3
16861681
'''
16871682
16881683
class Tests(unittest.TestCase):
16891684
def test_error(self):
1690-
json.loads("{") # error
1685+
{}['key'] # fail
16911686
16921687
def test_fail(self):
16931688
self.assertEqual(1, 2) # fail
16941689
16951690
def test_subtest(self):
1696-
with self.subTest(x=1.5):
1697-
self.fail("subtest") # subtest
1691+
with self.subTest(x=1):
1692+
self.fail() # fail
16981693
16991694
class SetUpClassTests(unittest.TestCase):
1695+
# Fixture errors are only listed in the job summary
17001696
@classmethod
17011697
def setUpClass(cls):
1702-
raise ValueError("setUpClass")
1698+
raise ValueError
17031699
17041700
def test_never(self):
17051701
pass
17061702
""")
1707-
setup_module, _ = self.create_marked_test(
1708-
'github_setup_module', """
1709-
import unittest
1710-
1711-
def setUpModule():
1712-
raise ValueError("setUpModule")
1713-
1714-
class Tests(unittest.TestCase):
1715-
def test_never(self):
1716-
pass
1717-
""")
1718-
env_changed, _ = self.create_marked_test('github_env_changed', """
1703+
cases = self.create_test('github_cases', code)
1704+
fail_lines = {str(lineno)
1705+
for lineno, line in enumerate(code.splitlines(), 1)
1706+
if line.endswith('# fail')}
1707+
env_changed = self.create_test('github_env_changed', textwrap.dedent("""
17191708
import os, unittest
17201709
17211710
class Tests(unittest.TestCase):
17221711
def test_env_changed(self):
1723-
os.environ["REGRTEST_GITHUB_ENV_CHANGED"] = "1"
1724-
""")
1725-
crash, _ = self.create_marked_test('github_crash', """
1712+
os.environ['REGRTEST_GITHUB_ENV_CHANGED'] = '1'
1713+
"""))
1714+
crash = self.create_test('github_crash', textwrap.dedent("""
17261715
import os, unittest
17271716
17281717
class Tests(unittest.TestCase):
17291718
def test_crash(self):
17301719
os._exit(1)
1731-
""")
1732-
1733-
# Annotated test cases: (title, file, line)
1734-
test_cases = [
1735-
(f'test_error ({cases}.Tests.test_error)',
1736-
f'{cases}.py', lines['error']),
1737-
(f'test_fail ({cases}.Tests.test_fail)',
1738-
f'{cases}.py', lines['fail']),
1739-
(f'test_subtest ({cases}.Tests.test_subtest) (x=1.5)',
1740-
f'{cases}.py', lines['subtest']),
1741-
# Doctest examples are subtests: "[0]" is the example index
1742-
(f'doctest_fail ({cases}) [0]',
1743-
f'{cases}.py', lines['doctest']),
1720+
"""))
1721+
case_titles = [
1722+
f'test_error ({cases}.Tests.test_error)',
1723+
f'test_fail ({cases}.Tests.test_fail)',
1724+
f'test_subtest ({cases}.Tests.test_subtest) (x=1)',
1725+
f'doctest_fail ({cases}) [0]',
17441726
]
1745-
# Test cases only listed in the job summary: fixture errors
1746-
# (setUpClass, setUpModule) are not annotated
1747-
failed_titles = [title for title, _, _ in test_cases] + [
1748-
f'setUpClass ({cases}.SetUpClassTests)',
1749-
f'setUpModule ({setup_module})',
1750-
]
1751-
# Test files: heading in the job summary
1752-
test_files = {
1753-
cases: f'### {cases} failed (2 errors, 3 failures)',
1754-
setup_module: f'### {setup_module} failed (1 error)',
1755-
env_changed: f'### {env_changed} failed (env changed)',
1756-
crash: f'### {crash} worker non-zero exit code',
1757-
}
17581727

17591728
# Representative command lines of the CI jobs
1760-
command_lines = {
1761-
# "make ci", Windows, macOS, installed Python; the JIT jobs use
1762-
# the equivalent "-j0 --verbose2 --verbose3". Only failures of
1763-
# the re-run are annotated, not the reported first failures.
1764-
'fast-ci': ['--fast-ci', '-j2'],
1765-
# Sanitizers and Hypothesis: env changed is not a failure
1766-
'parallel': ['-j2', '-W'],
1767-
# iOS and WASI run tests in the main process: a crash kills it
1768-
'single-process': ['--fast-ci', '--single-process'],
1769-
# Profile task of PGO and BOLT builds: failures stop the build
1770-
'pgo': ['--pgo'],
1771-
}
1772-
for name, args in command_lines.items():
1773-
with self.subTest(name):
1774-
tests = list(test_files)
1775-
if '--single-process' in args or '--pgo' in args:
1776-
tests.remove(crash)
1777-
output, summary = self.run_tests_github(
1729+
for args in (
1730+
# "make ci": only the failures of the re-run are annotated
1731+
['--fast-ci', '-j2'],
1732+
# Sanitizers: env changed is not a failure
1733+
['-j2', '-W'],
1734+
# iOS and WASI: a crash would kill the main process
1735+
['--fast-ci', '--single-process'],
1736+
# PGO profile task
1737+
['--pgo'],
1738+
):
1739+
with self.subTest(args=args):
1740+
in_process = '--single-process' in args or '--pgo' in args
1741+
tests = [cases, env_changed] + ([] if in_process else [crash])
1742+
output, annotations, summary = self.run_tests_github(
17781743
*args, *tests, exitcode=EXITCODE_BAD_TEST)
17791744

1780-
expected = list(test_cases)
1745+
expected = [(cases, title) for title in case_titles]
17811746
if '--fast-ci' in args:
1782-
expected.append((env_changed, f'{env_changed}.py', None))
1747+
expected.append((env_changed, env_changed))
17831748
if crash in tests:
1784-
expected.append((crash, f'{crash}.py', None))
1785-
1786-
# "::error file=...,line=...,title=...::message"
1787-
annotations = []
1788-
output_lines = output.splitlines()
1789-
for i, line in enumerate(output_lines):
1790-
if not line.startswith('::error '):
1791-
continue
1792-
_, props, message = line.split('::', 2)
1793-
props = props.removeprefix('error ').split(',')
1794-
props = dict(prop.split('=', 1) for prop in props)
1795-
title = props['title']
1796-
annotations.append((title, os.path.basename(props['file']),
1797-
props.get('line')))
1798-
before = output_lines[:i]
1799-
if title == env_changed:
1800-
# Right after the env changed warnings
1801-
self.assertStartsWith(before[-1], 'Warning -- ')
1802-
elif title != crash:
1803-
# Right after the failure report of the test, which
1804-
# ends with the exception and an empty line
1805-
report = next(
1806-
text for text in reversed(before)
1807-
if text.startswith(('ERROR: ', 'FAIL: ')))
1808-
self.assertEqual(report.split(': ', 1)[1], title)
1809-
message = message.replace('%0A', '\n').splitlines()
1810-
self.assertEqual(before[-len(message) - 1:],
1811-
message + [''])
1812-
self.assertCountEqual(annotations, expected, output)
1813-
1814-
# The job summary lists the failed tests in completion order
1815-
summary_lines = summary.splitlines()
1816-
self.assertEqual(summary_lines[0],
1817-
f'## FAILURE: {len(tests)} test files and '
1818-
f'{len(failed_titles)} test cases failed')
1749+
expected.append((crash, crash))
18191750
self.assertCountEqual(
1820-
[line for line in summary_lines
1821-
if line.startswith('### ')],
1822-
[test_files[name] for name in tests])
1823-
self.assertIn('- os.environ was modified', summary_lines)
1824-
self.assertCountEqual(
1825-
re.findall(r'<summary>(.*)</summary>', summary),
1826-
failed_titles)
1827-
self.assertEqual(summary.count('```pytb\n'),
1828-
len(failed_titles))
1751+
[(file, title) for file, _, title in annotations],
1752+
[(f'{name}.py', title) for name, title in expected],
1753+
output)
1754+
for file, line, title in annotations:
1755+
# Test files without a failed test case have no line
1756+
self.assertIn(line, fail_lines if file == f'{cases}.py'
1757+
else {''}, title)
1758+
1759+
self.assertIn(f'## FAILURE: {len(tests)} test files and '
1760+
f'5 test cases failed', summary)
1761+
for name in tests:
1762+
self.assertIn(f'### {name} ', summary)
1763+
self.assertIn('- os.environ was modified', summary)
1764+
self.assertEqual(summary.count('<summary>'), 5)
18291765

18301766
def test_github_summary_success(self):
18311767
# No annotation and no job summary when all tests pass
1832-
testname = self.create_test()
1833-
output, summary = self.run_tests_github(testname)
1834-
self.assertNotIn('::error', output)
1835-
self.assertIsNone(summary)
1768+
output, annotations, summary = self.run_tests_github(self.create_test())
1769+
self.assertEqual(annotations, [])
1770+
self.assertEqual(summary, '')
18361771

18371772
def test_rerun_fail(self):
18381773
# FAILURE then FAILURE

0 commit comments

Comments
 (0)