Skip to content

Commit

Permalink
Add CLI tests
Browse files Browse the repository at this point in the history
  • Loading branch information
AnonymousRandomPerson committed Aug 31, 2024
1 parent f48d52e commit d5e7091
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 22 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ coverage.xml
.hypothesis/
.pytest_cache/
test/skytemple_files_test/common/fixtures/rom_copy.nds
test/skytemple_files_test/common/fixtures/temp_asset_project/

# Translations
*.mo
Expand Down
17 changes: 15 additions & 2 deletions skytemple_files/common/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def extract_rom_files_to_project(rom_path: Path, asset_dir: Path, file_types: li
for file_path, data_handler in rom_files.items():
if accepted_file_types is None or data_handler in accepted_file_types:
extract_rom_file_to_project(project, data_handler, file_path)
project.file_storage.save_rom_object_hash(file_path, project.file_storage.hash_of_rom_object(file_path))


def save_project_to_rom(rom_path: Path, asset_dir: Path, extracted_rom_dir: Path | None, file_types: list[str] | None):
Expand All @@ -49,6 +50,10 @@ def save_project_to_rom(rom_path: Path, asset_dir: Path, extracted_rom_dir: Path
if accepted_file_types is None or data_handler in accepted_file_types:
assets = project.load_assets(data_handler, file_path)
save_project_file_to_rom(project, data_handler, file_path, extracted_rom_dir, assets)
for asset in [asset for asset in assets if isinstance(asset, Asset)]:
project.file_storage.save_asset_hash(
asset.spec.path, project.file_storage.hash_of_asset(asset.spec.path)
)


def sync_project_and_rom(rom_path: Path, asset_dir: Path, extracted_rom_dir: Path | None, file_types: list[str] | None):
Expand All @@ -59,8 +64,9 @@ def sync_project_and_rom(rom_path: Path, asset_dir: Path, extracted_rom_dir: Pat
for file_path, data_handler in rom_files.items():
if accepted_file_types is None or data_handler in accepted_file_types:
assets = project.load_assets(data_handler, file_path)
asset_hashes_match = all(asset.do_asset_hashes_match() for asset in assets)
rom_hashes_match = all(asset.do_rom_hashes_match() for asset in assets)
asset_hashes_match = all(isinstance(asset, Asset) and asset.do_asset_hashes_match() for asset in assets)
assets_exist = any(isinstance(asset, Asset) for asset in assets)
rom_hashes_match = all(isinstance(asset, Asset) and asset.do_rom_hashes_match() for asset in assets)

def extract_rom_file_to_project_and_save_hash():
extract_rom_file_to_project(project, data_handler, file_path)
Expand All @@ -76,10 +82,17 @@ def save_project_file_to_rom_and_save_hash():

if asset_hashes_match and not rom_hashes_match:
# If ROM hashes don't match, then the ROM was modified. Sync the ROM changes to assets.
# If no assets exist, extract the ROM to assets.
extract_rom_file_to_project_and_save_hash()
elif not asset_hashes_match and rom_hashes_match:
# If asset hashes don't match, then the assets were modified. Sync the asset changes to ROM.
save_project_file_to_rom_and_save_hash()
elif not assets_exist:
extract_rom_file_to_project_and_save_hash()
if isinstance(assets[0], AssetSpec):
project.file_storage.save_rom_object_hash(
assets[0].rom_path, project.file_storage.hash_of_rom_object(assets[0].rom_path)
)
elif not asset_hashes_match and not rom_hashes_match:
# If both hashes don't match, there is a conflict.
# Ask the user which version of the files to use.
Expand Down
4 changes: 3 additions & 1 deletion skytemple_files/common/file_api_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,9 @@ def _save_hash_file(self, hash_file_name: str, hashes: dict[Path, AssetHash | No
lines = []
for file_name in sorted(hashes.keys()):
lines.append(f"{hashes[file_name]} {file_name}\n")
with open(Path(self.project_dir, hash_file_name), "w") as hash_file:
if not self.project_dir.exists():
self.project_dir.mkdir(parents=True, exist_ok=True)
with open(Path(self.project_dir, hash_file_name), "w+") as hash_file:
hash_file.writelines(lines)

@staticmethod
Expand Down
234 changes: 234 additions & 0 deletions test/skytemple_files_test/common/cli_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import os.path
import shutil
from pathlib import Path
from unittest import TestCase

import skytemple_files.common.cli as cli
from skytemple_files.common.file_api_v2 import ROM_HASHES_FILE, ASSET_HASHES_FILE
from skytemple_files_test.common.temp_rom import (
copy_rom_to_temp_file,
delete_temp_asset_project,
TEMP_ASSET_PROJECT_PATH,
ASSET_PROJECT_PATH,
)


class CliTestCase(TestCase):
def test_extract_rom_files_to_project(self):
try:
rom_path = copy_rom_to_temp_file()
cli.extract_rom_files_to_project(rom_path, TEMP_ASSET_PROJECT_PATH, None)

self.assertTrue(os.path.exists(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json")))

rom_hashes_path = Path(TEMP_ASSET_PROJECT_PATH, ROM_HASHES_FILE)
self.assertTrue(os.path.exists(rom_hashes_path))
with open(rom_hashes_path, "r") as rom_hashes_file:
self.assertTrue("f85089b1c47c9392c93f76f5d1baf9b28677454c BALANCE/waza_p.bin" in rom_hashes_file.read())
finally:
delete_temp_asset_project()

def test_extract_rom_files_to_project_with_file_type(self):
try:
rom_path = copy_rom_to_temp_file()
cli.extract_rom_files_to_project(rom_path, TEMP_ASSET_PROJECT_PATH, ["WAZA_P"])

self.assertTrue(os.path.exists(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json")))
finally:
delete_temp_asset_project()

def test_extract_rom_files_to_project_with_unimplemented_file_type(self):
try:
rom_path = copy_rom_to_temp_file()
cli.extract_rom_files_to_project(rom_path, TEMP_ASSET_PROJECT_PATH, ["KAO"])

self.assertFalse(os.path.exists(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json")))
finally:
delete_temp_asset_project()

def test_save_project_to_rom(self):
try:
rom_path = copy_rom_to_temp_file()
Path(TEMP_ASSET_PROJECT_PATH, "pokemon").mkdir(parents=True, exist_ok=True)
shutil.copyfile(
Path(ASSET_PROJECT_PATH, "pokemon", "moves.json"),
Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json"),
)
cli.save_project_to_rom(rom_path, TEMP_ASSET_PROJECT_PATH, None, None)

rom_hashes_path = Path(TEMP_ASSET_PROJECT_PATH, ROM_HASHES_FILE)
self.assertTrue(os.path.exists(rom_hashes_path))
with open(rom_hashes_path, "r") as rom_hashes_file:
self.assertTrue("a7ed113f8b4f542ffc927a2fd5e4509327bef7d8 BALANCE/waza_p.bin" in rom_hashes_file.read())

asset_hashes_path = Path(TEMP_ASSET_PROJECT_PATH, ASSET_HASHES_FILE)
self.assertTrue(os.path.exists(asset_hashes_path))
with open(asset_hashes_path, "r") as asset_hashes_file:
self.assertTrue(
"bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f pokemon/moves.json" in asset_hashes_file.read()
)
finally:
delete_temp_asset_project()

def test_save_project_to_rom_with_file_type(self):
try:
rom_path = copy_rom_to_temp_file()
Path(TEMP_ASSET_PROJECT_PATH, "pokemon").mkdir(parents=True, exist_ok=True)
shutil.copyfile(
Path(ASSET_PROJECT_PATH, "pokemon", "moves.json"),
Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json"),
)
cli.save_project_to_rom(rom_path, TEMP_ASSET_PROJECT_PATH, None, ["WAZA_P"])

rom_hashes_path = Path(TEMP_ASSET_PROJECT_PATH, ROM_HASHES_FILE)
self.assertTrue(os.path.exists(rom_hashes_path))
with open(rom_hashes_path, "r") as rom_hashes_file:
self.assertTrue("a7ed113f8b4f542ffc927a2fd5e4509327bef7d8 BALANCE/waza_p.bin" in rom_hashes_file.read())
finally:
delete_temp_asset_project()

def test_save_project_to_rom_with_unimplemented_file_type(self):
try:
rom_path = copy_rom_to_temp_file()
Path(TEMP_ASSET_PROJECT_PATH, "pokemon").mkdir(parents=True, exist_ok=True)
shutil.copyfile(
Path(ASSET_PROJECT_PATH, "pokemon", "moves.json"),
Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json"),
)
cli.save_project_to_rom(rom_path, TEMP_ASSET_PROJECT_PATH, None, ["KAO"])

self.assertFalse(os.path.exists(Path(TEMP_ASSET_PROJECT_PATH, "rom_hashes.sha1")))
finally:
delete_temp_asset_project()

def test_save_project_to_rom_with_extracted_rom_dir(self):
try:
rom_path = copy_rom_to_temp_file()
Path(TEMP_ASSET_PROJECT_PATH, "pokemon").mkdir(parents=True, exist_ok=True)
shutil.copyfile(
Path(ASSET_PROJECT_PATH, "pokemon", "moves.json"),
Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json"),
)
cli.save_project_to_rom(rom_path, TEMP_ASSET_PROJECT_PATH, TEMP_ASSET_PROJECT_PATH, None)

self.assertTrue(os.path.exists(Path(TEMP_ASSET_PROJECT_PATH, "BALANCE", "waza_p.bin")))
finally:
delete_temp_asset_project()

def test_sync_project_and_rom_with_no_asset_project(self):
try:
rom_path = copy_rom_to_temp_file()
cli.sync_project_and_rom(rom_path, TEMP_ASSET_PROJECT_PATH, None, ["WAZA_P"])

# If there are no assets, assets should be created.
self.assertTrue(os.path.exists(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json")))
self.assertTrue(os.path.exists(Path(TEMP_ASSET_PROJECT_PATH, ROM_HASHES_FILE)))
self.assertTrue(os.path.exists(Path(TEMP_ASSET_PROJECT_PATH, ASSET_HASHES_FILE)))
finally:
delete_temp_asset_project()

def test_sync_project_and_rom_with_rom_hashes_mismatch(self):
try:
rom_path = copy_rom_to_temp_file()
Path(TEMP_ASSET_PROJECT_PATH, "pokemon").mkdir(parents=True, exist_ok=True)
with open(Path(TEMP_ASSET_PROJECT_PATH, ROM_HASHES_FILE), "w+") as rom_hashes_file:
rom_hashes_file.write("0000000000000000000000000000000000000000 BALANCE/waza_p.bin\n")
with open(Path(TEMP_ASSET_PROJECT_PATH, ASSET_HASHES_FILE), "w+") as asset_hashes_file:
asset_hashes_file.writelines(
[
"bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f pokemon/learnsets.json\n",
"bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f pokemon/moves.json\n",
]
)
with open(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "learnsets.json"), "w+") as learnsets_file:
learnsets_file.write("{}")
with open(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json"), "w+") as moves_files:
moves_files.write("{}")

cli.sync_project_and_rom(rom_path, TEMP_ASSET_PROJECT_PATH, None, ["WAZA_P"])

# If ROM hashes don't match and asset hashes do, extract ROM files to assets.
self.assertTrue(os.path.getsize(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json")) > 2)
finally:
delete_temp_asset_project()

def test_sync_project_and_rom_with_both_hashes_match(self):
try:
rom_path = copy_rom_to_temp_file()
Path(TEMP_ASSET_PROJECT_PATH, "pokemon").mkdir(parents=True, exist_ok=True)
with open(Path(TEMP_ASSET_PROJECT_PATH, ROM_HASHES_FILE), "w+") as rom_hashes_file:
rom_hashes_file.write("f85089b1c47c9392c93f76f5d1baf9b28677454c BALANCE/waza_p.bin\n")
with open(Path(TEMP_ASSET_PROJECT_PATH, ASSET_HASHES_FILE), "w+") as asset_hashes_file:
asset_hashes_file.writelines(
[
"bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f pokemon/learnsets.json\n",
"bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f pokemon/moves.json\n",
]
)
with open(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "learnsets.json"), "w+") as learnsets_file:
learnsets_file.write("{}")
with open(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json"), "w+") as moves_files:
moves_files.write("{}")

cli.sync_project_and_rom(rom_path, TEMP_ASSET_PROJECT_PATH, None, ["WAZA_P"])

# If asset and ROM hashes match, nothing should happen.
self.assertEqual(2, os.path.getsize(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json")))
finally:
delete_temp_asset_project()

def test_sync_project_and_rom_with_asset_hashes_mismatch(self):
try:
rom_path = copy_rom_to_temp_file()
cli.extract_rom_files_to_project(rom_path, TEMP_ASSET_PROJECT_PATH, ["WAZA_P"])

Path(TEMP_ASSET_PROJECT_PATH, "pokemon").mkdir(parents=True, exist_ok=True)
with open(Path(TEMP_ASSET_PROJECT_PATH, ROM_HASHES_FILE), "w+") as rom_hashes_file:
rom_hashes_file.write("f85089b1c47c9392c93f76f5d1baf9b28677454c BALANCE/waza_p.bin\n")
with open(Path(TEMP_ASSET_PROJECT_PATH, ASSET_HASHES_FILE), "w+") as asset_hashes_file:
asset_hashes_file.writelines(
[
"0000000000000000000000000000000000000000 pokemon/learnsets.json\n",
"0000000000000000000000000000000000000000 pokemon/moves.json\n",
]
)
with open(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "learnsets.json"), "w+") as learnsets_file:
learnsets_file.write("{}")
with open(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json"), "w+") as moves_files:
moves_files.write("{}")

cli.sync_project_and_rom(rom_path, TEMP_ASSET_PROJECT_PATH, None, ["WAZA_P"])

# If ROM hashes match and asset hashes don't, save assets to ROM.
with open(Path(TEMP_ASSET_PROJECT_PATH, ROM_HASHES_FILE), "r") as rom_hashes_file:
self.assertTrue("a7ed113f8b4f542ffc927a2fd5e4509327bef7d8 BALANCE/waza_p.bin" in rom_hashes_file.read())
finally:
delete_temp_asset_project()

def test_sync_project_and_rom_with_both_hashes_mismatch(self):
try:
rom_path = copy_rom_to_temp_file()
cli.extract_rom_files_to_project(rom_path, TEMP_ASSET_PROJECT_PATH, ["WAZA_P"])

Path(TEMP_ASSET_PROJECT_PATH, "pokemon").mkdir(parents=True, exist_ok=True)
with open(Path(TEMP_ASSET_PROJECT_PATH, ROM_HASHES_FILE), "w+") as rom_hashes_file:
rom_hashes_file.write("0000000000000000000000000000000000000000 BALANCE/waza_p.bin\n")
with open(Path(TEMP_ASSET_PROJECT_PATH, ASSET_HASHES_FILE), "w+") as asset_hashes_file:
asset_hashes_file.writelines(
[
"0000000000000000000000000000000000000000 pokemon/learnsets.json\n",
"0000000000000000000000000000000000000000 pokemon/moves.json\n",
]
)
with open(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "learnsets.json"), "w+") as learnsets_file:
learnsets_file.write("{}")
with open(Path(TEMP_ASSET_PROJECT_PATH, "pokemon", "moves.json"), "w+") as moves_files:
moves_files.write("{}")

# If both hashes mismatch, the user is prompted to select which side to use.
# pytest can't input to stdin and throws an OSError, but that still indicates the user was prompted.
self.assertRaises(
OSError, cli.sync_project_and_rom, rom_path, TEMP_ASSET_PROJECT_PATH, None, ["WAZA_P"]
)
finally:
delete_temp_asset_project()
20 changes: 1 addition & 19 deletions test/skytemple_files_test/common/file_api_v2_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import shutil
from pathlib import Path
from unittest import TestCase

Expand All @@ -13,24 +12,7 @@
)
from skytemple_files.common.types.file_types import FileType
from skytemple_files_test.case import load_rom_path

ASSET_PROJECT_PATH = Path("skytemple_files_test", "common", "fixtures", "asset_project")
ROM_COPY_PATH = Path("skytemple_files_test", "common", "fixtures", "rom_copy.nds")


def copy_rom_to_temp_file() -> Path:
"""
Copies the provided ROM to a temporary file for testing.
This allows testing writes to the ROM without changing the ROM supplied by the user.
"""
rom_path = load_rom_path()
shutil.copy(rom_path, ROM_COPY_PATH)
return ROM_COPY_PATH


def delete_temp_rom():
if os.path.exists(ROM_COPY_PATH):
os.remove(ROM_COPY_PATH)
from skytemple_files_test.common.temp_rom import ASSET_PROJECT_PATH, copy_rom_to_temp_file, delete_temp_rom


def revert_hash_files():
Expand Down
29 changes: 29 additions & 0 deletions test/skytemple_files_test/common/temp_rom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os
import shutil
from pathlib import Path

from skytemple_files_test.case import load_rom_path

ASSET_PROJECT_PATH = Path("skytemple_files_test", "common", "fixtures", "asset_project")
TEMP_ASSET_PROJECT_PATH = Path("skytemple_files_test", "common", "fixtures", "temp_asset_project")
ROM_COPY_PATH = Path("skytemple_files_test", "common", "fixtures", "rom_copy.nds")


def copy_rom_to_temp_file() -> Path:
"""
Copies the provided ROM to a temporary file for testing.
This allows testing writes to the ROM without changing the ROM supplied by the user.
"""
rom_path = load_rom_path()
shutil.copy(rom_path, ROM_COPY_PATH)
return ROM_COPY_PATH


def delete_temp_rom():
if os.path.exists(ROM_COPY_PATH):
os.remove(ROM_COPY_PATH)


def delete_temp_asset_project():
if os.path.exists(TEMP_ASSET_PROJECT_PATH):
shutil.rmtree(TEMP_ASSET_PROJECT_PATH)

0 comments on commit d5e7091

Please sign in to comment.