blob: 3d921d22cb90a77006d22d78e59c71e9e2ac614e [file] [log] [blame]
Rich Lane3f075972013-03-15 22:56:29 -07001#!/usr/bin/env python
2# Copyright 2013, Big Switch Networks, Inc.
3#
4# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
5# the following special exception:
6#
7# LOXI Exception
8#
9# As a special exception to the terms of the EPL, you may distribute libraries
10# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
11# that copyright and licensing notices generated by LoxiGen are not altered or removed
12# from the LoxiGen Libraries and the notice provided below is (i) included in
13# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
14# documentation for the LoxiGen Libraries, if distributed in binary form.
15#
16# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
17#
18# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
19# a copy of the EPL at:
20#
21# http://www.eclipse.org/legal/epl-v10.html
22#
23# Unless required by applicable law or agreed to in writing, software
24# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
25# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
26# EPL for the specific language governing permissions and limitations
27# under the EPL.
28import unittest
Rich Lane4c764982013-05-01 16:12:22 -070029import difflib
Rich Lane3f075972013-03-15 22:56:29 -070030
31try:
32 import loxi.of13 as ofp
Rich Lane57026dc2013-05-01 10:13:16 -070033 from loxi.generic_util import OFReader
Rich Lane3f075972013-03-15 22:56:29 -070034except ImportError:
35 exit("loxi package not found. Try setting PYTHONPATH.")
36
Rich Lane4c764982013-05-01 16:12:22 -070037# Human-friendly format for binary strings. 8 bytes per line.
38def format_binary(buf):
39 byts = map(ord, buf)
40 lines = [[]]
41 for byt in byts:
42 if len(lines[-1]) == 8:
43 lines.append([])
44 lines[-1].append(byt)
45 return '\n'.join([' '.join(['%02x' % y for y in x]) for x in lines])
46
47def diff(a, b):
48 return '\n'.join(difflib.ndiff(a.splitlines(), b.splitlines()))
49
50# Test serialization / deserialization of a sample object.
51# Depends on the __eq__ method being correct.
52def test_serialization(obj, buf):
53 packed = obj.pack()
54 if packed != buf:
55 a = format_binary(buf)
56 b = format_binary(packed)
57 raise AssertionError("Serialization of %s failed\nExpected:\n%s\nActual:\n%s\nDiff:\n%s" % \
58 (type(obj).__name__, a, b, diff(a, b)))
59 unpacked = type(obj).unpack(buf)
60 if obj != unpacked:
61 a = obj.show()
62 b = unpacked.show()
63 raise AssertionError("Deserialization of %s failed\nExpected:\n%s\nActual:\n%s\nDiff:\n%s" % \
64 (type(obj).__name__, a, b, diff(a, b)))
65
Rich Lane3f075972013-03-15 22:56:29 -070066class TestImports(unittest.TestCase):
67 def test_toplevel(self):
68 import loxi
69 self.assertTrue(hasattr(loxi, "ProtocolError"))
Rich Lane00549ea2013-04-25 13:33:16 -070070 self.assertEquals(loxi.version_names[4], "1.3")
Rich Lane3f075972013-03-15 22:56:29 -070071 ofp = loxi.protocol(4)
72 self.assertEquals(ofp.OFP_VERSION, 4)
73 self.assertTrue(hasattr(ofp, "action"))
74 self.assertTrue(hasattr(ofp, "common"))
75 self.assertTrue(hasattr(ofp, "const"))
76 self.assertTrue(hasattr(ofp, "message"))
Rich Lanea22233e2013-04-25 13:18:41 -070077 self.assertTrue(hasattr(ofp, "oxm"))
Rich Lane3f075972013-03-15 22:56:29 -070078
79 def test_version(self):
80 import loxi
81 self.assertTrue(hasattr(loxi.of13, "ProtocolError"))
82 self.assertTrue(hasattr(loxi.of13, "OFP_VERSION"))
83 self.assertEquals(loxi.of13.OFP_VERSION, 4)
84 self.assertTrue(hasattr(loxi.of13, "action"))
85 self.assertTrue(hasattr(loxi.of13, "common"))
86 self.assertTrue(hasattr(loxi.of13, "const"))
87 self.assertTrue(hasattr(loxi.of13, "message"))
Rich Lanea22233e2013-04-25 13:18:41 -070088 self.assertTrue(hasattr(loxi.of13, "oxm"))
Rich Lane3f075972013-03-15 22:56:29 -070089
Rich Lanee90685c2013-04-05 17:27:41 -070090class TestCommon(unittest.TestCase):
91 sample_hello_elem_buf = ''.join([
92 '\x00\x01', # type
93 '\x00\x0c', # length
94 '\x01\x23\x45\x67', # bitmaps[0]
95 '\x89\xab\xcd\xef', # bitmaps[1]
96 ])
97
98 def test_hello_elem_versionbitmap_pack(self):
99 obj = ofp.hello_elem_versionbitmap(bitmaps=[ofp.uint32(0x01234567),ofp.uint32(0x89abcdef)])
100 self.assertEquals(self.sample_hello_elem_buf, obj.pack())
101
102 def test_hello_elem_versionbitmap_unpack(self):
103 obj = ofp.hello_elem_versionbitmap.unpack(self.sample_hello_elem_buf)
104 self.assertEquals(len(obj.bitmaps), 2)
105 self.assertEquals(obj.bitmaps[0], ofp.uint32(0x01234567))
106 self.assertEquals(obj.bitmaps[1], ofp.uint32(0x89abcdef))
107
108 def test_list_hello_elem_unpack(self):
109 buf = ''.join([
110 '\x00\x01\x00\x04', # versionbitmap
111 '\x00\x00\x00\x04', # unknown type
112 '\x00\x01\x00\x04', # versionbitmap
113 ])
Rich Lane57026dc2013-05-01 10:13:16 -0700114 l = ofp.unpack_list_hello_elem(OFReader(buf))
Rich Lanee90685c2013-04-05 17:27:41 -0700115 self.assertEquals(len(l), 2)
116 self.assertTrue(isinstance(l[0], ofp.hello_elem_versionbitmap))
117 self.assertTrue(isinstance(l[1], ofp.hello_elem_versionbitmap))
118
Rich Lane4c764982013-05-01 16:12:22 -0700119class TestMessages(unittest.TestCase):
120 def test_hello(self):
121 obj = ofp.message.hello(
122 xid=0x12345678,
123 elements=[
124 ofp.hello_elem_versionbitmap(
125 bitmaps=[ofp.uint32(1), ofp.uint32(2)]),
126 ofp.hello_elem_versionbitmap(
127 bitmaps=[ofp.uint32(3), ofp.uint32(4)])])
128 buf = ''.join([
129 '\x04', '\x00', # version, type
130 '\x00\x20', # length
131 '\x12\x34\x56\x78', # xid
132 '\x00\x01', # elements[0].type
133 '\x00\x0c', # elements[0].length
134 '\x00\x00\x00\x01', # elements[0].bitmaps[0]
135 '\x00\x00\x00\x02', # elements[0].bitmaps[1]
136 '\x00\x01', # elements[1].type
137 '\x00\x0c', # elements[1].length
138 '\x00\x00\x00\x03', # elements[1].bitmaps[0]
139 '\x00\x00\x00\x04', # elements[1].bitmaps[1]
140 ])
141 test_serialization(obj, buf)
142
143 def test_error(self):
144 obj = ofp.message.error_msg(
145 xid=0x12345678,
146 err_type=ofp.OFPET_BAD_MATCH,
147 code=ofp.OFPBMC_BAD_MASK,
148 data="abc")
149 buf = ''.join([
150 '\x04', '\x01', # version, type
151 '\x00\x0f', # length
152 '\x12\x34\x56\x78', # xid
153 '\x00\x04', # err_type
154 '\x00\x08', # code
155 'abc', # data
156 ])
157 test_serialization(obj, buf)
158
159 def test_echo_request(self):
160 obj = ofp.message.echo_request(
161 xid=0x12345678,
162 data="abc")
163 buf = ''.join([
164 '\x04', '\x02', # version, type
165 '\x00\x0b', # length
166 '\x12\x34\x56\x78', # xid
167 'abc', # data
168 ])
169 test_serialization(obj, buf)
170
171 def test_echo_reply(self):
172 obj = ofp.message.echo_reply(
173 xid=0x12345678,
174 data="abc")
175 buf = ''.join([
176 '\x04', '\x03', # version, type
177 '\x00\x0b', # length
178 '\x12\x34\x56\x78', # xid
179 'abc', # data
180 ])
181 test_serialization(obj, buf)
182
183 def test_features_request(self):
184 obj = ofp.message.features_request(xid=0x12345678)
185 buf = ''.join([
186 '\x04', '\x05', # version, type
187 '\x00\x08', # length
188 '\x12\x34\x56\x78', # xid
189 ])
190 test_serialization(obj, buf)
191
192 def test_features_reply(self):
193 obj = ofp.message.features_reply(
194 xid=0x12345678,
195 datapath_id=0xFEDCBA9876543210,
196 n_buffers=64,
197 n_tables=200,
198 auxiliary_id=5,
199 capabilities=ofp.OFPC_FLOW_STATS|ofp.OFPC_PORT_BLOCKED,
200 reserved=0)
201 buf = ''.join([
202 '\x04', '\x06', # version, type
203 '\x00\x20', # length
204 '\x12\x34\x56\x78', # xid
205 '\xfe\xdc\xba\x98\x76\x54\x32\x10', # datapath_id
206 '\x00\x00\x00\x40', # n_buffers
207 '\xc8', # n_tables
208 '\x05', # auxiliary_id
209 '\x00\x00', # pad
210 '\x00\x00\x01\x01', # capabilities
211 '\x00\x00\x00\x00', # reserved
212 ])
213 test_serialization(obj, buf)
214
215 def test_get_config_request(self):
216 obj = ofp.message.get_config_request(xid=0x12345678)
217 buf = ''.join([
218 '\x04', '\x07', # version, type
219 '\x00\x08', # length
220 '\x12\x34\x56\x78', # xid
221 ])
222 test_serialization(obj, buf)
223
224 def test_get_config_reply(self):
225 obj = ofp.message.get_config_reply(
226 xid=0x12345678,
227 flags=ofp.OFPC_FRAG_REASM,
228 miss_send_len=0xffff)
229 buf = ''.join([
230 '\x04', '\x08', # version, type
231 '\x00\x0c', # length
232 '\x12\x34\x56\x78', # xid
233 '\x00\x02', # flags
234 '\xff\xff', # miss_send_len
235 ])
236 test_serialization(obj, buf)
237
238 def test_set_config(self):
239 obj = ofp.message.set_config(
240 xid=0x12345678,
241 flags=ofp.OFPC_FRAG_REASM,
242 miss_send_len=0xffff)
243 buf = ''.join([
244 '\x04', '\x09', # version, type
245 '\x00\x0c', # length
246 '\x12\x34\x56\x78', # xid
247 '\x00\x02', # flags
248 '\xff\xff', # miss_send_len
249 ])
250 test_serialization(obj, buf)
251
252 def test_packet_in(self):
253 obj = ofp.message.packet_in(
254 xid=0x12345678,
255 buffer_id=100,
256 total_len=17000,
257 reason=ofp.OFPR_ACTION,
258 table_id=20,
259 cookie=0xFEDCBA9876543210,
260 match=ofp.match(oxm_list=[
261 ofp.oxm.arp_op(value=1),
262 ofp.oxm.in_port_masked(value=4, value_mask=5)]),
263 data="abc")
264 buf = ''.join([
265 '\x04', '\x0a', # version, type
266 '\x00\x35', # length
267 '\x12\x34\x56\x78', # xid
268 '\x00\x00\x00\x64', # buffer_id
269 '\x42\x68', # total_len
270 '\x01', # reason
271 '\x14', # table_id
272 '\xfe\xdc\xba\x98\x76\x54\x32\x10', # cookie
273 '\x00\x01', # match.type
274 '\x00\x16', # match.length
275 '\x80\x00\x2A\x02', # match.oxm_list[0].type_len
276 '\x00\x01', # match.oxm_list[0].value
277 '\x80\x00\x01\x08', # match.oxm_list[1].type_len
278 '\x00\x00\x00\x04', # match.oxm_list[1].value
279 '\x00\x00\x00\x05', # match.oxm_list[1].mask
280 '\x00\x00', # match.pad
281 '\x00\x00', # pad
282 'abc', # data
283 ])
284 test_serialization(obj, buf)
285
286 def test_flow_removed(self):
287 obj = ofp.message.flow_removed(
288 xid=0x12345678,
289 cookie=0xFEDCBA9876543210,
290 priority=17000,
291 reason=ofp.OFPRR_DELETE,
292 table_id=20,
293 duration_sec=10,
294 duration_nsec=1000,
295 idle_timeout=5,
296 hard_timeout=30,
297 packet_count=1,
298 byte_count=2,
299 match=ofp.match(oxm_list=[
300 ofp.oxm.arp_op(value=1),
301 ofp.oxm.in_port_masked(value=4, value_mask=5)]))
302 buf = ''.join([
303 '\x04', '\x0b', # version, type
304 '\x00\x48', # length
305 '\x12\x34\x56\x78', # xid
306 '\xfe\xdc\xba\x98\x76\x54\x32\x10', # cookie
307 '\x42\x68', # priority
308 '\x02', # reason
309 '\x14', # table_id
310 '\x00\x00\x00\x0a', # duration_sec
311 '\x00\x00\x03\xe8', # duration_nsec
312 '\x00\x05', # idle_timeout
313 '\x00\x1e', # hard_timeout
314 '\x00\x00\x00\x00\x00\x00\x00\x01', # packet_count
315 '\x00\x00\x00\x00\x00\x00\x00\x02', # byte_count
316 '\x00\x01', # match.type
317 '\x00\x16', # match.length
318 '\x80\x00\x2A\x02', # match.oxm_list[0].type_len
319 '\x00\x01', # match.oxm_list[0].value
320 '\x80\x00\x01\x08', # match.oxm_list[1].type_len
321 '\x00\x00\x00\x04', # match.oxm_list[1].value
322 '\x00\x00\x00\x05', # match.oxm_list[1].mask
323 '\x00\x00', # match.pad
324 ])
325 test_serialization(obj, buf)
326
327 def test_port_status(self):
328 obj = ofp.message.port_status(
329 xid=0x12345678,
330 reason=ofp.OFPPR_MODIFY,
331 desc=ofp.port_desc(
332 port_no=4,
333 hw_addr=[1,2,3,4,5,6],
334 name="foo",
335 config=ofp.OFPPC_NO_FWD|ofp.OFPPC_NO_RECV,
336 state=ofp.OFPPS_BLOCKED,
337 curr=ofp.OFPPF_10MB_HD,
338 advertised=ofp.OFPPF_10MB_FD,
339 supported=ofp.OFPPF_100MB_HD,
340 peer=ofp.OFPPF_100MB_FD,
341 curr_speed=10,
342 max_speed=20))
343 buf = ''.join([
344 '\x04', '\x0c', # version, type
345 '\x00\x50', # length
346 '\x12\x34\x56\x78', # xid
347 '\x02', # reason
348 '\x00' * 7, # pad
349 '\x00\x00\x00\x04', # port_no
350 '\x00' * 4, # pad
351 '\x01\x02\x03\x04\x05\x06', # hw_addr
352 '\x00' * 2, # pad
353 'foo' + '\x00' * 13, # name
354 '\x00\x00\x00\x24', # config
355 '\x00\x00\x00\x02', # state
356 '\x00\x00\x00\x01', # curr
357 '\x00\x00\x00\x02', # advertised
358 '\x00\x00\x00\x04', # supported
359 '\x00\x00\x00\x08', # peer
360 '\x00\x00\x00\x0a', # curr_speed
361 '\x00\x00\x00\x14', # max_speed
362 ])
363 test_serialization(obj, buf)
364
365 def test_packet_out(self):
Rich Lanea4d3d2d2013-05-01 16:57:00 -0700366 obj = ofp.message.packet_out(
367 xid=0x12345678,
368 buffer_id=100,
369 in_port=4,
370 actions=[
371 ofp.action.output(port=2, max_len=0xffff),
372 ofp.action.dec_nw_ttl()],
373 data="abc")
374 buf = ''.join([
375 '\x04', '\x0d', # version, type
376 '\x00\x33', # length
377 '\x12\x34\x56\x78', # xid
378 '\x00\x00\x00\x64', # buffer_id
379 '\x00\x00\x00\x04', # in_port
380 '\x00\x18', # actions_len
381 '\x00' * 6, # pad
382 '\x00\x00', # actions[0].type
383 '\x00\x10', # actions[0].length
384 '\x00\x00\x00\x02', # actions[0].port
385 '\xff\xff', # actions[0].max_len
386 '\x00' * 6, # pad
387 '\x00\x18', # actions[1].type
388 '\x00\x08', # actions[1].length
389 '\x00' * 4, # pad
390 'abc', # data
391 ])
392 test_serialization(obj, buf)
Rich Lane4c764982013-05-01 16:12:22 -0700393
394
395 ## Flow-mods
396
397 def test_flow_add(self):
398 # TODO
399 pass
400
401 def test_flow_modify(self):
402 # TODO
403 pass
404
405 def test_flow_modify_strict(self):
406 # TODO
407 pass
408
409 def test_flow_delete(self):
410 # TODO
411 pass
412
413 def test_flow_delete_strict(self):
414 # TODO
415 pass
416
417
418 def test_group_mod(self):
419 # TODO
420 pass
421
422 def test_port_mod(self):
423 # TODO
424 pass
425
426 def test_table_mod(self):
427 # TODO
428 pass
429
430
431 ## Multipart messages
432
433 def test_desc_stats_request(self):
434 # TODO
435 pass
436
437 def test_desc_stats_reply(self):
438 # TODO
439 pass
440
441 def test_flow_stats_request(self):
442 # TODO
443 pass
444
445 def test_flow_stats_reply(self):
446 # TODO
447 pass
448
449 def test_aggregate_stats_request(self):
450 # TODO
451 pass
452
453 def test_aggregate_stats_reply(self):
454 # TODO
455 pass
456
457 def test_port_stats_request(self):
458 # TODO
459 pass
460
461 def test_port_stats_reply(self):
462 # TODO
463 pass
464
465 def test_queue_stats_request(self):
466 # TODO
467 pass
468
469 def test_queue_stats_reply(self):
470 # TODO
471 pass
472
473 def test_group_stats_request(self):
474 # TODO
475 pass
476
477 def test_group_stats_reply(self):
478 # TODO
479 pass
480
481 def test_group_desc_stats_request(self):
482 # TODO
483 pass
484
485 def test_group_desc_stats_reply(self):
486 # TODO
487 pass
488
489 def test_group_features_stats_request(self):
490 # TODO
491 pass
492
493 def test_group_features_stats_reply(self):
494 # TODO
495 pass
496
497 def test_meter_stats_request(self):
498 # TODO
499 pass
500
501 def test_meter_stats_reply(self):
502 # TODO
503 pass
504
505 def test_meter_config_stats_request(self):
506 # TODO
507 pass
508
509 def test_meter_config_stats_reply(self):
510 # TODO
511 pass
512
513 def test_meter_features_stats_request(self):
514 # TODO
515 pass
516
517 def test_meter_features_stats_reply(self):
518 # TODO
519 pass
520
521 def test_table_features_stats_request(self):
522 # TODO
523 pass
524
525 def test_table_features_stats_reply(self):
526 # TODO
527 pass
528
529 def test_port_desc_stats_request(self):
530 # TODO
531 pass
532
533 def test_port_desc_stats_reply(self):
534 # TODO
535 pass
536
537
538 def test_barrier_request(self):
539 # TODO
540 pass
541
542 def test_barrier_reply(self):
543 # TODO
544 pass
545
546 def test_queue_get_config_request(self):
547 # TODO
548 pass
549
550 def test_queue_get_config_reply(self):
551 # TODO
552 pass
553
554 def test_role_request(self):
555 # TODO
556 pass
557
558 def test_role_reply(self):
559 # TODO
560 pass
561
562 def test_get_async_request(self):
563 # TODO
564 pass
565
566 def test_get_async_reply(self):
567 # TODO
568 pass
569
570 def test_set_async(self):
571 # TODO
572 pass
573
574 def test_meter_mod(self):
575 # TODO
576 pass
577
578 # TODO test experimenter messages
579
580
Rich Laneea693752013-03-18 11:05:45 -0700581class TestOXM(unittest.TestCase):
582 def test_oxm_in_phy_port_pack(self):
583 import loxi.of13 as ofp
584 obj = ofp.oxm.in_phy_port(value=42)
585 expected = ''.join([
586 '\x80\x00', # class
587 '\x02', # type/masked
Rich Lane82e9f6e2013-04-25 17:32:22 -0700588 '\x04', # length
Rich Laneea693752013-03-18 11:05:45 -0700589 '\x00\x00\x00\x2a' # value
590 ])
591 self.assertEquals(expected, obj.pack())
592
593 def test_oxm_in_phy_port_masked_pack(self):
594 import loxi.of13 as ofp
595 obj = ofp.oxm.in_phy_port_masked(value=42, value_mask=0xaabbccdd)
596 expected = ''.join([
597 '\x80\x00', # class
598 '\x03', # type/masked
Rich Lane82e9f6e2013-04-25 17:32:22 -0700599 '\x08', # length
Rich Laneea693752013-03-18 11:05:45 -0700600 '\x00\x00\x00\x2a', # value
601 '\xaa\xbb\xcc\xdd' # mask
602 ])
603 self.assertEquals(expected, obj.pack())
604
Rich Lane41805642013-03-19 15:00:26 -0700605 def test_oxm_ipv6_dst_pack(self):
606 import loxi.of13 as ofp
607 obj = ofp.oxm.ipv6_dst(value='\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0d\x0f')
608 expected = ''.join([
609 '\x80\x00', # class
610 '\x36', # type/masked
Rich Lane82e9f6e2013-04-25 17:32:22 -0700611 '\x10', # length
Rich Lane41805642013-03-19 15:00:26 -0700612 '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0d\x0f', # value
613 ])
614 self.assertEquals(expected, obj.pack())
615
Rich Lane3f075972013-03-15 22:56:29 -0700616class TestAllOF13(unittest.TestCase):
617 """
618 Round-trips every class through serialization/deserialization.
619 Not a replacement for handcoded tests because it only uses the
620 default member values.
621 """
622
623 def setUp(self):
Rich Laneea693752013-03-18 11:05:45 -0700624 mods = [ofp.action,ofp.message,ofp.common,ofp.oxm]
Rich Lane3f075972013-03-15 22:56:29 -0700625 self.klasses = [klass for mod in mods
626 for klass in mod.__dict__.values()
627 if hasattr(klass, 'show')]
628 self.klasses.sort(key=lambda x: str(x))
629
630 def test_serialization(self):
631 expected_failures = [
Rich Lane3f075972013-03-15 22:56:29 -0700632 ofp.common.group_desc_stats_entry,
Rich Lane3f075972013-03-15 22:56:29 -0700633 ofp.message.group_desc_stats_reply,
634 ofp.message.group_mod,
635 ofp.message.group_stats_reply,
Rich Lane3f075972013-03-15 22:56:29 -0700636 ofp.message.meter_stats_reply,
Rich Lanea0186052013-05-01 14:18:39 -0700637 ofp.message.meter_features_stats_reply,
Rich Lane3f075972013-03-15 22:56:29 -0700638 ofp.message.table_features_stats_reply,
639 ofp.message.table_features_stats_request,
640 ]
641 for klass in self.klasses:
642 def fn():
643 obj = klass()
644 if hasattr(obj, "xid"): obj.xid = 42
645 buf = obj.pack()
646 obj2 = klass.unpack(buf)
647 self.assertEquals(obj, obj2)
648 if klass in expected_failures:
649 self.assertRaises(Exception, fn)
650 else:
651 fn()
652
653 def test_show(self):
Rich Lane8ca3b772013-04-30 13:36:55 -0700654 expected_failures = []
Rich Lane3f075972013-03-15 22:56:29 -0700655 for klass in self.klasses:
656 def fn():
657 obj = klass()
658 if hasattr(obj, "xid"): obj.xid = 42
659 obj.show()
660 if klass in expected_failures:
661 self.assertRaises(Exception, fn)
662 else:
663 fn()
664
665if __name__ == '__main__':
666 unittest.main()