Skip to content

Commit 4d88af2

Browse files
committed
test: refactor test suite to improve coverage and have consistent script names
1 parent 7c0a412 commit 4d88af2

19 files changed

Lines changed: 1741 additions & 1071 deletions

test/scripts/alignment_test.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,55 @@ class s_t(struct):
308308
s = s_t.from_bytes(memory)
309309
self.assertEqual(s.a.value, 0x41)
310310
self.assertEqual(s.flags.value, 5)
311+
312+
313+
class AlignedStructTailPaddingInstanceTest(unittest.TestCase):
314+
"""Instance size must include tail padding for aligned structs."""
315+
316+
def test_instance_size_matches_class_size(self):
317+
"""size_of(instance) should equal size_of(class) for aligned structs."""
318+
class aligned_t(struct):
319+
_aligned_ = True
320+
a: c_int
321+
b: c_char
322+
323+
self.assertEqual(size_of(aligned_t), 8)
324+
325+
memory = pystruct.pack("<ib", 42, 0x41) + b"\x00" * 3
326+
s = aligned_t.from_bytes(memory)
327+
328+
self.assertEqual(size_of(s), 8)
329+
330+
def test_to_bytes_includes_tail_padding(self):
331+
"""to_bytes() should return 8 bytes (including tail padding), not 5."""
332+
class aligned_t(struct):
333+
_aligned_ = True
334+
a: c_int
335+
b: c_char
336+
337+
memory = pystruct.pack("<ib", 42, 0x41) + b"\x00" * 3
338+
s = aligned_t.from_bytes(memory)
339+
340+
self.assertEqual(len(s.to_bytes()), 8)
341+
342+
def test_nested_aligned_struct_tail_padding(self):
343+
"""Nested aligned structs should also have correct tail padding."""
344+
class inner_t(struct):
345+
_aligned_ = True
346+
x: c_int
347+
y: c_char
348+
349+
class outer_t(struct):
350+
_aligned_ = True
351+
a: inner_t
352+
b: c_int
353+
354+
self.assertEqual(size_of(inner_t), 8)
355+
356+
memory = b"\x00" * 16
357+
s = outer_t.from_bytes(memory)
358+
self.assertEqual(size_of(s), size_of(outer_t))
359+
360+
361+
if __name__ == "__main__":
362+
unittest.main()
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#
2+
# This file is part of libdestruct (https://github.com/mrindeciso/libdestruct).
3+
# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved.
4+
# Licensed under the MIT license. See LICENSE file in the project root for details.
5+
#
6+
7+
import unittest
8+
9+
from libdebug import debugger
10+
from libdestruct import array, array_of, inflater, c_int, c_long, struct
11+
12+
class ArrayTest(unittest.TestCase):
13+
def test_linear_arrays_1(self):
14+
d = debugger("binaries/array_test")
15+
16+
d.run()
17+
18+
bp = d.bp("do_nothing")
19+
20+
d.cont()
21+
22+
libdestruct = inflater(d.memory)
23+
24+
self.assertTrue(bp.hit_on(d))
25+
26+
test1 = libdestruct.inflate(array_of(c_int, 10), d.regs.rdi)
27+
28+
self.assertEqual(len(test1), 10)
29+
30+
for i in range(10):
31+
self.assertEqual(test1[i].value, i ** 2)
32+
33+
self.assertEqual(bytes(test1), b"".join((i ** 2).to_bytes(4, "little") for i in range(10)))
34+
35+
self.assertIn(test1[0], test1)
36+
37+
d.cont()
38+
39+
class test2_t(struct):
40+
a: c_int
41+
42+
test2 = libdestruct.inflate(array_of(test2_t, 10), d.regs.rdi)
43+
44+
for i in range(10):
45+
self.assertEqual(test2[i].a.value, i ** 3)
46+
47+
d.cont()
48+
49+
class test3_t(struct):
50+
a: c_int
51+
b: c_long
52+
53+
test3 = libdestruct.inflate(array_of(test3_t, 10), d.regs.rdi)
54+
55+
for i in range(10):
56+
self.assertEqual(test3[i].a.value, i * 100)
57+
self.assertEqual(test3[i].b.value, i * 1000)
58+
59+
d.cont()
60+
61+
class test4_t(struct):
62+
a: c_int
63+
b: array = array_of(c_int, 10)
64+
65+
test4 = libdestruct.inflate(array_of(test4_t, 10), d.regs.rdi)
66+
67+
for i in range(10):
68+
self.assertEqual(test4[i].a.value, i ** 4)
69+
for j in range(10):
70+
self.assertEqual(test4[i].b[j].value, (i + 1) * j)
71+
72+
with self.assertRaises(NotImplementedError):
73+
test4[i] = 4
74+
75+
with self.assertRaises(NotImplementedError):
76+
test4._set([1, 2, 3])
77+
78+
with self.assertRaises(NotImplementedError):
79+
test4.set([1, 2, 3])
80+
81+
d.kill()
82+
d.terminate()

