File size: 2,490 Bytes
bb3fbf9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
"""

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