blob: e0180f7c76b51b9d1a46aae01412e6fd2b4b17b6 [file] [log] [blame]
Barry Warsaw41075852001-09-23 03:18:13 +00001# Copyright (C) 2001 Python Software Foundation
2# email package unit tests
3
4import os
5import time
6import unittest
7import base64
8from cStringIO import StringIO
9from types import StringType
10
11import email
12
Barry Warsawbf7a59d2001-10-11 15:44:50 +000013from email.Parser import Parser, HeaderParser
Barry Warsaw41075852001-09-23 03:18:13 +000014from email.Generator import Generator, DecodedGenerator
15from email.Message import Message
Barry Warsawfee435a2001-10-09 19:23:57 +000016from email.MIMEAudio import MIMEAudio
Barry Warsaw65279d02001-09-26 05:47:08 +000017from email.MIMEText import MIMEText
18from email.MIMEImage import MIMEImage
Barry Warsaw41075852001-09-23 03:18:13 +000019from email.MIMEBase import MIMEBase
Barry Warsaw65279d02001-09-26 05:47:08 +000020from email.MIMEMessage import MIMEMessage
Barry Warsaw41075852001-09-23 03:18:13 +000021from email import Utils
22from email import Errors
23from email import Encoders
24from email import Iterators
25
Barry Warsawfee435a2001-10-09 19:23:57 +000026import test_email
27from test_support import findfile
Barry Warsaw41075852001-09-23 03:18:13 +000028
29NL = '\n'
30EMPTYSTRING = ''
Barry Warsaw07227d12001-10-17 20:52:26 +000031SPACE = ' '
Barry Warsaw41075852001-09-23 03:18:13 +000032
33
Barry Warsaw08a534d2001-10-04 17:58:50 +000034
Barry Warsaw41075852001-09-23 03:18:13 +000035def openfile(filename):
Barry Warsawfee435a2001-10-09 19:23:57 +000036 path = os.path.join(os.path.dirname(test_email.__file__), 'data', filename)
Barry Warsaw41075852001-09-23 03:18:13 +000037 return open(path)
38
39
Barry Warsaw08a534d2001-10-04 17:58:50 +000040
Barry Warsaw41075852001-09-23 03:18:13 +000041# Base test class
42class TestEmailBase(unittest.TestCase):
43 def _msgobj(self, filename):
44 fp = openfile(filename)
45 try:
Barry Warsaw65279d02001-09-26 05:47:08 +000046 msg = email.message_from_file(fp)
Barry Warsaw41075852001-09-23 03:18:13 +000047 finally:
48 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +000049 return msg
Barry Warsaw41075852001-09-23 03:18:13 +000050
51
Barry Warsaw08a534d2001-10-04 17:58:50 +000052
Barry Warsaw41075852001-09-23 03:18:13 +000053# Test various aspects of the Message class's API
54class TestMessageAPI(TestEmailBase):
Barry Warsaw2f6a0b02001-10-09 15:49:35 +000055 def test_get_all(self):
56 eq = self.assertEqual
57 msg = self._msgobj('msg_20.txt')
58 eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
59 eq(msg.get_all('xx', 'n/a'), 'n/a')
60
Barry Warsaw41075852001-09-23 03:18:13 +000061 def test_get_charsets(self):
62 eq = self.assertEqual
Tim Peters527e64f2001-10-04 05:36:56 +000063
Barry Warsaw65279d02001-09-26 05:47:08 +000064 msg = self._msgobj('msg_08.txt')
65 charsets = msg.get_charsets()
66 eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +000067
Barry Warsaw65279d02001-09-26 05:47:08 +000068 msg = self._msgobj('msg_09.txt')
69 charsets = msg.get_charsets('dingbat')
70 eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
71 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +000072
Barry Warsaw65279d02001-09-26 05:47:08 +000073 msg = self._msgobj('msg_12.txt')
74 charsets = msg.get_charsets()
75 eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
76 'iso-8859-3', 'us-ascii', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +000077
78 def test_get_filename(self):
79 eq = self.assertEqual
80
Barry Warsaw65279d02001-09-26 05:47:08 +000081 msg = self._msgobj('msg_04.txt')
82 filenames = [p.get_filename() for p in msg.get_payload()]
Barry Warsaw41075852001-09-23 03:18:13 +000083 eq(filenames, ['msg.txt', 'msg.txt'])
84
Barry Warsaw65279d02001-09-26 05:47:08 +000085 msg = self._msgobj('msg_07.txt')
86 subpart = msg.get_payload(1)
Barry Warsaw41075852001-09-23 03:18:13 +000087 eq(subpart.get_filename(), 'dingusfish.gif')
88
89 def test_get_boundary(self):
90 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +000091 msg = self._msgobj('msg_07.txt')
Barry Warsaw41075852001-09-23 03:18:13 +000092 # No quotes!
Barry Warsaw65279d02001-09-26 05:47:08 +000093 eq(msg.get_boundary(), 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +000094
95 def test_set_boundary(self):
96 eq = self.assertEqual
97 # This one has no existing boundary parameter, but the Content-Type:
98 # header appears fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +000099 msg = self._msgobj('msg_01.txt')
100 msg.set_boundary('BOUNDARY')
101 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000102 eq(header.lower(), 'content-type')
103 eq(value, 'text/plain; charset=us-ascii; boundary="BOUNDARY"')
104 # This one has a Content-Type: header, with a boundary, stuck in the
105 # middle of its headers. Make sure the order is preserved; it should
106 # be fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +0000107 msg = self._msgobj('msg_04.txt')
108 msg.set_boundary('BOUNDARY')
109 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000110 eq(header.lower(), 'content-type')
111 eq(value, 'multipart/mixed; boundary="BOUNDARY"')
112 # And this one has no Content-Type: header at all.
Barry Warsaw65279d02001-09-26 05:47:08 +0000113 msg = self._msgobj('msg_03.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000114 self.assertRaises(Errors.HeaderParseError,
Barry Warsaw65279d02001-09-26 05:47:08 +0000115 msg.set_boundary, 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +0000116
117 def test_get_decoded_payload(self):
118 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000119 msg = self._msgobj('msg_10.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000120 # The outer message is a multipart
Barry Warsaw65279d02001-09-26 05:47:08 +0000121 eq(msg.get_payload(decode=1), None)
Barry Warsaw41075852001-09-23 03:18:13 +0000122 # Subpart 1 is 7bit encoded
Barry Warsaw65279d02001-09-26 05:47:08 +0000123 eq(msg.get_payload(0).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000124 'This is a 7bit encoded message.\n')
125 # Subpart 2 is quopri
Barry Warsaw65279d02001-09-26 05:47:08 +0000126 eq(msg.get_payload(1).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000127 '\xa1This is a Quoted Printable encoded message!\n')
128 # Subpart 3 is base64
Barry Warsaw65279d02001-09-26 05:47:08 +0000129 eq(msg.get_payload(2).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000130 'This is a Base64 encoded message.')
131 # Subpart 4 has no Content-Transfer-Encoding: header.
Barry Warsaw65279d02001-09-26 05:47:08 +0000132 eq(msg.get_payload(3).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000133 'This has no Content-Transfer-Encoding: header.\n')
134
135 def test_decoded_generator(self):
136 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000137 msg = self._msgobj('msg_07.txt')
138 fp = openfile('msg_17.txt')
139 try:
140 text = fp.read()
141 finally:
142 fp.close()
Barry Warsaw41075852001-09-23 03:18:13 +0000143 s = StringIO()
144 g = DecodedGenerator(s)
Barry Warsaw65279d02001-09-26 05:47:08 +0000145 g(msg)
146 eq(s.getvalue(), text)
Barry Warsaw41075852001-09-23 03:18:13 +0000147
148 def test__contains__(self):
149 msg = Message()
150 msg['From'] = 'Me'
151 msg['to'] = 'You'
152 # Check for case insensitivity
153 self.failUnless('from' in msg)
154 self.failUnless('From' in msg)
155 self.failUnless('FROM' in msg)
156 self.failUnless('to' in msg)
157 self.failUnless('To' in msg)
158 self.failUnless('TO' in msg)
159
160 def test_as_string(self):
161 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000162 msg = self._msgobj('msg_01.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000163 fp = openfile('msg_01.txt')
164 try:
165 text = fp.read()
166 finally:
167 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000168 eq(text, msg.as_string())
169 fullrepr = str(msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000170 lines = fullrepr.split('\n')
171 self.failUnless(lines[0].startswith('From '))
172 eq(text, NL.join(lines[1:]))
173
174 def test_bad_param(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000175 msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000176 self.assertEqual(msg.get_param('baz'), '')
177
178 def test_missing_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000179 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000180 self.assertEqual(msg.get_filename(), None)
181
182 def test_bogus_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000183 msg = email.message_from_string(
184 "Content-Disposition: blarg; filename\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000185 self.assertEqual(msg.get_filename(), '')
Tim Peters527e64f2001-10-04 05:36:56 +0000186
Barry Warsaw41075852001-09-23 03:18:13 +0000187 def test_missing_boundary(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000188 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000189 self.assertEqual(msg.get_boundary(), None)
190
Barry Warsaw65279d02001-09-26 05:47:08 +0000191 def test_get_params(self):
192 eq = self.assertEqual
193 msg = email.message_from_string(
194 'X-Header: foo=one; bar=two; baz=three\n')
195 eq(msg.get_params(header='x-header'),
196 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
197 msg = email.message_from_string(
198 'X-Header: foo; bar=one; baz=two\n')
199 eq(msg.get_params(header='x-header'),
200 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
201 eq(msg.get_params(), None)
202 msg = email.message_from_string(
203 'X-Header: foo; bar="one"; baz=two\n')
204 eq(msg.get_params(header='x-header'),
205 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
206
207 def test_get_param(self):
208 eq = self.assertEqual
209 msg = email.message_from_string(
210 "X-Header: foo=one; bar=two; baz=three\n")
211 eq(msg.get_param('bar', header='x-header'), 'two')
212 eq(msg.get_param('quuz', header='x-header'), None)
213 eq(msg.get_param('quuz'), None)
214 msg = email.message_from_string(
215 'X-Header: foo; bar="one"; baz=two\n')
216 eq(msg.get_param('foo', header='x-header'), '')
217 eq(msg.get_param('bar', header='x-header'), 'one')
218 eq(msg.get_param('baz', header='x-header'), 'two')
219
220 def test_has_key(self):
221 msg = email.message_from_string('Header: exists')
222 self.failUnless(msg.has_key('header'))
223 self.failUnless(msg.has_key('Header'))
224 self.failUnless(msg.has_key('HEADER'))
225 self.failIf(msg.has_key('headeri'))
226
Barry Warsaw41075852001-09-23 03:18:13 +0000227
Barry Warsaw08a534d2001-10-04 17:58:50 +0000228
Barry Warsaw41075852001-09-23 03:18:13 +0000229# Test the email.Encoders module
230class TestEncoders(unittest.TestCase):
231 def test_encode_noop(self):
232 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000233 msg = MIMEText('hello world', _encoder=Encoders.encode_noop)
Barry Warsaw41075852001-09-23 03:18:13 +0000234 eq(msg.get_payload(), 'hello world\n')
235 eq(msg['content-transfer-encoding'], None)
236
237 def test_encode_7bit(self):
238 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000239 msg = MIMEText('hello world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000240 eq(msg.get_payload(), 'hello world\n')
241 eq(msg['content-transfer-encoding'], '7bit')
Barry Warsaw65279d02001-09-26 05:47:08 +0000242 msg = MIMEText('hello \x7f world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000243 eq(msg.get_payload(), 'hello \x7f world\n')
244 eq(msg['content-transfer-encoding'], '7bit')
245
246 def test_encode_8bit(self):
247 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000248 msg = MIMEText('hello \x80 world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000249 eq(msg.get_payload(), 'hello \x80 world\n')
250 eq(msg['content-transfer-encoding'], '8bit')
251
252 def test_encode_base64(self):
253 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000254 msg = MIMEText('hello world', _encoder=Encoders.encode_base64)
Barry Warsaw41075852001-09-23 03:18:13 +0000255 eq(msg.get_payload(), 'aGVsbG8gd29ybGQK\n')
256 eq(msg['content-transfer-encoding'], 'base64')
257
258 def test_encode_quoted_printable(self):
259 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000260 msg = MIMEText('hello world', _encoder=Encoders.encode_quopri)
Barry Warsaw41075852001-09-23 03:18:13 +0000261 eq(msg.get_payload(), 'hello=20world\n')
262 eq(msg['content-transfer-encoding'], 'quoted-printable')
263
264
Barry Warsaw08a534d2001-10-04 17:58:50 +0000265
266# Test long header wrapping
Barry Warsaw41075852001-09-23 03:18:13 +0000267class TestLongHeaders(unittest.TestCase):
268 def test_header_splitter(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000269 msg = MIMEText('')
Barry Warsaw41075852001-09-23 03:18:13 +0000270 # It'd be great if we could use add_header() here, but that doesn't
271 # guarantee an order of the parameters.
272 msg['X-Foobar-Spoink-Defrobnit'] = (
273 'wasnipoop; giraffes="very-long-necked-animals"; '
274 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
275 sfp = StringIO()
276 g = Generator(sfp)
277 g(msg)
Barry Warsaw08a534d2001-10-04 17:58:50 +0000278 self.assertEqual(sfp.getvalue(), openfile('msg_18.txt').read())
Barry Warsaw41075852001-09-23 03:18:13 +0000279
Barry Warsaw07227d12001-10-17 20:52:26 +0000280 def test_no_semis_header_splitter(self):
281 msg = Message()
282 msg['From'] = 'test@dom.ain'
283 refparts = []
284 for i in range(10):
285 refparts.append('<%d@dom.ain>' % i)
286 msg['References'] = SPACE.join(refparts)
287 msg.set_payload('Test')
288 sfp = StringIO()
289 g = Generator(sfp)
290 g(msg)
291 self.assertEqual(sfp.getvalue(), """\
292From: test@dom.ain
293References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
294 <5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
295
296Test""")
297
298 def test_no_split_long_header(self):
299 msg = Message()
300 msg['From'] = 'test@dom.ain'
301 refparts = []
302 msg['References'] = 'x' * 80
303 msg.set_payload('Test')
304 sfp = StringIO()
305 g = Generator(sfp)
306 g(msg)
307 self.assertEqual(sfp.getvalue(), """\
308From: test@dom.ain
309References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
310
311Test""")
312
Barry Warsaw41075852001-09-23 03:18:13 +0000313
Barry Warsaw08a534d2001-10-04 17:58:50 +0000314
315# Test mangling of "From " lines in the body of a message
Barry Warsaw41075852001-09-23 03:18:13 +0000316class TestFromMangling(unittest.TestCase):
317 def setUp(self):
318 self.msg = Message()
319 self.msg['From'] = 'aaa@bbb.org'
320 self.msg.add_payload("""\
321From the desk of A.A.A.:
322Blah blah blah
323""")
324
325 def test_mangled_from(self):
326 s = StringIO()
327 g = Generator(s, mangle_from_=1)
328 g(self.msg)
329 self.assertEqual(s.getvalue(), """\
330From: aaa@bbb.org
331
332>From the desk of A.A.A.:
333Blah blah blah
334""")
335
336 def test_dont_mangle_from(self):
337 s = StringIO()
338 g = Generator(s, mangle_from_=0)
339 g(self.msg)
340 self.assertEqual(s.getvalue(), """\
341From: aaa@bbb.org
342
343From the desk of A.A.A.:
344Blah blah blah
345""")
346
347
Barry Warsaw08a534d2001-10-04 17:58:50 +0000348
Barry Warsawfee435a2001-10-09 19:23:57 +0000349# Test the basic MIMEAudio class
350class TestMIMEAudio(unittest.TestCase):
351 def setUp(self):
352 # In Python, audiotest.au lives in Lib/test not Lib/test/data
353 fp = open(findfile('audiotest.au'))
354 try:
355 self._audiodata = fp.read()
356 finally:
357 fp.close()
358 self._au = MIMEAudio(self._audiodata)
359
360 def test_guess_minor_type(self):
361 self.assertEqual(self._au.get_type(), 'audio/basic')
362
363 def test_encoding(self):
364 payload = self._au.get_payload()
365 self.assertEqual(base64.decodestring(payload), self._audiodata)
366
367 def checkSetMinor(self):
368 au = MIMEAudio(self._audiodata, 'fish')
369 self.assertEqual(im.get_type(), 'audio/fish')
370
371 def test_custom_encoder(self):
372 eq = self.assertEqual
373 def encoder(msg):
374 orig = msg.get_payload()
375 msg.set_payload(0)
376 msg['Content-Transfer-Encoding'] = 'broken64'
377 au = MIMEAudio(self._audiodata, _encoder=encoder)
378 eq(au.get_payload(), 0)
379 eq(au['content-transfer-encoding'], 'broken64')
380
381 def test_add_header(self):
382 eq = self.assertEqual
383 unless = self.failUnless
384 self._au.add_header('Content-Disposition', 'attachment',
385 filename='audiotest.au')
386 eq(self._au['content-disposition'],
387 'attachment; filename="audiotest.au"')
388 eq(self._au.get_params(header='content-disposition'),
389 [('attachment', ''), ('filename', 'audiotest.au')])
390 eq(self._au.get_param('filename', header='content-disposition'),
391 'audiotest.au')
392 missing = []
393 eq(self._au.get_param('attachment', header='content-disposition'), '')
394 unless(self._au.get_param('foo', failobj=missing,
395 header='content-disposition') is missing)
396 # Try some missing stuff
397 unless(self._au.get_param('foobar', missing) is missing)
398 unless(self._au.get_param('attachment', missing,
399 header='foobar') is missing)
400
401
402
Barry Warsaw65279d02001-09-26 05:47:08 +0000403# Test the basic MIMEImage class
404class TestMIMEImage(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000405 def setUp(self):
406 fp = openfile('PyBanner048.gif')
407 try:
408 self._imgdata = fp.read()
409 finally:
410 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000411 self._im = MIMEImage(self._imgdata)
Barry Warsaw41075852001-09-23 03:18:13 +0000412
413 def test_guess_minor_type(self):
414 self.assertEqual(self._im.get_type(), 'image/gif')
415
416 def test_encoding(self):
417 payload = self._im.get_payload()
418 self.assertEqual(base64.decodestring(payload), self._imgdata)
419
420 def checkSetMinor(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000421 im = MIMEImage(self._imgdata, 'fish')
Barry Warsaw41075852001-09-23 03:18:13 +0000422 self.assertEqual(im.get_type(), 'image/fish')
423
424 def test_custom_encoder(self):
425 eq = self.assertEqual
426 def encoder(msg):
427 orig = msg.get_payload()
428 msg.set_payload(0)
429 msg['Content-Transfer-Encoding'] = 'broken64'
Barry Warsaw65279d02001-09-26 05:47:08 +0000430 im = MIMEImage(self._imgdata, _encoder=encoder)
Barry Warsaw41075852001-09-23 03:18:13 +0000431 eq(im.get_payload(), 0)
432 eq(im['content-transfer-encoding'], 'broken64')
433
434 def test_add_header(self):
435 eq = self.assertEqual
436 unless = self.failUnless
437 self._im.add_header('Content-Disposition', 'attachment',
438 filename='dingusfish.gif')
439 eq(self._im['content-disposition'],
440 'attachment; filename="dingusfish.gif"')
441 eq(self._im.get_params(header='content-disposition'),
Barry Warsaw65279d02001-09-26 05:47:08 +0000442 [('attachment', ''), ('filename', 'dingusfish.gif')])
Barry Warsaw41075852001-09-23 03:18:13 +0000443 eq(self._im.get_param('filename', header='content-disposition'),
444 'dingusfish.gif')
445 missing = []
Barry Warsaw65279d02001-09-26 05:47:08 +0000446 eq(self._im.get_param('attachment', header='content-disposition'), '')
447 unless(self._im.get_param('foo', failobj=missing,
Barry Warsaw41075852001-09-23 03:18:13 +0000448 header='content-disposition') is missing)
449 # Try some missing stuff
450 unless(self._im.get_param('foobar', missing) is missing)
451 unless(self._im.get_param('attachment', missing,
452 header='foobar') is missing)
453
454
Barry Warsaw08a534d2001-10-04 17:58:50 +0000455
Barry Warsaw65279d02001-09-26 05:47:08 +0000456# Test the basic MIMEText class
457class TestMIMEText(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000458 def setUp(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000459 self._msg = MIMEText('hello there')
Barry Warsaw41075852001-09-23 03:18:13 +0000460
461 def test_types(self):
462 eq = self.assertEqual
463 unless = self.failUnless
464 eq(self._msg.get_type(), 'text/plain')
465 eq(self._msg.get_param('charset'), 'us-ascii')
466 missing = []
467 unless(self._msg.get_param('foobar', missing) is missing)
468 unless(self._msg.get_param('charset', missing, header='foobar')
469 is missing)
470
471 def test_payload(self):
472 self.assertEqual(self._msg.get_payload(), 'hello there\n')
473 self.failUnless(not self._msg.is_multipart())
474
475
Barry Warsaw08a534d2001-10-04 17:58:50 +0000476
477# Test a more complicated multipart/mixed type message
Barry Warsaw41075852001-09-23 03:18:13 +0000478class TestMultipartMixed(unittest.TestCase):
479 def setUp(self):
480 fp = openfile('PyBanner048.gif')
481 try:
482 data = fp.read()
483 finally:
484 fp.close()
485
486 container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
Barry Warsaw65279d02001-09-26 05:47:08 +0000487 image = MIMEImage(data, name='dingusfish.gif')
Barry Warsaw41075852001-09-23 03:18:13 +0000488 image.add_header('content-disposition', 'attachment',
489 filename='dingusfish.gif')
Barry Warsaw65279d02001-09-26 05:47:08 +0000490 intro = MIMEText('''\
Barry Warsaw41075852001-09-23 03:18:13 +0000491Hi there,
492
493This is the dingus fish.
494''')
495 container.add_payload(intro)
496 container.add_payload(image)
497 container['From'] = 'Barry <barry@digicool.com>'
498 container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
499 container['Subject'] = 'Here is your dingus fish'
Tim Peters527e64f2001-10-04 05:36:56 +0000500
Barry Warsaw41075852001-09-23 03:18:13 +0000501 now = 987809702.54848599
502 timetuple = time.localtime(now)
503 if timetuple[-1] == 0:
504 tzsecs = time.timezone
505 else:
506 tzsecs = time.altzone
507 if tzsecs > 0:
508 sign = '-'
509 else:
510 sign = '+'
511 tzoffset = ' %s%04d' % (sign, tzsecs / 36)
512 container['Date'] = time.strftime(
513 '%a, %d %b %Y %H:%M:%S',
514 time.localtime(now)) + tzoffset
515 self._msg = container
516 self._im = image
517 self._txt = intro
518
519 def test_hierarchy(self):
520 # convenience
521 eq = self.assertEqual
522 unless = self.failUnless
523 raises = self.assertRaises
524 # tests
525 m = self._msg
526 unless(m.is_multipart())
527 eq(m.get_type(), 'multipart/mixed')
528 eq(len(m.get_payload()), 2)
529 raises(IndexError, m.get_payload, 2)
530 m0 = m.get_payload(0)
531 m1 = m.get_payload(1)
532 unless(m0 is self._txt)
533 unless(m1 is self._im)
534 eq(m.get_payload(), [m0, m1])
535 unless(not m0.is_multipart())
536 unless(not m1.is_multipart())
537
538
Barry Warsaw08a534d2001-10-04 17:58:50 +0000539
540# Test some badly formatted messages
Barry Warsaw41075852001-09-23 03:18:13 +0000541class TestNonConformant(TestEmailBase):
542 def test_parse_missing_minor_type(self):
543 eq = self.assertEqual
544 msg = self._msgobj('msg_14.txt')
545 eq(msg.get_type(), 'text')
546 eq(msg.get_main_type(), 'text')
547 self.failUnless(msg.get_subtype() is None)
548
549 def test_bogus_boundary(self):
550 fp = openfile('msg_15.txt')
551 try:
552 data = fp.read()
553 finally:
554 fp.close()
555 p = Parser()
556 # Note, under a future non-strict parsing mode, this would parse the
557 # message into the intended message tree.
558 self.assertRaises(Errors.BoundaryError, p.parsestr, data)
559
560
Barry Warsaw08a534d2001-10-04 17:58:50 +0000561
562# Test RFC 2047 header encoding and decoding
Barry Warsaw41075852001-09-23 03:18:13 +0000563class TestRFC2047(unittest.TestCase):
564 def test_iso_8859_1(self):
565 eq = self.assertEqual
566 s = '=?iso-8859-1?q?this=20is=20some=20text?='
567 eq(Utils.decode(s), 'this is some text')
568 s = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
569 eq(Utils.decode(s), u'Keld_J\xf8rn_Simonsen')
570 s = '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
571 '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
572 eq(Utils.decode(s), 'If you can read this you understand the example.')
573 s = '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
574 eq(Utils.decode(s),
575 u'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
576 s = '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
577 eq(Utils.decode(s), u'this is some text')
578
579 def test_encode_header(self):
580 eq = self.assertEqual
581 s = 'this is some text'
582 eq(Utils.encode(s), '=?iso-8859-1?q?this=20is=20some=20text?=')
583 s = 'Keld_J\xf8rn_Simonsen'
584 eq(Utils.encode(s), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
585 s1 = 'If you can read this yo'
586 s2 = 'u understand the example.'
587 eq(Utils.encode(s1, encoding='b'),
588 '=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
589 eq(Utils.encode(s2, charset='iso-8859-2', encoding='b'),
590 '=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')
591
592
Barry Warsaw08a534d2001-10-04 17:58:50 +0000593
594# Test the MIMEMessage class
Barry Warsaw65279d02001-09-26 05:47:08 +0000595class TestMIMEMessage(TestEmailBase):
Barry Warsaw41075852001-09-23 03:18:13 +0000596 def setUp(self):
597 fp = openfile('msg_11.txt')
598 self._text = fp.read()
599 fp.close()
600
601 def test_type_error(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000602 self.assertRaises(TypeError, MIMEMessage, 'a plain string')
Barry Warsaw41075852001-09-23 03:18:13 +0000603
604 def test_valid_argument(self):
605 eq = self.assertEqual
606 subject = 'A sub-message'
607 m = Message()
608 m['Subject'] = subject
Barry Warsaw65279d02001-09-26 05:47:08 +0000609 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000610 eq(r.get_type(), 'message/rfc822')
611 self.failUnless(r.get_payload() is m)
612 eq(r.get_payload()['subject'], subject)
613
614 def test_generate(self):
615 # First craft the message to be encapsulated
616 m = Message()
617 m['Subject'] = 'An enclosed message'
618 m.add_payload('Here is the body of the message.\n')
Barry Warsaw65279d02001-09-26 05:47:08 +0000619 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000620 r['Subject'] = 'The enclosing message'
621 s = StringIO()
622 g = Generator(s)
623 g(r)
Barry Warsaw65279d02001-09-26 05:47:08 +0000624 self.assertEqual(s.getvalue(), """\
625Content-Type: message/rfc822
626MIME-Version: 1.0
627Subject: The enclosing message
628
629Subject: An enclosed message
630
631Here is the body of the message.
632""")
633
634 def test_parse_message_rfc822(self):
635 eq = self.assertEqual
636 msg = self._msgobj('msg_11.txt')
637 eq(msg.get_type(), 'message/rfc822')
638 eq(len(msg.get_payload()), 1)
639 submsg = msg.get_payload()
640 self.failUnless(isinstance(submsg, Message))
641 eq(submsg['subject'], 'An enclosed message')
642 eq(submsg.get_payload(), 'Here is the body of the message.\n')
643
644 def test_dsn(self):
645 eq = self.assertEqual
646 unless = self.failUnless
647 # msg 16 is a Delivery Status Notification, see RFC XXXX
648 msg = self._msgobj('msg_16.txt')
649 eq(msg.get_type(), 'multipart/report')
650 unless(msg.is_multipart())
651 eq(len(msg.get_payload()), 3)
652 # Subpart 1 is a text/plain, human readable section
653 subpart = msg.get_payload(0)
654 eq(subpart.get_type(), 'text/plain')
655 eq(subpart.get_payload(), """\
656This report relates to a message you sent with the following header fields:
657
658 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
659 Date: Sun, 23 Sep 2001 20:10:55 -0700
660 From: "Ian T. Henry" <henryi@oxy.edu>
661 To: SoCal Raves <scr@socal-raves.org>
662 Subject: [scr] yeah for Ians!!
663
664Your message cannot be delivered to the following recipients:
665
666 Recipient address: jangel1@cougar.noc.ucla.edu
667 Reason: recipient reached disk quota
668
669""")
670 # Subpart 2 contains the machine parsable DSN information. It
671 # consists of two blocks of headers, represented by two nested Message
672 # objects.
673 subpart = msg.get_payload(1)
674 eq(subpart.get_type(), 'message/delivery-status')
675 eq(len(subpart.get_payload()), 2)
676 # message/delivery-status should treat each block as a bunch of
677 # headers, i.e. a bunch of Message objects.
678 dsn1 = subpart.get_payload(0)
679 unless(isinstance(dsn1, Message))
680 eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
681 eq(dsn1.get_param('dns', header='reporting-mta'), '')
682 # Try a missing one <wink>
683 eq(dsn1.get_param('nsd', header='reporting-mta'), None)
684 dsn2 = subpart.get_payload(1)
685 unless(isinstance(dsn2, Message))
686 eq(dsn2['action'], 'failed')
687 eq(dsn2.get_params(header='original-recipient'),
688 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
689 eq(dsn2.get_param('rfc822', header='final-recipient'), '')
690 # Subpart 3 is the original message
691 subpart = msg.get_payload(2)
692 eq(subpart.get_type(), 'message/rfc822')
693 subsubpart = subpart.get_payload()
694 unless(isinstance(subsubpart, Message))
695 eq(subsubpart.get_type(), 'text/plain')
696 eq(subsubpart['message-id'],
697 '<002001c144a6$8752e060$56104586@oxy.edu>')
Barry Warsaw41075852001-09-23 03:18:13 +0000698
699
Barry Warsaw08a534d2001-10-04 17:58:50 +0000700
701# A general test of parser->model->generator idempotency. IOW, read a message
702# in, parse it into a message object tree, then without touching the tree,
703# regenerate the plain text. The original text and the transformed text
704# should be identical. Note: that we ignore the Unix-From since that may
705# contain a changed date.
Barry Warsaw41075852001-09-23 03:18:13 +0000706class TestIdempotent(unittest.TestCase):
707 def _msgobj(self, filename):
708 fp = openfile(filename)
709 try:
710 data = fp.read()
711 finally:
712 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000713 msg = email.message_from_string(data)
714 return msg, data
Barry Warsaw41075852001-09-23 03:18:13 +0000715
716 def _idempotent(self, msg, text):
717 eq = self.assertEquals
718 s = StringIO()
719 g = Generator(s, maxheaderlen=0)
720 g(msg)
721 eq(text, s.getvalue())
722
723 def test_parse_text_message(self):
724 eq = self.assertEquals
725 msg, text = self._msgobj('msg_01.txt')
726 eq(msg.get_type(), 'text/plain')
727 eq(msg.get_main_type(), 'text')
728 eq(msg.get_subtype(), 'plain')
Barry Warsaw65279d02001-09-26 05:47:08 +0000729 eq(msg.get_params()[1], ('charset', 'us-ascii'))
Barry Warsaw41075852001-09-23 03:18:13 +0000730 eq(msg.get_param('charset'), 'us-ascii')
731 eq(msg.preamble, None)
732 eq(msg.epilogue, None)
733 self._idempotent(msg, text)
734
735 def test_parse_untyped_message(self):
736 eq = self.assertEquals
737 msg, text = self._msgobj('msg_03.txt')
738 eq(msg.get_type(), None)
739 eq(msg.get_params(), None)
740 eq(msg.get_param('charset'), None)
741 self._idempotent(msg, text)
742
743 def test_simple_multipart(self):
744 msg, text = self._msgobj('msg_04.txt')
745 self._idempotent(msg, text)
746
747 def test_MIME_digest(self):
748 msg, text = self._msgobj('msg_02.txt')
749 self._idempotent(msg, text)
750
751 def test_mixed_with_image(self):
752 msg, text = self._msgobj('msg_06.txt')
753 self._idempotent(msg, text)
Tim Peters527e64f2001-10-04 05:36:56 +0000754
Barry Warsaw41075852001-09-23 03:18:13 +0000755 def test_multipart_report(self):
756 msg, text = self._msgobj('msg_05.txt')
757 self._idempotent(msg, text)
Barry Warsaw65279d02001-09-26 05:47:08 +0000758
759 def test_dsn(self):
760 msg, text = self._msgobj('msg_16.txt')
761 self._idempotent(msg, text)
Tim Peters527e64f2001-10-04 05:36:56 +0000762
Barry Warsaw41075852001-09-23 03:18:13 +0000763 def test_content_type(self):
764 eq = self.assertEquals
765 # Get a message object and reset the seek pointer for other tests
766 msg, text = self._msgobj('msg_05.txt')
767 eq(msg.get_type(), 'multipart/report')
768 # Test the Content-Type: parameters
769 params = {}
Barry Warsaw65279d02001-09-26 05:47:08 +0000770 for pk, pv in msg.get_params():
Barry Warsaw41075852001-09-23 03:18:13 +0000771 params[pk] = pv
772 eq(params['report-type'], 'delivery-status')
Barry Warsaw65279d02001-09-26 05:47:08 +0000773 eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
Barry Warsaw41075852001-09-23 03:18:13 +0000774 eq(msg.preamble, 'This is a MIME-encapsulated message.\n\n')
775 eq(msg.epilogue, '\n\n')
776 eq(len(msg.get_payload()), 3)
777 # Make sure the subparts are what we expect
778 msg1 = msg.get_payload(0)
779 eq(msg1.get_type(), 'text/plain')
780 eq(msg1.get_payload(), 'Yadda yadda yadda\n')
781 msg2 = msg.get_payload(1)
782 eq(msg2.get_type(), None)
783 eq(msg2.get_payload(), 'Yadda yadda yadda\n')
784 msg3 = msg.get_payload(2)
785 eq(msg3.get_type(), 'message/rfc822')
786 self.failUnless(isinstance(msg3, Message))
787 msg4 = msg3.get_payload()
788 self.failUnless(isinstance(msg4, Message))
789 eq(msg4.get_payload(), 'Yadda yadda yadda\n')
790
791 def test_parser(self):
792 eq = self.assertEquals
793 msg, text = self._msgobj('msg_06.txt')
794 # Check some of the outer headers
795 eq(msg.get_type(), 'message/rfc822')
796 # Make sure there's exactly one thing in the payload and that's a
797 # sub-Message object of type text/plain
798 msg1 = msg.get_payload()
799 self.failUnless(isinstance(msg1, Message))
800 eq(msg1.get_type(), 'text/plain')
801 self.failUnless(isinstance(msg1.get_payload(), StringType))
802 eq(msg1.get_payload(), '\n')
Barry Warsaw41075852001-09-23 03:18:13 +0000803
Tim Peters527e64f2001-10-04 05:36:56 +0000804
Barry Warsaw08a534d2001-10-04 17:58:50 +0000805
806# Test various other bits of the package's functionality
Barry Warsaw41075852001-09-23 03:18:13 +0000807class TestMiscellaneous(unittest.TestCase):
808 def test_message_from_string(self):
809 fp = openfile('msg_01.txt')
810 try:
811 text = fp.read()
812 finally:
813 fp.close()
814 msg = email.message_from_string(text)
815 s = StringIO()
816 # Don't wrap/continue long headers since we're trying to test
817 # idempotency.
818 g = Generator(s, maxheaderlen=0)
819 g(msg)
820 self.assertEqual(text, s.getvalue())
821
822 def test_message_from_file(self):
823 fp = openfile('msg_01.txt')
824 try:
825 text = fp.read()
826 fp.seek(0)
827 msg = email.message_from_file(fp)
828 s = StringIO()
829 # Don't wrap/continue long headers since we're trying to test
830 # idempotency.
831 g = Generator(s, maxheaderlen=0)
832 g(msg)
833 self.assertEqual(text, s.getvalue())
834 finally:
835 fp.close()
836
837 def test_message_from_string_with_class(self):
838 unless = self.failUnless
839 fp = openfile('msg_01.txt')
840 try:
841 text = fp.read()
842 finally:
843 fp.close()
844 # Create a subclass
845 class MyMessage(Message):
846 pass
Tim Peters527e64f2001-10-04 05:36:56 +0000847
Barry Warsaw41075852001-09-23 03:18:13 +0000848 msg = email.message_from_string(text, MyMessage)
849 unless(isinstance(msg, MyMessage))
850 # Try something more complicated
851 fp = openfile('msg_02.txt')
852 try:
853 text = fp.read()
854 finally:
855 fp.close()
856 msg = email.message_from_string(text, MyMessage)
857 for subpart in msg.walk():
858 unless(isinstance(subpart, MyMessage))
859
Barry Warsaw41075852001-09-23 03:18:13 +0000860 def test_message_from_file_with_class(self):
861 unless = self.failUnless
862 # Create a subclass
863 class MyMessage(Message):
864 pass
Tim Peters527e64f2001-10-04 05:36:56 +0000865
Barry Warsaw41075852001-09-23 03:18:13 +0000866 fp = openfile('msg_01.txt')
867 try:
868 msg = email.message_from_file(fp, MyMessage)
869 finally:
870 fp.close()
871 unless(isinstance(msg, MyMessage))
872 # Try something more complicated
873 fp = openfile('msg_02.txt')
874 try:
875 msg = email.message_from_file(fp, MyMessage)
876 finally:
877 fp.close()
878 for subpart in msg.walk():
879 unless(isinstance(subpart, MyMessage))
880
Barry Warsawfee435a2001-10-09 19:23:57 +0000881 def test__all__(self):
882 module = __import__('email')
883 all = module.__all__
884 all.sort()
885 self.assertEqual(all, ['Encoders', 'Errors', 'Generator', 'Iterators',
886 'MIMEAudio', 'MIMEBase', 'MIMEImage',
887 'MIMEMessage', 'MIMEText', 'Message', 'Parser',
888 'Utils',
889 'message_from_file', 'message_from_string'])
890
Barry Warsaw41075852001-09-23 03:18:13 +0000891
Barry Warsaw08a534d2001-10-04 17:58:50 +0000892
893# Test the iterator/generators
Barry Warsaw41075852001-09-23 03:18:13 +0000894class TestIterators(TestEmailBase):
895 def test_body_line_iterator(self):
896 eq = self.assertEqual
897 # First a simple non-multipart message
898 msg = self._msgobj('msg_01.txt')
899 it = Iterators.body_line_iterator(msg)
Barry Warsawd1de6ea2001-10-04 18:18:37 +0000900 lines = list(it)
Barry Warsaw41075852001-09-23 03:18:13 +0000901 eq(len(lines), 6)
902 eq(EMPTYSTRING.join(lines), msg.get_payload())
903 # Now a more complicated multipart
904 msg = self._msgobj('msg_02.txt')
905 it = Iterators.body_line_iterator(msg)
Barry Warsawd1de6ea2001-10-04 18:18:37 +0000906 lines = list(it)
Barry Warsaw41075852001-09-23 03:18:13 +0000907 eq(len(lines), 43)
Barry Warsaw08a534d2001-10-04 17:58:50 +0000908 eq(EMPTYSTRING.join(lines), openfile('msg_19.txt').read())
Barry Warsaw41075852001-09-23 03:18:13 +0000909
910 def test_typed_subpart_iterator(self):
911 eq = self.assertEqual
912 msg = self._msgobj('msg_04.txt')
913 it = Iterators.typed_subpart_iterator(msg, 'text')
Barry Warsawd1de6ea2001-10-04 18:18:37 +0000914 lines = [subpart.get_payload() for subpart in it]
915 eq(len(lines), 2)
Barry Warsaw41075852001-09-23 03:18:13 +0000916 eq(EMPTYSTRING.join(lines), """\
917a simple kind of mirror
918to reflect upon our own
919a simple kind of mirror
920to reflect upon our own
921""")
922
Barry Warsawcdc632c2001-10-15 04:39:02 +0000923 def test_typed_subpart_iterator_default_type(self):
924 eq = self.assertEqual
925 msg = self._msgobj('msg_03.txt')
926 it = Iterators.typed_subpart_iterator(msg, 'text', 'plain')
927 lines = []
928 subparts = 0
929 for subpart in it:
930 subparts += 1
931 lines.append(subpart.get_payload())
932 eq(subparts, 1)
933 eq(EMPTYSTRING.join(lines), """\
934
935Hi,
936
937Do you like this message?
938
939-Me
940""")
Barry Warsaw41075852001-09-23 03:18:13 +0000941
Barry Warsaw08a534d2001-10-04 17:58:50 +0000942
Barry Warsawbf7a59d2001-10-11 15:44:50 +0000943class TestParsers(unittest.TestCase):
944 def test_header_parser(self):
945 eq = self.assertEqual
946 # Parse only the headers of a complex multipart MIME document
947 p = HeaderParser()
948 fp = openfile('msg_02.txt')
949 msg = p.parse(fp)
950 eq(msg['from'], 'ppp-request@zzz.org')
951 eq(msg['to'], 'ppp@zzz.org')
952 eq(msg.get_type(), 'multipart/mixed')
953 eq(msg.is_multipart(), 0)
954 self.failUnless(isinstance(msg.get_payload(), StringType))
955
956
957
Barry Warsaw41075852001-09-23 03:18:13 +0000958def suite():
959 suite = unittest.TestSuite()
960 suite.addTest(unittest.makeSuite(TestMessageAPI))
961 suite.addTest(unittest.makeSuite(TestEncoders))
962 suite.addTest(unittest.makeSuite(TestLongHeaders))
963 suite.addTest(unittest.makeSuite(TestFromMangling))
Barry Warsawfee435a2001-10-09 19:23:57 +0000964 suite.addTest(unittest.makeSuite(TestMIMEAudio))
Barry Warsaw65279d02001-09-26 05:47:08 +0000965 suite.addTest(unittest.makeSuite(TestMIMEImage))
966 suite.addTest(unittest.makeSuite(TestMIMEText))
Barry Warsaw41075852001-09-23 03:18:13 +0000967 suite.addTest(unittest.makeSuite(TestMultipartMixed))
968 suite.addTest(unittest.makeSuite(TestNonConformant))
969 suite.addTest(unittest.makeSuite(TestRFC2047))
Barry Warsaw65279d02001-09-26 05:47:08 +0000970 suite.addTest(unittest.makeSuite(TestMIMEMessage))
Barry Warsaw41075852001-09-23 03:18:13 +0000971 suite.addTest(unittest.makeSuite(TestIdempotent))
972 suite.addTest(unittest.makeSuite(TestMiscellaneous))
973 suite.addTest(unittest.makeSuite(TestIterators))
Barry Warsawbf7a59d2001-10-11 15:44:50 +0000974 suite.addTest(unittest.makeSuite(TestParsers))
Barry Warsaw41075852001-09-23 03:18:13 +0000975 return suite
976
977
Barry Warsaw08a534d2001-10-04 17:58:50 +0000978
Barry Warsaw41075852001-09-23 03:18:13 +0000979if __name__ == '__main__':
980 unittest.main(defaultTest='suite')
981else:
982 from test_support import run_suite
983 run_suite(suite())