diff --git a/docs/source/contributing/conventions/test-coverage.rst b/docs/source/contributing/conventions/test-coverage.rst index fd4ebce718..d9858108b6 100644 --- a/docs/source/contributing/conventions/test-coverage.rst +++ b/docs/source/contributing/conventions/test-coverage.rst @@ -51,18 +51,19 @@ The rules those tables carry: A code that fails is never skipped. * **A failing case is an entry, never a changed argument.** An ``EXPECTED_FAILURES`` entry records the status, a failure-detail fragment narrow enough to match one - site, and the defect: the ``file:line`` that causes it and the issue that tracks it. + site, and the defect: the function, class or method that causes it, by name, and the + issue that tracks it. Routing a case around a defect hides it: the generator's ``HIP_COPIES`` once put two parameters in every HIP packet, so the unrepresentable length of a lone one (:issue:`651`) was never constructed. ``KNOWN_DEGRADED`` entries have a different shape -- a fragment and a reason -- and they record why the probe's placeholder payload cannot reach the target, not a - defect to fix, so the ``file:line``-and-issue rule does not apply to them. + defect to fix, so the name-and-issue rule does not apply to them. * **A regression is fixed; a pre-existing defect is recorded.** A case that a change breaks -- including a code it registers that does not close -- is fixed in that change, not added to a table. A newly found defect that predates the change gets an - entry naming the ``file:line`` that causes it, as the failure message asks, and an - issue. + entry naming the function, class or method that causes it, as the failure message + asks, and an issue. * **The tables are checked in both directions.** In the option suite, an entry whose case starts passing turns ``test_round_trip_is_identity_or_a_recorded_gap`` red, and one naming a case that no longer exists fails diff --git a/docs/source/contributing/workflows.rst b/docs/source/contributing/workflows.rst index a6e4be8cf0..9c78b0e568 100644 --- a/docs/source/contributing/workflows.rst +++ b/docs/source/contributing/workflows.rst @@ -769,19 +769,19 @@ grepping every workflow file for its name: - ``unit-tests.yml`` * - ``Compat Python 3.11`` - job ``compatibility``, matrix leg ``3.11`` - - ``python-compatibility.yml``, ``.github/python-versions.toml:19`` + - ``python-compatibility.yml``, key ``"3.11"`` of ``.github/python-versions.toml`` * - ``Compat Python 3.12`` - job ``compatibility``, matrix leg ``3.12`` - - ``python-compatibility.yml``, ``.github/python-versions.toml:20`` + - ``python-compatibility.yml``, key ``"3.12"`` of ``.github/python-versions.toml`` * - ``Compat Python 3.13`` - job ``compatibility``, matrix leg ``3.13`` - - ``python-compatibility.yml``, ``.github/python-versions.toml:21`` + - ``python-compatibility.yml``, key ``"3.13"`` of ``.github/python-versions.toml`` * - ``Compat Python 3.14`` - job ``compatibility``, matrix leg ``3.14`` - - ``python-compatibility.yml``, ``.github/python-versions.toml:22`` + - ``python-compatibility.yml``, key ``"3.14"`` of ``.github/python-versions.toml`` * - ``Compat Python 3.15`` - job ``compatibility``, matrix leg ``3.15`` - - ``python-compatibility.yml``, ``.github/python-versions.toml:23`` + - ``python-compatibility.yml``, key ``"3.15"`` of ``.github/python-versions.toml`` That is the list after `#1633 `__, which added ``Compat Python 3.15`` and removed ``Compat Python 3.10``. Moving a version @@ -791,8 +791,8 @@ between the tiers below means editing the ruleset in the same change. ``required-checks`` job in ``unit-tests.yml``. ``Compat Python`` is defined by one, the ``name:`` of the ``compatibility`` job in ``python-compatibility.yml``, whose ``matrix`` comes from :file:`.github/python-versions.toml`: the required legs -are its supported tier (``python-versions.toml:19-23``), and its KTLO legs -(``python-versions.toml:16-18``) have names ending in ``(KTLO)``, so no required +are its supported tier (keys ``"3.11"`` to ``"3.15"``), and its KTLO legs +(keys ``"3.8"`` to ``"3.10"``) have names ending in ``(KTLO)``, so no required context matches one. The comment above ``required-checks`` mentions the string without defining it. @@ -1006,8 +1006,8 @@ monitor`` ends the sampler, ``if: always()``. The file is uploaded as only where the runner survives the tests. `always() `__ runs a step "even when canceled", and docs.github.com says nothing of a runner's -shutdown, after which the runner evaluates no later step's condition (actions/runner -``src/Runner.Worker/StepsRunner.cs:205-208``, at ``796cc18ff``). Job 116057255255 +shutdown, after which the runner evaluates no later step's condition (actions/runner's +``StepsRunner.RunAsync``, at ``796cc18ff``). Job 116057255255 skipped every step after its tests, ``Post Run actions/checkout@v7`` too, whose `post-if `__ diff --git a/tests/corekit/_roundtrip.py b/tests/corekit/_roundtrip.py index 398967e62a..f640ce29db 100644 --- a/tests/corekit/_roundtrip.py +++ b/tests/corekit/_roundtrip.py @@ -54,7 +54,7 @@ class Gap(NamedTuple): #: GitHub issue tracking the defect. issue: 'Optional[int]' - #: The defect, with the ``file:line`` that causes it. + #: The defect, naming the function, class or method that causes it. defect: 'str' #: Status every case below reports, or a tuple of which each reports one. status: 'str | tuple[str, ...]' diff --git a/tests/foundation/_roundtrip.py b/tests/foundation/_roundtrip.py index 56768405dc..3b72701770 100644 --- a/tests/foundation/_roundtrip.py +++ b/tests/foundation/_roundtrip.py @@ -54,7 +54,7 @@ class Gap(NamedTuple): #: GitHub issue tracking the defect. issue: 'Optional[int]' - #: The defect, with the ``file:line`` that causes it. + #: The defect, naming the function, class or method that causes it. defect: 'str' #: Status every case below reports, or the statuses of which each must #: report one. diff --git a/tests/project/test_python_versions_1660.py b/tests/project/test_python_versions_1660.py index b706969010..8e475d1e0a 100644 --- a/tests/project/test_python_versions_1660.py +++ b/tests/project/test_python_versions_1660.py @@ -990,22 +990,24 @@ def test_the_required_contexts_table_is_the_supported_tier(self) -> None: page = prose(ROOT / 'docs' / 'source' / 'contributing' / 'workflows.rst') self.assertEqual(re.findall(r'\* - ``Compat Python (3\.\d+)``', page), self.supported) - def test_the_workflows_page_cites_each_version_on_its_line(self) -> None: - """The required contexts' rows, and the two tiers' spans under them, cite the file by line.""" - lines = TIER_FILE.read_text(encoding='utf-8').splitlines() + def test_the_workflows_page_cites_each_version_by_its_key(self) -> None: + """The required contexts' rows, and the two tiers' spans under them, cite the file's keys (#2165). + + A key names its entry; a line number has to be looked up, so none is cited. + + """ page = prose(ROOT / 'docs' / 'source' / 'contributing' / 'workflows.rst') - rows = re.findall(r'``Compat Python (3\.\d+)`` .*? ``\.github/python-versions\.toml:(\d+)``', page) + self.assertNotRegex(page, r'python-versions\.toml:\d') + rows = re.findall(r'``Compat Python (3\.\d+)`` .*? key ``"(3\.\d+)"`` of ``\.github/python-versions\.toml``', + page) self.assertEqual([version for version, _ in rows], self.supported) - for version, line in rows: + for version, key in rows: with self.subTest(version=version): - self.assertTrue(lines[int(line) - 1].startswith(f'"{version}" = '), lines[int(line) - 1]) + self.assertEqual(key, version) for label, versions in (('supported tier', self.supported), ('KTLO legs', self.ktlo)): - spans = re.findall(rf'its {label} \(``python-versions\.toml:(\d+)-(\d+)``\)', page) + spans = re.findall(rf'its {label} \(keys ``"(3\.\d+)"`` to ``"(3\.\d+)"``\)', page) with self.subTest(tier=label): - self.assertEqual(len(spans), 1, spans) - first, last = map(int, spans[0]) - self.assertEqual([line.split(' = ', 1)[0] for line in lines[first - 1:last]], - [f'"{version}"' for version in versions]) + self.assertEqual(spans, [(versions[0], versions[-1])]) def test_the_bug_report_template_offers_supported_versions(self) -> None: text = (ROOT / '.github' / 'ISSUE_TEMPLATE' / 'bug_report.md').read_text(encoding='utf-8') diff --git a/tests/project/test_python_versions_update_1660.py b/tests/project/test_python_versions_update_1660.py index 6281e7a5eb..f4aca7edab 100644 --- a/tests/project/test_python_versions_update_1660.py +++ b/tests/project/test_python_versions_update_1660.py @@ -682,7 +682,7 @@ def test_every_claim_and_rewriter_matches_once_on_main(self) -> None: workflows = module.load_workflows(ROOT) data = module.pv.load() values = module.pv.outputs(data) - facts = module.make_facts(data, values, workflows, (ROOT / module.TIER_FILE).read_text(encoding='utf-8')) + facts = module.make_facts(data, values, workflows) for path in module.TARGETS: if path == module.TIER_FILE: continue @@ -696,7 +696,7 @@ def test_every_claim_and_rewriter_matches_once_on_main(self) -> None: self.assertEqual(rewritten, text) def test_a_claim_rewrites_each_group_and_never_spans_a_break(self) -> None: - """#1811 retired the count claims and their spelt-out words; a claim states versions and lines.""" + """#1811 retired the count claims and their spelt-out words, #2165 the line ones: a claim states versions.""" module = updater() claim = module.Claim(r'(3\.\d+) to (3\.\d+)', lambda facts: ('3.12', '3.16')) self.assertEqual(module.apply_claims('Tests 3.11 to 3.15.', [claim], {}), ('Tests 3.12 to 3.16.', [])) diff --git a/tests/project/test_resource_monitor_1740.py b/tests/project/test_resource_monitor_1740.py index 79179e16e8..205c9b2828 100644 --- a/tests/project/test_resource_monitor_1740.py +++ b/tests/project/test_resource_monitor_1740.py @@ -578,7 +578,9 @@ class TestTheDocsSayIt(unittest.TestCase): def test_the_workflows_page_describes_the_monitor(self) -> None: page = prose(ROOT / 'docs' / 'source' / 'contributing' / 'workflows.rst') for phrase in ('``Start the resource monitor``', '``[monitor]``', '``resource-monitor-``', - '``Stop the resource monitor``', 'src/Runner.Worker/StepsRunner.cs:205-208'): + '``Stop the resource monitor``', + # #2165: the runner's shutdown check, cited by its method rather than by line. + "actions/runner's ``StepsRunner.RunAsync``, at ``796cc18ff``"): with self.subTest(phrase=phrase): self.assertIn(phrase, page) diff --git a/tests/protocols/_edge_roundtrip.py b/tests/protocols/_edge_roundtrip.py index 2ae35cfbc8..5c9ffd120c 100644 --- a/tests/protocols/_edge_roundtrip.py +++ b/tests/protocols/_edge_roundtrip.py @@ -98,7 +98,7 @@ class Gap(NamedTuple): #: GitHub issue tracking the defect, or :data:`None` if not filed yet. issue: 'Optional[int]' - #: The defect, with the ``file:line`` that causes it. + #: The defect, naming the function, class or method that causes it. defect: 'str' #: Expected status of every case below, or the statuses one root cause #: shows as, of which each case must report one. diff --git a/tests/protocols/test_dispatch_registry_unit.py b/tests/protocols/test_dispatch_registry_unit.py index 8a2966c885..782cde84d7 100644 --- a/tests/protocols/test_dispatch_registry_unit.py +++ b/tests/protocols/test_dispatch_registry_unit.py @@ -267,9 +267,9 @@ def test_dispatch_reaches_target_or_is_a_recorded_degrade(self) -> None: outcome.reached, True, f'{case.label} no longer dispatches to {case.target!r}: ' f'chain was {outcome.chain!r} ({outcome.detail}). If this ' - f'is a newly found defect, add it to KNOWN_DEGRADED with ' - f'the file:line that causes it -- do not change the case ' - f'to avoid it.' + f'is a newly found defect, add it to KNOWN_DEGRADED naming ' + f'the function, class or method that causes it -- do not ' + f'change the case to avoid it.' ) continue diff --git a/tests/protocols/test_option_roundtrip_unit.py b/tests/protocols/test_option_roundtrip_unit.py index bc529e1da7..e8b330b179 100644 --- a/tests/protocols/test_option_roundtrip_unit.py +++ b/tests/protocols/test_option_roundtrip_unit.py @@ -125,8 +125,8 @@ class Gap(NamedTuple): #: messages whose stable parts are not contiguous, so that a fragment does #: not have to bake in a rendering that is itself defective. fragment: 'str | tuple[str, ...]' - #: The defect, named with the ``file:line`` that causes it. This is the - #: field that makes the entry worth keeping rather than just silencing. + #: The defect, naming the function, class or method that causes it. This is + #: the field that makes the entry worth keeping rather than just silencing. defect: 'str' @@ -594,7 +594,8 @@ def test_round_trip_is_identity_or_a_recorded_gap(self) -> None: outcome.status, 'OK', f'{case.label} no longer round-trips: {outcome.detail}. ' f'If this is a newly found defect, add it to ' - f'EXPECTED_FAILURES with the file:line that causes it -- ' + f'EXPECTED_FAILURES naming the function, class or method that ' + f'causes it -- ' f'do not change the case to avoid it.' ) continue diff --git a/util/update_python_versions.py b/util/update_python_versions.py index 0104acc57a..fdbd62ee4d 100644 --- a/util/update_python_versions.py +++ b/util/update_python_versions.py @@ -966,12 +966,6 @@ def span(tier: 'list[str]') -> 'Optional[tuple[str, str]]': return (tier[0], tier[-1]) if consecutive([pv.key(version)[1] for version in tier]) else None -def lines_of(facts: 'dict[str, Any]', tier: 'str') -> 'Optional[tuple[int, int]]': - """The tier file lines of a CPython tier, if they are one run of lines.""" - numbers = [facts['tier_lines'][version] for version in facts[tier]] - return (numbers[0], numbers[-1]) if consecutive(numbers) else None - - #: The sentences restating the tiers, by file. Never a workflow (see the module docstring). CLAIMS = { 'pyproject.toml': ( @@ -995,9 +989,9 @@ def lines_of(facts: 'dict[str, Any]', tier: 'str') -> 'Optional[tuple[int, int]] 'docs/source/contributing/workflows.rst': ( Claim(r'The \*\*supported\*\* tier, (3\.\d+)-(3\.\d+),', lambda f: span(f['supported'])), Claim(r'end-of-life (3\.\d+)-(3\.\d+), still built', lambda f: span(f['ktlo'])), - Claim(r'the required legs are its supported tier \(``python-versions\.toml:(\d+)-(\d+)``\)', - lambda f: lines_of(f, 'supported')), - Claim(r'its KTLO legs \(``python-versions\.toml:(\d+)-(\d+)``\)', lambda f: lines_of(f, 'ktlo')), + Claim(r'the required legs are its supported tier \(keys ``"(3\.\d+)"`` to ``"(3\.\d+)"``\)', + lambda f: span(f['supported'])), + Claim(r'its KTLO legs \(keys ``"(3\.\d+)"`` to ``"(3\.\d+)"``\)', lambda f: span(f['ktlo'])), Claim(r'and the ``Compat Python (3\.\d+)``-``(3\.\d+)`` legs', lambda f: span(f['supported'])), Claim(r'the ``Python (3\.\d+)`` leg of ``test`` \(matrix key ``coverage: true``\)', lambda f: (f['latest'],)), @@ -1022,7 +1016,8 @@ def lines_of(facts: 'dict[str, Any]', tier: 'str') -> 'Optional[tuple[int, int]] #: One row of the workflows page's required-contexts table. COMPAT_ROW = re.compile(r'^(?P[ \t]*)\* - ``Compat Python (?P3\.\d+)``\n' r'(?P[ \t]*)- job ``compatibility``, matrix leg ``(?P=version)``\n' - r'(?P=inner)- (?P``[^`\n]+``), ``\.github/python-versions\.toml:\d+``\n', re.M) + r'(?P=inner)- (?P``[^`\n]+``), key ``"(?P=version)"`` ' + r'of ``\.github/python-versions\.toml``\n', re.M) BUG_VERSIONS = re.compile(r'(Python Version: \[e\.g\. )([^\]\n]+)(\])') @@ -1047,8 +1042,7 @@ def rewrite_compat_rows(text: 'str', facts: 'dict[str, Any]') -> 'tuple[str, lis first = matches[0] block = ''.join(f'{first["indent"]}* - ``Compat Python {version}``\n' f'{first["inner"]}- job ``compatibility``, matrix leg ``{version}``\n' - f'{first["inner"]}- {first["where"]}, ' - f'``.github/python-versions.toml:{facts["tier_lines"][version]}``\n' + f'{first["inner"]}- {first["where"]}, key ``"{version}"`` of ``.github/python-versions.toml``\n' for version in facts['supported']) return text[:first.start()] + block + text[matches[-1].end():], [] @@ -1074,21 +1068,14 @@ def rewrite_bug_template(text: 'str', facts: 'dict[str, Any]') -> 'tuple[str, li TARGETS = tuple(sorted({TIER_FILE} | set(CLAIMS) | set(REWRITERS))) -def make_facts(data: 'dict[str, Any]', values: 'dict[str, str]', workflows: 'dict[str, Any]', - tier_file: 'str') -> 'dict[str, Any]': +def make_facts(data: 'dict[str, Any]', values: 'dict[str, str]', workflows: 'dict[str, Any]') -> 'dict[str, Any]': """What the prose states, computed from the new tiers and the workflows' matrices.""" - tier_lines = {} - for number, line in enumerate(tier_file.splitlines(), 1): - match = ENTRY.match(line) - if match and pv.SPELLING['cpython'].fullmatch(match['version']): - tier_lines[match['version']] = number return { 'latest': data['latest'], 'supported': pv.tier(data, 'cpython', 'supported'), 'ktlo': pv.tier(data, 'cpython', 'ktlo'), 'experimental': [version for impl in data['versions'] for version in pv.tier(data, impl, 'experimental')], 'required': required_contexts(workflows, values), - 'tier_lines': tier_lines, } @@ -1167,7 +1154,7 @@ def compute_plan(root: 'pathlib.Path', sources: 'dict[str, Any]') -> 'tuple[dict validate(new_tier_text) after_values = pv.outputs(after) - facts = make_facts(after, after_values, workflows, new_tier_text) + facts = make_facts(after, after_values, workflows) texts = {TIER_FILE: new_tier_text} for path in TARGETS: if path == TIER_FILE: