blob: 05203e52cd7d170bfa37074666d411d7213698bb [file] [log] [blame]
Serhiy Storchakabfe18242015-03-31 13:12:37 +03001from _compat_pickle import (IMPORT_MAPPING, REVERSE_IMPORT_MAPPING,
2 NAME_MAPPING, REVERSE_NAME_MAPPING)
3import builtins
Jeremy Hyltonbe467e52000-09-15 15:14:51 +00004import pickle
Guido van Rossumcfe5f202007-05-08 21:26:54 +00005import io
Antoine Pitrou8d3c2902012-03-04 18:31:48 +01006import collections
Serhiy Storchaka5bbd2312014-12-16 19:39:08 +02007import struct
8import sys
Tim Peters47a6b132003-01-28 22:34:11 +00009
Serhiy Storchaka5bbd2312014-12-16 19:39:08 +020010import unittest
Benjamin Petersonee8712c2008-05-20 21:35:26 +000011from test import support
Jeremy Hylton66426532001-10-15 21:38:56 +000012
Serhiy Storchakac6b54b42015-09-29 15:33:24 +030013from test.pickletester import AbstractUnpickleTests
Tim Peters47a6b132003-01-28 22:34:11 +000014from test.pickletester import AbstractPickleTests
Tim Peters47a6b132003-01-28 22:34:11 +000015from test.pickletester import AbstractPickleModuleTests
16from test.pickletester import AbstractPersistentPicklerTests
Serhiy Storchakadec25af2016-07-17 11:24:17 +030017from test.pickletester import AbstractIdentityPersistentPicklerTests
Collin Winter771d8342009-04-16 03:18:06 +000018from test.pickletester import AbstractPicklerUnpicklerObjectTests
Antoine Pitrou8d3c2902012-03-04 18:31:48 +010019from test.pickletester import AbstractDispatchTableTests
Antoine Pitrou82be19f2011-08-29 23:09:33 +020020from test.pickletester import BigmemPickleTests
Tim Peters47a6b132003-01-28 22:34:11 +000021
Alexandre Vassalottica2d6102008-06-12 18:26:05 +000022try:
23 import _pickle
24 has_c_implementation = True
25except ImportError:
26 has_c_implementation = False
Jeremy Hylton66426532001-10-15 21:38:56 +000027
Guido van Rossum98297ee2007-11-06 21:34:58 +000028
Alexandre Vassalottica2d6102008-06-12 18:26:05 +000029class PickleTests(AbstractPickleModuleTests):
30 pass
Guido van Rossum5d9113d2003-01-29 17:58:45 +000031
Tim Peterse0c446b2001-10-18 21:57:37 +000032
Serhiy Storchakac6b54b42015-09-29 15:33:24 +030033class PyUnpicklerTests(AbstractUnpickleTests):
34
35 unpickler = pickle._Unpickler
Serhiy Storchakae9b30742015-11-23 15:17:43 +020036 bad_stack_errors = (IndexError,)
Serhiy Storchaka7279bef2015-11-29 13:12:10 +020037 truncated_errors = (pickle.UnpicklingError, EOFError,
38 AttributeError, ValueError,
39 struct.error, IndexError, ImportError)
Serhiy Storchakac6b54b42015-09-29 15:33:24 +030040
41 def loads(self, buf, **kwds):
42 f = io.BytesIO(buf)
43 u = self.unpickler(f, **kwds)
44 return u.load()
45
46
Alexandre Vassalottica2d6102008-06-12 18:26:05 +000047class PyPicklerTests(AbstractPickleTests):
Jeremy Hylton66426532001-10-15 21:38:56 +000048
Alexandre Vassalottica2d6102008-06-12 18:26:05 +000049 pickler = pickle._Pickler
50 unpickler = pickle._Unpickler
Jeremy Hylton66426532001-10-15 21:38:56 +000051
Guido van Rossumf4169812008-03-17 22:56:06 +000052 def dumps(self, arg, proto=None):
Guido van Rossumcfe5f202007-05-08 21:26:54 +000053 f = io.BytesIO()
Alexandre Vassalottica2d6102008-06-12 18:26:05 +000054 p = self.pickler(f, proto)
Jeremy Hylton66426532001-10-15 21:38:56 +000055 p.dump(arg)
56 f.seek(0)
Guido van Rossumcfe5f202007-05-08 21:26:54 +000057 return bytes(f.read())
Jeremy Hylton66426532001-10-15 21:38:56 +000058
Alexander Belopolskyec8f0df2011-02-24 20:34:38 +000059 def loads(self, buf, **kwds):
Guido van Rossumcfe5f202007-05-08 21:26:54 +000060 f = io.BytesIO(buf)
Alexander Belopolskyec8f0df2011-02-24 20:34:38 +000061 u = self.unpickler(f, **kwds)
Jeremy Hylton66426532001-10-15 21:38:56 +000062 return u.load()
63
Alexandre Vassalottica2d6102008-06-12 18:26:05 +000064
Serhiy Storchakac6b54b42015-09-29 15:33:24 +030065class InMemoryPickleTests(AbstractPickleTests, AbstractUnpickleTests,
66 BigmemPickleTests):
Antoine Pitrouea99c5c2010-09-09 18:33:21 +000067
68 pickler = pickle._Pickler
69 unpickler = pickle._Unpickler
Serhiy Storchakae9b30742015-11-23 15:17:43 +020070 bad_stack_errors = (pickle.UnpicklingError, IndexError)
Serhiy Storchaka7279bef2015-11-29 13:12:10 +020071 truncated_errors = (pickle.UnpicklingError, EOFError,
72 AttributeError, ValueError,
73 struct.error, IndexError, ImportError)
Antoine Pitrouea99c5c2010-09-09 18:33:21 +000074
Antoine Pitrou82be19f2011-08-29 23:09:33 +020075 def dumps(self, arg, protocol=None):
76 return pickle.dumps(arg, protocol)
Antoine Pitrouea99c5c2010-09-09 18:33:21 +000077
Alexander Belopolskyec8f0df2011-02-24 20:34:38 +000078 def loads(self, buf, **kwds):
79 return pickle.loads(buf, **kwds)
Antoine Pitrouea99c5c2010-09-09 18:33:21 +000080
81
Serhiy Storchakadec25af2016-07-17 11:24:17 +030082class PersistentPicklerUnpicklerMixin(object):
Jeremy Hylton5e0f4e72002-11-13 22:01:27 +000083
Guido van Rossumf4169812008-03-17 22:56:06 +000084 def dumps(self, arg, proto=None):
Alexandre Vassalottica2d6102008-06-12 18:26:05 +000085 class PersPickler(self.pickler):
Jeremy Hylton5e0f4e72002-11-13 22:01:27 +000086 def persistent_id(subself, obj):
87 return self.persistent_id(obj)
Guido van Rossumcfe5f202007-05-08 21:26:54 +000088 f = io.BytesIO()
Guido van Rossum9d32bb12003-01-28 03:51:53 +000089 p = PersPickler(f, proto)
Jeremy Hylton5e0f4e72002-11-13 22:01:27 +000090 p.dump(arg)
Serhiy Storchakadec25af2016-07-17 11:24:17 +030091 return f.getvalue()
Jeremy Hylton5e0f4e72002-11-13 22:01:27 +000092
Alexander Belopolskyec8f0df2011-02-24 20:34:38 +000093 def loads(self, buf, **kwds):
Alexandre Vassalottica2d6102008-06-12 18:26:05 +000094 class PersUnpickler(self.unpickler):
Jeremy Hylton5e0f4e72002-11-13 22:01:27 +000095 def persistent_load(subself, obj):
96 return self.persistent_load(obj)
Guido van Rossumcfe5f202007-05-08 21:26:54 +000097 f = io.BytesIO(buf)
Alexander Belopolskyec8f0df2011-02-24 20:34:38 +000098 u = PersUnpickler(f, **kwds)
Jeremy Hylton5e0f4e72002-11-13 22:01:27 +000099 return u.load()
100
Alexandre Vassalottica2d6102008-06-12 18:26:05 +0000101
Serhiy Storchakadec25af2016-07-17 11:24:17 +0300102class PyPersPicklerTests(AbstractPersistentPicklerTests,
103 PersistentPicklerUnpicklerMixin):
104
105 pickler = pickle._Pickler
106 unpickler = pickle._Unpickler
107
108
109class PyIdPersPicklerTests(AbstractIdentityPersistentPicklerTests,
110 PersistentPicklerUnpicklerMixin):
111
112 pickler = pickle._Pickler
113 unpickler = pickle._Unpickler
114
115
Collin Winter771d8342009-04-16 03:18:06 +0000116class PyPicklerUnpicklerObjectTests(AbstractPicklerUnpicklerObjectTests):
117
118 pickler_class = pickle._Pickler
119 unpickler_class = pickle._Unpickler
120
121
Antoine Pitrou8d3c2902012-03-04 18:31:48 +0100122class PyDispatchTableTests(AbstractDispatchTableTests):
Alexandre Vassalottid05c9ff2013-12-07 01:09:27 -0800123
Antoine Pitrou8d3c2902012-03-04 18:31:48 +0100124 pickler_class = pickle._Pickler
Alexandre Vassalottid05c9ff2013-12-07 01:09:27 -0800125
Antoine Pitrou8d3c2902012-03-04 18:31:48 +0100126 def get_dispatch_table(self):
127 return pickle.dispatch_table.copy()
128
129
130class PyChainDispatchTableTests(AbstractDispatchTableTests):
Alexandre Vassalottid05c9ff2013-12-07 01:09:27 -0800131
Antoine Pitrou8d3c2902012-03-04 18:31:48 +0100132 pickler_class = pickle._Pickler
Alexandre Vassalottid05c9ff2013-12-07 01:09:27 -0800133
Antoine Pitrou8d3c2902012-03-04 18:31:48 +0100134 def get_dispatch_table(self):
135 return collections.ChainMap({}, pickle.dispatch_table)
136
137
Alexandre Vassalottica2d6102008-06-12 18:26:05 +0000138if has_c_implementation:
Serhiy Storchakac6b54b42015-09-29 15:33:24 +0300139 class CUnpicklerTests(PyUnpicklerTests):
140 unpickler = _pickle.Unpickler
Serhiy Storchakae9b30742015-11-23 15:17:43 +0200141 bad_stack_errors = (pickle.UnpicklingError,)
Serhiy Storchaka90493ab2016-09-06 23:55:11 +0300142 truncated_errors = (pickle.UnpicklingError,)
Serhiy Storchakac6b54b42015-09-29 15:33:24 +0300143
Alexandre Vassalottica2d6102008-06-12 18:26:05 +0000144 class CPicklerTests(PyPicklerTests):
145 pickler = _pickle.Pickler
146 unpickler = _pickle.Unpickler
147
148 class CPersPicklerTests(PyPersPicklerTests):
149 pickler = _pickle.Pickler
150 unpickler = _pickle.Unpickler
151
Serhiy Storchakadec25af2016-07-17 11:24:17 +0300152 class CIdPersPicklerTests(PyIdPersPicklerTests):
153 pickler = _pickle.Pickler
154 unpickler = _pickle.Unpickler
155
Collin Winter771d8342009-04-16 03:18:06 +0000156 class CDumpPickle_LoadPickle(PyPicklerTests):
157 pickler = _pickle.Pickler
158 unpickler = pickle._Unpickler
159
160 class DumpPickle_CLoadPickle(PyPicklerTests):
161 pickler = pickle._Pickler
162 unpickler = _pickle.Unpickler
163
164 class CPicklerUnpicklerObjectTests(AbstractPicklerUnpicklerObjectTests):
165 pickler_class = _pickle.Pickler
166 unpickler_class = _pickle.Unpickler
167
Christian Heimesa24b4d22013-07-01 15:17:45 +0200168 def test_issue18339(self):
169 unpickler = self.unpickler_class(io.BytesIO())
Christian Heimes21782482013-07-01 23:00:13 +0200170 with self.assertRaises(TypeError):
171 unpickler.memo = object
Christian Heimesa24b4d22013-07-01 15:17:45 +0200172 # used to cause a segfault
Christian Heimes21782482013-07-01 23:00:13 +0200173 with self.assertRaises(ValueError):
174 unpickler.memo = {-1: None}
Christian Heimesa24b4d22013-07-01 15:17:45 +0200175 unpickler.memo = {1: None}
176
Antoine Pitrou8d3c2902012-03-04 18:31:48 +0100177 class CDispatchTableTests(AbstractDispatchTableTests):
178 pickler_class = pickle.Pickler
179 def get_dispatch_table(self):
180 return pickle.dispatch_table.copy()
181
182 class CChainDispatchTableTests(AbstractDispatchTableTests):
183 pickler_class = pickle.Pickler
184 def get_dispatch_table(self):
185 return collections.ChainMap({}, pickle.dispatch_table)
186
Serhiy Storchaka5bbd2312014-12-16 19:39:08 +0200187 @support.cpython_only
188 class SizeofTests(unittest.TestCase):
189 check_sizeof = support.check_sizeof
190
191 def test_pickler(self):
192 basesize = support.calcobjsize('5P2n3i2n3iP')
193 p = _pickle.Pickler(io.BytesIO())
194 self.assertEqual(object.__sizeof__(p), basesize)
195 MT_size = struct.calcsize('3nP0n')
196 ME_size = struct.calcsize('Pn0P')
197 check = self.check_sizeof
198 check(p, basesize +
199 MT_size + 8 * ME_size + # Minimal memo table size.
200 sys.getsizeof(b'x'*4096)) # Minimal write buffer size.
201 for i in range(6):
202 p.dump(chr(i))
203 check(p, basesize +
204 MT_size + 32 * ME_size + # Size of memo table required to
205 # save references to 6 objects.
206 0) # Write buffer is cleared after every dump().
207
208 def test_unpickler(self):
209 basesize = support.calcobjsize('2Pn2P 2P2n2i5P 2P3n6P2n2i')
210 unpickler = _pickle.Unpickler
211 P = struct.calcsize('P') # Size of memo table entry.
212 n = struct.calcsize('n') # Size of mark table entry.
213 check = self.check_sizeof
214 for encoding in 'ASCII', 'UTF-16', 'latin-1':
215 for errors in 'strict', 'replace':
216 u = unpickler(io.BytesIO(),
217 encoding=encoding, errors=errors)
218 self.assertEqual(object.__sizeof__(u), basesize)
219 check(u, basesize +
220 32 * P + # Minimal memo table size.
221 len(encoding) + 1 + len(errors) + 1)
222
223 stdsize = basesize + len('ASCII') + 1 + len('strict') + 1
224 def check_unpickler(data, memo_size, marks_size):
225 dump = pickle.dumps(data)
226 u = unpickler(io.BytesIO(dump),
227 encoding='ASCII', errors='strict')
228 u.load()
229 check(u, stdsize + memo_size * P + marks_size * n)
230
231 check_unpickler(0, 32, 0)
232 # 20 is minimal non-empty mark stack size.
233 check_unpickler([0] * 100, 32, 20)
234 # 128 is memo table size required to save references to 100 objects.
235 check_unpickler([chr(i) for i in range(100)], 128, 20)
236 def recurse(deep):
237 data = 0
238 for i in range(deep):
239 data = [data, data]
240 return data
241 check_unpickler(recurse(0), 32, 0)
242 check_unpickler(recurse(1), 32, 20)
243 check_unpickler(recurse(20), 32, 58)
244 check_unpickler(recurse(50), 64, 58)
245 check_unpickler(recurse(100), 128, 134)
246
247 u = unpickler(io.BytesIO(pickle.dumps('a', 0)),
248 encoding='ASCII', errors='strict')
249 u.load()
250 check(u, stdsize + 32 * P + 2 + 1)
251
Alexandre Vassalottica2d6102008-06-12 18:26:05 +0000252
Serhiy Storchakabfe18242015-03-31 13:12:37 +0300253ALT_IMPORT_MAPPING = {
254 ('_elementtree', 'xml.etree.ElementTree'),
255 ('cPickle', 'pickle'),
Serhiy Storchaka5c1d9d22016-01-18 22:33:44 +0200256 ('StringIO', 'io'),
257 ('cStringIO', 'io'),
Serhiy Storchakabfe18242015-03-31 13:12:37 +0300258}
259
260ALT_NAME_MAPPING = {
261 ('__builtin__', 'basestring', 'builtins', 'str'),
262 ('exceptions', 'StandardError', 'builtins', 'Exception'),
263 ('UserDict', 'UserDict', 'collections', 'UserDict'),
264 ('socket', '_socketobject', 'socket', 'SocketType'),
265}
266
267def mapping(module, name):
268 if (module, name) in NAME_MAPPING:
269 module, name = NAME_MAPPING[(module, name)]
270 elif module in IMPORT_MAPPING:
271 module = IMPORT_MAPPING[module]
272 return module, name
273
274def reverse_mapping(module, name):
275 if (module, name) in REVERSE_NAME_MAPPING:
276 module, name = REVERSE_NAME_MAPPING[(module, name)]
277 elif module in REVERSE_IMPORT_MAPPING:
278 module = REVERSE_IMPORT_MAPPING[module]
279 return module, name
280
281def getmodule(module):
282 try:
283 return sys.modules[module]
284 except KeyError:
Serhiy Storchakab9100e52015-03-31 16:49:26 +0300285 try:
286 __import__(module)
287 except AttributeError as exc:
288 if support.verbose:
289 print("Can't import module %r: %s" % (module, exc))
290 raise ImportError
291 except ImportError as exc:
292 if support.verbose:
293 print(exc)
294 raise
Serhiy Storchakabfe18242015-03-31 13:12:37 +0300295 return sys.modules[module]
296
297def getattribute(module, name):
298 obj = getmodule(module)
299 for n in name.split('.'):
300 obj = getattr(obj, n)
301 return obj
302
303def get_exceptions(mod):
304 for name in dir(mod):
305 attr = getattr(mod, name)
306 if isinstance(attr, type) and issubclass(attr, BaseException):
307 yield name, attr
308
309class CompatPickleTests(unittest.TestCase):
310 def test_import(self):
311 modules = set(IMPORT_MAPPING.values())
312 modules |= set(REVERSE_IMPORT_MAPPING)
313 modules |= {module for module, name in REVERSE_NAME_MAPPING}
314 modules |= {module for module, name in NAME_MAPPING.values()}
315 for module in modules:
316 try:
317 getmodule(module)
Serhiy Storchakab9100e52015-03-31 16:49:26 +0300318 except ImportError:
319 pass
Serhiy Storchakabfe18242015-03-31 13:12:37 +0300320
321 def test_import_mapping(self):
322 for module3, module2 in REVERSE_IMPORT_MAPPING.items():
323 with self.subTest((module3, module2)):
324 try:
325 getmodule(module3)
Serhiy Storchakab9100e52015-03-31 16:49:26 +0300326 except ImportError:
327 pass
Serhiy Storchakabfe18242015-03-31 13:12:37 +0300328 if module3[:1] != '_':
329 self.assertIn(module2, IMPORT_MAPPING)
330 self.assertEqual(IMPORT_MAPPING[module2], module3)
331
332 def test_name_mapping(self):
333 for (module3, name3), (module2, name2) in REVERSE_NAME_MAPPING.items():
334 with self.subTest(((module3, name3), (module2, name2))):
Serhiy Storchakabfe18242015-03-31 13:12:37 +0300335 if (module2, name2) == ('exceptions', 'OSError'):
Serhiy Storchakab9100e52015-03-31 16:49:26 +0300336 attr = getattribute(module3, name3)
Serhiy Storchakabfe18242015-03-31 13:12:37 +0300337 self.assertTrue(issubclass(attr, OSError))
338 else:
339 module, name = mapping(module2, name2)
340 if module3[:1] != '_':
341 self.assertEqual((module, name), (module3, name3))
Serhiy Storchakab9100e52015-03-31 16:49:26 +0300342 try:
343 attr = getattribute(module3, name3)
344 except ImportError:
345 pass
346 else:
347 self.assertEqual(getattribute(module, name), attr)
Serhiy Storchakabfe18242015-03-31 13:12:37 +0300348
349 def test_reverse_import_mapping(self):
350 for module2, module3 in IMPORT_MAPPING.items():
351 with self.subTest((module2, module3)):
352 try:
353 getmodule(module3)
354 except ImportError as exc:
355 if support.verbose:
356 print(exc)
357 if ((module2, module3) not in ALT_IMPORT_MAPPING and
358 REVERSE_IMPORT_MAPPING.get(module3, None) != module2):
359 for (m3, n3), (m2, n2) in REVERSE_NAME_MAPPING.items():
360 if (module3, module2) == (m3, m2):
361 break
362 else:
363 self.fail('No reverse mapping from %r to %r' %
364 (module3, module2))
365 module = REVERSE_IMPORT_MAPPING.get(module3, module3)
366 module = IMPORT_MAPPING.get(module, module)
367 self.assertEqual(module, module3)
368
369 def test_reverse_name_mapping(self):
370 for (module2, name2), (module3, name3) in NAME_MAPPING.items():
371 with self.subTest(((module2, name2), (module3, name3))):
Serhiy Storchakab9100e52015-03-31 16:49:26 +0300372 try:
373 attr = getattribute(module3, name3)
374 except ImportError:
375 pass
Serhiy Storchakabfe18242015-03-31 13:12:37 +0300376 module, name = reverse_mapping(module3, name3)
377 if (module2, name2, module3, name3) not in ALT_NAME_MAPPING:
378 self.assertEqual((module, name), (module2, name2))
379 module, name = mapping(module, name)
380 self.assertEqual((module, name), (module3, name3))
381
382 def test_exceptions(self):
383 self.assertEqual(mapping('exceptions', 'StandardError'),
384 ('builtins', 'Exception'))
385 self.assertEqual(mapping('exceptions', 'Exception'),
386 ('builtins', 'Exception'))
387 self.assertEqual(reverse_mapping('builtins', 'Exception'),
388 ('exceptions', 'Exception'))
389 self.assertEqual(mapping('exceptions', 'OSError'),
390 ('builtins', 'OSError'))
391 self.assertEqual(reverse_mapping('builtins', 'OSError'),
392 ('exceptions', 'OSError'))
393
394 for name, exc in get_exceptions(builtins):
395 with self.subTest(name):
Yury Selivanov75445082015-05-11 22:57:16 -0400396 if exc in (BlockingIOError,
397 ResourceWarning,
Yury Selivanovf488fb42015-07-03 01:04:23 -0400398 StopAsyncIteration,
399 RecursionError):
Serhiy Storchakabfe18242015-03-31 13:12:37 +0300400 continue
401 if exc is not OSError and issubclass(exc, OSError):
402 self.assertEqual(reverse_mapping('builtins', name),
403 ('exceptions', 'OSError'))
404 else:
405 self.assertEqual(reverse_mapping('builtins', name),
406 ('exceptions', name))
407 self.assertEqual(mapping('exceptions', name),
408 ('builtins', name))
409
Serhiy Storchaka7b2cfc42015-10-10 20:10:07 +0300410 def test_multiprocessing_exceptions(self):
411 module = support.import_module('multiprocessing.context')
412 for name, exc in get_exceptions(module):
Serhiy Storchakabfe18242015-03-31 13:12:37 +0300413 with self.subTest(name):
414 self.assertEqual(reverse_mapping('multiprocessing.context', name),
415 ('multiprocessing', name))
416 self.assertEqual(mapping('multiprocessing', name),
417 ('multiprocessing.context', name))
418
419
Fred Drake694ed092001-12-19 16:42:15 +0000420def test_main():
Serhiy Storchakadec25af2016-07-17 11:24:17 +0300421 tests = [PickleTests, PyUnpicklerTests, PyPicklerTests,
422 PyPersPicklerTests, PyIdPersPicklerTests,
Serhiy Storchakabfe18242015-03-31 13:12:37 +0300423 PyDispatchTableTests, PyChainDispatchTableTests,
424 CompatPickleTests]
Alexandre Vassalottica2d6102008-06-12 18:26:05 +0000425 if has_c_implementation:
Serhiy Storchakadec25af2016-07-17 11:24:17 +0300426 tests.extend([CUnpicklerTests, CPicklerTests,
427 CPersPicklerTests, CIdPersPicklerTests,
Collin Winter771d8342009-04-16 03:18:06 +0000428 CDumpPickle_LoadPickle, DumpPickle_CLoadPickle,
429 PyPicklerUnpicklerObjectTests,
Antoine Pitrouea99c5c2010-09-09 18:33:21 +0000430 CPicklerUnpicklerObjectTests,
Antoine Pitrou8d3c2902012-03-04 18:31:48 +0100431 CDispatchTableTests, CChainDispatchTableTests,
Serhiy Storchaka5bbd2312014-12-16 19:39:08 +0200432 InMemoryPickleTests, SizeofTests])
Alexandre Vassalottica2d6102008-06-12 18:26:05 +0000433 support.run_unittest(*tests)
Benjamin Petersonee8712c2008-05-20 21:35:26 +0000434 support.run_doctest(pickle)
Fred Drake694ed092001-12-19 16:42:15 +0000435
Jeremy Hylton66426532001-10-15 21:38:56 +0000436if __name__ == "__main__":
Fred Drake694ed092001-12-19 16:42:15 +0000437 test_main()