fix(tests): make the suite hermetic against real user state - #76
Open
Mokuroh54 wants to merge 3 commits into
Open
fix(tests): make the suite hermetic against real user state#76Mokuroh54 wants to merge 3 commits into
Mokuroh54 wants to merge 3 commits into
Conversation
…t touch real calibration `tmp_lerobot_home` patched the constants on `makermodslab.utils.config`, which reaches every consumer that reads them THROUGH the module — but not a `from .utils.config import FOLLOWER_CONFIG_PATH`, which binds a COPY of the string at import time. Seven such copies exist (auto_calibrate x3, arm_identity x2, server x2) and none of them were redirected by anything in the suite. That is not theoretical on this branch: the calibration-config DELETE endpoint builds its `os.remove()` target from server.py's own copy of FOLLOWER_CONFIG_PATH, and no test repoints it, while the developer's real `calibration/robots/so_follower/home.json` sits at the other end. The follower side had no patching at all; `tests/test_server.py` repoints only LEADER_CONFIG_PATH, and says so in a comment. - Add `_FROM_IMPORTED_PATH_CONSTANTS` (the seven copies) and repoint each one on its own module inside `tmp_lerobot_home`. - Extend the fixture's guard into a probe over every config constant AND every from-imported copy, via `_assert_outside`, so an unpatched path fails loudly at fixture time instead of being discovered by a write. - Add a session-scoped autouse canary that fingerprints the real `calibration/**` tree (mtime+size+sha1, `.DS_Store` ignored, absent-dir safe for CI) and fails the session loudly if anything changed. - Correct the `tmp_lerobot_home` docstring, which claimed from-imports were covered by patching `cfg`. Root cure is a call-time-accessor refactor of utils/config.py (follow-up), after which `_FROM_IMPORTED_PATH_CONSTANTS` empties and the guard becomes a pure regression detector. Conftest protections derived from b95680a, whose test-side half targets rig: the three stray-cleanup tests it repoints were added by 5e95ea9, which is not on main, so only the conftest half applies here and is authored fresh against main's actual fixture rather than as a widening. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… and HF identity
On a clean CI runner the suite is green; on a developer's populated machine
`tests/test_models.py` fails 7 tests before any change of ours, because the
model listing folds in the real `saved_custom_models.json` — a listing asserted
to be `["imported_policy"]` also carries the developer's real pins. The dataset
listing has the same shape of leak in both directions (real pins added, real
`hidden_datasets.json` entries silently subtracting a row a test just seeded);
it is latent rather than failing on the current machine state.
`tmp_lerobot_home` never covered the four saved-repo JSON files, and the listing
tests that don't request that fixture got no redirection at all.
- Add one autouse `_isolate_real_user_state` in conftest covering all six
redirects — the four SAVED_CUSTOM/SAVED_HIDDEN datasets+models files,
`HF_LEROBOT_HOME`, and `huggingface_hub.constants.HF_HUB_CACHE` — into
per-test tmp dirs. Consolidated deliberately: the same fixture was drafted
twice, once per test file, and belongs in one place. Plain monkeypatches, so
`tmp_lerobot_home` and per-test overrides still win — a floor, not a wall.
- Probe HF_HUB_CACHE in `tmp_lerobot_home` now that something patches it.
- Add autouse `_stub_cached_whoami`. `cached_whoami()` calls `get_token()`, so
an unmocked test resolves the developer's real username and orgs, which decide
default repo owners and which Hub rows survive a listing. Same from-import
disease as the path constants, so the same per-module cure: the five copies
(server, models, datasets, jobs, runners/hf_cloud) are stubbed to `None` — the
"not logged in" shape the existing per-test mocks already use. `hf_auth`'s own
`cached_whoami` is left real so tests/test_utils_hf_auth.py keeps testing the
genuine caching behaviour, and per-test mocks still override.
- Drop `_reset_model_cache` from tests/test_models.py: conftest's
`_reset_hub_listing_caches` already calls `invalidate_model_listing_cache()`,
so the fixture was redundant and its docstring ("but not this one") false.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mokuroh54
added a commit
that referenced
this pull request
Aug 11, 2026
Addresses the three findings on PR #74. All three reproduce against the branch as it stood; each now has a regression test. 1. Only the first read was watchdogged; later reads were a raw cap.read() under the entry lock. A device dying mid-stream left the generator blocked inside cv2 forever, so the MJPEG response never completed and uvicorn's graceful shutdown waited on it — the exact hang this PR says it contains. _read_first_frame becomes _read_with_deadline and every read goes through it (READ_TIMEOUT for the steady state, the existing FIRST_FRAME_TIMEOUT for the first). A watchdog thread per frame costs ~25us, ~0.04% of a core at TARGET_FPS. 2. A wedge deregistered its entry, so the next client built a fresh one and opened a fresh capture on the same dead device. The preview tile retries forever by design, so that leaked a thread and a handle every 12s: measured 10 captures and 10 stranded threads over 10 retries. The entry is now kept as a tombstone (_mark_wedged), which is what the class docstring already claimed and what identity keying made safe. Capped at one leaked thread and one capture per wedged camera. stop_all skips tombstones rather than re-waiting the lock on each Start. 3. LOCK_ACQUIRE_TIMEOUT (2s) sat below FIRST_FRAME_TIMEOUT (5s), so a contender gave up sooner than a healthy camera is allowed to spend on a first read: a second tile on a camera with a 3s warm-up refused it and marked it wedged. The contention timeout is now derived to sit above every read deadline, which is also what makes "lock still held => the reader is wedged" a sound inference for the first time. Full suite: 1276 passed; the 7 test_models failures are pre-existing on this base (real-user-state leakage, fixed separately by PR #76).
…nted before it Ported from the parked fix/stray-cleanup-tests-real-calibration branch (b95680ae): the three stray-cleanup tests now have the fake subprocess write the calibration file DURING the run (new _follower_file_path helper) instead of planting it before start(). A file that exists before start() is, by construction, indistinguishable from the user's previous calibration under the same name, which the cleanup's design deliberately preserves (P0-1 / C1) — planting made the tests assert removal of something the design says to keep. b95680ae's conftest hardening is already on this branch in evolved form; this test-fidelity piece was the one part left behind. Verified: tests/test_auto_calibrate.py 56 passed; pre-commit --all-files clean; real calibration tree untouched (session canary passed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Why
The suite reads and writes the developer's real
~/.cache/huggingface/in several places. On a clean CI runner that is invisible, which is exactly why it survived: every one of these is green in CI and only misbehaves on a machine that has actually been used.1. The unprotected from-import path (the one with a hardware cost)
tmp_lerobot_homepatched the path constants onmakermodslab.utils.config. That reaches every consumer reading them through the module — but not afrom .utils.config import FOLLOWER_CONFIG_PATH, which binds a copy of the string at import time. Rebinding the name oncfgafterwards leaves that copy pointing at the real~/.cache.Seven such copies exist (
auto_calibrate×3,arm_identity×2,server×2) and nothing in the suite redirected any of them.The concrete hazard verified on this branch: the calibration-config DELETE endpoint (
server.py) builds itsos.remove()target fromserver.py's own copy ofFOLLOWER_CONFIG_PATH. No test repoints it —tests/test_server.pyrepoints onlyLEADER_CONFIG_PATH, and says so in a comment — while the developer's realcalibration/robots/so_follower/home.jsonsits at the other end. Recovering that file costs a physical recalibration.2. Real pins / hidden lists leaking into listing assertions
tests/test_models.pyfails 7 tests on an unmodifiedmainon a populated machine: the model listing folds in the realsaved_custom_models.json, so a listing asserted to be["imported_policy"]also carries the developer's real pins (lerobot/smolvla_base, …). The dataset listing leaks the same way in both directions — real pins added, realhidden_datasets.jsonentries silently subtracting a row a test just seeded.tmp_lerobot_homenever covered these four files, and listing tests that don't request it got no redirection at all.3. Real HF identity
cached_whoami()callsget_token(), so an unmocked test resolves the developer's real username and orgs — which decide default repo owners and which Hub rows survive a listing.What changed
Guardrails
_FROM_IMPORTED_PATH_CONSTANTS— the seven from-imported copies, each repointed on its own module insidetmp_lerobot_home.REFUSING TO RUNprobe over every config constant and every from-imported copy, so an unpatched path fails at fixture time instead of being discovered by a write.calibration/**tree (mtime+size+sha1,.DS_Storeignored, absent-dir safe for CI) that fails the session loudly if anything changed._isolate_real_user_state) covering all six redirects: the fourSAVED_CUSTOM_*/SAVED_HIDDEN_*files,HF_LEROBOT_HOME, andhuggingface_hub.constants.HF_HUB_CACHE. Plain monkeypatches, sotmp_lerobot_homeand per-test overrides still win — a floor, not a wall._stub_cached_whoami— same from-import disease, same per-module cure: the five copies (server,models,datasets,jobs,runners/hf_cloud) stubbed toNone, the "not logged in" shape existing per-test mocks already use.hf_auth.cached_whoamiis left real sotests/test_utils_hf_auth.pykeeps testing genuine caching behaviour; per-test mocks still override.Cleanup
tmp_lerobot_homedocstring, which claimed from-imports were covered by patchingcfg._reset_model_cachefromtests/test_models.py: conftest's_reset_hub_listing_cachesalready callsinvalidate_model_listing_cache(), so it was redundant and its docstring ("but not this one") was false.tests/conftest.pyrather than duplicated.Verification
HF_HUB_OFFLINE=1, beforetest_models, 1 ×test_list_all_datasets_merges_hub_and_local)HF_HUB_OFFLINE=1, after~/.cache/huggingface/lerobotsha256 manifest (1053 files)ruff check/ruff format --checkpre-commit run --all-filesThe populated-HOME simulation seeds an adversarial HOME-shaped tree — pins, hidden lists naming ids the tests seed themselves, local dataset dirs, and a hub-cache entry — to prove the redirects hold rather than merely that CI is empty.
Deferred / follow-ups
utils/config.py. After it,_FROM_IMPORTED_PATH_CONSTANTSempties and the guard becomes a pure regression detector. The list is a stopgap that must be kept in sync withgrep -rn "from .utils.config import" makermodslab/.tests/test_auto_calibrate.py(added by5e95ea9, which is not onmain) write throughauto_calibrate's copy ofCALIBRATION_BASE_PATH_ROBOTSinto the real follower calibration. Their fix is commitb95680a, which must land onrigbefore the P0 safety train carve — this PR targetsmain, where neither those tests nor the feature they cover exist. The conftest protections here are derived fromb95680abut authored fresh againstmain's actual fixture.tests/test_jobs.pycloud-resume tests fail under a token-less fake HOME (test_local_parent_resumed_on_the_cloud_*). Identical failures at baseline, so not a regression from this PR, but they indicate those tests depend on ambient HF auth — the same family of leak, worth a separate pass.🤖 Generated with Claude Code