test/scripts/array_test.py

Lines changed: 156 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,190 @@
11
#
22
# This file is part of libdestruct (https://github.com/mrindeciso/libdestruct).
3-
# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved.
3+
# Copyright (c) 2026 Roberto Alessandro Bertolini. All rights reserved.
44
# Licensed under the MIT license. See LICENSE file in the project root for details.
55
#
66

77
import unittest
8+
from enum import IntEnum
89

9-
from libdebug import debugger
10-
from libdestruct import array, array_of, inflater, c_int, c_long, struct
10+
from libdestruct import array, c_int, inflater, struct, array_of, enum, enum_of, size_of
1111

12-
class ArrayTest(unittest.TestCase):
13-
def test_linear_arrays_1(self):
14-
d = debugger("binaries/array_test")
1512

16-
d.run()
13+
class ArrayUnitTest(unittest.TestCase):
14+
"""Array operations without debugger."""
1715

18-
bp = d.bp("do_nothing")
16+
def test_array_value_property(self):
17+
""".value calls self.get() without args - should not raise TypeError."""
18+
memory = b"".join((i).to_bytes(4, "little") for i in range(5))
19+
lib = inflater(memory)
20+
arr = lib.inflate(array_of(c_int, 5), 0)
1921

20-
d.cont()
22+
val = arr.value
23+
self.assertIsNotNone(val)
24+
25+
def test_array_repr(self):
26+
memory = b"".join((i).to_bytes(4, "little") for i in range(3))
27+
lib = inflater(memory)
28+
arr = lib.inflate(array_of(c_int, 3), 0)
2129

22-
libdestruct = inflater(d.memory)
30+
r = repr(arr)
31+
self.assertIsInstance(r, str)
2332

24-
self.assertTrue(bp.hit_on(d))
33+
def test_array_indexing(self):
34+
memory = b"".join((i).to_bytes(4, "little") for i in range(5))
35+
lib = inflater(memory)
36+
arr = lib.inflate(array_of(c_int, 5), 0)
2537

26-
test1 = libdestruct.inflate(array_of(c_int, 10), d.regs.rdi)
38+
for i in range(5):
39+
self.assertEqual(arr[i].value, i)
40+
41+
def test_array_iteration(self):
42+
memory = b"".join((i * 10).to_bytes(4, "little") for i in range(3))
43+
lib = inflater(memory)
44+
arr = lib.inflate(array_of(c_int, 3), 0)
2745

28-
self.assertEqual(len(test1), 10)
46+
values = [x.value for x in arr]
47+
self.assertEqual(values, [0, 10, 20])
2948

30-
for i in range(10):
31-
self.assertEqual(test1[i].value, i ** 2)
49+
def test_array_len(self):
50+
memory = b"".join((0).to_bytes(4, "little") for _ in range(7))
51+
lib = inflater(memory)
52+
arr = lib.inflate(array_of(c_int, 7), 0)
3253

33-
self.assertEqual(bytes(test1), b"".join((i ** 2).to_bytes(4, "little") for i in range(10)))
54+
self.assertEqual(len(arr), 7)
3455

35-
self.assertIn(test1[0], test1)
56+
def test_array_contains(self):
57+
memory = b"".join((i).to_bytes(4, "little") for i in range(5))
58+
lib = inflater(memory)
59+
arr = lib.inflate(array_of(c_int, 5), 0)
3660

37-
d.cont()
61+
elem = arr[2]
62+
self.assertIn(elem, arr)
3863

39-
class test2_t(struct):
40-
a: c_int
64+
def test_array_to_bytes(self):
65+
memory = b"".join((i).to_bytes(4, "little") for i in range(3))
66+
lib = inflater(memory)
67+
arr = lib.inflate(array_of(c_int, 3), 0)
4168

42-
test2 = libdestruct.inflate(array_of(test2_t, 10), d.regs.rdi)
69+
result = arr.to_bytes()
70+
self.assertIsInstance(result, bytes)
71+
self.assertEqual(result, memory)
4372

44-
for i in range(10):
45-
self.assertEqual(test2[i].a.value, i ** 3)
73+
def test_array_to_str(self):
74+
memory = b"".join((i).to_bytes(4, "little") for i in range(3))
75+
lib = inflater(memory)
76+
arr = lib.inflate(array_of(c_int, 3), 0)
4677

47-
d.cont()
78+
result = arr.to_str()
79+
self.assertIn("0", result)
80+
self.assertIn("1", result)
81+
self.assertIn("2", result)
4882

49-
class test3_t(struct):
50-
a: c_int
51-
b: c_long
83+
def test_array_value_returns_all_elements(self):
84+
memory = b"".join((i).to_bytes(4, "little") for i in range(4))
85+
lib = inflater(memory)
86+
arr = lib.inflate(array_of(c_int, 4), 0)
5287

