Hello community, here is the log from the commit of package python3-biplist for openSUSE:Factory checked in at 2016-02-01 19:57:23 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python3-biplist (Old) and /work/SRC/openSUSE:Factory/.python3-biplist.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python3-biplist" Changes: -------- --- /work/SRC/openSUSE:Factory/python3-biplist/python3-biplist.changes 2015-01-14 11:44:42.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.python3-biplist.new/python3-biplist.changes 2016-02-01 19:57:40.000000000 +0100 @@ -1,0 +2,26 @@ +Mon Feb 1 04:12:01 UTC 2016 - [email protected] + +- specfile: + * update copyright year + * enabled test for all distros + * switch to setuptools + +- update to version 1.0.1: + * Adding back in Python 2.6 support. This will be removed again in a + future version. + +- changes from version 1.0.0: + * This release changes the type of Uid from a subclass of int to a + subclass of object. + * This change was made to address GitHub issue #9 Ints are being + turned into Uids and vice versa when both are present in a plist. + + This release also bumps the minimum supported Python versions to + 2.7 and 3.4. + +- changes from version 0.9.1: + * Fixes GitHub issue #8 ERROR: testLargeDates + (test_valid.TestValidPlistFile) + * Fixes #6 Empty Data object converted as empty string + * Creates 1-byte strings when possible, per PR #4 + +------------------------------------------------------------------- Old: ---- biplist-0.9.tar.gz New: ---- biplist-1.0.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python3-biplist.spec ++++++ --- /var/tmp/diff_new_pack.SXtDwL/_old 2016-02-01 19:57:41.000000000 +0100 +++ /var/tmp/diff_new_pack.SXtDwL/_new 2016-02-01 19:57:41.000000000 +0100 @@ -1,7 +1,7 @@ # # spec file for package python3-biplist # -# Copyright (c) 2015 SUSE LINUX Products GmbH, Nuernberg, Germany. +# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: python3-biplist -Version: 0.9 +Version: 1.0.1 Release: 0 Url: https://github.com/wooster/biplist Summary: A library for reading/writing binary plists @@ -28,8 +28,8 @@ BuildRequires: python3 BuildRequires: python3-coverage BuildRequires: python3-devel -BuildRequires: python3-distribute BuildRequires: python3-nose +BuildRequires: python3-setuptools BuildRequires: python3-six BuildArch: noarch @@ -50,10 +50,7 @@ python3 setup.py install --prefix=%{_prefix} --root=%{buildroot} %check -# Failed on Factory, need to be fixed but does not really critical -%if 0%{?suse_version} < 1230 python3 setup.py test -%endif %files %defattr(-,root,root,-) ++++++ biplist-0.9.tar.gz -> biplist-1.0.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/biplist-0.9/PKG-INFO new/biplist-1.0.1/PKG-INFO --- old/biplist-0.9/PKG-INFO 2014-10-26 20:09:20.000000000 +0100 +++ new/biplist-1.0.1/PKG-INFO 2016-01-11 09:07:10.000000000 +0100 @@ -1,19 +1,19 @@ Metadata-Version: 1.1 Name: biplist -Version: 0.9 +Version: 1.0.1 Summary: biplist is a library for reading/writing binary plists. Home-page: https://bitbucket.org/wooster/biplist Author: Andrew Wooster Author-email: [email protected] License: BSD -Download-URL: https://bitbucket.org/wooster/biplist/downloads/biplist-0.9.tar.gz +Download-URL: https://bitbucket.org/wooster/biplist/downloads/biplist-1.0.1.tar.gz Description: `biplist` is a binary plist parser/generator for Python. Binary Property List (plist) files provide a faster and smaller serialization format for property lists on OS X. This is a library for generating binary plists which can be read by OS X, iOS, or other clients. - This module requires Python 2.6 or higher or Python 3.2 or higher. + This module requires Python 2.6 or higher or Python 3.4 or higher. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/biplist-0.9/biplist/__init__.py new/biplist-1.0.1/biplist/__init__.py --- old/biplist-0.9/biplist/__init__.py 2014-10-26 20:03:11.000000000 +0100 +++ new/biplist-1.0.1/biplist/__init__.py 2016-01-11 08:41:21.000000000 +0100 @@ -44,13 +44,12 @@ print "Not a plist:", e """ -import sys from collections import namedtuple import datetime import io import math import plistlib -from struct import pack, unpack +from struct import pack, unpack, unpack_from from struct import error as struct_error import sys import time @@ -79,23 +78,41 @@ # Apple uses Jan 1, 2001 as a base for all plist date/times. apple_reference_date = datetime.datetime.utcfromtimestamp(978307200) -class Uid(int): +class Uid(object): """Wrapper around integers for representing UID values. This is used in keyed archiving.""" + integer = 0 + def __init__(self, integer): + self.integer = integer + def __repr__(self): - return "Uid(%d)" % self + return "Uid(%d)" % self.integer + + def __eq__(self, other): + if isinstance(self, Uid) and isinstance(other, Uid): + return self.integer == other.integer + return False + + def __cmp__(self, other): + return self.integer - other.integer + + def __lt__(self, other): + return self.integer < other.integer + + def __hash__(self): + return self.integer + + def __int__(self): + return int(self.integer) class Data(bytes): - """Wrapper around str types for representing Data values.""" - pass + """Wrapper around bytes to distinguish Data values.""" class InvalidPlistException(Exception): """Raised when the plist is incorrectly formatted.""" - pass class NotBinaryPlistException(Exception): """Raised when a binary plist was expected but not encountered.""" - pass def readPlist(pathOrFile): """Raises NotBinaryPlistException, InvalidPlistException""" @@ -379,7 +396,7 @@ def readAsciiString(self, length): result = unpack("!%ds" % length, self.contents[self.currentOffset:self.currentOffset+length])[0] self.currentOffset += length - return result + return str(result.decode('ascii')) def readUnicode(self, length): actual_length = length*2 @@ -426,7 +443,9 @@ result = int.from_bytes(data, 'big') else: for byte in data: - result = (result << 8) | unpack('>B', byte)[0] + if not isinstance(byte, int): # Python3.0-3.1.x return ints, 2.x return str + byte = unpack_from('>B', byte)[0] + result = (result << 8) | byte else: raise InvalidPlistException("Encountered integer longer than 16 bytes.") return result @@ -456,6 +475,48 @@ def __repr__(self): return "<FloatWrapper: %s>" % self.value +class StringWrapper(object): + __instances = {} + + encodedValue = None + encoding = None + + def __new__(cls, value): + '''Ensure we only have a only one instance for any string, + and that we encode ascii as 1-byte-per character when possible''' + + encodedValue = None + + for encoding in ('ascii', 'utf_16_be'): + try: + encodedValue = value.encode(encoding) + except: pass + if encodedValue is not None: + if encodedValue not in cls.__instances: + cls.__instances[encodedValue] = super(StringWrapper, cls).__new__(cls) + cls.__instances[encodedValue].encodedValue = encodedValue + cls.__instances[encodedValue].encoding = encoding + return cls.__instances[encodedValue] + + raise ValueError('Unable to get ascii or utf_16_be encoding for %s' % repr(value)) + + def __len__(self): + '''Return roughly the number of characters in this string (half the byte length)''' + if self.encoding == 'ascii': + return len(self.encodedValue) + else: + return len(self.encodedValue)//2 + + @property + def encodingMarker(self): + if self.encoding == 'ascii': + return 0b0101 + else: + return 0b0110 + + def __repr__(self): + return '<StringWrapper (%s): %s>' % (self.encoding, self.encodedValue) + class PlistWriter(object): header = b'bplist00bybiplist1.0' file = None @@ -507,10 +568,9 @@ """ output = self.header wrapped_root = self.wrapRoot(root) - should_reference_root = True#not isinstance(wrapped_root, HashableWrapper) - self.computeOffsets(wrapped_root, asReference=should_reference_root, isRoot=True) + self.computeOffsets(wrapped_root, asReference=True, isRoot=True) self.trailer = self.trailer._replace(**{'objectRefSize':self.intSize(len(self.computedUniques))}) - (_, output) = self.writeObjectReference(wrapped_root, output) + self.writeObjectReference(wrapped_root, output) output = self.writeObject(wrapped_root, output, setReferencePosition=True) # output size at this point is an upper bound on how big the @@ -552,6 +612,10 @@ elif isinstance(root, tuple): n = tuple([self.wrapRoot(value) for value in root]) return HashableWrapper(n) + elif isinstance(root, (str, unicode)) and not isinstance(root, Data): + return StringWrapper(root) + elif isinstance(root, bytes): + return Data(root) else: return root @@ -564,7 +628,7 @@ raise InvalidPlistException('Dictionary keys cannot be null in plists.') elif isinstance(key, Data): raise InvalidPlistException('Data cannot be dictionary keys in plists.') - elif not isinstance(key, (bytes, unicode)): + elif not isinstance(key, StringWrapper): raise InvalidPlistException('Keys must be strings.') def proc_size(size): @@ -584,7 +648,7 @@ elif isinstance(obj, BoolWrapper): self.incrementByteCount('boolBytes') elif isinstance(obj, Uid): - size = self.intSize(obj) + size = self.intSize(obj.integer) self.incrementByteCount('uidBytes', incr=1+size) elif isinstance(obj, (int, long)): size = self.intSize(obj) @@ -597,7 +661,7 @@ elif isinstance(obj, Data): size = proc_size(len(obj)) self.incrementByteCount('dataBytes', incr=1+size) - elif isinstance(obj, (unicode, bytes)): + elif isinstance(obj, StringWrapper): size = proc_size(len(obj)) self.incrementByteCount('stringBytes', incr=1+size) elif isinstance(obj, HashableWrapper): @@ -621,7 +685,7 @@ self.computeOffsets(key, asReference=True) self.computeOffsets(value, asReference=True) else: - raise InvalidPlistException("Unknown object type.") + raise InvalidPlistException("Unknown object type: %s (%s)" % (type(obj).__name__, repr(obj))) def writeObjectReference(self, obj, output): """Tries to write an object reference, adding it to the references @@ -653,9 +717,10 @@ result += pack('!B', (format << 4) | length) return result - if isinstance(obj, (str, unicode)) and obj == unicodeEmpty: - # The Apple Plist decoder can't decode a zero length Unicode string. - obj = b'' + def timedelta_total_seconds(td): + # Shim for Python 2.6 compatibility, which doesn't have total_seconds. + # Make one argument a float to ensure the right calculation. + return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10.0**6) / 10.0**6 if setReferencePosition: self.referencePositions[obj] = len(output) @@ -668,9 +733,9 @@ else: output += pack('!B', 0b00001001) elif isinstance(obj, Uid): - size = self.intSize(obj) + size = self.intSize(obj.integer) output += pack('!B', (0b1000 << 4) | size - 1) - output += self.binaryInt(obj) + output += self.binaryInt(obj.integer) elif isinstance(obj, (int, long)): byteSize = self.intSize(obj) root = math.log(byteSize, 2) @@ -681,16 +746,18 @@ output += pack('!B', (0b0010 << 4) | 3) output += self.binaryReal(obj) elif isinstance(obj, datetime.datetime): - timestamp = (obj - apple_reference_date).total_seconds() + try: + timestamp = (obj - apple_reference_date).total_seconds() + except AttributeError: + timestamp = timedelta_total_seconds(obj - apple_reference_date) output += pack('!B', 0b00110011) output += pack('!d', float(timestamp)) elif isinstance(obj, Data): output += proc_variable_length(0b0100, len(obj)) output += obj - elif isinstance(obj, unicode): - byteData = obj.encode('utf_16_be') - output += proc_variable_length(0b0110, len(byteData)//2) - output += byteData + elif isinstance(obj, StringWrapper): + output += proc_variable_length(obj.encodingMarker, len(obj)) + output += obj.encodedValue elif isinstance(obj, bytes): output += proc_variable_length(0b0101, len(obj)) output += obj diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/biplist-0.9/biplist.egg-info/PKG-INFO new/biplist-1.0.1/biplist.egg-info/PKG-INFO --- old/biplist-0.9/biplist.egg-info/PKG-INFO 2014-10-26 20:09:20.000000000 +0100 +++ new/biplist-1.0.1/biplist.egg-info/PKG-INFO 2016-01-11 09:07:10.000000000 +0100 @@ -1,19 +1,19 @@ Metadata-Version: 1.1 Name: biplist -Version: 0.9 +Version: 1.0.1 Summary: biplist is a library for reading/writing binary plists. Home-page: https://bitbucket.org/wooster/biplist Author: Andrew Wooster Author-email: [email protected] License: BSD -Download-URL: https://bitbucket.org/wooster/biplist/downloads/biplist-0.9.tar.gz +Download-URL: https://bitbucket.org/wooster/biplist/downloads/biplist-1.0.1.tar.gz Description: `biplist` is a binary plist parser/generator for Python. Binary Property List (plist) files provide a faster and smaller serialization format for property lists on OS X. This is a library for generating binary plists which can be read by OS X, iOS, or other clients. - This module requires Python 2.6 or higher or Python 3.2 or higher. + This module requires Python 2.6 or higher or Python 3.4 or higher. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/biplist-0.9/setup.py new/biplist-1.0.1/setup.py --- old/biplist-0.9/setup.py 2014-10-26 20:04:50.000000000 +0100 +++ new/biplist-1.0.1/setup.py 2016-01-11 08:36:39.000000000 +0100 @@ -12,21 +12,21 @@ major, minor, micro, releaselevel, serial = sys.version_info -if major <= 1 or (major == 2 and minor < 6) or (major == 3 and minor < 2): +if major <= 1 or (major == 2 and minor < 6) or (major == 3 and minor < 4): # N.B.: Haven't tested with older py3k versions. - print('This module supports Python 2 >= 2.6 and Python 3 >= 3.2.') + print('This module supports Python 2 >= 2.6 and Python 3 >= 3.4.') sys.exit(1) author = 'Andrew Wooster' email = '[email protected]' -version = '0.9' +version = '1.0.1' desc = 'biplist is a library for reading/writing binary plists.' setup( name = 'biplist', version = version, url = 'https://bitbucket.org/wooster/biplist', - download_url = 'https://bitbucket.org/wooster/biplist/downloads/biplist-0.9.tar.gz', + download_url = 'https://bitbucket.org/wooster/biplist/downloads/biplist-%s.tar.gz' % version, license = 'BSD', description = desc, long_description = @@ -36,7 +36,7 @@ format for property lists on OS X. This is a library for generating binary plists which can be read by OS X, iOS, or other clients. -This module requires Python 2.6 or higher or Python 3.2 or higher.""", +This module requires Python 2.6 or higher or Python 3.4 or higher.""", author = author, author_email = email, packages = find_packages(), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/biplist-0.9/tests/test_valid.py new/biplist-1.0.1/tests/test_valid.py --- old/biplist-0.9/tests/test_valid.py 2014-10-26 20:03:33.000000000 +0100 +++ new/biplist-1.0.1/tests/test_valid.py 2016-01-10 08:04:45.000000000 +0100 @@ -19,19 +19,19 @@ def validateSimpleBinaryRoot(self, root): self.assertTrue(type(root) == dict, "Root should be dictionary.") - self.assertTrue(type(root[b'dateItem']) == datetime.datetime, "date should be datetime") - us = root[b'dateItem'].microsecond + self.assertTrue(type(root['dateItem']) == datetime.datetime, "date should be datetime") + us = root['dateItem'].microsecond if us == 385448: # Python 3 doesn't round microseconds to the nearest value. - self.assertEqual(root[b'dateItem'], datetime.datetime(2010, 8, 19, 22, 27, 30, 385448), "dates not equal" ) + self.assertEqual(root['dateItem'], datetime.datetime(2010, 8, 19, 22, 27, 30, 385448), "dates not equal" ) else: - self.assertEqual(root[b'dateItem'], datetime.datetime(2010, 8, 19, 22, 27, 30, 385449), "dates not equal" ) - self.assertEqual(root[b'numberItem'], -10000000000000000, "number not of expected value") - self.assertEqual(root[b'unicodeItem'], toUnicode('abc\u212cdef\u2133')) - self.assertEqual(root[b'stringItem'], b'Hi there') - self.assertEqual(root[b'realItem'], 0.47) - self.assertEqual(root[b'boolItem'], True) - self.assertEqual(root[b'arrayItem'], [b'item0']) + self.assertEqual(root['dateItem'], datetime.datetime(2010, 8, 19, 22, 27, 30, 385449), "dates not equal" ) + self.assertEqual(root['numberItem'], -10000000000000000, "number not of expected value") + self.assertEqual(root['unicodeItem'], toUnicode('abc\u212cdef\u2133')) + self.assertEqual(root['stringItem'], 'Hi there') + self.assertEqual(root['realItem'], 0.47) + self.assertEqual(root['boolItem'], True) + self.assertEqual(root['arrayItem'], ['item0']) def testFileRead(self): try: @@ -55,17 +55,17 @@ # 0b0101 (ASCII string), so the value being asserted against # appears to be what is wrong. result = readPlist(data_path('unicode_empty.plist')) - self.assertEqual(result, b'') + self.assertEqual(result, '') def testSmallReal(self): result = readPlist(data_path('small_real.plist')) - self.assertEqual(result, {b'4 byte real':0.5}) + self.assertEqual(result, {'4 byte real':0.5}) def testLargeIntegers(self): result = readPlist(data_path('large_int_limits.plist')) - self.assertEqual(result[b'Max 8 Byte Unsigned Integer'], 18446744073709551615) - self.assertEqual(result[b'Min 8 Byte Signed Integer'], -9223372036854775808) - self.assertEqual(result[b'Max 8 Byte Signed Integer'], 9223372036854775807) + self.assertEqual(result['Max 8 Byte Unsigned Integer'], 18446744073709551615) + self.assertEqual(result['Min 8 Byte Signed Integer'], -9223372036854775808) + self.assertEqual(result['Max 8 Byte Signed Integer'], 9223372036854775807) def testLargeDates(self): result = readPlist(data_path("BFPersistentEventInfo.plist")) @@ -86,15 +86,31 @@ ... """ result = readPlist(data_path('nskeyedarchiver_example.plist')) - self.assertEqual(result, {b'$version': 100000, - b'$objects': - [b'$null', - {b'$class': Uid(3), b'somekey': Uid(2)}, - b'object value as string', - {b'$classes': [b'Archived', b'NSObject'], b'$classname': b'Archived'} - ], - b'$top': {b'root': Uid(1)}, b'$archiver': b'NSKeyedArchiver'}) + self.assertEqual(result, { + '$version': 100000, + '$objects': + [ + '$null', + {'$class':Uid(3), 'somekey':Uid(2)}, + 'object value as string', + {'$classes':['Archived', 'NSObject'], '$classname':'Archived'} + ], + '$top': {'root':Uid(1)}, + '$archiver':'NSKeyedArchiver' + }) self.assertEqual("Uid(1)", repr(Uid(1))) + def testUidComparisons(self): + self.assertTrue(Uid(-2) < Uid(-1)) + self.assertTrue(Uid(-1) < Uid(0)) + self.assertTrue(Uid(1) > Uid(0)) + self.assertTrue(Uid(1) > Uid(-2)) + self.assertTrue(Uid(-1) == Uid(-1)) + self.assertTrue(Uid(0) == Uid(0)) + self.assertTrue(Uid(1) == Uid(1)) + + self.assertFalse(1 == Uid(1)) + self.assertFalse(Uid(0) == 0) + if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/biplist-0.9/tests/test_write.py new/biplist-1.0.1/tests/test_write.py --- old/biplist-0.9/tests/test_write.py 2014-10-26 20:03:11.000000000 +0100 +++ new/biplist-1.0.1/tests/test_write.py 2016-01-10 08:10:36.000000000 +0100 @@ -1,39 +1,82 @@ +#!/usr/local/env python +# -*- coding: utf-8 -*- + +import datetime, io, os, subprocess, sys, tempfile, unittest + from biplist import * from biplist import PlistWriter -import datetime -import io -import os -#from cStringIO import StringIO -import subprocess -import tempfile from test_utils import * -import unittest try: unicode + unicodeStr = lambda x: x.decode('utf-8') + toUnicode = lambda x: x.decode('unicode-escape') except NameError: unicode = str + unicodeStr = lambda x: x + toUnicode = lambda x: x +try: + xrange +except NameError: + xrange = range class TestWritePlist(unittest.TestCase): - def setUp(self): - pass - def roundTrip(self, root, xml=False, expected=None): - # 'expected' is more fallout from the - # don't-write-empty-unicode-strings issue. - plist = writePlistToString(root, binary=(not xml)) + def roundTrip(self, case, xml=False, expected=None, reprTest=True): + # reprTest may fail randomly if True and the values being encoded include a dictionary with more + # than one key. + + # convert to plist string + plist = writePlistToString(case, binary=(not xml)) self.assertTrue(len(plist) > 0) + + # confirm that lint is happy with the result + self.lintPlist(plist) + + # convert back readResult = readPlistFromString(plist) - self.assertEqual(readResult, (expected if expected is not None else root)) - self.lintPlist(plist) - - def lintPlist(self, plistString): - if os.path.exists('/usr/bin/plutil'): - f = tempfile.NamedTemporaryFile() - f.write(plistString) - f.flush() - name = f.name - (status, output) = run_command(['/usr/bin/plutil', '-lint', name]) + + # test equality + if reprTest is True: + self.assertEqual(repr(case if expected is None else expected), repr(readResult)) + else: + self.assertEqual((case if expected is None else expected), readResult) + + # write to file + plistFile = tempfile.NamedTemporaryFile(mode='wb+', suffix='.plist') + writePlist(case, plistFile, binary=(xml is False)) + plistFile.seek(0) + + # confirm that lint is happy with the result + self.lintPlist(plistFile) + + # read back from file + fileResult = readPlist(plistFile) + + # test equality + if reprTest is True: + self.assertEqual(repr(case if expected is None else expected), repr(fileResult)) + else: + self.assertEqual((case if expected is None else expected), fileResult) + + def lintPlist(self, plist): + if os.access('/usr/bin/plutil', os.X_OK): + plistFile = None + plistFilePath = None + + if hasattr(plist, 'name'): + plistFilePath = plist.name + else: + if hasattr(plist, 'read'): + plistFile = tempfile.NamedTemporaryFile('w%s' % ('b' if 'b' in plist.mode else '')) + plistFile.write(plist.read()) + else: + plistFile = tempfile.NamedTemporaryFile('w%s' % ('b' if isinstance(plist, bytes) else '')) + plistFile.write(plist) + plistFilePath = plistFile.name + plistFile.flush() + + status, output = run_command(['/usr/bin/plutil', '-lint', plistFilePath]) if status != 0: self.fail("plutil verification failed (status %d): %s" % (status, output)) @@ -58,36 +101,32 @@ self.roundTrip(False) def testDuplicate(self): - l = ["foo" for i in range(0, 100)] + l = ["foo" for i in xrange(0, 100)] self.roundTrip(l) def testListRoot(self): self.roundTrip([1, 2, 3]) def testDictRoot(self): - self.roundTrip({'a':1, 'B':'d'}) + self.roundTrip({'a':1, 'B':'d'}, reprTest=False) def mixedNumericTypesHelper(self, cases): result = readPlistFromString(writePlistToString(cases)) - for i in range(0, len(cases)): + for i in xrange(0, len(cases)): self.assertTrue(cases[i] == result[i]) self.assertEqual(type(cases[i]), type(result[i]), "Type mismatch on %d: %s != %s" % (i, repr(cases[i]), repr(result[i]))) - def reprChecker(self, case): - result = readPlistFromString(writePlistToString(case)) - self.assertEqual(repr(case), repr(result)) - def testBoolsAndIntegersMixed(self): self.mixedNumericTypesHelper([0, 1, True, False, None]) self.mixedNumericTypesHelper([False, True, 0, 1, None]) - self.reprChecker({unicode('1'):[True, False, 1, 0], unicode('0'):[1, 2, 0, {unicode('2'):[1, 0, False]}]}) - self.reprChecker([1, 1, 1, 1, 1, True, True, True, True]) + self.roundTrip({'1':[True, False, 1, 0], '0':[1, 2, 0, {'2':[1, 0, False]}]}, reprTest=False) + self.roundTrip([1, 1, 1, 1, 1, True, True, True, True]) def testFloatsAndIntegersMixed(self): self.mixedNumericTypesHelper([0, 1, 1.0, 0.0, None]) self.mixedNumericTypesHelper([0.0, 1.0, 0, 1, None]) - self.reprChecker({unicode('1'):[1.0, 0.0, 1, 0], unicode('0'):[1, 2, 0, {unicode('2'):[1, 0, 0.0]}]}) - self.reprChecker([1, 1, 1, 1, 1, 1.0, 1.0, 1.0, 1.0]) + self.roundTrip({'1':[1.0, 0.0, 1, 0], '0':[1, 2, 0, {'2':[1, 0, 0.0]}]}, reprTest=False) + self.roundTrip([1, 1, 1, 1, 1, 1.0, 1.0, 1.0, 1.0]) def testSetRoot(self): self.roundTrip(set((1, 2, 3))) @@ -112,36 +151,102 @@ self.lintPlist(writePlistToString(root)) self.roundTrip(root) - def testString(self): + def testBytes(self): self.roundTrip(b'0') self.roundTrip(b'') - self.roundTrip({b'a':b''}) + + self.roundTrip([b'0']) + self.roundTrip([b'']) + + self.roundTrip({'a': b'0'}) + self.roundTrip({'a': b''}) - def testLargeDict(self): - d = {} - for i in range(0, 1000): - d['%d' % i] = '%d' % i - self.roundTrip(d) + def testString(self): + self.roundTrip('') + self.roundTrip('a') + self.roundTrip('1') + + self.roundTrip(['']) + self.roundTrip(['a']) + self.roundTrip(['1']) + + self.roundTrip({'a':''}) + self.roundTrip({'a':'a'}) + self.roundTrip({'1':'a'}) + self.roundTrip({'a':'a'}) + self.roundTrip({'a':'1'}) + + def testUnicode(self): + # defaulting to 1 byte strings + if str != unicode: + self.roundTrip(unicodeStr(r''), expected='') + self.roundTrip(unicodeStr(r'a'), expected='a') + + self.roundTrip([unicodeStr(r'a')], expected=['a']) + + self.roundTrip({'a':unicodeStr(r'a')}, expected={'a':'a'}) + self.roundTrip({unicodeStr(r'a'):'a'}, expected={'a':'a'}) + self.roundTrip({unicodeStr(r''):unicodeStr(r'')}, expected={'':''}) + + # TODO: need a 4-byte unicode character + self.roundTrip(unicodeStr(r'ü')) + self.roundTrip([unicodeStr(r'ü')]) + self.roundTrip({'a':unicodeStr(r'ü')}) + self.roundTrip({unicodeStr(r'ü'):'a'}) + + self.roundTrip(toUnicode('\u00b6')) + self.roundTrip([toUnicode('\u00b6')]) + self.roundTrip({toUnicode('\u00b6'):toUnicode('\u00b6')}) + + self.roundTrip(toUnicode('\u1D161')) + self.roundTrip([toUnicode('\u1D161')]) + self.roundTrip({toUnicode('\u1D161'):toUnicode('\u1D161')}) + + # Smiley face emoji + self.roundTrip(toUnicode('\U0001f604')) + self.roundTrip([toUnicode('\U0001f604'), toUnicode('\U0001f604')]) + self.roundTrip({toUnicode('\U0001f604'):toUnicode('\U0001f604')}) + + def testNone(self): + self.roundTrip(None) + self.roundTrip({'1':None}) + self.roundTrip([None, None, None]) + def testBools(self): + self.roundTrip(True) + self.roundTrip(False) + self.roundTrip([True, False]) + + self.roundTrip({'a':True, 'b':False}, reprTest=False) def testUniques(self): root = {'hi':'there', 'halloo':'there'} - self.roundTrip(root) + self.roundTrip(root, reprTest=False) + + def testAllEmpties(self): + '''Primarily testint that an empty unicode and bytes are not mixed up''' + self.roundTrip([unicodeStr(''), '', b'', [], {}], expected=['', '', b'', [], {}]) + def testLargeDict(self): + d = dict((str(x), str(x)) for x in xrange(0, 1000)) + self.roundTrip(d, reprTest=False) + def testWriteToFile(self): for is_binary in [True, False]: - path = '/var/tmp/test.plist' - writePlist([1, 2, 3], path, binary=is_binary) - self.assertTrue(os.path.exists(path)) - with open(path, 'rb') as f: - self.lintPlist(f.read()) - - def testNone(self): - self.roundTrip(None) - self.roundTrip({'1':None}) - self.roundTrip([None, None, None]) + with tempfile.NamedTemporaryFile(mode='w%s' % ('b' if is_binary else ''), suffix='.plist') as plistFile: + # clear out the created file + os.unlink(plistFile.name) + self.assertFalse(os.path.exists(plistFile.name)) + + # write to disk + writePlist([1, 2, 3], plistFile.name, binary=is_binary) + self.assertTrue(os.path.exists(plistFile.name)) + + with open(plistFile.name, 'r%s' % ('b' if is_binary else '')) as f: + fileContents = f.read() + self.lintPlist(fileContents) def testBadKeys(self): try: @@ -169,7 +274,7 @@ -pow(2, 15), pow(2, 15) - 1, -pow(2, 31), pow(2, 31) - 1, -pow(2, 63), pow(2, 64) - 1] - self.roundTrip(edges) + self.roundTrip(edges, reprTest=False) ioBytes = io.BytesIO() writer = PlistWriter(ioBytes) @@ -185,7 +290,7 @@ self.assertEqual(bytelen, got, "Byte size is wrong. Expected %d, got %d" % (bytelen, got)) bytes_lists = [list(x) for x in bytes] - self.roundTrip(bytes_lists) + self.roundTrip(bytes_lists, reprTest=False) try: self.roundTrip([0x10000000000000000, pow(2, 64)]) @@ -193,17 +298,23 @@ except InvalidPlistException as e: pass - def testWriteData(self): - self.roundTrip(Data(b"woohoo")) - - def testUnicode(self): - unicodeRoot = unicode("Mirror's Edge\u2122 for iPad") - writePlist(unicodeRoot, "/tmp/odd.plist") + def testUnicode2(self): + unicodeRoot = toUnicode("Mirror's Edge\u2122 for iPad") self.roundTrip(unicodeRoot) - unicodeStrings = [unicode("Mirror's Edge\u2122 for iPad"), unicode('Weightbot \u2014 Track your Weight in Style')] + unicodeStrings = [toUnicode("Mirror's Edge\u2122 for iPad"), toUnicode('Weightbot \u2014 Track your Weight in Style')] self.roundTrip(unicodeStrings) - self.roundTrip({unicode(""):unicode("")}, expected={b'':b''}) - self.roundTrip(unicode(""), expected=b'') + self.roundTrip({toUnicode(""):toUnicode("")}, expected={'':''}) + self.roundTrip(toUnicode(""), expected='') + + def testWriteData(self): + self.roundTrip(Data(b"woohoo")) + + def testEmptyData(self): + data = Data(b'') + binplist = writePlistToString(data) + plist = readPlistFromString(binplist) + self.assertEqual(plist, data) + self.assertEqual(type(plist), type(data)) def testUidWrite(self): self.roundTrip({'$version': 100000, @@ -213,7 +324,14 @@ 'object value as string', {'$classes': ['Archived', 'NSObject'], '$classname': 'Archived'} ], - '$top': {'root': Uid(1)}, '$archiver': 'NSKeyedArchiver'}) + '$top': {'root': Uid(1)}, '$archiver': 'NSKeyedArchiver'}, reprTest=False) + + def testUidRoundTrip(self): + # Per https://github.com/wooster/biplist/issues/9 + self.roundTrip(Uid(1)) + self.roundTrip([Uid(1), 1]) + self.roundTrip([1, Uid(1)]) + self.roundTrip([Uid(1), Uid(1)]) if __name__ == '__main__': unittest.main()
