#12586: add provisional email policy with new header parsing and folding.

When the new policies are used (and only when the new policies are explicitly
used) headers turn into objects that have attributes based on their parsed
values, and can be set using objects that encapsulate the values, as well as
set directly from unicode strings.  The folding algorithm then takes care of
encoding unicode where needed, and folding according to the highest level
syntactic objects.

With this patch only date and time headers are parsed as anything other than
unstructured, but that is all the helper methods in the existing API handle.
I do plan to add more parsers, and complete the set specified in the RFC
before the package becomes stable.
diff --git a/Lib/test/test_email/__init__.py b/Lib/test/test_email/__init__.py
index b05fb3c..75dc64d 100644
--- a/Lib/test/test_email/__init__.py
+++ b/Lib/test/test_email/__init__.py
@@ -65,3 +65,9 @@
     def assertBytesEqual(self, first, second, msg):
         """Our byte strings are really encoded strings; improve diff output"""
         self.assertEqual(self._bytes_repr(first), self._bytes_repr(second))
+
+    def assertDefectsEqual(self, actual, expected):
+        self.assertEqual(len(actual), len(expected), actual)
+        for i in range(len(actual)):
+            self.assertIsInstance(actual[i], expected[i],
+                                    'item {}'.format(i))
diff --git a/Lib/test/test_email/test__encoded_words.py b/Lib/test/test_email/test__encoded_words.py
new file mode 100644
index 0000000..14395fe
--- /dev/null
+++ b/Lib/test/test_email/test__encoded_words.py
@@ -0,0 +1,187 @@
+import unittest
+from email import _encoded_words as _ew
+from email import errors
+from test.test_email import TestEmailBase
+
+
+class TestDecodeQ(TestEmailBase):
+
+    def _test(self, source, ex_result, ex_defects=[]):
+        result, defects = _ew.decode_q(source)
+        self.assertEqual(result, ex_result)
+        self.assertDefectsEqual(defects, ex_defects)
+
+    def test_no_encoded(self):
+        self._test(b'foobar', b'foobar')
+
+    def test_spaces(self):
+        self._test(b'foo=20bar=20', b'foo bar ')
+        self._test(b'foo_bar_', b'foo bar ')
+
+    def test_run_of_encoded(self):
+        self._test(b'foo=20=20=21=2Cbar', b'foo  !,bar')
+
+
+class TestDecodeB(TestEmailBase):
+
+    def _test(self, source, ex_result, ex_defects=[]):
+        result, defects = _ew.decode_b(source)
+        self.assertEqual(result, ex_result)
+        self.assertDefectsEqual(defects, ex_defects)
+
+    def test_simple(self):
+        self._test(b'Zm9v', b'foo')
+
+    def test_missing_padding(self):
+        self._test(b'dmk', b'vi', [errors.InvalidBase64PaddingDefect])
+
+    def test_invalid_character(self):
+        self._test(b'dm\x01k===', b'vi', [errors.InvalidBase64CharactersDefect])
+
+    def test_invalid_character_and_bad_padding(self):
+        self._test(b'dm\x01k', b'vi', [errors.InvalidBase64CharactersDefect,
+                                       errors.InvalidBase64PaddingDefect])
+
+
+class TestDecode(TestEmailBase):
+
+    def test_wrong_format_input_raises(self):
+        with self.assertRaises(ValueError):
+            _ew.decode('=?badone?=')
+        with self.assertRaises(ValueError):
+            _ew.decode('=?')
+        with self.assertRaises(ValueError):
+            _ew.decode('')
+
+    def _test(self, source, result, charset='us-ascii', lang='', defects=[]):
+        res, char, l, d = _ew.decode(source)
+        self.assertEqual(res, result)
+        self.assertEqual(char, charset)
+        self.assertEqual(l, lang)
+        self.assertDefectsEqual(d, defects)
+
+    def test_simple_q(self):
+        self._test('=?us-ascii?q?foo?=', 'foo')
+
+    def test_simple_b(self):
+        self._test('=?us-ascii?b?dmk=?=', 'vi')
+
+    def test_q_case_ignored(self):
+        self._test('=?us-ascii?Q?foo?=', 'foo')
+
+    def test_b_case_ignored(self):
+        self._test('=?us-ascii?B?dmk=?=', 'vi')
+
+    def test_non_trivial_q(self):
+        self._test('=?latin-1?q?=20F=fcr=20Elise=20?=', ' Für Elise ', 'latin-1')
+
+    def test_q_escpaed_bytes_preserved(self):
+        self._test(b'=?us-ascii?q?=20\xACfoo?='.decode('us-ascii',
+                                                       'surrogateescape'),
+                   ' \uDCACfoo',
+                   defects = [errors.UndecodableBytesDefect])
+
+    def test_b_undecodable_bytes_ignored_with_defect(self):
+        self._test(b'=?us-ascii?b?dm\xACk?='.decode('us-ascii',
+                                                   'surrogateescape'),
+                   'vi',
+                   defects = [
+                    errors.InvalidBase64CharactersDefect,
+                    errors.InvalidBase64PaddingDefect])
+
+    def test_b_invalid_bytes_ignored_with_defect(self):
+        self._test('=?us-ascii?b?dm\x01k===?=',
+                   'vi',
+                   defects = [errors.InvalidBase64CharactersDefect])
+
+    def test_b_invalid_bytes_incorrect_padding(self):
+        self._test('=?us-ascii?b?dm\x01k?=',
+                   'vi',
+                   defects = [
+                    errors.InvalidBase64CharactersDefect,
+                    errors.InvalidBase64PaddingDefect])
+
+    def test_b_padding_defect(self):
+        self._test('=?us-ascii?b?dmk?=',
+                   'vi',
+                    defects = [errors.InvalidBase64PaddingDefect])
+
+    def test_nonnull_lang(self):
+        self._test('=?us-ascii*jive?q?test?=', 'test', lang='jive')
+
+    def test_unknown_8bit_charset(self):
+        self._test('=?unknown-8bit?q?foo=ACbar?=',
+                   b'foo\xacbar'.decode('ascii', 'surrogateescape'),
+                   charset = 'unknown-8bit',
+                   defects = [])
+
+    def test_unknown_charset(self):
+        self._test('=?foobar?q?foo=ACbar?=',
+                   b'foo\xacbar'.decode('ascii', 'surrogateescape'),
+                   charset = 'foobar',
+                   # XXX Should this be a new Defect instead?
+                   defects = [errors.CharsetError])
+
+
+class TestEncodeQ(TestEmailBase):
+
+    def _test(self, src, expected):
+        self.assertEqual(_ew.encode_q(src), expected)
+
+    def test_all_safe(self):
+        self._test(b'foobar', 'foobar')
+
+    def test_spaces(self):
+        self._test(b'foo bar ', 'foo_bar_')
+
+    def test_run_of_encodables(self):
+        self._test(b'foo  ,,bar', 'foo__=2C=2Cbar')
+
+
+class TestEncodeB(TestEmailBase):
+
+    def test_simple(self):
+        self.assertEqual(_ew.encode_b(b'foo'), 'Zm9v')
+
+    def test_padding(self):
+        self.assertEqual(_ew.encode_b(b'vi'), 'dmk=')
+
+
+class TestEncode(TestEmailBase):
+
+    def test_q(self):
+        self.assertEqual(_ew.encode('foo', 'utf-8', 'q'), '=?utf-8?q?foo?=')
+
+    def test_b(self):
+        self.assertEqual(_ew.encode('foo', 'utf-8', 'b'), '=?utf-8?b?Zm9v?=')
+
+    def test_auto_q(self):
+        self.assertEqual(_ew.encode('foo', 'utf-8'), '=?utf-8?q?foo?=')
+
+    def test_auto_q_if_short_mostly_safe(self):
+        self.assertEqual(_ew.encode('vi.', 'utf-8'), '=?utf-8?q?vi=2E?=')
+
+    def test_auto_b_if_enough_unsafe(self):
+        self.assertEqual(_ew.encode('.....', 'utf-8'), '=?utf-8?b?Li4uLi4=?=')
+
+    def test_auto_b_if_long_unsafe(self):
+        self.assertEqual(_ew.encode('vi.vi.vi.vi.vi.', 'utf-8'),
+                         '=?utf-8?b?dmkudmkudmkudmkudmku?=')
+
+    def test_auto_q_if_long_mostly_safe(self):
+        self.assertEqual(_ew.encode('vi vi vi.vi ', 'utf-8'),
+                         '=?utf-8?q?vi_vi_vi=2Evi_?=')
+
+    def test_utf8_default(self):
+        self.assertEqual(_ew.encode('foo'), '=?utf-8?q?foo?=')
+
+    def test_lang(self):
+        self.assertEqual(_ew.encode('foo', lang='jive'), '=?utf-8*jive?q?foo?=')
+
+    def test_unknown_8bit(self):
+        self.assertEqual(_ew.encode('foo\uDCACbar', charset='unknown-8bit'),
+                         '=?unknown-8bit?q?foo=ACbar?=')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Lib/test/test_email/test__header_value_parser.py b/Lib/test/test_email/test__header_value_parser.py
new file mode 100644
index 0000000..75fe299
--- /dev/null
+++ b/Lib/test/test_email/test__header_value_parser.py
@@ -0,0 +1,2466 @@
+import string
+import unittest
+from email import _header_value_parser as parser
+from email import errors
+from email import policy
+from test.test_email import TestEmailBase
+
+class TestTokens(TestEmailBase):
+
+    # EWWhiteSpaceTerminal
+
+    def test_EWWhiteSpaceTerminal(self):
+        x = parser.EWWhiteSpaceTerminal(' \t', 'fws')
+        self.assertEqual(x, ' \t')
+        self.assertEqual(str(x), '')
+        self.assertEqual(x.value, '')
+        self.assertEqual(x.encoded, ' \t')
+
+    # UnstructuredTokenList
+
+    def test_undecodable_bytes_error_preserved(self):
+        badstr = b"le pouf c\xaflebre".decode('ascii', 'surrogateescape')
+        unst = parser.get_unstructured(badstr)
+        self.assertDefectsEqual(unst.all_defects, [errors.UndecodableBytesDefect])
+        parts = list(unst.parts)
+        self.assertDefectsEqual(parts[0].all_defects, [])
+        self.assertDefectsEqual(parts[1].all_defects, [])
+        self.assertDefectsEqual(parts[2].all_defects, [errors.UndecodableBytesDefect])
+
+
+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'])
+
+
+    # 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_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_encoded_word
+
+    def test_get_encoded_word_missing_start_raises(self):
+        with self.assertRaises(errors.HeaderParseError):
+            parser.get_encoded_word('abc')
+
+    def test_get_encoded_word_missing_end_raises(self):
+        with self.assertRaises(errors.HeaderParseError):
+            parser.get_encoded_word('=?abc')
+
+    def test_get_encoded_word_missing_middle_raises(self):
+        with self.assertRaises(errors.HeaderParseError):
+            parser.get_encoded_word('=?abc?=')
+
+    def test_get_encoded_word_valid_ew(self):
+        self._test_get_x(parser.get_encoded_word,
+                         '=?us-ascii?q?this_is_a_test?=  bird',
+                         'this is a test',
+                         'this is a test',
+                         [],
+                         '  bird')
+
+    def test_get_encoded_word_internal_spaces(self):
+        self._test_get_x(parser.get_encoded_word,
+                         '=?us-ascii?q?this is a test?=  bird',
+                         'this is a test',
+                         'this is a test',
+                         [errors.InvalidHeaderDefect],
+                         '  bird')
+
+    def test_get_encoded_word_gets_first(self):
+        self._test_get_x(parser.get_encoded_word,
+                         '=?us-ascii?q?first?=  =?utf-8?q?second?=',
+                         'first',
+                         'first',
+                         [],
+                         '  =?utf-8?q?second?=')
+
+    def test_get_encoded_word_gets_first_even_if_no_space(self):
+        self._test_get_x(parser.get_encoded_word,
+                         '=?us-ascii?q?first?==?utf-8?q?second?=',
+                         'first',
+                         'first',
+                         [],
+                         '=?utf-8?q?second?=')
+
+    def test_get_encoded_word_sets_extra_attributes(self):
+        ew = self._test_get_x(parser.get_encoded_word,
+                         '=?us-ascii*jive?q?first_second?=',
+                         'first second',
+                         'first second',
+                         [],
+                         '')
+        self.assertEqual(ew.encoded, '=?us-ascii*jive?q?first_second?=')
+        self.assertEqual(ew.charset, 'us-ascii')
+        self.assertEqual(ew.lang, 'jive')
+
+    def test_get_encoded_word_lang_default_is_blank(self):
+        ew = self._test_get_x(parser.get_encoded_word,
+                         '=?us-ascii?q?first_second?=',
+                         'first second',
+                         'first second',
+                         [],
+                         '')
+        self.assertEqual(ew.encoded, '=?us-ascii?q?first_second?=')
+        self.assertEqual(ew.charset, 'us-ascii')
+        self.assertEqual(ew.lang, '')
+
+    def test_get_encoded_word_non_printable_defect(self):
+        self._test_get_x(parser.get_encoded_word,
+                         '=?us-ascii?q?first\x02second?=',
+                         'first\x02second',
+                         'first\x02second',
+                         [errors.NonPrintableDefect],
+                         '')
+
+    def test_get_encoded_word_leading_internal_space(self):
+        self._test_get_x(parser.get_encoded_word,
+                        '=?us-ascii?q?=20foo?=',
+                        ' foo',
+                        ' foo',
+                        [],
+                        '')
+
+    # get_unstructured
+
+    def _get_unst(self, value):
+        token = parser.get_unstructured(value)
+        return token, ''
+
+    def test_get_unstructured_null(self):
+        self._test_get_x(self._get_unst, '', '', '', [], '')
+
+    def test_get_unstructured_one_word(self):
+        self._test_get_x(self._get_unst, 'foo', 'foo', 'foo', [], '')
+
+    def test_get_unstructured_normal_phrase(self):
+        self._test_get_x(self._get_unst, 'foo bar bird',
+                                         'foo bar bird',
+                                         'foo bar bird',
+                                         [],
+                                         '')
+
+    def test_get_unstructured_normal_phrase_with_whitespace(self):
+        self._test_get_x(self._get_unst, 'foo \t bar      bird',
+                                         'foo \t bar      bird',
+                                         'foo bar bird',
+                                         [],
+                                         '')
+
+    def test_get_unstructured_leading_whitespace(self):
+        self._test_get_x(self._get_unst, '  foo bar',
+                                         '  foo bar',
+                                         ' foo bar',
+                                         [],
+                                         '')
+
+    def test_get_unstructured_trailing_whitespace(self):
+        self._test_get_x(self._get_unst, 'foo bar  ',
+                                         'foo bar  ',
+                                         'foo bar ',
+                                         [],
+                                         '')
+
+    def test_get_unstructured_leading_and_trailing_whitespace(self):
+        self._test_get_x(self._get_unst, '  foo bar  ',
+                                         '  foo bar  ',
+                                         ' foo bar ',
+                                         [],
+                                         '')
+
+    def test_get_unstructured_one_valid_ew_no_ws(self):
+        self._test_get_x(self._get_unst, '=?us-ascii?q?bar?=',
+                                         'bar',
+                                         'bar',
+                                         [],
+                                         '')
+
+    def test_get_unstructured_one_ew_trailing_ws(self):
+        self._test_get_x(self._get_unst, '=?us-ascii?q?bar?=  ',
+                                         'bar  ',
+                                         'bar ',
+                                         [],
+                                         '')
+
+    def test_get_unstructured_one_valid_ew_trailing_text(self):
+        self._test_get_x(self._get_unst, '=?us-ascii?q?bar?= bird',
+                                         'bar bird',
+                                         'bar bird',
+                                         [],
+                                         '')
+
+    def test_get_unstructured_phrase_with_ew_in_middle_of_text(self):
+        self._test_get_x(self._get_unst, 'foo =?us-ascii?q?bar?= bird',
+                                         'foo bar bird',
+                                         'foo bar bird',
+                                         [],
+                                         '')
+
+    def test_get_unstructured_phrase_with_two_ew(self):
+        self._test_get_x(self._get_unst,
+            'foo =?us-ascii?q?bar?= =?us-ascii?q?bird?=',
+            'foo barbird',
+            'foo barbird',
+            [],
+            '')
+
+    def test_get_unstructured_phrase_with_two_ew_trailing_ws(self):
+        self._test_get_x(self._get_unst,
+            'foo =?us-ascii?q?bar?= =?us-ascii?q?bird?=   ',
+            'foo barbird   ',
+            'foo barbird ',
+            [],
+            '')
+
+    def test_get_unstructured_phrase_with_ew_with_leading_ws(self):
+        self._test_get_x(self._get_unst,
+            '  =?us-ascii?q?bar?=',
+            '  bar',
+            ' bar',
+            [],
+            '')
+
+    def test_get_unstructured_phrase_with_two_ew_extra_ws(self):
+        self._test_get_x(self._get_unst,
+            'foo =?us-ascii?q?bar?= \t  =?us-ascii?q?bird?=',
+            'foo barbird',
+            'foo barbird',
+            [],
+            '')
+
+    def test_get_unstructured_two_ew_extra_ws_trailing_text(self):
+        self._test_get_x(self._get_unst,
+            '=?us-ascii?q?test?=   =?us-ascii?q?foo?=  val',
+            'testfoo  val',
+            'testfoo val',
+            [],
+            '')
+
+    def test_get_unstructured_ew_with_internal_ws(self):
+        self._test_get_x(self._get_unst,
+            '=?iso-8859-1?q?hello=20world?=',
+            'hello world',
+            'hello world',
+            [],
+            '')
+
+    def test_get_unstructured_ew_with_internal_leading_ws(self):
+        self._test_get_x(self._get_unst,
+            '   =?us-ascii?q?=20test?=   =?us-ascii?q?=20foo?=  val',
+            '    test foo  val',
+            '  test foo val',
+            [],
+            '')
+
+    def test_get_unstructured_invaild_ew(self):
+        self._test_get_x(self._get_unst,
+            '=?test val',
+            '=?test val',
+            '=?test val',
+            [],
+            '')
+
+    def test_get_unstructured_undecodable_bytes(self):
+        self._test_get_x(self._get_unst,
+            b'test \xACfoo  val'.decode('ascii', 'surrogateescape'),
+            'test \uDCACfoo  val',
+            'test \uDCACfoo val',
+            [errors.UndecodableBytesDefect],
+            '')
+
+    def test_get_unstructured_undecodable_bytes_in_EW(self):
+        self._test_get_x(self._get_unst,
+            (b'=?us-ascii?q?=20test?=   =?us-ascii?q?=20\xACfoo?='
+                b'  val').decode('ascii', 'surrogateescape'),
+            ' test \uDCACfoo  val',
+            ' test \uDCACfoo val',
+            [errors.UndecodableBytesDefect]*2,
+            '')
+
+    def test_get_unstructured_missing_base64_padding(self):
+        self._test_get_x(self._get_unst,
+            '=?utf-8?b?dmk?=',
+            'vi',
+            'vi',
+            [errors.InvalidBase64PaddingDefect],
+            '')
+
+    def test_get_unstructured_invalid_base64_character(self):
+        self._test_get_x(self._get_unst,
+            '=?utf-8?b?dm\x01k===?=',
+            'vi',
+            'vi',
+            [errors.InvalidBase64CharactersDefect],
+            '')
+
+    def test_get_unstructured_invalid_base64_character_and_bad_padding(self):
+        self._test_get_x(self._get_unst,
+            '=?utf-8?b?dm\x01k?=',
+            'vi',
+            'vi',
+            [errors.InvalidBase64CharactersDefect,
+             errors.InvalidBase64PaddingDefect],
+            '')
+
+    def test_get_unstructured_no_whitespace_between_ews(self):
+        self._test_get_x(self._get_unst,
+            '=?utf-8?q?foo?==?utf-8?q?bar?=',
+            'foobar',
+            'foobar',
+            [errors.InvalidHeaderDefect],
+            '')
+
+    # 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_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')
+
+    def test_get_local_part_trailing_dot(self):
+        local_part = self._test_get_x(parser.get_local_part,
+            ' borris.@python.org',
+            ' borris.',
+            ' borris.',
+            [errors.InvalidHeaderDefect]*2,
+            '@python.org')
+        self.assertEqual(local_part.local_part, 'borris.')
+
+    def test_get_local_part_trailing_dot_with_ws(self):
+        local_part = self._test_get_x(parser.get_local_part,
+            ' borris. @python.org',
+            ' borris. ',
+            ' borris. ',
+            [errors.InvalidHeaderDefect]*2,
+            '@python.org')
+        self.assertEqual(local_part.local_part, 'borris.')
+
+    def test_get_local_part_leading_dot(self):
+        local_part = self._test_get_x(parser.get_local_part,
+            '.borris@python.org',
+            '.borris',
+            '.borris',
+            [errors.InvalidHeaderDefect]*2,
+            '@python.org')
+        self.assertEqual(local_part.local_part, '.borris')
+
+    def test_get_local_part_leading_dot_after_ws(self):
+        local_part = self._test_get_x(parser.get_local_part,
+            ' .borris@python.org',
+            ' .borris',
+            ' .borris',
+            [errors.InvalidHeaderDefect]*2,
+            '@python.org')
+        self.assertEqual(local_part.local_part, '.borris')
+
+    def test_get_local_part_double_dot_raises(self):
+        local_part = self._test_get_x(parser.get_local_part,
+            ' borris.(foo).natasha@python.org',
+            ' borris.(foo).natasha',
+            ' borris. .natasha',
+            [errors.InvalidHeaderDefect]*2,
+            '@python.org')
+        self.assertEqual(local_part.local_part, 'borris..natasha')
+
+    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')
+
+    def test_get_local_part_unicode_defect(self):
+        # Currently this only happens when parsing unicode, not when parsing
+        # stuff that was originally binary.
+        local_part = self._test_get_x(parser.get_local_part,
+            'exámple@example.com',
+            'exámple',
+            'exámple',
+            [errors.NonASCIILocalPartDefect],
+            '@example.com')
+        self.assertEqual(local_part.local_part, 'exámple')
+
+    # 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')
+
+    def test_get_addr_spec_dot_atom(self):
+        addr_spec = self._test_get_x(parser.get_addr_spec,
+            'star.a.star@example.com',
+            'star.a.star@example.com',
+            'star.a.star@example.com',
+            [],
+            '')
+        self.assertEqual(addr_spec.local_part, 'star.a.star')
+        self.assertEqual(addr_spec.domain, 'example.com')
+        self.assertEqual(addr_spec.addr_spec, 'star.a.star@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_troup_null_addr_spec(self):
+        group = self._test_get_x(parser.get_group,
+            'foo: <>;',
+            'foo: <>;',
+            'foo: <>;',
+            [errors.InvalidHeaderDefect],
+            '')
+        self.assertEqual(group.display_name, 'foo')
+        self.assertEqual(len(group.mailboxes), 0)
+        self.assertEqual(len(group.all_mailboxes), 1)
+        self.assertEqual(group.all_mailboxes[0].value, '<>')
+
+    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]))
+
+
+class TestFolding(TestEmailBase):
+
+    policy = policy.default
+
+    def _test(self, tl, folded, policy=policy):
+        self.assertEqual(tl.fold(policy=policy), folded, tl.ppstr())
+
+    def test_simple_unstructured_no_folds(self):
+        self._test(parser.get_unstructured("This is a test"),
+                   "This is a test\n")
+
+    def test_simple_unstructured_folded(self):
+        self._test(parser.get_unstructured("This is also a test, but this "
+                        "time there are enough words (and even some "
+                        "symbols) to make it wrap; at least in theory."),
+                   "This is also a test, but this time there are enough "
+                        "words (and even some\n"
+                   " symbols) to make it wrap; at least in theory.\n")
+
+    def test_unstructured_with_unicode_no_folds(self):
+        self._test(parser.get_unstructured("hübsch kleiner beißt"),
+                   "=?utf-8?q?h=C3=BCbsch_kleiner_bei=C3=9Ft?=\n")
+
+    def test_one_ew_on_each_of_two_wrapped_lines(self):
+        self._test(parser.get_unstructured("Mein kleiner Kaktus ist sehr "
+                                           "hübsch.  Es hat viele Stacheln "
+                                           "und oft beißt mich."),
+                   "Mein kleiner Kaktus ist sehr =?utf-8?q?h=C3=BCbsch=2E?=  "
+                        "Es hat viele Stacheln\n"
+                   " und oft =?utf-8?q?bei=C3=9Ft?= mich.\n")
+
+    def test_ews_combined_before_wrap(self):
+        self._test(parser.get_unstructured("Mein Kaktus ist hübsch.  "
+                                           "Es beißt mich.  "
+                                           "And that's all I'm sayin."),
+                   "Mein Kaktus ist =?utf-8?q?h=C3=BCbsch=2E__Es_bei=C3=9Ft?= "
+                        "mich.  And that's\n"
+                   " all I'm sayin.\n")
+
+    # XXX Need test of an encoded word so long that it needs to be wrapped
+
+    def test_simple_address(self):
+        self._test(parser.get_address_list("abc <xyz@example.com>")[0],
+                   "abc <xyz@example.com>\n")
+
+    def test_address_list_folding_at_commas(self):
+        self._test(parser.get_address_list('abc <xyz@example.com>, '
+                                            '"Fred Blunt" <sharp@example.com>, '
+                                            '"J.P.Cool" <hot@example.com>, '
+                                            '"K<>y" <key@example.com>, '
+                                            'Firesale <cheap@example.com>, '
+                                            '<end@example.com>')[0],
+                    'abc <xyz@example.com>, "Fred Blunt" <sharp@example.com>,\n'
+                    ' "J.P.Cool" <hot@example.com>, "K<>y" <key@example.com>,\n'
+                    ' Firesale <cheap@example.com>, <end@example.com>\n')
+
+    def test_address_list_with_unicode_names(self):
+        self._test(parser.get_address_list(
+            'Hübsch Kaktus <beautiful@example.com>, '
+                'beißt beißt <biter@example.com>')[0],
+            '=?utf-8?q?H=C3=BCbsch?= Kaktus <beautiful@example.com>,\n'
+                ' =?utf-8?q?bei=C3=9Ft_bei=C3=9Ft?= <biter@example.com>\n')
+
+    def test_address_list_with_unicode_names_in_quotes(self):
+        self._test(parser.get_address_list(
+            '"Hübsch Kaktus" <beautiful@example.com>, '
+                '"beißt" beißt <biter@example.com>')[0],
+            '=?utf-8?q?H=C3=BCbsch?= Kaktus <beautiful@example.com>,\n'
+                ' =?utf-8?q?bei=C3=9Ft_bei=C3=9Ft?= <biter@example.com>\n')
+
+    # XXX Need tests with comments on various sides of a unicode token,
+    # and with unicode tokens in the comments.  Spaces inside the quotes
+    # currently don't do the right thing.
+
+    def test_initial_whitespace_splitting(self):
+        body = parser.get_unstructured('   ' + 'x'*77)
+        header = parser.Header([
+            parser.HeaderLabel([parser.ValueTerminal('test:', 'atext')]),
+            parser.CFWSList([parser.WhiteSpaceTerminal(' ', 'fws')]), body])
+        self._test(header, 'test:   \n ' + 'x'*77 + '\n')
+
+    def test_whitespace_splitting(self):
+        self._test(parser.get_unstructured('xxx   ' + 'y'*77),
+                   'xxx  \n ' + 'y'*77 + '\n')
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Lib/test/test_email/test__headerregistry.py b/Lib/test/test_email/test__headerregistry.py
new file mode 100644
index 0000000..4398e29
--- /dev/null
+++ b/Lib/test/test_email/test__headerregistry.py
@@ -0,0 +1,717 @@
+import datetime
+import textwrap
+import unittest
+from email import errors
+from email import policy
+from test.test_email import TestEmailBase
+from email import _headerregistry
+# Address and Group are public but I'm not sure where to put them yet.
+from email._headerregistry import Address, Group
+
+
+class TestHeaderRegistry(TestEmailBase):
+
+    def test_arbitrary_name_unstructured(self):
+        factory = _headerregistry.HeaderRegistry()
+        h = factory('foobar', 'test')
+        self.assertIsInstance(h, _headerregistry.BaseHeader)
+        self.assertIsInstance(h, _headerregistry.UnstructuredHeader)
+
+    def test_name_case_ignored(self):
+        factory = _headerregistry.HeaderRegistry()
+        # Whitebox check that test is valid
+        self.assertNotIn('Subject', factory.registry)
+        h = factory('Subject', 'test')
+        self.assertIsInstance(h, _headerregistry.BaseHeader)
+        self.assertIsInstance(h, _headerregistry.UniqueUnstructuredHeader)
+
+    class FooBase:
+        def __init__(self, *args, **kw):
+            pass
+
+    def test_override_default_base_class(self):
+        factory = _headerregistry.HeaderRegistry(base_class=self.FooBase)
+        h = factory('foobar', 'test')
+        self.assertIsInstance(h, self.FooBase)
+        self.assertIsInstance(h, _headerregistry.UnstructuredHeader)
+
+    class FooDefault:
+        parse = _headerregistry.UnstructuredHeader.parse
+
+    def test_override_default_class(self):
+        factory = _headerregistry.HeaderRegistry(default_class=self.FooDefault)
+        h = factory('foobar', 'test')
+        self.assertIsInstance(h, _headerregistry.BaseHeader)
+        self.assertIsInstance(h, self.FooDefault)
+
+    def test_override_default_class_only_overrides_default(self):
+        factory = _headerregistry.HeaderRegistry(default_class=self.FooDefault)
+        h = factory('subject', 'test')
+        self.assertIsInstance(h, _headerregistry.BaseHeader)
+        self.assertIsInstance(h, _headerregistry.UniqueUnstructuredHeader)
+
+    def test_dont_use_default_map(self):
+        factory = _headerregistry.HeaderRegistry(use_default_map=False)
+        h = factory('subject', 'test')
+        self.assertIsInstance(h, _headerregistry.BaseHeader)
+        self.assertIsInstance(h, _headerregistry.UnstructuredHeader)
+
+    def test_map_to_type(self):
+        factory = _headerregistry.HeaderRegistry()
+        h1 = factory('foobar', 'test')
+        factory.map_to_type('foobar', _headerregistry.UniqueUnstructuredHeader)
+        h2 = factory('foobar', 'test')
+        self.assertIsInstance(h1, _headerregistry.BaseHeader)
+        self.assertIsInstance(h1, _headerregistry.UnstructuredHeader)
+        self.assertIsInstance(h2, _headerregistry.BaseHeader)
+        self.assertIsInstance(h2, _headerregistry.UniqueUnstructuredHeader)
+
+
+class TestHeaderBase(TestEmailBase):
+
+    factory = _headerregistry.HeaderRegistry()
+
+    def make_header(self, name, value):
+        return self.factory(name, value)
+
+
+class TestBaseHeaderFeatures(TestHeaderBase):
+
+    def test_str(self):
+        h = self.make_header('subject', 'this is a test')
+        self.assertIsInstance(h, str)
+        self.assertEqual(h, 'this is a test')
+        self.assertEqual(str(h), 'this is a test')
+
+    def test_substr(self):
+        h = self.make_header('subject', 'this is a test')
+        self.assertEqual(h[5:7], 'is')
+
+    def test_has_name(self):
+        h = self.make_header('subject', 'this is a test')
+        self.assertEqual(h.name, 'subject')
+
+    def _test_attr_ro(self, attr):
+        h = self.make_header('subject', 'this is a test')
+        with self.assertRaises(AttributeError):
+            setattr(h, attr, 'foo')
+
+    def test_name_read_only(self):
+        self._test_attr_ro('name')
+
+    def test_defects_read_only(self):
+        self._test_attr_ro('defects')
+
+    def test_defects_is_tuple(self):
+        h = self.make_header('subject', 'this is a test')
+        self.assertEqual(len(h.defects), 0)
+        self.assertIsInstance(h.defects, tuple)
+        # Make sure it is still true when there are defects.
+        h = self.make_header('date', '')
+        self.assertEqual(len(h.defects), 1)
+        self.assertIsInstance(h.defects, tuple)
+
+    # XXX: FIXME
+    #def test_CR_in_value(self):
+    #    # XXX: this also re-raises the issue of embedded headers,
+    #    # need test and solution for that.
+    #    value = '\r'.join(['this is', ' a test'])
+    #    h = self.make_header('subject', value)
+    #    self.assertEqual(h, value)
+    #    self.assertDefectsEqual(h.defects, [errors.ObsoleteHeaderDefect])
+
+    def test_RFC2047_value_decoded(self):
+        value = '=?utf-8?q?this_is_a_test?='
+        h = self.make_header('subject', value)
+        self.assertEqual(h, 'this is a test')
+
+
+class TestDateHeader(TestHeaderBase):
+
+    datestring = 'Sun, 23 Sep 2001 20:10:55 -0700'
+    utcoffset = datetime.timedelta(hours=-7)
+    tz = datetime.timezone(utcoffset)
+    dt = datetime.datetime(2001, 9, 23, 20, 10, 55, tzinfo=tz)
+
+    def test_parse_date(self):
+        h = self.make_header('date', self.datestring)
+        self.assertEqual(h, self.datestring)
+        self.assertEqual(h.datetime, self.dt)
+        self.assertEqual(h.datetime.utcoffset(), self.utcoffset)
+        self.assertEqual(h.defects, ())
+
+    def test_set_from_datetime(self):
+        h = self.make_header('date', self.dt)
+        self.assertEqual(h, self.datestring)
+        self.assertEqual(h.datetime, self.dt)
+        self.assertEqual(h.defects, ())
+
+    def test_date_header_properties(self):
+        h = self.make_header('date', self.datestring)
+        self.assertIsInstance(h, _headerregistry.UniqueDateHeader)
+        self.assertEqual(h.max_count, 1)
+        self.assertEqual(h.defects, ())
+
+    def test_resent_date_header_properties(self):
+        h = self.make_header('resent-date', self.datestring)
+        self.assertIsInstance(h, _headerregistry.DateHeader)
+        self.assertEqual(h.max_count, None)
+        self.assertEqual(h.defects, ())
+
+    def test_no_value_is_defect(self):
+        h = self.make_header('date', '')
+        self.assertEqual(len(h.defects), 1)
+        self.assertIsInstance(h.defects[0], errors.HeaderMissingRequiredValue)
+
+    def test_datetime_read_only(self):
+        h = self.make_header('date', self.datestring)
+        with self.assertRaises(AttributeError):
+            h.datetime = 'foo'
+
+
+class TestAddressHeader(TestHeaderBase):
+
+    examples = {
+
+        'empty':
+            ('<>',
+             [errors.InvalidHeaderDefect],
+             '<>',
+             '',
+             '<>',
+             '',
+             '',
+             None),
+
+        'address_only':
+            ('zippy@pinhead.com',
+             [],
+             'zippy@pinhead.com',
+             '',
+             'zippy@pinhead.com',
+             'zippy',
+             'pinhead.com',
+             None),
+
+        'name_and_address':
+            ('Zaphrod Beblebrux <zippy@pinhead.com>',
+             [],
+             'Zaphrod Beblebrux <zippy@pinhead.com>',
+             'Zaphrod Beblebrux',
+             'zippy@pinhead.com',
+             'zippy',
+             'pinhead.com',
+             None),
+
+        'quoted_local_part':
+            ('Zaphrod Beblebrux <"foo bar"@pinhead.com>',
+             [],
+             'Zaphrod Beblebrux <"foo bar"@pinhead.com>',
+             'Zaphrod Beblebrux',
+             '"foo bar"@pinhead.com',
+             'foo bar',
+             'pinhead.com',
+             None),
+
+        'quoted_parens_in_name':
+            (r'"A \(Special\) Person" <person@dom.ain>',
+             [],
+             '"A (Special) Person" <person@dom.ain>',
+             'A (Special) Person',
+             'person@dom.ain',
+             'person',
+             'dom.ain',
+             None),
+
+        'quoted_backslashes_in_name':
+            (r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>',
+             [],
+             r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>',
+             r'Arthur \Backslash\ Foobar',
+             'person@dom.ain',
+             'person',
+             'dom.ain',
+             None),
+
+        'name_with_dot':
+            ('John X. Doe <jxd@example.com>',
+             [errors.ObsoleteHeaderDefect],
+             '"John X. Doe" <jxd@example.com>',
+             'John X. Doe',
+             'jxd@example.com',
+             'jxd',
+             'example.com',
+             None),
+
+        'quoted_strings_in_local_part':
+            ('""example" example"@example.com',
+             [errors.InvalidHeaderDefect]*3,
+             '"example example"@example.com',
+             '',
+             '"example example"@example.com',
+             'example example',
+             'example.com',
+             None),
+
+        'escaped_quoted_strings_in_local_part':
+            (r'"\"example\" example"@example.com',
+             [],
+             r'"\"example\" example"@example.com',
+             '',
+             r'"\"example\" example"@example.com',
+             r'"example" example',
+             'example.com',
+            None),
+
+        'escaped_escapes_in_local_part':
+            (r'"\\"example\\" example"@example.com',
+             [errors.InvalidHeaderDefect]*5,
+             r'"\\example\\\\ example"@example.com',
+             '',
+             r'"\\example\\\\ example"@example.com',
+             r'\example\\ example',
+             'example.com',
+            None),
+
+        'spaces_in_unquoted_local_part_collapsed':
+            ('merwok  wok  @example.com',
+             [errors.InvalidHeaderDefect]*2,
+             '"merwok wok"@example.com',
+             '',
+             '"merwok wok"@example.com',
+             'merwok wok',
+             'example.com',
+             None),
+
+        'spaces_around_dots_in_local_part_removed':
+            ('merwok. wok .  wok@example.com',
+             [errors.ObsoleteHeaderDefect],
+             'merwok.wok.wok@example.com',
+             '',
+             'merwok.wok.wok@example.com',
+             'merwok.wok.wok',
+             'example.com',
+             None),
+
+        }
+
+        # XXX: Need many more examples, and in particular some with names in
+        # trailing comments, which aren't currently handled.  comments in
+        # general are not handled yet.
+
+    def _test_single_addr(self, source, defects, decoded, display_name,
+                          addr_spec, username, domain, comment):
+        h = self.make_header('sender', source)
+        self.assertEqual(h, decoded)
+        self.assertDefectsEqual(h.defects, defects)
+        a = h.address
+        self.assertEqual(str(a), decoded)
+        self.assertEqual(len(h.groups), 1)
+        self.assertEqual([a], list(h.groups[0].addresses))
+        self.assertEqual([a], list(h.addresses))
+        self.assertEqual(a.display_name, display_name)
+        self.assertEqual(a.addr_spec, addr_spec)
+        self.assertEqual(a.username, username)
+        self.assertEqual(a.domain, domain)
+        # XXX: we have no comment support yet.
+        #self.assertEqual(a.comment, comment)
+
+    for name in examples:
+        locals()['test_'+name] = (
+            lambda self, name=name:
+                self._test_single_addr(*self.examples[name]))
+
+    def _test_group_single_addr(self, source, defects, decoded, display_name,
+                                addr_spec, username, domain, comment):
+        source = 'foo: {};'.format(source)
+        gdecoded = 'foo: {};'.format(decoded) if decoded else 'foo:;'
+        h = self.make_header('to', source)
+        self.assertEqual(h, gdecoded)
+        self.assertDefectsEqual(h.defects, defects)
+        self.assertEqual(h.groups[0].addresses, h.addresses)
+        self.assertEqual(len(h.groups), 1)
+        self.assertEqual(len(h.addresses), 1)
+        a = h.addresses[0]
+        self.assertEqual(str(a), decoded)
+        self.assertEqual(a.display_name, display_name)
+        self.assertEqual(a.addr_spec, addr_spec)
+        self.assertEqual(a.username, username)
+        self.assertEqual(a.domain, domain)
+
+    for name in examples:
+        locals()['test_group_'+name] = (
+            lambda self, name=name:
+                self._test_group_single_addr(*self.examples[name]))
+
+    def test_simple_address_list(self):
+        value = ('Fred <dinsdale@python.org>, foo@example.com, '
+                    '"Harry W. Hastings" <hasty@example.com>')
+        h = self.make_header('to', value)
+        self.assertEqual(h, value)
+        self.assertEqual(len(h.groups), 3)
+        self.assertEqual(len(h.addresses), 3)
+        for i in range(3):
+            self.assertEqual(h.groups[i].addresses[0], h.addresses[i])
+        self.assertEqual(str(h.addresses[0]), 'Fred <dinsdale@python.org>')
+        self.assertEqual(str(h.addresses[1]), 'foo@example.com')
+        self.assertEqual(str(h.addresses[2]),
+            '"Harry W. Hastings" <hasty@example.com>')
+        self.assertEqual(h.addresses[2].display_name,
+            'Harry W. Hastings')
+
+    def test_complex_address_list(self):
+        examples = list(self.examples.values())
+        source = ('dummy list:;, another: (empty);,' +
+                 ', '.join([x[0] for x in examples[:4]]) + ', ' +
+                 r'"A \"list\"": ' +
+                    ', '.join([x[0] for x in examples[4:6]]) + ';,' +
+                 ', '.join([x[0] for x in examples[6:]])
+            )
+        # XXX: the fact that (empty) disappears here is a potential API design
+        # bug.  We don't currently have a way to preserve comments.
+        expected = ('dummy list:;, another:;, ' +
+                 ', '.join([x[2] for x in examples[:4]]) + ', ' +
+                 r'"A \"list\"": ' +
+                    ', '.join([x[2] for x in examples[4:6]]) + ';, ' +
+                 ', '.join([x[2] for x in examples[6:]])
+            )
+
+        h = self.make_header('to', source)
+        self.assertEqual(h.split(','), expected.split(','))
+        self.assertEqual(h, expected)
+        self.assertEqual(len(h.groups), 7 + len(examples) - 6)
+        self.assertEqual(h.groups[0].display_name, 'dummy list')
+        self.assertEqual(h.groups[1].display_name, 'another')
+        self.assertEqual(h.groups[6].display_name, 'A "list"')
+        self.assertEqual(len(h.addresses), len(examples))
+        for i in range(4):
+            self.assertIsNone(h.groups[i+2].display_name)
+            self.assertEqual(str(h.groups[i+2].addresses[0]), examples[i][2])
+        for i in range(7, 7 + len(examples) - 6):
+            self.assertIsNone(h.groups[i].display_name)
+            self.assertEqual(str(h.groups[i].addresses[0]), examples[i-1][2])
+        for i in range(len(examples)):
+            self.assertEqual(str(h.addresses[i]), examples[i][2])
+            self.assertEqual(h.addresses[i].addr_spec, examples[i][4])
+
+    def test_address_read_only(self):
+        h = self.make_header('sender', 'abc@xyz.com')
+        with self.assertRaises(AttributeError):
+            h.address = 'foo'
+
+    def test_addresses_read_only(self):
+        h = self.make_header('sender', 'abc@xyz.com')
+        with self.assertRaises(AttributeError):
+            h.addresses = 'foo'
+
+    def test_groups_read_only(self):
+        h = self.make_header('sender', 'abc@xyz.com')
+        with self.assertRaises(AttributeError):
+            h.groups = 'foo'
+
+    def test_addresses_types(self):
+        source = 'me <who@example.com>'
+        h = self.make_header('to', source)
+        self.assertIsInstance(h.addresses, tuple)
+        self.assertIsInstance(h.addresses[0], Address)
+
+    def test_groups_types(self):
+        source = 'me <who@example.com>'
+        h = self.make_header('to', source)
+        self.assertIsInstance(h.groups, tuple)
+        self.assertIsInstance(h.groups[0], Group)
+
+    def test_set_from_Address(self):
+        h = self.make_header('to', Address('me', 'foo', 'example.com'))
+        self.assertEqual(h, 'me <foo@example.com>')
+
+    def test_set_from_Address_list(self):
+        h = self.make_header('to', [Address('me', 'foo', 'example.com'),
+                                    Address('you', 'bar', 'example.com')])
+        self.assertEqual(h, 'me <foo@example.com>, you <bar@example.com>')
+
+    def test_set_from_Address_and_Group_list(self):
+        h = self.make_header('to', [Address('me', 'foo', 'example.com'),
+                                    Group('bing', [Address('fiz', 'z', 'b.com'),
+                                                   Address('zif', 'f', 'c.com')]),
+                                    Address('you', 'bar', 'example.com')])
+        self.assertEqual(h, 'me <foo@example.com>, bing: fiz <z@b.com>, '
+                            'zif <f@c.com>;, you <bar@example.com>')
+        self.assertEqual(h.fold(policy=policy.default.clone(max_line_length=40)),
+                        'to: me <foo@example.com>,\n'
+                        ' bing: fiz <z@b.com>, zif <f@c.com>;,\n'
+                        ' you <bar@example.com>\n')
+
+    def test_set_from_Group_list(self):
+        h = self.make_header('to', [Group('bing', [Address('fiz', 'z', 'b.com'),
+                                                   Address('zif', 'f', 'c.com')])])
+        self.assertEqual(h, 'bing: fiz <z@b.com>, zif <f@c.com>;')
+
+
+class TestAddressAndGroup(TestEmailBase):
+
+    def _test_attr_ro(self, obj, attr):
+        with self.assertRaises(AttributeError):
+            setattr(obj, attr, 'foo')
+
+    def test_address_display_name_ro(self):
+        self._test_attr_ro(Address('foo', 'bar', 'baz'), 'display_name')
+
+    def test_address_username_ro(self):
+        self._test_attr_ro(Address('foo', 'bar', 'baz'), 'username')
+
+    def test_address_domain_ro(self):
+        self._test_attr_ro(Address('foo', 'bar', 'baz'), 'domain')
+
+    def test_group_display_name_ro(self):
+        self._test_attr_ro(Group('foo'), 'display_name')
+
+    def test_group_addresses_ro(self):
+        self._test_attr_ro(Group('foo'), 'addresses')
+
+    def test_address_from_username_domain(self):
+        a = Address('foo', 'bar', 'baz')
+        self.assertEqual(a.display_name, 'foo')
+        self.assertEqual(a.username, 'bar')
+        self.assertEqual(a.domain, 'baz')
+        self.assertEqual(a.addr_spec, 'bar@baz')
+        self.assertEqual(str(a), 'foo <bar@baz>')
+
+    def test_address_from_addr_spec(self):
+        a = Address('foo', addr_spec='bar@baz')
+        self.assertEqual(a.display_name, 'foo')
+        self.assertEqual(a.username, 'bar')
+        self.assertEqual(a.domain, 'baz')
+        self.assertEqual(a.addr_spec, 'bar@baz')
+        self.assertEqual(str(a), 'foo <bar@baz>')
+
+    def test_address_with_no_display_name(self):
+        a = Address(addr_spec='bar@baz')
+        self.assertEqual(a.display_name, '')
+        self.assertEqual(a.username, 'bar')
+        self.assertEqual(a.domain, 'baz')
+        self.assertEqual(a.addr_spec, 'bar@baz')
+        self.assertEqual(str(a), 'bar@baz')
+
+    def test_null_address(self):
+        a = Address()
+        self.assertEqual(a.display_name, '')
+        self.assertEqual(a.username, '')
+        self.assertEqual(a.domain, '')
+        self.assertEqual(a.addr_spec, '<>')
+        self.assertEqual(str(a), '<>')
+
+    def test_domain_only(self):
+        # This isn't really a valid address.
+        a = Address(domain='buzz')
+        self.assertEqual(a.display_name, '')
+        self.assertEqual(a.username, '')
+        self.assertEqual(a.domain, 'buzz')
+        self.assertEqual(a.addr_spec, '@buzz')
+        self.assertEqual(str(a), '@buzz')
+
+    def test_username_only(self):
+        # This isn't really a valid address.
+        a = Address(username='buzz')
+        self.assertEqual(a.display_name, '')
+        self.assertEqual(a.username, 'buzz')
+        self.assertEqual(a.domain, '')
+        self.assertEqual(a.addr_spec, 'buzz')
+        self.assertEqual(str(a), 'buzz')
+
+    def test_display_name_only(self):
+        a = Address('buzz')
+        self.assertEqual(a.display_name, 'buzz')
+        self.assertEqual(a.username, '')
+        self.assertEqual(a.domain, '')
+        self.assertEqual(a.addr_spec, '<>')
+        self.assertEqual(str(a), 'buzz <>')
+
+    def test_quoting(self):
+        # Ideally we'd check every special individually, but I'm not up for
+        # writing that many tests.
+        a = Address('Sara J.', 'bad name', 'example.com')
+        self.assertEqual(a.display_name, 'Sara J.')
+        self.assertEqual(a.username, 'bad name')
+        self.assertEqual(a.domain, 'example.com')
+        self.assertEqual(a.addr_spec, '"bad name"@example.com')
+        self.assertEqual(str(a), '"Sara J." <"bad name"@example.com>')
+
+    def test_il8n(self):
+        a = Address('Éric', 'wok', 'exàmple.com')
+        self.assertEqual(a.display_name, 'Éric')
+        self.assertEqual(a.username, 'wok')
+        self.assertEqual(a.domain, 'exàmple.com')
+        self.assertEqual(a.addr_spec, 'wok@exàmple.com')
+        self.assertEqual(str(a), 'Éric <wok@exàmple.com>')
+
+    # XXX: there is an API design issue that needs to be solved here.
+    #def test_non_ascii_username_raises(self):
+    #    with self.assertRaises(ValueError):
+    #        Address('foo', 'wők', 'example.com')
+
+    def test_non_ascii_username_in_addr_spec_raises(self):
+        with self.assertRaises(ValueError):
+            Address('foo', addr_spec='wők@example.com')
+
+    def test_address_addr_spec_and_username_raises(self):
+        with self.assertRaises(TypeError):
+            Address('foo', username='bing', addr_spec='bar@baz')
+
+    def test_address_addr_spec_and_domain_raises(self):
+        with self.assertRaises(TypeError):
+            Address('foo', domain='bing', addr_spec='bar@baz')
+
+    def test_address_addr_spec_and_username_and_domain_raises(self):
+        with self.assertRaises(TypeError):
+            Address('foo', username='bong', domain='bing', addr_spec='bar@baz')
+
+    def test_space_in_addr_spec_username_raises(self):
+        with self.assertRaises(ValueError):
+            Address('foo', addr_spec="bad name@example.com")
+
+    def test_bad_addr_sepc_raises(self):
+        with self.assertRaises(ValueError):
+            Address('foo', addr_spec="name@ex[]ample.com")
+
+    def test_empty_group(self):
+        g = Group('foo')
+        self.assertEqual(g.display_name, 'foo')
+        self.assertEqual(g.addresses, tuple())
+        self.assertEqual(str(g), 'foo:;')
+
+    def test_empty_group_list(self):
+        g = Group('foo', addresses=[])
+        self.assertEqual(g.display_name, 'foo')
+        self.assertEqual(g.addresses, tuple())
+        self.assertEqual(str(g), 'foo:;')
+
+    def test_null_group(self):
+        g = Group()
+        self.assertIsNone(g.display_name)
+        self.assertEqual(g.addresses, tuple())
+        self.assertEqual(str(g), 'None:;')
+
+    def test_group_with_addresses(self):
+        addrs = [Address('b', 'b', 'c'), Address('a', 'b','c')]
+        g = Group('foo', addrs)
+        self.assertEqual(g.display_name, 'foo')
+        self.assertEqual(g.addresses, tuple(addrs))
+        self.assertEqual(str(g), 'foo: b <b@c>, a <b@c>;')
+
+    def test_group_with_addresses_no_display_name(self):
+        addrs = [Address('b', 'b', 'c'), Address('a', 'b','c')]
+        g = Group(addresses=addrs)
+        self.assertIsNone(g.display_name)
+        self.assertEqual(g.addresses, tuple(addrs))
+        self.assertEqual(str(g), 'None: b <b@c>, a <b@c>;')
+
+    def test_group_with_one_address_no_display_name(self):
+        addrs = [Address('b', 'b', 'c')]
+        g = Group(addresses=addrs)
+        self.assertIsNone(g.display_name)
+        self.assertEqual(g.addresses, tuple(addrs))
+        self.assertEqual(str(g), 'b <b@c>')
+
+    def test_display_name_quoting(self):
+        g = Group('foo.bar')
+        self.assertEqual(g.display_name, 'foo.bar')
+        self.assertEqual(g.addresses, tuple())
+        self.assertEqual(str(g), '"foo.bar":;')
+
+    def test_display_name_blanks_not_quoted(self):
+        g = Group('foo bar')
+        self.assertEqual(g.display_name, 'foo bar')
+        self.assertEqual(g.addresses, tuple())
+        self.assertEqual(str(g), 'foo bar:;')
+
+
+class TestFolding(TestHeaderBase):
+
+    def test_short_unstructured(self):
+        h = self.make_header('subject', 'this is a test')
+        self.assertEqual(h.fold(policy=self.policy),
+                         'subject: this is a test\n')
+
+    def test_long_unstructured(self):
+        h = self.make_header('Subject', 'This is a long header '
+            'line that will need to be folded into two lines '
+            'and will demonstrate basic folding')
+        self.assertEqual(h.fold(policy=self.policy),
+                        'Subject: This is a long header line that will '
+                            'need to be folded into two lines\n'
+                        ' and will demonstrate basic folding\n')
+
+    def test_unstructured_short_max_line_length(self):
+        h = self.make_header('Subject', 'this is a short header '
+            'that will be folded anyway')
+        self.assertEqual(
+            h.fold(policy=policy.default.clone(max_line_length=20)),
+            textwrap.dedent("""\
+                Subject: this is a
+                 short header that
+                 will be folded
+                 anyway
+                """))
+
+    def test_fold_unstructured_single_word(self):
+        h = self.make_header('Subject', 'test')
+        self.assertEqual(h.fold(policy=self.policy), 'Subject: test\n')
+
+    def test_fold_unstructured_short(self):
+        h = self.make_header('Subject', 'test test test')
+        self.assertEqual(h.fold(policy=self.policy),
+                        'Subject: test test test\n')
+
+    def test_fold_unstructured_with_overlong_word(self):
+        h = self.make_header('Subject', 'thisisaverylonglineconsistingofa'
+            'singlewordthatwontfit')
+        self.assertEqual(
+            h.fold(policy=policy.default.clone(max_line_length=20)),
+            'Subject: thisisaverylonglineconsistingofasinglewordthatwontfit\n')
+
+    def test_fold_unstructured_with_two_overlong_words(self):
+        h = self.make_header('Subject', 'thisisaverylonglineconsistingofa'
+            'singlewordthatwontfit plusanotherverylongwordthatwontfit')
+        self.assertEqual(
+            h.fold(policy=policy.default.clone(max_line_length=20)),
+            'Subject: thisisaverylonglineconsistingofasinglewordthatwontfit\n'
+                ' plusanotherverylongwordthatwontfit\n')
+
+    def test_fold_unstructured_with_slightly_long_word(self):
+        h = self.make_header('Subject', 'thislongwordislessthanmaxlinelen')
+        self.assertEqual(
+            h.fold(policy=policy.default.clone(max_line_length=35)),
+            'Subject:\n thislongwordislessthanmaxlinelen\n')
+
+    def test_fold_unstructured_with_commas(self):
+        # The old wrapper would fold this at the commas.
+        h = self.make_header('Subject', "This header is intended to "
+            "demonstrate, in a fairly susinct way, that we now do "
+            "not give a , special treatment in unstructured headers.")
+        self.assertEqual(
+            h.fold(policy=policy.default.clone(max_line_length=60)),
+            textwrap.dedent("""\
+                Subject: This header is intended to demonstrate, in a fairly
+                 susinct way, that we now do not give a , special treatment
+                 in unstructured headers.
+                 """))
+
+    def test_fold_address_list(self):
+        h = self.make_header('To', '"Theodore H. Perfect" <yes@man.com>, '
+            '"My address is very long because my name is long" <foo@bar.com>, '
+            '"Only A. Friend" <no@yes.com>')
+        self.assertEqual(h.fold(policy=self.policy), textwrap.dedent("""\
+            To: "Theodore H. Perfect" <yes@man.com>,
+             "My address is very long because my name is long" <foo@bar.com>,
+             "Only A. Friend" <no@yes.com>
+             """))
+
+    def test_fold_date_header(self):
+        h = self.make_header('Date', 'Sat, 2 Feb 2002 17:00:06 -0800')
+        self.assertEqual(h.fold(policy=self.policy),
+                        'Date: Sat, 02 Feb 2002 17:00:06 -0800\n')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py
index 8f5fde7..c1af024 100644
--- a/Lib/test/test_email/test_generator.py
+++ b/Lib/test/test_email/test_generator.py
@@ -6,14 +6,16 @@
 from email import policy
 from test.test_email import TestEmailBase
 
