This commit is contained in:
AlxAI 2025-02-06 14:33:10 -08:00
parent e8530a146d
commit 93c073e2df
295 changed files with 86794 additions and 0 deletions

View file

@ -0,0 +1,16 @@
# ==============================================================================
# Copyright (C) 2019 - Philip Paquette
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.
# ==============================================================================

View file

@ -0,0 +1 @@
# Empty file to make tests a package

View file

@ -0,0 +1,73 @@
import os
import unittest
from lm_service_versus import (
OpenAIClient,
ClaudeClient,
GeminiClient,
DeepSeekClient
)
class TestOpenAIClient(unittest.TestCase):
def setUp(self):
self.model_name = "gpt-4o-mini" # or "o3-mini", etc.
self.client = OpenAIClient(self.model_name)
def test_openai_key_exists(self):
self.assertIsNotNone(os.environ.get("OPENAI_API_KEY"),
"OPENAI_API_KEY is not set in the environment.")
def test_openai_basic_response(self):
"""Integration test: calls the LLM with a minimal prompt."""
prompt = "Hello from unit test. Please respond with a short phrase."
response = self.client.generate_response(prompt)
self.assertTrue(len(response) > 0, "OpenAI returned an empty response.")
class TestClaudeClient(unittest.TestCase):
def setUp(self):
self.model_name = "claude-3-5-sonnet-20241022" # or "claude-3-5-sonnet-20241022"
self.client = ClaudeClient(self.model_name)
def test_claude_key_exists(self):
self.assertIsNotNone(os.environ.get("ANTHROPIC_API_KEY"),
"ANTHROPIC_API_KEY is not set in the environment.")
def test_claude_basic_response(self):
prompt = "Hello from unit test. Please respond with a short phrase."
response = self.client.generate_response(prompt)
self.assertTrue(len(response) > 0, "Claude returned an empty response.")
class TestGeminiClient(unittest.TestCase):
def setUp(self):
self.model_name = "gemini-1.5-flash"
self.client = GeminiClient(self.model_name)
def test_gemini_key_exists(self):
self.assertIsNotNone(os.environ.get("GEMINI_API_KEY"),
"GEMINI_API_KEY is not set in the environment.")
def test_gemini_basic_response(self):
prompt = "Hello from unit test. Please respond with a short phrase."
response = self.client.generate_response(prompt)
self.assertTrue(len(response) > 0, "Gemini returned an empty response.")
class TestDeepSeekClient(unittest.TestCase):
def setUp(self):
self.model_name = "deepseek-reasoner"
self.client = DeepSeekClient(self.model_name)
def test_deepseek_key_exists(self):
self.assertIsNotNone(os.environ.get("DEEPSEEK_API_KEY"),
"DEEPSEEK_API_KEY is not set in the environment.")
def test_deepseek_basic_response(self):
prompt = "Hello from unit test. Please respond with a short phrase."
response = self.client.generate_response(prompt)
self.assertTrue(len(response) > 0, "DeepSeek returned an empty response.")
if __name__ == '__main__':
unittest.main()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,16 @@
# ==============================================================================
# Copyright (C) 2019 - Philip Paquette
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.
# ==============================================================================

View file

