"""Regression test for feature_cache content-based keying. Ensures cache doesn't collide when CPython reuses id() after GC. """ from __future__ import annotations from werewolftown.features.card_value import card_cluster_gain def test_cache_by_content_not_id(make_state): """Cache must key on state content, not id(). Different states must get different results.""" state_a = make_state( plots=frozenset({4, 5, 6}), built_on={4: "Happy", 5: "Happy"}, shops=(), ) state_b = make_state( plots=frozenset({4, 5, 7}), built_on={4: "Happy", 5: "Happy"}, shops=("Happy",), ) gain_a = card_cluster_gain(state_a, "Happy") gain_b = card_cluster_gain(state_b, "Happy") # state_a: empty 6 adjacent to Happy 2-cluster, 0 cards → gain=20000 # state_b: empty 7 not adjacent, 1 card already → gain=0 assert gain_a != gain_b, "Cache polluted: different states returned same result" assert gain_a == 20000 assert gain_b == 0 def test_cache_same_state_reuses(make_state): """Same state instance called twice must return cached result (no recompute).""" state = make_state( plots=frozenset({4, 5, 6}), built_on={4: "Happy", 5: "Happy"}, shops=(), ) # Clear cache first card_cluster_gain._cache.clear() gain1 = card_cluster_gain(state, "Happy") assert len(card_cluster_gain._cache) == 1 gain2 = card_cluster_gain(state, "Happy") assert gain1 == gain2 assert len(card_cluster_gain._cache) == 1 # No new entry def test_cache_survives_gc(make_state): """Cache must not break when intermediate states are GC'd.""" import gc state_main = make_state( plots=frozenset({4, 5, 6}), built_on={4: "Happy", 5: "Happy"}, shops=(), ) # Create and discard many states to trigger id reuse for i in range(100): _ = make_state(plots=frozenset({i}), round=i) gc.collect() # Original state should still return correct result gain = card_cluster_gain(state_main, "Happy") assert gain == 20000