-# XXX: move generator tests from test_email into here at some point.
 
+class TestGeneratorBase:
 
-class TestGeneratorBase():
+    policy = policy.default
 
-    policy = policy.compat32
+    def msgmaker(self, msg, policy=None):
+        policy = self.policy if policy is None else policy
+        return self.msgfunc(msg, policy=policy)
 
-    long_subject = {
+    refold_long_expected = {
         0: textwrap.dedent("""\
             To: whom_it_may_concern@example.com
             From: nobody_you_want_to_know@example.com
@@ -23,33 +25,32 @@
 
             None
             """),
+        # From is wrapped because wrapped it fits in 40.
         40: textwrap.dedent("""\
             To: whom_it_may_concern@example.com
-            From:\x20
+            From:
              nobody_you_want_to_know@example.com
             Subject: We the willing led by the
-             unknowing are doing the
-             impossible for the ungrateful. We have
-             done so much for so long with so little
-             we are now qualified to do anything
-             with nothing.
+             unknowing are doing the impossible for
+             the ungrateful. We have done so much
+             for so long with so little we are now
+             qualified to do anything with nothing.
 
             None
             """),
+        # Neither to nor from fit even if put on a new line,
+        # so we leave them sticking out on the first line.
         20: textwrap.dedent("""\
-            To:\x20
-             whom_it_may_concern@example.com
-            From:\x20
-             nobody_you_want_to_know@example.com
+            To: whom_it_may_concern@example.com
+            From: nobody_you_want_to_know@example.com
             Subject: We the
              willing led by the
              unknowing are doing
-             the
-             impossible for the
-             ungrateful. We have
-             done so much for so
-             long with so little
-             we are now
+             the impossible for
+             the ungrateful. We
+             have done so much
+             for so long with so
+             little we are now
              qualified to do
              anything with
              nothing.
@@ -57,65 +58,90 @@
             None
             """),
         }
-    long_subject[100] = long_subject[0]
+    refold_long_expected[100] = refold_long_expected[0]
 
-    def maxheaderlen_parameter_test(self, n):
-        msg = self.msgmaker(self.typ(self.long_subject[0]))
+    refold_all_expected = refold_long_expected.copy()
+    refold_all_expected[0] = (
+            "To: whom_it_may_concern@example.com\n"
+            "From: nobody_you_want_to_know@example.com\n"
+            "Subject: We the willing led by the unknowing are doing the "
+              "impossible for the ungrateful. We have done so much for "
+              "so long with so little we are now qualified to do anything "
+              "with nothing.\n"
+              "\n"
+              "None\n")
+    refold_all_expected[100] = (
+            "To: whom_it_may_concern@example.com\n"
+            "From: nobody_you_want_to_know@example.com\n"
+            "Subject: We the willing led by the unknowing are doing the "
+                "impossible for the ungrateful. We have\n"
+              " done so much for so long with so little we are now qualified "
+                "to do anything with nothing.\n"
+              "\n"
+              "None\n")
+
+    def _test_maxheaderlen_parameter(self, n):
+        msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
         s = self.ioclass()
-        g = self.genclass(s, maxheaderlen=n)
+        g = self.genclass(s, maxheaderlen=n, policy=self.policy)
         g.flatten(msg)
-        self.assertEqual(s.getvalue(), self.typ(self.long_subject[n]))
+        self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[n]))
 
