diff options
author | Philip Paquette <pcpaquette@gmail.com> | 2018-09-26 07:48:55 -0400 |
---|---|---|
committer | Philip Paquette <pcpaquette@gmail.com> | 2019-04-18 11:14:24 -0400 |
commit | 6187faf20384b0c5a4966343b2d4ca47f8b11e45 (patch) | |
tree | 151ccd21aea20180432c13fe4b58240d3d9e98b6 /diplomacy/utils/tests | |
parent | 96b7e2c03ed98705754f13ae8efa808b948ee3a8 (diff) |
Release v1.0.0 - Diplomacy Game Engine - AGPL v3+ License
Diffstat (limited to 'diplomacy/utils/tests')
-rw-r--r-- | diplomacy/utils/tests/__init__.py | 16 | ||||
-rw-r--r-- | diplomacy/utils/tests/test_common.py | 147 | ||||
-rw-r--r-- | diplomacy/utils/tests/test_jsonable.py | 81 | ||||
-rw-r--r-- | diplomacy/utils/tests/test_jsonable_changes.py | 189 | ||||
-rw-r--r-- | diplomacy/utils/tests/test_parsing.py | 307 | ||||
-rw-r--r-- | diplomacy/utils/tests/test_priority_dict.py | 102 | ||||
-rw-r--r-- | diplomacy/utils/tests/test_sorted_dict.py | 154 | ||||
-rw-r--r-- | diplomacy/utils/tests/test_sorted_set.py | 168 | ||||
-rw-r--r-- | diplomacy/utils/tests/test_time.py | 77 |
9 files changed, 1241 insertions, 0 deletions
diff --git a/diplomacy/utils/tests/__init__.py b/diplomacy/utils/tests/__init__.py new file mode 100644 index 0000000..4f2769f --- /dev/null +++ b/diplomacy/utils/tests/__init__.py @@ -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/>. +# ============================================================================== diff --git a/diplomacy/utils/tests/test_common.py b/diplomacy/utils/tests/test_common.py new file mode 100644 index 0000000..a1c303d --- /dev/null +++ b/diplomacy/utils/tests/test_common.py @@ -0,0 +1,147 @@ +# ============================================================================== +# Copyright (C) 2019 - Philip Paquette, Steven Bocco +# +# 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 for diplomacy network code utils. """ +import ujson as json + +from diplomacy.utils import common, exceptions + +def assert_raises(callback, expected_exceptions): + """ Checks that given callback raises given exceptions. """ + + try: + callback() + except expected_exceptions: + pass + else: + raise AssertionError('Should fail %s %s' % (callback, str(expected_exceptions))) + +def assert_equals(expected, computed): + """ Checks that expect == computed. """ + + if expected != computed: + raise AssertionError('\nExpected:\n=========\n%s\n\nComputed:\n=========\n%s\n' % (expected, computed)) + +def test_hash_password(): + """ Test passwords hashing. Note: slower than the other tests. """ + + password1 = '123456789' + password2 = 'abcdef' + password_unicode = 'しろいねこをみた。 白い猫を見た。' + for password in (password1, password2, password_unicode): + hashed_password = common.hash_password(password) + json_hashed_password = json.dumps(common.hash_password(password)) + hashed_password_from_json = json.loads(json_hashed_password) + # It seems hashed passwords are not necessarily the same for 2 different calls to hash function. + assert common.is_valid_password(password, hashed_password), (password, hashed_password) + assert common.is_valid_password(password, hashed_password_from_json), (password, hashed_password_from_json) + +def test_generate_token(): + """ Test token generation. """ + + for n_bytes in (128, 344): + token = common.generate_token(n_bytes) + assert isinstance(token, str) and len(token) == 2 * n_bytes + +def test_is_sequence(): + """ Test sequence type checking function. """ + + assert common.is_sequence((1, 2, 3)) + assert common.is_sequence([1, 2, 3]) + assert common.is_sequence({1, 2, 3}) + assert common.is_sequence(()) + assert common.is_sequence([]) + assert common.is_sequence(set()) + assert not common.is_sequence('i am a string') + assert not common.is_sequence({}) + assert not common.is_sequence(1) + assert not common.is_sequence(False) + assert not common.is_sequence(-2.5) + +def test_is_dictionary(): + """ Test dictionary type checking function. """ + + assert common.is_dictionary({'a': 1, 'b': 2}) + assert not common.is_dictionary((1, 2, 3)) + assert not common.is_dictionary([1, 2, 3]) + assert not common.is_dictionary({1, 2, 3}) + + assert not common.is_dictionary(()) + assert not common.is_dictionary([]) + assert not common.is_dictionary(set()) + + assert not common.is_dictionary('i am a string') + +def test_camel_to_snake_case(): + """ Test conversion from camel case to snake case. """ + + for camel, expected_snake in [ + ('a', 'a'), + ('A', 'a'), + ('AA', 'a_a'), + ('AbCdEEf', 'ab_cd_e_ef'), + ('Aa', 'aa'), + ('OnGameDone', 'on_game_done'), + ('AbstractSuperClass', 'abstract_super_class'), + ('ABCDEFghikKLm', 'a_b_c_d_e_fghik_k_lm'), + ('is_a_thing', 'is_a_thing'), + ('A_a_Aa__', 'a_a_aa__'), + ('Horrible_SuperClass_nameWith_mixedSyntax', 'horrible_super_class_name_with_mixed_syntax'), + ]: + computed_snake = common.camel_case_to_snake_case(camel) + assert computed_snake == expected_snake, ('camel : expected : computed:', camel, expected_snake, computed_snake) + +def test_snake_to_camel_case(): + """ Test conversion from snake case to camel upper case. """ + + for expected_camel, snake in [ + ('A', 'a'), + ('AA', 'a_a'), + ('AbCdEEf', 'ab_cd_e_ef'), + ('Aa', 'aa'), + ('OnGameDone', 'on_game_done'), + ('AbstractSuperClass', 'abstract_super_class'), + ('ABCDEFghikKLm', 'a_b_c_d_e_fghik_k_lm'), + ('IsAThing', 'is_a_thing'), + ('AAAa__', 'a_a_aa__'), + ('_AnHorrible_ClassName', '__an_horrible__class_name'), + ]: + computed_camel = common.snake_case_to_upper_camel_case(snake) + assert computed_camel == expected_camel, ('snake : expected : computed:', snake, expected_camel, computed_camel) + +def test_assert_no_common_keys(): + """ Test dictionary disjunction checking function. """ + + dct1 = {'a': 1, 'b': 2, 'c': 3} + dct2 = {'a': 1, 'e': 2, 'd': 3} + dct3 = {'m': 1, 'e': 2, 'f': 3} + common.assert_no_common_keys(dct1, dct3) + assert_raises(lambda: common.assert_no_common_keys(dct1, dct2), exceptions.CommonKeyException) + assert_raises(lambda: common.assert_no_common_keys(dct2, dct3), exceptions.CommonKeyException) + +def test_timestamp(): + """ Test timestamp generation. """ + + timestamp1 = common.timestamp_microseconds() + timestamp2 = common.timestamp_microseconds() + timestamp3 = common.timestamp_microseconds() + assert isinstance(timestamp1, int) + assert isinstance(timestamp2, int) + assert isinstance(timestamp3, int) + assert timestamp1 > 1e6 + assert timestamp2 > 1e6 + assert timestamp3 > 1e6 + assert timestamp1 <= timestamp2 <= timestamp3, (timestamp1, timestamp2, timestamp3) diff --git a/diplomacy/utils/tests/test_jsonable.py b/diplomacy/utils/tests/test_jsonable.py new file mode 100644 index 0000000..73d65c1 --- /dev/null +++ b/diplomacy/utils/tests/test_jsonable.py @@ -0,0 +1,81 @@ +# ============================================================================== +# Copyright (C) 2019 - Philip Paquette, Steven Bocco +# +# 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 Jsonable. """ +import ujson as json + +from diplomacy.utils import parsing +from diplomacy.utils.jsonable import Jsonable +from diplomacy.utils.sorted_dict import SortedDict +from diplomacy.utils.sorted_set import SortedSet + +class MyJsonable(Jsonable): + """ Example of class derived from Jsonable. """ + __slots__ = ('field_a', 'field_b', 'field_c', 'field_d', 'field_e', 'field_f', 'field_g') + + model = { + 'field_a': bool, + 'field_b': str, + 'field_c': parsing.OptionalValueType(float), + 'field_d': parsing.DefaultValueType(str, 'super'), + 'field_e': parsing.SequenceType(int), + 'field_f': parsing.SequenceType(float, sequence_builder=SortedSet.builder(float)), + 'field_g': parsing.DefaultValueType(parsing.DictType(str, int, SortedDict.builder(str, int)), {'x': -1}) + } + + def __init__(self, **kwargs): + """ Constructor """ + self.field_a = None + self.field_b = None + self.field_c = None + self.field_d = None + self.field_e = None + self.field_f = None + self.field_g = {} + super(MyJsonable, self).__init__(**kwargs) + +def test_jsonable_parsing(): + """ Test parsing for Jsonable. """ + + attributes = ('field_a', 'field_b', 'field_c', 'field_d', 'field_e', 'field_f', 'field_g') + + # Building and validating + my_jsonable = MyJsonable(field_a=False, field_b='test', field_e={1}, field_f=[6.5]) + for attribute_name in attributes: + assert hasattr(my_jsonable, attribute_name) + assert isinstance(my_jsonable.field_a, bool) + assert isinstance(my_jsonable.field_b, str) + assert my_jsonable.field_c is None + assert isinstance(my_jsonable.field_d, str), my_jsonable.field_d + assert isinstance(my_jsonable.field_e, list) + assert isinstance(my_jsonable.field_f, SortedSet) + assert isinstance(my_jsonable.field_g, SortedDict) + assert my_jsonable.field_d == 'super' + assert my_jsonable.field_e == [1] + assert my_jsonable.field_f == SortedSet(float, (6.5,)) + assert len(my_jsonable.field_g) == 1 and my_jsonable.field_g['x'] == -1 + + # Building from its json representation and validating + from_json = MyJsonable.from_dict(json.loads(json.dumps(my_jsonable.to_dict()))) + for attribute_name in attributes: + assert hasattr(from_json, attribute_name), attribute_name + assert from_json.field_a == my_jsonable.field_a + assert from_json.field_b == my_jsonable.field_b + assert from_json.field_c == my_jsonable.field_c + assert from_json.field_d == my_jsonable.field_d + assert from_json.field_e == my_jsonable.field_e + assert from_json.field_f == my_jsonable.field_f + assert from_json.field_g == my_jsonable.field_g diff --git a/diplomacy/utils/tests/test_jsonable_changes.py b/diplomacy/utils/tests/test_jsonable_changes.py new file mode 100644 index 0000000..992a8fb --- /dev/null +++ b/diplomacy/utils/tests/test_jsonable_changes.py @@ -0,0 +1,189 @@ +# ============================================================================== +# Copyright (C) 2019 - Philip Paquette, Steven Bocco +# +# 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 changes in a Jsonable schema. """ +#pylint: disable=invalid-name +from diplomacy.utils import parsing +from diplomacy.utils.jsonable import Jsonable + +def converter_to_int(val): + """ A converter from given value to an integer. Used in Version1. """ + try: + return int(val) + except ValueError: + return 0 + +class Version1(Jsonable): + """ A Jsonable with fields a, b, c, d. + NB: To parse a dict from Version22 to Version1, modified fields a and c must be convertible in Version1. + using ConverterType in Version1. + """ + model = { + 'a': parsing.ConverterType(int, converter_to_int), + 'b': parsing.OptionalValueType(str), + 'c': parsing.ConverterType(float, converter_function=float), + 'd': parsing.DefaultValueType(bool, True), + } + + def __init__(self, **kwargs): + self.a = None + self.b = None + self.c = None + self.d = None + super(Version1, self).__init__(**kwargs) + +class Version20(Jsonable): + """ Version1 with removed fields b and d. + NB: To parse a dict from Version20 to Version1, removed fields b and d must be optional in Version1. + """ + model = { + 'a': int, + 'c': float, + } + + def __init__(self, **kwargs): + self.a = None + self.c = None + super(Version20, self).__init__(**kwargs) + +class Version21(Jsonable): + """ Version1 with added fields e and f. + NB: To parse a dict from Version1 to Version21, added fields e and f must be optional in Version21. + """ + model = { + 'a': int, + 'b': str, + 'c': float, + 'd': bool, + 'e': parsing.DefaultValueType(parsing.EnumerationType([100, 200, 300, 400]), 100), + 'f': parsing.DefaultValueType(dict, {'x': 1, 'y': 2}) + } + + def __init__(self, **kwargs): + self.a = None + self.b = None + self.c = None + self.d = None + self.e = None + self.f = {} + super(Version21, self).__init__(**kwargs) + +class Version22(Jsonable): + """ Version1 with modified types for a and c. + NB: To parse a dict from Version1 to Version22, modified fields a and c must be convertible + using ConverterType in Version22. + """ + model = { + 'a': parsing.ConverterType(str, converter_function=str), + 'b': str, + 'c': parsing.ConverterType(bool, converter_function=bool), + 'd': bool, + } + + def __init__(self, **kwargs): + self.a = None + self.b = None + self.c = None + self.d = None + super(Version22, self).__init__(**kwargs) + +class Version3(Jsonable): + """ Version 1 with a modified, b removed, e added. + To parse a dict between Version3 and Version1: + - a must be convertible in both versions. + - b must be optional in Version1. + - e must be optional in Version3. + """ + model = { + 'a': parsing.ConverterType(str, converter_function=str), + 'c': float, + 'd': bool, + 'e': parsing.OptionalValueType(parsing.SequenceType(int)) + } + + def __init__(self, **kwargs): + self.a = None + self.c = None + self.d = None + self.e = None + super(Version3, self).__init__(**kwargs) + +def test_jsonable_changes_v1_v20(): + """ Test changes from Version1 to Version20. """ + v20 = Version20(a=1, c=1.5) + v1 = Version1(a=1, b='b', c=1.5, d=False) + json_v1 = v1.to_dict() + v20_from_v1 = Version20.from_dict(json_v1) + json_v20_from_v1 = v20_from_v1.to_dict() + v1_from_v20_from_v1 = Version1.from_dict(json_v20_from_v1) + assert v1_from_v20_from_v1.b is None + assert v1_from_v20_from_v1.d is True + json_v20 = v20.to_dict() + v1_from_v20 = Version1.from_dict(json_v20) + assert v1_from_v20.b is None + assert v1_from_v20.d is True + +def test_jsonable_changes_v1_v21(): + """ Test changes from Version1 to Version21. """ + v21 = Version21(a=1, b='b21', c=1.5, d=True, e=300, f=dict(x=1, y=2)) + v1 = Version1(a=1, b='b', c=1.5, d=False) + json_v1 = v1.to_dict() + v21_from_v1 = Version21.from_dict(json_v1) + assert v21_from_v1.e == 100 + assert v21_from_v1.f['x'] == 1 + assert v21_from_v1.f['y'] == 2 + json_v21_from_v1 = v21_from_v1.to_dict() + v1_from_v21_from_v1 = Version1.from_dict(json_v21_from_v1) + assert v1_from_v21_from_v1.b == 'b' + assert v1_from_v21_from_v1.d is False + json_v21 = v21.to_dict() + v1_from_v21 = Version1.from_dict(json_v21) + assert v1_from_v21.b == 'b21' + assert v1_from_v21.d is True + +def test_jsonable_changes_v1_v22(): + """ Test changes from Version1 to Version22. """ + v22 = Version22(a='a', b='b', c=False, d=False) + v1 = Version1(a=1, b='b', c=1.5, d=False) + json_v1 = v1.to_dict() + v22_from_v1 = Version22.from_dict(json_v1) + assert v22_from_v1.a == '1' + assert v22_from_v1.c is True + json_v22_from_v1 = v22_from_v1.to_dict() + v1_from_v22_from_v1 = Version1.from_dict(json_v22_from_v1) + assert v1_from_v22_from_v1.a == 1 + assert v1_from_v22_from_v1.c == 1.0 + json_v22 = v22.to_dict() + v1_from_v22 = Version1.from_dict(json_v22) + assert v1_from_v22.a == 0 + assert v1_from_v22.c == 0.0 + +def test_jsonable_changes_v1_v3(): + """ Test changes from Version1 to Version3. """ + v3 = Version3(a='a', c=1.5, d=False, e=(1, 2, 3)) + v1 = Version1(a=1, b='b', c=1.5, d=False) + json_v1 = v1.to_dict() + v3_from_v1 = Version3.from_dict(json_v1) + assert v3_from_v1.a == '1' + assert v3_from_v1.e is None + json_v3_from_v1 = v3_from_v1.to_dict() + v1_from_v3_from_v1 = Version1.from_dict(json_v3_from_v1) + assert v1_from_v3_from_v1.a == 1 + assert v1_from_v3_from_v1.b is None + json_v3 = v3.to_dict() + v1_from_v3 = Version1.from_dict(json_v3) + assert v1_from_v3.a == 0 + assert v1_from_v3.b is None diff --git a/diplomacy/utils/tests/test_parsing.py b/diplomacy/utils/tests/test_parsing.py new file mode 100644 index 0000000..f64ad26 --- /dev/null +++ b/diplomacy/utils/tests/test_parsing.py @@ -0,0 +1,307 @@ +# ============================================================================== +# Copyright (C) 2019 - Philip Paquette, Steven Bocco +# +# 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 module parsing. """ +from diplomacy.utils import exceptions, parsing +from diplomacy.utils.sorted_dict import SortedDict +from diplomacy.utils.sorted_set import SortedSet +from diplomacy.utils.tests.test_common import assert_raises +from diplomacy.utils.tests.test_jsonable import MyJsonable + +class MyStringable(): + """ Example of Stringable class. + As instances of such class may be used as dict keys, class should define a proper __hash__(). + """ + + def __init__(self, value): + self.attribute = str(value) + + def __str__(self): + return 'MyStringable %s' % self.attribute + + def __hash__(self): + return hash(self.attribute) + + def __eq__(self, other): + return isinstance(other, MyStringable) and self.attribute == other.attribute + + def __lt__(self, other): + return isinstance(other, MyStringable) and self.attribute < other.attribute + + @staticmethod + def from_string(str_repr): + """ Converts a string representation `str_repr` of MyStringable to an instance of MyStringable. """ + return MyStringable(str_repr[len('MyStringable '):]) + +def test_default_value_type(): + """ Test default value type. """ + + for default_value in (True, False, None): + checker = parsing.DefaultValueType(bool, default_value) + assert_raises(lambda ch=checker: ch.validate(1), exceptions.TypeException) + assert_raises(lambda ch=checker: ch.validate(1.1), exceptions.TypeException) + assert_raises(lambda ch=checker: ch.validate(''), exceptions.TypeException) + for value in (True, False, None): + checker.validate(value) + if value is None: + assert checker.to_type(value) is default_value + assert checker.to_json(value) is default_value + else: + assert checker.to_type(value) is value + assert checker.to_json(value) is value + assert checker.update(None) is default_value + +def test_optional_value_type(): + """ Test optional value type. """ + + checker = parsing.OptionalValueType(bool) + assert_raises(lambda ch=checker: ch.validate(1), exceptions.TypeException) + assert_raises(lambda ch=checker: ch.validate(1.1), exceptions.TypeException) + assert_raises(lambda ch=checker: ch.validate(''), exceptions.TypeException) + for value in (True, False, None): + checker.validate(value) + assert checker.to_type(value) is value + assert checker.to_json(value) is value + assert checker.update(None) is None + +def test_sequence_type(): + """ Test sequence type. """ + + # With default sequence builder. + checker = parsing.SequenceType(int) + checker.validate((1, 2, 3)) + checker.validate([1, 2, 3]) + checker.validate({1, 2, 3}) + checker.validate(SortedSet(int)) + checker.validate(SortedSet(int, (1, 2, 3))) + assert_raises(lambda: checker.validate((1, 2, 3.0)), exceptions.TypeException) + assert_raises(lambda: checker.validate((1.0, 2.0, 3.0)), exceptions.TypeException) + assert isinstance(checker.to_type((1, 2, 3)), list) + # With SortedSet as sequence builder. + checker = parsing.SequenceType(float) + checker.validate((1.0, 2.0, 3.0)) + checker.validate([1.0, 2.0, 3.0]) + checker.validate({1.0, 2.0, 3.0}) + assert_raises(lambda: checker.validate((1, 2, 3.0)), exceptions.TypeException) + assert_raises(lambda: checker.validate((1.0, 2.0, 3)), exceptions.TypeException) + checker = parsing.SequenceType(int, sequence_builder=SortedSet.builder(int)) + initial_list = (1, 2, 7, 7, 1) + checker.validate(initial_list) + updated_list = checker.update(initial_list) + assert isinstance(updated_list, SortedSet) and updated_list.element_type is int + assert updated_list == SortedSet(int, (1, 2, 7)) + assert checker.to_json(updated_list) == [1, 2, 7] + assert checker.to_type([7, 2, 1, 1, 7, 1, 7]) == updated_list + +def test_jsonable_class_type(): + """ Test parser for Jsonable sub-classes. """ + + checker = parsing.JsonableClassType(MyJsonable) + my_jsonable = MyJsonable(field_a=False, field_b='test', field_e={1}, field_f=[6.5]) + my_jsonable_dict = { + 'field_a': False, + 'field_b': 'test', + 'field_e': (1, 2), + 'field_f': (1.0, 2.0), + } + checker.validate(my_jsonable) + assert_raises(lambda: checker.validate(None), exceptions.TypeException) + assert_raises(lambda: checker.validate(my_jsonable_dict), exceptions.TypeException) + +def test_stringable_type(): + """ Test stringable type. """ + + checker = parsing.StringableType(str) + checker.validate('0') + checker = parsing.StringableType(MyStringable) + checker.validate(MyStringable('test')) + assert_raises(lambda: checker.validate('test'), exceptions.TypeException) + assert_raises(lambda: checker.validate(None), exceptions.TypeException) + +def test_dict_type(): + """ Test dict type. """ + + checker = parsing.DictType(str, int) + checker.validate({'test': 1}) + assert_raises(lambda: checker.validate({'test': 1.0}), exceptions.TypeException) + checker = parsing.DictType(MyStringable, float) + checker.validate({MyStringable('12'): 2.5}) + assert_raises(lambda: checker.validate({'12': 2.5}), exceptions.TypeException) + assert_raises(lambda: checker.validate({MyStringable('12'): 2}), exceptions.TypeException) + checker = parsing.DictType(MyStringable, float, dict_builder=SortedDict.builder(MyStringable, float)) + value = {MyStringable(12): 22.0} + checker.validate(value) + updated_value = checker.update(value) + assert isinstance(updated_value, SortedDict) + assert updated_value.key_type is MyStringable + assert updated_value.val_type is float + json_value = {'MyStringable 12': 22.0} + assert checker.to_type(json_value) == SortedDict(MyStringable, float, {MyStringable('12'): 22.0}) + assert checker.to_json(SortedDict(MyStringable, float, {MyStringable(12): 22.0})) == json_value + +def test_sequence_of_values_type(): + """ Test parser for sequence of allowed values. """ + + checker = parsing.EnumerationType({'a', 'b', 'c', 'd'}) + checker.validate('d') + checker.validate('c') + checker.validate('b') + checker.validate('a') + assert_raises(lambda: checker.validate('e'), exceptions.ValueException) + +def test_sequence_of_primitives_type(): + """ Test parser for sequence of primitive types. """ + + checker = parsing.SequenceOfPrimitivesType((int, bool)) + checker.validate(False) + checker.validate(True) + checker.validate(0) + checker.validate(1) + assert_raises(lambda: checker.validate(0.0), exceptions.TypeException) + assert_raises(lambda: checker.validate(1.0), exceptions.TypeException) + assert_raises(lambda: checker.validate(''), exceptions.TypeException) + assert_raises(lambda: checker.validate('a non-empty string'), exceptions.TypeException) + +def test_primitive_type(): + """ Test parser for primitive type. """ + + checker = parsing.PrimitiveType(bool) + checker.validate(True) + checker.validate(False) + assert_raises(lambda: checker.validate(None), exceptions.TypeException) + assert_raises(lambda: checker.validate(0), exceptions.TypeException) + assert_raises(lambda: checker.validate(1), exceptions.TypeException) + assert_raises(lambda: checker.validate(''), exceptions.TypeException) + assert_raises(lambda: checker.validate('a non-empty string'), exceptions.TypeException) + +def test_model_parsing(): + """ Test parsing for a real model. """ + + model = { + 'name': str, + 'language': ('fr', 'en'), + 'myjsonable': parsing.JsonableClassType(MyJsonable), + 'mydict': parsing.DictType(str, float), + 'nothing': (bool, str), + 'default_float': parsing.DefaultValueType(float, 33.44), + 'optional_float': parsing.OptionalValueType(float) + } + bad_data_field = { + '_name_': 'hello', + 'language': 'fr', + 'myjsonable': MyJsonable(field_a=False, field_b='test', field_e={1}, field_f=[6.5]), + 'mydict': { + 'a': 2.5, + 'b': -1.6 + }, + 'nothing': 'thanks' + } + bad_data_type = { + 'name': 'hello', + 'language': 'fr', + 'myjsonable': MyJsonable(field_a=False, field_b='test', field_e={1}, field_f=[6.5]), + 'mydict': { + 'a': 2.5, + 'b': -1.6 + }, + 'nothing': 2.5 + } + bad_data_value = { + 'name': 'hello', + 'language': '__', + 'myjsonable': MyJsonable(field_a=False, field_b='test', field_e={1}, field_f=[6.5]), + 'mydict': { + 'a': 2.5, + 'b': -1.6 + }, + 'nothing': '2.5' + } + good_data = { + 'name': 'hello', + 'language': 'fr', + 'myjsonable': MyJsonable(field_a=False, field_b='test', field_e={1}, field_f=[6.5]), + 'mydict': { + 'a': 2.5, + 'b': -1.6 + }, + 'nothing': '2.5' + } + assert_raises(lambda: parsing.validate_data(bad_data_field, model), (exceptions.TypeException,)) + assert_raises(lambda: parsing.validate_data(bad_data_type, model), (exceptions.TypeException,)) + assert_raises(lambda: parsing.validate_data(bad_data_value, model), (exceptions.ValueException,)) + + assert 'default_float' not in good_data + assert 'optional_float' not in good_data + parsing.validate_data(good_data, model) + updated_good_data = parsing.update_data(good_data, model) + assert 'default_float' in updated_good_data + assert 'optional_float' in updated_good_data + assert updated_good_data['default_float'] == 33.44 + assert updated_good_data['optional_float'] is None + +def test_converter_type(): + """ Test parser converter type. """ + + def converter_to_int(val): + """ Converts value to integer """ + try: + return int(val) + except (ValueError, TypeError): + return 0 + + checker = parsing.ConverterType(str, converter_function=lambda val: 'String of %s' % val) + checker.validate('a string') + checker.validate(10) + checker.validate(True) + checker.validate(None) + checker.validate(-2.5) + assert checker.update(10) == 'String of 10' + assert checker.update(False) == 'String of False' + assert checker.update('string') == 'String of string' + checker = parsing.ConverterType(int, converter_function=converter_to_int) + checker.validate(10) + checker.validate(True) + checker.validate(None) + checker.validate(-2.5) + assert checker.update(10) == 10 + assert checker.update(True) == 1 + assert checker.update(False) == 0 + assert checker.update(-2.5) == -2 + assert checker.update('44') == 44 + assert checker.update('a') == 0 + +def test_indexed_sequence(): + """ Test parser type for dicts stored as sequences. """ + checker = parsing.IndexedSequenceType(parsing.DictType(str, parsing.JsonableClassType(MyJsonable)), 'field_b') + sequence = [ + MyJsonable(field_a=True, field_b='x1', field_e=[1, 2, 3], field_f=[1., 2., 3.]), + MyJsonable(field_a=True, field_b='x3', field_e=[1, 2, 3], field_f=[1., 2., 3.]), + MyJsonable(field_a=True, field_b='x2', field_e=[1, 2, 3], field_f=[1., 2., 3.]), + MyJsonable(field_a=True, field_b='x5', field_e=[1, 2, 3], field_f=[1., 2., 3.]), + MyJsonable(field_a=True, field_b='x4', field_e=[1, 2, 3], field_f=[1., 2., 3.]) + ] + dct = {element.field_b: element for element in sequence} + checker.validate(dct) + checker.update(dct) + jval = checker.to_json(dct) + assert isinstance(jval, list), type(jval) + from_jval = checker.to_type(jval) + assert isinstance(from_jval, dict), type(from_jval) + assert len(dct) == 5 + assert len(from_jval) == 5 + for key in ('x1', 'x2', 'x3', 'x4', 'x5'): + assert key in dct, (key, list(dct.keys())) + assert key in from_jval, (key, list(from_jval.keys())) diff --git a/diplomacy/utils/tests/test_priority_dict.py b/diplomacy/utils/tests/test_priority_dict.py new file mode 100644 index 0000000..cb7023d --- /dev/null +++ b/diplomacy/utils/tests/test_priority_dict.py @@ -0,0 +1,102 @@ +# ============================================================================== +# Copyright (C) 2019 - Philip Paquette, Steven Bocco +# +# 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 class PriorityDict. """ +from diplomacy.utils.priority_dict import PriorityDict +from diplomacy.utils.tests.test_common import assert_equals + +def test_priority_dict(): + """ Test Heap class PriorityDict. """ + + for unordered_list in [ + [464, 21, 43453, 211, 324, 321, 102, 1211, 14, 875, 1, 33444, 22], + 'once upon a time in West'.split(), + 'This is a sentence with many words like panthera, lion, tiger, cat or cheetah!'.split() + ]: + expected_ordered_set = list(sorted(set(unordered_list))) + computed_sorted_list = [] + priority_dict = PriorityDict() + for element in unordered_list: + priority_dict[element] = element + while priority_dict: + value, key = priority_dict.smallest() + computed_sorted_list.append(value) + del priority_dict[key] + assert_equals(expected_ordered_set, computed_sorted_list) + +def test_item_getter_setter_deletion(): + """ Test PriorityDict item setter/getter/deletion. """ + + priority_dict = PriorityDict() + priority_dict['a'] = 12 + priority_dict['f'] = 9 + priority_dict['b'] = 23 + assert list(priority_dict.keys()) == ['f', 'a', 'b'] + assert priority_dict['a'] == 12 + assert priority_dict['f'] == 9 + assert priority_dict['b'] == 23 + priority_dict['e'] = -1 + priority_dict['a'] = 8 + del priority_dict['b'] + assert list(priority_dict.keys()) == ['e', 'a', 'f'] + assert list(priority_dict.values()) == [-1, 8, 9] + +def test_iterations(): + """ test iterations: + - for key in priority_dict + - priority_dict.keys() + - priority_dict.values() + - priority_dict.items() + """ + priorities = [464, 21, 43453, 211, 324, 321, 102, 1211, 14, 875, 1, 33444, 22] + + # Build priority dict. + priority_dict = PriorityDict() + for priority in priorities: + priority_dict['value of %s' % priority] = priority + + # Build expected priorities and keys. + expected_sorted_priorities = list(sorted(priorities)) + expected_sorted_keys = ['value of %s' % priority for priority in sorted(priorities)] + + # Iterate on priority dict. + computed_sorted_priorities = [priority_dict[key] for key in priority_dict] + # Iterate on priority dict keys. + sorted_priorities_from_key = [priority_dict[key] for key in priority_dict.keys()] + # Iterate on priority dict values. + sorted_priorities_from_values = list(priority_dict.values()) + # Iterate on priority dict items. + priority_dict_items = list(priority_dict.items()) + # Get priority dict keys. + priority_dict_keys = list(priority_dict.keys()) + # Get priority dict keys from items (to validate items). + priority_dict_keys_from_items = [item[0] for item in priority_dict_items] + # Get priority dict values from items (to validate items). + priority_dict_values_from_items = [item[1] for item in priority_dict_items] + + for expected, computed in [ + (expected_sorted_priorities, computed_sorted_priorities), + (expected_sorted_priorities, sorted_priorities_from_key), + (expected_sorted_priorities, sorted_priorities_from_values), + (expected_sorted_priorities, priority_dict_values_from_items), + (expected_sorted_keys, priority_dict_keys_from_items), + (expected_sorted_keys, priority_dict_keys), + ]: + assert_equals(expected, computed) + + # Priority dict should have not been modified. + assert_equals(len(priorities), len(priority_dict)) + assert all(key in priority_dict for key in expected_sorted_keys) diff --git a/diplomacy/utils/tests/test_sorted_dict.py b/diplomacy/utils/tests/test_sorted_dict.py new file mode 100644 index 0000000..559c36d --- /dev/null +++ b/diplomacy/utils/tests/test_sorted_dict.py @@ -0,0 +1,154 @@ +# ============================================================================== +# Copyright (C) 2019 - Philip Paquette, Steven Bocco +# +# 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 class SortedDict. """ +from diplomacy.utils import common +from diplomacy.utils.sorted_dict import SortedDict +from diplomacy.utils.tests.test_common import assert_equals + +def test_init_bool_and_len(): + """ Test SortedDict initialization, length and conversion to boolean. """ + + sorted_dict = SortedDict(int, str) + assert not sorted_dict + sorted_dict = SortedDict(int, str, {2: 'two', 4: 'four', 99: 'ninety-nine'}) + assert sorted_dict + assert len(sorted_dict) == 3 + +def test_builder_and_properties(): + """ Test SortedDict builder and properties key_type and val_type. """ + + builder_float_to_bool = SortedDict.builder(float, bool) + sorted_dict = builder_float_to_bool({2.5: True, 2.7: False, 2.9: True}) + assert isinstance(sorted_dict, SortedDict) and sorted_dict.key_type is float and sorted_dict.val_type is bool + +def test_items_functions(): + """ Test SortedDict item setter/getter and methods put() and __contains__(). """ + + expected_keys = ['cat', 'lion', 'panthera', 'serval', 'tiger'] + sorted_dict = SortedDict(str, float, {'lion': 1.5, 'tiger': -2.7}) + # Test setter. + sorted_dict['panthera'] = -.88 + sorted_dict['cat'] = 2223. + # Test put(). + sorted_dict.put('serval', 39e12) + # Test __contains__. + assert 'lions' not in sorted_dict + assert all(key in sorted_dict for key in expected_keys) + # Test getter. + assert sorted_dict['cat'] == 2223. + assert sorted_dict['serval'] == 39e12 + # Test setter then getter. + assert sorted_dict['lion'] == 1.5 + sorted_dict['lion'] = 2.3 + assert sorted_dict['lion'] == 2.3 + # Test get, + assert sorted_dict.get('lions') is None + assert sorted_dict.get('lion') == 2.3 + +def test_item_deletion_and_remove(): + """ Test SortedDict methods remove() and __delitem__(). """ + + sorted_dict = SortedDict(str, float, {'lion': 1.5, 'tiger': -2.7, 'panthera': -.88, 'cat': 2223., 'serval': 39e12}) + assert len(sorted_dict) == 5 + assert 'serval' in sorted_dict + sorted_dict.remove('serval') + assert len(sorted_dict) == 4 + assert 'serval' not in sorted_dict + removed = sorted_dict.remove('tiger') + assert len(sorted_dict) == 3 + assert 'tiger' not in sorted_dict + assert removed == -2.7 + assert sorted_dict.remove('tiger') is None + assert sorted_dict.remove('key not in dict') is None + del sorted_dict['panthera'] + assert len(sorted_dict) == 2 + assert 'panthera' not in sorted_dict + assert 'cat' in sorted_dict + assert 'lion' in sorted_dict + +def test_iterations(): + """ Test SortedDict iterations (for key in dict, keys(), values(), items()). """ + + expected_sorted_keys = ['cat', 'lion', 'panthera', 'serval', 'tiger'] + expected_sorted_values = [2223., 1.5, -.88, 39e12, -2.7] + sorted_dict = SortedDict(str, float, {'lion': 1.5, 'tiger': -2.7, 'panthera': -.88, 'cat': 2223., 'serval': 39e12}) + computed_sorted_keys = [key for key in sorted_dict] + computed_sorted_keys_from_keys = list(sorted_dict.keys()) + computed_sorted_values = list(sorted_dict.values()) + keys_from_items = [] + values_from_items = [] + for key, value in sorted_dict.items(): + keys_from_items.append(key) + values_from_items.append(value) + assert_equals(expected_sorted_keys, computed_sorted_keys) + assert_equals(expected_sorted_keys, computed_sorted_keys_from_keys) + assert_equals(expected_sorted_keys, keys_from_items) + assert_equals(expected_sorted_values, values_from_items) + assert_equals(expected_sorted_values, computed_sorted_values) + +def test_bound_keys_getters(): + """ Test SortedDict methods first_key(), last_key(), last_value(), last_item(), + get_previous_key(), get_next_key(). + """ + + sorted_dict = SortedDict(str, float, {'lion': 1.5, 'tiger': -2.7}) + sorted_dict['panthera'] = -.88 + sorted_dict['cat'] = 2223. + sorted_dict['serval'] = 39e12 + assert sorted_dict.first_key() == 'cat' + assert sorted_dict.last_key() == 'tiger' + assert sorted_dict.last_value() == sorted_dict['tiger'] == -2.7 + assert sorted_dict.last_item() == ('tiger', -2.7) + assert sorted_dict.get_previous_key('cat') is None + assert sorted_dict.get_next_key('cat') == 'lion' + assert sorted_dict.get_previous_key('tiger') == 'serval' + assert sorted_dict.get_next_key('tiger') is None + assert sorted_dict.get_previous_key('panthera') == 'lion' + assert sorted_dict.get_next_key('panthera') == 'serval' + +def test_equality(): + """ Test SortedDict equality. """ + + empty_sorted_dict_float_int = SortedDict(float, int) + empty_sorted_dict_float_bool_1 = SortedDict(float, bool) + empty_sorted_dict_float_bool_2 = SortedDict(float, bool) + sorted_dict_float_int_1 = SortedDict(float, int, {2.5: 17, 3.3: 49, -5.7: 71}) + sorted_dict_float_int_2 = SortedDict(float, int, {2.5: 17, 3.3: 49, -5.7: 71}) + sorted_dict_float_int_3 = SortedDict(float, int, {2.5: -17, 3.3: 49, -5.7: 71}) + assert empty_sorted_dict_float_int != empty_sorted_dict_float_bool_1 + assert empty_sorted_dict_float_bool_1 == empty_sorted_dict_float_bool_2 + assert sorted_dict_float_int_1 == sorted_dict_float_int_2 + assert sorted_dict_float_int_1 != sorted_dict_float_int_3 + +def test_sub_and_remove_sub(): + """Test SortedDict methods sub() and remove_sub().""" + + sorted_dict = SortedDict(int, str, {k: 'value of %s' % k for k in (2, 5, 1, 9, 4, 5, 20, 0, 6, 17, 8, 3, 7, 0, 4)}) + assert sorted_dict.sub() == list(sorted_dict.values()) + assert sorted_dict.sub(-10, 4) == ['value of 0', 'value of 1', 'value of 2', 'value of 3', 'value of 4'] + assert sorted_dict.sub(15) == ['value of 17', 'value of 20'] + sorted_dict.remove_sub(-10, 4) + assert all(k not in sorted_dict for k in (0, 1, 2, 3, 4)) + sorted_dict.remove_sub(15) + assert all(k not in sorted_dict for k in (17, 20)) + +def test_is_sequence_and_is_dict(): + """Check sorted dict with is_sequence() and is_dict().""" + + assert common.is_dictionary(SortedDict(str, int, {'a': 3, 'b': -1, 'c': 12})) + assert common.is_dictionary(SortedDict(int, float), ) + assert not common.is_sequence(SortedDict(str, str)) diff --git a/diplomacy/utils/tests/test_sorted_set.py b/diplomacy/utils/tests/test_sorted_set.py new file mode 100644 index 0000000..1208cd3 --- /dev/null +++ b/diplomacy/utils/tests/test_sorted_set.py @@ -0,0 +1,168 @@ +# ============================================================================== +# Copyright (C) 2019 - Philip Paquette, Steven Bocco +# +# 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 class SortedSet. """ +from diplomacy.utils import common +from diplomacy.utils.sorted_set import SortedSet +from diplomacy.utils.tests.test_common import assert_equals + +def test_init_bool_and_len(): + """ Test SortedSet initialization, length and conversion to boolean. """ + + sorted_set = SortedSet(int) + assert not sorted_set + sorted_set = SortedSet(int, (2, 4, 99)) + assert sorted_set + assert len(sorted_set) == 3 + +def test_builder_and_property(): + """ Test SortedSet builder and property element_type. """ + + builder_float = SortedSet.builder(float) + sorted_set = builder_float((2.5, 2.7, 2.9)) + assert isinstance(sorted_set, SortedSet) and sorted_set.element_type is float + +def test_item_add_get_and_contains(): + """ Test SortedSet methods add(), __getitem__(), and __contains__(). """ + + expected_values = ['cat', 'lion', 'panthera', 'serval', 'tiger'] + sorted_set = SortedSet(str, ('lion', 'tiger')) + # Test setter. + sorted_set.add('panthera') + sorted_set.add('cat') + sorted_set.add('serval') + # Test __contains__. + assert 'lions' not in sorted_set + assert all(key in sorted_set for key in expected_values) + # Test getter. + assert sorted_set[0] == 'cat' + assert sorted_set[1] == 'lion' + assert sorted_set[2] == 'panthera' + assert sorted_set[3] == 'serval' + assert sorted_set[4] == 'tiger' + # Test add then getter. + sorted_set.add('onca') + assert sorted_set[1] == 'lion' + assert sorted_set[2] == 'onca' + assert sorted_set[3] == 'panthera' + +def test_pop_and_remove(): + """ Test SortedSet methods remove() and pop(). """ + + sorted_set = SortedSet(str, ('lion', 'tiger', 'panthera', 'cat', 'serval')) + assert len(sorted_set) == 5 + assert 'serval' in sorted_set + sorted_set.remove('serval') + assert len(sorted_set) == 4 + assert 'serval' not in sorted_set + assert sorted_set.remove('tiger') == 'tiger' + assert len(sorted_set) == 3 + assert 'tiger' not in sorted_set + assert sorted_set.remove('tiger') is None + assert sorted_set.remove('key not in set') is None + index_of_panthera = sorted_set.index('panthera') + assert index_of_panthera == 2 + assert sorted_set.pop(index_of_panthera) == 'panthera' + assert len(sorted_set) == 2 + assert 'panthera' not in sorted_set + assert 'cat' in sorted_set + assert 'lion' in sorted_set + +def test_iteration(): + """ Test SortedSet iteration. """ + + expected_sorted_values = ['cat', 'lion', 'panthera', 'serval', 'tiger'] + sorted_set = SortedSet(str, ('lion', 'tiger', 'panthera', 'cat', 'serval')) + computed_sorted_values = [key for key in sorted_set] + assert_equals(expected_sorted_values, computed_sorted_values) + +def test_equality(): + """ Test SortedSet equality. """ + + empty_sorted_set_float = SortedSet(float) + empty_sorted_set_int = SortedSet(int) + another_empty_sorted_set_int = SortedSet(int) + sorted_set_float_1 = SortedSet(float, (2.5, 3.3, -5.7)) + sorted_set_float_2 = SortedSet(float, (2.5, 3.3, -5.7)) + sorted_set_float_3 = SortedSet(float, (2.5, 3.3, 5.7)) + assert empty_sorted_set_float != empty_sorted_set_int + assert empty_sorted_set_int == another_empty_sorted_set_int + assert sorted_set_float_1 == sorted_set_float_2 + assert sorted_set_float_1 != sorted_set_float_3 + +def test_getters_around_values(): + """Test SortedSet methods get_next_value() and get_previous_value().""" + + sorted_set = SortedSet(int, (2, 5, 1, 9, 4, 5, 20, 0, 6, 17, 8, 3, 7, 0, 4)) + expected = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 17, 20) + assert sorted_set + assert len(sorted_set) == len(expected) + assert all(expected[i] == sorted_set[i] for i in range(len(expected))) + assert all(e in sorted_set for e in expected) + assert sorted_set.get_next_value(0) == 1 + assert sorted_set.get_next_value(5) == 6 + assert sorted_set.get_next_value(9) == 17 + assert sorted_set.get_next_value(-1) == 0 + assert sorted_set.get_next_value(20) is None + assert sorted_set.get_previous_value(0) is None + assert sorted_set.get_previous_value(17) == 9 + assert sorted_set.get_previous_value(20) == 17 + assert sorted_set.get_previous_value(1) == 0 + assert sorted_set.get_previous_value(6) == 5 + + assert sorted_set.get_next_value(3) == 4 + assert sorted_set.get_next_value(4) == 5 + assert sorted_set.get_next_value(7) == 8 + assert sorted_set.get_next_value(8) == 9 + assert sorted_set.get_previous_value(5) == 4 + assert sorted_set.get_previous_value(4) == 3 + assert sorted_set.get_previous_value(9) == 8 + assert sorted_set.get_previous_value(8) == 7 + sorted_set.remove(8) + assert len(sorted_set) == len(expected) - 1 + assert 8 not in sorted_set + sorted_set.remove(4) + assert len(sorted_set) == len(expected) - 2 + assert 4 not in sorted_set + assert sorted_set.get_next_value(3) == 5 + assert sorted_set.get_next_value(4) == 5 + assert sorted_set.get_next_value(7) == 9 + assert sorted_set.get_next_value(8) == 9 + assert sorted_set.get_previous_value(5) == 3 + assert sorted_set.get_previous_value(4) == 3 + assert sorted_set.get_previous_value(9) == 7 + assert sorted_set.get_previous_value(8) == 7 + +def test_index(): + """ Test SortedSet method index(). """ + + sorted_set = SortedSet(int, (2, 5, 1, 9, 4, 5, 20, 0, 6, 17, 8, 3, 7, 0, 4)) + sorted_set.remove(8) + sorted_set.remove(4) + index_of_2 = sorted_set.index(2) + index_of_17 = sorted_set.index(17) + assert index_of_2 == 2 + assert sorted_set.index(4) is None + assert sorted_set.index(8) is None + assert index_of_17 == len(sorted_set) - 2 + assert sorted_set.pop(index_of_2) == 2 + +def test_common_utils_with_sorted_set(): + """Check sorted set with is_sequence() and is_dictionary().""" + assert common.is_sequence(SortedSet(int, (1, 2, 3))) + assert common.is_sequence(SortedSet(int)) + assert not common.is_dictionary(SortedSet(int, (1, 2, 3))) + assert not common.is_dictionary(SortedSet(int)) diff --git a/diplomacy/utils/tests/test_time.py b/diplomacy/utils/tests/test_time.py new file mode 100644 index 0000000..a2e7a63 --- /dev/null +++ b/diplomacy/utils/tests/test_time.py @@ -0,0 +1,77 @@ +# ============================================================================== +# Copyright (C) 2019 - Philip Paquette, Steven Bocco +# +# 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 time function""" +from diplomacy.utils import str_to_seconds, next_time_at, trunc_time + +def test_str_to_seconds(): + """ Tests for str_to_seconds """ + assert str_to_seconds('1W') == 604800 + assert str_to_seconds('1D') == 86400 + assert str_to_seconds('1H') == 3600 + assert str_to_seconds('1M') == 60 + assert str_to_seconds('1S') == 1 + assert str_to_seconds('1') == 1 + assert str_to_seconds(1) == 1 + + assert str_to_seconds('10W') == 10 * 604800 + assert str_to_seconds('10D') == 10 * 86400 + assert str_to_seconds('10H') == 10 * 3600 + assert str_to_seconds('10M') == 10 * 60 + assert str_to_seconds('10S') == 10 * 1 + assert str_to_seconds('10') == 10 * 1 + assert str_to_seconds(10) == 10 * 1 + + assert str_to_seconds('1W2D3H4M5S') == 1 * 604800 + 2 * 86400 + 3 * 3600 + 4 * 60 + 5 + assert str_to_seconds('1W2D3H4M5') == 1 * 604800 + 2 * 86400 + 3 * 3600 + 4 * 60 + 5 + assert str_to_seconds('11W12D13H14M15S') == 11 * 604800 + 12 * 86400 + 13 * 3600 + 14 * 60 + 15 + assert str_to_seconds('11W12D13H14M15') == 11 * 604800 + 12 * 86400 + 13 * 3600 + 14 * 60 + 15 + +def test_trunc_time(): + """ Tests for trunc_time """ + # 1498746123 = Thursday, June 29, 2017 10:22:03 AM GMT-04:00 DST + assert trunc_time(1498746123, '1M', 'America/Montreal') == 1498746180 # 10:23 + assert trunc_time(1498746123, '5M', 'America/Montreal') == 1498746300 # 10:25 + assert trunc_time(1498746123, '10M', 'America/Montreal') == 1498746600 # 10:30 + assert trunc_time(1498746123, '15M', 'America/Montreal') == 1498746600 # 10:30 + assert trunc_time(1498746123, '20M', 'America/Montreal') == 1498747200 # 10:40 + assert trunc_time(1498746123, '25M', 'America/Montreal') == 1498746300 # 10:25 + + # 1498731723 = Thursday, June 29, 2017 10:22:03 AM GMT + assert trunc_time(1498731723, '1M', 'GMT') == 1498731780 # 10:23 + assert trunc_time(1498731723, '5M', 'GMT') == 1498731900 # 10:25 + assert trunc_time(1498731723, '10M', 'GMT') == 1498732200 # 10:30 + assert trunc_time(1498731723, '15M', 'GMT') == 1498732200 # 10:30 + assert trunc_time(1498731723, '20M', 'GMT') == 1498732800 # 10:40 + assert trunc_time(1498731723, '25M', 'GMT') == 1498731900 # 10:25 + +def test_next_time_at(): + """ Tests for next_time_at """ + # 1498746123 = Thursday, June 29, 2017 10:22:03 AM GMT-04:00 DST + assert next_time_at(1498746123, '10:23', 'America/Montreal') == 1498746180 # 10:23 + assert next_time_at(1498746123, '10:25', 'America/Montreal') == 1498746300 # 10:25 + assert next_time_at(1498746123, '10:30', 'America/Montreal') == 1498746600 # 10:30 + assert next_time_at(1498746123, '10:40', 'America/Montreal') == 1498747200 # 10:40 + assert next_time_at(1498746123, '16:40', 'America/Montreal') == 1498768800 # 16:40 + assert next_time_at(1498746123, '6:20', 'America/Montreal') == 1498818000 # 6:20 (Next day) + + # 1498731723 = Thursday, June 29, 2017 10:22:03 AM GMT + assert next_time_at(1498731723, '10:23', 'GMT') == 1498731780 # 10:23 + assert next_time_at(1498731723, '10:25', 'GMT') == 1498731900 # 10:25 + assert next_time_at(1498731723, '10:30', 'GMT') == 1498732200 # 10:30 + assert next_time_at(1498731723, '10:40', 'GMT') == 1498732800 # 10:40 + assert next_time_at(1498731723, '16:40', 'GMT') == 1498754400 # 16:40 + assert next_time_at(1498731723, '6:20', 'GMT') == 1498803600 # 6:20 (Next day) |