import string
import unittest
from email6 import _header_value_parser as parser
from email6 import errors
from email6.test_email import TestEmailBase

class TestParser(TestEmailBase):

    # _wsp_splitter

    rfc_printable_ascii = bytes(range(33, 127)).decode('ascii')
    rfc_atext_chars = (string.ascii_letters + string.digits +
                        "!#$%&\'*+-/=?^_`{}|~")
    rfc_dtext_chars = rfc_printable_ascii.translate(str.maketrans('','',r'\[]'))

    def test__wsp_splitter_one_word(self):
        self.assertEqual(parser._wsp_splitter('foo', 1), ['foo'])

    def test__wsp_splitter_two_words(self):
        self.assertEqual(parser._wsp_splitter('foo def', 1),
                                               ['foo', ' ', 'def'])

    def test__wsp_splitter_ws_runs(self):
        self.assertEqual(parser._wsp_splitter('foo \t def jik', 1),
                                              ['foo', ' \t ', 'def jik'])

    # _decode_ew_run

    def _test_ew_run(self, value, ex_text, ex_value, ex_defects=[]):
        text, value, defects = parser._decode_ew_run(value)
        self.assertEqual(text, ex_text)
        self.assertEqual(value, ex_value)
        self.assertDefectsEqual(defects, ex_defects)

    def test__decode_ew_run_invaild_ew(self):
        self._test_ew_run('=?test val', '', '=?test val')

    def test__decode_ew_run_one_valid_ew_trailing_text(self):
        self._test_ew_run('=?us-ascii?q?test?= val', 'test', ' val')

    def test__decode_ew_run_one_valid_ew_trailing_ws(self):
        self._test_ew_run('=?us-ascii?q?test?=  ', 'test', '  ')

    def test__decode_ew_run_one_valid_ew_no_trailing_ws(self):
        self._test_ew_run('=?us-ascii?q?test?=', 'test', '')

    def test__decode_ew_run_two_ew_with_ws_run(self):
        self._test_ew_run(
            '=?us-ascii?q?test?=   =?us-ascii?q?foo?=  val',
            'testfoo',
            '  val')

    def test__decode_ew_run_ew_with_internal_leading_ws(self):
        self._test_ew_run(
            '=?us-ascii?q?=20test?=   =?us-ascii?q?=20foo?=  val',
            ' test foo',
            '  val')

    def test__decode_ew_run_undecodable_bytes(self):
        self._test_ew_run(
            (b'=?us-ascii?q?=20test?=   =?us-ascii?q?=20\xACfoo?='
                b'  val').decode('ascii', 'surrogateescape'),
            ' test \uFFFDfoo',
            '  val',
            [errors.UndecodableBytesDefect])

    def test__decode_ew_run_missing_base64_padding(self):
        self._test_ew_run('=?utf-8?b?dmk?=',
            'vi',
            '',
            [errors.InvalidBase64PaddingDefect])

    def test__decode_ew_run_invalid_base64_character(self):
        self._test_ew_run('=?utf-8?b?dm\x01k===?=',
            'vi',
            '',
            [errors.InvalidBase64CharactersDefect])

    def test__decode_ew_run_invalid_base64_character_and_bad_padding(self):
        self._test_ew_run('=?utf-8?b?dm\x01k?=',
            'vi',
            '',
            [errors.InvalidBase64CharactersDefect, errors.InvalidBase64PaddingDefect])

    # get_unstructured

    def test_get_unstructured_null(self):
        u = parser.get_unstructured('')
        self.assertEqual(str(u),    '')
        self.assertEqual(len(u), 0)

    def test_get_unstructured_one_word(self):
        u = parser.get_unstructured('foo')
        self.assertEqual(str(u),    'foo')

    def test_get_unstructured_normal_phrase(self):
        u = parser.get_unstructured('foo bar bird')
        self.assertEqual(str(u),    'foo bar bird')

    def test_get_unstructured_normal_phrase_with_whitespace(self):
        u = parser.get_unstructured('foo \t bar      bird ')
        self.assertEqual(str(u),    'foo \t bar      bird ')

    def test_get_unstructured_leading_whitespace(self):
        u = parser.get_unstructured(' foo bar')
        self.assertEqual(str(u),    ' foo bar')

    def test_get_unstructured_leading_and_trailing_whitespace(self):
        u = parser.get_unstructured(' foo bar ')
        self.assertEqual(str(u),    ' foo bar ')

    def test_get_unstructured_leading_and_trailing_whitespace(self):
        u = parser.get_unstructured(' foo bar ')
        self.assertEqual(str(u),    ' foo bar ')

    def test_get_unstructured_phrase_with_ew(self):
        u = parser.get_unstructured('foo =?us-ascii?q?bar?= bird')
        self.assertEqual(str(u), 'foo bar bird')

    def test_get_unstructured_phrase_with_two_ew(self):
        u = parser.get_unstructured(
            'foo =?us-ascii?q?bar?= =?us-ascii?q?bird?=')
        self.assertEqual(str(u), 'foo barbird')

    def test_get_unstructured_phrase_with_two_ew_trailing_ws(self):
        u = parser.get_unstructured(
            'foo =?us-ascii?q?bar?= =?us-ascii?q?bird?= ')
        self.assertEqual(str(u), 'foo barbird ')

    def test_get_unstructured_phrase_with_ew_with_leading_ws(self):
        u = parser.get_unstructured(' =?us-ascii?q?bar?=')
        self.assertEqual(str(u), ' bar')

    def test_get_unstructured_phrase_with_two_ew_extra_ws(self):
        u = parser.get_unstructured(
            'foo =?us-ascii?q?bar?= \t  =?us-ascii?q?bird?=')
        self.assertEqual(str(u), 'foo barbird')

    def test_get_unstructured_ew_with_internal_ws(self):
        u = parser.get_unstructured('=?iso-8859-1?q?hello=20world?=')
        self.assertEqual(str(u), 'hello world')
        self.assertEqual(len(u), 3)

    # test harness

    def _test_get_x(self, method, input, string, value, defects,
                          remainder, comments=None):
        token, rest = method(input)
        self.assertEqual(str(token), string)
        self.assertEqual(token.value, value)
        self.assertDefectsEqual(token.all_defects, defects)
        self.assertEqual(rest, remainder)
        if comments is not None:
            self.assertEqual(token.comments, comments)
        return token

    # get_qp_ctext

    def test_get_qp_ctext_only(self):
        ptext = self._test_get_x(parser.get_qp_ctext,
                                'foobar', 'foobar', ' ', [], '')
        self.assertEqual(ptext.token_type, 'ptext')

    def test_get_qp_ctext_all_printables(self):
        with_qp = self.rfc_printable_ascii.replace('\\', '\\\\')
        with_qp = with_qp.  replace('(', r'\(')
        with_qp = with_qp.replace(')', r'\)')
        ptext = self._test_get_x(parser.get_qp_ctext,
                                 with_qp, self.rfc_printable_ascii, ' ', [], '')

    def test_get_qp_ctext_two_words_gets_first(self):
        self._test_get_x(parser.get_qp_ctext,
                        'foo de', 'foo', ' ', [], ' de')

    def test_get_qp_ctext_following_wsp_preserved(self):
        self._test_get_x(parser.get_qp_ctext,
                        'foo \t\tde', 'foo', ' ', [], ' \t\tde')

    def test_get_qp_ctext_up_to_close_paren_only(self):
        self._test_get_x(parser.get_qp_ctext,
                        'foo)', 'foo', ' ', [], ')')

    def test_get_qp_ctext_wsp_before_close_paren_preserved(self):
        self._test_get_x(parser.get_qp_ctext,
                        'foo  )', 'foo', ' ', [], '  )')

    def test_get_qp_ctext_close_paren_mid_word(self):
        self._test_get_x(parser.get_qp_ctext,
                        'foo)bar', 'foo', ' ', [], ')bar')

    def test_get_qp_ctext_up_to_open_paren_only(self):
        self._test_get_x(parser.get_qp_ctext,
                        'foo(', 'foo', ' ', [], '(')

    def test_get_qp_ctext_wsp_before_open_paren_preserved(self):
        self._test_get_x(parser.get_qp_ctext,
                        'foo  (', 'foo', ' ', [], '  (')

    def test_get_qp_ctext_open_paren_mid_word(self):
        self._test_get_x(parser.get_qp_ctext,
                        'foo(bar', 'foo', ' ', [], '(bar')

    def test_get_qp_ctext_non_printables(self):
        ptext = self._test_get_x(parser.get_qp_ctext,
                                'foo\x00bar)', 'foo\x00bar', ' ',
                                [errors.NonPrintableDefect], ')')
        self.assertEqual(ptext.defects[0].non_printables[0], '\x00')

    # get_qcontent

    def test_get_qcontent_only(self):
        ptext = self._test_get_x(parser.get_qcontent,
                                'foobar', 'foobar', 'foobar', [], '')
        self.assertEqual(ptext.token_type, 'ptext')

    def test_get_qcontent_all_printables(self):
        with_qp = self.rfc_printable_ascii.replace('\\', '\\\\')
        with_qp = with_qp.  replace('"', r'\"')
        ptext = self._test_get_x(parser.get_qcontent, with_qp,
                                 self.rfc_printable_ascii,
                                 self.rfc_printable_ascii, [], '')

    def test_get_qcontent_two_words_gets_first(self):
        self._test_get_x(parser.get_qcontent,
                        'foo de', 'foo', 'foo', [], ' de')

    def test_get_qcontent_following_wsp_preserved(self):
        self._test_get_x(parser.get_qcontent,
                        'foo \t\tde', 'foo', 'foo', [], ' \t\tde')

    def test_get_qcontent_up_to_dquote_only(self):
        self._test_get_x(parser.get_qcontent,
                        'foo"', 'foo', 'foo', [], '"')

    def test_get_qcontent_wsp_before_close_paren_preserved(self):
        self._test_get_x(parser.get_qcontent,
                        'foo  "', 'foo', 'foo', [], '  "')

    def test_get_qcontent_close_paren_mid_word(self):
        self._test_get_x(parser.get_qcontent,
                        'foo"bar', 'foo', 'foo', [], '"bar')

    def test_get_qcontent_non_printables(self):
        ptext = self._test_get_x(parser.get_qcontent,
                                'foo\x00fg"', 'foo\x00fg', 'foo\x00fg',
                                [errors.NonPrintableDefect], '"')
        self.assertEqual(ptext.defects[0].non_printables[0], '\x00')

    # get_atext

    def test_get_atext_only(self):
        atext = self._test_get_x(parser.get_atext,
                                'foobar', 'foobar', 'foobar', [], '')
        self.assertEqual(atext.token_type, 'atext')

    def test_get_atext_all_atext(self):
        atext = self._test_get_x(parser.get_atext, self.rfc_atext_chars,
                                 self.rfc_atext_chars,
                                 self.rfc_atext_chars, [], '')

    def test_get_atext_two_words_gets_first(self):
        self._test_get_x(parser.get_atext,
                        'foo bar', 'foo', 'foo', [], ' bar')

    def test_get_atext_following_wsp_preserved(self):
        self._test_get_x(parser.get_atext,
                        'foo \t\tbar', 'foo', 'foo', [], ' \t\tbar')

    def test_get_atext_up_to_special(self):
        self._test_get_x(parser.get_atext,
                        'foo@bar', 'foo', 'foo', [], '@bar')

    def test_get_atext_non_printables(self):
        atext = self._test_get_x(parser.get_atext,
                                'foo\x00bar(', 'foo\x00bar', 'foo\x00bar',
                                [errors.NonPrintableDefect], '(')
        self.assertEqual(atext.defects[0].non_printables[0], '\x00')

    # get_fws

    def test_get_fws_only(self):
        fws = self._test_get_x(parser.get_fws, ' \t  ', ' \t  ', ' ', [], '')
        self.assertEqual(fws.token_type, 'fws')

    def test_get_fws_space(self):
        self._test_get_x(parser.get_fws, ' foo', ' ', ' ', [], 'foo')

    def test_get_fws_ws_run(self):
        self._test_get_x(parser.get_fws, ' \t foo ', ' \t ', ' ', [], 'foo ')

    # get_bare_quoted_string

    def test_get_bare_quoted_string_only(self):
        bqs = self._test_get_x(parser.get_bare_quoted_string,
                               '"foo"', '"foo"', 'foo', [], '')
        self.assertEqual(bqs.token_type, 'bare-quoted-string')

    def test_get_bare_quoted_string_must_start_with_dquote(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_bare_quoted_string('foo"')
        with self.assertRaises(errors.HeaderParseError):
            parser.get_bare_quoted_string('  "foo"')

    def test_get_bare_quoted_string_following_wsp_preserved(self):
        self._test_get_x(parser.get_bare_quoted_string,
             '"foo"\t bar', '"foo"', 'foo', [], '\t bar')

    def test_get_bare_quoted_string_multiple_words(self):
        self._test_get_x(parser.get_bare_quoted_string,
             '"foo bar moo"', '"foo bar moo"', 'foo bar moo', [], '')

    def test_get_bare_quoted_string_multiple_words_wsp_preserved(self):
        self._test_get_x(parser.get_bare_quoted_string,
             '" foo  moo\t"', '" foo  moo\t"', ' foo  moo\t', [], '')

    def test_get_bare_quoted_string_end_dquote_mid_word(self):
        self._test_get_x(parser.get_bare_quoted_string,
             '"foo"bar', '"foo"', 'foo', [], 'bar')

    def test_get_bare_quoted_string_quoted_dquote(self):
        self._test_get_x(parser.get_bare_quoted_string,
             r'"foo\"in"a', r'"foo\"in"', 'foo"in', [], 'a')

    def test_get_bare_quoted_string_non_printables(self):
        self._test_get_x(parser.get_bare_quoted_string,
             '"a\x01a"', '"a\x01a"', 'a\x01a',
             [errors.NonPrintableDefect], '')

    def test_get_bare_quoted_string_no_end_dquote(self):
        self._test_get_x(parser.get_bare_quoted_string,
             '"foo', '"foo"', 'foo',
             [errors.InvalidHeaderDefect], '')
        self._test_get_x(parser.get_bare_quoted_string,
             '"foo ', '"foo "', 'foo ',
             [errors.InvalidHeaderDefect], '')

    def test_get_bare_quoted_string_empty_quotes(self):
        self._test_get_x(parser.get_bare_quoted_string,
            '""', '""', '', [], '')

    # get_comment

    def test_get_comment_only(self):
        comment = self._test_get_x(parser.get_comment,
            '(comment)', '(comment)', ' ', [], '', ['comment'])
        self.assertEqual(comment.token_type, 'comment')

    def test_get_comment_must_start_with_paren(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_comment('foo"')
        with self.assertRaises(errors.HeaderParseError):
            parser.get_comment('  (foo"')

    def test_get_comment_following_wsp_preserved(self):
        self._test_get_x(parser.get_comment,
            '(comment)  \t', '(comment)', ' ', [], '  \t', ['comment'])

    def test_get_comment_multiple_words(self):
        self._test_get_x(parser.get_comment,
            '(foo bar)  \t', '(foo bar)', ' ', [], '  \t', ['foo bar'])

    def test_get_comment_multiple_words_wsp_preserved(self):
        self._test_get_x(parser.get_comment,
            '( foo  bar\t )  \t', '( foo  bar\t )', ' ', [], '  \t',
                [' foo  bar\t '])

    def test_get_comment_end_paren_mid_word(self):
        self._test_get_x(parser.get_comment,
            '(foo)bar', '(foo)', ' ', [], 'bar', ['foo'])

    def test_get_comment_quoted_parens(self):
        self._test_get_x(parser.get_comment,
            '(foo\) \(\)bar)', '(foo\) \(\)bar)', ' ', [], '', ['foo) ()bar'])

    def test_get_comment_non_printable(self):
        self._test_get_x(parser.get_comment,
            '(foo\x7Fbar)', '(foo\x7Fbar)', ' ',
            [errors.NonPrintableDefect], '', ['foo\x7Fbar'])

    def test_get_comment_no_end_paren(self):
        self._test_get_x(parser.get_comment,
            '(foo bar', '(foo bar)', ' ',
            [errors.InvalidHeaderDefect], '', ['foo bar'])
        self._test_get_x(parser.get_comment,
            '(foo bar  ', '(foo bar  )', ' ',
            [errors.InvalidHeaderDefect], '', ['foo bar  '])

    def test_get_comment_nested_comment(self):
        comment = self._test_get_x(parser.get_comment,
            '(foo(bar))', '(foo(bar))', ' ', [], '', ['foo(bar)'])
        self.assertEqual(comment[1].content, 'bar')

    def test_get_comment_nested_comment_wsp(self):
        comment = self._test_get_x(parser.get_comment,
            '(foo ( bar ) )', '(foo ( bar ) )', ' ', [], '', ['foo ( bar ) '])
        self.assertEqual(comment[2].content, ' bar ')

    def test_get_comment_empty_comment(self):
        self._test_get_x(parser.get_comment,
            '()', '()', ' ', [], '', [''])

    def test_get_comment_multiple_nesting(self):
        comment = self._test_get_x(parser.get_comment,
            '(((((foo)))))', '(((((foo)))))', ' ', [], '', ['((((foo))))'])
        for i in range(4, 0, -1):
            self.assertEqual(comment[0].content, '('*(i-1)+'foo'+')'*(i-1))
            comment = comment[0]
        self.assertEqual(comment.content, 'foo')

    def test_get_comment_missing_end_of_nesting(self):
        self._test_get_x(parser.get_comment,
            '(((((foo)))', '(((((foo)))))', ' ',
            [errors.InvalidHeaderDefect]*2, '', ['((((foo))))'])

    def test_get_comment_qs_in_nested_comment(self):
        comment = self._test_get_x(parser.get_comment,
            '(foo (b\)))', '(foo (b\)))', ' ', [], '', ['foo (b\))'])
        self.assertEqual(comment[2].content, 'b)')

    # get_cfws

    def test_get_cfws_only_ws(self):
        cfws = self._test_get_x(parser.get_cfws,
            '  \t \t', '  \t \t', ' ', [], '', [])
        self.assertEqual(cfws.token_type, 'cfws')

    def test_get_cfws_only_comment(self):
        cfws = self._test_get_x(parser.get_cfws,
            '(foo)', '(foo)', ' ', [], '', ['foo'])
        self.assertEqual(cfws[0].content, 'foo')

    def test_get_cfws_only_mixed(self):
        cfws = self._test_get_x(parser.get_cfws,
            ' (foo )  ( bar) ', ' (foo )  ( bar) ', ' ', [], '',
                ['foo ', ' bar'])
        self.assertEqual(cfws[1].content, 'foo ')
        self.assertEqual(cfws[3].content, ' bar')

    def test_get_cfws_ends_at_non_leader(self):
        cfws = self._test_get_x(parser.get_cfws,
            '(foo) bar', '(foo) ', ' ', [], 'bar', ['foo'])
        self.assertEqual(cfws[0].content, 'foo')

    def test_get_cfws_ends_at_non_printable(self):
        cfws = self._test_get_x(parser.get_cfws,
            '(foo) \x07', '(foo) ', ' ', [], '\x07', ['foo'])
        self.assertEqual(cfws[0].content, 'foo')

    def test_get_cfws_non_printable_in_comment(self):
        cfws = self._test_get_x(parser.get_cfws,
            '(foo \x07) "test"', '(foo \x07) ', ' ',
            [errors.NonPrintableDefect], '"test"', ['foo \x07'])
        self.assertEqual(cfws[0].content, 'foo \x07')

    def test_get_cfws_header_ends_in_comment(self):
        cfws = self._test_get_x(parser.get_cfws,
            '  (foo ', '  (foo )', ' ',
            [errors.InvalidHeaderDefect], '', ['foo '])
        self.assertEqual(cfws[1].content, 'foo ')

    def test_get_cfws_multiple_nested_comments(self):
        cfws = self._test_get_x(parser.get_cfws,
            '(foo (bar)) ((a)(a))', '(foo (bar)) ((a)(a))', ' ', [],
                '', ['foo (bar)', '(a)(a)'])
        self.assertEqual(cfws[0].comments, ['foo (bar)'])
        self.assertEqual(cfws[2].comments, ['(a)(a)'])

    # get_quoted_string

    def test_get_quoted_string_only(self):
        qs = self._test_get_x(parser.get_quoted_string,
            '"bob"', '"bob"', 'bob', [], '')
        self.assertEqual(qs.token_type, 'quoted-string')
        self.assertEqual(qs.quoted_value, '"bob"')
        self.assertEqual(qs.content, 'bob')

    def test_get_quoted_string_with_wsp(self):
        qs = self._test_get_x(parser.get_quoted_string,
            '\t "bob"  ', '\t "bob"  ', ' bob ', [], '')
        self.assertEqual(qs.quoted_value, ' "bob" ')
        self.assertEqual(qs.content, 'bob')

    def test_get_quoted_string_with_comments_and_wsp(self):
        qs = self._test_get_x(parser.get_quoted_string,
            ' (foo) "bob"(bar)', ' (foo) "bob"(bar)', ' bob ', [], '')
        self.assertEqual(qs[0][1].content, 'foo')
        self.assertEqual(qs[2][0].content, 'bar')
        self.assertEqual(qs.content, 'bob')
        self.assertEqual(qs.quoted_value, ' "bob" ')

    def test_get_quoted_string_with_multiple_comments(self):
        qs = self._test_get_x(parser.get_quoted_string,
            ' (foo) (bar) "bob"(bird)', ' (foo) (bar) "bob"(bird)', ' bob ',
                [], '')
        self.assertEqual(qs[0].comments, ['foo', 'bar'])
        self.assertEqual(qs[2].comments, ['bird'])
        self.assertEqual(qs.content, 'bob')
        self.assertEqual(qs.quoted_value, ' "bob" ')

    def test_get_quoted_string_non_printable_in_comment(self):
        qs = self._test_get_x(parser.get_quoted_string,
            ' (\x0A) "bob"', ' (\x0A) "bob"', ' bob',
                [errors.NonPrintableDefect], '')
        self.assertEqual(qs[0].comments, ['\x0A'])
        self.assertEqual(qs.content, 'bob')
        self.assertEqual(qs.quoted_value, ' "bob"')

    def test_get_quoted_string_non_printable_in_qcontent(self):
        qs = self._test_get_x(parser.get_quoted_string,
            ' (a) "a\x0B"', ' (a) "a\x0B"', ' a\x0B',
                [errors.NonPrintableDefect], '')
        self.assertEqual(qs[0].comments, ['a'])
        self.assertEqual(qs.content, 'a\x0B')
        self.assertEqual(qs.quoted_value, ' "a\x0B"')

    def test_get_quoted_string_internal_ws(self):
        qs = self._test_get_x(parser.get_quoted_string,
            ' (a) "foo  bar "', ' (a) "foo  bar "', ' foo  bar ',
                [], '')
        self.assertEqual(qs[0].comments, ['a'])
        self.assertEqual(qs.content, 'foo  bar ')
        self.assertEqual(qs.quoted_value, ' "foo  bar "')

    def test_get_quoted_string_header_ends_in_comment(self):
        qs = self._test_get_x(parser.get_quoted_string,
            ' (a) "bob" (a', ' (a) "bob" (a)', ' bob ',
                [errors.InvalidHeaderDefect], '')
        self.assertEqual(qs[0].comments, ['a'])
        self.assertEqual(qs[2].comments, ['a'])
        self.assertEqual(qs.content, 'bob')
        self.assertEqual(qs.quoted_value, ' "bob" ')

    def test_get_quoted_string_header_ends_in_qcontent(self):
        qs = self._test_get_x(parser.get_quoted_string,
            ' (a) "bob', ' (a) "bob"', ' bob',
                [errors.InvalidHeaderDefect], '')
        self.assertEqual(qs[0].comments, ['a'])
        self.assertEqual(qs.content, 'bob')
        self.assertEqual(qs.quoted_value, ' "bob"')

    def test_get_quoted_string_no_quoted_string(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_quoted_string(' (ab) xyz')

    def test_get_quoted_string_qs_ends_at_noncfws(self):
        qs = self._test_get_x(parser.get_quoted_string,
            '\t "bob" fee', '\t "bob" ', ' bob ', [], 'fee')
        self.assertEqual(qs.content, 'bob')
        self.assertEqual(qs.quoted_value, ' "bob" ')

    # get_atom

    def test_get_atom_only(self):
        atom = self._test_get_x(parser.get_atom,
            'bob', 'bob', 'bob', [], '')
        self.assertEqual(atom.token_type, 'atom')

    def test_get_atom_with_wsp(self):
        self._test_get_x(parser.get_atom,
            '\t bob  ', '\t bob  ', ' bob ', [], '')

    def test_get_atom_with_comments_and_wsp(self):
        atom = self._test_get_x(parser.get_atom,
            ' (foo) bob(bar)', ' (foo) bob(bar)', ' bob ', [], '')
        self.assertEqual(atom[0][1].content, 'foo')
        self.assertEqual(atom[2][0].content, 'bar')

    def test_get_atom_with_multiple_comments(self):
        atom = self._test_get_x(parser.get_atom,
            ' (foo) (bar) bob(bird)', ' (foo) (bar) bob(bird)', ' bob ',
                [], '')
        self.assertEqual(atom[0].comments, ['foo', 'bar'])
        self.assertEqual(atom[2].comments, ['bird'])

    def test_get_atom_non_printable_in_comment(self):
        atom = self._test_get_x(parser.get_atom,
            ' (\x0A) bob', ' (\x0A) bob', ' bob',
                [errors.NonPrintableDefect], '')
        self.assertEqual(atom[0].comments, ['\x0A'])

    def test_get_atom_non_printable_in_atext(self):
        atom = self._test_get_x(parser.get_atom,
            ' (a) a\x0B', ' (a) a\x0B', ' a\x0B',
                [errors.NonPrintableDefect], '')
        self.assertEqual(atom[0].comments, ['a'])

    def test_get_atom_header_ends_in_comment(self):
        atom = self._test_get_x(parser.get_atom,
            ' (a) bob (a', ' (a) bob (a)', ' bob ',
                [errors.InvalidHeaderDefect], '')
        self.assertEqual(atom[0].comments, ['a'])
        self.assertEqual(atom[2].comments, ['a'])

    def test_get_atom_no_atom(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_atom(' (ab) ')

    def test_get_atom_no_atom_before_special(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_atom(' (ab) @')

    def test_get_atom_atom_ends_at_special(self):
        atom = self._test_get_x(parser.get_atom,
            ' (foo) bob(bar)  @bang', ' (foo) bob(bar)  ', ' bob ', [], '@bang')
        self.assertEqual(atom[0].comments, ['foo'])
        self.assertEqual(atom[2].comments, ['bar'])

    def test_get_atom_atom_ends_at_noncfws(self):
        atom = self._test_get_x(parser.get_atom,
            'bob  fred', 'bob  ', 'bob ', [], 'fred')

    # get_dot_atom_text

    def test_get_dot_atom_text(self):
        dot_atom_text = self._test_get_x(parser.get_dot_atom_text,
            'foo.bar.bang', 'foo.bar.bang', 'foo.bar.bang', [], '')
        self.assertEqual(dot_atom_text.token_type, 'dot-atom-text')
        self.assertEqual(len(dot_atom_text), 5)

    def test_get_dot_atom_text_lone_atom_is_valid(self):
        dot_atom_text = self._test_get_x(parser.get_dot_atom_text,
            'foo', 'foo', 'foo', [], '')

    def test_get_dot_atom_text_raises_on_leading_dot(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_dot_atom_text('.foo.bar')

    def test_get_dot_atom_text_raises_on_trailing_dot(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_dot_atom_text('foo.bar.')

    def test_get_dot_atom_text_raises_on_leading_non_atext(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_dot_atom_text(' foo.bar')
        with self.assertRaises(errors.HeaderParseError):
            parser.get_dot_atom_text('@foo.bar')
        with self.assertRaises(errors.HeaderParseError):
            parser.get_dot_atom_text('"foo.bar"')

    def test_get_dot_atom_text_trailing_text_preserved(self):
        dot_atom_text = self._test_get_x(parser.get_dot_atom_text,
            'foo@bar', 'foo', 'foo', [], '@bar')

    def test_get_dot_atom_text_trailing_ws_preserved(self):
        dot_atom_text = self._test_get_x(parser.get_dot_atom_text,
            'foo .bar', 'foo', 'foo', [], ' .bar')

    # get_dot_atom

    def test_get_dot_atom_only(self):
        dot_atom = self._test_get_x(parser.get_dot_atom,
            'foo.bar.bing', 'foo.bar.bing', 'foo.bar.bing', [], '')
        self.assertEqual(dot_atom.token_type, 'dot-atom')
        self.assertEqual(len(dot_atom), 1)

    def test_get_dot_atom_with_wsp(self):
        self._test_get_x(parser.get_dot_atom,
            '\t  foo.bar.bing  ', '\t  foo.bar.bing  ', ' foo.bar.bing ', [], '')

    def test_get_dot_atom_with_comments_and_wsp(self):
        self._test_get_x(parser.get_dot_atom,
            ' (sing)  foo.bar.bing (here) ', ' (sing)  foo.bar.bing (here) ',
                ' foo.bar.bing ', [], '')

    def test_get_dot_atom_space_ends_dot_atom(self):
        self._test_get_x(parser.get_dot_atom,
            ' (sing)  foo.bar .bing (here) ', ' (sing)  foo.bar ',
                ' foo.bar ', [], '.bing (here) ')

    def test_get_dot_atom_no_atom_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_dot_atom(' (foo) ')

    def test_get_dot_atom_leading_dot_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_dot_atom(' (foo) .bar')

    def test_get_dot_atom_two_dots_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_dot_atom('bar..bang')

    def test_get_dot_atom_trailing_dot_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_dot_atom(' (foo) bar.bang. foo')

    # get_word (if this were black box we'd repeat all the qs/atom tests)

    def test_get_word_atom_yields_atom(self):
        word = self._test_get_x(parser.get_word,
            ' (foo) bar (bang) :ah', ' (foo) bar (bang) ', ' bar ', [], ':ah')
        self.assertEqual(word.token_type, 'atom')
        self.assertEqual(word[0].token_type, 'cfws')

    def test_get_word_qs_yields_qs(self):
        word = self._test_get_x(parser.get_word,
            '"bar " (bang) ah', '"bar " (bang) ', 'bar  ', [], 'ah')
        self.assertEqual(word.token_type, 'quoted-string')
        self.assertEqual(word[0].token_type, 'bare-quoted-string')
        self.assertEqual(word[0].value, 'bar ')
        self.assertEqual(word.content, 'bar ')

    def test_get_word_ends_at_dot(self):
        self._test_get_x(parser.get_word,
            'foo.', 'foo', 'foo', [], '.')

    # get_phrase

    def test_get_phrase_simple(self):
        phrase = self._test_get_x(parser.get_phrase,
            '"Fred A. Johnson" is his name, oh.',
            '"Fred A. Johnson" is his name',
            'Fred A. Johnson is his name',
            [],
            ', oh.')
        self.assertEqual(phrase.token_type, 'phrase')

    def test_get_phrase_complex(self):
        phrase = self._test_get_x(parser.get_phrase,
            ' (A) bird (in (my|your)) "hand  " is messy\t<>\t',
            ' (A) bird (in (my|your)) "hand  " is messy\t',
            ' bird hand   is messy ',
            [],
            '<>\t')
        self.assertEqual(phrase[0][0].comments, ['A'])
        self.assertEqual(phrase[0][2].comments, ['in (my|your)'])

    def test_get_phrase_obsolete(self):
        phrase = self._test_get_x(parser.get_phrase,
            'Fred A.(weird).O Johnson',
            'Fred A.(weird).O Johnson',
            'Fred A. .O Johnson',
            [errors.ObsoleteHeaderDefect]*3,
            '')
        self.assertEqual(len(phrase), 7)
        self.assertEqual(phrase[3].comments, ['weird'])

    def test_get_phrase_pharse_must_start_with_word(self):
        phrase = self._test_get_x(parser.get_phrase,
            '(even weirder).name',
            '(even weirder).name',
            ' .name',
            [errors.InvalidHeaderDefect] + [errors.ObsoleteHeaderDefect]*2,
            '')
        self.assertEqual(len(phrase), 3)
        self.assertEqual(phrase[0].comments, ['even weirder'])

    def test_get_phrase_ending_with_obsolete(self):
        phrase = self._test_get_x(parser.get_phrase,
            'simple phrase.(with trailing comment):boo',
            'simple phrase.(with trailing comment)',
            'simple phrase. ',
            [errors.ObsoleteHeaderDefect]*2,
            ':boo')
        self.assertEqual(len(phrase), 4)
        self.assertEqual(phrase[3].comments, ['with trailing comment'])

    def get_phrase_cfws_only_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_phrase(' (foo) ')

    # get_local_part

    def test_get_local_part_simple(self):
        local_part = self._test_get_x(parser.get_local_part,
            'dinsdale@python.org', 'dinsdale', 'dinsdale', [], '@python.org')
        self.assertEqual(local_part.token_type, 'local-part')
        self.assertEqual(local_part.local_part, 'dinsdale')

    def test_get_local_part_with_dot(self):
        local_part = self._test_get_x(parser.get_local_part,
            'Fred.A.Johnson@python.org',
            'Fred.A.Johnson',
            'Fred.A.Johnson',
            [],
            '@python.org')
        self.assertEqual(local_part.local_part, 'Fred.A.Johnson')

    def test_get_local_part_with_whitespace(self):
        local_part = self._test_get_x(parser.get_local_part,
            ' Fred.A.Johnson  @python.org',
            ' Fred.A.Johnson  ',
            ' Fred.A.Johnson ',
            [],
            '@python.org')
        self.assertEqual(local_part.local_part, 'Fred.A.Johnson')

    def test_get_local_part_with_cfws(self):
        local_part = self._test_get_x(parser.get_local_part,
            ' (foo) Fred.A.Johnson (bar (bird))  @python.org',
            ' (foo) Fred.A.Johnson (bar (bird))  ',
            ' Fred.A.Johnson ',
            [],
            '@python.org')
        self.assertEqual(local_part.local_part, 'Fred.A.Johnson')
        self.assertEqual(local_part[0][0].comments, ['foo'])
        self.assertEqual(local_part[0][2].comments, ['bar (bird)'])

    def test_get_local_part_simple_quoted(self):
        local_part = self._test_get_x(parser.get_local_part,
            '"dinsdale"@python.org', '"dinsdale"', '"dinsdale"', [], '@python.org')
        self.assertEqual(local_part.token_type, 'local-part')
        self.assertEqual(local_part.local_part, 'dinsdale')

    def test_get_local_part_with_quoted_dot(self):
        local_part = self._test_get_x(parser.get_local_part,
            '"Fred.A.Johnson"@python.org',
            '"Fred.A.Johnson"',
            '"Fred.A.Johnson"',
            [],
            '@python.org')
        self.assertEqual(local_part.local_part, 'Fred.A.Johnson')

    def test_get_local_part_quoted_with_whitespace(self):
        local_part = self._test_get_x(parser.get_local_part,
            ' "Fred A. Johnson"  @python.org',
            ' "Fred A. Johnson"  ',
            ' "Fred A. Johnson" ',
            [],
            '@python.org')
        self.assertEqual(local_part.local_part, 'Fred A. Johnson')

    def test_get_local_part_quoted_with_cfws(self):
        local_part = self._test_get_x(parser.get_local_part,
            ' (foo) " Fred A. Johnson " (bar (bird))  @python.org',
            ' (foo) " Fred A. Johnson " (bar (bird))  ',
            ' " Fred A. Johnson " ',
            [],
            '@python.org')
        self.assertEqual(local_part.local_part, ' Fred A. Johnson ')
        self.assertEqual(local_part[0][0].comments, ['foo'])
        self.assertEqual(local_part[0][2].comments, ['bar (bird)'])


    def test_get_local_part_simple_obsolete(self):
        local_part = self._test_get_x(parser.get_local_part,
            'Fred. A.Johnson@python.org',
            'Fred. A.Johnson',
            'Fred. A.Johnson',
            [errors.ObsoleteHeaderDefect],
            '@python.org')
        self.assertEqual(local_part.local_part, 'Fred. A.Johnson')

    def test_get_local_part_complex_obsolete_1(self):
        local_part = self._test_get_x(parser.get_local_part,
            ' (foo )Fred (bar).(bird) A.(sheep)Johnson."and  dogs "@python.org',
            ' (foo )Fred (bar).(bird) A.(sheep)Johnson."and  dogs "',
            ' Fred . A. Johnson.and  dogs ',
            [errors.ObsoleteHeaderDefect],
            '@python.org')
        self.assertEqual(local_part.local_part, 'Fred . A. Johnson.and  dogs ')

    def test_get_local_part_complex_obsolete_invalid(self):
        local_part = self._test_get_x(parser.get_local_part,
            ' (foo )Fred (bar).(bird) A.(sheep)Johnson "and  dogs"@python.org',
            ' (foo )Fred (bar).(bird) A.(sheep)Johnson "and  dogs"',
            ' Fred . A. Johnson and  dogs',
            [errors.InvalidHeaderDefect]*2,
            '@python.org')
        self.assertEqual(local_part.local_part, 'Fred . A. Johnson and  dogs')

    def test_get_local_part_no_part_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_local_part(' (foo) ')

    def test_get_local_part_special_instead_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_local_part(' (foo) @python.org')

    #    XXX Should these three be errors or defects?

    def test_get_local_part_leading_dot_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_local_part(' .borris@python.org')

    def test_get_local_part_trailing_dot_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_local_part(' borris.@python.org')

    def test_get_local_part_double_dot_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_local_part(' borris.(foo).natasha@python.org')

    def test_get_local_part_quoted_strings_in_atom_list(self):
        local_part = self._test_get_x(parser.get_local_part,
            '""example" example"@example.com',
            '""example" example"',
            'example example',
            [errors.InvalidHeaderDefect]*3,
            '@example.com')
        self.assertEqual(local_part.local_part, 'example example')

    def test_get_local_part_valid_and_invalid_qp_in_atom_list(self):
        local_part = self._test_get_x(parser.get_local_part,
            r'"\\"example\\" example"@example.com',
            r'"\\"example\\" example"',
            r'\example\\ example',
            [errors.InvalidHeaderDefect]*5,
            '@example.com')
        self.assertEqual(local_part.local_part, r'\example\\ example')

    # get_dtext

    def test_get_dtext_only(self):
        dtext = self._test_get_x(parser.get_dtext,
                                'foobar', 'foobar', 'foobar', [], '')
        self.assertEqual(dtext.token_type, 'ptext')

    def test_get_dtext_all_dtext(self):
        dtext = self._test_get_x(parser.get_dtext, self.rfc_dtext_chars,
                                 self.rfc_dtext_chars,
                                 self.rfc_dtext_chars, [], '')

    def test_get_dtext_two_words_gets_first(self):
        self._test_get_x(parser.get_dtext,
                        'foo bar', 'foo', 'foo', [], ' bar')

    def test_get_dtext_following_wsp_preserved(self):
        self._test_get_x(parser.get_dtext,
                        'foo \t\tbar', 'foo', 'foo', [], ' \t\tbar')

    def test_get_dtext_non_printables(self):
        dtext = self._test_get_x(parser.get_dtext,
                                'foo\x00bar]', 'foo\x00bar', 'foo\x00bar',
                                [errors.NonPrintableDefect], ']')
        self.assertEqual(dtext.defects[0].non_printables[0], '\x00')

    def test_get_dtext_with_qp(self):
        ptext = self._test_get_x(parser.get_dtext,
                                 r'foo\]\[\\bar\b\e\l\l',
                                 r'foo][\barbell',
                                 r'foo][\barbell',
                                 [errors.ObsoleteHeaderDefect],
                                 '')

    def test_get_dtext_up_to_close_bracket_only(self):
        self._test_get_x(parser.get_dtext,
                        'foo]', 'foo', 'foo', [], ']')

    def test_get_dtext_wsp_before_close_bracket_preserved(self):
        self._test_get_x(parser.get_dtext,
                        'foo  ]', 'foo', 'foo', [], '  ]')

    def test_get_dtext_close_bracket_mid_word(self):
        self._test_get_x(parser.get_dtext,
                        'foo]bar', 'foo', 'foo', [], ']bar')

    def test_get_dtext_up_to_open_bracket_only(self):
        self._test_get_x(parser.get_dtext,
                        'foo[', 'foo', 'foo', [], '[')

    def test_get_dtext_wsp_before_open_bracket_preserved(self):
        self._test_get_x(parser.get_dtext,
                        'foo  [', 'foo', 'foo', [], '  [')

    def test_get_dtext_open_bracket_mid_word(self):
        self._test_get_x(parser.get_dtext,
                        'foo[bar', 'foo', 'foo', [], '[bar')

    # get_domain_literal

    def test_get_domain_literal_only(self):
        domain_literal = domain_literal = self._test_get_x(parser.get_domain_literal,
                                '[127.0.0.1]',
                                '[127.0.0.1]',
                                '[127.0.0.1]',
                                [],
                                '')
        self.assertEqual(domain_literal.token_type, 'domain-literal')
        self.assertEqual(domain_literal.domain, '[127.0.0.1]')
        self.assertEqual(domain_literal.ip, '127.0.0.1')

    def test_get_domain_literal_with_internal_ws(self):
        domain_literal = self._test_get_x(parser.get_domain_literal,
                                '[  127.0.0.1\t ]',
                                '[  127.0.0.1\t ]',
                                '[ 127.0.0.1 ]',
                                [],
                                '')
        self.assertEqual(domain_literal.domain, '[127.0.0.1]')
        self.assertEqual(domain_literal.ip, '127.0.0.1')

    def test_get_domain_literal_with_surrounding_cfws(self):
        domain_literal = self._test_get_x(parser.get_domain_literal,
                                '(foo)[  127.0.0.1] (bar)',
                                '(foo)[  127.0.0.1] (bar)',
                                ' [ 127.0.0.1] ',
                                [],
                                '')
        self.assertEqual(domain_literal.domain, '[127.0.0.1]')
        self.assertEqual(domain_literal.ip, '127.0.0.1')

    def test_get_domain_literal_no_start_char_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_domain_literal('(foo) ')

    def test_get_domain_literal_no_start_char_before_special_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_domain_literal('(foo) @')

    def test_get_domain_literal_bad_dtext_char_before_special_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_domain_literal('(foo) [abc[@')

    # get_domain

    def test_get_domain_regular_domain_only(self):
        domain = self._test_get_x(parser.get_domain,
                                  'example.com',
                                  'example.com',
                                  'example.com',
                                  [],
                                  '')
        self.assertEqual(domain.token_type, 'domain')
        self.assertEqual(domain.domain, 'example.com')

    def test_get_domain_domain_literal_only(self):
        domain = self._test_get_x(parser.get_domain,
                                  '[127.0.0.1]',
                                  '[127.0.0.1]',
                                  '[127.0.0.1]',
                                  [],
                                  '')
        self.assertEqual(domain.token_type, 'domain')
        self.assertEqual(domain.domain, '[127.0.0.1]')

    def test_get_domain_with_cfws(self):
        domain = self._test_get_x(parser.get_domain,
                                  '(foo) example.com(bar)\t',
                                  '(foo) example.com(bar)\t',
                                  ' example.com ',
                                  [],
                                  '')
        self.assertEqual(domain.domain, 'example.com')

    def test_get_domain_domain_literal_with_cfws(self):
        domain = self._test_get_x(parser.get_domain,
                                  '(foo)[127.0.0.1]\t(bar)',
                                  '(foo)[127.0.0.1]\t(bar)',
                                  ' [127.0.0.1] ',
                                  [],
                                  '')
        self.assertEqual(domain.domain, '[127.0.0.1]')

    def test_get_domain_domain_with_cfws_ends_at_special(self):
        domain = self._test_get_x(parser.get_domain,
                                  '(foo)example.com\t(bar), next',
                                  '(foo)example.com\t(bar)',
                                  ' example.com ',
                                  [],
                                  ', next')
        self.assertEqual(domain.domain, 'example.com')

    def test_get_domain_domain_literal_with_cfws_ends_at_special(self):
        domain = self._test_get_x(parser.get_domain,
                                  '(foo)[127.0.0.1]\t(bar), next',
                                  '(foo)[127.0.0.1]\t(bar)',
                                  ' [127.0.0.1] ',
                                  [],
                                  ', next')
        self.assertEqual(domain.domain, '[127.0.0.1]')

    def test_get_domain_obsolete(self):
        domain = self._test_get_x(parser.get_domain,
                                  '(foo) example . (bird)com(bar)\t',
                                  '(foo) example . (bird)com(bar)\t',
                                  ' example . com ',
                                  [errors.ObsoleteHeaderDefect],
                                  '')
        self.assertEqual(domain.domain, 'example.com')

    def test_get_domain_no_non_cfws_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_domain("  (foo)\t")

    def test_get_domain_no_atom_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_domain("  (foo)\t, broken")


    # get_addr_spec

    def test_get_addr_spec_normal(self):
        addr_spec = self._test_get_x(parser.get_addr_spec,
                                    'dinsdale@example.com',
                                    'dinsdale@example.com',
                                    'dinsdale@example.com',
                                    [],
                                    '')
        self.assertEqual(addr_spec.token_type, 'addr-spec')
        self.assertEqual(addr_spec.local_part, 'dinsdale')
        self.assertEqual(addr_spec.domain, 'example.com')
        self.assertEqual(addr_spec.addr_spec, 'dinsdale@example.com')

    def test_get_addr_spec_with_doamin_literal(self):
        addr_spec = self._test_get_x(parser.get_addr_spec,
                                    'dinsdale@[127.0.0.1]',
                                    'dinsdale@[127.0.0.1]',
                                    'dinsdale@[127.0.0.1]',
                                    [],
                                    '')
        self.assertEqual(addr_spec.local_part, 'dinsdale')
        self.assertEqual(addr_spec.domain, '[127.0.0.1]')
        self.assertEqual(addr_spec.addr_spec, 'dinsdale@[127.0.0.1]')

    def test_get_addr_spec_with_cfws(self):
        addr_spec = self._test_get_x(parser.get_addr_spec,
                '(foo) dinsdale(bar)@ (bird) example.com (bog)',
                '(foo) dinsdale(bar)@ (bird) example.com (bog)',
                ' dinsdale@example.com ',
                [],
                '')
        self.assertEqual(addr_spec.local_part, 'dinsdale')
        self.assertEqual(addr_spec.domain, 'example.com')
        self.assertEqual(addr_spec.addr_spec, 'dinsdale@example.com')

    def test_get_addr_spec_with_qouoted_string_and_cfws(self):
        addr_spec = self._test_get_x(parser.get_addr_spec,
                '(foo) "roy a bug"(bar)@ (bird) example.com (bog)',
                '(foo) "roy a bug"(bar)@ (bird) example.com (bog)',
                ' "roy a bug"@example.com ',
                [],
                '')
        self.assertEqual(addr_spec.local_part, 'roy a bug')
        self.assertEqual(addr_spec.domain, 'example.com')
        self.assertEqual(addr_spec.addr_spec, '"roy a bug"@example.com')

    def test_get_addr_spec_ends_at_special(self):
        addr_spec = self._test_get_x(parser.get_addr_spec,
                '(foo) "roy a bug"(bar)@ (bird) example.com (bog) , next',
                '(foo) "roy a bug"(bar)@ (bird) example.com (bog) ',
                ' "roy a bug"@example.com ',
                [],
                ', next')
        self.assertEqual(addr_spec.local_part, 'roy a bug')
        self.assertEqual(addr_spec.domain, 'example.com')
        self.assertEqual(addr_spec.addr_spec, '"roy a bug"@example.com')

    def test_get_addr_spec_quoted_strings_in_atom_list(self):
        addr_spec = self._test_get_x(parser.get_addr_spec,
            '""example" example"@example.com',
            '""example" example"@example.com',
            'example example@example.com',
            [errors.InvalidHeaderDefect]*3,
            '')
        self.assertEqual(addr_spec.local_part, 'example example')
        self.assertEqual(addr_spec.domain, 'example.com')
        self.assertEqual(addr_spec.addr_spec, '"example example"@example.com')

    # get_obs_route

    def test_get_obs_route_simple(self):
        obs_route = self._test_get_x(parser.get_obs_route,
            '@example.com, @two.example.com:',
            '@example.com, @two.example.com:',
            '@example.com, @two.example.com:',
            [],
            '')
        self.assertEqual(obs_route.token_type, 'obs-route')
        self.assertEqual(obs_route.domains, ['example.com', 'two.example.com'])

    def test_get_obs_route_complex(self):
        obs_route = self._test_get_x(parser.get_obs_route,
            '(foo),, (blue)@example.com (bar),@two.(foo) example.com (bird):',
            '(foo),, (blue)@example.com (bar),@two.(foo) example.com (bird):',
            ' ,, @example.com ,@two. example.com :',
            [errors.ObsoleteHeaderDefect],  # This is the obs-domain
            '')
        self.assertEqual(obs_route.token_type, 'obs-route')
        self.assertEqual(obs_route.domains, ['example.com', 'two.example.com'])

    def test_get_obs_route_no_route_before_end_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_obs_route('(foo) @example.com,')

    def test_get_obs_route_no_route_before_special_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_obs_route('(foo) [abc],')

    def test_get_obs_route_no_route_before_special_raises2(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_obs_route('(foo) @example.com [abc],')

    # get_angle_addr

    def test_get_angle_addr_simple(self):
        angle_addr = self._test_get_x(parser.get_angle_addr,
            '<dinsdale@example.com>',
            '<dinsdale@example.com>',
            '<dinsdale@example.com>',
            [],
            '')
        self.assertEqual(angle_addr.token_type, 'angle-addr')
        self.assertEqual(angle_addr.local_part, 'dinsdale')
        self.assertEqual(angle_addr.domain, 'example.com')
        self.assertIsNone(angle_addr.route)
        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')

    def test_get_angle_addr_with_cfws(self):
        angle_addr = self._test_get_x(parser.get_angle_addr,
            ' (foo) <dinsdale@example.com>(bar)',
            ' (foo) <dinsdale@example.com>(bar)',
            ' <dinsdale@example.com> ',
            [],
            '')
        self.assertEqual(angle_addr.token_type, 'angle-addr')
        self.assertEqual(angle_addr.local_part, 'dinsdale')
        self.assertEqual(angle_addr.domain, 'example.com')
        self.assertIsNone(angle_addr.route)
        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')

    def test_get_angle_addr_qs_and_domain_literal(self):
        angle_addr = self._test_get_x(parser.get_angle_addr,
            '<"Fred Perfect"@[127.0.0.1]>',
            '<"Fred Perfect"@[127.0.0.1]>',
            '<"Fred Perfect"@[127.0.0.1]>',
            [],
            '')
        self.assertEqual(angle_addr.local_part, 'Fred Perfect')
        self.assertEqual(angle_addr.domain, '[127.0.0.1]')
        self.assertIsNone(angle_addr.route)
        self.assertEqual(angle_addr.addr_spec, '"Fred Perfect"@[127.0.0.1]')

    def test_get_angle_addr_internal_cfws(self):
        angle_addr = self._test_get_x(parser.get_angle_addr,
            '<(foo) dinsdale@example.com(bar)>',
            '<(foo) dinsdale@example.com(bar)>',
            '< dinsdale@example.com >',
            [],
            '')
        self.assertEqual(angle_addr.local_part, 'dinsdale')
        self.assertEqual(angle_addr.domain, 'example.com')
        self.assertIsNone(angle_addr.route)
        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')

    def test_get_angle_addr_obs_route(self):
        angle_addr = self._test_get_x(parser.get_angle_addr,
            '(foo)<@example.com, (bird) @two.example.com: dinsdale@example.com> (bar) ',
            '(foo)<@example.com, (bird) @two.example.com: dinsdale@example.com> (bar) ',
            ' <@example.com, @two.example.com: dinsdale@example.com> ',
            [errors.ObsoleteHeaderDefect],
            '')
        self.assertEqual(angle_addr.local_part, 'dinsdale')
        self.assertEqual(angle_addr.domain, 'example.com')
        self.assertEqual(angle_addr.route, ['example.com', 'two.example.com'])
        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')

    def test_get_angle_addr_missing_closing_angle(self):
        angle_addr = self._test_get_x(parser.get_angle_addr,
            '<dinsdale@example.com',
            '<dinsdale@example.com>',
            '<dinsdale@example.com>',
            [errors.InvalidHeaderDefect],
            '')
        self.assertEqual(angle_addr.local_part, 'dinsdale')
        self.assertEqual(angle_addr.domain, 'example.com')
        self.assertIsNone(angle_addr.route)
        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')

    def test_get_angle_addr_missing_closing_angle_with_cfws(self):
        angle_addr = self._test_get_x(parser.get_angle_addr,
            '<dinsdale@example.com (foo)',
            '<dinsdale@example.com (foo)>',
            '<dinsdale@example.com >',
            [errors.InvalidHeaderDefect],
            '')
        self.assertEqual(angle_addr.local_part, 'dinsdale')
        self.assertEqual(angle_addr.domain, 'example.com')
        self.assertIsNone(angle_addr.route)
        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')

    def test_get_angle_addr_ends_at_special(self):
        angle_addr = self._test_get_x(parser.get_angle_addr,
            '<dinsdale@example.com> (foo), next',
            '<dinsdale@example.com> (foo)',
            '<dinsdale@example.com> ',
            [],
            ', next')
        self.assertEqual(angle_addr.local_part, 'dinsdale')
        self.assertEqual(angle_addr.domain, 'example.com')
        self.assertIsNone(angle_addr.route)
        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')

    def test_get_angle_addr_no_angle_raise(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_angle_addr('(foo) ')

    def test_get_angle_addr_no_angle_before_special_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_angle_addr('(foo) , next')

    def test_get_angle_addr_no_angle_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_angle_addr('bar')

    def test_get_angle_addr_special_after_angle_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_angle_addr('(foo) <, bar')

    # get_display_name  This is phrase but with a different value.

    def test_get_display_name_simple(self):
        display_name = self._test_get_x(parser.get_display_name,
            'Fred A Johnson',
            'Fred A Johnson',
            'Fred A Johnson',
            [],
            '')
        self.assertEqual(display_name.token_type, 'display-name')
        self.assertEqual(display_name.display_name, 'Fred A Johnson')

    def test_get_display_name_complex1(self):
        display_name = self._test_get_x(parser.get_display_name,
            '"Fred A. Johnson" is his name, oh.',
            '"Fred A. Johnson" is his name',
            '"Fred A. Johnson is his name"',
            [],
            ', oh.')
        self.assertEqual(display_name.token_type, 'display-name')
        self.assertEqual(display_name.display_name, 'Fred A. Johnson is his name')

    def test_get_display_name_complex2(self):
        display_name = self._test_get_x(parser.get_display_name,
            ' (A) bird (in (my|your)) "hand  " is messy\t<>\t',
            ' (A) bird (in (my|your)) "hand  " is messy\t',
            ' "bird hand   is messy" ',
            [],
            '<>\t')
        self.assertEqual(display_name[0][0].comments, ['A'])
        self.assertEqual(display_name[0][2].comments, ['in (my|your)'])
        self.assertEqual(display_name.display_name, 'bird hand   is messy')

    def test_get_display_name_obsolete(self):
        display_name = self._test_get_x(parser.get_display_name,
            'Fred A.(weird).O Johnson',
            'Fred A.(weird).O Johnson',
            '"Fred A. .O Johnson"',
            [errors.ObsoleteHeaderDefect]*3,
            '')
        self.assertEqual(len(display_name), 7)
        self.assertEqual(display_name[3].comments, ['weird'])
        self.assertEqual(display_name.display_name, 'Fred A. .O Johnson')

    def test_get_display_name_pharse_must_start_with_word(self):
        display_name = self._test_get_x(parser.get_display_name,
            '(even weirder).name',
            '(even weirder).name',
            ' ".name"',
            [errors.InvalidHeaderDefect] + [errors.ObsoleteHeaderDefect]*2,
            '')
        self.assertEqual(len(display_name), 3)
        self.assertEqual(display_name[0].comments, ['even weirder'])
        self.assertEqual(display_name.display_name, '.name')

    def test_get_display_name_ending_with_obsolete(self):
        display_name = self._test_get_x(parser.get_display_name,
            'simple phrase.(with trailing comment):boo',
            'simple phrase.(with trailing comment)',
            '"simple phrase." ',
            [errors.ObsoleteHeaderDefect]*2,
            ':boo')
        self.assertEqual(len(display_name), 4)
        self.assertEqual(display_name[3].comments, ['with trailing comment'])
        self.assertEqual(display_name.display_name, 'simple phrase.')

    # get_name_addr

    def test_get_name_addr_angle_addr_only(self):
        name_addr = self._test_get_x(parser.get_name_addr,
            '<dinsdale@example.com>',
            '<dinsdale@example.com>',
            '<dinsdale@example.com>',
            [],
            '')
        self.assertEqual(name_addr.token_type, 'name-addr')
        self.assertIsNone(name_addr.display_name)
        self.assertEqual(name_addr.local_part, 'dinsdale')
        self.assertEqual(name_addr.domain, 'example.com')
        self.assertIsNone(name_addr.route)
        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')

    def test_get_name_addr_atom_name(self):
        name_addr = self._test_get_x(parser.get_name_addr,
            'Dinsdale <dinsdale@example.com>',
            'Dinsdale <dinsdale@example.com>',
            'Dinsdale <dinsdale@example.com>',
            [],
            '')
        self.assertEqual(name_addr.token_type, 'name-addr')
        self.assertEqual(name_addr.display_name, 'Dinsdale')
        self.assertEqual(name_addr.local_part, 'dinsdale')
        self.assertEqual(name_addr.domain, 'example.com')
        self.assertIsNone(name_addr.route)
        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')

    def test_get_name_addr_atom_name_with_cfws(self):
        name_addr = self._test_get_x(parser.get_name_addr,
            '(foo) Dinsdale (bar) <dinsdale@example.com> (bird)',
            '(foo) Dinsdale (bar) <dinsdale@example.com> (bird)',
            ' Dinsdale <dinsdale@example.com> ',
            [],
            '')
        self.assertEqual(name_addr.display_name, 'Dinsdale')
        self.assertEqual(name_addr.local_part, 'dinsdale')
        self.assertEqual(name_addr.domain, 'example.com')
        self.assertIsNone(name_addr.route)
        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')

    def test_get_name_addr_name_with_cfws_and_dots(self):
        name_addr = self._test_get_x(parser.get_name_addr,
            '(foo) Roy.A.Bear (bar) <dinsdale@example.com> (bird)',
            '(foo) Roy.A.Bear (bar) <dinsdale@example.com> (bird)',
            ' "Roy.A.Bear" <dinsdale@example.com> ',
            [errors.ObsoleteHeaderDefect]*2,
            '')
        self.assertEqual(name_addr.display_name, 'Roy.A.Bear')
        self.assertEqual(name_addr.local_part, 'dinsdale')
        self.assertEqual(name_addr.domain, 'example.com')
        self.assertIsNone(name_addr.route)
        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')

    def test_get_name_addr_qs_name(self):
        name_addr = self._test_get_x(parser.get_name_addr,
            '"Roy.A.Bear" <dinsdale@example.com>',
            '"Roy.A.Bear" <dinsdale@example.com>',
            '"Roy.A.Bear" <dinsdale@example.com>',
            [],
            '')
        self.assertEqual(name_addr.display_name, 'Roy.A.Bear')
        self.assertEqual(name_addr.local_part, 'dinsdale')
        self.assertEqual(name_addr.domain, 'example.com')
        self.assertIsNone(name_addr.route)
        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')

    def test_get_name_addr_with_route(self):
        name_addr = self._test_get_x(parser.get_name_addr,
            '"Roy.A.Bear" <@two.example.com: dinsdale@example.com>',
            '"Roy.A.Bear" <@two.example.com: dinsdale@example.com>',
            '"Roy.A.Bear" <@two.example.com: dinsdale@example.com>',
            [errors.ObsoleteHeaderDefect],
            '')
        self.assertEqual(name_addr.display_name, 'Roy.A.Bear')
        self.assertEqual(name_addr.local_part, 'dinsdale')
        self.assertEqual(name_addr.domain, 'example.com')
        self.assertEqual(name_addr.route, ['two.example.com'])
        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')

    def test_get_name_addr_ends_at_special(self):
        name_addr = self._test_get_x(parser.get_name_addr,
            '"Roy.A.Bear" <dinsdale@example.com>, next',
            '"Roy.A.Bear" <dinsdale@example.com>',
            '"Roy.A.Bear" <dinsdale@example.com>',
            [],
            ', next')
        self.assertEqual(name_addr.display_name, 'Roy.A.Bear')
        self.assertEqual(name_addr.local_part, 'dinsdale')
        self.assertEqual(name_addr.domain, 'example.com')
        self.assertIsNone(name_addr.route)
        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')

    def test_get_name_addr_no_content_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_name_addr(' (foo) ')

    def test_get_name_addr_no_content_before_special_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_name_addr(' (foo) ,')

    def test_get_name_addr_no_angle_after_display_name_raises(self):
        with self.assertRaises(errors.HeaderParseError):
            parser.get_name_addr('foo bar')

    # get_mailbox

    def test_get_mailbox_addr_spec_only(self):
        mailbox = self._test_get_x(parser.get_mailbox,
            'dinsdale@example.com',
            'dinsdale@example.com',
            'dinsdale@example.com',
            [],
            '')
        self.assertEqual(mailbox.token_type, 'mailbox')
        self.assertIsNone(mailbox.display_name)
        self.assertEqual(mailbox.local_part, 'dinsdale')
        self.assertEqual(mailbox.domain, 'example.com')
        self.assertIsNone(mailbox.route)
        self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')

    def test_get_mailbox_angle_addr_only(self):
        mailbox = self._test_get_x(parser.get_mailbox,
            '<dinsdale@example.com>',
            '<dinsdale@example.com>',
            '<dinsdale@example.com>',
            [],
            '')
        self.assertEqual(mailbox.token_type, 'mailbox')
        self.assertIsNone(mailbox.display_name)
        self.assertEqual(mailbox.local_part, 'dinsdale')
        self.assertEqual(mailbox.domain, 'example.com')
        self.assertIsNone(mailbox.route)
        self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')

    def test_get_mailbox_name_addr(self):
        mailbox = self._test_get_x(parser.get_mailbox,
            '"Roy A. Bear" <dinsdale@example.com>',
            '"Roy A. Bear" <dinsdale@example.com>',
            '"Roy A. Bear" <dinsdale@example.com>',
            [],
            '')
        self.assertEqual(mailbox.token_type, 'mailbox')
        self.assertEqual(mailbox.display_name, 'Roy A. Bear')
        self.assertEqual(mailbox.local_part, 'dinsdale')
        self.assertEqual(mailbox.domain, 'example.com')
        self.assertIsNone(mailbox.route)
        self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')

    def test_get_mailbox_ends_at_special(self):
        mailbox = self._test_get_x(parser.get_mailbox,
            '"Roy A. Bear" <dinsdale@example.com>, rest',
            '"Roy A. Bear" <dinsdale@example.com>',
            '"Roy A. Bear" <dinsdale@example.com>',
            [],
            ', rest')
        self.assertEqual(mailbox.token_type, 'mailbox')
        self.assertEqual(mailbox.display_name, 'Roy A. Bear')
        self.assertEqual(mailbox.local_part, 'dinsdale')
        self.assertEqual(mailbox.domain, 'example.com')
        self.assertIsNone(mailbox.route)
        self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')

    def test_get_mailbox_quoted_strings_in_atom_list(self):
        mailbox = self._test_get_x(parser.get_mailbox,
            '""example" example"@example.com',
            '""example" example"@example.com',
            'example example@example.com',
            [errors.InvalidHeaderDefect]*3,
            '')
        self.assertEqual(mailbox.local_part, 'example example')
        self.assertEqual(mailbox.domain, 'example.com')
        self.assertEqual(mailbox.addr_spec, '"example example"@example.com')

    # get_mailbox_list

    def test_get_mailbox_list_single_addr(self):
        mailbox_list = self._test_get_x(parser.get_mailbox_list,
            'dinsdale@example.com',
            'dinsdale@example.com',
            'dinsdale@example.com',
            [],
            '')
        self.assertEqual(mailbox_list.token_type, 'mailbox-list')
        self.assertEqual(len(mailbox_list.mailboxes), 1)
        mailbox = mailbox_list.mailboxes[0]
        self.assertIsNone(mailbox.display_name)
        self.assertEqual(mailbox.local_part, 'dinsdale')
        self.assertEqual(mailbox.domain, 'example.com')
        self.assertIsNone(mailbox.route)
        self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')
        self.assertEqual(mailbox_list.mailboxes,
                         mailbox_list.all_mailboxes)

    def test_get_mailbox_list_two_simple_addr(self):
        mailbox_list = self._test_get_x(parser.get_mailbox_list,
            'dinsdale@example.com, dinsdale@test.example.com',
            'dinsdale@example.com, dinsdale@test.example.com',
            'dinsdale@example.com, dinsdale@test.example.com',
            [],
            '')
        self.assertEqual(mailbox_list.token_type, 'mailbox-list')
        self.assertEqual(len(mailbox_list.mailboxes), 2)
        self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
                        'dinsdale@example.com')
        self.assertEqual(mailbox_list.mailboxes[1].addr_spec,
                        'dinsdale@test.example.com')
        self.assertEqual(mailbox_list.mailboxes,
                         mailbox_list.all_mailboxes)

    def test_get_mailbox_list_two_name_addr(self):
        mailbox_list = self._test_get_x(parser.get_mailbox_list,
            ('"Roy A. Bear" <dinsdale@example.com>,'
                ' "Fred Flintstone" <dinsdale@test.example.com>'),
            ('"Roy A. Bear" <dinsdale@example.com>,'
                ' "Fred Flintstone" <dinsdale@test.example.com>'),
            ('"Roy A. Bear" <dinsdale@example.com>,'
                ' "Fred Flintstone" <dinsdale@test.example.com>'),
            [],
            '')
        self.assertEqual(len(mailbox_list.mailboxes), 2)
        self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
                        'dinsdale@example.com')
        self.assertEqual(mailbox_list.mailboxes[0].display_name,
                        'Roy A. Bear')
        self.assertEqual(mailbox_list.mailboxes[1].addr_spec,
                        'dinsdale@test.example.com')
        self.assertEqual(mailbox_list.mailboxes[1].display_name,
                        'Fred Flintstone')
        self.assertEqual(mailbox_list.mailboxes,
                         mailbox_list.all_mailboxes)

    def test_get_mailbox_list_two_complex(self):
        mailbox_list = self._test_get_x(parser.get_mailbox_list,
            ('(foo) "Roy A. Bear" <dinsdale@example.com>(bar),'
                ' "Fred Flintstone" <dinsdale@test.(bird)example.com>'),
            ('(foo) "Roy A. Bear" <dinsdale@example.com>(bar),'
                ' "Fred Flintstone" <dinsdale@test.(bird)example.com>'),
            (' "Roy A. Bear" <dinsdale@example.com> ,'
                ' "Fred Flintstone" <dinsdale@test. example.com>'),
            [errors.ObsoleteHeaderDefect],
            '')
        self.assertEqual(len(mailbox_list.mailboxes), 2)
        self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
                        'dinsdale@example.com')
        self.assertEqual(mailbox_list.mailboxes[0].display_name,
                        'Roy A. Bear')
        self.assertEqual(mailbox_list.mailboxes[1].addr_spec,
                        'dinsdale@test.example.com')
        self.assertEqual(mailbox_list.mailboxes[1].display_name,
                        'Fred Flintstone')
        self.assertEqual(mailbox_list.mailboxes,
                         mailbox_list.all_mailboxes)

    def test_get_mailbox_list_unparseable_mailbox_null(self):
        mailbox_list = self._test_get_x(parser.get_mailbox_list,
            ('"Roy A. Bear"[] dinsdale@example.com,'
                ' "Fred Flintstone" <dinsdale@test.(bird)example.com>'),
            ('"Roy A. Bear"[] dinsdale@example.com,'
                ' "Fred Flintstone" <dinsdale@test.(bird)example.com>'),
            ('"Roy A. Bear"[] dinsdale@example.com,'
                ' "Fred Flintstone" <dinsdale@test. example.com>'),
            [errors.InvalidHeaderDefect,   # the 'extra' text after the local part
             errors.InvalidHeaderDefect,   # the local part with no angle-addr
             errors.ObsoleteHeaderDefect,  # period in extra text (example.com)
             errors.ObsoleteHeaderDefect], # (bird) in valid address.
            '')
        self.assertEqual(len(mailbox_list.mailboxes), 1)
        self.assertEqual(len(mailbox_list.all_mailboxes), 2)
        self.assertEqual(mailbox_list.all_mailboxes[0].token_type,
                        'invalid-mailbox')
        self.assertIsNone(mailbox_list.all_mailboxes[0].display_name)
        self.assertEqual(mailbox_list.all_mailboxes[0].local_part,
                        'Roy A. Bear')
        self.assertIsNone(mailbox_list.all_mailboxes[0].domain)
        self.assertEqual(mailbox_list.all_mailboxes[0].addr_spec,
                        '"Roy A. Bear"')
        self.assertIs(mailbox_list.all_mailboxes[1],
                        mailbox_list.mailboxes[0])
        self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
                        'dinsdale@test.example.com')
        self.assertEqual(mailbox_list.mailboxes[0].display_name,
                        'Fred Flintstone')

    def test_get_mailbox_list_junk_after_valid_address(self):
        mailbox_list = self._test_get_x(parser.get_mailbox_list,
            ('"Roy A. Bear" <dinsdale@example.com>@@,'
                ' "Fred Flintstone" <dinsdale@test.example.com>'),
            ('"Roy A. Bear" <dinsdale@example.com>@@,'
                ' "Fred Flintstone" <dinsdale@test.example.com>'),
            ('"Roy A. Bear" <dinsdale@example.com>@@,'
                ' "Fred Flintstone" <dinsdale@test.example.com>'),
            [errors.InvalidHeaderDefect],
            '')
        self.assertEqual(len(mailbox_list.mailboxes), 1)
        self.assertEqual(len(mailbox_list.all_mailboxes), 2)
        self.assertEqual(mailbox_list.all_mailboxes[0].addr_spec,
                        'dinsdale@example.com')
        self.assertEqual(mailbox_list.all_mailboxes[0].display_name,
                        'Roy A. Bear')
        self.assertEqual(mailbox_list.all_mailboxes[0].token_type,
                        'invalid-mailbox')
        self.assertIs(mailbox_list.all_mailboxes[1],
                        mailbox_list.mailboxes[0])
        self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
                        'dinsdale@test.example.com')
        self.assertEqual(mailbox_list.mailboxes[0].display_name,
                        'Fred Flintstone')

    def test_get_mailbox_list_empty_list_element(self):
        mailbox_list = self._test_get_x(parser.get_mailbox_list,
            ('"Roy A. Bear" <dinsdale@example.com>, (bird),,'
                ' "Fred Flintstone" <dinsdale@test.example.com>'),
            ('"Roy A. Bear" <dinsdale@example.com>, (bird),,'
                ' "Fred Flintstone" <dinsdale@test.example.com>'),
            ('"Roy A. Bear" <dinsdale@example.com>, ,,'
                ' "Fred Flintstone" <dinsdale@test.example.com>'),
            [errors.ObsoleteHeaderDefect]*2,
            '')
        self.assertEqual(len(mailbox_list.mailboxes), 2)
        self.assertEqual(mailbox_list.all_mailboxes,
                         mailbox_list.mailboxes)
        self.assertEqual(mailbox_list.all_mailboxes[0].addr_spec,
                        'dinsdale@example.com')
        self.assertEqual(mailbox_list.all_mailboxes[0].display_name,
                        'Roy A. Bear')
        self.assertEqual(mailbox_list.mailboxes[1].addr_spec,
                        'dinsdale@test.example.com')
        self.assertEqual(mailbox_list.mailboxes[1].display_name,
                        'Fred Flintstone')

    def test_get_mailbox_list_only_empty_elements(self):
        mailbox_list = self._test_get_x(parser.get_mailbox_list,
            '(foo),, (bar)',
            '(foo),, (bar)',
            ' ,, ',
            [errors.ObsoleteHeaderDefect]*3,
            '')
        self.assertEqual(len(mailbox_list.mailboxes), 0)
        self.assertEqual(mailbox_list.all_mailboxes,
                         mailbox_list.mailboxes)

    # get_group_list

    def test_get_group_list_cfws_only(self):
        group_list = self._test_get_x(parser.get_group_list,
            '(hidden);',
            '(hidden)',
            ' ',
            [],
            ';')
        self.assertEqual(group_list.token_type, 'group-list')
        self.assertEqual(len(group_list.mailboxes), 0)
        self.assertEqual(group_list.mailboxes,
                         group_list.all_mailboxes)

    def test_get_group_list_mailbox_list(self):
        group_list = self._test_get_x(parser.get_group_list,
            'dinsdale@example.org, "Fred A. Bear" <dinsdale@example.org>',
            'dinsdale@example.org, "Fred A. Bear" <dinsdale@example.org>',
            'dinsdale@example.org, "Fred A. Bear" <dinsdale@example.org>',
            [],
            '')
        self.assertEqual(group_list.token_type, 'group-list')
        self.assertEqual(len(group_list.mailboxes), 2)
        self.assertEqual(group_list.mailboxes,
                         group_list.all_mailboxes)
        self.assertEqual(group_list.mailboxes[1].display_name,
                         'Fred A. Bear')

    def test_get_group_list_obs_group_list(self):
        group_list = self._test_get_x(parser.get_group_list,
            ', (foo),,(bar)',
            ', (foo),,(bar)',
            ', ,, ',
            [errors.ObsoleteHeaderDefect],
            '')
        self.assertEqual(group_list.token_type, 'group-list')
        self.assertEqual(len(group_list.mailboxes), 0)
        self.assertEqual(group_list.mailboxes,
                         group_list.all_mailboxes)

    def test_get_group_list_comment_only_invalid(self):
        group_list = self._test_get_x(parser.get_group_list,
            '(bar)',
            '(bar)',
            ' ',
            [errors.InvalidHeaderDefect],
            '')
        self.assertEqual(group_list.token_type, 'group-list')
        self.assertEqual(len(group_list.mailboxes), 0)
        self.assertEqual(group_list.mailboxes,
                         group_list.all_mailboxes)

    # get_group

    def test_get_group_empty(self):
        group = self._test_get_x(parser.get_group,
            'Monty Python:;',
            'Monty Python:;',
            'Monty Python:;',
            [],
            '')
        self.assertEqual(group.token_type, 'group')
        self.assertEqual(group.display_name, 'Monty Python')
        self.assertEqual(len(group.mailboxes), 0)
        self.assertEqual(group.mailboxes,
                         group.all_mailboxes)

    def test_get_group_cfws_only(self):
        group = self._test_get_x(parser.get_group,
            'Monty Python: (hidden);',
            'Monty Python: (hidden);',
            'Monty Python: ;',
            [],
            '')
        self.assertEqual(group.token_type, 'group')
        self.assertEqual(group.display_name, 'Monty Python')
        self.assertEqual(len(group.mailboxes), 0)
        self.assertEqual(group.mailboxes,
                         group.all_mailboxes)

    def test_get_group_single_mailbox(self):
        group = self._test_get_x(parser.get_group,
            'Monty Python: "Fred A. Bear" <dinsdale@example.com>;',
            'Monty Python: "Fred A. Bear" <dinsdale@example.com>;',
            'Monty Python: "Fred A. Bear" <dinsdale@example.com>;',
            [],
            '')
        self.assertEqual(group.token_type, 'group')
        self.assertEqual(group.display_name, 'Monty Python')
        self.assertEqual(len(group.mailboxes), 1)
        self.assertEqual(group.mailboxes,
                         group.all_mailboxes)
        self.assertEqual(group.mailboxes[0].addr_spec,
                         'dinsdale@example.com')

    def test_get_group_mixed_list(self):
        group = self._test_get_x(parser.get_group,
            ('Monty Python: "Fred A. Bear" <dinsdale@example.com>,'
                '(foo) Roger <ping@exampele.com>, x@test.example.com;'),
            ('Monty Python: "Fred A. Bear" <dinsdale@example.com>,'
                '(foo) Roger <ping@exampele.com>, x@test.example.com;'),
            ('Monty Python: "Fred A. Bear" <dinsdale@example.com>,'
                ' Roger <ping@exampele.com>, x@test.example.com;'),
            [],
            '')
        self.assertEqual(group.token_type, 'group')
        self.assertEqual(group.display_name, 'Monty Python')
        self.assertEqual(len(group.mailboxes), 3)
        self.assertEqual(group.mailboxes,
                         group.all_mailboxes)
        self.assertEqual(group.mailboxes[0].display_name,
                         'Fred A. Bear')
        self.assertEqual(group.mailboxes[1].display_name,
                         'Roger')
        self.assertEqual(group.mailboxes[2].local_part, 'x')

    def test_get_group_one_invalid(self):
        group = self._test_get_x(parser.get_group,
            ('Monty Python: "Fred A. Bear" <dinsdale@example.com>,'
                '(foo) Roger ping@exampele.com, x@test.example.com;'),
            ('Monty Python: "Fred A. Bear" <dinsdale@example.com>,'
                '(foo) Roger ping@exampele.com, x@test.example.com;'),
            ('Monty Python: "Fred A. Bear" <dinsdale@example.com>,'
                ' Roger ping@exampele.com, x@test.example.com;'),
            [errors.InvalidHeaderDefect,   # non-angle addr makes local part invalid
             errors.InvalidHeaderDefect],   # and its not obs-local either: no dots.
            '')
        self.assertEqual(group.token_type, 'group')
        self.assertEqual(group.display_name, 'Monty Python')
        self.assertEqual(len(group.mailboxes), 2)
        self.assertEqual(len(group.all_mailboxes), 3)
        self.assertEqual(group.mailboxes[0].display_name,
                         'Fred A. Bear')
        self.assertEqual(group.mailboxes[1].local_part, 'x')
        self.assertIsNone(group.all_mailboxes[1].display_name)

    # get_address

    def test_get_address_simple(self):
        address = self._test_get_x(parser.get_address,
            'dinsdale@example.com',
            'dinsdale@example.com',
            'dinsdale@example.com',
            [],
            '')
        self.assertEqual(address.token_type, 'address')
        self.assertEqual(len(address.mailboxes), 1)
        self.assertEqual(address.mailboxes,
                         address.all_mailboxes)
        self.assertEqual(address.mailboxes[0].domain,
                         'example.com')
        self.assertEqual(address[0].token_type,
                         'mailbox')

    def test_get_address_complex(self):
        address = self._test_get_x(parser.get_address,
            '(foo) "Fred A. Bear" <(bird)dinsdale@example.com>',
            '(foo) "Fred A. Bear" <(bird)dinsdale@example.com>',
            ' "Fred A. Bear" < dinsdale@example.com>',
            [],
            '')
        self.assertEqual(address.token_type, 'address')
        self.assertEqual(len(address.mailboxes), 1)
        self.assertEqual(address.mailboxes,
                         address.all_mailboxes)
        self.assertEqual(address.mailboxes[0].display_name,
                         'Fred A. Bear')
        self.assertEqual(address[0].token_type,
                         'mailbox')

    def test_get_address_empty_group(self):
        address = self._test_get_x(parser.get_address,
            'Monty Python:;',
            'Monty Python:;',
            'Monty Python:;',
            [],
            '')
        self.assertEqual(address.token_type, 'address')
        self.assertEqual(len(address.mailboxes), 0)
        self.assertEqual(address.mailboxes,
                         address.all_mailboxes)
        self.assertEqual(address[0].token_type,
                         'group')
        self.assertEqual(address[0].display_name,
                         'Monty Python')

    def test_get_address_group(self):
        address = self._test_get_x(parser.get_address,
            'Monty Python: x@example.com, y@example.com;',
            'Monty Python: x@example.com, y@example.com;',
            'Monty Python: x@example.com, y@example.com;',
            [],
            '')
        self.assertEqual(address.token_type, 'address')
        self.assertEqual(len(address.mailboxes), 2)
        self.assertEqual(address.mailboxes,
                         address.all_mailboxes)
        self.assertEqual(address[0].token_type,
                         'group')
        self.assertEqual(address[0].display_name,
                         'Monty Python')
        self.assertEqual(address.mailboxes[0].local_part, 'x')

    def test_get_address_quoted_local_part(self):
        address = self._test_get_x(parser.get_address,
            '"foo bar"@example.com',
            '"foo bar"@example.com',
            '"foo bar"@example.com',
            [],
            '')
        self.assertEqual(address.token_type, 'address')
        self.assertEqual(len(address.mailboxes), 1)
        self.assertEqual(address.mailboxes,
                         address.all_mailboxes)
        self.assertEqual(address.mailboxes[0].domain,
                         'example.com')
        self.assertEqual(address.mailboxes[0].local_part,
                         'foo bar')
        self.assertEqual(address[0].token_type, 'mailbox')

    def test_get_address_ends_at_special(self):
        address = self._test_get_x(parser.get_address,
            'dinsdale@example.com, next',
            'dinsdale@example.com',
            'dinsdale@example.com',
            [],
            ', next')
        self.assertEqual(address.token_type, 'address')
        self.assertEqual(len(address.mailboxes), 1)
        self.assertEqual(address.mailboxes,
                         address.all_mailboxes)
        self.assertEqual(address.mailboxes[0].domain,
                         'example.com')
        self.assertEqual(address[0].token_type, 'mailbox')

    def test_get_address_invalid_mailbox_invalid(self):
        address = self._test_get_x(parser.get_address,
            'ping example.com, next',
            'ping example.com',
            'ping example.com',
            [errors.InvalidHeaderDefect,    # addr-spec with no domain
             errors.InvalidHeaderDefect,    # invalid local-part
             errors.InvalidHeaderDefect,    # missing .s in local-part
            ],
            ', next')
        self.assertEqual(address.token_type, 'address')
        self.assertEqual(len(address.mailboxes), 0)
        self.assertEqual(len(address.all_mailboxes), 1)
        self.assertIsNone(address.all_mailboxes[0].domain)
        self.assertEqual(address.all_mailboxes[0].local_part, 'ping example.com')
        self.assertEqual(address[0].token_type, 'invalid-mailbox')

    def test_get_address_quoted_strings_in_atom_list(self):
        address = self._test_get_x(parser.get_address,
            '""example" example"@example.com',
            '""example" example"@example.com',
            'example example@example.com',
            [errors.InvalidHeaderDefect]*3,
            '')
        self.assertEqual(address.all_mailboxes[0].local_part, 'example example')
        self.assertEqual(address.all_mailboxes[0].domain, 'example.com')
        self.assertEqual(address.all_mailboxes[0].addr_spec, '"example example"@example.com')


    # get_address_list

    def test_get_address_list_mailboxes_simple(self):
        address_list = self._test_get_x(parser.get_address_list,
            'dinsdale@example.com',
            'dinsdale@example.com',
            'dinsdale@example.com',
            [],
            '')
        self.assertEqual(address_list.token_type, 'address-list')
        self.assertEqual(len(address_list.mailboxes), 1)
        self.assertEqual(address_list.mailboxes,
                         address_list.all_mailboxes)
        self.assertEqual([str(x) for x in address_list.mailboxes],
                         [str(x) for x in address_list.addresses])
        self.assertEqual(address_list.mailboxes[0].domain, 'example.com')
        self.assertEqual(address_list[0].token_type, 'address')
        self.assertIsNone(address_list[0].display_name)

    def test_get_address_list_mailboxes_two_simple(self):
        address_list = self._test_get_x(parser.get_address_list,
            'foo@example.com, "Fred A. Bar" <bar@example.com>',
            'foo@example.com, "Fred A. Bar" <bar@example.com>',
            'foo@example.com, "Fred A. Bar" <bar@example.com>',
            [],
            '')
        self.assertEqual(address_list.token_type, 'address-list')
        self.assertEqual(len(address_list.mailboxes), 2)
        self.assertEqual(address_list.mailboxes,
                         address_list.all_mailboxes)
        self.assertEqual([str(x) for x in address_list.mailboxes],
                         [str(x) for x in address_list.addresses])
        self.assertEqual(address_list.mailboxes[0].local_part, 'foo')
        self.assertEqual(address_list.mailboxes[1].display_name, "Fred A. Bar")

    def test_get_address_list_mailboxes_complex(self):
        address_list = self._test_get_x(parser.get_address_list,
            ('"Roy A. Bear" <dinsdale@example.com>, '
                '(ping) Foo <x@example.com>,'
                'Nobody Is. Special <y@(bird)example.(bad)com>'),
            ('"Roy A. Bear" <dinsdale@example.com>, '
                '(ping) Foo <x@example.com>,'
                'Nobody Is. Special <y@(bird)example.(bad)com>'),
            ('"Roy A. Bear" <dinsdale@example.com>, '
                'Foo <x@example.com>,'
                '"Nobody Is. Special" <y@example. com>'),
            [errors.ObsoleteHeaderDefect, # period in Is.
            errors.ObsoleteHeaderDefect], # cfws in domain
            '')
        self.assertEqual(address_list.token_type, 'address-list')
        self.assertEqual(len(address_list.mailboxes), 3)
        self.assertEqual(address_list.mailboxes,
                         address_list.all_mailboxes)
        self.assertEqual([str(x) for x in address_list.mailboxes],
                         [str(x) for x in address_list.addresses])
        self.assertEqual(address_list.mailboxes[0].domain, 'example.com')
        self.assertEqual(address_list.mailboxes[0].token_type, 'mailbox')
        self.assertEqual(address_list.addresses[0].token_type, 'address')
        self.assertEqual(address_list.mailboxes[1].local_part, 'x')
        self.assertEqual(address_list.mailboxes[2].display_name,
                         'Nobody Is. Special')

    def test_get_address_list_mailboxes_invalid_addresses(self):
        address_list = self._test_get_x(parser.get_address_list,
            ('"Roy A. Bear" <dinsdale@example.com>, '
                '(ping) Foo x@example.com[],'
                'Nobody Is. Special <(bird)example.(bad)com>'),
            ('"Roy A. Bear" <dinsdale@example.com>, '
                '(ping) Foo x@example.com[],'
                'Nobody Is. Special <(bird)example.(bad)com>'),
            ('"Roy A. Bear" <dinsdale@example.com>, '
                'Foo x@example.com[],'
                '"Nobody Is. Special" < example. com>'),
             [errors.InvalidHeaderDefect,   # invalid address in list
              errors.InvalidHeaderDefect,   # 'Foo x' local part invalid.
              errors.InvalidHeaderDefect,   # Missing . in 'Foo x' local part
              errors.ObsoleteHeaderDefect,  # period in 'Is.' disp-name phrase
              errors.InvalidHeaderDefect,   # no domain part in addr-spec
              errors.ObsoleteHeaderDefect], # addr-spec has comment in it
            '')
        self.assertEqual(address_list.token_type, 'address-list')
        self.assertEqual(len(address_list.mailboxes), 1)
        self.assertEqual(len(address_list.all_mailboxes), 3)
        self.assertEqual([str(x) for x in address_list.all_mailboxes],
                         [str(x) for x in address_list.addresses])
        self.assertEqual(address_list.mailboxes[0].domain, 'example.com')
        self.assertEqual(address_list.mailboxes[0].token_type, 'mailbox')
        self.assertEqual(address_list.addresses[0].token_type, 'address')
        self.assertEqual(address_list.addresses[1].token_type, 'address')
        self.assertEqual(len(address_list.addresses[0].mailboxes), 1)
        self.assertEqual(len(address_list.addresses[1].mailboxes), 0)
        self.assertEqual(len(address_list.addresses[1].mailboxes), 0)
        self.assertEqual(
            address_list.addresses[1].all_mailboxes[0].local_part, 'Foo x')
        self.assertEqual(
            address_list.addresses[2].all_mailboxes[0].display_name,
                "Nobody Is. Special")

    def test_get_address_list_group_empty(self):
        address_list = self._test_get_x(parser.get_address_list,
            'Monty Python: ;',
            'Monty Python: ;',
            'Monty Python: ;',
            [],
            '')
        self.assertEqual(address_list.token_type, 'address-list')
        self.assertEqual(len(address_list.mailboxes), 0)
        self.assertEqual(address_list.mailboxes,
                         address_list.all_mailboxes)
        self.assertEqual(len(address_list.addresses), 1)
        self.assertEqual(address_list.addresses[0].token_type, 'address')
        self.assertEqual(address_list.addresses[0].display_name, 'Monty Python')
        self.assertEqual(len(address_list.addresses[0].mailboxes), 0)

    def test_get_address_list_group_simple(self):
        address_list = self._test_get_x(parser.get_address_list,
            'Monty Python: dinsdale@example.com;',
            'Monty Python: dinsdale@example.com;',
            'Monty Python: dinsdale@example.com;',
            [],
            '')
        self.assertEqual(address_list.token_type, 'address-list')
        self.assertEqual(len(address_list.mailboxes), 1)
        self.assertEqual(address_list.mailboxes,
                         address_list.all_mailboxes)
        self.assertEqual(address_list.mailboxes[0].domain, 'example.com')
        self.assertEqual(address_list.addresses[0].display_name,
                         'Monty Python')
        self.assertEqual(address_list.addresses[0].mailboxes[0].domain,
                         'example.com')

    def test_get_address_list_group_and_mailboxes(self):
        address_list = self._test_get_x(parser.get_address_list,
            ('Monty Python: dinsdale@example.com, "Fred" <flint@example.com>;, '
                'Abe <x@example.com>, Bee <y@example.com>'),
            ('Monty Python: dinsdale@example.com, "Fred" <flint@example.com>;, '
                'Abe <x@example.com>, Bee <y@example.com>'),
            ('Monty Python: dinsdale@example.com, "Fred" <flint@example.com>;, '
                'Abe <x@example.com>, Bee <y@example.com>'),
            [],
            '')
        self.assertEqual(address_list.token_type, 'address-list')
        self.assertEqual(len(address_list.mailboxes), 4)
        self.assertEqual(address_list.mailboxes,
                         address_list.all_mailboxes)
        self.assertEqual(len(address_list.addresses), 3)
        self.assertEqual(address_list.mailboxes[0].local_part, 'dinsdale')
        self.assertEqual(address_list.addresses[0].display_name,
                         'Monty Python')
        self.assertEqual(address_list.addresses[0].mailboxes[0].domain,
                         'example.com')
        self.assertEqual(address_list.addresses[0].mailboxes[1].local_part,
                         'flint')
        self.assertEqual(address_list.addresses[1].mailboxes[0].local_part,
                         'x')
        self.assertEqual(address_list.addresses[2].mailboxes[0].local_part,
                         'y')
        self.assertEqual(str(address_list.addresses[1]),
                         str(address_list.mailboxes[2]))


if __name__ == '__main__':
    unittest.main()
