def test_decode(mock_dictionary_byte_hash: MagicMock): mock_dictionary_byte_hash.return_value = 120 # We're mocking the database hash to avoid breaking tests every single time we change the database # This test should break whenever we change how permalinks are created # When this happens, we must bump the permalink version and change the tests encoded = "gAAAfReLCAAC4wAAAOaANg==" expected = Permalink( seed_number=1000, spoiler=True, patcher_configuration=PatcherConfiguration( menu_mod=True, warp_to_start=False, ), layout_configuration=LayoutConfiguration.from_params( trick_level_configuration=TrickLevelConfiguration( LayoutTrickLevel.HARD), elevators=LayoutElevators.RANDOMIZED, ), ) # Uncomment this line to quickly get the new encoded permalink # assert expected.as_str == "" # print(expected.as_str) # Run link = Permalink.from_str(encoded) # Assert assert link == expected
def test_decode_mock_other(encoded, num_players, mocker): preset = MagicMock() def read_values(decoder: BitPackDecoder, metadata): decoder.decode(100, 100) return preset mock_preset_unpack: MagicMock = mocker.patch( "randovania.layout.preset.Preset.bit_pack_unpack", side_effect=read_values) expected = Permalink( seed_number=1000, spoiler=True, presets={i: preset for i in range(num_players)}, ) preset.bit_pack_encode.return_value = [(0, 100), (5, 100)] # Uncomment this line to quickly get the new encoded permalink # assert expected.as_base64_str == "" # print(expected.as_base64_str) # Run round_trip = expected.as_base64_str link = Permalink.from_str(encoded) # Assert assert link == expected assert round_trip == encoded mock_preset_unpack.assert_called_once_with(ANY, {"manager": ANY})
def test_decode_mock_other( mock_packer_unpack: MagicMock, mock_layout_unpack: MagicMock, ): encoded = "gAAAfRggLQ==" patcher_configuration = mock_packer_unpack.return_value layout_configuration = mock_layout_unpack.return_value expected = Permalink( seed_number=1000, spoiler=True, patcher_configuration=patcher_configuration, layout_configuration=layout_configuration, ) patcher_configuration.bit_pack_encode.return_value = [] layout_configuration.bit_pack_encode.return_value = [] mock_layout_unpack.return_value.game_data = {"test": True} # Uncomment this line to quickly get the new encoded permalink # assert expected.as_str == "" # print(expected.as_str) # Run link = Permalink.from_str(encoded) round_trip = expected.as_str # Assert assert link == expected assert encoded == round_trip mock_packer_unpack.assert_called_once() mock_layout_unpack.assert_called_once() patcher_configuration.bit_pack_encode.assert_called_once_with({}) layout_configuration.bit_pack_encode.assert_called_once_with({})
def test_round_trip(spoiler: bool, layout: dict, default_preset, mocker): # Setup random_uuid = uuid.uuid4() mocker.patch("uuid.uuid4", return_value=random_uuid) preset = Preset( name="{} Custom".format(default_preset.name), description="A customized preset.", uuid=random_uuid, base_preset_uuid=default_preset.uuid, game=default_preset.game, configuration=dataclasses.replace(default_preset.configuration, **layout), ) link = Permalink( seed_number=1000, spoiler=spoiler, presets={0: preset}, ) # Run after = Permalink.from_str(link.as_base64_str) # Assert assert link == after
def test_decode_old_version(permalink: str, version: int): with pytest.raises(ValueError) as exp: Permalink.from_str(permalink) assert str( exp.value) == ("Given permalink has version {}, but this Randovania " "support only permalink of version {}.".format( version, Permalink.current_version()))
def test_round_trip(spoiler: bool, patcher: PatcherConfiguration, layout: LayoutConfiguration): # Setup link = Permalink( seed_number=1000, spoiler=spoiler, patcher_configuration=patcher, layout_configuration=layout, ) # Run after = Permalink.from_str(link.as_str) # Assert assert link == after
def _async_create_description( permalink: Permalink, status_update: Callable[[str], None], attempts: int, ) -> LayoutDescription: """ :param permalink: :param status_update: :return: """ rng = Random(permalink.as_bytes) presets = { i: permalink.get_preset(i) for i in range(permalink.player_count) } retrying = tenacity.Retrying( stop=tenacity.stop_after_attempt(attempts), retry=tenacity.retry_if_exception_type(UnableToGenerate), reraise=True) filler_results = retrying(_create_pools_and_fill, rng, presets, status_update) all_patches = _distribute_remaining_items(rng, filler_results.player_results) return LayoutDescription( permalink=permalink, version=VERSION, all_patches=all_patches, item_order=filler_results.action_log, )
def batch_distribute_command_logic(args): finished_count = 0 validate: bool = args.validate output_dir: Path = args.output_dir output_dir.mkdir(parents=True, exist_ok=True) base_permalink = Permalink.from_str(args.permalink) def callback(result): nonlocal finished_count finished_count += 1 print("Finished seed in {} seconds. At {} of {} seeds.".format(result, finished_count, args.seed_count)) def error_callback(e): nonlocal finished_count finished_count += 1 print("Failed to generate seed: {}".format(e)) with multiprocessing.Pool() as pool: for seed_number in range(base_permalink.seed_number, base_permalink.seed_number + args.seed_count): pool.apply_async( func=batch_distribute_helper, args=(base_permalink, seed_number, output_dir, validate), callback=callback, error_callback=error_callback, ) pool.close() pool.join()
def test_batch_distribute_helper( mock_perf_counter: MagicMock, mock_generate_description: MagicMock, ): # Setup base_permalink = MagicMock() seed_number = 5000 validate = MagicMock() output_dir = MagicMock() timeout = 67 expected_permalink = Permalink( seed_number=seed_number, spoiler=True, presets=base_permalink.presets, ) mock_perf_counter.side_effect = [1000, 5000] # Run delta_time = batch_distribute.batch_distribute_helper( base_permalink, seed_number, timeout, validate, output_dir) # Assert mock_generate_description.assert_called_once_with( permalink=expected_permalink, status_update=None, validate_after_generation=validate, timeout=timeout) assert delta_time == 4000 output_dir.joinpath.assert_called_once_with("{}.json".format(seed_number)) mock_generate_description.return_value.save_to_file.assert_called_once_with( output_dir.joinpath.return_value)
def batch_distribute_helper( base_permalink, seed_number: int, timeout: int, validate: bool, output_dir: Path, ) -> float: from randovania.generator import generator from randovania.layout.permalink import Permalink permalink = Permalink( seed_number=seed_number, spoiler=True, presets=typing.cast(Permalink, base_permalink).presets, ) start_time = time.perf_counter() description = asyncio.run( generator.generate_and_validate_description( permalink=permalink, status_update=None, validate_after_generation=validate, timeout=timeout, attempts=0, )) delta_time = time.perf_counter() - start_time description.save_to_file( output_dir.joinpath("{}.{}".format(seed_number, description.file_extension()))) return delta_time
def _test_data(default_preset): data = default_data.decode_default_prime2() game = data_reader.decode_data(data) permalink = Permalink( seed_number=15000, spoiler=True, presets={0: default_preset}, ) configuration = permalink.get_preset(0).layout_configuration patches = game.create_game_patches() patches = patches.assign_gate_assignment( base_patches_factory.gate_assignment_for_configuration( configuration, game.resource_database, Random(15000))) game, state = logic_bootstrap(configuration, game, patches) return game, state, permalink
def test_batch_distribute_helper( mock_perf_counter: MagicMock, mock_generate_description: MagicMock, ): # Setup base_permalink = MagicMock() seed_number = 5000 validate = MagicMock() output_dir = MagicMock() expected_permalink = Permalink( seed_number=seed_number, spoiler=True, patcher_configuration=base_permalink.patcher_configuration, layout_configuration=base_permalink.layout_configuration, ) mock_perf_counter.side_effect = [1000, 5000] # Run delta_time = batch_distribute.batch_distribute_helper( base_permalink, seed_number, validate, output_dir) # Assert mock_generate_description.assert_called_once_with(expected_permalink, None, validate) assert delta_time == 4000 output_dir.joinpath.assert_called_once_with("{}.json".format(seed_number)) mock_generate_description.return_value.save_to_file.assert_called_once_with( output_dir.joinpath.return_value)
def test_round_trip_generated_patches(echoes_game_data, preset_manager): # Setup preset = dataclasses.replace( preset_manager.default_preset, layout_configuration=dataclasses.replace( preset_manager.default_preset.layout_configuration, trick_level_configuration=TrickLevelConfiguration( global_level=LayoutTrickLevel.MINIMAL_RESTRICTIONS, specific_levels={}, ) ) ) patches = generator._create_randomized_patches( permalink=Permalink( seed_number=1000, spoiler=True, preset=preset, ), game=data_reader.decode_data(echoes_game_data), status_update=lambda x: None, ) # Run encoded = game_patches_serializer.serialize(patches, echoes_game_data) decoded = game_patches_serializer.decode(encoded, preset.layout_configuration) # Assert assert patches == decoded
async def on_request_presets(self, ctx: ComponentContext): try: title = ctx.origin_message.embeds[0].title # Trim leading and trailing `s permalink = Permalink.from_str(title[1:-1]) except (IndexError, ValueError, UnsupportedPermalink) as e: logging.exception("Unable to find permalink on message that sent attach_presets_of_permalink") permalink = None files = [] if permalink is not None: for player, preset in enumerate(permalink.parameters.presets): data = io.BytesIO() VersionedPreset.with_preset(preset).save_to_io(data) data.seek(0) files.append( discord.File(data, filename=f"Player {player + 1}'s Preset.{VersionedPreset.file_extension()}") ) await ctx.edit_origin( components=[], files=files, )
def _generate_new_seed(self, spoiler: bool): self.generate_seed_from_permalink( Permalink( seed_number=random.randint(0, 2**31), spoiler=spoiler, preset=self._current_preset_data, ))
def test_generate_new_seed(tab, preset_manager, mocker): # Setup mock_randint: MagicMock = mocker.patch("random.randint", return_value=12341234) tab.window.create_preset_tree = MagicMock() tab.window.create_preset_tree.current_preset_data = preset_manager.default_preset tab.generate_seed_from_permalink = MagicMock() spoiler = MagicMock(spec=bool) retries = MagicMock(spec=int) # Run tab._generate_new_seed(spoiler, retries) # Assert tab.generate_seed_from_permalink.assert_called_once_with( Permalink.from_parameters( GeneratorParameters( seed_number=12341234, spoiler=spoiler, presets=[preset_manager.default_preset.get_preset()], ) ), retries=retries ) mock_randint.assert_called_once_with(0, 2 ** 31)
async def test_round_trip_generated_patches(default_preset): # Setup preset = dataclasses.replace( default_preset, uuid=uuid.UUID('b41fde84-1f57-4b79-8cd6-3e5a78077fa6'), base_preset_uuid=default_preset.uuid, configuration=dataclasses.replace(default_preset.configuration, trick_level=TrickLevelConfiguration( minimal_logic=True, specific_levels={}, game=default_preset.game, ))) description = await generator._create_description( permalink=Permalink( seed_number=1000, spoiler=True, presets={0: preset}, ), status_update=lambda x: None, attempts=0, ) all_patches = description.all_patches # Run encoded = game_patches_serializer.serialize(all_patches, {0: default_preset.game}) decoded = game_patches_serializer.decode(encoded, {0: preset.configuration}) # Assert assert all_patches == decoded
def _async_create_description( permalink: Permalink, status_update: Callable[[str], None], ) -> LayoutDescription: """ :param permalink: :param status_update: :return: """ rng = Random(permalink.as_str) presets = { i: permalink.get_preset(i) for i in range(permalink.player_count) } filler_results = _retryable_create_patches(rng, presets, status_update) all_patches = _distribute_remaining_items(rng, filler_results.player_results) return LayoutDescription( permalink=permalink, version=VERSION, all_patches=all_patches, item_order=filler_results.action_log, )
def test_round_trip_generated_patches(echoes_game_data, default_preset): # Setup preset = dataclasses.replace( default_preset, base_preset_name=default_preset.name, configuration=dataclasses.replace( default_preset.configuration, trick_level=TrickLevelConfiguration( minimal_logic=True, specific_levels={}, game=RandovaniaGame.PRIME2, ) ) ) all_patches = generator._async_create_description( permalink=Permalink( seed_number=1000, spoiler=True, presets={0: preset}, ), status_update=lambda x: None, attempts=0, ).all_patches # Run encoded = game_patches_serializer.serialize(all_patches, {0: echoes_game_data}) decoded = game_patches_serializer.decode(encoded, {0: preset.configuration}) # Assert assert all_patches == decoded
def distribute_command_logic(args): def status_update(s): if args.status_update: print(s) if args.permalink is not None: permalink = Permalink.from_str(args.permalink) else: permalink = asyncio.run(_create_permalink(args)) print(f"Permalink: {permalink.as_base64_str}") if permalink.spoiler: debug.set_level(args.debug) extra_args = {} if args.no_retry: extra_args["attempts"] = 0 before = time.perf_counter() layout_description = generator.generate_description( permalink=permalink, status_update=status_update, validate_after_generation=args.validate, timeout=None, **extra_args) after = time.perf_counter() print("Took {} seconds. Hash: {}".format( after - before, layout_description.shareable_hash)) layout_description.save_to_file(args.output_file)
def _create_test_layout_description( configuration: LayoutConfiguration, pickup_mapping: Iterable[int], ) -> LayoutDescription: """ Creates a LayoutDescription for the given configuration, with the patches being for the given pickup_mapping :param configuration: :param pickup_mapping: :return: """ game = data_reader.decode_data(configuration.game_data) pickup_database = game.pickup_database return LayoutDescription( version=VERSION, permalink=Permalink( seed_number=0, spoiler=True, patcher_configuration=PatcherConfiguration.default(), layout_configuration=configuration, ), patches=GamePatches.with_game(game).assign_new_pickups([ (PickupIndex(i), pickup_database.original_pickup_mapping[PickupIndex(new_index)]) for i, new_index in enumerate(pickup_mapping) ]), solver_path=())
def batch_distribute_helper( base_permalink: Permalink, seed_number: int, timeout: int, validate: bool, output_dir: Path, ) -> float: permalink = Permalink( seed_number=seed_number, spoiler=True, patcher_configuration=base_permalink.patcher_configuration, layout_configuration=base_permalink.layout_configuration, ) start_time = time.perf_counter() description = generator.generate_description( permalink=permalink, status_update=None, validate_after_generation=validate, timeout=timeout) delta_time = time.perf_counter() - start_time description.save_to_file(output_dir.joinpath( "{}.json".format(seed_number))) return delta_time
def test_distribute_command_logic(mock_generate_list: MagicMock, ): # Setup args = MagicMock() args.trick_level = LayoutTrickLevel.HARD.value args.major_items_mode = False args.sky_temple_keys = LayoutSkyTempleKeyMode.ALL_BOSSES.value args.skip_item_loss = True args.seed = 15000 args.output_file = "asdfasdf/qwerqwerqwer/zxcvzxcv.json" # Run echoes.distribute_command_logic(args) # Assert mock_generate_list.assert_called_once_with(permalink=Permalink( seed_number=args.seed, spoiler=True, patcher_configuration=PatcherConfiguration.default(), layout_configuration=LayoutConfiguration.from_params( trick_level=LayoutTrickLevel.HARD, sky_temple_keys=LayoutSkyTempleKeyMode.ALL_BOSSES, elevators=LayoutRandomizedFlag.VANILLA, pickup_quantities={}, starting_location=StartingLocation.default(), starting_resources=StartingResources.from_non_custom_configuration( StartingResourcesConfiguration.VANILLA_ITEM_LOSS_DISABLED), ), ), status_update=ANY) save_file_mock: MagicMock = mock_generate_list.return_value.save_to_file save_file_mock.assert_called_once_with(Path(args.output_file))
def test_round_trip_generated_patches(echoes_game_data): # Setup configuration = LayoutConfiguration.from_params( trick_level_configuration=TrickLevelConfiguration( global_level=LayoutTrickLevel.MINIMAL_RESTRICTIONS, specific_levels={}, )) patches = generator._create_randomized_patches( permalink=Permalink( seed_number=1000, spoiler=True, patcher_configuration=PatcherConfiguration.default(), layout_configuration=configuration, ), game=data_reader.decode_data(echoes_game_data), status_update=lambda x: None, ) # Run encoded = game_patches_serializer.serialize(patches, echoes_game_data) decoded = game_patches_serializer.decode(encoded, configuration) # Assert assert patches == decoded
async def test_generate_game(window, mocker, preset_manager): mock_generate_layout: MagicMock = mocker.patch("randovania.interface_common.simplified_patcher.generate_layout") mock_randint: MagicMock = mocker.patch("random.randint", return_value=5000) spoiler = True game_session = MagicMock() game_session.presets = [preset_manager.default_preset, preset_manager.default_preset] window._game_session = game_session window._upload_layout_description = AsyncMock() window._admin_global_action = AsyncMock() # Run await window.generate_game(spoiler) # Assert mock_randint.assert_called_once_with(0, 2 ** 31) mock_generate_layout.assert_called_once_with( progress_update=ANY, permalink=Permalink( seed_number=mock_randint.return_value, spoiler=spoiler, presets={ 0: preset_manager.default_preset.get_preset(), 1: preset_manager.default_preset.get_preset(), }, ), options=window._options ) window._upload_layout_description.assert_awaited_once_with(mock_generate_layout.return_value)
def from_json_dict(cls, json_dict: dict) -> "LayoutDescription": json_dict = migrate_description(json_dict) has_spoiler = "game_modifications" in json_dict if not has_spoiler: raise ValueError( "Unable to read details of seed log with spoiler disabled") permalink = Permalink( seed_number=json_dict["info"]["seed"], spoiler=has_spoiler, presets={ index: VersionedPreset(preset).get_preset() for index, preset in enumerate(json_dict["info"]["presets"]) }, ) return LayoutDescription( version=json_dict["info"]["version"], permalink=permalink, all_patches=game_patches_serializer.decode( json_dict["game_modifications"], { index: preset.configuration for index, preset in permalink.presets.items() }), item_order=json_dict["item_order"], )
def test_round_trip_generated_patches(echoes_game_data, preset_manager): # Setup preset = dataclasses.replace( preset_manager.default_preset, layout_configuration=dataclasses.replace( preset_manager.default_preset.layout_configuration, trick_level_configuration=TrickLevelConfiguration( global_level=LayoutTrickLevel.MINIMAL_RESTRICTIONS, specific_levels={}, ))) all_patches = generator._async_create_description( permalink=Permalink( seed_number=1000, spoiler=True, presets={0: preset}, ), status_update=lambda x: None, ).all_patches # Run encoded = game_patches_serializer.serialize(all_patches, {0: echoes_game_data}) decoded = game_patches_serializer.decode(encoded, {0: preset.layout_configuration}) # Assert assert all_patches == decoded
def create_permalink(args): from randovania.layout.permalink import Permalink from randovania.layout.generator_parameters import GeneratorParameters from randovania.interface_common.preset_manager import PresetManager game: RandovaniaGame = RandovaniaGame(args.game) preset_manager = PresetManager(None) presets = [] for preset_name in args.preset_name: versioned = preset_manager.included_preset_with(game, preset_name) if versioned is None: raise ValueError( "Unknown included preset '{}' for game {}. Valid options are: {}" .format(preset_name, game.long_name, [ preset.name for preset in preset_manager.included_presets.values() if preset.game == game ])) presets.append(versioned.get_preset()) seed = args.seed_number if seed is None: seed = random.randint(0, 2**31) return Permalink.from_parameters( GeneratorParameters( seed, spoiler=not args.race, presets=presets, ), )
def test_create_permalink_logic(mock_print: MagicMock, ): # Setup args = MagicMock() args.trick_level = LayoutTrickLevel.HARD.value args.major_items_mode = False args.sky_temple_keys = LayoutSkyTempleKeyMode.ALL_BOSSES.value args.skip_item_loss = True args.seed = 15000 args.menu_mod = False args.warp_to_start = False # Run randovania.cli.commands.create_permalink.create_permalink_logic(args) # Assert permalink = Permalink( seed_number=args.seed, spoiler=True, patcher_configuration=PatcherConfiguration( menu_mod=args.menu_mod, warp_to_start=args.warp_to_start, ), layout_configuration=LayoutConfiguration.from_params( trick_level_configuration=TrickLevelConfiguration( LayoutTrickLevel.HARD), sky_temple_keys=LayoutSkyTempleKeyMode.ALL_BOSSES, elevators=LayoutElevators.VANILLA, starting_location=StartingLocation.default(), ), ) # Assert mock_print.assert_called_once_with(permalink)
def test_round_trip(seed_hash, fake_generator_parameters, mocker): mock_from_bytes: MagicMock = mocker.patch( "randovania.layout.generator_parameters.GeneratorParameters.from_bytes", autospec=True, return_value=fake_generator_parameters) # Setup link = Permalink(parameters=fake_generator_parameters, seed_hash=seed_hash, randovania_version=b"0123") # Run after = Permalink.from_str(link.as_base64_str) # Assert assert link == after mock_from_bytes.assert_called_once_with(b"\xA0\xB0\xC0")