Add unit tests for BinaryDiff, fix a few edge cases caught by those tests.
This commit is contained in:
parent
0aa4d6403d
commit
b837742d6b
|
@ -75,6 +75,12 @@ The tools are also lint clean (save for line length lints which are useless driv
|
||||||
flake8 --ignore E501 .
|
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
|
## Including This Package
|
||||||
|
|
||||||
By design, this code can be used as a library by other python code, and as it is Public Domain,
|
By design, this code can be used as a library by other python code, and as it is Public Domain,
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
from .binary import BinaryDiffException, BinaryDiff, ByteUtil
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"BinaryDiffException",
|
||||||
|
"BinaryDiff",
|
||||||
|
"ByteUtil",
|
||||||
|
]
|
|
@ -30,8 +30,9 @@ class BinaryDiff:
|
||||||
# Chunk the differences, assuming files are usually about the same,
|
# Chunk the differences, assuming files are usually about the same,
|
||||||
# for a massive speed boost.
|
# for a massive speed boost.
|
||||||
for offset in range(0, binlength, BinaryDiff.CHUNK_SIZE):
|
for offset in range(0, binlength, BinaryDiff.CHUNK_SIZE):
|
||||||
if bin1[offset:(offset + BinaryDiff.CHUNK_SIZE)] != bin2[offset:(offset + BinaryDiff.CHUNK_SIZE)]:
|
length = min(BinaryDiff.CHUNK_SIZE, binlength - offset)
|
||||||
for i in range(BinaryDiff.CHUNK_SIZE):
|
if bin1[offset:(offset + length)] != bin2[offset:(offset + length)]:
|
||||||
|
for i in range(length):
|
||||||
byte1 = bin1[offset + i]
|
byte1 = bin1[offset + i]
|
||||||
byte2 = bin2[offset + i]
|
byte2 = bin2[offset + i]
|
||||||
|
|
||||||
|
@ -91,7 +92,10 @@ class BinaryDiff:
|
||||||
# This is a comment, ignore it, unless its a file-size comment
|
# This is a comment, ignore it, unless its a file-size comment
|
||||||
patch = patch[1:].strip().lower()
|
patch = patch[1:].strip().lower()
|
||||||
if patch.startswith('file size:'):
|
if patch.startswith('file size:'):
|
||||||
return int(patch[10:].strip())
|
try:
|
||||||
|
return int(patch[10:].strip())
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -225,7 +229,11 @@ class BinaryDiff:
|
||||||
f"Patch is for binary of size {file_size} but binary is {len(binary)} "
|
f"Patch is for binary of size {file_size} but binary is {len(binary)} "
|
||||||
f"bytes long!"
|
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
|
# Now, verify the changes to the binary data
|
||||||
for diff in differences:
|
for diff in differences:
|
||||||
|
|
|
@ -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!')
|
Loading…
Reference in New Issue