Home | History | Annotate | Download | only in test
      1 # Copyright (C) 2003-2013 Python Software Foundation
      2 
      3 import unittest
      4 import plistlib
      5 import os
      6 import datetime
      7 import codecs
      8 import binascii
      9 import collections
     10 from test import support
     11 from io import BytesIO
     12 
     13 ALL_FORMATS=(plistlib.FMT_XML, plistlib.FMT_BINARY)
     14 
     15 # The testdata is generated using Mac/Tools/plistlib_generate_testdata.py
     16 # (which using PyObjC to control the Cocoa classes for generating plists)
     17 TESTDATA={
     18     plistlib.FMT_XML: binascii.a2b_base64(b'''
     19         PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
     20         WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
     21         IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
     22         LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+YUJp
     23         Z0ludDwva2V5PgoJPGludGVnZXI+OTIyMzM3MjAzNjg1NDc3NTc2NDwvaW50
     24         ZWdlcj4KCTxrZXk+YUJpZ0ludDI8L2tleT4KCTxpbnRlZ2VyPjkyMjMzNzIw
     25         MzY4NTQ3NzU4NTI8L2ludGVnZXI+Cgk8a2V5PmFEYXRlPC9rZXk+Cgk8ZGF0
     26         ZT4yMDA0LTEwLTI2VDEwOjMzOjMzWjwvZGF0ZT4KCTxrZXk+YURpY3Q8L2tl
     27         eT4KCTxkaWN0PgoJCTxrZXk+YUZhbHNlVmFsdWU8L2tleT4KCQk8ZmFsc2Uv
     28         PgoJCTxrZXk+YVRydWVWYWx1ZTwva2V5PgoJCTx0cnVlLz4KCQk8a2V5PmFV
     29         bmljb2RlVmFsdWU8L2tleT4KCQk8c3RyaW5nPk3DpHNzaWcsIE1hw588L3N0
     30         cmluZz4KCQk8a2V5PmFub3RoZXJTdHJpbmc8L2tleT4KCQk8c3RyaW5nPiZs
     31         dDtoZWxsbyAmYW1wOyAnaGknIHRoZXJlISZndDs8L3N0cmluZz4KCQk8a2V5
     32         PmRlZXBlckRpY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5hPC9rZXk+CgkJ
     33         CTxpbnRlZ2VyPjE3PC9pbnRlZ2VyPgoJCQk8a2V5PmI8L2tleT4KCQkJPHJl
     34         YWw+MzIuNTwvcmVhbD4KCQkJPGtleT5jPC9rZXk+CgkJCTxhcnJheT4KCQkJ
     35         CTxpbnRlZ2VyPjE8L2ludGVnZXI+CgkJCQk8aW50ZWdlcj4yPC9pbnRlZ2Vy
     36         PgoJCQkJPHN0cmluZz50ZXh0PC9zdHJpbmc+CgkJCTwvYXJyYXk+CgkJPC9k
     37         aWN0PgoJPC9kaWN0PgoJPGtleT5hRmxvYXQ8L2tleT4KCTxyZWFsPjAuNTwv
     38         cmVhbD4KCTxrZXk+YUxpc3Q8L2tleT4KCTxhcnJheT4KCQk8c3RyaW5nPkE8
     39         L3N0cmluZz4KCQk8c3RyaW5nPkI8L3N0cmluZz4KCQk8aW50ZWdlcj4xMjwv
     40         aW50ZWdlcj4KCQk8cmVhbD4zMi41PC9yZWFsPgoJCTxhcnJheT4KCQkJPGlu
     41         dGVnZXI+MTwvaW50ZWdlcj4KCQkJPGludGVnZXI+MjwvaW50ZWdlcj4KCQkJ
     42         PGludGVnZXI+MzwvaW50ZWdlcj4KCQk8L2FycmF5PgoJPC9hcnJheT4KCTxr
     43         ZXk+YU5lZ2F0aXZlQmlnSW50PC9rZXk+Cgk8aW50ZWdlcj4tODAwMDAwMDAw
     44         MDA8L2ludGVnZXI+Cgk8a2V5PmFOZWdhdGl2ZUludDwva2V5PgoJPGludGVn
     45         ZXI+LTU8L2ludGVnZXI+Cgk8a2V5PmFTdHJpbmc8L2tleT4KCTxzdHJpbmc+
     46         RG9vZGFoPC9zdHJpbmc+Cgk8a2V5PmFuRW1wdHlEaWN0PC9rZXk+Cgk8ZGlj
     47         dC8+Cgk8a2V5PmFuRW1wdHlMaXN0PC9rZXk+Cgk8YXJyYXkvPgoJPGtleT5h
     48         bkludDwva2V5PgoJPGludGVnZXI+NzI4PC9pbnRlZ2VyPgoJPGtleT5uZXN0
     49         ZWREYXRhPC9rZXk+Cgk8YXJyYXk+CgkJPGRhdGE+CgkJUEd4dmRITWdiMlln
     50         WW1sdVlYSjVJR2QxYm1zK0FBRUNBenhzYjNSeklHOW1JR0pwYm1GeWVTQm5k
     51         VzVyCgkJUGdBQkFnTThiRzkwY3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJ
     52         RFBHeHZkSE1nYjJZZ1ltbHVZWEo1CgkJSUdkMWJtcytBQUVDQXp4c2IzUnpJ
     53         RzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004Ykc5MGN5QnZaaUJpCgkJYVc1
     54         aGNua2daM1Z1YXo0QUFRSURQR3h2ZEhNZ2IyWWdZbWx1WVhKNUlHZDFibXMr
     55         QUFFQ0F6eHNiM1J6CgkJSUc5bUlHSnBibUZ5ZVNCbmRXNXJQZ0FCQWdNOGJH
     56         OTBjeUJ2WmlCaWFXNWhjbmtnWjNWdWF6NEFBUUlECgkJUEd4dmRITWdiMlln
     57         WW1sdVlYSjVJR2QxYm1zK0FBRUNBdz09CgkJPC9kYXRhPgoJPC9hcnJheT4K
     58         CTxrZXk+c29tZURhdGE8L2tleT4KCTxkYXRhPgoJUEdKcGJtRnllU0JuZFc1
     59         clBnPT0KCTwvZGF0YT4KCTxrZXk+c29tZU1vcmVEYXRhPC9rZXk+Cgk8ZGF0
     60         YT4KCVBHeHZkSE1nYjJZZ1ltbHVZWEo1SUdkMWJtcytBQUVDQXp4c2IzUnpJ
     61         RzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004CgliRzkwY3lCdlppQmlhVzVo
     62         Y25rZ1ozVnVhejRBQVFJRFBHeHZkSE1nYjJZZ1ltbHVZWEo1SUdkMWJtcytB
     63         QUVDQXp4cwoJYjNSeklHOW1JR0pwYm1GeWVTQm5kVzVyUGdBQkFnTThiRzkw
     64         Y3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJRFBHeHYKCWRITWdiMllnWW1s
     65         dVlYSjVJR2QxYm1zK0FBRUNBenhzYjNSeklHOW1JR0pwYm1GeWVTQm5kVzVy
     66         UGdBQkFnTThiRzkwCgljeUJ2WmlCaWFXNWhjbmtnWjNWdWF6NEFBUUlEUEd4
     67         dmRITWdiMllnWW1sdVlYSjVJR2QxYm1zK0FBRUNBdz09Cgk8L2RhdGE+Cgk8
     68         a2V5PsOFYmVucmFhPC9rZXk+Cgk8c3RyaW5nPlRoYXQgd2FzIGEgdW5pY29k
     69         ZSBrZXkuPC9zdHJpbmc+CjwvZGljdD4KPC9wbGlzdD4K'''),
     70     plistlib.FMT_BINARY: binascii.a2b_base64(b'''
     71         YnBsaXN0MDDfEBABAgMEBQYHCAkKCwwNDg8QERITFCgpLzAxMjM0NTc2OFdh
     72         QmlnSW50WGFCaWdJbnQyVWFEYXRlVWFEaWN0VmFGbG9hdFVhTGlzdF8QD2FO
     73         ZWdhdGl2ZUJpZ0ludFxhTmVnYXRpdmVJbnRXYVN0cmluZ1thbkVtcHR5RGlj
     74         dFthbkVtcHR5TGlzdFVhbkludFpuZXN0ZWREYXRhWHNvbWVEYXRhXHNvbWVN
     75         b3JlRGF0YWcAxQBiAGUAbgByAGEAYRN/////////1BQAAAAAAAAAAIAAAAAA
     76         AAAsM0GcuX30AAAA1RUWFxgZGhscHR5bYUZhbHNlVmFsdWVaYVRydWVWYWx1
     77         ZV1hVW5pY29kZVZhbHVlXWFub3RoZXJTdHJpbmdaZGVlcGVyRGljdAgJawBN
     78         AOQAcwBzAGkAZwAsACAATQBhAN9fEBU8aGVsbG8gJiAnaGknIHRoZXJlIT7T
     79         HyAhIiMkUWFRYlFjEBEjQEBAAAAAAACjJSYnEAEQAlR0ZXh0Iz/gAAAAAAAA
     80         pSorLCMtUUFRQhAMoyUmLhADE////+1foOAAE//////////7VkRvb2RhaNCg
     81         EQLYoTZPEPo8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmlu
     82         YXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBv
     83         ZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxs
     84         b3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4A
     85         AQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBn
     86         dW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDTTxiaW5hcnkgZ3Vu
     87         az5fEBdUaGF0IHdhcyBhIHVuaWNvZGUga2V5LgAIACsAMwA8AEIASABPAFUA
     88         ZwB0AHwAiACUAJoApQCuALsAygDTAOQA7QD4AQQBDwEdASsBNgE3ATgBTwFn
     89         AW4BcAFyAXQBdgF/AYMBhQGHAYwBlQGbAZ0BnwGhAaUBpwGwAbkBwAHBAcIB
     90         xQHHAsQC0gAAAAAAAAIBAAAAAAAAADkAAAAAAAAAAAAAAAAAAALs'''),
     91 }
     92 
     93 
     94 class TestPlistlib(unittest.TestCase):
     95 
     96     def tearDown(self):
     97         try:
     98             os.unlink(support.TESTFN)
     99         except:
    100             pass
    101 
    102     def _create(self, fmt=None):
    103         pl = dict(
    104             aString="Doodah",
    105             aList=["A", "B", 12, 32.5, [1, 2, 3]],
    106             aFloat = 0.5,
    107             anInt = 728,
    108             aBigInt = 2 ** 63 - 44,
    109             aBigInt2 = 2 ** 63 + 44,
    110             aNegativeInt = -5,
    111             aNegativeBigInt = -80000000000,
    112             aDict=dict(
    113                 anotherString="<hello & 'hi' there!>",
    114                 aUnicodeValue='M\xe4ssig, Ma\xdf',
    115                 aTrueValue=True,
    116                 aFalseValue=False,
    117                 deeperDict=dict(a=17, b=32.5, c=[1, 2, "text"]),
    118             ),
    119             someData = b"<binary gunk>",
    120             someMoreData = b"<lots of binary gunk>\0\1\2\3" * 10,
    121             nestedData = [b"<lots of binary gunk>\0\1\2\3" * 10],
    122             aDate = datetime.datetime(2004, 10, 26, 10, 33, 33),
    123             anEmptyDict = dict(),
    124             anEmptyList = list()
    125         )
    126         pl['\xc5benraa'] = "That was a unicode key."
    127         return pl
    128 
    129     def test_create(self):
    130         pl = self._create()
    131         self.assertEqual(pl["aString"], "Doodah")
    132         self.assertEqual(pl["aDict"]["aFalseValue"], False)
    133 
    134     def test_io(self):
    135         pl = self._create()
    136         with open(support.TESTFN, 'wb') as fp:
    137             plistlib.dump(pl, fp)
    138 
    139         with open(support.TESTFN, 'rb') as fp:
    140             pl2 = plistlib.load(fp)
    141 
    142         self.assertEqual(dict(pl), dict(pl2))
    143 
    144         self.assertRaises(AttributeError, plistlib.dump, pl, 'filename')
    145         self.assertRaises(AttributeError, plistlib.load, 'filename')
    146 
    147     def test_invalid_type(self):
    148         pl = [ object() ]
    149 
    150         for fmt in ALL_FORMATS:
    151             with self.subTest(fmt=fmt):
    152                 self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt)
    153 
    154     def test_int(self):
    155         for pl in [0, 2**8-1, 2**8, 2**16-1, 2**16, 2**32-1, 2**32,
    156                    2**63-1, 2**64-1, 1, -2**63]:
    157             for fmt in ALL_FORMATS:
    158                 with self.subTest(pl=pl, fmt=fmt):
    159                     data = plistlib.dumps(pl, fmt=fmt)
    160                     pl2 = plistlib.loads(data)
    161                     self.assertIsInstance(pl2, int)
    162                     self.assertEqual(pl, pl2)
    163                     data2 = plistlib.dumps(pl2, fmt=fmt)
    164                     self.assertEqual(data, data2)
    165 
    166         for fmt in ALL_FORMATS:
    167             for pl in (2 ** 64 + 1, 2 ** 127-1, -2**64, -2 ** 127):
    168                 with self.subTest(pl=pl, fmt=fmt):
    169                     self.assertRaises(OverflowError, plistlib.dumps,
    170                                       pl, fmt=fmt)
    171 
    172     def test_bytearray(self):
    173         for pl in (b'<binary gunk>', b"<lots of binary gunk>\0\1\2\3" * 10):
    174             for fmt in ALL_FORMATS:
    175                 with self.subTest(pl=pl, fmt=fmt):
    176                     data = plistlib.dumps(bytearray(pl), fmt=fmt)
    177                     pl2 = plistlib.loads(data)
    178                     self.assertIsInstance(pl2, bytes)
    179                     self.assertEqual(pl2, pl)
    180                     data2 = plistlib.dumps(pl2, fmt=fmt)
    181                     self.assertEqual(data, data2)
    182 
    183     def test_bytes(self):
    184         pl = self._create()
    185         data = plistlib.dumps(pl)
    186         pl2 = plistlib.loads(data)
    187         self.assertEqual(dict(pl), dict(pl2))
    188         data2 = plistlib.dumps(pl2)
    189         self.assertEqual(data, data2)
    190 
    191     def test_indentation_array(self):
    192         data = [[[[[[[[{'test': b'aaaaaa'}]]]]]]]]
    193         self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
    194 
    195     def test_indentation_dict(self):
    196         data = {'1': {'2': {'3': {'4': {'5': {'6': {'7': {'8': {'9': b'aaaaaa'}}}}}}}}}
    197         self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
    198 
    199     def test_indentation_dict_mix(self):
    200         data = {'1': {'2': [{'3': [[[[[{'test': b'aaaaaa'}]]]]]}]}}
    201         self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
    202 
    203     def test_appleformatting(self):
    204         for use_builtin_types in (True, False):
    205             for fmt in ALL_FORMATS:
    206                 with self.subTest(fmt=fmt, use_builtin_types=use_builtin_types):
    207                     pl = plistlib.loads(TESTDATA[fmt],
    208                         use_builtin_types=use_builtin_types)
    209                     data = plistlib.dumps(pl, fmt=fmt)
    210                     self.assertEqual(data, TESTDATA[fmt],
    211                         "generated data was not identical to Apple's output")
    212 
    213 
    214     def test_appleformattingfromliteral(self):
    215         self.maxDiff = None
    216         for fmt in ALL_FORMATS:
    217             with self.subTest(fmt=fmt):
    218                 pl = self._create(fmt=fmt)
    219                 pl2 = plistlib.loads(TESTDATA[fmt], fmt=fmt)
    220                 self.assertEqual(dict(pl), dict(pl2),
    221                     "generated data was not identical to Apple's output")
    222                 pl2 = plistlib.loads(TESTDATA[fmt])
    223                 self.assertEqual(dict(pl), dict(pl2),
    224                     "generated data was not identical to Apple's output")
    225 
    226     def test_bytesio(self):
    227         for fmt in ALL_FORMATS:
    228             with self.subTest(fmt=fmt):
    229                 b = BytesIO()
    230                 pl = self._create(fmt=fmt)
    231                 plistlib.dump(pl, b, fmt=fmt)
    232                 pl2 = plistlib.load(BytesIO(b.getvalue()), fmt=fmt)
    233                 self.assertEqual(dict(pl), dict(pl2))
    234                 pl2 = plistlib.load(BytesIO(b.getvalue()))
    235                 self.assertEqual(dict(pl), dict(pl2))
    236 
    237     def test_keysort_bytesio(self):
    238         pl = collections.OrderedDict()
    239         pl['b'] = 1
    240         pl['a'] = 2
    241         pl['c'] = 3
    242 
    243         for fmt in ALL_FORMATS:
    244             for sort_keys in (False, True):
    245                 with self.subTest(fmt=fmt, sort_keys=sort_keys):
    246                     b = BytesIO()
    247 
    248                     plistlib.dump(pl, b, fmt=fmt, sort_keys=sort_keys)
    249                     pl2 = plistlib.load(BytesIO(b.getvalue()),
    250                         dict_type=collections.OrderedDict)
    251 
    252                     self.assertEqual(dict(pl), dict(pl2))
    253                     if sort_keys:
    254                         self.assertEqual(list(pl2.keys()), ['a', 'b', 'c'])
    255                     else:
    256                         self.assertEqual(list(pl2.keys()), ['b', 'a', 'c'])
    257 
    258     def test_keysort(self):
    259         pl = collections.OrderedDict()
    260         pl['b'] = 1
    261         pl['a'] = 2
    262         pl['c'] = 3
    263 
    264         for fmt in ALL_FORMATS:
    265             for sort_keys in (False, True):
    266                 with self.subTest(fmt=fmt, sort_keys=sort_keys):
    267                     data = plistlib.dumps(pl, fmt=fmt, sort_keys=sort_keys)
    268                     pl2 = plistlib.loads(data, dict_type=collections.OrderedDict)
    269 
    270                     self.assertEqual(dict(pl), dict(pl2))
    271                     if sort_keys:
    272                         self.assertEqual(list(pl2.keys()), ['a', 'b', 'c'])
    273                     else:
    274                         self.assertEqual(list(pl2.keys()), ['b', 'a', 'c'])
    275 
    276     def test_keys_no_string(self):
    277         pl = { 42: 'aNumber' }
    278 
    279         for fmt in ALL_FORMATS:
    280             with self.subTest(fmt=fmt):
    281                 self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt)
    282 
    283                 b = BytesIO()
    284                 self.assertRaises(TypeError, plistlib.dump, pl, b, fmt=fmt)
    285 
    286     def test_skipkeys(self):
    287         pl = {
    288             42: 'aNumber',
    289             'snake': 'aWord',
    290         }
    291 
    292         for fmt in ALL_FORMATS:
    293             with self.subTest(fmt=fmt):
    294                 data = plistlib.dumps(
    295                     pl, fmt=fmt, skipkeys=True, sort_keys=False)
    296 
    297                 pl2 = plistlib.loads(data)
    298                 self.assertEqual(pl2, {'snake': 'aWord'})
    299 
    300                 fp = BytesIO()
    301                 plistlib.dump(
    302                     pl, fp, fmt=fmt, skipkeys=True, sort_keys=False)
    303                 data = fp.getvalue()
    304                 pl2 = plistlib.loads(fp.getvalue())
    305                 self.assertEqual(pl2, {'snake': 'aWord'})
    306 
    307     def test_tuple_members(self):
    308         pl = {
    309             'first': (1, 2),
    310             'second': (1, 2),
    311             'third': (3, 4),
    312         }
    313 
    314         for fmt in ALL_FORMATS:
    315             with self.subTest(fmt=fmt):
    316                 data = plistlib.dumps(pl, fmt=fmt)
    317                 pl2 = plistlib.loads(data)
    318                 self.assertEqual(pl2, {
    319                     'first': [1, 2],
    320                     'second': [1, 2],
    321                     'third': [3, 4],
    322                 })
    323                 if fmt != plistlib.FMT_BINARY:
    324                     self.assertIsNot(pl2['first'], pl2['second'])
    325 
    326     def test_list_members(self):
    327         pl = {
    328             'first': [1, 2],
    329             'second': [1, 2],
    330             'third': [3, 4],
    331         }
    332 
    333         for fmt in ALL_FORMATS:
    334             with self.subTest(fmt=fmt):
    335                 data = plistlib.dumps(pl, fmt=fmt)
    336                 pl2 = plistlib.loads(data)
    337                 self.assertEqual(pl2, {
    338                     'first': [1, 2],
    339                     'second': [1, 2],
    340                     'third': [3, 4],
    341                 })
    342                 self.assertIsNot(pl2['first'], pl2['second'])
    343 
    344     def test_dict_members(self):
    345         pl = {
    346             'first': {'a': 1},
    347             'second': {'a': 1},
    348             'third': {'b': 2 },
    349         }
    350 
    351         for fmt in ALL_FORMATS:
    352             with self.subTest(fmt=fmt):
    353                 data = plistlib.dumps(pl, fmt=fmt)
    354                 pl2 = plistlib.loads(data)
    355                 self.assertEqual(pl2, {
    356                     'first': {'a': 1},
    357                     'second': {'a': 1},
    358                     'third': {'b': 2 },
    359                 })
    360                 self.assertIsNot(pl2['first'], pl2['second'])
    361 
    362     def test_controlcharacters(self):
    363         for i in range(128):
    364             c = chr(i)
    365             testString = "string containing %s" % c
    366             if i >= 32 or c in "\r\n\t":
    367                 # \r, \n and \t are the only legal control chars in XML
    368                 data = plistlib.dumps(testString, fmt=plistlib.FMT_XML)
    369                 if c != "\r":
    370                     self.assertEqual(plistlib.loads(data), testString)
    371             else:
    372                 with self.assertRaises(ValueError):
    373                     plistlib.dumps(testString, fmt=plistlib.FMT_XML)
    374             plistlib.dumps(testString, fmt=plistlib.FMT_BINARY)
    375 
    376     def test_non_bmp_characters(self):
    377         pl = {'python': '\U0001f40d'}
    378         for fmt in ALL_FORMATS:
    379             with self.subTest(fmt=fmt):
    380                 data = plistlib.dumps(pl, fmt=fmt)
    381                 self.assertEqual(plistlib.loads(data), pl)
    382 
    383     def test_lone_surrogates(self):
    384         for fmt in ALL_FORMATS:
    385             with self.subTest(fmt=fmt):
    386                 with self.assertRaises(UnicodeEncodeError):
    387                     plistlib.dumps('\ud8ff', fmt=fmt)
    388                 with self.assertRaises(UnicodeEncodeError):
    389                     plistlib.dumps('\udcff', fmt=fmt)
    390 
    391     def test_nondictroot(self):
    392         for fmt in ALL_FORMATS:
    393             with self.subTest(fmt=fmt):
    394                 test1 = "abc"
    395                 test2 = [1, 2, 3, "abc"]
    396                 result1 = plistlib.loads(plistlib.dumps(test1, fmt=fmt))
    397                 result2 = plistlib.loads(plistlib.dumps(test2, fmt=fmt))
    398                 self.assertEqual(test1, result1)
    399                 self.assertEqual(test2, result2)
    400 
    401     def test_invalidarray(self):
    402         for i in ["<key>key inside an array</key>",
    403                   "<key>key inside an array2</key><real>3</real>",
    404                   "<true/><key>key inside an array3</key>"]:
    405             self.assertRaises(ValueError, plistlib.loads,
    406                               ("<plist><array>%s</array></plist>"%i).encode())
    407 
    408     def test_invaliddict(self):
    409         for i in ["<key><true/>k</key><string>compound key</string>",
    410                   "<key>single key</key>",
    411                   "<string>missing key</string>",
    412                   "<key>k1</key><string>v1</string><real>5.3</real>"
    413                   "<key>k1</key><key>k2</key><string>double key</string>"]:
    414             self.assertRaises(ValueError, plistlib.loads,
    415                               ("<plist><dict>%s</dict></plist>"%i).encode())
    416             self.assertRaises(ValueError, plistlib.loads,
    417                               ("<plist><array><dict>%s</dict></array></plist>"%i).encode())
    418 
    419     def test_invalidinteger(self):
    420         self.assertRaises(ValueError, plistlib.loads,
    421                           b"<plist><integer>not integer</integer></plist>")
    422 
    423     def test_invalidreal(self):
    424         self.assertRaises(ValueError, plistlib.loads,
    425                           b"<plist><integer>not real</integer></plist>")
    426 
    427     def test_xml_encodings(self):
    428         base = TESTDATA[plistlib.FMT_XML]
    429 
    430         for xml_encoding, encoding, bom in [
    431                     (b'utf-8', 'utf-8', codecs.BOM_UTF8),
    432                     (b'utf-16', 'utf-16-le', codecs.BOM_UTF16_LE),
    433                     (b'utf-16', 'utf-16-be', codecs.BOM_UTF16_BE),
    434                     # Expat does not support UTF-32
    435                     #(b'utf-32', 'utf-32-le', codecs.BOM_UTF32_LE),
    436                     #(b'utf-32', 'utf-32-be', codecs.BOM_UTF32_BE),
    437                 ]:
    438 
    439             pl = self._create(fmt=plistlib.FMT_XML)
    440             with self.subTest(encoding=encoding):
    441                 data = base.replace(b'UTF-8', xml_encoding)
    442                 data = bom + data.decode('utf-8').encode(encoding)
    443                 pl2 = plistlib.loads(data)
    444                 self.assertEqual(dict(pl), dict(pl2))
    445 
    446 
    447 class TestBinaryPlistlib(unittest.TestCase):
    448 
    449     def test_nonstandard_refs_size(self):
    450         # Issue #21538: Refs and offsets are 24-bit integers
    451         data = (b'bplist00'
    452                 b'\xd1\x00\x00\x01\x00\x00\x02QaQb'
    453                 b'\x00\x00\x08\x00\x00\x0f\x00\x00\x11'
    454                 b'\x00\x00\x00\x00\x00\x00'
    455                 b'\x03\x03'
    456                 b'\x00\x00\x00\x00\x00\x00\x00\x03'
    457                 b'\x00\x00\x00\x00\x00\x00\x00\x00'
    458                 b'\x00\x00\x00\x00\x00\x00\x00\x13')
    459         self.assertEqual(plistlib.loads(data), {'a': 'b'})
    460 
    461     def test_dump_duplicates(self):
    462         # Test effectiveness of saving duplicated objects
    463         for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde',
    464                   datetime.datetime(2004, 10, 26, 10, 33, 33),
    465                   plistlib.Data(b'abcde'), bytearray(b'abcde'),
    466                   [12, 345], (12, 345), {'12': 345}):
    467             with self.subTest(x=x):
    468                 data = plistlib.dumps([x]*1000, fmt=plistlib.FMT_BINARY)
    469                 self.assertLess(len(data), 1100, repr(data))
    470 
    471     def test_identity(self):
    472         for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde',
    473                   datetime.datetime(2004, 10, 26, 10, 33, 33),
    474                   plistlib.Data(b'abcde'), bytearray(b'abcde'),
    475                   [12, 345], (12, 345), {'12': 345}):
    476             with self.subTest(x=x):
    477                 data = plistlib.dumps([x]*2, fmt=plistlib.FMT_BINARY)
    478                 a, b = plistlib.loads(data)
    479                 if isinstance(x, tuple):
    480                     x = list(x)
    481                 self.assertEqual(a, x)
    482                 self.assertEqual(b, x)
    483                 self.assertIs(a, b)
    484 
    485     def test_cycles(self):
    486         # recursive list
    487         a = []
    488         a.append(a)
    489         b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY))
    490         self.assertIs(b[0], b)
    491         # recursive tuple
    492         a = ([],)
    493         a[0].append(a)
    494         b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY))
    495         self.assertIs(b[0][0], b)
    496         # recursive dict
    497         a = {}
    498         a['x'] = a
    499         b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY))
    500         self.assertIs(b['x'], b)
    501 
    502     def test_large_timestamp(self):
    503         # Issue #26709: 32-bit timestamp out of range
    504         for ts in -2**31-1, 2**31:
    505             with self.subTest(ts=ts):
    506                 d = (datetime.datetime.utcfromtimestamp(0) +
    507                      datetime.timedelta(seconds=ts))
    508                 data = plistlib.dumps(d, fmt=plistlib.FMT_BINARY)
    509                 self.assertEqual(plistlib.loads(data), d)
    510 
    511     def test_invalid_binary(self):
    512         for data in [
    513                 # too short data
    514                 b'',
    515                 # too large offset_table_offset and nonstandard offset_size
    516                 b'\x00\x08'
    517                 b'\x00\x00\x00\x00\x00\x00\x03\x01'
    518                 b'\x00\x00\x00\x00\x00\x00\x00\x01'
    519                 b'\x00\x00\x00\x00\x00\x00\x00\x00'
    520                 b'\x00\x00\x00\x00\x00\x00\x00\x2a',
    521                 # integer overflow in offset_table_offset
    522                 b'\x00\x08'
    523                 b'\x00\x00\x00\x00\x00\x00\x01\x01'
    524                 b'\x00\x00\x00\x00\x00\x00\x00\x01'
    525                 b'\x00\x00\x00\x00\x00\x00\x00\x00'
    526                 b'\xff\xff\xff\xff\xff\xff\xff\xff',
    527                 # offset_size = 0
    528                 b'\x00\x08'
    529                 b'\x00\x00\x00\x00\x00\x00\x00\x01'
    530                 b'\x00\x00\x00\x00\x00\x00\x00\x01'
    531                 b'\x00\x00\x00\x00\x00\x00\x00\x00'
    532                 b'\x00\x00\x00\x00\x00\x00\x00\x09',
    533                 # ref_size = 0
    534                 b'\xa1\x01\x00\x08\x0a'
    535                 b'\x00\x00\x00\x00\x00\x00\x01\x00'
    536                 b'\x00\x00\x00\x00\x00\x00\x00\x02'
    537                 b'\x00\x00\x00\x00\x00\x00\x00\x00'
    538                 b'\x00\x00\x00\x00\x00\x00\x00\x0b',
    539                 # integer overflow in offset
    540                 b'\x00\xff\xff\xff\xff\xff\xff\xff\xff'
    541                 b'\x00\x00\x00\x00\x00\x00\x08\x01'
    542                 b'\x00\x00\x00\x00\x00\x00\x00\x01'
    543                 b'\x00\x00\x00\x00\x00\x00\x00\x00'
    544                 b'\x00\x00\x00\x00\x00\x00\x00\x09',
    545                 # invalid ASCII
    546                 b'\x51\xff\x08'
    547                 b'\x00\x00\x00\x00\x00\x00\x01\x01'
    548                 b'\x00\x00\x00\x00\x00\x00\x00\x01'
    549                 b'\x00\x00\x00\x00\x00\x00\x00\x00'
    550                 b'\x00\x00\x00\x00\x00\x00\x00\x0a',
    551                 # invalid UTF-16
    552                 b'\x61\xd8\x00\x08'
    553                 b'\x00\x00\x00\x00\x00\x00\x01\x01'
    554                 b'\x00\x00\x00\x00\x00\x00\x00\x01'
    555                 b'\x00\x00\x00\x00\x00\x00\x00\x00'
    556                 b'\x00\x00\x00\x00\x00\x00\x00\x0b',
    557                 ]:
    558             with self.assertRaises(plistlib.InvalidFileException):
    559                 plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY)
    560 
    561 
    562 class TestPlistlibDeprecated(unittest.TestCase):
    563     def test_io_deprecated(self):
    564         pl_in = {
    565             'key': 42,
    566             'sub': {
    567                 'key': 9,
    568                 'alt': 'value',
    569                 'data': b'buffer',
    570             }
    571         }
    572         pl_out = {
    573             'key': 42,
    574             'sub': {
    575                 'key': 9,
    576                 'alt': 'value',
    577                 'data': plistlib.Data(b'buffer'),
    578             }
    579         }
    580 
    581         self.addCleanup(support.unlink, support.TESTFN)
    582         with self.assertWarns(DeprecationWarning):
    583             plistlib.writePlist(pl_in, support.TESTFN)
    584 
    585         with self.assertWarns(DeprecationWarning):
    586             pl2 = plistlib.readPlist(support.TESTFN)
    587 
    588         self.assertEqual(pl_out, pl2)
    589 
    590         os.unlink(support.TESTFN)
    591 
    592         with open(support.TESTFN, 'wb') as fp:
    593             with self.assertWarns(DeprecationWarning):
    594                 plistlib.writePlist(pl_in, fp)
    595 
    596         with open(support.TESTFN, 'rb') as fp:
    597             with self.assertWarns(DeprecationWarning):
    598                 pl2 = plistlib.readPlist(fp)
    599 
    600         self.assertEqual(pl_out, pl2)
    601 
    602     def test_bytes_deprecated(self):
    603         pl = {
    604             'key': 42,
    605             'sub': {
    606                 'key': 9,
    607                 'alt': 'value',
    608                 'data': b'buffer',
    609             }
    610         }
    611         with self.assertWarns(DeprecationWarning):
    612             data = plistlib.writePlistToBytes(pl)
    613 
    614         with self.assertWarns(DeprecationWarning):
    615             pl2 = plistlib.readPlistFromBytes(data)
    616 
    617         self.assertIsInstance(pl2, dict)
    618         self.assertEqual(pl2, dict(
    619             key=42,
    620             sub=dict(
    621                 key=9,
    622                 alt='value',
    623                 data=plistlib.Data(b'buffer'),
    624             )
    625         ))
    626 
    627         with self.assertWarns(DeprecationWarning):
    628             data2 = plistlib.writePlistToBytes(pl2)
    629         self.assertEqual(data, data2)
    630 
    631     def test_dataobject_deprecated(self):
    632         in_data = { 'key': plistlib.Data(b'hello') }
    633         out_data = { 'key': b'hello' }
    634 
    635         buf = plistlib.dumps(in_data)
    636 
    637         cur = plistlib.loads(buf)
    638         self.assertEqual(cur, out_data)
    639         self.assertEqual(cur, in_data)
    640 
    641         cur = plistlib.loads(buf, use_builtin_types=False)
    642         self.assertEqual(cur, out_data)
    643         self.assertEqual(cur, in_data)
    644 
    645         with self.assertWarns(DeprecationWarning):
    646             cur = plistlib.readPlistFromBytes(buf)
    647         self.assertEqual(cur, out_data)
    648         self.assertEqual(cur, in_data)
    649 
    650 
    651 class MiscTestCase(unittest.TestCase):
    652     def test__all__(self):
    653         blacklist = {"PlistFormat", "PLISTHEADER"}
    654         support.check__all__(self, plistlib, blacklist=blacklist)
    655 
    656 
    657 def test_main():
    658     support.run_unittest(TestPlistlib, TestPlistlibDeprecated, MiscTestCase)
    659 
    660 
    661 if __name__ == '__main__':
    662     test_main()
    663