Repository navigation
test-guard: the returning-test sweep cannot see an assert neutralised by a bare except #15195
Description
Activity
github-actions commented
on Aug 28, 2026 on Aug 28, 2026 – with GitHub ActionsContributorMore actionsVerified in merged
Dev_new_gui— closingMerged as
c5306fe85f(PR #15241).Criterion Verdict Evidence The sweep counts a test whose only assertions sit under a swallowing handler MET swallowed_assertions_inatrepo_tests/collected_test_model.py:307; the assert/raise exemption is narrowed so the swallowed shape is counted tooLegitimate assert-and-return drivers are still spared, proved synthetically MET tests_that_return_instead_of_asserting_test.py:497— sparesassert True; return Trueand atry/except ValueErrorwrapper (a non-assertion handler leaves the assert live), while flaggingexcept BaseException,except AssertionError, bareexcept:,except (ValueError, Exception)andexcept Exception as excPopulation re-measured and the offender set adjusted in the same commit, with the rise stated as a detector improvement MET :193-228records "71 → 86 and 121 → 127 … A DELIBERATE RE-MEASUREMENT, NOT A RATCHET VIOLATION", enumerates all 21 newly-counted returns across 8 functions in 3 named files, and states the base set is a verified strict subsetA guard for except Exceptionwrapping assertions in tests generallyMET :427test_no_test_smothers_its_own_assertions_under_a_swallowing_handler— catches the shape whether or not the function returns, with a down-only budget, a population floor, and drained-entry assertionsThe third criterion is the one worth dwelling on. A count that rises because the detector got sharper looks identical, in a diff, to a ratchet being loosened — so stating it explicitly, enumerating every newly-counted site, and proving the old set is a strict subset is what separates the two. That record is why this closes rather than reading as a quietly raised budget.
The budgets have already walked down once since (
autobot-backend6 → 5,autobot-infrastructuredeleted) ind4e4c428e5— the ratchet working as designed.Closing — all four acceptance criteria verified in
origin/Dev_new_guiAudit of open issues whose work landed via a merged commit (
Closesnever auto-fires onDev_new_gui). Verified against the merged tree, not the PR diff. Delivered byc5306fe85f(PR #15241).AC Verdict Evidence in origin/Dev_new_gui1. The sweep counts a test whose only assertions sit under a swallowing handler Met repo_tests/tests_that_return_instead_of_asserting_test.py:301— the assert/raise exemption is nowif propagating_guards(function): continue, so the exemption is spent only on assertions that can fire (rationale at:295-300). The predicate lives inrepo_tests/collected_test_model.py:276, keyed onASSERTION_CATCHING = frozenset({"Exception", "BaseException", "AssertionError"})at:208and the handler test at:224.2. Legitimate assert-and-return drivers are still excluded, proved by a synthetic case Met tests_that_return_instead_of_asserting_test.py:497—test_the_detector_finds_a_planted_return_and_spares_the_legitimate_onesnow carries both halves: a handler naming a non-assertion exception (except ValueError) leaves the assert able to fire and is spared (:534-547), and an assert underexcept Exceptionis counted (:549-560).3. Population re-measured and _KNOWN_OFFENDERSbudgets adjusted in the same commit, with the rise stated explicitly as a detector improvementMet Budgets at :234-236(autobot-backend71 -> 86,autobot-infrastructure121 -> 127, since revised to 121 by #15189). The note at:193-232names it "A DELIBERATE RE-MEASUREMENT, NOT A RATCHET VIOLATION, AND THE ONLY ONE THIS FILE SANCTIONS", enumerates all 21 newly-counted returns across the 3 files, and records that the base set is a strict subset of the new one. The terms on which a future raise is permissible are stated at:230-232.4. A guard, or an explicit note, for except Exceptionwrapping assertions in test functions generallyMet — a guard, not a note tests_that_return_instead_of_asserting_test.py:427—test_no_test_smothers_its_own_assertions_under_a_swallowing_handler, backed by_swallowed_by_tree()at:311andcollected_test_model.swallowed_assertions_inat:307. It fires on any swallowed assertion in a test function, whether or not the function returns — the broader question the issue raised.AC4 is met in its stronger form: the issue offered "a guard, or an explicit note" and the merged change is a guard with its own ceiling, so the general shape is enforced rather than described. The failure message at
:464-467names the remedy (catch the specific exception, or re-raise /pytest.fail).Note for anyone reading the ratchet history: the
autobot-infrastructureceiling moved 121 -> 127 here and back to 121 under #15189, which unwrapped three swallowing drivers inautobot-infrastructure/shared/scripts/test_configuration.py. Both moves are recorded in the comment block above the dict.Closing.
Problem
repo_tests/tests_that_return_instead_of_asserting_test.py(#14920) skips any testfunction that contains an
assertorraise:The reasoning is sound as stated, and the exclusion is right for the case it
describes. But it assumes an
assertthat is present is anassertthat canfire. That assumption fails for this shape:
A bare
except ExceptioncatchesAssertionError. The assertion is inert: thetest cannot fail whichever way it goes. The sweep sees the
assertnode, concludesthe test can fail, and skips it — so the guard designed to find tests that cannot
fail is blind to a whole family of tests that cannot fail.
It is the worse variant, too. A plain
return Trueat least tripsPytestReturnNotNoneWarningat runtime. This shape trips that warning and looksdefended in an AST sweep, so both detectors agree it is fine.
Measured
Found while fixing
autobot-backend/cache/cache_consolidation_p4_test.pyfor #14979.All ten of its
test_*functions had this shape. The guard attributed only 2offending returns in the whole file — both in
test_migrated_files_import, the onefunction with no
assertat all (its body waspassplus sixprint()callsclaiming six modules import, with the
importstatements stripped at some point).So nine tests that could not fail sat inside the guarded population, counted as
compliant, for as long as the file has existed.
Suggested detection
An
assertshould only excuse a test if it can actually propagate. A cheap andprecise rule: do not count an
assertas protective when it is lexically inside atrywhose handler catchesException,BaseException, orAssertionErroranddoes not re-raise. That is a local AST check needing no execution, and it is
narrow enough not to disturb the legitimate assert-and-return drivers the current
exclusion exists to protect.
Worth pairing with a broader question the same shape raises: a bare
except Exceptionaround assertions in anytest_*function is almost always a bug,whether or not the function returns.
Acceptance criteria
still excluded — proved by a synthetic case in
test_the_detector_finds_a_planted_return_and_spares_the_legitimate_ones_KNOWN_OFFENDERSbudgets adjusted inthe same commit (the number will rise as the blind spot closes — that is a
detector improvement, not a ratchet violation, and needs saying explicitly in
that file so the down-only rule is not read as forbidding it)
except Exceptionwrapping assertions intest functions generally
Related
also misses, since the warning fires but nothing acts on it