blob: 5c2d0265079a21352657c34074803aa57dc8df06 [file] [log] [blame]
Ronald Oussorenc5cf7972013-11-21 15:46:49 +01001# Copyright (C) 2003-2013 Python Software Foundation
Jon Janzenc981ad12019-05-15 22:14:38 +02002import copy
3import operator
4import pickle
Jack Jansen838e76a2003-02-25 12:58:58 +00005import unittest
6import plistlib
7import os
Just van Rossumfc93e172004-10-26 11:02:08 +00008import datetime
Ronald Oussorenc5cf7972013-11-21 15:46:49 +01009import codecs
10import binascii
11import collections
Benjamin Petersonee8712c2008-05-20 21:35:26 +000012from test import support
Ronald Oussorenc5cf7972013-11-21 15:46:49 +010013from io import BytesIO
Jack Jansen838e76a2003-02-25 12:58:58 +000014
Jon Janzenc981ad12019-05-15 22:14:38 +020015from plistlib import UID
16
Ronald Oussorenc5cf7972013-11-21 15:46:49 +010017ALL_FORMATS=(plistlib.FMT_XML, plistlib.FMT_BINARY)
Just van Rossumfc93e172004-10-26 11:02:08 +000018
Ronald Oussorenc5cf7972013-11-21 15:46:49 +010019# The testdata is generated using Mac/Tools/plistlib_generate_testdata.py
20# (which using PyObjC to control the Cocoa classes for generating plists)
21TESTDATA={
22 plistlib.FMT_XML: binascii.a2b_base64(b'''
23 PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
24 WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
25 IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
Ronald Oussoren6db66532014-01-15 11:32:35 +010026 LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+YUJp
27 Z0ludDwva2V5PgoJPGludGVnZXI+OTIyMzM3MjAzNjg1NDc3NTc2NDwvaW50
Ronald Oussoren94e44a92014-02-06 11:19:18 +010028 ZWdlcj4KCTxrZXk+YUJpZ0ludDI8L2tleT4KCTxpbnRlZ2VyPjkyMjMzNzIw
29 MzY4NTQ3NzU4NTI8L2ludGVnZXI+Cgk8a2V5PmFEYXRlPC9rZXk+Cgk8ZGF0
30 ZT4yMDA0LTEwLTI2VDEwOjMzOjMzWjwvZGF0ZT4KCTxrZXk+YURpY3Q8L2tl
31 eT4KCTxkaWN0PgoJCTxrZXk+YUZhbHNlVmFsdWU8L2tleT4KCQk8ZmFsc2Uv
32 PgoJCTxrZXk+YVRydWVWYWx1ZTwva2V5PgoJCTx0cnVlLz4KCQk8a2V5PmFV
33 bmljb2RlVmFsdWU8L2tleT4KCQk8c3RyaW5nPk3DpHNzaWcsIE1hw588L3N0
34 cmluZz4KCQk8a2V5PmFub3RoZXJTdHJpbmc8L2tleT4KCQk8c3RyaW5nPiZs
35 dDtoZWxsbyAmYW1wOyAnaGknIHRoZXJlISZndDs8L3N0cmluZz4KCQk8a2V5
36 PmRlZXBlckRpY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5hPC9rZXk+CgkJ
37 CTxpbnRlZ2VyPjE3PC9pbnRlZ2VyPgoJCQk8a2V5PmI8L2tleT4KCQkJPHJl
38 YWw+MzIuNTwvcmVhbD4KCQkJPGtleT5jPC9rZXk+CgkJCTxhcnJheT4KCQkJ
39 CTxpbnRlZ2VyPjE8L2ludGVnZXI+CgkJCQk8aW50ZWdlcj4yPC9pbnRlZ2Vy
40 PgoJCQkJPHN0cmluZz50ZXh0PC9zdHJpbmc+CgkJCTwvYXJyYXk+CgkJPC9k
41 aWN0PgoJPC9kaWN0PgoJPGtleT5hRmxvYXQ8L2tleT4KCTxyZWFsPjAuNTwv
42 cmVhbD4KCTxrZXk+YUxpc3Q8L2tleT4KCTxhcnJheT4KCQk8c3RyaW5nPkE8
43 L3N0cmluZz4KCQk8c3RyaW5nPkI8L3N0cmluZz4KCQk8aW50ZWdlcj4xMjwv
44 aW50ZWdlcj4KCQk8cmVhbD4zMi41PC9yZWFsPgoJCTxhcnJheT4KCQkJPGlu
45 dGVnZXI+MTwvaW50ZWdlcj4KCQkJPGludGVnZXI+MjwvaW50ZWdlcj4KCQkJ
46 PGludGVnZXI+MzwvaW50ZWdlcj4KCQk8L2FycmF5PgoJPC9hcnJheT4KCTxr
47 ZXk+YU5lZ2F0aXZlQmlnSW50PC9rZXk+Cgk8aW50ZWdlcj4tODAwMDAwMDAw
48 MDA8L2ludGVnZXI+Cgk8a2V5PmFOZWdhdGl2ZUludDwva2V5PgoJPGludGVn
49 ZXI+LTU8L2ludGVnZXI+Cgk8a2V5PmFTdHJpbmc8L2tleT4KCTxzdHJpbmc+
50 RG9vZGFoPC9zdHJpbmc+Cgk8a2V5PmFuRW1wdHlEaWN0PC9rZXk+Cgk8ZGlj
51 dC8+Cgk8a2V5PmFuRW1wdHlMaXN0PC9rZXk+Cgk8YXJyYXkvPgoJPGtleT5h
52 bkludDwva2V5PgoJPGludGVnZXI+NzI4PC9pbnRlZ2VyPgoJPGtleT5uZXN0
53 ZWREYXRhPC9rZXk+Cgk8YXJyYXk+CgkJPGRhdGE+CgkJUEd4dmRITWdiMlln
Ronald Oussoren6db66532014-01-15 11:32:35 +010054 WW1sdVlYSjVJR2QxYm1zK0FBRUNBenhzYjNSeklHOW1JR0pwYm1GeWVTQm5k
Ronald Oussoren94e44a92014-02-06 11:19:18 +010055 VzVyCgkJUGdBQkFnTThiRzkwY3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJ
56 RFBHeHZkSE1nYjJZZ1ltbHVZWEo1CgkJSUdkMWJtcytBQUVDQXp4c2IzUnpJ
57 RzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004Ykc5MGN5QnZaaUJpCgkJYVc1
58 aGNua2daM1Z1YXo0QUFRSURQR3h2ZEhNZ2IyWWdZbWx1WVhKNUlHZDFibXMr
59 QUFFQ0F6eHNiM1J6CgkJSUc5bUlHSnBibUZ5ZVNCbmRXNXJQZ0FCQWdNOGJH
60 OTBjeUJ2WmlCaWFXNWhjbmtnWjNWdWF6NEFBUUlECgkJUEd4dmRITWdiMlln
61 WW1sdVlYSjVJR2QxYm1zK0FBRUNBdz09CgkJPC9kYXRhPgoJPC9hcnJheT4K
62 CTxrZXk+c29tZURhdGE8L2tleT4KCTxkYXRhPgoJUEdKcGJtRnllU0JuZFc1
63 clBnPT0KCTwvZGF0YT4KCTxrZXk+c29tZU1vcmVEYXRhPC9rZXk+Cgk8ZGF0
64 YT4KCVBHeHZkSE1nYjJZZ1ltbHVZWEo1SUdkMWJtcytBQUVDQXp4c2IzUnpJ
65 RzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004CgliRzkwY3lCdlppQmlhVzVo
66 Y25rZ1ozVnVhejRBQVFJRFBHeHZkSE1nYjJZZ1ltbHVZWEo1SUdkMWJtcytB
67 QUVDQXp4cwoJYjNSeklHOW1JR0pwYm1GeWVTQm5kVzVyUGdBQkFnTThiRzkw
68 Y3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJRFBHeHYKCWRITWdiMllnWW1s
69 dVlYSjVJR2QxYm1zK0FBRUNBenhzYjNSeklHOW1JR0pwYm1GeWVTQm5kVzVy
70 UGdBQkFnTThiRzkwCgljeUJ2WmlCaWFXNWhjbmtnWjNWdWF6NEFBUUlEUEd4
71 dmRITWdiMllnWW1sdVlYSjVJR2QxYm1zK0FBRUNBdz09Cgk8L2RhdGE+Cgk8
72 a2V5PsOFYmVucmFhPC9rZXk+Cgk8c3RyaW5nPlRoYXQgd2FzIGEgdW5pY29k
73 ZSBrZXkuPC9zdHJpbmc+CjwvZGljdD4KPC9wbGlzdD4K'''),
Ronald Oussorenc5cf7972013-11-21 15:46:49 +010074 plistlib.FMT_BINARY: binascii.a2b_base64(b'''
Ronald Oussoren94e44a92014-02-06 11:19:18 +010075 YnBsaXN0MDDfEBABAgMEBQYHCAkKCwwNDg8QERITFCgpLzAxMjM0NTc2OFdh
76 QmlnSW50WGFCaWdJbnQyVWFEYXRlVWFEaWN0VmFGbG9hdFVhTGlzdF8QD2FO
77 ZWdhdGl2ZUJpZ0ludFxhTmVnYXRpdmVJbnRXYVN0cmluZ1thbkVtcHR5RGlj
78 dFthbkVtcHR5TGlzdFVhbkludFpuZXN0ZWREYXRhWHNvbWVEYXRhXHNvbWVN
79 b3JlRGF0YWcAxQBiAGUAbgByAGEAYRN/////////1BQAAAAAAAAAAIAAAAAA
80 AAAsM0GcuX30AAAA1RUWFxgZGhscHR5bYUZhbHNlVmFsdWVaYVRydWVWYWx1
81 ZV1hVW5pY29kZVZhbHVlXWFub3RoZXJTdHJpbmdaZGVlcGVyRGljdAgJawBN
82 AOQAcwBzAGkAZwAsACAATQBhAN9fEBU8aGVsbG8gJiAnaGknIHRoZXJlIT7T
83 HyAhIiMkUWFRYlFjEBEjQEBAAAAAAACjJSYnEAEQAlR0ZXh0Iz/gAAAAAAAA
84 pSorLCMtUUFRQhAMoyUmLhADE////+1foOAAE//////////7VkRvb2RhaNCg
85 EQLYoTZPEPo8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmlu
86 YXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBv
87 ZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxs
88 b3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4A
89 AQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBn
90 dW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDTTxiaW5hcnkgZ3Vu
91 az5fEBdUaGF0IHdhcyBhIHVuaWNvZGUga2V5LgAIACsAMwA8AEIASABPAFUA
92 ZwB0AHwAiACUAJoApQCuALsAygDTAOQA7QD4AQQBDwEdASsBNgE3ATgBTwFn
93 AW4BcAFyAXQBdgF/AYMBhQGHAYwBlQGbAZ0BnwGhAaUBpwGwAbkBwAHBAcIB
94 xQHHAsQC0gAAAAAAAAIBAAAAAAAAADkAAAAAAAAAAAAAAAAAAALs'''),
Jon Janzenc981ad12019-05-15 22:14:38 +020095 'KEYED_ARCHIVE': binascii.a2b_base64(b'''
96 YnBsaXN0MDDUAQIDBAUGHB1YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVy
97 VCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OVnB5dHlwZVYkY2xhc3NZTlMu
98 c3RyaW5nEAGAAl8QE0tleUFyY2hpdmUgVUlEIFRlc3TTEBESExQZWiRjbGFz
99 c25hbWVYJGNsYXNzZXNbJGNsYXNzaGludHNfEBdPQ19CdWlsdGluUHl0aG9u
100 VW5pY29kZaQVFhcYXxAXT0NfQnVpbHRpblB5dGhvblVuaWNvZGVfEBBPQ19Q
101 eXRob25Vbmljb2RlWE5TU3RyaW5nWE5TT2JqZWN0ohobXxAPT0NfUHl0aG9u
102 U3RyaW5nWE5TU3RyaW5nXxAPTlNLZXllZEFyY2hpdmVy0R4fVHJvb3SAAQAI
103 ABEAGgAjAC0AMgA3ADsAQQBIAE8AVgBgAGIAZAB6AIEAjACVAKEAuwDAANoA
104 7QD2AP8BAgEUAR0BLwEyATcAAAAAAAACAQAAAAAAAAAgAAAAAAAAAAAAAAAA
105 AAABOQ=='''),
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100106}
Just van Rossumfc93e172004-10-26 11:02:08 +0000107
108
Jack Jansen838e76a2003-02-25 12:58:58 +0000109class TestPlistlib(unittest.TestCase):
110
111 def tearDown(self):
112 try:
Benjamin Petersonee8712c2008-05-20 21:35:26 +0000113 os.unlink(support.TESTFN)
Jack Jansen838e76a2003-02-25 12:58:58 +0000114 except:
115 pass
116
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100117 def _create(self, fmt=None):
Just van Rossumfc93e172004-10-26 11:02:08 +0000118 pl = dict(
Jack Jansen838e76a2003-02-25 12:58:58 +0000119 aString="Doodah",
Just van Rossumfc93e172004-10-26 11:02:08 +0000120 aList=["A", "B", 12, 32.5, [1, 2, 3]],
121 aFloat = 0.5,
Jack Jansen838e76a2003-02-25 12:58:58 +0000122 anInt = 728,
Ronald Oussoren6db66532014-01-15 11:32:35 +0100123 aBigInt = 2 ** 63 - 44,
Ronald Oussoren94e44a92014-02-06 11:19:18 +0100124 aBigInt2 = 2 ** 63 + 44,
Ronald Oussoren6db66532014-01-15 11:32:35 +0100125 aNegativeInt = -5,
126 aNegativeBigInt = -80000000000,
Just van Rossumfc93e172004-10-26 11:02:08 +0000127 aDict=dict(
128 anotherString="<hello & 'hi' there!>",
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000129 aUnicodeValue='M\xe4ssig, Ma\xdf',
Jack Jansen838e76a2003-02-25 12:58:58 +0000130 aTrueValue=True,
131 aFalseValue=False,
Just van Rossumfc93e172004-10-26 11:02:08 +0000132 deeperDict=dict(a=17, b=32.5, c=[1, 2, "text"]),
Jack Jansen838e76a2003-02-25 12:58:58 +0000133 ),
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100134 someData = b"<binary gunk>",
135 someMoreData = b"<lots of binary gunk>\0\1\2\3" * 10,
136 nestedData = [b"<lots of binary gunk>\0\1\2\3" * 10],
Just van Rossumfc93e172004-10-26 11:02:08 +0000137 aDate = datetime.datetime(2004, 10, 26, 10, 33, 33),
Hynek Schlawack52209d32012-05-29 12:04:54 +0200138 anEmptyDict = dict(),
139 anEmptyList = list()
Jack Jansen838e76a2003-02-25 12:58:58 +0000140 )
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000141 pl['\xc5benraa'] = "That was a unicode key."
Jack Jansen838e76a2003-02-25 12:58:58 +0000142 return pl
Tim Peters669454e2003-03-07 17:30:48 +0000143
Jack Jansen838e76a2003-02-25 12:58:58 +0000144 def test_create(self):
145 pl = self._create()
146 self.assertEqual(pl["aString"], "Doodah")
147 self.assertEqual(pl["aDict"]["aFalseValue"], False)
148
149 def test_io(self):
150 pl = self._create()
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100151 with open(support.TESTFN, 'wb') as fp:
152 plistlib.dump(pl, fp)
153
154 with open(support.TESTFN, 'rb') as fp:
155 pl2 = plistlib.load(fp)
156
Tim Peters669454e2003-03-07 17:30:48 +0000157 self.assertEqual(dict(pl), dict(pl2))
Jack Jansen838e76a2003-02-25 12:58:58 +0000158
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100159 self.assertRaises(AttributeError, plistlib.dump, pl, 'filename')
160 self.assertRaises(AttributeError, plistlib.load, 'filename')
161
Ronald Oussoren6db66532014-01-15 11:32:35 +0100162 def test_invalid_type(self):
163 pl = [ object() ]
164
165 for fmt in ALL_FORMATS:
166 with self.subTest(fmt=fmt):
167 self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt)
168
Jon Janzenc981ad12019-05-15 22:14:38 +0200169 def test_invalid_uid(self):
170 with self.assertRaises(TypeError):
171 UID("not an int")
172 with self.assertRaises(ValueError):
173 UID(2 ** 64)
174 with self.assertRaises(ValueError):
175 UID(-19)
176
Ronald Oussoren6db66532014-01-15 11:32:35 +0100177 def test_int(self):
178 for pl in [0, 2**8-1, 2**8, 2**16-1, 2**16, 2**32-1, 2**32,
Ronald Oussoren94e44a92014-02-06 11:19:18 +0100179 2**63-1, 2**64-1, 1, -2**63]:
Ronald Oussoren6db66532014-01-15 11:32:35 +0100180 for fmt in ALL_FORMATS:
181 with self.subTest(pl=pl, fmt=fmt):
182 data = plistlib.dumps(pl, fmt=fmt)
183 pl2 = plistlib.loads(data)
184 self.assertIsInstance(pl2, int)
185 self.assertEqual(pl, pl2)
186 data2 = plistlib.dumps(pl2, fmt=fmt)
187 self.assertEqual(data, data2)
188
189 for fmt in ALL_FORMATS:
190 for pl in (2 ** 64 + 1, 2 ** 127-1, -2**64, -2 ** 127):
191 with self.subTest(pl=pl, fmt=fmt):
192 self.assertRaises(OverflowError, plistlib.dumps,
193 pl, fmt=fmt)
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100194
Serhiy Storchakaa897aee2017-11-30 23:26:11 +0200195 def test_bytearray(self):
196 for pl in (b'<binary gunk>', b"<lots of binary gunk>\0\1\2\3" * 10):
197 for fmt in ALL_FORMATS:
198 with self.subTest(pl=pl, fmt=fmt):
199 data = plistlib.dumps(bytearray(pl), fmt=fmt)
200 pl2 = plistlib.loads(data)
201 self.assertIsInstance(pl2, bytes)
202 self.assertEqual(pl2, pl)
203 data2 = plistlib.dumps(pl2, fmt=fmt)
204 self.assertEqual(data, data2)
205
Guido van Rossumcd869d82007-08-07 14:26:40 +0000206 def test_bytes(self):
Just van Rossumfc93e172004-10-26 11:02:08 +0000207 pl = self._create()
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100208 data = plistlib.dumps(pl)
209 pl2 = plistlib.loads(data)
Just van Rossumfc93e172004-10-26 11:02:08 +0000210 self.assertEqual(dict(pl), dict(pl2))
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100211 data2 = plistlib.dumps(pl2)
Just van Rossumfc93e172004-10-26 11:02:08 +0000212 self.assertEqual(data, data2)
213
Ronald Oussoren326edfd2013-04-23 13:47:22 +0200214 def test_indentation_array(self):
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100215 data = [[[[[[[[{'test': b'aaaaaa'}]]]]]]]]
216 self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
Ronald Oussoren326edfd2013-04-23 13:47:22 +0200217
218 def test_indentation_dict(self):
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100219 data = {'1': {'2': {'3': {'4': {'5': {'6': {'7': {'8': {'9': b'aaaaaa'}}}}}}}}}
220 self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
Ronald Oussoren326edfd2013-04-23 13:47:22 +0200221
222 def test_indentation_dict_mix(self):
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100223 data = {'1': {'2': [{'3': [[[[[{'test': b'aaaaaa'}]]]]]}]}}
224 self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
Ronald Oussoren326edfd2013-04-23 13:47:22 +0200225
Jon Janzenc981ad12019-05-15 22:14:38 +0200226 def test_uid(self):
227 data = UID(1)
228 self.assertEqual(plistlib.loads(plistlib.dumps(data, fmt=plistlib.FMT_BINARY)), data)
229 dict_data = {
230 'uid0': UID(0),
231 'uid2': UID(2),
232 'uid8': UID(2 ** 8),
233 'uid16': UID(2 ** 16),
234 'uid32': UID(2 ** 32),
235 'uid63': UID(2 ** 63)
236 }
237 self.assertEqual(plistlib.loads(plistlib.dumps(dict_data, fmt=plistlib.FMT_BINARY)), dict_data)
238
239 def test_uid_data(self):
240 uid = UID(1)
241 self.assertEqual(uid.data, 1)
242
243 def test_uid_eq(self):
244 self.assertEqual(UID(1), UID(1))
245 self.assertNotEqual(UID(1), UID(2))
246 self.assertNotEqual(UID(1), "not uid")
247
248 def test_uid_hash(self):
249 self.assertEqual(hash(UID(1)), hash(UID(1)))
250
251 def test_uid_repr(self):
252 self.assertEqual(repr(UID(1)), "UID(1)")
253
254 def test_uid_index(self):
255 self.assertEqual(operator.index(UID(1)), 1)
256
257 def test_uid_pickle(self):
258 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
259 self.assertEqual(pickle.loads(pickle.dumps(UID(19), protocol=proto)), UID(19))
260
261 def test_uid_copy(self):
262 self.assertEqual(copy.copy(UID(1)), UID(1))
263 self.assertEqual(copy.deepcopy(UID(1)), UID(1))
264
Just van Rossumfc93e172004-10-26 11:02:08 +0000265 def test_appleformatting(self):
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100266 for use_builtin_types in (True, False):
267 for fmt in ALL_FORMATS:
268 with self.subTest(fmt=fmt, use_builtin_types=use_builtin_types):
269 pl = plistlib.loads(TESTDATA[fmt],
270 use_builtin_types=use_builtin_types)
271 data = plistlib.dumps(pl, fmt=fmt)
272 self.assertEqual(data, TESTDATA[fmt],
273 "generated data was not identical to Apple's output")
274
Just van Rossumfc93e172004-10-26 11:02:08 +0000275
276 def test_appleformattingfromliteral(self):
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100277 self.maxDiff = None
278 for fmt in ALL_FORMATS:
279 with self.subTest(fmt=fmt):
280 pl = self._create(fmt=fmt)
Serhiy Storchaka89667592014-07-23 18:49:31 +0300281 pl2 = plistlib.loads(TESTDATA[fmt], fmt=fmt)
282 self.assertEqual(dict(pl), dict(pl2),
283 "generated data was not identical to Apple's output")
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100284 pl2 = plistlib.loads(TESTDATA[fmt])
285 self.assertEqual(dict(pl), dict(pl2),
286 "generated data was not identical to Apple's output")
Just van Rossumfc93e172004-10-26 11:02:08 +0000287
Guido van Rossumcd869d82007-08-07 14:26:40 +0000288 def test_bytesio(self):
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100289 for fmt in ALL_FORMATS:
290 with self.subTest(fmt=fmt):
291 b = BytesIO()
292 pl = self._create(fmt=fmt)
293 plistlib.dump(pl, b, fmt=fmt)
Serhiy Storchaka89667592014-07-23 18:49:31 +0300294 pl2 = plistlib.load(BytesIO(b.getvalue()), fmt=fmt)
295 self.assertEqual(dict(pl), dict(pl2))
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100296 pl2 = plistlib.load(BytesIO(b.getvalue()))
297 self.assertEqual(dict(pl), dict(pl2))
298
299 def test_keysort_bytesio(self):
300 pl = collections.OrderedDict()
301 pl['b'] = 1
302 pl['a'] = 2
303 pl['c'] = 3
304
305 for fmt in ALL_FORMATS:
306 for sort_keys in (False, True):
307 with self.subTest(fmt=fmt, sort_keys=sort_keys):
308 b = BytesIO()
309
310 plistlib.dump(pl, b, fmt=fmt, sort_keys=sort_keys)
311 pl2 = plistlib.load(BytesIO(b.getvalue()),
312 dict_type=collections.OrderedDict)
313
314 self.assertEqual(dict(pl), dict(pl2))
315 if sort_keys:
316 self.assertEqual(list(pl2.keys()), ['a', 'b', 'c'])
317 else:
318 self.assertEqual(list(pl2.keys()), ['b', 'a', 'c'])
319
320 def test_keysort(self):
321 pl = collections.OrderedDict()
322 pl['b'] = 1
323 pl['a'] = 2
324 pl['c'] = 3
325
326 for fmt in ALL_FORMATS:
327 for sort_keys in (False, True):
328 with self.subTest(fmt=fmt, sort_keys=sort_keys):
329 data = plistlib.dumps(pl, fmt=fmt, sort_keys=sort_keys)
330 pl2 = plistlib.loads(data, dict_type=collections.OrderedDict)
331
332 self.assertEqual(dict(pl), dict(pl2))
333 if sort_keys:
334 self.assertEqual(list(pl2.keys()), ['a', 'b', 'c'])
335 else:
336 self.assertEqual(list(pl2.keys()), ['b', 'a', 'c'])
337
338 def test_keys_no_string(self):
339 pl = { 42: 'aNumber' }
340
341 for fmt in ALL_FORMATS:
342 with self.subTest(fmt=fmt):
343 self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt)
344
345 b = BytesIO()
346 self.assertRaises(TypeError, plistlib.dump, pl, b, fmt=fmt)
347
348 def test_skipkeys(self):
349 pl = {
350 42: 'aNumber',
351 'snake': 'aWord',
352 }
353
354 for fmt in ALL_FORMATS:
355 with self.subTest(fmt=fmt):
356 data = plistlib.dumps(
357 pl, fmt=fmt, skipkeys=True, sort_keys=False)
358
359 pl2 = plistlib.loads(data)
360 self.assertEqual(pl2, {'snake': 'aWord'})
361
362 fp = BytesIO()
363 plistlib.dump(
364 pl, fp, fmt=fmt, skipkeys=True, sort_keys=False)
365 data = fp.getvalue()
366 pl2 = plistlib.loads(fp.getvalue())
367 self.assertEqual(pl2, {'snake': 'aWord'})
368
369 def test_tuple_members(self):
370 pl = {
371 'first': (1, 2),
372 'second': (1, 2),
373 'third': (3, 4),
374 }
375
376 for fmt in ALL_FORMATS:
377 with self.subTest(fmt=fmt):
378 data = plistlib.dumps(pl, fmt=fmt)
379 pl2 = plistlib.loads(data)
380 self.assertEqual(pl2, {
381 'first': [1, 2],
382 'second': [1, 2],
383 'third': [3, 4],
384 })
Serhiy Storchaka0e069a12017-12-12 19:03:08 +0200385 if fmt != plistlib.FMT_BINARY:
386 self.assertIsNot(pl2['first'], pl2['second'])
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100387
388 def test_list_members(self):
389 pl = {
390 'first': [1, 2],
391 'second': [1, 2],
392 'third': [3, 4],
393 }
394
395 for fmt in ALL_FORMATS:
396 with self.subTest(fmt=fmt):
397 data = plistlib.dumps(pl, fmt=fmt)
398 pl2 = plistlib.loads(data)
399 self.assertEqual(pl2, {
400 'first': [1, 2],
401 'second': [1, 2],
402 'third': [3, 4],
403 })
404 self.assertIsNot(pl2['first'], pl2['second'])
405
406 def test_dict_members(self):
407 pl = {
408 'first': {'a': 1},
409 'second': {'a': 1},
410 'third': {'b': 2 },
411 }
412
413 for fmt in ALL_FORMATS:
414 with self.subTest(fmt=fmt):
415 data = plistlib.dumps(pl, fmt=fmt)
416 pl2 = plistlib.loads(data)
417 self.assertEqual(pl2, {
418 'first': {'a': 1},
419 'second': {'a': 1},
420 'third': {'b': 2 },
421 })
422 self.assertIsNot(pl2['first'], pl2['second'])
Just van Rossumbcc58e82003-07-01 20:22:30 +0000423
Just van Rossum48ecacc2004-11-12 08:34:32 +0000424 def test_controlcharacters(self):
Just van Rossum2dae7642004-11-12 09:36:12 +0000425 for i in range(128):
426 c = chr(i)
427 testString = "string containing %s" % c
428 if i >= 32 or c in "\r\n\t":
429 # \r, \n and \t are the only legal control chars in XML
Serhiy Storchakadb91e0f2017-10-31 14:05:53 +0200430 data = plistlib.dumps(testString, fmt=plistlib.FMT_XML)
431 if c != "\r":
432 self.assertEqual(plistlib.loads(data), testString)
Just van Rossum2dae7642004-11-12 09:36:12 +0000433 else:
Serhiy Storchakadb91e0f2017-10-31 14:05:53 +0200434 with self.assertRaises(ValueError):
435 plistlib.dumps(testString, fmt=plistlib.FMT_XML)
436 plistlib.dumps(testString, fmt=plistlib.FMT_BINARY)
Just van Rossum48ecacc2004-11-12 08:34:32 +0000437
Serhiy Storchaka7338ebc2016-10-04 20:04:30 +0300438 def test_non_bmp_characters(self):
439 pl = {'python': '\U0001f40d'}
440 for fmt in ALL_FORMATS:
441 with self.subTest(fmt=fmt):
442 data = plistlib.dumps(pl, fmt=fmt)
443 self.assertEqual(plistlib.loads(data), pl)
444
Serhiy Storchakadb91e0f2017-10-31 14:05:53 +0200445 def test_lone_surrogates(self):
446 for fmt in ALL_FORMATS:
447 with self.subTest(fmt=fmt):
448 with self.assertRaises(UnicodeEncodeError):
449 plistlib.dumps('\ud8ff', fmt=fmt)
450 with self.assertRaises(UnicodeEncodeError):
451 plistlib.dumps('\udcff', fmt=fmt)
452
Just van Rossum48ecacc2004-11-12 08:34:32 +0000453 def test_nondictroot(self):
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100454 for fmt in ALL_FORMATS:
455 with self.subTest(fmt=fmt):
456 test1 = "abc"
457 test2 = [1, 2, 3, "abc"]
458 result1 = plistlib.loads(plistlib.dumps(test1, fmt=fmt))
459 result2 = plistlib.loads(plistlib.dumps(test2, fmt=fmt))
460 self.assertEqual(test1, result1)
461 self.assertEqual(test2, result2)
Jack Jansen838e76a2003-02-25 12:58:58 +0000462
Ned Deilyb8e59f72011-05-28 02:19:19 -0700463 def test_invalidarray(self):
464 for i in ["<key>key inside an array</key>",
465 "<key>key inside an array2</key><real>3</real>",
466 "<true/><key>key inside an array3</key>"]:
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100467 self.assertRaises(ValueError, plistlib.loads,
Ned Deilyb8e59f72011-05-28 02:19:19 -0700468 ("<plist><array>%s</array></plist>"%i).encode())
469
470 def test_invaliddict(self):
471 for i in ["<key><true/>k</key><string>compound key</string>",
472 "<key>single key</key>",
473 "<string>missing key</string>",
474 "<key>k1</key><string>v1</string><real>5.3</real>"
475 "<key>k1</key><key>k2</key><string>double key</string>"]:
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100476 self.assertRaises(ValueError, plistlib.loads,
Ned Deilyb8e59f72011-05-28 02:19:19 -0700477 ("<plist><dict>%s</dict></plist>"%i).encode())
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100478 self.assertRaises(ValueError, plistlib.loads,
Ned Deilyb8e59f72011-05-28 02:19:19 -0700479 ("<plist><array><dict>%s</dict></array></plist>"%i).encode())
480
481 def test_invalidinteger(self):
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100482 self.assertRaises(ValueError, plistlib.loads,
Ned Deilyb8e59f72011-05-28 02:19:19 -0700483 b"<plist><integer>not integer</integer></plist>")
484
485 def test_invalidreal(self):
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100486 self.assertRaises(ValueError, plistlib.loads,
Ned Deilyb8e59f72011-05-28 02:19:19 -0700487 b"<plist><integer>not real</integer></plist>")
488
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100489 def test_xml_encodings(self):
490 base = TESTDATA[plistlib.FMT_XML]
491
492 for xml_encoding, encoding, bom in [
493 (b'utf-8', 'utf-8', codecs.BOM_UTF8),
494 (b'utf-16', 'utf-16-le', codecs.BOM_UTF16_LE),
495 (b'utf-16', 'utf-16-be', codecs.BOM_UTF16_BE),
496 # Expat does not support UTF-32
497 #(b'utf-32', 'utf-32-le', codecs.BOM_UTF32_LE),
498 #(b'utf-32', 'utf-32-be', codecs.BOM_UTF32_BE),
499 ]:
500
501 pl = self._create(fmt=plistlib.FMT_XML)
502 with self.subTest(encoding=encoding):
503 data = base.replace(b'UTF-8', xml_encoding)
504 data = bom + data.decode('utf-8').encode(encoding)
505 pl2 = plistlib.loads(data)
506 self.assertEqual(dict(pl), dict(pl2))
507
Serhiy Storchakaa897aee2017-11-30 23:26:11 +0200508
509class TestBinaryPlistlib(unittest.TestCase):
510
Serhiy Storchaka06526642014-05-23 16:13:33 +0300511 def test_nonstandard_refs_size(self):
512 # Issue #21538: Refs and offsets are 24-bit integers
513 data = (b'bplist00'
514 b'\xd1\x00\x00\x01\x00\x00\x02QaQb'
515 b'\x00\x00\x08\x00\x00\x0f\x00\x00\x11'
516 b'\x00\x00\x00\x00\x00\x00'
517 b'\x03\x03'
518 b'\x00\x00\x00\x00\x00\x00\x00\x03'
519 b'\x00\x00\x00\x00\x00\x00\x00\x00'
520 b'\x00\x00\x00\x00\x00\x00\x00\x13')
521 self.assertEqual(plistlib.loads(data), {'a': 'b'})
522
Serhiy Storchakaa897aee2017-11-30 23:26:11 +0200523 def test_dump_duplicates(self):
524 # Test effectiveness of saving duplicated objects
525 for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde',
526 datetime.datetime(2004, 10, 26, 10, 33, 33),
527 plistlib.Data(b'abcde'), bytearray(b'abcde'),
528 [12, 345], (12, 345), {'12': 345}):
529 with self.subTest(x=x):
530 data = plistlib.dumps([x]*1000, fmt=plistlib.FMT_BINARY)
531 self.assertLess(len(data), 1100, repr(data))
532
533 def test_identity(self):
534 for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde',
535 datetime.datetime(2004, 10, 26, 10, 33, 33),
536 plistlib.Data(b'abcde'), bytearray(b'abcde'),
537 [12, 345], (12, 345), {'12': 345}):
538 with self.subTest(x=x):
539 data = plistlib.dumps([x]*2, fmt=plistlib.FMT_BINARY)
540 a, b = plistlib.loads(data)
541 if isinstance(x, tuple):
542 x = list(x)
543 self.assertEqual(a, x)
544 self.assertEqual(b, x)
545 self.assertIs(a, b)
546
547 def test_cycles(self):
548 # recursive list
549 a = []
550 a.append(a)
551 b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY))
552 self.assertIs(b[0], b)
553 # recursive tuple
554 a = ([],)
555 a[0].append(a)
556 b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY))
557 self.assertIs(b[0][0], b)
558 # recursive dict
559 a = {}
560 a['x'] = a
561 b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY))
562 self.assertIs(b['x'], b)
563
Serhiy Storchaka94ad49f2016-04-08 15:00:02 +0300564 def test_large_timestamp(self):
565 # Issue #26709: 32-bit timestamp out of range
566 for ts in -2**31-1, 2**31:
567 with self.subTest(ts=ts):
568 d = (datetime.datetime.utcfromtimestamp(0) +
569 datetime.timedelta(seconds=ts))
570 data = plistlib.dumps(d, fmt=plistlib.FMT_BINARY)
571 self.assertEqual(plistlib.loads(data), d)
572
Serhiy Storchakadb91e0f2017-10-31 14:05:53 +0200573 def test_invalid_binary(self):
574 for data in [
575 # too short data
576 b'',
577 # too large offset_table_offset and nonstandard offset_size
578 b'\x00\x08'
579 b'\x00\x00\x00\x00\x00\x00\x03\x01'
580 b'\x00\x00\x00\x00\x00\x00\x00\x01'
581 b'\x00\x00\x00\x00\x00\x00\x00\x00'
582 b'\x00\x00\x00\x00\x00\x00\x00\x2a',
583 # integer overflow in offset_table_offset
584 b'\x00\x08'
585 b'\x00\x00\x00\x00\x00\x00\x01\x01'
586 b'\x00\x00\x00\x00\x00\x00\x00\x01'
587 b'\x00\x00\x00\x00\x00\x00\x00\x00'
588 b'\xff\xff\xff\xff\xff\xff\xff\xff',
589 # offset_size = 0
590 b'\x00\x08'
591 b'\x00\x00\x00\x00\x00\x00\x00\x01'
592 b'\x00\x00\x00\x00\x00\x00\x00\x01'
593 b'\x00\x00\x00\x00\x00\x00\x00\x00'
594 b'\x00\x00\x00\x00\x00\x00\x00\x09',
595 # ref_size = 0
596 b'\xa1\x01\x00\x08\x0a'
597 b'\x00\x00\x00\x00\x00\x00\x01\x00'
598 b'\x00\x00\x00\x00\x00\x00\x00\x02'
599 b'\x00\x00\x00\x00\x00\x00\x00\x00'
600 b'\x00\x00\x00\x00\x00\x00\x00\x0b',
601 # integer overflow in offset
602 b'\x00\xff\xff\xff\xff\xff\xff\xff\xff'
603 b'\x00\x00\x00\x00\x00\x00\x08\x01'
604 b'\x00\x00\x00\x00\x00\x00\x00\x01'
605 b'\x00\x00\x00\x00\x00\x00\x00\x00'
606 b'\x00\x00\x00\x00\x00\x00\x00\x09',
607 # invalid ASCII
608 b'\x51\xff\x08'
609 b'\x00\x00\x00\x00\x00\x00\x01\x01'
610 b'\x00\x00\x00\x00\x00\x00\x00\x01'
611 b'\x00\x00\x00\x00\x00\x00\x00\x00'
612 b'\x00\x00\x00\x00\x00\x00\x00\x0a',
613 # invalid UTF-16
614 b'\x61\xd8\x00\x08'
615 b'\x00\x00\x00\x00\x00\x00\x01\x01'
616 b'\x00\x00\x00\x00\x00\x00\x00\x01'
617 b'\x00\x00\x00\x00\x00\x00\x00\x00'
618 b'\x00\x00\x00\x00\x00\x00\x00\x0b',
619 ]:
620 with self.assertRaises(plistlib.InvalidFileException):
621 plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY)
622
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100623
624class TestPlistlibDeprecated(unittest.TestCase):
625 def test_io_deprecated(self):
626 pl_in = {
627 'key': 42,
628 'sub': {
629 'key': 9,
630 'alt': 'value',
631 'data': b'buffer',
632 }
633 }
Serhiy Storchakaedef3582017-05-15 13:21:31 +0300634 pl_out = {
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100635 'key': 42,
Serhiy Storchakaedef3582017-05-15 13:21:31 +0300636 'sub': {
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100637 'key': 9,
638 'alt': 'value',
639 'data': plistlib.Data(b'buffer'),
Serhiy Storchakaedef3582017-05-15 13:21:31 +0300640 }
641 }
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100642
643 self.addCleanup(support.unlink, support.TESTFN)
644 with self.assertWarns(DeprecationWarning):
645 plistlib.writePlist(pl_in, support.TESTFN)
646
647 with self.assertWarns(DeprecationWarning):
648 pl2 = plistlib.readPlist(support.TESTFN)
649
650 self.assertEqual(pl_out, pl2)
651
652 os.unlink(support.TESTFN)
653
654 with open(support.TESTFN, 'wb') as fp:
655 with self.assertWarns(DeprecationWarning):
656 plistlib.writePlist(pl_in, fp)
657
658 with open(support.TESTFN, 'rb') as fp:
659 with self.assertWarns(DeprecationWarning):
660 pl2 = plistlib.readPlist(fp)
661
662 self.assertEqual(pl_out, pl2)
663
664 def test_bytes_deprecated(self):
665 pl = {
666 'key': 42,
667 'sub': {
668 'key': 9,
669 'alt': 'value',
670 'data': b'buffer',
671 }
672 }
673 with self.assertWarns(DeprecationWarning):
674 data = plistlib.writePlistToBytes(pl)
675
676 with self.assertWarns(DeprecationWarning):
677 pl2 = plistlib.readPlistFromBytes(data)
678
Serhiy Storchakaedef3582017-05-15 13:21:31 +0300679 self.assertIsInstance(pl2, dict)
680 self.assertEqual(pl2, dict(
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100681 key=42,
Serhiy Storchakaedef3582017-05-15 13:21:31 +0300682 sub=dict(
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100683 key=9,
684 alt='value',
685 data=plistlib.Data(b'buffer'),
686 )
687 ))
688
689 with self.assertWarns(DeprecationWarning):
690 data2 = plistlib.writePlistToBytes(pl2)
691 self.assertEqual(data, data2)
692
693 def test_dataobject_deprecated(self):
694 in_data = { 'key': plistlib.Data(b'hello') }
695 out_data = { 'key': b'hello' }
696
697 buf = plistlib.dumps(in_data)
698
699 cur = plistlib.loads(buf)
700 self.assertEqual(cur, out_data)
Serhiy Storchakadd1bcdf2016-05-01 13:36:16 +0300701 self.assertEqual(cur, in_data)
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100702
703 cur = plistlib.loads(buf, use_builtin_types=False)
Serhiy Storchakadd1bcdf2016-05-01 13:36:16 +0300704 self.assertEqual(cur, out_data)
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100705 self.assertEqual(cur, in_data)
706
707 with self.assertWarns(DeprecationWarning):
708 cur = plistlib.readPlistFromBytes(buf)
Serhiy Storchakadd1bcdf2016-05-01 13:36:16 +0300709 self.assertEqual(cur, out_data)
Ronald Oussorenc5cf7972013-11-21 15:46:49 +0100710 self.assertEqual(cur, in_data)
711
Jack Jansen838e76a2003-02-25 12:58:58 +0000712
Jon Janzenc981ad12019-05-15 22:14:38 +0200713class TestKeyedArchive(unittest.TestCase):
714 def test_keyed_archive_data(self):
715 # This is the structure of a NSKeyedArchive packed plist
716 data = {
717 '$version': 100000,
718 '$objects': [
719 '$null', {
720 'pytype': 1,
721 '$class': UID(2),
722 'NS.string': 'KeyArchive UID Test'
723 },
724 {
725 '$classname': 'OC_BuiltinPythonUnicode',
726 '$classes': [
727 'OC_BuiltinPythonUnicode',
728 'OC_PythonUnicode',
729 'NSString',
730 'NSObject'
731 ],
732 '$classhints': [
733 'OC_PythonString', 'NSString'
734 ]
735 }
736 ],
737 '$archiver': 'NSKeyedArchiver',
738 '$top': {
739 'root': UID(1)
740 }
741 }
742 self.assertEqual(plistlib.loads(TESTDATA["KEYED_ARCHIVE"]), data)
743
744
Martin Panterd04d2132016-06-06 02:00:50 +0000745class MiscTestCase(unittest.TestCase):
746 def test__all__(self):
747 blacklist = {"PlistFormat", "PLISTHEADER"}
748 support.check__all__(self, plistlib, blacklist=blacklist)
749
750
Jack Jansen838e76a2003-02-25 12:58:58 +0000751def test_main():
Jon Janzenc981ad12019-05-15 22:14:38 +0200752 support.run_unittest(TestPlistlib, TestPlistlibDeprecated, TestKeyedArchive, MiscTestCase)
Jack Jansen838e76a2003-02-25 12:58:58 +0000753
754
755if __name__ == '__main__':
756 test_main()