Add unit tests for BinaryDiff, fix a few edge cases caught by those tests.

This commit is contained in:
Jennifer Taylor 2021-10-20 15:00:02 +00:00
parent 0aa4d6403d
commit b837742d6b
5 changed files with 433 additions and 4 deletions

View File

@ -75,6 +75,12 @@ The tools are also lint clean (save for line length lints which are useless driv
flake8 --ignore E501 .
```
The tools also have their own unit tests. To verify tests, run the following:
```
python3 -m unittest discover tests
```
## Including This Package
By design, this code can be used as a library by other python code, and as it is Public Domain,

View File

@ -0,0 +1,7 @@
from .binary import BinaryDiffException, BinaryDiff, ByteUtil
__all__ = [
"BinaryDiffException",
"BinaryDiff",
"ByteUtil",
]

View File

@ -30,8 +30,9 @@ class BinaryDiff:
# Chunk the differences, assuming files are usually about the same,
# for a massive speed boost.
for offset in range(0, binlength, BinaryDiff.CHUNK_SIZE):
if bin1[offset:(offset + BinaryDiff.CHUNK_SIZE)] != bin2[offset:(offset + BinaryDiff.CHUNK_SIZE)]:
for i in range(BinaryDiff.CHUNK_SIZE):
length = min(BinaryDiff.CHUNK_SIZE, binlength - offset)
if bin1[offset:(offset + length)] != bin2[offset:(offset + length)]:
for i in range(length):
byte1 = bin1[offset + i]
byte2 = bin2[offset + i]
@ -91,7 +92,10 @@ class BinaryDiff:
# This is a comment, ignore it, unless its a file-size comment
patch = patch[1:].strip().lower()
if patch.startswith('file size:'):
return int(patch[10:].strip())
try:
return int(patch[10:].strip())
except ValueError:
return None
return None
@staticmethod
@ -225,7 +229,11 @@ class BinaryDiff:
f"Patch is for binary of size {file_size} but binary is {len(binary)} "
f"bytes long!"
)
differences: List[Tuple[int, Optional[bytes], bytes]] = BinaryDiff._gather_differences(patchlines, reverse)
try:
differences: List[Tuple[int, Optional[bytes], bytes]] = BinaryDiff._gather_differences(patchlines, reverse)
except BinaryDiffException as e:
return (False, str(e))
# Now, verify the changes to the binary data
for diff in differences:

0
tests/__init__.py Normal file
View File

408
tests/test_BinaryDiff.py Normal file
View File

@ -0,0 +1,408 @@
from arcadeutils import BinaryDiff, BinaryDiffException
import unittest
class TestBinaryDiff(unittest.TestCase):
def test_diff_no_differences(self) -> None:
self.assertEqual(
BinaryDiff.diff(b"abcd", b"abcd"),
[],
)
self.assertEqual(
BinaryDiff.diff(b"", b""),
[],
)
def test_diff_different_sizes(self) -> None:
with self.assertRaises(BinaryDiffException):
BinaryDiff.diff(b"1234", b"123")
with self.assertRaises(BinaryDiffException):
BinaryDiff.diff(b"123", b"1234")
def test_diff_simple(self) -> None:
self.assertEqual(
BinaryDiff.diff(b"abcd1234", b"bbcd1234"),
[
'# File size: 8',
'00: 61 -> 62',
]
)
self.assertEqual(
BinaryDiff.diff(b"abcd1234", b"abcd1235"),
[
'# File size: 8',
'07: 34 -> 35',
]
)
self.assertEqual(
BinaryDiff.diff(b"abcd1234", b"abdc1224"),
[
'# File size: 8',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
]
)
self.assertEqual(
BinaryDiff.diff(b"abcd1234", b"4321bcda"),
[
'# File size: 8',
'00: 61 62 63 64 31 32 33 34 -> 34 33 32 31 62 63 64 61',
]
)
def test_size(self) -> None:
self.assertEqual(
BinaryDiff.size([]),
None,
)
self.assertEqual(
BinaryDiff.size(['# Comment']),
None,
)
self.assertEqual(
BinaryDiff.size(['00: 01 -> 02']),
None,
)
self.assertEqual(
BinaryDiff.size(['# File Size: 1024']),
1024,
)
self.assertEqual(
BinaryDiff.size(['# File Size: invalid']),
None,
)
def test_description(self) -> None:
self.assertEqual(
BinaryDiff.description([]),
None,
)
self.assertEqual(
BinaryDiff.description(['# Comment']),
None,
)
self.assertEqual(
BinaryDiff.description(['00: 01 -> 02']),
None,
)
self.assertEqual(
BinaryDiff.description(['# Description: sample text']),
"sample text",
)
def test_needed_amount(self) -> None:
self.assertEqual(
BinaryDiff.needed_amount([]),
0,
)
self.assertEqual(
BinaryDiff.needed_amount(
[
'# File size: 8',
'00: 61 -> 62',
]
),
1,
)
self.assertEqual(
BinaryDiff.needed_amount(
[
'# File size: 8',
'07: 34 -> 35',
]
),
8,
)
self.assertEqual(
BinaryDiff.needed_amount(
[
'# File size: 8',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
]
),
7,
)
self.assertEqual(
BinaryDiff.needed_amount(
[
'# File size: 8',
'00: 61 62 63 64 31 32 33 34 -> 34 33 32 31 62 63 64 61',
]
),
8,
)
def test_can_patch_normal(self) -> None:
self.assertEqual(
BinaryDiff.can_patch(
b"abcd1234",
[
'# File size: 8',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
),
(True, ''),
)
self.assertEqual(
BinaryDiff.can_patch(
b"abcd1234",
[
'# File size: 12',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
),
(False, 'Patch is for binary of size 12 but binary is 8 bytes long!'),
)
self.assertEqual(
BinaryDiff.can_patch(
b"abcd1234",
[
'# File size: 12',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
ignore_size_differences=True,
),
(True, '')
)
self.assertEqual(
BinaryDiff.can_patch(
b"abcd",
[
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
),
(False, 'Patch offset 06 is beyond the end of the binary!'),
)
self.assertEqual(
BinaryDiff.can_patch(
b"4321bcda",
[
'# File size: 8',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
),
(False, 'Patch offset 02 expecting 63 but found 32!'),
)
self.assertEqual(
BinaryDiff.can_patch(
b"abcd1234",
[
'# File size: 8',
'06: * -> 32',
],
),
(True, ''),
)
def test_can_patch_reverse(self) -> None:
self.assertEqual(
BinaryDiff.can_patch(
b"abdc1224",
[
'# File size: 8',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
reverse=True,
),
(True, ''),
)
self.assertEqual(
BinaryDiff.can_patch(
b"abdc1224",
[
'# File size: 12',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
reverse=True,
),
(False, 'Patch is for binary of size 12 but binary is 8 bytes long!'),
)
self.assertEqual(
BinaryDiff.can_patch(
b"abdc1224",
[
'# File size: 12',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
reverse=True,
ignore_size_differences=True,
),
(True, ''),
)
self.assertEqual(
BinaryDiff.can_patch(
b"abdc",
[
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
reverse=True,
),
(False, 'Patch offset 06 is beyond the end of the binary!'),
)
self.assertEqual(
BinaryDiff.can_patch(
b"4321bcda",
[
'# File size: 8',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
reverse=True,
),
(False, 'Patch offset 02 expecting 64 but found 32!'),
)
self.assertEqual(
BinaryDiff.can_patch(
b"abcd1234",
[
'# File size: 8',
'06: * -> 32',
],
reverse=True,
),
(False, 'Patch offset 06 specifies a wildcard and cannot be reversed!'),
)
def test_patch_normal(self) -> None:
self.assertEqual(
BinaryDiff.patch(
b"abcd1234",
[
'# File size: 8',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
),
b'abdc1224',
)
with self.assertRaises(BinaryDiffException) as context:
BinaryDiff.patch(
b"abcd1234",
[
'# File size: 12',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
)
self.assertEqual(str(context.exception), 'Patch is for binary of size 12 but binary is 8 bytes long!')
self.assertEqual(
BinaryDiff.patch(
b"abcd1234",
[
'# File size: 12',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
ignore_size_differences=True,
),
b'abdc1224',
)
with self.assertRaises(BinaryDiffException) as context:
BinaryDiff.patch(
b"abcd",
[
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
)
self.assertEqual(str(context.exception), 'Patch offset 06 is beyond the end of the binary!')
with self.assertRaises(BinaryDiffException) as context:
BinaryDiff.patch(
b"4321bcda",
[
'# File size: 8',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
)
self.assertEqual(str(context.exception), 'Patch offset 02 expecting 63 but found 32!')
self.assertEqual(
BinaryDiff.patch(
b"abcd1234",
[
'# File size: 8',
'06: * -> 32',
],
),
b'abcd1224',
)
def test_patch_reverse(self) -> None:
self.assertEqual(
BinaryDiff.patch(
b"abdc1224",
[
'# File size: 8',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
reverse=True,
),
b'abcd1234',
)
with self.assertRaises(BinaryDiffException) as context:
BinaryDiff.patch(
b"abdc1224",
[
'# File size: 12',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
reverse=True,
)
self.assertEqual(str(context.exception), 'Patch is for binary of size 12 but binary is 8 bytes long!')
self.assertEqual(
BinaryDiff.patch(
b"abdc1224",
[
'# File size: 12',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
reverse=True,
ignore_size_differences=True,
),
b'abcd1234',
)
with self.assertRaises(BinaryDiffException) as context:
BinaryDiff.patch(
b"abdc",
[
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
reverse=True,
)
self.assertEqual(str(context.exception), 'Patch offset 06 is beyond the end of the binary!')
with self.assertRaises(BinaryDiffException) as context:
BinaryDiff.patch(
b"4321bcda",
[
'# File size: 8',
'02: 63 64 -> 64 63',
'06: 33 -> 32',
],
reverse=True,
)
self.assertEqual(str(context.exception), 'Patch offset 02 expecting 64 but found 32!')
with self.assertRaises(BinaryDiffException) as context:
BinaryDiff.patch(
b"abcd1234",
[
'# File size: 8',
'06: * -> 32',
],
reverse=True,
)
self.assertEqual(str(context.exception), 'Patch offset 06 specifies a wildcard and cannot be reversed!')