Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,15 @@ jobs:
# by a root-level module survives. The modules directly in tests/protocols
# stay in the rest cell, as moving them would split more pairs than it joins.
#
# #2254: the project cell's run step ran near its step cap, so tests/project,
# which has no subdirectories, is cut at a module name: `--half 1` and
# `--half 2`, at the one boundary SHARD_BOUNDARIES in util/run_unittest_leg.py
# holds, which `project-tests` cuts at too, so every module, a new one
# included, runs in exactly one half. The boundary is chosen so the halves
# take about the same CI time; both also run the root-level modules. Move
# SHARD_BOUNDARIES to rebalance. The same cost as #1029's: a pair split
# between the two cells does not share a process.
#
# Every tests/ subdirectory is a leg except those below, and
# tests/project/test_unittest_ordering_shards.py fails on a new one left out:
#
Expand All @@ -1140,7 +1149,7 @@ jobs:
# files, so the leg would run the root-level modules alone -- green,
# while covering none of it.
#
# The `project` cell keeps the project pins (#1954): whether a module passes
# The `project` cells keep the project pins (#1954): whether a module passes
# in one interpreter, in file order, with no xdist, is a question about the
# module, so a pin can fail it as readily as any other.
#
Expand Down Expand Up @@ -1170,7 +1179,6 @@ jobs:
- dumpkit
- foundation
- interface
- project
- protocols/internet
- toolkit
- utilities
Expand All @@ -1180,6 +1188,13 @@ jobs:
- leg: protocols
exclude: --exclude protocols/internet
label: protocols (rest)
# tests/project, cut at one module name -- see the #2254 note above.
- leg: project
shard: --half 1
label: project (half 1)
- leg: project
shard: --half 2
label: project (half 2)

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -1213,7 +1228,7 @@ jobs:
# under the job's, so a hang reports as this step timing out.
- name: Run tests/${{ matrix.leg }} and the root-level modules under plain unittest
timeout-minutes: 20
run: python util/cap_file_size.py python util/run_unittest_leg.py ${{ matrix.leg }} ${{ matrix.exclude }} --verbose
run: python util/cap_file_size.py python util/run_unittest_leg.py ${{ matrix.leg }} ${{ matrix.exclude }} ${{ matrix.shard }} --verbose

# ``CHANGELOG.md`` is generated from the newest entry under
# ``docs/source/changelog/`` by ``util/changelog_md.py``, and ``Create
Expand Down Expand Up @@ -1355,13 +1370,22 @@ jobs:
--durations=30 --durations-min=1.0 --junitxml="$RUNNER_TEMP/junit.xml" "${pins[@]}"

# #2164: the script's default stall dump sits under `unittest-ordering`'s
# 20-minute step cap, so this step passes its own, a minute under its
# 20-minute step cap, so these steps pass their own, a minute under their
# 15. tests/project/test_run_unittest_leg_stall_dump.py pins every step
# running the script to a dump inside its cap.
- name: Run tests/project and the root-level modules under plain unittest
#
# #2254: the whole leg ran near this cap, so it runs as
# `unittest-ordering`'s two `project` halves do, one step each, at the
# same boundary.
- name: Run the first half of tests/project and the root-level modules under plain unittest
if: ${{ needs.changes.outputs.code == 'false' }}
timeout-minutes: 15
run: python util/cap_file_size.py python util/run_unittest_leg.py project --half 1 --verbose --stall-dump 840

- name: Run the second half of tests/project and the root-level modules under plain unittest
if: ${{ needs.changes.outputs.code == 'false' }}
timeout-minutes: 15
run: python util/cap_file_size.py python util/run_unittest_leg.py project --verbose --stall-dump 840
run: python util/cap_file_size.py python util/run_unittest_leg.py project --half 2 --verbose --stall-dump 840

- name: Run the other tests that read docs/ or Markdown
if: ${{ needs.changes.outputs.code == 'false' }}
Expand Down
2 changes: 2 additions & 0 deletions docs/source/contributing/conventions/test-coverage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ Running the Tests

python util/run_unittest_leg.py protocols/internet
python util/run_unittest_leg.py protocols --exclude protocols/internet
python util/run_unittest_leg.py project --half 1
python util/run_unittest_leg.py project --half 2

