Skip to content

Commit 1209106

Browse files
Make test_tests_collected_once actually verify coverage (#3130)
* Fix test_tests_collected_once collecting nothing Use a pytest plugin to record collected node IDs directly instead of parsing captured stdout, and disable pytest-retry in the nested run so it does not exit with INTERNAL_ERROR. Also remove a stray breakpoint and assert that the baseline collection is non-empty. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add missing test files to CI matrix The matrix in test.yml was missing entries for several test files and docs, so those tests were never run in CI. Add them now that test_tests_collected_once actually verifies coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Use a single **/*.rst entry in the CI matrix Replaces the per-file rst entries with a glob. Enable bash globstar in the workflow run step so the pattern expands, and expand globs in the linter helper since pytest.main is invoked without a shell. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Use docs/ as a single CI matrix entry instead of per-rst entries Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b99d99b commit 1209106

3 files changed

Lines changed: 37 additions & 28 deletions

File tree

.github/workflows/test.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,14 @@ jobs:
113113
- tests/mock_vws/test_update_target.py::TestWidth
114114
- tests/mock_vws/test_update_target.py::TestInactiveProject
115115
- tests/mock_vws/test_requests_mock_usage.py
116+
- tests/mock_vws/test_respx_mock_usage.py
116117
- tests/mock_vws/test_flask_app_usage.py
117118
- tests/mock_vws/test_vumark_generation_api.py
119+
- tests/mock_vws/test_target_validators.py
118120
- tests/mock_vws/test_docker.py
121+
- ci/test_custom_linters.py
119122
- README.rst
120-
- docs/source/basic-example.rst
123+
- docs/
121124

122125
steps:
123126
- uses: actions/checkout@v6

ci/test_custom_linters.py

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
"""Custom lint tests."""
22

33
from pathlib import Path
4-
from typing import TYPE_CHECKING
54

65
import pytest
76
import yaml
87
from beartype import beartype
98

10-
if TYPE_CHECKING:
11-
from collections.abc import Iterable
12-
139

1410
@beartype
1511
def _ci_patterns(*, repository_root: Path) -> set[str]:
@@ -23,32 +19,44 @@ def _ci_patterns(*, repository_root: Path) -> set[str]:
2319
return ci_patterns
2420

2521

22+
class _CollectPlugin:
23+
"""Pytest plugin that records the node IDs of collected items."""
24+
25+
def __init__(self) -> None:
26+
"""Start with an empty set of collected node IDs."""
27+
self.collected: set[str] = set()
28+
29+
def pytest_itemcollected(self, item: pytest.Item) -> None:
30+
"""Record each collected item's node ID."""
31+
self.collected.add(item.nodeid)
32+
33+
2634
@beartype
27-
def _tests_from_pattern(
28-
*,
29-
ci_pattern: str,
30-
capsys: pytest.CaptureFixture[str],
31-
) -> set[str]:
35+
def _tests_from_pattern(*, ci_pattern: str) -> set[str]:
3236
"""From a CI pattern, get all tests ``pytest`` would collect."""
33-
# Clear the captured output.
34-
capsys.readouterr()
35-
tests: Iterable[str] = set()
37+
plugin = _CollectPlugin()
3638
pytest.main(
3739
args=[
3840
"-q",
3941
"--collect-only",
40-
# If there are any warnings, these obscure the output.
42+
# Disable pytest-retry to avoid:
43+
# ```
44+
# ValueError: no option named 'filtered_exceptions'
45+
# ```
46+
# which causes the nested run to exit with INTERNAL_ERROR
47+
# before any items are collected.
48+
"-p",
49+
"no:pytest-retry",
50+
# Disable warnings to avoid many instances of:
51+
# ```
52+
# Unknown config option: retry_delay
53+
# ```
4154
"--disable-warnings",
4255
ci_pattern,
4356
],
57+
plugins=[plugin],
4458
)
45-
data = capsys.readouterr().out
46-
for line in data.splitlines():
47-
# We filter empty lines and lines which look like
48-
# "9 tests collected in 0.01s".
49-
if line and "collected in" not in line:
50-
tests = {*tests, line}
51-
return set(tests)
59+
return plugin.collected
5260

5361

5462
def test_ci_patterns_valid(request: pytest.FixtureRequest) -> None:
@@ -82,20 +90,18 @@ def test_ci_patterns_valid(request: pytest.FixtureRequest) -> None:
8290
assert collect_only_result == 0, message
8391

8492

85-
def test_tests_collected_once(
86-
*,
87-
capsys: pytest.CaptureFixture[str],
88-
request: pytest.FixtureRequest,
89-
) -> None:
93+
def test_tests_collected_once(request: pytest.FixtureRequest) -> None:
9094
"""Each test in the test suite is collected exactly once.
9195
9296
This does not necessarily mean that they are run - they may be skipped.
9397
"""
9498
ci_patterns = _ci_patterns(repository_root=request.config.rootpath)
99+
all_tests = _tests_from_pattern(ci_pattern=".")
100+
assert all_tests
95101
tests_to_patterns: dict[str, set[str]] = {}
96102

97103
for pattern in ci_patterns:
98-
tests = _tests_from_pattern(ci_pattern=pattern, capsys=capsys)
104+
tests = _tests_from_pattern(ci_pattern=pattern)
99105
for test in tests:
100106
if test in tests_to_patterns:
101107
tests_to_patterns[test].add(pattern)
@@ -110,6 +116,5 @@ def test_tests_collected_once(
110116
)
111117
assert len(patterns) == 1, message
112118

113-
all_tests = _tests_from_pattern(ci_pattern=".", capsys=capsys)
114119
assert tests_to_patterns.keys() - all_tests == set()
115120
assert all_tests - tests_to_patterns.keys() == set()

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ ignore_names = [
417417
# pytest configuration
418418
"pytest_collect_file",
419419
"pytest_collection_modifyitems",
420+
"pytest_itemcollected",
420421
"pytest_plugins",
421422
"pytest_set_filtered_exceptions",
422423
"pytest_addoption",

0 commit comments

Comments
 (0)