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,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)

View file

@ -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

View file

@ -0,0 +1,190 @@
# ==============================================================================
# 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

View file

@ -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()))

View file

@ -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)

View file

@ -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))

View file

@ -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))

View file

@ -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)