@ -0,0 +1,82 @@
#!/usr/bin/env python3
# ==============================================================================
# Copyright (C) 2019 - Philip Paquette
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.
# ==============================================================================
""" Run tests from diplomacy.tests.network.test_real_game to test games in a real environment.
Each test run a game and checks game messages and phases against an expected game data file.
Current tested gama data files are JSON files located into folder diplomacy/tests/network:
- 1.json
- 2.json
- 3.json
Need a local diplomacy server running. You must specify
this server port using parameter ``--port=<server_port>``.
To run all tests: ::
python -m diplomacy.tests.network.run_real_game --port=<server_port>
To run a specific test (e.g. 2.json, or 2.json and 1.json): ::
python -m diplomacy.tests.network.run_real_game --cases=2 --port=<server_port>
python -m diplomacy.tests.network.run_real_game --cases=1,2 --port=<server_port>
For help: ::
python -m diplomacy.tests.network.run_real_game --help
"""
import argparse
from tornado import gen
from tornado.ioloop import IOLoop
from diplomacy.tests.network import test_real_game
def launch_case(case_name, port, io_loop):
""" Launch a game case. """
case_data = test_real_game.CaseData(case_name, port=port)
case_data.io_loop = io_loop
return test_real_game.main(case_data)
def main():
""" Main function for this module. Load and run tests.
Each test run a game and checks game messages and phases against an expected game data file.
Current tested gama data files are JSON files located into folder diplomacy/tests/network.
"""
parser = argparse.ArgumentParser(description='Run test cases against an external server to connect.')
parser.add_argument('--port', type=int, required=True,
help='run on the given port (required)')
parser.add_argument('--cases', action='append',
help="Run given cases. "
"Each case <C> must match a test case file <C>.json located in diplomacy.tests.network. "
"If not provided, all available cases are run.")
args = parser.parse_args()
io_loop = IOLoop()
io_loop.make_current()
@gen.coroutine
def run():
""" Run all tests consecutively in one call. """
tests = set(args.cases) if args.cases else {'1', '2', '3'}
for test_case in list(sorted(tests)):
yield launch_case('%s.json' % test_case, args.port, io_loop)
io_loop.run_sync(run)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,630 @@
# ==============================================================================
# Copyright (C) 2019 - Philip Paquette
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.
# ==============================================================================
""" Test server game in real environment with test data in files `{15, 20, 23}.json`. """
# pylint: disable=unused-argument
import logging
import os
import random
from typing import Dict
from tornado import gen
from tornado.concurrent import Future
from tornado.ioloop import IOLoop
import ujson as json
from diplomacy.client.connection import connect
from diplomacy.server.server import Server
from diplomacy.engine.game import Game
from diplomacy.engine.map import Map
from diplomacy.engine.message import GLOBAL, Message as EngineMessage
from diplomacy.utils import common, constants, strings
LOGGER = logging.getLogger('diplomacy.tests.network.test_real_game')
DEFAULT_HOSTNAME = 'localhost'
DEFAULT_PORT = random.randint(9000, 10000)
class ExpectedPhase:
""" Helper class to manage data from an expected phase. """
__slots__ = ['name', 'state', 'orders', 'messages']
def __init__(self, json_phase):
""" Initialize expected phase.
:param json_phase: JSON dict representing a phase. Expected fields: name, state, orders, messages.
"""
self.name = json_phase['name']
self.state = json_phase['state']
self.orders = json_phase['orders']
self.messages = [EngineMessage(**json_message) for json_message in json_phase['messages']]
self.messages.sort(key=lambda msg: msg.time_sent)
def get_power_orders(self, power_name):
""" Return expected orders for given power name. """
return self.orders[power_name]
def get_power_related_messages(self, power_name):
""" Return expected messages for given power name. """
return [message for message in self.messages
if message.sender == power_name or message.recipient in (power_name, GLOBAL)]
class ExpectedMessages:
""" Expected list of messages sent and received by a power name. """
__slots__ = ['power_name', 'messages', 'next_messages_to_send']
def __init__(self, power_name, messages):
""" Initialize the expected messages.
:param power_name: power name which exchanges these messages
:param messages: messages exchanged
"""
self.power_name = power_name
self.messages = messages # type: [EngineMessage]
self.next_messages_to_send = []
def has_messages_to_receive(self):
""" Return True if messages list still contains messages to receive. """
return any(message.sender != self.power_name for message in self.messages)
def has_messages_to_send(self):
""" Return True if messages list still contains messages to send. """
return any(message.sender == self.power_name for message in self.messages)
def move_forward(self):
""" Move next messages to send from messages list to sending queue (self.next_messages_to_send). """
self.next_messages_to_send.clear()
if self.messages:
if self.messages[0].sender != self.power_name:
# First message in stack is a message to receive. We cannot send any message
# until all messages to receive at top of stack were indeed received.
return
next_message_to_receive = len(self.messages)
for index, message in enumerate(self.messages):
if message.sender != self.power_name:
next_message_to_receive = index
break
self.next_messages_to_send.extend(self.messages[:next_message_to_receive])
del self.messages[:next_message_to_receive]
class ExpectedData:
""" Expected data for a power in a game. """
__slots__ = ['messages', 'phases', '__phase_index', 'playing']
def __init__(self, power_name, phases, phase_index):
""" Initialize expected data for a game power.
:param power_name: name of power for which those data are expected.
:param phases: list of expected phases.
:param phase_index: index of current expected phase in given phases.
:type power_name: str
:type phases: list[ExpectedPhase]
:type phase_index: int
"""
self.messages = ExpectedMessages(power_name, phases[phase_index].get_power_related_messages(power_name))
self.phases = phases
self.__phase_index = phase_index
self.playing = False
power_name = property(lambda self: self.messages.power_name)
phase_index = property(lambda self: self.__phase_index)
expected_phase = property(lambda self: self.phases[self.__phase_index])
def move_forward(self):
""" Move to next expected phase. """
self.__phase_index += 1
if self.__phase_index < len(self.phases):
self.messages = ExpectedMessages(
self.messages.power_name, self.phases[self.__phase_index].get_power_related_messages(self.power_name))
class CaseData:
""" Helper class to store test data. """
FILE_FOLDER_NAME = os.path.abspath(os.path.dirname(__file__))
def __init__(self, case_file_name, hostname=DEFAULT_HOSTNAME, port=DEFAULT_PORT):
""" Initialize game test.
:param case_file_name: File name of JSON file containing expected game data.
JSON file must be located in folder FILE_FOLDER_NAME.
:param hostname: hostname to use to load server.
:param port: port to use to load server.
"""
full_file_path = os.path.join(self.FILE_FOLDER_NAME, case_file_name)
with open(full_file_path, 'rb') as file:
data = json.load(file)
self.case_name = case_file_name
self.map_name = data['map']
self.phases = [ExpectedPhase(json_phase) for json_phase in data['phases']]
self.rules = set(data['rules'])
self.rules.add('POWER_CHOICE')
self.rules.add('REAL_TIME')
self.test_server = None
self.io_loop = None # type: IOLoop
self.connection = None
self.admin_channel = None
self.admin_game = None
self.user_games = {}
self.future_games_ended = {} # type: Dict[str, Future]
self.hostname = hostname
self.port = port
def terminate_game(self, power_name):
""" Tell Tornado that a power game is finished. """
self.future_games_ended[power_name].set_result(None)
@gen.coroutine
def on_power_phase_update(self, game, notification=None):
""" User game notification callback for game phase updated.
:param game: game
:param notification: notification
:type game: NetworkGame
:type notification: diplomacy.communication.notifications.GameProcessed | None
"""
print('We changed phase for power', game.power.name)
expected_data = game.data # type: ExpectedData
expected_data.move_forward()
if expected_data.phase_index >= len(expected_data.phases):
assert expected_data.phase_index == len(expected_data.phases)
self.terminate_game(game.data.power_name)
print('Game fully terminated at phase', game.phase)
else:
yield verify_current_phase(game)
@gen.coroutine
def on_power_state_update(self, game, notification):
""" User game notification callback for game state update.
:param game: game
:param notification: notification
:type game: NetworkGame
:type notification: diplomacy.communication.notifications.GamePhaseUpdate
"""
if notification.phase_data_type == strings.PHASE:
yield self.on_power_phase_update(game, None)
@gen.coroutine
def send_messages_if_needed(game, expected_messages):
""" Take messages to send in top of given messages list and send them.
:param game: a NetworkGame object.
:param expected_messages: an instance of ExpectedMessages.
:type game: NetworkGame
:type expected_messages: ExpectedMessages
"""
power_name = game.power.name
if expected_messages.messages:
expected_messages.move_forward()
for message in expected_messages.next_messages_to_send:
if message.recipient == GLOBAL:
print('%s/sending global message (time %d)' % (power_name, message.time_sent))
yield game.send_game_message(message=game.new_global_message(message.message))
print('%s/sent global message (time %d)' % (power_name, message.time_sent))
else:
print('%s/sending message to %s (time %d)' % (power_name, message.recipient, message.time_sent))
yield game.send_game_message(message=game.new_power_message(
message.recipient, message.message))
print('%s/sent message to %s (time %d)' % (power_name, message.recipient, message.time_sent))
expected_messages.next_messages_to_send.clear()
@gen.coroutine
def send_current_orders(game):
""" Send expected orders for current phase.
:param game: a Network game object.
:type game: NetworkGame
"""
expected_data = game.data # type: ExpectedData
orders_to_send = expected_data.expected_phase.get_power_orders(expected_data.power_name)
if orders_to_send is None:
orders_to_send = []
if not orders_to_send and not game.get_orderable_locations(expected_data.power_name):
print('%s/no need to send empty orders for unorderable power at phase %s' % (
expected_data.power_name, expected_data.expected_phase.name))
return
print('%s/sending %d orders for phase %s: %s' % (expected_data.power_name, len(orders_to_send),
expected_data.expected_phase.name, orders_to_send))
yield game.set_orders(orders=orders_to_send)
print('%s/sent orders for phase %s' % (expected_data.power_name, expected_data.expected_phase.name))
def on_message_received(game, notification):
""" User game notification callback for messages received.
:param game: a NetworkGame object,
:param notification: a notification received by this game.
:type game: NetworkGame
:type notification: diplomacy.communication.notifications.GameMessageReceived
"""
power_name = game.power.name
messages = game.data.messages # type: ExpectedMessages
if not messages.has_messages_to_receive():
raise AssertionError('%s/should not receive more messages.' % power_name)
power_from = notification.message.sender
index_found = None
for index, expected_message in enumerate(messages.messages):
if expected_message.recipient == power_from:
raise AssertionError(
'%s/there are still messages to send to %s (%d) before receiving messages from him. Received: %s'
% (power_name, power_from, expected_message.time_sent, notification.message.message))
if expected_message.sender == power_from:
if notification.message.is_global():
if not (expected_message.recipient == GLOBAL
and expected_message.message == notification.message.message):
raise AssertionError(
'%s/first expected message from %s does not match received global message: %s'
% (power_name, power_from, notification.message.message))
else:
if not (expected_message.recipient == notification.message.recipient
and expected_message.message == notification.message.message):
raise AssertionError(
'%s/first expected message from %s does not match received power message: to %s: %s'
% (power_name, power_from, notification.message.recipient, notification.message.message))
index_found = index
break
if index_found is None:
raise AssertionError('%s/Received unknown message from %s to %s: %s' % (
power_name, notification.message.sender, notification.message.recipient, notification.message.message))
expected_message = messages.messages.pop(index_found)
print('%s/checked message (time %d)' % (power_name, expected_message.time_sent))
def on_admin_game_phase_update(admin_game, notification=None):
""" Admin game notification callback for game phase update.
:param admin_game: admin game
:param notification: notification
:type admin_game: NetworkGame
:type notification: diplomacy.communication.notifications.GameProcessed | None
"""
assert admin_game.is_omniscient_game()
expected_data = admin_game.data # type: ExpectedData
expected_data.move_forward()
print('=' * 80)
print('We changed phase for admin game, moving from phase', expected_data.phase_index,
'to phase', (expected_data.phase_index + 1), '/', len(expected_data.phases))
print('=' * 80)
# state_history must not be empty.
assert len(admin_game.state_history) == expected_data.phase_index, (
len(admin_game.state_history), expected_data.phase_index)
# Verify previous game state.
if admin_game.state_history:
expected_state = expected_data.phases[expected_data.phase_index - 1].state
expected_engine = Game(initial_state=expected_state)
given_state = admin_game.state_history.last_value()
given_engine = Game(initial_state=given_state)
print('Verifying expected previous phase', expected_engine.get_current_phase())
print('Verifying game processing from previous phase to next phase.')
other_expected_engine = Game(initial_state=expected_state)
other_expected_engine.process()
other_given_engine = Game(initial_state=given_state)
other_given_engine.rules.append('SOLITAIRE')
other_given_engine.process()
assert other_expected_engine.get_current_phase() == other_given_engine.get_current_phase(), (
'Computed expected next phase %s, got computed given next phase %s'
% (other_expected_engine.get_current_phase(), other_given_engine.get_current_phase())
)
assert expected_engine.map_name == given_engine.map_name
assert expected_engine.get_current_phase() == given_engine.get_current_phase()
expected_orders = expected_engine.get_orders()
given_orders = given_engine.get_orders()
assert len(expected_orders) == len(given_orders), (expected_orders, given_orders)
for power_name in given_orders:
assert power_name in expected_orders, power_name
given_power_orders = list(sorted(given_orders[power_name]))
expected_power_orders = list(sorted(expected_orders[power_name]))
assert expected_power_orders == given_power_orders, (
'Power orders for %s\nExpected: %s\nGiven: %s\nAll given: %s\n'
% (power_name, expected_power_orders, given_power_orders, given_orders))
expected_units = expected_engine.get_units()
given_units = expected_engine.get_units()
assert len(expected_units) == len(given_units)
for power_name in given_units:
assert power_name in expected_units, (power_name, expected_units, given_units)
expected_power_units = list(sorted(expected_units[power_name]))
given_power_units = list(sorted(given_units[power_name]))
assert expected_power_units == given_power_units, (
power_name, expected_power_units, given_power_units, given_units)
expected_centers = expected_engine.get_centers()
given_centers = given_engine.get_centers()
assert len(expected_centers) == len(given_centers), (expected_centers, given_centers)
for power_name in given_centers:
assert power_name in expected_centers
expected_power_centers = list(sorted(expected_centers[power_name]))
given_power_centers = list(sorted(given_centers[power_name]))
assert expected_power_centers == given_power_centers, (
power_name, expected_power_centers, given_power_centers)
assert expected_engine.get_hash() == given_engine.get_hash(), (
expected_engine.get_hash(), given_engine.get_hash())
if expected_data.phase_index >= len(expected_data.phases):
assert expected_data.phase_index == len(expected_data.phases)
assert admin_game.state_history.last_value()['name'] == expected_data.phases[-1].name, (
'Wrong last phase, expected %s, got %s'
% (admin_game.state_history.last_value()['name'], expected_data.phases[-1].name)
)
print('Admin game terminated.')
def on_admin_game_state_update(admin_game, notification):
""" Admin game notification callback for game state update.
:param admin_game: admin game
:param notification: notification
:type admin_game: NetworkGame
:type notification: diplomacy.communication.notifications.GamePhaseUpdate
"""
if notification.phase_data_type == strings.PHASE:
on_admin_game_phase_update(admin_game, None)
def on_admin_powers_controllers(admin_game, notification):
""" Admin game notification callback for powers controllers received (unexpected).
:param admin_game: game
:param notification: notification
:type admin_game: NetworkGame
:type notification: diplomacy.communication.notifications.PowersControllers
"""
LOGGER.warning('%d dummy power(s).',
len([controller for controller in notification.powers.values() if controller == strings.DUMMY]))
def on_admin_game_status_update(admin_game, notification):
""" Admin game notification callback for game status update.
:param admin_game: admin game
:param notification: notification
:type admin_game: NetworkGame
"""
print('(admin game) game status of %s updated to %s' % (admin_game.role, admin_game.status))
@gen.coroutine
def play_phase(game, expected_messages):
""" Play a phase for a user game:
#. Send messages
#. wait for messages to receive
#. send current orders.
:param game: user game
:param expected_messages: expected messages
:type game: NetworkGame
:type expected_messages: ExpectedMessages
"""
while expected_messages.has_messages_to_send():
yield gen.sleep(10e-6)
yield send_messages_if_needed(game, expected_messages)
while expected_messages.has_messages_to_receive():
yield gen.sleep(10e-6)
yield send_current_orders(game)
@gen.coroutine
def on_game_status_update(game, notification):
""" User game notification callback for game status update.
Used to start the game locally when game started on server.
:param game: game
:param notification: notification
:type game: NetworkGame
:type notification: diplomacy.communication.notifications.GameStatusUpdate
"""
LOGGER.warning('Game status of %s updated to %s', game.role, game.status)
expected_data = game.data # type: ExpectedData
if not expected_data.playing and game.is_game_active:
# Game started on server.
expected_data.playing = True
print('Playing.')
yield play_phase(game, expected_data.messages)
@gen.coroutine
def verify_current_phase(game):
""" Check and play current phase.
:param game: a NetWork game object.
:type game: NetworkGame
"""
expected_data = game.data # type: ExpectedData
# Verify current phase.
expected_messages = expected_data.messages
print('=' * 80)
print('Checking expected phase', expected_data.expected_phase.name,
'(%d/%d) for' % (expected_data.phase_index + 1, len(expected_data.phases)), expected_data.power_name,
'with', len(expected_data.messages.messages), 'messages.')
print('=' * 80)
# Verify phase name.
if game.current_short_phase != str(expected_data.expected_phase.name):
raise AssertionError(str(expected_data.expected_phase.name), str(game.current_short_phase))
if game.is_game_active:
yield play_phase(game, expected_messages)
def get_user_game_fn(case_data, power_name):
""" Return a coroutine procedure that loads and play a user game for given power name.
:param case_data: case data
:param power_name: str
:return: a procedure.
:type case_data: CaseData
"""
@gen.coroutine
def load_fn():
""" Coroutine for loading power game for given power name. """
yield load_power_game(case_data, power_name)
return load_fn
def get_future_game_done_fn(power_name):
""" Return a callback to call when a power game is finished.
Callback currently just prints a message to tell that power game is terminated.
:param power_name: power name of associated game.
:return: a callable that receives the future done when game is finished.
"""
def game_done_fn(future):
""" Function called when related game is done. """
print('Game ended (%s).' % power_name)
return game_done_fn
@gen.coroutine
def load_power_game(case_data, power_name):
""" Load and play a power game from admin game for given power name.
:type case_data: CaseData
"""
print('Loading game for power', power_name)
username = 'user_%s' % power_name
password = 'password_%s' % power_name
user_channel = yield case_data.connection.authenticate(username, password)
print('User', username, 'connected.')
user_game = yield user_channel.join_game(game_id=case_data.admin_game.game_id, power_name=power_name)
assert user_game.is_player_game()
assert user_game.power.name == power_name
case_data.user_games[power_name] = user_game
print('Game created for user %s.' % username, len(user_game.messages), len(user_game.state_history))
# Set notification callback for user game to manage messages received.
user_game.add_on_game_status_update(on_game_status_update)
user_game.add_on_game_message_received(on_message_received)
user_game.add_on_game_processed(case_data.on_power_phase_update)
user_game.add_on_game_phase_update(case_data.on_power_state_update)
# Save expected data into attribute user_game.data.
user_game.data = ExpectedData(power_name=power_name, phases=case_data.phases, phase_index=0)
# Start to play and test game.
yield verify_current_phase(user_game)
@gen.coroutine
def main(case_data):
""" Test real game environment with one game and all power controlled (no dummy powers).
This method may be called form a non-test code to run a real game case.
:param case_data: test data
:type case_data: CaseData
"""
# ================
# Initialize test.
# ================
if case_data.admin_channel is None:
LOGGER.info('Creating connection, admin channel and admin game.')
case_data.connection = yield connect(case_data.hostname, case_data.port)
case_data.admin_channel = yield case_data.connection.authenticate('admin', 'password')
# NB: For all test cases, first game state should be default game engine state when starting.
# So, we don't need to pass game state of first expected phase when creating a server game.
case_data.admin_game = yield case_data.admin_channel.create_game(
map_name=case_data.map_name, rules=case_data.rules, deadline=0)
assert case_data.admin_game.power_choice
assert case_data.admin_game.real_time
case_data.admin_game.data = ExpectedData(power_name='', phases=case_data.phases, phase_index=0)
case_data.admin_game.add_on_game_status_update(on_admin_game_status_update)
case_data.admin_game.add_on_game_processed(on_admin_game_phase_update)
case_data.admin_game.add_on_game_phase_update(on_admin_game_state_update)
case_data.admin_game.add_on_powers_controllers(on_admin_powers_controllers)
# ==========
# Test game.
# ==========
# Get available maps to retrieve map power names.
available_maps = yield case_data.admin_channel.get_available_maps()
print('Map: %s, powers:' % case_data.map_name,
', '.join(power_name for power_name in sorted(available_maps[case_data.map_name])))
# Load one game per power name.
for power_name in available_maps[case_data.map_name]['powers']:
case_data.future_games_ended[power_name] = Future()
case_data.future_games_ended[power_name].add_done_callback(get_future_game_done_fn(power_name))
case_data.io_loop.add_callback(get_user_game_fn(case_data, power_name))
# Wait to let power games play.
print('Running ...')
yield case_data.future_games_ended
print('All game terminated. Just wait a little ...')
yield gen.sleep(2)
print('End running.')
def run(case_data, **server_kwargs):
""" Real test function called for a given case data.
Load a server (with optional given server kwargs),
call function main(case_data) as client code
and wait for main function to terminate.
:type case_data: CaseData
"""
print()
io_loop = IOLoop()
io_loop.make_current()
common.Tornado.stop_loop_on_callback_error(io_loop)
case_data.io_loop = io_loop
case_data.test_server = Server(**server_kwargs)
@gen.coroutine
def coroutine_func():
""" Concrete call to main function. """
yield main(case_data)
case_data.io_loop.stop()
print('Finished', case_data.case_name, 'at', common.timestamp_microseconds())
io_loop.add_callback(coroutine_func)
case_data.test_server.start(case_data.port, io_loop)
case_data.io_loop.clear_current()
case_data.io_loop.close()
case_data.test_server.backend.http_server.stop()
def test_maps():
""" Building required maps to avoid timeout on the primary test """
for map_name in ('ancmed', 'colonial', 'empire', 'known_world_901', 'modern', 'standard',
'standard_france_austria', 'standard_germany_italy', 'world'):
Map(map_name)
def test_3():
""" Test case 3. """
case_data = CaseData('3.json')
run(case_data, ping_seconds=constants.DEFAULT_PING_SECONDS)
# We must clear server caches to allow to re-create a Server with same test case but different server attributes.
Server.__cache__.clear()
def test_3_ping_1s():
""" Test case 3 with small ping (1 second). """
case_data = CaseData('3.json')
run(case_data, ping_seconds=1)
# We must clear server caches to allow to re-create a Server with same test case but different server attributes.
Server.__cache__.clear()

