From f12b73f487c3188a2791eec8ae657fe8ce8889fe Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Tue, 18 Oct 2022 09:54:41 -0700 Subject: [PATCH] Tests: world test base class (#1116) * world test base class * game not instance property * I would have guessed that this only collected 1. * game property * move SoE tests into worlds * don't force auto world setup --- .gitignore | 2 +- BaseClasses.py | 4 +- Options.py | 3 ++ test/worlds/__init__.py | 0 test/{ => worlds}/soe/TestAccess.py | 0 test/{ => worlds}/soe/TestGoal.py | 0 test/worlds/soe/__init__.py | 5 +++ test/{soe/__init__.py => worlds/test_base.py} | 40 +++++++++++++------ 8 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 test/worlds/__init__.py rename test/{ => worlds}/soe/TestAccess.py (100%) rename test/{ => worlds}/soe/TestGoal.py (100%) create mode 100644 test/worlds/soe/__init__.py rename test/{soe/__init__.py => worlds/test_base.py} (70%) diff --git a/.gitignore b/.gitignore index 925a4bd0..3f623168 100644 --- a/.gitignore +++ b/.gitignore @@ -128,7 +128,7 @@ ipython_config.py # Environments .env -.venv +.venv* env/ venv/ ENV/ diff --git a/BaseClasses.py b/BaseClasses.py index 143b1f51..ce2fc9e3 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1,4 +1,5 @@ from __future__ import annotations +from argparse import Namespace import copy from enum import unique, IntEnum, IntFlag @@ -54,6 +55,7 @@ class MultiWorld(): indirect_connections: Dict[Region, Set[Entrance]] exclude_locations: Dict[int, Options.ExcludeLocations] + game: Dict[int, str] class AttributeProxy(): def __init__(self, rule): @@ -200,7 +202,7 @@ class MultiWorld(): self.slot_seeds = {player: random.Random(self.random.getrandbits(64)) for player in range(1, self.players + 1)} - def set_options(self, args): + def set_options(self, args: Namespace) -> None: for option_key in Options.common_options: setattr(self, option_key, getattr(args, option_key, {})) for option_key in Options.per_game_common_options: diff --git a/Options.py b/Options.py index c243c8fe..c2007c1c 100644 --- a/Options.py +++ b/Options.py @@ -78,6 +78,9 @@ class AssembleOptions(abc.ABCMeta): return super(AssembleOptions, mcs).__new__(mcs, name, bases, attrs) + @abc.abstractclassmethod + def from_any(cls, value: typing.Any) -> "Option[typing.Any]": ... + T = typing.TypeVar('T') diff --git a/test/worlds/__init__.py b/test/worlds/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/soe/TestAccess.py b/test/worlds/soe/TestAccess.py similarity index 100% rename from test/soe/TestAccess.py rename to test/worlds/soe/TestAccess.py diff --git a/test/soe/TestGoal.py b/test/worlds/soe/TestGoal.py similarity index 100% rename from test/soe/TestGoal.py rename to test/worlds/soe/TestGoal.py diff --git a/test/worlds/soe/__init__.py b/test/worlds/soe/__init__.py new file mode 100644 index 00000000..c79544e0 --- /dev/null +++ b/test/worlds/soe/__init__.py @@ -0,0 +1,5 @@ +from test.worlds.test_base import WorldTestBase + + +class SoETestBase(WorldTestBase): + game = "Secret of Evermore" diff --git a/test/soe/__init__.py b/test/worlds/test_base.py similarity index 70% rename from test/soe/__init__.py rename to test/worlds/test_base.py index 0161a6c3..1aa6ff23 100644 --- a/test/soe/__init__.py +++ b/test/worlds/test_base.py @@ -7,54 +7,66 @@ from worlds import AutoWorld from worlds.AutoWorld import call_all -class SoETestBase(unittest.TestCase): +class WorldTestBase(unittest.TestCase): options: typing.Dict[str, typing.Any] = {} world: MultiWorld - game = "Secret of Evermore" - def setUp(self): + game: typing.ClassVar[str] # define game name in subclass, example "Secret of Evermore" + auto_construct: typing.ClassVar[bool] = True + """ automatically set up a world for each test in this class """ + + def setUp(self) -> None: + if self.auto_construct: + self.world_setup() + + def world_setup(self) -> None: + if not hasattr(self, "game"): + raise NotImplementedError("didn't define game name") self.world = MultiWorld(1) self.world.game[1] = self.game self.world.player_name = {1: "Tester"} self.world.set_seed() args = Namespace() for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].option_definitions.items(): - setattr(args, name, {1: option.from_any(self.options.get(name, option.default))}) + setattr(args, name, { + 1: option.from_any(self.options.get(name, getattr(option, "default"))) + }) self.world.set_options(args) self.world.set_default_common_options() for step in gen_steps: call_all(self.world, step) - def collect_all_but(self, item_names: typing.Union[str, typing.Iterable[str]]): + def collect_all_but(self, item_names: typing.Union[str, typing.Iterable[str]]) -> None: if isinstance(item_names, str): item_names = (item_names,) for item in self.world.get_items(): if item.name not in item_names: self.world.state.collect(item) - def get_item_by_name(self, item_name: str): + def get_item_by_name(self, item_name: str) -> Item: for item in self.world.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]]): + def get_items_by_name(self, item_names: typing.Union[str, typing.Iterable[str]]) -> typing.List[Item]: if isinstance(item_names, str): item_names = (item_names,) return [item for item in self.world.itempool if item.name in item_names] - def collect_by_name(self, item_names: typing.Union[str, typing.Iterable[str]]): + def collect_by_name(self, item_names: typing.Union[str, typing.Iterable[str]]) -> typing.List[Item]: + """ collect all of the items in the item pool that have the given names """ items = self.get_items_by_name(item_names) self.collect(items) return items - def collect(self, items: typing.Union[Item, typing.Iterable[Item]]): + def collect(self, items: typing.Union[Item, typing.Iterable[Item]]) -> None: if isinstance(items, Item): items = (items,) for item in items: self.world.state.collect(item) - def remove(self, items: typing.Union[Item, typing.Iterable[Item]]): + def remove(self, items: typing.Union[Item, typing.Iterable[Item]]) -> None: if isinstance(items, Item): items = (items,) for item in items: @@ -62,13 +74,15 @@ class SoETestBase(unittest.TestCase): self.world.state.events.remove(item.location) self.world.state.remove(item) - def can_reach_location(self, location): + def can_reach_location(self, location: str) -> bool: return self.world.state.can_reach(location, "Location", 1) - def count(self, item_name): + def count(self, item_name: str) -> int: return self.world.state.count(item_name, 1) - def assertAccessDependency(self, locations, possible_items): + def assertAccessDependency(self, + locations: typing.List[str], + possible_items: typing.Iterable[typing.Iterable[str]]) -> None: all_items = [item_name for item_names in possible_items for item_name in item_names] self.collect_all_but(all_items)