test(executor): add unit tests for wrap_executor and its helpers#782
test(executor): add unit tests for wrap_executor and its helpers#782pmrv wants to merge 3 commits into
Conversation
The integration suite exercises wrap_executor end-to-end through real thread and process pools, but the module's own control flow — _is_fleche_function, _split_submit_kwargs (both branches of the inspect.signature try/except), the already-wrapped shortcut, and the cache-hit/miss branches inside the patched submit — was not covered by any unit-level test. Coverage of src/fleche/executor.py from tests/unit was 16%; with these 15 in-process tests it is 100% (statements and branches). The tests use lightweight executor stubs (_StubExecutor, _NeverSubmitExecutor, _KeywordOnlyExecutor) so each branch runs without paying for a subprocess spin-up.
|
🤖 CI failure summary for Summary Root cause
The problem is that Suggested fix
This ensures idempotence so that pre-bound wrappers aren't double-wrapped. Generated by Claude Haiku 4.5. |
|
@claude ^ |
|
Claude finished @pmrv's task in 6m 48s —— View job Root cause found — fixed a test-isolation bug, not
|
…ate) Reloading fleche.state in place (in the restore_fleche_state fixture used by three config tests) gives it fresh class objects, e.g. a new BoundWrapper, but fleche/__init__.py re-exports the same names via `from .state import ...` bound at package-import time. Without also reloading fleche itself, fleche.BoundWrapper kept pointing at the pre-reload class while executor.py's `state.BoundWrapper` (looked up fresh via `from . import state`) picked up the new one. That silent divergence broke the new isinstance(func, BoundWrapper) checks in tests/unit/test_executor.py for every test running after the config tests in the same session, since both classes have the same repr. Co-authored-by: Marvin Poul <2719909+pmrv@users.noreply.github.com>
|
@claude still broken |
|
Claude finished @pmrv's task in 4m 0s —— View job Fixed — real root cause this time
Why the previous fix ( Actual fix ( Verification note: as with the previous pass, I could not execute |
The prior fix (a793292) reloaded fleche/__init__.py to resync fleche.BoundWrapper with the new class object created by importlib.reload(fleche.state). But tests/unit/test_executor.py does `from fleche import BoundWrapper` at module-collection time, before any test runs, so its reference stays frozen to the pre-reload class regardless of any later resync -- reproducing the same isinstance failures. Instead, restore the original BoundWrapper class object onto the reloaded module. Since importlib.reload re-executes a module in its existing __dict__, the restored class's bind() classmethod still resolves the freshly reloaded _CACHE/_METADATA ContextVars -- only the class identity is preserved, so every reference taken before or after the reload (in this file, test_executor.py, or elsewhere) keeps pointing at the same class. Co-authored-by: Marvin Poul <2719909+pmrv@users.noreply.github.com>
Motivation
A branch-coverage pass over
tests/unit(pytest-cov,--cov-branch)identified
src/fleche/executor.pyas the least covered module in thepackage: 16.0% of statements, all 12 branches missed. Every other
src/fleche/*module was above 80%, most above 93%.The gap comes from
wrap_executorand its two helpers(
_is_fleche_function,_split_submit_kwargs) only being reachedend-to-end through the process-pool tests in
tests/integration/test_parallel_execution.py— the module itself hadno unit-level test file. That makes the executor branches a soft spot in
regression coverage: each subprocess spin-up is slow, and none of the
branches (already-wrapped shortcut; signature-parse fallback;
cache-hit-skips-submit) reads like something the integration suite is
guaranteed to keep exercising as the surrounding code evolves.
What this changes
Adds
tests/unit/test_executor.py, a 15-test file that drives themodule in isolation with three lightweight executor stubs:
_StubExecutor— records what gets submitted; runs the callablesynchronously in a completed
Future._NeverSubmitExecutor— raises on anysubmitcall, so acache-hit branch that fails to short-circuit fails loudly.
_KeywordOnlyExecutor— declares a keyword-onlyresources=param, modelling the HPC-executor shape (executorlib / dask) that
motivated
_split_submit_kwargs.Test groups:
TestIsFlecheFunctionSimpleNamespace.flecheattribute (branch coverage forisinstancecheck)TestSplitSubmitKwargsinspect.signatureTypeErrorfallback (viaunittest.mock.patch)TestWrapExecutorsubmit._fleche_wrapped, idempotent second wrap, non-flecheBoundWrapper.bind, already-BoundWrapperpassthrough (PR #724), cache-hit skips submit, cache-miss binds and forwards, keyword-only kwargs partitioned correctlyCoverage impact
Measured with
pytest tests/unit --cov=fleche --cov-branch:src/fleche/executor.pyin isolation goes from 16% / 0% branchto 100% / 100% branch (0 missing lines, 0 missing branches) — no
other module's coverage regresses.
tests/unitbranch coverage was 94.0% before; the 30 newstatements + 12 new branches lift it further, with no offsetting
drop elsewhere.
Test plan
pytest tests/unit/test_executor.py -v— all 15 pass locally.pytest tests/unit/test_executor.py --cov=flecheshowssrc/fleche/executor.pyat 100% statements + 100% branches.(no cloudpickle / no executorlib / no ssh path) — the file runs
on the minimum-deps CI matrix.
Generated by Claude Code