Build and Push Docker Container / build-and-push (push) Successful in 3m52s
- 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.
50 lines
1.8 KiB
Python
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()
|