-    def test_maxheaderlen_parameter_0(self):
-        self.maxheaderlen_parameter_test(0)
+    for n in refold_long_expected:
+        locals()['test_maxheaderlen_parameter_' + str(n)] = (
+            lambda self, n=n:
+                self._test_maxheaderlen_parameter(n))
 
-    def test_maxheaderlen_parameter_100(self):
-        self.maxheaderlen_parameter_test(100)
-
-    def test_maxheaderlen_parameter_40(self):
-        self.maxheaderlen_parameter_test(40)
-
-    def test_maxheaderlen_parameter_20(self):
-        self.maxheaderlen_parameter_test(20)
-
-    def maxheaderlen_policy_test(self, n):
-        msg = self.msgmaker(self.typ(self.long_subject[0]))
+    def _test_max_line_length_policy(self, n):
+        msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
         s = self.ioclass()
-        g = self.genclass(s, policy=policy.default.clone(max_line_length=n))
+        g = self.genclass(s, policy=self.policy.clone(max_line_length=n))
         g.flatten(msg)
-        self.assertEqual(s.getvalue(), self.typ(self.long_subject[n]))
+        self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[n]))
 
-    def test_maxheaderlen_policy_0(self):
-        self.maxheaderlen_policy_test(0)
+    for n in refold_long_expected:
+        locals()['test_max_line_length_policy' + str(n)] = (
+            lambda self, n=n:
+                self._test_max_line_length_policy(n))
 