5467
diplomacy/tests/test_datc.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,106 @@
# ==============================================================================
# Copyright (C) 2019 - Philip Paquette
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.
# ==============================================================================
""" DATC Test Cases (Using rule NO_CHECK)
- Contains the diplomacy adjudication test cases (without order validation)
"""
from diplomacy.engine.game import Game
from diplomacy.tests.test_datc import TestDATC as RootDATC
from diplomacy.utils.order_results import OK, VOID
# -----------------
# DATC TEST CASES (Without order validation)
# -----------------
class TestDATCNoCheck(RootDATC):
""" DATC test cases"""
@staticmethod
def create_game():
""" Creates a game object"""
game = Game()
game.add_rule('NO_CHECK')
return game
@staticmethod
def check_results(game, unit, value, phase='M'):
""" Checks adjudication results """
# pylint: disable=too-many-return-statements
if not game:
return False
result = game.result_history.last_value()
# Checking if the results contain duplicate values
unit_result = result.get(unit, [])
if len(unit_result) != len(set(unit_result)):
raise RuntimeError('Duplicate values detected in %s' % unit_result)
# Done self.processing a retreats phase
if phase == 'R':
if value == VOID and VOID in unit_result:
return True
if value == OK:
success = unit not in game.popped and unit_result == []
if not success:
print('Results: %s - Expected: []' % result.get(unit, '<Not Found>'))
return success
success = unit in game.popped and value in unit_result
if not success:
print('Results: %s - Expected: %s' % (result.get(unit, '<Not Found>'), value))
return success
# Done self.processing a retreats phase
if phase == 'A':
if value == VOID and VOID in unit_result:
return True
success = value == unit_result
if not success:
print('Results: %s - Expected: %s' % (result.get(unit, '<Not Found>'), value))
return success
order_status = game.get_order_status(unit=unit)
# >>>>>>>>>>>>>>>>>>>>>>>>
# For NO_CHECK, we expect to find the unit in game.orderable_units
# But we require that the order is marked as 'void'
# As opposed to a regular game, where an invalid order is dropped
# <<<<<<<<<<<<<<<<<<<<<<<<
# Invalid order
if value == VOID:
if VOID in result.get(unit, []):
return True
return False
# Invalid unit
if unit not in game.command:
print('Results: %s NOT FOUND - Expected: %s' % (unit, value))
return False
# Expected no errors
if value == OK:
if order_status:
print('Results: %s - Expected: []' % order_status)
return False
return True
# Incorrect error
if value not in game.get_order_status(unit=unit):
print('Results: %s - Expected: %s' % (order_status, value))
return False
# Correct value
return True

