Tests: now autoload tests from /worlds/*/test ()

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
This commit is contained in:
Fabian Dill 2022-12-11 13:15:23 +01:00 committed by GitHub
parent ec45479c52
commit 2dcfbff751
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 715 additions and 683 deletions
.github/workflows
Utils.py
test
worlds

View File

@ -37,4 +37,4 @@ jobs:
python ModuleUpdate.py --yes --force --append "WebHostLib/requirements.txt"
- name: Unittests
run: |
pytest test
pytest

View File

@ -99,7 +99,7 @@ def local_path(*path: str) -> str:
local_path.cached_path = os.path.dirname(os.path.abspath(sys.argv[0]))
else:
import __main__
if hasattr(__main__, "__file__"):
if hasattr(__main__, "__file__") and os.path.isfile(__main__.__file__):
# we are running in a normal Python environment
local_path.cached_path = os.path.dirname(os.path.abspath(__main__.__file__))
else:

View File

@ -1,12 +1,17 @@
import typing
import unittest
import pathlib
from argparse import Namespace
import Utils
from test.general import gen_steps
from worlds import AutoWorld
from worlds.AutoWorld import call_all
file_path = pathlib.Path(__file__).parent.parent
Utils.local_path.cached_path = file_path
from BaseClasses import MultiWorld, CollectionState, ItemClassification
from BaseClasses import MultiWorld, CollectionState, ItemClassification, Item
from worlds.alttp.Items import ItemFactory
@ -92,3 +97,94 @@ class TestBase(unittest.TestCase):
new_items.remove(missing_item)
items = ItemFactory(new_items, 1)
return self.get_state(items)
class WorldTestBase(unittest.TestCase):
options: typing.Dict[str, typing.Any] = {}
multiworld: MultiWorld
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, seed: typing.Optional[int] = None) -> None:
if not hasattr(self, "game"):
raise NotImplementedError("didn't define game name")
self.multiworld = MultiWorld(1)
self.multiworld.game[1] = self.game
self.multiworld.player_name = {1: "Tester"}
self.multiworld.set_seed(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, getattr(option, "default")))
})
self.multiworld.set_options(args)
self.multiworld.set_default_common_options()
for step in gen_steps:
call_all(self.multiworld, step)
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.multiworld.get_items():
if item.name not in item_names:
self.multiworld.state.collect(item)
def get_item_by_name(self, item_name: str) -> Item:
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]:
if isinstance(item_names, str):
item_names = (item_names,)
return [item for item in self.multiworld.itempool if item.name in item_names]
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]]) -> None:
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:
if isinstance(items, Item):
items = (items,)
for item in items:
if item.location and item.location.event and item.location in self.multiworld.state.events:
self.multiworld.state.events.remove(item.location)
self.multiworld.state.remove(item)
def can_reach_location(self, location: str) -> bool:
return self.multiworld.state.can_reach(location, "Location", 1)
def count(self, item_name: str) -> int:
return self.multiworld.state.count(item_name, 1)
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)
for location in self.multiworld.get_locations():
self.assertEqual(self.multiworld.state.can_reach(location), location.name not in locations)
for item_names in possible_items:
items = self.collect_by_name(item_names)
for location in locations:
self.assertTrue(self.can_reach_location(location))
self.remove(items)
def assertBeatable(self, beatable: bool):
self.assertEqual(self.multiworld.can_beat_game(self.multiworld.state), beatable)

View File

@ -1,2 +1,3 @@
import warnings
warnings.simplefilter("always")

View File

@ -9,14 +9,13 @@ import os
import ModuleUpdate
ModuleUpdate.update_ran = True # don't upgrade
import Generate
import Utils
class TestGenerateMain(unittest.TestCase):
"""This tests Generate.py (ArchipelagoGenerate.exe) main"""
generate_dir = Path(Generate.__file__).parent
run_dir = generate_dir / 'test' # reproducible cwd that's neither __file__ nor Generate.__file__
run_dir = generate_dir / "test" # reproducible cwd that's neither __file__ nor Generate.__file__
abs_input_dir = Path(__file__).parent / 'data' / 'OnePlayer'
rel_input_dir = abs_input_dir.relative_to(run_dir) # directly supplied relative paths are relative to cwd
yaml_input_dir = abs_input_dir.relative_to(generate_dir) # yaml paths are relative to user_path
@ -30,12 +29,29 @@ class TestGenerateMain(unittest.TestCase):
f"{list(output_path.glob('*'))}")
def setUp(self):
Utils.local_path.cached_path = str(self.generate_dir)
self.original_argv = sys.argv.copy()
self.original_cwd = os.getcwd()
self.original_local_path = Generate.Utils.local_path.cached_path
self.original_user_path = Generate.Utils.user_path.cached_path
# Force both user_path and local_path to a specific path. They have independent caches.
Generate.Utils.user_path.cached_path = Generate.Utils.local_path.cached_path = str(self.generate_dir)
os.chdir(self.run_dir)
self.output_tempdir = TemporaryDirectory(prefix='AP_out_')
def tearDown(self):
self.output_tempdir.cleanup()
os.chdir(self.original_cwd)
sys.argv = self.original_argv
Generate.Utils.local_path.cached_path = self.original_local_path
Generate.Utils.user_path.cached_path = self.original_user_path
def test_paths(self):
self.assertTrue(os.path.exists(self.generate_dir))
self.assertTrue(os.path.exists(self.run_dir))
self.assertTrue(os.path.exists(self.abs_input_dir))
self.assertTrue(os.path.exists(self.rel_input_dir))
self.assertFalse(os.path.exists(self.yaml_input_dir)) # relative to user_path, not cwd
def test_generate_absolute(self):
sys.argv = [sys.argv[0], '--seed', '0',
@ -57,7 +73,7 @@ class TestGenerateMain(unittest.TestCase):
def test_generate_yaml(self):
# override host.yaml
defaults = Utils.get_options()["generator"]
defaults = Generate.Utils.get_options()["generator"]
defaults["player_files_path"] = str(self.yaml_input_dir)
defaults["players"] = 0

View File

@ -0,0 +1,14 @@
def load_tests(loader, standard_tests, pattern):
import os
import unittest
from ..TestBase import file_path
from worlds.AutoWorld import AutoWorldRegister
suite = unittest.TestSuite()
suite.addTests(standard_tests)
folders = [os.path.join(os.path.split(world.__file__)[0], "test")
for world in AutoWorldRegister.world_types.values()]
for folder in folders:
if os.path.exists(folder):
suite.addTests(loader.discover(folder, top_level_dir=file_path))
return suite

View File

@ -1,98 +0,0 @@
import typing
import unittest
from argparse import Namespace
from test.general import gen_steps
from BaseClasses import MultiWorld, Item
from worlds import AutoWorld
from worlds.AutoWorld import call_all
class WorldTestBase(unittest.TestCase):
options: typing.Dict[str, typing.Any] = {}
multiworld: MultiWorld
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, seed: typing.Optional[int] = None) -> None:
if not hasattr(self, "game"):
raise NotImplementedError("didn't define game name")
self.multiworld = MultiWorld(1)
self.multiworld.game[1] = self.game
self.multiworld.player_name = {1: "Tester"}
self.multiworld.set_seed(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, getattr(option, "default")))
})
self.multiworld.set_options(args)
self.multiworld.set_default_common_options()
for step in gen_steps:
call_all(self.multiworld, step)
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.multiworld.get_items():
if item.name not in item_names:
self.multiworld.state.collect(item)
def get_item_by_name(self, item_name: str) -> Item:
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]:
if isinstance(item_names, str):
item_names = (item_names,)
return [item for item in self.multiworld.itempool if item.name in item_names]
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]]) -> None:
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:
if isinstance(items, Item):
items = (items,)
for item in items:
if item.location and item.location.event and item.location in self.multiworld.state.events:
self.multiworld.state.events.remove(item.location)
self.multiworld.state.remove(item)
def can_reach_location(self, location: str) -> bool:
return self.multiworld.state.can_reach(location, "Location", 1)
def count(self, item_name: str) -> int:
return self.multiworld.state.count(item_name, 1)
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)
for location in self.multiworld.get_locations():
self.assertEqual(self.multiworld.state.can_reach(location), location.name not in locations)
for item_names in possible_items:
items = self.collect_by_name(item_names)
for location in locations:
self.assertTrue(self.can_reach_location(location))
self.remove(items)
def assertBeatable(self, beatable: bool):
self.assertEqual(self.multiworld.can_beat_game(self.multiworld.state), beatable)

View File

@ -1,4 +1,4 @@
from test.dungeons.TestDungeon import TestDungeon
from .TestDungeon import TestDungeon
class TestAgahnimsTower(TestDungeon):

View File

@ -1,4 +1,4 @@
from test.dungeons.TestDungeon import TestDungeon
from .TestDungeon import TestDungeon
class TestDarkPalace(TestDungeon):

View File

@ -1,4 +1,4 @@
from test.dungeons.TestDungeon import TestDungeon
from .TestDungeon import TestDungeon
class TestDesertPalace(TestDungeon):

View File

@ -1,4 +1,4 @@
from test.dungeons.TestDungeon import TestDungeon
from .TestDungeon import TestDungeon
class TestEasternPalace(TestDungeon):

View File

@ -1,4 +1,4 @@
from test.dungeons.TestDungeon import TestDungeon
from .TestDungeon import TestDungeon
class TestGanonsTower(TestDungeon):

View File

@ -1,4 +1,4 @@
from test.dungeons.TestDungeon import TestDungeon
from .TestDungeon import TestDungeon
class TestIcePalace(TestDungeon):

View File

@ -1,4 +1,4 @@
from test.dungeons.TestDungeon import TestDungeon
from .TestDungeon import TestDungeon
class TestMiseryMire(TestDungeon):

View File

@ -1,4 +1,4 @@
from test.dungeons.TestDungeon import TestDungeon
from .TestDungeon import TestDungeon
class TestSkullWoods(TestDungeon):

View File

@ -1,4 +1,4 @@
from test.dungeons.TestDungeon import TestDungeon
from .TestDungeon import TestDungeon
class TestSwampPalace(TestDungeon):

View File

@ -1,4 +1,4 @@
from test.dungeons.TestDungeon import TestDungeon
from .TestDungeon import TestDungeon
class TestThievesTown(TestDungeon):

View File

@ -1,4 +1,4 @@
from test.dungeons.TestDungeon import TestDungeon
from .TestDungeon import TestDungeon
class TestTowerOfHera(TestDungeon):

View File

@ -1,4 +1,4 @@
from test.inverted.TestInverted import TestInverted
from .TestInverted import TestInverted
class TestInvertedDarkWorld(TestInverted):

View File

@ -1,4 +1,4 @@
from test.inverted.TestInverted import TestInverted
from .TestInverted import TestInverted
class TestInvertedDeathMountain(TestInverted):

View File

@ -1,4 +1,4 @@
from test.inverted.TestInverted import TestInverted
from .TestInverted import TestInverted
class TestEntrances(TestInverted):

View File

@ -1,4 +1,4 @@
from test.inverted.TestInverted import TestInverted
from .TestInverted import TestInverted
class TestInvertedLightWorld(TestInverted):

View File

@ -1,4 +1,4 @@
from test.inverted.TestInverted import TestInverted
from .TestInverted import TestInverted
class TestInvertedTurtleRock(TestInverted):

View File

@ -1,4 +1,4 @@
from test.inverted_minor_glitches.TestInvertedMinor import TestInvertedMinor
from .TestInvertedMinor import TestInvertedMinor
class TestInvertedDarkWorld(TestInvertedMinor):

View File

@ -1,4 +1,4 @@
from test.inverted_minor_glitches.TestInvertedMinor import TestInvertedMinor
from .TestInvertedMinor import TestInvertedMinor
class TestInvertedDeathMountain(TestInvertedMinor):

View File

@ -1,4 +1,4 @@
from test.inverted_minor_glitches.TestInvertedMinor import TestInvertedMinor
from .TestInvertedMinor import TestInvertedMinor
class TestEntrances(TestInvertedMinor):

View File

@ -1,4 +1,4 @@
from test.inverted_minor_glitches.TestInvertedMinor import TestInvertedMinor
from .TestInvertedMinor import TestInvertedMinor
class TestInvertedLightWorld(TestInvertedMinor):

View File

@ -1,4 +1,4 @@
from test.inverted_minor_glitches.TestInvertedMinor import TestInvertedMinor
from .TestInvertedMinor import TestInvertedMinor
class TestInvertedTurtleRock(TestInvertedMinor):

View File

@ -1,4 +1,4 @@
from test.inverted_owg.TestInvertedOWG import TestInvertedOWG
from .TestInvertedOWG import TestInvertedOWG
class TestDarkWorld(TestInvertedOWG):

View File

@ -1,4 +1,4 @@
from test.inverted_owg.TestInvertedOWG import TestInvertedOWG
from .TestInvertedOWG import TestInvertedOWG
class TestDeathMountain(TestInvertedOWG):

View File

@ -1,4 +1,5 @@
from test.inverted_owg.TestInvertedOWG import TestInvertedOWG
from .TestInvertedOWG import TestInvertedOWG
class TestDungeons(TestInvertedOWG):

View File

@ -1,4 +1,4 @@
from test.inverted_owg.TestInvertedOWG import TestInvertedOWG
from .TestInvertedOWG import TestInvertedOWG
class TestLightWorld(TestInvertedOWG):

View File

@ -1,4 +1,4 @@
from test.minor_glitches.TestMinor import TestMinor
from .TestMinor import TestMinor
class TestDarkWorld(TestMinor):

View File

@ -1,4 +1,4 @@
from test.minor_glitches.TestMinor import TestMinor
from .TestMinor import TestMinor
class TestDeathMountain(TestMinor):

View File

@ -1,4 +1,4 @@
from test.minor_glitches.TestMinor import TestMinor
from .TestMinor import TestMinor
class TestEntrances(TestMinor):

View File

@ -1,4 +1,4 @@
from test.minor_glitches.TestMinor import TestMinor
from .TestMinor import TestMinor
class TestLightWorld(TestMinor):

View File

@ -1,4 +1,4 @@
from test.owg.TestVanillaOWG import TestVanillaOWG
from .TestVanillaOWG import TestVanillaOWG
class TestDarkWorld(TestVanillaOWG):

View File

@ -1,4 +1,4 @@
from test.owg.TestVanillaOWG import TestVanillaOWG
from .TestVanillaOWG import TestVanillaOWG
class TestDeathMountain(TestVanillaOWG):

View File

@ -1,4 +1,4 @@
from test.owg.TestVanillaOWG import TestVanillaOWG
from .TestVanillaOWG import TestVanillaOWG
class TestDungeons(TestVanillaOWG):

View File

@ -1,4 +1,4 @@
from test.owg.TestVanillaOWG import TestVanillaOWG
from .TestVanillaOWG import TestVanillaOWG
class TestLightWorld(TestVanillaOWG):

View File

@ -1,4 +1,4 @@
from test.vanilla.TestVanilla import TestVanilla
from .TestVanilla import TestVanilla
class TestDarkWorld(TestVanilla):

View File

@ -1,4 +1,4 @@
from test.vanilla.TestVanilla import TestVanilla
from .TestVanilla import TestVanilla
class TestDeathMountain(TestVanilla):

View File

@ -1,4 +1,4 @@
from test.vanilla.TestVanilla import TestVanilla
from .TestVanilla import TestVanilla
class TestEntrances(TestVanilla):

View File

@ -1,4 +1,4 @@
from test.vanilla.TestVanilla import TestVanilla
from .TestVanilla import TestVanilla
class TestLightWorld(TestVanilla):

View File

@ -1,4 +1,5 @@
from test.minecraft.TestMinecraft import TestMinecraft
from .TestMinecraft import TestMinecraft
class TestEntrances(TestMinecraft):

View File

@ -1,11 +1,12 @@
import worlds.minecraft.Options
from test.TestBase import TestBase
from BaseClasses import MultiWorld, ItemClassification
from worlds import AutoWorld
from worlds.minecraft import MinecraftWorld
from worlds.minecraft.Items import MinecraftItem, item_table
from worlds.minecraft.Options import *
from Options import Toggle, Range
from Options import Toggle
from worlds.minecraft.Options import AdvancementGoal, EggShardsRequired, EggShardsAvailable, BossGoal, BeeTraps, \
ShuffleStructures, CombatDifficulty
# Converts the name of an item into an item object
def MCItemFactory(items, player: int):
@ -27,6 +28,7 @@ def MCItemFactory(items, player: int):
return ret[0]
return ret
class TestMinecraft(TestBase):
def setUp(self):
@ -39,10 +41,10 @@ class TestMinecraft(TestBase):
setattr(self.multiworld, "advancement_goal", {1: AdvancementGoal(30)})
setattr(self.multiworld, "egg_shards_required", {1: EggShardsRequired(0)})
setattr(self.multiworld, "egg_shards_available", {1: EggShardsAvailable(0)})
setattr(self.multiworld, "required_bosses", {1: BossGoal(1)}) # ender dragon
setattr(self.multiworld, "required_bosses", {1: BossGoal(1)}) # ender dragon
setattr(self.multiworld, "shuffle_structures", {1: ShuffleStructures(False)})
setattr(self.multiworld, "bee_traps", {1: BeeTraps(0)})
setattr(self.multiworld, "combat_difficulty", {1: CombatDifficulty(1)}) # normal
setattr(self.multiworld, "combat_difficulty", {1: CombatDifficulty(1)}) # normal
setattr(self.multiworld, "structure_compasses", {1: Toggle(False)})
setattr(self.multiworld, "death_link", {1: Toggle(False)})
AutoWorld.call_single(self.multiworld, "create_regions", 1)
@ -64,4 +66,3 @@ class TestMinecraft(TestBase):
new_items.remove(missing_item)
items = MCItemFactory(new_items, 1)
return self.get_state(items)

View File

@ -1,5 +1,4 @@
import unittest
import json
from random import Random

View File

View File

@ -1,4 +1,4 @@
from test.worlds.test_base import WorldTestBase
from test.TestBase import WorldTestBase
class RLTestBase(WorldTestBase):

View File

@ -1,4 +1,4 @@
from test.worlds.test_base import WorldTestBase
from test.TestBase import WorldTestBase
class SoETestBase(WorldTestBase):

View File

@ -1,4 +1,4 @@
from test.worlds.zillion import ZillionTestBase
from . import ZillionTestBase
from worlds.zillion.options import ZillionJumpLevels, ZillionGunLevels, validate
from zilliandomizer.options import VBLR_CHOICES

View File

@ -1,5 +1,5 @@
from typing import cast
from test.worlds.zillion import ZillionTestBase
from . import ZillionTestBase
from worlds.zillion import ZillionWorld

View File

@ -1,5 +1,5 @@
from typing import cast
from test.worlds.test_base import WorldTestBase
from test.TestBase import WorldTestBase
from worlds.zillion import ZillionWorld