The leg list lives in the workflow, not the script. Every :file:`tests/`
subdirectory is a leg except :file:`tests/integration/`, which holds no unit-tier
Expand Down
2 changes: 1 addition & 1 deletion docs/source/contributing/workflows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1415,7 +1415,7 @@ Every ``test``, ``test-ktlo`` and ``experimental`` leg passes each pin as an
selection, so no workflow lists the pins, and :file:`tests/test_tier_guard.py` runs
the steps to check what each does with the answer. Under ``gate-only``, ``gate``'s
full suite runs the pins with everything else. ``unittest-ordering``'s ``project``
cell keeps them, as it asks how a module runs in one interpreter, in file order and
cells keep them, as they ask how a module runs in one interpreter, in file order and
without xdist, which is a question about the module rather than the Python version.

A pin's skip where ``tomllib`` is missing, before 3.11, runs on no leg: the scripts
Expand Down
17 changes: 13 additions & 4 deletions tests/project/test_project_pins_once_1954.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
``pcapkit`` stay on every leg. :func:`tests._tiers.is_project_pin` decides which,
and :file:`tests/test_tier_guard.py` checks that rule and runs the steps that use
it. This module pins the rest: the job runs on every diff, on ``latest``, and
blocks; the release gate and the ``unittest-ordering`` cell keep every pin; the
blocks; the release gate and the ``unittest-ordering`` ``project`` cells keep every pin; the
workflows page says so.

"""
Expand Down Expand Up @@ -67,15 +67,24 @@ def test_it_runs_under_the_caps(self) -> None:


class TestEveryPinStillRunsWhereTheMatrixDoesNot(unittest.TestCase):
"""The release gate's full suite and ``unittest-ordering``'s ``project`` cell keep every pin."""
"""The release gate's full suite and ``unittest-ordering``'s ``project`` cells keep every pin."""

def test_the_gate_runs_the_whole_suite(self) -> None:
self.assertEqual(step(('unit-tests.yml', 'gate'), 'Run full test suite')['run'],
'python util/cap_file_size.py python -m pytest -q -n auto --dist load')

def test_the_ordering_cell_runs_every_pin(self) -> None:
self.assertIn('project', job('unit-tests.yml', 'unittest-ordering')['strategy']['matrix']['leg'])
modules = set(run_unittest_leg.leg_modules('project'))
"""#2254: the ``project`` cells between them, as they cut ``tests/project`` in two."""
matrix = job('unit-tests.yml', 'unittest-ordering')['strategy']['matrix']
cells = [{'leg': name} for name in matrix['leg']] + list(matrix.get('include') or [])
modules = set()
for cell in cells:
if cell['leg'] == 'project':
words = str(cell.get('shard', '')).split()
flags = dict(zip(words[::2], words[1::2]))
cut = run_unittest_leg.half_bounds('project', int(flags['--half'])) if '--half' in flags else {}
modules.update(run_unittest_leg.leg_modules('project', **cut))
self.assertTrue(modules, 'no unittest-ordering cell runs tests/project')
pins = {pin[:-len('.py')].replace('/', '.') for pin in _tiers.project_pin_paths()}
self.assertTrue(pins)
self.assertEqual(pins - modules, set())
Expand Down
4 changes: 3 additions & 1 deletion tests/project/test_unit_tests_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,9 @@ def test_project_tests_runs_on_every_diff_and_never_duplicates_test(self) -> Non
self.assertIn('if: ${{ inputs.gate-only != true }}', head)
self.assertNotIn('needs.changes.outputs.code', head)
for name, condition in (('Run the project pins', "needs.changes.outputs.code != 'false'"),
('Run tests/project and the root-level modules under plain unittest',
('Run the first half of tests/project and the root-level modules under plain unittest',
"needs.changes.outputs.code == 'false'"),
('Run the second half of tests/project and the root-level modules under plain unittest',
"needs.changes.outputs.code == 'false'"),
('Run the other tests that read docs/ or Markdown',
"needs.changes.outputs.code == 'false'")):
Expand Down
Loading
Loading