mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Restructure save games into a zipped bundle.
This is the first step toward bundling all assets related to a save game into a single item. That makes it easier to avoid clobbering "temporary" assets from other games like the state.json, but also makes it easier for players to file bug reports, since there's only a single asset to upload. This is only the first step because so far it only includes the various save files: start of turn, end of last turn before results processing, and "latest" (the game saved explicitly by the player).
This commit is contained in:
0
tests/persistence/__init__.py
Normal file
0
tests/persistence/__init__.py
Normal file
16
tests/persistence/conftest.py
Normal file
16
tests/persistence/conftest.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import datetime
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
|
||||
from game import Game
|
||||
|
||||
|
||||
class StubGame:
|
||||
def __init__(self) -> None:
|
||||
self.date = datetime.date.min
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def game() -> Game:
|
||||
return cast(Game, StubGame())
|
||||
104
tests/persistence/test_savegamebundle.py
Normal file
104
tests/persistence/test_savegamebundle.py
Normal file
@@ -0,0 +1,104 @@
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
from zipfile import ZipFile
|
||||
|
||||
import pytest
|
||||
|
||||
from game import Game
|
||||
from game.persistence.savegamebundle import SaveGameBundle
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_bundle(tmp_zip: Path) -> SaveGameBundle:
|
||||
return SaveGameBundle(tmp_zip)
|
||||
|
||||
|
||||
def test_save_player_new_save(game: Game, tmp_bundle: SaveGameBundle) -> None:
|
||||
with ZipFile(tmp_bundle.bundle_path, "r") as zip_file:
|
||||
with pytest.raises(KeyError):
|
||||
zip_file.read(SaveGameBundle.MANUAL_SAVE_NAME)
|
||||
tmp_bundle.save_player(game, copy_from=None)
|
||||
|
||||
with ZipFile(tmp_bundle.bundle_path, "r") as zip_file:
|
||||
assert zip_file.namelist() == [SaveGameBundle.MANUAL_SAVE_NAME]
|
||||
|
||||
|
||||
def test_save_player_existing_save(game: Game, tmp_bundle: SaveGameBundle) -> None:
|
||||
game.date = datetime.date.min
|
||||
tmp_bundle.save_start_of_turn(game)
|
||||
tmp_bundle.save_player(game, copy_from=tmp_bundle)
|
||||
|
||||
test_date = datetime.date.today()
|
||||
game.date = test_date
|
||||
tmp_bundle.save_player(game, copy_from=tmp_bundle)
|
||||
|
||||
assert tmp_bundle.load_start_of_turn().date == datetime.date.min
|
||||
assert tmp_bundle.load_player().date == test_date
|
||||
|
||||
|
||||
def test_save_last_turn(game: Game, tmp_bundle: SaveGameBundle) -> None:
|
||||
with ZipFile(tmp_bundle.bundle_path, "r") as zip_file:
|
||||
with pytest.raises(KeyError):
|
||||
zip_file.read(SaveGameBundle.LAST_TURN_SAVE_NAME)
|
||||
tmp_bundle.save_last_turn(game)
|
||||
|
||||
with ZipFile(tmp_bundle.bundle_path, "r") as zip_file:
|
||||
assert zip_file.namelist() == [SaveGameBundle.LAST_TURN_SAVE_NAME]
|
||||
|
||||
|
||||
def test_save_start_of_turn(game: Game, tmp_bundle: SaveGameBundle) -> None:
|
||||
with ZipFile(tmp_bundle.bundle_path, "r") as zip_file:
|
||||
with pytest.raises(KeyError):
|
||||
zip_file.read(SaveGameBundle.START_OF_TURN_SAVE_NAME)
|
||||
tmp_bundle.save_start_of_turn(game)
|
||||
|
||||
with ZipFile(tmp_bundle.bundle_path, "r") as zip_file:
|
||||
assert zip_file.namelist() == [SaveGameBundle.START_OF_TURN_SAVE_NAME]
|
||||
|
||||
|
||||
def test_failed_save_leaves_original_intact(
|
||||
game: Game, tmp_bundle: SaveGameBundle
|
||||
) -> None:
|
||||
expect_date = datetime.date.today()
|
||||
game.date = expect_date
|
||||
tmp_bundle.save_player(game, copy_from=None)
|
||||
|
||||
# Add some non-pickleable member to the game to cause an error on save.
|
||||
def local_f() -> None:
|
||||
pass
|
||||
|
||||
game.garbage = local_f # type: ignore
|
||||
with pytest.raises(AttributeError):
|
||||
tmp_bundle.save_player(game, copy_from=tmp_bundle)
|
||||
|
||||
assert tmp_bundle.load_player().date == expect_date
|
||||
|
||||
|
||||
def test_load_reads_correct_data(game: Game, tmp_bundle: SaveGameBundle) -> None:
|
||||
last_turn_date = datetime.date.today() - datetime.timedelta(days=2)
|
||||
game.date = last_turn_date
|
||||
tmp_bundle.save_last_turn(game)
|
||||
|
||||
start_of_turn_date = datetime.date.today() - datetime.timedelta(days=1)
|
||||
game.date = start_of_turn_date
|
||||
tmp_bundle.save_start_of_turn(game)
|
||||
|
||||
player_date = datetime.date.today()
|
||||
game.date = player_date
|
||||
tmp_bundle.save_player(game, copy_from=tmp_bundle)
|
||||
|
||||
assert tmp_bundle.load_last_turn().date == last_turn_date
|
||||
assert tmp_bundle.load_start_of_turn().date == start_of_turn_date
|
||||
assert tmp_bundle.load_player().date == player_date
|
||||
|
||||
|
||||
def test_load_from_absent_file_raises(tmp_bundle: SaveGameBundle) -> None:
|
||||
tmp_bundle.bundle_path.unlink(missing_ok=True)
|
||||
with pytest.raises(FileNotFoundError):
|
||||
tmp_bundle.load_last_turn()
|
||||
|
||||
|
||||
def test_load_from_absent_member_raises(game: Game, tmp_bundle: SaveGameBundle) -> None:
|
||||
tmp_bundle.save_start_of_turn(game)
|
||||
with pytest.raises(KeyError):
|
||||
tmp_bundle.load_last_turn()
|
||||
152
tests/persistence/test_savemanager.py
Normal file
152
tests/persistence/test_savemanager.py
Normal file
@@ -0,0 +1,152 @@
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from game import Game
|
||||
from game.persistence import SaveManager, set_dcs_save_game_directory
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_setup_last_save_file(mocker: MockerFixture) -> Mock:
|
||||
return mocker.patch("qt_ui.liberation_install.setup_last_save_file")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_save_config(mocker: MockerFixture) -> Mock:
|
||||
return mocker.patch("qt_ui.liberation_install.save_config")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_persistence_paths(tmp_path: Path) -> None:
|
||||
set_dcs_save_game_directory(tmp_path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def save_manager(game: Game) -> SaveManager:
|
||||
return SaveManager(game)
|
||||
|
||||
|
||||
def test_new_savemanager_saves_to_autosave(save_manager: SaveManager) -> None:
|
||||
assert save_manager.default_save_location == save_manager.autosave_path
|
||||
|
||||
|
||||
def test_savemanager_saves_to_last_save_location(save_manager: SaveManager) -> None:
|
||||
save_manager.player_save_location = Path("Saves/foo.liberation.zip")
|
||||
assert save_manager.default_save_location == save_manager.player_save_location
|
||||
|
||||
|
||||
def test_saving_without_name_saves_to_autosave_path(save_manager: SaveManager) -> None:
|
||||
assert not save_manager.autosave_path.exists()
|
||||
save_manager.save_player()
|
||||
assert save_manager.autosave_path.exists()
|
||||
|
||||
|
||||
def test_saving_with_name_updates_last_save_location(
|
||||
save_manager: SaveManager, tmp_path: Path
|
||||
) -> None:
|
||||
save_path = tmp_path / "foo.liberation.zip"
|
||||
assert not save_path.exists()
|
||||
save_manager.save_player(override_destination=save_path)
|
||||
assert save_path.exists()
|
||||
assert save_manager.last_saved_bundle is not None
|
||||
assert save_manager.last_saved_bundle.bundle_path == save_path
|
||||
|
||||
|
||||
def test_player_save_location(save_manager: SaveManager, tmp_path: Path) -> None:
|
||||
assert save_manager.player_save_location is None
|
||||
save_manager.save_last_turn()
|
||||
assert save_manager.player_save_location is None
|
||||
save_manager.save_start_of_turn()
|
||||
assert save_manager.player_save_location is None
|
||||
expect_location = tmp_path / "player.liberation.zip"
|
||||
save_manager.save_player(override_destination=expect_location)
|
||||
assert save_manager.player_save_location == expect_location
|
||||
|
||||
|
||||
def test_saving_updates_preferences_with_save_location(
|
||||
save_manager: SaveManager, mock_setup_last_save_file: Mock, mock_save_config: Mock
|
||||
) -> None:
|
||||
save_manager.save_player()
|
||||
mock_setup_last_save_file.assert_called_once_with(
|
||||
str(save_manager.default_save_location)
|
||||
)
|
||||
mock_save_config.assert_called_once()
|
||||
|
||||
|
||||
def test_non_player_saves_do_not_update_preferences(
|
||||
save_manager: SaveManager, mock_setup_last_save_file: Mock
|
||||
) -> None:
|
||||
save_manager.save_last_turn()
|
||||
mock_setup_last_save_file.assert_not_called()
|
||||
save_manager.save_start_of_turn()
|
||||
mock_setup_last_save_file.assert_not_called()
|
||||
|
||||
|
||||
def test_load_game_loads_correct_data(save_manager: SaveManager) -> None:
|
||||
test_date = datetime.date.today()
|
||||
assert save_manager.game.date != test_date
|
||||
save_manager.game.date = test_date
|
||||
save_manager.save_player()
|
||||
game = SaveManager.load_player_save(save_manager.default_save_location)
|
||||
assert game.date == test_date
|
||||
|
||||
|
||||
def test_loading_missing_save_raises() -> None:
|
||||
with pytest.raises(FileNotFoundError):
|
||||
SaveManager.load_player_save(Path("does not exist"))
|
||||
|
||||
|
||||
def test_saving_after_autosave_copies_autosave_members(
|
||||
save_manager: SaveManager, tmp_path: Path
|
||||
) -> None:
|
||||
save_manager.save_start_of_turn()
|
||||
|
||||
save_path = tmp_path / "foo.liberation.zip"
|
||||
save_manager.save_player(override_destination=save_path)
|
||||
|
||||
SaveManager.load_start_of_turn(save_path)
|
||||
|
||||
|
||||
def test_failed_save_does_not_update_last_saved_path(
|
||||
save_manager: SaveManager, tmp_path: Path
|
||||
) -> None:
|
||||
expect_date = datetime.date.today()
|
||||
save_manager.game.date = expect_date
|
||||
save_manager.save_player()
|
||||
assert save_manager.last_saved_bundle is not None
|
||||
expect_path = save_manager.last_saved_bundle.bundle_path
|
||||
|
||||
# Add some non-pickleable member to the game to cause an error on save.
|
||||
def local_f() -> None:
|
||||
pass
|
||||
|
||||
save_manager.game.garbage = local_f # type: ignore
|
||||
with pytest.raises(AttributeError):
|
||||
save_manager.save_player(
|
||||
override_destination=tmp_path / "badsave.liberation.zip"
|
||||
)
|
||||
|
||||
assert save_manager.last_saved_bundle.bundle_path == expect_path
|
||||
|
||||
|
||||
def test_load_reads_correct_data(save_manager: SaveManager) -> None:
|
||||
last_turn_date = datetime.date.today() - datetime.timedelta(days=2)
|
||||
save_manager.game.date = last_turn_date
|
||||
save_manager.save_last_turn()
|
||||
|
||||
start_of_turn_date = datetime.date.today() - datetime.timedelta(days=1)
|
||||
save_manager.game.date = start_of_turn_date
|
||||
save_manager.save_start_of_turn()
|
||||
|
||||
player_date = datetime.date.today()
|
||||
save_manager.game.date = player_date
|
||||
save_manager.save_player()
|
||||
|
||||
assert save_manager.last_saved_bundle is not None
|
||||
bundle_path = save_manager.last_saved_bundle.bundle_path
|
||||
assert SaveManager.load_last_turn(bundle_path).date == last_turn_date
|
||||
assert SaveManager.load_start_of_turn(bundle_path).date == start_of_turn_date
|
||||
assert SaveManager.load_player_save(bundle_path).date == player_date
|
||||
Reference in New Issue
Block a user