53-
test3 = libdestruct.inflate(array_of(test3_t, 10), d.regs.rdi)
88+
val = arr.value
89+
self.assertIsInstance(val, list)
90+
self.assertEqual(len(val), 4)
91+
self.assertEqual([x.value for x in val], [0, 1, 2, 3])
5492

55-
for i in range(10):
56-
self.assertEqual(test3[i].a.value, i * 100)
57-
self.assertEqual(test3[i].b.value, i * 1000)
93+
def test_bytes_on_bytearray_backed_array(self):
94+
memory = bytearray(b"".join((i).to_bytes(4, "little") for i in range(3)))
95+
lib = inflater(memory)
96+
arr = lib.inflate(array_of(c_int, 3), 0)
5897

59-
d.cont()
98+
result = bytes(arr)
99+
self.assertIsInstance(result, bytes)
100+
self.assertEqual(len(result), 12)
60101

61-
class test4_t(struct):
62-
a: c_int
63-
b: array = array_of(c_int, 10)
64102

65-
test4 = libdestruct.inflate(array_of(test4_t, 10), d.regs.rdi)
103+
class NegativeArrayCountTest(unittest.TestCase):
104+
"""array[T, N] must reject non-positive counts at handler time."""
66105

67-
for i in range(10):
68-
self.assertEqual(test4[i].a.value, i ** 4)
69-
for j in range(10):
70-
self.assertEqual(test4[i].b[j].value, (i + 1) * j)
106+
def test_negative_count_raises(self):
107+
"""array[c_int, -5] must raise ValueError."""
108+
with self.assertRaises(ValueError):
109+
class s_t(struct):
110+
data: array[c_int, -5]
111+
size_of(s_t)
71112

72-
with self.assertRaises(NotImplementedError):
73-
test4[i] = 4
113+
def test_zero_count_raises(self):
114+
"""array[c_int, 0] must raise ValueError."""
115+
with self.assertRaises(ValueError):
116+
class s_t(struct):
117+
data: array[c_int, 0]
118+
size_of(s_t)
74119

75-
with self.assertRaises(NotImplementedError):
76-
test4._set([1, 2, 3])
120+
def test_positive_count_works(self):
121+
"""array[c_int, 3] must work fine."""
122+
class s_t(struct):
123+
data: array[c_int, 3]
124+
self.assertEqual(size_of(s_t), 12)
77125

78-
with self.assertRaises(NotImplementedError):
79-
test4.set([1, 2, 3])
80126

81-
d.kill()
82-
d.terminate()
127+
class ArrayFreezeElementsTest(unittest.TestCase):
128+
"""Freezing a struct with an array must freeze the array elements too."""
129+
130+
def test_array_element_access_returns_frozen_value(self):
131+
"""s.data[0].value should return the frozen value, not live memory."""
132+
class arr_struct_t(struct):
133+
count: c_int
134+
data: array_of(c_int, 3)
135+
136+
memory = bytearray(16)
137+
memory[0:4] = (3).to_bytes(4, "little")
138+
memory[4:8] = (10).to_bytes(4, "little")
139+
memory[8:12] = (20).to_bytes(4, "little")
140+
memory[12:16] = (30).to_bytes(4, "little")
141+
142+
lib = inflater(memory)
143+
s = lib.inflate(arr_struct_t, 0)
144+
s.freeze()
145+
146+
memory[4:8] = (99).to_bytes(4, "little")
147+
148+
self.assertEqual(s.count.value, 3)
149+
self.assertEqual(s.data[0].value, 10)
150+
151+
def test_array_value_property_returns_frozen_elements(self):
152+
"""s.data.value should contain frozen element values after memory mutation."""
153+
class arr_struct_t(struct):
154+
data: array_of(c_int, 2)
155+
156+
memory = bytearray(8)
157+
memory[0:4] = (10).to_bytes(4, "little")
158+
memory[4:8] = (20).to_bytes(4, "little")
159+
160+
lib = inflater(memory)
161+
s = lib.inflate(arr_struct_t, 0)
162+
s.freeze()
163+
164+
memory[0:4] = (99).to_bytes(4, "little")
165+
166+
frozen_vals = [elem.value for elem in s.data.value]
167+
self.assertEqual(frozen_vals, [10, 20])
168+
169+
def test_array_to_bytes_returns_frozen_bytes(self):
170+
"""s.data.to_bytes() should return frozen bytes, not live memory."""
171+
class arr_struct_t(struct):
172+
data: array_of(c_int, 2)
173+
174+
memory = bytearray(8)
175+
memory[0:4] = (10).to_bytes(4, "little")
176+
memory[4:8] = (20).to_bytes(4, "little")
177+
178+
lib = inflater(memory)
179+
s = lib.inflate(arr_struct_t, 0)
180+
s.freeze()
181+
182+
expected_bytes = bytes(memory)
183+
184+
memory[0:4] = (99).to_bytes(4, "little")
185+
186+
self.assertEqual(s.data.to_bytes(), expected_bytes)
187+
188+
189+
if __name__ == "__main__":
190+
unittest.main()

0 commit comments

Comments
 (0)