""" 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