| """Tests for the process-lifetime in-memory AnnData cache (src/cache.py). |
| |
| These guard the two correctness properties the persistent HTTP server relies on: |
| 1. Each read returns an independent copy, so a caller mutating (or writing |
| back) the result cannot corrupt the shared, read-mostly cache — this is |
| what makes one resident server safe to share across concurrent sessions. |
| 2. A rewritten file (newer mtime/size) is transparently re-read instead of |
| serving a stale parse. |
| """ |
|
|
| import time |
|
|
| import anndata as ad |
| import numpy as np |
| import pytest |
|
|
| from src import cache |
|
|
|
|
| @pytest.fixture(autouse=True) |
| def _clear_cache(): |
| cache._MEM_CACHE.clear() |
| yield |
| cache._MEM_CACHE.clear() |
|
|
|
|
| def _write(path, fill): |
| a = ad.AnnData(X=np.full((10, 5), float(fill))) |
| a.write_h5ad(path) |
| return a |
|
|
|
|
| def test_second_read_hits_cache_single_entry(tmp_path): |
| p = tmp_path / "x.h5ad" |
| _write(p, 1) |
| cache.read_h5ad_cached(str(p)) |
| cache.read_h5ad_cached(str(p)) |
| assert len(cache._MEM_CACHE) == 1 |
|
|
|
|
| def test_returns_independent_copies(tmp_path): |
| p = tmp_path / "x.h5ad" |
| _write(p, 1) |
| a = cache.read_h5ad_cached(str(p)) |
| b = cache.read_h5ad_cached(str(p)) |
| assert a is not b |
| |
| a.X[:] = 999 |
| c = cache.read_h5ad_cached(str(p)) |
| assert float(c.X.mean()) == 1.0 |
|
|
|
|
| def test_rewrite_invalidates_stale_entry(tmp_path): |
| p = tmp_path / "x.h5ad" |
| _write(p, 1) |
| assert float(cache.read_h5ad_cached(str(p)).X.mean()) == 1.0 |
| time.sleep(1.1) |
| _write(p, 7) |
| assert float(cache.read_h5ad_cached(str(p)).X.mean()) == 7.0 |
| |
| assert len(cache._MEM_CACHE) == 1 |
|
|
|
|
| def test_missing_path_falls_through(tmp_path): |
| with pytest.raises(Exception): |
| cache.read_h5ad_cached(str(tmp_path / "nope.h5ad")) |
|
|
|
|
| def test_eviction_bounds_entries(tmp_path): |
| for i in range(cache._MEM_MAX_ENTRIES + 3): |
| p = tmp_path / f"d{i}.h5ad" |
| _write(p, i) |
| cache.read_h5ad_cached(str(p)) |
| assert len(cache._MEM_CACHE) <= cache._MEM_MAX_ENTRIES |
|
|