Skip to content

Commit

Permalink
(extensions.diff) PakFileDiff testing & fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
snake-biscuits committed Jul 16, 2023
1 parent a0c752f commit 700d2bb
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 27 deletions.
46 changes: 36 additions & 10 deletions bsp_tool/extensions/diff/shared.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import difflib
from typing import Dict, Generator, List
import io
import os
from typing import Dict, Generator, List, Tuple

from . import base
from . import core
Expand Down Expand Up @@ -64,31 +66,55 @@ def long_repr(entity: Dict[str, str]) -> List[str]:
return ["{", *out, "}"]


class PakFileDiff:
class PakFileDiff(base.Diff):
"""Works on any ValveBsp based .bsp (except CS:O2)"""

def shortstat(self) -> str:
def short_stats(self) -> str:
"""quick & dirty namelist check"""
old = set(self.old.namelist())
new = set(self.new.namelist())
return f"{new.difference(old)} insertions(+) {old.difference(new)} deletions(-)"
added = len(new.difference(old))
removed = len(old.difference(new))
return f"{added} insertions(+) {removed} deletions(-)"

def unified_diff(self) -> Generator[str, None, None]:
old_filelist = [f"{fn}\n" for fn in self.old.namelist()]
new_filelist = [f"{fn}\n" for fn in self.new.namelist()]
meta_diff = difflib.ndiff(old_filelist, new_filelist)
for meta_line in meta_diff:
if meta_line.startswith(" "): # maybe a match
filename = meta_line.lstrip()
filename = meta_line.lstrip(" ").rstrip("\n")
old_file = self.old.read(filename)
new_file = self.new.read(filename)
if old_file == new_file:
yield meta_line
else:
yield f"# BEGIN {filename}"
# NOTE: we could grab date_time from ZipInfo & format it, but lazy
for line in difflib.unified_diff(core.xxd(old_file), core.xxd(new_file), [filename] * 2):
yield line
yield f"# END {filename}"
for line in self.diff_file(filename):
yield f" {line}"
else:
yield meta_line

def diff_file(self, filename: str) -> Generator[str, None, None]:
old_file = self.old.read(filename)
new_file = self.new.read(filename)
_, ext = os.path.splitext(filename)
# TODO: check MegaTest .bsps for other common plaintext file formats
if ext in (".log", ".vmt", ".txt"):
try:
old = io.StringIO(old_file.decode()).readlines()
new = io.StringIO(new_file.decode()).readlines()
except UnicodeDecodeError: # not pure utf-8
old = list(core.xxd(old_file))
new = list(core.xxd(new_file))
else: # binary diff
old = list(core.xxd(old_file))
new = list(core.xxd(new_file))
old_time = self.formatted_date_time(self.old.getinfo(filename).date_time)
new_time = self.formatted_date_time(self.new.getinfo(filename).date_time)
for line in difflib.unified_diff(old, new, filename, filename, old_time, new_time):
yield line

@staticmethod
def formatted_date_time(zipinfo_date_time: Tuple[int]) -> str:
year, month, day, hour, minute, second = zipinfo_date_time
return f"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
64 changes: 47 additions & 17 deletions tests/extensions/diff/test_shared.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,60 @@
import itertools
import zipfile

from bsp_tool.extensions import diff


old = [dict(classname="worldspawn"),
dict(classname="light", origin="0 0 0")]
new = [dict(classname="worldspawn"),
dict(classname="info_player_start", origin="0 0 0"),
dict(classname="light", origin="0 0 64")]
sample = diff.shared.EntitiesDiff(old, new)
from bsp_tool.branches.shared import PakFile


class TestEntitiesDiff:
def setup_method(self, method):
old = [dict(classname="worldspawn"),
dict(classname="light", origin="0 0 0")]
new = [dict(classname="worldspawn"),
dict(classname="info_player_start", origin="0 0 0"),
dict(classname="light", origin="0 0 64")]
self.sample = diff.shared.EntitiesDiff(old, new)

def test_short_stats(self):
stats = sample.short_stats()
stats = self.sample.short_stats()
assert stats == "2 insertions(+) 1 deletions(-)"

def test_unified_diff(self):
# TODO: test a multiline diff
expected = ["- <light @ 0 0 0>\n",
"+ <info_player_start @ 0 0 0>\n",
"+ <light @ 0 0 64>\n"]
for line, expected_line in itertools.zip_longest(sample.unified_diff(), expected):
assert line == expected_line
# TODO: test a multiline repr diff
expected_lines = ["- <light @ 0 0 0>\n",
"+ <info_player_start @ 0 0 0>\n",
"+ <light @ 0 0 64>\n"]
diff_lines = self.sample.unified_diff()
for diff_line, expected_line in itertools.zip_longest(diff_lines, expected_lines):
assert diff_line == expected_line


class TestPakfileDiff:
# TODO: generate dummy pakfiles for comparison
...
def setup_method(self, method):
old = PakFile()
old.writestr("same.txt", "same hat!\n")
old.writestr(".secret", "!!! DO NOT SHIP !!!\n")
old.writestr("mispelt", "oops, typo\n")
compile_log = zipfile.ZipInfo(filename="compile.log", date_time=(1980, 1, 1, 0, 0, 0))
old.writestr(compile_log, "REVISION: 01\n")
new = PakFile()
new.writestr("same.txt", "same hat!\n")
new.writestr("mispelled", "oops, typo\n")
compile_log = zipfile.ZipInfo(filename="compile.log", date_time=(1980, 1, 2, 0, 0, 0))
new.writestr(compile_log, "REVISION: 02\n")
self.sample = diff.shared.PakFileDiff(old, new)

def test_short_stats(self):
stats = self.sample.short_stats()
assert stats == "1 insertions(+) 2 deletions(-)"

def test_unified_diff(self):
expected_lines = [" same.txt\n",
"- .secret\n",
"- mispelt\n",
"? ^\n",
"+ mispelled\n",
"? ^^^\n",
*[f" {line}" for line in self.sample.diff_file("compile.log")]]
diff_lines = self.sample.unified_diff()
for diff_line, expected_line in itertools.zip_longest(diff_lines, expected_lines):
assert diff_line == expected_line

0 comments on commit 700d2bb

Please sign in to comment.