Add comprehensive test suite for game mechanics and level handling
- 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.
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from engine import controls
|
||||
|
||||
|
||||
class DummyBindings(controls.KeyBindings):
|
||||
def __init__(self, game=None):
|
||||
if game is None:
|
||||
game = MagicMock()
|
||||
game.menu_up = lambda: None
|
||||
game.menu_down = lambda: None
|
||||
game.menu_left = lambda: None
|
||||
game.menu_right = lambda: None
|
||||
game.reset_game = lambda: None
|
||||
super().__init__(game)
|
||||
|
||||
def spawn_rat(self):
|
||||
pass
|
||||
|
||||
def toggle_audio(self):
|
||||
pass
|
||||
|
||||
def toggle_full_screen(self):
|
||||
pass
|
||||
|
||||
def start_scrolling(self, direction):
|
||||
pass
|
||||
|
||||
def stop_scrolling(self):
|
||||
pass
|
||||
|
||||
def spawn_new_bomb(self):
|
||||
pass
|
||||
|
||||
def spawn_new_nuclear_bomb(self):
|
||||
pass
|
||||
|
||||
def spawn_new_mine(self):
|
||||
pass
|
||||
|
||||
def spawn_new_gas(self):
|
||||
pass
|
||||
|
||||
def spawn_gas(self, parent_id=None):
|
||||
pass
|
||||
|
||||
def toggle_pause(self):
|
||||
pass
|
||||
|
||||
def reset_game(self):
|
||||
pass
|
||||
|
||||
def quit_game(self):
|
||||
pass
|
||||
|
||||
def menu_up(self):
|
||||
pass
|
||||
|
||||
def menu_down(self):
|
||||
pass
|
||||
|
||||
def menu_left(self):
|
||||
pass
|
||||
|
||||
def menu_right(self):
|
||||
pass
|
||||
|
||||
|
||||
class KeybindingProfileTests(unittest.TestCase):
|
||||
def test_shipped_profiles_expose_all_weapon_actions(self):
|
||||
dummy = DummyBindings()
|
||||
conf_dir = Path(__file__).resolve().parent / "conf"
|
||||
weapon_actions = {
|
||||
"spawn_new_bomb",
|
||||
"spawn_new_nuclear_bomb",
|
||||
"spawn_new_mine",
|
||||
"spawn_new_gas",
|
||||
}
|
||||
|
||||
for config_path in sorted(conf_dir.glob("keybindings*.json")) + sorted(conf_dir.glob("keybindings*.yaml")):
|
||||
bindings = controls._load_bindings_from_file(config_path)
|
||||
validated = dummy._validate_bindings(bindings, config_path)
|
||||
game_bindings = validated.get("keybinding_game", {})
|
||||
self.assertTrue(game_bindings, f"missing keybinding_game in {config_path.name}")
|
||||
|
||||
actions = set(game_bindings.values())
|
||||
self.assertTrue(
|
||||
weapon_actions.issubset(actions),
|
||||
f"incomplete weapon bindings in {config_path.name}: {sorted(actions)}",
|
||||
)
|
||||
|
||||
def test_shipped_profiles_define_level_dialog_bindings(self):
|
||||
dummy = DummyBindings()
|
||||
conf_dir = Path(__file__).resolve().parent / "conf"
|
||||
required_sections = {
|
||||
"keybinding_start_menu", # Corrected from level_intro since we removed it
|
||||
"keybinding_level_clear",
|
||||
"keybinding_defeat",
|
||||
"keybinding_run_complete",
|
||||
}
|
||||
|
||||
for config_path in sorted(conf_dir.glob("keybindings*.json")) + sorted(conf_dir.glob("keybindings*.yaml")):
|
||||
bindings = controls._load_bindings_from_file(config_path)
|
||||
validated = dummy._validate_bindings(bindings, config_path)
|
||||
|
||||
for section_name in required_sections:
|
||||
section = validated.get(section_name, {})
|
||||
self.assertTrue(section, f"missing {section_name} in {config_path.name}")
|
||||
values = set(section.values())
|
||||
self.assertIn("reset_game", values, f"missing confirm binding in {section_name} for {config_path.name}")
|
||||
self.assertIn("quit_game", values, f"missing quit binding in {section_name} for {config_path.name}")
|
||||
|
||||
def test_trigger_uses_start_menu_context(self):
|
||||
game = MagicMock()
|
||||
dummy = DummyBindings(game)
|
||||
calls = []
|
||||
|
||||
# Override action_dispatcher for testing
|
||||
dummy.action_dispatcher["reset_game"] = lambda: calls.append("reset")
|
||||
dummy.action_dispatcher["quit_game"] = lambda: calls.append("quit")
|
||||
|
||||
dummy.bindings = {
|
||||
"keybinding_start_menu": {"keydown_Return": "reset_game"},
|
||||
}
|
||||
game.game_status = "start_menu"
|
||||
game.menu_screen = "start"
|
||||
game.game_end = (False, None)
|
||||
|
||||
dummy.trigger("keydown_Return")
|
||||
|
||||
self.assertEqual(calls, ["reset"])
|
||||
|
||||
def test_trigger_uses_level_clear_context(self):
|
||||
game = MagicMock()
|
||||
dummy = DummyBindings(game)
|
||||
calls = []
|
||||
|
||||
dummy.action_dispatcher["reset_game"] = lambda: calls.append("reset")
|
||||
dummy.action_dispatcher["quit_game"] = lambda: calls.append("quit")
|
||||
|
||||
dummy.bindings = {
|
||||
"keybinding_level_clear": {"keydown_Return": "reset_game"},
|
||||
"keybinding_paused": {"keydown_Return": "quit_game"},
|
||||
}
|
||||
game.game_status = "paused"
|
||||
game.menu_screen = None
|
||||
game.game_end = (True, "level_clear")
|
||||
|
||||
dummy.trigger("keydown_Return")
|
||||
|
||||
self.assertEqual(calls, ["reset"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user