mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Stardew Valley: Move BaseTest out of __init__.py
to comply with future conventions (#4991)
* move everything out of init; fix from imports and some typing errors * why is there a change in multiserver * fix some relative shits
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from . import SVTestBase
|
||||
from .bases import SVTestBase
|
||||
from .. import options
|
||||
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from . import SVTestBase
|
||||
from .bases import SVTestBase
|
||||
from ..options import ExcludeGingerIsland, Booksanity, Shipsanity
|
||||
from ..strings.book_names import Book, LostBook
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import unittest
|
||||
|
||||
from . import SVTestBase
|
||||
from .bases import SVTestBase
|
||||
from .. import BundleRandomization
|
||||
from ..data.bundle_data import all_bundle_items_except_money, quality_crops_items_thematic, quality_foraging_items, quality_fish_items
|
||||
from ..options import BundlePlando
|
||||
@@ -87,4 +87,3 @@ class TestRemixedAnywhereBundles(SVTestBase):
|
||||
for bundle_name in self.fish_bundle_names:
|
||||
with self.subTest(f"{bundle_name}"):
|
||||
self.assertIn(bundle_name, location_names)
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from . import SVTestBase
|
||||
from .bases import SVTestBase
|
||||
from .. import options
|
||||
from ..strings.ap_names.transport_names import Transportation
|
||||
from ..strings.building_names import Building
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from typing import List, Tuple
|
||||
|
||||
from . import SVTestBase
|
||||
from .assertion import WorldAssertMixin
|
||||
from .bases import SVTestBase
|
||||
from .. import options, StardewItem
|
||||
from ..strings.ap_names.ap_weapon_names import APWeapon
|
||||
from ..strings.ap_names.transport_names import Transportation
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from collections import Counter
|
||||
|
||||
from . import SVTestBase
|
||||
from .assertion import WorldAssertMixin
|
||||
from .bases import SVTestBase
|
||||
from .. import options
|
||||
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from . import SVTestBase
|
||||
from .assertion import WorldAssertMixin
|
||||
from .bases import SVTestBase
|
||||
from .options.presets import minimal_locations_maximal_items
|
||||
from .. import options
|
||||
from ..mods.mod_data import ModNames
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import unittest
|
||||
from typing import ClassVar, Set
|
||||
|
||||
from . import SVTestBase
|
||||
from .assertion import WorldAssertMixin
|
||||
from .bases import SVTestBase
|
||||
from ..content.feature import fishsanity
|
||||
from ..mods.mod_data import ModNames
|
||||
from ..options import Fishsanity, ExcludeGingerIsland, Mods, SpecialOrderLocations, Goal, QuestLocations
|
||||
|
@@ -2,7 +2,7 @@ import unittest
|
||||
from collections import Counter
|
||||
from typing import ClassVar, Set
|
||||
|
||||
from . import SVTestBase
|
||||
from .bases import SVTestBase
|
||||
from ..content.feature import friendsanity
|
||||
from ..options import Friendsanity, FriendsanityHeartSize
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from typing import List
|
||||
|
||||
from BaseClasses import ItemClassification, Item
|
||||
from . import SVTestBase
|
||||
from .bases import SVTestBase
|
||||
from .. import location_table, options, items
|
||||
from ..items import Group, ItemData, item_data
|
||||
from ..locations import LocationTags
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from . import SVTestBase
|
||||
from .bases import SVTestBase
|
||||
from .. import options, item_table, Group
|
||||
|
||||
max_iterations = 2000
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from BaseClasses import MultiWorld, get_seed, ItemClassification
|
||||
from . import setup_solo_multiworld, SVTestCase, solo_multiworld
|
||||
from .bases import SVTestCase, solo_multiworld, setup_solo_multiworld
|
||||
from .options.presets import allsanity_no_mods_6_x_x, get_minsanity_options
|
||||
from .. import StardewValleyWorld
|
||||
from ..items import Group, item_table
|
||||
|
@@ -3,8 +3,8 @@ import unittest
|
||||
from unittest import TestCase, SkipTest
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from . import setup_solo_multiworld
|
||||
from .assertion import RuleAssertMixin
|
||||
from .bases import setup_solo_multiworld
|
||||
from .options.presets import allsanity_mods_6_x_x, minimal_locations_maximal_items
|
||||
from .. import StardewValleyWorld
|
||||
from ..data.bundle_data import all_bundle_items_except_money
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from . import SVTestCase, setup_multiworld
|
||||
from .bases import SVTestCase, setup_multiworld
|
||||
from .. import True_
|
||||
from ..options import FestivalLocations, StartingMoney
|
||||
from ..strings.festival_check_names import FestivalCheck
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from . import SVTestBase
|
||||
from .bases import SVTestBase
|
||||
from .options.presets import default_6_x_x, allsanity_no_mods_6_x_x, allsanity_mods_6_x_x_exclude_disabled, get_minsanity_options, \
|
||||
minimal_locations_maximal_items, minimal_locations_maximal_items_with_island
|
||||
from .. import location_table
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from . import SVTestBase
|
||||
from .bases import SVTestBase
|
||||
from .. import BuildingProgression
|
||||
from ..options import ToolProgression
|
||||
|
||||
|
@@ -3,8 +3,8 @@ from typing import ClassVar
|
||||
|
||||
from BaseClasses import ItemClassification
|
||||
from test.param import classvar_matrix
|
||||
from . import SVTestCase, solo_multiworld, SVTestBase
|
||||
from .assertion import WorldAssertMixin
|
||||
from .bases import SVTestCase, SVTestBase, solo_multiworld
|
||||
from .options.option_names import all_option_choices
|
||||
from .options.presets import allsanity_no_mods_6_x_x, allsanity_mods_6_x_x
|
||||
from .. import items_by_group, Group
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from . import SVTestBase
|
||||
from .assertion import WorldAssertMixin
|
||||
from .bases import SVTestBase
|
||||
from .. import options
|
||||
|
||||
|
||||
|
@@ -2,8 +2,8 @@ from typing import ClassVar
|
||||
|
||||
from BaseClasses import MultiWorld, get_seed
|
||||
from test.param import classvar_matrix
|
||||
from . import SVTestCase, skip_long_tests, solo_multiworld
|
||||
from .assertion import GoalAssertMixin, OptionAssertMixin, WorldAssertMixin
|
||||
from .bases import skip_long_tests, SVTestCase, solo_multiworld
|
||||
from .options.option_names import generate_random_world_options
|
||||
|
||||
|
||||
|
@@ -3,7 +3,7 @@ import unittest
|
||||
from typing import Set
|
||||
|
||||
from BaseClasses import get_seed
|
||||
from . import SVTestCase
|
||||
from .bases import SVTestCase
|
||||
from .options.utils import fill_dataclass_with_default
|
||||
from .. import create_content
|
||||
from ..options import EntranceRandomization, ExcludeGingerIsland, SkillProgression
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from . import SVTestBase
|
||||
from .assertion import WorldAssertMixin
|
||||
from .bases import SVTestBase
|
||||
from .. import options
|
||||
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import unittest
|
||||
|
||||
from . import SVTestBase
|
||||
from .assertion import WorldAssertMixin
|
||||
from .bases import SVTestBase
|
||||
from .. import options, items_by_group, Group
|
||||
from ..options import TrapDistribution
|
||||
|
||||
@@ -119,4 +119,3 @@ class TestDistributionIsRespectedAllTraps(WorldAssertMixin, SVTestBase):
|
||||
self.assertLess(num_bark, num_debris - threshold_difference)
|
||||
self.assertGreater(num_meow, num_time_flies + threshold_difference)
|
||||
self.assertGreater(num_meow, num_debris + threshold_difference)
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from . import SVTestBase
|
||||
from .bases import SVTestBase
|
||||
from ..options import ExcludeGingerIsland, Walnutsanity, ToolProgression, SkillProgression
|
||||
from ..strings.ap_names.ap_option_names import WalnutsanityOptionName
|
||||
|
||||
|
@@ -1,308 +0,0 @@
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
import unittest
|
||||
from contextlib import contextmanager
|
||||
from typing import Dict, ClassVar, Iterable, Tuple, Optional, List, Union, Any
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState, get_seed, Location, Item
|
||||
from test.bases import WorldTestBase
|
||||
from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_multiworld
|
||||
from worlds.AutoWorld import call_all
|
||||
from .assertion import RuleAssertMixin
|
||||
from .options.utils import fill_namespace_with_default, parse_class_option_keys, fill_dataclass_with_default
|
||||
from .. import StardewValleyWorld, StardewItem, StardewRule
|
||||
from ..logic.time_logic import MONTH_COEFFICIENT
|
||||
from ..options import StardewValleyOption, options
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_TEST_SEED = get_seed()
|
||||
logger.info(f"Default Test Seed: {DEFAULT_TEST_SEED}")
|
||||
|
||||
|
||||
def skip_default_tests() -> bool:
|
||||
return not bool(os.environ.get("base", False))
|
||||
|
||||
|
||||
def skip_long_tests() -> bool:
|
||||
return not bool(os.environ.get("long", False))
|
||||
|
||||
|
||||
class SVTestCase(unittest.TestCase):
|
||||
skip_default_tests: bool = skip_default_tests()
|
||||
"""Set False to not skip the base fill tests"""
|
||||
skip_long_tests: bool = skip_long_tests()
|
||||
"""Set False to run tests that take long"""
|
||||
|
||||
@contextmanager
|
||||
def solo_world_sub_test(self, msg: Optional[str] = None,
|
||||
/,
|
||||
world_options: Optional[Dict[Union[str, StardewValleyOption], Any]] = None,
|
||||
*,
|
||||
seed=DEFAULT_TEST_SEED,
|
||||
world_caching=True,
|
||||
**kwargs) -> Tuple[MultiWorld, StardewValleyWorld]:
|
||||
if msg is not None:
|
||||
msg += " "
|
||||
else:
|
||||
msg = ""
|
||||
msg += f"[Seed = {seed}]"
|
||||
|
||||
with self.subTest(msg, **kwargs):
|
||||
with solo_multiworld(world_options, seed=seed, world_caching=world_caching) as (multiworld, world):
|
||||
yield multiworld, world
|
||||
|
||||
|
||||
class SVTestBase(RuleAssertMixin, WorldTestBase, SVTestCase):
|
||||
game = "Stardew Valley"
|
||||
world: StardewValleyWorld
|
||||
player: ClassVar[int] = 1
|
||||
|
||||
seed = DEFAULT_TEST_SEED
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
if cls is SVTestBase:
|
||||
raise unittest.SkipTest("No running tests on SVTestBase import.")
|
||||
|
||||
super().setUpClass()
|
||||
|
||||
def world_setup(self, *args, **kwargs):
|
||||
self.options = parse_class_option_keys(self.options)
|
||||
|
||||
self.multiworld = setup_solo_multiworld(self.options, seed=self.seed)
|
||||
self.multiworld.lock.acquire()
|
||||
world = self.multiworld.worlds[self.player]
|
||||
|
||||
self.original_state = self.multiworld.state.copy()
|
||||
self.original_itempool = self.multiworld.itempool.copy()
|
||||
self.unfilled_locations = self.multiworld.get_unfilled_locations(1)
|
||||
if self.constructed:
|
||||
self.world = world # noqa
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.multiworld.state = self.original_state
|
||||
self.multiworld.itempool = self.original_itempool
|
||||
for location in self.unfilled_locations:
|
||||
location.item = None
|
||||
|
||||
self.multiworld.lock.release()
|
||||
|
||||
@property
|
||||
def run_default_tests(self) -> bool:
|
||||
if self.skip_default_tests:
|
||||
return False
|
||||
return super().run_default_tests
|
||||
|
||||
def collect_months(self, months: int) -> None:
|
||||
real_total_prog_items = self.world.total_progression_items
|
||||
percent = months * MONTH_COEFFICIENT
|
||||
self.collect("Stardrop", real_total_prog_items * 100 // percent)
|
||||
self.world.total_progression_items = real_total_prog_items
|
||||
|
||||
def collect_lots_of_money(self, percent: float = 0.25):
|
||||
self.collect("Shipping Bin")
|
||||
real_total_prog_items = self.world.total_progression_items
|
||||
required_prog_items = int(round(real_total_prog_items * percent))
|
||||
self.collect("Stardrop", required_prog_items)
|
||||
|
||||
def collect_all_the_money(self):
|
||||
self.collect_lots_of_money(0.95)
|
||||
|
||||
def collect_everything(self):
|
||||
non_event_items = [item for item in self.multiworld.get_items() if item.code]
|
||||
for item in non_event_items:
|
||||
self.multiworld.state.collect(item)
|
||||
|
||||
def collect_all_except(self, item_to_not_collect: str):
|
||||
non_event_items = [item for item in self.multiworld.get_items() if item.code]
|
||||
for item in non_event_items:
|
||||
if item.name != item_to_not_collect:
|
||||
self.multiworld.state.collect(item)
|
||||
|
||||
def get_real_locations(self) -> List[Location]:
|
||||
return [location for location in self.multiworld.get_locations(self.player) if location.address is not None]
|
||||
|
||||
def get_real_location_names(self) -> List[str]:
|
||||
return [location.name for location in self.get_real_locations()]
|
||||
|
||||
def collect(self, item: Union[str, Item, Iterable[Item]], count: int = 1) -> Union[None, Item, List[Item]]:
|
||||
assert count > 0
|
||||
|
||||
if not isinstance(item, str):
|
||||
super().collect(item)
|
||||
return
|
||||
|
||||
if count == 1:
|
||||
item = self.create_item(item)
|
||||
self.multiworld.state.collect(item)
|
||||
return item
|
||||
|
||||
items = []
|
||||
for i in range(count):
|
||||
item = self.create_item(item)
|
||||
self.multiworld.state.collect(item)
|
||||
items.append(item)
|
||||
|
||||
return items
|
||||
|
||||
def create_item(self, item: str) -> StardewItem:
|
||||
return self.world.create_item(item)
|
||||
|
||||
def get_all_created_items(self) -> list[str]:
|
||||
return [item.name for item in itertools.chain(self.multiworld.get_items(), self.multiworld.precollected_items[self.player])]
|
||||
|
||||
def remove_one_by_name(self, item: str) -> None:
|
||||
self.remove(self.create_item(item))
|
||||
|
||||
def reset_collection_state(self) -> None:
|
||||
self.multiworld.state = self.original_state.copy()
|
||||
|
||||
def assert_rule_true(self, rule: StardewRule, state: CollectionState | None = None) -> None:
|
||||
if state is None:
|
||||
state = self.multiworld.state
|
||||
super().assert_rule_true(rule, state)
|
||||
|
||||
def assert_rule_false(self, rule: StardewRule, state: CollectionState | None = None) -> None:
|
||||
if state is None:
|
||||
state = self.multiworld.state
|
||||
super().assert_rule_false(rule, state)
|
||||
|
||||
def assert_can_reach_location(self, location: Location | str, state: CollectionState | None = None) -> None:
|
||||
if state is None:
|
||||
state = self.multiworld.state
|
||||
super().assert_can_reach_location(location, state)
|
||||
|
||||
def assert_cannot_reach_location(self, location: Location | str, state: CollectionState | None = None) -> None:
|
||||
if state is None:
|
||||
state = self.multiworld.state
|
||||
super().assert_cannot_reach_location(location, state)
|
||||
|
||||
|
||||
pre_generated_worlds = {}
|
||||
|
||||
|
||||
@contextmanager
|
||||
def solo_multiworld(world_options: Optional[Dict[Union[str, StardewValleyOption], Any]] = None,
|
||||
*,
|
||||
seed=DEFAULT_TEST_SEED,
|
||||
world_caching=True) -> Tuple[MultiWorld, StardewValleyWorld]:
|
||||
if not world_caching:
|
||||
multiworld = setup_solo_multiworld(world_options, seed, _cache={})
|
||||
yield multiworld, multiworld.worlds[1]
|
||||
else:
|
||||
multiworld = setup_solo_multiworld(world_options, seed)
|
||||
try:
|
||||
multiworld.lock.acquire()
|
||||
world = multiworld.worlds[1]
|
||||
|
||||
original_state = multiworld.state.copy()
|
||||
original_itempool = multiworld.itempool.copy()
|
||||
unfilled_locations = multiworld.get_unfilled_locations(1)
|
||||
|
||||
yield multiworld, world
|
||||
|
||||
multiworld.state = original_state
|
||||
multiworld.itempool = original_itempool
|
||||
for location in unfilled_locations:
|
||||
location.item = None
|
||||
finally:
|
||||
multiworld.lock.release()
|
||||
|
||||
|
||||
# Mostly a copy of test.general.setup_solo_multiworld, I just don't want to change the core.
|
||||
def setup_solo_multiworld(test_options: Optional[Dict[Union[str, StardewValleyOption], str]] = None,
|
||||
seed=DEFAULT_TEST_SEED,
|
||||
_cache: Dict[frozenset, MultiWorld] = {}, # noqa
|
||||
_steps=gen_steps) -> MultiWorld:
|
||||
test_options = parse_class_option_keys(test_options)
|
||||
|
||||
# Yes I reuse the worlds generated between tests, its speeds the execution by a couple seconds
|
||||
# If the simple dict caching ends up taking too much memory, we could replace it with some kind of lru cache.
|
||||
should_cache = should_cache_world(test_options)
|
||||
if should_cache:
|
||||
frozen_options = make_hashable(test_options, seed)
|
||||
cached_multi_world = search_world_cache(_cache, frozen_options)
|
||||
if cached_multi_world:
|
||||
print(f"Using cached solo multi world [Seed = {cached_multi_world.seed}] [Cache size = {len(_cache)}]")
|
||||
return cached_multi_world
|
||||
|
||||
multiworld = setup_base_solo_multiworld(StardewValleyWorld, (), seed=seed)
|
||||
# print(f"Seed: {multiworld.seed}") # Uncomment to print the seed for every test
|
||||
|
||||
args = fill_namespace_with_default(test_options)
|
||||
multiworld.set_options(args)
|
||||
|
||||
if "start_inventory" in test_options:
|
||||
for item, amount in test_options["start_inventory"].items():
|
||||
for _ in range(amount):
|
||||
multiworld.push_precollected(multiworld.create_item(item, 1))
|
||||
|
||||
for step in _steps:
|
||||
call_all(multiworld, step)
|
||||
|
||||
if should_cache:
|
||||
add_to_world_cache(_cache, frozen_options, multiworld) # noqa
|
||||
|
||||
# Lock is needed for multi-threading tests
|
||||
setattr(multiworld, "lock", threading.Lock())
|
||||
|
||||
return multiworld
|
||||
|
||||
|
||||
def should_cache_world(test_options):
|
||||
if "start_inventory" in test_options:
|
||||
return False
|
||||
|
||||
trap_distribution_key = "trap_distribution"
|
||||
if trap_distribution_key not in test_options:
|
||||
return True
|
||||
|
||||
trap_distribution = test_options[trap_distribution_key]
|
||||
for key in trap_distribution:
|
||||
if trap_distribution[key] != options.TrapDistribution.default_weight:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def make_hashable(test_options, seed):
|
||||
return frozenset(test_options.items()).union({("seed", seed)})
|
||||
|
||||
|
||||
def search_world_cache(cache: Dict[frozenset, MultiWorld], frozen_options: frozenset) -> Optional[MultiWorld]:
|
||||
try:
|
||||
return cache[frozen_options]
|
||||
except KeyError:
|
||||
for cached_options, multi_world in cache.items():
|
||||
if frozen_options.issubset(cached_options):
|
||||
return multi_world
|
||||
return None
|
||||
|
||||
|
||||
def add_to_world_cache(cache: Dict[frozenset, MultiWorld], frozen_options: frozenset, multi_world: MultiWorld) -> None:
|
||||
# We could complete the key with all the default options, but that does not seem to improve performances.
|
||||
cache[frozen_options] = multi_world
|
||||
|
||||
|
||||
def setup_multiworld(test_options: Iterable[Dict[str, int]] = None, seed=None) -> MultiWorld: # noqa
|
||||
if test_options is None:
|
||||
test_options = []
|
||||
|
||||
multiworld = MultiWorld(len(test_options))
|
||||
multiworld.player_name = {}
|
||||
multiworld.set_seed(seed)
|
||||
multiworld.state = CollectionState(multiworld)
|
||||
for i in range(1, len(test_options) + 1):
|
||||
multiworld.game[i] = StardewValleyWorld.game
|
||||
multiworld.player_name.update({i: f"Tester{i}"})
|
||||
args = fill_namespace_with_default(test_options)
|
||||
multiworld.set_options(args)
|
||||
|
||||
for step in gen_steps:
|
||||
call_all(multiworld, step)
|
||||
|
||||
return multiworld
|
||||
|
306
worlds/stardew_valley/test/bases.py
Normal file
306
worlds/stardew_valley/test/bases.py
Normal file
@@ -0,0 +1,306 @@
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
import typing
|
||||
import unittest
|
||||
from contextlib import contextmanager
|
||||
from typing import Optional, Dict, Union, Any, List, Iterable
|
||||
|
||||
from BaseClasses import get_seed, MultiWorld, Location, Item, CollectionState
|
||||
from test.bases import WorldTestBase
|
||||
from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_multiworld
|
||||
from worlds.AutoWorld import call_all
|
||||
from .assertion import RuleAssertMixin
|
||||
from .options.utils import parse_class_option_keys, fill_namespace_with_default
|
||||
from .. import StardewValleyWorld, StardewItem, StardewRule
|
||||
from ..logic.time_logic import MONTH_COEFFICIENT
|
||||
from ..options import StardewValleyOption, options
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
DEFAULT_TEST_SEED = get_seed()
|
||||
logger.info(f"Default Test Seed: {DEFAULT_TEST_SEED}")
|
||||
|
||||
|
||||
def skip_default_tests() -> bool:
|
||||
return not bool(os.environ.get("base", False))
|
||||
|
||||
|
||||
def skip_long_tests() -> bool:
|
||||
return not bool(os.environ.get("long", False))
|
||||
|
||||
|
||||
class SVTestCase(unittest.TestCase):
|
||||
skip_default_tests: bool = skip_default_tests()
|
||||
"""Set False to not skip the base fill tests"""
|
||||
skip_long_tests: bool = skip_long_tests()
|
||||
"""Set False to run tests that take long"""
|
||||
|
||||
@contextmanager
|
||||
def solo_world_sub_test(self, msg: str | None = None,
|
||||
/,
|
||||
world_options: dict[str | type[StardewValleyOption], Any] | None = None,
|
||||
*,
|
||||
seed=DEFAULT_TEST_SEED,
|
||||
world_caching=True,
|
||||
**kwargs) -> Iterable[tuple[MultiWorld, StardewValleyWorld]]:
|
||||
if msg is not None:
|
||||
msg += " "
|
||||
else:
|
||||
msg = ""
|
||||
msg += f"[Seed = {seed}]"
|
||||
|
||||
with self.subTest(msg, **kwargs):
|
||||
with solo_multiworld(world_options, seed=seed, world_caching=world_caching) as (multiworld, world):
|
||||
yield multiworld, world
|
||||
|
||||
|
||||
class SVTestBase(RuleAssertMixin, WorldTestBase, SVTestCase):
|
||||
game = "Stardew Valley"
|
||||
world: StardewValleyWorld
|
||||
|
||||
seed = DEFAULT_TEST_SEED
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
if cls is SVTestBase:
|
||||
raise unittest.SkipTest("No running tests on SVTestBase import.")
|
||||
|
||||
super().setUpClass()
|
||||
|
||||
def world_setup(self, *args, **kwargs):
|
||||
self.options = parse_class_option_keys(self.options)
|
||||
|
||||
self.multiworld = setup_solo_multiworld(self.options, seed=self.seed)
|
||||
self.multiworld.lock.acquire()
|
||||
world = self.multiworld.worlds[self.player]
|
||||
|
||||
self.original_state = self.multiworld.state.copy()
|
||||
self.original_itempool = self.multiworld.itempool.copy()
|
||||
self.unfilled_locations = self.multiworld.get_unfilled_locations(1)
|
||||
if self.constructed:
|
||||
self.world = world # noqa
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.multiworld.state = self.original_state
|
||||
self.multiworld.itempool = self.original_itempool
|
||||
for location in self.unfilled_locations:
|
||||
location.item = None
|
||||
|
||||
self.multiworld.lock.release()
|
||||
|
||||
@property
|
||||
def run_default_tests(self) -> bool:
|
||||
if self.skip_default_tests:
|
||||
return False
|
||||
return super().run_default_tests
|
||||
|
||||
def collect_months(self, months: int) -> None:
|
||||
real_total_prog_items = self.world.total_progression_items
|
||||
percent = months * MONTH_COEFFICIENT
|
||||
self.collect("Stardrop", real_total_prog_items * 100 // percent)
|
||||
self.world.total_progression_items = real_total_prog_items
|
||||
|
||||
def collect_lots_of_money(self, percent: float = 0.25):
|
||||
self.collect("Shipping Bin")
|
||||
real_total_prog_items = self.world.total_progression_items
|
||||
required_prog_items = int(round(real_total_prog_items * percent))
|
||||
self.collect("Stardrop", required_prog_items)
|
||||
|
||||
def collect_all_the_money(self):
|
||||
self.collect_lots_of_money(0.95)
|
||||
|
||||
def collect_everything(self):
|
||||
non_event_items = [item for item in self.multiworld.get_items() if item.code]
|
||||
for item in non_event_items:
|
||||
self.multiworld.state.collect(item)
|
||||
|
||||
def collect_all_except(self, item_to_not_collect: str):
|
||||
non_event_items = [item for item in self.multiworld.get_items() if item.code]
|
||||
for item in non_event_items:
|
||||
if item.name != item_to_not_collect:
|
||||
self.multiworld.state.collect(item)
|
||||
|
||||
def get_real_locations(self) -> List[Location]:
|
||||
return [location for location in self.multiworld.get_locations(self.player) if location.address is not None]
|
||||
|
||||
def get_real_location_names(self) -> List[str]:
|
||||
return [location.name for location in self.get_real_locations()]
|
||||
|
||||
def collect(self, item: Union[str, Item, Iterable[Item]], count: int = 1) -> Union[None, Item, List[Item]]:
|
||||
assert count > 0
|
||||
|
||||
if not isinstance(item, str):
|
||||
super().collect(item)
|
||||
return
|
||||
|
||||
if count == 1:
|
||||
item = self.create_item(item)
|
||||
self.multiworld.state.collect(item)
|
||||
return item
|
||||
|
||||
items = []
|
||||
for i in range(count):
|
||||
item = self.create_item(item)
|
||||
self.multiworld.state.collect(item)
|
||||
items.append(item)
|
||||
|
||||
return items
|
||||
|
||||
def create_item(self, item: str) -> StardewItem:
|
||||
return self.world.create_item(item)
|
||||
|
||||
def get_all_created_items(self) -> list[str]:
|
||||
return [item.name for item in itertools.chain(self.multiworld.get_items(), self.multiworld.precollected_items[self.player])]
|
||||
|
||||
def remove_one_by_name(self, item: str) -> None:
|
||||
self.remove(self.create_item(item))
|
||||
|
||||
def reset_collection_state(self) -> None:
|
||||
self.multiworld.state = self.original_state.copy()
|
||||
|
||||
def assert_rule_true(self, rule: StardewRule, state: CollectionState | None = None) -> None:
|
||||
if state is None:
|
||||
state = self.multiworld.state
|
||||
super().assert_rule_true(rule, state)
|
||||
|
||||
def assert_rule_false(self, rule: StardewRule, state: CollectionState | None = None) -> None:
|
||||
if state is None:
|
||||
state = self.multiworld.state
|
||||
super().assert_rule_false(rule, state)
|
||||
|
||||
def assert_can_reach_location(self, location: Location | str, state: CollectionState | None = None) -> None:
|
||||
if state is None:
|
||||
state = self.multiworld.state
|
||||
super().assert_can_reach_location(location, state)
|
||||
|
||||
def assert_cannot_reach_location(self, location: Location | str, state: CollectionState | None = None) -> None:
|
||||
if state is None:
|
||||
state = self.multiworld.state
|
||||
super().assert_cannot_reach_location(location, state)
|
||||
|
||||
|
||||
pre_generated_worlds = {}
|
||||
|
||||
|
||||
@contextmanager
|
||||
def solo_multiworld(world_options: dict[str | type[StardewValleyOption], Any] | None = None,
|
||||
*,
|
||||
seed=DEFAULT_TEST_SEED,
|
||||
world_caching=True) -> Iterable[tuple[MultiWorld, StardewValleyWorld]]:
|
||||
if not world_caching:
|
||||
multiworld = setup_solo_multiworld(world_options, seed, _cache={})
|
||||
yield multiworld, typing.cast(StardewValleyWorld, multiworld.worlds[1])
|
||||
else:
|
||||
multiworld = setup_solo_multiworld(world_options, seed)
|
||||
try:
|
||||
multiworld.lock.acquire()
|
||||
world = multiworld.worlds[1]
|
||||
|
||||
original_state = multiworld.state.copy()
|
||||
original_itempool = multiworld.itempool.copy()
|
||||
unfilled_locations = multiworld.get_unfilled_locations(1)
|
||||
|
||||
yield multiworld, typing.cast(StardewValleyWorld, world)
|
||||
|
||||
multiworld.state = original_state
|
||||
multiworld.itempool = original_itempool
|
||||
for location in unfilled_locations:
|
||||
location.item = None
|
||||
finally:
|
||||
multiworld.lock.release()
|
||||
|
||||
|
||||
# Mostly a copy of test.general.setup_solo_multiworld, I just don't want to change the core.
|
||||
def setup_solo_multiworld(test_options: Optional[Dict[Union[str, StardewValleyOption], str]] = None,
|
||||
seed=DEFAULT_TEST_SEED,
|
||||
_cache: Dict[frozenset, MultiWorld] = {}, # noqa
|
||||
_steps=gen_steps) -> MultiWorld:
|
||||
test_options = parse_class_option_keys(test_options)
|
||||
|
||||
# Yes I reuse the worlds generated between tests, its speeds the execution by a couple seconds
|
||||
# If the simple dict caching ends up taking too much memory, we could replace it with some kind of lru cache.
|
||||
should_cache = should_cache_world(test_options)
|
||||
if should_cache:
|
||||
frozen_options = make_hashable(test_options, seed)
|
||||
cached_multi_world = search_world_cache(_cache, frozen_options)
|
||||
if cached_multi_world:
|
||||
print(f"Using cached solo multi world [Seed = {cached_multi_world.seed}] [Cache size = {len(_cache)}]")
|
||||
return cached_multi_world
|
||||
|
||||
multiworld = setup_base_solo_multiworld(StardewValleyWorld, (), seed=seed)
|
||||
# print(f"Seed: {multiworld.seed}") # Uncomment to print the seed for every test
|
||||
|
||||
args = fill_namespace_with_default(test_options)
|
||||
multiworld.set_options(args)
|
||||
|
||||
if "start_inventory" in test_options:
|
||||
for item, amount in test_options["start_inventory"].items():
|
||||
for _ in range(amount):
|
||||
multiworld.push_precollected(multiworld.create_item(item, 1))
|
||||
|
||||
for step in _steps:
|
||||
call_all(multiworld, step)
|
||||
|
||||
if should_cache:
|
||||
add_to_world_cache(_cache, frozen_options, multiworld) # noqa
|
||||
|
||||
# Lock is needed for multi-threading tests
|
||||
setattr(multiworld, "lock", threading.Lock())
|
||||
|
||||
return multiworld
|
||||
|
||||
|
||||
def should_cache_world(test_options):
|
||||
if "start_inventory" in test_options:
|
||||
return False
|
||||
|
||||
trap_distribution_key = "trap_distribution"
|
||||
if trap_distribution_key not in test_options:
|
||||
return True
|
||||
|
||||
trap_distribution = test_options[trap_distribution_key]
|
||||
for key in trap_distribution:
|
||||
if trap_distribution[key] != options.TrapDistribution.default_weight:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def make_hashable(test_options, seed):
|
||||
return frozenset(test_options.items()).union({("seed", seed)})
|
||||
|
||||
|
||||
def search_world_cache(cache: Dict[frozenset, MultiWorld], frozen_options: frozenset) -> Optional[MultiWorld]:
|
||||
try:
|
||||
return cache[frozen_options]
|
||||
except KeyError:
|
||||
for cached_options, multi_world in cache.items():
|
||||
if frozen_options.issubset(cached_options):
|
||||
return multi_world
|
||||
return None
|
||||
|
||||
|
||||
def add_to_world_cache(cache: Dict[frozenset, MultiWorld], frozen_options: frozenset, multi_world: MultiWorld) -> None:
|
||||
# We could complete the key with all the default options, but that does not seem to improve performances.
|
||||
cache[frozen_options] = multi_world
|
||||
|
||||
|
||||
def setup_multiworld(test_options: Iterable[Dict[str, int]] = None, seed=None) -> MultiWorld: # noqa
|
||||
if test_options is None:
|
||||
test_options = []
|
||||
|
||||
multiworld = MultiWorld(len(test_options))
|
||||
multiworld.player_name = {}
|
||||
multiworld.set_seed(seed)
|
||||
multiworld.state = CollectionState(multiworld)
|
||||
for i in range(1, len(test_options) + 1):
|
||||
multiworld.game[i] = StardewValleyWorld.game
|
||||
multiworld.player_name.update({i: f"Tester{i}"})
|
||||
args = fill_namespace_with_default(test_options)
|
||||
multiworld.set_options(args)
|
||||
|
||||
for step in gen_steps:
|
||||
call_all(multiworld, step)
|
||||
|
||||
return multiworld
|
@@ -1,5 +1,5 @@
|
||||
from . import SVContentPackTestBase
|
||||
from .. import SVTestBase
|
||||
from ..bases import SVTestBase
|
||||
from ... import options
|
||||
from ...content import content_packs
|
||||
from ...data.artisan import MachineSource
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from .. import SVContentPackTestBase
|
||||
from ... import SVTestBase
|
||||
from ...bases import SVTestBase
|
||||
from .... import options
|
||||
from ....content import content_packs
|
||||
from ....mods.mod_data import ModNames
|
||||
|
@@ -4,8 +4,8 @@ from typing import ClassVar
|
||||
|
||||
from BaseClasses import get_seed
|
||||
from test.param import classvar_matrix
|
||||
from .. import SVTestCase, solo_multiworld, skip_long_tests
|
||||
from ..assertion import WorldAssertMixin, ModAssertMixin
|
||||
from ..bases import skip_long_tests, SVTestCase, solo_multiworld
|
||||
from ..options.option_names import all_option_choices
|
||||
from ... import options
|
||||
from ...mods.mod_data import ModNames
|
||||
|
@@ -4,8 +4,8 @@ from typing import ClassVar
|
||||
|
||||
from BaseClasses import get_seed
|
||||
from test.param import classvar_matrix
|
||||
from .. import SVTestCase, solo_multiworld, skip_long_tests
|
||||
from ..assertion.world_assert import WorldAssertMixin
|
||||
from ..bases import skip_long_tests, SVTestCase, solo_multiworld
|
||||
from ..options.option_names import all_option_choices
|
||||
from ... import options
|
||||
|
||||
|
@@ -3,8 +3,8 @@ from typing import ClassVar
|
||||
|
||||
from BaseClasses import get_seed
|
||||
from test.param import classvar_matrix
|
||||
from .. import SVTestCase, solo_multiworld, skip_long_tests
|
||||
from ..assertion import WorldAssertMixin
|
||||
from ..bases import skip_long_tests, SVTestCase, solo_multiworld
|
||||
from ... import options
|
||||
|
||||
if skip_long_tests():
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from .. import SVTestBase
|
||||
from ..bases import SVTestBase
|
||||
from ...mods.mod_data import ModNames
|
||||
from ...options import Mods, BackpackProgression
|
||||
|
||||
|
@@ -3,9 +3,9 @@ from typing import ClassVar
|
||||
|
||||
from BaseClasses import get_seed
|
||||
from test.param import classvar_matrix
|
||||
from .. import SVTestBase, SVTestCase, solo_multiworld
|
||||
from ..TestGeneration import get_all_permanent_progression_items
|
||||
from ..assertion import ModAssertMixin, WorldAssertMixin
|
||||
from ..bases import SVTestCase, SVTestBase, solo_multiworld
|
||||
from ..options.presets import allsanity_mods_6_x_x
|
||||
from ..options.utils import fill_dataclass_with_default
|
||||
from ... import options, Group, create_content
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from .. import SVTestBase
|
||||
from ..bases import SVTestBase
|
||||
from ... import options
|
||||
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from .. import SVTestBase
|
||||
from ..bases import SVTestBase
|
||||
from ... import options
|
||||
from ...mods.mod_data import ModNames
|
||||
from ...strings.ap_names.mods.mod_items import SVEQuestItem
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from Options import PerGameCommonOptions, OptionSet, OptionDict
|
||||
from .. import SVTestCase
|
||||
from ..bases import SVTestCase
|
||||
from ...options import StardewValleyOptions, TrapItems
|
||||
from ...options.presets import sv_options_presets
|
||||
|
||||
|
@@ -8,7 +8,7 @@ from typing import List
|
||||
from BaseClasses import get_seed
|
||||
from Fill import distribute_items_restrictive, balance_multiworld_progression
|
||||
from worlds import AutoWorld
|
||||
from .. import SVTestCase, setup_multiworld
|
||||
from ..bases import SVTestCase, setup_multiworld
|
||||
from ..options.presets import default_6_x_x, allsanity_no_mods_6_x_x, allsanity_mods_6_x_x, minimal_locations_maximal_items
|
||||
|
||||
assert default_6_x_x
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from ..bases import SVTestBase
|
||||
from ... import options
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestArcadeMachinesLogic(SVTestBase):
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from ..bases import SVTestBase
|
||||
from ... import options
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestBooksLogic(SVTestBase):
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from ..bases import SVTestBase
|
||||
from ...options import BuildingProgression, FarmType
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestBuildingLogic(SVTestBase):
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from ..bases import SVTestBase
|
||||
from ... import options
|
||||
from ...options import BundleRandomization
|
||||
from ...strings.bundle_names import BundleName
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestBundlesLogic(SVTestBase):
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from ..bases import SVTestBase
|
||||
from ... import options
|
||||
from ...options import BuildingProgression, ExcludeGingerIsland, Chefsanity
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestRecipeLearnLogic(SVTestBase):
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from ..bases import SVTestBase
|
||||
from ... import options
|
||||
from ...data.craftable_data import all_crafting_recipes_by_name
|
||||
from ...options import BuildingProgression, ExcludeGingerIsland, Craftsanity, SeasonRandomization
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestCraftsanityLogic(SVTestBase):
|
||||
|
@@ -1,8 +1,8 @@
|
||||
from ..bases import SVTestBase
|
||||
from ... import options
|
||||
from ...locations import locations_by_tag, LocationTags, location_table
|
||||
from ...strings.entrance_names import Entrance
|
||||
from ...strings.region_names import Region
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestDonationLogicAll(SVTestBase):
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from ..bases import SVTestBase
|
||||
from ...options import SeasonRandomization, Fishsanity, ExcludeGingerIsland, SkillProgression, ToolProgression, ElevatorProgression, SpecialOrderLocations
|
||||
from ...strings.fish_names import Fish
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestNeedRegionToCatchFish(SVTestBase):
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from ..bases import SVTestBase
|
||||
from ...options import SeasonRandomization, Friendsanity, FriendsanityHeartSize
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestFriendsanityDatingRules(SVTestBase):
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from collections import Counter
|
||||
|
||||
from ..bases import SVTestBase
|
||||
from ...options import Museumsanity
|
||||
from .. import SVTestBase
|
||||
|
||||
|
||||
class TestMuseumMilestones(SVTestBase):
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from ..bases import SVTestBase
|
||||
from ...locations import LocationTags, location_table
|
||||
from ...options import BuildingProgression, Shipsanity
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestShipsanityNone(SVTestBase):
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from ..bases import SVTestBase
|
||||
from ... import HasProgressionPercent, StardewLogic
|
||||
from ...options import ToolProgression, SkillProgression, Mods
|
||||
from ...strings.skill_names import all_skills, all_vanilla_skills, Skill
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestSkillProgressionVanilla(SVTestBase):
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from .. import SVTestBase
|
||||
from ..bases import SVTestBase
|
||||
from ..options.presets import allsanity_mods_6_x_x
|
||||
from ...stardew_rule import HasProgressionPercent
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from collections import Counter
|
||||
|
||||
from .. import SVTestBase
|
||||
from ..bases import SVTestBase
|
||||
from ... import options
|
||||
from ...options import ToolProgression, SeasonRandomization
|
||||
from ...strings.entrance_names import Entrance
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from ..bases import SVTestBase
|
||||
from ... import options
|
||||
from ...options import ToolProgression
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestWeaponsLogic(SVTestBase):
|
||||
|
@@ -15,8 +15,9 @@ import typing
|
||||
|
||||
from BaseClasses import CollectionState, Location
|
||||
from Utils import init_logging
|
||||
from worlds.stardew_valley.stardew_rule.rule_explain import explain
|
||||
from ... import test
|
||||
from ..bases import setup_solo_multiworld
|
||||
from ..options import presets
|
||||
from ...stardew_rule.rule_explain import explain
|
||||
|
||||
|
||||
def run_locations_benchmark():
|
||||
@@ -56,12 +57,12 @@ def run_locations_benchmark():
|
||||
parser.add_argument('--state', help="Define the state in which the location will be benchmarked.", type=str, default=None)
|
||||
args = parser.parse_args()
|
||||
options_set = args.options
|
||||
options = getattr(test, options_set)()
|
||||
options = getattr(presets, options_set)()
|
||||
seed = args.seed
|
||||
location = args.location
|
||||
state = args.state
|
||||
|
||||
multiworld = test.setup_solo_multiworld(options, seed)
|
||||
multiworld = setup_solo_multiworld(options, seed)
|
||||
gc.collect()
|
||||
|
||||
if location:
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from .. import setup_solo_multiworld
|
||||
from ..bases import setup_solo_multiworld
|
||||
from ..options.presets import allsanity_mods_6_x_x_exclude_disabled
|
||||
from ...options import FarmType, EntranceRandomization
|
||||
|
||||
|
@@ -5,7 +5,7 @@ import sys
|
||||
import unittest
|
||||
|
||||
from BaseClasses import get_seed
|
||||
from .. import SVTestCase
|
||||
from ..bases import SVTestCase
|
||||
|
||||
# <function Location.<lambda> at 0x102ca98a0>
|
||||
lambda_regex = re.compile(r"^<function Location\.<lambda> at (.*)>$")
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import unittest
|
||||
from unittest.mock import Mock
|
||||
|
||||
from .. import SVTestBase, fill_namespace_with_default, skip_long_tests
|
||||
from ..bases import skip_long_tests, SVTestBase
|
||||
from ..options.presets import allsanity_mods_6_x_x
|
||||
from ... import STARDEW_VALLEY, FarmType, BundleRandomization, EntranceRandomization
|
||||
from ..options.utils import fill_namespace_with_default
|
||||
from ... import STARDEW_VALLEY
|
||||
from ...options import FarmType, BundleRandomization, EntranceRandomization
|
||||
|
||||
|
||||
@unittest.skipIf(skip_long_tests(), "Long tests disabled")
|
||||
|
Reference in New Issue
Block a user