Tests: test all state and empty state on world test bases (#1476)
* Tests: test all state and empty state on world test bases * actually add the test methods to the dict * only test if the world test base has non default options * remove temp logging * ditch the meta class and document methods * Tests: WorldTestBase comment and docstring cleanup * skip default tests if setUp or world_setup are modified and use a property * negation hurts my head * docstring * use a better name for the property --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
This commit is contained in:
parent
af7d0dbf37
commit
f3bdf0c5ed
|
@ -112,6 +112,8 @@ class WorldTestBase(unittest.TestCase):
|
|||
self.world_setup()
|
||||
|
||||
def world_setup(self, seed: typing.Optional[int] = None) -> None:
|
||||
if type(self) is WorldTestBase:
|
||||
return # setUp gets called for tests defined in the base class. We skip world_setup here.
|
||||
if not hasattr(self, "game"):
|
||||
raise NotImplementedError("didn't define game name")
|
||||
self.multiworld = MultiWorld(1)
|
||||
|
@ -128,7 +130,9 @@ class WorldTestBase(unittest.TestCase):
|
|||
for step in gen_steps:
|
||||
call_all(self.multiworld, step)
|
||||
|
||||
# methods that can be called within tests
|
||||
def collect_all_but(self, item_names: typing.Union[str, typing.Iterable[str]]) -> None:
|
||||
"""Collects all pre-placed items and items in the multiworld itempool except those provided"""
|
||||
if isinstance(item_names, str):
|
||||
item_names = (item_names,)
|
||||
for item in self.multiworld.get_items():
|
||||
|
@ -136,12 +140,14 @@ class WorldTestBase(unittest.TestCase):
|
|||
self.multiworld.state.collect(item)
|
||||
|
||||
def get_item_by_name(self, item_name: str) -> Item:
|
||||
"""Returns the first item found in placed items, or in the itempool with the matching name"""
|
||||
for item in self.multiworld.get_items():
|
||||
if item.name == item_name:
|
||||
return item
|
||||
raise ValueError("No such item")
|
||||
|
||||
def get_items_by_name(self, item_names: typing.Union[str, typing.Iterable[str]]) -> typing.List[Item]:
|
||||
"""Returns actual items from the itempool that match the provided name(s)"""
|
||||
if isinstance(item_names, str):
|
||||
item_names = (item_names,)
|
||||
return [item for item in self.multiworld.itempool if item.name in item_names]
|
||||
|
@ -153,12 +159,14 @@ class WorldTestBase(unittest.TestCase):
|
|||
return items
|
||||
|
||||
def collect(self, items: typing.Union[Item, typing.Iterable[Item]]) -> None:
|
||||
"""Collects the provided item(s) into state"""
|
||||
if isinstance(items, Item):
|
||||
items = (items,)
|
||||
for item in items:
|
||||
self.multiworld.state.collect(item)
|
||||
|
||||
def remove(self, items: typing.Union[Item, typing.Iterable[Item]]) -> None:
|
||||
"""Removes the provided item(s) from state"""
|
||||
if isinstance(items, Item):
|
||||
items = (items,)
|
||||
for item in items:
|
||||
|
@ -167,17 +175,22 @@ class WorldTestBase(unittest.TestCase):
|
|||
self.multiworld.state.remove(item)
|
||||
|
||||
def can_reach_location(self, location: str) -> bool:
|
||||
"""Determines if the current state can reach the provide location name"""
|
||||
return self.multiworld.state.can_reach(location, "Location", 1)
|
||||
|
||||
def can_reach_entrance(self, entrance: str) -> bool:
|
||||
"""Determines if the current state can reach the provided entrance name"""
|
||||
return self.multiworld.state.can_reach(entrance, "Entrance", 1)
|
||||
|
||||
def count(self, item_name: str) -> int:
|
||||
"""Returns the amount of an item currently in state"""
|
||||
return self.multiworld.state.count(item_name, 1)
|
||||
|
||||
def assertAccessDependency(self,
|
||||
locations: typing.List[str],
|
||||
possible_items: typing.Iterable[typing.Iterable[str]]) -> None:
|
||||
"""Asserts that the provided locations can't be reached without the listed items but can be reached with any
|
||||
one of the provided combinations"""
|
||||
all_items = [item_name for item_names in possible_items for item_name in item_names]
|
||||
|
||||
self.collect_all_but(all_items)
|
||||
|
@ -190,4 +203,36 @@ class WorldTestBase(unittest.TestCase):
|
|||
self.remove(items)
|
||||
|
||||
def assertBeatable(self, beatable: bool):
|
||||
"""Asserts that the game can be beaten with the current state"""
|
||||
self.assertEqual(self.multiworld.can_beat_game(self.multiworld.state), beatable)
|
||||
|
||||
# following tests are automatically run
|
||||
@property
|
||||
def skip_default_tests(self) -> bool:
|
||||
"""Not possible or identical to the base test that's always being run already"""
|
||||
constructed = hasattr(self, "game") and hasattr(self, "multiworld")
|
||||
return not constructed or (not self.options
|
||||
and self.setUp is WorldTestBase.setUp
|
||||
and self.world_setup is WorldTestBase.world_setup)
|
||||
|
||||
def testAllStateCanReachEverything(self):
|
||||
"""Ensure all state can reach everything with the defined options"""
|
||||
if self.skip_default_tests:
|
||||
return
|
||||
with self.subTest("Game", game=self.game):
|
||||
excluded = self.multiworld.exclude_locations[1].value
|
||||
state = self.multiworld.get_all_state(False)
|
||||
for location in self.multiworld.get_locations():
|
||||
if location.name not in excluded:
|
||||
with self.subTest("Location should be reached", location=location):
|
||||
self.assertTrue(location.can_reach(state), f"{location.name} unreachable")
|
||||
|
||||
def testEmptyStateCanReachSomething(self):
|
||||
"""Ensure empty state can reach at least one location with the defined options"""
|
||||
if self.skip_default_tests:
|
||||
return
|
||||
with self.subTest("Game", game=self.game):
|
||||
state = CollectionState(self.multiworld)
|
||||
locations = self.multiworld.get_reachable_locations(state, 1)
|
||||
self.assertGreater(len(locations), 0,
|
||||
"Need to be able to reach at least one location to get started.")
|
||||
|
|
Loading…
Reference in New Issue