LovecaSim / engine /tests /framework /test_property_fuzz.py
trioskosmos's picture
Upload folder using huggingface_hub
bb3fbf9 verified
"""
Property-based tests using Hypothesis.
Fuzzes the game engine with random action sequences to detect crashes and invariant violations.
"""
from hypothesis import HealthCheck, given, settings
from hypothesis import strategies as st
from engine.game.game_state import GameState
from engine.tests.framework.state_validators import StateValidator
# Strategy for generating a list of actions (0-800 covers most cases)
# We can refine this to be smarter, but random integers is a good "dumb fuzzer"
action_seq_strategy = st.lists(st.integers(min_value=0, max_value=800), min_size=1, max_size=50)
@settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.function_scoped_fixture], max_examples=10)
@given(action_seq_strategy)
def test_fuzz_random_actions_no_crash(data, actions):
"""
Fuzz test: execute random valid actions.
If this crashes, it means the engine accepts an action it claimed was legal
but failed to handle it.
"""
# Create a fresh game state
gs = GameState()
# Simple deck setup using loaded data
member_db, live_db = data
available_members = list(member_db.keys())
if not available_members:
return # Can't test without cards
import random
# Seed random for determinism within hypothesis loop?
# Hypothesis handles its own randomness. We just need a valid start state.
rng = random.Random(42)
for p in gs.players:
p.main_deck = [rng.choice(available_members) for _ in range(20)]
p.hand = [rng.choice(available_members) for _ in range(5)]
p.hand_added_turn = [0] * len(p.hand)
p.energy_zone = [2000] * 5 # Give some energy to allow play
# Check initial validity
StateValidator.assert_valid(gs)
for action in actions:
legal_mask = gs.get_legal_actions()
# Check boundary
if action < 0 or action >= len(legal_mask):
continue
if legal_mask[action]:
try:
# Execute legal action
# Note: step() returns NEW state (copy)
gs = gs.step(action)
# Check invariants
StateValidator.assert_valid(gs)
except Exception as e:
# Crash during LEGAL action is a fail
print(f"CRASH Action {action} in phase {gs.phase}")
print(f"Pending choices: {gs.pending_choices}")
raise e