View file

@ -0,0 +1,95 @@
# ==============================================================================
# Copyright (C) 2019 - Philip Paquette
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.
# ==============================================================================
""" DATC Test Cases (No Expansion)
- Contains the diplomacy adjudication test cases (without order expansion)
"""
from diplomacy.tests.test_datc import TestDATC as RootDATC
from diplomacy.utils.order_results import OK, BOUNCE, VOID
# -----------------
# DATC TEST CASES (Without order expansion)
# -----------------
class TestDATCNoExpand(RootDATC):
""" DATC test cases"""
@staticmethod
def set_orders(game, power_name, orders):
""" Submit orders """
game.set_orders(power_name, orders, expand=False)
def test_6_b_2(self):
""" 6.B.2. TEST CASE, MOVING WITH UNSPECIFIED COAST WHEN COAST IS NOT NECESSARY
There is only one coast possible in this case:
France: F Gascony - Spain
Since the North Coast is the only coast that can be reached, it seems logical that
the a move is attempted to the north coast of Spain. Some adjudicators require that a coast
is also specified in this case and will decide that the move fails or take a default coast (see 4.B.2).
I prefer that an attempt is made to the only possible coast, the north coast of Spain.
"""
# Expected to failed
def test_6_b_9(self):
""" 6.B.9. TEST CASE, SUPPORTING WITH WRONG COAST
Coasts can be specified in a support, but the result depends on the house rules.
France: F Portugal Supports F Mid-Atlantic Ocean - Spain(nc)
France: F Mid-Atlantic Ocean - Spain(sc)
Italy: F Gulf of Lyon Supports F Western Mediterranean - Spain(sc)
Italy: F Western Mediterranean - Spain(sc)
See issue 4.B.4. If it is required that the coast matches, then the support of the French fleet in the
Mid-Atlantic Ocean fails and that the Italian fleet in the Western Mediterranean moves successfully. Some
adjudicators ignores the coasts in support orders. In that case, the move of the Italian fleet bounces.
I prefer that the support fails and that the Italian fleet in the Western Mediterranean moves successfully.
"""
game = self.create_game()
self.clear_units(game)
self.set_units(game, 'FRANCE', ['F POR', 'F MAO'])
self.set_units(game, 'ITALY', ['F LYO', 'F WES'])
self.set_orders(game, 'FRANCE', ['F POR S F MAO - SPA/NC', 'F MAO - SPA/SC'])
self.set_orders(game, 'ITALY', ['F LYO S F WES - SPA/SC', 'F WES - SPA/SC'])
self.process(game)
assert self.check_results(game, 'F POR', VOID)
assert self.check_results(game, 'F MAO', BOUNCE)
assert self.check_results(game, 'F LYO', OK)
assert self.check_results(game, 'F WES', OK)
assert self.owner_name(game, 'F POR') == 'FRANCE'
assert self.owner_name(game, 'F MAO') == 'FRANCE'
assert self.owner_name(game, 'F SPA') == 'ITALY'
assert self.owner_name(game, 'F SPA/NC') is None
assert self.owner_name(game, 'F SPA/SC') == 'ITALY'
assert self.owner_name(game, 'F LYO') == 'ITALY'
assert self.owner_name(game, 'F WES') is None
def test_6_b_10(self):
""" 6.B.10. TEST CASE, UNIT ORDERED WITH WRONG COAST
A player might specify the wrong coast for the ordered unit.
France has a fleet on the south coast of Spain and orders:
France: F Spain(nc) - Gulf of Lyon
If only perfect orders are accepted, then the move will fail, but since the coast for the ordered unit
has no purpose, it might also be ignored (see issue 4.B.5).
I prefer that a move will be attempted.
"""
# Expected to fail
def test_6_b_12(self):
""" 6.B.12. TEST CASE, ARMY MOVEMENT WITH COASTAL SPECIFICATION
For armies the coasts are irrelevant:
France: A Gascony - Spain(nc)
If only perfect orders are accepted, then the move will fail. But it is also possible that coasts are
ignored in this case and a move will be attempted (see issue 4.B.6).
I prefer that a move will be attempted.
"""
# Expected to fail

View file

