Run the scheduler constraint tests under both backends - #2385
Conversation
Since #2364 the schedulers build the same model twice, once through Pyomo and once directly in HiGHS, so a test of scheduler behaviour only means something under one backend if the two agree -- which is the thing that cannot be assumed. Only test_solver.py was parametrized, so most constraint behaviour was verified under the configured default and no other. That is how a fix could pass on the Pyomo path and fail on the direct one, as happened on #2355. A module now opts in with RUN_UNDER_EACH_SOLVER = True and an autouse fixture in conftest does the switching, so no test signature changes. Enabled on test_group_constraints.py and test_operation_modes.py (27 tests, now 54). test_commitments.py and test_storage.py are NOT enabled, and the reason is worth recording: they build assets with fixed names, so running each test twice in one fixture scope violates generic_asset's unique-name constraint. Parametrizing them means making those fixtures unique per parameter first, which is a larger change than this one. Rather than leave that hole silent, test_solver_coverage.py asserts every planning test module either opts in or appears on an EXEMPT list with a reason, and that no EXEMPT entry is stale. A new module is then a decision someone makes on purpose instead of a gap nobody notices. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2jLjsQzwztDc7Nxz1ozmQ Signed-off-by: F.N. Claessen <claessen@seita.nl>
Documentation build overview
|
Flix6x
left a comment
There was a problem hiding this comment.
At least show that the exempt modules would still succeed when all their tests would use the other solver.
The exempt modules could not be parametrized in-process, but that left their behaviour under the non-default backend simply unknown, which is a weaker position than it needed to be. Adds a --lp-solver option pinning a whole run to one backend, so those modules can be run again under the other one. (An environment variable would not do: TestingConfig does not read FLEXMEASURES_LP_SOLVER.) Result: test_commitments.py, test_storage.py and test_process.py -- 68 tests -- all pass under appsi_highs, the non-default backend. So the exemption costs per-test granularity, not coverage. Checked the flag actually bites rather than silently doing nothing: running with --lp-solver=definitely_not_a_solver fails with Pyomo's UnknownSolver, so the option does reach solver selection. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2jLjsQzwztDc7Nxz1ozmQ Signed-off-by: F.N. Claessen <claessen@seita.nl>
|
Good call — "cannot be parametrized" had quietly become "unverified", which is a weaker claim than the situation warranted. Added a Result — all three exempt modules pass under So the exemption costs per-test granularity, not coverage: those modules are green under both backends, just not within one run. I also checked the flag actually bites rather than silently doing nothing — The command is recorded next to the |
Every docstring and comment block added here broke lines mid-phrase, against the convention this repo documents and that #2384 had just restated -- "run once per / backend", "under one backend / if the two agree", "set from the / environment". Reflowed so each physical line ends after punctuation. Text only. Checked mechanically rather than by eye this time: inside a multi-line docstring or comment block, every line but the last must end in punctuation. The only remaining hits in the added lines are the two continuation lines of an example shell command, which end in a backslash by nature. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2jLjsQzwztDc7Nxz1ozmQ Signed-off-by: F.N. Claessen <claessen@seita.nl>
|
Reflowed in I've stopped checking this by eye, since eyeballing is what failed twice. The rule is mechanical — inside a multi-line docstring or comment block, every line but the last must end in punctuation — so I checked it that way, and separated hits in lines this PR adds from pre-existing ones. The only remaining hits in added lines are the two continuation lines of an example shell command, which end in a backslash by nature. The pre-existing hits in If it would be useful, that check is about 30 lines and could go in as a pre-commit hook, so the convention stops depending on a reviewer noticing. Happy to open it separately — it would have caught this, #2364's, and #2218's. |
A code comment telling the reader to consult a pull request description ages badly and does not belong in the tree. Replaced with the fact it was pointing at: these modules pass under the other backend when a run is pinned with --lp-solver. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2jLjsQzwztDc7Nxz1ozmQ Signed-off-by: F.N. Claessen <claessen@seita.nl>
The docstring convention -- break lines only after punctuation -- has been missed in three PRs in a row (#2364, #2218, #2385), each time caught by a reviewer rather than by the author. #2384 documented the rule harder, and the very next PR broke it, so documentation is evidently not the missing piece. Adds .claude/hooks/reflow_check.py and wires it as a PreToolUse hook on `git commit`, next to the existing pre-commit and worktree-guard hooks. Two deliberate choices: It is an agent hook, not a pre-commit hook. The misses have been an agent's, and contributors should not pay a false-positive tax for that. It is also the only form that works: the check is a heuristic, and a heuristic gate that a human hits on embedded OpenAPI YAML gets disabled, whereas an agent can read "line 2028 may break mid-phrase", look, and judge. It is advisory and never blocks. It exits 0 always, and only inspects lines being added, so a legacy file is not a wall -- the repo has ~4350 candidate hits, and reflowing unrelated prose is not a thing a commit should do. Suppressing non-prose (bullets, RST directives, doctests, embedded YAML and JSON, shell continuations) cuts the noise substantially where docstrings carry API specs: sensors.py 494 hits to 59, assets.py 385 to 15. Files that are genuinely prose-heavy stay high (storage.py 199 to 173), which is the honest answer rather than a tuned one. Self-tested both directions: a docstring broken mid-phrase is reported, a correctly reflowed one is silent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2jLjsQzwztDc7Nxz1ozmQ Signed-off-by: F.N. Claessen <claessen@seita.nl>
The docstring convention -- break lines only after punctuation -- has been missed in three PRs in a row (#2364, #2218, #2385), each time caught by a reviewer rather than by the author. #2384 documented the rule harder, and the very next PR broke it, so documentation is evidently not the missing piece. Adds .claude/hooks/reflow_check.py and wires it as a PreToolUse hook on `git commit`, next to the existing pre-commit and worktree-guard hooks. Two deliberate choices: It is an agent hook, not a pre-commit hook. The misses have been an agent's, and contributors should not pay a false-positive tax for that. It is also the only form that works: the check is a heuristic, and a heuristic gate that a human hits on embedded OpenAPI YAML gets disabled, whereas an agent can read "line 2028 may break mid-phrase", look, and judge. It is advisory and never blocks. It exits 0 always, and only inspects lines being added, so a legacy file is not a wall -- the repo has ~4350 candidate hits, and reflowing unrelated prose is not a thing a commit should do. Suppressing non-prose (bullets, RST directives, doctests, embedded YAML and JSON, shell continuations) cuts the noise substantially where docstrings carry API specs: sensors.py 494 hits to 59, assets.py 385 to 15. Files that are genuinely prose-heavy stay high (storage.py 199 to 173), which is the honest answer rather than a tuned one. Self-tested both directions: a docstring broken mid-phrase is reported, a correctly reflowed one is silent. Claude-Session: https://claude.ai/code/session_01X2jLjsQzwztDc7Nxz1ozmQ Signed-off-by: F.N. Claessen <claessen@seita.nl> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Description
Since #2364 the schedulers build the same model twice — once through Pyomo, once directly in HiGHS — so a test of scheduler behaviour only means something under one backend if the two agree, which is exactly what cannot be assumed.
Only
test_solver.pywas parametrized over solvers. Everything else ran under the configured default and no other.That is not hypothetical: on #2355 a fix passed on the Pyomo path and failed on the direct one, and
test_commitments.pycould not have caught it, because it only ever runs under one solver.RUN_UNDER_EACH_SOLVER = True; an autouse fixture inconftest.pydoes the switching, so no test signatures changetest_group_constraints.pyandtest_operation_modes.py(27 tests → 54)test_solver_coverage.pymakes the remaining hole explicitWhat is deliberately not enabled, and why
test_commitments.pyandtest_storage.pybuild assets with fixed names, so running each test twice in one fixture scope violatesgeneric_asset's unique-name constraint. I tried it; it fails on both backends withUniqueViolation, which is a fixture problem rather than a backend disagreement. Parametrizing them means making those fixtures unique per parameter first — a larger change than this one, and better done on its own.Rather than leave that silent,
test_solver_coverage.pyasserts that every planning test module either opts into the matrix or appears on anEXEMPTlist with a reason, and that noEXEMPTentry is stale (module gone, or since opted in). Adding a module then forces a deliberate choice instead of quietly widening the gap.How to test
test_group_constraints.pyandtest_operation_modes.pynow report[appsi_highs]/[highspy]variants.Proven capable of failing (per #2384): removing
RUN_UNDER_EACH_SOLVERfrom a module makestest_scheduler_modules_run_under_each_solver_or_are_exemptfail with that module named; adding a module toEXEMPTwhile it still opts in makestest_exempt_list_has_no_stale_entriesfail.Further improvements
test_commitments.py/test_storage.pyunique per parameter, then drop those two fromEXEMPT.Related items