feat: add Zenith strategy and move latency tracing
Build and Push Docker Container / build-and-push (push) Successful in 2m18s
Build and Push Docker Container / build-and-push (push) Successful in 2m18s
- Register Zenith 1.1 with balanced duel verdicts and forced-outcome overrides. - Prioritize permanent-trail space in Constrictor and health-aware hazard routes. - Deduct executor queue delays and return fallback directions on move timeouts. - Correlate response headers, replay timings, and sampled structured trace logs. - Move shared metrics publication off the response path without losing local counts. - Allow configurable tournament opponents and add a psql helper recipe. - Preserve empty latency values through compact replay encoding and migration. - Update Arena game links and use default assets for missing customizations. - Cover strategy, HTTP lifecycle, tracing, replay codecs, and dashboard behavior. - Validate with 260 passing Python tests, 4 skips, and 2 passing JavaScript tests.
This commit is contained in:
@@ -135,6 +135,58 @@ class TestBattlesnakeLifecycle(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertNotEqual(replay['game']['status'], 'running')
|
||||
print(f'Real SQLite HTTP timings: {timings}')
|
||||
|
||||
async def test_timed_out_move_returns_valid_fallback(self):
|
||||
server = self.server
|
||||
server.logger = Mock()
|
||||
server.logger.warning.return_value = None
|
||||
server.game_lifecycle.turn = AsyncMock()
|
||||
server.metrics_collector.record_move = AsyncMock()
|
||||
state = deepcopy(self.state)
|
||||
state['game']['timeout'] = 100
|
||||
|
||||
async def stalled_board(*args):
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
server.game_runtime.get_game_board = stalled_board
|
||||
response = await server.app.test_client().post('/move', json=state, headers=self.headers)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn((await response.get_json())['move'], ('up', 'down', 'left', 'right'))
|
||||
await server.game_lifecycle.drain()
|
||||
|
||||
async def test_slow_metrics_publish_is_off_response_path_and_traced(self):
|
||||
server = self.server
|
||||
gate = asyncio.Event()
|
||||
snapshots = []
|
||||
|
||||
async def slow_publish():
|
||||
await gate.wait()
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
async def capture(state, move, thinking):
|
||||
snapshots.append(thinking)
|
||||
|
||||
server.metrics_collector.publish_move_metrics = slow_publish
|
||||
server.gameplay_tracking.record_gameplay_turn_snapshot = capture
|
||||
server.dashboard_query.push_dashboard_game_replay_update = AsyncMock()
|
||||
client = server.app.test_client()
|
||||
started = perf_counter()
|
||||
response = await asyncio.wait_for(client.post('/move', json=self.state, headers=self.headers), 0.5)
|
||||
elapsed = (perf_counter() - started) * 1000
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertLess(elapsed, 500)
|
||||
trace_id = response.headers['X-Move-Trace-ID']
|
||||
self.assertEqual(len(trace_id), 32)
|
||||
self.assertEqual(snapshots, [])
|
||||
gate.set()
|
||||
await server.game_lifecycle.drain()
|
||||
trace = snapshots[0]['move_trace']
|
||||
self.assertEqual(trace['trace_id'], trace_id)
|
||||
self.assertGreaterEqual(trace['metrics_publish_ms'], 90)
|
||||
self.assertIn('executor_queue_ms', trace)
|
||||
self.assertIn('strategy_ms', trace)
|
||||
self.assertIn('response_encode_ms', trace)
|
||||
self.assertFalse(trace['timed_out'])
|
||||
|
||||
async def test_invalid_agent_does_not_schedule_work(self):
|
||||
response = await self.server.app.test_client().post('/start', json=self.state)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
Reference in New Issue
Block a user