Passed
Pull Request — master (#404)
by
unknown
01:53
created

TestStruct.test_unpack()   A

Complexity

Conditions 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
c 0
b 0
f 0
rs 9.4285
cc 2
1
"""Automate struct tests."""
2
import unittest
3
4
from pyof.utils import unpack
5
from tests.raw_dump import RawDump
6
7
8
class TestStruct(unittest.TestCase):
9
    """Run tests related to struct packing and unpacking.
10
11
    Test the lib with raw dump files from an OpenFlow switch. We assume the
12
    raw files are valid according to the OF specs to check whether our pack and
13
    unpack implementations are correct.
14
15
    Also, check the minimum size of the struct by instantiating an object with
16
    no parameters.
17
18
    To run these tests, just extends this class and call 2 methods in the
19
    ``setUp`` method like the example.
20
21
    Example:
22
        .. code-block:: python3
23
24
            class MyTest(TestDump):
25
                @classmethod
26
                def setUpClass(cls):
27
                    super().setUpClass()
28
                    super().set_raw_dump_file('v0x01', 'ofpt_barrier_reply')
29
                    # Create BarrierReply(xid=5) when needed
30
                    super().set_raw_dump_object(BarrierReply, xid=5)
31
                    # As in spec: ``OFP_ASSERT(sizeof(struct ...) == ...);``
32
                    super().set_minimum_size(8)
33
34
        To only test the minimum size and skip packing/unpacking:
35
36
        .. code-block:: python3
37
            class MyTest(TestDump):
38
                @classmethod
39
                def setUpClass(cls):
40
                    super().set_minimum_size(8, BarrierReply)
41
    """
42
43
    def __init__(self, *args, **kwargs):
44
        """The constructor will avoid that this class tests are executed.
45
46
        The tests in this class are executed through the child, so there's no
47
        no need for them to be executed once more through the parent.
48
        """
49
        super().__init__(*args, **kwargs)
50
        # Override the run method, so it does nothing instead of running the
51
        # tests (again).
52
        if self.__class__ == TestStruct:
53
            self.run = lambda self, *args, **kwargs: None
54
55
    _new_raw_dump = None
56
    _new_raw_object = None
57
    _msg_cls = None
58
    _min_size = None
59
60
    @classmethod
61
    def set_raw_dump_file(cls, version, basename):
62
        """Set which raw dump the tests will use.
63
64
        Args:
65
            protocol_version (str): OpenFlow protocol version,
66
                e.g. ``v0x01``.
67
            basename (str): The raw filename without extension.
68
                E.g. ``ofpt_echo_reply``.
69
        """
70
        cls._new_raw_dump = lambda: RawDump(version, basename)
71
72
    @classmethod
73
    def get_raw_dump(cls):
74
        """Return a new instance of :class:`.RawDump`.
75
76
        Use the parameters set in :meth:`set_raw_dump_file`.
77
78
        Returns:
79
            RawDump: with parameters previously set using
80
                :meth:`set_raw_dump_file`.
81
        """
82
        if cls._new_raw_dump is None:
83
            raise FileNotFoundError()
84
        return cls._new_raw_dump()
85
86
    @classmethod
87
    def set_raw_dump_object(cls, msg_cls, *args, **kwargs):
88
        """Set how to create the object that is dumped in a raw file.
89
90
        Args:
91
            msg_class (:obj:`type`): The message class that is packed as a
92
                raw file, followed by its parameters to instantiate an
93
                object.
94
95
        Example:
96
            ``super().__init__(BarrierReply, xid=5)`` will create
97
            ``BarrierReply(xid=5)``.
98
        """
99
        TestStruct._msg_cls = msg_cls
100
        cls._new_raw_object = lambda: msg_cls(*args, **kwargs)
101
102
    @classmethod
103
    def get_raw_object(cls):
104
        """Create a new object of the dumped message.
105
106
        Use the class and parameters set in :meth:`set_raw_dump_object`.
107
108
        Returns:
109
            A new object using class and parameters priviously set through
110
                :meth:`set_raw_dump_object`.
111
        """
112
        return cls._new_raw_object()
113
114
    @classmethod
115
    def set_minimum_size(cls, size, msg_cls=None):
116
        """Set the struct minimum size.
117
118
        The minimum size can be found in OF spec. For example,
119
        :class:`.PhyPort` minimum size is 48 because of
120
        ``OFP_ASSERT(sizeof(struct ofp_phy_port) == 48);`` (spec 1.0.0).
121
122
        Args:
123
            size (int): The minimum size of the struct, in bytes.
124
            msg_cls (class): The class (or function) to have its size checked.
125
                If None, use the same class set in :meth:`set_raw_dump_object`.
126
        """
127
        cls._min_size = size
128
        if msg_cls is not None:
129
            TestStruct._msg_cls = msg_cls
130
131
    def test_pack(self):
132
        """Check whether packed objects equals to dump file."""
133
        try:
134
            raw_file = self.get_raw_dump().read()
135
            msg = self.get_raw_object()
136
            packed_obj = msg.pack()
137
            self.assertEqual(packed_obj, raw_file)
138
        except FileNotFoundError:
139
            raise self.skipTest('No raw dump file found.')
140
141
    def test_unpack(self):
142
        """Check whether the unpacked dump equals to expected object."""
143
        try:
144
            unpacked = self.get_raw_dump().unpack()
145
            obj = self.get_raw_object()
146
            self.assertEqual(unpacked, obj)
147
        except FileNotFoundError:
148
            raise self.skipTest('No raw dump file found.')
149
150
    def test_minimum_size(self):
151
        """Test struct minimum size."""
152
        if self._min_size is None:
153
            raise self.skipTest('minimum size was not set.')
154
        obj = TestStruct._msg_cls()
155
        self.assertEqual(obj.get_size(), self._min_size)
156
157
    def test_raw_dump_size(self):
158
        """Check whether the unpacked dump has the expected size."""
159
        try:
160
            unpacked = self.get_raw_dump().unpack()
161
            obj = self.get_raw_object()
162
            self.assertEqual(obj.get_size(), unpacked.get_size())
163
        except FileNotFoundError:
164
            raise self.skipTest('No raw dump file found.')
165
166
167
class TestStructDump(unittest.TestCase):
168
    """Run message pack, unpack and get_size tests using bytes struct dump.
169
170
    Test the lib with raw dumps. We assume the raw files are valid according
171
    to the OF specs to check whether our pack, unpack and get_size
172
    implementations are correct.
173
174
    Also, check the minimum size of the struct by instantiating an object with
175
    no parameters.
176
177
    To run the tests, just extends this class and set the 'dump' and 'obj'
178
    attributes. You can also optionally set the 'min_size' attribute.
179
180
    Example:
181
        .. code-block:: python3
182
183
            from pyof.v0x01.foundation.basic_type import BinaryData
184
185
            class TestBinaryData(TestStructDump):
186
                dump = b'data'
187
                obj = BinaryData(xid=0)
188
                min_size = 0
189
    """
190
191
    dump = b''
192
    obj = None
193
    min_size = 0
194
195
    def __init__(self, *args, **kwargs):
196
        """Constructor to avoid this base class being executed as a test."""
197
        if self.__class__ == TestStructDump:
198
            self.run = self.run = lambda self, *args, **kwargs: None
199
        super().__init__(*args, **kwargs)
200
201
    def setUp(self):
202
        """Setup the instance before testing."""
203
        self._msg_cls = type(self.obj)
204
        self._unpacked_dump = self._unpack_dump()
205
        super().setUp()
206
207
    def _unpack_dump(self):
208
        obj = self._msg_cls()
209
        obj.unpack(self.dump)
210
        return obj
211
212
    def test_pack(self):
213
        """Check whether packed objects equals to dump file."""
214
        self.assertEqual(self.dump, self.obj.pack())
215
216
    def test_unpack(self):
217
        """Check whether the unpacked dump equals to expected object."""
218
        self.assertEqual(self._unpacked_dump, self.obj)
219
220
    def test_get_size(self):
221
        """Check if get_size method return the correct size."""
222
        self.assertEqual(self.obj.get_size(), len(self.dump))
223
224
    def test_minimum_size(self):
225
        """Test struct minimum size."""
226
        if not self.min_size:
227
            raise self.skipTest('skipped, no minimum size set.')
228
        obj = self._msg_cls()
229
        self.assertEqual(obj.get_size(), self.min_size)
230
231
    def test_raw_dump_size(self):
232
        """Check whether the unpacked dump has the expected size."""
233
        self.assertEqual(self.obj.get_size(), self._unpacked_dump.get_size())
234
235
236
class TestMsgDump(TestStructDump):
237
    r"""Run message pack, unpack and get_size tests using bytes message dump.
238
239
    Test the lib with raw dumps. We assume the raw files are valid according
240
    to the OF specs to check whether our pack, unpack and get_size
241
    implementations are correct.
242
243
    Also, check the minimum size of the struct by instantiating an object with
244
    no parameters.
245
246
    To run the tests, just extends this class and set the 'dump' and 'obj'
247
    attributes. You can also optionally set the 'min_size' attribute.
248
249
    Example:
250
        .. code-block:: python3
251
252
            class TestHello(TestMsgDump):
253
                dump = b'\x01\x00\x00\x08\x00\x00\x00\x00'
254
                obj = pyof.v0x01.symmetric.hello.Hello(xid=0)
255
                min_size = 8
256
    """
257
258
    def __init__(self, *args, **kwargs):
259
        """Constructor to avoid this base class beeing executed as a test."""
260
        if self.__class__ == TestMsgDump:
261
            self.run = self.run = lambda self, *args, **kwargs: None
262
        super().__init__(*args, **kwargs)
263
264
    def _unpack_dump(self):
265
        return unpack(self.dump)
266
267
268
class TestMsgDumpFile(TestMsgDump):
269
    """Run message pack, unpack and get_size tests using message in a dumpfile.
270
271
    Test the lib with raw dumps. We assume the raw files are valid according
272
    to the OF specs to check whether our pack, unpack and get_size
273
    implementations are correct.
274
275
    Also, check the minimum size of the message by instantiating an object with
276
    no parameters.
277
278
    To run the tests, just extends this class and set the 'dumpfile' and 'obj'
279
    attributes. You can also optionally set the 'min_size' attribute.
280
281
    Example:
282
        .. code-block:: python3
283
284
            class TestHelloFileDump(TestMsgDumpFile):
285
                dumpfile = 'v0x01/ofpt_hello.dat'
286
                obj = pyof.v0x01.symmetric.hello.Hello(xid=1)
287
288
    """
289
290
    dumpfile = None
291
292
    def __init__(self, *args, **kwargs):
293
        """Constructor to avoid this base class beeing executed as a test."""
294
        if self.__class__ == TestMsgDumpFile:
295
            self.run = self.run = lambda self, *args, **kwargs: None
296
        super().__init__(*args, **kwargs)
297
298
    def setUp(self):
299
        """Setup the instance before testing."""
300
        self._read_dump_file()
301
        super().setUp()
302
303
    def _read_dump_file(self):
304
        dumpfile = f'raw/{self.dumpfile}'
305
        try:
306
            with open(dumpfile, 'rb') as fd:
307
                self.dump = fd.read()
308
        except FileNotFoundError:
309
            raise self.skipTest(f'No raw dump file found: {dumpfile}')
310