"""A check of kit/room.py against the room protocol (docs/games.md ยง8.3), on a toy game with one human seat: a stale or illegal answer is ignored, a handover re-asks with a new version and drops the old controller's late answer, a silent seat gets auto's move, and stop ends the room. python3 kit/test_room.py (or pytest) """ import json, os, subprocess, sys KIT = os.path.dirname(os.path.abspath(__file__)) GAME = f''' import sys; sys.path.insert(0, {KIT!r}) from room import Room, Stop room = Room(None) try: for i in range(4): room.event({{'seat': '0', 'move': room.ask('0', {{'i': i}}, ['a', 'b'], auto=lambda: 'auto')}}) except Stop: room.event({{'stopped': True}}) room.end('done', {{'0': 0}}) ''' def test_room(): env = {**os.environ, 'SS_ROOM': json.dumps({'limit': 0.5, 'seats': {'0': {'kind': 'human'}}})} p = subprocess.Popen([sys.executable, '-c', GAME], env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) def said(): line = p.stdout.readline() assert line.startswith('@shoal '), line return json.loads(line[7:]) def send(msg): p.stdin.write(json.dumps(msg) + '\n') p.stdin.flush() assert said() == {'to': '0', 'version': 1, 'obs': {'i': 0}, 'legal': ['a', 'b']} send({'act': {'seat': '0', 'version': 0, 'action': 'a'}}) # stale send({'act': {'seat': '0', 'version': 1, 'action': 'z'}}) # illegal send({'act': {'seat': '0', 'version': 1, 'action': 'b'}}) assert said() == {'event': {'seat': '0', 'move': 'b'}} assert said()['version'] == 2 send({'control': {'seat': '0', 'kind': 'agent', 'name': 'ada-codex'}}) send({'act': {'seat': '0', 'version': 2, 'action': 'b'}}) # the old controller, late assert said() == {'event': {'type': 'control', 'seat': '0', 'kind': 'agent', 'name': 'ada-codex'}} assert said()['version'] == 3 send({'act': {'seat': '0', 'version': 3, 'action': 'a'}}) assert said() == {'event': {'seat': '0', 'move': 'a'}} assert said()['version'] == 4 # left unanswered: auto plays after the limit assert said() == {'event': {'seat': '0', 'move': 'auto'}} assert said()['version'] == 5 send({'stop': True}) assert said() == {'event': {'stopped': True}} assert said() == {'end': {'result': 'done', 'scores': {'0': 0}}} assert p.wait(5) == 0 if __name__ == '__main__': test_room() print('room protocol: ok') def test_bot_processes_are_private_to_seats(): # Same strategy, different seats: neither process can remember the other seat's hand. from room import Room class Child: def __init__(self): self.seen = [] def ask(self, msg): self.seen.append(msg) return 'a' def close(self): pass room = object.__new__(Room) room.start, room.field, room.bots = lambda folder: Child(), 'field', {} a = room._bot({'field': 'same'}, seat='0') b = room._bot({'field': 'same'}, seat='1') assert a is not b assert room._bot({'field': 'same'}, seat='0') is a a.ask({'hand': 'private'}) assert b.seen == [] room.close() if __name__ == '__main__': test_bot_processes_are_private_to_seats() print('seat process isolation: ok')