Files
snake-python/tests/test_BenchmarkStates.py
T
daniel156161 39a7a53873
Build and Push Docker Container / build-and-push (push) Successful in 3m52s
feat(database): store gameplay replays in compact form
- Add compact storage package: codec, records, hydrate, replay blobs, cache.
- Running games write game_turns and snake_states keyed by integer ids.
- Encode moves as 1-byte codes and bodies as head plus step codes.
- Store food and hazards only when they changed since the previous turn.
- Strip reasoning fields that duplicate stored values and restore on read.
- Pack finished games into one zstd blob per game in game_replays.
- Merge late turns into packed blobs during the stale-game sweep.
- Extract SQLite and PostgreSQL schemas into dedicated modules.
- Add read/import game bundle and list_game_ids to the backend template.
- Add legacy readers and converter for the old turns/snake_turns layout.
- Add migrate_gameplay_to_compact.py with in-place or cross-DB migration.
- Verify every migrated game and keep legacy rows on failure.
- Support --since, --minimum-quality and guarded --drop-legacy.
- Add just recipe migrate-gameplay-postgres.
- Rewrite merge script to copy game by game via backend bundles.
- Move benchmark state loading into benchmark_states for both backends.
- Update PostgreSQL cleanup script to the new replay tables.
- Drop sqlite-zstd build stages from the Dockerfile.
- Remove old migration scripts, normalized_turn and their tests.
- Add tests for compact storage, schema, legacy migration and PostgreSQL.
- Document the compact layout and migration workflow in the README.
2026-11-05 00:02:56 +01:00

50 lines
1.8 KiB
Python

import asyncio
import unittest
from unittest.mock import AsyncMock
from server.database.benchmark_states import is_postgresql_source, replay_turn_to_state, sample_states
GAME = {
"game_id": "game-id", "source": "league", "map": "standard",
"ruleset_name": "standard", "ruleset_version": "v1",
}
def _turn(number:int) -> dict:
you = {"id": "you-id", "head": {"x": 1, "y": 2}, "body": [{"x": 1, "y": 2}]}
return {
"turn": number,
"board": {"width": 11, "height": 11, "food": [{"x": 5, "y": 5}], "hazards": [], "snakes": [you]},
"you": you,
}
class TestBenchmarkStates(unittest.TestCase):
def test_detects_postgresql_dsn(self):
self.assertTrue(is_postgresql_source("postgresql://user:pass@example.com/db"))
self.assertTrue(is_postgresql_source("postgres://user:pass@example.com/db"))
self.assertFalse(is_postgresql_source("/tmp/gameplay.sqlite3"))
def test_builds_state_from_replay_turn(self):
board, metadata = replay_turn_to_state(GAME, _turn(7))
self.assertEqual(board["food"], [{"x": 5, "y": 5}])
self.assertEqual(metadata["you"]["id"], "you-id")
self.assertEqual(metadata["turn"], 7)
self.assertEqual(metadata["ruleset"]["version"], "v1")
def test_turn_without_snakes_is_skipped(self):
self.assertIsNone(replay_turn_to_state(GAME, {"turn": 0, "board": {"snakes": []}, "you": {}}))
def test_samples_every_stride_turn_from_newest_games(self):
backend = AsyncMock()
backend.list_game_ids.return_value = ["old", "new"]
backend.get_game_replay.side_effect = lambda game_id: {
"game": {**GAME, "game_id": game_id},
"turns": [_turn(number) for number in range(4)],
}
states = asyncio.run(sample_states(backend, samples=3, stride=3))
self.assertEqual([(m["game_id"], m["turn"]) for _, m in states], [("new", 3), ("new", 0), ("old", 1)])
if __name__ == "__main__":
unittest.main()