-    def test_maxheaderlen_policy_100(self):
-        self.maxheaderlen_policy_test(100)
-
-    def test_maxheaderlen_policy_40(self):
-        self.maxheaderlen_policy_test(40)
-
-    def test_maxheaderlen_policy_20(self):
-        self.maxheaderlen_policy_test(20)
-
-    def maxheaderlen_parm_overrides_policy_test(self, n):
-        msg = self.msgmaker(self.typ(self.long_subject[0]))
+    def _test_maxheaderlen_parm_overrides_policy(self, n):
+        msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
         s = self.ioclass()
         g = self.genclass(s, maxheaderlen=n,
-                          policy=policy.default.clone(max_line_length=10))
+                          policy=self.policy.clone(max_line_length=10))
         g.flatten(msg)
-        self.assertEqual(s.getvalue(), self.typ(self.long_subject[n]))
+        self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[n]))
 
-    def test_maxheaderlen_parm_overrides_policy_0(self):
-        self.maxheaderlen_parm_overrides_policy_test(0)
+    for n in refold_long_expected:
+        locals()['test_maxheaderlen_parm_overrides_policy' + str(n)] = (
+            lambda self, n=n:
+                self._test_maxheaderlen_parm_overrides_policy(n))
 
-    def test_maxheaderlen_parm_overrides_policy_100(self):
-        self.maxheaderlen_parm_overrides_policy_test(100)
+    def _test_refold_none_does_not_fold(self, n):
+        msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
+        s = self.ioclass()
+        g = self.genclass(s, policy=self.policy.clone(refold_source='none',
+                                                      max_line_length=n))
+        g.flatten(msg)
+        self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[0]))
 
