Included gomill framework for SGF and GTP support, and sketched out SGF game-loading code.
This commit is contained in:
1
gomill/gomill_tests/__init__.py
Normal file
1
gomill/gomill_tests/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# gomill_tests package
|
289
gomill/gomill_tests/allplayall_tests.py
Normal file
289
gomill/gomill_tests/allplayall_tests.py
Normal file
@ -0,0 +1,289 @@
|
||||
"""Tests for allplayalls.py"""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from textwrap import dedent
|
||||
import cPickle as pickle
|
||||
|
||||
from gomill import competitions
|
||||
from gomill import allplayalls
|
||||
from gomill.gtp_games import Game_result
|
||||
from gomill.game_jobs import Game_job, Game_job_result
|
||||
from gomill.competitions import (
|
||||
Player_config, NoGameAvailable, CompetitionError, ControlFileError)
|
||||
from gomill.allplayalls import Competitor_config
|
||||
|
||||
from gomill_tests import competition_test_support
|
||||
from gomill_tests import gomill_test_support
|
||||
from gomill_tests import test_framework
|
||||
from gomill_tests.competition_test_support import (
|
||||
fake_response, check_screen_report)
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
def check_short_report(tc, comp,
|
||||
expected_grid, expected_matchups, expected_players,
|
||||
competition_name="testcomp"):
|
||||
"""Check that an allplayall's short report is as expected."""
|
||||
expected = ("allplayall: %s\n\n%s\n%s\n%s\n" %
|
||||
(competition_name, expected_grid,
|
||||
expected_matchups, expected_players))
|
||||
tc.assertMultiLineEqual(competition_test_support.get_short_report(comp),
|
||||
expected)
|
||||
|
||||
class Allplayall_fixture(test_framework.Fixture):
|
||||
"""Fixture setting up a Allplayall.
|
||||
|
||||
attributes:
|
||||
comp -- Allplayall
|
||||
|
||||
"""
|
||||
def __init__(self, tc, config=None):
|
||||
if config is None:
|
||||
config = default_config()
|
||||
self.tc = tc
|
||||
self.comp = allplayalls.Allplayall('testcomp')
|
||||
self.comp.initialise_from_control_file(config)
|
||||
self.comp.set_clean_status()
|
||||
|
||||
def check_screen_report(self, expected):
|
||||
"""Check that the screen report is as expected."""
|
||||
check_screen_report(self.tc, self.comp, expected)
|
||||
|
||||
def check_short_report(self, *args, **kwargs):
|
||||
"""Check that the short report is as expected."""
|
||||
check_short_report(self.tc, self.comp, *args, **kwargs)
|
||||
|
||||
|
||||
def default_config():
|
||||
return {
|
||||
'players' : {
|
||||
't1' : Player_config("test1"),
|
||||
't2' : Player_config("test2"),
|
||||
't3' : Player_config("test3"),
|
||||
},
|
||||
'board_size' : 13,
|
||||
'komi' : 7.5,
|
||||
'competitors' : [
|
||||
Competitor_config('t1'),
|
||||
Competitor_config('t2'),
|
||||
't3',
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
||||
def test_default_config(tc):
|
||||
comp = allplayalls.Allplayall('test')
|
||||
config = default_config()
|
||||
comp.initialise_from_control_file(config)
|
||||
comp.set_clean_status()
|
||||
tr = comp.get_tournament_results()
|
||||
tc.assertListEqual(tr.get_matchup_ids(), ['AvB', 'AvC', 'BvC'])
|
||||
mBvC = tr.get_matchup('BvC')
|
||||
tc.assertEqual(mBvC.player_1, 't2')
|
||||
tc.assertEqual(mBvC.player_2, 't3')
|
||||
tc.assertEqual(mBvC.board_size, 13)
|
||||
tc.assertEqual(mBvC.komi, 7.5)
|
||||
tc.assertEqual(mBvC.move_limit, 1000)
|
||||
tc.assertEqual(mBvC.scorer, 'players')
|
||||
tc.assertEqual(mBvC.internal_scorer_handicap_compensation, 'full')
|
||||
tc.assertEqual(mBvC.number_of_games, None)
|
||||
tc.assertIs(mBvC.alternating, True)
|
||||
tc.assertIs(mBvC.handicap, None)
|
||||
tc.assertEqual(mBvC.handicap_style, 'fixed')
|
||||
|
||||
def test_basic_config(tc):
|
||||
comp = allplayalls.Allplayall('test')
|
||||
config = default_config()
|
||||
config['description'] = "default\nconfig"
|
||||
config['board_size'] = 9
|
||||
config['komi'] = 0.5
|
||||
config['move_limit'] = 200
|
||||
config['scorer'] = 'internal'
|
||||
config['internal_scorer_handicap_compensation'] = 'short'
|
||||
config['rounds'] = 20
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertEqual(comp.description, "default\nconfig")
|
||||
comp.set_clean_status()
|
||||
mBvC = comp.get_tournament_results().get_matchup('BvC')
|
||||
tc.assertEqual(mBvC.player_1, 't2')
|
||||
tc.assertEqual(mBvC.player_2, 't3')
|
||||
tc.assertEqual(mBvC.board_size, 9)
|
||||
tc.assertEqual(mBvC.komi, 0.5)
|
||||
tc.assertEqual(mBvC.move_limit, 200)
|
||||
tc.assertEqual(mBvC.scorer, 'internal')
|
||||
tc.assertEqual(mBvC.internal_scorer_handicap_compensation, 'short')
|
||||
tc.assertEqual(mBvC.number_of_games, 20)
|
||||
tc.assertIs(mBvC.alternating, True)
|
||||
tc.assertIs(mBvC.handicap, None)
|
||||
tc.assertEqual(mBvC.handicap_style, 'fixed')
|
||||
|
||||
def test_unknown_player(tc):
|
||||
comp = allplayalls.Allplayall('test')
|
||||
config = default_config()
|
||||
config['competitors'].append('nonex')
|
||||
tc.assertRaisesRegexp(
|
||||
ControlFileError, "competitor nonex: unknown player",
|
||||
comp.initialise_from_control_file, config)
|
||||
|
||||
def test_duplicate_player(tc):
|
||||
comp = allplayalls.Allplayall('test')
|
||||
config = default_config()
|
||||
config['competitors'].append('t2')
|
||||
tc.assertRaisesRegexp(
|
||||
ControlFileError, "duplicate competitor: t2",
|
||||
comp.initialise_from_control_file, config)
|
||||
|
||||
def test_game_id_format(tc):
|
||||
config = default_config()
|
||||
config['rounds'] = 1000
|
||||
fx = Allplayall_fixture(tc, config)
|
||||
tc.assertEqual(fx.comp.get_game().game_id, 'AvB_000')
|
||||
|
||||
def test_get_player_checks(tc):
|
||||
fx = Allplayall_fixture(tc)
|
||||
checks = fx.comp.get_player_checks()
|
||||
tc.assertEqual(len(checks), 3)
|
||||
tc.assertEqual(checks[0].board_size, 13)
|
||||
tc.assertEqual(checks[0].komi, 7.5)
|
||||
tc.assertEqual(checks[0].player.code, "t1")
|
||||
tc.assertEqual(checks[0].player.cmd_args, ['test1'])
|
||||
tc.assertEqual(checks[1].player.code, "t2")
|
||||
tc.assertEqual(checks[1].player.cmd_args, ['test2'])
|
||||
tc.assertEqual(checks[2].player.code, "t3")
|
||||
tc.assertEqual(checks[2].player.cmd_args, ['test3'])
|
||||
|
||||
def test_play(tc):
|
||||
fx = Allplayall_fixture(tc)
|
||||
tc.assertIsNone(fx.comp.description)
|
||||
|
||||
job1 = fx.comp.get_game()
|
||||
tc.assertIsInstance(job1, Game_job)
|
||||
tc.assertEqual(job1.game_id, 'AvB_0')
|
||||
tc.assertEqual(job1.player_b.code, 't1')
|
||||
tc.assertEqual(job1.player_w.code, 't2')
|
||||
tc.assertEqual(job1.board_size, 13)
|
||||
tc.assertEqual(job1.komi, 7.5)
|
||||
tc.assertEqual(job1.move_limit, 1000)
|
||||
tc.assertIs(job1.use_internal_scorer, False)
|
||||
tc.assertEqual(job1.internal_scorer_handicap_compensation, 'full')
|
||||
tc.assertEqual(job1.game_data, ('AvB', 0))
|
||||
tc.assertIsNone(job1.sgf_filename)
|
||||
tc.assertIsNone(job1.sgf_dirname)
|
||||
tc.assertIsNone(job1.void_sgf_dirname)
|
||||
tc.assertEqual(job1.sgf_event, 'testcomp')
|
||||
tc.assertIsNone(job1.gtp_log_pathname)
|
||||
|
||||
job2 = fx.comp.get_game()
|
||||
tc.assertIsInstance(job2, Game_job)
|
||||
tc.assertEqual(job2.game_id, 'AvC_0')
|
||||
tc.assertEqual(job2.player_b.code, 't1')
|
||||
tc.assertEqual(job2.player_w.code, 't3')
|
||||
|
||||
response1 = fake_response(job1, 'b')
|
||||
fx.comp.process_game_result(response1)
|
||||
response2 = fake_response(job2, None)
|
||||
fx.comp.process_game_result(response2)
|
||||
|
||||
expected_grid = dedent("""\
|
||||
2 games played
|
||||
|
||||
A B C
|
||||
A t1 1-0 0.5-0.5
|
||||
B t2 0-1 0-0
|
||||
C t3 0.5-0.5 0-0
|
||||
""")
|
||||
expected_matchups = dedent("""\
|
||||
t1 v t2 (1 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins
|
||||
t1 1 100.00% (black)
|
||||
t2 0 0.00% (white)
|
||||
|
||||
t1 v t3 (1 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins
|
||||
t1 0.5 50.00% (black)
|
||||
t3 0.5 50.00% (white)
|
||||
""")
|
||||
expected_players = dedent("""\
|
||||
player t1: t1 engine:v1.2.3
|
||||
player t2: t2 engine
|
||||
testdescription
|
||||
player t3: t3 engine
|
||||
testdescription
|
||||
""")
|
||||
fx.check_screen_report(expected_grid)
|
||||
fx.check_short_report(expected_grid, expected_matchups, expected_players)
|
||||
|
||||
avb_results = fx.comp.get_tournament_results().get_matchup_results('AvB')
|
||||
tc.assertEqual(avb_results, [response1.game_result])
|
||||
|
||||
def test_play_many(tc):
|
||||
config = default_config()
|
||||
config['rounds'] = 30
|
||||
fx = Allplayall_fixture(tc, config)
|
||||
|
||||
jobs = [fx.comp.get_game() for _ in xrange(57)]
|
||||
for i in xrange(57):
|
||||
response = fake_response(jobs[i], 'b')
|
||||
fx.comp.process_game_result(response)
|
||||
|
||||
fx.check_screen_report(dedent("""\
|
||||
57/90 games played
|
||||
|
||||
A B C
|
||||
A t1 10-9 10-9
|
||||
B t2 9-10 10-9
|
||||
C t3 9-10 9-10
|
||||
"""))
|
||||
|
||||
tc.assertEqual(
|
||||
len(fx.comp.get_tournament_results().get_matchup_results('AvB')), 19)
|
||||
|
||||
comp2 = competition_test_support.check_round_trip(tc, fx.comp, config)
|
||||
jobs2 = [comp2.get_game() for _ in range(4)]
|
||||
tc.assertListEqual([job.game_id for job in jobs2],
|
||||
['AvB_19', 'AvC_19', 'BvC_19', 'AvB_20'])
|
||||
tr = comp2.get_tournament_results()
|
||||
tc.assertEqual(len(tr.get_matchup_results('AvB')), 19)
|
||||
ms = tr.get_matchup_stats('AvB')
|
||||
tc.assertEqual(ms.total, 19)
|
||||
tc.assertEqual(ms.wins_1, 10)
|
||||
tc.assertIs(ms.alternating, True)
|
||||
|
||||
def test_competitor_change(tc):
|
||||
fx = Allplayall_fixture(tc)
|
||||
status = pickle.loads(pickle.dumps(fx.comp.get_status()))
|
||||
|
||||
config2 = default_config()
|
||||
del config2['competitors'][2]
|
||||
comp2 = allplayalls.Allplayall('testcomp')
|
||||
comp2.initialise_from_control_file(config2)
|
||||
with tc.assertRaises(CompetitionError) as ar:
|
||||
comp2.set_status(status)
|
||||
tc.assertEqual(
|
||||
str(ar.exception),
|
||||
"competitor has been removed from control file")
|
||||
|
||||
config3 = default_config()
|
||||
config3['players']['t4'] = Player_config("test4")
|
||||
config3['competitors'][2] = 't4'
|
||||
comp3 = allplayalls.Allplayall('testcomp')
|
||||
comp3.initialise_from_control_file(config3)
|
||||
with tc.assertRaises(CompetitionError) as ar:
|
||||
comp3.set_status(status)
|
||||
tc.assertEqual(
|
||||
str(ar.exception),
|
||||
"competitors have changed in the control file")
|
||||
|
||||
config4 = default_config()
|
||||
config4['players']['t4'] = Player_config("test4")
|
||||
config4['competitors'].append('t4')
|
||||
comp4 = allplayalls.Allplayall('testcomp')
|
||||
comp4.initialise_from_control_file(config4)
|
||||
comp4.set_status(status)
|
||||
|
395
gomill/gomill_tests/board_test_data.py
Normal file
395
gomill/gomill_tests/board_test_data.py
Normal file
@ -0,0 +1,395 @@
|
||||
play_tests = [
|
||||
|
||||
# code, list of moves to play, board representation, simple ko point, score
|
||||
|
||||
('blank', [
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, 0),
|
||||
|
||||
('twostone', [
|
||||
"B B2", "W C2",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . # o . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, 0),
|
||||
|
||||
('many-groups-1-capture', [
|
||||
"B C3", "W D3", "B C5", "B C4", "W D4", "B H1", "B B9", "B J6",
|
||||
"B A7", "B B7", "W A3", "W J2", "W H2", "W G2", "W J3",
|
||||
"B F7",
|
||||
"W E6", "W G8", "W G6", "W F8", "W E7", "W F6", "W G7", "W E8",
|
||||
], """\
|
||||
9 . # . . . . . . .
|
||||
8 . . . . o o o . .
|
||||
7 # # . . o . o . .
|
||||
6 . . . . o o o . #
|
||||
5 . . # . . . . . .
|
||||
4 . . # o . . . . .
|
||||
3 o . # o . . . . o
|
||||
2 . . . . . . o o o
|
||||
1 . . . . . . . # .
|
||||
A B C D E F G H J
|
||||
""", None, -8),
|
||||
|
||||
('corner-bl', [
|
||||
"B A1", "W B1", "W A2",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 o . . . . . . . .
|
||||
1 . o . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, -81),
|
||||
|
||||
('corner-all', [
|
||||
"B A1", "W B1", "W A2",
|
||||
"B J1", "W H1", "W J2",
|
||||
"B A9", "W B9", "W A8",
|
||||
"B J9", "W H9", "W J8",
|
||||
], """\
|
||||
9 . o . . . . . o .
|
||||
8 o . . . . . . . o
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 o . . . . . . . o
|
||||
1 . o . . . . . o .
|
||||
A B C D E F G H J
|
||||
""", None, -81),
|
||||
|
||||
('multiple', [
|
||||
"W D4", "B D3", "W C5", "B C4", "W E5", "B E4",
|
||||
"B B5", "B F5", "B C6", "B E6", "B D7", "W D6",
|
||||
"B D5",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . # . . . . .
|
||||
6 . . # . # . . . .
|
||||
5 . # . # . # . . .
|
||||
4 . . # . # . . . .
|
||||
3 . . . # . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, 81),
|
||||
|
||||
('large', [
|
||||
"W D2", "W G2", "W E3", "W F3", "W F4", "W D5", "W E5", "W F5", "B E2", "B F2",
|
||||
"B D3", "B G3", "B D4", "B G4", "B C5", "B G5", "B D6", "B E6", "B F6", "B E4",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . # # # . . .
|
||||
5 . . # . . . # . .
|
||||
4 . . . # # . # . .
|
||||
3 . . . # . . # . .
|
||||
2 . . . o # # o . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, 16),
|
||||
|
||||
('pre-recapture', [
|
||||
"W A1", "W B1", "W B2", "W C2", "W D3", "W E3", "W A4", "W B4", "W C4", "W E4",
|
||||
"B A2", "B D2", "B E2", "B A3", "B B3", "B F3", "B D4", "B F4", "B E5",
|
||||
"B C3",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . # . . . .
|
||||
4 o o o # . # . . .
|
||||
3 # # # . . # . . .
|
||||
2 # o o # # . . . .
|
||||
1 o o . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, 6),
|
||||
|
||||
('recapture', [
|
||||
"W A1", "W B1", "W B2", "W C2", "W D3", "W E3", "W A4", "W B4", "W C4", "W E4",
|
||||
"B A2", "B D2", "B E2", "B A3", "B B3", "B F3", "B D4", "B F4", "B E5",
|
||||
"B C3", "W D3",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . # . . . .
|
||||
4 o o o # . # . . .
|
||||
3 . . . o . # . . .
|
||||
2 . o o # # . . . .
|
||||
1 o o . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, -6),
|
||||
|
||||
('self-capture-1', [
|
||||
"B D4", "B C5", "B E5", "B D6", "W D5",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . # . . . . .
|
||||
5 . . # . # . . . .
|
||||
4 . . . # . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, 81),
|
||||
|
||||
('self-capture-2', [
|
||||
"B D4", "B E4", "B C5", "B F5", "B D6", "B E6", "W D5", "W E5",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . # # . . . .
|
||||
5 . . # . . # . . .
|
||||
4 . . . # # . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, 81),
|
||||
|
||||
('self-capture-3', [
|
||||
"B D4", "B E4", "B F4", "B C5", "B G5", "B D6", "B E6", "B F6",
|
||||
"W D5", "W F5", "W E5",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . # # # . . .
|
||||
5 . . # . . . # . .
|
||||
4 . . . # # # . . .
|
||||
3 . . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, 81),
|
||||
|
||||
('self-capture-many', [
|
||||
"B E2", "B D3", "B F3", "B D4", "B F4", "B G4", "B C5", "B H5", "B C6",
|
||||
"B F6", "B G6", "B D7", "B E7",
|
||||
"W E3", "W E4", "W D5", "W F5", "W G5", "W D6", "W E6", "W E5",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . # # . . . .
|
||||
6 . . # . . # # . .
|
||||
5 . . # . . . . # .
|
||||
4 . . . # . # # . .
|
||||
3 . . . # . # . . .
|
||||
2 . . . . # . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, 81),
|
||||
|
||||
('ko-corner', [
|
||||
"B A1", "B B2", "B A3",
|
||||
"W B1", "W A2",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 # . . . . . . . .
|
||||
2 o # . . . . . . .
|
||||
1 . o . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", 'A1', -1),
|
||||
|
||||
('notko-twocaptured', [
|
||||
"B B2", "B B3", "B A4",
|
||||
"W B1", "W A2", "W A3",
|
||||
"B A1",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 # . . . . . . . .
|
||||
3 . # . . . . . . .
|
||||
2 . # . . . . . . .
|
||||
1 # o . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, 5),
|
||||
|
||||
('notko-tworecaptured', [
|
||||
"B A1", "B B3", "B A4",
|
||||
"W B1", "W B2", "W A3",
|
||||
"B A2",
|
||||
], """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 # . . . . . . . .
|
||||
3 . # . . . . . . .
|
||||
2 # o . . . . . . .
|
||||
1 # o . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", None, 3),
|
||||
|
||||
]
|
||||
|
||||
|
||||
score_tests = [
|
||||
|
||||
# code, board representation, score
|
||||
|
||||
('empty', """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", 0),
|
||||
|
||||
('onestone', """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . # . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", 81),
|
||||
|
||||
('easy', """\
|
||||
9 . . . # o . . . .
|
||||
8 . . . # o . . . .
|
||||
7 . . . # o . . . .
|
||||
6 . . . # o . . . .
|
||||
5 . . . # o . . . .
|
||||
4 . . . # o . . . .
|
||||
3 . . . # o . . . .
|
||||
2 . . . # o . . . .
|
||||
1 . . . # o . . . .
|
||||
A B C D E F G H J
|
||||
""", -9),
|
||||
|
||||
('spoilt', """\
|
||||
9 . . . # o . . . .
|
||||
8 . . . # o . . . .
|
||||
7 . . . # o . . . .
|
||||
6 . . . # o . . . .
|
||||
5 . . . # o . . # .
|
||||
4 . . . # o . . . .
|
||||
3 . . . # o . . . .
|
||||
2 . . . # o . . . .
|
||||
1 . . . # o . . . .
|
||||
A B C D E F G H J
|
||||
""", 28),
|
||||
|
||||
('busy', """\
|
||||
9 . . o . . o # . #
|
||||
8 . o o o o o # . #
|
||||
7 . o . o o # # # o
|
||||
6 o . o # o # o o o
|
||||
5 o o # # # # # o o
|
||||
4 . o o # . o # o .
|
||||
3 o # # # o o o o o
|
||||
2 . o o # # # o o .
|
||||
1 o . o # . # o o o
|
||||
A B C D E F G H J
|
||||
""", -26),
|
||||
|
||||
]
|
||||
|
||||
|
||||
setup_tests = [
|
||||
|
||||
# code, black points, white points, empty points, diagram, is_legal
|
||||
|
||||
('blank',
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
"""\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", True),
|
||||
|
||||
('simple',
|
||||
['D4', 'D5'],
|
||||
['B1', 'J9'],
|
||||
[],
|
||||
"""\
|
||||
9 . . . . . . . . o
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . # . . . . .
|
||||
4 . . . # . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . o . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", True),
|
||||
|
||||
('illegal',
|
||||
['A8', 'B9'],
|
||||
['A9'],
|
||||
[],
|
||||
"""\
|
||||
9 . # . . . . . . .
|
||||
8 # . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J
|
||||
""", False),
|
||||
|
||||
]
|
184
gomill/gomill_tests/board_tests.py
Normal file
184
gomill/gomill_tests/board_tests.py
Normal file
@ -0,0 +1,184 @@
|
||||
"""Tests for boards.py and ascii_boards.py
|
||||
|
||||
We test these together because it's convenient for later boards tests to use
|
||||
ascii_boards facilities.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from gomill.common import format_vertex, move_from_vertex
|
||||
from gomill import ascii_boards
|
||||
from gomill import boards
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
from gomill_tests import board_test_data
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
for t in board_test_data.play_tests:
|
||||
suite.addTest(Play_test_TestCase(*t))
|
||||
for t in board_test_data.score_tests:
|
||||
suite.addTest(Score_test_TestCase(*t))
|
||||
for t in board_test_data.setup_tests:
|
||||
suite.addTest(Setup_test_TestCase(*t))
|
||||
|
||||
def test_attributes(tc):
|
||||
b = boards.Board(5)
|
||||
tc.assertEqual(b.side, 5)
|
||||
tc.assertEqual(
|
||||
b.board_points,
|
||||
[(0, 0), (0, 1), (0, 2), (0, 3), (0, 4),
|
||||
(1, 0), (1, 1), (1, 2), (1, 3), (1, 4),
|
||||
(2, 0), (2, 1), (2, 2), (2, 3), (2, 4),
|
||||
(3, 0), (3, 1), (3, 2), (3, 3), (3, 4),
|
||||
(4, 0), (4, 1), (4, 2), (4, 3), (4, 4)])
|
||||
|
||||
def test_basics(tc):
|
||||
b = boards.Board(9)
|
||||
|
||||
tc.assertTrue(b.is_empty())
|
||||
tc.assertItemsEqual(b.list_occupied_points(), [])
|
||||
|
||||
tc.assertEqual(b.get(2, 3), None)
|
||||
b.play(2, 3, 'b')
|
||||
tc.assertEqual(b.get(2, 3), 'b')
|
||||
tc.assertFalse(b.is_empty())
|
||||
b.play(3, 4, 'w')
|
||||
|
||||
with tc.assertRaises(ValueError):
|
||||
b.play(3, 4, 'w')
|
||||
|
||||
tc.assertItemsEqual(b.list_occupied_points(),
|
||||
[('b', (2, 3)), ('w', (3, 4))])
|
||||
|
||||
|
||||
_9x9_expected = """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . o . . . .
|
||||
3 . . . # . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J\
|
||||
"""
|
||||
|
||||
_13x13_expected = """\
|
||||
13 . . . . . . . . . . . . .
|
||||
12 . . . . . . . . . . . . .
|
||||
11 . . . . . . . . . . . . .
|
||||
10 . . . . . . . . . . . . .
|
||||
9 . . . . . . . . . . . . .
|
||||
8 . . . . . . . . . . . . .
|
||||
7 . . . . . . . . . . . . .
|
||||
6 . . . . . . . . . . . . .
|
||||
5 . . . . . . . . . . . . .
|
||||
4 . . . . o . . . . . . . .
|
||||
3 . . . # . . . . . . . . .
|
||||
2 . . . . . . . . . . . . .
|
||||
1 . . . . . . . . . . . . .
|
||||
A B C D E F G H J K L M N\
|
||||
"""
|
||||
|
||||
def test_render_board_9x9(tc):
|
||||
b = boards.Board(9)
|
||||
b.play(2, 3, 'b')
|
||||
b.play(3, 4, 'w')
|
||||
tc.assertDiagramEqual(ascii_boards.render_board(b), _9x9_expected)
|
||||
|
||||
def test_render_board_13x13(tc):
|
||||
b = boards.Board(13)
|
||||
b.play(2, 3, 'b')
|
||||
b.play(3, 4, 'w')
|
||||
tc.assertDiagramEqual(ascii_boards.render_board(b), _13x13_expected)
|
||||
|
||||
def test_interpret_diagram(tc):
|
||||
b1 = boards.Board(9)
|
||||
b1.play(2, 3, 'b')
|
||||
b1.play(3, 4, 'w')
|
||||
b2 = ascii_boards.interpret_diagram(_9x9_expected, 9)
|
||||
tc.assertEqual(b1, b2)
|
||||
b3 = boards.Board(9)
|
||||
b4 = ascii_boards.interpret_diagram(_9x9_expected, 9, b3)
|
||||
tc.assertIs(b3, b4)
|
||||
tc.assertEqual(b1, b3)
|
||||
tc.assertRaisesRegexp(ValueError, "board not empty",
|
||||
ascii_boards.interpret_diagram, _9x9_expected, 9, b3)
|
||||
b5 = boards.Board(19)
|
||||
tc.assertRaisesRegexp(ValueError, "wrong board size, must be 9$",
|
||||
ascii_boards.interpret_diagram, _9x9_expected, 9, b5)
|
||||
|
||||
tc.assertRaises(ValueError, ascii_boards.interpret_diagram, "nonsense", 9)
|
||||
b6 = ascii_boards.interpret_diagram(_13x13_expected, 13)
|
||||
tc.assertDiagramEqual(ascii_boards.render_board(b6), _13x13_expected)
|
||||
|
||||
def test_copy(tc):
|
||||
b1 = boards.Board(9)
|
||||
b1.play(2, 3, 'b')
|
||||
b1.play(3, 4, 'w')
|
||||
b2 = b1.copy()
|
||||
tc.assertEqual(b1, b2)
|
||||
b2.play(5, 5, 'b')
|
||||
b2.play(2, 1, 'b')
|
||||
tc.assertNotEqual(b1, b2)
|
||||
b1.play(5, 5, 'b')
|
||||
b1.play(2, 1, 'b')
|
||||
tc.assertEqual(b1, b2)
|
||||
|
||||
|
||||
class Play_test_TestCase(gomill_test_support.Gomill_ParameterisedTestCase):
|
||||
"""Check final position reached by playing a sequence of moves."""
|
||||
test_name = "play_test"
|
||||
parameter_names = ('moves', 'diagram', 'ko_vertex', 'score')
|
||||
|
||||
def runTest(self):
|
||||
b = boards.Board(9)
|
||||
ko_point = None
|
||||
for move in self.moves:
|
||||
colour, vertex = move.split()
|
||||
colour = colour.lower()
|
||||
row, col = move_from_vertex(vertex, b.side)
|
||||
ko_point = b.play(row, col, colour)
|
||||
self.assertDiagramEqual(ascii_boards.render_board(b),
|
||||
self.diagram.rstrip())
|
||||
if ko_point is None:
|
||||
ko_vertex = None
|
||||
else:
|
||||
ko_vertex = format_vertex(ko_point)
|
||||
self.assertEqual(ko_vertex, self.ko_vertex, "wrong ko point")
|
||||
self.assertEqual(b.area_score(), self.score, "wrong score")
|
||||
|
||||
|
||||
class Score_test_TestCase(gomill_test_support.Gomill_ParameterisedTestCase):
|
||||
"""Check score of a diagram."""
|
||||
test_name = "score_test"
|
||||
parameter_names = ('diagram', 'score')
|
||||
|
||||
def runTest(self):
|
||||
b = ascii_boards.interpret_diagram(self.diagram, 9)
|
||||
self.assertEqual(b.area_score(), self.score, "wrong score")
|
||||
|
||||
|
||||
class Setup_test_TestCase(gomill_test_support.Gomill_ParameterisedTestCase):
|
||||
"""Check apply_setup()."""
|
||||
test_name = "setup_test"
|
||||
parameter_names = ('black_points', 'white_points', 'empty_points',
|
||||
'diagram', 'is_legal')
|
||||
|
||||
def runTest(self):
|
||||
def _interpret(moves):
|
||||
return [move_from_vertex(v, b.side) for v in moves]
|
||||
|
||||
b = boards.Board(9)
|
||||
is_legal = b.apply_setup(_interpret(self.black_points),
|
||||
_interpret(self.white_points),
|
||||
_interpret(self.empty_points))
|
||||
self.assertDiagramEqual(ascii_boards.render_board(b),
|
||||
self.diagram.rstrip())
|
||||
if self.is_legal:
|
||||
self.assertTrue(is_legal, "setup should be considered legal")
|
||||
else:
|
||||
self.assertFalse(is_legal, "setup should be considered illegal")
|
227
gomill/gomill_tests/cem_tuner_tests.py
Normal file
227
gomill/gomill_tests/cem_tuner_tests.py
Normal file
@ -0,0 +1,227 @@
|
||||
"""Tests for cem_tuners.py"""
|
||||
|
||||
from __future__ import with_statement, division
|
||||
|
||||
import cPickle as pickle
|
||||
from textwrap import dedent
|
||||
|
||||
from gomill import cem_tuners
|
||||
from gomill.game_jobs import Game_job, Game_job_result
|
||||
from gomill.gtp_games import Game_result
|
||||
from gomill.cem_tuners import Parameter_config
|
||||
from gomill.competitions import (
|
||||
Player_config, CompetitionError, ControlFileError)
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
def simple_make_candidate(*args):
|
||||
if -1 in args:
|
||||
raise ValueError("oops")
|
||||
return Player_config("cand " + " ".join(map(str, args)))
|
||||
|
||||
def clip_axisb(f):
|
||||
f = float(f)
|
||||
return max(0.0, min(100.0, f))
|
||||
|
||||
def default_config():
|
||||
return {
|
||||
'board_size' : 13,
|
||||
'komi' : 7.5,
|
||||
'players' : {
|
||||
'opp' : Player_config("test"),
|
||||
},
|
||||
'candidate_colour' : 'w',
|
||||
'opponent' : 'opp',
|
||||
'parameters' : [
|
||||
Parameter_config(
|
||||
'axisa',
|
||||
initial_mean = 0.5,
|
||||
initial_variance = 1.0,
|
||||
format = "axa %.3f"),
|
||||
Parameter_config(
|
||||
'axisb',
|
||||
initial_mean = 50.0,
|
||||
initial_variance = 1000.0,
|
||||
transform = clip_axisb,
|
||||
format = "axb %.1f"),
|
||||
],
|
||||
'batch_size' : 3,
|
||||
'samples_per_generation' : 4,
|
||||
'number_of_generations' : 3,
|
||||
'elite_proportion' : 0.1,
|
||||
'step_size' : 0.8,
|
||||
'make_candidate' : simple_make_candidate,
|
||||
}
|
||||
|
||||
def test_parameter_config(tc):
|
||||
comp = cem_tuners.Cem_tuner('cemtest')
|
||||
config = default_config()
|
||||
comp.initialise_from_control_file(config)
|
||||
|
||||
tc.assertEqual(comp.initial_distribution.format(),
|
||||
" 0.50~1.00 50.00~1000.00")
|
||||
tc.assertEqual(comp.format_engine_parameters((0.5, 23)),
|
||||
"axa 0.500; axb 23.0")
|
||||
tc.assertEqual(comp.format_engine_parameters(('x', 23)),
|
||||
"[axisa?x]; axb 23.0")
|
||||
tc.assertEqual(comp.format_optimiser_parameters((0.5, 500)),
|
||||
"axa 0.500; axb 100.0")
|
||||
|
||||
tc.assertEqual(comp.transform_parameters((0.5, 1e6)), (0.5, 100.0))
|
||||
with tc.assertRaises(CompetitionError) as ar:
|
||||
comp.transform_parameters((0.5, None))
|
||||
tc.assertTracebackStringEqual(str(ar.exception), dedent("""\
|
||||
error from transform for axisb
|
||||
TypeError: expected-float
|
||||
traceback (most recent call last):
|
||||
cem_tuner_tests|clip_axisb
|
||||
failing line:
|
||||
f = float(f)
|
||||
"""), fixups=[
|
||||
("float() argument must be a string or a number", "expected-float"),
|
||||
("expected float, got NoneType object", "expected-float"),
|
||||
])
|
||||
|
||||
tc.assertRaisesRegexp(
|
||||
ValueError, "'initial_variance': must be nonnegative",
|
||||
comp.parameter_spec_from_config,
|
||||
Parameter_config('pa1', initial_mean=0,
|
||||
initial_variance=-1, format="%s"))
|
||||
tc.assertRaisesRegexp(
|
||||
ControlFileError, "'format': invalid format string",
|
||||
comp.parameter_spec_from_config,
|
||||
Parameter_config('pa1', initial_mean=0, initial_variance=1,
|
||||
format="nopct"))
|
||||
|
||||
def test_nonsense_parameter_config(tc):
|
||||
comp = cem_tuners.Cem_tuner('cemtest')
|
||||
config = default_config()
|
||||
config['parameters'].append(99)
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertMultiLineEqual(str(ar.exception), dedent("""\
|
||||
'parameters': item 2: not a Parameter"""))
|
||||
|
||||
def test_transform_check(tc):
|
||||
comp = cem_tuners.Cem_tuner('cemtest')
|
||||
config = default_config()
|
||||
config['parameters'][0] = Parameter_config(
|
||||
'axisa',
|
||||
initial_mean = 0.5,
|
||||
initial_variance = 1.0,
|
||||
transform = str.split)
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertTracebackStringEqual(str(ar.exception), dedent("""\
|
||||
parameter axisa: error from transform (applied to initial_mean)
|
||||
TypeError: split-wants-float-not-str
|
||||
traceback (most recent call last):
|
||||
"""), fixups=[
|
||||
("descriptor 'split' requires a 'str' object but received a 'float'",
|
||||
"split-wants-float-not-str"),
|
||||
("unbound method split() must be called with str instance as "
|
||||
"first argument (got float instance instead)",
|
||||
"split-wants-float-not-str"),
|
||||
])
|
||||
|
||||
def test_format_validation(tc):
|
||||
comp = cem_tuners.Cem_tuner('cemtest')
|
||||
config = default_config()
|
||||
config['parameters'][0] = Parameter_config(
|
||||
'axisa',
|
||||
initial_mean = 0.5,
|
||||
initial_variance = 1.0,
|
||||
transform = str,
|
||||
format = "axa %f")
|
||||
tc.assertRaisesRegexp(
|
||||
ControlFileError, "'format': invalid format string",
|
||||
comp.initialise_from_control_file, config)
|
||||
|
||||
def test_make_candidate(tc):
|
||||
comp = cem_tuners.Cem_tuner('cemtest')
|
||||
config = default_config()
|
||||
comp.initialise_from_control_file(config)
|
||||
cand = comp.make_candidate('g0#1', (0.5, 23.0))
|
||||
tc.assertEqual(cand.code, 'g0#1')
|
||||
tc.assertListEqual(cand.cmd_args, ['cand', '0.5', '23.0'])
|
||||
with tc.assertRaises(CompetitionError) as ar:
|
||||
comp.make_candidate('g0#1', (-1, 23))
|
||||
tc.assertTracebackStringEqual(str(ar.exception), dedent("""\
|
||||
error from make_candidate()
|
||||
ValueError: oops
|
||||
traceback (most recent call last):
|
||||
cem_tuner_tests|simple_make_candidate
|
||||
failing line:
|
||||
raise ValueError("oops")
|
||||
"""))
|
||||
|
||||
def test_play(tc):
|
||||
comp = cem_tuners.Cem_tuner('cemtest')
|
||||
comp.initialise_from_control_file(default_config())
|
||||
comp.set_clean_status()
|
||||
|
||||
tc.assertEqual(comp.generation, 0)
|
||||
tc.assertEqual(comp.distribution.format(),
|
||||
" 0.50~1.00 50.00~1000.00")
|
||||
|
||||
job1 = comp.get_game()
|
||||
tc.assertIsInstance(job1, Game_job)
|
||||
tc.assertEqual(job1.game_id, 'g0#0r0')
|
||||
tc.assertEqual(job1.player_b.code, 'g0#0')
|
||||
tc.assertEqual(job1.player_w.code, 'opp')
|
||||
tc.assertEqual(job1.board_size, 13)
|
||||
tc.assertEqual(job1.komi, 7.5)
|
||||
tc.assertEqual(job1.move_limit, 1000)
|
||||
tc.assertIs(job1.use_internal_scorer, False)
|
||||
tc.assertEqual(job1.internal_scorer_handicap_compensation, 'full')
|
||||
tc.assertEqual(job1.game_data, (0, 'g0#0', 0))
|
||||
tc.assertEqual(job1.sgf_event, 'cemtest')
|
||||
tc.assertRegexpMatches(job1.sgf_note, '^Candidate parameters: axa ')
|
||||
|
||||
job2 = comp.get_game()
|
||||
tc.assertIsInstance(job2, Game_job)
|
||||
tc.assertEqual(job2.game_id, 'g0#1r0')
|
||||
tc.assertEqual(job2.player_b.code, 'g0#1')
|
||||
tc.assertEqual(job2.player_w.code, 'opp')
|
||||
|
||||
tc.assertEqual(comp.wins, [0, 0, 0, 0])
|
||||
|
||||
result1 = Game_result({'b' : 'g0#0', 'w' : 'opp'}, 'b')
|
||||
result1.sgf_result = "B+8.5"
|
||||
response1 = Game_job_result()
|
||||
response1.game_id = job1.game_id
|
||||
response1.game_result = result1
|
||||
response1.engine_names = {
|
||||
'opp' : 'opp engine:v1.2.3',
|
||||
'g0#0' : 'candidate engine',
|
||||
}
|
||||
response1.engine_descriptions = {
|
||||
'opp' : 'opp engine:v1.2.3',
|
||||
'g0#0' : 'candidate engine description',
|
||||
}
|
||||
response1.game_data = job1.game_data
|
||||
comp.process_game_result(response1)
|
||||
|
||||
tc.assertEqual(comp.wins, [1, 0, 0, 0])
|
||||
|
||||
comp2 = cem_tuners.Cem_tuner('cemtest')
|
||||
comp2.initialise_from_control_file(default_config())
|
||||
status = pickle.loads(pickle.dumps(comp.get_status()))
|
||||
comp2.set_status(status)
|
||||
tc.assertEqual(comp2.wins, [1, 0, 0, 0])
|
||||
|
||||
result2 = Game_result({'b' : 'g0#1', 'w' : 'opp'}, None)
|
||||
result2.set_jigo()
|
||||
response2 = Game_job_result()
|
||||
response2.game_id = job2.game_id
|
||||
response2.game_result = result2
|
||||
response2.engine_names = response1.engine_names
|
||||
response2.engine_descriptions = response1.engine_descriptions
|
||||
response2.game_data = job2.game_data
|
||||
comp.process_game_result(response2)
|
||||
|
||||
tc.assertEqual(comp.wins, [1, 0.5, 0, 0])
|
||||
|
72
gomill/gomill_tests/common_tests.py
Normal file
72
gomill/gomill_tests/common_tests.py
Normal file
@ -0,0 +1,72 @@
|
||||
"""Tests for common.py."""
|
||||
|
||||
import string
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
|
||||
from gomill import common
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
def test_opponent_of(tc):
|
||||
oo = common.opponent_of
|
||||
tc.assertEqual(oo('b'), 'w')
|
||||
tc.assertEqual(oo('w'), 'b')
|
||||
tc.assertRaises(ValueError, oo, 'x')
|
||||
tc.assertRaises(ValueError, oo, None)
|
||||
tc.assertRaises(ValueError, oo, 'B')
|
||||
|
||||
def test_colour_name(tc):
|
||||
cn = common.colour_name
|
||||
tc.assertEqual(cn('b'), 'black')
|
||||
tc.assertEqual(cn('w'), 'white')
|
||||
tc.assertRaises(ValueError, cn, 'x')
|
||||
tc.assertRaises(ValueError, cn, None)
|
||||
tc.assertRaises(ValueError, cn, 'B')
|
||||
|
||||
def test_column_letters(tc):
|
||||
tc.assertEqual(common.column_letters,
|
||||
"".join(c for c in string.uppercase if c != 'I'))
|
||||
|
||||
def test_format_vertex(tc):
|
||||
fv = common.format_vertex
|
||||
tc.assertEqual(fv(None), "pass")
|
||||
tc.assertEqual(fv((0, 0)), "A1")
|
||||
tc.assertEqual(fv((8, 8)), "J9")
|
||||
tc.assertEqual(fv((1, 5)), "F2")
|
||||
tc.assertEqual(fv((24, 24)), "Z25")
|
||||
tc.assertRaises(ValueError, fv, (-1, 2))
|
||||
tc.assertRaises(ValueError, fv, (2, -1))
|
||||
tc.assertRaises(ValueError, fv, (25, 1))
|
||||
tc.assertRaises(ValueError, fv, (1, 25))
|
||||
|
||||
def test_format_vertex_list(tc):
|
||||
fvl = common.format_vertex_list
|
||||
tc.assertEqual(fvl([]), "")
|
||||
tc.assertEqual(fvl([(0, 0)]), "A1")
|
||||
tc.assertEqual(fvl([(0, 0), (1, 5)]), "A1,F2")
|
||||
tc.assertEqual(fvl([(0, 0), None, (1, 5)]), "A1,pass,F2")
|
||||
|
||||
def test_move_from_vertex(tc):
|
||||
cv = common.move_from_vertex
|
||||
tc.assertEqual(cv("pass", 9), None)
|
||||
tc.assertEqual(cv("pAss", 9), None)
|
||||
tc.assertEqual(cv("A1", 9), (0, 0))
|
||||
tc.assertEqual(cv("a1", 9), (0, 0))
|
||||
tc.assertEqual(cv("A01", 9), (0, 0))
|
||||
tc.assertEqual(cv("J9", 9), (8, 8))
|
||||
tc.assertEqual(cv("M11", 19), (10, 11))
|
||||
tc.assertRaises(ValueError, cv, "M11", 9)
|
||||
tc.assertRaises(ValueError, cv, "K9", 9)
|
||||
tc.assertRaises(ValueError, cv, "J10", 9)
|
||||
tc.assertRaises(ValueError, cv, "I5", 9)
|
||||
tc.assertRaises(ValueError, cv, "", 9)
|
||||
tc.assertRaises(ValueError, cv, "29", 9)
|
||||
tc.assertRaises(ValueError, cv, "@9", 9)
|
||||
tc.assertRaises(ValueError, cv, "A-3", 9)
|
||||
tc.assertRaises(ValueError, cv, None, 9)
|
||||
tc.assertRaises(ValueError, cv, "A1", 0)
|
||||
tc.assertRaises(ValueError, cv, "A1", 30)
|
||||
|
98
gomill/gomill_tests/competition_scheduler_tests.py
Normal file
98
gomill/gomill_tests/competition_scheduler_tests.py
Normal file
@ -0,0 +1,98 @@
|
||||
"""Tests for competition_schedulers.py"""
|
||||
|
||||
import cPickle as pickle
|
||||
|
||||
from gomill import competition_schedulers
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
def test_simple(tc):
|
||||
sc = competition_schedulers.Simple_scheduler()
|
||||
|
||||
def issue(n):
|
||||
result = [sc.issue() for _ in xrange(n)]
|
||||
sc._check_consistent()
|
||||
return result
|
||||
|
||||
sc._check_consistent()
|
||||
tc.assertEqual(issue(4), [0, 1, 2, 3])
|
||||
sc.fix(2)
|
||||
sc._check_consistent()
|
||||
sc.fix(1)
|
||||
sc._check_consistent()
|
||||
tc.assertEqual(sc.issue(), 4)
|
||||
tc.assertEqual(sc.fixed, 2)
|
||||
tc.assertEqual(sc.issued, 5)
|
||||
sc.rollback()
|
||||
sc._check_consistent()
|
||||
tc.assertEqual(sc.issued, 2)
|
||||
tc.assertEqual(sc.fixed, 2)
|
||||
|
||||
tc.assertListEqual(issue(2), [0, 3])
|
||||
|
||||
sc.rollback()
|
||||
sc._check_consistent()
|
||||
|
||||
tc.assertListEqual(issue(4), [0, 3, 4, 5])
|
||||
sc.fix(3)
|
||||
sc._check_consistent()
|
||||
sc.fix(5)
|
||||
sc._check_consistent()
|
||||
tc.assertEqual(sc.issue(), 6)
|
||||
sc._check_consistent()
|
||||
|
||||
sc = pickle.loads(pickle.dumps(sc))
|
||||
sc._check_consistent()
|
||||
sc.rollback()
|
||||
sc._check_consistent()
|
||||
|
||||
tc.assertListEqual(issue(6), [0, 4, 6, 7, 8, 9])
|
||||
tc.assertEqual(sc.issued, 10)
|
||||
tc.assertEqual(sc.fixed, 4)
|
||||
|
||||
|
||||
def test_grouped(tc):
|
||||
sc = competition_schedulers.Group_scheduler()
|
||||
def issue(n):
|
||||
return [sc.issue() for _ in xrange(n)]
|
||||
|
||||
sc.set_groups([('m1', 4), ('m2', None)])
|
||||
|
||||
tc.assertTrue(sc.nothing_issued_yet())
|
||||
tc.assertFalse(sc.all_fixed())
|
||||
|
||||
tc.assertListEqual(issue(3), [
|
||||
('m1', 0),
|
||||
('m2', 0),
|
||||
('m1', 1),
|
||||
])
|
||||
|
||||
tc.assertFalse(sc.nothing_issued_yet())
|
||||
|
||||
sc.fix('m1', 1)
|
||||
sc.rollback()
|
||||
issued = issue(14)
|
||||
tc.assertListEqual(issued, [
|
||||
('m2', 0),
|
||||
('m1', 0),
|
||||
('m2', 1),
|
||||
('m1', 2),
|
||||
('m2', 2),
|
||||
('m1', 3),
|
||||
('m2', 3),
|
||||
('m2', 4),
|
||||
('m2', 5),
|
||||
('m2', 6),
|
||||
('m2', 7),
|
||||
('m2', 8),
|
||||
('m2', 9),
|
||||
('m2', 10),
|
||||
])
|
||||
tc.assertFalse(sc.all_fixed())
|
||||
for token in issued:
|
||||
sc.fix(*token)
|
||||
tc.assertTrue(sc.all_fixed())
|
80
gomill/gomill_tests/competition_test_support.py
Normal file
80
gomill/gomill_tests/competition_test_support.py
Normal file
@ -0,0 +1,80 @@
|
||||
"""Test support code for testing Competitions and Ringmasters."""
|
||||
|
||||
import cPickle as pickle
|
||||
from cStringIO import StringIO
|
||||
|
||||
from gomill import game_jobs
|
||||
from gomill import gtp_games
|
||||
|
||||
def fake_response(job, winner):
|
||||
"""Produce a response for the specified job.
|
||||
|
||||
job -- Game_job
|
||||
winner -- winning colour (None for a jigo, 'unknown' for unknown result)
|
||||
|
||||
The winning margin (if not a jigo) is 1.5.
|
||||
|
||||
"""
|
||||
players = {'b' : job.player_b.code, 'w' : job.player_w.code}
|
||||
if winner == 'unknown':
|
||||
winner = None
|
||||
is_unknown = True
|
||||
else:
|
||||
is_unknown = False
|
||||
result = gtp_games.Game_result(players, winner)
|
||||
result.game_id = job.game_id
|
||||
if winner is None:
|
||||
if is_unknown:
|
||||
result.sgf_result = "Void"
|
||||
result.detail = "fake unknown result"
|
||||
else:
|
||||
result.set_jigo()
|
||||
else:
|
||||
result.sgf_result += "1.5"
|
||||
response = game_jobs.Game_job_result()
|
||||
response.game_id = job.game_id
|
||||
response.game_result = result
|
||||
response.engine_names = {
|
||||
job.player_b.code : '%s engine:v1.2.3' % job.player_b.code,
|
||||
job.player_w.code : '%s engine' % job.player_w.code,
|
||||
}
|
||||
response.engine_descriptions = {
|
||||
job.player_b.code : '%s engine:v1.2.3' % job.player_b.code,
|
||||
job.player_w.code : '%s engine\ntestdescription' % job.player_w.code,
|
||||
}
|
||||
response.game_data = job.game_data
|
||||
response.warnings = []
|
||||
response.log_entries = []
|
||||
return response
|
||||
|
||||
def get_screen_report(comp):
|
||||
"""Retrieve a competition's screen report."""
|
||||
out = StringIO()
|
||||
comp.write_screen_report(out)
|
||||
return out.getvalue()
|
||||
|
||||
def get_short_report(comp):
|
||||
"""Retrieve a competition's short report."""
|
||||
out = StringIO()
|
||||
comp.write_short_report(out)
|
||||
return out.getvalue()
|
||||
|
||||
def check_screen_report(tc, comp, expected):
|
||||
"""Check that a competition's screen report is as expected."""
|
||||
tc.assertMultiLineEqual(get_screen_report(comp), expected)
|
||||
|
||||
def check_round_trip(tc, comp, config):
|
||||
"""Check that a competition round-trips through saved state.
|
||||
|
||||
Makes a new Competition, loads it from comp's saved state, and checks that
|
||||
the resulting screen report is identical.
|
||||
|
||||
Returns the new Competition.
|
||||
|
||||
"""
|
||||
comp2 = comp.__class__(comp.competition_code)
|
||||
comp2.initialise_from_control_file(config)
|
||||
status = pickle.loads(pickle.dumps(comp.get_status()))
|
||||
comp2.set_status(status)
|
||||
check_screen_report(tc, comp2, get_screen_report(comp))
|
||||
return comp2
|
165
gomill/gomill_tests/competition_tests.py
Normal file
165
gomill/gomill_tests/competition_tests.py
Normal file
@ -0,0 +1,165 @@
|
||||
"""Tests for competitions.py"""
|
||||
|
||||
import os
|
||||
|
||||
from gomill import competitions
|
||||
from gomill.competitions import ControlFileError, Player_config
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
def test_global_config(tc):
|
||||
comp = competitions.Competition('test')
|
||||
config = {
|
||||
'description' : "\nsome\ndescription ",
|
||||
'players' : {},
|
||||
}
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertEqual(comp.description, "some\ndescription")
|
||||
|
||||
def test_player_config(tc):
|
||||
comp = competitions.Competition('test')
|
||||
p1 = comp.game_jobs_player_from_config('pp', Player_config("cmd"))
|
||||
tc.assertEqual(p1.code, 'pp')
|
||||
tc.assertEqual(p1.cmd_args, ["cmd"])
|
||||
p2 = comp.game_jobs_player_from_config('pp', Player_config(command="cmd"))
|
||||
tc.assertEqual(p2.code, 'pp')
|
||||
tc.assertEqual(p2.cmd_args, ["cmd"])
|
||||
|
||||
tc.assertRaisesRegexp(
|
||||
Exception, "'command' not specified",
|
||||
comp.game_jobs_player_from_config, 'pp',
|
||||
Player_config())
|
||||
tc.assertRaisesRegexp(
|
||||
Exception, "too many positional arguments",
|
||||
comp.game_jobs_player_from_config, 'pp',
|
||||
Player_config("cmd", "xxx"))
|
||||
tc.assertRaisesRegexp(
|
||||
Exception, "command specified as both positional and keyword argument",
|
||||
comp.game_jobs_player_from_config, 'pp',
|
||||
Player_config("cmd", command="cmd2"))
|
||||
tc.assertRaisesRegexp(
|
||||
Exception, "unknown argument 'unexpected'",
|
||||
comp.game_jobs_player_from_config, 'pp',
|
||||
Player_config("cmd", unexpected=3))
|
||||
|
||||
def test_bad_player(tc):
|
||||
comp = competitions.Competition('test')
|
||||
config = {
|
||||
'players' : {
|
||||
't1' : Player_config("test"),
|
||||
't2' : None,
|
||||
}
|
||||
}
|
||||
tc.assertRaisesRegexp(
|
||||
ControlFileError, "'players': bad value for 't2': not a Player",
|
||||
comp.initialise_from_control_file, config)
|
||||
|
||||
def test_player_command(tc):
|
||||
comp = competitions.Competition('test')
|
||||
comp.set_base_directory("/base")
|
||||
config = {
|
||||
'players' : {
|
||||
't1' : Player_config("test"),
|
||||
't2' : Player_config("/bin/test foo"),
|
||||
't3' : Player_config(["bin/test", "foo"]),
|
||||
't4' : Player_config("~/test foo"),
|
||||
't5' : Player_config("~root"),
|
||||
}
|
||||
}
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertEqual(comp.players['t1'].cmd_args, ["test"])
|
||||
tc.assertEqual(comp.players['t2'].cmd_args, ["/bin/test", "foo"])
|
||||
tc.assertEqual(comp.players['t3'].cmd_args, ["/base/bin/test", "foo"])
|
||||
tc.assertEqual(comp.players['t4'].cmd_args,
|
||||
[os.path.expanduser("~") + "/test", "foo"])
|
||||
tc.assertEqual(comp.players['t5'].cmd_args, ["~root"])
|
||||
|
||||
def test_player_is_reliable_scorer(tc):
|
||||
comp = competitions.Competition('test')
|
||||
config = {
|
||||
'players' : {
|
||||
't1' : Player_config("test"),
|
||||
't2' : Player_config("test", is_reliable_scorer=False),
|
||||
't3' : Player_config("test", is_reliable_scorer=True),
|
||||
}
|
||||
}
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertTrue(comp.players['t1'].is_reliable_scorer)
|
||||
tc.assertFalse(comp.players['t2'].is_reliable_scorer)
|
||||
tc.assertTrue(comp.players['t3'].is_reliable_scorer)
|
||||
|
||||
def test_player_cwd(tc):
|
||||
comp = competitions.Competition('test')
|
||||
comp.set_base_directory("/base")
|
||||
config = {
|
||||
'players' : {
|
||||
't1' : Player_config("test"),
|
||||
't2' : Player_config("test", cwd="/abs"),
|
||||
't3' : Player_config("test", cwd="rel/sub"),
|
||||
't4' : Player_config("test", cwd="."),
|
||||
't5' : Player_config("test", cwd="~/tmp/sub"),
|
||||
}
|
||||
}
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertIsNone(comp.players['t1'].cwd)
|
||||
tc.assertEqual(comp.players['t2'].cwd, "/abs")
|
||||
tc.assertEqual(comp.players['t3'].cwd, "/base/rel/sub")
|
||||
tc.assertEqual(comp.players['t4'].cwd, "/base/.")
|
||||
tc.assertEqual(comp.players['t5'].cwd, os.path.expanduser("~") + "/tmp/sub")
|
||||
|
||||
def test_player_stderr(tc):
|
||||
comp = competitions.Competition('test')
|
||||
config = {
|
||||
'players' : {
|
||||
't1' : Player_config("test"),
|
||||
't2' : Player_config("test", discard_stderr=True),
|
||||
't3' : Player_config("test", discard_stderr=False),
|
||||
}
|
||||
}
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertIs(comp.players['t1'].discard_stderr, False)
|
||||
tc.assertEqual(comp.players['t2'].discard_stderr, True)
|
||||
tc.assertIs(comp.players['t3'].discard_stderr, False)
|
||||
|
||||
def test_player_startup_gtp_commands(tc):
|
||||
comp = competitions.Competition('test')
|
||||
config = {
|
||||
'players' : {
|
||||
't1' : Player_config(
|
||||
"test", startup_gtp_commands=["foo"]),
|
||||
't2' : Player_config(
|
||||
"test", startup_gtp_commands=["foo bar baz"]),
|
||||
't3' : Player_config(
|
||||
"test", startup_gtp_commands=[["foo", "bar", "baz"]]),
|
||||
't4' : Player_config(
|
||||
"test", startup_gtp_commands=[
|
||||
"xyzzy test",
|
||||
["foo", "bar", "baz"]]),
|
||||
}
|
||||
}
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertListEqual(comp.players['t1'].startup_gtp_commands,
|
||||
[("foo", [])])
|
||||
tc.assertListEqual(comp.players['t2'].startup_gtp_commands,
|
||||
[("foo", ["bar", "baz"])])
|
||||
tc.assertListEqual(comp.players['t3'].startup_gtp_commands,
|
||||
[("foo", ["bar", "baz"])])
|
||||
tc.assertListEqual(comp.players['t4'].startup_gtp_commands,
|
||||
[("xyzzy", ["test"]),
|
||||
("foo", ["bar", "baz"])])
|
||||
|
||||
def test_player_gtp_aliases(tc):
|
||||
comp = competitions.Competition('test')
|
||||
config = {
|
||||
'players' : {
|
||||
't1' : Player_config(
|
||||
"test", gtp_aliases={'foo' : 'bar', 'baz' : 'quux'}),
|
||||
}
|
||||
}
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertDictEqual(comp.players['t1'].gtp_aliases,
|
||||
{'foo' : 'bar', 'baz' : 'quux'})
|
||||
|
458
gomill/gomill_tests/game_job_tests.py
Normal file
458
gomill/gomill_tests/game_job_tests.py
Normal file
@ -0,0 +1,458 @@
|
||||
"""Tests for game_jobs.py"""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import os
|
||||
from textwrap import dedent
|
||||
|
||||
from gomill import gtp_controller
|
||||
from gomill import game_jobs
|
||||
from gomill.gtp_engine import GtpError, GtpFatalError
|
||||
from gomill.job_manager import JobFailed
|
||||
|
||||
from gomill_tests import test_framework
|
||||
from gomill_tests import gomill_test_support
|
||||
from gomill_tests import gtp_engine_fixtures
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
### Game_job proper
|
||||
|
||||
class Test_game_job(game_jobs.Game_job):
|
||||
"""Variant of Game_job that doesn't write sgf files.
|
||||
|
||||
Additional attributes:
|
||||
_sgf_pathname_written -- pathname sgf file would have been written to
|
||||
_sgf_written -- contents that would have been written
|
||||
_mkdir_pathname -- directory pathname that would have been created
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
game_jobs.Game_job.__init__(self, *args, **kwargs)
|
||||
self._sgf_pathname_written = None
|
||||
self._sgf_written = None
|
||||
self._mkdir_pathname = None
|
||||
|
||||
def _write_sgf(self, pathname, sgf_string):
|
||||
self._sgf_pathname_written = pathname
|
||||
self._sgf_written = sgf_string
|
||||
|
||||
def _mkdir(self, pathname):
|
||||
self._mkdir_pathname = pathname
|
||||
|
||||
def _get_sgf_written(self):
|
||||
"""Return the 'scrubbed' sgf contents."""
|
||||
return gomill_test_support.scrub_sgf(self._sgf_written)
|
||||
|
||||
class Game_job_fixture(test_framework.Fixture):
|
||||
"""Fixture setting up a Game_job.
|
||||
|
||||
attributes:
|
||||
job -- game_jobs.Game_job (in fact, a Test_game_job)
|
||||
|
||||
"""
|
||||
def __init__(self, tc):
|
||||
player_b = game_jobs.Player()
|
||||
player_b.code = 'one'
|
||||
player_b.cmd_args = ['test', 'id=one']
|
||||
player_w = game_jobs.Player()
|
||||
player_w.code = 'two'
|
||||
player_w.cmd_args = ['test', 'id=two']
|
||||
self.job = Test_game_job()
|
||||
self.job.game_id = 'gameid'
|
||||
self.job.player_b = player_b
|
||||
self.job.player_w = player_w
|
||||
self.job.board_size = 9
|
||||
self.job.komi = 7.5
|
||||
self.job.move_limit = 1000
|
||||
self.job.sgf_dirname = "/sgf/test.games"
|
||||
self.job.void_sgf_dirname = "/sgf/test.void"
|
||||
self.job.sgf_filename = "gjtest.sgf"
|
||||
|
||||
def test_player_copy(tc):
|
||||
gj = Game_job_fixture(tc)
|
||||
p1 = gj.job.player_b
|
||||
p2 = p1.copy("clone")
|
||||
tc.assertEqual(p2.code, "clone")
|
||||
tc.assertEqual(p2.cmd_args, ['test', 'id=one'])
|
||||
tc.assertIsNot(p1.cmd_args, p2.cmd_args)
|
||||
|
||||
def test_game_job(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.game_data = 'gamedata'
|
||||
gj.job.sgf_game_name = "gjt 0_000"
|
||||
gj.job.sgf_event = "game_job_tests"
|
||||
gj.job.sgf_note = "test sgf_note\non two lines"
|
||||
result = gj.job.run()
|
||||
# Win by 18 on the board minus 7.5 komi
|
||||
tc.assertEqual(result.game_result.sgf_result, "B+10.5")
|
||||
tc.assertEqual(result.game_id, 'gameid')
|
||||
tc.assertEqual(result.game_result.game_id, 'gameid')
|
||||
tc.assertEqual(result.game_data, 'gamedata')
|
||||
tc.assertEqual(result.warnings, [])
|
||||
tc.assertEqual(result.log_entries, [])
|
||||
channel = fx.get_channel('one')
|
||||
tc.assertIsNone(channel.requested_stderr)
|
||||
tc.assertIsNone(channel.requested_cwd)
|
||||
tc.assertIsNone(channel.requested_env)
|
||||
tc.assertEqual(gj.job._sgf_pathname_written, '/sgf/test.games/gjtest.sgf')
|
||||
tc.assertIsNone(gj.job._mkdir_pathname)
|
||||
tc.assertMultiLineEqual(gj.job._get_sgf_written(), dedent("""\
|
||||
(;FF[4]AP[gomill:VER]
|
||||
C[Event: game_job_tests
|
||||
Game id gameid
|
||||
Date ***
|
||||
Result one beat two B+10.5
|
||||
test sgf_note
|
||||
on two lines
|
||||
Black one one
|
||||
White two two]
|
||||
CA[UTF-8]DT[***]EV[game_job_tests]GM[1]GN[gjt 0_000]KM[7.5]PB[one]
|
||||
PW[two]RE[B+10.5]SZ[9];B[ei];W[gi];B[eh];W[gh];B[eg];W[gg];B[ef];W[gf];B[ee];
|
||||
W[ge];B[ed];W[gd];B[ec];W[gc];B[eb];W[gb];B[ea];W[ga];B[tt];
|
||||
C[one beat two B+10.5]W[tt])
|
||||
"""))
|
||||
|
||||
def test_duplicate_player_codes(tc):
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_w.code = "one"
|
||||
tc.assertRaisesRegexp(
|
||||
JobFailed, "error creating game: player codes must be distinct",
|
||||
gj.job.run)
|
||||
|
||||
def test_game_job_no_sgf(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.sgf_dirname = None
|
||||
result = gj.job.run()
|
||||
tc.assertEqual(result.game_result.sgf_result, "B+10.5")
|
||||
tc.assertIsNone(gj.job._sgf_pathname_written)
|
||||
|
||||
def test_game_job_forfeit(tc):
|
||||
def handle_genmove(args):
|
||||
raise GtpError("error")
|
||||
def reject_genmove(channel):
|
||||
channel.engine.add_command('genmove', handle_genmove)
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
fx.register_init_callback('reject_genmove', reject_genmove)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_w.cmd_args.append('init=reject_genmove')
|
||||
result = gj.job.run()
|
||||
tc.assertEqual(result.game_result.sgf_result, "B+F")
|
||||
tc.assertEqual(
|
||||
result.game_result.detail,
|
||||
"forfeit: failure response from 'genmove w' to player two:\n"
|
||||
"error")
|
||||
tc.assertEqual(
|
||||
result.warnings,
|
||||
["forfeit: failure response from 'genmove w' to player two:\n"
|
||||
"error"])
|
||||
tc.assertEqual(result.log_entries, [])
|
||||
tc.assertEqual(gj.job._sgf_pathname_written, '/sgf/test.games/gjtest.sgf')
|
||||
|
||||
def test_game_job_forfeit_and_quit(tc):
|
||||
def handle_genmove(args):
|
||||
raise GtpFatalError("I'm out of here")
|
||||
def reject_genmove(channel):
|
||||
channel.engine.add_command('genmove', handle_genmove)
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
fx.register_init_callback('reject_genmove', reject_genmove)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_w.cmd_args.append('init=reject_genmove')
|
||||
result = gj.job.run()
|
||||
tc.assertEqual(result.game_result.sgf_result, "B+F")
|
||||
tc.assertEqual(
|
||||
result.game_result.detail,
|
||||
"forfeit: failure response from 'genmove w' to player two:\n"
|
||||
"I'm out of here")
|
||||
tc.assertEqual(
|
||||
result.warnings,
|
||||
["forfeit: failure response from 'genmove w' to player two:\n"
|
||||
"I'm out of here"])
|
||||
tc.assertEqual(
|
||||
result.log_entries,
|
||||
["error sending 'known_command gomill-cpu_time' to player two:\n"
|
||||
"engine has closed the command channel"])
|
||||
tc.assertEqual(gj.job._sgf_pathname_written, '/sgf/test.games/gjtest.sgf')
|
||||
|
||||
def test_game_job_exec_failure(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_w.cmd_args.append('fail=startup')
|
||||
with tc.assertRaises(JobFailed) as ar:
|
||||
gj.job.run()
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"aborting game due to error:\n"
|
||||
"error starting subprocess for player two:\n"
|
||||
"exec forced to fail")
|
||||
# No void sgf file unless at least one move was played
|
||||
tc.assertIsNone(gj.job._sgf_pathname_written)
|
||||
|
||||
def test_game_job_channel_error(tc):
|
||||
def fail_first_genmove(channel):
|
||||
channel.fail_command = 'genmove'
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
fx.register_init_callback('fail_first_genmove', fail_first_genmove)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_w.cmd_args.append('init=fail_first_genmove')
|
||||
with tc.assertRaises(JobFailed) as ar:
|
||||
gj.job.run()
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"aborting game due to error:\n"
|
||||
"transport error sending 'genmove w' to player two:\n"
|
||||
"forced failure for send_command_line")
|
||||
tc.assertEqual(gj.job._sgf_pathname_written, '/sgf/test.void/gjtest.sgf')
|
||||
tc.assertEqual(gj.job._mkdir_pathname, '/sgf/test.void')
|
||||
tc.assertMultiLineEqual(gj.job._get_sgf_written(), dedent("""\
|
||||
(;FF[4]AP[gomill:VER]
|
||||
C[Game id gameid
|
||||
Date ***
|
||||
Black one one
|
||||
White two two]CA[UTF-8]
|
||||
DT[***]GM[1]GN[gameid]KM[7.5]PB[one]PW[two]RE[Void]SZ[9];B[ei]
|
||||
C[aborting game due to error:
|
||||
transport error sending 'genmove w' to player two:
|
||||
forced failure for send_command_line]
|
||||
)
|
||||
"""))
|
||||
|
||||
def test_game_job_late_errors(tc):
|
||||
def fail_close(channel):
|
||||
channel.fail_close = True
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
fx.register_init_callback('fail_close', fail_close)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_w.cmd_args.append('init=fail_close')
|
||||
result = gj.job.run()
|
||||
tc.assertEqual(result.game_result.sgf_result, "B+10.5")
|
||||
tc.assertEqual(result.warnings, [])
|
||||
tc.assertEqual(result.log_entries,
|
||||
["error closing player two:\nforced failure for close"])
|
||||
|
||||
def test_game_job_late_error_from_void_game(tc):
|
||||
def fail_genmove_and_close(channel):
|
||||
channel.fail_command = 'genmove'
|
||||
channel.fail_close = True
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
fx.register_init_callback('fail_genmove_and_close', fail_genmove_and_close)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_w.cmd_args.append('init=fail_genmove_and_close')
|
||||
with tc.assertRaises(JobFailed) as ar:
|
||||
gj.job.run()
|
||||
tc.assertMultiLineEqual(
|
||||
str(ar.exception),
|
||||
"aborting game due to error:\n"
|
||||
"transport error sending 'genmove w' to player two:\n"
|
||||
"forced failure for send_command_line\n"
|
||||
"also:\n"
|
||||
"error closing player two:\n"
|
||||
"forced failure for close")
|
||||
tc.assertEqual(gj.job._sgf_pathname_written, '/sgf/test.void/gjtest.sgf')
|
||||
|
||||
def test_game_job_cwd_env(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_b.cwd = "/nonexistent_directory"
|
||||
gj.job.player_b.environ = {'GOMILL_TEST' : 'gomill'}
|
||||
result = gj.job.run()
|
||||
channel = fx.get_channel('one')
|
||||
tc.assertIsNone(channel.requested_stderr)
|
||||
tc.assertEqual(channel.requested_cwd, "/nonexistent_directory")
|
||||
tc.assertEqual(channel.requested_env['GOMILL_TEST'], 'gomill')
|
||||
# Check environment was merged, not replaced
|
||||
tc.assertIn('PATH', channel.requested_env)
|
||||
tc.assertEqual(gj.job._sgf_pathname_written, '/sgf/test.games/gjtest.sgf')
|
||||
|
||||
def test_game_job_stderr_discarded(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_b.discard_stderr = True
|
||||
result = gj.job.run()
|
||||
channel = fx.get_channel('one')
|
||||
tc.assertIsInstance(channel.requested_stderr, file)
|
||||
tc.assertEqual(channel.requested_stderr.name, os.devnull)
|
||||
|
||||
def test_game_job_stderr_set(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.stderr_pathname = "/dev/full"
|
||||
result = gj.job.run()
|
||||
channel = fx.get_channel('one')
|
||||
tc.assertIsInstance(channel.requested_stderr, file)
|
||||
tc.assertEqual(channel.requested_stderr.name, "/dev/full")
|
||||
|
||||
def test_game_job_stderr_set_and_discarded(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_b.discard_stderr = True
|
||||
result = gj.job.run()
|
||||
channel = fx.get_channel('one')
|
||||
tc.assertIsInstance(channel.requested_stderr, file)
|
||||
tc.assertEqual(channel.requested_stderr.name, os.devnull)
|
||||
|
||||
def test_game_job_gtp_aliases(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_w.gtp_aliases = {'genmove': 'fail'}
|
||||
result = gj.job.run()
|
||||
tc.assertEqual(result.game_result.sgf_result, "B+F")
|
||||
|
||||
def test_game_job_claim(tc):
|
||||
def handle_genmove_ex(args):
|
||||
tc.assertIn('claim', args)
|
||||
return "claim"
|
||||
def register_genmove_ex(channel):
|
||||
channel.engine.add_command('gomill-genmove_ex', handle_genmove_ex)
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
fx.register_init_callback('genmove_ex', register_genmove_ex)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_b.cmd_args.append('init=genmove_ex')
|
||||
gj.job.player_w.cmd_args.append('init=genmove_ex')
|
||||
gj.job.player_w.allow_claim = True
|
||||
result = gj.job.run()
|
||||
tc.assertEqual(result.game_result.sgf_result, "W+")
|
||||
tc.assertEqual(gj.job._sgf_pathname_written, '/sgf/test.games/gjtest.sgf')
|
||||
|
||||
def test_game_job_handicap(tc):
|
||||
def handle_fixed_handicap(args):
|
||||
return "D4 K10 D10"
|
||||
def register_fixed_handicap(channel):
|
||||
channel.engine.add_command('fixed_handicap', handle_fixed_handicap)
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
fx.register_init_callback('handicap', register_fixed_handicap)
|
||||
gj = Game_job_fixture(tc)
|
||||
gj.job.player_b.cmd_args.append('init=handicap')
|
||||
gj.job.player_w.cmd_args.append('init=handicap')
|
||||
gj.job.board_size = 13
|
||||
gj.job.handicap = 3
|
||||
gj.job.handicap_is_free = False
|
||||
gj.job.internal_scorer_handicap_compensation = 'full'
|
||||
result = gj.job.run()
|
||||
# area score 53, less 7.5 komi, less 3 handicap compensation
|
||||
tc.assertEqual(result.game_result.sgf_result, "B+42.5")
|
||||
|
||||
|
||||
|
||||
### check_player
|
||||
|
||||
class Player_check_fixture(test_framework.Fixture):
|
||||
"""Fixture setting up a Player_check.
|
||||
|
||||
attributes:
|
||||
player -- game_jobs.Player
|
||||
check -- game_jobs.Player_check
|
||||
|
||||
"""
|
||||
def __init__(self, tc):
|
||||
self.player = game_jobs.Player()
|
||||
self.player.code = 'test'
|
||||
self.player.cmd_args = ['test', 'id=test']
|
||||
self.check = game_jobs.Player_check()
|
||||
self.check.player = self.player
|
||||
self.check.board_size = 9
|
||||
self.check.komi = 7.0
|
||||
|
||||
def test_check_player(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
ck = Player_check_fixture(tc)
|
||||
tc.assertEqual(game_jobs.check_player(ck.check), [])
|
||||
channel = fx.get_channel('test')
|
||||
tc.assertIsNone(channel.requested_stderr)
|
||||
tc.assertIsNone(channel.requested_cwd)
|
||||
tc.assertIsNone(channel.requested_env)
|
||||
|
||||
def test_check_player_discard_stderr(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
ck = Player_check_fixture(tc)
|
||||
tc.assertEqual(game_jobs.check_player(ck.check, discard_stderr=True), [])
|
||||
channel = fx.get_channel('test')
|
||||
tc.assertIsInstance(channel.requested_stderr, file)
|
||||
tc.assertEqual(channel.requested_stderr.name, os.devnull)
|
||||
|
||||
def test_check_player_boardsize_fails(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
engine = gtp_engine_fixtures.get_test_engine()
|
||||
fx.register_engine('no_boardsize', engine)
|
||||
ck = Player_check_fixture(tc)
|
||||
ck.player.cmd_args.append('engine=no_boardsize')
|
||||
with tc.assertRaises(game_jobs.CheckFailed) as ar:
|
||||
game_jobs.check_player(ck.check)
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"failure response from 'boardsize 9' to test:\n"
|
||||
"unknown command")
|
||||
|
||||
def test_check_player_startup_gtp_commands(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
ck = Player_check_fixture(tc)
|
||||
ck.player.startup_gtp_commands = [('list_commands', []),
|
||||
('nonexistent', ['command'])]
|
||||
with tc.assertRaises(game_jobs.CheckFailed) as ar:
|
||||
game_jobs.check_player(ck.check)
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"failure response from 'nonexistent command' to test:\n"
|
||||
"unknown command")
|
||||
|
||||
def test_check_player_nonexistent_cwd(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
ck = Player_check_fixture(tc)
|
||||
ck.player.cwd = "/nonexistent/directory"
|
||||
with tc.assertRaises(game_jobs.CheckFailed) as ar:
|
||||
game_jobs.check_player(ck.check)
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"bad working directory: /nonexistent/directory")
|
||||
|
||||
def test_check_player_cwd(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
ck = Player_check_fixture(tc)
|
||||
ck.player.cwd = "/"
|
||||
tc.assertEqual(game_jobs.check_player(ck.check), [])
|
||||
channel = fx.get_channel('test')
|
||||
tc.assertEqual(channel.requested_cwd, "/")
|
||||
|
||||
def test_check_player_env(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
ck = Player_check_fixture(tc)
|
||||
ck.player.environ = {'GOMILL_TEST' : 'gomill'}
|
||||
tc.assertEqual(game_jobs.check_player(ck.check), [])
|
||||
channel = fx.get_channel('test')
|
||||
tc.assertEqual(channel.requested_env['GOMILL_TEST'], 'gomill')
|
||||
# Check environment was merged, not replaced
|
||||
tc.assertIn('PATH', channel.requested_env)
|
||||
|
||||
def test_check_player_exec_failure(tc):
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
ck = Player_check_fixture(tc)
|
||||
ck.player.cmd_args.append('fail=startup')
|
||||
with tc.assertRaises(game_jobs.CheckFailed) as ar:
|
||||
game_jobs.check_player(ck.check)
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"error starting subprocess for test:\n"
|
||||
"exec forced to fail")
|
||||
|
||||
def test_check_player_channel_error(tc):
|
||||
def fail_first_command(channel):
|
||||
channel.fail_next_command = True
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
fx.register_init_callback('fail_first_command', fail_first_command)
|
||||
ck = Player_check_fixture(tc)
|
||||
ck.player.cmd_args.append('init=fail_first_command')
|
||||
with tc.assertRaises(game_jobs.CheckFailed) as ar:
|
||||
game_jobs.check_player(ck.check)
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"transport error sending first command (protocol_version) "
|
||||
"to test:\n"
|
||||
"forced failure for send_command_line")
|
||||
|
||||
def test_check_player_channel_error_on_close(tc):
|
||||
def fail_close(channel):
|
||||
channel.fail_close = True
|
||||
fx = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
fx.register_init_callback('fail_close', fail_close)
|
||||
ck = Player_check_fixture(tc)
|
||||
ck.player.cmd_args.append('init=fail_close')
|
||||
tc.assertEqual(game_jobs.check_player(ck.check),
|
||||
["error closing test:\nforced failure for close"])
|
||||
|
161
gomill/gomill_tests/gomill_test_support.py
Normal file
161
gomill/gomill_tests/gomill_test_support.py
Normal file
@ -0,0 +1,161 @@
|
||||
"""Gomill-specific test support code."""
|
||||
|
||||
import re
|
||||
|
||||
from gomill import __version__
|
||||
from gomill_tests.test_framework import unittest2
|
||||
from gomill_tests import test_framework
|
||||
|
||||
from gomill.common import *
|
||||
from gomill import ascii_boards
|
||||
from gomill import boards
|
||||
|
||||
# This makes TestResult ignore lines from this module in tracebacks
|
||||
__unittest = True
|
||||
|
||||
def compare_boards(b1, b2):
|
||||
"""Check whether two boards have the same position.
|
||||
|
||||
returns a pair (position_is_the_same, message)
|
||||
|
||||
"""
|
||||
if b1.side != b2.side:
|
||||
raise ValueError("size is different: %s, %s" % (b1.side, b2.side))
|
||||
differences = []
|
||||
for row, col in b1.board_points:
|
||||
if b1.get(row, col) != b2.get(row, col):
|
||||
differences.append((row, col))
|
||||
if not differences:
|
||||
return True, None
|
||||
msg = "boards differ at %s" % " ".join(map(format_vertex, differences))
|
||||
try:
|
||||
msg += "\n%s\n%s" % (
|
||||
ascii_boards.render_board(b1), ascii_boards.render_board(b2))
|
||||
except Exception:
|
||||
pass
|
||||
return False, msg
|
||||
|
||||
def compare_diagrams(d1, d2):
|
||||
"""Compare two ascii board diagrams.
|
||||
|
||||
returns a pair (strings_are_equal, message)
|
||||
|
||||
(assertMultiLineEqual tends to look nasty for these, so we just show them
|
||||
both in full)
|
||||
|
||||
"""
|
||||
if d1 == d2:
|
||||
return True, None
|
||||
return False, "diagrams differ:\n%s\n\n%s" % (d1, d2)
|
||||
|
||||
def scrub_sgf(s):
|
||||
"""Normalise sgf string for convenience of testing.
|
||||
|
||||
Replaces dates with '***', and 'gomill:<__version__>' with 'gomill:VER'.
|
||||
|
||||
Be careful: gomill version length can affect line wrapping. Either
|
||||
serialise with wrap=None or remove newlines before comparing.
|
||||
|
||||
"""
|
||||
s = re.sub(r"(?m)(?<=^Date ).*$", "***", s)
|
||||
s = re.sub(r"(?<=DT\[)[-0-9]+(?=\])", "***", s)
|
||||
s = re.sub(r"gomill:" + re.escape(__version__), "gomill:VER", s)
|
||||
return s
|
||||
|
||||
|
||||
traceback_line_re = re.compile(
|
||||
r" .*/([a-z0-9_]+)\.pyc?:[0-9]+ \(([a-z0-9_]+)\)")
|
||||
|
||||
class Gomill_testcase_mixin(object):
|
||||
"""TestCase mixin adding support for gomill-specific types.
|
||||
|
||||
This adds:
|
||||
assertBoardEqual
|
||||
assertEqual and assertNotEqual for Boards
|
||||
|
||||
"""
|
||||
def init_gomill_testcase_mixin(self):
|
||||
self.addTypeEqualityFunc(boards.Board, self.assertBoardEqual)
|
||||
|
||||
def _format_message(self, msg, standardMsg):
|
||||
# This is the same as _formatMessage from python 2.7 unittest; copying
|
||||
# it because it's not part of the public API.
|
||||
if not self.longMessage:
|
||||
return msg or standardMsg
|
||||
if msg is None:
|
||||
return standardMsg
|
||||
try:
|
||||
return '%s : %s' % (standardMsg, msg)
|
||||
except UnicodeDecodeError:
|
||||
return '%s : %s' % (unittest2.util.safe_repr(standardMsg),
|
||||
unittest2.util.safe_repr(msg))
|
||||
|
||||
def assertBoardEqual(self, b1, b2, msg=None):
|
||||
are_equal, desc = compare_boards(b1, b2)
|
||||
if not are_equal:
|
||||
self.fail(self._format_message(msg, desc+"\n"))
|
||||
|
||||
def assertDiagramEqual(self, d1, d2, msg=None):
|
||||
are_equal, desc = compare_diagrams(d1, d2)
|
||||
if not are_equal:
|
||||
self.fail(self._format_message(msg, desc+"\n"))
|
||||
|
||||
def assertNotEqual(self, first, second, msg=None):
|
||||
if isinstance(first, boards.Board) and isinstance(second, boards.Board):
|
||||
are_equal, _ = compare_boards(first, second)
|
||||
if not are_equal:
|
||||
return
|
||||
msg = self._format_message(msg, 'boards have the same position')
|
||||
raise self.failureException(msg)
|
||||
super(Gomill_testcase_mixin, self).assertNotEqual(first, second, msg)
|
||||
|
||||
def assertTracebackStringEqual(self, seen, expected, fixups=()):
|
||||
"""Compare two strings which include tracebacks.
|
||||
|
||||
This is for comparing strings containing tracebacks from
|
||||
the compact_tracebacks module.
|
||||
|
||||
Replaces the traceback lines describing source locations with
|
||||
'<filename>|<functionname>', for robustness.
|
||||
|
||||
fixups -- list of pairs of strings
|
||||
(additional substitutions to make in the 'seen' string)
|
||||
|
||||
"""
|
||||
lines = seen.split("\n")
|
||||
new_lines = []
|
||||
for l in lines:
|
||||
match = traceback_line_re.match(l)
|
||||
if match:
|
||||
l = "|".join(match.groups())
|
||||
for a, b in fixups:
|
||||
l = l.replace(a, b)
|
||||
new_lines.append(l)
|
||||
self.assertMultiLineEqual("\n".join(new_lines), expected)
|
||||
|
||||
|
||||
class Gomill_SimpleTestCase(
|
||||
Gomill_testcase_mixin, test_framework.SimpleTestCase):
|
||||
"""SimpleTestCase with the Gomill mixin."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
test_framework.SimpleTestCase.__init__(self, *args, **kwargs)
|
||||
self.init_gomill_testcase_mixin()
|
||||
|
||||
class Gomill_ParameterisedTestCase(
|
||||
Gomill_testcase_mixin, test_framework.ParameterisedTestCase):
|
||||
"""ParameterisedTestCase with the Gomill mixin."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
test_framework.ParameterisedTestCase.__init__(self, *args, **kwargs)
|
||||
self.init_gomill_testcase_mixin()
|
||||
|
||||
|
||||
def make_simple_tests(source, prefix="test_"):
|
||||
"""Make test cases from a module's test_xxx functions.
|
||||
|
||||
See test_framework for details.
|
||||
|
||||
The test functions can use the Gomill_testcase_mixin enhancements.
|
||||
|
||||
"""
|
||||
return test_framework.make_simple_tests(
|
||||
source, prefix, testcase_class=Gomill_SimpleTestCase)
|
141
gomill/gomill_tests/gtp_controller_test_support.py
Normal file
141
gomill/gomill_tests/gtp_controller_test_support.py
Normal file
@ -0,0 +1,141 @@
|
||||
"""Support code for gtp_controller_tests.
|
||||
|
||||
This is also used by gtp_engine_fixtures (and so by gtp proxy tests).
|
||||
|
||||
"""
|
||||
|
||||
from gomill import gtp_controller
|
||||
from gomill.gtp_controller import (
|
||||
GtpChannelError, GtpProtocolError, GtpTransportError, GtpChannelClosed,
|
||||
BadGtpResponse)
|
||||
|
||||
from gomill_tests import test_support
|
||||
from gomill_tests.test_framework import SupporterError
|
||||
|
||||
|
||||
class Preprogrammed_gtp_channel(gtp_controller.Subprocess_gtp_channel):
|
||||
"""A Linebased_gtp_channel with hardwired response stream.
|
||||
|
||||
Instantiate with a string containing the complete response stream.
|
||||
|
||||
This sends the contents of the response stream, irrespective of what
|
||||
commands are received.
|
||||
|
||||
Pass hangs_before_eof True to simulate an engine that doesn't close its
|
||||
response pipe when the preprogrammed response data runs out.
|
||||
|
||||
The command stream is available from get_command_stream().
|
||||
|
||||
"""
|
||||
def __init__(self, response, hangs_before_eof=False):
|
||||
gtp_controller.Linebased_gtp_channel.__init__(self)
|
||||
self.command_pipe = test_support.Mock_writing_pipe()
|
||||
self.response_pipe = test_support.Mock_reading_pipe(response)
|
||||
self.response_pipe.hangs_before_eof = hangs_before_eof
|
||||
|
||||
def close(self):
|
||||
self.command_pipe.close()
|
||||
self.response_pipe.close()
|
||||
|
||||
def get_command_stream(self):
|
||||
"""Return the complete contents of the command stream sent so far."""
|
||||
return self.command_pipe.getvalue()
|
||||
|
||||
def break_command_stream(self):
|
||||
"""Break the simulated pipe for the command stream."""
|
||||
self.command_pipe.simulate_broken_pipe()
|
||||
|
||||
def break_response_stream(self):
|
||||
"""Break the simulated pipe for the response stream."""
|
||||
self.response_pipe.simulate_broken_pipe()
|
||||
|
||||
|
||||
class Testing_gtp_channel(gtp_controller.Linebased_gtp_channel):
|
||||
"""Linebased GTP channel that runs an internal Gtp_engine.
|
||||
|
||||
Instantiate with a Gtp_engine_protocol object.
|
||||
|
||||
This is used for testing how controllers handle GtpChannelError.
|
||||
|
||||
Public attributes:
|
||||
engine -- the engine it was instantiated with
|
||||
is_closed -- bool (closed() has been called without a forced error)
|
||||
|
||||
This raises an error if sent two commands without requesting a response in
|
||||
between, or if asked for a response when no command was sent since the last
|
||||
response. (GTP permits stacking up commands, but Gtp_controller should never
|
||||
do it, so we want to report it).
|
||||
|
||||
Unlike Internal_gtp_channel, this runs the command as the point when it is
|
||||
sent.
|
||||
|
||||
If you send a command after the engine has exited, this raises
|
||||
GtpChannelClosed. Set the attribute engine_exit_breaks_commands to False to
|
||||
disable this behaviour (it will ignore the command and respond with EOF
|
||||
instead).
|
||||
|
||||
You can force errors by setting the following attributes:
|
||||
fail_next_command -- bool (send_command_line raises GtpTransportError)
|
||||
fail_command -- string (like fail_next_command, if command line
|
||||
starts with this string)
|
||||
fail_next_response -- bool (get_response_line raises GtpTransportError)
|
||||
force_next_response -- string (get_response_line uses this string)
|
||||
fail_close -- bool (close raises GtpTransportError)
|
||||
|
||||
"""
|
||||
def __init__(self, engine):
|
||||
gtp_controller.Linebased_gtp_channel.__init__(self)
|
||||
self.engine = engine
|
||||
self.stored_response = ""
|
||||
self.session_is_ended = False
|
||||
self.is_closed = False
|
||||
self.engine_exit_breaks_commands = True
|
||||
self.fail_next_command = False
|
||||
self.fail_next_response = False
|
||||
self.force_next_response = None
|
||||
self.fail_close = False
|
||||
self.fail_command = None
|
||||
|
||||
def send_command_line(self, command):
|
||||
if self.is_closed:
|
||||
raise SupporterError("channel is closed")
|
||||
if self.stored_response != "":
|
||||
raise SupporterError("two commands in a row")
|
||||
if self.session_is_ended:
|
||||
if self.engine_exit_breaks_commands:
|
||||
raise GtpChannelClosed("engine has closed the command channel")
|
||||
return
|
||||
if self.fail_next_command:
|
||||
self.fail_next_command = False
|
||||
raise GtpTransportError("forced failure for send_command_line")
|
||||
if self.fail_command and command.startswith(self.fail_command):
|
||||
self.fail_command = None
|
||||
raise GtpTransportError("forced failure for send_command_line")
|
||||
cmd_list = command.strip().split(" ")
|
||||
is_error, response, end_session = \
|
||||
self.engine.run_command(cmd_list[0], cmd_list[1:])
|
||||
if end_session:
|
||||
self.session_is_ended = True
|
||||
self.stored_response = ("? " if is_error else "= ") + response + "\n\n"
|
||||
|
||||
def get_response_line(self):
|
||||
if self.is_closed:
|
||||
raise SupporterError("channel is closed")
|
||||
if self.stored_response == "":
|
||||
if self.session_is_ended:
|
||||
return ""
|
||||
raise SupporterError("response request without command")
|
||||
if self.fail_next_response:
|
||||
self.fail_next_response = False
|
||||
raise GtpTransportError("forced failure for get_response_line")
|
||||
if self.force_next_response is not None:
|
||||
self.stored_response = self.force_next_response
|
||||
self.force_next_response = None
|
||||
line, self.stored_response = self.stored_response.split("\n", 1)
|
||||
return line + "\n"
|
||||
|
||||
def close(self):
|
||||
if self.fail_close:
|
||||
raise GtpTransportError("forced failure for close")
|
||||
self.is_closed = True
|
||||
|
698
gomill/gomill_tests/gtp_controller_tests.py
Normal file
698
gomill/gomill_tests/gtp_controller_tests.py
Normal file
@ -0,0 +1,698 @@
|
||||
"""Tests for gtp_controller.py"""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import os
|
||||
|
||||
from gomill import gtp_controller
|
||||
from gomill.gtp_controller import (
|
||||
GtpChannelError, GtpProtocolError, GtpTransportError, GtpChannelClosed,
|
||||
BadGtpResponse, Gtp_controller)
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
from gomill_tests import gtp_controller_test_support
|
||||
from gomill_tests import gtp_engine_fixtures
|
||||
from gomill_tests.test_framework import SupporterError
|
||||
from gomill_tests.gtp_controller_test_support import Preprogrammed_gtp_channel
|
||||
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
|
||||
### Channel-level
|
||||
|
||||
def test_linebased_channel(tc):
|
||||
channel = Preprogrammed_gtp_channel("=\n\n=\n\n")
|
||||
tc.assertEqual(channel.get_command_stream(), "")
|
||||
channel.send_command("play", ["b", "a3"])
|
||||
tc.assertEqual(channel.get_command_stream(), "play b a3\n")
|
||||
tc.assertEqual(channel.get_response(), (False, ""))
|
||||
channel.send_command("quit", [])
|
||||
tc.assertEqual(channel.get_command_stream(), "play b a3\nquit\n")
|
||||
tc.assertEqual(channel.get_response(), (False, ""))
|
||||
tc.assertRaisesRegexp(
|
||||
GtpChannelClosed, "engine has closed the response channel",
|
||||
channel.get_response)
|
||||
channel.close()
|
||||
|
||||
def test_linebased_channel_responses(tc):
|
||||
channel = Preprogrammed_gtp_channel(
|
||||
"= 2\n\n"
|
||||
# failure response
|
||||
"? unknown command\n\n"
|
||||
# final response with no newlines
|
||||
"= ok")
|
||||
channel.send_command("protocol_version", [])
|
||||
tc.assertEqual(channel.get_response(), (False, "2"))
|
||||
channel.send_command("xyzzy", ["1", "2"])
|
||||
tc.assertEqual(channel.get_response(), (True, "unknown command"))
|
||||
channel.send_command("quit", ["1", "2"])
|
||||
tc.assertEqual(channel.get_response(), (False, "ok"))
|
||||
|
||||
def test_linebased_channel_response_cleaning(tc):
|
||||
channel = Preprogrammed_gtp_channel(
|
||||
# empty response
|
||||
"=\n\n"
|
||||
# whitespace-only response
|
||||
"= \n\n"
|
||||
# ignores CRs (GTP spec)
|
||||
"= 1abc\rde\r\n\r\n"
|
||||
# ignores extra blank lines (GTP spec)
|
||||
"= 2abcde\n\n\n\n"
|
||||
# strips control characters (GTP spec)
|
||||
"= 3a\x7fbc\x00d\x07e\n\x01\n"
|
||||
# converts tabs to spaces (GTP spec)
|
||||
"= 4abc\tde\n\n"
|
||||
# strips leading whitespace (channel docs)
|
||||
"= \t 5abcde\n\n"
|
||||
# strips trailing whitepace (channel docs)
|
||||
"= 6abcde \t \n\n"
|
||||
# doesn't strip whitespace in the middle of a multiline response
|
||||
"= 7aaa \n bbb\tccc\nddd \t \n\n"
|
||||
# passes high characters through
|
||||
"= 8ab\xc3\xa7de\n\n"
|
||||
# all this at once, in a failure response
|
||||
"? a\raa \r\n b\rbb\tcc\x01c\nddd \t \n\n"
|
||||
)
|
||||
tc.assertEqual(channel.get_response(), (False, ""))
|
||||
tc.assertEqual(channel.get_response(), (False, ""))
|
||||
tc.assertEqual(channel.get_response(), (False, "1abcde"))
|
||||
tc.assertEqual(channel.get_response(), (False, "2abcde"))
|
||||
tc.assertEqual(channel.get_response(), (False, "3abcde"))
|
||||
tc.assertEqual(channel.get_response(), (False, "4abc de"))
|
||||
tc.assertEqual(channel.get_response(), (False, "5abcde"))
|
||||
tc.assertEqual(channel.get_response(), (False, "6abcde"))
|
||||
tc.assertEqual(channel.get_response(), (False, "7aaa \n bbb ccc\nddd"))
|
||||
tc.assertEqual(channel.get_response(), (False, "8ab\xc3\xa7de"))
|
||||
tc.assertEqual(channel.get_response(), (True, "aaa \n bbb ccc\nddd"))
|
||||
|
||||
def test_linebased_channel_invalid_responses(tc):
|
||||
channel = Preprogrammed_gtp_channel(
|
||||
# good response first, to get past the "isn't speaking GTP" checking
|
||||
"=\n\n"
|
||||
"ERROR\n\n"
|
||||
"# comments not allowed in responses\n\n"
|
||||
)
|
||||
tc.assertEqual(channel.get_response(), (False, ""))
|
||||
tc.assertRaisesRegexp(
|
||||
GtpProtocolError, "^no success/failure indication from engine: "
|
||||
"first line is `ERROR`$",
|
||||
channel.get_response)
|
||||
tc.assertRaisesRegexp(
|
||||
GtpProtocolError, "^no success/failure indication from engine: "
|
||||
"first line is `#",
|
||||
channel.get_response)
|
||||
|
||||
def test_linebased_channel_without_response(tc):
|
||||
channel = Preprogrammed_gtp_channel("")
|
||||
channel.send_command("protocol_version", [])
|
||||
tc.assertRaisesRegexp(
|
||||
GtpChannelClosed, "^engine has closed the response channel$",
|
||||
channel.get_response)
|
||||
channel.close()
|
||||
|
||||
def test_linebased_channel_with_usage_message_response(tc):
|
||||
channel = Preprogrammed_gtp_channel(
|
||||
"Usage: randomprogram [options]\n\nOptions:\n"
|
||||
"--help show this help message and exit\n")
|
||||
channel.send_command("protocol_version", [])
|
||||
tc.assertRaisesRegexp(
|
||||
GtpProtocolError, "^engine isn't speaking GTP: first byte is 'U'$",
|
||||
channel.get_response)
|
||||
channel.close()
|
||||
|
||||
def test_linebased_channel_with_interactive_response(tc):
|
||||
channel = Preprogrammed_gtp_channel("prompt> \n", hangs_before_eof=True)
|
||||
channel.send_command("protocol_version", [])
|
||||
tc.assertRaisesRegexp(
|
||||
GtpProtocolError, "^engine isn't speaking GTP", channel.get_response)
|
||||
channel.close()
|
||||
|
||||
def test_linebased_channel_hang(tc):
|
||||
# Correct behaviour for a GTP controller here is to wait for a newline.
|
||||
# (Would be nice to have a timeout.)
|
||||
# This serves as a check that the hangs_before_eof modelling is working.
|
||||
channel = Preprogrammed_gtp_channel("=prompt> ", hangs_before_eof=True)
|
||||
channel.send_command("protocol_version", [])
|
||||
tc.assertRaisesRegexp(
|
||||
SupporterError, "this would hang", channel.get_response)
|
||||
channel.close()
|
||||
|
||||
def test_linebased_channel_with_gmp_response(tc):
|
||||
channel = Preprogrammed_gtp_channel("\x01\xa1\xa0\x80",
|
||||
hangs_before_eof=True)
|
||||
channel.send_command("protocol_version", [])
|
||||
tc.assertRaisesRegexp(
|
||||
GtpProtocolError, "appears to be speaking GMP", channel.get_response)
|
||||
channel.close()
|
||||
|
||||
def test_linebased_channel_with_broken_command_pipe(tc):
|
||||
channel = Preprogrammed_gtp_channel(
|
||||
"Usage: randomprogram [options]\n\nOptions:\n"
|
||||
"--help show this help message and exit\n")
|
||||
channel.break_command_stream()
|
||||
tc.assertRaisesRegexp(
|
||||
GtpChannelClosed, "^engine has closed the command channel$",
|
||||
channel.send_command, "protocol_version", [])
|
||||
channel.close()
|
||||
|
||||
def test_linebased_channel_with_broken_response_pipe(tc):
|
||||
channel = Preprogrammed_gtp_channel("= 2\n\n? unreached\n\n")
|
||||
channel.send_command("protocol_version", [])
|
||||
tc.assertEqual(channel.get_response(), (False, "2"))
|
||||
channel.break_response_stream()
|
||||
channel.send_command("list_commands", [])
|
||||
tc.assertRaisesRegexp(
|
||||
GtpChannelClosed, "^engine has closed the response channel$",
|
||||
channel.get_response)
|
||||
channel.close()
|
||||
|
||||
def test_channel_command_validation(tc):
|
||||
channel = Preprogrammed_gtp_channel("\n\n")
|
||||
# empty command
|
||||
tc.assertRaises(ValueError, channel.send_command, "", [])
|
||||
# space in command
|
||||
tc.assertRaises(ValueError, channel.send_command, "play b a3", [])
|
||||
# space after command
|
||||
tc.assertRaises(ValueError, channel.send_command, "play ", ["b", "a3"])
|
||||
# control character in command
|
||||
tc.assertRaises(ValueError, channel.send_command, "pla\x01y", ["b", "a3"])
|
||||
# unicode command
|
||||
tc.assertRaises(ValueError, channel.send_command, u"protocol_version", [])
|
||||
# space in argument
|
||||
tc.assertRaises(ValueError, channel.send_command, "play", ["b a3"])
|
||||
# unicode argument
|
||||
tc.assertRaises(ValueError, channel.send_command, "play ", [u"b", "a3"])
|
||||
# high characters
|
||||
channel.send_command("pl\xc3\xa1y", ["b", "\xc3\xa13"])
|
||||
tc.assertEqual(channel.get_command_stream(), "pl\xc3\xa1y b \xc3\xa13\n")
|
||||
|
||||
|
||||
### Validating Testing_gtp_channel
|
||||
|
||||
def test_testing_gtp_channel(tc):
|
||||
engine = gtp_engine_fixtures.get_test_engine()
|
||||
channel = gtp_controller_test_support.Testing_gtp_channel(engine)
|
||||
channel.send_command("play", ["b", "a3"])
|
||||
tc.assertEqual(channel.get_response(), (True, "unknown command"))
|
||||
channel.send_command("test", [])
|
||||
tc.assertEqual(channel.get_response(), (False, "test response"))
|
||||
channel.send_command("multiline", [])
|
||||
tc.assertEqual(channel.get_response(),
|
||||
(False, "first line \n second line\nthird line"))
|
||||
channel.send_command("quit", [])
|
||||
tc.assertEqual(channel.get_response(), (False, ""))
|
||||
tc.assertRaisesRegexp(
|
||||
GtpChannelClosed, "engine has closed the command channel",
|
||||
channel.send_command, "quit", [])
|
||||
channel.close()
|
||||
|
||||
def test_testing_gtp_channel_alt(tc):
|
||||
engine = gtp_engine_fixtures.get_test_engine()
|
||||
channel = gtp_controller_test_support.Testing_gtp_channel(engine)
|
||||
channel.engine_exit_breaks_commands = False
|
||||
channel.send_command("test", [])
|
||||
tc.assertEqual(channel.get_response(), (False, "test response"))
|
||||
channel.send_command("quit", [])
|
||||
tc.assertEqual(channel.get_response(), (False, ""))
|
||||
channel.send_command("test", [])
|
||||
tc.assertRaisesRegexp(
|
||||
GtpChannelClosed, "engine has closed the response channel",
|
||||
channel.get_response)
|
||||
channel.close()
|
||||
|
||||
def test_testing_gtp_channel_fatal_errors(tc):
|
||||
engine = gtp_engine_fixtures.get_test_engine()
|
||||
channel = gtp_controller_test_support.Testing_gtp_channel(engine)
|
||||
channel.send_command("fatal", [])
|
||||
tc.assertEqual(channel.get_response(), (True, "fatal error"))
|
||||
tc.assertRaisesRegexp(
|
||||
GtpChannelClosed, "engine has closed the response channel",
|
||||
channel.get_response)
|
||||
channel.close()
|
||||
|
||||
def test_testing_gtp_channel_sequencing(tc):
|
||||
engine = gtp_engine_fixtures.get_test_engine()
|
||||
channel = gtp_controller_test_support.Testing_gtp_channel(engine)
|
||||
tc.assertRaisesRegexp(
|
||||
SupporterError, "response request without command",
|
||||
channel.get_response)
|
||||
channel.send_command("test", [])
|
||||
tc.assertRaisesRegexp(
|
||||
SupporterError, "two commands in a row",
|
||||
channel.send_command, "test", [])
|
||||
|
||||
def test_testing_gtp_force_error(tc):
|
||||
engine = gtp_engine_fixtures.get_test_engine()
|
||||
channel = gtp_controller_test_support.Testing_gtp_channel(engine)
|
||||
channel.fail_next_command = True
|
||||
tc.assertRaisesRegexp(
|
||||
GtpTransportError, "forced failure for send_command_line",
|
||||
channel.send_command, "test", [])
|
||||
channel.send_command("test", [])
|
||||
channel.fail_next_response = True
|
||||
tc.assertRaisesRegexp(
|
||||
GtpTransportError, "forced failure for get_response_line",
|
||||
channel.get_response)
|
||||
channel.force_next_response = "# error\n\n"
|
||||
tc.assertRaisesRegexp(
|
||||
GtpProtocolError,
|
||||
"no success/failure indication from engine: first line is `# error`",
|
||||
channel.get_response)
|
||||
channel.fail_close = True
|
||||
tc.assertRaisesRegexp(
|
||||
GtpTransportError, "forced failure for close",
|
||||
channel.close)
|
||||
|
||||
|
||||
### Controller-level
|
||||
|
||||
def test_controller(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
tc.assertEqual(controller.name, 'player test')
|
||||
tc.assertIs(controller.channel, channel)
|
||||
tc.assertFalse(controller.channel_is_bad)
|
||||
|
||||
tc.assertEqual(controller.do_command("test", "ab", "cd"), "args: ab cd")
|
||||
with tc.assertRaises(BadGtpResponse) as ar:
|
||||
controller.do_command("error")
|
||||
tc.assertEqual(ar.exception.gtp_error_message, "normal error")
|
||||
tc.assertEqual(ar.exception.gtp_command, "error")
|
||||
tc.assertSequenceEqual(ar.exception.gtp_arguments, [])
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"failure response from 'error' to player test:\n"
|
||||
"normal error")
|
||||
with tc.assertRaises(BadGtpResponse) as ar:
|
||||
controller.do_command("fatal")
|
||||
tc.assertFalse(controller.channel_is_bad)
|
||||
|
||||
with tc.assertRaises(GtpChannelClosed) as ar:
|
||||
controller.do_command("test")
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"error sending 'test' to player test:\n"
|
||||
"engine has closed the command channel")
|
||||
tc.assertTrue(controller.channel_is_bad)
|
||||
controller.close()
|
||||
tc.assertListEqual(controller.retrieve_error_messages(), [])
|
||||
|
||||
def test_controller_alt_exit(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
channel.engine_exit_breaks_commands = False
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
controller.do_command("quit")
|
||||
tc.assertFalse(controller.channel_is_bad)
|
||||
with tc.assertRaises(GtpChannelClosed) as ar:
|
||||
controller.do_command("test")
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"error reading response to 'test' from player test:\n"
|
||||
"engine has closed the response channel")
|
||||
tc.assertTrue(controller.channel_is_bad)
|
||||
controller.close()
|
||||
tc.assertListEqual(controller.retrieve_error_messages(), [])
|
||||
|
||||
def test_controller_first_command_error(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
with tc.assertRaises(BadGtpResponse) as ar:
|
||||
controller.do_command("error")
|
||||
tc.assertEqual(
|
||||
str(ar.exception),
|
||||
"failure response from first command (error) to player test:\n"
|
||||
"normal error")
|
||||
tc.assertListEqual(controller.retrieve_error_messages(), [])
|
||||
|
||||
def test_controller_command_transport_error(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
tc.assertEqual(controller.do_command("test"), "test response")
|
||||
tc.assertFalse(controller.channel_is_bad)
|
||||
channel.fail_next_command = True
|
||||
with tc.assertRaises(GtpTransportError) as ar:
|
||||
controller.do_command("test")
|
||||
tc.assertEqual(
|
||||
str(ar.exception),
|
||||
"transport error sending 'test' to player test:\n"
|
||||
"forced failure for send_command_line")
|
||||
tc.assertTrue(controller.channel_is_bad)
|
||||
tc.assertListEqual(controller.retrieve_error_messages(), [])
|
||||
|
||||
def test_controller_response_transport_error(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
tc.assertFalse(controller.channel_is_bad)
|
||||
channel.fail_next_response = True
|
||||
with tc.assertRaises(GtpTransportError) as ar:
|
||||
controller.do_command("test")
|
||||
tc.assertEqual(
|
||||
str(ar.exception),
|
||||
"transport error reading response to first command (test) "
|
||||
"from player test:\n"
|
||||
"forced failure for get_response_line")
|
||||
tc.assertTrue(controller.channel_is_bad)
|
||||
tc.assertListEqual(controller.retrieve_error_messages(), [])
|
||||
|
||||
def test_controller_response_protocol_error(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
tc.assertEqual(controller.do_command("test"), "test response")
|
||||
tc.assertFalse(controller.channel_is_bad)
|
||||
channel.force_next_response = "# error\n\n"
|
||||
with tc.assertRaises(GtpProtocolError) as ar:
|
||||
controller.do_command("test")
|
||||
tc.assertEqual(
|
||||
str(ar.exception),
|
||||
"GTP protocol error reading response to 'test' from player test:\n"
|
||||
"no success/failure indication from engine: first line is `# error`")
|
||||
tc.assertTrue(controller.channel_is_bad)
|
||||
tc.assertListEqual(controller.retrieve_error_messages(), [])
|
||||
|
||||
def test_controller_close(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
tc.assertFalse(controller.channel_is_closed)
|
||||
tc.assertEqual(controller.do_command("test"), "test response")
|
||||
tc.assertFalse(controller.channel_is_closed)
|
||||
tc.assertFalse(controller.channel.is_closed)
|
||||
controller.close()
|
||||
tc.assertTrue(controller.channel_is_closed)
|
||||
tc.assertTrue(controller.channel.is_closed)
|
||||
tc.assertRaisesRegexp(StandardError, "^channel is closed$",
|
||||
controller.do_command, "test")
|
||||
tc.assertRaisesRegexp(StandardError, "^channel is closed$",
|
||||
controller.close)
|
||||
tc.assertListEqual(controller.retrieve_error_messages(), [])
|
||||
|
||||
def test_controller_close_error(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
channel.fail_close = True
|
||||
with tc.assertRaises(GtpTransportError) as ar:
|
||||
controller.close()
|
||||
tc.assertEqual(
|
||||
str(ar.exception),
|
||||
"error closing player test:\n"
|
||||
"forced failure for close")
|
||||
tc.assertListEqual(controller.retrieve_error_messages(), [])
|
||||
|
||||
def test_controller_safe_close(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
tc.assertFalse(controller.channel_is_closed)
|
||||
tc.assertEqual(controller.do_command("test"), "test response")
|
||||
tc.assertFalse(controller.channel_is_closed)
|
||||
tc.assertFalse(controller.channel.is_closed)
|
||||
controller.safe_close()
|
||||
tc.assertTrue(controller.channel_is_closed)
|
||||
tc.assertTrue(controller.channel.is_closed)
|
||||
tc.assertListEqual(channel.engine.commands_handled,
|
||||
[('test', []), ('quit', [])])
|
||||
# safe to call twice
|
||||
controller.safe_close()
|
||||
tc.assertListEqual(controller.retrieve_error_messages(), [])
|
||||
|
||||
def test_controller_safe_close_after_error(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
tc.assertEqual(controller.do_command("test"), "test response")
|
||||
tc.assertFalse(controller.channel_is_bad)
|
||||
channel.force_next_response = "# error\n\n"
|
||||
with tc.assertRaises(GtpProtocolError) as ar:
|
||||
controller.do_command("test")
|
||||
tc.assertTrue(controller.channel_is_bad)
|
||||
# doesn't send quit when channel_is_bad
|
||||
controller.safe_close()
|
||||
tc.assertTrue(controller.channel_is_closed)
|
||||
tc.assertTrue(controller.channel.is_closed)
|
||||
tc.assertListEqual(channel.engine.commands_handled,
|
||||
[('test', []), ('test', [])])
|
||||
tc.assertListEqual(controller.retrieve_error_messages(), [])
|
||||
|
||||
def test_controller_safe_close_with_error_from_quit(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
channel.force_next_response = "# error\n\n"
|
||||
controller.safe_close()
|
||||
tc.assertTrue(controller.channel_is_closed)
|
||||
tc.assertTrue(controller.channel.is_closed)
|
||||
tc.assertListEqual(channel.engine.commands_handled,
|
||||
[('quit', [])])
|
||||
tc.assertListEqual(
|
||||
controller.retrieve_error_messages(),
|
||||
["GTP protocol error reading response to first command (quit) "
|
||||
"from player test:\n"
|
||||
"no success/failure indication from engine: first line is `# error`"])
|
||||
|
||||
def test_controller_safe_close_with_failure_response_from_quit(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
channel.engine.force_error("quit")
|
||||
controller.safe_close()
|
||||
tc.assertTrue(controller.channel_is_closed)
|
||||
tc.assertTrue(controller.channel.is_closed)
|
||||
tc.assertListEqual(channel.engine.commands_handled,
|
||||
[('quit', [])])
|
||||
error_messages = controller.retrieve_error_messages()
|
||||
tc.assertEqual(len(error_messages), 1)
|
||||
tc.assertEqual(
|
||||
error_messages[0],
|
||||
"failure response from first command (quit) to player test:\n"
|
||||
"handler forced to fail")
|
||||
|
||||
def test_controller_safe_close_with_error_from_close(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
channel.fail_close = True
|
||||
controller.safe_close()
|
||||
tc.assertTrue(controller.channel_is_closed)
|
||||
tc.assertListEqual(channel.engine.commands_handled,
|
||||
[('quit', [])])
|
||||
tc.assertListEqual(
|
||||
controller.retrieve_error_messages(),
|
||||
["error closing player test:\n"
|
||||
"forced failure for close"])
|
||||
|
||||
def test_safe_do_command(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
tc.assertEqual(controller.safe_do_command("test", "ab"), "args: ab")
|
||||
with tc.assertRaises(BadGtpResponse) as ar:
|
||||
controller.safe_do_command("error")
|
||||
tc.assertFalse(controller.channel_is_bad)
|
||||
channel.fail_next_response = True
|
||||
tc.assertIsNone(controller.safe_do_command("test"))
|
||||
tc.assertTrue(controller.channel_is_bad)
|
||||
tc.assertIsNone(controller.safe_do_command("test"))
|
||||
tc.assertListEqual(
|
||||
controller.retrieve_error_messages(),
|
||||
["transport error reading response to 'test' from player test:\n"
|
||||
"forced failure for get_response_line"])
|
||||
controller.safe_close()
|
||||
# check that third 'test' wasn't sent, and nor was 'quit'
|
||||
tc.assertListEqual(channel.engine.commands_handled,
|
||||
[('test', ['ab']), ('error', []), ('test', [])])
|
||||
|
||||
def test_safe_do_command_closed_channel(tc):
|
||||
# check it's ok to call safe_do_command() on a closed channel
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
controller.safe_close()
|
||||
tc.assertIsNone(controller.safe_do_command("test"))
|
||||
tc.assertListEqual(channel.engine.commands_handled,
|
||||
[('quit', [])])
|
||||
tc.assertListEqual(controller.retrieve_error_messages(), [])
|
||||
|
||||
|
||||
def test_known_command(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'kc test')
|
||||
tc.assertTrue(controller.known_command("test"))
|
||||
tc.assertFalse(controller.known_command("nonesuch"))
|
||||
tc.assertTrue(controller.known_command("test"))
|
||||
tc.assertFalse(controller.known_command("nonesuch"))
|
||||
|
||||
def test_known_command_2(tc):
|
||||
# Checking that known_command caches its responses
|
||||
# and that it treats an error or unknown value the same as 'false'.
|
||||
channel = Preprogrammed_gtp_channel(
|
||||
"= true\n\n= absolutely not\n\n? error\n\n# unreached\n\n")
|
||||
controller = Gtp_controller(channel, 'kc2 test')
|
||||
tc.assertTrue(controller.known_command("one"))
|
||||
tc.assertFalse(controller.known_command("two"))
|
||||
tc.assertFalse(controller.known_command("three"))
|
||||
tc.assertTrue(controller.known_command("one"))
|
||||
tc.assertFalse(controller.known_command("two"))
|
||||
tc.assertEqual(
|
||||
channel.get_command_stream(),
|
||||
"known_command one\nknown_command two\nknown_command three\n")
|
||||
|
||||
def test_check_protocol_version(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'pv test')
|
||||
controller.check_protocol_version()
|
||||
|
||||
def test_check_protocol_version_2(tc):
|
||||
channel = Preprogrammed_gtp_channel("= 1\n\n? error\n\n# unreached\n\n")
|
||||
controller = Gtp_controller(channel, 'pv2 test')
|
||||
with tc.assertRaises(BadGtpResponse) as ar:
|
||||
controller.check_protocol_version()
|
||||
tc.assertEqual(str(ar.exception), "pv2 test reports GTP protocol version 1")
|
||||
tc.assertEqual(ar.exception.gtp_error_message, None)
|
||||
# check error is not treated as a check failure
|
||||
controller.check_protocol_version()
|
||||
|
||||
def test_list_commands(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'lc test')
|
||||
channel.engine.add_command("xyzzy", None)
|
||||
channel.engine.add_command("pl ugh", None)
|
||||
tc.assertListEqual(
|
||||
controller.list_commands(),
|
||||
['error', 'fatal', 'known_command', 'list_commands',
|
||||
'multiline', 'protocol_version', 'quit', 'test', 'xyzzy'])
|
||||
|
||||
def test_gtp_aliases(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'alias test')
|
||||
controller.set_gtp_aliases({
|
||||
'aliased' : 'test',
|
||||
'aliased2' : 'nonesuch',
|
||||
})
|
||||
tc.assertIs(controller.known_command("test"), True)
|
||||
tc.assertIs(controller.known_command("aliased"), True)
|
||||
tc.assertIs(controller.known_command("nonesuch"), False)
|
||||
tc.assertIs(controller.known_command("test"), True)
|
||||
tc.assertIs(controller.known_command("aliased"), True)
|
||||
tc.assertIs(controller.known_command("nonesuch"), False)
|
||||
tc.assertEqual(controller.do_command("test"), "test response")
|
||||
tc.assertEqual(controller.do_command("aliased"), "test response")
|
||||
with tc.assertRaises(BadGtpResponse) as ar:
|
||||
controller.do_command("aliased2")
|
||||
tc.assertEqual(ar.exception.gtp_error_message, "unknown command")
|
||||
tc.assertEqual(ar.exception.gtp_command, "nonesuch")
|
||||
|
||||
def test_gtp_aliases_safe(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'alias test')
|
||||
controller.set_gtp_aliases({
|
||||
'aliased' : 'test',
|
||||
'aliased2' : 'nonesuch',
|
||||
})
|
||||
tc.assertIs(controller.safe_known_command("test"), True)
|
||||
tc.assertIs(controller.safe_known_command("aliased"), True)
|
||||
tc.assertIs(controller.safe_known_command("nonesuch"), False)
|
||||
tc.assertIs(controller.safe_known_command("test"), True)
|
||||
tc.assertIs(controller.safe_known_command("aliased"), True)
|
||||
tc.assertIs(controller.safe_known_command("nonesuch"), False)
|
||||
tc.assertEqual(controller.safe_do_command("test"), "test response")
|
||||
tc.assertEqual(controller.safe_do_command("aliased"), "test response")
|
||||
with tc.assertRaises(BadGtpResponse) as ar:
|
||||
controller.safe_do_command("aliased2")
|
||||
tc.assertEqual(ar.exception.gtp_error_message, "unknown command")
|
||||
tc.assertEqual(ar.exception.gtp_command, "nonesuch")
|
||||
|
||||
|
||||
def test_fix_version(tc):
|
||||
fv = gtp_controller._fix_version
|
||||
tc.assertEqual(fv("foo", "bar"), "bar")
|
||||
tc.assertEqual(fv("foo", "FOO bar"), "bar")
|
||||
tc.assertEqual(fv("foo", "asd " * 16), "asd " * 16)
|
||||
tc.assertEqual(fv("foo", "asd " * 17), "asd")
|
||||
tc.assertEqual(
|
||||
fv("MoGo", "MoGo release 1. Please read http://www.lri.fr/~gelly/MoGo.htm for more information. That is NOT an official developpement MoGo version, but it is a public release. Its strength highly depends on your hardware and the time settings."),
|
||||
"release 1")
|
||||
tc.assertEqual(
|
||||
fv("Pachi UCT Engine", "8.99 (Hakugen-devel): I'm playing UCT. When I'm losing, I will resign, if I think I win, I play until you pass. Anyone can send me 'winrate' in private chat to get my assessment of the position."),
|
||||
"8.99 (Hakugen-devel)")
|
||||
|
||||
def test_describe_engine(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
short_s, long_s = gtp_controller.describe_engine(controller)
|
||||
tc.assertEqual(short_s, "unknown")
|
||||
tc.assertEqual(long_s, "unknown")
|
||||
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
channel.engine.add_command('name', lambda args:"test engine")
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
short_s, long_s = gtp_controller.describe_engine(controller)
|
||||
tc.assertEqual(short_s, "test engine")
|
||||
tc.assertEqual(long_s, "test engine")
|
||||
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
channel.engine.add_command('name', lambda args:"test engine")
|
||||
channel.engine.add_command('version', lambda args:"1.2.3")
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
short_s, long_s = gtp_controller.describe_engine(controller)
|
||||
tc.assertEqual(short_s, "test engine:1.2.3")
|
||||
tc.assertEqual(long_s, "test engine:1.2.3")
|
||||
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
channel.engine.add_command('name', lambda args:"test engine")
|
||||
channel.engine.add_command('version', lambda args:"1.2.3")
|
||||
channel.engine.add_command(
|
||||
'gomill-describe_engine',
|
||||
lambda args:"test engine (v1.2.3):\n pl\xc3\xa1yer \xa3")
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
short_s, long_s = gtp_controller.describe_engine(controller)
|
||||
tc.assertEqual(short_s, "test engine:1.2.3")
|
||||
tc.assertEqual(long_s, "test engine (v1.2.3):\n pl\xc3\xa1yer ?")
|
||||
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
channel.engine.add_command('name', lambda args:"test engine")
|
||||
channel.engine.add_command('version', lambda args:"test engine v1.2.3")
|
||||
controller = Gtp_controller(channel, 'player test')
|
||||
short_s, long_s = gtp_controller.describe_engine(controller)
|
||||
tc.assertEqual(short_s, "test engine:v1.2.3")
|
||||
tc.assertEqual(long_s, "test engine:v1.2.3")
|
||||
|
||||
|
||||
### Subprocess-specific
|
||||
|
||||
def test_subprocess_channel(tc):
|
||||
# This tests that Subprocess_gtp_channel really launches a subprocess.
|
||||
# It also checks that the 'stderr', 'env' and 'cwd' parameters work.
|
||||
# This test relies on there being a 'python' executable on the PATH
|
||||
# (doesn't have to be the same version as is running the testsuite).
|
||||
fx = gtp_engine_fixtures.State_reporter_fixture(tc)
|
||||
rd, wr = os.pipe()
|
||||
try:
|
||||
channel = gtp_controller.Subprocess_gtp_channel(
|
||||
fx.cmd,
|
||||
stderr=wr,
|
||||
env={'GOMILL_TEST' : "from_gtp_controller_tests"},
|
||||
cwd="/")
|
||||
tc.assertEqual(os.read(rd, 256), "subprocess_state_reporter: testing\n")
|
||||
finally:
|
||||
os.close(wr)
|
||||
os.close(rd)
|
||||
tc.assertIsNone(channel.exit_status)
|
||||
tc.assertIsNone(channel.resource_usage)
|
||||
channel.send_command("tell", [])
|
||||
tc.assertEqual(channel.get_response(),
|
||||
(False, "cwd: /\nGOMILL_TEST:from_gtp_controller_tests"))
|
||||
channel.close()
|
||||
tc.assertEqual(channel.exit_status, 0)
|
||||
rusage = channel.resource_usage
|
||||
tc.assertTrue(hasattr(rusage, 'ru_utime'))
|
||||
tc.assertTrue(hasattr(rusage, 'ru_stime'))
|
||||
|
||||
def test_subprocess_channel_nonexistent_program(tc):
|
||||
with tc.assertRaises(GtpChannelError) as ar:
|
||||
gtp_controller.Subprocess_gtp_channel(["/nonexistent/program"])
|
||||
tc.assertIn("[Errno 2] No such file or directory", str(ar.exception))
|
||||
|
||||
def test_subprocess_channel_with_controller(tc):
|
||||
# Also tests that leaving 'env' and 'cwd' unset works
|
||||
fx = gtp_engine_fixtures.State_reporter_fixture(tc)
|
||||
channel = gtp_controller.Subprocess_gtp_channel(fx.cmd, stderr=fx.devnull)
|
||||
controller = Gtp_controller(channel, 'subprocess test')
|
||||
tc.assertEqual(controller.do_command("tell"),
|
||||
"cwd: %s\nGOMILL_TEST:None" % os.getcwd())
|
||||
controller.close()
|
||||
tc.assertEqual(channel.exit_status, 0)
|
||||
rusage = channel.resource_usage
|
||||
tc.assertTrue(hasattr(rusage, 'ru_utime'))
|
||||
|
390
gomill/gomill_tests/gtp_engine_fixtures.py
Normal file
390
gomill/gomill_tests/gtp_engine_fixtures.py
Normal file
@ -0,0 +1,390 @@
|
||||
"""Engines (and channels) provided for the use of controller-side testing."""
|
||||
|
||||
import os
|
||||
|
||||
from gomill import gtp_controller
|
||||
from gomill import gtp_engine
|
||||
from gomill.gtp_engine import GtpError, GtpFatalError
|
||||
from gomill.gtp_controller import GtpChannelError
|
||||
from gomill.common import *
|
||||
|
||||
from gomill_tests import test_framework
|
||||
from gomill_tests import gtp_controller_test_support
|
||||
from gomill_tests.test_framework import SupporterError
|
||||
|
||||
|
||||
## Test engine
|
||||
|
||||
class Test_gtp_engine_protocol(gtp_engine.Gtp_engine_protocol):
|
||||
"""Variant of Gtp_engine_protocol with additional facilities for testing.
|
||||
|
||||
Public attributes:
|
||||
commands_handled -- list of pairs (command, args)
|
||||
|
||||
This records all commands sent to the engine and makes them available in the
|
||||
commands_handled attribute. It also provides a mechanism to force commands
|
||||
to fail.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
gtp_engine.Gtp_engine_protocol.__init__(self)
|
||||
self.commands_handled = []
|
||||
|
||||
def run_command(self, command, args):
|
||||
self.commands_handled.append((command, args))
|
||||
return gtp_engine.Gtp_engine_protocol.run_command(self, command, args)
|
||||
|
||||
def _forced_error(self, args):
|
||||
raise GtpError("handler forced to fail")
|
||||
|
||||
def _forced_fatal_error(self, args):
|
||||
raise GtpFatalError("handler forced to fail and exit")
|
||||
|
||||
def force_error(self, command):
|
||||
"""Set the handler for 'command' to report failure."""
|
||||
self.add_command(command, self._forced_error)
|
||||
|
||||
def force_fatal_error(self, command):
|
||||
"""Set the handler for 'command' to report failure and exit."""
|
||||
self.add_command(command, self._forced_fatal_error)
|
||||
|
||||
|
||||
def get_test_engine():
|
||||
"""Return a Gtp_engine_protocol useful for testing controllers.
|
||||
|
||||
Actually returns a Test_gtp_engine_protocol.
|
||||
|
||||
"""
|
||||
|
||||
def handle_test(args):
|
||||
if args:
|
||||
return "args: " + " ".join(args)
|
||||
else:
|
||||
return "test response"
|
||||
|
||||
def handle_multiline(args):
|
||||
return "first line \n second line\nthird line"
|
||||
|
||||
def handle_error(args):
|
||||
raise GtpError("normal error")
|
||||
|
||||
def handle_fatal_error(args):
|
||||
raise GtpFatalError("fatal error")
|
||||
|
||||
engine = Test_gtp_engine_protocol()
|
||||
engine.add_protocol_commands()
|
||||
engine.add_command('test', handle_test)
|
||||
engine.add_command('multiline', handle_multiline)
|
||||
engine.add_command('error', handle_error)
|
||||
engine.add_command('fatal', handle_fatal_error)
|
||||
return engine
|
||||
|
||||
def get_test_channel():
|
||||
"""Return a Testing_gtp_channel connected to the test engine."""
|
||||
engine = get_test_engine()
|
||||
return gtp_controller_test_support.Testing_gtp_channel(engine)
|
||||
|
||||
|
||||
## Test player engine
|
||||
|
||||
class Test_player(object):
|
||||
"""Trivial player.
|
||||
|
||||
This supports at least the minimal commands required to play a game.
|
||||
|
||||
At present, this plays up column 4 (for black) or 6 (for white), then
|
||||
passes. It pays no attention to its opponent's moves, and doesn't maintain a
|
||||
board position.
|
||||
|
||||
(This means that if two Test_players play each other, black will win by
|
||||
boardsize*2 on the board).
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.boardsize = None
|
||||
self.row_to_play = 0
|
||||
|
||||
def handle_boardsize(self, args):
|
||||
self.boardsize = gtp_engine.interpret_int(args[0])
|
||||
|
||||
def handle_clear_board(self, args):
|
||||
pass
|
||||
|
||||
def handle_komi(self, args):
|
||||
pass
|
||||
|
||||
def handle_play(self, args):
|
||||
pass
|
||||
|
||||
def handle_genmove(self, args):
|
||||
colour = gtp_engine.interpret_colour(args[0])
|
||||
if self.row_to_play < self.boardsize:
|
||||
col = 4 if colour == 'b' else 6
|
||||
result = format_vertex((self.row_to_play, col))
|
||||
self.row_to_play += 1
|
||||
return result
|
||||
else:
|
||||
return "pass"
|
||||
|
||||
def handle_fail(self, args):
|
||||
raise GtpError("test player forced to fail")
|
||||
|
||||
def get_handlers(self):
|
||||
return {
|
||||
'boardsize' : self.handle_boardsize,
|
||||
'clear_board' : self.handle_clear_board,
|
||||
'komi' : self.handle_komi,
|
||||
'play' : self.handle_play,
|
||||
'genmove' : self.handle_genmove,
|
||||
'fail' : self.handle_fail,
|
||||
}
|
||||
|
||||
class Programmed_player(object):
|
||||
"""Player that follows a preset sequence of moves.
|
||||
|
||||
Instantiate with
|
||||
moves -- a sequence of pairs (colour, vertex)
|
||||
|
||||
The sequence can have moves for both colours; genmove goes through the
|
||||
moves in order and ignores ones for the colour that wasn't requested (the
|
||||
idea is that you can create two players with the same move list).
|
||||
|
||||
Passes when it runs out of moves.
|
||||
|
||||
if 'vertex' is a tuple, it's interpreted as (row, col) and converted to a
|
||||
gtp vertex. The special value 'fail' causes a GtpError. Otherwise it's
|
||||
returned literally.
|
||||
|
||||
"""
|
||||
def __init__(self, moves, reject=None):
|
||||
self.moves = []
|
||||
for colour, vertex in moves:
|
||||
if isinstance(vertex, tuple):
|
||||
vertex = format_vertex(vertex)
|
||||
self.moves.append((colour, vertex))
|
||||
self.reject = reject
|
||||
self._reset()
|
||||
|
||||
def _reset(self):
|
||||
self.iter = iter(self.moves)
|
||||
|
||||
def handle_boardsize(self, args):
|
||||
pass
|
||||
|
||||
def handle_clear_board(self, args):
|
||||
self._reset()
|
||||
|
||||
def handle_komi(self, args):
|
||||
pass
|
||||
|
||||
def handle_play(self, args):
|
||||
if self.reject is None:
|
||||
return
|
||||
vertex, msg = self.reject
|
||||
if args[1].lower() == vertex.lower():
|
||||
raise GtpError(msg)
|
||||
|
||||
def handle_genmove(self, args):
|
||||
colour = gtp_engine.interpret_colour(args[0])
|
||||
for move_colour, vertex in self.iter:
|
||||
if move_colour == colour:
|
||||
if vertex == 'fail':
|
||||
raise GtpError("forced to fail")
|
||||
return vertex
|
||||
return "pass"
|
||||
|
||||
def get_handlers(self):
|
||||
return {
|
||||
'boardsize' : self.handle_boardsize,
|
||||
'clear_board' : self.handle_clear_board,
|
||||
'komi' : self.handle_komi,
|
||||
'play' : self.handle_play,
|
||||
'genmove' : self.handle_genmove,
|
||||
}
|
||||
|
||||
|
||||
def make_player_engine(player):
|
||||
"""Return a Gtp_engine_protocol based on a specified player object.
|
||||
|
||||
Actually returns a Test_gtp_engine_protocol.
|
||||
|
||||
It has an additional 'player' attribute, which gives access to the
|
||||
player object.
|
||||
|
||||
"""
|
||||
engine = Test_gtp_engine_protocol()
|
||||
engine.add_protocol_commands()
|
||||
engine.add_commands(player.get_handlers())
|
||||
engine.player = player
|
||||
return engine
|
||||
|
||||
def get_test_player_engine():
|
||||
"""Return a Gtp_engine_protocol based on a Test_player.
|
||||
|
||||
Actually returns a Test_gtp_engine_protocol.
|
||||
|
||||
It has an additional 'player' attribute, which gives access to the
|
||||
Test_player.
|
||||
|
||||
"""
|
||||
return make_player_engine(Test_player())
|
||||
|
||||
def get_test_player_channel():
|
||||
"""Return a Testing_gtp_channel connected to the test player engine."""
|
||||
engine = get_test_player_engine()
|
||||
return gtp_controller_test_support.Testing_gtp_channel(engine)
|
||||
|
||||
|
||||
## State reporter subprocess
|
||||
|
||||
class State_reporter_fixture(test_framework.Fixture):
|
||||
"""Fixture for use with suprocess_state_reporter.py
|
||||
|
||||
Attributes:
|
||||
pathname -- pathname of the state reporter python script
|
||||
cmd -- command list suitable for use with suprocess.Popen
|
||||
devnull -- file open for writing to /dev/null
|
||||
|
||||
"""
|
||||
def __init__(self, tc):
|
||||
self._pathname = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__),
|
||||
"subprocess_state_reporter.py"))
|
||||
self.cmd = ["python", self._pathname]
|
||||
self.devnull = open(os.devnull, "w")
|
||||
tc.addCleanup(self.devnull.close)
|
||||
|
||||
|
||||
## Mock subprocess gtp channel
|
||||
|
||||
class Mock_subprocess_gtp_channel(
|
||||
gtp_controller_test_support.Testing_gtp_channel):
|
||||
"""Mock substitute for Subprocess_gtp_channel.
|
||||
|
||||
This has the same construction interface as Subprocess_gtp_channel, but is
|
||||
in fact a Testing_gtp_channel.
|
||||
|
||||
This accepts the following 'command line arguments' in the 'command' list:
|
||||
id=<string> -- id to use in the channels registry
|
||||
engine=<string> -- look up engine in the engine registry
|
||||
init=<string> -- look up initialisation fn in the callback registry
|
||||
fail=startup -- simulate exec failure
|
||||
|
||||
By default, the underlying engine is a newly-created test player engine.
|
||||
You can override this using 'engine=xxx'.
|
||||
|
||||
If you want to get at the channel object after creating it, pass 'id=xxx'
|
||||
and find it using the 'channels' class attribute.
|
||||
|
||||
If you want to modify the returned channel object, pass 'init=xxx' and
|
||||
register a callback function taking a channel parameter.
|
||||
|
||||
Class attributes:
|
||||
engine_registry -- map engine code -> Gtp_engine_protocol
|
||||
callback_registry -- map callback code -> function
|
||||
channels -- map id string -> Mock_subprocess_gtp_channel
|
||||
|
||||
Instance attributes:
|
||||
id -- string or None
|
||||
requested_command -- list of strings
|
||||
requested_stderr
|
||||
requested_cwd
|
||||
requested_env
|
||||
|
||||
"""
|
||||
engine_registry = {}
|
||||
callback_registry = {}
|
||||
channels = {}
|
||||
|
||||
def __init__(self, command, stderr=None, cwd=None, env=None):
|
||||
self.requested_command = command
|
||||
self.requested_stderr = stderr
|
||||
self.requested_cwd = cwd
|
||||
self.requested_env = env
|
||||
self.id = None
|
||||
engine = None
|
||||
callback = None
|
||||
for arg in command[1:]:
|
||||
key, eq, value = arg.partition("=")
|
||||
if not eq:
|
||||
raise SupporterError("Mock_subprocess_gtp_channel: "
|
||||
"bad command-line argument: %s" % arg)
|
||||
if key == 'id':
|
||||
self.id = value
|
||||
self.channels[value] = self
|
||||
elif key == 'engine':
|
||||
try:
|
||||
engine = self.engine_registry[value]
|
||||
except KeyError:
|
||||
raise SupporterError(
|
||||
"Mock_subprocess_gtp_channel: unregistered engine '%s'"
|
||||
% value)
|
||||
elif key == 'init':
|
||||
try:
|
||||
callback = self.callback_registry[value]
|
||||
except KeyError:
|
||||
raise SupporterError(
|
||||
"Mock_subprocess_gtp_channel: unregistered init '%s'"
|
||||
% value)
|
||||
elif key == 'fail' and value == 'startup':
|
||||
raise GtpChannelError("exec forced to fail")
|
||||
else:
|
||||
raise SupporterError("Mock_subprocess_gtp_channel: "
|
||||
"bad command-line argument: %s" % arg)
|
||||
|
||||
if engine is None:
|
||||
engine = get_test_player_engine()
|
||||
gtp_controller_test_support.Testing_gtp_channel.__init__(self, engine)
|
||||
if callback is not None:
|
||||
callback(self)
|
||||
|
||||
|
||||
class Mock_subprocess_fixture(test_framework.Fixture):
|
||||
"""Fixture for using Mock_subprocess_gtp_channel.
|
||||
|
||||
While this fixture is active, attempts to instantiate a
|
||||
Subprocess_gtp_channel will produce a Testing_gtp_channel.
|
||||
|
||||
"""
|
||||
def __init__(self, tc):
|
||||
self._patch()
|
||||
tc.addCleanup(self._unpatch)
|
||||
|
||||
def _patch(self):
|
||||
self._sgc = gtp_controller.Subprocess_gtp_channel
|
||||
gtp_controller.Subprocess_gtp_channel = Mock_subprocess_gtp_channel
|
||||
|
||||
def _unpatch(self):
|
||||
Mock_subprocess_gtp_channel.engine_registry.clear()
|
||||
Mock_subprocess_gtp_channel.channels.clear()
|
||||
gtp_controller.Subprocess_gtp_channel = self._sgc
|
||||
|
||||
def register_engine(self, code, engine):
|
||||
"""Specify an engine for a mock subprocess channel to run.
|
||||
|
||||
code -- string
|
||||
engine -- Gtp_engine_protocol
|
||||
|
||||
After this is called, attempts to instantiate a Subprocess_gtp_channel
|
||||
with an 'engine=code' argument will return a Testing_gtp_channel based
|
||||
on the specified engine.
|
||||
|
||||
"""
|
||||
Mock_subprocess_gtp_channel.engine_registry[code] = engine
|
||||
|
||||
def register_init_callback(self, code, fn):
|
||||
"""Specify an initialisation callback for the mock subprocess channel.
|
||||
|
||||
code -- string
|
||||
fn -- function
|
||||
|
||||
After this is called, attempts to instantiate a Subprocess_gtp_channel
|
||||
with an 'init=code' argument will call the specified function, passing
|
||||
the Testing_gtp_channel as its parameter.
|
||||
|
||||
"""
|
||||
Mock_subprocess_gtp_channel.callback_registry[code] = fn
|
||||
|
||||
def get_channel(self, id):
|
||||
"""Retrieve a channel via its 'id' command-line argument."""
|
||||
return Mock_subprocess_gtp_channel.channels[id]
|
51
gomill/gomill_tests/gtp_engine_test_support.py
Normal file
51
gomill/gomill_tests/gtp_engine_test_support.py
Normal file
@ -0,0 +1,51 @@
|
||||
"""Test support code for testing gomill GTP engines.
|
||||
|
||||
(Which includes proxy engines.)
|
||||
|
||||
"""
|
||||
|
||||
def check_engine(tc, engine, command, args, expected,
|
||||
expect_failure=False, expect_end=False,
|
||||
expect_internal_error=False):
|
||||
"""Send a command to an engine and check its response.
|
||||
|
||||
tc -- TestCase
|
||||
engine -- Gtp_engine_protocol
|
||||
command -- GTP command to send
|
||||
args -- list of GTP arguments to send
|
||||
expected -- expected response string (None to skip check)
|
||||
expect_failure -- expect a GTP failure response
|
||||
expect_end -- expect the engine to report 'end session'
|
||||
expect_internal_error -- see below
|
||||
|
||||
If the response isn't as expected, uses 'tc' to report this.
|
||||
|
||||
If expect_internal_error is true, expect_failure is forced true, and the
|
||||
check for expected (if specified) is that it's included in the response,
|
||||
rather than equal to the response.
|
||||
|
||||
"""
|
||||
failure, response, end = engine.run_command(command, args)
|
||||
if expect_internal_error:
|
||||
expect_failure = True
|
||||
if expect_failure:
|
||||
tc.assertTrue(failure,
|
||||
"unexpected GTP success response: %s" % response)
|
||||
else:
|
||||
tc.assertFalse(failure,
|
||||
"unexpected GTP failure response: %s" % response)
|
||||
if expect_internal_error:
|
||||
tc.assertTrue(response.startswith("internal error\n"), response)
|
||||
if expected is not None:
|
||||
tc.assertTrue(expected in response, response)
|
||||
elif expected is not None:
|
||||
if command == "showboard":
|
||||
tc.assertDiagramEqual(response, expected,
|
||||
"showboard response not as expected")
|
||||
else:
|
||||
tc.assertEqual(response, expected, "GTP response not as expected")
|
||||
if expect_end:
|
||||
tc.assertTrue(end, "expected end-session not seen")
|
||||
else:
|
||||
tc.assertFalse(end, "unexpected end-session")
|
||||
|
56
gomill/gomill_tests/gtp_engine_tests.py
Normal file
56
gomill/gomill_tests/gtp_engine_tests.py
Normal file
@ -0,0 +1,56 @@
|
||||
"""Tests for gtp_engine.py"""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from gomill import gtp_engine
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
from gomill_tests import gtp_engine_test_support
|
||||
from gomill_tests import test_support
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
def test_engine(tc):
|
||||
def handle_test(args):
|
||||
if args:
|
||||
return "args: " + " ".join(args)
|
||||
else:
|
||||
return "test response"
|
||||
|
||||
engine = gtp_engine.Gtp_engine_protocol()
|
||||
engine.add_protocol_commands()
|
||||
engine.add_command('test', handle_test)
|
||||
|
||||
check_engine = gtp_engine_test_support.check_engine
|
||||
check_engine(tc, engine, 'test', ['ab', 'cd'], "args: ab cd")
|
||||
|
||||
def test_run_gtp_session(tc):
|
||||
engine = gtp_engine.Gtp_engine_protocol()
|
||||
engine.add_protocol_commands()
|
||||
|
||||
stream = "known_command list_commands\nxyzzy\nquit\n"
|
||||
command_pipe = test_support.Mock_reading_pipe(stream)
|
||||
response_pipe = test_support.Mock_writing_pipe()
|
||||
gtp_engine.run_gtp_session(engine, command_pipe, response_pipe)
|
||||
tc.assertMultiLineEqual(response_pipe.getvalue(),
|
||||
"= true\n\n? unknown command\n\n=\n\n")
|
||||
command_pipe.close()
|
||||
response_pipe.close()
|
||||
|
||||
def test_run_gtp_session_broken_pipe(tc):
|
||||
def break_pipe(args):
|
||||
response_pipe.simulate_broken_pipe()
|
||||
|
||||
engine = gtp_engine.Gtp_engine_protocol()
|
||||
engine.add_protocol_commands()
|
||||
engine.add_command("break", break_pipe)
|
||||
|
||||
stream = "known_command list_commands\nbreak\nquit\n"
|
||||
command_pipe = test_support.Mock_reading_pipe(stream)
|
||||
response_pipe = test_support.Mock_writing_pipe()
|
||||
with tc.assertRaises(gtp_engine.ControllerDisconnected) as ar:
|
||||
gtp_engine.run_gtp_session(engine, command_pipe, response_pipe)
|
||||
command_pipe.close()
|
||||
response_pipe.close()
|
||||
|
630
gomill/gomill_tests/gtp_game_tests.py
Normal file
630
gomill/gomill_tests/gtp_game_tests.py
Normal file
@ -0,0 +1,630 @@
|
||||
"""Tests for gtp_games.py"""
|
||||
|
||||
import cPickle as pickle
|
||||
|
||||
from gomill import gtp_controller
|
||||
from gomill import gtp_games
|
||||
from gomill import sgf
|
||||
from gomill.common import format_vertex
|
||||
|
||||
from gomill_tests import test_framework
|
||||
from gomill_tests import gomill_test_support
|
||||
from gomill_tests import gtp_controller_test_support
|
||||
from gomill_tests import gtp_engine_fixtures
|
||||
from gomill_tests.gtp_engine_fixtures import Programmed_player
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
for t in handicap_compensation_tests:
|
||||
suite.addTest(Handicap_compensation_TestCase(*t))
|
||||
|
||||
|
||||
class Game_fixture(test_framework.Fixture):
|
||||
"""Fixture managing a Gtp_game.
|
||||
|
||||
Instantiate with the player objects (defaults to a Test_player) and
|
||||
optionally komi.
|
||||
|
||||
attributes:
|
||||
game -- Gtp_game
|
||||
controller_b -- Gtp_controller
|
||||
controller_w -- Gtp_controller
|
||||
channel_b -- Testing_gtp_channel
|
||||
channel_w -- Testing_gtp_channel
|
||||
engine_b -- Test_gtp_engine_protocol
|
||||
engine_w -- Test_gtp_engine_protocol
|
||||
player_b -- player object
|
||||
player_w -- player object
|
||||
|
||||
"""
|
||||
def __init__(self, tc, player_b=None, player_w=None,
|
||||
komi=0.0, board_size=9):
|
||||
self.tc = tc
|
||||
game = gtp_games.Game(board_size, komi=komi)
|
||||
game.set_player_code('b', 'one')
|
||||
game.set_player_code('w', 'two')
|
||||
if player_b is None:
|
||||
player_b = gtp_engine_fixtures.Test_player()
|
||||
if player_w is None:
|
||||
player_w = gtp_engine_fixtures.Test_player()
|
||||
engine_b = gtp_engine_fixtures.make_player_engine(player_b)
|
||||
engine_w = gtp_engine_fixtures.make_player_engine(player_w)
|
||||
channel_b = gtp_controller_test_support.Testing_gtp_channel(engine_b)
|
||||
channel_w = gtp_controller_test_support.Testing_gtp_channel(engine_w)
|
||||
controller_b = gtp_controller.Gtp_controller(channel_b, 'player one')
|
||||
controller_w = gtp_controller.Gtp_controller(channel_w, 'player two')
|
||||
game.set_player_controller('b', controller_b)
|
||||
game.set_player_controller('w', controller_w)
|
||||
self.game = game
|
||||
self.controller_b = controller_b
|
||||
self.controller_w = controller_w
|
||||
self.channel_b = channel_b
|
||||
self.channel_w = channel_w
|
||||
self.engine_b = channel_b.engine
|
||||
self.engine_w = channel_w.engine
|
||||
self.player_b = channel_b.engine.player
|
||||
self.player_w = channel_w.engine.player
|
||||
|
||||
def check_moves(self, expected_moves):
|
||||
"""Check that the game's moves are as expected.
|
||||
|
||||
expected_moves -- list of pairs (colour, vertex)
|
||||
|
||||
"""
|
||||
game_moves = [(colour, format_vertex(move))
|
||||
for (colour, move, comment) in self.game.moves]
|
||||
self.tc.assertListEqual(game_moves, expected_moves)
|
||||
|
||||
def run_score_test(self, b_score, w_score, allowed_scorers="bw"):
|
||||
"""Run a game and let the players score it.
|
||||
|
||||
b_score, w_score -- string for final_score to return
|
||||
|
||||
If b_score or w_score is None, the player won't implement final_score.
|
||||
If b_score or w_score is an exception, the final_score will fail
|
||||
|
||||
"""
|
||||
def handle_final_score_b(args):
|
||||
if b_score is Exception:
|
||||
raise b_score
|
||||
return b_score
|
||||
def handle_final_score_w(args):
|
||||
if w_score is Exception:
|
||||
raise w_score
|
||||
return w_score
|
||||
if b_score is not None:
|
||||
self.engine_b.add_command('final_score', handle_final_score_b)
|
||||
if w_score is not None:
|
||||
self.engine_w.add_command('final_score', handle_final_score_w)
|
||||
for colour in allowed_scorers:
|
||||
self.game.allow_scorer(colour)
|
||||
self.game.ready()
|
||||
self.game.run()
|
||||
|
||||
def sgf_string(self):
|
||||
return gomill_test_support.scrub_sgf(
|
||||
self.game.make_sgf().serialise(wrap=None))
|
||||
|
||||
|
||||
def test_game(tc):
|
||||
fx = Game_fixture(tc)
|
||||
tc.assertDictEqual(fx.game.players, {'b' : 'one', 'w' : 'two'})
|
||||
tc.assertIs(fx.game.get_controller('b'), fx.controller_b)
|
||||
tc.assertIs(fx.game.get_controller('w'), fx.controller_w)
|
||||
fx.game.use_internal_scorer()
|
||||
fx.game.ready()
|
||||
tc.assertIsNone(fx.game.game_id)
|
||||
tc.assertIsNone(fx.game.result)
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertIsNone(fx.game.describe_late_errors())
|
||||
tc.assertDictEqual(fx.game.result.players, {'b' : 'one', 'w' : 'two'})
|
||||
tc.assertEqual(fx.game.result.player_b, 'one')
|
||||
tc.assertEqual(fx.game.result.player_w, 'two')
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'b')
|
||||
tc.assertEqual(fx.game.result.losing_colour, 'w')
|
||||
tc.assertEqual(fx.game.result.winning_player, 'one')
|
||||
tc.assertEqual(fx.game.result.losing_player, 'two')
|
||||
tc.assertEqual(fx.game.result.sgf_result, "B+18")
|
||||
tc.assertFalse(fx.game.result.is_forfeit)
|
||||
tc.assertIs(fx.game.result.is_jigo, False)
|
||||
tc.assertIsNone(fx.game.result.detail)
|
||||
tc.assertIsNone(fx.game.result.game_id)
|
||||
tc.assertEqual(fx.game.result.describe(), "one beat two B+18")
|
||||
result2 = pickle.loads(pickle.dumps(fx.game.result))
|
||||
tc.assertEqual(result2.describe(), "one beat two B+18")
|
||||
tc.assertEqual(fx.game.describe_scoring(), "one beat two B+18")
|
||||
tc.assertEqual(result2.player_b, 'one')
|
||||
tc.assertEqual(result2.player_w, 'two')
|
||||
tc.assertIs(result2.is_jigo, False)
|
||||
tc.assertDictEqual(fx.game.result.cpu_times, {'one' : None, 'two' : None})
|
||||
tc.assertListEqual(fx.game.moves, [
|
||||
('b', (0, 4), None), ('w', (0, 6), None),
|
||||
('b', (1, 4), None), ('w', (1, 6), None),
|
||||
('b', (2, 4), None), ('w', (2, 6), None),
|
||||
('b', (3, 4), None), ('w', (3, 6), None),
|
||||
('b', (4, 4), None), ('w', (4, 6), None),
|
||||
('b', (5, 4), None), ('w', (5, 6), None),
|
||||
('b', (6, 4), None), ('w', (6, 6), None),
|
||||
('b', (7, 4), None), ('w', (7, 6), None),
|
||||
('b', (8, 4), None), ('w', (8, 6), None),
|
||||
('b', None, None), ('w', None, None)])
|
||||
fx.check_moves([
|
||||
('b', 'E1'), ('w', 'G1'),
|
||||
('b', 'E2'), ('w', 'G2'),
|
||||
('b', 'E3'), ('w', 'G3'),
|
||||
('b', 'E4'), ('w', 'G4'),
|
||||
('b', 'E5'), ('w', 'G5'),
|
||||
('b', 'E6'), ('w', 'G6'),
|
||||
('b', 'E7'), ('w', 'G7'),
|
||||
('b', 'E8'), ('w', 'G8'),
|
||||
('b', 'E9'), ('w', 'G9'),
|
||||
('b', 'pass'), ('w', 'pass'),
|
||||
])
|
||||
|
||||
def test_unscored_game(tc):
|
||||
fx = Game_fixture(tc)
|
||||
tc.assertDictEqual(fx.game.players, {'b' : 'one', 'w' : 'two'})
|
||||
tc.assertIs(fx.game.get_controller('b'), fx.controller_b)
|
||||
tc.assertIs(fx.game.get_controller('w'), fx.controller_w)
|
||||
fx.game.allow_scorer('b') # it can't score
|
||||
fx.game.ready()
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertIsNone(fx.game.describe_late_errors())
|
||||
tc.assertDictEqual(fx.game.result.players, {'b' : 'one', 'w' : 'two'})
|
||||
tc.assertIsNone(fx.game.result.winning_colour)
|
||||
tc.assertIsNone(fx.game.result.losing_colour)
|
||||
tc.assertIsNone(fx.game.result.winning_player)
|
||||
tc.assertIsNone(fx.game.result.losing_player)
|
||||
tc.assertEqual(fx.game.result.sgf_result, "?")
|
||||
tc.assertFalse(fx.game.result.is_forfeit)
|
||||
tc.assertIs(fx.game.result.is_jigo, False)
|
||||
tc.assertEqual(fx.game.result.detail, "no score reported")
|
||||
tc.assertEqual(fx.game.result.describe(),
|
||||
"one vs two ? (no score reported)")
|
||||
tc.assertEqual(fx.game.describe_scoring(),
|
||||
"one vs two ? (no score reported)")
|
||||
result2 = pickle.loads(pickle.dumps(fx.game.result))
|
||||
tc.assertEqual(result2.describe(), "one vs two ? (no score reported)")
|
||||
tc.assertIs(result2.is_jigo, False)
|
||||
|
||||
def test_jigo(tc):
|
||||
fx = Game_fixture(tc, komi=18.0)
|
||||
fx.game.use_internal_scorer()
|
||||
fx.game.ready()
|
||||
tc.assertIsNone(fx.game.result)
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertIsNone(fx.game.describe_late_errors())
|
||||
tc.assertDictEqual(fx.game.result.players, {'b' : 'one', 'w' : 'two'})
|
||||
tc.assertEqual(fx.game.result.player_b, 'one')
|
||||
tc.assertEqual(fx.game.result.player_w, 'two')
|
||||
tc.assertEqual(fx.game.result.winning_colour, None)
|
||||
tc.assertEqual(fx.game.result.losing_colour, None)
|
||||
tc.assertEqual(fx.game.result.winning_player, None)
|
||||
tc.assertEqual(fx.game.result.losing_player, None)
|
||||
tc.assertEqual(fx.game.result.sgf_result, "0")
|
||||
tc.assertIs(fx.game.result.is_forfeit, False)
|
||||
tc.assertIs(fx.game.result.is_jigo, True)
|
||||
tc.assertIsNone(fx.game.result.detail)
|
||||
tc.assertEqual(fx.game.result.describe(), "one vs two jigo")
|
||||
tc.assertEqual(fx.game.describe_scoring(), "one vs two jigo")
|
||||
result2 = pickle.loads(pickle.dumps(fx.game.result))
|
||||
tc.assertEqual(result2.describe(), "one vs two jigo")
|
||||
tc.assertEqual(result2.player_b, 'one')
|
||||
tc.assertEqual(result2.player_w, 'two')
|
||||
tc.assertIs(result2.is_jigo, True)
|
||||
|
||||
def test_players_score_agree(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test("b+3", "B+3.0")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "B+3")
|
||||
tc.assertIsNone(fx.game.result.detail)
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'b')
|
||||
tc.assertEqual(fx.game.describe_scoring(), "one beat two B+3")
|
||||
|
||||
def test_players_score_agree_draw(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test("0", "0")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "0")
|
||||
tc.assertIsNone(fx.game.result.detail)
|
||||
tc.assertIsNone(fx.game.result.winning_colour)
|
||||
tc.assertEqual(fx.game.describe_scoring(), "one vs two jigo")
|
||||
|
||||
def test_players_score_disagree(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test("b+3.0", "W+4")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "?")
|
||||
tc.assertEqual(fx.game.result.detail, "players disagreed")
|
||||
tc.assertIsNone(fx.game.result.winning_colour)
|
||||
tc.assertEqual(fx.game.describe_scoring(),
|
||||
"one vs two ? (players disagreed)\n"
|
||||
"one final_score: b+3.0\n"
|
||||
"two final_score: W+4")
|
||||
|
||||
def test_players_score_disagree_one_no_margin(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test("b+", "W+4")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "?")
|
||||
tc.assertEqual(fx.game.result.detail, "players disagreed")
|
||||
tc.assertEqual(fx.game.describe_scoring(),
|
||||
"one vs two ? (players disagreed)\n"
|
||||
"one final_score: b+\n"
|
||||
"two final_score: W+4")
|
||||
|
||||
def test_players_score_disagree_one_jigo(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test("0", "W+4")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "?")
|
||||
tc.assertEqual(fx.game.result.detail, "players disagreed")
|
||||
tc.assertIsNone(fx.game.result.winning_colour)
|
||||
tc.assertEqual(fx.game.describe_scoring(),
|
||||
"one vs two ? (players disagreed)\n"
|
||||
"one final_score: 0\n"
|
||||
"two final_score: W+4")
|
||||
|
||||
def test_players_score_disagree_equal_margin(tc):
|
||||
# check equal margin in both directions doesn't confuse it
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test("b+4", "W+4")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "?")
|
||||
tc.assertEqual(fx.game.result.detail, "players disagreed")
|
||||
tc.assertIsNone(fx.game.result.winning_colour)
|
||||
tc.assertEqual(fx.game.describe_scoring(),
|
||||
"one vs two ? (players disagreed)\n"
|
||||
"one final_score: b+4\n"
|
||||
"two final_score: W+4")
|
||||
|
||||
def test_players_score_one_unreliable(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test("b+3", "W+4", allowed_scorers="w")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "W+4")
|
||||
tc.assertIsNone(fx.game.result.detail)
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'w')
|
||||
tc.assertEqual(fx.game.describe_scoring(), "two beat one W+4")
|
||||
|
||||
def test_players_score_one_cannot_score(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test(None, "W+4")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "W+4")
|
||||
tc.assertIsNone(fx.game.result.detail)
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'w')
|
||||
tc.assertEqual(fx.game.describe_scoring(), "two beat one W+4")
|
||||
|
||||
def test_players_score_one_fails(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test(Exception, "W+4")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "W+4")
|
||||
tc.assertIsNone(fx.game.result.detail)
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'w')
|
||||
tc.assertEqual(fx.game.describe_scoring(), "two beat one W+4")
|
||||
|
||||
def test_players_score_one_illformed(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test("black wins", "W+4.5")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "W+4.5")
|
||||
tc.assertIsNone(fx.game.result.detail)
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'w')
|
||||
tc.assertEqual(fx.game.describe_scoring(),
|
||||
"two beat one W+4.5\n"
|
||||
"one final_score: black wins\n"
|
||||
"two final_score: W+4.5")
|
||||
|
||||
def test_players_score_agree_except_margin(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test("b+3", "B+4.0")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "B+")
|
||||
tc.assertEqual(fx.game.result.detail, "unknown margin")
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'b')
|
||||
tc.assertEqual(fx.game.describe_scoring(),
|
||||
"one beat two B+ (unknown margin)\n"
|
||||
"one final_score: b+3\n"
|
||||
"two final_score: B+4.0")
|
||||
|
||||
def test_players_score_agree_one_no_margin(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test("b+3", "B+")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "B+")
|
||||
tc.assertEqual(fx.game.result.detail, "unknown margin")
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'b')
|
||||
tc.assertEqual(fx.game.describe_scoring(),
|
||||
"one beat two B+ (unknown margin)\n"
|
||||
"one final_score: b+3\n"
|
||||
"two final_score: B+")
|
||||
|
||||
def test_players_score_agree_one_illformed_margin(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test("b+3", "B+a")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "B+")
|
||||
tc.assertEqual(fx.game.result.detail, "unknown margin")
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'b')
|
||||
tc.assertEqual(fx.game.describe_scoring(),
|
||||
"one beat two B+ (unknown margin)\n"
|
||||
"one final_score: b+3\n"
|
||||
"two final_score: B+a")
|
||||
|
||||
def test_players_score_agree_margin_zero(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.run_score_test("b+0", "B+0")
|
||||
tc.assertEqual(fx.game.result.sgf_result, "B+")
|
||||
tc.assertEqual(fx.game.result.detail, "unknown margin")
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'b')
|
||||
tc.assertEqual(fx.game.describe_scoring(),
|
||||
"one beat two B+ (unknown margin)\n"
|
||||
"one final_score: b+0\n"
|
||||
"two final_score: B+0")
|
||||
|
||||
|
||||
|
||||
def test_claim(tc):
|
||||
def handle_genmove_ex_b(args):
|
||||
tc.assertIn('claim', args)
|
||||
if fx.player_b.row_to_play < 3:
|
||||
return fx.player_b.handle_genmove(args)
|
||||
return "claim"
|
||||
def handle_genmove_ex_w(args):
|
||||
return "claim"
|
||||
fx = Game_fixture(tc)
|
||||
fx.engine_b.add_command('gomill-genmove_ex', handle_genmove_ex_b)
|
||||
fx.engine_w.add_command('gomill-genmove_ex', handle_genmove_ex_w)
|
||||
fx.game.use_internal_scorer()
|
||||
fx.game.set_claim_allowed('b')
|
||||
fx.game.ready()
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertEqual(fx.game.result.sgf_result, "B+")
|
||||
tc.assertEqual(fx.game.result.detail, "claim")
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'b')
|
||||
tc.assertEqual(fx.game.result.winning_player, 'one')
|
||||
tc.assertFalse(fx.game.result.is_forfeit)
|
||||
tc.assertEqual(fx.game.result.describe(), "one beat two B+ (claim)")
|
||||
tc.assertEqual(fx.game.describe_scoring(), "one beat two B+ (claim)")
|
||||
fx.check_moves([
|
||||
('b', 'E1'), ('w', 'G1'),
|
||||
('b', 'E2'), ('w', 'G2'),
|
||||
('b', 'E3'), ('w', 'G3'),
|
||||
])
|
||||
|
||||
def test_forfeit_occupied_point(tc):
|
||||
moves = [
|
||||
('b', 'C3'), ('w', 'D3'),
|
||||
('b', 'D4'), ('w', 'D4'), # occupied point
|
||||
]
|
||||
fx = Game_fixture(tc, Programmed_player(moves), Programmed_player(moves))
|
||||
fx.game.use_internal_scorer()
|
||||
fx.game.ready()
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertEqual(fx.game.result.sgf_result, "B+F")
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'b')
|
||||
tc.assertEqual(fx.game.result.winning_player, 'one')
|
||||
tc.assertTrue(fx.game.result.is_forfeit)
|
||||
tc.assertEqual(fx.game.result.detail,
|
||||
"forfeit: two attempted move to occupied point d4")
|
||||
tc.assertEqual(fx.game.result.describe(),
|
||||
"one beat two B+F "
|
||||
"(forfeit: two attempted move to occupied point d4)")
|
||||
fx.check_moves(moves[:-1])
|
||||
|
||||
def test_forfeit_simple_ko(tc):
|
||||
moves = [
|
||||
('b', 'C5'), ('w', 'F5'),
|
||||
('b', 'D6'), ('w', 'E4'),
|
||||
('b', 'D4'), ('w', 'E6'),
|
||||
('b', 'E5'), ('w', 'D5'),
|
||||
('b', 'E5'), # ko violation
|
||||
]
|
||||
fx = Game_fixture(tc, Programmed_player(moves), Programmed_player(moves))
|
||||
fx.game.use_internal_scorer()
|
||||
fx.game.ready()
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertEqual(fx.game.result.sgf_result, "W+F")
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'w')
|
||||
tc.assertEqual(fx.game.result.winning_player, 'two')
|
||||
tc.assertTrue(fx.game.result.is_forfeit)
|
||||
tc.assertEqual(fx.game.result.detail,
|
||||
"forfeit: one attempted move to ko-forbidden point e5")
|
||||
fx.check_moves(moves[:-1])
|
||||
|
||||
def test_forfeit_illformed_move(tc):
|
||||
moves = [
|
||||
('b', 'C5'), ('w', 'F5'),
|
||||
('b', 'D6'), ('w', 'Z99'), # ill-formed move
|
||||
]
|
||||
fx = Game_fixture(tc, Programmed_player(moves), Programmed_player(moves))
|
||||
fx.game.use_internal_scorer()
|
||||
fx.game.ready()
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertEqual(fx.game.result.sgf_result, "B+F")
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'b')
|
||||
tc.assertEqual(fx.game.result.winning_player, 'one')
|
||||
tc.assertTrue(fx.game.result.is_forfeit)
|
||||
tc.assertEqual(fx.game.result.detail,
|
||||
"forfeit: two attempted ill-formed move z99")
|
||||
fx.check_moves(moves[:-1])
|
||||
|
||||
def test_forfeit_genmove_fails(tc):
|
||||
moves = [
|
||||
('b', 'C5'), ('w', 'F5'),
|
||||
('b', 'fail'), # GTP failure response
|
||||
]
|
||||
fx = Game_fixture(tc, Programmed_player(moves), Programmed_player(moves))
|
||||
fx.game.use_internal_scorer()
|
||||
fx.game.ready()
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertEqual(fx.game.result.sgf_result, "W+F")
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'w')
|
||||
tc.assertEqual(fx.game.result.winning_player, 'two')
|
||||
tc.assertTrue(fx.game.result.is_forfeit)
|
||||
tc.assertEqual(fx.game.result.detail,
|
||||
"forfeit: failure response from 'genmove b' to player one:\n"
|
||||
"forced to fail")
|
||||
fx.check_moves(moves[:-1])
|
||||
|
||||
def test_forfeit_rejected_as_illegal(tc):
|
||||
moves = [
|
||||
('b', 'C5'), ('w', 'F5'),
|
||||
('b', 'D6'), ('w', 'E4'), # will be rejected
|
||||
]
|
||||
fx = Game_fixture(tc,
|
||||
Programmed_player(moves, reject=('E4', 'illegal move')),
|
||||
Programmed_player(moves))
|
||||
fx.game.use_internal_scorer()
|
||||
fx.game.ready()
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertEqual(fx.game.result.sgf_result, "B+F")
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'b')
|
||||
tc.assertEqual(fx.game.result.winning_player, 'one')
|
||||
tc.assertTrue(fx.game.result.is_forfeit)
|
||||
tc.assertEqual(fx.game.result.detail,
|
||||
"forfeit: one claims move e4 is illegal")
|
||||
fx.check_moves(moves[:-1])
|
||||
|
||||
def test_forfeit_play_failed(tc):
|
||||
moves = [
|
||||
('b', 'C5'), ('w', 'F5'),
|
||||
('b', 'D6'), ('w', 'E4'), # will be rejected
|
||||
]
|
||||
fx = Game_fixture(tc,
|
||||
Programmed_player(moves, reject=('E4', 'crash')),
|
||||
Programmed_player(moves))
|
||||
fx.game.use_internal_scorer()
|
||||
fx.game.ready()
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertEqual(fx.game.result.sgf_result, "W+F")
|
||||
tc.assertEqual(fx.game.result.winning_colour, 'w')
|
||||
tc.assertEqual(fx.game.result.winning_player, 'two')
|
||||
tc.assertTrue(fx.game.result.is_forfeit)
|
||||
tc.assertEqual(fx.game.result.detail,
|
||||
"forfeit: failure response from 'play w e4' to player one:\n"
|
||||
"crash")
|
||||
fx.check_moves(moves[:-1])
|
||||
|
||||
def test_same_player_code(tc):
|
||||
game = gtp_games.Game(board_size=9, komi=0)
|
||||
game.set_player_code('b', 'one')
|
||||
tc.assertRaisesRegexp(ValueError, "player codes must be distinct",
|
||||
game.set_player_code, 'w', 'one')
|
||||
|
||||
|
||||
def test_make_sgf(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.game.use_internal_scorer()
|
||||
fx.game.ready()
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertMultiLineEqual(fx.sgf_string(), """\
|
||||
(;FF[4]AP[gomill:VER]CA[UTF-8]DT[***]GM[1]KM[0]RE[B+18]SZ[9];B[ei];W[gi];B[eh];W[gh];B[eg];W[gg];B[ef];W[gf];B[ee];W[ge];B[ed];W[gd];B[ec];W[gc];B[eb];W[gb];B[ea];W[ga];B[tt];C[one beat two B+18]W[tt])
|
||||
""")
|
||||
tc.assertMultiLineEqual(gomill_test_support.scrub_sgf(
|
||||
fx.game.make_sgf(game_end_message="zzzz").serialise(wrap=None)), """\
|
||||
(;FF[4]AP[gomill:VER]CA[UTF-8]DT[***]GM[1]KM[0]RE[B+18]SZ[9];B[ei];W[gi];B[eh];W[gh];B[eg];W[gg];B[ef];W[gf];B[ee];W[ge];B[ed];W[gd];B[ec];W[gc];B[eb];W[gb];B[ea];W[ga];B[tt];C[one beat two B+18
|
||||
|
||||
zzzz]W[tt])
|
||||
""")
|
||||
|
||||
|
||||
def test_game_id(tc):
|
||||
fx = Game_fixture(tc)
|
||||
fx.game.use_internal_scorer()
|
||||
fx.game.set_game_id("gitest")
|
||||
fx.game.ready()
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertEqual(fx.game.result.game_id, "gitest")
|
||||
tc.assertMultiLineEqual(fx.sgf_string(), """\
|
||||
(;FF[4]AP[gomill:VER]CA[UTF-8]DT[***]GM[1]GN[gitest]KM[0]RE[B+18]SZ[9];B[ei];W[gi];B[eh];W[gh];B[eg];W[gg];B[ef];W[gf];B[ee];W[ge];B[ed];W[gd];B[ec];W[gc];B[eb];W[gb];B[ea];W[ga];B[tt];C[one beat two B+18]W[tt])
|
||||
""")
|
||||
|
||||
def test_explain_last_move(tc):
|
||||
counter = [0]
|
||||
def handle_explain_last_move(args):
|
||||
counter[0] += 1
|
||||
return "EX%d" % counter[0]
|
||||
fx = Game_fixture(tc)
|
||||
fx.engine_b.add_command('gomill-explain_last_move',
|
||||
handle_explain_last_move)
|
||||
fx.game.ready()
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertMultiLineEqual(fx.sgf_string(), """\
|
||||
(;FF[4]AP[gomill:VER]CA[UTF-8]DT[***]GM[1]KM[0]RE[?]SZ[9];B[ei]C[EX1];W[gi];B[eh]C[EX2];W[gh];B[eg]C[EX3];W[gg];B[ef]C[EX4];W[gf];B[ee]C[EX5];W[ge];B[ed]C[EX6];W[gd];B[ec]C[EX7];W[gc];B[eb]C[EX8];W[gb];B[ea]C[EX9];W[ga];B[tt]C[EX10];C[one vs two ? (no score reported)]W[tt])
|
||||
""")
|
||||
|
||||
|
||||
def test_fixed_handicap(tc):
|
||||
fh_calls = []
|
||||
def handle_fixed_handicap(args):
|
||||
fh_calls.append(args[0])
|
||||
return "C3 G7 C7"
|
||||
fx = Game_fixture(tc)
|
||||
fx.engine_b.add_command('fixed_handicap', handle_fixed_handicap)
|
||||
fx.engine_w.add_command('fixed_handicap', handle_fixed_handicap)
|
||||
fx.game.ready()
|
||||
fx.game.set_handicap(3, is_free=False)
|
||||
tc.assertEqual(fh_calls, ["3", "3"])
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
tc.assertEqual(fx.game.result.sgf_result, "B+F")
|
||||
tc.assertEqual(fx.game.result.detail,
|
||||
"forfeit: two attempted move to occupied point g7")
|
||||
fx.check_moves([
|
||||
('w', 'G1'), ('b', 'E1'),
|
||||
('w', 'G2'), ('b', 'E2'),
|
||||
('w', 'G3'), ('b', 'E3'),
|
||||
('w', 'G4'), ('b', 'E4'),
|
||||
('w', 'G5'), ('b', 'E5'),
|
||||
('w', 'G6'), ('b', 'E6'),
|
||||
])
|
||||
tc.assertMultiLineEqual(fx.sgf_string(), """\
|
||||
(;FF[4]AB[cc][cg][gc]AP[gomill:VER]CA[UTF-8]DT[***]GM[1]HA[3]KM[0]RE[B+F]SZ[9];W[gi];B[ei];W[gh];B[eh];W[gg];B[eg];W[gf];B[ef];W[ge];B[ee];W[gd];B[ed]C[one beat two B+F (forfeit: two attempted move to occupied point g7)])
|
||||
""")
|
||||
|
||||
def test_fixed_handicap_bad_engine(tc):
|
||||
fh_calls = []
|
||||
def handle_fixed_handicap_good(args):
|
||||
fh_calls.append(args[0])
|
||||
return "g7 c7 c3"
|
||||
def handle_fixed_handicap_bad(args):
|
||||
fh_calls.append(args[0])
|
||||
return "C3 G3 C7" # Should be G7, not G3
|
||||
fx = Game_fixture(tc)
|
||||
fx.engine_b.add_command('fixed_handicap', handle_fixed_handicap_good)
|
||||
fx.engine_w.add_command('fixed_handicap', handle_fixed_handicap_bad)
|
||||
fx.game.ready()
|
||||
tc.assertRaisesRegexp(
|
||||
gtp_controller.BadGtpResponse,
|
||||
"^bad response from fixed_handicap command to two: C3 G3 C7$",
|
||||
fx.game.set_handicap, 3, is_free=False)
|
||||
|
||||
|
||||
handicap_compensation_tests = [
|
||||
# test code, handicap_compensation, result
|
||||
('no', 'no', "B+53"),
|
||||
('full', 'full', "B+50"),
|
||||
('short', 'short', "B+51"),
|
||||
]
|
||||
|
||||
class Handicap_compensation_TestCase(
|
||||
gomill_test_support.Gomill_ParameterisedTestCase):
|
||||
test_name = "test_handicap_compensation"
|
||||
parameter_names = ('hc', 'result')
|
||||
|
||||
def runTest(self):
|
||||
def handle_fixed_handicap(args):
|
||||
return "D4 K10 D10"
|
||||
fx = Game_fixture(self, board_size=13)
|
||||
fx.engine_b.add_command('fixed_handicap', handle_fixed_handicap)
|
||||
fx.engine_w.add_command('fixed_handicap', handle_fixed_handicap)
|
||||
fx.game.use_internal_scorer(handicap_compensation=self.hc)
|
||||
fx.game.set_handicap(3, is_free=False)
|
||||
fx.game.ready()
|
||||
fx.game.run()
|
||||
fx.game.close_players()
|
||||
self.assertEqual(fx.game.result.sgf_result, self.result)
|
242
gomill/gomill_tests/gtp_proxy_tests.py
Normal file
242
gomill/gomill_tests/gtp_proxy_tests.py
Normal file
@ -0,0 +1,242 @@
|
||||
"""Tests for gtp_proxy.py"""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from gomill import gtp_controller
|
||||
from gomill import gtp_proxy
|
||||
from gomill.gtp_engine import GtpError, GtpFatalError
|
||||
from gomill.gtp_controller import (
|
||||
GtpChannelError, GtpProtocolError, GtpTransportError, GtpChannelClosed,
|
||||
BadGtpResponse, Gtp_controller)
|
||||
from gomill.gtp_proxy import BackEndError
|
||||
|
||||
from gomill_tests import test_framework
|
||||
from gomill_tests import gomill_test_support
|
||||
from gomill_tests import gtp_controller_test_support
|
||||
from gomill_tests import gtp_engine_fixtures
|
||||
from gomill_tests import gtp_engine_test_support
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
class Proxy_fixture(test_framework.Fixture):
|
||||
"""Fixture managing a Gtp_proxy with the test engine as its back-end.
|
||||
|
||||
attributes:
|
||||
proxy -- Gtp_proxy
|
||||
controller -- Gtp_controller
|
||||
channel -- Testing_gtp_channel (like get_test_channel())
|
||||
engine -- the proxy engine
|
||||
underlying_engine -- the underlying test engine (like get_test_engine())
|
||||
commands_handled -- from the underlying Test_gtp_engine_protocol
|
||||
|
||||
"""
|
||||
def __init__(self, tc):
|
||||
self.tc = tc
|
||||
self.channel = gtp_engine_fixtures.get_test_channel()
|
||||
self.underlying_engine = self.channel.engine
|
||||
self.controller = gtp_controller.Gtp_controller(
|
||||
self.channel, 'testbackend')
|
||||
self.proxy = gtp_proxy.Gtp_proxy()
|
||||
self.proxy.set_back_end_controller(self.controller)
|
||||
self.engine = self.proxy.engine
|
||||
self.commands_handled = self.underlying_engine.commands_handled
|
||||
|
||||
def check_command(self, *args, **kwargs):
|
||||
"""Send a command to the proxy engine and check its response.
|
||||
|
||||
(This is testing the proxy engine, not the underlying engine.)
|
||||
|
||||
parameters as for gtp_engine_test_support.check_engine()
|
||||
|
||||
"""
|
||||
gtp_engine_test_support.check_engine(
|
||||
self.tc, self.engine, *args, **kwargs)
|
||||
|
||||
|
||||
def test_proxy(tc):
|
||||
fx = Proxy_fixture(tc)
|
||||
fx.check_command('test', ['ab', 'cd'], "args: ab cd")
|
||||
fx.proxy.close()
|
||||
tc.assertEqual(
|
||||
fx.commands_handled,
|
||||
[('list_commands', []), ('test', ['ab', 'cd']), ('quit', [])])
|
||||
tc.assertTrue(fx.controller.channel.is_closed)
|
||||
|
||||
def test_close_after_quit(tc):
|
||||
fx = Proxy_fixture(tc)
|
||||
fx.check_command('quit', [], "", expect_end=True)
|
||||
fx.proxy.close()
|
||||
tc.assertEqual(
|
||||
fx.commands_handled,
|
||||
[('list_commands', []), ('quit', [])])
|
||||
tc.assertTrue(fx.channel.is_closed)
|
||||
|
||||
def test_list_commands(tc):
|
||||
fx = Proxy_fixture(tc)
|
||||
tc.assertListEqual(
|
||||
fx.engine.list_commands(),
|
||||
['error', 'fatal', 'gomill-passthrough', 'known_command',
|
||||
'list_commands', 'multiline', 'protocol_version', 'quit', 'test'])
|
||||
fx.proxy.close()
|
||||
|
||||
def test_back_end_has_command(tc):
|
||||
fx = Proxy_fixture(tc)
|
||||
tc.assertTrue(fx.proxy.back_end_has_command('test'))
|
||||
tc.assertFalse(fx.proxy.back_end_has_command('xyzzy'))
|
||||
tc.assertFalse(fx.proxy.back_end_has_command('gomill-passthrough'))
|
||||
|
||||
def test_passthrough(tc):
|
||||
fx = Proxy_fixture(tc)
|
||||
fx.check_command('known_command', ['gomill-passthrough'], "true")
|
||||
fx.check_command('gomill-passthrough', ['test', 'ab', 'cd'], "args: ab cd")
|
||||
fx.check_command(
|
||||
'gomill-passthrough', ['known_command', 'gomill-passthrough'], "false")
|
||||
fx.check_command('gomill-passthrough', [],
|
||||
"invalid arguments", expect_failure=True)
|
||||
tc.assertEqual(
|
||||
fx.commands_handled,
|
||||
[('list_commands', []), ('test', ['ab', 'cd']),
|
||||
('known_command', ['gomill-passthrough'])])
|
||||
|
||||
def test_pass_command(tc):
|
||||
fx = Proxy_fixture(tc)
|
||||
tc.assertEqual(fx.proxy.pass_command("test", ["ab", "cd"]), "args: ab cd")
|
||||
with tc.assertRaises(BadGtpResponse) as ar:
|
||||
fx.proxy.pass_command("error", [])
|
||||
tc.assertEqual(ar.exception.gtp_error_message, "normal error")
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"failure response from 'error' to testbackend:\n"
|
||||
"normal error")
|
||||
|
||||
def test_pass_command_with_channel_error(tc):
|
||||
fx = Proxy_fixture(tc)
|
||||
fx.channel.fail_next_command = True
|
||||
with tc.assertRaises(BackEndError) as ar:
|
||||
fx.proxy.pass_command("test", [])
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"transport error sending 'test' to testbackend:\n"
|
||||
"forced failure for send_command_line")
|
||||
tc.assertIsInstance(ar.exception.cause, GtpTransportError)
|
||||
fx.proxy.close()
|
||||
tc.assertEqual(fx.commands_handled, [('list_commands', [])])
|
||||
|
||||
def test_handle_command(tc):
|
||||
def handle_xyzzy(args):
|
||||
if args and args[0] == "error":
|
||||
return fx.proxy.handle_command("error", [])
|
||||
else:
|
||||
return fx.proxy.handle_command("test", ["nothing", "happens"])
|
||||
fx = Proxy_fixture(tc)
|
||||
fx.engine.add_command("xyzzy", handle_xyzzy)
|
||||
fx.check_command('xyzzy', [], "args: nothing happens")
|
||||
fx.check_command('xyzzy', ['error'],
|
||||
"normal error", expect_failure=True)
|
||||
|
||||
def test_handle_command_with_channel_error(tc):
|
||||
def handle_xyzzy(args):
|
||||
return fx.proxy.handle_command("test", [])
|
||||
fx = Proxy_fixture(tc)
|
||||
fx.engine.add_command("xyzzy", handle_xyzzy)
|
||||
fx.channel.fail_next_command = True
|
||||
fx.check_command('xyzzy', [],
|
||||
"transport error sending 'test' to testbackend:\n"
|
||||
"forced failure for send_command_line",
|
||||
expect_failure=True, expect_end=True)
|
||||
fx.proxy.close()
|
||||
tc.assertEqual(fx.commands_handled, [('list_commands', [])])
|
||||
|
||||
def test_back_end_goes_away(tc):
|
||||
fx = Proxy_fixture(tc)
|
||||
tc.assertEqual(fx.proxy.pass_command("quit", []), "")
|
||||
fx.check_command('test', ['ab', 'cd'],
|
||||
"error sending 'test ab cd' to testbackend:\n"
|
||||
"engine has closed the command channel",
|
||||
expect_failure=True, expect_end=True)
|
||||
|
||||
def test_close_with_errors(tc):
|
||||
fx = Proxy_fixture(tc)
|
||||
fx.channel.fail_next_command = True
|
||||
with tc.assertRaises(BackEndError) as ar:
|
||||
fx.proxy.close()
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"transport error sending 'quit' to testbackend:\n"
|
||||
"forced failure for send_command_line")
|
||||
tc.assertTrue(fx.channel.is_closed)
|
||||
|
||||
def test_quit_ignores_already_closed(tc):
|
||||
fx = Proxy_fixture(tc)
|
||||
tc.assertEqual(fx.proxy.pass_command("quit", []), "")
|
||||
fx.check_command('quit', [], "", expect_end=True)
|
||||
fx.proxy.close()
|
||||
tc.assertEqual(fx.commands_handled,
|
||||
[('list_commands', []), ('quit', [])])
|
||||
|
||||
def test_quit_with_failure_response(tc):
|
||||
fx = Proxy_fixture(tc)
|
||||
fx.underlying_engine.force_error("quit")
|
||||
fx.check_command('quit', [], None,
|
||||
expect_failure=True, expect_end=True)
|
||||
fx.proxy.close()
|
||||
tc.assertEqual(fx.commands_handled,
|
||||
[('list_commands', []), ('quit', [])])
|
||||
|
||||
def test_quit_with_channel_error(tc):
|
||||
fx = Proxy_fixture(tc)
|
||||
fx.channel.fail_next_command = True
|
||||
fx.check_command('quit', [],
|
||||
"transport error sending 'quit' to testbackend:\n"
|
||||
"forced failure for send_command_line",
|
||||
expect_failure=True, expect_end=True)
|
||||
fx.proxy.close()
|
||||
tc.assertEqual(fx.commands_handled, [('list_commands', [])])
|
||||
|
||||
def test_nontgtp_backend(tc):
|
||||
channel = gtp_controller_test_support.Preprogrammed_gtp_channel(
|
||||
"Usage: randomprogram [options]\n\nOptions:\n"
|
||||
"--help show this help message and exit\n")
|
||||
controller = gtp_controller.Gtp_controller(channel, 'testbackend')
|
||||
proxy = gtp_proxy.Gtp_proxy()
|
||||
with tc.assertRaises(BackEndError) as ar:
|
||||
proxy.set_back_end_controller(controller)
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"GTP protocol error reading response to first command "
|
||||
"(list_commands) from testbackend:\n"
|
||||
"engine isn't speaking GTP: first byte is 'U'")
|
||||
tc.assertIsInstance(ar.exception.cause, GtpProtocolError)
|
||||
proxy.close()
|
||||
|
||||
def test_error_from_list_commands(tc):
|
||||
channel = gtp_engine_fixtures.get_test_channel()
|
||||
channel.engine.force_error("list_commands")
|
||||
controller = gtp_controller.Gtp_controller(channel, 'testbackend')
|
||||
proxy = gtp_proxy.Gtp_proxy()
|
||||
with tc.assertRaises(BackEndError) as ar:
|
||||
proxy.set_back_end_controller(controller)
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"failure response from first command "
|
||||
"(list_commands) to testbackend:\n"
|
||||
"handler forced to fail")
|
||||
tc.assertIsInstance(ar.exception.cause, BadGtpResponse)
|
||||
proxy.close()
|
||||
|
||||
|
||||
def test_set_back_end_subprocess(tc):
|
||||
fx = gtp_engine_fixtures.State_reporter_fixture(tc)
|
||||
proxy = gtp_proxy.Gtp_proxy()
|
||||
# the state-report will be taken as the response to list_commands
|
||||
proxy.set_back_end_subprocess(fx.cmd, stderr=fx.devnull)
|
||||
proxy.expect_back_end_exit()
|
||||
proxy.close()
|
||||
|
||||
def test_set_back_end_subprocess_nonexistent_program(tc):
|
||||
proxy = gtp_proxy.Gtp_proxy()
|
||||
with tc.assertRaises(BackEndError) as ar:
|
||||
proxy.set_back_end_subprocess("/nonexistent/program")
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"can't launch back end command\n"
|
||||
"[Errno 2] No such file or directory")
|
||||
tc.assertIsInstance(ar.exception.cause, GtpChannelError)
|
||||
# check it's safe to close when the controller was never set
|
||||
proxy.close()
|
106
gomill/gomill_tests/gtp_state_test_support.py
Normal file
106
gomill/gomill_tests/gtp_state_test_support.py
Normal file
@ -0,0 +1,106 @@
|
||||
"""Support code for testing gtp_states."""
|
||||
|
||||
from gomill import gtp_states
|
||||
from gomill.common import *
|
||||
|
||||
class Player(object):
|
||||
"""Player (stateful move generator) for testing gtp_states.
|
||||
|
||||
Public attributes:
|
||||
last_game_state -- the Game_state from the last genmove-like command
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.next_move = None
|
||||
self.next_comment = None
|
||||
self.next_cookie = None
|
||||
self.last_game_state = None
|
||||
self.resign_next_move = False
|
||||
|
||||
def set_next_move(self, vertex, comment=None, cookie=None):
|
||||
"""Specify what to return from the next genmove-like command."""
|
||||
self.next_move = move_from_vertex(vertex, 19)
|
||||
self.next_comment = comment
|
||||
self.next_cookie = cookie
|
||||
|
||||
def set_next_move_resign(self):
|
||||
self.resign_next_move = True
|
||||
|
||||
def genmove(self, game_state, player):
|
||||
"""Move generator returns points from the move list.
|
||||
|
||||
game_state -- gtp_states.Game_state
|
||||
player -- 'b' or 'w'
|
||||
|
||||
"""
|
||||
self.last_game_state = game_state
|
||||
# Freeze the move_history as we saw it
|
||||
self.last_game_state.move_history = self.last_game_state.move_history[:]
|
||||
|
||||
result = gtp_states.Move_generator_result()
|
||||
if self.resign_next_move:
|
||||
result.resign = True
|
||||
elif self.next_move is not None:
|
||||
result.move = self.next_move
|
||||
else:
|
||||
result.pass_move = True
|
||||
if self.next_comment is not None:
|
||||
result.comments = self.next_comment
|
||||
if self.next_cookie is not None:
|
||||
result.cookie = self.next_cookie
|
||||
self.next_move = None
|
||||
self.next_comment = None
|
||||
self.next_cookie = None
|
||||
self.resign_next_move = False
|
||||
return result
|
||||
|
||||
class Testing_gtp_state(gtp_states.Gtp_state):
|
||||
"""Variant of Gtp_state suitable for use in tests.
|
||||
|
||||
This doesn't read from or write to the filesystem.
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Testing_gtp_state, self).__init__(*args, **kwargs)
|
||||
self._file_contents = {}
|
||||
|
||||
def _register_file(self, pathname, contents):
|
||||
self._file_contents[pathname] = contents
|
||||
|
||||
def _load_file(self, pathname):
|
||||
try:
|
||||
return self._file_contents[pathname]
|
||||
except KeyError:
|
||||
raise EnvironmentError("unknown file: %s" % pathname)
|
||||
|
||||
def _save_file(self, pathname, contents):
|
||||
if pathname == "force_fail":
|
||||
open("/nonexistent_directory/foo.sgf", "w")
|
||||
self._file_contents[pathname] = contents
|
||||
|
||||
def _choose_free_handicap_moves(self, number_of_stones):
|
||||
"""Implementation of place_free_handicap.
|
||||
|
||||
Returns for a given number of stones:
|
||||
2 -- A1 A2 A3 (too many stones)
|
||||
4 -- A1 A2 A3 pass (pass isn't permitted)
|
||||
5 -- A1 A2 A3 A4 A5 (which is ok)
|
||||
6 -- A1 A2 A3 A4 A5 A1 (repeated point)
|
||||
8 -- not even the right result type
|
||||
otherwise Gtp_state default (which is to use the fixed handicap points)
|
||||
|
||||
"""
|
||||
if number_of_stones == 2:
|
||||
return ((0, 0), (1, 0), (2, 0))
|
||||
elif number_of_stones == 4:
|
||||
return ((0, 0), (1, 0), (2, 0), None)
|
||||
elif number_of_stones == 5:
|
||||
return ((0, 0), (1, 0), (2, 0), (3, 0), (4, 0))
|
||||
elif number_of_stones == 6:
|
||||
return ((0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (0, 0))
|
||||
elif number_of_stones == 8:
|
||||
return "nonsense"
|
||||
else:
|
||||
return super(Testing_gtp_state, self).\
|
||||
_choose_free_handicap_moves(number_of_stones)
|
581
gomill/gomill_tests/gtp_state_tests.py
Normal file
581
gomill/gomill_tests/gtp_state_tests.py
Normal file
@ -0,0 +1,581 @@
|
||||
"""Tests for gtp_state.py."""
|
||||
|
||||
from textwrap import dedent
|
||||
|
||||
from gomill import boards
|
||||
from gomill import gtp_engine
|
||||
from gomill import gtp_states
|
||||
from gomill.common import format_vertex
|
||||
|
||||
from gomill_tests import test_framework
|
||||
from gomill_tests import gomill_test_support
|
||||
from gomill_tests import gtp_engine_test_support
|
||||
from gomill_tests import gtp_state_test_support
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
class Gtp_state_fixture(test_framework.Fixture):
|
||||
"""Fixture for managing a Gtp_state.
|
||||
|
||||
The move generator comes from gtp_state_test_support.Player
|
||||
|
||||
Adds a type equality function for History_move.
|
||||
|
||||
"""
|
||||
def __init__(self, tc):
|
||||
self.tc = tc
|
||||
self.player = gtp_state_test_support.Player()
|
||||
self.gtp_state = gtp_state_test_support.Testing_gtp_state(
|
||||
move_generator=self.player.genmove,
|
||||
acceptable_sizes=(9, 11, 13, 19))
|
||||
self.engine = gtp_engine.Gtp_engine_protocol()
|
||||
self.engine.add_protocol_commands()
|
||||
self.engine.add_commands(self.gtp_state.get_handlers())
|
||||
self.tc.addTypeEqualityFunc(
|
||||
gtp_states.History_move, self.assertHistoryMoveEqual)
|
||||
|
||||
def assertHistoryMoveEqual(self, hm1, hm2, msg=None):
|
||||
t1 = (hm1.colour, hm1.move, hm1.comments, hm1.cookie)
|
||||
t2 = (hm2.colour, hm2.move, hm2.comments, hm2.cookie)
|
||||
self.tc.assertEqual(t1, t2, "History_moves differ")
|
||||
|
||||
def check_command(self, *args, **kwargs):
|
||||
"""Check a single GTP command.
|
||||
|
||||
parameters as for gtp_engine_test_support.check_engine()
|
||||
|
||||
"""
|
||||
gtp_engine_test_support.check_engine(
|
||||
self.tc, self.engine, *args, **kwargs)
|
||||
|
||||
def check_board_empty_9(self):
|
||||
self.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
|
||||
def test_gtp_state(tc):
|
||||
fx = Gtp_state_fixture(tc)
|
||||
|
||||
fx.check_command('nonsense', [''], "unknown command",
|
||||
expect_failure=True)
|
||||
fx.check_command('protocol_version', [''], "2")
|
||||
|
||||
fx.player.set_next_move("A3", "preprogrammed move 0")
|
||||
fx.check_command('genmove', ['B'], "A3")
|
||||
game_state = fx.player.last_game_state
|
||||
# default board size is min(acceptable_sizes)
|
||||
tc.assertEqual(game_state.size, 9)
|
||||
b = boards.Board(9)
|
||||
b.play(2, 0, 'b')
|
||||
tc.assertEqual(game_state.board, b)
|
||||
tc.assertEqual(game_state.komi, 0.0)
|
||||
tc.assertEqual(game_state.history_base, boards.Board(9))
|
||||
tc.assertEqual(game_state.move_history, [])
|
||||
tc.assertIsNone(game_state.ko_point)
|
||||
tc.assertIsNone(game_state.handicap)
|
||||
tc.assertIs(game_state.for_regression, False)
|
||||
tc.assertIsNone(game_state.time_settings)
|
||||
tc.assertIsNone(game_state.time_remaining)
|
||||
tc.assertIsNone(game_state.canadian_stones_remaining)
|
||||
fx.check_command('gomill-explain_last_move', [], "preprogrammed move 0")
|
||||
|
||||
fx.check_command('play', ['W', 'A4'], "")
|
||||
fx.check_command('komi', ['5.5'], "")
|
||||
fx.player.set_next_move("C9")
|
||||
fx.check_command('genmove', ['B'], "C9")
|
||||
game_state = fx.player.last_game_state
|
||||
tc.assertEqual(game_state.komi, 5.5)
|
||||
tc.assertEqual(game_state.history_base, boards.Board(9))
|
||||
tc.assertEqual(len(game_state.move_history), 2)
|
||||
tc.assertEqual(game_state.move_history[0],
|
||||
gtp_states.History_move('b', (2, 0), "preprogrammed move 0"))
|
||||
tc.assertEqual(game_state.move_history[1],
|
||||
gtp_states.History_move('w', (3, 0)))
|
||||
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
fx.check_command('gomill-explain_last_move', [], "")
|
||||
fx.check_command('genmove', ['W'], "pass")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . # . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 o . . . . . . . .
|
||||
3 # . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
|
||||
fx.player.set_next_move_resign()
|
||||
fx.check_command('genmove', ['B'], "resign")
|
||||
fx.check_command('quit', [''], "", expect_end=True)
|
||||
|
||||
|
||||
def test_clear_board_and_boardsize(tc):
|
||||
fx = Gtp_state_fixture(tc)
|
||||
fx.check_command('play', ['W', 'A4'], "")
|
||||
fx.check_command('boardsize', ['7'], "unacceptable size",
|
||||
expect_failure=True)
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 o . . . . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.check_command('clear_board', [], "")
|
||||
fx.check_board_empty_9()
|
||||
fx.check_command('play', ['W', 'A4'], "")
|
||||
fx.check_command('boardsize', ['11'], "")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
11 . . . . . . . . . . .
|
||||
10 . . . . . . . . . . .
|
||||
9 . . . . . . . . . . .
|
||||
8 . . . . . . . . . . .
|
||||
7 . . . . . . . . . . .
|
||||
6 . . . . . . . . . . .
|
||||
5 . . . . . . . . . . .
|
||||
4 . . . . . . . . . . .
|
||||
3 . . . . . . . . . . .
|
||||
2 . . . . . . . . . . .
|
||||
1 . . . . . . . . . . .
|
||||
A B C D E F G H J K L"""))
|
||||
|
||||
def test_play(tc):
|
||||
fx = Gtp_state_fixture(tc)
|
||||
fx.check_command('play', ['B', "E5"], "")
|
||||
fx.check_command('play', ['w', "e4"], "")
|
||||
fx.check_command('play', [], "invalid arguments", expect_failure=True)
|
||||
fx.check_command('play', ['B'], "invalid arguments", expect_failure=True)
|
||||
# additional arguments are ignored (following gnugo)
|
||||
fx.check_command('play', ['B', "F4", "E5"], "")
|
||||
fx.check_command('play', ['white', "f5"], "")
|
||||
fx.check_command('play', ['W', "K3"], "vertex is off board: 'k3'",
|
||||
expect_failure=True)
|
||||
fx.check_command('play', ['X', "A4"], "invalid colour: 'X'",
|
||||
expect_failure=True)
|
||||
fx.check_command('play', ['BLACK', "e4"], "illegal move",
|
||||
expect_failure=True)
|
||||
fx.check_command('play', ['B', "pass"], "")
|
||||
fx.check_command('play', ['W', "PASS"], "")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . # o . . .
|
||||
4 . . . . o # . . .
|
||||
3 . . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
|
||||
def test_komi(tc):
|
||||
fx = Gtp_state_fixture(tc)
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
tc.assertEqual(fx.player.last_game_state.komi, 0.0)
|
||||
fx.check_command('komi', ['1'], "")
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
tc.assertEqual(fx.player.last_game_state.komi, 1.0)
|
||||
fx.check_command('komi', ['1.0'], "")
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
tc.assertEqual(fx.player.last_game_state.komi, 1.0)
|
||||
fx.check_command('komi', ['7.5'], "")
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
tc.assertEqual(fx.player.last_game_state.komi, 7.5)
|
||||
fx.check_command('komi', ['-3.5'], "")
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
tc.assertEqual(fx.player.last_game_state.komi, -3.5)
|
||||
fx.check_command('komi', ['20000'], "")
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
tc.assertEqual(fx.player.last_game_state.komi, 625.0)
|
||||
fx.check_command('komi', ['-20000'], "")
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
tc.assertEqual(fx.player.last_game_state.komi, -625.0)
|
||||
fx.check_command('komi', ['nonsense'], "invalid float: 'nonsense'",
|
||||
expect_failure=True)
|
||||
fx.check_command('komi', ['NaN'], "invalid float: 'NaN'",
|
||||
expect_failure=True)
|
||||
fx.check_command('komi', ['inf'], "invalid float: 'inf'",
|
||||
expect_failure=True)
|
||||
fx.check_command('komi', ['-1e400'], "invalid float: '-1e400'",
|
||||
expect_failure=True)
|
||||
|
||||
def test_undo(tc):
|
||||
fx = Gtp_state_fixture(tc)
|
||||
fx.player.set_next_move("A3", "preprogrammed move A3")
|
||||
fx.check_command('genmove', ['B'], "A3")
|
||||
fx.check_command('gomill-explain_last_move', [], "preprogrammed move A3")
|
||||
fx.check_command('play', ['W', 'A4'], "")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 o . . . . . . . .
|
||||
3 # . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.check_command('undo', [], "")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 # . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.player.set_next_move("D4", "preprogrammed move D4")
|
||||
fx.check_command('genmove', ['W'], "D4")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . o . . . . .
|
||||
3 # . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.check_command('gomill-explain_last_move', [], "preprogrammed move D4")
|
||||
fx.check_command('undo', [], "")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 # . . . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.check_command('gomill-explain_last_move', [], "preprogrammed move A3")
|
||||
fx.check_command('undo', [], "")
|
||||
fx.check_board_empty_9()
|
||||
fx.check_command('gomill-explain_last_move', [], "")
|
||||
fx.check_command('undo', [], "cannot undo", expect_failure=True)
|
||||
|
||||
def test_fixed_handicap(tc):
|
||||
fx = Gtp_state_fixture(tc)
|
||||
fx.check_command('fixed_handicap', ['3'], "C3 G7 C7")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . # . . . # . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . # . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
tc.assertEqual(fx.player.last_game_state.handicap, 3)
|
||||
fx.check_command('boardsize', ['19'], "")
|
||||
fx.check_command('fixed_handicap', ['7'], "D4 Q16 D16 Q4 D10 Q10 K10")
|
||||
fx.check_command('fixed_handicap', ['7'], "board not empty",
|
||||
expect_failure=True)
|
||||
fx.check_command('boardsize', ['9'], "")
|
||||
fx.check_command('play', ['B', 'B2'], "")
|
||||
fx.check_command('fixed_handicap', ['2'], "board not empty",
|
||||
expect_failure=True)
|
||||
fx.check_command('clear_board', [], "")
|
||||
fx.check_command('fixed_handicap', ['0'], "invalid number of stones",
|
||||
expect_failure=True)
|
||||
fx.check_command('fixed_handicap', ['1'], "invalid number of stones",
|
||||
expect_failure=True)
|
||||
fx.check_command('fixed_handicap', ['10'], "invalid number of stones",
|
||||
expect_failure=True)
|
||||
fx.check_command('fixed_handicap', ['2.5'], "invalid int: '2.5'",
|
||||
expect_failure=True)
|
||||
fx.check_command('fixed_handicap', [], "invalid arguments",
|
||||
expect_failure=True)
|
||||
|
||||
def test_place_free_handicap(tc):
|
||||
# See gtp_state_test_support.Testing_gtp_state for description of the choice
|
||||
# of points.
|
||||
fx = Gtp_state_fixture(tc)
|
||||
fx.check_command('place_free_handicap', ['3'], "C3 G7 C7")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . # . . . # . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . # . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
tc.assertEqual(fx.player.last_game_state.handicap, 3)
|
||||
fx.check_command('boardsize', ['19'], "")
|
||||
fx.check_command('place_free_handicap', ['7'], "D4 Q16 D16 Q4 D10 Q10 K10")
|
||||
fx.check_command('place_free_handicap', ['7'], "board not empty",
|
||||
expect_failure=True)
|
||||
fx.check_command('boardsize', ['9'], "")
|
||||
fx.check_command('play', ['B', 'B2'], "")
|
||||
fx.check_command('place_free_handicap', ['2'], "board not empty",
|
||||
expect_failure=True)
|
||||
fx.check_command('clear_board', [], "")
|
||||
fx.check_command('place_free_handicap', ['0'], "invalid number of stones",
|
||||
expect_failure=True)
|
||||
fx.check_command('place_free_handicap', ['1'], "invalid number of stones",
|
||||
expect_failure=True)
|
||||
fx.check_command('place_free_handicap', ['2.5'], "invalid int: '2.5'",
|
||||
expect_failure=True)
|
||||
fx.check_command('place_free_handicap', [], "invalid arguments",
|
||||
expect_failure=True)
|
||||
fx.check_command('place_free_handicap', ['10'],
|
||||
"C3 G7 C7 G3 C5 G5 E3 E7 E5")
|
||||
fx.check_command('clear_board', [''], "")
|
||||
fx.check_command('place_free_handicap', ['5'],
|
||||
"A1 A2 A3 A4 A5")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 # . . . . . . . .
|
||||
4 # . . . . . . . .
|
||||
3 # . . . . . . . .
|
||||
2 # . . . . . . . .
|
||||
1 # . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.check_command('clear_board', [''], "")
|
||||
fx.check_command('place_free_handicap', ['6'],
|
||||
"invalid result from move generator: A1,A2,A3,A4,A5,A1",
|
||||
expect_failure=True)
|
||||
fx.check_board_empty_9()
|
||||
fx.check_command('place_free_handicap', ['2'],
|
||||
"invalid result from move generator: A1,A2,A3",
|
||||
expect_failure=True)
|
||||
fx.check_board_empty_9()
|
||||
fx.check_command('place_free_handicap', ['4'],
|
||||
"invalid result from move generator: A1,A2,A3,pass",
|
||||
expect_failure=True)
|
||||
fx.check_board_empty_9()
|
||||
fx.check_command('place_free_handicap', ['8'],
|
||||
"ValueError: need more than 1 value to unpack",
|
||||
expect_internal_error=True)
|
||||
fx.check_board_empty_9()
|
||||
|
||||
def test_set_free_handicap(tc):
|
||||
fx = Gtp_state_fixture(tc)
|
||||
fx.check_command('set_free_handicap', ["C3", "E5", "C7"], "")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . # . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . # . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . # . . . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
tc.assertEqual(fx.player.last_game_state.handicap, 3)
|
||||
fx.check_command('boardsize', ['9'], "")
|
||||
fx.check_command('play', ['B', 'B2'], "")
|
||||
fx.check_command('set_free_handicap', ["C3", "E5"], "board not empty",
|
||||
expect_failure=True)
|
||||
fx.check_command('clear_board', [], "")
|
||||
fx.check_command('set_free_handicap', ["C3"], "invalid number of stones",
|
||||
expect_failure=True)
|
||||
fx.check_command('set_free_handicap', [], "invalid number of stones",
|
||||
expect_failure=True)
|
||||
all_points = [format_vertex((i, j)) for i in range(9) for j in range(9)]
|
||||
fx.check_command('set_free_handicap', all_points,
|
||||
"invalid number of stones", expect_failure=True)
|
||||
fx.check_command('set_free_handicap', ["C3", "asdasd"],
|
||||
"invalid vertex: 'asdasd'", expect_failure=True)
|
||||
fx.check_board_empty_9()
|
||||
fx.check_command('set_free_handicap', ["C3", "pass"],
|
||||
"'pass' not permitted", expect_failure=True)
|
||||
fx.check_board_empty_9()
|
||||
fx.check_command('set_free_handicap', ["C3", "E5", "C3"],
|
||||
"engine error: C3 is occupied", expect_failure=True)
|
||||
fx.check_board_empty_9()
|
||||
|
||||
|
||||
def test_loadsgf(tc):
|
||||
fx = Gtp_state_fixture(tc)
|
||||
fx.gtp_state._register_file("invalid.sgf", "non-SGF data")
|
||||
fx.gtp_state._register_file(
|
||||
"test1.sgf",
|
||||
"(;SZ[9];B[ee];W[eg];B[dg];W[dh];B[df];W[fh];B[];W[])")
|
||||
fx.gtp_state._register_file(
|
||||
"test2.sgf",
|
||||
"(;SZ[9]AB[fe:ff]AW[gf:gg]PL[W];W[eh];B[ge])")
|
||||
fx.check_command('loadsgf', ["unknown.sgf"],
|
||||
"cannot load file", expect_failure=True)
|
||||
fx.check_command('loadsgf', ["invalid.sgf"],
|
||||
"cannot load file", expect_failure=True)
|
||||
fx.check_command('loadsgf', ["test1.sgf"], "")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . # . . . .
|
||||
4 . . . # . . . . .
|
||||
3 . . . # o . . . .
|
||||
2 . . . o . o . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.check_command('loadsgf', ["test1.sgf", "4"], "")
|
||||
# position _before_ move 4
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . # . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . . # o . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.check_command('undo', [], "")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . # . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . . . o . . . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.check_command('loadsgf', ["test2.sgf"], "")
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . # # . .
|
||||
4 . . . . . # o . .
|
||||
3 . . . . . . o . .
|
||||
2 . . . . o . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
fx.check_command('undo', [], "")
|
||||
fx.check_command('undo', [], "")
|
||||
fx.check_command('undo', [], "cannot undo", expect_failure=True)
|
||||
fx.check_command('showboard', [], dedent("""
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . # . . .
|
||||
4 . . . . . # o . .
|
||||
3 . . . . . . o . .
|
||||
2 . . . . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J"""))
|
||||
|
||||
def test_savesgf(tc):
|
||||
scrub_sgf = gomill_test_support.scrub_sgf
|
||||
|
||||
fx = Gtp_state_fixture(tc)
|
||||
fx.check_command("play", ['B', 'D4'], "")
|
||||
fx.player.set_next_move("C3", "preprogrammed move C3")
|
||||
fx.check_command('genmove', ['W'], "C3")
|
||||
fx.check_command('gomill-savesgf', ['out1.sgf'], "")
|
||||
tc.assertEqual(
|
||||
scrub_sgf(fx.gtp_state._load_file('out1.sgf')).replace("\n", ""),
|
||||
"(;FF[4]AP[gomill:VER]CA[UTF-8]DT[***]GM[1]KM[0]"
|
||||
"SZ[9];B[df];C[preprogrammed move C3]W[cg])")
|
||||
fx.check_command(
|
||||
'gomill-savesgf',
|
||||
['out2.sgf', "PB=testplayer", "PW=GNU\_Go:3.8", "RE=W+3.5"],
|
||||
"")
|
||||
tc.assertEqual(
|
||||
scrub_sgf(fx.gtp_state._load_file('out2.sgf')).replace("\n", ""),
|
||||
"(;FF[4]AP[gomill:VER]CA[UTF-8]DT[***]GM[1]KM[0]"
|
||||
"PB[testplayer]PW[GNU Go:3.8]RE[W+3.5]SZ[9];B[df]"
|
||||
";C[preprogrammed move C3]W[cg])")
|
||||
|
||||
fx.check_command("boardsize", ['19'], "")
|
||||
fx.check_command("fixed_handicap", ['3'], "D4 Q16 D16")
|
||||
fx.check_command("komi", ['5.5'], "")
|
||||
fx.check_command("play", ['W', 'A2'], "")
|
||||
fx.check_command('gomill-savesgf', ['out3.sgf'], "")
|
||||
tc.assertEqual(
|
||||
scrub_sgf(fx.gtp_state._load_file('out3.sgf')).replace("\n", ""),
|
||||
"(;FF[4]AB[dd][dp][pd]AP[gomill:VER]CA[UTF-8]DT[***]"
|
||||
"GM[1]HA[3]KM[5.5]SZ[19];W[ar])")
|
||||
fx.check_command(
|
||||
'gomill-savesgf', ['force_fail'],
|
||||
"error writing file: [Errno 2] "
|
||||
"No such file or directory: '/nonexistent_directory/foo.sgf'",
|
||||
expect_failure=True)
|
||||
|
||||
def test_get_last_move(tc):
|
||||
fx = Gtp_state_fixture(tc)
|
||||
fx.player.set_next_move("A3", "preprogrammed move A3")
|
||||
fx.check_command('genmove', ['B'], "A3")
|
||||
history_moves = fx.player.last_game_state.move_history
|
||||
tc.assertEqual(gtp_states.get_last_move(history_moves, 'b'), (False, None))
|
||||
tc.assertEqual(gtp_states.get_last_move(history_moves, 'w'), (False, None))
|
||||
|
||||
fx.player.set_next_move("B3", "preprogrammed move B3")
|
||||
fx.check_command('genmove', ['W'], "B3")
|
||||
history_moves = fx.player.last_game_state.move_history
|
||||
tc.assertEqual(gtp_states.get_last_move(history_moves, 'b'), (False, None))
|
||||
move_is_available, move = gtp_states.get_last_move(history_moves, 'w')
|
||||
tc.assertIs(move_is_available, True)
|
||||
tc.assertEqual(format_vertex(move), "A3")
|
||||
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
history_moves = fx.player.last_game_state.move_history
|
||||
move_is_available, move = gtp_states.get_last_move(history_moves, 'b')
|
||||
tc.assertIs(move_is_available, True)
|
||||
tc.assertEqual(format_vertex(move), "B3")
|
||||
tc.assertEqual(gtp_states.get_last_move(history_moves, 'w'), (False, None))
|
||||
|
||||
def test_get_last_move_and_cookie(tc):
|
||||
fx = Gtp_state_fixture(tc)
|
||||
fx.player.set_next_move("A3", "preprogrammed move A3", "COOKIE 1")
|
||||
fx.check_command('genmove', ['B'], "A3")
|
||||
history_moves = fx.player.last_game_state.move_history
|
||||
tc.assertEqual(gtp_states.get_last_move_and_cookie(history_moves, 'b'),
|
||||
(False, None, None))
|
||||
tc.assertEqual(gtp_states.get_last_move_and_cookie(history_moves, 'w'),
|
||||
(False, None, None))
|
||||
|
||||
fx.check_command('play', ['W', 'B3'], "")
|
||||
fx.check_command('genmove', ['B'], "pass")
|
||||
history_moves = fx.player.last_game_state.move_history
|
||||
move_is_available, move, cookie = gtp_states.get_last_move_and_cookie(
|
||||
history_moves, 'b')
|
||||
tc.assertIs(move_is_available, True)
|
||||
tc.assertEqual(format_vertex(move), "B3")
|
||||
tc.assertIs(cookie, "COOKIE 1")
|
||||
tc.assertEqual(gtp_states.get_last_move_and_cookie(history_moves, 'w'),
|
||||
(False, None, None))
|
||||
|
481
gomill/gomill_tests/mcts_tuner_tests.py
Normal file
481
gomill/gomill_tests/mcts_tuner_tests.py
Normal file
@ -0,0 +1,481 @@
|
||||
"""Tests for mcts_tuners.py"""
|
||||
|
||||
from __future__ import with_statement, division
|
||||
|
||||
from math import sqrt
|
||||
import random
|
||||
from textwrap import dedent
|
||||
import cPickle as pickle
|
||||
|
||||
from gomill import mcts_tuners
|
||||
from gomill.game_jobs import Game_job, Game_job_result
|
||||
from gomill.gtp_games import Game_result
|
||||
from gomill.mcts_tuners import Parameter_config
|
||||
from gomill.competitions import (
|
||||
Player_config, CompetitionError, ControlFileError)
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
def simple_make_candidate(*args):
|
||||
if -1 in args:
|
||||
raise ValueError("oops")
|
||||
return Player_config("cand " + " ".join(map(str, args)))
|
||||
|
||||
def default_config():
|
||||
return {
|
||||
'board_size' : 13,
|
||||
'komi' : 7.5,
|
||||
'players' : {
|
||||
'opp' : Player_config("test"),
|
||||
},
|
||||
'candidate_colour' : 'w',
|
||||
'opponent' : 'opp',
|
||||
'exploration_coefficient' : 0.2,
|
||||
'initial_visits' : 10,
|
||||
'initial_wins' : 5,
|
||||
'parameters' : [
|
||||
Parameter_config(
|
||||
'resign_at',
|
||||
scale = float,
|
||||
split = 12,
|
||||
format = "rsn@ %.2f"),
|
||||
|
||||
Parameter_config(
|
||||
'initial_wins',
|
||||
scale = mcts_tuners.LINEAR(0, 100),
|
||||
split = 10,
|
||||
format = "iwins %d"),
|
||||
],
|
||||
'make_candidate' : simple_make_candidate,
|
||||
}
|
||||
|
||||
def test_bad_komi(tc):
|
||||
comp = mcts_tuners.Mcts_tuner('mctstest')
|
||||
config = default_config()
|
||||
config['komi'] = 6
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"komi: must be fractional to prevent jigos")
|
||||
|
||||
def test_parameter_config(tc):
|
||||
comp = mcts_tuners.Mcts_tuner('mctstest')
|
||||
config = default_config()
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertEqual(comp.tree.max_depth, 1)
|
||||
tc.assertEqual(comp.format_engine_parameters((0.5, 23)),
|
||||
"rsn@ 0.50; iwins 23")
|
||||
tc.assertEqual(comp.format_engine_parameters(('x', 23)),
|
||||
"[resign_at?x]; iwins 23")
|
||||
tc.assertEqual(comp.format_optimiser_parameters((0.5, 0.23)),
|
||||
"rsn@ 0.50; iwins 23")
|
||||
tc.assertEqual(comp.scale_parameters((0.5, 0.23)), (0.5, 23))
|
||||
with tc.assertRaises(CompetitionError) as ar:
|
||||
comp.scale_parameters((0.5, None))
|
||||
tc.assertTracebackStringEqual(str(ar.exception), dedent("""\
|
||||
error from scale for initial_wins
|
||||
TypeError: unsupported operand type(s) for *: 'NoneType' and 'float'
|
||||
traceback (most recent call last):
|
||||
mcts_tuners|__call__
|
||||
failing line:
|
||||
result = (f * self.range) + self.lower_bound
|
||||
"""))
|
||||
|
||||
tc.assertRaisesRegexp(
|
||||
ValueError, "'code' not specified",
|
||||
comp.parameter_spec_from_config, Parameter_config())
|
||||
tc.assertRaisesRegexp(
|
||||
ValueError, "code specified as both positional and keyword argument",
|
||||
comp.parameter_spec_from_config,
|
||||
Parameter_config('pa1', code='pa2', scale=float, split=2, format="%s"))
|
||||
tc.assertRaisesRegexp(
|
||||
ValueError, "too many positional arguments",
|
||||
comp.parameter_spec_from_config,
|
||||
Parameter_config('pa1', float, scale=float, split=2, format="%s"))
|
||||
tc.assertRaisesRegexp(
|
||||
ValueError, "'scale': invalid callable",
|
||||
comp.parameter_spec_from_config,
|
||||
Parameter_config('pa1', scale=None, split=2, format="%s"))
|
||||
pspec = comp.parameter_spec_from_config(
|
||||
Parameter_config('pa1', scale=float, split=2))
|
||||
tc.assertRaisesRegexp(
|
||||
ControlFileError, "'format': invalid format string",
|
||||
comp.parameter_spec_from_config,
|
||||
Parameter_config('pa1', scale=float, split=2, format="nopct"))
|
||||
|
||||
def test_bad_parameter_config(tc):
|
||||
comp = mcts_tuners.Mcts_tuner('mctstest')
|
||||
config = default_config()
|
||||
config['parameters'].append(
|
||||
Parameter_config(
|
||||
'bad',
|
||||
scale = mcts_tuners.LOG(0.0, 1.0),
|
||||
split = 10))
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertMultiLineEqual(str(ar.exception), dedent("""\
|
||||
parameter bad: 'scale': invalid parameters for LOG:
|
||||
lower bound is zero"""))
|
||||
|
||||
def test_nonsense_parameter_config(tc):
|
||||
comp = mcts_tuners.Mcts_tuner('mctstest')
|
||||
config = default_config()
|
||||
config['parameters'].append(99)
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertMultiLineEqual(str(ar.exception), dedent("""\
|
||||
'parameters': item 2: not a Parameter"""))
|
||||
|
||||
def test_nocode_parameter_config(tc):
|
||||
comp = mcts_tuners.Mcts_tuner('mctstest')
|
||||
config = default_config()
|
||||
config['parameters'].append(Parameter_config())
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertMultiLineEqual(str(ar.exception), dedent("""\
|
||||
parameter 2: 'code' not specified"""))
|
||||
|
||||
def test_scale_check(tc):
|
||||
comp = mcts_tuners.Mcts_tuner('mctstest')
|
||||
config = default_config()
|
||||
config['parameters'].append(
|
||||
Parameter_config(
|
||||
'bad',
|
||||
scale = str.split,
|
||||
split = 10))
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertTracebackStringEqual(str(ar.exception), dedent("""\
|
||||
parameter bad: error from scale (applied to 0.05)
|
||||
TypeError: split-wants-float-not-str
|
||||
traceback (most recent call last):
|
||||
"""), fixups=[
|
||||
("descriptor 'split' requires a 'str' object but received a 'float'",
|
||||
"split-wants-float-not-str"),
|
||||
("unbound method split() must be called with str instance as "
|
||||
"first argument (got float instance instead)",
|
||||
"split-wants-float-not-str"),
|
||||
])
|
||||
|
||||
def test_format_validation(tc):
|
||||
comp = mcts_tuners.Mcts_tuner('mctstest')
|
||||
config = default_config()
|
||||
config['parameters'].append(
|
||||
Parameter_config(
|
||||
'bad',
|
||||
scale = str,
|
||||
split = 10,
|
||||
format = "bad: %.2f"))
|
||||
tc.assertRaisesRegexp(
|
||||
ControlFileError, "'format': invalid format string",
|
||||
comp.initialise_from_control_file, config)
|
||||
|
||||
def test_make_candidate(tc):
|
||||
comp = mcts_tuners.Mcts_tuner('mctstest')
|
||||
config = default_config()
|
||||
comp.initialise_from_control_file(config)
|
||||
cand = comp.make_candidate('c#1', (0.5, 23))
|
||||
tc.assertEqual(cand.code, 'c#1')
|
||||
tc.assertListEqual(cand.cmd_args, ['cand', '0.5', '23'])
|
||||
with tc.assertRaises(CompetitionError) as ar:
|
||||
comp.make_candidate('c#1', (-1, 23))
|
||||
tc.assertTracebackStringEqual(str(ar.exception), dedent("""\
|
||||
error from make_candidate()
|
||||
ValueError: oops
|
||||
traceback (most recent call last):
|
||||
mcts_tuner_tests|simple_make_candidate
|
||||
failing line:
|
||||
raise ValueError("oops")
|
||||
"""))
|
||||
|
||||
def test_get_player_checks(tc):
|
||||
comp = mcts_tuners.Mcts_tuner('mctstest')
|
||||
config = default_config()
|
||||
comp.initialise_from_control_file(config)
|
||||
checks = comp.get_player_checks()
|
||||
tc.assertEqual(len(checks), 2)
|
||||
tc.assertEqual(checks[0].player.code, "candidate")
|
||||
tc.assertEqual(checks[0].player.cmd_args, ['cand', str(1/24), '5.0'])
|
||||
tc.assertEqual(checks[1].player.code, "opp")
|
||||
tc.assertEqual(checks[1].player.cmd_args, ['test'])
|
||||
|
||||
def test_linear_scale(tc):
|
||||
lsf = mcts_tuners.Linear_scale_fn(20.0, 30.0)
|
||||
tc.assertEqual(lsf(0.0), 20.0)
|
||||
tc.assertEqual(lsf(1.0), 30.0)
|
||||
tc.assertEqual(lsf(0.5), 25.0)
|
||||
tc.assertEqual(lsf(0.49), 24.9)
|
||||
|
||||
lsi = mcts_tuners.Linear_scale_fn(20.0, 30.0, integer=True)
|
||||
tc.assertEqual(lsi(0.0), 20)
|
||||
tc.assertEqual(lsi(1.0), 30)
|
||||
tc.assertEqual(lsi(0.49), 25)
|
||||
tc.assertEqual(lsi(0.51), 25)
|
||||
|
||||
def test_log_scale(tc):
|
||||
lsf = mcts_tuners.Log_scale_fn(2, 200000)
|
||||
tc.assertAlmostEqual(lsf(0.0), 2.0)
|
||||
tc.assertAlmostEqual(lsf(0.2), 20.0)
|
||||
tc.assertAlmostEqual(lsf(0.4), 200.0)
|
||||
tc.assertAlmostEqual(lsf(0.5), 2*sqrt(100000.00))
|
||||
tc.assertAlmostEqual(lsf(0.6), 2000.0)
|
||||
tc.assertAlmostEqual(lsf(0.8), 20000.0)
|
||||
tc.assertAlmostEqual(lsf(1.0), 200000.0)
|
||||
|
||||
lsi = mcts_tuners.Log_scale_fn(1, 100, integer=True)
|
||||
tc.assertAlmostEqual(lsi(0.1), 2)
|
||||
|
||||
lsn = mcts_tuners.Log_scale_fn(-2, -200)
|
||||
tc.assertAlmostEqual(lsn(0.5), -20)
|
||||
|
||||
tc.assertRaises(ValueError, mcts_tuners.Log_scale_fn, 1, -2)
|
||||
|
||||
|
||||
def test_explicit_scale(tc):
|
||||
tc.assertRaises(ValueError, mcts_tuners.Explicit_scale_fn, [])
|
||||
|
||||
pvalues = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
|
||||
|
||||
comp = mcts_tuners.Mcts_tuner('mctstest')
|
||||
config = default_config()
|
||||
config['parameters'] = [
|
||||
Parameter_config(
|
||||
'range',
|
||||
scale = mcts_tuners.EXPLICIT(pvalues),
|
||||
split = len(pvalues))]
|
||||
comp.initialise_from_control_file(config)
|
||||
candidate_sees = [
|
||||
comp.scale_parameters(comp.tree.parameters_for_path([i]))[0]
|
||||
for i, _ in enumerate(pvalues)
|
||||
]
|
||||
tc.assertEqual(candidate_sees, pvalues)
|
||||
|
||||
def test_integer_scale_example(tc):
|
||||
comp = mcts_tuners.Mcts_tuner('mctstest')
|
||||
config = default_config()
|
||||
config['parameters'] = [
|
||||
Parameter_config(
|
||||
'range',
|
||||
scale = mcts_tuners.LINEAR(-.5, 10.5, integer=True),
|
||||
split = 11)]
|
||||
comp.initialise_from_control_file(config)
|
||||
candidate_sees = [
|
||||
comp.scale_parameters(comp.tree.parameters_for_path([i]))[0]
|
||||
for i in xrange(11)
|
||||
]
|
||||
tc.assertEqual(candidate_sees, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
||||
|
||||
|
||||
def test_tree(tc):
|
||||
tree1 = mcts_tuners.Tree(
|
||||
splits=[3, 3],
|
||||
max_depth=5,
|
||||
exploration_coefficient=0.5,
|
||||
initial_visits=10,
|
||||
initial_wins=5,
|
||||
parameter_formatter=str,
|
||||
)
|
||||
|
||||
tree2 = mcts_tuners.Tree(
|
||||
splits=[2, 4],
|
||||
max_depth=5,
|
||||
exploration_coefficient=0.5,
|
||||
initial_visits=10,
|
||||
initial_wins=5,
|
||||
parameter_formatter=str,
|
||||
)
|
||||
|
||||
tc.assertEqual(tree1.max_depth, 5)
|
||||
|
||||
tc.assertEqual(tree1._cube_coordinates, [
|
||||
(0, 0), (0, 1), (0, 2),
|
||||
(1, 0), (1, 1), (1, 2),
|
||||
(2, 0), (2, 1), (2, 2),
|
||||
])
|
||||
|
||||
tc.assertEqual(tree2._cube_coordinates, [
|
||||
(0, 0), (0, 1), (0, 2), (0, 3),
|
||||
(1, 0), (1, 1), (1, 2), (1, 3),
|
||||
])
|
||||
|
||||
def scaleup(vector, scales):
|
||||
"""Multiply each member of 'vector' by the corresponding scale.
|
||||
|
||||
Rounds to nearest integer if the difference is very small.
|
||||
|
||||
"""
|
||||
result = []
|
||||
for v, scale in zip(vector, scales):
|
||||
f = v*scale
|
||||
i = int(f+.5)
|
||||
if abs(f - i) > 0.0000000001:
|
||||
result.append(f)
|
||||
else:
|
||||
result.append(i)
|
||||
return result
|
||||
|
||||
def pfp1(choice_path):
|
||||
optimiser_params = tree1.parameters_for_path(choice_path)
|
||||
scale = 3**len(choice_path) * 2
|
||||
return scaleup(optimiser_params, [scale, scale])
|
||||
|
||||
# scale is 1/6
|
||||
tc.assertEqual(pfp1([0]), [1, 1])
|
||||
tc.assertEqual(pfp1([1]), [1, 3])
|
||||
tc.assertEqual(pfp1([3]), [3, 1])
|
||||
tc.assertEqual(pfp1([8]), [5, 5])
|
||||
# scale is 1/18
|
||||
tc.assertEqual(pfp1([0, 0]), [1, 1])
|
||||
tc.assertEqual(pfp1([0, 1]), [1, 3])
|
||||
tc.assertEqual(pfp1([3, 1]), [7, 3])
|
||||
tc.assertEqual(pfp1([3, 4]), [9, 3])
|
||||
|
||||
def pfp2(choice_path):
|
||||
optimiser_params = tree2.parameters_for_path(choice_path)
|
||||
scale1 = 2**len(choice_path) * 2
|
||||
scale2 = 4**len(choice_path) * 2
|
||||
return scaleup(optimiser_params, [scale1, scale2])
|
||||
|
||||
# scale is 1/4, 1/8
|
||||
tc.assertEqual(pfp2([0]), [1, 1])
|
||||
tc.assertEqual(pfp2([1]), [1, 3])
|
||||
tc.assertEqual(pfp2([2]), [1, 5])
|
||||
tc.assertEqual(pfp2([3]), [1, 7])
|
||||
tc.assertEqual(pfp2([4]), [3, 1])
|
||||
tc.assertEqual(pfp2([5]), [3, 3])
|
||||
# scale is 1/8, 1/32
|
||||
tc.assertEqual(pfp2([0, 0]), [1, 1])
|
||||
tc.assertEqual(pfp2([0, 1]), [1, 3])
|
||||
tc.assertEqual(pfp2([0, 2]), [1, 5])
|
||||
tc.assertEqual(pfp2([0, 3]), [1, 7])
|
||||
tc.assertEqual(pfp2([0, 4]), [3, 1])
|
||||
tc.assertEqual(pfp2([1, 0]), [1, 9])
|
||||
tc.assertEqual(pfp2([7, 7]), [7, 31])
|
||||
|
||||
|
||||
def test_play(tc):
|
||||
comp = mcts_tuners.Mcts_tuner('mctstest')
|
||||
comp.initialise_from_control_file(default_config())
|
||||
comp.set_clean_status()
|
||||
tree = comp.tree
|
||||
tc.assertEqual(comp.outstanding_simulations, {})
|
||||
|
||||
tc.assertEqual(tree.root.visits, 10)
|
||||
tc.assertEqual(tree.root.wins, 5)
|
||||
tc.assertEqual(sum(node.visits-10 for node in tree.root.children), 0)
|
||||
tc.assertEqual(sum(node.wins-5 for node in tree.root.children), 0)
|
||||
|
||||
job1 = comp.get_game()
|
||||
tc.assertIsInstance(job1, Game_job)
|
||||
tc.assertEqual(job1.game_id, '0')
|
||||
tc.assertEqual(job1.player_b.code, 'opp')
|
||||
tc.assertEqual(job1.player_w.code, '#0')
|
||||
tc.assertEqual(job1.board_size, 13)
|
||||
tc.assertEqual(job1.komi, 7.5)
|
||||
tc.assertEqual(job1.move_limit, 1000)
|
||||
tc.assertIs(job1.use_internal_scorer, False)
|
||||
tc.assertEqual(job1.internal_scorer_handicap_compensation, 'full')
|
||||
tc.assertEqual(job1.game_data, 0)
|
||||
tc.assertEqual(job1.sgf_event, 'mctstest')
|
||||
tc.assertRegexpMatches(job1.sgf_note, '^Candidate parameters: rsn@ ')
|
||||
tc.assertItemsEqual(comp.outstanding_simulations.keys(), [0])
|
||||
|
||||
job2 = comp.get_game()
|
||||
tc.assertIsInstance(job2, Game_job)
|
||||
tc.assertEqual(job2.game_id, '1')
|
||||
tc.assertEqual(job2.player_b.code, 'opp')
|
||||
tc.assertEqual(job2.player_w.code, '#1')
|
||||
tc.assertItemsEqual(comp.outstanding_simulations.keys(), [0, 1])
|
||||
|
||||
result1 = Game_result({'b' : 'opp', 'w' : '#1'}, 'w')
|
||||
result1.sgf_result = "W+8.5"
|
||||
response1 = Game_job_result()
|
||||
response1.game_id = job1.game_id
|
||||
response1.game_result = result1
|
||||
response1.engine_names = {
|
||||
'opp' : 'opp engine:v1.2.3',
|
||||
'#0' : 'candidate engine',
|
||||
}
|
||||
response1.engine_descriptions = {
|
||||
'opp' : 'opp engine:v1.2.3',
|
||||
'#0' : 'candidate engine description',
|
||||
}
|
||||
response1.game_data = job1.game_data
|
||||
comp.process_game_result(response1)
|
||||
tc.assertItemsEqual(comp.outstanding_simulations.keys(), [1])
|
||||
|
||||
tc.assertEqual(tree.root.visits, 11)
|
||||
tc.assertEqual(tree.root.wins, 6)
|
||||
tc.assertEqual(sum(node.visits-10 for node in tree.root.children), 1)
|
||||
tc.assertEqual(sum(node.wins-5 for node in tree.root.children), 1)
|
||||
|
||||
comp2 = mcts_tuners.Mcts_tuner('mctstest')
|
||||
comp2.initialise_from_control_file(default_config())
|
||||
status = pickle.loads(pickle.dumps(comp.get_status()))
|
||||
comp2.set_status(status)
|
||||
tc.assertEqual(comp2.tree.root.visits, 11)
|
||||
tc.assertEqual(comp2.tree.root.wins, 6)
|
||||
tc.assertEqual(sum(node.visits-10 for node in comp2.tree.root.children), 1)
|
||||
tc.assertEqual(sum(node.wins-5 for node in comp2.tree.root.children), 1)
|
||||
|
||||
config3 = default_config()
|
||||
# changed split
|
||||
config3['parameters'][0] = Parameter_config(
|
||||
'resign_at',
|
||||
scale = float,
|
||||
split = 11,
|
||||
format = "rsn@ %.2f")
|
||||
comp3 = mcts_tuners.Mcts_tuner('mctstest')
|
||||
comp3.initialise_from_control_file(config3)
|
||||
status = pickle.loads(pickle.dumps(comp.get_status()))
|
||||
with tc.assertRaises(CompetitionError) as ar:
|
||||
comp3.set_status(status)
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"status file is inconsistent with control file")
|
||||
|
||||
config4 = default_config()
|
||||
# changed upper bound
|
||||
config4['parameters'][1] = Parameter_config(
|
||||
'initial_wins',
|
||||
scale = mcts_tuners.LINEAR(0, 200),
|
||||
split = 10,
|
||||
format = "iwins %d")
|
||||
comp4 = mcts_tuners.Mcts_tuner('mctstest')
|
||||
comp4.initialise_from_control_file(config4)
|
||||
status = pickle.loads(pickle.dumps(comp.get_status()))
|
||||
with tc.assertRaises(CompetitionError) as ar:
|
||||
comp4.set_status(status)
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"status file is inconsistent with control file")
|
||||
|
||||
|
||||
def _disabled_test_tree_run(tc):
|
||||
# Something like this test can be useful when changing the tree code,
|
||||
# if you want to verify that you're not changing behaviour.
|
||||
|
||||
tree = mcts_tuners.Tree(
|
||||
splits=[2, 3],
|
||||
max_depth=5,
|
||||
exploration_coefficient=0.5,
|
||||
initial_visits=10,
|
||||
initial_wins=5,
|
||||
parameter_formatter=str,
|
||||
)
|
||||
|
||||
tree.new_root()
|
||||
random.seed(12345)
|
||||
for i in range(1100):
|
||||
simulation = mcts_tuners.Simulation(tree)
|
||||
simulation.run()
|
||||
simulation.update_stats(candidate_won=random.randrange(2))
|
||||
tc.assertEqual(simulation.get_parameters(),
|
||||
[0.0625, 0.42592592592592593])
|
||||
tc.assertEqual(tree.node_count, 1597)
|
||||
tc.assertEqual(simulation.choice_path, [1, 0, 2])
|
||||
tc.assertEqual(tree.retrieve_best_parameters(),
|
||||
[0.609375, 0.68930041152263366])
|
||||
|
570
gomill/gomill_tests/playoff_tests.py
Normal file
570
gomill/gomill_tests/playoff_tests.py
Normal file
@ -0,0 +1,570 @@
|
||||
"""Tests for playoffs.py"""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from textwrap import dedent
|
||||
import cPickle as pickle
|
||||
|
||||
from gomill import competitions
|
||||
from gomill import playoffs
|
||||
from gomill.gtp_games import Game_result
|
||||
from gomill.game_jobs import Game_job, Game_job_result
|
||||
from gomill.competitions import (
|
||||
Player_config, NoGameAvailable, CompetitionError, ControlFileError)
|
||||
from gomill.playoffs import Matchup_config
|
||||
|
||||
from gomill_tests import competition_test_support
|
||||
from gomill_tests import gomill_test_support
|
||||
from gomill_tests import test_framework
|
||||
from gomill_tests.competition_test_support import (
|
||||
fake_response, check_screen_report)
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
def check_short_report(tc, comp, expected_matchups, expected_players,
|
||||
competition_name="testcomp"):
|
||||
"""Check that a playoff's short report is as expected."""
|
||||
expected = ("playoff: %s\n\n%s\n%s\n" %
|
||||
(competition_name, expected_matchups, expected_players))
|
||||
tc.assertMultiLineEqual(competition_test_support.get_short_report(comp),
|
||||
expected)
|
||||
|
||||
expected_fake_players = dedent("""\
|
||||
player t1: t1 engine
|
||||
testdescription
|
||||
player t2: t2 engine:v1.2.3
|
||||
""")
|
||||
|
||||
|
||||
class Playoff_fixture(test_framework.Fixture):
|
||||
"""Fixture setting up a Playoff.
|
||||
|
||||
attributes:
|
||||
comp -- Playoff
|
||||
|
||||
"""
|
||||
def __init__(self, tc, config=None):
|
||||
if config is None:
|
||||
config = default_config()
|
||||
self.tc = tc
|
||||
self.comp = playoffs.Playoff('testcomp')
|
||||
self.comp.initialise_from_control_file(config)
|
||||
self.comp.set_clean_status()
|
||||
|
||||
def check_screen_report(self, expected):
|
||||
"""Check that the screen report is as expected."""
|
||||
check_screen_report(self.tc, self.comp, expected)
|
||||
|
||||
def check_short_report(self, *args, **kwargs):
|
||||
"""Check that the short report is as expected."""
|
||||
check_short_report(self.tc, self.comp, *args, **kwargs)
|
||||
|
||||
|
||||
def default_config():
|
||||
return {
|
||||
'players' : {
|
||||
't1' : Player_config("test1"),
|
||||
't2' : Player_config("test2"),
|
||||
},
|
||||
'board_size' : 13,
|
||||
'komi' : 7.5,
|
||||
'matchups' : [
|
||||
Matchup_config('t1', 't2', alternating=True),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_basic_config(tc):
|
||||
comp = playoffs.Playoff('test')
|
||||
config = default_config()
|
||||
config['description'] = "default\nconfig"
|
||||
config['matchups'] = [
|
||||
Matchup_config(
|
||||
't1', 't2', board_size=9, komi=0.5, alternating=True,
|
||||
handicap=6, handicap_style='free',
|
||||
move_limit=50,
|
||||
scorer="internal", internal_scorer_handicap_compensation='no',
|
||||
number_of_games=20),
|
||||
Matchup_config('t2', 't1', id='m1'),
|
||||
Matchup_config('t1', 't2'),
|
||||
]
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertEqual(comp.description, "default\nconfig")
|
||||
|
||||
comp.set_clean_status()
|
||||
tr = comp.get_tournament_results()
|
||||
m0 = tr.get_matchup('0')
|
||||
m1 = tr.get_matchup('m1')
|
||||
m2 = tr.get_matchup('2')
|
||||
|
||||
tc.assertListEqual(tr.get_matchup_ids(), ['0', 'm1', '2'])
|
||||
tc.assertDictEqual(tr.get_matchups(), {'0' : m0, 'm1' : m1, '2' : m2})
|
||||
|
||||
tc.assertEqual(m0.player_1, 't1')
|
||||
tc.assertEqual(m0.player_2, 't2')
|
||||
tc.assertEqual(m0.board_size, 9)
|
||||
tc.assertEqual(m0.komi, 0.5)
|
||||
tc.assertIs(m0.alternating, True)
|
||||
tc.assertEqual(m0.handicap, 6)
|
||||
tc.assertEqual(m0.handicap_style, 'free')
|
||||
tc.assertEqual(m0.move_limit, 50)
|
||||
tc.assertEqual(m0.scorer, 'internal')
|
||||
tc.assertEqual(m0.internal_scorer_handicap_compensation, 'no')
|
||||
tc.assertEqual(m0.number_of_games, 20)
|
||||
|
||||
tc.assertEqual(m1.player_1, 't2')
|
||||
tc.assertEqual(m1.player_2, 't1')
|
||||
tc.assertEqual(m1.board_size, 13)
|
||||
tc.assertEqual(m1.komi, 7.5)
|
||||
tc.assertIs(m1.alternating, False)
|
||||
tc.assertEqual(m1.handicap, None)
|
||||
tc.assertEqual(m1.handicap_style, 'fixed')
|
||||
tc.assertEqual(m1.move_limit, 1000)
|
||||
tc.assertEqual(m1.scorer, 'players')
|
||||
tc.assertEqual(m1.internal_scorer_handicap_compensation, 'full')
|
||||
tc.assertEqual(m1.number_of_games, None)
|
||||
|
||||
def test_nonsense_matchup_config(tc):
|
||||
comp = playoffs.Playoff('test')
|
||||
config = default_config()
|
||||
config['matchups'].append(99)
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertMultiLineEqual(str(ar.exception), dedent("""\
|
||||
'matchups': item 1: not a Matchup"""))
|
||||
|
||||
def test_bad_matchup_config(tc):
|
||||
comp = playoffs.Playoff('test')
|
||||
config = default_config()
|
||||
config['matchups'].append(Matchup_config())
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertMultiLineEqual(str(ar.exception), dedent("""\
|
||||
matchup 1: not enough arguments"""))
|
||||
|
||||
def test_bad_matchup_config_bad_id(tc):
|
||||
comp = playoffs.Playoff('test')
|
||||
config = default_config()
|
||||
config['matchups'].append(Matchup_config('t1', 't2', id=99))
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertMultiLineEqual(str(ar.exception), dedent("""\
|
||||
matchup 1: 'id': not a string"""))
|
||||
|
||||
def test_bad_matchup_config_bad_setting(tc):
|
||||
comp = playoffs.Playoff('test')
|
||||
config = default_config()
|
||||
config['matchups'].append(Matchup_config('t1', 't2', board_size="X"))
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertMultiLineEqual(str(ar.exception), dedent("""\
|
||||
matchup 1: 'board_size': invalid integer"""))
|
||||
|
||||
def test_bad_matchup_config_unknown_player(tc):
|
||||
comp = playoffs.Playoff('test')
|
||||
config = default_config()
|
||||
config['matchups'].append(Matchup_config('t1', 'nonex'))
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertMultiLineEqual(str(ar.exception), dedent("""\
|
||||
matchup 1: unknown player nonex"""))
|
||||
|
||||
def test_bad_matchup_config_no_board_size(tc):
|
||||
comp = playoffs.Playoff('test')
|
||||
config = default_config()
|
||||
del config['board_size']
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertMultiLineEqual(str(ar.exception), dedent("""\
|
||||
matchup 0: 'board_size' not specified"""))
|
||||
|
||||
def test_bad_matchup_config_bad_handicap(tc):
|
||||
comp = playoffs.Playoff('test')
|
||||
config = default_config()
|
||||
config['matchups'].append(Matchup_config('t1', 't2', handicap=10))
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertMultiLineEqual(str(ar.exception), dedent("""\
|
||||
matchup 1: fixed handicap out of range for board size 13"""))
|
||||
|
||||
def test_matchup_config_board_size_in_matchup_only(tc):
|
||||
comp = playoffs.Playoff('test')
|
||||
config = default_config()
|
||||
del config['board_size']
|
||||
config['matchups'] = [Matchup_config('t1', 't2', alternating=True,
|
||||
board_size=9)]
|
||||
comp.initialise_from_control_file(config)
|
||||
comp.set_clean_status()
|
||||
tr = comp.get_tournament_results()
|
||||
m0 = tr.get_matchup('0')
|
||||
tc.assertEqual(m0.board_size, 9)
|
||||
|
||||
def test_matchup_name(tc):
|
||||
comp = playoffs.Playoff('test')
|
||||
config = default_config()
|
||||
config['matchups'] = [Matchup_config('t1', 't2', name="asd")]
|
||||
comp.initialise_from_control_file(config)
|
||||
comp.set_clean_status()
|
||||
tr = comp.get_tournament_results()
|
||||
m0 = tr.get_matchup('0')
|
||||
tc.assertEqual(m0.name, "asd")
|
||||
|
||||
def test_global_handicap_validation(tc):
|
||||
comp = playoffs.Playoff('testcomp')
|
||||
config = default_config()
|
||||
config['board_size'] = 12
|
||||
config['handicap'] = 6
|
||||
with tc.assertRaises(ControlFileError) as ar:
|
||||
comp.initialise_from_control_file(config)
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"default fixed handicap out of range for board size 12")
|
||||
|
||||
|
||||
def test_game_id_format(tc):
|
||||
config = default_config()
|
||||
config['matchups'][0] = Matchup_config('t1', 't2', number_of_games=1000)
|
||||
fx = Playoff_fixture(tc, config)
|
||||
tc.assertEqual(fx.comp.get_game().game_id, '0_000')
|
||||
|
||||
def test_get_player_checks(tc):
|
||||
comp = playoffs.Playoff('testcomp')
|
||||
config = default_config()
|
||||
config['players']['t3'] = Player_config("test3")
|
||||
config['matchups'].append(
|
||||
Matchup_config('t1', 't3', number_of_games=0),
|
||||
),
|
||||
comp.initialise_from_control_file(config)
|
||||
checks = comp.get_player_checks()
|
||||
tc.assertEqual(len(checks), 2)
|
||||
tc.assertEqual(checks[0].board_size, 13)
|
||||
tc.assertEqual(checks[0].komi, 7.5)
|
||||
tc.assertEqual(checks[0].player.code, "t1")
|
||||
tc.assertEqual(checks[0].player.cmd_args, ['test1'])
|
||||
tc.assertEqual(checks[1].player.code, "t2")
|
||||
tc.assertEqual(checks[1].player.cmd_args, ['test2'])
|
||||
|
||||
def test_play(tc):
|
||||
fx = Playoff_fixture(tc)
|
||||
tc.assertIsNone(fx.comp.description)
|
||||
|
||||
job1 = fx.comp.get_game()
|
||||
tc.assertIsInstance(job1, Game_job)
|
||||
tc.assertEqual(job1.game_id, '0_0')
|
||||
tc.assertEqual(job1.player_b.code, 't1')
|
||||
tc.assertEqual(job1.player_w.code, 't2')
|
||||
tc.assertEqual(job1.board_size, 13)
|
||||
tc.assertEqual(job1.komi, 7.5)
|
||||
tc.assertEqual(job1.move_limit, 1000)
|
||||
tc.assertIs(job1.use_internal_scorer, False)
|
||||
tc.assertEqual(job1.internal_scorer_handicap_compensation, 'full')
|
||||
tc.assertEqual(job1.game_data, ('0', 0))
|
||||
tc.assertIsNone(job1.sgf_filename)
|
||||
tc.assertIsNone(job1.sgf_dirname)
|
||||
tc.assertIsNone(job1.void_sgf_dirname)
|
||||
tc.assertEqual(job1.sgf_event, 'testcomp')
|
||||
tc.assertIsNone(job1.gtp_log_pathname)
|
||||
|
||||
job2 = fx.comp.get_game()
|
||||
tc.assertIsInstance(job2, Game_job)
|
||||
tc.assertEqual(job2.game_id, '0_1')
|
||||
tc.assertEqual(job2.player_b.code, 't2')
|
||||
tc.assertEqual(job2.player_w.code, 't1')
|
||||
|
||||
result1 = Game_result({'b' : 't1', 'w' : 't2'}, 'b')
|
||||
result1.sgf_result = "B+8.5"
|
||||
response1 = Game_job_result()
|
||||
response1.game_id = job1.game_id
|
||||
response1.game_result = result1
|
||||
response1.engine_names = {
|
||||
't1' : 't1 engine:v1.2.3',
|
||||
't2' : 't2 engine',
|
||||
}
|
||||
response1.engine_descriptions = {
|
||||
't1' : 't1 engine:v1.2.3',
|
||||
't2' : 't2 engine\ntest \xc2\xa3description',
|
||||
}
|
||||
response1.game_data = job1.game_data
|
||||
fx.comp.process_game_result(response1)
|
||||
|
||||
expected_report = dedent("""\
|
||||
t1 v t2 (1 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins
|
||||
t1 1 100.00% (black)
|
||||
t2 0 0.00% (white)
|
||||
""")
|
||||
expected_players = dedent("""\
|
||||
player t1: t1 engine:v1.2.3
|
||||
player t2: t2 engine
|
||||
test \xc2\xa3description
|
||||
""")
|
||||
fx.check_screen_report(expected_report)
|
||||
fx.check_short_report(expected_report, expected_players)
|
||||
|
||||
tc.assertListEqual(
|
||||
fx.comp.get_tournament_results().get_matchup_results('0'), [result1])
|
||||
|
||||
def test_play_many(tc):
|
||||
fx = Playoff_fixture(tc)
|
||||
|
||||
jobs = [fx.comp.get_game() for _ in range(8)]
|
||||
for i in [0, 3]:
|
||||
response = fake_response(jobs[i], 'b')
|
||||
fx.comp.process_game_result(response)
|
||||
jobs += [fx.comp.get_game() for _ in range(3)]
|
||||
for i in [4, 2, 6, 7]:
|
||||
response = fake_response(jobs[i], 'w')
|
||||
fx.comp.process_game_result(response)
|
||||
|
||||
fx.check_screen_report(dedent("""\
|
||||
t1 v t2 (6 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins black white
|
||||
t1 2 33.33% 1 25.00% 1 50.00%
|
||||
t2 4 66.67% 1 50.00% 3 75.00%
|
||||
2 33.33% 4 66.67%
|
||||
"""))
|
||||
|
||||
tc.assertEqual(
|
||||
len(fx.comp.get_tournament_results().get_matchup_results('0')), 6)
|
||||
|
||||
#tc.assertEqual(fx.comp.scheduler.allocators['0'].issued, 11)
|
||||
#tc.assertEqual(fx.comp.scheduler.allocators['0'].fixed, 6)
|
||||
|
||||
comp2 = competition_test_support.check_round_trip(
|
||||
tc, fx.comp, default_config())
|
||||
|
||||
#tc.assertEqual(comp2.scheduler.allocators['0'].issued, 6)
|
||||
#tc.assertEqual(comp2.scheduler.allocators['0'].fixed, 6)
|
||||
|
||||
jobs2 = [comp2.get_game() for _ in range(4)]
|
||||
tc.assertListEqual([job.game_id for job in jobs2],
|
||||
['0_1', '0_5', '0_8', '0_9'])
|
||||
tr = comp2.get_tournament_results()
|
||||
tc.assertEqual(len(tr.get_matchup_results('0')), 6)
|
||||
ms = tr.get_matchup_stats('0')
|
||||
tc.assertEqual(ms.total, 6)
|
||||
tc.assertEqual(ms.wins_1, 2)
|
||||
tc.assertEqual(ms.wins_b, 2)
|
||||
|
||||
def test_jigo_reporting(tc):
|
||||
fx = Playoff_fixture(tc)
|
||||
|
||||
def winner(i):
|
||||
if i in (0, 3):
|
||||
return 'b'
|
||||
elif i in (2, 4):
|
||||
return 'w'
|
||||
else:
|
||||
return None
|
||||
jobs = [fx.comp.get_game() for _ in range(8)]
|
||||
for i in range(6):
|
||||
response = fake_response(jobs[i], winner(i))
|
||||
fx.comp.process_game_result(response)
|
||||
fx.check_screen_report(dedent("""\
|
||||
t1 v t2 (6 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins black white
|
||||
t1 2 33.33% 2 66.67% 1 33.33%
|
||||
t2 4 66.67% 2 66.67% 3 100.00%
|
||||
3 50.00% 3 50.00%
|
||||
"""))
|
||||
|
||||
response = fake_response(jobs[6], None)
|
||||
fx.comp.process_game_result(response)
|
||||
fx.check_screen_report(dedent("""\
|
||||
t1 v t2 (7 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins black white
|
||||
t1 2.5 35.71% 2.5 62.50% 1.5 50.00%
|
||||
t2 4.5 64.29% 2.5 83.33% 3.5 87.50%
|
||||
3.5 50.00% 3.5 50.00%
|
||||
"""))
|
||||
|
||||
def test_unknown_result_reporting(tc):
|
||||
fx = Playoff_fixture(tc)
|
||||
|
||||
def winner(i):
|
||||
if i in (0, 3):
|
||||
return 'b'
|
||||
elif i in (2, 4):
|
||||
return 'w'
|
||||
else:
|
||||
return 'unknown'
|
||||
jobs = [fx.comp.get_game() for _ in range(8)]
|
||||
for i in range(6):
|
||||
response = fake_response(jobs[i], winner(i))
|
||||
fx.comp.process_game_result(response)
|
||||
fx.check_screen_report(dedent("""\
|
||||
t1 v t2 (6 games)
|
||||
unknown results: 2 33.33%
|
||||
board size: 13 komi: 7.5
|
||||
wins black white
|
||||
t1 1 16.67% 1 33.33% 0 0.00%
|
||||
t2 3 50.00% 1 33.33% 2 66.67%
|
||||
2 33.33% 2 33.33%
|
||||
"""))
|
||||
|
||||
def test_self_play(tc):
|
||||
config = default_config()
|
||||
config['matchups'] = [
|
||||
Matchup_config('t1', 't1', alternating=True),
|
||||
Matchup_config('t1', 't1', alternating=False),
|
||||
]
|
||||
fx = Playoff_fixture(tc, config)
|
||||
|
||||
jobs = [fx.comp.get_game() for _ in range(20)]
|
||||
for i, job in enumerate(jobs):
|
||||
response = fake_response(job, 'b' if i < 9 else 'w')
|
||||
fx.comp.process_game_result(response)
|
||||
fx.check_screen_report(dedent("""\
|
||||
t1 v t1#2 (10 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins black white
|
||||
t1 6 60.00% 3 60.00% 3 60.00%
|
||||
t1#2 4 40.00% 2 40.00% 2 40.00%
|
||||
5 50.00% 5 50.00%
|
||||
|
||||
t1 v t1#2 (10 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins
|
||||
t1 4 40.00% (black)
|
||||
t1#2 6 60.00% (white)
|
||||
"""))
|
||||
|
||||
competition_test_support.check_round_trip(tc, fx.comp, config)
|
||||
|
||||
|
||||
def test_bad_state(tc):
|
||||
fx = Playoff_fixture(tc)
|
||||
bad_status = fx.comp.get_status()
|
||||
del bad_status['scheduler']
|
||||
|
||||
comp2 = playoffs.Playoff('testcomp')
|
||||
comp2.initialise_from_control_file(default_config())
|
||||
tc.assertRaises(KeyError, comp2.set_status, bad_status)
|
||||
|
||||
def test_matchup_change(tc):
|
||||
fx = Playoff_fixture(tc)
|
||||
|
||||
jobs = [fx.comp.get_game() for _ in range(8)]
|
||||
for i in [0, 2, 3, 4, 6, 7]:
|
||||
response = fake_response(jobs[i], ('b' if i in (0, 3) else 'w'))
|
||||
fx.comp.process_game_result(response)
|
||||
|
||||
fx.check_screen_report(dedent("""\
|
||||
t1 v t2 (6 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins black white
|
||||
t1 2 33.33% 1 25.00% 1 50.00%
|
||||
t2 4 66.67% 1 50.00% 3 75.00%
|
||||
2 33.33% 4 66.67%
|
||||
"""))
|
||||
|
||||
config2 = default_config()
|
||||
config2['players']['t3'] = Player_config("test3")
|
||||
config2['matchups'][0] = Matchup_config('t1', 't3', alternating=True)
|
||||
comp2 = playoffs.Playoff('testcomp')
|
||||
comp2.initialise_from_control_file(config2)
|
||||
|
||||
status = pickle.loads(pickle.dumps(fx.comp.get_status()))
|
||||
with tc.assertRaises(CompetitionError) as ar:
|
||||
comp2.set_status(status)
|
||||
tc.assertEqual(
|
||||
str(ar.exception),
|
||||
"existing results for matchup 0 are inconsistent with control file:\n"
|
||||
"result players are t1,t2;\n"
|
||||
"control file players are t1,t3")
|
||||
|
||||
def test_matchup_reappearance(tc):
|
||||
# Test that if a matchup is removed and added again, we remember the game
|
||||
# number. Test that we report the 'ghost' matchup in the short report (but
|
||||
# not the screen report).
|
||||
config1 = default_config()
|
||||
config1['matchups'].append(Matchup_config('t2', 't1'))
|
||||
config2 = default_config()
|
||||
config3 = default_config()
|
||||
config3['matchups'].append(Matchup_config('t2', 't1'))
|
||||
|
||||
comp1 = playoffs.Playoff('testcomp')
|
||||
comp1.initialise_from_control_file(config1)
|
||||
comp1.set_clean_status()
|
||||
jobs1 = [comp1.get_game() for _ in range(8)]
|
||||
for job in jobs1:
|
||||
comp1.process_game_result(fake_response(job, 'b'))
|
||||
tc.assertListEqual(
|
||||
[job.game_id for job in jobs1],
|
||||
['0_0', '1_0', '0_1', '1_1', '0_2', '1_2', '0_3', '1_3'])
|
||||
expected_matchups_1 = dedent("""\
|
||||
t1 v t2 (4 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins black white
|
||||
t1 2 50.00% 2 100.00% 0 0.00%
|
||||
t2 2 50.00% 2 100.00% 0 0.00%
|
||||
4 100.00% 0 0.00%
|
||||
|
||||
t2 v t1 (4 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins
|
||||
t2 4 100.00% (black)
|
||||
t1 0 0.00% (white)
|
||||
""")
|
||||
check_screen_report(tc, comp1, expected_matchups_1)
|
||||
check_short_report(tc, comp1, expected_matchups_1, expected_fake_players)
|
||||
|
||||
comp2 = playoffs.Playoff('testcomp')
|
||||
comp2.initialise_from_control_file(config2)
|
||||
comp2.set_status(pickle.loads(pickle.dumps(comp1.get_status())))
|
||||
jobs2 = [comp2.get_game() for _ in range(4)]
|
||||
tc.assertListEqual(
|
||||
[job.game_id for job in jobs2],
|
||||
['0_4', '0_5', '0_6', '0_7'])
|
||||
for job in jobs2:
|
||||
comp2.process_game_result(fake_response(job, 'b'))
|
||||
expected_matchups_2 = dedent("""\
|
||||
t1 v t2 (8 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins black white
|
||||
t1 4 50.00% 4 100.00% 0 0.00%
|
||||
t2 4 50.00% 4 100.00% 0 0.00%
|
||||
8 100.00% 0 0.00%
|
||||
""")
|
||||
check_screen_report(tc, comp2, expected_matchups_2)
|
||||
expected_matchups_2b = dedent("""\
|
||||
t2 v t1 (4 games)
|
||||
?? (missing from control file)
|
||||
wins
|
||||
t2 4 100.00% (black)
|
||||
t1 0 0.00% (white)
|
||||
""")
|
||||
check_short_report(
|
||||
tc, comp2,
|
||||
expected_matchups_2 + "\n" + expected_matchups_2b,
|
||||
expected_fake_players)
|
||||
|
||||
comp3 = playoffs.Playoff('testcomp')
|
||||
comp3.initialise_from_control_file(config3)
|
||||
comp3.set_status(pickle.loads(pickle.dumps(comp2.get_status())))
|
||||
jobs3 = [comp3.get_game() for _ in range(8)]
|
||||
tc.assertListEqual(
|
||||
[job.game_id for job in jobs3],
|
||||
['1_4', '1_5', '1_6', '1_7', '0_8', '1_8', '0_9', '1_9'])
|
||||
expected_matchups_3 = dedent("""\
|
||||
t1 v t2 (8 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins black white
|
||||
t1 4 50.00% 4 100.00% 0 0.00%
|
||||
t2 4 50.00% 4 100.00% 0 0.00%
|
||||
8 100.00% 0 0.00%
|
||||
|
||||
t2 v t1 (4 games)
|
||||
board size: 13 komi: 7.5
|
||||
wins
|
||||
t2 4 100.00% (black)
|
||||
t1 0 0.00% (white)
|
||||
""")
|
||||
check_screen_report(tc, comp3, expected_matchups_3)
|
||||
check_short_report(tc, comp3, expected_matchups_3, expected_fake_players)
|
||||
|
93
gomill/gomill_tests/ringmaster_test_support.py
Normal file
93
gomill/gomill_tests/ringmaster_test_support.py
Normal file
@ -0,0 +1,93 @@
|
||||
"""Test support code for testing Ringmasters."""
|
||||
|
||||
from collections import defaultdict
|
||||
from cStringIO import StringIO
|
||||
|
||||
from gomill import ringmasters
|
||||
from gomill import ringmaster_presenters
|
||||
|
||||
class Test_presenter(ringmaster_presenters.Presenter):
|
||||
"""Presenter which stores the messages."""
|
||||
def __init__(self):
|
||||
ringmaster_presenters.Presenter.__init__(self)
|
||||
self.channels = defaultdict(list)
|
||||
|
||||
shows_warnings_only = False
|
||||
|
||||
def clear(self, channel):
|
||||
self.channels[channel] = []
|
||||
|
||||
def say(self, channel, s):
|
||||
self.channels[channel].append(s)
|
||||
|
||||
def refresh(self):
|
||||
pass
|
||||
|
||||
def recent_messages(self, channel):
|
||||
"""Retrieve messages sent since the channel was last cleared.
|
||||
|
||||
Returns a list of strings.
|
||||
|
||||
"""
|
||||
return self.channels[channel][:]
|
||||
|
||||
|
||||
class Testing_ringmaster(ringmasters.Ringmaster):
|
||||
"""Variant of ringmaster suitable for use in tests.
|
||||
|
||||
This doesn't read from or write to the filesystem.
|
||||
|
||||
(If you're testing run(), make sure record_games is False, and
|
||||
discard_stderr is True for each player.)
|
||||
|
||||
(Currently, write_status is made to do nothing, so it's not usefully
|
||||
testable.)
|
||||
|
||||
Instantiate with the control file contents as an 8-bit string.
|
||||
|
||||
It will act as if the control file had been loaded from
|
||||
/nonexistent/ctl/test.ctl.
|
||||
|
||||
You'll want to run set_display_mode('test') with this.
|
||||
|
||||
"""
|
||||
def __init__(self, control_file_contents):
|
||||
self._control_file_contents = control_file_contents
|
||||
self._test_status = None
|
||||
self._written_status = None
|
||||
ringmasters.Ringmaster.__init__(self, '/nonexistent/ctl/test.ctl')
|
||||
|
||||
_presenter_classes = {
|
||||
'test' : Test_presenter,
|
||||
}
|
||||
|
||||
def _open_files(self):
|
||||
self.logfile = StringIO()
|
||||
self.historyfile = StringIO()
|
||||
|
||||
def _close_files(self):
|
||||
# Don't want to close the StringIOs
|
||||
pass
|
||||
|
||||
def _read_control_file(self):
|
||||
return self._control_file_contents
|
||||
|
||||
def set_test_status(self, test_status):
|
||||
"""Specify the value that will be loaded from the state file.
|
||||
|
||||
test_status -- fake state file contents
|
||||
|
||||
test_status should be a pair (status_format_version, status dict)
|
||||
|
||||
"""
|
||||
self._test_status = test_status
|
||||
|
||||
def _load_status(self):
|
||||
return self._test_status
|
||||
|
||||
def status_file_exists(self):
|
||||
return (self._test_status is not None)
|
||||
|
||||
def _write_status(self, value):
|
||||
self._written_status = value
|
||||
|
520
gomill/gomill_tests/ringmaster_tests.py
Normal file
520
gomill/gomill_tests/ringmaster_tests.py
Normal file
@ -0,0 +1,520 @@
|
||||
"""Tests for ringmaster.py."""
|
||||
|
||||
import os
|
||||
import re
|
||||
from textwrap import dedent
|
||||
|
||||
from gomill_tests import test_framework
|
||||
from gomill_tests import gomill_test_support
|
||||
from gomill_tests import ringmaster_test_support
|
||||
from gomill_tests import gtp_engine_fixtures
|
||||
from gomill_tests.playoff_tests import fake_response
|
||||
|
||||
from gomill.ringmasters import RingmasterError
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
class Ringmaster_fixture(test_framework.Fixture):
|
||||
"""Fixture setting up a Ringmaster with mock suprocesses.
|
||||
|
||||
Instantiate with testcase, the text to be used as the contents of the
|
||||
control file, and a list of strings to be added (as a line each) to the end
|
||||
of the control file.
|
||||
|
||||
attributes:
|
||||
ringmaster -- Testing_ringmaster
|
||||
msf -- Mock_subprocess_fixture
|
||||
|
||||
See Mock_subprocess_gtp_channel for an explanation of how player command
|
||||
lines are interpreted.
|
||||
|
||||
"""
|
||||
def __init__(self, tc, control_file_contents, extra_lines=[]):
|
||||
self.ringmaster = ringmaster_test_support.Testing_ringmaster(
|
||||
control_file_contents + "\n".join(extra_lines))
|
||||
self.ringmaster.set_display_mode('test')
|
||||
self.msf = gtp_engine_fixtures.Mock_subprocess_fixture(tc)
|
||||
|
||||
def messages(self, channel):
|
||||
"""Return messages sent to the specified channel."""
|
||||
return self.ringmaster.presenter.recent_messages(channel)
|
||||
|
||||
def initialise_clean(self):
|
||||
"""Initialise the ringmaster (with clean status)."""
|
||||
self.ringmaster.set_clean_status()
|
||||
self.ringmaster._open_files()
|
||||
self.ringmaster._initialise_presenter()
|
||||
self.ringmaster._initialise_terminal_reader()
|
||||
|
||||
def initialise_with_state(self, ringmaster_status):
|
||||
"""Initialise the ringmaster with specified status."""
|
||||
self.ringmaster.set_test_status(ringmaster_status)
|
||||
self.ringmaster.load_status()
|
||||
self.ringmaster._open_files()
|
||||
self.ringmaster._initialise_presenter()
|
||||
self.ringmaster._initialise_terminal_reader()
|
||||
|
||||
def get_job(self):
|
||||
"""Initialise the ringmaster, and call get_job() once."""
|
||||
self.initialise_clean()
|
||||
return self.ringmaster.get_job()
|
||||
|
||||
def get_log(self):
|
||||
"""Retrieve the log file contents with timestamps scrubbed out."""
|
||||
s = self.ringmaster.logfile.getvalue()
|
||||
s = re.sub(r"(?<= at )([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2})",
|
||||
"***", s)
|
||||
return s
|
||||
|
||||
def get_history(self):
|
||||
"""Retrieve the history file contents."""
|
||||
return self.ringmaster.historyfile.getvalue()
|
||||
|
||||
def get_written_state(self):
|
||||
"""Return the unpickled value written to the state file."""
|
||||
return self.ringmaster._written_status
|
||||
|
||||
|
||||
playoff_ctl = """
|
||||
|
||||
competition_type = 'playoff'
|
||||
|
||||
description = 'gomill_tests playoff.'
|
||||
|
||||
players = {
|
||||
'p1' : Player('test'),
|
||||
'p2' : Player('test'),
|
||||
}
|
||||
|
||||
move_limit = 400
|
||||
record_games = False
|
||||
board_size = 9
|
||||
komi = 7.5
|
||||
scorer = 'internal'
|
||||
|
||||
number_of_games = 400
|
||||
|
||||
matchups = [
|
||||
Matchup('p1', 'p2'),
|
||||
]
|
||||
|
||||
"""
|
||||
|
||||
allplayall_ctl = """
|
||||
|
||||
competition_type = 'allplayall'
|
||||
|
||||
description = 'gomill_tests allplayall_ctl.'
|
||||
|
||||
players = {
|
||||
'p1' : Player('test'),
|
||||
'p2' : Player('test'),
|
||||
}
|
||||
|
||||
move_limit = 400
|
||||
record_games = False
|
||||
board_size = 9
|
||||
komi = 7.5
|
||||
scorer = 'internal'
|
||||
|
||||
rounds = 8
|
||||
|
||||
competitors = ['p1', 'p2']
|
||||
|
||||
"""
|
||||
|
||||
mcts_ctl = """
|
||||
|
||||
competition_type = 'mc_tuner'
|
||||
|
||||
description = 'gomill_tests mc_tuner.'
|
||||
|
||||
players = {
|
||||
'p1' : Player('test'),
|
||||
}
|
||||
|
||||
record_games = False
|
||||
board_size = 9
|
||||
komi = 7.5
|
||||
candidate_colour = 'w'
|
||||
opponent = 'p1'
|
||||
|
||||
exploration_coefficient = 0.45
|
||||
initial_visits = 10
|
||||
initial_wins = 5
|
||||
|
||||
parameters = [
|
||||
Parameter('foo',
|
||||
scale = LOG(0.01, 5.0),
|
||||
split = 8,
|
||||
format = 'I: %4.2f'),
|
||||
]
|
||||
|
||||
def make_candidate(foo):
|
||||
return Player('candidate')
|
||||
|
||||
"""
|
||||
|
||||
def test_get_job(tc):
|
||||
fx = Ringmaster_fixture(tc, playoff_ctl, [
|
||||
"players['p2'] = Player('test sing song')",
|
||||
])
|
||||
job = fx.get_job()
|
||||
tc.assertEqual(job.game_id, "0_000")
|
||||
tc.assertEqual(job.game_data, ("0", 0))
|
||||
tc.assertEqual(job.board_size, 9)
|
||||
tc.assertEqual(job.komi, 7.5)
|
||||
tc.assertEqual(job.move_limit, 400)
|
||||
tc.assertEqual(job.handicap, None)
|
||||
tc.assertIs(job.handicap_is_free, False)
|
||||
tc.assertIs(job.use_internal_scorer, True)
|
||||
tc.assertEqual(job.sgf_game_name, 'test 0_000')
|
||||
tc.assertEqual(job.sgf_event, 'test')
|
||||
tc.assertIsNone(job.gtp_log_pathname)
|
||||
tc.assertIsNone(job.sgf_filename)
|
||||
tc.assertIsNone(job.sgf_dirname)
|
||||
tc.assertIsNone(job.void_sgf_dirname)
|
||||
tc.assertEqual(job.player_b.code, 'p1')
|
||||
tc.assertEqual(job.player_w.code, 'p2')
|
||||
tc.assertEqual(job.player_b.cmd_args, ['test'])
|
||||
tc.assertEqual(job.player_w.cmd_args, ['test', 'sing', 'song'])
|
||||
tc.assertDictEqual(job.player_b.gtp_aliases, {})
|
||||
tc.assertListEqual(job.player_b.startup_gtp_commands, [])
|
||||
tc.assertEqual(job.stderr_pathname, "/nonexistent/ctl/test.log")
|
||||
tc.assertIsNone(job.player_b.cwd)
|
||||
tc.assertIsNone(job.player_b.environ)
|
||||
tc.assertEqual(fx.ringmaster.games_in_progress, {'0_000': job})
|
||||
tc.assertEqual(fx.get_log(),
|
||||
"starting game 0_000: p1 (b) vs p2 (w)\n")
|
||||
tc.assertEqual(fx.get_history(), "")
|
||||
|
||||
|
||||
def test_settings(tc):
|
||||
fx = Ringmaster_fixture(tc, playoff_ctl, [
|
||||
"handicap = 9",
|
||||
"handicap_style = 'free'",
|
||||
"record_games = True",
|
||||
"scorer = 'players'"
|
||||
])
|
||||
fx.ringmaster.enable_gtp_logging()
|
||||
job = fx.get_job()
|
||||
tc.assertEqual(job.game_id, "0_000")
|
||||
tc.assertEqual(job.handicap, 9)
|
||||
tc.assertIs(job.handicap_is_free, True)
|
||||
tc.assertIs(job.use_internal_scorer, False)
|
||||
tc.assertEqual(job.stderr_pathname, "/nonexistent/ctl/test.log")
|
||||
tc.assertEqual(job.gtp_log_pathname,
|
||||
'/nonexistent/ctl/test.gtplogs/0_000.log')
|
||||
tc.assertEqual(job.sgf_filename, '0_000.sgf')
|
||||
tc.assertEqual(job.sgf_dirname, '/nonexistent/ctl/test.games')
|
||||
tc.assertEqual(job.void_sgf_dirname, '/nonexistent/ctl/test.void')
|
||||
tc.assertEqual(fx.ringmaster.get_sgf_filename("0_000"), "0_000.sgf")
|
||||
tc.assertEqual(fx.ringmaster.get_sgf_pathname("0_000"),
|
||||
"/nonexistent/ctl/test.games/0_000.sgf")
|
||||
|
||||
def test_stderr_settings(tc):
|
||||
fx = Ringmaster_fixture(tc, playoff_ctl, [
|
||||
"players['p2'] = Player('test', discard_stderr=True)",
|
||||
])
|
||||
job = fx.get_job()
|
||||
tc.assertEqual(job.stderr_pathname, "/nonexistent/ctl/test.log")
|
||||
tc.assertIs(job.player_b.discard_stderr, False)
|
||||
tc.assertIs(job.player_w.discard_stderr, True)
|
||||
|
||||
def test_stderr_settings_nolog(tc):
|
||||
fx = Ringmaster_fixture(tc, playoff_ctl, [
|
||||
"players['p2'] = Player('test', discard_stderr=True)",
|
||||
"stderr_to_log = False",
|
||||
])
|
||||
job = fx.get_job()
|
||||
tc.assertIs(job.stderr_pathname, None)
|
||||
tc.assertIs(job.player_b.discard_stderr, False)
|
||||
tc.assertIs(job.player_w.discard_stderr, True)
|
||||
|
||||
|
||||
def test_get_tournament_results(tc):
|
||||
fx = Ringmaster_fixture(tc, playoff_ctl)
|
||||
tc.assertRaisesRegexp(RingmasterError, "^status is not loaded$",
|
||||
fx.ringmaster.get_tournament_results)
|
||||
fx.initialise_clean()
|
||||
tr = fx.ringmaster.get_tournament_results()
|
||||
tc.assertEqual(tr.get_matchup_ids(), ['0'])
|
||||
|
||||
fx2 = Ringmaster_fixture(tc, mcts_ctl)
|
||||
fx2.initialise_clean()
|
||||
tc.assertRaisesRegexp(RingmasterError, "^competition is not a tournament$",
|
||||
fx2.ringmaster.get_tournament_results)
|
||||
|
||||
def test_process_response(tc):
|
||||
fx = Ringmaster_fixture(tc, playoff_ctl)
|
||||
job = fx.get_job()
|
||||
tc.assertEqual(fx.ringmaster.games_in_progress, {'0_000': job})
|
||||
tc.assertEqual(
|
||||
fx.ringmaster.get_tournament_results().get_matchup_results('0'), [])
|
||||
response = fake_response(job, 'w')
|
||||
response.warnings = ['warningtest']
|
||||
response.log_entries = ['logtest']
|
||||
fx.ringmaster.process_response(response)
|
||||
tc.assertEqual(fx.ringmaster.games_in_progress, {})
|
||||
tc.assertListEqual(
|
||||
fx.messages('warnings'),
|
||||
["warningtest"])
|
||||
tc.assertListEqual(
|
||||
fx.messages('results'),
|
||||
["game 0_000: p2 beat p1 W+1.5"])
|
||||
tc.assertEqual(
|
||||
fx.ringmaster.get_tournament_results().get_matchup_results('0'),
|
||||
[response.game_result])
|
||||
tc.assertEqual(fx.get_log(),
|
||||
"starting game 0_000: p1 (b) vs p2 (w)\n"
|
||||
"response from game 0_000\n"
|
||||
"warningtest\n"
|
||||
"logtest\n")
|
||||
tc.assertEqual(fx.get_history(), "")
|
||||
|
||||
|
||||
def test_check_players(tc):
|
||||
fx = Ringmaster_fixture(tc, playoff_ctl)
|
||||
tc.assertTrue(fx.ringmaster.check_players(discard_stderr=True))
|
||||
|
||||
def test_run(tc):
|
||||
fx = Ringmaster_fixture(tc, playoff_ctl, [
|
||||
"players['p1'] = Player('test', discard_stderr=True)",
|
||||
"players['p2'] = Player('test', discard_stderr=True)",
|
||||
])
|
||||
fx.initialise_clean()
|
||||
fx.ringmaster.run(max_games=3)
|
||||
tc.assertListEqual(
|
||||
fx.messages('warnings'),
|
||||
[])
|
||||
tc.assertListEqual(
|
||||
fx.messages('screen_report'),
|
||||
["p1 v p2 (3/400 games)\n"
|
||||
"board size: 9 komi: 7.5\n"
|
||||
" wins\n"
|
||||
"p1 3 100.00% (black)\n"
|
||||
"p2 0 0.00% (white)"])
|
||||
tc.assertMultiLineEqual(
|
||||
fx.get_log(),
|
||||
"run started at *** with max_games 3\n"
|
||||
"starting game 0_000: p1 (b) vs p2 (w)\n"
|
||||
"response from game 0_000\n"
|
||||
"starting game 0_001: p1 (b) vs p2 (w)\n"
|
||||
"response from game 0_001\n"
|
||||
"starting game 0_002: p1 (b) vs p2 (w)\n"
|
||||
"response from game 0_002\n"
|
||||
"halting competition: max-games reached for this run\n"
|
||||
"run finished at ***\n"
|
||||
)
|
||||
tc.assertMultiLineEqual(
|
||||
fx.get_history(),
|
||||
" 0_000 p1 beat p2 B+10.5\n"
|
||||
" 0_001 p1 beat p2 B+10.5\n"
|
||||
" 0_002 p1 beat p2 B+10.5\n")
|
||||
|
||||
def test_run_allplayall(tc):
|
||||
fx = Ringmaster_fixture(tc, allplayall_ctl, [
|
||||
"players['p1'] = Player('test', discard_stderr=True)",
|
||||
"players['p2'] = Player('test', discard_stderr=True)",
|
||||
])
|
||||
fx.initialise_clean()
|
||||
fx.ringmaster.run(max_games=3)
|
||||
tc.assertListEqual(
|
||||
fx.messages('warnings'),
|
||||
[])
|
||||
tc.assertListEqual(
|
||||
fx.messages('screen_report'),
|
||||
[dedent("""\
|
||||
3/8 games played
|
||||
|
||||
A B
|
||||
A p1 2-1
|
||||
B p2 1-2""")])
|
||||
tc.assertMultiLineEqual(
|
||||
fx.get_log(),
|
||||
"run started at *** with max_games 3\n"
|
||||
"starting game AvB_0: p1 (b) vs p2 (w)\n"
|
||||
"response from game AvB_0\n"
|
||||
"starting game AvB_1: p2 (b) vs p1 (w)\n"
|
||||
"response from game AvB_1\n"
|
||||
"starting game AvB_2: p1 (b) vs p2 (w)\n"
|
||||
"response from game AvB_2\n"
|
||||
"halting competition: max-games reached for this run\n"
|
||||
"run finished at ***\n"
|
||||
)
|
||||
tc.assertMultiLineEqual(
|
||||
fx.get_history(),
|
||||
" AvB_0 p1 beat p2 B+10.5\n"
|
||||
" AvB_1 p2 beat p1 B+10.5\n"
|
||||
" AvB_2 p1 beat p2 B+10.5\n")
|
||||
|
||||
def test_check_players_fail(tc):
|
||||
fx = Ringmaster_fixture(tc, playoff_ctl, [
|
||||
"players['p2'] = Player('test fail=startup')"
|
||||
])
|
||||
tc.assertFalse(fx.ringmaster.check_players(discard_stderr=True))
|
||||
|
||||
def test_run_fail(tc):
|
||||
fx = Ringmaster_fixture(tc, playoff_ctl, [
|
||||
"players['p1'] = Player('test', discard_stderr=True)",
|
||||
"players['p2'] = Player('test fail=startup', discard_stderr=True)",
|
||||
])
|
||||
fx.initialise_clean()
|
||||
fx.ringmaster.run()
|
||||
tc.assertListEqual(
|
||||
fx.messages('warnings'),
|
||||
["game 0_000 -- aborting game due to error:\n"
|
||||
"error starting subprocess for player p2:\n"
|
||||
"exec forced to fail",
|
||||
"halting run due to void games"])
|
||||
tc.assertListEqual(
|
||||
fx.messages('screen_report'),
|
||||
["1 void games; see log file."])
|
||||
tc.assertMultiLineEqual(
|
||||
fx.get_log(),
|
||||
"run started at *** with max_games None\n"
|
||||
"starting game 0_000: p1 (b) vs p2 (w)\n"
|
||||
"game 0_000 -- aborting game due to error:\n"
|
||||
"error starting subprocess for player p2:\n"
|
||||
"exec forced to fail\n"
|
||||
"halting competition: too many void games\n"
|
||||
"run finished at ***\n")
|
||||
tc.assertMultiLineEqual(fx.get_history(), "")
|
||||
|
||||
def test_run_with_late_errors(tc):
|
||||
fx = Ringmaster_fixture(tc, playoff_ctl, [
|
||||
"players['p1'] = Player('test', discard_stderr=True)",
|
||||
"players['p2'] = Player('test init=fail_close', discard_stderr=True)",
|
||||
])
|
||||
def fail_close(channel):
|
||||
channel.fail_close = True
|
||||
fx.msf.register_init_callback('fail_close', fail_close)
|
||||
fx.initialise_clean()
|
||||
fx.ringmaster.run(max_games=2)
|
||||
tc.assertListEqual(fx.messages('warnings'), [])
|
||||
tc.assertMultiLineEqual(
|
||||
fx.get_log(),
|
||||
"run started at *** with max_games 2\n"
|
||||
"starting game 0_000: p1 (b) vs p2 (w)\n"
|
||||
"response from game 0_000\n"
|
||||
"error closing player p2:\n"
|
||||
"forced failure for close\n"
|
||||
"starting game 0_001: p1 (b) vs p2 (w)\n"
|
||||
"response from game 0_001\n"
|
||||
"error closing player p2:\n"
|
||||
"forced failure for close\n"
|
||||
"halting competition: max-games reached for this run\n"
|
||||
"run finished at ***\n")
|
||||
tc.assertMultiLineEqual(
|
||||
fx.get_history(),
|
||||
" 0_000 p1 beat p2 B+10.5\n"
|
||||
" 0_001 p1 beat p2 B+10.5\n")
|
||||
|
||||
def test_status_roundtrip(tc):
|
||||
fx1 = Ringmaster_fixture(tc, playoff_ctl, [
|
||||
"players['p1'] = Player('test', discard_stderr=True)",
|
||||
"players['p2'] = Player('test', discard_stderr=True)",
|
||||
])
|
||||
fx1.initialise_clean()
|
||||
fx1.ringmaster.run(max_games=2)
|
||||
tc.assertListEqual(
|
||||
fx1.messages('warnings'),
|
||||
[])
|
||||
state = fx1.get_written_state()
|
||||
|
||||
fx2 = Ringmaster_fixture(tc, playoff_ctl, [
|
||||
"players['p1'] = Player('test', discard_stderr=True)",
|
||||
"players['p2'] = Player('test', discard_stderr=True)",
|
||||
])
|
||||
fx2.initialise_with_state(state)
|
||||
fx2.ringmaster.run(max_games=1)
|
||||
tc.assertListEqual(
|
||||
fx2.messages('warnings'),
|
||||
[])
|
||||
tc.assertListEqual(
|
||||
fx2.messages('screen_report'),
|
||||
["p1 v p2 (3/400 games)\n"
|
||||
"board size: 9 komi: 7.5\n"
|
||||
" wins\n"
|
||||
"p1 3 100.00% (black)\n"
|
||||
"p2 0 0.00% (white)"])
|
||||
|
||||
def test_status(tc):
|
||||
# Construct suitable competition status
|
||||
fx1 = Ringmaster_fixture(tc, playoff_ctl, [
|
||||
"players['p1'] = Player('test', discard_stderr=True)",
|
||||
"players['p2'] = Player('test', discard_stderr=True)",
|
||||
])
|
||||
sfv = fx1.ringmaster.status_format_version
|
||||
fx1.initialise_clean()
|
||||
fx1.ringmaster.run(max_games=2)
|
||||
competition_status = fx1.ringmaster.competition.get_status()
|
||||
tc.assertListEqual(
|
||||
fx1.messages('warnings'),
|
||||
[])
|
||||
status = {
|
||||
'void_game_count' : 0,
|
||||
'comp_vn' : fx1.ringmaster.competition.status_format_version,
|
||||
'comp' : competition_status,
|
||||
}
|
||||
|
||||
fx = Ringmaster_fixture(tc, playoff_ctl, [
|
||||
"players['p1'] = Player('test', discard_stderr=True)",
|
||||
"players['p2'] = Player('test', discard_stderr=True)",
|
||||
])
|
||||
fx.initialise_with_state((sfv, status.copy()))
|
||||
fx.ringmaster.run(max_games=1)
|
||||
tc.assertListEqual(
|
||||
fx.messages('warnings'),
|
||||
[])
|
||||
tc.assertListEqual(
|
||||
fx.messages('screen_report'),
|
||||
["p1 v p2 (3/400 games)\n"
|
||||
"board size: 9 komi: 7.5\n"
|
||||
" wins\n"
|
||||
"p1 3 100.00% (black)\n"
|
||||
"p2 0 0.00% (white)"])
|
||||
|
||||
fx.ringmaster.set_test_status((-1, status.copy()))
|
||||
tc.assertRaisesRegexp(
|
||||
RingmasterError,
|
||||
"incompatible status file",
|
||||
fx.ringmaster.load_status)
|
||||
|
||||
bad_status = status.copy()
|
||||
del bad_status['void_game_count']
|
||||
fx.ringmaster.set_test_status((sfv, bad_status))
|
||||
tc.assertRaisesRegexp(
|
||||
RingmasterError,
|
||||
"incompatible status file: missing 'void_game_count'",
|
||||
fx.ringmaster.load_status)
|
||||
|
||||
bad_competition_status = competition_status.copy()
|
||||
del bad_competition_status['results']
|
||||
bad_status_2 = status.copy()
|
||||
bad_status_2['comp'] = bad_competition_status
|
||||
fx.ringmaster.set_test_status((sfv, bad_status_2))
|
||||
tc.assertRaisesRegexp(
|
||||
RingmasterError,
|
||||
"error loading competition state: missing 'results'",
|
||||
fx.ringmaster.load_status)
|
||||
|
||||
bad_competition_status_2 = competition_status.copy()
|
||||
bad_competition_status_2['scheduler'] = None
|
||||
bad_status_3 = status.copy()
|
||||
bad_status_3['comp'] = bad_competition_status_2
|
||||
fx.ringmaster.set_test_status((sfv, bad_status_3))
|
||||
tc.assertRaisesRegexp(
|
||||
RingmasterError,
|
||||
"error loading competition state:\n"
|
||||
"AttributeError: 'NoneType' object has no attribute 'set_groups'",
|
||||
fx.ringmaster.load_status)
|
||||
|
||||
bad_status_4 = status.copy()
|
||||
bad_status_4['comp_vn'] = -1
|
||||
fx.ringmaster.set_test_status((sfv, bad_status_4))
|
||||
tc.assertRaisesRegexp(
|
||||
RingmasterError,
|
||||
"incompatible status file",
|
||||
fx.ringmaster.load_status)
|
111
gomill/gomill_tests/run_gomill_testsuite.py
Normal file
111
gomill/gomill_tests/run_gomill_testsuite.py
Normal file
@ -0,0 +1,111 @@
|
||||
"""Construct and run the gomill testsuite."""
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
test_modules = [
|
||||
'utils_tests',
|
||||
'common_tests',
|
||||
'board_tests',
|
||||
'sgf_grammar_tests',
|
||||
'sgf_properties_tests',
|
||||
'sgf_tests',
|
||||
'sgf_moves_tests',
|
||||
'gtp_engine_tests',
|
||||
'gtp_state_tests',
|
||||
'gtp_controller_tests',
|
||||
'gtp_proxy_tests',
|
||||
'gtp_game_tests',
|
||||
'game_job_tests',
|
||||
'setting_tests',
|
||||
'competition_scheduler_tests',
|
||||
'competition_tests',
|
||||
'playoff_tests',
|
||||
'allplayall_tests',
|
||||
'mcts_tuner_tests',
|
||||
'cem_tuner_tests',
|
||||
'ringmaster_tests',
|
||||
]
|
||||
|
||||
def get_test_module(name):
|
||||
"""Import the specified gomill_tests module and return it."""
|
||||
dotted_name = "gomill_tests." + name
|
||||
__import__(dotted_name)
|
||||
return sys.modules[dotted_name]
|
||||
|
||||
def get_test_modules():
|
||||
"""Import all _tests modules in the specified order.
|
||||
|
||||
Returns a list of module objects.
|
||||
|
||||
"""
|
||||
return [get_test_module(name) for name in test_modules]
|
||||
|
||||
def run_testsuite(module_names, failfast, buffer):
|
||||
"""Run the gomill testsuite.
|
||||
|
||||
module_names -- names of modules from gomill_tests, or None for all
|
||||
failfast -- bool (stop at first failing test)
|
||||
buffer -- bool (show stderr/stdout only for failing tests)
|
||||
|
||||
Output is to stderr
|
||||
|
||||
"""
|
||||
try:
|
||||
# This gives 'catchbreak' behaviour
|
||||
unittest2.signals.installHandler()
|
||||
except Exception:
|
||||
pass
|
||||
if module_names is None:
|
||||
modules = get_test_modules()
|
||||
else:
|
||||
modules = [get_test_module(name) for name in module_names]
|
||||
suite = unittest2.TestSuite()
|
||||
for mdl in modules:
|
||||
mdl.make_tests(suite)
|
||||
runner = unittest2.TextTestRunner(failfast=failfast, buffer=buffer)
|
||||
runner.run(suite)
|
||||
|
||||
def run(argv):
|
||||
parser = OptionParser(usage="%prog [options] [module] ...")
|
||||
parser.add_option("-f", "--failfast", action="store_true",
|
||||
help="stop after first test")
|
||||
parser.add_option("-p", "--nobuffer", action="store_true",
|
||||
help="show stderr/stdout for successful tests")
|
||||
(options, args) = parser.parse_args(argv)
|
||||
if args:
|
||||
module_names = args
|
||||
for module_name in module_names:
|
||||
if module_name not in test_modules:
|
||||
parser.error("unknown module: %s" % module_name)
|
||||
else:
|
||||
module_names = None
|
||||
run_testsuite(module_names, options.failfast, not options.nobuffer)
|
||||
|
||||
def import_unittest():
|
||||
"""Import unittest2 into global scope.
|
||||
|
||||
Raises NameError if it isn't available.
|
||||
|
||||
Call this before using the functions in this module other than main().
|
||||
|
||||
"""
|
||||
global unittest2
|
||||
try:
|
||||
from gomill_tests.test_framework import unittest2
|
||||
except ImportError, e:
|
||||
if hasattr(e, 'unittest2_missing'):
|
||||
raise NameError("unittest2")
|
||||
raise
|
||||
|
||||
def main():
|
||||
try:
|
||||
import_unittest()
|
||||
except NameError:
|
||||
sys.exit("gomill_tests: requires either Python 2.7 or "
|
||||
"the 'unittest2' package")
|
||||
run(sys.argv[1:])
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
29
gomill/gomill_tests/setting_tests.py
Normal file
29
gomill/gomill_tests/setting_tests.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""Tests for settings.py"""
|
||||
|
||||
from gomill.settings import *
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
def test_interpret_shlex_sequence(tc):
|
||||
iss = interpret_shlex_sequence
|
||||
tc.assertEqual(iss("test"), ["test"])
|
||||
tc.assertEqual(iss("test "), ["test"])
|
||||
tc.assertEqual(iss("~test"), ["~test"])
|
||||
tc.assertEqual(iss("test foo bar"), ["test", "foo", "bar"])
|
||||
tc.assertEqual(iss("test 'foo bar'"), ["test", "foo bar"])
|
||||
tc.assertEqual(iss(u"test foo bar"), ["test", "foo", "bar"])
|
||||
tc.assertEqual(iss(["test"]), ["test"])
|
||||
tc.assertEqual(iss(["test", "foo", "bar"]), ["test", "foo", "bar"])
|
||||
tc.assertEqual(iss(["test", "foo bar"]), ["test", "foo bar"])
|
||||
tc.assertEqual(iss(("test", "foo", "bar")), ["test", "foo", "bar"])
|
||||
tc.assertRaisesRegexp(ValueError, "^empty$", iss, "")
|
||||
tc.assertRaisesRegexp(ValueError, "^not a string or a sequence$", iss, None)
|
||||
tc.assertRaisesRegexp(ValueError, "^element not a string$",
|
||||
iss, ["test", None])
|
||||
tc.assertRaisesRegexp(ValueError, "^element contains NUL$",
|
||||
iss, ["test", "fo\x00"])
|
||||
|
362
gomill/gomill_tests/sgf_grammar_tests.py
Normal file
362
gomill/gomill_tests/sgf_grammar_tests.py
Normal file
@ -0,0 +1,362 @@
|
||||
"""Tests for sgf_grammar.py."""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
|
||||
from gomill import sgf_grammar
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
def test_is_valid_property_identifier(tc):
|
||||
ivpi = sgf_grammar.is_valid_property_identifier
|
||||
tc.assertIs(ivpi("B"), True)
|
||||
tc.assertIs(ivpi("PB"), True)
|
||||
tc.assertIs(ivpi("ABCDEFGH"), True)
|
||||
tc.assertIs(ivpi("ABCDEFGHI"), False)
|
||||
tc.assertIs(ivpi(""), False)
|
||||
tc.assertIs(ivpi("b"), False)
|
||||
tc.assertIs(ivpi("Player"), False)
|
||||
tc.assertIs(ivpi("P2"), False)
|
||||
tc.assertIs(ivpi(" PB"), False)
|
||||
tc.assertIs(ivpi("PB "), False)
|
||||
tc.assertIs(ivpi("P B"), False)
|
||||
tc.assertIs(ivpi("PB\x00"), False)
|
||||
|
||||
def test_is_valid_property_value(tc):
|
||||
ivpv = sgf_grammar.is_valid_property_value
|
||||
tc.assertIs(ivpv(""), True)
|
||||
tc.assertIs(ivpv("hello world"), True)
|
||||
tc.assertIs(ivpv("hello\nworld"), True)
|
||||
tc.assertIs(ivpv("hello \x00 world"), True)
|
||||
tc.assertIs(ivpv("hello \xa3 world"), True)
|
||||
tc.assertIs(ivpv("hello \xc2\xa3 world"), True)
|
||||
tc.assertIs(ivpv("hello \\-) world"), True)
|
||||
tc.assertIs(ivpv("hello (;[) world"), True)
|
||||
tc.assertIs(ivpv("[hello world]"), False)
|
||||
tc.assertIs(ivpv("hello ] world"), False)
|
||||
tc.assertIs(ivpv("hello \\] world"), True)
|
||||
tc.assertIs(ivpv("hello world \\"), False)
|
||||
tc.assertIs(ivpv("hello world \\\\"), True)
|
||||
tc.assertIs(ivpv("x" * 70000), True)
|
||||
|
||||
def test_tokeniser(tc):
|
||||
tokenise = sgf_grammar.tokenise
|
||||
|
||||
tc.assertEqual(tokenise("(;B[ah][]C[a\xa3b])")[0],
|
||||
[('D', '('),
|
||||
('D', ';'),
|
||||
('I', 'B'),
|
||||
('V', 'ah'),
|
||||
('V', ''),
|
||||
('I', 'C'),
|
||||
('V', 'a\xa3b'),
|
||||
('D', ')')])
|
||||
|
||||
def check_complete(s, *args):
|
||||
tokens, tail_index = tokenise(s, *args)
|
||||
tc.assertEqual(tail_index, len(s))
|
||||
return len(tokens)
|
||||
|
||||
def check_incomplete(s, *args):
|
||||
tokens, tail_index = tokenise(s, *args)
|
||||
return len(tokens), tail_index
|
||||
|
||||
# check surrounding junk
|
||||
tc.assertEqual(check_complete(""), 0)
|
||||
tc.assertEqual(check_complete("junk (;B[ah])"), 5)
|
||||
tc.assertEqual(check_incomplete("junk"), (0, 0))
|
||||
tc.assertEqual(check_incomplete("junk (B[ah])"), (0, 0))
|
||||
tc.assertEqual(check_incomplete("(;B[ah]) junk"), (5, 8))
|
||||
|
||||
# check paren-balance count
|
||||
tc.assertEqual(check_incomplete("(; ))(([ag]B C[ah])"), (3, 4))
|
||||
tc.assertEqual(check_incomplete("(;( )) (;)"), (5, 6))
|
||||
tc.assertEqual(check_incomplete("(;(()())) (;)"), (9, 9))
|
||||
|
||||
# check start_position
|
||||
tc.assertEqual(check_complete("(; ))(;B[ah])", 4), 5)
|
||||
tc.assertEqual(check_complete("(; ))junk (;B[ah])", 4), 5)
|
||||
|
||||
tc.assertEqual(check_complete("(;XX[abc][def]KO[];B[bc])"), 11)
|
||||
tc.assertEqual(check_complete("( ;XX[abc][def]KO[];B[bc])"), 11)
|
||||
tc.assertEqual(check_complete("(; XX[abc][def]KO[];B[bc])"), 11)
|
||||
tc.assertEqual(check_complete("(;XX [abc][def]KO[];B[bc])"), 11)
|
||||
tc.assertEqual(check_complete("(;XX[abc] [def]KO[];B[bc])"), 11)
|
||||
tc.assertEqual(check_complete("(;XX[abc][def] KO[];B[bc])"), 11)
|
||||
tc.assertEqual(check_complete("(;XX[abc][def]KO [];B[bc])"), 11)
|
||||
tc.assertEqual(check_complete("(;XX[abc][def]KO[] ;B[bc])"), 11)
|
||||
tc.assertEqual(check_complete("(;XX[abc][def]KO[]; B[bc])"), 11)
|
||||
tc.assertEqual(check_complete("(;XX[abc][def]KO[];B [bc])"), 11)
|
||||
tc.assertEqual(check_complete("(;XX[abc][def]KO[];B[bc] )"), 11)
|
||||
|
||||
tc.assertEqual(check_complete("( ;\nB\t[ah]\f[ef]\v)"), 6)
|
||||
tc.assertEqual(check_complete("(;[Ran\xc2\xa3dom :\nstu@ff][ef]"), 4)
|
||||
tc.assertEqual(check_complete("(;[ah)])"), 4)
|
||||
|
||||
tc.assertEqual(check_incomplete("(;B[ag"), (3, 3))
|
||||
tc.assertEqual(check_incomplete("(;B[ag)"), (3, 3))
|
||||
tc.assertEqual(check_incomplete("(;AddBlack[ag])"), (3, 3))
|
||||
tc.assertEqual(check_incomplete("(;+B[ag])"), (2, 2))
|
||||
tc.assertEqual(check_incomplete("(;B+[ag])"), (3, 3))
|
||||
tc.assertEqual(check_incomplete("(;B[ag]+)"), (4, 7))
|
||||
|
||||
tc.assertEqual(check_complete(r"(;[ab \] cd][ef]"), 4)
|
||||
tc.assertEqual(check_complete(r"(;[ab \] cd\\][ef]"), 4)
|
||||
tc.assertEqual(check_complete(r"(;[ab \] cd\\\\][ef]"), 4)
|
||||
tc.assertEqual(check_complete(r"(;[ab \] \\\] cd][ef]"), 4)
|
||||
tc.assertEqual(check_incomplete(r"(;B[ag\])"), (3, 3))
|
||||
tc.assertEqual(check_incomplete(r"(;B[ag\\\])"), (3, 3))
|
||||
|
||||
def test_parser_structure(tc):
|
||||
parse_sgf_game = sgf_grammar.parse_sgf_game
|
||||
|
||||
def shape(s):
|
||||
coarse_game = parse_sgf_game(s)
|
||||
return len(coarse_game.sequence), len(coarse_game.children)
|
||||
|
||||
tc.assertEqual(shape("(;C[abc]KO[];B[bc])"), (2, 0))
|
||||
tc.assertEqual(shape("initial junk (;C[abc]KO[];B[bc])"), (2, 0))
|
||||
tc.assertEqual(shape("(;C[abc]KO[];B[bc]) final junk"), (2, 0))
|
||||
tc.assertEqual(shape("(;C[abc]KO[];B[bc]) (;B[ag])"), (2, 0))
|
||||
|
||||
tc.assertRaisesRegexp(ValueError, "no SGF data found",
|
||||
parse_sgf_game, r"")
|
||||
tc.assertRaisesRegexp(ValueError, "no SGF data found",
|
||||
parse_sgf_game, r"junk")
|
||||
tc.assertRaisesRegexp(ValueError, "no SGF data found",
|
||||
parse_sgf_game, r"()")
|
||||
tc.assertRaisesRegexp(ValueError, "no SGF data found",
|
||||
parse_sgf_game, r"(B[ag])")
|
||||
tc.assertRaisesRegexp(ValueError, "no SGF data found",
|
||||
parse_sgf_game, r"B[ag]")
|
||||
tc.assertRaisesRegexp(ValueError, "no SGF data found",
|
||||
parse_sgf_game, r"[ag]")
|
||||
|
||||
tc.assertEqual(shape("(;C[abc]AB[ab][bc];B[bc])"), (2, 0))
|
||||
tc.assertEqual(shape("(;C[abc] AB[ab]\n[bc]\t;B[bc])"), (2, 0))
|
||||
tc.assertEqual(shape("(;C[abc]KO[];;B[bc])"), (3, 0))
|
||||
tc.assertEqual(shape("(;)"), (1, 0))
|
||||
|
||||
tc.assertRaisesRegexp(ValueError, "property with no values",
|
||||
parse_sgf_game, r"(;B)")
|
||||
tc.assertRaisesRegexp(ValueError, "unexpected value",
|
||||
parse_sgf_game, r"(;[ag])")
|
||||
tc.assertRaisesRegexp(ValueError, "unexpected value",
|
||||
parse_sgf_game, r"(;[ag][ah])")
|
||||
tc.assertRaisesRegexp(ValueError, "unexpected value",
|
||||
parse_sgf_game, r"(;[B][ag])")
|
||||
tc.assertRaisesRegexp(ValueError, "unexpected end of SGF data",
|
||||
parse_sgf_game, r"(;B[ag]")
|
||||
tc.assertRaisesRegexp(ValueError, "unexpected end of SGF data",
|
||||
parse_sgf_game, r"(;B[ag][)]")
|
||||
tc.assertRaisesRegexp(ValueError, "property with no values",
|
||||
parse_sgf_game, r"(;B;W[ah])")
|
||||
tc.assertRaisesRegexp(ValueError, "unexpected value",
|
||||
parse_sgf_game, r"(;B[ag](;[ah]))")
|
||||
tc.assertRaisesRegexp(ValueError, "property with no values",
|
||||
parse_sgf_game, r"(;B W[ag])")
|
||||
|
||||
def test_parser_tree_structure(tc):
|
||||
parse_sgf_game = sgf_grammar.parse_sgf_game
|
||||
|
||||
def shape(s):
|
||||
coarse_game = parse_sgf_game(s)
|
||||
return len(coarse_game.sequence), len(coarse_game.children)
|
||||
|
||||
tc.assertEqual(shape("(;C[abc]AB[ab](;B[bc]))"), (1, 1))
|
||||
tc.assertEqual(shape("(;C[abc]AB[ab](;B[bc])))"), (1, 1))
|
||||
tc.assertEqual(shape("(;C[abc]AB[ab](;B[bc])(;B[bd]))"), (1, 2))
|
||||
|
||||
def shapetree(s):
|
||||
def _shapetree(coarse_game):
|
||||
return (
|
||||
len(coarse_game.sequence),
|
||||
[_shapetree(pg) for pg in coarse_game.children])
|
||||
return _shapetree(parse_sgf_game(s))
|
||||
|
||||
tc.assertEqual(shapetree("(;C[abc]AB[ab](;B[bc])))"),
|
||||
(1, [(1, [])])
|
||||
)
|
||||
tc.assertEqual(shapetree("(;C[abc]AB[ab](;B[bc]))))"),
|
||||
(1, [(1, [])])
|
||||
)
|
||||
tc.assertEqual(shapetree("(;C[abc]AB[ab](;B[bc])(;B[bd])))"),
|
||||
(1, [(1, []), (1, [])])
|
||||
)
|
||||
tc.assertEqual(shapetree("""
|
||||
(;C[abc]AB[ab];C[];C[]
|
||||
(;B[bc])
|
||||
(;B[bd];W[ca] (;B[da])(;B[db];W[ea]) )
|
||||
)"""),
|
||||
(3, [
|
||||
(1, []),
|
||||
(2, [(1, []), (2, [])])
|
||||
])
|
||||
)
|
||||
|
||||
tc.assertRaisesRegexp(ValueError, "unexpected end of SGF data",
|
||||
parse_sgf_game, "(;B[ag];W[ah](;B[ai])")
|
||||
tc.assertRaisesRegexp(ValueError, "empty sequence",
|
||||
parse_sgf_game, "(;B[ag];())")
|
||||
tc.assertRaisesRegexp(ValueError, "empty sequence",
|
||||
parse_sgf_game, "(;B[ag]())")
|
||||
tc.assertRaisesRegexp(ValueError, "empty sequence",
|
||||
parse_sgf_game, "(;B[ag]((;W[ah])(;W[ai]))")
|
||||
tc.assertRaisesRegexp(ValueError, "unexpected node",
|
||||
parse_sgf_game, "(;B[ag];W[ah](;B[ai]);W[bd])")
|
||||
tc.assertRaisesRegexp(ValueError, "property value outside a node",
|
||||
parse_sgf_game, "(;B[ag];(W[ah];B[ai]))")
|
||||
tc.assertRaisesRegexp(ValueError, "property value outside a node",
|
||||
parse_sgf_game, "(;B[ag](;W[ah];)B[ai])")
|
||||
tc.assertRaisesRegexp(ValueError, "property value outside a node",
|
||||
parse_sgf_game, "(;B[ag](;W[ah])(B[ai]))")
|
||||
|
||||
def test_parser_properties(tc):
|
||||
parse_sgf_game = sgf_grammar.parse_sgf_game
|
||||
|
||||
def props(s):
|
||||
coarse_game = parse_sgf_game(s)
|
||||
return coarse_game.sequence
|
||||
|
||||
tc.assertEqual(props("(;C[abc]KO[]AB[ai][bh][ee];B[ bc])"),
|
||||
[{'C': ['abc'], 'KO': [''], 'AB': ['ai', 'bh', 'ee']},
|
||||
{'B': [' bc']}])
|
||||
|
||||
tc.assertEqual(props(r"(;C[ab \] \) cd\\])"),
|
||||
[{'C': [r"ab \] \) cd\\"]}])
|
||||
|
||||
tc.assertEqual(props("(;XX[1]YY[2]XX[3]YY[4])"),
|
||||
[{'XX': ['1', '3'], 'YY' : ['2', '4']}])
|
||||
|
||||
def test_parse_sgf_collection(tc):
|
||||
parse_sgf_collection = sgf_grammar.parse_sgf_collection
|
||||
|
||||
tc.assertRaisesRegexp(ValueError, "no SGF data found",
|
||||
parse_sgf_collection, r"")
|
||||
tc.assertRaisesRegexp(ValueError, "no SGF data found",
|
||||
parse_sgf_collection, r"()")
|
||||
|
||||
games = parse_sgf_collection("(;C[abc]AB[ab];X[];X[](;B[bc]))")
|
||||
tc.assertEqual(len(games), 1)
|
||||
tc.assertEqual(len(games[0].sequence), 3)
|
||||
|
||||
games = parse_sgf_collection("(;X[1];X[2];X[3](;B[bc])) (;Y[1];Y[2])")
|
||||
tc.assertEqual(len(games), 2)
|
||||
tc.assertEqual(len(games[0].sequence), 3)
|
||||
tc.assertEqual(len(games[1].sequence), 2)
|
||||
|
||||
games = parse_sgf_collection(
|
||||
"dummy (;X[1];X[2];X[3](;B[bc])) junk (;Y[1];Y[2]) Nonsense")
|
||||
tc.assertEqual(len(games), 2)
|
||||
tc.assertEqual(len(games[0].sequence), 3)
|
||||
tc.assertEqual(len(games[1].sequence), 2)
|
||||
|
||||
games = parse_sgf_collection(
|
||||
"(( (;X[1];X[2];X[3](;B[bc])) ();) (;Y[1];Y[2]) )(Nonsense")
|
||||
tc.assertEqual(len(games), 2)
|
||||
tc.assertEqual(len(games[0].sequence), 3)
|
||||
tc.assertEqual(len(games[1].sequence), 2)
|
||||
|
||||
with tc.assertRaises(ValueError) as ar:
|
||||
parse_sgf_collection(
|
||||
"(( (;X[1];X[2];X[3](;B[bc])) ();) (;Y[1];Y[2]")
|
||||
tc.assertEqual(str(ar.exception),
|
||||
"error parsing game 1: unexpected end of SGF data")
|
||||
|
||||
|
||||
def test_parse_compose(tc):
|
||||
pc = sgf_grammar.parse_compose
|
||||
tc.assertEqual(pc("word"), ("word", None))
|
||||
tc.assertEqual(pc("word:"), ("word", ""))
|
||||
tc.assertEqual(pc("word:?"), ("word", "?"))
|
||||
tc.assertEqual(pc("word:123"), ("word", "123"))
|
||||
tc.assertEqual(pc("word:123:456"), ("word", "123:456"))
|
||||
tc.assertEqual(pc(":123"), ("", "123"))
|
||||
tc.assertEqual(pc(r"word\:more"), (r"word\:more", None))
|
||||
tc.assertEqual(pc(r"word\:more:?"), (r"word\:more", "?"))
|
||||
tc.assertEqual(pc(r"word\\:more:?"), ("word\\\\", "more:?"))
|
||||
tc.assertEqual(pc(r"word\\\:more:?"), (r"word\\\:more", "?"))
|
||||
tc.assertEqual(pc("word\\\nmore:123"), ("word\\\nmore", "123"))
|
||||
|
||||
def test_text_value(tc):
|
||||
text_value = sgf_grammar.text_value
|
||||
tc.assertEqual(text_value("abc "), "abc ")
|
||||
tc.assertEqual(text_value("ab c"), "ab c")
|
||||
tc.assertEqual(text_value("ab\tc"), "ab c")
|
||||
tc.assertEqual(text_value("ab \tc"), "ab c")
|
||||
tc.assertEqual(text_value("ab\nc"), "ab\nc")
|
||||
tc.assertEqual(text_value("ab\\\nc"), "abc")
|
||||
tc.assertEqual(text_value("ab\\\\\nc"), "ab\\\nc")
|
||||
tc.assertEqual(text_value("ab\xa0c"), "ab\xa0c")
|
||||
|
||||
tc.assertEqual(text_value("ab\rc"), "ab\nc")
|
||||
tc.assertEqual(text_value("ab\r\nc"), "ab\nc")
|
||||
tc.assertEqual(text_value("ab\n\rc"), "ab\nc")
|
||||
tc.assertEqual(text_value("ab\r\n\r\nc"), "ab\n\nc")
|
||||
tc.assertEqual(text_value("ab\r\n\r\n\rc"), "ab\n\n\nc")
|
||||
tc.assertEqual(text_value("ab\\\r\nc"), "abc")
|
||||
tc.assertEqual(text_value("ab\\\n\nc"), "ab\nc")
|
||||
|
||||
tc.assertEqual(text_value("ab\\\tc"), "ab c")
|
||||
|
||||
# These can't actually appear as SGF PropValues; anything sane will do
|
||||
tc.assertEqual(text_value("abc\\"), "abc")
|
||||
tc.assertEqual(text_value("abc]"), "abc]")
|
||||
|
||||
def test_simpletext_value(tc):
|
||||
simpletext_value = sgf_grammar.simpletext_value
|
||||
tc.assertEqual(simpletext_value("abc "), "abc ")
|
||||
tc.assertEqual(simpletext_value("ab c"), "ab c")
|
||||
tc.assertEqual(simpletext_value("ab\tc"), "ab c")
|
||||
tc.assertEqual(simpletext_value("ab \tc"), "ab c")
|
||||
tc.assertEqual(simpletext_value("ab\nc"), "ab c")
|
||||
tc.assertEqual(simpletext_value("ab\\\nc"), "abc")
|
||||
tc.assertEqual(simpletext_value("ab\\\\\nc"), "ab\\ c")
|
||||
tc.assertEqual(simpletext_value("ab\xa0c"), "ab\xa0c")
|
||||
|
||||
tc.assertEqual(simpletext_value("ab\rc"), "ab c")
|
||||
tc.assertEqual(simpletext_value("ab\r\nc"), "ab c")
|
||||
tc.assertEqual(simpletext_value("ab\n\rc"), "ab c")
|
||||
tc.assertEqual(simpletext_value("ab\r\n\r\nc"), "ab c")
|
||||
tc.assertEqual(simpletext_value("ab\r\n\r\n\rc"), "ab c")
|
||||
tc.assertEqual(simpletext_value("ab\\\r\nc"), "abc")
|
||||
tc.assertEqual(simpletext_value("ab\\\n\nc"), "ab c")
|
||||
|
||||
tc.assertEqual(simpletext_value("ab\\\tc"), "ab c")
|
||||
|
||||
# These can't actually appear as SGF PropValues; anything sane will do
|
||||
tc.assertEqual(simpletext_value("abc\\"), "abc")
|
||||
tc.assertEqual(simpletext_value("abc]"), "abc]")
|
||||
|
||||
def test_escape_text(tc):
|
||||
tc.assertEqual(sgf_grammar.escape_text("abc"), "abc")
|
||||
tc.assertEqual(sgf_grammar.escape_text(r"a\bc"), r"a\\bc")
|
||||
tc.assertEqual(sgf_grammar.escape_text(r"ab[c]"), r"ab[c\]")
|
||||
tc.assertEqual(sgf_grammar.escape_text(r"a\]bc"), r"a\\\]bc")
|
||||
|
||||
def test_text_roundtrip(tc):
|
||||
def roundtrip(s):
|
||||
return sgf_grammar.text_value(sgf_grammar.escape_text(s))
|
||||
tc.assertEqual(roundtrip(r"abc"), r"abc")
|
||||
tc.assertEqual(roundtrip(r"a\bc"), r"a\bc")
|
||||
tc.assertEqual(roundtrip("abc\\"), "abc\\")
|
||||
tc.assertEqual(roundtrip("ab]c"), "ab]c")
|
||||
tc.assertEqual(roundtrip("abc]"), "abc]")
|
||||
tc.assertEqual(roundtrip(r"abc\]"), r"abc\]")
|
||||
tc.assertEqual(roundtrip("ab\nc"), "ab\nc")
|
||||
tc.assertEqual(roundtrip("ab\n c"), "ab\n c")
|
||||
|
||||
tc.assertEqual(roundtrip("ab\tc"), "ab c")
|
||||
tc.assertEqual(roundtrip("ab\r\nc\n"), "ab\nc\n")
|
||||
|
||||
def test_serialise_game_tree(tc):
|
||||
serialised = ("(;AB[aa][ab][ac]C[comment \xa3];W[ab];C[];C[]"
|
||||
"(;B[bc])(;B[bd];W[ca](;B[da])(;B[db];\n"
|
||||
"W[ea])))\n")
|
||||
coarse_game = sgf_grammar.parse_sgf_game(serialised)
|
||||
tc.assertEqual(sgf_grammar.serialise_game_tree(coarse_game), serialised)
|
||||
tc.assertEqual(sgf_grammar.serialise_game_tree(coarse_game, wrap=None),
|
||||
serialised.replace("\n", "")+"\n")
|
||||
|
141
gomill/gomill_tests/sgf_moves_tests.py
Normal file
141
gomill/gomill_tests/sgf_moves_tests.py
Normal file
@ -0,0 +1,141 @@
|
||||
from gomill_tests import gomill_test_support
|
||||
|
||||
from gomill import ascii_boards
|
||||
from gomill import boards
|
||||
from gomill import sgf
|
||||
from gomill import sgf_moves
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
SAMPLE_SGF = """\
|
||||
(;AP[testsuite:0]CA[utf-8]DT[2009-06-06]FF[4]GM[1]KM[7.5]PB[Black engine]
|
||||
PL[B]PW[White engine]RE[W+R]SZ[9]AB[ai][bh][ee]AW[fc][gc];B[dg];W[ef]C[comment
|
||||
on two lines];B[];W[tt]C[Final comment])
|
||||
"""
|
||||
|
||||
DIAGRAM1 = """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . o o . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . # . . . .
|
||||
4 . . . . . . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . # . . . . . . .
|
||||
1 # . . . . . . . .
|
||||
A B C D E F G H J\
|
||||
"""
|
||||
|
||||
DIAGRAM2 = """\
|
||||
9 . . . . . . . . .
|
||||
8 . . . . . . . . .
|
||||
7 . . . . . . . . .
|
||||
6 . . . . . . . . .
|
||||
5 . . . . . . . . .
|
||||
4 . . . . # . . . .
|
||||
3 . . . . . . . . .
|
||||
2 . . # . . . . . .
|
||||
1 . . . . . . . . .
|
||||
A B C D E F G H J\
|
||||
"""
|
||||
|
||||
|
||||
def test_get_setup_and_moves(tc):
|
||||
g1 = sgf.Sgf_game.from_string(SAMPLE_SGF)
|
||||
board1, plays1 = sgf_moves.get_setup_and_moves(g1)
|
||||
tc.assertDiagramEqual(ascii_boards.render_board(board1), DIAGRAM1)
|
||||
tc.assertEqual(plays1,
|
||||
[('b', (2, 3)), ('w', (3, 4)), ('b', None), ('w', None)])
|
||||
|
||||
g2 = sgf.Sgf_game(size=9)
|
||||
root = g2.get_root()
|
||||
root.set("AB", [(1, 2), (3, 4)]);
|
||||
node = g2.extend_main_sequence()
|
||||
node.set("B", (5, 6))
|
||||
node = g2.extend_main_sequence()
|
||||
node.set("W", (5, 7))
|
||||
board2, plays2 = sgf_moves.get_setup_and_moves(g2)
|
||||
tc.assertDiagramEqual(ascii_boards.render_board(board2), DIAGRAM2)
|
||||
tc.assertEqual(plays2,
|
||||
[('b', (5, 6)), ('w', (5, 7))])
|
||||
|
||||
g3 = sgf.Sgf_game.from_string("(;AB[ab][ba]AW[aa])")
|
||||
tc.assertRaisesRegexp(ValueError, "setup position not legal",
|
||||
sgf_moves.get_setup_and_moves, g3)
|
||||
|
||||
g4 = sgf.Sgf_game.from_string("(;SZ[9];B[ab];AW[bc])")
|
||||
tc.assertRaisesRegexp(ValueError, "setup properties after the root node",
|
||||
sgf_moves.get_setup_and_moves, g4)
|
||||
|
||||
g5 = sgf.Sgf_game.from_string("(;SZ[26];B[ab];W[bc])")
|
||||
board5, plays5 = sgf_moves.get_setup_and_moves(g5)
|
||||
tc.assertEqual(plays5,
|
||||
[('b', (24, 0)), ('w', (23, 1))])
|
||||
|
||||
|
||||
def test_get_setup_and_moves_move_in_root(tc):
|
||||
# A move in the root node is allowed (though deprecated) if there are no
|
||||
# setup stones.
|
||||
g1 = sgf.Sgf_game(size=9)
|
||||
root = g1.get_root()
|
||||
root.set("B", (1, 2));
|
||||
node = g1.extend_main_sequence()
|
||||
node.set("W", (3, 4))
|
||||
board1, plays1 = sgf_moves.get_setup_and_moves(g1)
|
||||
tc.assertTrue(board1.is_empty())
|
||||
tc.assertEqual(plays1,
|
||||
[('b', (1, 2)), ('w', (3, 4))])
|
||||
|
||||
g2 = sgf.Sgf_game(size=9)
|
||||
root = g2.get_root()
|
||||
root.set("B", (1, 2));
|
||||
root.set("AW", [(3, 3)]);
|
||||
node = g2.extend_main_sequence()
|
||||
node.set("W", (3, 4))
|
||||
tc.assertRaisesRegexp(ValueError, "mixed setup and moves in root node",
|
||||
sgf_moves.get_setup_and_moves, g2)
|
||||
|
||||
def test_get_setup_and_moves_board_provided(tc):
|
||||
b = boards.Board(9)
|
||||
g1 = sgf.Sgf_game.from_string(SAMPLE_SGF)
|
||||
board1, plays1 = sgf_moves.get_setup_and_moves(g1, b)
|
||||
tc.assertIs(board1, b)
|
||||
tc.assertDiagramEqual(ascii_boards.render_board(board1), DIAGRAM1)
|
||||
tc.assertEqual(plays1,
|
||||
[('b', (2, 3)), ('w', (3, 4)), ('b', None), ('w', None)])
|
||||
tc.assertRaisesRegexp(ValueError, "board not empty",
|
||||
sgf_moves.get_setup_and_moves, g1, b)
|
||||
b2 = boards.Board(19)
|
||||
tc.assertRaisesRegexp(ValueError, "wrong board size, must be 9$",
|
||||
sgf_moves.get_setup_and_moves, g1, b2)
|
||||
|
||||
|
||||
def test_set_initial_position(tc):
|
||||
board = ascii_boards.interpret_diagram(DIAGRAM1, 9)
|
||||
sgf_game = sgf.Sgf_game(9)
|
||||
sgf_moves.set_initial_position(sgf_game, board)
|
||||
root = sgf_game.get_root()
|
||||
tc.assertEqual(root.get("AB"), set([(0, 0), (1, 1), (4, 4)]))
|
||||
tc.assertEqual(root.get("AW"), set([(6, 5), (6, 6)]))
|
||||
tc.assertRaises(KeyError, root.get, 'AE')
|
||||
|
||||
def test_indicate_first_player(tc):
|
||||
g1 = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9];B[aa];W[ab])")
|
||||
sgf_moves.indicate_first_player(g1)
|
||||
tc.assertEqual(g1.serialise(),
|
||||
"(;FF[4]GM[1]SZ[9];B[aa];W[ab])\n")
|
||||
g2 = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9];W[aa];B[ab])")
|
||||
sgf_moves.indicate_first_player(g2)
|
||||
tc.assertEqual(g2.serialise(),
|
||||
"(;FF[4]GM[1]PL[W]SZ[9];W[aa];B[ab])\n")
|
||||
g3 = sgf.Sgf_game.from_string("(;AW[bc]FF[4]GM[1]SZ[9];B[aa];W[ab])")
|
||||
sgf_moves.indicate_first_player(g3)
|
||||
tc.assertEqual(g3.serialise(),
|
||||
"(;FF[4]AW[bc]GM[1]PL[B]SZ[9];B[aa];W[ab])\n")
|
||||
g4 = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9];C[no game])")
|
||||
sgf_moves.indicate_first_player(g4)
|
||||
tc.assertEqual(g4.serialise(),
|
||||
"(;FF[4]GM[1]SZ[9];C[no game])\n")
|
||||
|
386
gomill/gomill_tests/sgf_properties_tests.py
Normal file
386
gomill/gomill_tests/sgf_properties_tests.py
Normal file
@ -0,0 +1,386 @@
|
||||
"""Tests for sgf_properties.py."""
|
||||
|
||||
from textwrap import dedent
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
|
||||
from gomill import sgf_properties
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
def test_interpret_simpletext(tc):
|
||||
def interpret(s, encoding):
|
||||
context = sgf_properties._Context(19, encoding)
|
||||
return sgf_properties.interpret_simpletext(s, context)
|
||||
tc.assertEqual(interpret("a\nb\\\\c", "utf-8"), "a b\\c")
|
||||
u = u"test \N{POUND SIGN}"
|
||||
tc.assertEqual(interpret(u.encode("utf-8"), "UTF-8"),
|
||||
u.encode("utf-8"))
|
||||
tc.assertEqual(interpret(u.encode("iso-8859-1"), "ISO-8859-1"),
|
||||
u.encode("utf-8"))
|
||||
tc.assertRaises(UnicodeDecodeError, interpret,
|
||||
u.encode("iso-8859-1"), "UTF-8")
|
||||
tc.assertRaises(UnicodeDecodeError, interpret, u.encode("utf-8"), "ASCII")
|
||||
|
||||
def test_serialise_simpletext(tc):
|
||||
def serialise(s, encoding):
|
||||
context = sgf_properties._Context(19, encoding)
|
||||
return sgf_properties.serialise_simpletext(s, context)
|
||||
tc.assertEqual(serialise("ab\\c", "utf-8"), "ab\\\\c")
|
||||
u = u"test \N{POUND SIGN}"
|
||||
tc.assertEqual(serialise(u.encode("utf-8"), "UTF-8"),
|
||||
u.encode("utf-8"))
|
||||
tc.assertEqual(serialise(u.encode("utf-8"), "ISO-8859-1"),
|
||||
u.encode("iso-8859-1"))
|
||||
tc.assertRaises(UnicodeEncodeError, serialise,
|
||||
u"\N{EN DASH}".encode("utf-8"), "ISO-8859-1")
|
||||
|
||||
def test_interpret_text(tc):
|
||||
def interpret(s, encoding):
|
||||
context = sgf_properties._Context(19, encoding)
|
||||
return sgf_properties.interpret_text(s, context)
|
||||
tc.assertEqual(interpret("a\nb\\\\c", "utf-8"), "a\nb\\c")
|
||||
u = u"test \N{POUND SIGN}"
|
||||
tc.assertEqual(interpret(u.encode("utf-8"), "UTF-8"),
|
||||
u.encode("utf-8"))
|
||||
tc.assertEqual(interpret(u.encode("iso-8859-1"), "ISO-8859-1"),
|
||||
u.encode("utf-8"))
|
||||
tc.assertRaises(UnicodeDecodeError, interpret,
|
||||
u.encode("iso-8859-1"), "UTF-8")
|
||||
tc.assertRaises(UnicodeDecodeError, interpret, u.encode("utf-8"), "ASCII")
|
||||
|
||||
def test_serialise_text(tc):
|
||||
def serialise(s, encoding):
|
||||
context = sgf_properties._Context(19, encoding)
|
||||
return sgf_properties.serialise_text(s, context)
|
||||
tc.assertEqual(serialise("ab\\c", "utf-8"), "ab\\\\c")
|
||||
u = u"test \N{POUND SIGN}"
|
||||
tc.assertEqual(serialise(u.encode("utf-8"), "UTF-8"),
|
||||
u.encode("utf-8"))
|
||||
tc.assertEqual(serialise(u.encode("utf-8"), "ISO-8859-1"),
|
||||
u.encode("iso-8859-1"))
|
||||
tc.assertRaises(UnicodeEncodeError, serialise,
|
||||
u"\N{EN DASH}".encode("utf-8"), "ISO-8859-1")
|
||||
|
||||
|
||||
def test_interpret_number(tc):
|
||||
interpret_number = sgf_properties.interpret_number
|
||||
tc.assertEqual(interpret_number("1"), 1)
|
||||
tc.assertIs(type(interpret_number("1")), int)
|
||||
tc.assertEqual(interpret_number("0"), 0)
|
||||
tc.assertEqual(interpret_number("-1"), -1)
|
||||
tc.assertEqual(interpret_number("+1"), 1)
|
||||
tc.assertRaises(ValueError, interpret_number, "1.5")
|
||||
tc.assertRaises(ValueError, interpret_number, "0xaf")
|
||||
tc.assertRaises(TypeError, interpret_number, 1)
|
||||
|
||||
|
||||
def test_interpret_real(tc):
|
||||
interpret_real = sgf_properties.interpret_real
|
||||
tc.assertEqual(interpret_real("1"), 1.0)
|
||||
tc.assertIs(type(interpret_real("1")), float)
|
||||
tc.assertEqual(interpret_real("0"), 0.0)
|
||||
tc.assertEqual(interpret_real("1.0"), 1.0)
|
||||
tc.assertEqual(interpret_real("1.5"), 1.5)
|
||||
tc.assertEqual(interpret_real("-1.5"), -1.5)
|
||||
tc.assertEqual(interpret_real("+0.5"), 0.5)
|
||||
tc.assertRaises(ValueError, interpret_real, "+")
|
||||
tc.assertRaises(ValueError, interpret_real, "0xaf")
|
||||
tc.assertRaises(ValueError, interpret_real, "inf")
|
||||
tc.assertRaises(ValueError, interpret_real, "-inf")
|
||||
tc.assertRaises(ValueError, interpret_real, "NaN")
|
||||
tc.assertRaises(ValueError, interpret_real, "1e400")
|
||||
#tc.assertRaises(TypeError, interpret_real, 1.0)
|
||||
|
||||
def test_serialise_real(tc):
|
||||
serialise_real = sgf_properties.serialise_real
|
||||
tc.assertEqual(serialise_real(1), "1")
|
||||
tc.assertEqual(serialise_real(-1), "-1")
|
||||
tc.assertEqual(serialise_real(1.0), "1")
|
||||
tc.assertEqual(serialise_real(-1.0), "-1")
|
||||
tc.assertEqual(serialise_real(1.5), "1.5")
|
||||
tc.assertEqual(serialise_real(-1.5), "-1.5")
|
||||
tc.assertEqual(serialise_real(0.001), "0.001")
|
||||
tc.assertEqual(serialise_real(0.0001), "0.0001")
|
||||
tc.assertEqual(serialise_real(0.00001), "0")
|
||||
tc.assertEqual(serialise_real(1e15), "1000000000000000")
|
||||
tc.assertEqual(serialise_real(1e16), "10000000000000000")
|
||||
tc.assertEqual(serialise_real(1e17), "100000000000000000")
|
||||
tc.assertEqual(serialise_real(1e18), "1000000000000000000")
|
||||
tc.assertEqual(serialise_real(-1e18), "-1000000000000000000")
|
||||
# 1e400 is inf
|
||||
tc.assertRaises(ValueError, serialise_real, 1e400)
|
||||
# Python 2.5 returns 0
|
||||
#tc.assertRaises(ValueError, serialise_real, float("NaN"))
|
||||
|
||||
|
||||
def test_interpret_move(tc):
|
||||
def interpret_move(s, size):
|
||||
context = sgf_properties._Context(size, "UTF-8")
|
||||
return sgf_properties.interpret_move(s, context)
|
||||
tc.assertEqual(interpret_move("aa", 19), (18, 0))
|
||||
tc.assertEqual(interpret_move("ai", 19), (10, 0))
|
||||
tc.assertEqual(interpret_move("ba", 9), (8, 1))
|
||||
tc.assertEqual(interpret_move("tt", 21), (1, 19))
|
||||
tc.assertIs(interpret_move("tt", 19), None)
|
||||
tc.assertIs(interpret_move("", 19), None)
|
||||
tc.assertIs(interpret_move("", 21), None)
|
||||
tc.assertRaises(ValueError, interpret_move, "Aa", 19)
|
||||
tc.assertRaises(ValueError, interpret_move, "aA", 19)
|
||||
tc.assertRaises(ValueError, interpret_move, "aaa", 19)
|
||||
tc.assertRaises(ValueError, interpret_move, "a", 19)
|
||||
tc.assertRaises(ValueError, interpret_move, "au", 19)
|
||||
tc.assertRaises(ValueError, interpret_move, "ua", 19)
|
||||
tc.assertRaises(ValueError, interpret_move, "a`", 19)
|
||||
tc.assertRaises(ValueError, interpret_move, "`a", 19)
|
||||
tc.assertRaises(ValueError, interpret_move, "11", 19)
|
||||
tc.assertRaises(ValueError, interpret_move, " aa", 19)
|
||||
tc.assertRaises(ValueError, interpret_move, "aa\x00", 19)
|
||||
tc.assertRaises(TypeError, interpret_move, None, 19)
|
||||
#tc.assertRaises(TypeError, interpret_move, ('a', 'a'), 19)
|
||||
|
||||
def test_serialise_move(tc):
|
||||
def serialise_move(s, size):
|
||||
context = sgf_properties._Context(size, "UTF-8")
|
||||
return sgf_properties.serialise_move(s, context)
|
||||
tc.assertEqual(serialise_move((18, 0), 19), "aa")
|
||||
tc.assertEqual(serialise_move((10, 0), 19), "ai")
|
||||
tc.assertEqual(serialise_move((8, 1), 19), "bk")
|
||||
tc.assertEqual(serialise_move((8, 1), 9), "ba")
|
||||
tc.assertEqual(serialise_move((1, 19), 21), "tt")
|
||||
tc.assertEqual(serialise_move(None, 19), "tt")
|
||||
tc.assertEqual(serialise_move(None, 20), "")
|
||||
tc.assertRaises(ValueError, serialise_move, (3, 3), 0)
|
||||
tc.assertRaises(ValueError, serialise_move, (3, 3), 27)
|
||||
tc.assertRaises(ValueError, serialise_move, (9, 0), 9)
|
||||
tc.assertRaises(ValueError, serialise_move, (-1, 0), 9)
|
||||
tc.assertRaises(ValueError, serialise_move, (0, 9), 9)
|
||||
tc.assertRaises(ValueError, serialise_move, (0, -1), 9)
|
||||
tc.assertRaises(TypeError, serialise_move, (1, 1.5), 9)
|
||||
|
||||
def test_interpret_point(tc):
|
||||
def interpret_point(s, size):
|
||||
context = sgf_properties._Context(size, "UTF-8")
|
||||
return sgf_properties.interpret_point(s, context)
|
||||
tc.assertEqual(interpret_point("aa", 19), (18, 0))
|
||||
tc.assertEqual(interpret_point("ai", 19), (10, 0))
|
||||
tc.assertEqual(interpret_point("ba", 9), (8, 1))
|
||||
tc.assertEqual(interpret_point("tt", 21), (1, 19))
|
||||
tc.assertRaises(ValueError, interpret_point, "tt", 19)
|
||||
tc.assertRaises(ValueError, interpret_point, "", 19)
|
||||
tc.assertRaises(ValueError, interpret_point, "", 21)
|
||||
tc.assertRaises(ValueError, interpret_point, "Aa", 19)
|
||||
tc.assertRaises(ValueError, interpret_point, "aA", 19)
|
||||
tc.assertRaises(ValueError, interpret_point, "aaa", 19)
|
||||
tc.assertRaises(ValueError, interpret_point, "a", 19)
|
||||
tc.assertRaises(ValueError, interpret_point, "au", 19)
|
||||
tc.assertRaises(ValueError, interpret_point, "ua", 19)
|
||||
tc.assertRaises(ValueError, interpret_point, "a`", 19)
|
||||
tc.assertRaises(ValueError, interpret_point, "`a", 19)
|
||||
tc.assertRaises(ValueError, interpret_point, "11", 19)
|
||||
tc.assertRaises(ValueError, interpret_point, " aa", 19)
|
||||
tc.assertRaises(ValueError, interpret_point, "aa\x00", 19)
|
||||
tc.assertRaises(TypeError, interpret_point, None, 19)
|
||||
#tc.assertRaises(TypeError, interpret_point, ('a', 'a'), 19)
|
||||
|
||||
def test_serialise_point(tc):
|
||||
def serialise_point(s, size):
|
||||
context = sgf_properties._Context(size, "UTF-8")
|
||||
return sgf_properties.serialise_point(s, context)
|
||||
tc.assertEqual(serialise_point((18, 0), 19), "aa")
|
||||
tc.assertEqual(serialise_point((10, 0), 19), "ai")
|
||||
tc.assertEqual(serialise_point((8, 1), 19), "bk")
|
||||
tc.assertEqual(serialise_point((8, 1), 9), "ba")
|
||||
tc.assertEqual(serialise_point((1, 19), 21), "tt")
|
||||
tc.assertRaises(ValueError, serialise_point, None, 19)
|
||||
tc.assertRaises(ValueError, serialise_point, None, 20)
|
||||
tc.assertRaises(ValueError, serialise_point, (3, 3), 0)
|
||||
tc.assertRaises(ValueError, serialise_point, (3, 3), 27)
|
||||
tc.assertRaises(ValueError, serialise_point, (9, 0), 9)
|
||||
tc.assertRaises(ValueError, serialise_point, (-1, 0), 9)
|
||||
tc.assertRaises(ValueError, serialise_point, (0, 9), 9)
|
||||
tc.assertRaises(ValueError, serialise_point, (0, -1), 9)
|
||||
tc.assertRaises(TypeError, serialise_point, (1, 1.5), 9)
|
||||
|
||||
|
||||
def test_interpret_point_list(tc):
|
||||
def ipl(l, size):
|
||||
context = sgf_properties._Context(size, "UTF-8")
|
||||
return sgf_properties.interpret_point_list(l, context)
|
||||
tc.assertEqual(ipl([], 19),
|
||||
set())
|
||||
tc.assertEqual(ipl(["aa"], 19),
|
||||
set([(18, 0)]))
|
||||
tc.assertEqual(ipl(["aa", "ai"], 19),
|
||||
set([(18, 0), (10, 0)]))
|
||||
tc.assertEqual(ipl(["ab:bc"], 19),
|
||||
set([(16, 0), (16, 1), (17, 0), (17, 1)]))
|
||||
tc.assertEqual(ipl(["ab:bc", "aa"], 19),
|
||||
set([(18, 0), (16, 0), (16, 1), (17, 0), (17, 1)]))
|
||||
# overlap is forbidden by the spec, but we accept it
|
||||
tc.assertEqual(ipl(["aa", "aa"], 19),
|
||||
set([(18, 0)]))
|
||||
tc.assertEqual(ipl(["ab:bc", "bb:bc"], 19),
|
||||
set([(16, 0), (16, 1), (17, 0), (17, 1)]))
|
||||
# 1x1 rectangles are forbidden by the spec, but we accept them
|
||||
tc.assertEqual(ipl(["aa", "bb:bb"], 19),
|
||||
set([(18, 0), (17, 1)]))
|
||||
# 'backwards' rectangles are forbidden by the spec, and we reject them
|
||||
tc.assertRaises(ValueError, ipl, ["ab:aa"], 19)
|
||||
tc.assertRaises(ValueError, ipl, ["ba:aa"], 19)
|
||||
tc.assertRaises(ValueError, ipl, ["bb:aa"], 19)
|
||||
|
||||
tc.assertRaises(ValueError, ipl, ["aa", "tt"], 19)
|
||||
tc.assertRaises(ValueError, ipl, ["aa", ""], 19)
|
||||
tc.assertRaises(ValueError, ipl, ["aa:", "aa"], 19)
|
||||
tc.assertRaises(ValueError, ipl, ["aa:tt", "aa"], 19)
|
||||
tc.assertRaises(ValueError, ipl, ["tt:aa", "aa"], 19)
|
||||
|
||||
def test_compressed_point_list_spec_example(tc):
|
||||
# Checks the examples at http://www.red-bean.com/sgf/DD_VW.html
|
||||
def sgf_point(move, size):
|
||||
row, col = move
|
||||
row = size - row - 1
|
||||
col_s = "abcdefghijklmnopqrstuvwxy"[col]
|
||||
row_s = "abcdefghijklmnopqrstuvwxy"[row]
|
||||
return col_s + row_s
|
||||
|
||||
def ipl(l, size):
|
||||
context = sgf_properties._Context(size, "UTF-8")
|
||||
return sgf_properties.interpret_point_list(l, context)
|
||||
tc.assertEqual(
|
||||
set(sgf_point(move, 9) for move in ipl(["ac:ic"], 9)),
|
||||
set(["ac", "bc", "cc", "dc", "ec", "fc", "gc", "hc", "ic"]))
|
||||
tc.assertEqual(
|
||||
set(sgf_point(move, 9) for move in ipl(["ae:ie"], 9)),
|
||||
set(["ae", "be", "ce", "de", "ee", "fe", "ge", "he", "ie"]))
|
||||
tc.assertEqual(
|
||||
set(sgf_point(move, 9) for move in ipl(["aa:bi", "ca:ce"], 9)),
|
||||
set(["aa", "ab", "ac", "ad", "ae", "af", "ag", "ah", "ai",
|
||||
"bi", "bh", "bg", "bf", "be", "bd", "bc", "bb", "ba",
|
||||
"ca", "cb", "cc", "cd", "ce"]))
|
||||
|
||||
def test_serialise_point_list(tc):
|
||||
def ipl(l, size):
|
||||
context = sgf_properties._Context(size, "UTF-8")
|
||||
return sgf_properties.interpret_point_list(l, context)
|
||||
def spl(l, size):
|
||||
context = sgf_properties._Context(size, "UTF-8")
|
||||
return sgf_properties.serialise_point_list(l, context)
|
||||
|
||||
tc.assertEqual(spl([(18, 0), (17, 1)], 19), ['aa', 'bb'])
|
||||
tc.assertEqual(spl([(17, 1), (18, 0)], 19), ['aa', 'bb'])
|
||||
tc.assertEqual(spl([], 9), [])
|
||||
tc.assertEqual(ipl(spl([(1,2), (3,4), (4,5)], 19), 19),
|
||||
set([(1,2), (3,4), (4,5)]))
|
||||
tc.assertRaises(ValueError, spl, [(18, 0), None], 19)
|
||||
|
||||
|
||||
def test_AP(tc):
|
||||
def serialise(arg):
|
||||
context = sgf_properties._Context(19, "UTF-8")
|
||||
return sgf_properties.serialise_AP(arg, context)
|
||||
def interpret(arg):
|
||||
context = sgf_properties._Context(19, "UTF-8")
|
||||
return sgf_properties.interpret_AP(arg, context)
|
||||
|
||||
tc.assertEqual(serialise(("foo:bar", "2\n3")), "foo\\:bar:2\n3")
|
||||
tc.assertEqual(interpret("foo\\:bar:2 3"), ("foo:bar", "2 3"))
|
||||
tc.assertEqual(interpret("foo bar"), ("foo bar", ""))
|
||||
|
||||
def test_ARLN(tc):
|
||||
def serialise(arg, size):
|
||||
context = sgf_properties._Context(size, "UTF-8")
|
||||
return sgf_properties.serialise_ARLN_list(arg, context)
|
||||
def interpret(arg, size):
|
||||
context = sgf_properties._Context(size, "UTF-8")
|
||||
return sgf_properties.interpret_ARLN_list(arg, context)
|
||||
|
||||
tc.assertEqual(serialise([], 19), [])
|
||||
tc.assertEqual(interpret([], 19), [])
|
||||
tc.assertEqual(serialise([((7, 0), (5, 2)), ((4, 3), (2, 5))], 9),
|
||||
['ab:cd', 'de:fg'])
|
||||
tc.assertEqual(interpret(['ab:cd', 'de:fg'], 9),
|
||||
[((7, 0), (5, 2)), ((4, 3), (2, 5))])
|
||||
tc.assertRaises(ValueError, serialise, [((7, 0), None)], 9)
|
||||
tc.assertRaises(ValueError, interpret, ['ab:tt', 'de:fg'], 9)
|
||||
|
||||
def test_FG(tc):
|
||||
def serialise(arg):
|
||||
context = sgf_properties._Context(19, "UTF-8")
|
||||
return sgf_properties.serialise_FG(arg, context)
|
||||
def interpret(arg):
|
||||
context = sgf_properties._Context(19, "UTF-8")
|
||||
return sgf_properties.interpret_FG(arg, context)
|
||||
tc.assertEqual(serialise(None), "")
|
||||
tc.assertEqual(interpret(""), None)
|
||||
tc.assertEqual(serialise((515, "th]is")), "515:th\\]is")
|
||||
tc.assertEqual(interpret("515:th\\]is"), (515, "th]is"))
|
||||
|
||||
def test_LB(tc):
|
||||
def serialise(arg, size):
|
||||
context = sgf_properties._Context(size, "UTF-8")
|
||||
return sgf_properties.serialise_LB_list(arg, context)
|
||||
def interpret(arg, size):
|
||||
context = sgf_properties._Context(size, "UTF-8")
|
||||
return sgf_properties.interpret_LB_list(arg, context)
|
||||
tc.assertEqual(serialise([], 19), [])
|
||||
tc.assertEqual(interpret([], 19), [])
|
||||
tc.assertEqual(
|
||||
serialise([((6, 0), "lbl"), ((6, 1), "lb]l2")], 9),
|
||||
["ac:lbl", "bc:lb\\]l2"])
|
||||
tc.assertEqual(
|
||||
interpret(["ac:lbl", "bc:lb\\]l2"], 9),
|
||||
[((6, 0), "lbl"), ((6, 1), "lb]l2")])
|
||||
tc.assertRaises(ValueError, serialise, [(None, "lbl")], 9)
|
||||
tc.assertRaises(ValueError, interpret, [':lbl', 'de:lbl2'], 9)
|
||||
|
||||
|
||||
def test_presenter_interpret(tc):
|
||||
p9 = sgf_properties.Presenter(9, "UTF-8")
|
||||
p19 = sgf_properties.Presenter(19, "UTF-8")
|
||||
tc.assertEqual(p9.interpret('KO', [""]), True)
|
||||
tc.assertEqual(p9.interpret('SZ', ["9"]), 9)
|
||||
tc.assertRaisesRegexp(ValueError, "multiple values",
|
||||
p9.interpret, 'SZ', ["9", "blah"])
|
||||
tc.assertEqual(p9.interpret('CR', ["ab", "cd"]), set([(5, 2), (7, 0)]))
|
||||
tc.assertRaises(ValueError, p9.interpret, 'SZ', [])
|
||||
tc.assertRaises(ValueError, p9.interpret, 'CR', [])
|
||||
tc.assertEqual(p9.interpret('DD', [""]), set())
|
||||
# all lists are treated like elists
|
||||
tc.assertEqual(p9.interpret('CR', [""]), set())
|
||||
|
||||
def test_presenter_serialise(tc):
|
||||
p9 = sgf_properties.Presenter(9, "UTF-8")
|
||||
p19 = sgf_properties.Presenter(19, "UTF-8")
|
||||
|
||||
tc.assertEqual(p9.serialise('KO', True), [""])
|
||||
tc.assertEqual(p9.serialise('SZ', 9), ["9"])
|
||||
tc.assertEqual(p9.serialise('KM', 3.5), ["3.5"])
|
||||
tc.assertEqual(p9.serialise('C', "foo\\:b]ar\n"), ["foo\\\\:b\\]ar\n"])
|
||||
tc.assertEqual(p19.serialise('B', (1, 2)), ["cr"])
|
||||
tc.assertEqual(p9.serialise('B', None), ["tt"])
|
||||
tc.assertEqual(p19.serialise('AW', set([(17, 1), (18, 0)])),["aa", "bb"])
|
||||
tc.assertEqual(p9.serialise('DD', [(1, 2), (3, 4)]), ["ch", "ef"])
|
||||
tc.assertEqual(p9.serialise('DD', []), [""])
|
||||
tc.assertRaisesRegexp(ValueError, "empty list", p9.serialise, 'CR', [])
|
||||
tc.assertEqual(p9.serialise('AP', ("na:me", "2.3")), ["na\\:me:2.3"])
|
||||
tc.assertEqual(p9.serialise('FG', (515, "th]is")), ["515:th\\]is"])
|
||||
tc.assertEqual(p9.serialise('XX', "foo\\bar"), ["foo\\\\bar"])
|
||||
|
||||
tc.assertRaises(ValueError, p9.serialise, 'B', (1, 9))
|
||||
|
||||
def test_presenter_private_properties(tc):
|
||||
p9 = sgf_properties.Presenter(9, "UTF-8")
|
||||
tc.assertEqual(p9.serialise('XX', "9"), ["9"])
|
||||
tc.assertEqual(p9.interpret('XX', ["9"]), "9")
|
||||
p9.set_private_property_type(p9.get_property_type("SZ"))
|
||||
tc.assertEqual(p9.serialise('XX', 9), ["9"])
|
||||
tc.assertEqual(p9.interpret('XX', ["9"]), 9)
|
||||
p9.set_private_property_type(None)
|
||||
tc.assertRaisesRegexp(ValueError, "unknown property",
|
||||
p9.serialise, 'XX', "foo\\bar")
|
||||
tc.assertRaisesRegexp(ValueError, "unknown property",
|
||||
p9.interpret, 'XX', ["asd"])
|
||||
|
786
gomill/gomill_tests/sgf_tests.py
Normal file
786
gomill/gomill_tests/sgf_tests.py
Normal file
@ -0,0 +1,786 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for sgf.py."""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from textwrap import dedent
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
|
||||
from gomill import sgf
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
def test_new_sgf_game(tc):
|
||||
g1 = sgf.Sgf_game(9)
|
||||
tc.assertEqual(g1.get_size(), 9)
|
||||
root = g1.get_root()
|
||||
tc.assertEqual(root.get_raw('FF'), '4')
|
||||
tc.assertEqual(root.get_raw('GM'), '1')
|
||||
tc.assertEqual(root.get_raw('SZ'), '9')
|
||||
tc.assertEqual(root.get_raw_property_map(), {
|
||||
'FF': ['4'],
|
||||
'GM': ['1'],
|
||||
'SZ': ['9'],
|
||||
'CA': ['UTF-8'],
|
||||
});
|
||||
tc.assertEqual(list(root), [])
|
||||
tc.assertEqual(root.parent, None)
|
||||
tc.assertIs(root.owner, g1)
|
||||
|
||||
def test_sgf_game_from_coarse_game_tree(tc):
|
||||
class Namespace(object):
|
||||
pass
|
||||
coarse_game = Namespace()
|
||||
coarse_game.sequence = [{'SZ' : ["9"]}, {'B' : ["aa"]}]
|
||||
coarse_game.children = []
|
||||
g1 = sgf.Sgf_game.from_coarse_game_tree(coarse_game)
|
||||
tc.assertEqual(g1.get_size(), 9)
|
||||
root = g1.get_root()
|
||||
tc.assertIs(root.get_raw_property_map(), coarse_game.sequence[0])
|
||||
tc.assertEqual(root.parent, None)
|
||||
tc.assertIs(root.owner, g1)
|
||||
tc.assertEqual(len(root), 1)
|
||||
|
||||
coarse_game2 = Namespace()
|
||||
coarse_game2.sequence = [{'SZ' : ["0"]}, {'B' : ["aa"]}]
|
||||
coarse_game2.children = []
|
||||
tc.assertRaisesRegexp(ValueError, "size out of range: 0",
|
||||
sgf.Sgf_game.from_coarse_game_tree, coarse_game2)
|
||||
|
||||
def test_sgf_game_from_string(tc):
|
||||
g1 = sgf.Sgf_game.from_string("(;)")
|
||||
tc.assertEqual(g1.get_size(), 19)
|
||||
tc.assertRaisesRegexp(ValueError, "unexpected end of SGF data",
|
||||
sgf.Sgf_game.from_string, "(;SZ[9]")
|
||||
g2 = sgf.Sgf_game.from_string("(;SZ[9])")
|
||||
tc.assertEqual(g2.get_size(), 9)
|
||||
tc.assertRaisesRegexp(ValueError, "bad SZ property: a",
|
||||
sgf.Sgf_game.from_string, "(;SZ[a])")
|
||||
tc.assertRaisesRegexp(ValueError, "size out of range: 27",
|
||||
sgf.Sgf_game.from_string, "(;SZ[27])")
|
||||
tc.assertRaisesRegexp(ValueError, "unknown encoding: $",
|
||||
sgf.Sgf_game.from_string, "(;CA[])")
|
||||
|
||||
def test_node(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(
|
||||
r"(;KM[6.5]C[sample\: comment]AB[ai][bh][ee]AE[];B[dg])")
|
||||
node0 = sgf_game.get_root()
|
||||
node1 = list(sgf_game.main_sequence_iter())[1]
|
||||
tc.assertEqual(node0.get_size(), 19)
|
||||
tc.assertEqual(node0.get_encoding(), "ISO-8859-1")
|
||||
tc.assertIs(node0.has_property('KM'), True)
|
||||
tc.assertIs(node0.has_property('XX'), False)
|
||||
tc.assertIs(node1.has_property('KM'), False)
|
||||
tc.assertEqual(set(node0.properties()), set(["KM", "C", "AB", "AE"]))
|
||||
tc.assertEqual(set(node1.properties()), set(["B"]))
|
||||
tc.assertEqual(node0.get_raw('C'), r"sample\: comment")
|
||||
tc.assertEqual(node0.get_raw('AB'), "ai")
|
||||
tc.assertEqual(node0.get_raw('AE'), "")
|
||||
tc.assertRaises(KeyError, node0.get_raw, 'XX')
|
||||
tc.assertEqual(node0.get_raw_list('KM'), ['6.5'])
|
||||
tc.assertEqual(node0.get_raw_list('AB'), ['ai', 'bh', 'ee'])
|
||||
tc.assertEqual(node0.get_raw_list('AE'), [''])
|
||||
tc.assertRaises(KeyError, node0.get_raw_list, 'XX')
|
||||
tc.assertRaises(KeyError, node0.get_raw, 'XX')
|
||||
|
||||
def test_property_combination(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string("(;XX[1]YY[2]XX[3]YY[4])")
|
||||
node0 = sgf_game.get_root()
|
||||
tc.assertEqual(node0.get_raw_list("XX"), ["1", "3"])
|
||||
tc.assertEqual(node0.get_raw_list("YY"), ["2", "4"])
|
||||
|
||||
def test_node_get(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(dedent(r"""
|
||||
(;AP[testsuite:0]CA[utf-8]DT[2009-06-06]FF[4]GM[1]KM[7.5]PB[Black engine]
|
||||
PL[B]PW[White engine][xs]RE[W+R]SZ[9]AB[ai][bh][ee]AW[fd][gc]AE[]BM[2]VW[]
|
||||
EV[Test
|
||||
event]
|
||||
C[123:\)
|
||||
abc]
|
||||
YY[none
|
||||
sense]
|
||||
;B[dg]KO[]AR[ab:cd][de:fg]FG[515:first move]
|
||||
LB[ac:lbl][bc:lbl2])
|
||||
"""))
|
||||
root = sgf_game.get_root()
|
||||
node1 = list(sgf_game.main_sequence_iter())[1]
|
||||
tc.assertRaises(KeyError, root.get, 'XX')
|
||||
tc.assertEqual(root.get('C'), "123:)\nabc") # Text
|
||||
tc.assertEqual(root.get('EV'), "Test event") # Simpletext
|
||||
tc.assertEqual(root.get('BM'), 2) # Double
|
||||
tc.assertEqual(root.get('YY'), "none\nsense") # unknown (Text)
|
||||
tc.assertIs(node1.get('KO'), True) # None
|
||||
tc.assertEqual(root.get('KM'), 7.5) # Real
|
||||
tc.assertEqual(root.get('GM'), 1) # Number
|
||||
tc.assertEqual(root.get('PL'), 'b') # Color
|
||||
tc.assertEqual(node1.get('B'), (2, 3)) # Point
|
||||
tc.assertEqual(root.get('AB'),
|
||||
set([(0, 0), (1, 1), (4, 4)])) # List of Point
|
||||
tc.assertEqual(root.get('VW'), set()) # Empty elist
|
||||
tc.assertEqual(root.get('AP'), ("testsuite", "0")) # Application
|
||||
tc.assertEqual(node1.get('AR'),
|
||||
[((7, 0), (5, 2)), ((4, 3), (2, 5))]) # Arrow
|
||||
tc.assertEqual(node1.get('FG'), (515, "first move")) # Figure
|
||||
tc.assertEqual(node1.get('LB'),
|
||||
[((6, 0), "lbl"), ((6, 1), "lbl2")]) # Label
|
||||
# Check we (leniently) treat lists like elists on read
|
||||
tc.assertEqual(root.get('AE'), set())
|
||||
tc.assertRaisesRegexp(ValueError, "multiple values", root.get, 'PW')
|
||||
|
||||
def test_text_values(tc):
|
||||
def check(s):
|
||||
sgf_game = sgf.Sgf_game.from_string(s)
|
||||
return sgf_game.get_root().get("C")
|
||||
# Round-trip check of Text values through tokeniser, parser, and
|
||||
# text_value().
|
||||
tc.assertEqual(check(r"(;C[abc]KO[])"), r"abc")
|
||||
tc.assertEqual(check(r"(;C[a\\bc]KO[])"), r"a\bc")
|
||||
tc.assertEqual(check(r"(;C[a\\bc\]KO[])"), r"a\bc]KO[")
|
||||
tc.assertEqual(check(r"(;C[abc\\]KO[])"), r"abc" + "\\")
|
||||
tc.assertEqual(check(r"(;C[abc\\\]KO[])"), r"abc\]KO[")
|
||||
tc.assertEqual(check(r"(;C[abc\\\\]KO[])"), r"abc" + "\\\\")
|
||||
tc.assertEqual(check(r"(;C[abc\\\\\]KO[])"), r"abc\\]KO[")
|
||||
tc.assertEqual(check(r"(;C[xxx :\) yyy]KO[])"), r"xxx :) yyy")
|
||||
tc.assertEqual(check("(;C[ab\\\nc])"), "abc")
|
||||
tc.assertEqual(check("(;C[ab\nc])"), "ab\nc")
|
||||
|
||||
|
||||
SAMPLE_SGF = """\
|
||||
(;AP[testsuite:0]CA[utf-8]DT[2009-06-06]FF[4]GM[1]KM[7.5]PB[Black engine]
|
||||
PL[B]PW[White engine]RE[W+R]SZ[9]AB[ai][bh][ee]AW[fc][gc];B[dg];W[ef]C[comment
|
||||
on two lines];B[];W[tt]C[Final comment])
|
||||
"""
|
||||
|
||||
SAMPLE_SGF_VAR = """\
|
||||
(;AP[testsuite:0]CA[utf-8]DT[2009-06-06]FF[4]GM[1]KM[7.5]PB[Black engine]
|
||||
PL[B]RE[W+R]SZ[9]AB[ai][bh][ee]AW[fd][gc]VW[]
|
||||
;B[dg]
|
||||
;W[ef]C[comment
|
||||
on two lines]
|
||||
;B[]
|
||||
;C[Nonfinal comment]VW[aa:bb]
|
||||
(;B[ia];W[ib];B[ic])
|
||||
(;B[ib];W[ic]
|
||||
(;B[id])
|
||||
(;B[ie])
|
||||
))
|
||||
"""
|
||||
|
||||
def test_node_string(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF)
|
||||
node = sgf_game.get_root()
|
||||
tc.assertMultiLineEqual(str(node), dedent("""\
|
||||
AB[ai][bh][ee]
|
||||
AP[testsuite:0]
|
||||
AW[fc][gc]
|
||||
CA[utf-8]
|
||||
DT[2009-06-06]
|
||||
FF[4]
|
||||
GM[1]
|
||||
KM[7.5]
|
||||
PB[Black engine]
|
||||
PL[B]
|
||||
PW[White engine]
|
||||
RE[W+R]
|
||||
SZ[9]
|
||||
"""))
|
||||
|
||||
def test_node_get_move(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF)
|
||||
nodes = list(sgf_game.main_sequence_iter())
|
||||
tc.assertEqual(nodes[0].get_move(), (None, None))
|
||||
tc.assertEqual(nodes[1].get_move(), ('b', (2, 3)))
|
||||
tc.assertEqual(nodes[2].get_move(), ('w', (3, 4)))
|
||||
tc.assertEqual(nodes[3].get_move(), ('b', None))
|
||||
tc.assertEqual(nodes[4].get_move(), ('w', None))
|
||||
|
||||
def test_node_get_setup_stones(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(
|
||||
r"(;KM[6.5]SZ[9]C[sample\: comment]AB[ai][bh][ee]AE[bb];B[dg])")
|
||||
node0 = sgf_game.get_root()
|
||||
node1 = list(sgf_game.main_sequence_iter())[1]
|
||||
tc.assertIs(node0.has_setup_stones(), True)
|
||||
tc.assertIs(node1.has_setup_stones(), False)
|
||||
tc.assertEqual(node0.get_setup_stones(),
|
||||
(set([(0, 0), (1, 1), (4, 4)]), set(), set([(7, 1)])))
|
||||
tc.assertEqual(node1.get_setup_stones(),
|
||||
(set(), set(), set()))
|
||||
|
||||
def test_sgf_game(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
|
||||
nodes = list(sgf_game.main_sequence_iter())
|
||||
tc.assertEqual(sgf_game.get_size(), 9)
|
||||
tc.assertEqual(sgf_game.get_komi(), 7.5)
|
||||
tc.assertIs(sgf_game.get_handicap(), None)
|
||||
tc.assertEqual(sgf_game.get_player_name('b'), "Black engine")
|
||||
tc.assertIs(sgf_game.get_player_name('w'), None)
|
||||
tc.assertEqual(sgf_game.get_winner(), 'w')
|
||||
tc.assertEqual(nodes[2].get('C'), "comment\non two lines")
|
||||
tc.assertEqual(nodes[4].get('C'), "Nonfinal comment")
|
||||
|
||||
g2 = sgf.Sgf_game.from_string("(;)")
|
||||
tc.assertEqual(g2.get_size(), 19)
|
||||
tc.assertEqual(g2.get_komi(), 0.0)
|
||||
tc.assertIs(g2.get_handicap(), None)
|
||||
tc.assertIs(g2.get_player_name('b'), None)
|
||||
tc.assertIs(g2.get_player_name('w'), None)
|
||||
tc.assertEqual(g2.get_winner(), None)
|
||||
|
||||
def test_tree_view(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
|
||||
root = sgf_game.get_root()
|
||||
tc.assertIsInstance(root, sgf.Tree_node)
|
||||
tc.assertIs(root.parent, None)
|
||||
tc.assertIs(root.owner, sgf_game)
|
||||
tc.assertEqual(len(root), 1)
|
||||
tc.assertEqual(root[0].get_raw('B'), "dg")
|
||||
tc.assertTrue(root)
|
||||
tc.assertEqual(root.index(root[0]), 0)
|
||||
|
||||
branchnode = root[0][0][0][0]
|
||||
tc.assertIsInstance(branchnode, sgf.Tree_node)
|
||||
tc.assertIs(branchnode.parent, root[0][0][0])
|
||||
tc.assertIs(branchnode.owner, sgf_game)
|
||||
tc.assertEqual(len(branchnode), 2)
|
||||
tc.assertIs(branchnode[1], branchnode[-1])
|
||||
tc.assertEqual(branchnode[:1], [branchnode[0]])
|
||||
tc.assertEqual([node for node in branchnode],
|
||||
[branchnode[0], branchnode[1]])
|
||||
with tc.assertRaises(IndexError):
|
||||
branchnode[2]
|
||||
tc.assertEqual(branchnode[0].get_raw('B'), "ia")
|
||||
tc.assertEqual(branchnode[1].get_raw('B'), "ib")
|
||||
tc.assertEqual(branchnode.index(branchnode[0]), 0)
|
||||
tc.assertEqual(branchnode.index(branchnode[1]), 1)
|
||||
|
||||
tc.assertEqual(len(branchnode[1][0]), 2)
|
||||
|
||||
leaf = branchnode[1][0][1]
|
||||
tc.assertIs(leaf.parent, branchnode[1][0])
|
||||
tc.assertEqual(len(leaf), 0)
|
||||
tc.assertFalse(leaf)
|
||||
|
||||
tc.assertIs(sgf_game.get_last_node(), root[0][0][0][0][0][0][0])
|
||||
|
||||
# check nothing breaks when first retrieval is by index
|
||||
game2 = sgf.Sgf_game.from_string(SAMPLE_SGF)
|
||||
root2 = game2.get_root()
|
||||
tc.assertEqual(root2[0].get_raw('B'), "dg")
|
||||
|
||||
def test_serialise(tc):
|
||||
# Doesn't cover transcoding
|
||||
sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
|
||||
serialised = sgf_game.serialise()
|
||||
tc.assertEqual(serialised, dedent("""\
|
||||
(;FF[4]AB[ai][bh][ee]AP[testsuite:0]AW[fd][gc]CA[utf-8]DT[2009-06-06]GM[1]
|
||||
KM[7.5]PB[Black engine]PL[B]RE[W+R]SZ[9]VW[];B[dg];C[comment
|
||||
on two lines]W[ef]
|
||||
;B[];C[Nonfinal comment]VW[aa:bb](;B[ia];W[ib];B[ic])(;B[ib];W[ic](;B[id])(;
|
||||
B[ie])))
|
||||
"""))
|
||||
sgf_game2 = sgf.Sgf_game.from_string(serialised)
|
||||
tc.assertEqual(map(str, sgf_game.get_main_sequence()),
|
||||
map(str, sgf_game2.get_main_sequence()))
|
||||
|
||||
def test_serialise_wrap(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
|
||||
serialised = sgf_game.serialise(wrap=None)
|
||||
tc.assertEqual(serialised, dedent("""\
|
||||
(;FF[4]AB[ai][bh][ee]AP[testsuite:0]AW[fd][gc]CA[utf-8]DT[2009-06-06]GM[1]KM[7.5]PB[Black engine]PL[B]RE[W+R]SZ[9]VW[];B[dg];C[comment
|
||||
on two lines]W[ef];B[];C[Nonfinal comment]VW[aa:bb](;B[ia];W[ib];B[ic])(;B[ib];W[ic](;B[id])(;B[ie])))
|
||||
"""))
|
||||
sgf_game2 = sgf.Sgf_game.from_string(serialised)
|
||||
tc.assertEqual(map(str, sgf_game.get_main_sequence()),
|
||||
map(str, sgf_game2.get_main_sequence()))
|
||||
|
||||
def test_encoding(tc):
|
||||
g1 = sgf.Sgf_game(19)
|
||||
tc.assertEqual(g1.get_charset(), "UTF-8")
|
||||
root = g1.get_root()
|
||||
tc.assertEqual(root.get_encoding(), "UTF-8")
|
||||
root.set("C", "£")
|
||||
tc.assertEqual(root.get("C"), "£")
|
||||
tc.assertEqual(root.get_raw("C"), "£")
|
||||
tc.assertEqual(g1.serialise(), dedent("""\
|
||||
(;FF[4]C[£]CA[UTF-8]GM[1]SZ[19])
|
||||
"""))
|
||||
|
||||
g2 = sgf.Sgf_game(19, encoding="iso-8859-1")
|
||||
tc.assertEqual(g2.get_charset(), "ISO-8859-1")
|
||||
root = g2.get_root()
|
||||
tc.assertEqual(root.get_encoding(), "ISO-8859-1")
|
||||
root.set("C", "£")
|
||||
tc.assertEqual(root.get("C"), "£")
|
||||
tc.assertEqual(root.get_raw("C"), "\xa3")
|
||||
tc.assertEqual(g2.serialise(), dedent("""\
|
||||
(;FF[4]C[\xa3]CA[ISO-8859-1]GM[1]SZ[19])
|
||||
"""))
|
||||
|
||||
tc.assertRaisesRegexp(ValueError, "unknown encoding: unknownencoding",
|
||||
sgf.Sgf_game, 19, "unknownencoding")
|
||||
|
||||
|
||||
def test_loaded_sgf_game_encoding(tc):
|
||||
g1 = sgf.Sgf_game.from_string("""
|
||||
(;FF[4]C[£]CA[utf-8]GM[1]SZ[19])
|
||||
""")
|
||||
tc.assertEqual(g1.get_charset(), "UTF-8")
|
||||
root = g1.get_root()
|
||||
tc.assertEqual(root.get_encoding(), "UTF-8")
|
||||
tc.assertEqual(root.get("C"), "£")
|
||||
tc.assertEqual(root.get_raw("C"), "£")
|
||||
tc.assertEqual(g1.serialise(), dedent("""\
|
||||
(;FF[4]C[£]CA[utf-8]GM[1]SZ[19])
|
||||
"""))
|
||||
|
||||
g2 = sgf.Sgf_game.from_string("""
|
||||
(;FF[4]C[\xa3]CA[iso-8859-1]GM[1]SZ[19])
|
||||
""")
|
||||
tc.assertEqual(g2.get_charset(), "ISO-8859-1")
|
||||
root = g2.get_root()
|
||||
tc.assertEqual(root.get_encoding(), "ISO-8859-1")
|
||||
tc.assertEqual(root.get("C"), "£")
|
||||
tc.assertEqual(root.get_raw("C"), "\xa3")
|
||||
tc.assertEqual(g2.serialise(), dedent("""\
|
||||
(;FF[4]C[\xa3]CA[iso-8859-1]GM[1]SZ[19])
|
||||
"""))
|
||||
|
||||
g3 = sgf.Sgf_game.from_string("""
|
||||
(;FF[4]C[\xa3]GM[1]SZ[19])
|
||||
""")
|
||||
tc.assertEqual(g3.get_charset(), "ISO-8859-1")
|
||||
root = g3.get_root()
|
||||
tc.assertEqual(root.get_encoding(), "ISO-8859-1")
|
||||
tc.assertEqual(root.get("C"), "£")
|
||||
tc.assertEqual(root.get_raw("C"), "\xa3")
|
||||
tc.assertEqual(g3.serialise(), dedent("""\
|
||||
(;FF[4]C[\xa3]GM[1]SZ[19])
|
||||
"""))
|
||||
|
||||
# This is invalidly encoded. get() notices, but serialise() doesn't care.
|
||||
g4 = sgf.Sgf_game.from_string("""
|
||||
(;FF[4]C[\xa3]CA[utf-8]GM[1]SZ[19])
|
||||
""")
|
||||
tc.assertEqual(g4.get_charset(), "UTF-8")
|
||||
root = g4.get_root()
|
||||
tc.assertEqual(root.get_encoding(), "UTF-8")
|
||||
tc.assertRaises(UnicodeDecodeError, root.get, "C")
|
||||
tc.assertEqual(root.get_raw("C"), "\xa3")
|
||||
tc.assertEqual(g4.serialise(), dedent("""\
|
||||
(;FF[4]C[\xa3]CA[utf-8]GM[1]SZ[19])
|
||||
"""))
|
||||
|
||||
tc.assertRaisesRegexp(
|
||||
ValueError, "unknown encoding: unknownencoding",
|
||||
sgf.Sgf_game.from_string, """
|
||||
(;FF[4]CA[unknownencoding]GM[1]SZ[19])
|
||||
""")
|
||||
|
||||
def test_override_encoding(tc):
|
||||
g1 = sgf.Sgf_game.from_string("""
|
||||
(;FF[4]C[£]CA[iso-8859-1]GM[1]SZ[19])
|
||||
""", override_encoding="utf-8")
|
||||
root = g1.get_root()
|
||||
tc.assertEqual(root.get_encoding(), "UTF-8")
|
||||
tc.assertEqual(root.get("C"), "£")
|
||||
tc.assertEqual(root.get_raw("C"), "£")
|
||||
tc.assertEqual(g1.serialise(), dedent("""\
|
||||
(;FF[4]C[£]CA[UTF-8]GM[1]SZ[19])
|
||||
"""))
|
||||
|
||||
g2 = sgf.Sgf_game.from_string("""
|
||||
(;FF[4]C[\xa3]CA[utf-8]GM[1]SZ[19])
|
||||
""", override_encoding="iso-8859-1")
|
||||
root = g2.get_root()
|
||||
tc.assertEqual(root.get_encoding(), "ISO-8859-1")
|
||||
tc.assertEqual(root.get("C"), "£")
|
||||
tc.assertEqual(root.get_raw("C"), "\xa3")
|
||||
tc.assertEqual(g2.serialise(), dedent("""\
|
||||
(;FF[4]C[\xa3]CA[ISO-8859-1]GM[1]SZ[19])
|
||||
"""))
|
||||
|
||||
def test_serialise_transcoding(tc):
|
||||
g1 = sgf.Sgf_game.from_string("""
|
||||
(;FF[4]C[£]CA[utf-8]GM[1]SZ[19])
|
||||
""")
|
||||
tc.assertEqual(g1.serialise(), dedent("""\
|
||||
(;FF[4]C[£]CA[utf-8]GM[1]SZ[19])
|
||||
"""))
|
||||
g1.get_root().set("CA", "latin-1")
|
||||
tc.assertEqual(g1.serialise(), dedent("""\
|
||||
(;FF[4]C[\xa3]CA[latin-1]GM[1]SZ[19])
|
||||
"""))
|
||||
g1.get_root().set("CA", "unknown")
|
||||
tc.assertRaisesRegexp(ValueError, "unsupported charset: \['unknown']",
|
||||
g1.serialise)
|
||||
|
||||
# improperly-encoded from the start
|
||||
g2 = sgf.Sgf_game.from_string("""
|
||||
(;FF[4]C[£]CA[ascii]GM[1]SZ[19])
|
||||
""")
|
||||
tc.assertEqual(g2.serialise(), dedent("""\
|
||||
(;FF[4]C[£]CA[ascii]GM[1]SZ[19])
|
||||
"""))
|
||||
g2.get_root().set("CA", "utf-8")
|
||||
tc.assertRaises(UnicodeDecodeError, g2.serialise)
|
||||
|
||||
g3 = sgf.Sgf_game.from_string("""
|
||||
(;FF[4]C[Δ]CA[utf-8]GM[1]SZ[19])
|
||||
""")
|
||||
g3.get_root().unset("CA")
|
||||
tc.assertRaises(UnicodeEncodeError, g3.serialise)
|
||||
|
||||
def test_tree_mutation(tc):
|
||||
sgf_game = sgf.Sgf_game(9)
|
||||
root = sgf_game.get_root()
|
||||
n1 = root.new_child()
|
||||
n1.set("N", "n1")
|
||||
n2 = root.new_child()
|
||||
n2.set("N", "n2")
|
||||
n3 = n1.new_child()
|
||||
n3.set("N", "n3")
|
||||
n4 = root.new_child(1)
|
||||
n4.set("N", "n4")
|
||||
tc.assertEqual(
|
||||
sgf_game.serialise(),
|
||||
"(;FF[4]CA[UTF-8]GM[1]SZ[9](;N[n1];N[n3])(;N[n4])(;N[n2]))\n")
|
||||
tc.assertEqual(
|
||||
[node.get_raw_property_map() for node in sgf_game.main_sequence_iter()],
|
||||
[node.get_raw_property_map() for node in root, root[0], n3])
|
||||
tc.assertIs(sgf_game.get_last_node(), n3)
|
||||
|
||||
n1.delete()
|
||||
tc.assertEqual(
|
||||
sgf_game.serialise(),
|
||||
"(;FF[4]CA[UTF-8]GM[1]SZ[9](;N[n4])(;N[n2]))\n")
|
||||
tc.assertRaises(ValueError, root.delete)
|
||||
|
||||
def test_tree_mutation_from_coarse_game(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string("(;SZ[9](;N[n1];N[n3])(;N[n2]))")
|
||||
root = sgf_game.get_root()
|
||||
n4 = root.new_child()
|
||||
n4.set("N", "n4")
|
||||
n3 = root[0][0]
|
||||
tc.assertEqual(n3.get("N"), "n3")
|
||||
n5 = n3.new_child()
|
||||
n5.set("N", "n5")
|
||||
tc.assertEqual(sgf_game.serialise(),
|
||||
"(;SZ[9](;N[n1];N[n3];N[n5])(;N[n2])(;N[n4]))\n")
|
||||
tc.assertEqual(
|
||||
[node.get_raw_property_map() for node in sgf_game.main_sequence_iter()],
|
||||
[node.get_raw_property_map() for node in root, root[0], n3, n5])
|
||||
tc.assertIs(sgf_game.get_last_node(), n5)
|
||||
n3.delete()
|
||||
tc.assertEqual(sgf_game.serialise(),
|
||||
"(;SZ[9](;N[n1])(;N[n2])(;N[n4]))\n")
|
||||
tc.assertRaises(ValueError, root.delete)
|
||||
|
||||
def test_reparent(tc):
|
||||
g1 = sgf.Sgf_game.from_string("(;SZ[9](;N[n1];N[n3])(;N[n2]))")
|
||||
root = g1.get_root()
|
||||
# Test with unexpanded root
|
||||
tc.assertRaisesRegexp(ValueError, "would create a loop",
|
||||
root.reparent, root)
|
||||
n1 = root[0]
|
||||
n2 = root[1]
|
||||
n3 = root[0][0]
|
||||
tc.assertEqual(n1.get("N"), "n1")
|
||||
tc.assertEqual(n2.get("N"), "n2")
|
||||
tc.assertEqual(n3.get("N"), "n3")
|
||||
n3.reparent(n2)
|
||||
tc.assertEqual(g1.serialise(), "(;SZ[9](;N[n1])(;N[n2];N[n3]))\n")
|
||||
n3.reparent(n2)
|
||||
tc.assertEqual(g1.serialise(), "(;SZ[9](;N[n1])(;N[n2];N[n3]))\n")
|
||||
tc.assertRaisesRegexp(ValueError, "would create a loop",
|
||||
root.reparent, n3)
|
||||
tc.assertRaisesRegexp(ValueError, "would create a loop",
|
||||
n3.reparent, n3)
|
||||
g2 = sgf.Sgf_game(9)
|
||||
tc.assertRaisesRegexp(
|
||||
ValueError, "new parent doesn't belong to the same game",
|
||||
n3.reparent, g2.get_root())
|
||||
|
||||
def test_reparent_index(tc):
|
||||
g1 = sgf.Sgf_game.from_string("(;SZ[9](;N[n1];N[n3])(;N[n2]))")
|
||||
root = g1.get_root()
|
||||
n1 = root[0]
|
||||
n2 = root[1]
|
||||
n3 = root[0][0]
|
||||
tc.assertEqual(n1.get("N"), "n1")
|
||||
tc.assertEqual(n2.get("N"), "n2")
|
||||
tc.assertEqual(n3.get("N"), "n3")
|
||||
n3.reparent(root, index=1)
|
||||
tc.assertEqual(g1.serialise(), "(;SZ[9](;N[n1])(;N[n3])(;N[n2]))\n")
|
||||
n3.reparent(root, index=1)
|
||||
tc.assertEqual(g1.serialise(), "(;SZ[9](;N[n1])(;N[n3])(;N[n2]))\n")
|
||||
n3.reparent(root, index=2)
|
||||
tc.assertEqual(g1.serialise(), "(;SZ[9](;N[n1])(;N[n2])(;N[n3]))\n")
|
||||
|
||||
def test_extend_main_sequence(tc):
|
||||
g1 = sgf.Sgf_game(9)
|
||||
for i in xrange(6):
|
||||
g1.extend_main_sequence().set("N", "e%d" % i)
|
||||
tc.assertEqual(
|
||||
g1.serialise(),
|
||||
"(;FF[4]CA[UTF-8]GM[1]SZ[9];N[e0];N[e1];N[e2];N[e3];N[e4];N[e5])\n")
|
||||
g2 = sgf.Sgf_game.from_string("(;SZ[9](;N[n1];N[n3])(;N[n2]))")
|
||||
for i in xrange(6):
|
||||
g2.extend_main_sequence().set("N", "e%d" % i)
|
||||
tc.assertEqual(
|
||||
g2.serialise(),
|
||||
"(;SZ[9](;N[n1];N[n3];N[e0];N[e1];N[e2];N[e3];N[e4];N[e5])(;N[n2]))\n")
|
||||
|
||||
|
||||
def test_get_sequence_above(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
|
||||
root = sgf_game.get_root()
|
||||
branchnode = root[0][0][0][0]
|
||||
leaf = branchnode[1][0][1]
|
||||
tc.assertEqual(sgf_game.get_sequence_above(root), [])
|
||||
|
||||
tc.assertEqual(sgf_game.get_sequence_above(branchnode),
|
||||
[root, root[0], root[0][0], root[0][0][0]])
|
||||
|
||||
tc.assertEqual(sgf_game.get_sequence_above(leaf),
|
||||
[root, root[0], root[0][0], root[0][0][0],
|
||||
branchnode, branchnode[1], branchnode[1][0]])
|
||||
|
||||
sgf_game2 = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
|
||||
tc.assertRaisesRegexp(ValueError, "node doesn't belong to this game",
|
||||
sgf_game2.get_sequence_above, leaf)
|
||||
|
||||
def test_get_main_sequence_below(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
|
||||
root = sgf_game.get_root()
|
||||
branchnode = root[0][0][0][0]
|
||||
leaf = branchnode[1][0][1]
|
||||
tc.assertEqual(sgf_game.get_main_sequence_below(leaf), [])
|
||||
|
||||
tc.assertEqual(sgf_game.get_main_sequence_below(branchnode),
|
||||
[branchnode[0], branchnode[0][0], branchnode[0][0][0]])
|
||||
|
||||
tc.assertEqual(sgf_game.get_main_sequence_below(root),
|
||||
[root[0], root[0][0], root[0][0][0], branchnode,
|
||||
branchnode[0], branchnode[0][0], branchnode[0][0][0]])
|
||||
|
||||
sgf_game2 = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
|
||||
tc.assertRaisesRegexp(ValueError, "node doesn't belong to this game",
|
||||
sgf_game2.get_main_sequence_below, branchnode)
|
||||
|
||||
def test_main_sequence(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
|
||||
root = sgf_game.get_root()
|
||||
|
||||
nodes = list(sgf_game.main_sequence_iter())
|
||||
tc.assertEqual(len(nodes), 8)
|
||||
tc.assertIs(root.get_raw_property_map(),
|
||||
nodes[0].get_raw_property_map())
|
||||
# Check that main_sequence_iter() optimisation has been used.
|
||||
# (Have to call this before making the tree expand.)
|
||||
with tc.assertRaises(AttributeError):
|
||||
nodes[1].parent
|
||||
|
||||
tree_nodes = sgf_game.get_main_sequence()
|
||||
tc.assertEqual(len(tree_nodes), 8)
|
||||
tc.assertIs(root.get_raw_property_map(),
|
||||
tree_nodes[0].get_raw_property_map())
|
||||
tc.assertIs(tree_nodes[0], root)
|
||||
tc.assertIs(tree_nodes[2].parent, tree_nodes[1])
|
||||
tc.assertIs(sgf_game.get_last_node(), tree_nodes[-1])
|
||||
|
||||
tree_node = root
|
||||
for node in nodes:
|
||||
tc.assertIs(tree_node.get_raw_property_map(),
|
||||
node.get_raw_property_map())
|
||||
if tree_node:
|
||||
tree_node = tree_node[0]
|
||||
|
||||
def test_find(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(SAMPLE_SGF_VAR)
|
||||
root = sgf_game.get_root()
|
||||
branchnode = root[0][0][0][0]
|
||||
leaf = branchnode[1][0][1]
|
||||
|
||||
tc.assertEqual(root.get("VW"), set())
|
||||
tc.assertIs(root.find("VW"), root)
|
||||
tc.assertRaises(KeyError, root[0].get, "VW")
|
||||
tc.assertEqual(root[0].find_property("VW"), set())
|
||||
tc.assertIs(root[0].find("VW"), root)
|
||||
|
||||
tc.assertEqual(branchnode.get("VW"),
|
||||
set([(7, 0), (7, 1), (8, 0), (8, 1)]))
|
||||
tc.assertIs(branchnode.find("VW"), branchnode)
|
||||
tc.assertEqual(branchnode.find_property("VW"),
|
||||
set([(7, 0), (7, 1), (8, 0), (8, 1)]))
|
||||
|
||||
tc.assertRaises(KeyError, leaf.get, "VW")
|
||||
tc.assertIs(leaf.find("VW"), branchnode)
|
||||
tc.assertEqual(leaf.find_property("VW"),
|
||||
set([(7, 0), (7, 1), (8, 0), (8, 1)]))
|
||||
|
||||
tc.assertIs(leaf.find("XX"), None)
|
||||
tc.assertRaises(KeyError, leaf.find_property, "XX")
|
||||
|
||||
def test_node_set_raw(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string(dedent(r"""
|
||||
(;AP[testsuite:0]CA[utf-8]DT[2009-06-06]FF[4]GM[1]KM[7.5]
|
||||
PB[Black engine]PW[White engine]RE[W+R]SZ[9]
|
||||
AB[ai][bh][ee]AW[fd][gc]BM[2]VW[]
|
||||
PL[B]
|
||||
C[123abc]
|
||||
;B[dg]C[first move])
|
||||
"""))
|
||||
root = sgf_game.get_root()
|
||||
tc.assertEqual(root.get_raw('RE'), "W+R")
|
||||
root.set_raw('RE', "W+2.5")
|
||||
tc.assertEqual(root.get_raw('RE'), "W+2.5")
|
||||
tc.assertRaises(KeyError, root.get_raw, 'XX')
|
||||
root.set_raw('XX', "xyz")
|
||||
tc.assertEqual(root.get_raw('XX'), "xyz")
|
||||
|
||||
root.set_raw_list('XX', ("abc", "def"))
|
||||
tc.assertEqual(root.get_raw('XX'), "abc")
|
||||
tc.assertEqual(root.get_raw_list('XX'), ["abc", "def"])
|
||||
|
||||
tc.assertRaisesRegexp(ValueError, "empty property list",
|
||||
root.set_raw_list, 'B', [])
|
||||
|
||||
values = ["123", "456"]
|
||||
root.set_raw_list('YY', values)
|
||||
tc.assertEqual(root.get_raw_list('YY'), ["123", "456"])
|
||||
values.append("789")
|
||||
tc.assertEqual(root.get_raw_list('YY'), ["123", "456"])
|
||||
|
||||
tc.assertRaisesRegexp(ValueError, "ill-formed property identifier",
|
||||
root.set_raw, 'Black', "aa")
|
||||
tc.assertRaisesRegexp(ValueError, "ill-formed property identifier",
|
||||
root.set_raw_list, 'Black', ["aa"])
|
||||
|
||||
root.set_raw('C', "foo\\]bar")
|
||||
tc.assertEqual(root.get_raw('C'), "foo\\]bar")
|
||||
root.set_raw('C', "abc\\\\")
|
||||
tc.assertEqual(root.get_raw('C'), "abc\\\\")
|
||||
tc.assertRaisesRegexp(ValueError, "ill-formed raw property value",
|
||||
root.set_raw, 'C', "foo]bar")
|
||||
tc.assertRaisesRegexp(ValueError, "ill-formed raw property value",
|
||||
root.set_raw, 'C', "abc\\")
|
||||
tc.assertRaisesRegexp(ValueError, "ill-formed raw property value",
|
||||
root.set_raw_list, 'C', ["abc", "de]f"])
|
||||
|
||||
root.set_raw('C', "foo\\]bar\\\nbaz")
|
||||
tc.assertEqual(root.get('C'), "foo]barbaz")
|
||||
|
||||
|
||||
def test_node_aliasing(tc):
|
||||
# Check that node objects retrieved by different means use the same
|
||||
# property map.
|
||||
|
||||
sgf_game = sgf.Sgf_game.from_string(dedent(r"""
|
||||
(;C[root];C[node 1])
|
||||
"""))
|
||||
root = sgf_game.get_root()
|
||||
plain_node = list(sgf_game.main_sequence_iter())[1]
|
||||
tree_node = root[0]
|
||||
# Check the main_sequence_iter() optimisation was used, otherwise this test
|
||||
# isn't checking what it's supposed to.
|
||||
tc.assertIsNot(tree_node, plain_node)
|
||||
tc.assertIs(tree_node.__class__, sgf.Tree_node)
|
||||
tc.assertIs(plain_node.__class__, sgf.Node)
|
||||
|
||||
tc.assertEqual(tree_node.get_raw('C'), "node 1")
|
||||
tree_node.set_raw('C', r"test\value")
|
||||
tc.assertEqual(tree_node.get_raw('C'), r"test\value")
|
||||
tc.assertEqual(plain_node.get_raw('C'), r"test\value")
|
||||
|
||||
plain_node.set_raw_list('XX', ["1", "2", "3"])
|
||||
tc.assertEqual(tree_node.get_raw_list('XX'), ["1", "2", "3"])
|
||||
|
||||
def test_node_set(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9])")
|
||||
root = sgf_game.get_root()
|
||||
root.set("KO", True)
|
||||
root.set("KM", 0.5)
|
||||
root.set('DD', [(3, 4), (5, 6)])
|
||||
root.set('AB', set([(0, 0), (1, 1), (4, 4)]))
|
||||
root.set('TW', set())
|
||||
root.set('XX', "nonsense [none]sense more n\\onsens\\e")
|
||||
|
||||
tc.assertEqual(sgf_game.serialise(), dedent("""\
|
||||
(;FF[4]AB[ai][bh][ee]DD[ef][gd]GM[1]KM[0.5]KO[]SZ[9]TW[]
|
||||
XX[nonsense [none\\]sense more n\\\\onsens\\\\e])
|
||||
"""))
|
||||
|
||||
def test_node_unset(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9]HA[3])")
|
||||
root = sgf_game.get_root()
|
||||
tc.assertEqual(root.get('HA'), 3)
|
||||
root.unset('HA')
|
||||
tc.assertRaises(KeyError, root.unset, 'PL')
|
||||
tc.assertEqual(sgf_game.serialise(),
|
||||
"(;FF[4]GM[1]SZ[9])\n")
|
||||
|
||||
def test_set_and_unset_size(tc):
|
||||
g1 = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9]HA[3])")
|
||||
root1 = g1.get_root()
|
||||
tc.assertRaisesRegexp(ValueError, "changing size is not permitted",
|
||||
root1.set, "SZ", 19)
|
||||
root1.set("SZ", 9)
|
||||
tc.assertRaisesRegexp(ValueError, "changing size is not permitted",
|
||||
root1.unset, "SZ")
|
||||
g2 = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[19]HA[3])")
|
||||
root2 = g2.get_root()
|
||||
root2.unset("SZ")
|
||||
root2.set("SZ", 19)
|
||||
|
||||
def test_set_and_unset_charset(tc):
|
||||
g1 = sgf.Sgf_game.from_string("(;FF[4]CA[utf-8]GM[1]SZ[9]HA[3])")
|
||||
tc.assertEqual(g1.get_charset(), "UTF-8")
|
||||
root1 = g1.get_root()
|
||||
root1.unset("CA")
|
||||
tc.assertEqual(g1.get_charset(), "ISO-8859-1")
|
||||
root1.set("CA", "iso-8859-1")
|
||||
tc.assertEqual(g1.get_charset(), "ISO-8859-1")
|
||||
root1.set("CA", "ascii")
|
||||
tc.assertEqual(g1.get_charset(), "ASCII")
|
||||
root1.set("CA", "unknownencoding")
|
||||
tc.assertRaisesRegexp(ValueError,
|
||||
"no codec available for CA unknownencoding",
|
||||
g1.get_charset)
|
||||
|
||||
def test_node_set_move(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9];B[aa];B[bb])")
|
||||
root, n1, n2 = sgf_game.get_main_sequence()
|
||||
tc.assertEqual(root.get_move(), (None, None))
|
||||
root.set_move('b', (1, 1))
|
||||
n1.set_move('w', (1, 2))
|
||||
n2.set_move('b', None)
|
||||
tc.assertEqual(root.get('B'), (1, 1))
|
||||
tc.assertRaises(KeyError, root.get, 'W')
|
||||
tc.assertEqual(n1.get('W'), (1, 2))
|
||||
tc.assertRaises(KeyError, n1.get, 'B')
|
||||
tc.assertEqual(n2.get('B'), None)
|
||||
tc.assertRaises(KeyError, n2.get, 'W')
|
||||
|
||||
def test_node_setup_stones(tc):
|
||||
sgf_game = sgf.Sgf_game.from_string("(;FF[4]GM[1]SZ[9]AW[aa:bb])")
|
||||
root = sgf_game.get_root()
|
||||
root.set_setup_stones(
|
||||
[(1, 2), (3, 4)],
|
||||
set(),
|
||||
[(1, 3), (4, 5)],
|
||||
)
|
||||
tc.assertEqual(root.get('AB'), set([(1, 2), (3, 4)]))
|
||||
tc.assertRaises(KeyError, root.get, 'AW')
|
||||
tc.assertEqual(root.get('AE'), set([(1, 3), (4, 5)]))
|
||||
|
||||
def test_add_comment_text(tc):
|
||||
sgf_game = sgf.Sgf_game(9)
|
||||
root = sgf_game.get_root()
|
||||
root.add_comment_text("hello\nworld")
|
||||
tc.assertEqual(root.get('C'), "hello\nworld")
|
||||
root.add_comment_text("hello\naga]in")
|
||||
tc.assertEqual(root.get('C'), "hello\nworld\n\nhello\naga]in")
|
||||
|
19
gomill/gomill_tests/subprocess_state_reporter.py
Normal file
19
gomill/gomill_tests/subprocess_state_reporter.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""Fake GTP engine that reports info about cwd and environment.
|
||||
|
||||
This is used by gtp_controller_tests.test_subprocess_channel
|
||||
|
||||
This mustn't import any gomill or gomill_tests code.
|
||||
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
|
||||
def main():
|
||||
sys.stderr.write("subprocess_state_reporter: testing\n")
|
||||
# Read the GTP command
|
||||
sys.stdin.readline()
|
||||
sys.stdout.write("= cwd: %s\nGOMILL_TEST:%s\n\n" %
|
||||
(os.getcwd(), os.environ.get("GOMILL_TEST")))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
158
gomill/gomill_tests/test_framework.py
Normal file
158
gomill/gomill_tests/test_framework.py
Normal file
@ -0,0 +1,158 @@
|
||||
"""Generic (non-gomill-specific) test framework code."""
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (2, 7):
|
||||
import unittest as unittest2
|
||||
else:
|
||||
try:
|
||||
import unittest2
|
||||
except ImportError, e:
|
||||
e.unittest2_missing = True
|
||||
raise
|
||||
|
||||
# This makes TestResult ignore lines from this module in tracebacks
|
||||
__unittest = True
|
||||
|
||||
class SupporterError(StandardError):
|
||||
"""Exception raised by support objects when something goes wrong.
|
||||
|
||||
This is raised to indicate things like sequencing errors detected by mock
|
||||
objects.
|
||||
|
||||
"""
|
||||
|
||||
class FrameworkTestCase(unittest2.TestCase):
|
||||
"""unittest2-style TestCase implementation with a few tweaks."""
|
||||
|
||||
# This is default in unittest2 but not python 2.7 unittest, so force it on.
|
||||
longMessage = True
|
||||
|
||||
def assertItemsEqual(self, expected_seq, actual_seq, msg=None):
|
||||
"""Variant implementation of standard assertItemsEqual.
|
||||
|
||||
This uses the unorderable_list_difference check even if the lists are
|
||||
sortable: I prefer its output.
|
||||
|
||||
"""
|
||||
expected = list(expected_seq)
|
||||
actual = list(actual_seq)
|
||||
missing, unexpected = unittest2.util.unorderable_list_difference(
|
||||
expected, actual, ignore_duplicate=False
|
||||
)
|
||||
errors = []
|
||||
if missing:
|
||||
errors.append('Expected, but missing:\n %s' %
|
||||
unittest2.util.safe_repr(missing))
|
||||
if unexpected:
|
||||
errors.append('Unexpected, but present:\n %s' %
|
||||
unittest2.util.safe_repr(unexpected))
|
||||
if errors:
|
||||
standardMsg = '\n'.join(errors)
|
||||
self.fail(self._formatMessage(msg, standardMsg))
|
||||
|
||||
|
||||
class SimpleTestCase(FrameworkTestCase):
|
||||
"""TestCase which runs a single function.
|
||||
|
||||
Instantiate with the test function, which takes a TestCase parameter, eg:
|
||||
def test_xxx(tc):
|
||||
tc.assertEqual(2+2, 4)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, fn):
|
||||
FrameworkTestCase.__init__(self)
|
||||
self.fn = fn
|
||||
try:
|
||||
self.name = fn.__module__.split(".", 1)[-1] + "." + fn.__name__
|
||||
except AttributeError:
|
||||
self.name = str(fn)
|
||||
|
||||
def runTest(self):
|
||||
self.fn(self)
|
||||
|
||||
def id(self):
|
||||
return self.name
|
||||
|
||||
def shortDescription(self):
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
return "<SimpleTestCase: %s>" % self.name
|
||||
|
||||
|
||||
class ParameterisedTestCase(FrameworkTestCase):
|
||||
"""Parameterised testcase.
|
||||
|
||||
Subclasses should define:
|
||||
test_name -- short string
|
||||
parameter_names -- list of identifiers
|
||||
runTest
|
||||
|
||||
"""
|
||||
def __init__(self, code, *parameters):
|
||||
FrameworkTestCase.__init__(self)
|
||||
self.code = code
|
||||
self.name = "%s.%s:%s" % (self.__class__.__module__.split(".", 1)[-1],
|
||||
self.test_name, code)
|
||||
for name, value in zip(self.parameter_names, parameters):
|
||||
setattr(self, name, value)
|
||||
|
||||
def runTest(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def id(self):
|
||||
return self.name
|
||||
|
||||
def shortDescription(self):
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s>" % (self.__class__.__name__, self.name)
|
||||
|
||||
|
||||
|
||||
def _function_sort_key(fn):
|
||||
try:
|
||||
return fn.__code__.co_firstlineno
|
||||
except AttributeError:
|
||||
return str(fn)
|
||||
|
||||
def make_simple_tests(source, prefix="test_", testcase_class=SimpleTestCase):
|
||||
"""Make test cases from a module's test_xxx functions.
|
||||
|
||||
source -- dict (usually a module's globals()).
|
||||
prefix -- string (default "test_")
|
||||
testcase_class -- SimpleTestCase subclass to use
|
||||
|
||||
Returns a list of TestCase objects.
|
||||
|
||||
This makes a TestCase for each function in the values of 'source' whose
|
||||
name begins with 'prefix'.
|
||||
|
||||
The list is in the order of function definition (using the line number
|
||||
attribute).
|
||||
|
||||
"""
|
||||
functions = [value for name, value in source.iteritems()
|
||||
if name.startswith(prefix) and callable(value)]
|
||||
functions.sort(key=_function_sort_key)
|
||||
return [testcase_class(fn) for fn in functions]
|
||||
|
||||
|
||||
class Fixture(object):
|
||||
"""A testing fixture.
|
||||
|
||||
Instantiate fixture objects with a TestCase parameter.
|
||||
|
||||
The fixture arranges for any necessary cleanup to be performed by calling
|
||||
TestCase.addCleanUp.
|
||||
|
||||
"""
|
78
gomill/gomill_tests/test_support.py
Normal file
78
gomill/gomill_tests/test_support.py
Normal file
@ -0,0 +1,78 @@
|
||||
"""Generic (non-gomill-specific) test support code."""
|
||||
|
||||
import errno
|
||||
from cStringIO import StringIO
|
||||
|
||||
from gomill_tests.test_framework import SupporterError
|
||||
|
||||
class Mock_writing_pipe(object):
|
||||
"""Mock writeable pipe object, with an interface like a cStringIO.
|
||||
|
||||
If this is 'broken', it raises IOError(EPIPE) on any further writes.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.sink = StringIO()
|
||||
self.is_broken = False
|
||||
|
||||
def write(self, s):
|
||||
if self.is_broken:
|
||||
raise IOError(errno.EPIPE, "Broken pipe")
|
||||
try:
|
||||
self.sink.write(s)
|
||||
except ValueError, e:
|
||||
raise IOError(errno.EIO, str(e))
|
||||
|
||||
def flush(self):
|
||||
self.sink.flush()
|
||||
|
||||
def close(self):
|
||||
self.sink.close()
|
||||
|
||||
def simulate_broken_pipe(self):
|
||||
self.is_broken = True
|
||||
|
||||
def getvalue(self):
|
||||
return self.sink.getvalue()
|
||||
|
||||
|
||||
class Mock_reading_pipe(object):
|
||||
"""Mock readable pipe object, with an interface like a cStringIO.
|
||||
|
||||
Instantiate with the data to provide on the pipe.
|
||||
|
||||
If this is 'broken', it always returns EOF from that point on.
|
||||
|
||||
Set the attribute hangs_before_eof true to simulate a pipe that isn't closed
|
||||
when it runs out of data.
|
||||
|
||||
"""
|
||||
def __init__(self, response):
|
||||
self.source = StringIO(response)
|
||||
self.is_broken = False
|
||||
self.hangs_before_eof = False
|
||||
|
||||
def read(self, n):
|
||||
if self.is_broken:
|
||||
return ""
|
||||
result = self.source.read(n)
|
||||
if self.hangs_before_eof and result == "":
|
||||
raise SupporterError("read called with no data; this would hang")
|
||||
return result
|
||||
|
||||
def readline(self):
|
||||
if self.is_broken:
|
||||
return ""
|
||||
result = self.source.readline()
|
||||
if self.hangs_before_eof and not result.endswith("\n"):
|
||||
raise SupporterError(
|
||||
"readline called with no newline; this would hang")
|
||||
return result
|
||||
|
||||
def close(self):
|
||||
self.source.close()
|
||||
|
||||
def simulate_broken_pipe(self):
|
||||
self.is_broken = True
|
||||
|
||||
|
63
gomill/gomill_tests/utils_tests.py
Normal file
63
gomill/gomill_tests/utils_tests.py
Normal file
@ -0,0 +1,63 @@
|
||||
"""Tests for utils.py."""
|
||||
|
||||
from gomill_tests import gomill_test_support
|
||||
|
||||
from gomill import utils
|
||||
|
||||
def make_tests(suite):
|
||||
suite.addTests(gomill_test_support.make_simple_tests(globals()))
|
||||
|
||||
|
||||
def test_format_float(tc):
|
||||
ff = utils.format_float
|
||||
tc.assertEqual(ff(1), "1")
|
||||
tc.assertEqual(ff(1.0), "1")
|
||||
tc.assertEqual(ff(1.5), "1.5")
|
||||
|
||||
def test_format_percent(tc):
|
||||
pct = utils.format_percent
|
||||
tc.assertEqual(pct(1, 1), "100.00%")
|
||||
tc.assertEqual(pct(1, 2), "50.00%")
|
||||
tc.assertEqual(pct(1.0, 2.0), "50.00%")
|
||||
tc.assertEqual(pct(1, 3), "33.33%")
|
||||
tc.assertEqual(pct(0, 3), "0.00%")
|
||||
tc.assertEqual(pct(2, 0), "??")
|
||||
tc.assertEqual(pct(0, 0), "--")
|
||||
|
||||
def test_sanitise_utf8(tc):
|
||||
su = utils.sanitise_utf8
|
||||
tc.assertIsNone(su(None))
|
||||
tc.assertEqual(su(""), "")
|
||||
tc.assertEqual(su("hello world"), "hello world")
|
||||
s = u"test \N{POUND SIGN}".encode("utf-8")
|
||||
tc.assertIs(su(s), s)
|
||||
tc.assertEqual(su(u"test \N{POUND SIGN}".encode("latin1")), "test ?")
|
||||
|
||||
def test_isinf(tc):
|
||||
tc.assertIs(utils.isinf(0), False)
|
||||
tc.assertIs(utils.isinf(0.0), False)
|
||||
tc.assertIs(utils.isinf(3), False)
|
||||
tc.assertIs(utils.isinf(3.0), False)
|
||||
tc.assertIs(utils.isinf(1e300), False)
|
||||
tc.assertIs(utils.isinf(1e400), True)
|
||||
tc.assertIs(utils.isinf(-1e300), False)
|
||||
tc.assertIs(utils.isinf(-1e400), True)
|
||||
tc.assertIs(utils.isinf(1e-300), False)
|
||||
tc.assertIs(utils.isinf(1e-400), False)
|
||||
tc.assertIs(utils.isinf(float("inf")), True)
|
||||
tc.assertIs(utils.isinf(float("-inf")), True)
|
||||
tc.assertIs(utils.isinf(float("NaN")), False)
|
||||
|
||||
def test_nan(tc):
|
||||
tc.assertIs(utils.isnan(0), False)
|
||||
tc.assertIs(utils.isnan(0.0), False)
|
||||
tc.assertIs(utils.isnan(1e300), False)
|
||||
tc.assertIs(utils.isnan(1e400), False)
|
||||
tc.assertIs(utils.isnan(-1e300), False)
|
||||
tc.assertIs(utils.isnan(-1e400), False)
|
||||
tc.assertIs(utils.isnan(1e-300), False)
|
||||
tc.assertIs(utils.isnan(1e-400), False)
|
||||
tc.assertIs(utils.isnan(float("inf")), False)
|
||||
tc.assertIs(utils.isnan(float("-inf")), False)
|
||||
tc.assertIs(utils.isnan(float("NaN")), True)
|
||||
|
Reference in New Issue
Block a user