Passed
Pull Request — master (#406)
by
unknown
02:32
created

TestStruct.set_raw_dump_object()   A

Complexity

Conditions 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
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
            class TestMatch(TestStructDump):
184
                dump = b'' # needs to be filled
185
                obj = pyof.v0x01.common.flow_match.Match(xid=0)
186
                min_size = 8
187
    """
188
189
    dump = b''
190
    obj = None
191
    min_size = 0
192
193
    def __init__(self, *args, **kwargs):
194
        """Constructor to avoid this base class being executed as a test."""
195
        if self.__class__ == TestStructDump:
196
            self.run = self.run = lambda self, *args, **kwargs: None
197
        super().__init__(*args, **kwargs)
198
199
    def setUp(self):
200
        """Setup the instance before testing."""
201
        self._msg_cls = type(self.obj)
202
        self._unpacked_dump = self._unpack_dump()
203
        super().setUp()
204
205
    def _unpack_dump(self):
206
        obj = self._msg_cls()
207
        obj.unpack(self.dump)
208
        return obj
209
210
    def test_pack(self):
211
        """Check whether packed objects equals to dump file."""
212
        self.assertEqual(self.dump, self.obj.pack())
213
214
    def test_unpack(self):
215
        """Check whether the unpacked dump equals to expected object."""
216
        self.assertEqual(self._unpacked_dump, self.obj)
217
218
    def test_get_size(self):
219
        """Check if get_size method return the correct size."""
220
        self.assertEqual(self.obj.get_size(), len(self.dump))
221
222
    def test_minimum_size(self):
223
        """Test struct minimum size."""
224
        if not self.min_size:
225
            raise self.skipTest('skipped, no minimum size set.')
226
        obj = self._msg_cls()
227
        self.assertEqual(obj.get_size(), self.min_size)
228
229
    def test_raw_dump_size(self):
230
        """Check whether the unpacked dump has the expected size."""
231
        self.assertEqual(self.obj.get_size(), self._unpacked_dump.get_size())
232
233
234
class TestMsgDump(TestStructDump):
235
    r"""Run message pack, unpack and get_size tests using bytes message dump.
236
237
    Test the lib with raw dumps. We assume the raw files are valid according
238
    to the OF specs to check whether our pack, unpack and get_size
239
    implementations are correct.
240
241
    Also, check the minimum size of the struct by instantiating an object with
242
    no parameters.
243
244
    To run the tests, just extends this class and set the 'dump' and 'obj'
245
    attributes. You can also optionally set the 'min_size' attribute.
246
247
    Example:
248
        .. code-block:: python3
249
250
            class TestHello(TestMsgDump):
251
                dump = b'\x01\x00\x00\x08\x00\x00\x00\x00'
252
                obj = pyof.v0x01.symmetric.hello.Hello(xid=0)
253
                min_size = 8
254
    """
255
256
    def __init__(self, *args, **kwargs):
257
        """Constructor to avoid this base class beeing executed as a test."""
258
        if self.__class__ == TestMsgDump:
259
            self.run = self.run = lambda self, *args, **kwargs: None
260
        super().__init__(*args, **kwargs)
261
262
    def _unpack_dump(self):
263
        return unpack(self.dump)
264
265
266
class TestMsgDumpFile(TestMsgDump):
267
    """Run message pack, unpack and get_size tests using message in a dumpfile.
268
269
    Test the lib with raw dumps. We assume the raw files are valid according
270
    to the OF specs to check whether our pack, unpack and get_size
271
    implementations are correct.
272
273
    Also, check the minimum size of the message by instantiating an object with
274
    no parameters.
275
276
    To run the tests, just extends this class and set the 'dumpfile' and 'obj'
277
    attributes. You can also optionally set the 'min_size' attribute.
278
279
    Example:
280
        .. code-block:: python3
281
282
            class TestHelloFileDump(TestMsgDumpFile):
283
                dumpfile = 'v0x01/ofpt_hello.dat'
284
                obj = pyof.v0x01.symmetric.hello.Hello(xid=1)
285
286
    """
287
288
    dumpfile = None
289
290
    def __init__(self, *args, **kwargs):
291
        """Constructor to avoid this base class beeing executed as a test."""
292
        if self.__class__ == TestMsgDumpFile:
293
            self.run = self.run = lambda self, *args, **kwargs: None
294
        super().__init__(*args, **kwargs)
295
296
    def setUp(self):
297
        """Setup the instance before testing."""
298
        self._read_dump_file()
299
        super().setUp()
300
301
    def _read_dump_file(self):
302
        dumpfile = f'raw/{self.dumpfile}'
303
        try:
304
            with open(dumpfile, 'rb') as fd:
305
                self.dump = fd.read()
306
        except FileNotFoundError:
307
            raise self.skipTest(f'No raw dump file found: {dumpfile}')
308