Repository navigation
fixval: count a reverted pytest exit as RED only with JUnit evidence (#28) - #331
Merged
Merged
Conversation
…28) Exits 1 and 2 on the reverted code used to count as "the test fails without the fix" no matter why pytest stopped. A broken fixture, a syntax error or an unrelated import in the test file therefore passed FIXVAL. The reverted pytest run now writes a JUnit report to a private temp file. The run is RED when the report has a <failure>, or a collection or setup <error> that is an import of a name the fix adds: a module-level name bound on a "+" line and not on a "-" line of the same file, or a module the patch creates ("cannot import name", "has no attribute", "No module named", "bad magic number"). Teardown errors never count. Anything else, including a missing or unparseable report, is FIXVAL_INCONCLUSIVE. Non-pytest runners and the require_witness path are unchanged. Refs #28
Touching tests/test_fixval.py makes lint_changed.py check the whole file, and this pre-existing exec of a fixed literal tripped ruff S102. Refs #28
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
…private dir (#28) Review follow-up: - red_witness no longer counts a <failure> whose message is a strict XPASS ('[XPASS(strict)]') or a unittest unexpected success ('Failed: Unexpected success'); #28 treats both as invalid witnesses. Tested with reports from real pytest runs. - The reverted run writes junit.xml inside a fresh mkdtemp directory that is removed with rmtree, instead of unlinking a predictable mkstemp path in the shared TMPDIR. - New tests pin that a require_witness run passes no --junitxml, that the report directory is private (0700), empty before the run and removed after, and that a path that is not an importable module (pkg/foo-bar.py) adds no names.
…#28) Two unit cases put an unexpected pass before a real <failure> in one testcase and expect RED, so red_witness must keep reading past the first <failure>. A new test makes os.rmdir fail on the private report directory and checks _test_reverted_candidate still returns its verdict, which needs ignore_errors=True on the cleanup rmtree.
…p --junitxml before -- (#28) - `from pkg import newmod` (or `pkg.newmod` attribute access) where the fix creates pkg/newmod.py is now RED instead of inconclusive. - added_names reads multi-line parenthesised imports, keeping separate state for the pre- and post-image of each hunk. - A renamed or copied file adds its module, minus the dotted names the old path already had. - A pytest command containing `--` gets --junitxml before that `--`; appended after it, pytest took the flag as a path and exited 4. - Known limits are now documented in comments: only `src/` is stripped from module paths, a missing namespace-package parent is not RED, a hunk that starts inside a parenthesised import misses its names, and entity-expansion caps need expat 2.4.0 or later. Tests: a real failure before an unexpected pass in one testcase (kills the only-last-<failure> mutant), the cases above, and a real pytest run with `--` in the command.
A module-level import line that ends with a backslash now carries on to the next line, so `from pkg.o import a, \` followed by `newname` binds `newname`. Before, that name was not read and an import error for it stayed inconclusive instead of RED. Only an import at column 0 starts a continuation; an assignment or an indented import does not. New tests close gaps that surviving mutants showed: - a comment after `from x import (` still opens the import; - a parenthesised import inside a function binds nothing; - an import left open at the end of one hunk does not carry into the next hunk, with an indented name and with a column-0 binding there; - `cannot import name` and `has no attribute` need the module to be exactly `M.X` (not a longer name, not another parent), and `No module named` needs a module the fix creates, not one it starts with. Refs #28
`import os # see \` was read as a continued import, so the next line (`def helper():`) was parsed as import names and `helper` was lost. An import error for `helper` then stayed inconclusive; at bb90558 it was RED. The backslash check now looks at the line with its comment removed, for both the opening line and continued lines. New tests: - `import os # see \` followed by `def helper():` binds both names, and the import error for `helper` is RED; - a continued line whose comment ends in a backslash ends the import; - a plain import does not continue onto the next line; - a parenthesised import with a `\` inside ends at its `)`; - `No module named 'pkg'` is not RED when the fix adds `pkg.newmod`, and `No module named 'pkg.new'` is not RED when it adds `pkg.new.mod` (the namespace parent limit). Refs #28
HouMinXi
pushed a commit
that referenced
this pull request
Nov 1, 2026
Brings in the fixpoint state-invariance tests (#337), the fixval JUnit evidence rule (#331) and the isolate startup diagnostic (#329). Both sides appended tests at the end of tests/test_review_interruption_source_controls.py; the resolution keeps both blocks unchanged. The touched test files and the neighbouring machine, state and verify suites pass on the merged tree. Signed-off-by: Minxi Hou <houminxi@gmail.com> Claude-Session: https://claude.ai/code/session_01Rf2jBZrtS24e7qpWUBy3uo
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.
Current-head acceptance update (2026-11-01)
Reviewed full range
0ef23cd942edb60bfbe0693f19ac93f21a1ba9f9..e4032a90d1e736aa199d7922f79a66f103ccabab(8 files, +1419/-19).This remains the narrow option-c slice of #28: reverted pytest exit 1/2 requires a failed-test witness (excluding unexpected passes), or a supported collection/setup import error tied to a binding/module the fix adds. Other errors remain inconclusive. It does not close #28 or settle its broader baseline, GREEN-evidence and three-run requirements. Non-pytest runners,
require_witness, GREEN policy and the overfit guard retain their prior behavior.Current implementation supersedes the historical hunk parser described below:
src/root mapping remains bounded, and unknown exception renderings fail closed. JUnit is not authenticated against a malicious plugin forging its report.Current validation includes real-pytest spoofing/positive controls, full-image binding boundaries and transaction restoration checks. Independent review reran 179 witness/boundary tests successfully; a separate five-file run had 510 passes and one environment-related failure reproduced at the exact base. Historical counts and mutant tables below apply only to their stated earlier revisions, not this head. The sixth requirements-acceptance lens found no blocking defect within this partial scope.
Verified at 2026-11-01 09:27 UTC: all three exact-head workflows completed successfully — Linux tests 41289226254, Lint 41289226267, and CodeQL 41289226215. Linux ownership, cgroup, full service-free selection and process-integration steps all succeeded. This records CI results, not PR approval or a merge action.
Historical description and evidence — earlier revisions
The original text is preserved below for provenance. Implementation details, line counts, test counts and mutation results refer to earlier revisions unless independently confirmed in the current-head update above; superseded descriptions are not current completion claims.
Refs #28 (option c)
What changes
FIXVAL used to count pytest exit 1 or 2 on the reverted code as "the test fails without the fix", whatever the reason. A test file with a broken fixture, a syntax error or an unrelated import therefore passed.
The reverted pytest run now writes
--junitxmltojunit.xmlinside a freshtempfile.mkdtempdirectory (mode 0700), and removes the directory withrmtreeafterwards. The run is RED only when the report has:<failure>(a test call failed) whose message is not an unexpected pass, or<error>that is an import of a name the fix adds.An unexpected pass is a strict XPASS (message
[XPASS(strict)] ...) or a unittestexpectedFailurethat succeeded (messageFailed: Unexpected success). pytest reports both as<failure>, and #28 counts neither as a RED witness."A name the fix adds" (
src/code_forge/_fixval_junit.py) means a module-leveldef/class/assignment/import bound on a+line and not on a-line of the same production file, keyed by its dotted module (with and withoutsrc.), or a module the patch creates, renames or copies into place. A name inside a multi-linefrom x import (...)counts too, and so does a name on a line that continues a module-level import ending in\. A\at the end of a comment (import os # see \) continues nothing. A path that is not an importable module (for examplepkg/foo-bar.py) adds nothing. The matched error texts arecannot import name 'X' from 'M',module 'M' has no attribute 'X', andNo module named 'M'/bad magic number in 'M'for created modules only.cannot import name 'X' from 'M'andmodule 'M' has no attribute 'X'also count when the fix creates moduleM.X, sofrom pkg import newmodis RED. Teardown errors never count.Everything else is
FIXVAL_INCONCLUSIVE:pytest exit N without a failed test or an import of a name the fix adds. That includes a missing or unparseable report. When the command already has a bare--,--junitxmlgoes right before it; after it, pytest would read the flag as a path and exit 4. If the project's pytestaddoptshas-p no:junitxml, pytest writes no report and the run is inconclusive. That is the conservative outcome: FIXVAL gives no verdict rather than a wrong RED.Unchanged: non-pytest runners, the
require_witnesspath (it passes no--junitxml), the overfit guard.Known limits
Each of these fails closed (inconclusive, never a wrong RED) and is noted in a code comment:
src/is stripped from a module path. A module under another root (lib/pkg/mod.pyimported aspkg.mod) matches only by its full dotted path, so an error that namespkg.modstays inconclusive. Guessing roots would widen RED.No module named 'pkg.new'is not RED when the fix createspkg/new/mod.pyas a namespace package, because the patch alone does not show whetherpkg/new/existed before.What remains on #28
This PR covers option c for exit 1 and 2 only. Still open on #28:
Tests
New file
tests/test_fixval_junit_witness.py(92 tests):added_names: module-level bindings only, a name on both-and+lines is not new, path to module mapping, a non-importable path (pkg/foo-bar.py) adds nothing, unreadable patch, multi-line parenthesised imports (added inside, opening line as context, a comment with), separate pre- and post-image state, a comment after the opening parenthesis, an import inside a function, an import left open at the end of one hunk), backslash-continued imports (with and without a space before\, a comment on the last line,import a, \, an assignment or an indented import that ends in\, a comment that ends in\on the opening line and on a continued line, a plain import followed by a binding, a\inside parentheses), renamed and copied files add their module, a rename to a name the module already had adds none, a plain modification adds none.red_witnessreads every<failure>in a testcase: an unexpected pass before a real failure, and a real failure before an unexpected pass, are both RED.red_witnessfor a submodule the fix creates:cannot import name 'newmod' from 'pkg'andmodule 'pkg' has no attribute 'newmod'are RED; another name frompkgis not, and neither ispkg.newmodwhen the fix createspkg.newmod2orother.newmod.No module named 'pkg.newer'is not RED when the fix createspkg.new,No module named 'pkg'is not RED when it createspkg.newmod, andNo module named 'pkg.new'is not RED when it createspkg.new.mod(the namespace parent limit).red_witnesscounterexamples: teardown error, same name from another module, a different name,has no attributefor another name, SyntaxError, setup RuntimeError, ModuleNotFoundError for a module the fix did not add, empty added set, skipped-only, empty and malformed XML.from pkg import newmodwithpkg/newmod.pymissing is PASS; a command with--before the test path still gets its report.--junitxmlgoes: before pytest's--forpytest,python -m pytestandcoverage run -m pytest; appended when the only--belongs touv run.require_witnessrun gets no--junitxml.run_fixvalend to end: the reverted module raises at import, and the run is now inconclusive (main: PASS).Updated tests:
test_fixval.py: the mock writes a report with a failed test.test_fixval_exit_classification.py: fake pytest exit 1/2 with no report, and a real import error with no added names, are now inconclusive.test_fixval_test_support.py: the collection-error case was renamed; it still passes, now through the added-name rule.Mutation testing, by hand: I worked on a copy of the tree with
__pycache__cleared andPYTHONDONTWRITEBYTECODE=1. The test set wastest_fixval_junit_witness.py,test_fixval.py,test_fixval_exit_classification.pyandtest_fixval_test_support.py(424 pass unmutated at the seventh commit).At the seventh commit I ran every mutant against all four files in full, without
-xand with nothing deselected. From a copy of the tree with__pycache__removed before each mutant:The unmutated run gave 424 passed with no flaky test. The deliberate mutant (
red_witnessalways returns True) ran first. All 22 mutants were killed:red_witnessalways returns True (sanity)No module named: a created module equal to or under the missing one (M17c)eliftoifafter the parenthesised openingfrom x import (not allowed.search) instead of matchedcannot import name:M.Xas a substring of a created modulehas no attribute: the same substring matchcannot import name: any created module ending in.Xhas no attribute: the same.Xsuffix matchNo module named: a prefix match on created modules\starting a continuation.searchfor.match)At the sixth commit, the same set without the first seven real mutants above was killed, but that run used
-x, so it showed only the first failing test for each mutant. Against the code at the fifth commit (bb90558), 7 tests of the file fail: the backslash cases that bind a name on a continued line. Against the sixth commit (f39df7d), 3 fail: the two comment-backslash cases and the comment-backslash RED case.At the fifth commit, a deliberate failing mutant (
red_witnessalways returns True) was killed first. All 19 real mutants were killed too:<failure>read (findall(...)[-1:]);<failure>read (findall(...)[:1]);ignore_errors=Truedropped from the cleanuprmtree;cannot import name;has no attribute;M.Xbuilt asX.M;--junitxmlalways appended;--offset taken fromargvinstead ofcmd;--;--searched in the whole command, wrapper included;_with_junitxmlbypassed.Earlier rounds: 13 mutants at the third commit (M14, M09, the two unexpected-pass prefixes, the skip check both ways, exact match for
startswith,continueforreturn,rmtreedropped, its guard inverted, a fixed report directory,--junitxmldropped, the verdict ignored) andfindin place offindallat the fourth were all killed. I did not rerun those 14 at the fifth commit.The new tests were also run against the code at the fourth commit (838be08): 11 of them fail there, including the
from pkg import newmodand--cases.Local run (Linux, Python 3.14): every test file that mentions fixval gave 1695 passed and 12 skipped, with no failures (
python -m pytest -q -p no:cacheprovider $(rg -l -i fixval tests -g '*.py'), 24 files).test_fixval_junit_witness.pyalso passes under Python 3.12.15 (92 passed), the version CI pins.scripts/lint_changed.py --base origin/mainwith ruff 0.17.3 is clean.Commits:
f8a9bcc: the JUnit RED rule.590054a: only adds# noqa: S102to an existingexecintests/test_fixval.py. Touching that file makes the changed-files lint check all of it.e127032: unexpected passes are not RED, and the report goes in a private directory.838be08: tests only. An unexpected pass before a real<failure>in one testcase, and a report directory thatos.rmdircannot remove (this test kills theignore_errorsmutant).bb90558: submodule imports, multi-line imports, renames and copies,--junitxmlbefore--, and the known-limit comments.f39df7d: backslash-continued imports, and tests for the opening-line comment, function-level imports, imports left open across hunks and theM.X/No module namedboundaries.bdcdba7: a\at the end of a comment continues no import, and tests for the comment-backslash case, a plain import, a\inside parentheses and theNo module namedparent cases.