mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 04:01:32 -06:00
The Witness: Comply with new test base structure #5265
This commit is contained in:
@@ -1,196 +0,0 @@
|
|||||||
from typing import Any, ClassVar, Dict, Iterable, List, Mapping, Union
|
|
||||||
|
|
||||||
from BaseClasses import CollectionState, Entrance, Item, Location, Region
|
|
||||||
|
|
||||||
from test.bases import WorldTestBase
|
|
||||||
from test.general import gen_steps, setup_multiworld
|
|
||||||
from test.multiworld.test_multiworlds import MultiworldTestBase
|
|
||||||
|
|
||||||
from .. import WitnessWorld
|
|
||||||
from ..data.utils import cast_not_none
|
|
||||||
|
|
||||||
|
|
||||||
class WitnessTestBase(WorldTestBase):
|
|
||||||
game = "The Witness"
|
|
||||||
player: ClassVar[int] = 1
|
|
||||||
|
|
||||||
world: WitnessWorld
|
|
||||||
|
|
||||||
def can_beat_game_with_items(self, items: Iterable[Item]) -> bool:
|
|
||||||
"""
|
|
||||||
Check that the items listed are enough to beat the game.
|
|
||||||
"""
|
|
||||||
|
|
||||||
state = CollectionState(self.multiworld)
|
|
||||||
for item in items:
|
|
||||||
state.collect(item)
|
|
||||||
return state.multiworld.can_beat_game(state)
|
|
||||||
|
|
||||||
def assert_dependency_on_event_item(self, spot: Union[Location, Region, Entrance], item_name: str) -> None:
|
|
||||||
"""
|
|
||||||
WorldTestBase.assertAccessDependency, but modified & simplified to work with event items
|
|
||||||
"""
|
|
||||||
event_items = [item for item in self.multiworld.get_items() if item.name == item_name]
|
|
||||||
self.assertTrue(event_items, f"Event item {item_name} does not exist.")
|
|
||||||
|
|
||||||
event_locations = [cast_not_none(event_item.location) for event_item in event_items]
|
|
||||||
|
|
||||||
# Checking for an access dependency on an event item requires a bit of extra work,
|
|
||||||
# as state.remove forces a sweep, which will pick up the event item again right after we tried to remove it.
|
|
||||||
# So, we temporarily set the access rules of the event locations to be impossible.
|
|
||||||
original_rules = {event_location.name: event_location.access_rule for event_location in event_locations}
|
|
||||||
for event_location in event_locations:
|
|
||||||
event_location.access_rule = lambda _: False
|
|
||||||
|
|
||||||
# We can't use self.assertAccessDependency here, it doesn't work for event items. (As of 2024-06-30)
|
|
||||||
test_state = self.multiworld.get_all_state(False)
|
|
||||||
|
|
||||||
self.assertFalse(spot.can_reach(test_state), f"{spot.name} is reachable without {item_name}")
|
|
||||||
|
|
||||||
test_state.collect(event_items[0])
|
|
||||||
|
|
||||||
self.assertTrue(spot.can_reach(test_state), f"{spot.name} is not reachable despite having {item_name}")
|
|
||||||
|
|
||||||
# Restore original access rules.
|
|
||||||
for event_location in event_locations:
|
|
||||||
event_location.access_rule = original_rules[event_location.name]
|
|
||||||
|
|
||||||
def assert_location_exists(self, location_name: str, strict_check: bool = True) -> None:
|
|
||||||
"""
|
|
||||||
Assert that a location exists in this world.
|
|
||||||
If strict_check, also make sure that this (non-event) location COULD exist.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if strict_check:
|
|
||||||
self.assertIn(location_name, self.world.location_name_to_id, f"Location {location_name} can never exist")
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.world.get_location(location_name)
|
|
||||||
except KeyError:
|
|
||||||
self.fail(f"Location {location_name} does not exist.")
|
|
||||||
|
|
||||||
def assert_location_does_not_exist(self, location_name: str, strict_check: bool = True) -> None:
|
|
||||||
"""
|
|
||||||
Assert that a location exists in this world.
|
|
||||||
If strict_check, be explicit about whether the location could exist in the first place.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if strict_check:
|
|
||||||
self.assertIn(location_name, self.world.location_name_to_id, f"Location {location_name} can never exist")
|
|
||||||
|
|
||||||
self.assertRaises(
|
|
||||||
KeyError,
|
|
||||||
lambda _: self.world.get_location(location_name),
|
|
||||||
f"Location {location_name} exists, but is not supposed to.",
|
|
||||||
)
|
|
||||||
|
|
||||||
def assert_can_beat_with_minimally(self, required_item_counts: Mapping[str, int]) -> None:
|
|
||||||
"""
|
|
||||||
Assert that the specified mapping of items is enough to beat the game,
|
|
||||||
and that having one less of any item would result in the game being unbeatable.
|
|
||||||
"""
|
|
||||||
# Find the actual items
|
|
||||||
found_items = [item for item in self.multiworld.get_items() if item.name in required_item_counts]
|
|
||||||
actual_items: Dict[str, List[Item]] = {item_name: [] for item_name in required_item_counts}
|
|
||||||
for item in found_items:
|
|
||||||
if len(actual_items[item.name]) < required_item_counts[item.name]:
|
|
||||||
actual_items[item.name].append(item)
|
|
||||||
|
|
||||||
# Assert that enough items exist in the item pool to satisfy the specified required counts
|
|
||||||
for item_name, item_objects in actual_items.items():
|
|
||||||
self.assertEqual(
|
|
||||||
len(item_objects),
|
|
||||||
required_item_counts[item_name],
|
|
||||||
f"Couldn't find {required_item_counts[item_name]} copies of item {item_name} available in the pool, "
|
|
||||||
f"only found {len(item_objects)}",
|
|
||||||
)
|
|
||||||
|
|
||||||
# assert that multiworld is beatable with the items specified
|
|
||||||
self.assertTrue(
|
|
||||||
self.can_beat_game_with_items(item for items in actual_items.values() for item in items),
|
|
||||||
f"Could not beat game with items: {required_item_counts}",
|
|
||||||
)
|
|
||||||
|
|
||||||
# assert that one less copy of any item would result in the multiworld being unbeatable
|
|
||||||
for item_name, item_objects in actual_items.items():
|
|
||||||
with self.subTest(f"Verify cannot beat game with one less copy of {item_name}"):
|
|
||||||
removed_item = item_objects.pop()
|
|
||||||
self.assertFalse(
|
|
||||||
self.can_beat_game_with_items(item for items in actual_items.values() for item in items),
|
|
||||||
f"Game was beatable despite having {len(item_objects)} copies of {item_name} "
|
|
||||||
f"instead of the specified {required_item_counts[item_name]}",
|
|
||||||
)
|
|
||||||
item_objects.append(removed_item)
|
|
||||||
|
|
||||||
|
|
||||||
class WitnessMultiworldTestBase(MultiworldTestBase):
|
|
||||||
options_per_world: List[Dict[str, Any]]
|
|
||||||
common_options: Dict[str, Any] = {}
|
|
||||||
|
|
||||||
def setUp(self) -> None:
|
|
||||||
"""
|
|
||||||
Set up a multiworld with multiple players, each using different options.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.multiworld = setup_multiworld([WitnessWorld] * len(self.options_per_world), ())
|
|
||||||
|
|
||||||
for world, options in zip(self.multiworld.worlds.values(), self.options_per_world):
|
|
||||||
for option_name, option_value in {**self.common_options, **options}.items():
|
|
||||||
option = getattr(world.options, option_name)
|
|
||||||
self.assertIsNotNone(option)
|
|
||||||
|
|
||||||
option.value = option.from_any(option_value).value
|
|
||||||
|
|
||||||
self.assertSteps(gen_steps)
|
|
||||||
|
|
||||||
def collect_by_name(self, item_names: Union[str, Iterable[str]], player: int) -> List[Item]:
|
|
||||||
"""
|
|
||||||
Collect all copies of a specified item name (or list of item names) for a player in the multiworld item pool.
|
|
||||||
"""
|
|
||||||
|
|
||||||
items = self.get_items_by_name(item_names, player)
|
|
||||||
for item in items:
|
|
||||||
self.multiworld.state.collect(item)
|
|
||||||
return items
|
|
||||||
|
|
||||||
def get_items_by_name(self, item_names: Union[str, Iterable[str]], player: int) -> List[Item]:
|
|
||||||
"""
|
|
||||||
Return all copies of a specified item name (or list of item names) for a player in the multiworld item pool.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if isinstance(item_names, str):
|
|
||||||
item_names = (item_names,)
|
|
||||||
return [item for item in self.multiworld.itempool if item.name in item_names and item.player == player]
|
|
||||||
|
|
||||||
def assert_location_exists(self, location_name: str, player: int, strict_check: bool = True) -> None:
|
|
||||||
"""
|
|
||||||
Assert that a location exists in this world.
|
|
||||||
If strict_check, also make sure that this (non-event) location COULD exist.
|
|
||||||
"""
|
|
||||||
|
|
||||||
world = self.multiworld.worlds[player]
|
|
||||||
|
|
||||||
if strict_check:
|
|
||||||
self.assertIn(location_name, world.location_name_to_id, f"Location {location_name} can never exist")
|
|
||||||
|
|
||||||
try:
|
|
||||||
world.get_location(location_name)
|
|
||||||
except KeyError:
|
|
||||||
self.fail(f"Location {location_name} does not exist.")
|
|
||||||
|
|
||||||
def assert_location_does_not_exist(self, location_name: str, player: int, strict_check: bool = True) -> None:
|
|
||||||
"""
|
|
||||||
Assert that a location exists in this world.
|
|
||||||
If strict_check, be explicit about whether the location could exist in the first place.
|
|
||||||
"""
|
|
||||||
|
|
||||||
world = self.multiworld.worlds[player]
|
|
||||||
|
|
||||||
if strict_check:
|
|
||||||
self.assertIn(location_name, world.location_name_to_id, f"Location {location_name} can never exist")
|
|
||||||
|
|
||||||
self.assertRaises(
|
|
||||||
KeyError,
|
|
||||||
lambda _: world.get_location(location_name),
|
|
||||||
f"Location {location_name} exists, but is not supposed to.",
|
|
||||||
)
|
|
||||||
|
196
worlds/witness/test/bases.py
Normal file
196
worlds/witness/test/bases.py
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
from typing import Any, ClassVar, Dict, Iterable, List, Mapping, Union
|
||||||
|
|
||||||
|
from BaseClasses import CollectionState, Entrance, Item, Location, Region
|
||||||
|
|
||||||
|
from test.bases import WorldTestBase
|
||||||
|
from test.general import gen_steps, setup_multiworld
|
||||||
|
from test.multiworld.test_multiworlds import MultiworldTestBase
|
||||||
|
|
||||||
|
from .. import WitnessWorld
|
||||||
|
from ..data.utils import cast_not_none
|
||||||
|
|
||||||
|
|
||||||
|
class WitnessTestBase(WorldTestBase):
|
||||||
|
game = "The Witness"
|
||||||
|
player: ClassVar[int] = 1
|
||||||
|
|
||||||
|
world: WitnessWorld
|
||||||
|
|
||||||
|
def can_beat_game_with_items(self, items: Iterable[Item]) -> bool:
|
||||||
|
"""
|
||||||
|
Check that the items listed are enough to beat the game.
|
||||||
|
"""
|
||||||
|
|
||||||
|
state = CollectionState(self.multiworld)
|
||||||
|
for item in items:
|
||||||
|
state.collect(item)
|
||||||
|
return state.multiworld.can_beat_game(state)
|
||||||
|
|
||||||
|
def assert_dependency_on_event_item(self, spot: Union[Location, Region, Entrance], item_name: str) -> None:
|
||||||
|
"""
|
||||||
|
WorldTestBase.assertAccessDependency, but modified & simplified to work with event items
|
||||||
|
"""
|
||||||
|
event_items = [item for item in self.multiworld.get_items() if item.name == item_name]
|
||||||
|
self.assertTrue(event_items, f"Event item {item_name} does not exist.")
|
||||||
|
|
||||||
|
event_locations = [cast_not_none(event_item.location) for event_item in event_items]
|
||||||
|
|
||||||
|
# Checking for an access dependency on an event item requires a bit of extra work,
|
||||||
|
# as state.remove forces a sweep, which will pick up the event item again right after we tried to remove it.
|
||||||
|
# So, we temporarily set the access rules of the event locations to be impossible.
|
||||||
|
original_rules = {event_location.name: event_location.access_rule for event_location in event_locations}
|
||||||
|
for event_location in event_locations:
|
||||||
|
event_location.access_rule = lambda _: False
|
||||||
|
|
||||||
|
# We can't use self.assertAccessDependency here, it doesn't work for event items. (As of 2024-06-30)
|
||||||
|
test_state = self.multiworld.get_all_state(False)
|
||||||
|
|
||||||
|
self.assertFalse(spot.can_reach(test_state), f"{spot.name} is reachable without {item_name}")
|
||||||
|
|
||||||
|
test_state.collect(event_items[0])
|
||||||
|
|
||||||
|
self.assertTrue(spot.can_reach(test_state), f"{spot.name} is not reachable despite having {item_name}")
|
||||||
|
|
||||||
|
# Restore original access rules.
|
||||||
|
for event_location in event_locations:
|
||||||
|
event_location.access_rule = original_rules[event_location.name]
|
||||||
|
|
||||||
|
def assert_location_exists(self, location_name: str, strict_check: bool = True) -> None:
|
||||||
|
"""
|
||||||
|
Assert that a location exists in this world.
|
||||||
|
If strict_check, also make sure that this (non-event) location COULD exist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if strict_check:
|
||||||
|
self.assertIn(location_name, self.world.location_name_to_id, f"Location {location_name} can never exist")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.world.get_location(location_name)
|
||||||
|
except KeyError:
|
||||||
|
self.fail(f"Location {location_name} does not exist.")
|
||||||
|
|
||||||
|
def assert_location_does_not_exist(self, location_name: str, strict_check: bool = True) -> None:
|
||||||
|
"""
|
||||||
|
Assert that a location exists in this world.
|
||||||
|
If strict_check, be explicit about whether the location could exist in the first place.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if strict_check:
|
||||||
|
self.assertIn(location_name, self.world.location_name_to_id, f"Location {location_name} can never exist")
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
KeyError,
|
||||||
|
lambda _: self.world.get_location(location_name),
|
||||||
|
f"Location {location_name} exists, but is not supposed to.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def assert_can_beat_with_minimally(self, required_item_counts: Mapping[str, int]) -> None:
|
||||||
|
"""
|
||||||
|
Assert that the specified mapping of items is enough to beat the game,
|
||||||
|
and that having one less of any item would result in the game being unbeatable.
|
||||||
|
"""
|
||||||
|
# Find the actual items
|
||||||
|
found_items = [item for item in self.multiworld.get_items() if item.name in required_item_counts]
|
||||||
|
actual_items: Dict[str, List[Item]] = {item_name: [] for item_name in required_item_counts}
|
||||||
|
for item in found_items:
|
||||||
|
if len(actual_items[item.name]) < required_item_counts[item.name]:
|
||||||
|
actual_items[item.name].append(item)
|
||||||
|
|
||||||
|
# Assert that enough items exist in the item pool to satisfy the specified required counts
|
||||||
|
for item_name, item_objects in actual_items.items():
|
||||||
|
self.assertEqual(
|
||||||
|
len(item_objects),
|
||||||
|
required_item_counts[item_name],
|
||||||
|
f"Couldn't find {required_item_counts[item_name]} copies of item {item_name} available in the pool, "
|
||||||
|
f"only found {len(item_objects)}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# assert that multiworld is beatable with the items specified
|
||||||
|
self.assertTrue(
|
||||||
|
self.can_beat_game_with_items(item for items in actual_items.values() for item in items),
|
||||||
|
f"Could not beat game with items: {required_item_counts}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# assert that one less copy of any item would result in the multiworld being unbeatable
|
||||||
|
for item_name, item_objects in actual_items.items():
|
||||||
|
with self.subTest(f"Verify cannot beat game with one less copy of {item_name}"):
|
||||||
|
removed_item = item_objects.pop()
|
||||||
|
self.assertFalse(
|
||||||
|
self.can_beat_game_with_items(item for items in actual_items.values() for item in items),
|
||||||
|
f"Game was beatable despite having {len(item_objects)} copies of {item_name} "
|
||||||
|
f"instead of the specified {required_item_counts[item_name]}",
|
||||||
|
)
|
||||||
|
item_objects.append(removed_item)
|
||||||
|
|
||||||
|
|
||||||
|
class WitnessMultiworldTestBase(MultiworldTestBase):
|
||||||
|
options_per_world: List[Dict[str, Any]]
|
||||||
|
common_options: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
"""
|
||||||
|
Set up a multiworld with multiple players, each using different options.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.multiworld = setup_multiworld([WitnessWorld] * len(self.options_per_world), ())
|
||||||
|
|
||||||
|
for world, options in zip(self.multiworld.worlds.values(), self.options_per_world):
|
||||||
|
for option_name, option_value in {**self.common_options, **options}.items():
|
||||||
|
option = getattr(world.options, option_name)
|
||||||
|
self.assertIsNotNone(option)
|
||||||
|
|
||||||
|
option.value = option.from_any(option_value).value
|
||||||
|
|
||||||
|
self.assertSteps(gen_steps)
|
||||||
|
|
||||||
|
def collect_by_name(self, item_names: Union[str, Iterable[str]], player: int) -> List[Item]:
|
||||||
|
"""
|
||||||
|
Collect all copies of a specified item name (or list of item names) for a player in the multiworld item pool.
|
||||||
|
"""
|
||||||
|
|
||||||
|
items = self.get_items_by_name(item_names, player)
|
||||||
|
for item in items:
|
||||||
|
self.multiworld.state.collect(item)
|
||||||
|
return items
|
||||||
|
|
||||||
|
def get_items_by_name(self, item_names: Union[str, Iterable[str]], player: int) -> List[Item]:
|
||||||
|
"""
|
||||||
|
Return all copies of a specified item name (or list of item names) for a player in the multiworld item pool.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(item_names, str):
|
||||||
|
item_names = (item_names,)
|
||||||
|
return [item for item in self.multiworld.itempool if item.name in item_names and item.player == player]
|
||||||
|
|
||||||
|
def assert_location_exists(self, location_name: str, player: int, strict_check: bool = True) -> None:
|
||||||
|
"""
|
||||||
|
Assert that a location exists in this world.
|
||||||
|
If strict_check, also make sure that this (non-event) location COULD exist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
world = self.multiworld.worlds[player]
|
||||||
|
|
||||||
|
if strict_check:
|
||||||
|
self.assertIn(location_name, world.location_name_to_id, f"Location {location_name} can never exist")
|
||||||
|
|
||||||
|
try:
|
||||||
|
world.get_location(location_name)
|
||||||
|
except KeyError:
|
||||||
|
self.fail(f"Location {location_name} does not exist.")
|
||||||
|
|
||||||
|
def assert_location_does_not_exist(self, location_name: str, player: int, strict_check: bool = True) -> None:
|
||||||
|
"""
|
||||||
|
Assert that a location exists in this world.
|
||||||
|
If strict_check, be explicit about whether the location could exist in the first place.
|
||||||
|
"""
|
||||||
|
|
||||||
|
world = self.multiworld.worlds[player]
|
||||||
|
|
||||||
|
if strict_check:
|
||||||
|
self.assertIn(location_name, world.location_name_to_id, f"Location {location_name} can never exist")
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
KeyError,
|
||||||
|
lambda _: world.get_location(location_name),
|
||||||
|
f"Location {location_name} exists, but is not supposed to.",
|
||||||
|
)
|
@@ -1,4 +1,4 @@
|
|||||||
from ..test import WitnessMultiworldTestBase
|
from ..test.bases import WitnessMultiworldTestBase
|
||||||
|
|
||||||
|
|
||||||
class TestElevatorsComeToYouBleed(WitnessMultiworldTestBase):
|
class TestElevatorsComeToYouBleed(WitnessMultiworldTestBase):
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
from ..rules import _has_lasers
|
from ..rules import _has_lasers
|
||||||
from ..test import WitnessTestBase
|
from ..test.bases import WitnessTestBase
|
||||||
|
|
||||||
|
|
||||||
class TestDisableNonRandomized(WitnessTestBase):
|
class TestDisableNonRandomized(WitnessTestBase):
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from .. import WitnessWorld
|
from .. import WitnessWorld
|
||||||
from ..test import WitnessMultiworldTestBase, WitnessTestBase
|
from ..test.bases import WitnessMultiworldTestBase, WitnessTestBase
|
||||||
|
|
||||||
|
|
||||||
class TestIndividualDoors(WitnessTestBase):
|
class TestIndividualDoors(WitnessTestBase):
|
||||||
|
@@ -3,7 +3,7 @@ from typing import cast
|
|||||||
from BaseClasses import LocationProgressType
|
from BaseClasses import LocationProgressType
|
||||||
|
|
||||||
from .. import WitnessWorld
|
from .. import WitnessWorld
|
||||||
from ..test import WitnessMultiworldTestBase
|
from ..test.bases import WitnessMultiworldTestBase
|
||||||
|
|
||||||
|
|
||||||
class TestEasterEggShuffle(WitnessMultiworldTestBase):
|
class TestEasterEggShuffle(WitnessMultiworldTestBase):
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from ..test import WitnessTestBase
|
from ..test.bases import WitnessTestBase
|
||||||
|
|
||||||
|
|
||||||
class TestIndividualEPs(WitnessTestBase):
|
class TestIndividualEPs(WitnessTestBase):
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from ..test import WitnessTestBase
|
from ..test.bases import WitnessTestBase
|
||||||
|
|
||||||
|
|
||||||
class TestSymbolsRequiredToWinElevatorNormal(WitnessTestBase):
|
class TestSymbolsRequiredToWinElevatorNormal(WitnessTestBase):
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
from BaseClasses import CollectionState
|
from BaseClasses import CollectionState
|
||||||
|
|
||||||
from worlds.witness.test import WitnessMultiworldTestBase, WitnessTestBase
|
from ..test.bases import WitnessMultiworldTestBase, WitnessTestBase
|
||||||
|
|
||||||
|
|
||||||
class TestMaxPanelHuntMinChecks(WitnessTestBase):
|
class TestMaxPanelHuntMinChecks(WitnessTestBase):
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
from ..options import ElevatorsComeToYou
|
from ..options import ElevatorsComeToYou
|
||||||
from ..test import WitnessTestBase
|
from ..test.bases import WitnessTestBase
|
||||||
|
|
||||||
# These are just some random options combinations, just to catch whether I broke anything obvious
|
# These are just some random options combinations, just to catch whether I broke anything obvious
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from ..test import WitnessMultiworldTestBase, WitnessTestBase
|
from ..test.bases import WitnessMultiworldTestBase, WitnessTestBase
|
||||||
|
|
||||||
|
|
||||||
class TestSymbols(WitnessTestBase):
|
class TestSymbols(WitnessTestBase):
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from ..test import WitnessTestBase
|
from ..test.bases import WitnessTestBase
|
||||||
|
|
||||||
|
|
||||||
class TestWeirdTraversalRequirements(WitnessTestBase):
|
class TestWeirdTraversalRequirements(WitnessTestBase):
|
||||||
|
Reference in New Issue
Block a user