Skip to content

Commit

Permalink
Tests: add test for 2-player-multiworlds (#2386)
Browse files Browse the repository at this point in the history
* Tests: add test for all games multiworld and test for two player multiworld per game

* make assertSteps behave like call_all

* review improvements

* fix stage calling and loc copying in accessibility

* add docstrings

* lttp is on the options api now

* skip the all games multiworld for now. likely needs to be modified to test specific worlds

* move skip to the class
  • Loading branch information
alwaysintreble authored Mar 11, 2024
1 parent 5fecb7f commit 078d793
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 8 deletions.
35 changes: 27 additions & 8 deletions test/general/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from argparse import Namespace
from typing import Optional, Tuple, Type
from typing import List, Optional, Tuple, Type, Union

from BaseClasses import MultiWorld, CollectionState
from worlds.AutoWorld import call_all, World
from BaseClasses import CollectionState, MultiWorld
from worlds.AutoWorld import World, call_all

gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill")

Expand All @@ -18,14 +18,33 @@ def setup_solo_multiworld(
steps through pre_fill
:param seed: The seed to be used when creating this multiworld
"""
multiworld = MultiWorld(1)
multiworld.game[1] = world_type.game
multiworld.player_name = {1: "Tester"}
return setup_multiworld(world_type, steps, seed)


def setup_multiworld(worlds: Union[List[Type[World]], Type[World]], steps: Tuple[str, ...] = gen_steps,
seed: Optional[int] = None) -> MultiWorld:
"""
Creates a multiworld with a player for each provided world type, allowing duplicates, setting default options, and
calling the provided gen steps.
:param worlds: type/s of worlds to generate a multiworld for
:param steps: gen steps that should be called before returning. Default calls through pre_fill
:param seed: The seed to be used when creating this multiworld
"""
if not isinstance(worlds, list):
worlds = [worlds]
players = len(worlds)
multiworld = MultiWorld(players)
multiworld.game = {player: world_type.game for player, world_type in enumerate(worlds, 1)}
multiworld.player_name = {player: f"Tester{player}" for player in multiworld.player_ids}
multiworld.set_seed(seed)
multiworld.state = CollectionState(multiworld)
args = Namespace()
for name, option in world_type.options_dataclass.type_hints.items():
setattr(args, name, {1: option.from_any(option.default)})
for player, world_type in enumerate(worlds, 1):
for key, option in world_type.options_dataclass.type_hints.items():
updated_options = getattr(args, key, {})
updated_options[player] = option.from_any(option.default)
setattr(args, key, updated_options)
multiworld.set_options(args)
for step in steps:
call_all(multiworld, step)
Expand Down
Empty file added test/multiworld/__init__.py
Empty file.
77 changes: 77 additions & 0 deletions test/multiworld/test_multiworlds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import unittest
from typing import List, Tuple
from unittest import TestCase

from BaseClasses import CollectionState, Location, MultiWorld
from Fill import distribute_items_restrictive
from Options import Accessibility
from worlds.AutoWorld import AutoWorldRegister, call_all, call_single
from ..general import gen_steps, setup_multiworld


class MultiworldTestBase(TestCase):
multiworld: MultiWorld

# similar to the implementation in WorldTestBase.test_fill
# but for multiple players and doesn't allow minimal accessibility
def fulfills_accessibility(self) -> bool:
"""
Checks that the multiworld satisfies locations accessibility requirements, failing if all locations are cleared
but not beatable, or some locations are unreachable.
"""
locations = [loc for loc in self.multiworld.get_locations()]
state = CollectionState(self.multiworld)
while locations:
sphere: List[Location] = []
for n in range(len(locations) - 1, -1, -1):
if locations[n].can_reach(state):
sphere.append(locations.pop(n))
self.assertTrue(sphere, f"Unreachable locations: {locations}")
if not sphere:
return False
for location in sphere:
if location.item:
state.collect(location.item, True, location)
return self.multiworld.has_beaten_game(state, 1)

def assertSteps(self, steps: Tuple[str, ...]) -> None:
"""Calls each step individually, continuing if a step for a specific world step fails."""
world_types = {world.__class__ for world in self.multiworld.worlds.values()}
for step in steps:
for player, world in self.multiworld.worlds.items():
with self.subTest(game=world.game, step=step):
call_single(self.multiworld, step, player)
for world_type in sorted(world_types, key=lambda world: world.__name__):
with self.subTest(game=world_type.game, step=f"stage_{step}"):
stage_callable = getattr(world_type, f"stage_{step}", None)
if stage_callable:
stage_callable(self.multiworld)


@unittest.skip("too slow for main")
class TestAllGamesMultiworld(MultiworldTestBase):
def test_fills(self) -> None:
"""Tests that a multiworld with one of every registered game world can generate."""
all_worlds = list(AutoWorldRegister.world_types.values())
self.multiworld = setup_multiworld(all_worlds, ())
for world in self.multiworld.worlds.values():
world.options.accessibility.value = Accessibility.option_locations
self.assertSteps(gen_steps)
with self.subTest("filling multiworld", seed=self.multiworld.seed):
distribute_items_restrictive(self.multiworld)
call_all(self.multiworld, "post_fill")
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")


class TestTwoPlayerMulti(MultiworldTestBase):
def test_two_player_single_game_fills(self) -> None:
"""Tests that a multiworld of two players for each registered game world can generate."""
for world in AutoWorldRegister.world_types.values():
self.multiworld = setup_multiworld([world, world], ())
for world in self.multiworld.worlds.values():
world.options.accessibility.value = Accessibility.option_locations
self.assertSteps(gen_steps)
with self.subTest("filling multiworld", seed=self.multiworld.seed):
distribute_items_restrictive(self.multiworld)
call_all(self.multiworld, "post_fill")
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")

0 comments on commit 078d793

Please sign in to comment.