blob: a02d37ce8ae9a2593eb969ce0354e907989de069 [file] [log] [blame]
Thomas Wouters4d70c3d2006-06-08 14:42:34 +00001import functools
R. David Murray378c0cf2010-02-24 01:46:21 +00002import sys
Raymond Hettinger9c323f82005-02-28 19:39:44 +00003import unittest
Benjamin Petersonee8712c2008-05-20 21:35:26 +00004from test import support
Raymond Hettingerc8b6d1b2005-03-08 06:14:50 +00005from weakref import proxy
Jack Diederiche0cbd692009-04-01 04:27:09 +00006import pickle
Georg Brandl2e7346a2010-07-31 18:09:23 +00007from random import choice
Raymond Hettinger9c323f82005-02-28 19:39:44 +00008
9@staticmethod
10def PythonPartial(func, *args, **keywords):
11 'Pure Python approximation of partial()'
12 def newfunc(*fargs, **fkeywords):
13 newkeywords = keywords.copy()
14 newkeywords.update(fkeywords)
15 return func(*(args + fargs), **newkeywords)
16 newfunc.func = func
17 newfunc.args = args
18 newfunc.keywords = keywords
19 return newfunc
20
21def capture(*args, **kw):
22 """capture all positional and keyword arguments"""
23 return args, kw
24
Jack Diederiche0cbd692009-04-01 04:27:09 +000025def signature(part):
26 """ return the signature of a partial object """
27 return (part.func, part.args, part.keywords, part.__dict__)
Guido van Rossumc1f779c2007-07-03 08:25:58 +000028
Raymond Hettinger9c323f82005-02-28 19:39:44 +000029class TestPartial(unittest.TestCase):
30
Thomas Wouters4d70c3d2006-06-08 14:42:34 +000031 thetype = functools.partial
Raymond Hettinger9c323f82005-02-28 19:39:44 +000032
33 def test_basic_examples(self):
34 p = self.thetype(capture, 1, 2, a=10, b=20)
35 self.assertEqual(p(3, 4, b=30, c=40),
36 ((1, 2, 3, 4), dict(a=10, b=30, c=40)))
37 p = self.thetype(map, lambda x: x*10)
Guido van Rossumc1f779c2007-07-03 08:25:58 +000038 self.assertEqual(list(p([1,2,3,4])), [10, 20, 30, 40])
Raymond Hettinger9c323f82005-02-28 19:39:44 +000039
40 def test_attributes(self):
41 p = self.thetype(capture, 1, 2, a=10, b=20)
42 # attributes should be readable
43 self.assertEqual(p.func, capture)
44 self.assertEqual(p.args, (1, 2))
45 self.assertEqual(p.keywords, dict(a=10, b=20))
46 # attributes should not be writable
47 if not isinstance(self.thetype, type):
48 return
Georg Brandl89fad142010-03-14 10:23:39 +000049 self.assertRaises(AttributeError, setattr, p, 'func', map)
50 self.assertRaises(AttributeError, setattr, p, 'args', (1, 2))
51 self.assertRaises(AttributeError, setattr, p, 'keywords', dict(a=1, b=2))
52
53 p = self.thetype(hex)
54 try:
55 del p.__dict__
56 except TypeError:
57 pass
58 else:
59 self.fail('partial object allowed __dict__ to be deleted')
Raymond Hettinger9c323f82005-02-28 19:39:44 +000060
61 def test_argument_checking(self):
62 self.assertRaises(TypeError, self.thetype) # need at least a func arg
63 try:
64 self.thetype(2)()
65 except TypeError:
66 pass
67 else:
68 self.fail('First arg not checked for callability')
69
70 def test_protection_of_callers_dict_argument(self):
71 # a caller's dictionary should not be altered by partial
72 def func(a=10, b=20):
73 return a
74 d = {'a':3}
75 p = self.thetype(func, a=5)
76 self.assertEqual(p(**d), 3)
77 self.assertEqual(d, {'a':3})
78 p(b=7)
79 self.assertEqual(d, {'a':3})
80
81 def test_arg_combinations(self):
82 # exercise special code paths for zero args in either partial
83 # object or the caller
84 p = self.thetype(capture)
85 self.assertEqual(p(), ((), {}))
86 self.assertEqual(p(1,2), ((1,2), {}))
87 p = self.thetype(capture, 1, 2)
88 self.assertEqual(p(), ((1,2), {}))
89 self.assertEqual(p(3,4), ((1,2,3,4), {}))
90
91 def test_kw_combinations(self):
92 # exercise special code paths for no keyword args in
93 # either the partial object or the caller
94 p = self.thetype(capture)
95 self.assertEqual(p(), ((), {}))
96 self.assertEqual(p(a=1), ((), {'a':1}))
97 p = self.thetype(capture, a=1)
98 self.assertEqual(p(), ((), {'a':1}))
99 self.assertEqual(p(b=2), ((), {'a':1, 'b':2}))
100 # keyword args in the call override those in the partial object
101 self.assertEqual(p(a=3, b=2), ((), {'a':3, 'b':2}))
102
103 def test_positional(self):
104 # make sure positional arguments are captured correctly
105 for args in [(), (0,), (0,1), (0,1,2), (0,1,2,3)]:
106 p = self.thetype(capture, *args)
107 expected = args + ('x',)
108 got, empty = p('x')
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000109 self.assertTrue(expected == got and empty == {})
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000110
111 def test_keyword(self):
112 # make sure keyword arguments are captured correctly
113 for a in ['a', 0, None, 3.5]:
114 p = self.thetype(capture, a=a)
115 expected = {'a':a,'x':None}
116 empty, got = p(x=None)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000117 self.assertTrue(expected == got and empty == ())
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000118
119 def test_no_side_effects(self):
120 # make sure there are no side effects that affect subsequent calls
121 p = self.thetype(capture, 0, a=1)
122 args1, kw1 = p(1, b=2)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000123 self.assertTrue(args1 == (0,1) and kw1 == {'a':1,'b':2})
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000124 args2, kw2 = p()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000125 self.assertTrue(args2 == (0,) and kw2 == {'a':1})
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000126
127 def test_error_propagation(self):
128 def f(x, y):
129 x / y
130 self.assertRaises(ZeroDivisionError, self.thetype(f, 1, 0))
131 self.assertRaises(ZeroDivisionError, self.thetype(f, 1), 0)
132 self.assertRaises(ZeroDivisionError, self.thetype(f), 1, 0)
133 self.assertRaises(ZeroDivisionError, self.thetype(f, y=0), 1)
134
Raymond Hettingerc8b6d1b2005-03-08 06:14:50 +0000135 def test_weakref(self):
136 f = self.thetype(int, base=16)
137 p = proxy(f)
138 self.assertEqual(f.func, p.func)
139 f = None
140 self.assertRaises(ReferenceError, getattr, p, 'func')
141
Raymond Hettinger26e512a2005-03-11 06:48:49 +0000142 def test_with_bound_and_unbound_methods(self):
Guido van Rossumc1f779c2007-07-03 08:25:58 +0000143 data = list(map(str, range(10)))
Raymond Hettinger26e512a2005-03-11 06:48:49 +0000144 join = self.thetype(str.join, '')
145 self.assertEqual(join(data), '0123456789')
146 join = self.thetype(''.join)
147 self.assertEqual(join(data), '0123456789')
Tim Peterseba28be2005-03-28 01:08:02 +0000148
Jack Diederiche0cbd692009-04-01 04:27:09 +0000149 def test_pickle(self):
150 f = self.thetype(signature, 'asdf', bar=True)
151 f.add_something_to__dict__ = True
152 f_copy = pickle.loads(pickle.dumps(f))
153 self.assertEqual(signature(f), signature(f_copy))
154
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000155class PartialSubclass(functools.partial):
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000156 pass
157
158class TestPartialSubclass(TestPartial):
159
160 thetype = PartialSubclass
161
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000162class TestPythonPartial(TestPartial):
163
164 thetype = PythonPartial
165
Jack Diederiche0cbd692009-04-01 04:27:09 +0000166 # the python version isn't picklable
167 def test_pickle(self): pass
168
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000169class TestUpdateWrapper(unittest.TestCase):
170
171 def check_wrapper(self, wrapper, wrapped,
172 assigned=functools.WRAPPER_ASSIGNMENTS,
173 updated=functools.WRAPPER_UPDATES):
174 # Check attributes were assigned
175 for name in assigned:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000176 self.assertTrue(getattr(wrapper, name) is getattr(wrapped, name))
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000177 # Check attributes were updated
178 for name in updated:
179 wrapper_attr = getattr(wrapper, name)
180 wrapped_attr = getattr(wrapped, name)
181 for key in wrapped_attr:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000182 self.assertTrue(wrapped_attr[key] is wrapper_attr[key])
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000183
R. David Murray378c0cf2010-02-24 01:46:21 +0000184 def _default_update(self):
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000185 def f():
186 """This is a test"""
187 pass
188 f.attr = 'This is also a test'
189 def wrapper():
190 pass
191 functools.update_wrapper(wrapper, f)
R. David Murray378c0cf2010-02-24 01:46:21 +0000192 return wrapper, f
193
194 def test_default_update(self):
195 wrapper, f = self._default_update()
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000196 self.check_wrapper(wrapper, f)
197 self.assertEqual(wrapper.__name__, 'f')
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000198 self.assertEqual(wrapper.attr, 'This is also a test')
199
R. David Murray378c0cf2010-02-24 01:46:21 +0000200 @unittest.skipIf(sys.flags.optimize >= 2,
201 "Docstrings are omitted with -O2 and above")
202 def test_default_update_doc(self):
203 wrapper, f = self._default_update()
204 self.assertEqual(wrapper.__doc__, 'This is a test')
205
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000206 def test_no_update(self):
207 def f():
208 """This is a test"""
209 pass
210 f.attr = 'This is also a test'
211 def wrapper():
212 pass
213 functools.update_wrapper(wrapper, f, (), ())
214 self.check_wrapper(wrapper, f, (), ())
215 self.assertEqual(wrapper.__name__, 'wrapper')
216 self.assertEqual(wrapper.__doc__, None)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000217 self.assertFalse(hasattr(wrapper, 'attr'))
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000218
219 def test_selective_update(self):
220 def f():
221 pass
222 f.attr = 'This is a different test'
223 f.dict_attr = dict(a=1, b=2, c=3)
224 def wrapper():
225 pass
226 wrapper.dict_attr = {}
227 assign = ('attr',)
228 update = ('dict_attr',)
229 functools.update_wrapper(wrapper, f, assign, update)
230 self.check_wrapper(wrapper, f, assign, update)
231 self.assertEqual(wrapper.__name__, 'wrapper')
232 self.assertEqual(wrapper.__doc__, None)
233 self.assertEqual(wrapper.attr, 'This is a different test')
234 self.assertEqual(wrapper.dict_attr, f.dict_attr)
235
Thomas Wouters89f507f2006-12-13 04:49:30 +0000236 def test_builtin_update(self):
237 # Test for bug #1576241
238 def wrapper():
239 pass
240 functools.update_wrapper(wrapper, max)
241 self.assertEqual(wrapper.__name__, 'max')
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000242 self.assertTrue(wrapper.__doc__.startswith('max('))
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000243
244class TestWraps(TestUpdateWrapper):
245
R. David Murray378c0cf2010-02-24 01:46:21 +0000246 def _default_update(self):
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000247 def f():
248 """This is a test"""
249 pass
250 f.attr = 'This is also a test'
251 @functools.wraps(f)
252 def wrapper():
253 pass
254 self.check_wrapper(wrapper, f)
R. David Murray378c0cf2010-02-24 01:46:21 +0000255 return wrapper
256
257 def test_default_update(self):
258 wrapper = self._default_update()
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000259 self.assertEqual(wrapper.__name__, 'f')
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000260 self.assertEqual(wrapper.attr, 'This is also a test')
261
R. David Murray378c0cf2010-02-24 01:46:21 +0000262 @unittest.skipIf(not sys.flags.optimize <= 1,
263 "Docstrings are omitted with -O2 and above")
264 def test_default_update_doc(self):
265 wrapper = self._default_update()
266 self.assertEqual(wrapper.__doc__, 'This is a test')
267
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000268 def test_no_update(self):
269 def f():
270 """This is a test"""
271 pass
272 f.attr = 'This is also a test'
273 @functools.wraps(f, (), ())
274 def wrapper():
275 pass
276 self.check_wrapper(wrapper, f, (), ())
277 self.assertEqual(wrapper.__name__, 'wrapper')
278 self.assertEqual(wrapper.__doc__, None)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000279 self.assertFalse(hasattr(wrapper, 'attr'))
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000280
281 def test_selective_update(self):
282 def f():
283 pass
284 f.attr = 'This is a different test'
285 f.dict_attr = dict(a=1, b=2, c=3)
286 def add_dict_attr(f):
287 f.dict_attr = {}
288 return f
289 assign = ('attr',)
290 update = ('dict_attr',)
291 @functools.wraps(f, assign, update)
292 @add_dict_attr
293 def wrapper():
294 pass
295 self.check_wrapper(wrapper, f, assign, update)
296 self.assertEqual(wrapper.__name__, 'wrapper')
297 self.assertEqual(wrapper.__doc__, None)
298 self.assertEqual(wrapper.attr, 'This is a different test')
299 self.assertEqual(wrapper.dict_attr, f.dict_attr)
300
Guido van Rossum0919a1a2006-08-26 20:49:04 +0000301class TestReduce(unittest.TestCase):
302 func = functools.reduce
303
304 def test_reduce(self):
305 class Squares:
306 def __init__(self, max):
307 self.max = max
308 self.sofar = []
309
310 def __len__(self):
311 return len(self.sofar)
312
313 def __getitem__(self, i):
314 if not 0 <= i < self.max: raise IndexError
315 n = len(self.sofar)
316 while n <= i:
317 self.sofar.append(n*n)
318 n += 1
319 return self.sofar[i]
Guido van Rossumd8faa362007-04-27 19:54:29 +0000320
Guido van Rossum0919a1a2006-08-26 20:49:04 +0000321 self.assertEqual(self.func(lambda x, y: x+y, ['a', 'b', 'c'], ''), 'abc')
322 self.assertEqual(
323 self.func(lambda x, y: x+y, [['a', 'c'], [], ['d', 'w']], []),
324 ['a','c','d','w']
325 )
326 self.assertEqual(self.func(lambda x, y: x*y, range(2,8), 1), 5040)
327 self.assertEqual(
Guido van Rossume2a383d2007-01-15 16:59:06 +0000328 self.func(lambda x, y: x*y, range(2,21), 1),
329 2432902008176640000
Guido van Rossum0919a1a2006-08-26 20:49:04 +0000330 )
331 self.assertEqual(self.func(lambda x, y: x+y, Squares(10)), 285)
332 self.assertEqual(self.func(lambda x, y: x+y, Squares(10), 0), 285)
333 self.assertEqual(self.func(lambda x, y: x+y, Squares(0), 0), 0)
334 self.assertRaises(TypeError, self.func)
335 self.assertRaises(TypeError, self.func, 42, 42)
336 self.assertRaises(TypeError, self.func, 42, 42, 42)
337 self.assertEqual(self.func(42, "1"), "1") # func is never called with one item
338 self.assertEqual(self.func(42, "", "1"), "1") # func is never called with one item
339 self.assertRaises(TypeError, self.func, 42, (42, 42))
340
341 class BadSeq:
342 def __getitem__(self, index):
343 raise ValueError
344 self.assertRaises(ValueError, self.func, 42, BadSeq())
345
346 # Test reduce()'s use of iterators.
347 def test_iterator_usage(self):
348 class SequenceClass:
349 def __init__(self, n):
350 self.n = n
351 def __getitem__(self, i):
352 if 0 <= i < self.n:
353 return i
354 else:
355 raise IndexError
Guido van Rossumd8faa362007-04-27 19:54:29 +0000356
Guido van Rossum0919a1a2006-08-26 20:49:04 +0000357 from operator import add
358 self.assertEqual(self.func(add, SequenceClass(5)), 10)
359 self.assertEqual(self.func(add, SequenceClass(5), 42), 52)
360 self.assertRaises(TypeError, self.func, add, SequenceClass(0))
361 self.assertEqual(self.func(add, SequenceClass(0), 42), 42)
362 self.assertEqual(self.func(add, SequenceClass(1)), 0)
363 self.assertEqual(self.func(add, SequenceClass(1), 42), 42)
364
365 d = {"one": 1, "two": 2, "three": 3}
366 self.assertEqual(self.func(add, d), "".join(d.keys()))
367
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000368class TestCmpToKey(unittest.TestCase):
369 def test_cmp_to_key(self):
370 def mycmp(x, y):
371 return y - x
372 self.assertEqual(sorted(range(5), key=functools.cmp_to_key(mycmp)),
373 [4, 3, 2, 1, 0])
Guido van Rossumd8faa362007-04-27 19:54:29 +0000374
Raymond Hettingerc50846a2010-04-05 18:56:31 +0000375 def test_hash(self):
376 def mycmp(x, y):
377 return y - x
378 key = functools.cmp_to_key(mycmp)
379 k = key(10)
380 self.assertRaises(TypeError, hash(k))
381
382class TestTotalOrdering(unittest.TestCase):
383
384 def test_total_ordering_lt(self):
385 @functools.total_ordering
386 class A:
387 def __init__(self, value):
388 self.value = value
389 def __lt__(self, other):
390 return self.value < other.value
391 self.assert_(A(1) < A(2))
392 self.assert_(A(2) > A(1))
393 self.assert_(A(1) <= A(2))
394 self.assert_(A(2) >= A(1))
395 self.assert_(A(2) <= A(2))
396 self.assert_(A(2) >= A(2))
397
398 def test_total_ordering_le(self):
399 @functools.total_ordering
400 class A:
401 def __init__(self, value):
402 self.value = value
403 def __le__(self, other):
404 return self.value <= other.value
405 self.assert_(A(1) < A(2))
406 self.assert_(A(2) > A(1))
407 self.assert_(A(1) <= A(2))
408 self.assert_(A(2) >= A(1))
409 self.assert_(A(2) <= A(2))
410 self.assert_(A(2) >= A(2))
411
412 def test_total_ordering_gt(self):
413 @functools.total_ordering
414 class A:
415 def __init__(self, value):
416 self.value = value
417 def __gt__(self, other):
418 return self.value > other.value
419 self.assert_(A(1) < A(2))
420 self.assert_(A(2) > A(1))
421 self.assert_(A(1) <= A(2))
422 self.assert_(A(2) >= A(1))
423 self.assert_(A(2) <= A(2))
424 self.assert_(A(2) >= A(2))
425
426 def test_total_ordering_ge(self):
427 @functools.total_ordering
428 class A:
429 def __init__(self, value):
430 self.value = value
431 def __ge__(self, other):
432 return self.value >= other.value
433 self.assert_(A(1) < A(2))
434 self.assert_(A(2) > A(1))
435 self.assert_(A(1) <= A(2))
436 self.assert_(A(2) >= A(1))
437 self.assert_(A(2) <= A(2))
438 self.assert_(A(2) >= A(2))
439
440 def test_total_ordering_no_overwrite(self):
441 # new methods should not overwrite existing
442 @functools.total_ordering
443 class A(int):
444 raise Exception()
445 self.assert_(A(1) < A(2))
446 self.assert_(A(2) > A(1))
447 self.assert_(A(1) <= A(2))
448 self.assert_(A(2) >= A(1))
449 self.assert_(A(2) <= A(2))
450 self.assert_(A(2) >= A(2))
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000451
Benjamin Peterson42ebee32010-04-11 01:43:16 +0000452 def test_no_operations_defined(self):
453 with self.assertRaises(ValueError):
454 @functools.total_ordering
455 class A:
456 pass
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000457
Georg Brandl2e7346a2010-07-31 18:09:23 +0000458class TestLRU(unittest.TestCase):
459
460 def test_lru(self):
461 def orig(x, y):
462 return 3*x+y
463 f = functools.lru_cache(maxsize=20)(orig)
464
465 domain = range(5)
466 for i in range(1000):
467 x, y = choice(domain), choice(domain)
468 actual = f(x, y)
469 expected = orig(x, y)
470 self.assertEquals(actual, expected)
471 self.assert_(f.hits > f.misses)
472 self.assertEquals(f.hits + f.misses, 1000)
473
474 f.clear() # test clearing
475 self.assertEqual(f.hits, 0)
476 self.assertEqual(f.misses, 0)
477 f(x, y)
478 self.assertEqual(f.hits, 0)
479 self.assertEqual(f.misses, 1)
480
481 def test_lfu(self):
482 def orig(x, y):
483 return 3*x+y
484 f = functools.lfu_cache(maxsize=20)(orig)
485
486 domain = range(5)
487 for i in range(1000):
488 x, y = choice(domain), choice(domain)
489 actual = f(x, y)
490 expected = orig(x, y)
491 self.assertEquals(actual, expected)
492 self.assert_(f.hits > f.misses)
493 self.assertEquals(f.hits + f.misses, 1000)
494
495 f.clear() # test clearing
496 self.assertEqual(f.hits, 0)
497 self.assertEqual(f.misses, 0)
498 f(x, y)
499 self.assertEqual(f.hits, 0)
500 self.assertEqual(f.misses, 1)
501
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000502def test_main(verbose=None):
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000503 test_classes = (
504 TestPartial,
505 TestPartialSubclass,
506 TestPythonPartial,
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000507 TestUpdateWrapper,
Guido van Rossum0919a1a2006-08-26 20:49:04 +0000508 TestWraps,
Georg Brandl2e7346a2010-07-31 18:09:23 +0000509 TestReduce,
510 TestLRU,
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000511 )
Benjamin Petersonee8712c2008-05-20 21:35:26 +0000512 support.run_unittest(*test_classes)
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000513
514 # verify reference counting
515 if verbose and hasattr(sys, "gettotalrefcount"):
516 import gc
517 counts = [None] * 5
Guido van Rossum805365e2007-05-07 22:24:25 +0000518 for i in range(len(counts)):
Benjamin Petersonee8712c2008-05-20 21:35:26 +0000519 support.run_unittest(*test_classes)
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000520 gc.collect()
521 counts[i] = sys.gettotalrefcount()
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000522 print(counts)
Raymond Hettinger9c323f82005-02-28 19:39:44 +0000523
524if __name__ == '__main__':
525 test_main(verbose=True)