diff --git a/tests/project/test_actionlint_in_lint_1967.py b/tests/project/test_actionlint_in_lint_1967.py index 1c222954fb..7ddc933547 100644 --- a/tests/project/test_actionlint_in_lint_1967.py +++ b/tests/project/test_actionlint_in_lint_1967.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- """#1967: Lint runs actionlint, pinned to a checksum-verified release. -Nothing ran actionlint over the workflows before #1967. Lint's ``lint`` job -has an ``actionlint`` step that downloads one release, named by -``ACTIONLINT_VERSION``, checks it against ``ACTIONLINT_SHA256`` before unpacking +Lint's ``lint`` job has an ``actionlint`` step that downloads one release, named +by ``ACTIONLINT_VERSION``, checks it against ``ACTIONLINT_SHA256`` before unpacking it, and runs it over every workflow. The version is 1.7.12 or newer: the workflows use YAML anchors, which 1.7.7 cannot read. diff --git a/tests/project/test_build_leftovers_1773.py b/tests/project/test_build_leftovers_1773.py index 3f6195de92..dc0b02410e 100644 --- a/tests/project/test_build_leftovers_1773.py +++ b/tests/project/test_build_leftovers_1773.py @@ -5,8 +5,8 @@ :file:`MANIFEST.in` names a directory the repository tracks or ignores. Every other line there changes what the template selects (#1794). * No reStructuredText is tracked outside ``docs/`` (the #719 ruling), the - examples' Markdown READMEs still ship in the sdist, and nothing points at a - README path that no longer exists. + examples' Markdown READMEs ship in the sdist, and nothing points at a + README path that does not exist. """ diff --git a/tests/project/test_bump_version.py b/tests/project/test_bump_version.py index 0c7325e58e..723de2160c 100644 --- a/tests/project/test_bump_version.py +++ b/tests/project/test_bump_version.py @@ -155,7 +155,7 @@ def test_an_indented_version_key_belongs_to_its_own_reference(self) -> None: def test_a_file_with_no_version_field_is_refused(self) -> None: # A file that is *present* and has no field to update means the shape has # changed underneath the script. Rewriting nothing while reporting success - # is the staleness this change exists to stop, so it raises instead. + # is the staleness this check exists to stop, so it raises instead. without = '\n'.join(line for line in FIXTURE.splitlines() if not line.startswith('version:')) + '\n' diff --git a/tests/project/test_changelog_md.py b/tests/project/test_changelog_md.py index 3885c261c8..701a38057c 100644 --- a/tests/project/test_changelog_md.py +++ b/tests/project/test_changelog_md.py @@ -252,7 +252,7 @@ def test_rule_6_collapses_blank_runs_and_drops_a_leading_one(self) -> None: # structurally rather than collapsed afterwards by a ``\n{3,}`` substitution # over the joined text. Same answer between blocks -- a run of blank lines # is one paragraph break however long it is -- and a better one before the - # first block, where the substitution used to leave two empty lines. + # first block, where such a substitution leaves two empty lines. markdown = self.convert( '\n\n9.9.9 -- 2026-01-02\n===================\n\n\n\n' 'One paragraph.\n\n\nAnother.\n' @@ -593,26 +593,24 @@ def test_a_simple_table_is_fatal(self) -> None: ) def test_a_sub_heading_underline_joined_into_the_prose_is_fatal(self) -> None: - # Retargeted from ``-`` to ``^``. Rule 1 used to consume ``=`` alone, so a - # ``-`` underlined sub-heading was joined onto its heading text by rule 6 and - # would have vanished into a paragraph -- which is what this rejected. The - # by-module changelog restructure made ``-`` and ``~`` real heading levels - # that rule 1 now converts, so the old input converts cleanly and **this test - # failed**: ``_reject`` is built on ``assertRaises(ResidualMarkupError)``, and - # nothing raises any more. The retarget was required to keep the guard - # exercised, not a tidy-up of a weak assertion. ``^`` is still outside rule 1's - # level map while remaining in ``_RESIDUAL``'s alternation, so it tests the - # same guard on a character the converter genuinely does not handle. + # ``^``, not ``-``: an underline rule 1 does not convert is joined onto its + # heading text by rule 6 and would vanish into a paragraph, which is what this + # rejects. The by-module changelog makes ``-`` and ``~`` real heading levels + # that rule 1 converts, so a ``-`` sub-heading converts cleanly and would leave + # ``_reject``, built on ``assertRaises(ResidualMarkupError)``, with nothing to + # raise. ``^`` is outside rule 1's level map while remaining in + # ``_RESIDUAL``'s alternation, so it exercises the guard on a character the + # converter genuinely does not handle. self._reject('\nA sub heading\n^^^^^^^^^^^^^\n\nSome prose.\n', 'setext underline joined') def test_the_two_new_sub_heading_levels_become_atx_headings(self) -> None: - # The other half of the same change: ``-`` and ``~`` must now *convert* - # rather than be rejected, one ATX level deeper each, so that the by-module - # sections and their Added/Changed/Fixed subsections survive into - # ``CHANGELOG.md``. Against the old converter this does not reach the - # assertions at all -- ``render()`` raises ``ResidualMarkupError`` on the - # joined ``A module --------`` first, which is what makes this a real check + # The other half: ``-`` and ``~`` *convert* rather than being rejected, one + # ATX level deeper each, so that the by-module sections and their + # Added/Changed/Fixed subsections survive into ``CHANGELOG.md``. Against a + # converter that handles ``=`` alone this does not reach the assertions at + # all -- ``render()`` raises ``ResidualMarkupError`` on the joined + # ``A module --------`` first, which is what makes this a real check # rather than a restatement of current behaviour. index = self.make_tree( entry=ENTRY + '\nA module\n--------\n\nAdded\n~~~~~\n\nSome prose.\n') @@ -625,10 +623,11 @@ def test_the_two_new_sub_heading_levels_become_atx_headings(self) -> None: self.assertNotIn('Added ~~~~~', rendered) def test_an_over_long_sub_heading_underline_is_fatal(self) -> None: - # ``_RESIDUAL``'s comment now claims ``=``, ``-`` and ``~`` all need to stay in + # ``_RESIDUAL``'s comment claims ``=``, ``-`` and ``~`` all need to stay in # its alternation, because rule 1 fires only on an *exact* length match while - # reStructuredText merely requires the underline to be no shorter. Only ``=`` - # was pinned, so the other two thirds of that claim rode on the comment alone. + # reStructuredText merely requires the underline to be no shorter. The test + # below pins ``=``; this pins the other two, so no part of that claim rides on + # the comment alone. for underline in ('-' * 18, '~' * 18): with self.subTest(underline=underline[0]): self._reject(f'\nA sub heading\n{underline}\n\nSome prose.\n', @@ -722,8 +721,8 @@ def test_a_cited_line_holds_the_construct_it_was_cited_for(self) -> None: f'line {number} of the entry does not hold the cited construct') def test_a_cited_line_can_lie_past_the_end_of_the_converted_body(self) -> None: - # The bound that made the old numbers provably wrong: they were counted in - # the body, so they could never reach a construct written below it. + # The bound that makes body-counted line numbers provably wrong: counted in + # the body, they could never reach a construct written below it. entry = padded_entry('and then :term:`sys.modules` at the very end.') body = changelog_md.convert(entry) diff --git a/tests/project/test_conda_intel_cli_upload_2044.py b/tests/project/test_conda_intel_cli_upload_2044.py index 820051e8f3..07c12a0a51 100644 --- a/tests/project/test_conda_intel_cli_upload_2044.py +++ b/tests/project/test_conda_intel_cli_upload_2044.py @@ -3,12 +3,13 @@ ``anaconda/actions/upload-package`` v0.3.1 installs its CLI from ``anaconda.sh/releases/stable/v0.2.1/ana-darwin-x86_64``, which returns 404, so -the step "Upload conda packages to Anaconda" failed on every Intel leg of -``conda-dist`` in :file:`.github/workflows/cron-conda.yml` and would on ``conda`` -in :file:`.github/workflows/create-release.yml`. That step skips the Intel -leg, and "Upload conda packages to Anaconda (Intel)" runs there alone, making -the action's own ``anaconda --token … upload --user …`` call from conda-forge's -anaconda-client, with ``--skip-existing``. +the action cannot upload from an Intel leg of ``conda-dist`` in +:file:`.github/workflows/cron-conda.yml` or of ``conda`` in +:file:`.github/workflows/create-release.yml`. The step "Upload conda packages to +Anaconda" therefore skips the Intel leg, and "Upload conda packages to Anaconda +(Intel)" runs there alone, making the action's own +``anaconda --token … upload --user …`` call from conda-forge's anaconda-client, +with ``--skip-existing``. Each step's ``if:`` is evaluated per matrix leg with :func:`tests.project.test_release_gates.evaluate_condition`. The CLI step's diff --git a/tests/project/test_conda_intel_miniforge_2044.py b/tests/project/test_conda_intel_miniforge_2044.py index a7396b1d37..03615f0991 100644 --- a/tests/project/test_conda_intel_miniforge_2044.py +++ b/tests/project/test_conda_intel_miniforge_2044.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- """#2044: the conda jobs' ``macos-26-intel`` legs install Miniforge. -The step "Conda environment creation and activation" of ``conda-dist`` in +Given no installer, setup-miniconda downloads +``Miniconda3-latest-MacOSX-x86_64.sh`` on the Intel runner. That installer is +frozen at conda 25.7.0, which conflicts with conda-forge's conda-content-trust +plugin and has no py314 build. So on the Intel legs, the step "Conda environment +creation and activation" of ``conda-dist`` in :file:`.github/workflows/cron-conda.yml` and of ``conda`` in -:file:`.github/workflows/create-release.yml` named no installer, so on the Intel -runner setup-miniconda downloaded ``Miniconda3-latest-MacOSX-x86_64.sh``. That -installer is frozen at conda 25.7.0, which conflicts with conda-forge's -conda-content-trust plugin and has no py314 build. The Intel legs pass +:file:`.github/workflows/create-release.yml` passes ``miniforge-version: latest``; every other leg passes an empty string, which names no installer, so it keeps the bundled or downloaded Miniconda. @@ -17,12 +18,12 @@ ``installer-url`` first, then Miniforge when ``miniforge-version`` or ``miniforge-variant`` is non-empty, else Miniconda. -#2123: with Miniforge, the Intel legs broke twice more. Installing the matrix -Python into base rebuilt conda-forge's libmambapy at 2.10.0, which -conda-libmamba-solver 26.10.0 cannot drive, so the Intel legs leave base's -Python alone, as 3.15 does everywhere; :class:`TestTheIntelLegsKeepBasesPython` -pins that. And conda-build 26.9's rpath relocation failed on the vendored chardet -extensions, so :file:`conda/pypcapkit/meta.yaml` turns it off on osx-64 alone; +#2123: with Miniforge, installing the matrix Python into base rebuilds +conda-forge's libmambapy at 2.10.0, which conda-libmamba-solver 26.10.0 cannot +drive, so the Intel legs leave base's Python alone, as 3.15 does everywhere; +:class:`TestTheIntelLegsKeepBasesPython` pins that. And conda-build 26.9's rpath +relocation fails on the vendored chardet extensions, so +:file:`conda/pypcapkit/meta.yaml` turns it off on osx-64 alone; :class:`TestOsx64SkipsBinaryRelocation` evaluates that line's selector as conda-build does, for each leg's platform. diff --git a/tests/project/test_conda_no_windows_steps_1967.py b/tests/project/test_conda_no_windows_steps_1967.py index 3fe8b19aef..76276ef0e8 100644 --- a/tests/project/test_conda_no_windows_steps_1967.py +++ b/tests/project/test_conda_no_windows_steps_1967.py @@ -1,24 +1,23 @@ # -*- coding: utf-8 -*- """#1967: the conda jobs keep no step or ``case`` arm for a runner they have no leg on. -``conda`` in :file:`.github/workflows/create-release.yml` and ``conda-dist`` in -:file:`.github/workflows/cron-conda.yml` had no ``windows-latest`` leg from 2023 -until #2060, yet carried ``Install and Setup (Windows)``, ``Setup Conda Distribution -(Windows)``, the ``matrix.os != 'windows-latest'`` guards on their twins, and a -``windows-latest`` arm in each ``case "${{ matrix.os }}"``: code no run reached. +This guards ``conda`` in :file:`.github/workflows/create-release.yml` and +``conda-dist`` in :file:`.github/workflows/cron-conda.yml` against code no run +reaches: in a job with no ``windows-latest`` leg, a Windows-only step such as +``Install and Setup (Windows)``, a ``matrix.os != 'windows-latest'`` guard on its +twin, or a ``windows-latest`` arm in a ``case "${{ matrix.os }}"``. * :class:`TestNoDeadRunnerCode` pins that no step's ``if:`` or ``continue-on-error`` names a runner label the job's matrix lacks, no ``case`` arm maps one, :file:`util/release_conda.py`, which both jobs' steps run since #1967, maps no label that neither job has a leg on, and, with no Windows leg, - no step is Windows-only. So adding a - Windows leg back means adding its steps back deliberately, rather than - reviving ones nobody has run. #2060 did so for both jobs, whose Windows - steps are therefore live. -* :class:`TestTheCheckSeesDeadCode` feeds :func:`dead` the jobs as they stood - before, rebuilt from the current ones, so the pin cannot pass by reading - nothing, and ``conda-dist`` without its Windows leg, whose steps must then read - as dead. + no step is Windows-only. So a Windows leg comes with its steps added + deliberately, rather than reviving ones nobody has run, as #2060 added both + jobs' Windows legs and steps. +* :class:`TestTheCheckSeesDeadCode` feeds :func:`dead` the jobs with such dead + Windows code rebuilt into them from the current ones, so the pin cannot pass by + reading nothing, and ``conda-dist`` without its Windows leg, whose steps must + then read as dead. """ @@ -112,12 +111,12 @@ def without_windows(step: 'dict[str, Any]') -> 'dict[str, Any]': class TestTheCheckSeesDeadCode(unittest.TestCase): - """:func:`dead` reports each kind of dead code the jobs held before #1967.""" + """:func:`dead` reports each kind of dead runner code: a step, a guard and a ``case`` arm.""" def test_it_reports_a_windows_step_guard_and_arm(self) -> None: for name, job_id in JOBS: spec = copy.deepcopy(_workflows.job(name, job_id)) - # #2060: conda-dist as it stood before its Windows leg came back. + # #2060: the job without its Windows leg. spec['strategy']['matrix']['os'] = [os for os in spec['strategy']['matrix']['os'] if not WINDOWS.search(os)] spec['steps'] = [without_windows(step) for step in spec['steps'] diff --git a/tests/project/test_conda_platform_pin_refresh_1851.py b/tests/project/test_conda_platform_pin_refresh_1851.py index f232b9c43f..be78c04ab2 100644 --- a/tests/project/test_conda_platform_pin_refresh_1851.py +++ b/tests/project/test_conda_platform_pin_refresh_1851.py @@ -387,7 +387,7 @@ def test_json_without_releases_leaves_the_pin_and_warns(self) -> None: "PyPI's JSON has no releases")]) def test_a_pin_no_release_can_move_to_warns_with_the_missing_tags(self) -> None: - # #2025: no release has a cp38 wheel, so the pin used to stay without a word. + # #2025: no release has a cp38 wheel, so the pin stays, and must not stay without a word. result, notes = vendor().refresh(table(), lambda name: recorded(), lambda name: '50.0.2', ('3.8', *PYTHONS), TODAY) self.assertEqual(result, table()) @@ -406,7 +406,7 @@ def test_a_pin_no_single_release_serves_warns_for_every_tag(self) -> None: 'on osx-64 at once')]) def test_a_pin_whose_release_lost_a_wheel_warns_with_the_tag_it_lacks(self) -> None: - # #2028: 47.0.0 still has every wheel, so the 48.0.1 pin used to stay without a word. + # #2028: 47.0.0 still has every wheel, so the 48.0.1 pin stays, and must not stay without a word. result, notes = self.refresh(lost_wheel()) self.assertEqual(result, table()) self.assertEqual(notes, [('warning', 'cryptography 48.0.1 on osx-64 stays as it is, and may no longer ' diff --git a/tests/project/test_conventions_doc_claims.py b/tests/project/test_conventions_doc_claims.py index b30bcc3488..cd1f712c38 100644 --- a/tests/project/test_conventions_doc_claims.py +++ b/tests/project/test_conventions_doc_claims.py @@ -1,28 +1,26 @@ # -*- coding: utf-8 -*- """Claims the split *House Conventions* pages make that the tree can be asked about. -:file:`docs/source/contributing/conventions.rst` used to record every design ruling -on a single page, and most of what it records is reasoning -- which no test can -check. Some of it is not: a count of classes, a per-class classification, a retired -name, an exception type. Those are the parts that rot silently, because Sphinx builds -without ``-W`` and without ``nitpicky``, so a page whose every factual claim has gone -stale still renders and CI still passes. - -GitHub issue #918 part 1 harvested the settled rulings onto that page; part 2 then +The *House Conventions* pages, under :file:`docs/source/contributing/conventions/`, +record the settled design rulings, and most of what they record is reasoning -- +which no test can check. Some of it is not: a count of classes, a per-class +classification, a retired name, an exception type. Those are the parts that rot +silently, because Sphinx builds without ``-W`` and without ``nitpicky``, so a page +whose every factual claim has gone stale still renders and CI still passes. + +GitHub issue #918 part 1 harvested the settled rulings onto a single page; part 2 split it into :file:`docs/source/contributing/conventions/`, one file per ``.. _label:`` anchor plus the :file:`index.rst` that carries the ``.. important::`` preamble and the toctree. This file pins the checkable claims, plus the split's own structure: -* :class:`ConventionAnchorTests` -- every ``.. _label:`` anchor, one now per - file. Before the split the original anchors lived on one page, and - ``tests/corekit/test_sentinel_exports_unit.py`` sliced the file *between* two of - them -- which is exactly what GitHub issue #930 named as the split's concrete - blocker, since separating those two anchors into different files made that slice - raise :exc:`ValueError`. This class pins the post-split shape: every anchor still - exists, lives in exactly its own file, is listed in the index's toctree, and the - top-level :file:`docs/source/index.rst` points at the new index page rather than - the retired bare document path. +* :class:`ConventionAnchorTests` -- every ``.. _label:`` anchor, one per file: + every anchor exists, lives in exactly its own file, is listed in the index's + toctree, and the top-level :file:`docs/source/index.rst` points at the index + page rather than a bare ``contributing/conventions`` document path. A reader + that slices one page *between* two anchors cannot work on this layout -- GitHub + issue #930 named that slice the split's concrete blocker -- so each reader takes + an anchor's file whole. * :class:`PhaseTwoRemainderTests` -- what the page says about `#877 `__'s phase 2 and the population its audit covers, measured rather than remembered. The page said the @@ -37,16 +35,16 @@ good. A ruling that a name must not exist is exactly the kind a later change reintroduces without noticing. * :class:`FailedLookupExceptionTests` -- the worked example the page gives for a name - miss, which named :exc:`KeyError` until #918 and now names - :exc:`~pcapkit.utilities.exceptions.EnumKeyError`. + miss, which names :exc:`~pcapkit.utilities.exceptions.EnumKeyError`, not the + true but unspecific :exc:`KeyError` (#918). * :class:`GetOverrideContractTests` -- what a ``get`` override owes the base: ``@classmethod`` for delegation, which Python forces rather than anyone ruling (#913's precedent, followed by #908), plus the rulings part 1 harvested -- ``quiet=True`` on the raise (#933), no suppression standing in for an honoured signature (#935), and deletion rather than repair when the override only - reimplements the base (#940). Includes the guard for the audit row #940 falsified, - which rendered fine and failed nothing while claiming overrides that no longer - exist. + reimplements the base (#940). Includes a guard against an audit row claiming + overrides that do not exist, as the one #940 falsified did while rendering fine + and failing nothing. * :class:`AenumRoleExclusionTests` -- GitHub issue #934 part C's ruling that ``aenum`` cannot be cross-referenced at all (``conf.py`` excludes it: its ``objects.inv`` carries no ``py:`` objects), converted to plain literals rather @@ -97,12 +95,10 @@ #: it either way would hide that. CONVENTIONS_DIR = ROOT / 'docs' / 'source' / 'contributing' / 'conventions' -#: The index page that carries the toctree and the ``.. important::`` preamble the -#: single page used to open with. +#: The index page that carries the toctree and the ``.. important::`` preamble. INDEX = CONVENTIONS_DIR / 'index.rst' -#: Every ``.. _label:`` anchor, in the narrative order the pre-split page carried -#: them in -- which is also the order the index's toctree lists the files in. +#: Every ``.. _label:`` anchor, in the order the index's toctree lists the files in. #: ``mint-criterion``, ``sentinel-convention`` and ``registry-protocol`` predate #: #918; ``extension-header-subclassing`` arrived with it, and ``process`` came #: next, on the owner's ruling that the settled *process* @@ -130,17 +126,17 @@ 'utilities-or-corekit', ) -#: Each anchor's own file, one-to-one since the split -- there is no longer a single -#: shared page to slice between two of them. +#: Each anchor's own file, one-to-one -- there is no shared page to slice between +#: two of them. PAGES = {anchor: CONVENTIONS_DIR / f'{anchor}.rst' for anchor in ANCHORS} def _page(anchor: 'str') -> 'str': """``anchor``'s own page, whole. - The split retired the anchor-to-anchor slice this used to need: each anchor is - now the whole of its own file, rather than a range between two markers in one - shared page. ``tests/corekit/test_sentinel_exports_unit.py``'s - ``_sentinel_section`` made the same change, for the same reason. + Each anchor is the whole of its own file, rather than a range between two + markers in one shared page, so no anchor-to-anchor slice is needed. + ``tests/corekit/test_sentinel_exports_unit.py``'s ``_sentinel_section`` reads + its page whole for the same reason. Args: anchor: One of :data:`ANCHORS`. @@ -166,9 +162,9 @@ def _page(anchor: 'str') -> 'str': def _every_page() -> 'str': """Every split page's text, concatenated, index included. - For the checks that used to scan the single-page file end to end -- a forbidden - role, a qualified reference -- and still need to scan across every section - plus the preamble, since either could in principle land on any of them. + For the checks that scan across every section plus the preamble -- a forbidden + role, a qualified reference -- since either could in principle land on any of + them. """ return '\n'.join([INDEX.read_text(encoding='utf-8')] @@ -277,7 +273,7 @@ def walk(container: 'type') -> 'None': class ConventionAnchorTests(unittest.TestCase): - """The labels other files cross-reference, one now per file.""" + """The labels other files cross-reference, one per file.""" def test_every_page_exists(self) -> 'None': """Every page :data:`ANCHORS` names, plus the index, is on disk.""" @@ -289,9 +285,8 @@ def test_every_cross_referenced_anchor_is_present(self) -> 'None': """A missing anchor is a dead ``:ref:`` that renders as plain text. Sphinx is run here without ``-W`` and without ``nitpicky``, so an unresolved - reference is not a build failure -- it is a word that used to be a link. - Checked against each anchor's own file, since the split gave each anchor - exactly one home rather than one shared page. + reference is not a build failure -- it is a plain word where a link belongs. + Checked against each anchor's own file, its one home. """ for anchor in ANCHORS: @@ -306,18 +301,16 @@ def test_every_cross_referenced_anchor_is_present(self) -> 'None': def test_no_page_carries_a_different_page_s_anchor(self) -> 'None': """Each anchor lives in exactly one file -- the split's whole point. - Before the split, ``tests/corekit/test_sentinel_exports_unit.py`` sliced one - shared page between ``.. _sentinel-convention:`` and - ``.. _registry-protocol:`` -- reading ``text.index('.. _sentinel-convention:')`` - through ``text.index('.. _registry-protocol:', start)``. GitHub issue #930 - named separating those two anchors into different files as the split's - concrete blocker, because that slice would then raise :exc:`ValueError` from - :meth:`str.index` rather than a wrong answer. #918 part 2 retired the slice - instead of working around it -- ``_sentinel_section`` now reads - :file:`sentinel-convention.rst` whole. What used to be a hard constraint on - the single page is now a fact about the separate files, pinned here so a later - merge back into one page, or a bad copy-paste across two of them, does not - silently resurrect it. + A reader that slices one shared page between ``.. _sentinel-convention:`` + and ``.. _registry-protocol:`` -- ``text.index('.. _sentinel-convention:')`` + through ``text.index('.. _registry-protocol:', start)`` -- raises + :exc:`ValueError` from :meth:`str.index` once those anchors sit in different + files, which is why GitHub issue #930 named that slice the split's concrete + blocker. ``tests/corekit/test_sentinel_exports_unit.py``'s + ``_sentinel_section`` reads :file:`sentinel-convention.rst` whole instead + (#918 part 2). One anchor per file is pinned here, so a later merge back into + one page, or a bad copy-paste across two of them, fails here rather than + silently. """ for anchor in ANCHORS: @@ -336,8 +329,7 @@ def test_the_index_toctree_lists_every_page_in_order(self) -> 'None': An orphan page still resolves cross-references -- Sphinx's reference inventory does not care whether a page is reachable from a toctree -- but it produces a distinct "document isn't included in any toctree" warning. Order - matches :data:`ANCHORS`, the narrative order the single page used to carry - its sections in, with pages added since appended in the order they arrived. + matches :data:`ANCHORS`. """ entries = _toctree_entries(INDEX.read_text(encoding='utf-8')) @@ -348,11 +340,11 @@ def test_the_index_toctree_lists_every_page_in_order(self) -> 'None': def test_the_top_level_index_points_at_the_new_index_page(self) -> 'None': """:file:`docs/source/index.rst` has to name a document, not a directory. - ``.. toctree::`` entries are docnames, and the bare ``contributing/conventions`` - entry it used to carry stopped being one the moment the split turned that - path into a directory -- Sphinx would report "unknown document" for it, - silently, since the build here runs without ``-W``/``-n``. Checked - structurally rather than trusted to a nitpicky build alone. + ``.. toctree::`` entries are docnames, and a bare ``contributing/conventions`` + entry is not one, that path being a directory -- Sphinx would report + "unknown document" for it, silently, since the build here runs without + ``-W``/``-n``. Checked structurally rather than trusted to a nitpicky build + alone. """ text = (ROOT / 'docs' / 'source' / 'index.rst').read_text(encoding='utf-8') @@ -367,24 +359,23 @@ def test_the_top_level_index_points_at_the_new_index_page(self) -> 'None': def test_the_index_preamble_does_not_claim_to_be_a_ruling_page_itself(self) -> 'None': """The index's own prose has to read true of a hub, not of a page. - Round 1 of #918 part 2's split moved the ``.. important::`` preamble onto - :data:`INDEX` unedited, and its prose was written when the whole thing was - one page: *"This page records design rulings"*, and a future one *"is - written onto this page"*. Both went false the moment the index stopped - carrying any ruling of its own -- its children do -- and the second - is worse than stale, because it is the standing instruction #918 part 3 - exists to keep alive, now telling a contributor to write onto the wrong - file. A cross-review caught this on the first PR revision. + Preamble prose written for a single page -- *"This page records design + rulings"*, and a future one *"is written onto this page"* -- reads false on + :data:`INDEX`, which carries no ruling of its own; its children do. The + second is worse than stale, because it is the standing instruction #918 + part 3 exists to keep alive, and on the index it tells a contributor to + write onto the wrong file. #918 part 2's first round moved the preamble + across unedited, and a cross-review caught it. Checked as a ban on the self-referential singular ``"this page"`` rather - than against the exact old sentences, so a future rewrite that - reintroduces the same mistake in different words still trips this -- the - index legitimately never needs that phrase, since every true statement - about ruling content here names a child page, or says "the pages below" - / "here" for the set of them. Paired with a positive check that the - corrected standing-instruction phrase actually landed, rather than merely - that the old one is gone, following :class:`RetiredNameTests`'s two-sided - pattern for a retired name elsewhere in this module. + than against those exact sentences, so a rewrite that makes the same + mistake in different words still trips this -- the index legitimately + never needs that phrase, since every true statement about ruling content + here names a child page, or says "the pages below" / "here" for the set of + them. Paired with a positive check that the standing-instruction phrase is + present, rather than merely that the single-page one is absent, following + :class:`RetiredNameTests`'s two-sided pattern for a retired name elsewhere + in this module. """ text = INDEX.read_text(encoding='utf-8') @@ -668,10 +659,11 @@ class FailedLookupExceptionTests(unittest.TestCase): """The worked example the page gives for a lookup that does not resolve.""" def test_the_page_names_the_exception_the_code_actually_raises(self) -> 'None': - """It said :exc:`KeyError` until #918, which was true but no longer specific. + """It names ``EnumKeyError``, not the true but unspecific :exc:`KeyError`. - ``EnumKeyError`` is what GitHub issue #923 made ``get`` raise, and it *is* a - :exc:`KeyError` -- which is why the stale wording never failed anything. + ``EnumKeyError`` is what ``get`` raises (GitHub issue #923), and it *is* a + :exc:`KeyError` -- which is why a page naming :exc:`KeyError` fails nothing + else. """ from pcapkit.const.ftp.command import FEATCode @@ -788,9 +780,8 @@ class GetOverrideContractTests(unittest.TestCase): half is what rots: the same claims are already pinned in ``tests/corekit`` and ``tests/protocols`` against the *code*, so a later change that moves the code fails there, while a page still describing the old shape fails nothing. This - class is the other direction, and it is how the stale audit row this change - corrected (*"Two of them define a ``get`` of their own"*, false since #940) - would have been caught. + class is the other direction: it is what catches a stale audit row such as + *"Two of them define a ``get`` of their own"*, false since #940. """ @@ -830,7 +821,7 @@ def test_a_delegating_override_is_a_classmethod(self) -> 'None': # -- which is exactly what GitHub pull request #940 did to the mh/ngap helpers, # so it is a live failure mode rather than a hypothetical one. The KeyError # fails the test either way, so nothing regressed silently; what it does not do - # is say *which* class stopped defining `get`, or that the page is now wrong to + # is say *which* class stopped defining `get`, or that the page is then wrong to # list it. The sibling at `test_the_two_redundant_overrides_are_gone` already # does this the right way round, with `assertNotIn('get', vars(klass))`. # Deliberately *not* under `subTest`: a subTest records its failure and lets @@ -999,9 +990,9 @@ def test_the_two_redundant_overrides_are_gone(self) -> 'None': """#940: the owner ruled for deleting the redundant overrides, not widening them. Several things at once, because the ruling is only settled if they all hold: - neither class defines ``get``, the module defines none at all, and the - ``[override]``/``arguments-differ`` pair the old signatures needed went with - them. Scoped to that exact pair rather than to ``arguments-differ`` alone, + neither class defines ``get``, the module defines none at all, and no + ``[override]``/``arguments-differ`` pair, which such signatures need, is + left behind. Scoped to that exact pair rather than to ``arguments-differ`` alone, which mh.py still carries for ``read`` and ``__post_init__`` -- unrelated to any ``get``, and measured before asserting on it. @@ -1070,9 +1061,9 @@ def test_a_non_string_key_raises_a_value_miss_on_every_helper(self) -> 'None': {'FastBindingAcknowledgmentStatus', 'IPv6AddressPrefixCode', 'LMAAddressCode', 'LocalizedRoutingStatus', 'Criticality', 'PDUKind'}) - # The page's own row, which measurement alone does not pin. It once stated a - # helper count that disagreed with the measured one; it now names each - # helper, so a helper added without its name on the row fails here. + # The page's own row, which measurement alone does not pin. It names each + # helper rather than a count, which can disagree with the measured one, so a + # helper added without its name on the row fails here. page = _page('registry-protocol') head = ' * - The :mod:`~pcapkit.protocols.internet.mh` and' self.assertIn(head, page, 'the audit table has no mh/ngap helper row') @@ -1351,10 +1342,9 @@ def test_the_changelog_file_is_not_shaped_one_entry_per_commit(self) -> 'None': """ text = self.CHANGELOG.read_text(encoding='utf-8') - # Was ``assertIn(f'* **{kind}** --')`` against the inline bold labels the file - # used before the by-module restructure. Those labels are gone -- the kinds are - # now ``~``-underlined subsections inside each module section -- so the inline - # form would fail for a reason that has nothing to do with this claim. + # The kinds are ``~``-underlined subsections inside each module section, not + # inline bold ``* **{kind}** --`` labels, so the subsection heading is what is + # read. for kind in ('Added', 'Changed', 'Fixed'): with self.subTest(kind=kind): self.assertRegex( @@ -1491,12 +1481,12 @@ def test_the_page_says_the_labels_are_set_by_hand(self) -> 'None': ecosystems, ['pip', 'github-actions'], 'dependabot now watches a different set of ecosystems, so the page\'s claim ' f'about which labels it applies needs re-deriving: {ecosystems}') - # Derived rather than pinned to one phrasing. The previous form asserted the - # single literal ``dependencies`` and ``github_actions``, so a re-attribution - # worded any other way -- "dependabot applies ``github_actions``", a comma for - # the "and", the two labels in the other order -- slipped straight past it, which - # is the weakness GitHub issue #949 asked to be looked at. Every sentence pairing - # the tool with the label is checked instead, so the phrasing no longer matters. + # Derived rather than pinned to one phrasing. Asserting the single literal + # ``dependencies`` and ``github_actions`` lets a re-attribution worded any other + # way -- "dependabot applies ``github_actions``", a comma for the "and", the two + # labels in the other order -- slip straight past, which is the weakness GitHub + # issue #949 asked to be looked at. Every sentence pairing the tool with the + # label is checked instead, so the phrasing does not matter. # # Proximity is not the test, and a first attempt at #949 that used it was wrong: # the section then stated more than once, deliberately, that ``github_actions`` @@ -1575,13 +1565,12 @@ def test_every_issue_link_number_matches_its_own_url(self) -> 'None': Scans every page rather than one, since a link can land on any of them. - The pages have since moved to the ``:issue:``/``:pr:`` roles configured in - ``docs/source/conf.py``, which is the move the floor below was written to - notice. A role carries no second number, so there is nothing to disagree with - itself -- the mismatch class this check exists for cannot arise there, and - Sphinx errors on a role name it does not know. So the floor now counts both - forms, and the pairwise comparison still runs over whatever explicit links - remain. + The pages also cite with the ``:issue:``/``:pr:`` roles configured in + ``docs/source/conf.py``. A role carries no second number, so there is nothing + to disagree with itself -- the mismatch class this check exists for cannot + arise there, and Sphinx errors on a role name it does not know. So the floor + counts both forms, and the pairwise comparison runs over whatever explicit + links remain. """ pattern = re.compile( @@ -1595,8 +1584,8 @@ def test_every_issue_link_number_matches_its_own_url(self) -> 'None': # check disables itself silently on any citation-style change -- a # single-underscore named reference, or a further move away from the roles, # takes both patterns to zero while the docstring goes on claiming it closes - # the largest class of unpinned claim. The module guards this shape five other - # times; this one had been left out. + # the largest class of unpinned claim -- the shape the module's other floors + # guard against too. self.assertGreater( len(found) + len(roles), 40, f'only {len(found)} explicit links and {len(roles)} role citations matched ' @@ -1613,20 +1602,11 @@ def test_every_issue_link_number_matches_its_own_url(self) -> 'None': def test_the_page_describes_the_changelog_grouping_as_it_is(self) -> 'None': """The current shape of ``1.5.0.rst``, counted rather than eyeballed. - This replaces a check on the file's *previous* shape. The page used to say the - entries carried inline kind labels in a given number of separate runs, - and this test measured those runs and required the figure in the prose. The - by-module restructure removed the inline labels outright, so that test did not - merely go stale -- it went **vacuous**: its ``re.findall`` matched nothing, - ``runs`` was empty, and its ``assertGreater`` floors failed before the - prose needle was ever reached. A test whose subject has been deleted cannot be - repaired by updating a number. - - What is checked is the shape that actually shipped: one ``-`` underlined - section per top-level module plus one for what belongs to none, each carrying - ``~`` underlined kind subsections, with no inline kind label left anywhere. The - page used to state the section count as well, pinned here; it states the shape - instead (#1103's ruling, #1811), so what is checked is its one-to-one claim: + What is checked is the file's shape: one ``-`` underlined section per + top-level module plus one for what belongs to none, each carrying ``~`` + underlined kind subsections, with no inline kind label anywhere. The page + states the shape rather than a section count (#1103's ruling, #1811), so what + is checked is its one-to-one claim: the sections are the packages under :file:`pcapkit/` and *Project infrastructure*, nothing more and nothing less. @@ -1688,9 +1668,9 @@ def test_the_page_keeps_breaking_additive_and_its_coverage_honest(self) -> 'None from pcapkit.utilities.exceptions import BaseError, ProtocolError # Not `issubclass(ProtocolError, Exception)`, which every exception class - # satisfies and which therefore verified nothing. The page's claim is that - # #811 made a *library* error escape where a bare struct.error used to, so - # what is checkable locally is that the type is the library's own. + # satisfies and which therefore verifies nothing. The page claims that a + # *library* `ProtocolError` escapes rather than a bare `struct.error` (#811), + # so what is checkable locally is that the type is the library's own. self.assertEqual(ProtocolError.__module__, 'pcapkit.utilities.exceptions', 'the page cites ProtocolError as an in-library exception ' 'type, but it no longer comes from ' diff --git a/tests/project/test_cron_reports_2030.py b/tests/project/test_cron_reports_2030.py index 0cf289b4a5..b6fa651e3d 100644 --- a/tests/project/test_cron_reports_2030.py +++ b/tests/project/test_cron_reports_2030.py @@ -48,7 +48,8 @@ (VERSIONS, 'detect', 'cron-python-versions: Python version update report', 'python-versions-report', 'python-versions-report.md'), ) -#: Every job's own ``permissions:``, and the top-level block, as before #2030. +#: Every job's own ``permissions:`` but ``discussion``'s, and the top-level block, +#: so that only ``discussion`` may write discussions (#2030). PERMISSIONS = { CONDA: {None: {'contents': 'read'}, 'mode': {}, 'head': {'contents': 'read'}, 'conda-update': {'contents': 'write'}, 'conda-tag': {'contents': 'write'}, diff --git a/tests/project/test_discussion_report_2030.py b/tests/project/test_discussion_report_2030.py index 7ae2d222dc..1c32fdec29 100644 --- a/tests/project/test_discussion_report_2030.py +++ b/tests/project/test_discussion_report_2030.py @@ -504,8 +504,9 @@ def test_each_warning_is_one_escaped_line(self) -> None: ('cron-vendor.yml', 'vendor-update', 'cron-vendor: vendor constant update report', 'vendor-report', 'vendor-report.md'), ) -#: Every job's own ``permissions:``, and the top-level block, as before #2030; -#: ``None`` for a job with none of its own. +#: Every job's own ``permissions:`` but ``discussion``'s, and the top-level block, +#: so that only ``discussion`` may write discussions (#2030); ``None`` for a job +#: with none of its own. PERMISSIONS = { 'ci-timing.yml': {None: {'actions': 'read', 'contents': 'read'}, 'report': None}, 'cron-vendor.yml': {None: {}, 'head': {'contents': 'read'}, 'vendor-update': {'contents': 'write'}}, diff --git a/tests/project/test_docs_ambiguous_xrefs_1775_unit.py b/tests/project/test_docs_ambiguous_xrefs_1775_unit.py index de9c9f39a4..8aee5fcd1b 100644 --- a/tests/project/test_docs_ambiguous_xrefs_1775_unit.py +++ b/tests/project/test_docs_ambiguous_xrefs_1775_unit.py @@ -127,7 +127,7 @@ def sphinx_skip_reason() -> 'str': REGISTRY_MEMBERS = ('pcapkit.const.reg.apptype.tcp.TCP.dict', 'pcapkit.const.reg.apptype.udp.UDP.dict') -#: A registry member's docstring whose first line napoleon used to split. +#: A registry member's docstring whose first line napoleon, unaided, splits. H_323 = 'pcapkit.const.sctp.payload_protocol_identifier.PayloadProtocolIdentifier.H_323' H_323_DOC = ['H.323 [http://standard.pictel.com/ftp/avc-site/0206 Bru/AVD-2198.zip][H.323', 'over SCTP October 2002.]'] @@ -254,11 +254,11 @@ def resolve(self, node: 'Any') -> 'Any': node['reftarget'], node, nodes.literal('', 'x')) def test_annotation_builtin_is_left_to_intersphinx(self) -> 'None': - """``dict`` in an annotation used to link to ``TCP.dict``.""" + """``dict`` in an annotation does not link to ``TCP.dict``.""" self.assertIsNone(self.resolve(self.annotation('dict'))) def test_field_type_builtin_is_left_to_intersphinx(self) -> 'None': - """``dict`` in a ``:rtype:`` field used to link to ``TCP.dict`` too.""" + """``dict`` in a ``:rtype:`` field does not link to ``TCP.dict`` either.""" self.assertIsNone(self.resolve(self.field_type('dict'))) def test_dotted_role_keeps_its_fuzzy_search(self) -> 'None': @@ -305,7 +305,7 @@ def process(self, name: 'str', obj: 'object', lines: 'list[str]') -> 'list[str]' return lines def test_registry_member_keeps_its_docstring(self) -> 'None': - """The URL's colon used to turn ``H.323 [http`` into the member's type. + """Left to napoleon, the URL's colon turns ``H.323 [http`` into the member's type. Autodoc hands over the plain value -- ``13``, not the member -- so that is what is passed here. diff --git a/tests/project/test_docs_engines_page_unit.py b/tests/project/test_docs_engines_page_unit.py index 4df9c13be2..f21fb7df37 100644 --- a/tests/project/test_docs_engines_page_unit.py +++ b/tests/project/test_docs_engines_page_unit.py @@ -2,7 +2,7 @@ """Pins the engine comparison on its own page, :file:`docs/source/engines.rst`. The comparison -- engines, Python ceilings, prerequisites and the benchmark -tables -- moved off the root document, which keeps a summary and a link. +tables -- lives on its own page; the root document carries a summary and a link. * :class:`PlacementTests` -- the page is in a toctree, the root document links it and does not carry the tables, and the prerequisites label is on the page. diff --git a/tests/project/test_docs_warns_xrefs_2094_unit.py b/tests/project/test_docs_warns_xrefs_2094_unit.py index 79165faa51..096b18641c 100644 --- a/tests/project/test_docs_warns_xrefs_2094_unit.py +++ b/tests/project/test_docs_warns_xrefs_2094_unit.py @@ -126,7 +126,7 @@ def test_a_role_is_kept_as_written(self) -> 'None': ':class:`~pcapkit.utilities.warnings.InfoWarning`') def test_real_docstring(self) -> 'None': - """``register_protocol``'s ``Warns:`` entry used to be ``**pcapkit.utilities.warnings.RegistryWarning**``.""" + """``register_protocol``'s ``Warns:`` entry is not ``**pcapkit.utilities.warnings.RegistryWarning**``.""" from pcapkit.foundation.registry.protocols import register_protocol # pylint: disable=import-outside-toplevel lines = self.google(inspect.getdoc(register_protocol)) warns = [line for line in lines if line.startswith(':Warns:')] diff --git a/tests/project/test_documentation_claims.py b/tests/project/test_documentation_claims.py index f249a34d46..e8ed932f29 100644 --- a/tests/project/test_documentation_claims.py +++ b/tests/project/test_documentation_claims.py @@ -83,10 +83,10 @@ def test_documented_sense_matches_the_code(self) -> None: it. Note: - The condition was a bare ``if self._flag_n:`` until #620, which is why - this checks for the ``continue`` under a test *mentioning* the flag - rather than for that exact line -- the flag now shares the condition - with a termination check, since on its own it never stopped. That also + The flag shares its condition with a termination check, since on its + own it never stopped (#620), which is why this checks for the + ``continue`` under a test *mentioning* the flag rather than for an + exact ``if self._flag_n:`` line. That also means this test does not distinguish #620's defect from its fix: it pins the documented *sense* of the flag, not that the loop ends. :file:`tests/foundation/test_extraction_no_eof.py` pins the ending. diff --git a/tests/project/test_engine_stubs_1878_unit.py b/tests/project/test_engine_stubs_1878_unit.py index ec5ca45ce7..316936d18e 100644 --- a/tests/project/test_engine_stubs_1878_unit.py +++ b/tests/project/test_engine_stubs_1878_unit.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- """Each third-party engine types the package it drives (#1878). -An engine keeps that package in ``_expkg``. #1870 annotated it ``'ModuleType'``, -which mypy reads as :data:`~typing.Any` for every attribute, so nothing called -through it was checked. Each engine now annotates it with a +An engine keeps that package in ``_expkg``. Each engine annotates it with a :class:`~typing.Protocol` of its own, defined in its module-level -``if TYPE_CHECKING:`` block and declaring what the engine reads through it. +``if TYPE_CHECKING:`` block and declaring what the engine reads through it. An +``'ModuleType'`` annotation, as #1870 gave it, is read by mypy as +:data:`~typing.Any` for every attribute, so nothing called through it would be +checked. ``scapy`` ships its own types. The other engine packages ship none, so :file:`typings/` holds stubs for the part of each that :mod:`pcapkit` uses, diff --git a/tests/project/test_ftp_featcode_doc_page_934_unit.py b/tests/project/test_ftp_featcode_doc_page_934_unit.py index 531d115fc0..711a37384e 100644 --- a/tests/project/test_ftp_featcode_doc_page_934_unit.py +++ b/tests/project/test_ftp_featcode_doc_page_934_unit.py @@ -1,19 +1,17 @@ # -*- coding: utf-8 -*- """Pins :class:`~pcapkit.const.ftp.command.FEATCode` as a documented API target. -GitHub issue #934, part B: what is now -:file:`docs/source/contributing/conventions/registry-protocol.rst` (GitHub issue -#918 split it out of the single-page :file:`conventions.rst` that carried it at -the time) cross-references :class:`~pcapkit.const.ftp.command.FEATCode` three -times (the ``:class:`` role, at what were lines 592, 733 and 740 on -``83c7552b8``), and none of -them used to resolve, because no page under :file:`docs/source/` documented that -class -- confirmed by an explicit nitpicky ``sphinx-build`` before -:file:`docs/source/pcapkit/const/ftp.rst` gained an ``autoclass`` directive for it, -and again after, to confirm the fix. ``FEATCode`` is not in +GitHub issue #934, part B: +:file:`docs/source/contributing/conventions/registry-protocol.rst` +cross-references :class:`~pcapkit.const.ftp.command.FEATCode` with the +``:class:`` role, and those references resolve only because +:file:`docs/source/pcapkit/const/ftp.rst` carries an ``autoclass`` directive for +the class -- without it no page under :file:`docs/source/` documents it and none +of them resolves, as an explicit nitpicky ``sphinx-build`` with and without the +directive confirmed. ``FEATCode`` is not in :mod:`pcapkit.const.ftp.command`'s ``__all__`` (only ``Command`` is), which is why the -page's existing ``autoclass:: pcapkit.const.ftp.command.Command`` directive never -pulled it in as a side effect. +page's ``autoclass:: pcapkit.const.ftp.command.Command`` directive does not +pull it in as a side effect. This does **not** re-test Sphinx's cross-reference resolution itself -- as :file:`tests/project/test_sentinels_doc_page_934_unit.py` explains for the sibling @@ -34,7 +32,7 @@ class -- confirmed by an explicit nitpicky ``sphinx-build`` before ROOT = pathlib.Path(__file__).resolve().parents[2] -#: The page that gained the ``FEATCode`` entry for GitHub issue #934. +#: The page that carries the ``FEATCode`` entry (GitHub issue #934). PAGE = ROOT / 'docs' / 'source' / 'pcapkit' / 'const' / 'ftp.rst' diff --git a/tests/project/test_generators_import_their_own_tree.py b/tests/project/test_generators_import_their_own_tree.py index 7bc7c6cda1..44b24eb1e8 100644 --- a/tests/project/test_generators_import_their_own_tree.py +++ b/tests/project/test_generators_import_their_own_tree.py @@ -2,10 +2,11 @@ """The sample generators build from their own tree's :mod:`pcapkit`. GitHub issue #1343. Run as a script, a generator's ``sys.path[0]`` is -:file:`examples/generators/`, not the tree root, so ``import pcapkit`` fell -through to whatever was installed. An editable install pins one checkout, so a -worktree's generators silently built their fixtures from *that* checkout's -library: fewer captured cases than the tree defines, with nothing failing. +:file:`examples/generators/`, not the tree root, so on its own ``import pcapkit`` +falls through to whatever is installed. An editable install pins one checkout, +so a worktree's generators would silently build their fixtures from *that* +checkout's library: fewer captured cases than the tree defines, with nothing +failing. Each generator that imports :mod:`pcapkit` puts its own tree root first on :data:`sys.path`. The probe below proves it from a scratch worktree, whose path @@ -13,9 +14,9 @@ The probe script lives outside the tree, so -- exactly as when a generator is run as a script -- nothing but the generator itself can put the tree on the path. -The second half of the issue is that the shortfall was silent: -``options.generate()`` recorded each failing case and carried on. It now raises -once the captures it could build are written, which +The second half of the issue is a silent shortfall, ``options.generate()`` +recording each failing case and carrying on. It raises once the captures it +could build are written, which :meth:`OptionsGenerateTests.test_left_out_case_raises` pins. This module is unit-tier and reads no capture. diff --git a/tests/project/test_graalpy_worker_heap_1753.py b/tests/project/test_graalpy_worker_heap_1753.py index 64b3ee59ac..e2ba60f902 100644 --- a/tests/project/test_graalpy_worker_heap_1753.py +++ b/tests/project/test_graalpy_worker_heap_1753.py @@ -127,7 +127,7 @@ def test_the_summary_warns_of_the_lost_workers(self) -> None: self.assertIn('7 worker(s) down', result.stdout) def test_a_status_without_the_count_warns_of_nothing_new(self) -> None: - """A ``tests-status`` from before #1753, or a partial one, is no crash.""" + """A ``tests-status`` without #1753's count, or a partial one, is no crash.""" (self.temp / 'tests-status').write_text('exit=1\nscope=12G\noom_kills=0\nmemory_errors=0\n', encoding='utf-8') result = sh(step(SUMMARY)['run'], self.env, BASH) self.assertEqual(result.returncode, 0, result.stderr) diff --git a/tests/project/test_isort_clean.py b/tests/project/test_isort_clean.py index 28486aaa04..8ab1ff89f8 100644 --- a/tests/project/test_isort_clean.py +++ b/tests/project/test_isort_clean.py @@ -1,135 +1,75 @@ # -*- coding: utf-8 -*- """Pins ``make isort`` clean on every one of its lines (#757). -#757 was filed from a reproduction that ran bare ``isort --check-only --diff -pcapkit/protocols/schema/schema.py``, with none of the flags ``make isort`` -actually uses. Bare isort wraps at its own default of 79 columns, so it flagged -the ``misc`` import (89 characters) and proposed wrapping it -- and from that -diff the issue reasoned that isort's wrap width must disagree with the -project's ``line-too-long`` threshold of 120, since 89 is under 120 but over -79. - -Re-running with the Makefile's own flags -- see :data:`MAKEFILE_LINES` below, -one entry per line of the ``isort:`` target -- shows a different picture. +Each line of the Makefile's ``isort:`` target -- see :data:`MAKEFILE_LINES` +below, one entry per line -- is run as a real ``isort`` invocation with that +line's exact flags, not a hand-rolled guess about what those flags imply, so an +edit that introduces an unsorted or mis-wrapped import anywhere ``make isort`` +reaches fails here instead of surfacing as "clean locally, red in CI" the way +#757 did. + Those flags set the width to 100, exactly as :file:`CONTRIBUTING.md` documents ("120 for ``pylint`` and 100 for ``isort``, not PEP 8's 79"): a deliberate, -written-down split, not a misconfiguration. Under *that* width the ``misc`` -import is not flagged at all (89 < 100). What *was* flagged, on the commit -this test landed with, were things the whole-tree line's flags alone -would not have caught: - -* :file:`pcapkit/protocols/schema/schema.py` -- its ``pcapkit.utilities.warnings`` - import was wrapped across two lines at some earlier width, and unwrapped it - is 96 characters, under 100, so isort's real verdict was to collapse it back - to one line rather than wrap another import. -* :file:`examples/generators/dispatch.py` -- two function-local import blocks - isort wants a blank line after (the util/examples-generators line's - targets, unreachable from the whole-tree line's ``pcapkit`` argument). -* :file:`examples/generators/options.py` -- an ``import ipaddress`` isort wants - moved above the ``from pcapkit...`` block, and a ``from scapy.all import - ...`` isort wants reordered by its constant/class split (``IP, TCP`` before - ``Ether, IPv6, Raw``). Also the util/examples-generators line only; only - import order changed, since this module is loaded and run at test time, - not just read at review time. - These test modules load it by path via - ``importlib.util.spec_from_file_location``: - :file:`tests/protocols/test_option_generator_tcp_base_unit.py`, - :file:`tests/protocols/test_option_coverage_runtime.py`, and - :file:`tests/protocols/test_option_roundtrip_unit.py`. - :file:`examples/generators/dispatch.py`'s module docstring and - :file:`docs/source/changelog/1.5.0.rst` both name it - directly, too. - -So this module does not re-litigate the width. It just pins isort's own -verdict on each of those lines -- via real ``isort`` invocations with the -Makefile's exact flags, not a hand-rolled guess about what those flags imply -- -so a future edit that reintroduces an unsorted or mis-wrapped import anywhere -``make isort`` reaches fails here instead of surfacing as "clean locally, red -in CI" the way #757 did. Earlier revisions of this test covered the -whole-tree line only (:file:`pcapkit`, skipping ``__init__.py``) and so -missed exactly the :file:`examples/generators/` files above and the -``__init__.py`` files that sit at least one directory below -:file:`pcapkit/const/` or :file:`pcapkit/vendor/` -- the const and vendor -lines have no ``--skip-glob``, so between them they re-cover whichever of -the files the whole-tree line skips happen to live there, even though -the whole-tree line skips them package-wide. The const and vendor lines -used to be one, :file:`pcapkit/const/*/*.py` and :file:`pcapkit/vendor/*/*.py` -combined into a single brace-expanded glob that stopped at a fixed two -levels, which the #754 split outgrew the moment -:file:`pcapkit/const/reg/apptype/__init__.py` landed a level deeper still and -nothing sorted it any more (#765). - -The first fix tried here widened that one line to -``pcapkit/{const,vendor}/*/**/*.py`` under ``shopt -s globstar``, reaching -any depth below the first subdirectory. That was itself wrong, and in the -same shape as the bug it fixed: ``globstar`` needs bash >= 4.0, -:file:`Makefile`'s ``SHELL := $(shell command -v bash ...)`` only ever picks -*some* ``bash`` off ``$PATH``, and macOS ships 3.2 as ``/bin/bash`` with no -guarantee a newer one sits ahead of it. Without ``globstar`` the shell option -itself fails, ``shopt`` writes one line to stderr and returns non-zero, and -the *next* command in the recipe still runs under the plain, non-recursive -glob -- silently, since make does not stop a recipe line for a failed -``shopt``, only for the isort invocation that follows it. So on such a -machine that one line would have gone from reaching 264 files to reaching -12, sorting none of the 32 depth-two ``__init__.py`` files it used to cover -(the other two, three levels down under :file:`.../reg/apptype/`, stayed -reachable even by the degraded glob) -- the #765 hole reopened one level up, -by #765's own fix. - -The second fix replaced that with ``$$(find pcapkit/const -mindepth 2 -name -'*.py') $$(find pcapkit/vendor -mindepth 2 -name '*.py')``, both handed to -one ``isort`` invocation: ``find`` needs no particular bash version and no -shell option at all, and reaches any depth the same way. That closed the -bash-version gap but opened a narrower one of its own: merged into a single -invocation, only the case where *both* ``find`` calls come back empty is -loud -- ``isort`` then gets zero paths, exits 1 rather than reading -``stdin``, and make reports the recipe as failed because that command's own -status was non-zero, the same as it would for any other line (no ``set -e`` -needed for this, and none is set). If only *one* of the two came back empty, -though, the other's files would still reach ``isort`` in that same -invocation, which would exit 0 having quietly sorted half the tree. Neither -case is reachable -- no path under :file:`pcapkit/const/` or -:file:`pcapkit/vendor/` contains whitespace to break the expansion, and -neither directory can be absent where ``make isort`` runs -- but it is the -same shape of latent gap #765 itself was, so the third fix, now in place, is -to split the one invocation into two: one call covers -:file:`pcapkit/const/`, the other covers :file:`pcapkit/vendor/`, matching -:file:`.github/workflows/cron-vendor.yml`'s own two calls exactly (below) and -leaving neither case above reachable even in principle. The leading -``-mindepth 2`` on each stays for the same reason the original glob's -leading ``*/`` did: dropping it would also reach -:file:`pcapkit/const/__init__.py` and :file:`pcapkit/vendor/__init__.py` -themselves, whose hand-grouped import order isort would flatten -- see -:func:`_const_targets` and :func:`_vendor_targets`. - -The identical two-level glob also lived in -:file:`.github/workflows/cron-vendor.yml`, on the path that actually *writes* -the deep files: ``pcapkit-vendor`` regenerates :mod:`pcapkit.const` from the -:mod:`pcapkit.vendor` crawlers, and that job's own ``isort`` calls -- already -two, one per directory -- were as blind to :file:`pcapkit/const/reg/apptype/` -as the Makefile's own line used to be. Fixed there too (#765), the same way -and for the same reason: that job runs on a ``macos-latest`` runner, so -relying on ``bash`` >= 4.0 would have been exactly the mistake the Makefile's -line first made. This module does not cover that fix, though: it only +written-down split, not a misconfiguration. #757 was filed from a bare +``isort --check-only --diff pcapkit/protocols/schema/schema.py``, which wraps at +isort's own default of 79 columns and so flagged an 89-character import the +Makefile's width accepts. So this module does not re-litigate the width. + +Every line is covered, not the whole-tree one alone. That line (:file:`pcapkit`, +skipping ``__init__.py``) reaches neither :file:`examples/generators/` nor +:file:`util/`, which the last line sorts, nor the ``__init__.py`` files that sit +at least one directory below :file:`pcapkit/const/` or :file:`pcapkit/vendor/`. +The const and vendor lines have no ``--skip-glob``, so between them they +re-cover whichever of the files the whole-tree line skips happen to live there. +Their shape closes these gaps (#765): + +* **Any depth.** A brace-expanded ``pcapkit/{const,vendor}/*/*.py`` stops at a + fixed two levels, so :file:`pcapkit/const/reg/apptype/__init__.py`, a level + deeper still, would go unsorted. A ``**`` glob under ``shopt -s globstar`` + needs bash >= 4.0, but :file:`Makefile`'s ``SHELL := $(shell command -v bash + ...)`` only ever picks *some* ``bash`` off ``$PATH``, and macOS ships 3.2 as + ``/bin/bash`` with no guarantee a newer one sits ahead of it. There ``shopt`` + writes one line to stderr and returns non-zero, and the *next* command in the + recipe still runs under the plain, non-recursive glob -- silently, since make + judges that recipe line by the isort invocation that follows ``shopt``, not by + ``shopt`` itself. ``find`` needs no particular bash version and no shell + option at all, and reaches any depth the same way. +* **One invocation per directory.** Handed to a single ``isort``, the two + ``find`` calls are loud only when *both* come back empty -- ``isort`` then + gets zero paths, exits 1 rather than reading ``stdin``, and make reports the + recipe as failed (no ``set -e`` needed for this, and none is set). If only + *one* came back empty, the other's files would still reach ``isort``, which + would exit 0 having quietly sorted half the tree. Neither case is reachable -- + no path under :file:`pcapkit/const/` or :file:`pcapkit/vendor/` contains + whitespace to break the expansion, and neither directory can be absent where + ``make isort`` runs -- but one call per directory leaves neither reachable + even in principle, and matches :file:`.github/workflows/cron-vendor.yml`'s + own two calls (below). +* **``-mindepth 2``.** Without it ``find`` would also reach + :file:`pcapkit/const/__init__.py` and :file:`pcapkit/vendor/__init__.py` + themselves, whose hand-grouped import order isort would flatten -- see + :func:`_const_targets` and :func:`_vendor_targets`. + +:file:`.github/workflows/cron-vendor.yml` sorts the deep files the same way and +for the same reason (#765): it is the path that actually *writes* them, as +``pcapkit-vendor`` regenerates :mod:`pcapkit.const` from the :mod:`pcapkit.vendor` +crawlers, and that job runs on a ``macos-latest`` runner, where ``bash`` >= 4.0 +cannot be relied on. This module does not cover that job, though: it only exercises ``make isort``, and :file:`cron-vendor.yml`'s ``vendor-update`` job never runs it -- the tests that job runs before it commits are :file:`tests/const` and :file:`tests/vendor` alone (#1853). -Until #766 this module gated on isort with a ``try: import isort`` inside -:meth:`~TestIsortIsCleanOnThePackage.setUpClass`, raising -:exc:`~unittest.SkipTest` from the body -- and isort was in no -:file:`pyproject.toml` extra, so every CI leg skipped it. Both halves of that -are fixed here, and they had to move together. - -The gate is now a module-level :data:`HAS_ISORT` read by an -``@unittest.skipUnless`` decorator, because +The gate and the install line go together (#766). The gate is a module-level +:data:`HAS_ISORT` read by an ``@unittest.skipUnless`` decorator, not a +``try: import isort`` raising :exc:`~unittest.SkipTest` inside +:meth:`~TestIsortIsCleanOnThePackage.setUpClass`, because :func:`tests._dependency_gates._gates_of` walks a definition's ``decorator_list`` and never a function body: an inline ``skipTest`` is invisible to that scan *by construction*, which is the dark-test hazard #745 -exists to stop and which #779 fixed the same way for the sibling ``mypy`` +exists to stop and which #779 avoids the same way for the sibling ``mypy`` check. -The install line is the other half. ``isort`` is now a ``test`` extra +The install line is the other half. ``isort`` is a ``test`` extra requirement, so the pytest jobs whose selection collects this module -- ``test`` and ``gate``, since #849 took ``engine-tests`` off it -- run it for real rather than reporting a skip. That is the opposite resolution from @@ -141,14 +81,14 @@ not one of the linters it installs -- it appears only in :file:`cron-vendor.yml`, as a formatter that rewrites the generated constants on a schedule rather than as a check. So declining the install line here would -have left ``make isort``'s verdict checked nowhere at all, which is what #766 +leave ``make isort``'s verdict checked nowhere at all, which is what #766 objected to. It is cheap enough to be uncontroversial, too: the lines below take about 1.5s together, the "costs nothing per leg" category the test job's own install comment puts ``DPKT`` in rather than the ``Scapy`` one. ``isort`` is in ``Pipfile``'s ``[dev-packages]`` too, as of #810 -- that being what ``make isort``'s own ``pipenv run`` prefix resolves against, so the target -no longer depends on a contributor happening to have isort on ``PATH``. The two +does not depend on a contributor happening to have isort on ``PATH``. The two declarations are additive rather than duplicate: the ``pyproject.toml`` extra serves consumers installing the package, ``Pipfile`` serves this repository's own dev environment, and ``make isort`` reads only the latter. @@ -174,10 +114,10 @@ MAKEFILE = ROOT / 'Makefile' #: Whether :mod:`isort` is importable, for the one test that runs it. A visible -#: ``skipUnless`` flag rather than the inline ``skipTest`` this module used -#: until #766, so :func:`~tests._dependency_gates.gated_scopes` counts the gate -#: and :mod:`tests.test_tier_guard` can audit which CI jobs it darkens -- none -#: of them, now that the ``test`` extra carries isort; see this module's own +#: ``skipUnless`` flag rather than an inline ``skipTest`` (#766), so +#: :func:`~tests._dependency_gates.gated_scopes` counts the gate and +#: :mod:`tests.test_tier_guard` can audit which CI jobs it darkens -- none of +#: them, as the ``test`` extra carries isort; see this module's own #: docstring for why that install line was the right answer here and the wrong #: one for ``mypy``. HAS_ISORT = importlib.util.find_spec('isort') is not None @@ -209,7 +149,7 @@ #: * 9.0.1, 9.0.2 -- clean, on all four lines. #: #: So 9.0.1 is the first final release this test gets a verdict from. Anything -#: older skips, the 9.0.0 pre-releases included; 9.0.1 and newer run as before, +#: older skips, the 9.0.0 pre-releases included; 9.0.1 and newer run, #: so a later release whose verdict moves still turns the test red, as #: :file:`pyproject.toml` intends. ISORT_FLOOR = (9, 0, 1) @@ -273,7 +213,7 @@ def _find_mindepth_2_targets(sub: str) -> 'list[str]': into one alphabetical block, scattering the comments across it -- confirmed with ``isort --diff``, not assumed. That file stays reached by neither this call nor the whole-tree line (which skips every - ``__init__.py``), same as before #765. + ``__init__.py``). """ return sorted(str(path) for path in (PACKAGE / sub).glob('*/**/*.py')) @@ -371,8 +311,8 @@ def _isort_recipe_line_numbers() -> 'list[int]': #: removed without a matching entry here, nothing more. Flags and target #: expressions are plain literals in this list, never read back out of #: :file:`Makefile`, so a flag edited there (``-l100`` to ``-l120``, say) -#: is not something this file would notice at all -- pre-existing, and out -#: of scope for :func:`_isort_recipe_line_numbers` to fix. That asymmetry +#: is not something this file would notice at all, and out of scope for +#: :func:`_isort_recipe_line_numbers` to fix. That asymmetry #: is also what keeps a wrong line number cheap rather than dangerous: the #: number only labels which real Makefile line a subtest's failure message #: points at, and never decides which flags or targets the *test* actually diff --git a/tests/project/test_ktlo_jobs_gate_nothing_1738.py b/tests/project/test_ktlo_jobs_gate_nothing_1738.py index 7c063c2c72..b120995523 100644 --- a/tests/project/test_ktlo_jobs_gate_nothing_1738.py +++ b/tests/project/test_ktlo_jobs_gate_nothing_1738.py @@ -1,18 +1,19 @@ # -*- coding: utf-8 -*- """#1738: unit-tests.yml's KTLO legs are jobs of their own, which nothing gating waits on. -The owner's option A. Until it, the KTLO legs were legs of ``test``, -``integration`` and ``pypcap-parity``, kept from failing their job by step-level -``continue-on-error`` alone. A failure no step can contain -- a lost runner, the -job cap -- still failed the job, and so ``Required checks passed``, which reads one -``needs..result`` per job, and skipped ``unittest-ordering``, which needs -``test``. So they are ``test-ktlo``, ``integration-ktlo`` and ``pypcap-parity-ktlo`` -now, and this module pins the three things that make that hold: +The owner's option A. As legs of ``test``, ``integration`` and +``pypcap-parity``, kept from failing their job by step-level +``continue-on-error`` alone, a KTLO failure no step can contain -- a lost runner, +the job cap -- would still fail the job, and so ``Required checks passed``, which +reads one ``needs..result`` per job, and skip ``unittest-ordering``, which +needs ``test``. So they are ``test-ktlo``, ``integration-ktlo`` and +``pypcap-parity-ktlo``, and this module pins the three things that make that hold: * no job that can fail the run -- one without job-level ``continue-on-error`` -- ``needs:`` a KTLO job, directly or through another job; * none of those jobs holds a KTLO leg; -* every check name is what it was: rulesets match names literally. +* each leg's check name is its job's prefix and version, with ``(KTLO)`` on a + KTLO leg: rulesets match names literally. It also pins that each KTLO job runs its gating twin's steps, keeps the KTLO mechanics of #1672, and that :file:`util/ci_timing.py` tells the twins apart. @@ -125,7 +126,7 @@ def test_the_gating_jobs_keep_the_supported_legs(self) -> None: class TestTheCheckNamesAreUnchanged(unittest.TestCase): - """Each leg is named as it was when both tiers shared a job.""" + """Each leg is named `` ``, with ``(KTLO)`` after it on a KTLO job.""" def test_each_leg_keeps_its_name(self) -> None: for job_id, twin in TWINS.items(): @@ -146,7 +147,7 @@ def test_no_ktlo_name_is_a_required_context(self) -> None: class TestTheKTLOJobsKeepTheirMechanics(unittest.TestCase): - """#1633 and #1672 hold on each KTLO job as they did on the shared one.""" + """#1633 and #1672 hold on each KTLO job.""" def test_each_runs_its_twins_steps_on_ktlo_legs_only(self) -> None: for job_id, twin in TWINS.items(): diff --git a/tests/project/test_ktlo_legs_stay_green_1672.py b/tests/project/test_ktlo_legs_stay_green_1672.py index d301eb838a..b01ecc6ac1 100644 --- a/tests/project/test_ktlo_legs_stay_green_1672.py +++ b/tests/project/test_ktlo_legs_stay_green_1672.py @@ -2,12 +2,12 @@ """#1672: a failed KTLO leg concludes ``success`` and reports a warning. The owner's ruling: a KTLO failure must not turn ``main`` or a pull request red. -``Compat Python 3.7 (KTLO)`` carried job-level ``continue-on-error``, which keeps -the *run* from failing but still concludes the job's own check run ``failure`` -- -check run 115490362417, the one failing check of 4673cffa9 -- and the KTLO steps -of every other job wrote ``::error`` annotations. +Job-level ``continue-on-error`` is not enough: it keeps the *run* from failing +but still concludes the job's own check run ``failure`` -- check run +115490362417, ``Compat Python 3.7 (KTLO)``, the one failing check of 4673cffa9 -- +and a KTLO step's ``::error`` annotations are errors all the same. -Every job holding a KTLO leg now works one way: each step that can fail on the +So every job holding a KTLO leg works one way: each step that can fail on the leg carries ``continue-on-error: ${{ matrix.ktlo == true }}`` and an ``id``; a step that needs an earlier one waits on that step's ``outcome``; the last step, ``KTLO summary``, reports each failed ``outcome`` as a warning and a job-summary diff --git a/tests/project/test_lint_pages_reports_2030.py b/tests/project/test_lint_pages_reports_2030.py index 0f6349db25..2f66f35817 100644 --- a/tests/project/test_lint_pages_reports_2030.py +++ b/tests/project/test_lint_pages_reports_2030.py @@ -44,7 +44,8 @@ #: Each workflow's thread, and the report file the post step reads. TITLES = {LINT: ('lint: weekly lint report', 'lint-report.md'), PAGES: ('deploy-pages: documentation build report', 'pages-report.md')} -#: Every job's own ``permissions:``, and the top-level block, as before #2030. +#: Every job's own ``permissions:`` but ``discussion``'s, and the top-level block, +#: so that only ``discussion`` may write discussions (#2030). PERMISSIONS = { LINT: {None: {'contents': 'read'}, 'lint': None}, PAGES: {None: {'contents': 'read'}, 'head': {'contents': 'read'}, 'deploy-pages': None, diff --git a/tests/project/test_memory_error_count_1962.py b/tests/project/test_memory_error_count_1962.py index 2433bc035c..ac3d0c8a60 100644 --- a/tests/project/test_memory_error_count_1962.py +++ b/tests/project/test_memory_error_count_1962.py @@ -2,13 +2,13 @@ """#1962: ``Run unit tests`` counts the ``MemoryError`` raised, not a mention of one. The count behind ``Containment summary``'s ``tests raised MemoryError`` warning -was ``grep -cw MemoryError``, which also matched a test's docstring that a -traceback printed (``fail with :exc:`MemoryError```), so every GraalPy run that -timed out warned of a ``MemoryError`` none of its tests raised (job -118149195212). The step counts the lines that ``MemoryError`` heads, as an -exception's own line: at the line's start, or after pytest's ``E``, an -``INTERNALERROR>`` or an exception group's ``|``. The lines below are the -experimental logs' own, the raised one from job 116172356218 (#1753). +must not be a bare ``grep -cw MemoryError``: that also matches a test's +docstring that a traceback prints (``fail with :exc:`MemoryError```), and so +warned of a ``MemoryError`` none of a timed-out GraalPy run's tests raised (job +118149195212). Instead the step counts the lines that ``MemoryError`` heads, as an exception's own line: at +the line's start, or after pytest's ``E``, an ``INTERNALERROR>`` or an exception +group's ``|``. The lines below are the experimental logs' own, the raised one +from job 116172356218 (#1753). """ diff --git a/tests/project/test_module_isolation.py b/tests/project/test_module_isolation.py index a5134d6daf..feb2c7d525 100644 --- a/tests/project/test_module_isolation.py +++ b/tests/project/test_module_isolation.py @@ -30,20 +30,17 @@ ``pcapkit`` carrying only ``__path__``, so ``__all__`` entries resolve to nothing rather than raising. * :meth:`OrderIndependenceTests.test_the_cli_polluter_order_passes` is the third - leak, in ``tests/cli/test_main.py``. It used to pin the *structural* half of the - fix: that file rolled its own purge loop and did not import - :mod:`tests._support`, so nothing in that module could have fixed it and only - :func:`tests.conftest.restore_module_table` did, and it was deliberately left - that way as the standing witness that the guard covers a file which has not - opted into anything. That turned out to be the wrong trade. The guard healed the - leak on every run, which is exactly why it took an audit rather than a red test - to notice the leak was still there -- issue #688. The file now calls + leak, in ``tests/cli/test_main.py``. That file calls :func:`tests._support.isolate_modules` like its neighbours, so this case is a - control on the pairing rather than a witness for the guard, and the witness role - is left vacant on purpose: a test whose job is to leave :data:`sys.modules` - broken is a defect the suite would have to maintain deliberately, and the next - file written without the helpers is covered by the guard whether or not such a - test exists. + control on the pairing rather than a witness that the guard, + :func:`tests.conftest.restore_module_table`, covers a file which has not opted + into anything. The witness role is left vacant on purpose; keeping a file + unfixed to fill it is the wrong trade. The guard heals such a leak on every run, + which is exactly why it took an audit rather than a red test to notice this one + -- issue #688 -- and a test whose job is to leave :data:`sys.modules` broken is a + defect the suite would have to maintain deliberately, while the next file + written without the helpers is covered by the guard whether or not such a test + exists. The guard is also why the :program:`pytest` runs above cannot see a leak at all. What they assert is that the *guarded* suite is order-independent -- which it was @@ -99,8 +96,8 @@ #: interpreter's import system. __tier__ = 'ALL' -#: Selections that failed before #660 was fixed, each as (label, files). The -#: polluting file comes first in every one, because that is the order that broke. +#: Selections that fail without #660's fix, each as (label, files). The +#: polluting file comes first in every one, because that is the order that breaks. POLLUTING_ORDERS = [ ( 'the three files from the issue', @@ -233,7 +230,7 @@ def run_unittest(modules: 'list[str]') -> 'subprocess.CompletedProcess[str]': class OrderIndependenceTests(unittest.TestCase): - """Each selection that used to fail on collection order now passes.""" + """Each selection a module-table leak breaks by collection order passes.""" def assert_selection_passes(self, label: str, selection: 'list[str]') -> None: """Run ``selection`` and fail with its output if pytest did not exit 0. @@ -271,9 +268,9 @@ def test_the_cli_polluter_order_passes(self) -> None: def test_the_reverse_order_passes_too(self) -> None: """The control from the issue: the same files, victims first. - This one passed before the fix as well, and is kept because it is what - makes the pair meaningful -- a change that broke it would have traded one - order dependence for another rather than removing it. + This one passes with or without the fix, and is kept because it is what + makes the pair meaningful -- a change that broke it would trade one order + dependence for another rather than removing it. """ label, selection = POLLUTING_ORDERS[0] diff --git a/tests/project/test_no_moved_name_re_exports_1744_unit.py b/tests/project/test_no_moved_name_re_exports_1744_unit.py index b07c0ff4a9..754a765f08 100644 --- a/tests/project/test_no_moved_name_re_exports_1744_unit.py +++ b/tests/project/test_no_moved_name_re_exports_1744_unit.py @@ -44,7 +44,7 @@ #: Repository root. ROOT = pathlib.Path(__file__).resolve().parents[2] -#: Removed paths the old module no longer binds at all, as ``(old module, name)``. +#: Removed paths the old module does not bind at all, as ``(old module, name)``. PURE = ( ('pcapkit.protocols.misc.pcapng', 'TLSKeyLabel'), ('pcapkit.corekit.fields.field', 'NoValueType'), diff --git a/tests/project/test_no_version_directives_in_generated_tables_1884_unit.py b/tests/project/test_no_version_directives_in_generated_tables_1884_unit.py index eac19f92eb..1f744af10f 100644 --- a/tests/project/test_no_version_directives_in_generated_tables_1884_unit.py +++ b/tests/project/test_no_version_directives_in_generated_tables_1884_unit.py @@ -41,7 +41,7 @@ #: A version directive, as Sphinx spells it: reST allows any whitespace after the ``..`` (#1902). DIRECTIVE = re.compile(r'\.\.\s+version(?:added|changed|removed)::.*') -#: The crawler modules whose templates carried a directive before #1884. +#: The crawler modules #1884 names, whose templates must carry no directive. ISSUE_VENDORS = ( 'ftp/command.py', 'http/method.py', diff --git a/tests/project/test_pcapng_download_retry_1658_unit.py b/tests/project/test_pcapng_download_retry_1658_unit.py index 060d79414c..989f49c8a7 100644 --- a/tests/project/test_pcapng_download_retry_1658_unit.py +++ b/tests/project/test_pcapng_download_retry_1658_unit.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- """:file:`examples/generators/pcapng.py` retries a transient download failure. -GitHub issue #1658. ``_download`` made one attempt, so an HTTP 429 from the -Wireshark mirror sent a CI leg straight to the synthesised stand-in. An HTTP 429 -or 5xx, or a timeout, is now retried with backoff, honouring ``Retry-After``, -within a small budget; any other failure still falls back at once. +GitHub issue #1658. With one attempt, an HTTP 429 from the Wireshark mirror +sends a CI leg straight to the synthesised stand-in. So ``_download`` retries an +HTTP 429 or 5xx, or a timeout, with backoff, honouring ``Retry-After``, within a +small budget; any other failure falls back at once. #1894 moved the download from :mod:`urllib.request` to :mod:`requests`. :class:`DownloadRetryTests` runs against a stubbed ``requests.get`` and a fake @@ -49,7 +49,7 @@ NOW = 1_800_000_000.0 # The policy, pinned as literals so that the tests fail on behaviour, rather than -# on a missing name, against the generator as it was before #1658. +# on a missing name, against a generator without the retry (#1658). #: Attempts per download, the first included. ATTEMPTS = 3 #: Wait before the first retry, doubled for each one after. @@ -133,8 +133,8 @@ def setUpClass(cls) -> None: def setUp(self) -> None: self.sleeps = [] # type: list[float] clock = types.SimpleNamespace(time=lambda: NOW, sleep=self.sleeps.append) - # ``create``: the generator before #1658 had no ``time`` to bind over, - # and these tests should fail there on behaviour, not in setUp. + # ``create``: a generator without the retry (#1658) has no ``time`` to bind + # over, and these tests should fail there on behaviour, not in setUp. patcher = mock.patch.object(self.generator, 'time', clock, create=True) patcher.start() self.addCleanup(patcher.stop) diff --git a/tests/project/test_pip_install_1967.py b/tests/project/test_pip_install_1967.py index b607c11a08..a4f64e8cf5 100644 --- a/tests/project/test_pip_install_1967.py +++ b/tests/project/test_pip_install_1967.py @@ -6,9 +6,9 @@ and the arguments and levels it refuses. * :class:`TestTheFormerShellAgrees` runs the shell the steps held before the script against the same stand-in and requires the same output, log and status; - for the steps that had no annotation, the same except for the annotation the - script writes on a failed install; and for an indented ``ERROR:`` line, the same - except for the reason the shell missed (#2041). + for the steps whose shell wrote no annotation, the same except for the + annotation the script writes for a failed install; and for an indented + ``ERROR:`` line, the same except for the reason the shell missed (#2041). * :class:`TestTheStepsRunTheScript` pins each covered step to the script, with its guards and environment, and every other ``pip install`` in ``unit-tests.yml`` to a listed step that keeps its shell. @@ -364,8 +364,9 @@ def test_a_failed_upgrade_matches_on_both_forms(self) -> None: self.assertEqual(new, old) def test_the_bare_steps_gain_the_annotation(self) -> None: - """Formerly pip's status, its two streams apart and no annotation; now its streams merged - into standard output and the log, and on a failure status 1 and the annotation.""" + """The bare shell gives pip's status, its two streams apart and no annotation; the script + merges its streams into standard output and the log, and on a failure gives status 1 and + the annotation.""" for output, status in OUTCOMES: with self.subTest(output=output, status=status): (old, old_stderr), (new, new_stderr) = self.both(FORMER_BARE, 'error', install_output=output, diff --git a/tests/project/test_public_api.py b/tests/project/test_public_api.py index ebc388f15f..564c87214c 100644 --- a/tests/project/test_public_api.py +++ b/tests/project/test_public_api.py @@ -36,12 +36,12 @@ Two scoping decisions, both measured rather than assumed. **Nothing is excluded, :mod:`pcapkit.vendor` included.** The vendor crawlers -``import requests`` and ``import bs4`` at module scope, and the subtree used to be -skipped through :data:`EXCLUDED_ROOTS` on the grounds that those came only from -the ``vendor`` extra: this module is unit-tier -- see :mod:`tests._tiers` -- so it -has to pass on ``pip install -e '.[test]'``. Both were already in the ``test`` -extra, since #507 (:file:`pyproject.toml`), so the skip never bought anything. -Dropping it (#1772) brought in the vendor modules and packages, and found +``import requests`` and ``import bs4`` at module scope, which reads as a reason to +skip the subtree through :data:`EXCLUDED_ROOTS` if those came only from the +``vendor`` extra: this module is unit-tier -- see :mod:`tests._tiers` -- so it +has to pass on ``pip install -e '.[test]'``. Both are in the ``test`` extra too, +since #507 (:file:`pyproject.toml`), so such a skip buys nothing. Sweeping the +vendor modules and packages (#1772) found packages holding public attributes their :attr:`__all__` omitted: :mod:`pcapkit.vendor.ipx` leaked ``path``, the temporary of its ``sys.path`` save-and-restore, since renamed ``_path``; the attributes :mod:`pcapkit.vendor` diff --git a/tests/project/test_pyshark_encap_map.py b/tests/project/test_pyshark_encap_map.py index ff2f56e557..4fa9adb22d 100644 --- a/tests/project/test_pyshark_encap_map.py +++ b/tests/project/test_pyshark_encap_map.py @@ -157,8 +157,8 @@ def detect_tshark_version(tshark: 'str | None') -> 'str | None': class ParseParenthesisedNumberTests(unittest.TestCase): """:func:`~pyshark_encap_map.parse_parenthesised_number`'s own robustness. - **What this does not pin, stated plainly because it was previously - overclaimed here:** #851/#850's real defect -- rows lost, + **What this does not pin, stated plainly because it is easy to + overclaim:** #851/#850's real defect -- rows lost, including ``null`` -- was in how the original ad hoc script extracted the *name* portion of a PDML ``showname`` such as ``"Encapsulation type: NULL/Loopback (15)"``. This generator never does that extraction at all; diff --git a/tests/project/test_python_matrix_tiers_1633.py b/tests/project/test_python_matrix_tiers_1633.py index 08b4d89c71..c94ae1e999 100644 --- a/tests/project/test_python_matrix_tiers_1633.py +++ b/tests/project/test_python_matrix_tiers_1633.py @@ -134,9 +134,9 @@ def tier(impl: 'str', name: 'str') -> 'tuple[str, ...]': ('python-compatibility.yml', 'compatibility', 'Verify package'), ) -#: Claims the tier split made false, each worded as it was somewhere before #1633, -#: or while 3.7 was in the KTLO tier, before #2051. A denylist: it catches these -#: phrasings coming back and examines no others. +#: Claims the tiers make false (#1633), or the 3.8 floor does (#2051), each worded +#: as some file once had it. A denylist: it catches these phrasings coming back +#: and examines no others. RETIRED_CLAIMS = re.compile('|'.join(( r'3\.15 as an allowed-to-fail leg', r'3\.15 runs allowed-to-fail', @@ -806,8 +806,8 @@ class TestTheProseFollowsTheTiers(unittest.TestCase): def test_the_workflows_page_names_each_matrix_job_gate_only_skips(self) -> None: """The ``gate-only`` paragraph's matrix jobs, against ``unit-tests.yml``'s. - The paragraph counted each job's legs until #1811 (#1103's ruling: a count - drifts, and the tiers moved these). What it still claims is which matrix jobs + The paragraph states no leg counts (#1811; #1103's ruling: a count drifts, + and the tiers move these). What it claims is which matrix jobs a ``gate-only`` caller skips, so a matrix job added, dropped or no longer gated out fails here. """ diff --git a/tests/project/test_python_versions_update_1660.py b/tests/project/test_python_versions_update_1660.py index 6281e7a5eb..3ec1397e7d 100644 --- a/tests/project/test_python_versions_update_1660.py +++ b/tests/project/test_python_versions_update_1660.py @@ -711,8 +711,8 @@ def test_no_file_under_workflows_is_written_or_restates_a_tier(self) -> None: """An App token needs ``workflows: write`` to push a workflow change, and ``pr``'s has none. So the script writes no workflow, :func:`publish` refuses one, and the - workflow comments that restated the tiers before Part B now name them; - a restatement coming back would go stale on the first tier move. + workflow comments name the tiers rather than restate them; a + restatement would go stale on the first tier move. """ module = updater() self.assertFalse([path for path in module.TARGETS if path.startswith('.github/workflows/')]) diff --git a/tests/project/test_regenerate_samples_1967.py b/tests/project/test_regenerate_samples_1967.py index 3b0b98a6a2..21783aec7f 100644 --- a/tests/project/test_regenerate_samples_1967.py +++ b/tests/project/test_regenerate_samples_1967.py @@ -7,8 +7,8 @@ * :class:`TestTheFormerShellAgrees` runs the shell the steps held before the script against the same stand-ins and requires the same output, log and status. * :class:`TestTheStepsRunTheScript` pins the step in the ``integration``, - ``pypcap-parity`` and ``gate`` jobs to the script, and keeps the old inline - shell out of every workflow. + ``pypcap-parity`` and ``gate`` jobs to the script, and keeps an inline form of + it out of every workflow. """ @@ -32,7 +32,7 @@ #: The jobs whose step runs the script, and the level each takes from where. LEVELLED_JOBS = ('integration', 'pypcap-parity') GATE = 'gate' -#: Text only the old inline step held, which no workflow may hold. +#: Text only an inline form of the step holds, which no workflow may hold. INLINE = ('make_samples.py', 'make-samples.log', '(download unavailable)', 'Sample fixture generation failed') FAILED = ('title=Sample fixture generation failed::examples/generators/make_samples.py could not rebuild ' diff --git a/tests/project/test_release_concurrency_1705.py b/tests/project/test_release_concurrency_1705.py index fe6ede45db..5fa50d5026 100644 --- a/tests/project/test_release_concurrency_1705.py +++ b/tests/project/test_release_concurrency_1705.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- """#1705: a Create Release run that cannot release must not displace one that can. -Create Release serialises on one workflow-level group per ref, and a group holds -one pending run, cancelling it when another joins. Every Vendor Update completion -raises a run, whatever its conclusion, and the one ``version_check`` skips used -to join ``main``'s group too. On 2026-10-14, read off the runs' timestamps (each -Create Release run was created 2-3 s after a Vendor Update run completed): +Create Release serialises on a workflow-level concurrency group, and a group +holds one pending run, cancelling it when another joins. Every Vendor Update +completion raises a run, whatever its conclusion, and with a group keyed on the +ref alone the one ``version_check`` skips joins ``main``'s group too. So it went +on 2026-10-14, read off the runs' timestamps (each Create Release run was created +2-3 s after a Vendor Update run completed): * 38497299656, from Vendor Update 38497182777 (``success``), ran until 02:12:48Z; * 38499150441, from 38498670543 (``success``), was pending from 02:04:54Z; @@ -14,7 +15,7 @@ * 38499303910, from 38499299431 (``skipped``), cancelled that in turn, and was the run that eventually ran. -So the group is now keyed on the ref only for a run ``version_check`` can run; +So the group is keyed on the ref only for a run ``version_check`` can run; any other run is keyed on its own ``github.run_id``. These tests evaluate the group with the evaluator :mod:`tests.project.test_release_gates` pins. @@ -128,7 +129,7 @@ def test_a_vendor_update_that_updated_nothing_still_joins_mains_group(self) -> N """#1748: the group reads the event only, so it cannot see ``vendor_update``. Vendor Update 38650191283 concluded ``success`` with ``vendor-update`` - skipped. Its run now skips ``version_check`` but still waits in, and can + skipped. Its run skips ``version_check`` but still waits in, and can still displace a run pending in, ``main``'s group -- the one case where the two differ, recorded so that a change to either is a decision. The workflow's ``vendor_update`` comment gives what that costs. diff --git a/tests/project/test_release_gates.py b/tests/project/test_release_gates.py index c512b06293..60a6a5c157 100644 --- a/tests/project/test_release_gates.py +++ b/tests/project/test_release_gates.py @@ -60,18 +60,18 @@ :file:`tests/project/test_bump_version.py`: the assertion that must run everywhere does not need the dependency, and the one that needs it guards the substitute. -#888: every job past ``version_check`` used to share one guard -- "the ``v*`` -tag exists" -- as a proxy for "this version was already published", which -diverges exactly when a release half-completes and made every downstream job -skip silently while the run reported green. :class:`TestEvidenceBasedGating` -below asserts the fix: ``tag``, ``pypi`` and ``conda`` each read their own -evidence instead of that shared tag, and -- the part that is easy to get +#888: one guard shared by every job past ``version_check`` -- "the ``v*`` tag +exists" -- as a proxy for "this version was already published" diverges exactly +when a release half-completes, and makes every downstream job skip silently +while the run reports green. :class:`TestEvidenceBasedGating` below guards +against that: ``tag``, ``pypi`` and ``conda`` each read their own evidence +instead of a shared tag, and -- the part that is easy to get subtly wrong -- their ``if:`` conditions bypass GitHub's default status check explicitly (``!cancelled()``) rather than relying on evidence alone, because a job whose ``needs:`` includes a legitimately-skipped predecessor is skipped before its own ``if:`` is even evaluated otherwise. :func:`declared_if` is what makes an ``if:`` condition -- including the multi-line, folded-scalar -form this fix introduces -- checkable the same coarse, textual way the rest of +form these conditions take -- checkable the same coarse, textual way the rest of this module already checks ``environment:`` and ``needs:``. A cross-review of the #888 change (PR #905) found two defects that every test @@ -102,7 +102,7 @@ interpreter is 3.14 itself, which is exactly how both defects shipped despite every earlier test in this module passing. -#962: the same argument, one layer up, now against the ``if:`` expressions +#962: the same argument, one layer up, applies to the ``if:`` expressions themselves. Review rounds on #961 produced mutation after mutation of a single clause, and each strengthening of the text assertions was defeated by one the previous round had not anticipated -- because vocabulary is not structure and @@ -199,13 +199,13 @@ :class:`TestCondaPushReportsWhatLanded` runs that step's script against a local origin to show the verdict is the right one. -And making the chain reachable opened a third (#1643's review): Push Changes -rebased onto whatever ``main`` held by then and Conda Tag tagged ``main``'s tip -after a ``git pull``, so a commit landing during the run could be tagged and -uploaded with no ``Unit Tests`` verdict of its own. Push Changes now pushes only -when what its rebase brings in is covered by :file:`util/verdict_covers_checkout.py`'s -rule, and Conda Tag tags the ``CONDA_COMMIT`` it pushed; -:class:`TestCondaTagTagsWhatWasPushed` runs Conda Tag's own steps after it. +A reachable chain has a third hazard (#1643's review): a Push Changes that +rebases onto whatever ``main`` holds by then, and a Conda Tag that tags +``main``'s tip after a ``git pull``, let a commit landing during the run be +tagged and uploaded with no ``Unit Tests`` verdict of its own. So Push Changes +pushes only when what its rebase brings in is covered by +:file:`util/verdict_covers_checkout.py`'s rule, and Conda Tag tags the +``CONDA_COMMIT`` it pushed; :class:`TestCondaTagTagsWhatWasPushed` runs Conda Tag's own steps after it. Since #1967 the ``unit-tests`` gate and the ``verdict`` job are the jobs of :file:`cron-head.yml`, which ``cron-conda.yml`` calls as its ``head`` job. The @@ -1077,8 +1077,8 @@ def evaluate_condition(condition: 'str', context: 'dict[str, object]') -> 'bool' #: The direct dependencies every downstream gate holds to ``== 'success'`` #: alone, with no ``|| 'skipped'``: ``version_check`` because it produces the #: evidence they read, and -- #1052 -- ``unit-tests`` because it is the -#: release test gate, which since that issue skips whenever nothing is -#: publishable and so no longer gates by sitting first in the chain. A +#: release test gate, which skips whenever nothing is publishable and so +#: does not gate by sitting first in the chain. A #: skipped gate admitted here would publish untested. STRICT_PREDECESSORS = ('version_check', 'unit-tests') @@ -1800,7 +1800,7 @@ def conda_context(unit_tests: 'str' = 'skipped', #: while its result is handed downstream as ``success``), and then it passes or #: fails for reasons unrelated to the gate under test. CONDA_ROWS = [ - # The issue itself. Before the fix, `conda-tag` and `conda-dist` are + # The issue itself. Without `!cancelled()`, `conda-tag` and `conda-dist` are # cascade-skipped here while their own conditions are true. ('run 38325136710: workflow_run, the gate skipped, a bump committed', conda_context(), @@ -2103,14 +2103,14 @@ def gate_coverage_violations(gate: 'str', jobs: 'dict[str, str]') -> 'list[str]' class TestNothingPublishesWithoutTheGate(WorkflowMixin, unittest.TestCase): - """#1052: the release test gate now skips when nothing is publishable, and + """#1052: the release test gate skips when nothing is publishable, and no release may skip it. - Until #1052 ``unit-tests`` was the root of the graph, so every job reached - it through ``version_check``'s implicit ``success()`` and no publisher had - to name it. It now runs *after* ``version_check`` and only when a release - will happen, so a ``skipped`` gate is routine and position no longer gates - anything. Two independent halves keep "nothing publishes without a passing + A gate at the root of the graph would reach every job through + ``version_check``'s implicit ``success()``, so no publisher would have to + name it. ``unit-tests`` runs *after* ``version_check`` and only when a + release will happen, so a ``skipped`` gate is routine and position gates + nothing. Two independent halves keep "nothing publishes without a passing gate" true, and each is asserted here on its own, so that losing either is a failure even while the other still holds: @@ -2192,11 +2192,12 @@ def test_a_version_tagged_elsewhere_releases_nothing_off_a_v_tag(self) -> None: """#1748: the gate and every publisher skip for ``v1.5.0b12``'s shape. Its tag names ``2025da5e5`` and Anaconda holds 1 of its 10 files, so - ``conda`` used to run on every later commit -- after the gate -- and fail - #1634's check on each leg. ``github`` skipped there already, the tag - existing; its copy of the clause is for a tag pushed between - ``check_tag`` and ``owner``, which :meth:`test_the_gate_runs_whenever_any_publisher_would` - finds without it. A ``v*`` push of it still reaches that check. + without a ``PCAPKIT_RELEASED_ELSEWHERE`` clause ``conda`` would run on + every later commit -- after the gate -- and fail #1634's check on each + leg. ``github`` skips there regardless, the tag existing; its copy of the + clause is for a tag pushed between ``check_tag`` and ``owner``, which + :meth:`test_the_gate_runs_whenever_any_publisher_would` finds without it. + A ``v*`` push of it still reaches that check. """ shape = dict(released_elsewhere='true', @@ -2235,8 +2236,8 @@ def test_dropping_any_gate_clause_is_caught(self) -> None: class TestEvidenceBasedGating(WorkflowMixin, unittest.TestCase): - """#888: ``tag``, ``pypi`` and ``conda`` read their own evidence now, - instead of ``PCAPKIT_TAG_EXISTS`` -- the tag ``github`` creates, which + """#888: ``tag``, ``pypi`` and ``conda`` read their own evidence, + not ``PCAPKIT_TAG_EXISTS`` -- the tag ``github`` creates, which answers "was this tagged" and not "was this published", and those diverge exactly when a release half-completes. @@ -2399,12 +2400,11 @@ def test_version_check_must_strictly_succeed(self) -> None: def test_the_release_test_gate_must_strictly_succeed(self) -> None: """#1052: the same bar, for ``unit-tests``. - Before #1052 the gate sat first in the chain and ``version_check``'s - implicit ``success()`` carried it to every publisher, so no publisher - named it. Now ``version_check`` runs first and the gate skips whenever - nothing is publishable, so a ``skipped`` gate is routine -- and these - three open with ``!cancelled()``, which opts them out of the implicit - check that would otherwise have stopped them. Hence an explicit + ``version_check`` runs first and the gate skips whenever nothing is + publishable, so a ``skipped`` gate is routine and its place in the chain + carries it to no publisher -- and these three open with + ``!cancelled()``, which opts them out of the implicit check that would + otherwise stop them. Hence an explicit top-level ``needs.unit-tests.result == 'success'`` conjunct, referenced exactly once, for the reasons the test above gives. @@ -3400,7 +3400,7 @@ def _run(self, overrides: 'dict[str, str]') -> 'subprocess.CompletedProcess[str] 0, ), ( - # #1052: the gate now runs after version_check, so its failure + # #1052: the gate runs after version_check, so its failure # is reported with the evidence already in hand. 'release_test_gate_failed', { @@ -3884,8 +3884,8 @@ class TestCondaBumpIsTaggedAndUploaded(CondaWorkflowMixin, unittest.TestCase): #: Stripped from each publishing job's condition to recover the shape #1632 #: found. A removal from the real text rather than a hand-written copy of - #: the old condition, so it cannot drift into testing something the file no - #: longer says. + #: that shape, so it cannot drift into testing something the file does not + #: say. PRE_FIX_REMOVAL = '!cancelled() && ' def _pre_fix_jobs(self) -> 'dict[str, str]': @@ -4607,7 +4607,7 @@ def test_a_rebase_that_fetches_again_pushes_the_merge_under_the_bump(self) -> No def test_a_push_the_remote_declines_fails_the_step(self) -> None: """A refusal with ``main`` where it was fetched -- a hook here, a ruleset on GitHub -- is no race, and must not pass as one: the step fails, as - every push failure did before #1649. + for any push failure that is not a race (#1649). """ clone = self._clone('first') diff --git a/tests/project/test_release_reads_the_vendor_update_1748.py b/tests/project/test_release_reads_the_vendor_update_1748.py index e210e0201f..f13dccb702 100644 --- a/tests/project/test_release_reads_the_vendor_update_1748.py +++ b/tests/project/test_release_reads_the_vendor_update_1748.py @@ -106,7 +106,7 @@ def test_version_check_and_release_status_read_it(self) -> None: self.assertEqual(report['env'].get('PCAPKIT_VENDOR_UPDATED'), '${{ needs.vendor_update.outputs.updated }}') def test_only_a_skipped_vendor_update_job_stops_version_check(self) -> None: - """Run 38650191283's ``'false'`` skips it; an unread ``null`` runs it, as before #1748.""" + """Run 38650191283's ``'false'`` skips it; an unread ``null`` runs it, as with no read at all.""" condition = declared_if(self.jobs['version_check']) for updated, expected in (('false', False), ('true', True), (NULL, True)): with self.subTest(updated=updated): @@ -157,7 +157,7 @@ def test_run_38650782750_updated(self) -> None: self.assertEqual(self.run_step(COVERED)[1], 'updated=true\n') def test_what_it_cannot_read_leaves_updated_unset_and_warns(self) -> None: - """Unset runs ``version_check`` as before #1748: it can cost a run, never a release.""" + """Unset runs ``version_check``, as with no read at all: it can cost a run, never a release.""" cases = { 'the API refused': (COVERED, True), 'no vendor-update job': ({'total_count': 0, 'jobs': []}, False), diff --git a/tests/project/test_requires_python_floor_2051.py b/tests/project/test_requires_python_floor_2051.py index a5491a9c36..29daaa51a6 100644 --- a/tests/project/test_requires_python_floor_2051.py +++ b/tests/project/test_requires_python_floor_2051.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- """#2051: 1.5.0 requires Python 3.8, and nothing converts the sources. -The owner's ruling on #2040 raised ``requires-python`` to ``>=3.8`` and dropped the +The owner ruled on #2040 for a ``requires-python`` of ``>=3.8`` and none of the ``bpc-*`` backport tools, which are no longer maintained. Pinned here: * ``requires-python`` is floored at 3.8, and at nothing else; * no requirement in :file:`pyproject.toml` names a ``bpc-`` distribution; * no requirement carries a ``python_version`` marker that reads the same on every - version from the floor up, as ``scapy``'s ``>= '3.7'`` did; + version from the floor up, such as ``>= '3.7'`` on ``scapy``; * :file:`vermin.ini` targets the floor. """ diff --git a/tests/project/test_rfc_anchor_fragments.py b/tests/project/test_rfc_anchor_fragments.py index 7d0bf77a4c..51ba8bf354 100644 --- a/tests/project/test_rfc_anchor_fragments.py +++ b/tests/project/test_rfc_anchor_fragments.py @@ -31,23 +31,17 @@ same one-character fix -- fixed alongside #942's rather than carried as a documented exception. -A first version of this test accepted a *third* fragment shape, a bare -``N[.N...]`` with no ``section-`` word, to match ten fragments already in the -tree: nine in :file:`pcapkit/const/tcp/mp_tcp_option.py` -(``:rfc:`8684#3.1``` through ``#3.7```) and one in -:file:`pcapkit/const/reg/apptype/tcp.py` (``:rfc:`8765#6.1```). Those anchors -do not exist either -- RFC 8684's real HTML uses ``id="section-3.1"``, never -``id="3.1"`` -- so accepting that shape blessed exactly the defect class this -test exists to catch, the same mistake as the allowlist above one layer down. -The generators that produce them, -``MPTCPOption.process`` in :file:`pcapkit/vendor/tcp/mp_tcp_option.py` and -``AppType.records`` in :file:`pcapkit/vendor/reg/apptype/apptype.py` (both parsing IANA's +A bare ``N[.N...]`` fragment with no ``section-`` word is not accepted either, +as in ``:rfc:`8684#3.1``` or ``:rfc:`8765#6.1```. Such anchors do not exist -- +RFC 8684's real HTML uses ``id="section-3.1"``, never ``id="3.1"`` -- so +accepting that shape would bless exactly the defect class this test exists to +catch. The generators behind :mod:`pcapkit.const.tcp.mp_tcp_option` and +:mod:`pcapkit.const.reg.apptype.tcp`, ``MPTCPOption.process`` in +:file:`pcapkit/vendor/tcp/mp_tcp_option.py` and ``AppType.records`` in +:file:`pcapkit/vendor/reg/apptype/apptype.py`, both parse IANA's ``RFC8684, Section 3.1`` with the same ``re.fullmatch(r'RFC(?P\\d+)(, Section (?P.*?))?', ...)``, which discards the literal word ``Section`` and -keeps only the number), are fixed to emit ``#section-{sec}``, and the ten -already-generated lines are fixed to match -- exactly what a regeneration -would produce, verified by reading the generators rather than by running -them. The third shape is dropped from :data:`ACCEPTED_FRAGMENT` entirely. +keeps only the number, so each emits ``#section-{sec}`` explicitly. :data:`ACCEPTED_FRAGMENT`'s set is not a survey of shapes this tree happens to use -- it is Sphinx's own. ``sphinx.roles._format_rfc_target`` titles @@ -336,7 +330,7 @@ def test_no_malformed_fragments(self) -> 'None': ) def test_the_documentation_exemption_holds_on_every_known_mechanism(self) -> 'None': - """:func:`_is_documentation` on each input that defeated the old masker. + """:func:`_is_documentation` on each input known to defeat a naive masker. This is the regression suite for several rounds of review, and it exists because each of those rounds fixed the mechanism the previous postmortem had named and diff --git a/tests/project/test_rfc_constants_one_home_1739_unit.py b/tests/project/test_rfc_constants_one_home_1739_unit.py index 48ae3302b3..aba0f05ad1 100644 --- a/tests/project/test_rfc_constants_one_home_1739_unit.py +++ b/tests/project/test_rfc_constants_one_home_1739_unit.py @@ -15,8 +15,8 @@ name, at module or class level, and none spells the distinctive literals (the PCAP and PCAP-NG magic numbers and the PCAP timestamp resolutions) itself; * :class:`TestReaders` -- each reader imports the constant from its home and - reads it, and :mod:`pcapkit.toolkit.pypcapfile`, which defined several of them, - no longer binds those names at all; + reads it, and :mod:`pcapkit.toolkit.pypcapfile`, a reader of several, binds + none of those names at all; * :class:`TestNotReinlined` -- no module in :data:`SWEPT`, the homes included, spells a homed value anywhere but in the home's own definition: not as an operand, a call argument, a subscript, a default, a returned value, a tuple @@ -77,8 +77,8 @@ }), ) -#: Names that used to be defined elsewhere and now must not be, mapped to the -#: module that held them. ``MAGIC_NUMBER`` stays a class attribute of the PCAP +#: Names a module other than their home must not define, mapped to the module +#: that once held them (#1739). ``MAGIC_NUMBER`` stays a class attribute of the PCAP #: and PCAP-NG engines -- the extractor dispatches on it -- but is built from #: the home rather than spelled out, which :class:`TestOneDefinition` checks #: through the literals. @@ -187,8 +187,8 @@ #: Readers that reach a home as a module attribute, ``Module.NAME``, rather than #: importing the name: the module, the home's module path and the name. -#: :mod:`pcapkit.toolkit.pypcapfile` does so because binding the four names it -#: used to define would re-export them (see :meth:`TestReaders.test_pypcapfile_binds_none`). +#: :mod:`pcapkit.toolkit.pypcapfile` does so because binding the homed names it +#: reads would re-export them (see :meth:`TestReaders.test_pypcapfile_binds_none`). ATTRIBUTE_READERS = ( ('pcapkit/toolkit/pypcapfile.py', 'pcapkit.protocols.schema.internet.ipv4', 'IPV4_HEADER_LEN'), ('pcapkit/toolkit/pypcapfile.py', 'pcapkit.protocols.internet.ipv6', 'IPV6_HEADER_LEN'), diff --git a/tests/project/test_sentinels_doc_page_934_unit.py b/tests/project/test_sentinels_doc_page_934_unit.py index e7f7256692..cad8be98ba 100644 --- a/tests/project/test_sentinels_doc_page_934_unit.py +++ b/tests/project/test_sentinels_doc_page_934_unit.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """Pins the API page for :mod:`pcapkit.corekit.sentinels` existing and reachable. -GitHub issue #934: what is now -:file:`docs/source/contributing/conventions/sentinel-convention.rst` (GitHub -issue #918 split it out of the single-page :file:`conventions.rst` that carried -it at the time) cross-references :mod:`pcapkit.corekit.sentinels` repeatedly -(the ``:mod:`` role), and none of those references used to resolve, because -no page under :file:`docs/source/` documented that module -- confirmed by an -explicit nitpicky ``sphinx-build`` before this page existed, and again after, -to confirm the fix. +GitHub issue #934: +:file:`docs/source/contributing/conventions/sentinel-convention.rst` +cross-references :mod:`pcapkit.corekit.sentinels` repeatedly (the ``:mod:`` +role), and those references resolve only because this page documents the +module -- without it no page under :file:`docs/source/` does and none of them +resolves, as an explicit nitpicky ``sphinx-build`` with and without the page +confirmed. This does **not** re-test Sphinx's cross-reference resolution itself -- as :file:`tests/project/test_documentation_claims.py` explains at length, whether a @@ -30,7 +29,7 @@ ROOT = pathlib.Path(__file__).resolve().parents[2] -#: The page GitHub issue #934 added. +#: The module's API page (GitHub issue #934). PAGE = ROOT / 'docs' / 'source' / 'pcapkit' / 'corekit' / 'sentinels.rst' #: The ``corekit`` API index, whose toctree has to list the page above. diff --git a/tests/project/test_superseded_containment_summary_2077.py b/tests/project/test_superseded_containment_summary_2077.py index 3bb77ecbe6..32ba83fe6b 100644 --- a/tests/project/test_superseded_containment_summary_2077.py +++ b/tests/project/test_superseded_containment_summary_2077.py @@ -4,8 +4,8 @@ In Experimental Tests run 39898029852, job 120233865230, util/superseded.py's ``watch`` stopped pytest 61 s in, ``main`` having moved on, and exited 0 as it does for every stop. ``Containment summary`` read that status and closed on -``tests exited 0 after 61 s``, which reads as a pass. #2076 has the watcher -write ``stopped=true`` to ``Run unit tests``'s outputs, so the summary +``tests exited 0 after 61 s``, which reads as a pass. The watcher writes +``stopped=true`` to ``Run unit tests``'s outputs (#2076), so the summary reads it and says the tests were stopped as superseded. It adds no warning for the stop, as the watcher's ``Superseded`` notice already says why, and keeps every warning it gives for a real cause. diff --git a/tests/project/test_unit_tests_push_reports_2030.py b/tests/project/test_unit_tests_push_reports_2030.py index aa085ef196..203702649b 100644 --- a/tests/project/test_unit_tests_push_reports_2030.py +++ b/tests/project/test_unit_tests_push_reports_2030.py @@ -69,7 +69,8 @@ SHA = 'da8596414e3e6a53f43b20b85c86755cc2ed6686' SUITE = 'CS_kwDOBotKYc8AAAAZD_cS-g' POSTER = {'actions': 'read', 'checks': 'read', 'contents': 'read', 'discussions': 'write'} -#: The pull-request comment's condition and scopes, as before #2030. +#: The pull-request comment's condition and scopes, which the ``discussion`` job +#: leaves alone (#2030). COMMENT_IF = ("${{ github.event.workflow_run.event == 'pull_request' && " "github.event.workflow_run.conclusion != 'cancelled' }}") COMMENT_SCOPES = {'actions': 'read', 'pull-requests': 'write'} @@ -141,8 +142,8 @@ def nodes(*runs: 'tuple[str, str, list[tuple[str, ...]]]') -> 'list[dict[str, An #: An engine's KTLO cell that failed, in a blocking job (#1672). ENGINE_KTLO = ('Engines (Scapy)', 'SUCCESS', [('WARNING', 'Scapy failed on Python 3.9 (KTLO, non-blocking)'), ('FAILURE', '')]) -#: The list as it stood for pypy3.10's install failure, before #2040, which the -#: tests of the matching itself pass in place of a workflow's empty one. +#: A list that holds pypy3.10's install failure as by design, which the tests of +#: the matching itself pass in place of a workflow's empty one (#2040). LISTED = {'Python pypy3.10 (KTLO)': (('WARNING', 'install failed on Python pypy3.10 (KTLO, non-blocking)'), ('FAILURE', ''), ('WARNING', 'pcapkit did not install on Python pypy3.10 (KTLO, non-blocking)'))} @@ -609,7 +610,7 @@ def test_the_by_design_matching(self) -> None: self.assertEqual(found.warned, [f'`{job}`: `{title}`']) def test_with_nothing_listed_the_old_install_failures_warn(self) -> None: - """Run 39718608950's pypy3.10 install failure is no longer by design (#2040).""" + """Run 39718608950's pypy3.10 install failure warns: nothing lists it as by design (#2040).""" outputs, report, _ = self.report('success', check_runs(PYPY, GREEN)) self.assertEqual(outputs['status'], 'warning') self.assertIn('- `Python pypy3.10 (KTLO)`: `install failed on Python pypy3.10 (KTLO, non-blocking)`', report) diff --git a/tests/project/test_vendor_comment_only_no_bump_1674.py b/tests/project/test_vendor_comment_only_no_bump_1674.py index d77cf18378..1ab3bcf1d3 100644 --- a/tests/project/test_vendor_comment_only_no_bump_1674.py +++ b/tests/project/test_vendor_comment_only_no_bump_1674.py @@ -154,7 +154,7 @@ def edited(edits: 'list[tuple[str, str]]') -> 'str': def _load_script() -> 'Optional[types.ModuleType]': - """:file:`util/vendor_code_changed.py` as a module, or :data:`None` before #1674.""" + """:file:`util/vendor_code_changed.py` as a module, or :data:`None` on a tree without it (#1674).""" if not SCRIPT.exists(): return None spec = importlib.util.spec_from_file_location('vendor_code_changed', SCRIPT) @@ -319,7 +319,7 @@ def test_the_changed_files_output_is_read_from_the_environment(self) -> None: self.assertIn('::notice title=Comments and docstrings only::none of the 2 changed', result.stdout) def test_no_paths_is_an_error_and_writes_nothing(self) -> None: - """The step then fails, and the workflow bumps as it did before #1674.""" + """The step then fails, and the workflow bumps, as it does without the classifier (#1674).""" for changed_files in (None, '', ' \n'): with self.subTest(changed_files=changed_files): result, written = self.run_script(changed_files=changed_files) diff --git a/tests/project/test_vendor_test_gate_1853.py b/tests/project/test_vendor_test_gate_1853.py index fe90f7804f..4bde037383 100644 --- a/tests/project/test_vendor_test_gate_1853.py +++ b/tests/project/test_vendor_test_gate_1853.py @@ -509,8 +509,8 @@ def process_iter() -> 'list[_Process]': NoSuchProcess=_NoSuchProcess, AccessDenied=_AccessDenied) def rss(self, psutil: 'Any') -> 'Optional[int]': - # ``create``: before #1894 the script had no ``psutil`` to bind over, and - # this should fail there on what it returns. + # ``create``: a script that does not use ``psutil`` (#1894) has none to bind + # over, and this should fail there on what it returns. with mock.patch.object(self.gate, 'psutil', psutil, create=True): return self.gate.group_rss(os.getpgrp()) diff --git a/tests/project/test_workflow_hardening_1968.py b/tests/project/test_workflow_hardening_1968.py index d9e8703cb9..be4079e658 100644 --- a/tests/project/test_workflow_hardening_1968.py +++ b/tests/project/test_workflow_hardening_1968.py @@ -365,7 +365,7 @@ def test_a_rerun_of_the_published_commit_publishes(self) -> None: self.assertNotIn('compare/', calls) def test_what_it_cannot_read_publishes_as_before(self) -> None: - """A head this workflow did not write, or an API error: the old behaviour.""" + """A head this workflow did not write, or an API error: it publishes, as with no check at all.""" default = f'Deploying to gh-pages from @ JarryShaw/PyPCAPKit@{THEIRS} \U0001f680' self.assertEqual(self.run_check(default, 'behind')[0], {'publish': 'true'}) self.assertEqual(self.run_check(self.ours_message, 'behind', STUB_COMMITS_FAIL='1')[0], {'publish': 'true'})