[Python-checkins] CVS: python/dist/src/Lib/test test_email.py,NONE,1.1

Barry Warsaw bwarsaw@users.sourceforge.net
Sat, 22 Sep 2001 20:18:15 -0700


Update of /cvsroot/python/python/dist/src/Lib/test
In directory usw-pr-cvs1:/tmp/cvs-serv12965

Added Files:
	test_email.py 
Log Message:
An extensive test suite for the email package.


--- NEW FILE: test_email.py ---
# Copyright (C) 2001 Python Software Foundation
# email package unit tests

import os
import time
import unittest
import base64
from cStringIO import StringIO
from types import StringType

import email

from email.Parser import Parser
from email.Generator import Generator, DecodedGenerator
from email.Message import Message
from email.Text import Text
from email.Image import Image
from email.MIMEBase import MIMEBase
from email.MessageRFC822 import MessageRFC822
from email import Utils
from email import Errors
from email import Encoders
from email import Iterators

import test.regrtest

NL = '\n'
EMPTYSTRING = ''



def openfile(filename):
    path = os.path.join(os.path.dirname(test.regrtest.__file__),
                        'data', filename)
    return open(path)



# Base test class
class TestEmailBase(unittest.TestCase):
    def _msgobj(self, filename):
        fp = openfile(filename)
        try:
            msgobj = email.message_from_file(fp)
        finally:
            fp.close()
        return msgobj