@ -0,0 +1,669 @@
# ==============================================================================
# Copyright (C) 2019 - Philip Paquette
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.
# ==============================================================================
""" Test_game
- Contains tests for the game object
"""
from copy import deepcopy
from diplomacy.engine.game import Game
from diplomacy.utils.order_results import BOUNCE
def test_is_game_done():
""" Tests if the game is done """
game = Game()
assert not game.is_game_done
game.phase = 'COMPLETED'
assert game.is_game_done
def test_create_game():
""" Test - Creates a game """
game = Game()
assert not game.error
def test_get_units():
""" Tests - get units """
game = Game()
game.clear_units()
game.set_units('FRANCE', ['A PAR', 'A MAR'])
game.set_units('ENGLAND', ['A PAR', 'A LON'])
units = game.get_units()
assert units['AUSTRIA'] == []
assert units['ENGLAND'] == ['A PAR', 'A LON']
assert units['FRANCE'] == ['A MAR']
assert units['GERMANY'] == []
assert units['ITALY'] == []
assert units['RUSSIA'] == []
assert units['TURKEY'] == []
assert game.get_units('AUSTRIA') == []
assert game.get_units('ENGLAND') == ['A PAR', 'A LON']
assert game.get_units('FRANCE') == ['A MAR']
assert game.get_units('GERMANY') == []
assert game.get_units('ITALY') == []
assert game.get_units('RUSSIA') == []
assert game.get_units('TURKEY') == []
# Making sure we got a copy, and not a direct game reference
game.set_units('FRANCE', ['F MAR'])
units_2 = game.get_units()
assert units['FRANCE'] == ['A MAR']
assert units_2['FRANCE'] == ['F MAR']
def test_get_centers():
""" Test - get centers """
game = Game()
centers = game.get_centers()
assert centers['AUSTRIA'] == ['BUD', 'TRI', 'VIE']
assert centers['ENGLAND'] == ['EDI', 'LON', 'LVP']
assert centers['FRANCE'] == ['BRE', 'MAR', 'PAR']
assert centers['GERMANY'] == ['BER', 'KIE', 'MUN']
assert centers['ITALY'] == ['NAP', 'ROM', 'VEN']
assert centers['RUSSIA'] == ['MOS', 'SEV', 'STP', 'WAR']
assert centers['TURKEY'] == ['ANK', 'CON', 'SMY']
assert game.get_centers('AUSTRIA') == ['BUD', 'TRI', 'VIE']
assert game.get_centers('ENGLAND') == ['EDI', 'LON', 'LVP']
assert game.get_centers('FRANCE') == ['BRE', 'MAR', 'PAR']
assert game.get_centers('GERMANY') == ['BER', 'KIE', 'MUN']
assert game.get_centers('ITALY') == ['NAP', 'ROM', 'VEN']
assert game.get_centers('RUSSIA') == ['MOS', 'SEV', 'STP', 'WAR']
assert game.get_centers('TURKEY') == ['ANK', 'CON', 'SMY']
# Making sure we got a copy, and not a direct game reference
austria = game.get_power('AUSTRIA')
austria.centers.remove('BUD')
centers_2 = game.get_centers()
assert centers['AUSTRIA'] == ['BUD', 'TRI', 'VIE']
assert centers_2['AUSTRIA'] == ['TRI', 'VIE']
def test_get_orders():
""" Test - get orders """
check_sorted = lambda list_1, list_2: sorted(list_1) == sorted(list_2)
game = Game()
# Movement phase
game.set_orders('FRANCE', ['A PAR H', 'A MAR - BUR'])
game.set_orders('ENGLAND', ['LON H'])
orders = game.get_orders()
assert check_sorted(orders['AUSTRIA'], [])
assert check_sorted(orders['ENGLAND'], ['F LON H'])
assert check_sorted(orders['FRANCE'], ['A PAR H', 'A MAR - BUR'])
assert check_sorted(orders['GERMANY'], [])
assert check_sorted(orders['ITALY'], [])
assert check_sorted(orders['RUSSIA'], [])
assert check_sorted(orders['TURKEY'], [])
assert check_sorted(game.get_orders('AUSTRIA'), [])
assert check_sorted(game.get_orders('ENGLAND'), ['F LON H'])
assert check_sorted(game.get_orders('FRANCE'), ['A PAR H', 'A MAR - BUR'])
assert check_sorted(game.get_orders('GERMANY'), [])
assert check_sorted(game.get_orders('ITALY'), [])
assert check_sorted(game.get_orders('RUSSIA'), [])
assert check_sorted(game.get_orders('TURKEY'), [])
# Making sure we got a copy, and not a direct game reference
france = game.get_power('FRANCE')
del france.orders['A PAR']
orders_2 = game.get_orders()
assert check_sorted(orders['FRANCE'], ['A PAR H', 'A MAR - BUR'])
assert check_sorted(orders_2['FRANCE'], ['A MAR - BUR'])
# Moving to W1901A
game.clear_units('FRANCE')
game.set_centers('FRANCE', 'SPA')
game.process()
game.process()
assert game.get_current_phase() == 'W1901A'
# Adjustment phase
game.set_orders('FRANCE', ['A MAR B', 'F MAR B'])
game.set_orders('AUSTRIA', 'A PAR H')
orders = game.get_orders()
assert check_sorted(orders['AUSTRIA'], [])
assert check_sorted(orders['ENGLAND'], [])
assert check_sorted(orders['FRANCE'], ['A MAR B'])
assert check_sorted(orders['GERMANY'], [])
assert check_sorted(orders['ITALY'], [])
assert check_sorted(orders['RUSSIA'], [])
assert check_sorted(orders['TURKEY'], [])
assert check_sorted(game.get_orders('AUSTRIA'), [])
assert check_sorted(game.get_orders('ENGLAND'), [])
assert check_sorted(game.get_orders('FRANCE'), ['A MAR B'])
assert check_sorted(game.get_orders('GERMANY'), [])
assert check_sorted(game.get_orders('ITALY'), [])
assert check_sorted(game.get_orders('RUSSIA'), [])
assert check_sorted(game.get_orders('TURKEY'), [])
def test_get_orders_no_check():
""" Test - get orders NO_CHECK """
check_sorted = lambda list_1, list_2: sorted(list_1) == sorted(list_2)
game = Game()
game.add_rule('NO_CHECK')
# Movement phase
game.set_orders('FRANCE', ['A PAR H', 'A MAR - BUR'])
game.set_orders('ENGLAND', ['LON H'])
orders = game.get_orders()
assert check_sorted(orders['AUSTRIA'], [])
assert check_sorted(orders['ENGLAND'], ['LON H']) # Should not be fixed
assert check_sorted(orders['FRANCE'], ['A PAR H', 'A MAR - BUR'])
assert check_sorted(orders['GERMANY'], [])
assert check_sorted(orders['ITALY'], [])
assert check_sorted(orders['RUSSIA'], [])
assert check_sorted(orders['TURKEY'], [])
assert check_sorted(game.get_orders('AUSTRIA'), [])
assert check_sorted(game.get_orders('ENGLAND'), ['LON H']) # Should not be fixed
assert check_sorted(game.get_orders('FRANCE'), ['A PAR H', 'A MAR - BUR'])
assert check_sorted(game.get_orders('GERMANY'), [])
assert check_sorted(game.get_orders('ITALY'), [])
assert check_sorted(game.get_orders('RUSSIA'), [])
assert check_sorted(game.get_orders('TURKEY'), [])
# Making sure we got a copy, and not a direct game reference
france = game.get_power('FRANCE')
france.orders = {order_ix: order_value for order_ix, order_value in france.orders.items()
if not order_value.startswith('A PAR')}
orders_2 = game.get_orders()
assert check_sorted(orders['FRANCE'], ['A PAR H', 'A MAR - BUR'])
assert check_sorted(orders_2['FRANCE'], ['A MAR - BUR'])
# Moving to W1901A
game.clear_units('FRANCE')
game.set_centers('FRANCE', 'SPA')
game.process()
game.process()
assert game.get_current_phase() == 'W1901A'
# Adjustment phase
game.set_orders('FRANCE', ['A MAR B', 'F MAR B'])
game.set_orders('AUSTRIA', 'A PAR H')
orders = game.get_orders()
assert check_sorted(orders['AUSTRIA'], []) # 'A PAR H' is VOID
assert check_sorted(orders['ENGLAND'], [])
assert check_sorted(orders['FRANCE'], ['A MAR B']) # 'F MAR B' is VOID
assert check_sorted(orders['GERMANY'], [])
assert check_sorted(orders['ITALY'], [])
assert check_sorted(orders['RUSSIA'], [])
assert check_sorted(orders['TURKEY'], [])
assert check_sorted(game.get_orders('AUSTRIA'), [])
assert check_sorted(game.get_orders('ENGLAND'), [])
assert check_sorted(game.get_orders('FRANCE'), ['A MAR B'])
assert check_sorted(game.get_orders('GERMANY'), [])
assert check_sorted(game.get_orders('ITALY'), [])
assert check_sorted(game.get_orders('RUSSIA'), [])
assert check_sorted(game.get_orders('TURKEY'), [])
def test_get_order_status():
""" Tests - get order status """
game = Game()
game.clear_units()
game.set_units('ITALY', 'A VEN')
game.set_units('AUSTRIA', 'A VIE')
game.set_orders('ITALY', 'A VEN - TYR')
game.set_orders('AUSTRIA', 'A VIE - TYR')
game.process()
results = game.get_order_status()
assert BOUNCE in results['ITALY']['A VEN']
assert BOUNCE in results['AUSTRIA']['A VIE']
assert BOUNCE in game.get_order_status(unit='A VEN')
assert BOUNCE in game.get_order_status(unit='A VIE')
assert BOUNCE in game.get_order_status('ITALY')['A VEN']
assert BOUNCE in game.get_order_status('AUSTRIA')['A VIE']
def test_set_units():
""" Test - Sets units """
game = Game()
game.clear_units()
game.set_units('FRANCE', ['A PAR', 'A MAR', '*A GAS'], reset=False)
game.set_units('ENGLAND', ['A PAR', 'A LON'])
assert game.get_power('AUSTRIA').units == []
assert game.get_power('ENGLAND').units == ['A PAR', 'A LON']
assert game.get_power('FRANCE').units == ['A MAR']
assert 'A GAS' in game.get_power('FRANCE').retreats
assert game.get_power('GERMANY').units == []
assert game.get_power('ITALY').units == []
assert game.get_power('RUSSIA').units == []
assert game.get_power('TURKEY').units == []
# Adding F PIC to England without resetting
game.set_units('ENGLAND', ['F PIC'], reset=False)
assert game.get_power('ENGLAND').units == ['A PAR', 'A LON', 'F PIC']
# Adding F PIC to England with resetting
game.set_units('ENGLAND', ['F PIC'], reset=True)
assert game.get_power('ENGLAND').units == ['F PIC']
# Adding F PAR (Illegal unit) to England without resetting
game.set_units('ENGLAND', ['F PAR'], reset=False)
assert game.get_power('ENGLAND').units == ['F PIC']
def test_set_centers():
""" Tests - Sets centers """
game = Game()
game.clear_centers()
game.set_centers('FRANCE', ['PAR', 'MAR', 'GAS']) # GAS is not a valid SC loc
game.set_centers('ENGLAND', ['PAR', 'LON'])
assert game.get_power('AUSTRIA').centers == []
assert game.get_power('ENGLAND').centers == ['PAR', 'LON']
assert game.get_power('FRANCE').centers == ['MAR']
assert game.get_power('GERMANY').centers == []
assert game.get_power('ITALY').centers == []
assert game.get_power('RUSSIA').centers == []
assert game.get_power('TURKEY').centers == []
# Adding BUD to England without resetting
game.set_centers('ENGLAND', 'BUD', reset=False)
assert game.get_power('ENGLAND').centers == ['PAR', 'LON', 'BUD']
# Adding BUD to England with resetting
game.set_centers('ENGLAND', ['BUD'], reset=True)
assert game.get_power('ENGLAND').centers == ['BUD']
# Adding UKR to England (illegal SC)
game.set_centers('ENGLAND', 'UKR', reset=False)
assert game.get_power('ENGLAND').centers == ['BUD']
def test_set_orders():
""" Test - Sets orders """
game = Game()
game.clear_units()
game.set_units('ITALY', 'A VEN')
game.set_units('AUSTRIA', 'A VIE')
game.set_units('FRANCE', 'A PAR')
game.set_orders('ITALY', 'A VEN - TYR')
game.set_orders('AUSTRIA', 'A VIE - TYR')
game.set_orders('FRANCE', ['', '', 'A PAR - GAS', '', '', ''])
game.set_orders('RUSSIA', '')
game.set_orders('GERMANY', [])
assert game.get_orders('FRANCE') == ['A PAR - GAS']
assert not game.get_orders('RUSSIA')
assert not game.get_orders('GERMANY')
game.process()
results = game.get_order_status()
assert BOUNCE in results['ITALY']['A VEN']
assert BOUNCE in results['AUSTRIA']['A VIE']
def test_set_orders_replace():
""" Test - Sets orders with replace=True """
check_sorted = lambda list_1, list_2: sorted(list_1) == sorted(list_2)
# Regular Movement Phase
game = Game()
game.clear_units()
game.set_units('ITALY', ['A VEN', 'A PAR'])
game.set_units('AUSTRIA', 'A VIE')
game.set_orders('ITALY', ['A VEN - TYR', 'A PAR H'])
game.set_orders('AUSTRIA', 'A VIE - TYR')
game.set_orders('ITALY', 'A PAR - GAS')
orders = game.get_orders()
assert check_sorted(orders['ITALY'], ['A VEN - TYR', 'A PAR - GAS'])
assert check_sorted(orders['AUSTRIA'], ['A VIE - TYR'])
# NO_CHECK Movement Phase
game = Game()
game.add_rule('NO_CHECK')
game.clear_units()
game.set_units('ITALY', ['A VEN', 'A PAR'])
game.set_units('AUSTRIA', 'A VIE')
game.set_orders('ITALY', ['A VEN - TYR', 'A PAR H'])
game.set_orders('AUSTRIA', 'A VIE - TYR')
game.set_orders('ITALY', 'A PAR - GAS')
orders = game.get_orders()
assert check_sorted(orders['ITALY'], ['A VEN - TYR', 'A PAR - GAS'])
assert check_sorted(orders['AUSTRIA'], ['A VIE - TYR'])
# Regular Retreat Phase
game = Game()
game.clear_units()
game.set_units('ITALY', ['A BRE', 'A PAR'])
game.set_units('AUSTRIA', 'A GAS')
game.set_orders('ITALY', ['A BRE - GAS', 'A PAR S A BRE - GAS'])
game.set_orders('AUSTRIA', 'A GAS H')
game.process()
game.set_orders('AUSTRIA', 'A GAS R MAR')
game.set_orders('AUSTRIA', 'A GAS R SPA')
orders = game.get_orders()
assert check_sorted(orders['AUSTRIA'], ['A GAS R SPA'])
# NO_CHECK Retreat Phase
game = Game()
game.add_rule('NO_CHECK')
game.clear_units()
game.set_units('ITALY', ['A BRE', 'A PAR'])
game.set_units('AUSTRIA', 'A GAS')
game.set_orders('ITALY', ['A BRE - GAS', 'A PAR S A BRE - GAS'])
game.set_orders('AUSTRIA', 'A GAS H')
game.process()
game.set_orders('AUSTRIA', 'A GAS R MAR')
game.set_orders('AUSTRIA', 'A GAS R SPA')
orders = game.get_orders()
assert check_sorted(orders['AUSTRIA'], ['A GAS R SPA'])
# Regular Adjustment Phase
game = Game()
game.clear_units()
game.clear_centers()
game.set_units('FRANCE', ['A BRE', 'A PAR'])
game.set_units('AUSTRIA', 'A GAS')
game.set_orders('FRANCE', 'A PAR - PIC')
game.process()
game.set_orders('FRANCE', ['A PIC - BEL', 'A BRE - PAR'])
game.process()
game.set_orders('FRANCE', 'A BRE B')
game.set_orders('FRANCE', 'F BRE B')
orders = game.get_orders()
assert check_sorted(orders['FRANCE'], ['F BRE B'])
# NO_CHECK Adjustment Phase
game = Game()
game.add_rule('NO_CHECK')
game.clear_units()
game.clear_centers()
game.set_units('FRANCE', ['A BRE', 'A PAR'])
game.set_units('AUSTRIA', 'A GAS')
game.set_orders('FRANCE', 'A PAR - PIC')
game.process()
game.set_orders('FRANCE', ['A PIC - BEL', 'A BRE - PAR'])
game.process()
game.set_orders('FRANCE', 'A BRE B')
game.set_orders('FRANCE', 'F BRE B')
orders = game.get_orders()
assert check_sorted(orders['FRANCE'], ['F BRE B'])
def test_set_orders_no_replace():
""" Test - Sets orders with replace=False """
check_sorted = lambda list_1, list_2: sorted(list_1) == sorted(list_2)
# Regular Movement Phase
game = Game()
game.clear_units()
game.set_units('ITALY', ['A VEN', 'A PAR'])
game.set_units('AUSTRIA', 'A VIE')
game.set_orders('ITALY', ['A VEN - TYR', 'A PAR H'], replace=False)
game.set_orders('AUSTRIA', 'A VIE - TYR', replace=False)
game.set_orders('ITALY', 'A PAR - GAS', replace=False)
orders = game.get_orders()
assert check_sorted(orders['ITALY'], ['A VEN - TYR', 'A PAR H'])
assert check_sorted(orders['AUSTRIA'], ['A VIE - TYR'])
# NO_CHECK Movement Phase
game = Game()
game.add_rule('NO_CHECK')
game.clear_units()
game.set_units('ITALY', ['A VEN', 'A PAR'])
game.set_units('AUSTRIA', 'A VIE')
game.set_orders('ITALY', ['A VEN - TYR', 'A PAR H'], replace=False)
game.set_orders('AUSTRIA', 'A VIE - TYR', replace=False)
game.set_orders('ITALY', 'A PAR - GAS', replace=False)
orders = game.get_orders()
assert check_sorted(orders['ITALY'], ['A VEN - TYR', 'A PAR H', 'A PAR - GAS'])
assert check_sorted(orders['AUSTRIA'], ['A VIE - TYR'])
# Regular Retreat Phase
game = Game()
game.clear_units()
game.set_units('ITALY', ['A BRE', 'A PAR'])
game.set_units('AUSTRIA', 'A GAS')
game.set_orders('ITALY', ['A BRE - GAS', 'A PAR S A BRE - GAS'], replace=False)
game.set_orders('AUSTRIA', 'A GAS H', replace=False)
game.process()
game.set_orders('AUSTRIA', 'A GAS R MAR', replace=False)
game.set_orders('AUSTRIA', 'A GAS R SPA', replace=False)
orders = game.get_orders()
assert check_sorted(orders['AUSTRIA'], ['A GAS R MAR'])
# NO_CHECK Retreat Phase
game = Game()
game.add_rule('NO_CHECK')
game.clear_units()
game.set_units('ITALY', ['A BRE', 'A PAR'])
game.set_units('AUSTRIA', 'A GAS')
game.set_orders('ITALY', ['A BRE - GAS', 'A PAR S A BRE - GAS'], replace=False)
game.set_orders('AUSTRIA', 'A GAS H', replace=False)
game.process()
game.set_orders('AUSTRIA', 'A GAS R MAR', replace=False)
game.set_orders('AUSTRIA', 'A GAS R SPA', replace=False)
orders = game.get_orders()
assert check_sorted(orders['AUSTRIA'], ['A GAS R MAR'])
# Regular Adjustment Phase
game = Game()
game.clear_units()
game.clear_centers()
game.set_units('FRANCE', ['A BRE', 'A PAR'])
game.set_units('AUSTRIA', 'A GAS')
game.set_orders('FRANCE', 'A PAR - PIC', replace=False)
game.process()
game.set_orders('FRANCE', ['A PIC - BEL', 'A BRE - PAR'], replace=False)
game.process()
game.set_orders('FRANCE', 'A BRE B', replace=False)
game.set_orders('FRANCE', 'F BRE B', replace=False)
orders = game.get_orders()
assert check_sorted(orders['FRANCE'], ['A BRE B'])
# NO_CHECK Adjustment Phase
game = Game()
game.add_rule('NO_CHECK')
game.clear_units()
game.clear_centers()
game.set_units('FRANCE', ['A BRE', 'A PAR'])
game.set_units('AUSTRIA', 'A GAS')
game.set_orders('FRANCE', 'A PAR - PIC', replace=False)
game.process()
game.set_orders('FRANCE', ['A PIC - BEL', 'A BRE - PAR'], replace=False)
game.process()
game.set_orders('FRANCE', 'A BRE B', replace=False)
game.set_orders('FRANCE', 'F BRE B', replace=False)
orders = game.get_orders()
assert check_sorted(orders['FRANCE'], ['A BRE B'])
def test_clear_units():
""" Tests - Clear units """
game = Game()
game.clear_units()
for power in game.powers.values():
assert not power.units
assert not game.error
def test_clear_centers():
""" Tests - Clear centers """
game = Game()
game.clear_centers()
for power in game.powers.values():
assert not power.centers
assert not game.error
def test_clear_orders():
""" Test - Clear orders"""
game = Game()
game.clear_units()
game.set_units('ITALY', 'A VEN')
game.set_units('AUSTRIA', 'A VIE')
game.set_orders('ITALY', 'A VEN - TYR')
game.set_orders('AUSTRIA', 'A VIE - TYR')
game.clear_orders()
game.process()
results = game.get_order_status()
assert results['ITALY']['A VEN'] == []
assert results['AUSTRIA']['A VIE'] == []
def test_get_current_phase():
""" Tests - get current phase """
game = Game()
assert game.get_current_phase() == 'S1901M'
def test_set_current_phase():
""" Tests - set current phase"""
game = Game()
power = game.get_power('FRANCE')
power.units.remove('A PAR')
game.set_current_phase('W1901A')
game.clear_cache()
assert game.get_current_phase() == 'W1901A'
assert game.phase_type == 'A'
assert 'A PAR B' in game.get_all_possible_orders()['PAR']
def test_process_game():
""" Tests - Process game """
game = Game()
game.clear_units()
game.set_units('ITALY', 'A VEN')
game.set_units('AUSTRIA', 'A VIE')
game.set_orders('ITALY', 'A VEN - TYR')
game.set_orders('AUSTRIA', 'A VIE - TYR')
game.process()
results = game.get_order_status()
assert BOUNCE in results['ITALY']['A VEN']
assert BOUNCE in results['AUSTRIA']['A VIE']
def test_deepcopy():
""" Tests - deepcopy """
game = Game()
game2 = deepcopy(game)
assert game != game2
assert game.get_hash() == game2.get_hash()
def test_automatic_draw():
""" Tests - draw """
game = Game()
assert game.map.first_year == 1901
# fast forward 99 years with no winter
for year in range(1, 100):
game.process()
game.process()
assert int(game.get_current_phase()[1:5]) == game.map.first_year + year
assert game.is_game_done is False
# forward 2000 year. after this year should draw
game.process()
game.process()
assert game.is_game_done is True
assert list(sorted(game.outcome)) == list(
sorted(['W2000A', 'AUSTRIA', 'ENGLAND', 'FRANCE', 'GERMANY', 'ITALY', 'RUSSIA', 'TURKEY']))
def test_histories():
""" Test order_history, state_history, message_history and messages. """
from diplomacy.server.server_game import ServerGame
from diplomacy.utils.sorted_dict import SortedDict
from diplomacy.utils import strings
game = ServerGame(status=strings.ACTIVE)
assert game.solitaire
assert not game.n_controls
assert game.is_game_active
assert isinstance(game.messages, SortedDict)
assert isinstance(game.message_history, SortedDict)
assert isinstance(game.order_history, SortedDict)
assert isinstance(game.state_history, SortedDict)
assert not game.messages
assert not game.message_history
assert not game.order_history
assert not game.state_history
game.new_system_message('FRANCE', 'Hello France!')
game.new_system_message('GLOBAL', 'Hello World!')
game.set_orders('FRANCE', ['A PAR H'])
assert len(game.messages) == 2
previous_phase = game.get_current_phase()
game.process()
current_phase = game.get_current_phase()
assert previous_phase != current_phase, (previous_phase, current_phase)
assert not game.messages
assert len(game.message_history) == 1
assert len(game.order_history) == 1
assert len(game.state_history) == 1
game.set_orders('AUSTRIA', ['A BUD - GAL'])
game.set_orders('FRANCE', ['A PAR H'])
game.new_system_message('GLOBAL', 'New world.')
assert len(game.messages) == 1
game.process()
assert not game.messages
assert len(game.message_history) == 2
assert len(game.order_history) == 2
assert len(game.state_history) == 2
assert all((p1 == p2 == p3) for (p1, p2, p3) in zip(game.message_history.keys(),
game.order_history.keys(),
game.state_history.keys()))
assert game.map.compare_phases(str(game.state_history.first_key()), str(game.state_history.last_key())) < 0
messages_phase_1 = list(game.message_history.first_value().values())
messages_phase_2 = list(game.message_history.last_value().values())
orders_phase_1 = game.order_history.first_value()
orders_phase_2 = game.order_history.last_value()
assert len(messages_phase_1) == 2
assert len(messages_phase_2) == 1
assert messages_phase_1[0].message == 'Hello France!'
assert messages_phase_1[1].message == 'Hello World!'
assert messages_phase_2[0].message == 'New world.'
assert orders_phase_1['FRANCE'] == ['A PAR H']
assert orders_phase_2['FRANCE'] == ['A PAR H']
assert orders_phase_2['AUSTRIA'] == ['A BUD - GAL']
assert all('messages' not in state for state in game.state_history.values())
game_to_json = game.to_dict()
game_copy = ServerGame.from_dict(game_to_json)
assert list(game.state_history.keys()) == list(game_copy.state_history.keys())
assert list(game.message_history.keys()) == list(game_copy.message_history.keys())
assert list(game.order_history.keys()) == list(game_copy.order_history.keys())
# Check histories in game copy.
messages_phase_1 = list(game_copy.message_history.first_value().values())
messages_phase_2 = list(game_copy.message_history.last_value().values())
orders_phase_1 = game_copy.order_history.first_value()
orders_phase_2 = game_copy.order_history.last_value()
assert len(messages_phase_1) == 2
assert len(messages_phase_2) == 1
assert messages_phase_1[0].message == 'Hello France!'
assert messages_phase_1[1].message == 'Hello World!'
assert messages_phase_2[0].message == 'New world.'
assert orders_phase_1['FRANCE'] == ['A PAR H']
assert orders_phase_2['FRANCE'] == ['A PAR H']
assert orders_phase_2['AUSTRIA'] == ['A BUD - GAL']
def test_result_history():
""" Test result history. """
short_phase_name = 'S1901M'
game = Game()
game.set_orders('FRANCE', ['A PAR - BUR', 'A MAR - BUR'])
assert game.current_short_phase == short_phase_name
game.process()
assert game.current_short_phase == 'F1901M'
phase_data = game.get_phase_from_history(short_phase_name)
assert BOUNCE in phase_data.results['A PAR']
assert BOUNCE in phase_data.results['A MAR']
def test_unit_owner():
""" Test Unit Owner Resolver making sure the cached results are correct """
game = Game()
print(game.get_units('RUSSIA'))
assert game._unit_owner('F STP/SC', coast_required=1) is game.get_power('RUSSIA') # pylint: disable=protected-access
assert game._unit_owner('F STP/SC', coast_required=0) is game.get_power('RUSSIA') # pylint: disable=protected-access
assert game._unit_owner('F STP', coast_required=1) is None # pylint: disable=protected-access
assert game._unit_owner('F STP', coast_required=0) is game.get_power('RUSSIA') # pylint: disable=protected-access
assert game._unit_owner('A WAR', coast_required=0) is game.get_power('RUSSIA') # pylint: disable=protected-access
assert game._unit_owner('A WAR', coast_required=1) is game.get_power('RUSSIA') # pylint: disable=protected-access
assert game._unit_owner('F SEV', coast_required=0) is game.get_power('RUSSIA') # pylint: disable=protected-access
assert game._unit_owner('F SEV', coast_required=1) is game.get_power('RUSSIA') # pylint: disable=protected-access

