Spaces:
Running
Running
| """Tests for dee/core/scoring.py — the shared ESM-2 scorer cache and | |
| scoring concurrency guard used by both dee/server.py's /api/run pipeline | |
| and dee/core/agent_tools.py's design_variant_library tool. | |
| """ | |
| import threading | |
| import time | |
| import pandas as pd | |
| import pytest | |
| from dee.core import scoring | |
| def _semaphore_not_leaked(): | |
| """Every test must return SCORING_SEMAPHORE to its initial (available) | |
| state — a leaked acquire would hang every later test using it.""" | |
| yield | |
| acquired = scoring.SCORING_SEMAPHORE.acquire(timeout=0) | |
| assert acquired, "a test left SCORING_SEMAPHORE held" | |
| scoring.SCORING_SEMAPHORE.release() | |
| def _clear_scorer_cache(): | |
| scoring._SCORER_CACHE.clear() | |
| yield | |
| scoring._SCORER_CACHE.clear() | |
| def test_effective_model_downgrades_gpu_models_without_gpu(monkeypatch): | |
| monkeypatch.setattr(scoring, "gpu_available", lambda: False) | |
| assert scoring.effective_model("medium") == "small" | |
| assert scoring.effective_model("large") == "small" | |
| assert scoring.effective_model("small") == "small" | |
| assert scoring.effective_model(None) == "small" | |
| def test_effective_model_keeps_gpu_models_with_gpu(monkeypatch): | |
| monkeypatch.setattr(scoring, "gpu_available", lambda: True) | |
| assert scoring.effective_model("medium") == "medium" | |
| assert scoring.effective_model("large") == "large" | |
| def test_get_scorer_caches_by_effective_config(monkeypatch): | |
| monkeypatch.setattr(scoring, "gpu_available", lambda: False) | |
| built = [] | |
| class _FakeScorer: | |
| def __init__(self, config): | |
| built.append(config) | |
| monkeypatch.setattr("dee.models.scorer.ESM2Scorer", _FakeScorer) | |
| a = scoring.get_scorer("small") | |
| b = scoring.get_scorer("small") | |
| # 'medium' downgrades to 'small' on a no-GPU host — same cache key. | |
| c = scoring.get_scorer("medium") | |
| assert a is b is c | |
| assert len(built) == 1 | |
| def test_get_scorer_separate_cache_entries_per_model(monkeypatch): | |
| monkeypatch.setattr(scoring, "gpu_available", lambda: True) | |
| built = [] | |
| class _FakeScorer: | |
| def __init__(self, config): | |
| built.append(config.model_name) | |
| monkeypatch.setattr("dee.models.scorer.ESM2Scorer", _FakeScorer) | |
| scoring.get_scorer("small") | |
| scoring.get_scorer("medium") | |
| assert built == ["small", "medium"] | |
| def test_score_guarded_calls_scorer_and_returns_result(): | |
| df = pd.DataFrame({"position": [0], "wt_aa": ["A"], "mut_aa": ["G"], "delta_ll": [1.0]}) | |
| class _Scorer: | |
| def score_all_substitutions(self, protein): | |
| assert protein == "ACDE" | |
| return df | |
| result = scoring.score_guarded(_Scorer(), "ACDE") | |
| assert result is df | |
| def test_score_guarded_raises_busy_when_semaphore_held(): | |
| scoring.SCORING_SEMAPHORE.acquire() # simulate another request scoring | |
| try: | |
| class _Scorer: | |
| def score_all_substitutions(self, protein): | |
| raise AssertionError("must not run while the semaphore is held") | |
| with pytest.raises(scoring.ScoringBusyError): | |
| scoring.score_guarded(_Scorer(), "ACDE", wait_timeout=0.05) | |
| finally: | |
| scoring.SCORING_SEMAPHORE.release() | |
| def test_score_guarded_blocks_then_succeeds_once_released(): | |
| scoring.SCORING_SEMAPHORE.acquire() | |
| def _release_soon(): | |
| time.sleep(0.1) | |
| scoring.SCORING_SEMAPHORE.release() | |
| threading.Thread(target=_release_soon, daemon=True).start() | |
| class _Scorer: | |
| def score_all_substitutions(self, protein): | |
| return "scored" | |
| # No wait_timeout — blocks until the background thread releases, then | |
| # proceeds. If this hangs, the semaphore isn't being released/acquired | |
| # correctly. | |
| result = scoring.score_guarded(_Scorer(), "ACDE", wait_timeout=2.0) | |
| assert result == "scored" | |
| def test_score_guarded_releases_semaphore_even_if_scorer_raises(): | |
| class _Scorer: | |
| def score_all_substitutions(self, protein): | |
| raise RuntimeError("boom") | |
| with pytest.raises(RuntimeError): | |
| scoring.score_guarded(_Scorer(), "ACDE") | |
| # The autouse _semaphore_not_leaked fixture asserts this held true; | |
| # this test exists to name the specific behavior it's protecting. | |