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 .
|
||||
```
|
||||
|
||||
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,
|
||||
|
|
|
@ -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,
|
||||
# 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,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