Skip to content

Commit

Permalink
preparing to use extensions.diff in tests.save
Browse files Browse the repository at this point in the history
  • Loading branch information
snake-biscuits committed Jul 20, 2023
1 parent eda1917 commit 2c2a26b
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 45 deletions.
53 changes: 42 additions & 11 deletions bsp_tool/extensions/diff/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""determining the equivialency of bsps & reporting findings in detail"""
import difflib
from typing import Any, Dict, Generator, List

from . import base
from . import shared
from .valve import source
# TODO: lightmap diffs

from bsp_tool import branches
from bsp_tool.base import Bsp
Expand Down Expand Up @@ -44,6 +46,7 @@ def diff_lumps(old_lump: Any, new_lump: Any) -> base.Diff:

class BspDiff:
"""deferred diffs of lumps & headers etc."""
# NOTE: not a base.Diff subclass
old: Bsp
new: Bsp

Expand All @@ -53,10 +56,10 @@ def __init__(self, old: Bsp, new: Bsp):
self.old = old
self.new = new
self.headers = HeadersDiff(old.headers, new.headers)
# NOTE: a change in header offsets does not imply a change in lump data
# TODO: other metadata (file magic, version, revision, signature etc.)

def __getattr__(self, lump_name: str) -> Any:
"""retrieve differ for given lump"""
old_lump = getattr(self.old, lump_name, None)
new_lump = getattr(self.new, lump_name, None)
no_old_lump = old_lump is None
Expand All @@ -70,17 +73,39 @@ def __getattr__(self, lump_name: str) -> Any:
setattr(self, lump_name, diff) # cache
return diff

def has_no_changes(self) -> bool:
try:
assert self.headers.has_no_changes()
# TODO: other metadata
for lump in self.old.headers:
old_header = self.old.headers[lump]
new_header = self.new.headers[lump]
if old_header.length != 0 or new_header.length != 0:
assert getattr(self, lump).has_no_changes()
except AssertionError:
return False
return True

def what_changed(self) -> List[str]:
check = {"headers": self.headers.has_no_changes()}
# TODO: other metadata
for lump in self.old.headers:
old_header = self.old.headers[lump]
new_header = self.new.headers[lump]
if old_header.length != 0 or new_header.length != 0:
check[lump] = getattr(self, lump).has_no_changes()
return {attr for attr, unchanged in check.items() if not unchanged}

def save(self, base_filename: str, log_mode: base.LogMode = base.LogMode.VERBOSE):
"""generate & save .diff files"""
# for each lump (match by name)
# filename.lump.00.ENTITIES.diff: old_goldsrc.ENTITIES (0) -> new_blue_shift.ENTITIES (1)
# filename.lump.01.PLANES.diff: old_goldsrc.PLANES (1) -> new_blue_shift.PLANES (0)
# RespawnBsp
# -- filename.ENTITITES.fx.diff: filename_fx.ent
# -- filename.lump.00XX.LUMP_NAME.diff
# -- filename.lump.00XX.LUMP_NAME.bsp_lump.diff
# filename.bsp.diff: headers & Y/N lump matches
raise NotImplementedError()
# self.lump.unified_diff() -> individual files
# -- .bsp -> filename.00.ENTITIES.diff
# RespawnBsp format:
# -- .bsp -> filename.00XX.LUMP_NAME.diff
# -- .bsp_lump -> filename.external.00XX.LUMP_NAME.diff
# -- .ent -> filename.external.ent.xxxxx.diff
# should also generate a general / meta diff for metadata etc.


class NoneDiff(base.Diff):
Expand Down Expand Up @@ -118,13 +143,19 @@ def __getitem__(self, lump_name: str) -> str:
if diff is None:
old = f"{lump_name} {self.old[lump_name]!r}\n"
new = f"{lump_name} {self.new[lump_name]!r}\n"
diff = list(difflib.unified_diff([old], [new]))
diff = list(difflib.unified_diff([old], [new]))[3:]
self._cache[lump_name] = diff
return diff

# TODO: check headers in order
# -- sorted({(h.offset, h.length, i) for i, h in enumerate(self.old.headers.values()) if h.length > 0})
# -- find knock-on changes
# -- trivial differences (e.g. offset=0, length=0)

def short_stats(self) -> str:
raise NotImplementedError()
# TODO: how to summarise?
# change in any attr but "offset"
raise NotImplementedError()

def unified_diff(self) -> Generator[str, None, None]:
for lump_name in self.old:
Expand Down
2 changes: 1 addition & 1 deletion bsp_tool/extensions/diff/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def short_repr(entity: Dict[str, str]) -> str:
def long_repr(entity: Dict[str, str]) -> List[str]:
out = list()
for key, value in entity.items():
if not isinstance(value, list): # duplicate key (Source Input/Output)
if isinstance(value, list): # duplicate key (Source Input/Output)
out.extend([f'"{key}" "{v}"' for v in value])
else:
out.append(f'"{key}" "{value}"')
Expand Down
72 changes: 39 additions & 33 deletions tests/test_save.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,24 @@
from types import ModuleType
from typing import List

from bsp_tool import D3DBsp
# BspClasses
# from bsp_tool import D3DBsp
from bsp_tool import IdTechBsp
from bsp_tool import QuakeBsp
from bsp_tool import ReMakeQuakeBsp
from bsp_tool import RespawnBsp
from bsp_tool import ValveBsp
# branches
from bsp_tool.branches.id_software import quake
from bsp_tool.branches.id_software import quake2
from bsp_tool.branches.id_software import quake3
from bsp_tool.branches.id_software import remake_quake
from bsp_tool.branches.infinity_ward import modern_warfare
# from bsp_tool.branches.infinity_ward import modern_warfare
from bsp_tool.branches.respawn import titanfall2
from bsp_tool.branches.strata import strata
from bsp_tool.branches.valve import orange_box
# TODO: from bsp_tool.extensions import diff
# extensions
from bsp_tool.extensions import diff

import pytest

Expand Down Expand Up @@ -91,12 +94,9 @@ def save_and_diff_backup(BspClass: object, branch_script: ModuleType, map_path:
# get filename of pre-save backup
filename, ext = os.path.splitext(filename_ext) # ext includes "."
filename_bak_ext = f"{filename}.bak{ext}"
# TODO: use bsp_tool.extensions.diff
# -- this would also compare .bsp_lump & .ent
raise RuntimeError(f"didn't diff {os.path.basename(filename_bak_ext)}") # TOO SLOW!
# diff_lines = difflib.unified_diff(xxd(filename_bak_ext), xxd(filename_ext),
# os.path.basename(filename_bak_ext), os.path.basename(filename_ext))
# return "".join(diff_lines)
old_bsp = BspClass(branch_script, filename_bak_ext) # original file
new_bsp = BspClass(branch_script, filename_ext) # saved copy
return diff.BspDiff(old_bsp, new_bsp)


# tests
Expand All @@ -112,62 +112,68 @@ def save_and_diff_backup(BspClass: object, branch_script: ModuleType, map_path:
@pytest.mark.xfail(raises=NotImplementedError, reason="not implemented yet")
@map_dirs_to_test("Call of Duty 4", "Call of Duty 4/mp", ext="*.d3dbsp")
def test_D3DBsp_modern_warfare(map_path: str):
diff = save_and_diff_backup(D3DBsp, modern_warfare, map_path)
print("".join(diff))
assert len(diff) == 0, "not a perfect copy"
# TODO: diff.HeadersDiff isn't ready for modern_warfare
# bsp_diff = save_and_diff_backup(D3DBsp, modern_warfare, map_path)
assert False
...


@pytest.mark.xfail(raises=NotImplementedError, reason="not implemented yet")
@map_dirs_to_test("Quake 2")
def test_IdTechBsp_quake2(map_path: str):
diff = save_and_diff_backup(IdTechBsp, quake2, map_path)
print("".join(diff))
assert len(diff) == 0, "not a perfect copy"
# bsp_diff = save_and_diff_backup(IdTechBsp, quake2, map_path)
assert False
...


@pytest.mark.xfail(raises=NotImplementedError, reason="not implemented yet")
@map_dirs_to_test("Quake 3 Arena")
def test_IdTechBsp_quake3(map_path: str):
diff = save_and_diff_backup(IdTechBsp, quake3, map_path)
print("".join(diff))
assert len(diff) == 0, "not a perfect copy"
# bsp_diff = save_and_diff_backup(IdTechBsp, quake3, map_path)
assert False
...


@pytest.mark.xfail(raises=NotImplementedError, reason="not implemented yet")
@map_dirs_to_test("ReMakeQuake")
def test_ReMakeQuakeBsp_remake_quake(map_path: str):
diff = save_and_diff_backup(ReMakeQuakeBsp, remake_quake, map_path)
print("".join(diff))
assert len(diff) == 0, "not a perfect copy"
# bsp_diff = save_and_diff_backup(ReMakeQuakeBsp, remake_quake, map_path)
assert False
...


@pytest.mark.xfail
@map_dirs_to_test("Titanfall 2")
def test_RespawnBsp_titanfall2(map_path: str):
diff = save_and_diff_backup(RespawnBsp, titanfall2, map_path)
print("".join(diff))
assert len(diff) == 0, "not a perfect copy"
# bsp_diff = save_and_diff_backup(RespawnBsp, titanfall2, map_path)
assert False
...
# manually observed:
# -- bsp_diff.old.ENTITIES env_fog_controller: colour values separated w/ "\xA0"
# --- MRVN-Radiant/remap bug? mapsrc/*.map is clean
# --- might also be a "plaintext" issue, iirc \xA0 get wierd when decoded / translated
# -- bsp_diff.new.signature: +4 bytes of padding


@pytest.mark.xfail(raises=NotImplementedError, reason="not implemented yet")
@map_dirs_to_test("Quake")
def test_QuakeBsp_quake(map_path: str):
diff = save_and_diff_backup(QuakeBsp, quake, map_path)
print("".join(diff))
assert len(diff) == 0, "not a perfect copy"
# bsp_diff = save_and_diff_backup(QuakeBsp, quake, map_path)
assert False
...


@pytest.mark.xfail
@map_dirs_to_test("Team Fortress 2")
def test_ValveBsp_orange_box(map_path: str):
diff = save_and_diff_backup(ValveBsp, orange_box, map_path)
print("".join(diff))
assert len(diff) == 0, "not a perfect copy"
# bsp_diff = save_and_diff_backup(ValveBsp, orange_box, map_path)
assert False
...


@pytest.mark.xfail
@map_dirs_to_test("Momentum Mod")
def test_ValveBsp_strata(map_path: str):
diff = save_and_diff_backup(ValveBsp, strata, map_path)
print("".join(diff))
assert len(diff) == 0, "not a perfect copy"
# bsp_diff = save_and_diff_backup(ValveBsp, strata, map_path)
assert False
...

0 comments on commit 2c2a26b

Please sign in to comment.