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