699 lines
30 KiB
Python
699 lines
30 KiB
Python
|
"""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'))
|
||
|
|