Initial commit

This commit is contained in:
2026-10-09 02:48:24 +02:00
commit e05d973a8d
133 changed files with 101507 additions and 0 deletions

142
tests/test_workspace.py Normal file
View File

@@ -0,0 +1,142 @@
"""Apply / revert safety on a synthetic game: byte-exact reverts, size limits, conflict detection,
mod packages."""
import hashlib
import os
import pytest
from ackit import workspace as W
from ackit.inject import SizeLimitError, inject
from ackit.modpack import export_mod, import_mod, match_mod
import synth
def sha(p):
return hashlib.sha1(open(p, "rb").read()).hexdigest()
def files(root):
out = []
for d, _, fs in os.walk(os.path.join(root, "BmGame")):
out += [os.path.join(d, f) for f in fs]
return {f: sha(f) for f in sorted(out)}
def ws_for(root, **kw):
return W.Workspace(root, archives=["INT", "SFX"], **kw)
def test_apply_in_place_and_revert_byte_identical(synth_game):
root, originals = synth_game
before = files(root)
ws = ws_for(root)
s = ws.sound("CHR_RadioScanner_2")
x, sr = synth.tone(1.2, 24000, 900)
wem, info = ws.prepare(s, x, sr)
assert len(wem) <= ws.limit(s)
ws.apply([(s, wem, {"source": "test"})])
assert ws.current_bytes(s) == wem and ws.state(s) == W.MODIFIED
assert ws.original_bytes(s) == originals[s.target]
# neighbours untouched
for i in (0, 2):
t = "%08X:%d" % (synth.SPEECH_ENTRY, i)
assert ws.current_bytes(ws.catalog.by_key["INT:" + t]) == originals[t]
assert ws.revert([s]) == [s]
assert files(root) == before
assert ws.state(s) == W.ORIGINAL and not ws.store.entries
def test_pack_media_resize_and_revert_byte_identical(synth_game):
root, originals = synth_game
before = files(root)
ws = ws_for(root)
s = ws.catalog.by_key["SFX:%08X/%08X" % (synth.PACK_ID, synth.MEDIA[1])]
assert ws.limit(s) is None
x, sr = synth.tone(8.0, 48000, 200, 2)
wem, _ = ws.prepare(s, x, sr)
assert len(wem) > len(originals[s.target])
ws.apply([(s, wem, {})])
assert ws.current_bytes(s) == wem
ws.revert_all()
assert files(root) == before
def test_size_limit_enforced(synth_game):
root, _ = synth_game
ws = ws_for(root)
s = ws.sound("CHR_RadioScanner_3")
big = synth.wem(5.0, 440)
with pytest.raises(SizeLimitError):
ws.apply([(s, big, {})])
assert not ws.modified_keys()
def test_reapply_uses_original_limit(synth_game):
root, _ = synth_game
ws = ws_for(root)
s = ws.sound("CHR_RadioScanner_2")
lim = ws.limit(s)
ws.apply([(s, synth.wem(0.3, 1000), {})]) # much smaller
assert ws.limit(s) == lim # still the original's size
ws.apply([(s, synth.wem(1.0, 800), {})])
ws.revert([s])
assert not ws.store.entries
def test_conflict_when_changed_underneath(synth_game):
root, _ = synth_game
ws = ws_for(root)
s = ws.sound("CHR_RadioScanner_1")
ws.apply([(s, synth.wem(0.5, 1200), {})])
# simulate another tool / Steam writing something else into the same slot
other = synth.wem(0.4, 150)
inject(ws.wad_path(s), {s.target: other}, {s.target: ws.limit(s)})
assert ws.state(s) == W.CHANGED
with pytest.raises(W.ConflictError):
ws.apply([(s, synth.wem(0.5, 1300), {})])
ws.apply([(s, synth.wem(0.5, 1300), {})], force=True)
ws.revert([s])
assert ws.state(s) == W.ORIGINAL
def test_lost_state_after_external_restore(synth_game):
root, originals = synth_game
ws = ws_for(root)
s = ws.sound("CHR_RadioScanner_1")
ws.apply([(s, synth.wem(0.5, 1200), {})])
inject(ws.wad_path(s), {s.target: originals[s.target]}, {s.target: ws.limit(s)}) # like 'verify files'
assert ws.state(s) == W.LOST
def test_full_backup_taken_once(synth_game):
root, _ = synth_game
ws = ws_for(root)
s = ws.sound("CHR_RadioScanner_1")
ws.apply([(s, synth.wem(0.5, 1200), {})])
rel = ws.game.rel(ws.wad_path(s))
assert ws.store.full_copy(rel)
restored = ws.restore_from_full_backup()
assert rel in restored and ws.state(s) == W.ORIGINAL
def test_mod_export_import_roundtrip(synth_game, synth_template, tmp_path):
root, _ = synth_game
ws = ws_for(root)
a = ws.sound("CHR_RadioScanner_2")
b = ws.catalog.by_key["SFX:%08X/%08X" % (synth.PACK_ID, synth.MEDIA[0])]
wa, wb = synth.wem(1.0, 820), synth.wem(2.5, 410, 48000, 2)
ws.apply([(a, wa, {}), (b, wb, {})])
pkg = str(tmp_path / "test.acmod")
assert export_mod(ws, pkg, ws.modified_keys(), "t") == 2
ws.revert_all()
# import on a fresh copy of the game ("another machine")
import shutil
other = str(tmp_path / "other")
shutil.copytree(synth_template[0], other)
ws2 = ws_for(other, store_root=str(tmp_path / "store2"))
_, items = match_mod(ws2, pkg)
assert [i.how for i in items] == ["exact", "exact"]
res = import_mod(ws2, pkg)
assert all(i.note == "exact copy" for i in res)
assert ws2.current_bytes(ws2.catalog.by_key[a.key]) == wa
assert ws2.current_bytes(ws2.catalog.by_key[b.key]) == wb