-    def test_maxheaderlen_parm_overrides_policy_40(self):
-        self.maxheaderlen_parm_overrides_policy_test(40)
+    for n in refold_long_expected:
+        locals()['test_refold_none_does_not_fold' + str(n)] = (
+            lambda self, n=n:
+                self._test_refold_none_does_not_fold(n))
 
-    def test_maxheaderlen_parm_overrides_policy_20(self):
-        self.maxheaderlen_parm_overrides_policy_test(20)
+    def _test_refold_all(self, n):
+        msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
+        s = self.ioclass()
+        g = self.genclass(s, policy=self.policy.clone(refold_source='all',
+                                                      max_line_length=n))
+        g.flatten(msg)
+        self.assertEqual(s.getvalue(), self.typ(self.refold_all_expected[n]))
+
+    for n in refold_long_expected:
+        locals()['test_refold_all' + str(n)] = (
+            lambda self, n=n:
+                self._test_refold_all(n))
 
     def test_crlf_control_via_policy(self):
         source = "Subject: test\r\n\r\ntest body\r\n"
@@ -138,30 +164,24 @@
 
 class TestGenerator(TestGeneratorBase, TestEmailBase):
 
+    msgfunc = staticmethod(message_from_string)
     genclass = Generator
     ioclass = io.StringIO
     typ = str
 
