2023-07-04 17:12:43 +00:00
|
|
|
# Tests for _speedups.LocationStore and NetUtils._LocationStore
|
2024-06-12 16:54:59 +00:00
|
|
|
import os
|
2023-07-04 17:12:43 +00:00
|
|
|
import typing
|
|
|
|
import unittest
|
2023-07-05 08:35:03 +00:00
|
|
|
import warnings
|
2023-07-04 17:12:43 +00:00
|
|
|
from NetUtils import LocationStore, _LocationStore
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
State = typing.Dict[typing.Tuple[int, int], typing.Set[int]]
|
|
|
|
RawLocations = typing.Dict[int, typing.Dict[int, typing.Tuple[int, int, int]]]
|
2023-07-04 17:12:43 +00:00
|
|
|
|
2024-06-12 16:54:59 +00:00
|
|
|
ci = bool(os.environ.get("CI")) # always set in GitHub actions
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
sample_data: RawLocations = {
|
2023-07-04 17:12:43 +00:00
|
|
|
1: {
|
|
|
|
11: (21, 2, 7),
|
|
|
|
12: (22, 2, 0),
|
|
|
|
13: (13, 1, 0),
|
|
|
|
},
|
|
|
|
2: {
|
|
|
|
23: (11, 1, 0),
|
|
|
|
22: (12, 1, 0),
|
|
|
|
21: (23, 2, 0),
|
|
|
|
},
|
|
|
|
4: {
|
|
|
|
9: (99, 3, 0),
|
|
|
|
},
|
|
|
|
3: {
|
|
|
|
9: (99, 4, 0),
|
|
|
|
},
|
2024-06-12 16:54:59 +00:00
|
|
|
5: {
|
|
|
|
9: (99, 5, 0),
|
|
|
|
}
|
2023-07-04 17:12:43 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
empty_state: State = {
|
2023-07-04 17:12:43 +00:00
|
|
|
(0, slot): set() for slot in sample_data
|
|
|
|
}
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
full_state: State = {
|
2023-07-04 17:12:43 +00:00
|
|
|
(0, slot): set(locations) for (slot, locations) in sample_data.items()
|
|
|
|
}
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
one_state: State = {
|
2023-07-04 17:12:43 +00:00
|
|
|
(0, 1): {12}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Base:
|
|
|
|
class TestLocationStore(unittest.TestCase):
|
2023-07-05 08:35:03 +00:00
|
|
|
"""Test method calls on a loaded store."""
|
2023-07-04 17:12:43 +00:00
|
|
|
store: typing.Union[LocationStore, _LocationStore]
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
def test_len(self) -> None:
|
2024-06-12 16:54:59 +00:00
|
|
|
self.assertEqual(len(self.store), 5)
|
2023-07-04 17:12:43 +00:00
|
|
|
self.assertEqual(len(self.store[1]), 3)
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
def test_key_error(self) -> None:
|
2023-07-04 17:12:43 +00:00
|
|
|
with self.assertRaises(KeyError):
|
|
|
|
_ = self.store[0]
|
|
|
|
with self.assertRaises(KeyError):
|
2024-06-12 16:54:59 +00:00
|
|
|
_ = self.store[6]
|
2023-07-04 17:12:43 +00:00
|
|
|
locations = self.store[1] # no Exception
|
|
|
|
with self.assertRaises(KeyError):
|
|
|
|
_ = locations[7]
|
|
|
|
_ = locations[11] # no Exception
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
def test_getitem(self) -> None:
|
2023-07-04 17:12:43 +00:00
|
|
|
self.assertEqual(self.store[1][11], (21, 2, 7))
|
|
|
|
self.assertEqual(self.store[1][13], (13, 1, 0))
|
|
|
|
self.assertEqual(self.store[2][22], (12, 1, 0))
|
|
|
|
self.assertEqual(self.store[4][9], (99, 3, 0))
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
def test_get(self) -> None:
|
2023-07-04 17:12:43 +00:00
|
|
|
self.assertEqual(self.store.get(1, None), self.store[1])
|
|
|
|
self.assertEqual(self.store.get(0, None), None)
|
|
|
|
self.assertEqual(self.store[1].get(11, (None, None, None)), self.store[1][11])
|
|
|
|
self.assertEqual(self.store[1].get(10, (None, None, None)), (None, None, None))
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
def test_iter(self) -> None:
|
2024-06-12 16:54:59 +00:00
|
|
|
self.assertEqual(sorted(self.store), [1, 2, 3, 4, 5])
|
2023-07-04 17:12:43 +00:00
|
|
|
self.assertEqual(len(self.store), len(sample_data))
|
|
|
|
self.assertEqual(list(self.store[1]), [11, 12, 13])
|
|
|
|
self.assertEqual(len(self.store[1]), len(sample_data[1]))
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
def test_items(self) -> None:
|
2023-07-04 17:12:43 +00:00
|
|
|
self.assertEqual(sorted(p for p, _ in self.store.items()), sorted(self.store))
|
|
|
|
self.assertEqual(sorted(p for p, _ in self.store[1].items()), sorted(self.store[1]))
|
|
|
|
self.assertEqual(sorted(self.store.items())[0][0], 1)
|
|
|
|
self.assertEqual(sorted(self.store.items())[0][1], self.store[1])
|
|
|
|
self.assertEqual(sorted(self.store[1].items())[0][0], 11)
|
|
|
|
self.assertEqual(sorted(self.store[1].items())[0][1], self.store[1][11])
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
def test_find_item(self) -> None:
|
2024-06-12 16:54:59 +00:00
|
|
|
# empty player set
|
2023-07-04 17:12:43 +00:00
|
|
|
self.assertEqual(sorted(self.store.find_item(set(), 99)), [])
|
2024-06-12 16:54:59 +00:00
|
|
|
# no such player, single
|
|
|
|
self.assertEqual(sorted(self.store.find_item({6}, 99)), [])
|
|
|
|
# no such player, set
|
|
|
|
self.assertEqual(sorted(self.store.find_item({7, 8, 9}, 99)), [])
|
|
|
|
# no such item
|
2023-07-04 17:12:43 +00:00
|
|
|
self.assertEqual(sorted(self.store.find_item({3}, 1)), [])
|
2024-06-12 16:54:59 +00:00
|
|
|
# valid matches
|
2023-07-04 17:12:43 +00:00
|
|
|
self.assertEqual(sorted(self.store.find_item({3}, 99)),
|
|
|
|
[(4, 9, 99, 3, 0)])
|
|
|
|
self.assertEqual(sorted(self.store.find_item({3, 4}, 99)),
|
|
|
|
[(3, 9, 99, 4, 0), (4, 9, 99, 3, 0)])
|
2024-06-12 16:54:59 +00:00
|
|
|
self.assertEqual(sorted(self.store.find_item({2, 3, 4}, 99)),
|
|
|
|
[(3, 9, 99, 4, 0), (4, 9, 99, 3, 0)])
|
|
|
|
# test hash collision in set
|
|
|
|
self.assertEqual(sorted(self.store.find_item({3, 5}, 99)),
|
|
|
|
[(4, 9, 99, 3, 0), (5, 9, 99, 5, 0)])
|
|
|
|
self.assertEqual(sorted(self.store.find_item(set(range(2048)), 13)),
|
|
|
|
[(1, 13, 13, 1, 0)])
|
2023-07-04 17:12:43 +00:00
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
def test_get_for_player(self) -> None:
|
2023-07-04 17:12:43 +00:00
|
|
|
self.assertEqual(self.store.get_for_player(3), {4: {9}})
|
|
|
|
self.assertEqual(self.store.get_for_player(1), {1: {13}, 2: {22, 23}})
|
|
|
|
|
2023-07-29 17:44:10 +00:00
|
|
|
def test_get_checked(self) -> None:
|
2023-07-04 17:12:43 +00:00
|
|
|
self.assertEqual(self.store.get_checked(full_state, 0, 1), [11, 12, 13])
|
|
|
|
self.assertEqual(self.store.get_checked(one_state, 0, 1), [12])
|
|
|
|
self.assertEqual(self.store.get_checked(empty_state, 0, 1), [])
|
|
|
|
self.assertEqual(self.store.get_checked(full_state, 0, 3), [9])
|
|
|
|
|
2023-07-29 17:44:10 +00:00
|
|
|
def test_get_missing(self) -> None:
|
2023-07-04 17:12:43 +00:00
|
|
|
self.assertEqual(self.store.get_missing(full_state, 0, 1), [])
|
|
|
|
self.assertEqual(self.store.get_missing(one_state, 0, 1), [11, 13])
|
|
|
|
self.assertEqual(self.store.get_missing(empty_state, 0, 1), [11, 12, 13])
|
|
|
|
self.assertEqual(self.store.get_missing(empty_state, 0, 3), [9])
|
|
|
|
|
2023-07-29 17:44:10 +00:00
|
|
|
def test_get_remaining(self) -> None:
|
2023-07-04 17:12:43 +00:00
|
|
|
self.assertEqual(self.store.get_remaining(full_state, 0, 1), [])
|
2024-08-16 20:20:02 +00:00
|
|
|
self.assertEqual(self.store.get_remaining(one_state, 0, 1), [(1, 13), (2, 21)])
|
|
|
|
self.assertEqual(self.store.get_remaining(empty_state, 0, 1), [(1, 13), (2, 21), (2, 22)])
|
|
|
|
self.assertEqual(self.store.get_remaining(empty_state, 0, 3), [(4, 99)])
|
2023-07-04 17:12:43 +00:00
|
|
|
|
2023-07-29 17:44:10 +00:00
|
|
|
def test_location_set_intersection(self) -> None:
|
|
|
|
locations = {10, 11, 12}
|
|
|
|
locations.intersection_update(self.store[1])
|
|
|
|
self.assertEqual(locations, {11, 12})
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
class TestLocationStoreConstructor(unittest.TestCase):
|
|
|
|
"""Test constructors for a given store type."""
|
|
|
|
type: type
|
|
|
|
|
|
|
|
def test_hole(self) -> None:
|
|
|
|
with self.assertRaises(Exception):
|
|
|
|
self.type({
|
|
|
|
1: {1: (1, 1, 1)},
|
|
|
|
3: {1: (1, 1, 1)},
|
|
|
|
})
|
|
|
|
|
|
|
|
def test_no_slot1(self) -> None:
|
|
|
|
with self.assertRaises(Exception):
|
|
|
|
self.type({
|
|
|
|
2: {1: (1, 1, 1)},
|
|
|
|
3: {1: (1, 1, 1)},
|
|
|
|
})
|
|
|
|
|
|
|
|
def test_slot0(self) -> None:
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
self.type({
|
|
|
|
0: {1: (1, 1, 1)},
|
|
|
|
1: {1: (1, 1, 1)},
|
|
|
|
})
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
self.type({
|
|
|
|
0: {1: (1, 1, 1)},
|
|
|
|
2: {1: (1, 1, 1)},
|
|
|
|
})
|
|
|
|
|
|
|
|
def test_no_players(self) -> None:
|
|
|
|
with self.assertRaises(Exception):
|
|
|
|
_ = self.type({})
|
|
|
|
|
|
|
|
def test_no_locations(self) -> None:
|
|
|
|
with warnings.catch_warnings():
|
|
|
|
warnings.simplefilter("ignore")
|
|
|
|
store = self.type({
|
|
|
|
1: {},
|
|
|
|
})
|
|
|
|
self.assertEqual(len(store), 1)
|
|
|
|
self.assertEqual(len(store[1]), 0)
|
|
|
|
|
|
|
|
def test_no_locations_for_1(self) -> None:
|
|
|
|
store = self.type({
|
|
|
|
1: {},
|
|
|
|
2: {1: (1, 2, 3)},
|
|
|
|
})
|
|
|
|
self.assertEqual(len(store), 2)
|
|
|
|
self.assertEqual(len(store[1]), 0)
|
|
|
|
self.assertEqual(len(store[2]), 1)
|
|
|
|
|
|
|
|
def test_no_locations_for_last(self) -> None:
|
|
|
|
store = self.type({
|
|
|
|
1: {1: (1, 2, 3)},
|
|
|
|
2: {},
|
|
|
|
})
|
|
|
|
self.assertEqual(len(store), 2)
|
|
|
|
self.assertEqual(len(store[1]), 1)
|
|
|
|
self.assertEqual(len(store[2]), 0)
|
|
|
|
|
2023-07-04 17:12:43 +00:00
|
|
|
|
|
|
|
class TestPurePythonLocationStore(Base.TestLocationStore):
|
2023-07-05 08:35:03 +00:00
|
|
|
"""Run base method tests for pure python implementation."""
|
2023-07-04 17:12:43 +00:00
|
|
|
def setUp(self) -> None:
|
|
|
|
self.store = _LocationStore(sample_data)
|
|
|
|
super().setUp()
|
|
|
|
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
class TestPurePythonLocationStoreConstructor(Base.TestLocationStoreConstructor):
|
|
|
|
"""Run base constructor tests for the pure python implementation."""
|
|
|
|
def setUp(self) -> None:
|
|
|
|
self.type = _LocationStore
|
|
|
|
super().setUp()
|
|
|
|
|
|
|
|
|
2024-06-12 16:54:59 +00:00
|
|
|
@unittest.skipIf(LocationStore is _LocationStore and not ci, "_speedups not available")
|
2023-07-04 17:12:43 +00:00
|
|
|
class TestSpeedupsLocationStore(Base.TestLocationStore):
|
2023-07-05 08:35:03 +00:00
|
|
|
"""Run base method tests for cython implementation."""
|
2023-07-04 17:12:43 +00:00
|
|
|
def setUp(self) -> None:
|
2024-06-12 16:54:59 +00:00
|
|
|
self.assertFalse(LocationStore is _LocationStore, "Failed to load _speedups")
|
2023-07-04 17:12:43 +00:00
|
|
|
self.store = LocationStore(sample_data)
|
|
|
|
super().setUp()
|
|
|
|
|
|
|
|
|
2024-06-12 16:54:59 +00:00
|
|
|
@unittest.skipIf(LocationStore is _LocationStore and not ci, "_speedups not available")
|
2023-07-05 08:35:03 +00:00
|
|
|
class TestSpeedupsLocationStoreConstructor(Base.TestLocationStoreConstructor):
|
|
|
|
"""Run base constructor tests and tests the additional constraints for cython implementation."""
|
|
|
|
def setUp(self) -> None:
|
2024-06-12 16:54:59 +00:00
|
|
|
self.assertFalse(LocationStore is _LocationStore, "Failed to load _speedups")
|
2023-07-05 08:35:03 +00:00
|
|
|
self.type = LocationStore
|
|
|
|
super().setUp()
|
|
|
|
|
|
|
|
def test_float_key(self) -> None:
|
2023-07-04 17:12:43 +00:00
|
|
|
with self.assertRaises(Exception):
|
2023-07-05 08:35:03 +00:00
|
|
|
self.type({
|
2023-07-04 17:12:43 +00:00
|
|
|
1: {1: (1, 1, 1)},
|
|
|
|
1.1: {1: (1, 1, 1)},
|
|
|
|
3: {1: (1, 1, 1)}
|
|
|
|
})
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
def test_string_key(self) -> None:
|
2023-07-04 17:12:43 +00:00
|
|
|
with self.assertRaises(Exception):
|
2023-07-05 08:35:03 +00:00
|
|
|
self.type({
|
2023-07-04 17:12:43 +00:00
|
|
|
"1": {1: (1, 1, 1)},
|
|
|
|
})
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
def test_high_player_number(self) -> None:
|
2023-07-04 17:12:43 +00:00
|
|
|
with self.assertRaises(Exception):
|
2023-07-05 08:35:03 +00:00
|
|
|
self.type({
|
2023-07-04 17:12:43 +00:00
|
|
|
1 << 32: {1: (1, 1, 1)},
|
|
|
|
})
|
|
|
|
|
2023-07-05 08:35:03 +00:00
|
|
|
def test_not_a_tuple(self) -> None:
|
2023-07-04 17:12:43 +00:00
|
|
|
with self.assertRaises(Exception):
|
2023-07-05 08:35:03 +00:00
|
|
|
self.type({
|
2023-07-04 17:12:43 +00:00
|
|
|
1: {1: None},
|
|
|
|
})
|