c704fbc742
Build and Push Docker Container / build-and-push (push) Successful in 8m3s
- Add bitboard-accelerated Prism and versioned Supreme snake implementations. - Add database-backed move benchmarks and focused strategy tests. - Normalize gameplay storage while preserving replay compatibility. - Add deterministic game quality scoring and replay retention tiers. - Add backup-first SQLite cleanup, verification, and replacement tooling. - Add safe compact-plus-delta database merging with conflict detection. - Extend SQLite and PostgreSQL schemas for replay and quality metadata. - Add PostgreSQL development service and pytest import configuration. - Update gameplay documentation and the quart_common submodule revision.
94 lines
4.1 KiB
Python
94 lines
4.1 KiB
Python
import sqlite3
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
import unittest
|
|
|
|
from server.database.backend.SqliteGameplayBackend import SqliteGameplayBackend
|
|
|
|
class TestMergeGameplayDatabases(unittest.TestCase):
|
|
def _create_game(self, path:Path, game_id:str, winner_you:int, cleaned:bool=False) -> None:
|
|
SqliteGameplayBackend(str(path))
|
|
with sqlite3.connect(path) as connection:
|
|
connection.execute("""
|
|
INSERT INTO games (
|
|
game_id, started_at, ended_at, width, height, source, map_name,
|
|
ruleset_name, ruleset_version, your_snake_id, your_snake_name,
|
|
your_snake_type, your_snake_version, game_type, winner_name,
|
|
winner_you, final_turn, status, has_replay, quality_status,
|
|
quality_score, quality_tier, quality_reasons_json
|
|
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
""", (
|
|
game_id, "2026-01-01T00:00:00Z", "2026-01-01T00:01:00Z", 11, 11,
|
|
"league", "standard", "standard", "v1", "me", "PrismBattleSnake",
|
|
"PrismBattleSnake_GPT_5_6_Sol", "1.0.0", "duel", "PrismBattleSnake",
|
|
winner_you, 1, "finished", 1, "retained",
|
|
90 if cleaned else None, "high" if cleaned else None,
|
|
'["already_scored"]' if cleaned else None,
|
|
))
|
|
connection.execute(
|
|
"INSERT INTO game_snakes (game_id,snake_id,snake_name,is_you) VALUES (?,?,?,?)",
|
|
(game_id, "me", "PrismBattleSnake", 1),
|
|
)
|
|
connection.execute("""
|
|
INSERT INTO turns (
|
|
game_id,turn,observed_at,my_move,my_thinking_json,
|
|
board_state_json,snakes_json,you_json,food_json,hazards_json
|
|
) VALUES (?,?,?,?,?,?,?,?,?,?)
|
|
""", (game_id, 1, "2026-01-01T00:00:01Z", "up", '{"score":1}', '{}', '[]', '{}', '[]', '[]'))
|
|
connection.execute("""
|
|
INSERT INTO snake_turns (
|
|
game_id,turn,snake_id,health,length,head_x,head_y,body_json,is_you,inferred_move
|
|
) VALUES (?,?,?,?,?,?,?,?,?,?)
|
|
""", (game_id, 1, "me", 90, 3, 1, 1, '[{"x":1,"y":1}]', 0, "up"))
|
|
|
|
def test_merges_base_and_delta_and_regenerates_row_ids(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
root = Path(temp_dir)
|
|
base = root / "base.sqlite3"
|
|
delta = root / "delta.sqlite3"
|
|
merged = root / "merged.sqlite3"
|
|
self._create_game(base, "base-game", 1, cleaned=True)
|
|
self._create_game(delta, "delta-game", 0)
|
|
|
|
result = subprocess.run([
|
|
sys.executable, "scripts/merge_gameplay_databases.py",
|
|
"--base", str(base), "--delta", str(delta),
|
|
"--destination", str(merged), "--minimum-quality", "medium",
|
|
], cwd=Path(__file__).resolve().parents[1], text=True, capture_output=True)
|
|
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
with sqlite3.connect(merged) as connection:
|
|
games = connection.execute(
|
|
"SELECT game_id, winner_you, has_replay, quality_tier FROM games ORDER BY game_id"
|
|
).fetchall()
|
|
self.assertEqual(games, [
|
|
("base-game", 1, 1, "high"),
|
|
("delta-game", 0, 1, "medium"),
|
|
])
|
|
self.assertEqual(connection.execute("SELECT COUNT(*) FROM turns").fetchone()[0], 2)
|
|
self.assertEqual(connection.execute("SELECT COUNT(*) FROM snake_turns").fetchone()[0], 2)
|
|
self.assertEqual(connection.execute("PRAGMA foreign_key_check").fetchall(), [])
|
|
|
|
def test_conflicting_duplicate_aborts_without_destination(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
root = Path(temp_dir)
|
|
base = root / "base.sqlite3"
|
|
delta = root / "delta.sqlite3"
|
|
merged = root / "merged.sqlite3"
|
|
self._create_game(base, "same-game", 1, cleaned=True)
|
|
self._create_game(delta, "same-game", 0)
|
|
|
|
result = subprocess.run([
|
|
sys.executable, "scripts/merge_gameplay_databases.py",
|
|
"--base", str(base), "--delta", str(delta), "--destination", str(merged),
|
|
], cwd=Path(__file__).resolve().parents[1], text=True, capture_output=True)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("Conflicting duplicate game_id", result.stderr)
|
|
self.assertFalse(merged.exists())
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|