-    def msgmaker(self, msg, policy=None):
-        policy = self.policy if policy is None else policy
-        return message_from_string(msg, policy=policy)
-
 
 class TestBytesGenerator(TestGeneratorBase, TestEmailBase):
 
+    msgfunc = staticmethod(message_from_bytes)
     genclass = BytesGenerator
     ioclass = io.BytesIO
     typ = lambda self, x: x.encode('ascii')
 
-    def msgmaker(self, msg, policy=None):
-        policy = self.policy if policy is None else policy
-        return message_from_bytes(msg, policy=policy)
-
     def test_cte_type_7bit_handles_unknown_8bit(self):
         source = ("Subject: Maintenant je vous présente mon "
                  "collègue\n\n").encode('utf-8')
-        expected = ('Subject: =?unknown-8bit?q?Maintenant_je_vous_pr=C3=A9sente_mon_'
-                    'coll=C3=A8gue?=\n\n').encode('ascii')
+        expected = ('Subject: Maintenant je vous =?unknown-8bit?q?'
+                    'pr=C3=A9sente_mon_coll=C3=A8gue?=\n\n').encode('ascii')
         msg = message_from_bytes(source)
         s = io.BytesIO()
         g = BytesGenerator(s, policy=self.policy.clone(cte_type='7bit'))
