c7ed24483d
- Introduced `test_final_level_flow.py` to validate final level transitions and game end scenarios. - Created `test_game_over_flow.py` to ensure game over conditions trigger correctly based on rat counts. - Implemented `test_keybindings.py` to verify keybinding configurations and their context-specific actions. - Developed `test_level_editor.py` to assess level editor functionalities and layout computations. - Added `test_level_io.py` for testing level data serialization and deserialization. - Established `test_loop_logic_parity.py` to ensure consistent game state across multiple simulation runs. - Created `test_non_regression.py` to simulate game behavior and capture states for future verification. - Implemented `test_verify.py` to compare current game states against a golden master for regression detection.
124 lines
4.2 KiB
Python
124 lines
4.2 KiB
Python
|
|
import os
|
|
import sys
|
|
import random
|
|
import unittest
|
|
import json
|
|
from pathlib import Path
|
|
from PIL import Image
|
|
import numpy as np
|
|
|
|
# Add current directory to path
|
|
sys.path.append(os.getcwd())
|
|
|
|
# Set SDL to use dummy video driver for headless environments
|
|
os.environ["SDL_VIDEODRIVER"] = "dummy"
|
|
os.environ["SDL_AUDIODRIVER"] = "dummy"
|
|
os.environ["MICE_DISABLE_JOYSTICK"] = "1"
|
|
|
|
from rats import MiceMaze
|
|
from engine.sdl2 import GameWindow
|
|
|
|
def mock_init_audio(self):
|
|
self.music_enabled = False
|
|
self.audio_devs = {"base": 0, "effects": 0, "music": 0}
|
|
self.sound_volume = 0
|
|
self.music_volume = 0
|
|
|
|
import uuid
|
|
|
|
# Global counter for deterministic UUIDs
|
|
_uuid_counter = 0
|
|
def mock_uuid4():
|
|
global _uuid_counter
|
|
val = _uuid_counter
|
|
_uuid_counter += 1
|
|
return val
|
|
|
|
class NonRegressionVerification(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
# Deterministic UUIDs
|
|
uuid.uuid4 = mock_uuid4
|
|
GameWindow.show_intro = lambda *args, **kwargs: None
|
|
GameWindow.show_loading_screen = lambda *args, **kwargs: None
|
|
GameWindow._init_audio_system = mock_init_audio
|
|
GameWindow.play_sound = lambda *args, **kwargs: None
|
|
GameWindow.stop_sound = lambda *args, **kwargs: None
|
|
|
|
def setUp(self):
|
|
global _uuid_counter
|
|
_uuid_counter = 0
|
|
random.seed(42)
|
|
self.game = MiceMaze("assets/Rat/level.dat", level_index=0)
|
|
|
|
# Re-seed again to clear entropy consumed by asset loading
|
|
random.seed(42)
|
|
_uuid_counter = 0
|
|
self.game.start_game()
|
|
|
|
# Trigger background generation once to consume those random calls
|
|
# before the simulation starts, ensuring stability.
|
|
self.game.graphics.draw_maze()
|
|
|
|
self.game.game_status = "game"
|
|
self.game.menu_screen = None
|
|
|
|
def test_verify_against_golden_master(self):
|
|
golden_master_dir = Path("tests/golden_master")
|
|
if not golden_master_dir.exists():
|
|
self.skipTest("Golden master not found. Run recording first.")
|
|
|
|
with open(golden_master_dir / "states.json", "r") as f:
|
|
golden_states = json.load(f)
|
|
|
|
steps = 200
|
|
golden_idx = 0
|
|
|
|
print(f"Verifying against golden master for {steps} steps...")
|
|
|
|
for i in range(steps):
|
|
self.game.update_maze()
|
|
|
|
if i % 50 == 0 or i == steps - 1:
|
|
current_state = self.dump_game_state()
|
|
golden_state = golden_states[golden_idx]
|
|
|
|
# Compare unit count
|
|
self.assertEqual(current_state["unit_count"], golden_state["unit_count"],
|
|
f"Unit count mismatch at step {i}")
|
|
|
|
# Compare units
|
|
for u_idx, (curr_u, gold_u) in enumerate(zip(current_state["units"], golden_state["units"])):
|
|
self.assertEqual(curr_u["id"], gold_u["id"], f"Unit ID mismatch at step {i}, index {u_idx}")
|
|
self.assertEqual(curr_u["type"], gold_u["type"], f"Unit type mismatch at step {i}, unit {curr_u['id']}")
|
|
self.assertEqual(curr_u["pos"], gold_u["pos"], f"Unit pos mismatch at step {i}, unit {curr_u['id']}")
|
|
self.assertAlmostEqual(curr_u["partial_move"], gold_u["partial_move"], places=5,
|
|
msg=f"Unit partial_move mismatch at step {i}, unit {curr_u['id']}")
|
|
|
|
golden_idx += 1
|
|
|
|
print("Verification successful! No regressions detected.")
|
|
|
|
def dump_game_state(self):
|
|
state = {
|
|
"points": self.game.points,
|
|
"unit_count": len(self.game.units),
|
|
"units": []
|
|
}
|
|
# Sort units by ID to be deterministic
|
|
sorted_units = sorted(self.game.units.items(), key=lambda x: int(x[0]))
|
|
|
|
for uid, unit in sorted_units:
|
|
unit_state = {
|
|
"id": str(uid),
|
|
"type": unit.__class__.__name__,
|
|
"pos": list(unit.position),
|
|
"partial_move": float(unit.partial_move),
|
|
}
|
|
state["units"].append(unit_state)
|
|
return state
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|