headroom/tests/test_cache
Focused Instability 3b0bceecf4
fix(cache): name the missing piece in semantic detector guard (#1018)
## Description

The `test` and `test-extras` CI jobs are currently red on `main`:
`tests/test_cache/test_dynamic_detector.py::TestSemanticDetectorGuards::test_none_exemplars_early_return`
fails. This PR fixes the underlying regression. It is independent of any
feature branch (it only touches `headroom/cache/dynamic_detector.py` and
its test).

#950 folded the exemplar-embeddings None-check into the model
None-guard:

```python
if self._model is None or self._exemplar_embeddings is None:
    return [], self._load_error or "semantic detector is not initialized"
```

So a `SemanticDetector` with a loaded model but unset exemplar
embeddings now returns the generic *"semantic detector is not
initialized"* message, shadowing the specific *"exemplar embeddings not
initialized"* message and leaving the later guard as unreachable dead
code. That directly contradicts #950's own
`test_none_exemplars_early_return`, which asserts the specific message —
hence the red main.

## Type of Change

- [ ] New feature
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] Breaking change
- [ ] Documentation

## Changes Made

- `SemanticDetector.detect`: split the combined guard into two checks —
`_model` then `_exemplar_embeddings` — both *before* `encode()`. The
model-missing case keeps the generic message; the exemplar-missing case
reports the specific *"exemplar embeddings not initialized"*. Checking
before `encode()` avoids a wasted encode in the error path and preserves
the mypy narrowing for `np.dot(..., self._exemplar_embeddings.T)`. The
previously-shadowed duplicate guard is removed.
- `tests/test_cache/test_dynamic_detector.py`:
`test_missing_exemplar_embeddings_returns_warning` sets the same
model-present / exemplar-None state, so its assertion is aligned to the
specific message to match `test_none_exemplars_early_return`.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] New and existing unit tests pass locally with my changes

### Test Output

```text
$ pytest tests/test_cache/test_dynamic_detector.py -q
38 passed, 2 skipped in 9.94s

$ pytest tests/test_cache/ -q
198 passed, 2 skipped in 11.58s

$ ruff check headroom/cache/dynamic_detector.py tests/test_cache/test_dynamic_detector.py
All checks passed!

$ ruff format --check headroom/cache/dynamic_detector.py tests/test_cache/test_dynamic_detector.py
2 files already formatted

$ mypy headroom/cache/dynamic_detector.py
Success: no issues found in 1 source file
```

## Real Behavior Proof

- Environment: local macOS, repo .venv, Python 3.11.9, numpy installed
- Exact command / steps: construct a `SemanticDetector` via
`object.__new__` with `_model` set (mock) and `_exemplar_embeddings =
None`, then call `.detect(...)`. Before fix: on `upstream/main`
(7c8c909c) `test_none_exemplars_early_return` fails with `assert
'semantic detector is not initialized' == 'exemplar embeddings not
initialized'`. After fix: full file 38 passed, full `tests/test_cache/`
198 passed.
- Observed result: model-present + exemplar-None now returns `(spans=[],
"exemplar embeddings not initialized")`; model-None still returns the
generic message; `np.dot` is never reached with a None matrix.
- Not tested: live model load / real embeddings — the guards are the
unavailable-state paths, exercised via the existing mock-based unit
tests.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Additional Notes

This takes the **specific-message** direction because it matches #950's
newest test, the original (now-dead) specific guard string, and gives a
more actionable warning. The conservative **alternative** — keep the
generic unified message, delete the dead specific guard, and update
`test_none_exemplars_early_return` to assert the generic string — also
turns CI green with no production behavior change. Happy to switch to
that if you prefer; it's your call on the intended contract.
2026-06-15 16:29:52 -05:00
..
__init__.py Add cache optimization module with scalable dynamic content detection 2026-01-07 14:07:49 -08:00
test_anthropic.py Fix all ruff lint and format errors for CI 2026-01-10 15:33:44 -08:00
test_backends.py Add pluggable storage backend abstraction for CompressionStore 2026-01-20 23:25:28 -08:00
test_base.py Fix all ruff lint and format errors for CI 2026-01-10 15:33:44 -08:00
test_client_integration.py Fix ruff formatting 2026-01-17 17:14:12 -08:00
test_dynamic_detector.py fix(cache): name the missing piece in semantic detector guard (#1018) 2026-06-15 16:29:52 -05:00
test_google.py Fix all ruff lint and format errors for CI 2026-01-10 15:33:44 -08:00
test_openai.py Fix all ruff lint and format errors for CI 2026-01-10 15:33:44 -08:00
test_prefix_tracker.py fix: eliminate prefix cache busts from frozen count underestimation 2026-04-07 17:10:20 -07:00
test_registry.py Fix all ruff lint and format errors for CI 2026-01-10 15:33:44 -08:00
test_semantic.py Fix all ruff lint and format errors for CI 2026-01-10 15:33:44 -08:00