diff --git a/Lib/test/test_email/test_pickleable.py b/Lib/test/test_email/test_pickleable.py
new file mode 100644
index 0000000..e4c77ca
--- /dev/null
+++ b/Lib/test/test_email/test_pickleable.py
@@ -0,0 +1,57 @@
+import unittest
+import textwrap
+import copy
+import pickle
+from email import policy
+from email import message_from_string
+from email._headerregistry import HeaderRegistry
+from test.test_email import TestEmailBase
+
+class TestPickleCopyHeader(TestEmailBase):
+
+    unstructured = HeaderRegistry()('subject', 'this is a test')
+
+    def test_deepcopy_unstructured(self):
+        h = copy.deepcopy(self.unstructured)
+        self.assertEqual(str(h), str(self.unstructured))
+
+    def test_pickle_unstructured(self):
+        p = pickle.dumps(self.unstructured)
+        h = pickle.loads(p)
+        self.assertEqual(str(h), str(self.unstructured))
+
+    address = HeaderRegistry()('from', 'frodo@mordor.net')
+
+    def test_deepcopy_address(self):
+        h = copy.deepcopy(self.address)
+        self.assertEqual(str(h), str(self.address))
+
+    def test_pickle_address(self):
+        p = pickle.dumps(self.address)
+        h = pickle.loads(p)
+        self.assertEqual(str(h), str(self.address))
+
+
+class TestPickleCopyMessage(TestEmailBase):
+
+    testmsg = message_from_string(textwrap.dedent("""\
+            From: frodo@mordor.net
+            To: bilbo@underhill.org
+            Subject: help
+
+            I think I forgot the ring.
+            """), policy=policy.default)
+
+    def test_deepcopy(self):
+        msg2 = copy.deepcopy(self.testmsg)
+        self.assertEqual(msg2.as_string(), self.testmsg.as_string())
+
+    def test_pickle(self):
+        p = pickle.dumps(self.testmsg)
+        msg2 = pickle.loads(p)
+        self.assertEqual(msg2.as_string(), self.testmsg.as_string())
+
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py
index 07925a7..45a7666 100644
--- a/Lib/test/test_email/test_policy.py
+++ b/Lib/test/test_email/test_policy.py
@@ -5,49 +5,70 @@
 import email.policy
 import email.parser
 import email.generator