238
diplomacy/tests/test_map.py Normal file
View file

@ -0,0 +1,238 @@
# ==============================================================================
# Copyright (C) 2019 - Philip Paquette
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.
# ==============================================================================
""" Tests cases for Map
- Contains the test cases for the map object
"""
from copy import deepcopy
from diplomacy.engine.map import Map
def test_init():
""" Creates a map"""
Map()
def test_str():
""" Tests map.__str__ """
this_map = deepcopy(Map())
assert str(this_map) == this_map.name
def test_add_homes():
""" Tests map.add_homes """
this_map = deepcopy(Map())
this_map.add_homes('FRANCE', 'BRE MAR PAR'.split(), reinit=1)
assert this_map.homes['FRANCE'] == ['BRE', 'MAR', 'PAR']
this_map.add_homes('FRANCE', [], reinit=1)
assert this_map.homes['FRANCE'] == []
def test_drop():
""" Tests map.drop """
this_map = deepcopy(Map())
this_map.drop('STP')
assert not [loc for loc in list(this_map.locs) if loc.upper().startswith('STP')]
assert not [loc_name for (loc_name, loc) in list(this_map.loc_name.items()) if loc.startswith('STP')]
assert not [alias for (alias, value) in list(this_map.aliases.items()) if value.startswith('STP')]
assert not [homes for homes in list(this_map.homes.values()) if 'STP' in homes]
assert not [units for units in list(this_map.units.values()) for unit in units if unit[2:5] == 'STP'[:3]]
assert not [center for center in list(this_map.scs) if center.upper().startswith('STP')]
assert not [p_name for (p_name, scs) in this_map.centers.items() for center in scs if center.startswith('STP')]
assert not [loc for loc, abuts in list(this_map.loc_abut.items()) for there in abuts
if loc.startswith('STP') or there.startswith('STP')]
assert not [loc for loc in list(this_map.loc_type.keys()) if loc.startswith('STP')]
def test_compact():
""" Tests map.compact """
this_map = deepcopy(Map())
# Power name at top of string is removed by Map.compact().
assert this_map.compact('England: Fleet Western Mediterranean -> Tyrrhenian Sea. (*bounce*)') \
== ['F', 'WES', 'TYS', '|']
def test_norm_power():
""" Tests map.norm_power """
this_map = deepcopy(Map())
assert this_map.norm_power('abc def. ghi/jkl!-ABC|~ (Hello)') == 'ABCDEFGHI/JKL!ABC|~(HELLO)'
def test_norm():
""" Tests map.norm """
this_map = deepcopy(Map())
assert this_map.norm('abc def. ghi/jkl!-ABC|~ (Hello)') == 'ABC DEF GHI /JKL ! ABC | ~ ( HELLO )'
def test_vet():
""" Tests map.vet """
this_map = deepcopy(Map())
assert this_map.vet(['A B']) == [('A B', 0)]
assert this_map.vet(['SPAIN/NC']) == [('SPAIN/NC', 1)]
assert this_map.vet(['SPANISH']) == [('SPANISH', 1)]
assert this_map.vet(['A']) == [('A', 2)]
assert this_map.vet(['F']) == [('F', 2)]
assert this_map.vet(['POR']) == [('POR', 3)]
assert this_map.vet(['SPA']) == [('SPA', 3)]
assert this_map.vet(['SPA/NC']) == [('SPA/NC', 4)]
assert this_map.vet(['S']) == [('S', 5)]
assert this_map.vet(['C']) == [('C', 5)]
assert this_map.vet(['H']) == [('H', 5)]
assert this_map.vet(['-']) == [('-', 6)]
assert this_map.vet(['=']) == [('=', 6)]
assert this_map.vet(['_']) == [('_', 6)]
assert this_map.vet(['|']) == [('|', 7)]
assert this_map.vet(['?']) == [('?', 7)]
assert this_map.vet(['~']) == [('~', 7)]
assert this_map.vet(['ZZZ'], strict=0) == [('ZZZ', 3)]
assert this_map.vet(['ZZZ'], strict=1) == [('ZZZ', -3)]
def test_area_type():
""" Tests map.area_type """
this_map = deepcopy(Map())
assert this_map.area_type('ADR') == 'WATER'
assert this_map.area_type('ALB') == 'COAST'
assert this_map.area_type('BUL/EC') == 'COAST'
assert this_map.area_type('BUR') == 'LAND'
assert this_map.area_type('SWI') == 'SHUT'
def test_default_coast():
""" Tests map.default_coast """
this_map = deepcopy(Map())
assert this_map.default_coast(['F', 'GRE', '-', 'BUL']) == ['F', 'GRE', '-', 'BUL/SC']
assert this_map.default_coast(['F', 'MAO', '-', 'SPA']) == ['F', 'MAO', '-', 'SPA']
assert this_map.default_coast(['F', 'FIN', '-', 'STP']) == ['F', 'FIN', '-', 'STP/SC']
assert this_map.default_coast(['F', 'NAO', '-', 'MAO']) == ['F', 'NAO', '-', 'MAO']
def test_abuts():
""" Tests map.abuts """
this_map = deepcopy(Map())
assert this_map.abuts('A', 'POR', 'S', 'SPA/NC') == 1
assert this_map.abuts('A', 'POR', 'C', 'SPA/NC') == 0
assert this_map.abuts('A', 'MUN', 'S', 'SWI') == 0
assert this_map.abuts('?', 'YOR', 'S', 'LVP') == 1
assert this_map.abuts('F', 'YOR', 'S', 'LVP') == 0
assert this_map.abuts('A', 'YOR', 'S', 'LVP') == 1
assert this_map.abuts('F', 'BOT', 'S', 'STP') == 1
assert this_map.abuts('F', 'BOT', 'S', 'MOS') == 0
assert this_map.abuts('F', 'VEN', 'S', 'TUS') == 0
assert this_map.abuts('A', 'POR', 'C', 'MAO') == 1
def test_is_valid_unit():
""" Tests maps.is_valid_unit """
# ADR = WATER
# ALB = COAST
# BUL/EC = COAST
# BUR = LAND
# SWI = SHUT
this_map = deepcopy(Map())
assert this_map.is_valid_unit('A ADR', no_coast_ok=0, shut_ok=0) == 0
assert this_map.is_valid_unit('A ALB', no_coast_ok=0, shut_ok=0) == 1
assert this_map.is_valid_unit('A BUL', no_coast_ok=0, shut_ok=0) == 1
assert this_map.is_valid_unit('A BUL/EC', no_coast_ok=0, shut_ok=0) == 0
assert this_map.is_valid_unit('A BUR', no_coast_ok=0, shut_ok=0) == 1
assert this_map.is_valid_unit('A SWI', no_coast_ok=0, shut_ok=0) == 0
assert this_map.is_valid_unit('F ADR', no_coast_ok=0, shut_ok=0) == 1
assert this_map.is_valid_unit('F ALB', no_coast_ok=0, shut_ok=0) == 1
assert this_map.is_valid_unit('F BUL', no_coast_ok=0, shut_ok=0) == 0
assert this_map.is_valid_unit('F BUL/EC', no_coast_ok=0, shut_ok=0) == 1
assert this_map.is_valid_unit('F BUR', no_coast_ok=0, shut_ok=0) == 0
assert this_map.is_valid_unit('F SWI', no_coast_ok=0, shut_ok=0) == 0
assert this_map.is_valid_unit('F ADR', no_coast_ok=1, shut_ok=0) == 1
assert this_map.is_valid_unit('F ALB', no_coast_ok=1, shut_ok=0) == 1
assert this_map.is_valid_unit('F BUL', no_coast_ok=1, shut_ok=0) == 1
assert this_map.is_valid_unit('F BUL/EC', no_coast_ok=1, shut_ok=0) == 1
assert this_map.is_valid_unit('F BUR', no_coast_ok=1, shut_ok=0) == 0
assert this_map.is_valid_unit('F SWI', no_coast_ok=1, shut_ok=0) == 0
assert this_map.is_valid_unit('? ADR', no_coast_ok=0, shut_ok=0) == 1
assert this_map.is_valid_unit('? ALB', no_coast_ok=0, shut_ok=0) == 1
assert this_map.is_valid_unit('? BUL', no_coast_ok=0, shut_ok=0) == 1
assert this_map.is_valid_unit('? BUL/EC', no_coast_ok=0, shut_ok=0) == 1
assert this_map.is_valid_unit('? BUR', no_coast_ok=0, shut_ok=0) == 1
assert this_map.is_valid_unit('? SWI', no_coast_ok=0, shut_ok=0) == 0
assert this_map.is_valid_unit('A SWI', no_coast_ok=0, shut_ok=1) == 1
assert this_map.is_valid_unit('F SWI', no_coast_ok=0, shut_ok=1) == 1
assert this_map.is_valid_unit('? SWI', no_coast_ok=0, shut_ok=1) == 1
def test_abut_list():
""" Tests map.abut_list """
this_map = deepcopy(Map())
this_map.loc_abut['---'] = ['ABC', 'DEF', 'GHI']
this_map.loc_abut['aaa'] = ['LOW', 'HIG', 'MAY']
assert this_map.abut_list('---') == ['ABC', 'DEF', 'GHI']
assert this_map.abut_list('AAA') == ['LOW', 'HIG', 'MAY']
assert this_map.abut_list('LVP') == ['CLY', 'edi', 'IRI', 'NAO', 'WAL', 'yor']
def test_compare_phases():
""" Tests map.compare_phases """
this_map = deepcopy(Map())
assert this_map.compare_phases('FORMING', 'S1901M') == -1
assert this_map.compare_phases('COMPLETED', 'S1901M') == 1
assert this_map.compare_phases('S1901M', 'FORMING') == 1
assert this_map.compare_phases('S1901M', 'COMPLETED') == -1
assert this_map.compare_phases('FORMING', 'COMPLETED') == -1
assert this_map.compare_phases('COMPLETED', 'FORMING') == 1
assert this_map.compare_phases('S1901M', 'S1902M') == -1
assert this_map.compare_phases('S1902M', 'S1901M') == 1
assert this_map.compare_phases('S1901M', 'F1901M') == -1
assert this_map.compare_phases('F1901M', 'S1901M') == 1
assert this_map.compare_phases('S1901?', 'S1901R') == 0
assert this_map.compare_phases('F1901?', 'F1901R') == 0
assert this_map.compare_phases('W1901?', 'W1901A') == 0
def test_find_next_phase():
""" Tests map.find_next_phase """
this_map = deepcopy(Map())
assert this_map.find_next_phase('FORMING') == 'FORMING'
assert this_map.find_next_phase('COMPLETED') == 'COMPLETED'
assert this_map.find_next_phase('WINTER 1901 ADJUSTMENTS') == 'SPRING 1902 MOVEMENT'
assert this_map.find_next_phase('FALL 1901 RETREATS', phase_type='M') == 'SPRING 1902 MOVEMENT'
assert this_map.find_next_phase('SPRING 1902 RETREATS', phase_type='M', skip=1) == 'SPRING 1903 MOVEMENT'
def test_find_previous_phase():
""" Tests map.find_previous_phase """
this_map = deepcopy(Map())
assert this_map.find_previous_phase('FORMING') == 'FORMING'
assert this_map.find_previous_phase('COMPLETED') == 'COMPLETED'
assert this_map.find_previous_phase('SPRING 1902 MOVEMENT') == 'WINTER 1901 ADJUSTMENTS'
assert this_map.find_previous_phase('SPRING 1902 MOVEMENT', phase_type='R') == 'FALL 1901 RETREATS'
assert this_map.find_previous_phase('SPRING 1903 MOVEMENT', phase_type='R', skip=1) == 'SPRING 1902 RETREATS'
def test_phase_abbr():
""" Tests map.phase_abbr """
this_map = deepcopy(Map())
assert this_map.phase_abbr('SPRING 1901 MOVEMENT') == 'S1901M'
assert this_map.phase_abbr('SPRING 1901 RETREATS') == 'S1901R'
assert this_map.phase_abbr('FALL 1901 MOVEMENT') == 'F1901M'
assert this_map.phase_abbr('FALL 1901 RETREATS') == 'F1901R'
assert this_map.phase_abbr('WINTER 1901 ADJUSTMENTS') == 'W1901A'
assert this_map.phase_abbr('spring 1901 movement') == 'S1901M'
assert this_map.phase_abbr('spring 1901 retreats') == 'S1901R'
assert this_map.phase_abbr('fall 1901 movement') == 'F1901M'
assert this_map.phase_abbr('fall 1901 retreats') == 'F1901R'
assert this_map.phase_abbr('winter 1901 adjustments') == 'W1901A'
assert this_map.phase_abbr('COMPLETED') == 'COMPLETED'
assert this_map.phase_abbr('FORMING') == 'FORMING'
assert this_map.phase_abbr('Bad') == '?????'
assert this_map.phase_abbr('Bad', default='Test') == 'Test'
def test_phase_long():
""" Test map.phase_long """
this_map = deepcopy(Map())
assert this_map.phase_long('S1901M') == 'SPRING 1901 MOVEMENT'
assert this_map.phase_long('S1901R') == 'SPRING 1901 RETREATS'
assert this_map.phase_long('F1901M') == 'FALL 1901 MOVEMENT'
assert this_map.phase_long('F1901R') == 'FALL 1901 RETREATS'
assert this_map.phase_long('W1901A') == 'WINTER 1901 ADJUSTMENTS'
assert this_map.phase_long('s1901m') == 'SPRING 1901 MOVEMENT'
assert this_map.phase_long('s1901r') == 'SPRING 1901 RETREATS'
assert this_map.phase_long('f1901m') == 'FALL 1901 MOVEMENT'
assert this_map.phase_long('f1901r') == 'FALL 1901 RETREATS'
assert this_map.phase_long('w1901a') == 'WINTER 1901 ADJUSTMENTS'
assert this_map.phase_long('bad') == '?????'
assert this_map.phase_long('bad', default='Test') == 'Test'

View file

@ -0,0 +1,23 @@
# ==============================================================================
# Copyright (C) 2019 - Philip Paquette
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.
# ==============================================================================
""" Pytest checker """
# Check that tests are running
# Avoids pytests not having tests to run
def test_pytest():
""" Should always pass """
assert True