1 | #!/usr/bin/env python3 |
||
2 | |||
3 | """Tests for the starstruct class""" |
||
4 | |||
5 | import enum |
||
6 | import unittest |
||
7 | import pytest |
||
8 | |||
9 | from starstruct.message import Message |
||
10 | from starstruct.modes import Mode |
||
11 | |||
12 | |||
13 | class SimpleEnum(enum.Enum): |
||
14 | """Simple enum class for testing message pack/unpack""" |
||
15 | one = 1 |
||
16 | two = 2 |
||
17 | three = 3 |
||
18 | |||
19 | |||
20 | # pylint: disable=line-too-long,invalid-name |
||
21 | class TestStarStruct(unittest.TestCase): |
||
22 | """StarStruct module tests""" |
||
23 | |||
24 | teststruct = [ |
||
25 | ('a', 'b'), # signed byte: -128, 127 |
||
26 | ('pad1', '3x'), # 3 pad bytes |
||
27 | ('b', 'H'), # unsigned short: 0, 65535 |
||
28 | ('pad2', 'x'), # 1 pad byte |
||
29 | ('c', '10s'), # 10 byte string |
||
30 | ('d', 'x'), # 1 pad byte |
||
31 | ('e', '2H'), # 4 unsigned bytes: 0, 2^32-1 |
||
32 | ('type', 'B', SimpleEnum), # unsigned byte, enum validated |
||
33 | ('length', 'H', 'vardata'), # unsigned short length field |
||
34 | ('vardata', # variable length data |
||
35 | Message('VarTest', [('x', 'B'), ('y', 'B')]), |
||
36 | 'length'), |
||
37 | ('data', { # discriminated data |
||
38 | SimpleEnum.one: Message('Struct1', [('y', 'B'), ('pad', '3x'), ('z', 'i')]), |
||
39 | SimpleEnum.two: Message('Struct2', [('z', '20s')]), |
||
40 | SimpleEnum.three: Message('Struct3', []), |
||
41 | }, 'type'), |
||
42 | ] |
||
43 | |||
44 | testvalues = [ |
||
45 | { |
||
46 | 'a': -128, |
||
47 | 'b': 0, |
||
48 | 'c': '0123456789', |
||
49 | 'e': 0, |
||
50 | 'type': SimpleEnum.one, |
||
51 | 'length': 0, |
||
52 | 'vardata': [], |
||
53 | 'data': { |
||
54 | 'y': 50, |
||
55 | 'z': 0x5577AACC, |
||
56 | }, |
||
57 | }, |
||
58 | { |
||
59 | 'a': 127, |
||
60 | 'b': 65535, |
||
61 | 'c': 'abcdefghij', |
||
62 | 'e': 0xFFFFFFFF, |
||
63 | 'type': SimpleEnum.two, |
||
64 | 'length': 2, |
||
65 | 'vardata': [ |
||
66 | {'x': 1, 'y': 2}, |
||
67 | {'x': 3, 'y': 4}, |
||
68 | ], |
||
69 | 'data': { |
||
70 | 'z': '0123456789abcdefghij', |
||
71 | }, |
||
72 | }, |
||
73 | { |
||
74 | 'a': -1, |
||
75 | 'b': 32767, |
||
76 | 'c': '\n\tzyx', |
||
77 | 'e': 0x7FFFFFFF, |
||
78 | 'type': SimpleEnum.three, |
||
79 | 'length': 1, |
||
80 | 'vardata': [ |
||
81 | {'x': 255, 'y': 127}, |
||
82 | ], |
||
83 | 'data': {}, |
||
84 | }, |
||
85 | { |
||
86 | 'a': 100, |
||
87 | 'b': 100, |
||
88 | 'c': 'a0b1c2d3e4', |
||
89 | 'e': 10000, |
||
90 | 'type': SimpleEnum.one, |
||
91 | 'length': 10, |
||
92 | 'vardata': [ |
||
93 | {'x': 255, 'y': 127}, |
||
94 | {'x': 254, 'y': 128}, |
||
95 | {'x': 253, 'y': 129}, |
||
96 | {'x': 252, 'y': 130}, |
||
97 | {'x': 251, 'y': 131}, |
||
98 | {'x': 250, 'y': 132}, |
||
99 | {'x': 249, 'y': 133}, |
||
100 | {'x': 248, 'y': 134}, |
||
101 | {'x': 247, 'y': 135}, |
||
102 | {'x': 246, 'y': 136}, |
||
103 | ], |
||
104 | 'data': { |
||
105 | 'y': 100, |
||
106 | 'z': 2000, |
||
107 | }, |
||
108 | }, |
||
109 | ] |
||
110 | |||
111 | testbytes = { |
||
112 | 'little': [ |
||
113 | b'\x80\x00\x00\x00\x00\x00\x00\x30\x31' + |
||
114 | b'\x32\x33\x34\x35\x36\x37\x38\x39\x00' + |
||
115 | b'\x00\x00\x00\x00\x01\x00\x00\x32\x00\x00\x00\xCC\xAA\x77\x55', |
||
116 | b'\x7F\x00\x00\x00\xFF\xFF\x00\x61\x62' + |
||
117 | b'\x63\x64\x65\x66\x67\x68\x69\x6A\x00' + |
||
118 | b'\xFF\xFF\xFF\xFF\x02\x02\x00\x01\x02' + |
||
119 | b'\x03\x04\x30\x31\x32\x33\x34\x35\x36' + |
||
120 | b'\x37\x38\x39\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a', |
||
121 | b'\xFF\x00\x00\x00\xFF\x7F\x00\x0A\x09' + |
||
122 | b'\x7A\x79\x78\x00\x00\x00\x00\x00\x00' + |
||
123 | b'\xFF\xFF\xFF\x7F\x03\x01\x00\xFF\x7F', |
||
124 | b'\x64\x00\x00\x00\x64\x00\x00\x61\x30' + |
||
125 | b'\x62\x31\x63\x32\x64\x33\x65\x34\x00' + |
||
126 | b'\x10\x27\x00\x00\x01\x0A\x00\xFF\x7F' + |
||
127 | b'\xFE\x80\xFD\x81\xFC\x82\xFB\x83\xFA' + |
||
128 | b'\x84\xF9\x85\xF8\x86\xF7\x87\xF6\x88' + |
||
129 | b'\x64\x00\x00\x00\xD0\x07\x00\x00', |
||
130 | ], |
||
131 | 'big': [ |
||
132 | b'\x80\x00\x00\x00\x00\x00\x00\x30\x31' + |
||
133 | b'\x32\x33\x34\x35\x36\x37\x38\x39\x00' + |
||
134 | b'\x00\x00\x00\x00\x01\x00\x00\x32\x00' + |
||
135 | b'\x00\x00\x55\x77\xAA\xCC', |
||
136 | b'\x7F\x00\x00\x00\xFF\xFF\x00\x61\x62' + |
||
137 | b'\x63\x64\x65\x66\x67\x68\x69\x6A\x00' + |
||
138 | b'\xFF\xFF\xFF\xFF\x02\x00\x02\x01\x02' + |
||
139 | b'\x03\x04\x30\x31\x32\x33\x34\x35\x36' + |
||
140 | b'\x37\x38\x39\x61\x62\x63\x64\x65\x66' + |
||
141 | b'\x67\x68\x69\x6a', |
||
142 | b'\xFF\x00\x00\x00\x7F\xFF\x00\x0A\x09' + |
||
143 | b'\x7A\x79\x78\x00\x00\x00\x00\x00\x00' + |
||
144 | b'\x7F\xFF\xFF\xFF\x03\x00\x01\xFF\x7F', |
||
145 | b'\x64\x00\x00\x00\x00\x64\x00\x61\x30' + |
||
146 | b'\x62\x31\x63\x32\x64\x33\x65\x34\x00' + |
||
147 | b'\x00\x00\x27\x10\x01\x00\x0A\xFF\x7F' + |
||
148 | b'\xFE\x80\xFD\x81\xFC\x82\xFB\x83\xFA' + |
||
149 | b'\x84\xF9\x85\xF8\x86\xF7\x87\xF6\x88' + |
||
150 | b'\x64\x00\x00\x00\x00\x00\x07\xD0', |
||
151 | ], |
||
152 | } |
||
153 | |||
154 | def test_init_invalid_name(self): |
||
155 | """Test invalid Message names.""" |
||
156 | |||
157 | for name in [None, '', 1, dict(), list()]: |
||
158 | with self.subTest(name): # pylint: disable=no-member |
||
159 | with self.assertRaises(TypeError) as cm: |
||
160 | Message(name, self.teststruct) |
||
161 | self.assertEqual(str(cm.exception), 'invalid name: {}'.format(name)) |
||
162 | |||
163 | def test_init_invalid_mode(self): |
||
164 | """Test invalid Message modes.""" |
||
165 | |||
166 | for mode in ['=', 'stuff', 0, -1, 1]: |
||
167 | with self.subTest(mode): # pylint: disable=no-member |
||
168 | with self.assertRaises(TypeError) as cm: |
||
169 | Message('test', self.teststruct, mode) |
||
170 | self.assertEqual(str(cm.exception), 'invalid mode: {}'.format(mode)) |
||
171 | |||
172 | def test_init_empty_struct(self): |
||
173 | """Test an empty Message.""" |
||
174 | |||
175 | val = Message('test', []) |
||
176 | self.assertEqual(val._tuple._fields, ()) # pylint: disable=protected-access |
||
177 | |||
178 | def test_pack_little_endian(self): |
||
179 | """Test pack the test formats.""" |
||
180 | test_msg = Message('test', self.teststruct, Mode.Little) |
||
181 | for idx in range(len(self.testvalues)): |
||
182 | with self.subTest(idx): # pylint: disable=no-member |
||
183 | packed_msg = test_msg.pack(**self.testvalues[idx]) |
||
184 | self.assertEqual(self.testbytes['little'][idx], packed_msg) |
||
185 | |||
186 | View Code Duplication | def test_unpack_little_endian(self): |
|
0 ignored issues
–
show
Duplication
introduced
by
![]() |
|||
187 | """Test unpack the test formats.""" |
||
188 | test_msg = Message('test', self.teststruct, Mode.Little) |
||
189 | assert test_msg.mode.to_byteorder() == 'little' |
||
190 | for idx in range(len(self.testvalues)): |
||
191 | with self.subTest(idx): # pylint: disable=no-member |
||
192 | (unpacked_partial_msg, unused) = test_msg.unpack_partial(self.testbytes['little'][idx] + b'\xde\xad') |
||
193 | self.assertEqual(unused, b'\xde\xad') |
||
194 | unpacked_msg = test_msg.unpack(self.testbytes['little'][idx]) |
||
195 | expected_tuple = test_msg.make(**self.testvalues[idx]) # pylint: disable=protected-access |
||
196 | self.assertEqual(unpacked_msg, unpacked_partial_msg) |
||
197 | self.assertEqual(unpacked_msg, expected_tuple) |
||
198 | |||
199 | def test_pack_big_endian(self): |
||
200 | """Test pack the test formats.""" |
||
201 | test_msg = Message('test', self.teststruct, Mode.Big) |
||
202 | for idx in range(len(self.testvalues)): |
||
203 | with self.subTest(idx): # pylint: disable=no-member |
||
204 | packed_msg = test_msg.pack(**self.testvalues[idx]) |
||
205 | self.assertEqual(self.testbytes['big'][idx], packed_msg) |
||
206 | |||
207 | View Code Duplication | def test_unpack_big_endian(self): |
|
0 ignored issues
–
show
|
|||
208 | """Test unpack the test formats.""" |
||
209 | test_msg = Message('test', self.teststruct, Mode.Big) |
||
210 | |||
211 | assert test_msg.mode.to_byteorder() == 'big' |
||
212 | |||
213 | for idx in range(len(self.testvalues)): |
||
214 | with self.subTest(idx): # pylint: disable=no-member |
||
215 | (unpacked_partial_msg, unused) = test_msg.unpack_partial(self.testbytes['big'][idx] + b'\xde\xad') |
||
216 | self.assertEqual(unused, b'\xde\xad') |
||
217 | unpacked_msg = test_msg.unpack(self.testbytes['big'][idx]) |
||
218 | expected_tuple = test_msg.make(**self.testvalues[idx]) # pylint: disable=protected-access |
||
219 | self.assertEqual(unpacked_msg, unpacked_partial_msg) |
||
220 | self.assertEqual(unpacked_msg, expected_tuple) |
||
221 | |||
222 | def test_bad_names(self): |
||
223 | with pytest.raises(ValueError) as e: |
||
224 | test_msg = Message('test', [ |
||
225 | ('pack', 'H'), |
||
226 | ('_elements', 'H'), |
||
227 | ('_fields', 'H'), |
||
228 | ]) |
||
229 | |||
230 | print(test_msg) |
||
231 | |||
232 | assert 'pack' in str(e) |
||
233 | assert '_elements' in str(e) |
||
234 | assert '_fields' in str(e) |
||
235 |