Make two address-space tests able to fail (#213) - #574
Merged
Merged
Conversation
A sweep of all 2922 test functions for bodies that check nothing turned up two where the name promises a postcondition the body never asserted: `_apply_memory_limit` applying its cap, and `_pool_initializer` warming the registry. Each called the function and restored the rlimit in a `finally`, nothing more, so both passed whether or not the function did anything. The restore was a fiction as well. RLIMIT_AS starts at infinity here; lowering it is permitted and raising it back is not, so `setrlimit(original)` raises "not allowed to raise maximum limit", is suppressed, and leaves the test runner capped for the rest of the session. That also traps the obvious fix. Asserting the cap in the runner, after anything has lowered it, is satisfied whether or not the function under test did the lowering: my first attempt probed by setting the limit itself, could not undo it, and so asserted nothing. Both checks now run in a child process, which sheds the limit on exit, and the platform probe runs in a child of its own so a skip means the platform genuinely refuses rather than that an earlier probe already applied the cap. Verified by mutation, since a test that cannot fail is not worth adding: - `_apply_memory_limit` replaced by `return`: both tests fail. - `_pool_initializer` with the `discover_tools()` call removed: the initializer test fails, the other still passes, which is the separation wanted. Nothing else from the sweep is changed. Of the 31 flagged, six were my detector failing to follow a helper context manager that turns SpecificationWarning into an error, eleven are validators whose only contract is not raising, six assert serialisability through `json.dumps` raising, and six use a warnings context that does fail on a warning. Those read correctly as written. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0123j56Hk6jRz9zy91Doc1og
PATCH: tests only, no source change and no behaviour change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0123j56Hk6jRz9zy91Doc1og
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Tests only, no source change. A sweep of all 2922 test functions for bodies that check nothing turned up two real cases, both in
tests/test_tool_safety.py, and both where the test name promises a postcondition the body never asserted.The two tests
test_apply_memory_limit_is_safe_with_generous_capandtest_pool_initializer_warms_registryeach called the function, restored the rlimit in afinally, and asserted nothing. They passed whether or not the function did anything at all.Why the obvious fix does not work
The
finallyrestore is a fiction.RLIMIT_ASstarts at infinity in this environment; lowering it is permitted and raising it back is not:So
setrlimit(original)raises, is suppressed, and the test runner stays capped for the rest of the session.That also traps the obvious fix, and it trapped me. My first attempt probed whether the platform allows the change by setting the limit in the runner. It could not undo that, so by the time the assertion ran the cap was already in place and
getrlimit() == expectedwas satisfied regardless of what the function under test did. I only caught it because the mutation check below still passed.Both checks now run in a child process, which sheds the limit on exit. The platform probe runs in a child of its own, so a skip means the platform genuinely refuses rather than that an earlier probe already applied the cap.
One incidental trap worth recording, since it cost a cycle: interpolating a multi-line block into an indented f-string and calling
textwrap.dedentdoes not work. Only the first line of the interpolated block picks up the surrounding indent, sodedentfinds nothing in common to strip and the child dies with anIndentationError. The source is assembled by concatenation instead, with a comment saying why.Verified by mutation
A test that cannot fail is not worth adding, so both were checked against a broken source:
test_apply_memory_limit...test_pool_initializer..._apply_memory_limitreplaced byreturndiscover_tools()removed from_pool_initializerThe second row is the separation wanted: the initializer test catches the discovery regression, the cap test correctly does not.
Source restored clean afterwards (
git diff --statontool_safety.pyempty).What the sweep found, and what I left alone
31 of 2922 flagged. Only the two above are real:
_no_specification_warning()doessimplefilter("error", SpecificationWarning); a warning does fail them. The check was behind a helper, not inline.validate_inputreturnsNoneby design.json.dumps(...)with no assertdumpsraises on failure. Proves serialisability, not correctness.warningscontextI did not bolt assertions onto the other 29 for a metric's sake.
Test plan
tests/test_tool_safety.py -k "memory_limit or pool_initializer": 2 passed, and 2 failed / 1 failed under the two mutations above.ruff check .,ruff format --check .clean.Checklist
CITATION.cffversion and date in step in the same commit)ruff check .passesCHANGELOG.mdupdatedOn #213 itself
Every item the issue lists is already done, across #562, #563 and #566: the PLS structural identities replaced
test_pls_properties_todo, DModX is cross-checked against SIMCA-P, the TPLS plot and cross-validation tests carry 24 and 9 assertions,test_inconsistent_sizesasserts all three shapes, and the NaN items were split to #557 and #558 and fixed. There are zero live TODO/FIXME markers left intests/. This PR is the extra sweep rather than the issue's own list; #213 is closable once it merges.🤖 Generated with Claude Code
https://claude.ai/code/session_0123j56Hk6jRz9zy91Doc1og
Generated by Claude Code