# Test various aspects of the Message class's API
class TestMessageAPI(TestEmailBase):
    def test_get_charsets(self):
        eq = self.assertEqual
        
        msgobj = self._msgobj('msg_08.txt')
        charsets = msgobj.get_charsets()
        eq(charsets, ['us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])

        msgobj = self._msgobj('msg_09.txt')
        charsets = msgobj.get_charsets('dingbat')
        eq(charsets, ['us-ascii', 'iso-8859-1', 'dingbat', 'koi8-r'])

        msgobj = self._msgobj('msg_12.txt')
        charsets = msgobj.get_charsets()
        eq(charsets, ['us-ascii', 'iso-8859-1', 'iso-8859-2', 'iso-8859-3',
                      'us-ascii', 'koi8-r'])

    def test_get_filename(self):
        eq = self.assertEqual

        msgobj = self._msgobj('msg_04.txt')
        filenames = [p.get_filename() for p in msgobj.get_payload()]
        eq(filenames, ['msg.txt', 'msg.txt'])

        msgobj = self._msgobj('msg_07.txt')
        subpart = msgobj.get_payload(1)
        eq(subpart.get_filename(), 'dingusfish.gif')

    def test_get_boundary(self):
        eq = self.assertEqual
        msgobj = self._msgobj('msg_07.txt')
        # No quotes!
        eq(msgobj.get_boundary(), 'BOUNDARY')

    def test_set_boundary(self):
        eq = self.assertEqual
        # This one has no existing boundary parameter, but the Content-Type:
        # header appears fifth.
        msgobj = self._msgobj('msg_01.txt')
        msgobj.set_boundary('BOUNDARY')
        header, value = msgobj.items()[4]
        eq(header.lower(), 'content-type')
        eq(value, 'text/plain; charset=us-ascii; boundary="BOUNDARY"')
        # This one has a Content-Type: header, with a boundary, stuck in the
        # middle of its headers.  Make sure the order is preserved; it should
        # be fifth.
        msgobj = self._msgobj('msg_04.txt')
        msgobj.set_boundary('BOUNDARY')
        header, value = msgobj.items()[4]
        eq(header.lower(), 'content-type')
        eq(value, 'multipart/mixed; boundary="BOUNDARY"')
        # And this one has no Content-Type: header at all.
        msgobj = self._msgobj('msg_03.txt')
        self.assertRaises(Errors.HeaderParseError,
                          msgobj.set_boundary, 'BOUNDARY')

    def test_get_decoded_payload(self):
        eq = self.assertEqual
        msgobj = self._msgobj('msg_10.txt')
        # The outer message is a multipart
        eq(msgobj.get_payload(decode=1), None)
        # Subpart 1 is 7bit encoded
        eq(msgobj.get_payload(0).get_payload(decode=1),
           'This is a 7bit encoded message.\n')
        # Subpart 2 is quopri
        eq(msgobj.get_payload(1).get_payload(decode=1),
           '\xa1This is a Quoted Printable encoded message!\n')
        # Subpart 3 is base64
        eq(msgobj.get_payload(2).get_payload(decode=1),
           'This is a Base64 encoded message.')
        # Subpart 4 has no Content-Transfer-Encoding: header.
        eq(msgobj.get_payload(3).get_payload(decode=1),
           'This has no Content-Transfer-Encoding: header.\n')

    def test_decoded_generator(self):
        eq = self.assertEqual
        msgobj = self._msgobj('msg_07.txt')
        s = StringIO()
        g = DecodedGenerator(s)
        g(msgobj)
        eq(s.getvalue(), '''\
MIME-Version: 1.0
From: Barry <barry@digicool.com>
To: Dingus Lovers <cravindogs@cravindogs.com>
Subject: Here is your dingus fish
Date: Fri, 20 Apr 2001 19:35:02 -0400
Content-Type: multipart/mixed; boundary="BOUNDARY"

Hi there,

This is the dingus fish.

[Non-text (image/gif) part of message omitted, filename dingusfish.gif]
''')        

    def test__contains__(self):
        msg = Message()
        msg['From'] = 'Me'
        msg['to'] = 'You'
        # Check for case insensitivity
        self.failUnless('from' in msg)
        self.failUnless('From' in msg)
        self.failUnless('FROM' in msg)
        self.failUnless('to' in msg)
        self.failUnless('To' in msg)
        self.failUnless('TO' in msg)

    def test_as_string(self):
        eq = self.assertEqual
        msgobj = self._msgobj('msg_01.txt')
        fp = openfile('msg_01.txt')
        try:
            text = fp.read()
        finally:
            fp.close()
        eq(text, msgobj.as_string())
        fullrepr = str(msgobj)
        lines = fullrepr.split('\n')
        self.failUnless(lines[0].startswith('From '))
        eq(text, NL.join(lines[1:]))

    def test_bad_param(self):
        msg = email.message_from_string("""\
From: foo
Content-Type: blarg; baz; boo

""")
        self.assertEqual(msg.get_param('baz'), '')

    def test_missing_filename(self):
        msg = email.message_from_string("""\
From: foo

""")
        self.assertEqual(msg.get_filename(), None)

    def test_bogus_filename(self):
        msg = email.message_from_string("""\
From: foo
Content-Disposition: blarg; filename

""")
        self.assertEqual(msg.get_filename(), '')
        
    def test_missing_boundary(self):
        msg = email.message_from_string("""\
From: foo

""")
        self.assertEqual(msg.get_boundary(), None)



# Test the email.Encoders module
class TestEncoders(unittest.TestCase):
    def test_encode_noop(self):
        eq = self.assertEqual
        msg = Text('hello world', _encoder=Encoders.encode_noop)
        eq(msg.get_payload(), 'hello world\n')
        eq(msg['content-transfer-encoding'], None)

    def test_encode_7bit(self):
        eq = self.assertEqual
        msg = Text('hello world', _encoder=Encoders.encode_7or8bit)
        eq(msg.get_payload(), 'hello world\n')
        eq(msg['content-transfer-encoding'], '7bit')
        msg = Text('hello \x7f world', _encoder=Encoders.encode_7or8bit)
        eq(msg.get_payload(), 'hello \x7f world\n')
        eq(msg['content-transfer-encoding'], '7bit')

    def test_encode_8bit(self):
        eq = self.assertEqual
        msg = Text('hello \x80 world', _encoder=Encoders.encode_7or8bit)
        eq(msg.get_payload(), 'hello \x80 world\n')
        eq(msg['content-transfer-encoding'], '8bit')

    def test_encode_base64(self):
        eq = self.assertEqual
        msg = Text('hello world', _encoder=Encoders.encode_base64)
        eq(msg.get_payload(), 'aGVsbG8gd29ybGQK\n')
        eq(msg['content-transfer-encoding'], 'base64')

    def test_encode_quoted_printable(self):
        eq = self.assertEqual
        msg = Text('hello world', _encoder=Encoders.encode_quopri)
        eq(msg.get_payload(), 'hello=20world\n')
        eq(msg['content-transfer-encoding'], 'quoted-printable')



class TestLongHeaders(unittest.TestCase):
    def test_header_splitter(self):
        msg = Text('')
        # It'd be great if we could use add_header() here, but that doesn't
        # guarantee an order of the parameters.
        msg['X-Foobar-Spoink-Defrobnit'] = (
            'wasnipoop; giraffes="very-long-necked-animals"; '
            'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
        sfp = StringIO()
        g = Generator(sfp)
        g(msg)
        self.assertEqual(sfp.getvalue(), '''\
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
	spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"

''')



class TestFromMangling(unittest.TestCase):
    def setUp(self):
        self.msg = Message()
        self.msg['From'] = 'aaa@bbb.org'
        self.msg.add_payload("""\
>From the desk of A.A.A.:
Blah blah blah
""")

    def test_mangled_from(self):
        s = StringIO()
        g = Generator(s, mangle_from_=1)
        g(self.msg)
        self.assertEqual(s.getvalue(), """\
From: aaa@bbb.org

>From the desk of A.A.A.:
Blah blah blah
""")

    def test_dont_mangle_from(self):
        s = StringIO()
        g = Generator(s, mangle_from_=0)
        g(self.msg)
        self.assertEqual(s.getvalue(), """\
From: aaa@bbb.org

>From the desk of A.A.A.:
Blah blah blah
""")



# Test the basic Image class
class TestImage(unittest.TestCase):
    def setUp(self):
        fp = openfile('PyBanner048.gif')
        try:
            self._imgdata = fp.read()
        finally:
            fp.close()
        self._im = Image(self._imgdata)

    def test_guess_minor_type(self):
        self.assertEqual(self._im.get_type(), 'image/gif')

    def test_encoding(self):
        payload = self._im.get_payload()
        self.assertEqual(base64.decodestring(payload), self._imgdata)

    def checkSetMinor(self):
        im = Image(self._imgdata, 'fish')
        self.assertEqual(im.get_type(), 'image/fish')

    def test_custom_encoder(self):
        eq = self.assertEqual
        def encoder(msg):
            orig = msg.get_payload()
            msg.set_payload(0)
            msg['Content-Transfer-Encoding'] = 'broken64'
        im = Image(self._imgdata, _encoder=encoder)
        eq(im.get_payload(), 0)
        eq(im['content-transfer-encoding'], 'broken64')

    def test_add_header(self):
        eq = self.assertEqual
        unless = self.failUnless
        self._im.add_header('Content-Disposition', 'attachment',
                            filename='dingusfish.gif')
        eq(self._im['content-disposition'],
           'attachment; filename="dingusfish.gif"')
        eq(self._im.get_params(header='content-disposition'),
           ['filename="dingusfish.gif"'])
        eq(self._im.get_param('filename', header='content-disposition'),
           'dingusfish.gif')
        missing = []
        unless(self._im.get_param('attachment', missing,
                                  header='content-disposition') is missing)
        # Try some missing stuff
        unless(self._im.get_param('foobar', missing) is missing)
        unless(self._im.get_param('attachment', missing,
                                  header='foobar') is missing)



# Test the basic Text class
class TestText(unittest.TestCase):
    def setUp(self):
        self._msg = Text('hello there')

    def test_types(self):
        eq = self.assertEqual
        unless = self.failUnless
        eq(self._msg.get_type(), 'text/plain')
        eq(self._msg.get_param('charset'), 'us-ascii')
        missing = []
        unless(self._msg.get_param('foobar', missing) is missing)
        unless(self._msg.get_param('charset', missing, header='foobar')
               is missing)

    def test_payload(self):
        self.assertEqual(self._msg.get_payload(), 'hello there\n')
        self.failUnless(not self._msg.is_multipart())



class TestMultipartMixed(unittest.TestCase):
    def setUp(self):
        fp = openfile('PyBanner048.gif')
        try:
            data = fp.read()
        finally:
            fp.close()

        container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
        image = Image(data, name='dingusfish.gif')
        image.add_header('content-disposition', 'attachment',
                         filename='dingusfish.gif')
        intro = Text('''\
Hi there,

This is the dingus fish.
''')
        container.add_payload(intro)
        container.add_payload(image)
        container['From'] = 'Barry <barry@digicool.com>'
        container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
        container['Subject'] = 'Here is your dingus fish'
        
        now = 987809702.54848599
        timetuple = time.localtime(now)
        if timetuple[-1] == 0:
            tzsecs = time.timezone
        else:
            tzsecs = time.altzone
        if tzsecs > 0:
            sign = '-'
        else:
            sign = '+'
        tzoffset = ' %s%04d' % (sign, tzsecs / 36)
        container['Date'] = time.strftime(
            '%a, %d %b %Y %H:%M:%S',
            time.localtime(now)) + tzoffset
        self._msg = container
        self._im = image
        self._txt = intro

    def test_hierarchy(self):
        # convenience
        eq = self.assertEqual
        unless = self.failUnless
        raises = self.assertRaises
        # tests
        m = self._msg
        unless(m.is_multipart())
        eq(m.get_type(), 'multipart/mixed')
        eq(len(m.get_payload()), 2)
        raises(IndexError, m.get_payload, 2)
        m0 = m.get_payload(0)
        m1 = m.get_payload(1)
        unless(m0 is self._txt)
        unless(m1 is self._im)
        eq(m.get_payload(), [m0, m1])
        unless(not m0.is_multipart())
        unless(not m1.is_multipart())



class TestNonConformant(TestEmailBase):
    def test_parse_missing_minor_type(self):
        eq = self.assertEqual
        msg = self._msgobj('msg_14.txt')
        eq(msg.get_type(), 'text')
        eq(msg.get_main_type(), 'text')
        self.failUnless(msg.get_subtype() is None)

    def test_bogus_boundary(self):
        fp = openfile('msg_15.txt')
        try:
            data = fp.read()
        finally:
            fp.close()
        p = Parser()
        # Note, under a future non-strict parsing mode, this would parse the
        # message into the intended message tree.
        self.assertRaises(Errors.BoundaryError, p.parsestr, data)



class TestRFC2047(unittest.TestCase):
    def test_iso_8859_1(self):
        eq = self.assertEqual
        s = '=?iso-8859-1?q?this=20is=20some=20text?='
        eq(Utils.decode(s), 'this is some text')
        s = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
        eq(Utils.decode(s), u'Keld_J\xf8rn_Simonsen')
        s = '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
            '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
        eq(Utils.decode(s), 'If you can read this you understand the example.')
        s = '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
        eq(Utils.decode(s),
           u'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
        s = '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
        eq(Utils.decode(s), u'this is some text')

    def test_encode_header(self):
        eq = self.assertEqual
        s = 'this is some text'
        eq(Utils.encode(s), '=?iso-8859-1?q?this=20is=20some=20text?=')
        s = 'Keld_J\xf8rn_Simonsen'
        eq(Utils.encode(s), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
        s1 = 'If you can read this yo'
        s2 = 'u understand the example.'
        eq(Utils.encode(s1, encoding='b'),
           '=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
        eq(Utils.encode(s2, charset='iso-8859-2', encoding='b'),
           '=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')



class TestMessageRFC822(unittest.TestCase):
    def setUp(self):
        fp = openfile('msg_11.txt')
        self._text = fp.read()
        fp.close()

    def test_type_error(self):
        self.assertRaises(TypeError, MessageRFC822, 'a plain string')

    def test_valid_argument(self):
        eq = self.assertEqual
        subject = 'A sub-message'
        m = Message()
        m['Subject'] = subject
        r = MessageRFC822(m)
        eq(r.get_type(), 'message/rfc822')
        self.failUnless(r.get_payload() is m)
        eq(r.get_payload()['subject'], subject)

    def test_generate(self):
        # First craft the message to be encapsulated
        m = Message()
        m['Subject'] = 'An enclosed message'
        m.add_payload('Here is the body of the message.\n')
        r = MessageRFC822(m)
        r['Subject'] = 'The enclosing message'
        s = StringIO()
        g = Generator(s)
        g(r)
        self.assertEqual(s.getvalue(), self._text)



class TestIdempotent(unittest.TestCase):
    def _msgobj(self, filename):
        fp = openfile(filename)
        try:
            data = fp.read()
        finally:
            fp.close()
        msgobj = email.message_from_string(data)
        return msgobj, data

    def _idempotent(self, msg, text):
        eq = self.assertEquals
        s = StringIO()
        g = Generator(s, maxheaderlen=0)
        g(msg)
        eq(text, s.getvalue())

    def test_parse_text_message(self):
        eq = self.assertEquals
        msg, text = self._msgobj('msg_01.txt')
        eq(msg.get_type(), 'text/plain')
        eq(msg.get_main_type(), 'text')
        eq(msg.get_subtype(), 'plain')
        eq(msg.get_params(), ['charset=us-ascii'])
        eq(msg.get_param('charset'), 'us-ascii')
        eq(msg.preamble, None)
        eq(msg.epilogue, None)
        self._idempotent(msg, text)

    def test_parse_untyped_message(self):
        eq = self.assertEquals
        msg, text = self._msgobj('msg_03.txt')
        eq(msg.get_type(), None)
        eq(msg.get_params(), None)
        eq(msg.get_param('charset'), None)
        self._idempotent(msg, text)

    def test_simple_multipart(self):
        msg, text = self._msgobj('msg_04.txt')
        self._idempotent(msg, text)

    def test_MIME_digest(self):
        msg, text = self._msgobj('msg_02.txt')
        self._idempotent(msg, text)

    def test_mixed_with_image(self):
        msg, text = self._msgobj('msg_06.txt')
        self._idempotent(msg, text)
    
    def test_multipart_report(self):
        msg, text = self._msgobj('msg_05.txt')
        self._idempotent(msg, text)
        
    def test_content_type(self):
        eq = self.assertEquals
        # Get a message object and reset the seek pointer for other tests
        msg, text = self._msgobj('msg_05.txt')
        eq(msg.get_type(), 'multipart/report')
        # Test the Content-Type: parameters
        params = {}
        for p in msg.get_params():
            pk, pv = p.split('=', 1)
            params[pk] = pv
        eq(params['report-type'], 'delivery-status')
        eq(params['boundary'], '"D1690A7AC1.996856090/mail.example.com"')
        eq(msg.preamble, 'This is a MIME-encapsulated message.\n\n')
        eq(msg.epilogue, '\n\n')
        eq(len(msg.get_payload()), 3)
        # Make sure the subparts are what we expect
        msg1 = msg.get_payload(0)
        eq(msg1.get_type(), 'text/plain')
        eq(msg1.get_payload(), 'Yadda yadda yadda\n')
        msg2 = msg.get_payload(1)
        eq(msg2.get_type(), None)
        eq(msg2.get_payload(), 'Yadda yadda yadda\n')
        msg3 = msg.get_payload(2)
        eq(msg3.get_type(), 'message/rfc822')
        self.failUnless(isinstance(msg3, Message))
        msg4 = msg3.get_payload()
        self.failUnless(isinstance(msg4, Message))
        eq(msg4.get_payload(), 'Yadda yadda yadda\n')

    def test_parser(self):
        eq = self.assertEquals
        msg, text = self._msgobj('msg_06.txt')
        # Check some of the outer headers
        eq(msg.get_type(), 'message/rfc822')
        # Make sure there's exactly one thing in the payload and that's a
        # sub-Message object of type text/plain
        msg1 = msg.get_payload()
        self.failUnless(isinstance(msg1, Message))
        eq(msg1.get_type(), 'text/plain')
        self.failUnless(isinstance(msg1.get_payload(), StringType))
        eq(msg1.get_payload(), '\n')
        


class TestMiscellaneous(unittest.TestCase):
    def test_message_from_string(self):
        fp = openfile('msg_01.txt')
        try:
            text = fp.read()
        finally:
            fp.close()
        msg = email.message_from_string(text)
        s = StringIO()
        # Don't wrap/continue long headers since we're trying to test
        # idempotency.
        g = Generator(s, maxheaderlen=0)
        g(msg)
        self.assertEqual(text, s.getvalue())

    def test_message_from_file(self):
        fp = openfile('msg_01.txt')
        try:
            text = fp.read()
            fp.seek(0)
            msg = email.message_from_file(fp)
            s = StringIO()
            # Don't wrap/continue long headers since we're trying to test
            # idempotency.
            g = Generator(s, maxheaderlen=0)
            g(msg)
            self.assertEqual(text, s.getvalue())
        finally:
            fp.close()

    def test_message_from_string_with_class(self):
        unless = self.failUnless
        fp = openfile('msg_01.txt')
        try:
            text = fp.read()
        finally:
            fp.close()
        # Create a subclass
        class MyMessage(Message):
            pass
        
        msg = email.message_from_string(text, MyMessage)
        unless(isinstance(msg, MyMessage))
        # Try something more complicated
        fp = openfile('msg_02.txt')
        try:
            text = fp.read()
        finally:
            fp.close()
        msg = email.message_from_string(text, MyMessage)
        for subpart in msg.walk():
            unless(isinstance(subpart, MyMessage))


    def test_message_from_file_with_class(self):
        unless = self.failUnless
        # Create a subclass
        class MyMessage(Message):
            pass
        
        fp = openfile('msg_01.txt')
        try:
            msg = email.message_from_file(fp, MyMessage)
        finally:
            fp.close()
        unless(isinstance(msg, MyMessage))
        # Try something more complicated
        fp = openfile('msg_02.txt')
        try:
            msg = email.message_from_file(fp, MyMessage)
        finally:
            fp.close()
        for subpart in msg.walk():
            unless(isinstance(subpart, MyMessage))



class TestIterators(TestEmailBase):
    def test_body_line_iterator(self):
        eq = self.assertEqual
        # First a simple non-multipart message
        msg = self._msgobj('msg_01.txt')
        it = Iterators.body_line_iterator(msg)
        lines = []
        for line in it:
            lines.append(line)
        eq(len(lines), 6)
        eq(EMPTYSTRING.join(lines), msg.get_payload())
        # Now a more complicated multipart
        msg = self._msgobj('msg_02.txt')
        it = Iterators.body_line_iterator(msg)
        lines = []
        for line in it:
            lines.append(line)
        eq(len(lines), 43)
        eq(EMPTYSTRING.join(lines), """\
Send Ppp mailing list submissions to
	ppp@zzz.org

To subscribe or unsubscribe via the World Wide Web, visit
	http://www.zzz.org/mailman/listinfo/ppp
or, via email, send a message with subject or body 'help' to
	ppp-request@zzz.org

You can reach the person managing the list at
	ppp-admin@zzz.org

When replying, please edit your Subject line so it is more specific
than "Re: Contents of Ppp digest..."

Today's Topics:

   1. testing #1 (Barry A. Warsaw)
   2. testing #2 (Barry A. Warsaw)
   3. testing #3 (Barry A. Warsaw)
   4. testing #4 (Barry A. Warsaw)
   5. testing #5 (Barry A. Warsaw)

hello


hello


hello


hello


hello



_______________________________________________
Ppp mailing list
Ppp@zzz.org
http://www.zzz.org/mailman/listinfo/ppp

""")

    def test_typed_subpart_iterator(self):
        eq = self.assertEqual
        msg = self._msgobj('msg_04.txt')
        it = Iterators.typed_subpart_iterator(msg, 'text')
        lines = []
        subparts = 0
        for subpart in it:
            subparts += 1
            lines.append(subpart.get_payload())
        eq(subparts, 2)
        eq(EMPTYSTRING.join(lines), """\
a simple kind of mirror
to reflect upon our own
a simple kind of mirror
to reflect upon our own
""")



def suite():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(TestMessageAPI))
    suite.addTest(unittest.makeSuite(TestEncoders))
    suite.addTest(unittest.makeSuite(TestLongHeaders))
    suite.addTest(unittest.makeSuite(TestFromMangling))
    suite.addTest(unittest.makeSuite(TestImage))
    suite.addTest(unittest.makeSuite(TestText))
    suite.addTest(unittest.makeSuite(TestMultipartMixed))
    suite.addTest(unittest.makeSuite(TestNonConformant))
    suite.addTest(unittest.makeSuite(TestRFC2047))
    suite.addTest(unittest.makeSuite(TestMessageRFC822))
    suite.addTest(unittest.makeSuite(TestIdempotent))
    suite.addTest(unittest.makeSuite(TestMiscellaneous))
    suite.addTest(unittest.makeSuite(TestIterators))
    return suite



if __name__ == '__main__':
    unittest.main(defaultTest='suite')
else:
    from test_support import run_suite
    run_suite(suite())