You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When the vacuity layer refuses a run at collection time, the same run also prints:
VACUITY: 1 collected test(s) never reported an outcome, so the run lost them silently:
tmp_repro_vac/test_offender.py::test_one_offending_arm
The run did not lose that test silently. It refused it loudly, in the sentence
immediately above. So a reader who typed one bare @pytest.mark.skip gets the correct
diagnosis, then a second finding telling them a test vanished without saying so — and the
natural response is to go looking for a lost test that was never lost.
The guard whose subject is a silent loss is firing on the one event that is the opposite of
a silent loss.
Reproduce
Create a file with one offending arm, anywhere under test/pytest/:
This is not #988's doing, and #988 is what surfaced it
The serial rows are identical on both trees, so the behaviour is pre-existing. I found it
while reviewing #988, whose premise is xdist should report like serial: after that fix, xdist reports better than serial on this line. Filing it separately so the asymmetry is
not later read as a defect in that fix.
Where it comes from
test/pytest/pgc_vacuity.py:
The class hook pytest_collection_modifyitems adds every collected item to self.collected.
The module-level pytest_collection_modifyitems then raises pytest.UsageError("the pgColumnar vacuity layer refuses this run: ...").
No test runs, so nothing reaches pytest_runtest_logreport and self.reported stays
empty.
pytest_sessionfinish differences the two sets, finds the item missing, and prints the
"lost them silently" problem.
The set difference is correct. What it means is not: collected - reported is a silent
loss only when nobody said anything, and here the layer itself is the one that stopped the
run.
A second, smaller thing on the same path
pytest_sessionfinish also does session.exitstatus = 1. On this path the measured rc is 4, because UsageError decides the exit code after sessionfinish has run. So the
guard's own verdict is discarded here. That is the right outcome — 4 is the more specific
answer — but it is reached by accident rather than by the guard deciding not to lower it.
A possible shape, not a decided one
Have the refusal record that it refused — a flag set where the UsageError is raised — and
have pytest_sessionfinish drop the missing-outcome problem when that flag is set, because
every collected item is then accounted for by the refusal. The setup-skip problem should
still print: it describes something the refusal does not cover.
I have not built or measured that, and suppressing any part of a vacuity guard deserves its
own argument — a guard that learns to stay quiet is the thing most likely to stay quiet
when it should not. The pytest_deselected hook already in the file is the precedent for
teaching this guard a distinction rather than widening its silence, and the fix should get
an arm of its own in the corpus: one that asserts the refusal still prints and the loss
line does not.
What I am not claiming
No test hides behind this. rc is 4 either way, so nothing passes that should fail.
The cost is a reader's time and a contradictory message, not a false green.
I did not measure every refusal path. I drove the bare-skip path. The broad-except, sorted()-ordered and empty-parametrize refusals raise from the same hook, so the same
reasoning should apply to them, but I have only run the first.
When the vacuity layer refuses a run at collection time, the same run also prints:
The run did not lose that test silently. It refused it loudly, in the sentence
immediately above. So a reader who typed one bare
@pytest.mark.skipgets the correctdiagnosis, then a second finding telling them a test vanished without saying so — and the
natural response is to go looking for a lost test that was never lost.
The guard whose subject is a silent loss is firing on the one event that is the opposite of
a silent loss.
Reproduce
Create a file with one offending arm, anywhere under
test/pytest/:Run it serially, with the venv interpreter that has pytest:
Both sentences appear. Measured on
mainatc96f5262and again on test: a collection-time vacuity refusal keeps its reason under xdist (#963) #988's head992175e0:VACUITYlinemainc96f5262mainc96f5262-n 2992175e0992175e0-n 2This is not #988's doing, and #988 is what surfaced it
The serial rows are identical on both trees, so the behaviour is pre-existing. I found it
while reviewing #988, whose premise is xdist should report like serial: after that fix,
xdist reports better than serial on this line. Filing it separately so the asymmetry is
not later read as a defect in that fix.
Where it comes from
test/pytest/pgc_vacuity.py:pytest_collection_modifyitemsadds every collected item toself.collected.pytest_collection_modifyitemsthen raisespytest.UsageError("the pgColumnar vacuity layer refuses this run: ...").pytest_runtest_logreportandself.reportedstaysempty.
pytest_sessionfinishdifferences the two sets, finds the item missing, and prints the"lost them silently" problem.
The set difference is correct. What it means is not:
collected - reportedis a silentloss only when nobody said anything, and here the layer itself is the one that stopped the
run.
A second, smaller thing on the same path
pytest_sessionfinishalso doessession.exitstatus = 1. On this path the measured rc is4, because
UsageErrordecides the exit code after sessionfinish has run. So theguard's own verdict is discarded here. That is the right outcome — 4 is the more specific
answer — but it is reached by accident rather than by the guard deciding not to lower it.
A possible shape, not a decided one
Have the refusal record that it refused — a flag set where the
UsageErroris raised — andhave
pytest_sessionfinishdrop the missing-outcome problem when that flag is set, becauseevery collected item is then accounted for by the refusal. The setup-skip problem should
still print: it describes something the refusal does not cover.
I have not built or measured that, and suppressing any part of a vacuity guard deserves its
own argument — a guard that learns to stay quiet is the thing most likely to stay quiet
when it should not. The
pytest_deselectedhook already in the file is the precedent forteaching this guard a distinction rather than widening its silence, and the fix should get
an arm of its own in the corpus: one that asserts the refusal still prints and the loss
line does not.
What I am not claiming
The cost is a reader's time and a contradictory message, not a false green.
except,sorted()-ordered and empty-parametrize refusals raise from the same hook, so the samereasoning should apply to them, but I have only run the first.
Found while reviewing #988. Related: #963.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a