+from email import _headerregistry
+
+def make_defaults(base_defaults, differences):
+    defaults = base_defaults.copy()
+    defaults.update(differences)
+    return defaults
 
 class PolicyAPITests(unittest.TestCase):
 
     longMessage = True
 
-    # These default values are the ones set on email.policy.default.
-    # If any of these defaults change, the docs must be updated.
-    policy_defaults = {
+    # Base default values.
+    compat32_defaults = {
         'max_line_length':          78,
         'linesep':                  '\n',
         'cte_type':                 '8bit',
         'raise_on_defect':          False,
         }
+    # These default values are the ones set on email.policy.default.
+    # If any of these defaults change, the docs must be updated.
+    policy_defaults = compat32_defaults.copy()
+    policy_defaults.update({
+        'raise_on_defect':          False,
+        'header_factory':           email.policy.EmailPolicy.header_factory,
+        'refold_source':            'long',
+        })
 
-    # For each policy under test, we give here the values of the attributes
-    # that are different from the defaults for that policy.
+    # For each policy under test, we give here what we expect the defaults to
+    # be for that policy.  The second argument to make defaults is the
+    # difference between the base defaults and that for the particular policy.
+    new_policy = email.policy.EmailPolicy()
     policies = {
-        email.policy.Compat32(): {},
-        email.policy.compat32: {},
-        email.policy.default: {},
-        email.policy.SMTP: {'linesep': '\r\n'},
-        email.policy.HTTP: {'linesep': '\r\n', 'max_line_length': None},
-        email.policy.strict: {'raise_on_defect': True},
+        email.policy.compat32: make_defaults(compat32_defaults, {}),
+        email.policy.default: make_defaults(policy_defaults, {}),
+        email.policy.SMTP: make_defaults(policy_defaults,
+                                         {'linesep': '\r\n'}),
+        email.policy.HTTP: make_defaults(policy_defaults,
+                                         {'linesep': '\r\n',
+                                          'max_line_length': None}),
+        email.policy.strict: make_defaults(policy_defaults,
+                                           {'raise_on_defect': True}),
+        new_policy: make_defaults(policy_defaults, {}),
         }
+    # Creating a new policy creates a new header factory.  There is a test
+    # later that proves this.
+    policies[new_policy]['header_factory'] = new_policy.header_factory
 
     def test_defaults(self):
-        for policy, changed_defaults in self.policies.items():
-            expected = self.policy_defaults.copy()
-            expected.update(changed_defaults)
+        for policy, expected in self.policies.items():
             for attr, value in expected.items():
                 self.assertEqual(getattr(policy, attr), value,
                                 ("change {} docs/docstrings if defaults have "
                                 "changed").format(policy))
 
     def test_all_attributes_covered(self):
-        for attr in dir(email.policy.default):
-            if (attr.startswith('_') or
-               isinstance(getattr(email.policy.Policy, attr),
-                          types.FunctionType)):
-                continue
-            else:
-                self.assertIn(attr, self.policy_defaults,
-                              "{} is not fully tested".format(attr))
+        for policy, expected in self.policies.items():
+            for attr in dir(policy):
+                if (attr.startswith('_') or
+                        isinstance(getattr(email.policy.EmailPolicy, attr),
+                              types.FunctionType)):
+                    continue
+                else:
+                    self.assertIn(attr, expected,
+                                  "{} is not fully tested".format(attr))
 
     def test_abc(self):
         with self.assertRaises(TypeError) as cm:
@@ -62,18 +83,20 @@
             self.assertIn(method, msg)
 
     def test_policy_is_immutable(self):
-        for policy in self.policies:
-            for attr in self.policy_defaults:
+        for policy, defaults in self.policies.items():
+            for attr in defaults:
                 with self.assertRaisesRegex(AttributeError, attr+".*read-only"):
                     setattr(policy, attr, None)
             with self.assertRaisesRegex(AttributeError, 'no attribute.*foo'):
                 policy.foo = None
 
-    def test_set_policy_attrs_when_calledl(self):
-        testattrdict = { attr: None for attr in self.policy_defaults }
-        for policyclass in self.policies:
+    def test_set_policy_attrs_when_cloned(self):
+        # None of the attributes has a default value of None, so we set them
+        # all to None in the clone call and check that it worked.
+        for policyclass, defaults in self.policies.items():
+            testattrdict = {attr: None for attr in defaults}
             policy = policyclass.clone(**testattrdict)
-            for attr in self.policy_defaults:
+            for attr in defaults:
                 self.assertIsNone(getattr(policy, attr))
 
     def test_reject_non_policy_keyword_when_called(self):
@@ -105,7 +128,7 @@
                 self.defects = []
         obj = Dummy()
         defect = object()
-        policy = email.policy.Compat32()
+        policy = email.policy.EmailPolicy()
         policy.register_defect(obj, defect)
         self.assertEqual(obj.defects, [defect])
         defect2 = object()
@@ -134,7 +157,7 @@
         email.policy.default.handle_defect(foo, defect2)
         self.assertEqual(foo.defects, [defect1, defect2])
 
-    class MyPolicy(email.policy.Compat32):
+    class MyPolicy(email.policy.EmailPolicy):
         defects = None
         def __init__(self, *args, **kw):
             super().__init__(*args, defects=[], **kw)
@@ -159,6 +182,49 @@
         self.assertEqual(my_policy.defects, [defect1, defect2])
         self.assertEqual(foo.defects, [])
 
+    def test_default_header_factory(self):
+        h = email.policy.default.header_factory('Test', 'test')
+        self.assertEqual(h.name, 'Test')
+        self.assertIsInstance(h, _headerregistry.UnstructuredHeader)
+        self.assertIsInstance(h, _headerregistry.BaseHeader)
+
+    class Foo:
+        parse = _headerregistry.UnstructuredHeader.parse
+
+    def test_each_Policy_gets_unique_factory(self):
+        policy1 = email.policy.EmailPolicy()
+        policy2 = email.policy.EmailPolicy()
+        policy1.header_factory.map_to_type('foo', self.Foo)
+        h = policy1.header_factory('foo', 'test')
+        self.assertIsInstance(h, self.Foo)
+        self.assertNotIsInstance(h, _headerregistry.UnstructuredHeader)
+        h = policy2.header_factory('foo', 'test')
+        self.assertNotIsInstance(h, self.Foo)
+        self.assertIsInstance(h, _headerregistry.UnstructuredHeader)
+
+    def test_clone_copies_factory(self):
+        policy1 = email.policy.EmailPolicy()
+        policy2 = policy1.clone()
+        policy1.header_factory.map_to_type('foo', self.Foo)
+        h = policy1.header_factory('foo', 'test')
+        self.assertIsInstance(h, self.Foo)
+        h = policy2.header_factory('foo', 'test')
+        self.assertIsInstance(h, self.Foo)
+
+    def test_new_factory_overrides_default(self):
+        mypolicy = email.policy.EmailPolicy()
+        myfactory = mypolicy.header_factory
+        newpolicy = mypolicy + email.policy.strict
+        self.assertEqual(newpolicy.header_factory, myfactory)
+        newpolicy = email.policy.strict + mypolicy
+        self.assertEqual(newpolicy.header_factory, myfactory)
+
+    def test_adding_default_policies_preserves_default_factory(self):
+        newpolicy = email.policy.default + email.policy.strict
+        self.assertEqual(newpolicy.header_factory,
+                         email.policy.EmailPolicy.header_factory)
+        self.assertEqual(newpolicy.__dict__, {'raise_on_defect': True})
+
     # XXX: Need subclassing tests.
     # For adding subclassed objects, make sure the usual rules apply (subclass
     # wins), but that the order still works (right overrides left).