diff --git a/mercurial/thirdparty/cbor/.travis.yml b/mercurial/thirdparty/cbor/.travis.yml new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/.travis.yml @@ -0,0 +1,58 @@ +language: python +sudo: false + +stages: + - name: test + - name: deploy to pypi + if: type = push AND tag =~ ^\d+\.\d+\.\d+ + +jobs: + fast_finish: true + include: + - env: TOXENV=flake8 + + - env: TOXENV=pypy + python: pypy3 + + - env: TOXENV=pypy3 + python: pypy3 + + - env: TOXENV=py27 + python: "2.7" + after_success: &after_success + - pip install coveralls + - coveralls + + - env: TOXENV=py33 + python: "3.3" + after_success: *after_success + + - env: TOXENV=py34 + python: "3.4" + after_success: *after_success + + - env: TOXENV=py35 + python: "3.5" + after_success: *after_success + + - env: TOXENV=py36 + python: "3.6" + after_success: *after_success + + - stage: deploy to pypi + install: pip install "setuptools >= 36.2.7" + script: skip + deploy: + provider: pypi + user: agronholm + password: + secure: QZ5qoxsrzns/b27adWNzh/OAJp86yRuxTyAFhvas/pbkiALdlT/+PGyhJBnpe+7WBTWnkIXl+YU//voJ0btf6DJcWwgRavMsy22LJJPkvvK+2DHiZ//DbpLbqKWc74y4moce29BCajFTm9JkVwcL2dgN9WuZt+Tay0efcP4sESLxo5lIGdlaQbu+9zVs61Z4Ov+yyEMO/j3LeKshNmUq+84CveQWMiXndXBfJX5TWwjahmUNDp5fMctJxr4fqgL4HCTVQhU79dPc00yDEGS45QkpP8JDrF1DQvU5Ht4COz/Lvzt11pwsAvws2ddclqBUCQsGaWvEWH5rxZTYx/MaMVdTctaUVNoT0wnFUsXXZkomQV0x8vb5RtRLDrKwXosXlSEqnRyiKhdgHGoswHvB7XF5BtQ5RmydRX77pwEGmFd3lqRif2bos0MEeOJA8Xds0TGOKO4PyokBnj/a0tjT2LEVxObmTT6grz5QPXi386AWgxbNl0Lp7cnkSpCqC1hEHVqrDlbtu7uvfGwwe/sYlEcQ07PNCvFoR2GXJawbeHmJRfz+KXjffrt2yCzc671FL1goUysHKdBCppvUInI8FCMQpVWEh5MmQJKB4IpDrhqfo0VS+NNZgZ8lFStq27Pmwqf1HUTGlaDi9VQ0Vo7tW5j4JbD/JvOQSb3j9DjUFps= + distributions: sdist bdist_wheel + on: + tags: true + +install: + - pip install "setuptools >= 36.2.7" + - pip install tox + +script: tox diff --git a/mercurial/thirdparty/cbor/LICENSE.txt b/mercurial/thirdparty/cbor/LICENSE.txt new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/LICENSE.txt @@ -0,0 +1,19 @@ +This is the MIT license: http://www.opensource.org/licenses/mit-license.php + +Copyright (c) Alex Grönholm + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/mercurial/thirdparty/cbor/README.rst b/mercurial/thirdparty/cbor/README.rst new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/README.rst @@ -0,0 +1,24 @@ +.. image:: https://travis-ci.org/agronholm/cbor2.svg?branch=master + :target: https://travis-ci.org/agronholm/cbor2 + :alt: Build Status +.. image:: https://coveralls.io/repos/github/agronholm/cbor2/badge.svg?branch=master + :target: https://coveralls.io/github/agronholm/cbor2?branch=master + :alt: Code Coverage + +This library provides encoding and decoding for the Concise Binary Object Representation (CBOR) +(`RFC 7049`_) serialization format. + +There exists another Python CBOR implementation (cbor) which is faster on CPython due to its C +extensions. On PyPy, cbor2 and cbor are almost identical in performance. The other implementation +also lacks documentation and a comprehensive test suite, does not support most standard extension +tags and is known to crash (segfault) when passed a cyclic structure (say, a list containing +itself). + +.. _RFC 7049: https://tools.ietf.org/html/rfc7049 + +Project links +------------- + +* `Documentation `_ +* `Source code `_ +* `Issue tracker `_ diff --git a/mercurial/thirdparty/cbor/cbor2/__init__.py b/mercurial/thirdparty/cbor/cbor2/__init__.py new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/cbor2/__init__.py @@ -0,0 +1,3 @@ +from cbor2.decoder import load, loads, CBORDecoder, CBORDecodeError # noqa +from cbor2.encoder import dump, dumps, CBOREncoder, CBOREncodeError, shareable_encoder # noqa +from cbor2.types import CBORTag, CBORSimpleValue, undefined # noqa diff --git a/mercurial/thirdparty/cbor/cbor2/compat.py b/mercurial/thirdparty/cbor/cbor2/compat.py new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/cbor2/compat.py @@ -0,0 +1,101 @@ +from math import ldexp +import struct +import sys + + +if sys.version_info.major < 3: + from datetime import tzinfo, timedelta + + class timezone(tzinfo): + def __init__(self, offset): + self.offset = offset + + def utcoffset(self, dt): + return self.offset + + def dst(self, dt): + return timedelta(0) + + def tzname(self, dt): + return 'UTC+00:00' + + def as_unicode(string): + return string.decode('utf-8') + + def iteritems(self): + return self.iteritems() + + def bytes_from_list(values): + return bytes(bytearray(values)) + + byte_as_integer = ord + timezone.utc = timezone(timedelta(0)) + xrange = xrange # noqa: F821 + long = long # noqa: F821 + unicode = unicode # noqa: F821 +else: + from datetime import timezone + + def byte_as_integer(bytestr): + return bytestr[0] + + def as_unicode(string): + return string + + def iteritems(self): + return self.items() + + xrange = range + long = int + unicode = str + bytes_from_list = bytes + + +if sys.version_info.major >= 3 and sys.version_info.minor >= 6: + # Python 3.6 added 16 bit floating point to struct + + def pack_float16(value): + try: + return struct.pack('>Be', 0xf9, value) + except OverflowError: + return False + + def unpack_float16(payload): + return struct.unpack('>e', payload)[0] +else: + def pack_float16(value): + # Based on node-cbor by hildjj + # which was based in turn on Carsten Borman's cn-cbor + u32 = struct.pack('>f', value) + u = struct.unpack('>I', u32)[0] + + if u & 0x1FFF != 0: + return False + + s16 = (u >> 16) & 0x8000 + exponent = (u >> 23) & 0xff + mantissa = u & 0x7fffff + + if 113 <= exponent <= 142: + s16 += ((exponent - 112) << 10) + (mantissa >> 13) + elif 103 <= exponent < 113: + if mantissa & ((1 << (126 - exponent)) - 1): + return False + + s16 += ((mantissa + 0x800000) >> (126 - exponent)) + else: + return False + + return struct.pack('>BH', 0xf9, s16) + + def unpack_float16(payload): + # Code adapted from RFC 7049, appendix D + def decode_single(single): + return struct.unpack("!f", struct.pack("!I", single))[0] + + payload = struct.unpack('>H', payload)[0] + value = (payload & 0x7fff) << 13 | (payload & 0x8000) << 16 + if payload & 0x7c00 != 0x7c00: + return ldexp(decode_single(value), 112) + + return decode_single(value | 0x7f800000) diff --git a/mercurial/thirdparty/cbor/cbor2/decoder.py b/mercurial/thirdparty/cbor/cbor2/decoder.py new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/cbor2/decoder.py @@ -0,0 +1,407 @@ +import re +import struct +from datetime import datetime, timedelta +from io import BytesIO + +from cbor2.compat import timezone, xrange, byte_as_integer, unpack_float16 +from cbor2.types import CBORTag, undefined, break_marker, CBORSimpleValue + +timestamp_re = re.compile(r'^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)' + r'(?:\.(\d+))?(?:Z|([+-]\d\d):(\d\d))$') + + +class CBORDecodeError(Exception): + """Raised when an error occurs deserializing a CBOR datastream.""" + + +def decode_uint(decoder, subtype, shareable_index=None, allow_indefinite=False): + # Major tag 0 + if subtype < 24: + return subtype + elif subtype == 24: + return struct.unpack('>B', decoder.read(1))[0] + elif subtype == 25: + return struct.unpack('>H', decoder.read(2))[0] + elif subtype == 26: + return struct.unpack('>L', decoder.read(4))[0] + elif subtype == 27: + return struct.unpack('>Q', decoder.read(8))[0] + elif subtype == 31 and allow_indefinite: + return None + else: + raise CBORDecodeError('unknown unsigned integer subtype 0x%x' % subtype) + + +def decode_negint(decoder, subtype, shareable_index=None): + # Major tag 1 + uint = decode_uint(decoder, subtype) + return -uint - 1 + + +def decode_bytestring(decoder, subtype, shareable_index=None): + # Major tag 2 + length = decode_uint(decoder, subtype, allow_indefinite=True) + if length is None: + # Indefinite length + buf = bytearray() + while True: + initial_byte = byte_as_integer(decoder.read(1)) + if initial_byte == 255: + return buf + else: + length = decode_uint(decoder, initial_byte & 31) + value = decoder.read(length) + buf.extend(value) + else: + return decoder.read(length) + + +def decode_string(decoder, subtype, shareable_index=None): + # Major tag 3 + return decode_bytestring(decoder, subtype).decode('utf-8') + + +def decode_array(decoder, subtype, shareable_index=None): + # Major tag 4 + items = [] + decoder.set_shareable(shareable_index, items) + length = decode_uint(decoder, subtype, allow_indefinite=True) + if length is None: + # Indefinite length + while True: + value = decoder.decode() + if value is break_marker: + break + else: + items.append(value) + else: + for _ in xrange(length): + item = decoder.decode() + items.append(item) + + return items + + +def decode_map(decoder, subtype, shareable_index=None): + # Major tag 5 + dictionary = {} + decoder.set_shareable(shareable_index, dictionary) + length = decode_uint(decoder, subtype, allow_indefinite=True) + if length is None: + # Indefinite length + while True: + key = decoder.decode() + if key is break_marker: + break + else: + value = decoder.decode() + dictionary[key] = value + else: + for _ in xrange(length): + key = decoder.decode() + value = decoder.decode() + dictionary[key] = value + + if decoder.object_hook: + return decoder.object_hook(decoder, dictionary) + else: + return dictionary + + +def decode_semantic(decoder, subtype, shareable_index=None): + # Major tag 6 + tagnum = decode_uint(decoder, subtype) + + # Special handling for the "shareable" tag + if tagnum == 28: + shareable_index = decoder._allocate_shareable() + return decoder.decode(shareable_index) + + value = decoder.decode() + semantic_decoder = semantic_decoders.get(tagnum) + if semantic_decoder: + return semantic_decoder(decoder, value, shareable_index) + + tag = CBORTag(tagnum, value) + if decoder.tag_hook: + return decoder.tag_hook(decoder, tag, shareable_index) + else: + return tag + + +def decode_special(decoder, subtype, shareable_index=None): + # Simple value + if subtype < 20: + return CBORSimpleValue(subtype) + + # Major tag 7 + return special_decoders[subtype](decoder) + + +# +# Semantic decoders (major tag 6) +# + +def decode_datetime_string(decoder, value, shareable_index=None): + # Semantic tag 0 + match = timestamp_re.match(value) + if match: + year, month, day, hour, minute, second, micro, offset_h, offset_m = match.groups() + if offset_h: + tz = timezone(timedelta(hours=int(offset_h), minutes=int(offset_m))) + else: + tz = timezone.utc + + return datetime(int(year), int(month), int(day), int(hour), int(minute), int(second), + int(micro or 0), tz) + else: + raise CBORDecodeError('invalid datetime string: {}'.format(value)) + + +def decode_epoch_datetime(decoder, value, shareable_index=None): + # Semantic tag 1 + return datetime.fromtimestamp(value, timezone.utc) + + +def decode_positive_bignum(decoder, value, shareable_index=None): + # Semantic tag 2 + from binascii import hexlify + return int(hexlify(value), 16) + + +def decode_negative_bignum(decoder, value, shareable_index=None): + # Semantic tag 3 + return -decode_positive_bignum(decoder, value) - 1 + + +def decode_fraction(decoder, value, shareable_index=None): + # Semantic tag 4 + from decimal import Decimal + exp = Decimal(value[0]) + mantissa = Decimal(value[1]) + return mantissa * (10 ** exp) + + +def decode_bigfloat(decoder, value, shareable_index=None): + # Semantic tag 5 + from decimal import Decimal + exp = Decimal(value[0]) + mantissa = Decimal(value[1]) + return mantissa * (2 ** exp) + + +def decode_sharedref(decoder, value, shareable_index=None): + # Semantic tag 29 + try: + shared = decoder._shareables[value] + except IndexError: + raise CBORDecodeError('shared reference %d not found' % value) + + if shared is None: + raise CBORDecodeError('shared value %d has not been initialized' % value) + else: + return shared + + +def decode_rational(decoder, value, shareable_index=None): + # Semantic tag 30 + from fractions import Fraction + return Fraction(*value) + + +def decode_regexp(decoder, value, shareable_index=None): + # Semantic tag 35 + return re.compile(value) + + +def decode_mime(decoder, value, shareable_index=None): + # Semantic tag 36 + from email.parser import Parser + return Parser().parsestr(value) + + +def decode_uuid(decoder, value, shareable_index=None): + # Semantic tag 37 + from uuid import UUID + return UUID(bytes=value) + + +def decode_set(decoder, value, shareable_index=None): + # Semantic tag 258 + return set(value) + + +# +# Special decoders (major tag 7) +# + +def decode_simple_value(decoder, shareable_index=None): + return CBORSimpleValue(struct.unpack('>B', decoder.read(1))[0]) + + +def decode_float16(decoder, shareable_index=None): + payload = decoder.read(2) + return unpack_float16(payload) + + +def decode_float32(decoder, shareable_index=None): + return struct.unpack('>f', decoder.read(4))[0] + + +def decode_float64(decoder, shareable_index=None): + return struct.unpack('>d', decoder.read(8))[0] + + +major_decoders = { + 0: decode_uint, + 1: decode_negint, + 2: decode_bytestring, + 3: decode_string, + 4: decode_array, + 5: decode_map, + 6: decode_semantic, + 7: decode_special +} + +special_decoders = { + 20: lambda self: False, + 21: lambda self: True, + 22: lambda self: None, + 23: lambda self: undefined, + 24: decode_simple_value, + 25: decode_float16, + 26: decode_float32, + 27: decode_float64, + 31: lambda self: break_marker +} + +semantic_decoders = { + 0: decode_datetime_string, + 1: decode_epoch_datetime, + 2: decode_positive_bignum, + 3: decode_negative_bignum, + 4: decode_fraction, + 5: decode_bigfloat, + 29: decode_sharedref, + 30: decode_rational, + 35: decode_regexp, + 36: decode_mime, + 37: decode_uuid, + 258: decode_set +} + + +class CBORDecoder(object): + """ + Deserializes a CBOR encoded byte stream. + + :param tag_hook: Callable that takes 3 arguments: the decoder instance, the + :class:`~cbor2.types.CBORTag` and the shareable index for the resulting object, if any. + This callback is called for any tags for which there is no built-in decoder. + The return value is substituted for the CBORTag object in the deserialized output. + :param object_hook: Callable that takes 2 arguments: the decoder instance and the dictionary. + This callback is called for each deserialized :class:`dict` object. + The return value is substituted for the dict in the deserialized output. + """ + + __slots__ = ('fp', 'tag_hook', 'object_hook', '_shareables') + + def __init__(self, fp, tag_hook=None, object_hook=None): + self.fp = fp + self.tag_hook = tag_hook + self.object_hook = object_hook + self._shareables = [] + + def _allocate_shareable(self): + self._shareables.append(None) + return len(self._shareables) - 1 + + def set_shareable(self, index, value): + """ + Set the shareable value for the last encountered shared value marker, if any. + + If the given index is ``None``, nothing is done. + + :param index: the value of the ``shared_index`` argument to the decoder + :param value: the shared value + + """ + if index is not None: + self._shareables[index] = value + + def read(self, amount): + """ + Read bytes from the data stream. + + :param int amount: the number of bytes to read + + """ + data = self.fp.read(amount) + if len(data) < amount: + raise CBORDecodeError('premature end of stream (expected to read {} bytes, got {} ' + 'instead)'.format(amount, len(data))) + + return data + + def decode(self, shareable_index=None): + """ + Decode the next value from the stream. + + :raises CBORDecodeError: if there is any problem decoding the stream + + """ + try: + initial_byte = byte_as_integer(self.fp.read(1)) + major_type = initial_byte >> 5 + subtype = initial_byte & 31 + except Exception as e: + raise CBORDecodeError('error reading major type at index {}: {}' + .format(self.fp.tell(), e)) + + decoder = major_decoders[major_type] + try: + return decoder(self, subtype, shareable_index) + except CBORDecodeError: + raise + except Exception as e: + raise CBORDecodeError('error decoding value at index {}: {}'.format(self.fp.tell(), e)) + + def decode_from_bytes(self, buf): + """ + Wrap the given bytestring as a file and call :meth:`decode` with it as the argument. + + This method was intended to be used from the ``tag_hook`` hook when an object needs to be + decoded separately from the rest but while still taking advantage of the shared value + registry. + + """ + old_fp = self.fp + self.fp = BytesIO(buf) + retval = self.decode() + self.fp = old_fp + return retval + + +def loads(payload, **kwargs): + """ + Deserialize an object from a bytestring. + + :param bytes payload: the bytestring to serialize + :param kwargs: keyword arguments passed to :class:`~.CBORDecoder` + :return: the deserialized object + + """ + fp = BytesIO(payload) + return CBORDecoder(fp, **kwargs).decode() + + +def load(fp, **kwargs): + """ + Deserialize an object from an open file. + + :param fp: the input file (any file-like object) + :param kwargs: keyword arguments passed to :class:`~.CBORDecoder` + :return: the deserialized object + + """ + return CBORDecoder(fp, **kwargs).decode() diff --git a/mercurial/thirdparty/cbor/cbor2/encoder.py b/mercurial/thirdparty/cbor/cbor2/encoder.py new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/cbor2/encoder.py @@ -0,0 +1,425 @@ +import re +import struct +from collections import OrderedDict, defaultdict +from contextlib import contextmanager +from functools import wraps +from datetime import datetime, date, time +from io import BytesIO + +from cbor2.compat import iteritems, timezone, long, unicode, as_unicode, bytes_from_list +from cbor2.compat import pack_float16, unpack_float16 +from cbor2.types import CBORTag, undefined, CBORSimpleValue + + +class CBOREncodeError(Exception): + """Raised when an error occurs while serializing an object into a CBOR datastream.""" + + +def shareable_encoder(func): + """ + Wrap the given encoder function to gracefully handle cyclic data structures. + + If value sharing is enabled, this marks the given value shared in the datastream on the + first call. If the value has already been passed to this method, a reference marker is + instead written to the data stream and the wrapped function is not called. + + If value sharing is disabled, only infinite recursion protection is done. + + """ + @wraps(func) + def wrapper(encoder, value, *args, **kwargs): + value_id = id(value) + container, container_index = encoder._shared_containers.get(value_id, (None, None)) + if encoder.value_sharing: + if container is value: + # Generate a reference to the previous index instead of encoding this again + encoder.write(encode_length(0xd8, 0x1d)) + encode_int(encoder, container_index) + else: + # Mark the container as shareable + encoder._shared_containers[value_id] = (value, len(encoder._shared_containers)) + encoder.write(encode_length(0xd8, 0x1c)) + func(encoder, value, *args, **kwargs) + else: + if container is value: + raise CBOREncodeError('cyclic data structure detected but value sharing is ' + 'disabled') + else: + encoder._shared_containers[value_id] = (value, None) + func(encoder, value, *args, **kwargs) + del encoder._shared_containers[value_id] + + return wrapper + + +def encode_length(major_tag, length): + if length < 24: + return struct.pack('>B', major_tag | length) + elif length < 256: + return struct.pack('>BB', major_tag | 24, length) + elif length < 65536: + return struct.pack('>BH', major_tag | 25, length) + elif length < 4294967296: + return struct.pack('>BL', major_tag | 26, length) + else: + return struct.pack('>BQ', major_tag | 27, length) + + +def encode_int(encoder, value): + # Big integers (2 ** 64 and over) + if value >= 18446744073709551616 or value < -18446744073709551616: + if value >= 0: + major_type = 0x02 + else: + major_type = 0x03 + value = -value - 1 + + values = [] + while value > 0: + value, remainder = divmod(value, 256) + values.insert(0, remainder) + + payload = bytes_from_list(values) + encode_semantic(encoder, CBORTag(major_type, payload)) + elif value >= 0: + encoder.write(encode_length(0, value)) + else: + encoder.write(encode_length(0x20, abs(value) - 1)) + + +def encode_bytestring(encoder, value): + encoder.write(encode_length(0x40, len(value)) + value) + + +def encode_bytearray(encoder, value): + encode_bytestring(encoder, bytes(value)) + + +def encode_string(encoder, value): + encoded = value.encode('utf-8') + encoder.write(encode_length(0x60, len(encoded)) + encoded) + + +@shareable_encoder +def encode_array(encoder, value): + encoder.write(encode_length(0x80, len(value))) + for item in value: + encoder.encode(item) + + +@shareable_encoder +def encode_map(encoder, value): + encoder.write(encode_length(0xa0, len(value))) + for key, val in iteritems(value): + encoder.encode(key) + encoder.encode(val) + + +def encode_sortable_key(encoder, value): + """Takes a key and calculates the length of its optimal byte representation""" + encoded = encoder.encode_to_bytes(value) + return len(encoded), encoded + + +@shareable_encoder +def encode_canonical_map(encoder, value): + """Reorder keys according to Canonical CBOR specification""" + keyed_keys = ((encode_sortable_key(encoder, key), key) for key in value.keys()) + encoder.write(encode_length(0xa0, len(value))) + for sortkey, realkey in sorted(keyed_keys): + encoder.write(sortkey[1]) + encoder.encode(value[realkey]) + + +def encode_semantic(encoder, value): + encoder.write(encode_length(0xc0, value.tag)) + encoder.encode(value.value) + + +# +# Semantic decoders (major tag 6) +# + +def encode_datetime(encoder, value): + # Semantic tag 0 + if not value.tzinfo: + if encoder.timezone: + value = value.replace(tzinfo=encoder.timezone) + else: + raise CBOREncodeError( + 'naive datetime encountered and no default timezone has been set') + + if encoder.datetime_as_timestamp: + from calendar import timegm + timestamp = timegm(value.utctimetuple()) + value.microsecond // 1000000 + encode_semantic(encoder, CBORTag(1, timestamp)) + else: + datestring = as_unicode(value.isoformat().replace('+00:00', 'Z')) + encode_semantic(encoder, CBORTag(0, datestring)) + + +def encode_date(encoder, value): + value = datetime.combine(value, time()).replace(tzinfo=timezone.utc) + encode_datetime(encoder, value) + + +def encode_decimal(encoder, value): + # Semantic tag 4 + if value.is_nan(): + encoder.write(b'\xf9\x7e\x00') + elif value.is_infinite(): + encoder.write(b'\xf9\x7c\x00' if value > 0 else b'\xf9\xfc\x00') + else: + dt = value.as_tuple() + mantissa = sum(d * 10 ** i for i, d in enumerate(reversed(dt.digits))) + with encoder.disable_value_sharing(): + encode_semantic(encoder, CBORTag(4, [dt.exponent, mantissa])) + + +def encode_rational(encoder, value): + # Semantic tag 30 + with encoder.disable_value_sharing(): + encode_semantic(encoder, CBORTag(30, [value.numerator, value.denominator])) + + +def encode_regexp(encoder, value): + # Semantic tag 35 + encode_semantic(encoder, CBORTag(35, as_unicode(value.pattern))) + + +def encode_mime(encoder, value): + # Semantic tag 36 + encode_semantic(encoder, CBORTag(36, as_unicode(value.as_string()))) + + +def encode_uuid(encoder, value): + # Semantic tag 37 + encode_semantic(encoder, CBORTag(37, value.bytes)) + + +def encode_set(encoder, value): + # Semantic tag 258 + encode_semantic(encoder, CBORTag(258, tuple(value))) + + +def encode_canonical_set(encoder, value): + # Semantic tag 258 + values = sorted([(encode_sortable_key(encoder, key), key) for key in value]) + encode_semantic(encoder, CBORTag(258, [key[1] for key in values])) + + +# +# Special encoders (major tag 7) +# + +def encode_simple_value(encoder, value): + if value.value < 20: + encoder.write(struct.pack('>B', 0xe0 | value.value)) + else: + encoder.write(struct.pack('>BB', 0xf8, value.value)) + + +def encode_float(encoder, value): + # Handle special values efficiently + import math + if math.isnan(value): + encoder.write(b'\xf9\x7e\x00') + elif math.isinf(value): + encoder.write(b'\xf9\x7c\x00' if value > 0 else b'\xf9\xfc\x00') + else: + encoder.write(struct.pack('>Bd', 0xfb, value)) + + +def encode_minimal_float(encoder, value): + # Handle special values efficiently + import math + if math.isnan(value): + encoder.write(b'\xf9\x7e\x00') + elif math.isinf(value): + encoder.write(b'\xf9\x7c\x00' if value > 0 else b'\xf9\xfc\x00') + else: + encoded = struct.pack('>Bf', 0xfa, value) + if struct.unpack('>Bf', encoded)[1] != value: + encoded = struct.pack('>Bd', 0xfb, value) + encoder.write(encoded) + else: + f16 = pack_float16(value) + if f16 and unpack_float16(f16[1:]) == value: + encoder.write(f16) + else: + encoder.write(encoded) + + +def encode_boolean(encoder, value): + encoder.write(b'\xf5' if value else b'\xf4') + + +def encode_none(encoder, value): + encoder.write(b'\xf6') + + +def encode_undefined(encoder, value): + encoder.write(b'\xf7') + + +default_encoders = OrderedDict([ + (bytes, encode_bytestring), + (bytearray, encode_bytearray), + (unicode, encode_string), + (int, encode_int), + (long, encode_int), + (float, encode_float), + (('decimal', 'Decimal'), encode_decimal), + (bool, encode_boolean), + (type(None), encode_none), + (tuple, encode_array), + (list, encode_array), + (dict, encode_map), + (defaultdict, encode_map), + (OrderedDict, encode_map), + (type(undefined), encode_undefined), + (datetime, encode_datetime), + (date, encode_date), + (type(re.compile('')), encode_regexp), + (('fractions', 'Fraction'), encode_rational), + (('email.message', 'Message'), encode_mime), + (('uuid', 'UUID'), encode_uuid), + (CBORSimpleValue, encode_simple_value), + (CBORTag, encode_semantic), + (set, encode_set), + (frozenset, encode_set) +]) + +canonical_encoders = OrderedDict([ + (float, encode_minimal_float), + (dict, encode_canonical_map), + (defaultdict, encode_canonical_map), + (OrderedDict, encode_canonical_map), + (set, encode_canonical_set), + (frozenset, encode_canonical_set) +]) + + +class CBOREncoder(object): + """ + Serializes objects to a byte stream using Concise Binary Object Representation. + + :param datetime_as_timestamp: set to ``True`` to serialize datetimes as UNIX timestamps + (this makes datetimes more concise on the wire but loses the time zone information) + :param datetime.tzinfo timezone: the default timezone to use for serializing naive datetimes + :param value_sharing: if ``True``, allows more efficient serializing of repeated values and, + more importantly, cyclic data structures, at the cost of extra line overhead + :param default: a callable that is called by the encoder with three arguments + (encoder, value, file object) when no suitable encoder has been found, and should use the + methods on the encoder to encode any objects it wants to add to the data stream + :param canonical: Forces mapping types to be output in a stable order to guarantee that the + output will always produce the same hash given the same input. + """ + + __slots__ = ('fp', 'datetime_as_timestamp', 'timezone', 'default', 'value_sharing', + 'json_compatible', '_shared_containers', '_encoders') + + def __init__(self, fp, datetime_as_timestamp=False, timezone=None, value_sharing=False, + default=None, canonical=False): + self.fp = fp + self.datetime_as_timestamp = datetime_as_timestamp + self.timezone = timezone + self.value_sharing = value_sharing + self.default = default + self._shared_containers = {} # indexes used for value sharing + self._encoders = default_encoders.copy() + if canonical: + self._encoders.update(canonical_encoders) + + def _find_encoder(self, obj_type): + from sys import modules + + for type_, enc in list(iteritems(self._encoders)): + if type(type_) is tuple: + modname, typename = type_ + imported_type = getattr(modules.get(modname), typename, None) + if imported_type is not None: + del self._encoders[type_] + self._encoders[imported_type] = enc + type_ = imported_type + else: # pragma: nocover + continue + + if issubclass(obj_type, type_): + self._encoders[obj_type] = enc + return enc + + return None + + @contextmanager + def disable_value_sharing(self): + """Disable value sharing in the encoder for the duration of the context block.""" + old_value_sharing = self.value_sharing + self.value_sharing = False + yield + self.value_sharing = old_value_sharing + + def write(self, data): + """ + Write bytes to the data stream. + + :param data: the bytes to write + + """ + self.fp.write(data) + + def encode(self, obj): + """ + Encode the given object using CBOR. + + :param obj: the object to encode + + """ + obj_type = obj.__class__ + encoder = self._encoders.get(obj_type) or self._find_encoder(obj_type) or self.default + if not encoder: + raise CBOREncodeError('cannot serialize type %s' % obj_type.__name__) + + encoder(self, obj) + + def encode_to_bytes(self, obj): + """ + Encode the given object to a byte buffer and return its value as bytes. + + This method was intended to be used from the ``default`` hook when an object needs to be + encoded separately from the rest but while still taking advantage of the shared value + registry. + + """ + old_fp = self.fp + self.fp = fp = BytesIO() + self.encode(obj) + self.fp = old_fp + return fp.getvalue() + + +def dumps(obj, **kwargs): + """ + Serialize an object to a bytestring. + + :param obj: the object to serialize + :param kwargs: keyword arguments passed to :class:`~.CBOREncoder` + :return: the serialized output + :rtype: bytes + + """ + fp = BytesIO() + dump(obj, fp, **kwargs) + return fp.getvalue() + + +def dump(obj, fp, **kwargs): + """ + Serialize an object to a file. + + :param obj: the object to serialize + :param fp: a file-like object + :param kwargs: keyword arguments passed to :class:`~.CBOREncoder` + + """ + CBOREncoder(fp, **kwargs).encode(obj) diff --git a/mercurial/thirdparty/cbor/cbor2/types.py b/mercurial/thirdparty/cbor/cbor2/types.py new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/cbor2/types.py @@ -0,0 +1,55 @@ +class CBORTag(object): + """ + Represents a CBOR semantic tag. + + :param int tag: tag number + :param value: encapsulated value (any object) + """ + + __slots__ = 'tag', 'value' + + def __init__(self, tag, value): + self.tag = tag + self.value = value + + def __eq__(self, other): + if isinstance(other, CBORTag): + return self.tag == other.tag and self.value == other.value + return NotImplemented + + def __repr__(self): + return 'CBORTag({self.tag}, {self.value!r})'.format(self=self) + + +class CBORSimpleValue(object): + """ + Represents a CBOR "simple value". + + :param int value: the value (0-255) + """ + + __slots__ = 'value' + + def __init__(self, value): + if value < 0 or value > 255: + raise TypeError('simple value too big') + self.value = value + + def __eq__(self, other): + if isinstance(other, CBORSimpleValue): + return self.value == other.value + elif isinstance(other, int): + return self.value == other + return NotImplemented + + def __repr__(self): + return 'CBORSimpleValue({self.value})'.format(self=self) + + +class UndefinedType(object): + __slots__ = () + + +#: Represents the "undefined" value. +undefined = UndefinedType() +break_marker = object() diff --git a/mercurial/thirdparty/cbor/docs/conf.py b/mercurial/thirdparty/cbor/docs/conf.py new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/docs/conf.py @@ -0,0 +1,33 @@ +# coding: utf-8 +#!/usr/bin/env python +import pkg_resources + + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx' +] + +templates_path = ['_templates'] +source_suffix = '.rst' +master_doc = 'index' +project = 'cbor2' +author = u'Alex Grönholm' +copyright = u'2016, ' + author + +v = pkg_resources.get_distribution(project).parsed_version +version = v.base_version +release = v.public + +language = None + +exclude_patterns = ['_build'] +pygments_style = 'sphinx' +highlight_language = 'python' +todo_include_todos = False + +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] +htmlhelp_basename = project.replace('-', '') + 'doc' + +intersphinx_mapping = {'python': ('http://docs.python.org/', None)} diff --git a/mercurial/thirdparty/cbor/docs/customizing.rst b/mercurial/thirdparty/cbor/docs/customizing.rst new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/docs/customizing.rst @@ -0,0 +1,132 @@ +Customizing encoding and decoding +================================= + +Both the encoder and decoder can be customized to support a wider range of types. + +On the encoder side, this is accomplished by passing a callback as the ``default`` constructor +argument. This callback will receive an object that the encoder could not serialize on its own. +The callback should then return a value that the encoder can serialize on its own, although the +return value is allowed to contain objects that also require the encoder to use the callback, as +long as it won't result in an infinite loop. + +On the decoder side, you have two options: ``tag_hook`` and ``object_hook``. The former is called +by the decoder to process any semantic tags that have no predefined decoders. The latter is called +for any newly decoded ``dict`` objects, and is mostly useful for implementing a JSON compatible +custom type serialization scheme. Unless your requirements restrict you to JSON compatible types +only, it is recommended to use ``tag_hook`` for this purpose. + +JSON compatibility +------------------ + +In certain applications, it may be desirable to limit the supported types to the same ones +serializable as JSON: (unicode) string, integer, float, boolean, null, array and object (dict). +This can be done by passing the ``json_compatible`` option to the encoder. When incompatible types +are encountered, a :class:`~cbor2.encoder.CBOREncodeError` is then raised. + +For the decoder, there is no support for detecting incoming incompatible types yet. + +Using the CBOR tags for custom types +------------------------------------ + +The most common way to use ``default`` is to call :meth:`~cbor2.encoder.CBOREncoder.encode` +to add a custom tag in the data stream, with the payload as the value:: + + class Point(object): + def __init__(self, x, y): + self.x = x + self.y = y + + def default_encoder(encoder, value): + # Tag number 4000 was chosen arbitrarily + encoder.encode(CBORTag(4000, [value.x, value.y])) + +The corresponding ``tag_hook`` would be:: + + def tag_hook(decoder, tag, shareable_index=None): + if tag.tag != 4000: + return tag + + # tag.value is now the [x, y] list we serialized before + return Point(*tag.value) + +Using dicts to carry custom types +--------------------------------- + +The same could be done with ``object_hook``, except less efficiently:: + + def default_encoder(encoder, value): + encoder.encode(dict(typename='Point', x=value.x, y=value.y)) + + def object_hook(decoder, value): + if value.get('typename') != 'Point': + return value + + return Point(value['x'], value['y']) + +You should make sure that whatever way you decide to use for telling apart your "specially marked" +dicts from arbitrary data dicts won't mistake on for the other. + +Value sharing with custom types +------------------------------- + +In order to properly encode and decode cyclic references with custom types, some special care has +to be taken. Suppose you have a custom type as below, where every child object contains a reference +to its parent and the parent contains a list of children:: + + from cbor2 import dumps, loads, shareable_encoder, CBORTag + + + class MyType(object): + def __init__(self, parent=None): + self.parent = parent + self.children = [] + if parent: + self.parent.children.append(self) + +This would not normally be serializable, as it would lead to an endless loop (in the worst case) +and raise some exception (in the best case). Now, enter CBOR's extension tags 28 and 29. These tags +make it possible to add special markers into the data stream which can be later referenced and +substituted with the object marked earlier. + +To do this, in ``default`` hooks used with the encoder you will need to use the +:meth:`~cbor2.encoder.shareable_encoder` decorator on your ``default`` hook function. It will +automatically automatically add the object to the shared values registry on the encoder and prevent +it from being serialized twice (instead writing a reference to the data stream):: + + @shareable_encoder + def default_encoder(encoder, value): + # The state has to be serialized separately so that the decoder would have a chance to + # create an empty instance before the shared value references are decoded + serialized_state = encoder.encode_to_bytes(value.__dict__) + encoder.encode(CBORTag(3000, serialized_state)) + +On the decoder side, you will need to initialize an empty instance for shared value lookup before +the object's state (which may contain references to it) is decoded. +This is done with the :meth:`~cbor2.encoder.CBORDecoder.set_shareable` method:: + + def tag_hook(decoder, tag, shareable_index=None): + # Return all other tags as-is + if tag.tag != 3000: + return tag + + # Create a raw instance before initializing its state to make it possible for cyclic + # references to work + instance = MyType.__new__(MyType) + decoder.set_shareable(shareable_index, instance) + + # Separately decode the state of the new object and then apply it + state = decoder.decode_from_bytes(tag.value) + instance.__dict__.update(state) + return instance + +You could then verify that the cyclic references have been restored after deserialization:: + + parent = MyType() + child1 = MyType(parent) + child2 = MyType(parent) + serialized = dumps(parent, default=default_encoder, value_sharing=True) + + new_parent = loads(serialized, tag_hook=tag_hook) + assert new_parent.children[0].parent is new_parent + assert new_parent.children[1].parent is new_parent + diff --git a/mercurial/thirdparty/cbor/docs/index.rst b/mercurial/thirdparty/cbor/docs/index.rst new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/docs/index.rst @@ -0,0 +1,15 @@ +.. include:: ../README.rst + :start-line: 7 + :end-before: Project links + +Table of contents +----------------- + +.. toctree:: + :maxdepth: 2 + + usage + customizing + versionhistory + +* :ref:`API reference ` diff --git a/mercurial/thirdparty/cbor/docs/modules/decoder.rst b/mercurial/thirdparty/cbor/docs/modules/decoder.rst new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/docs/modules/decoder.rst @@ -0,0 +1,5 @@ +:mod:`cbor2.decoder` +==================== + +.. automodule:: cbor2.decoder + :members: diff --git a/mercurial/thirdparty/cbor/docs/modules/encoder.rst b/mercurial/thirdparty/cbor/docs/modules/encoder.rst new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/docs/modules/encoder.rst @@ -0,0 +1,5 @@ +:mod:`cbor2.encoder` +==================== + +.. automodule:: cbor2.encoder + :members: diff --git a/mercurial/thirdparty/cbor/docs/modules/types.rst b/mercurial/thirdparty/cbor/docs/modules/types.rst new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/docs/modules/types.rst @@ -0,0 +1,5 @@ +:mod:`cbor2.types` +================== + +.. automodule:: cbor2.types + :members: diff --git a/mercurial/thirdparty/cbor/docs/usage.rst b/mercurial/thirdparty/cbor/docs/usage.rst new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/docs/usage.rst @@ -0,0 +1,81 @@ +Basic usage +=========== + +Serializing and deserializing with cbor2 is pretty straightforward:: + + from cbor2 import dumps, loads + + # Serialize an object as a bytestring + data = dumps(['hello', 'world']) + + # Deserialize a bytestring + obj = loads(data) + + # Efficiently deserialize from a file + with open('input.cbor', 'rb') as fp: + obj = load(fp) + + # Efficiently serialize an object to a file + with open('output.cbor', 'wb') as fp: + dump(obj, fp) + +Some data types, however, require extra considerations, as detailed below. + +String/bytes handling on Python 2 +--------------------------------- + +The ``str`` type is encoded as binary on Python 2. If you want to encode strings as text on +Python 2, use unicode strings instead. + +Date/time handling +------------------ + +The CBOR specification does not support naïve datetimes (that is, datetimes where ``tzinfo`` is +missing). When the encoder encounters such a datetime, it needs to know which timezone it belongs +to. To this end, you can specify a default timezone by passing a :class:`~datetime.tzinfo` instance +to :func:`~cbor2.encoder.dump`/:func:`~cbor2.encoder.dumps` call as the ``timezone`` argument. +Decoded datetimes are always timezone aware. + +By default, datetimes are serialized in a manner that retains their timezone offsets. You can +optimize the data stream size by passing ``datetime_as_timestamp=False`` to +:func:`~cbor2.encoder.dump`/:func:`~cbor2.encoder.dumps`, but this causes the timezone offset +information to be lost. + +Cyclic (recursive) data structures +---------------------------------- + +If the encoder encounters a shareable object (ie. list or dict) that it has been before, it will +by default raise :exc:`~cbor2.encoder.CBOREncodeError` indicating that a cyclic reference has been +detected and value sharing was not enabled. CBOR has, however, an extension specification that +allows the encoder to reference a previously encoded value without processing it again. This makes +it possible to serialize such cyclic references, but value sharing has to be enabled by passing +``value_sharing=True`` to :func:`~cbor2.encoder.dump`/:func:`~cbor2.encoder.dumps`. + +.. warning:: Support for value sharing is rare in other CBOR implementations, so think carefully + whether you want to enable it. It also causes some line overhead, as all potentially shareable + values must be tagged as such. + +Tag support +----------- + +In addition to all standard CBOR tags, this library supports many extended tags: + +=== ======================================== ==================================================== +Tag Semantics Python type(s) +=== ======================================== ==================================================== +0 Standard date/time string datetime.date / datetime.datetime +1 Epoch-based date/time datetime.date / datetime.datetime +2 Positive bignum int / long +3 Negative bignum int / long +4 Decimal fraction decimal.Decimal +5 Bigfloat decimal.Decimal +28 Mark shared value N/A +29 Reference shared value N/A +30 Rational number fractions.Fraction +35 Regular expression ``_sre.SRE_Pattern`` (result of ``re.compile(...)``) +36 MIME message email.message.Message +37 Binary UUID uuid.UUID +258 Set of unique items set +=== ======================================== ==================================================== + +Arbitary tags can be represented with the :class:`~cbor2.types.CBORTag` class. diff --git a/mercurial/thirdparty/cbor/docs/versionhistory.rst b/mercurial/thirdparty/cbor/docs/versionhistory.rst new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/docs/versionhistory.rst @@ -0,0 +1,78 @@ +Version history +=============== + +This library adheres to `Semantic Versioning `_. + +**UNRELEASED** + +- Added canonical encoding (via ``canonical=True``) (PR by Sekenre) +- Added support for encoding/decoding sets (semantic tag 258) (PR by Sekenre) + +**4.0.1** (2017-08-21) + +- Fixed silent truncation of decoded data if there are not enough bytes in the stream for an exact + read (``CBORDecodeError`` is now raised instead) + +**4.0.0** (2017-04-24) + +- **BACKWARD INCOMPATIBLE** Value sharing has been disabled by default, for better compatibility + with other implementations and better performance (since it is rarely needed) +- **BACKWARD INCOMPATIBLE** Replaced the ``semantic_decoders`` decoder option with the ``tag_hook`` + option +- **BACKWARD INCOMPATIBLE** Replaced the ``encoders`` encoder option with the ``default`` option +- **BACKWARD INCOMPATIBLE** Factored out the file object argument (``fp``) from all callbacks +- **BACKWARD INCOMPATIBLE** The encoder no longer supports every imaginable type implementing the + ``Sequence`` or ``Map`` interface, as they turned out to be too broad +- Added the ``object_hook`` option for decoding dicts into complex objects + (intended for situations where JSON compatibility is required and semantic tags cannot be used) +- Added encoding and decoding of simple values (``CBORSimpleValue``) + (contributed by Jerry Lundström) +- Replaced the decoder for bignums with a simpler and faster version (contributed by orent) +- Made all relevant classes and functions available directly in the ``cbor2`` namespace +- Added proper documentation + +**3.0.4** (2016-09-24) + +- Fixed TypeError when trying to encode extension types (regression introduced in 3.0.3) + +**3.0.3** (2016-09-23) + +- No changes, just re-releasing due to git tagging screw-up + +**3.0.2** (2016-09-23) + +- Fixed decoding failure for datetimes with microseconds (tag 0) + +**3.0.1** (2016-08-08) + +- Fixed error in the cyclic structure detection code that could mistake one container for + another, sometimes causing a bogus error about cyclic data structures where there was none + +**3.0.0** (2016-07-03) + +- **BACKWARD INCOMPATIBLE** Encoder callbacks now receive three arguments: the encoder instance, + the value to encode and a file-like object. The callback must must now either write directly to + the file-like object or call another encoder callback instead of returning an iterable. +- **BACKWARD INCOMPATIBLE** Semantic decoder callbacks now receive four arguments: the decoder + instance, the primitive value, a file-like object and the shareable index for the decoded value. + Decoders that support value sharing must now set the raw value at the given index in + ``decoder.shareables``. +- **BACKWARD INCOMPATIBLE** Removed support for iterative encoding (``CBOREncoder.encode()`` is no + longer a generator function and always returns ``None``) +- Significantly improved performance (encoder ~30 % faster, decoder ~60 % faster) +- Fixed serialization round-trip for ``undefined`` (simple type #23) +- Added proper support for value sharing in callbacks + +**2.0.0** (2016-06-11) + +- **BACKWARD INCOMPATIBLE** Deserialize unknown tags as ``CBORTag`` objects so as not to lose + information +- Fixed error messages coming from nested structures + +**1.1.0** (2016-06-10) + +- Fixed deserialization of cyclic structures + +**1.0.0** (2016-06-08) + +- Initial release diff --git a/mercurial/thirdparty/cbor/setup.cfg b/mercurial/thirdparty/cbor/setup.cfg new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/setup.cfg @@ -0,0 +1,46 @@ +[metadata] +name = cbor2 +description = Pure Python CBOR (de)serializer with extensive tag support +long_description = file: README.rst +author = Alex Grönholm +author_email = alex.gronholm@nextday.fi +url = https://github.com/agronholm/cbor2 +license = MIT +license_file = LICENSE.txt +keywords = serialization cbor +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Programming Language :: Python + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + +[options] +packages = find: + +[options.extras_require] +test = + pytest + pytest-cov + +[tool:pytest] +addopts = -rsx --cov --tb=short +testpaths = tests + +[coverage:run] +source = cbor2 + +[coverage:report] +show_missing = true + +[flake8] +max-line-length = 99 +exclude = .tox,build,docs + +[bdist_wheel] +universal = 1 diff --git a/mercurial/thirdparty/cbor/setup.py b/mercurial/thirdparty/cbor/setup.py new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/setup.py @@ -0,0 +1,12 @@ +from setuptools import setup + +setup( + use_scm_version={ + 'version_scheme': 'post-release', + 'local_scheme': 'dirty-tag' + }, + setup_requires=[ + 'setuptools >= 36.2.7', + 'setuptools_scm >= 1.7.0' + ] +) diff --git a/mercurial/thirdparty/cbor/tests/test_decoder.py b/mercurial/thirdparty/cbor/tests/test_decoder.py new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/tests/test_decoder.py @@ -0,0 +1,342 @@ +from __future__ import division + +import math +import re +import sys +from binascii import unhexlify +from datetime import datetime, timedelta +from decimal import Decimal +from email.message import Message +from fractions import Fraction +from io import BytesIO +from uuid import UUID + +import pytest + +from cbor2.compat import timezone +from cbor2.decoder import loads, CBORDecodeError, load, CBORDecoder +from cbor2.types import CBORTag, undefined, CBORSimpleValue + + +@pytest.mark.parametrize('payload, expected', [ + ('00', 0), + ('01', 1), + ('0a', 10), + ('17', 23), + ('1818', 24), + ('1819', 25), + ('1864', 100), + ('1903e8', 1000), + ('1a000f4240', 1000000), + ('1b000000e8d4a51000', 1000000000000), + ('1bffffffffffffffff', 18446744073709551615), + ('c249010000000000000000', 18446744073709551616), + ('3bffffffffffffffff', -18446744073709551616), + ('c349010000000000000000', -18446744073709551617), + ('20', -1), + ('29', -10), + ('3863', -100), + ('3903e7', -1000) +]) +def test_integer(payload, expected): + decoded = loads(unhexlify(payload)) + assert decoded == expected + + +def test_invalid_integer_subtype(): + exc = pytest.raises(CBORDecodeError, loads, b'\x1c') + assert str(exc.value).endswith('unknown unsigned integer subtype 0x1c') + + +@pytest.mark.parametrize('payload, expected', [ + ('f90000', 0.0), + ('f98000', -0.0), + ('f93c00', 1.0), + ('fb3ff199999999999a', 1.1), + ('f93e00', 1.5), + ('f97bff', 65504.0), + ('fa47c35000', 100000.0), + ('fa7f7fffff', 3.4028234663852886e+38), + ('fb7e37e43c8800759c', 1.0e+300), + ('f90001', 5.960464477539063e-8), + ('f90400', 0.00006103515625), + ('f9c400', -4.0), + ('fbc010666666666666', -4.1), + ('f97c00', float('inf')), + ('f9fc00', float('-inf')), + ('fa7f800000', float('inf')), + ('faff800000', float('-inf')), + ('fb7ff0000000000000', float('inf')), + ('fbfff0000000000000', float('-inf')) +]) +def test_float(payload, expected): + decoded = loads(unhexlify(payload)) + assert decoded == expected + + +@pytest.mark.parametrize('payload', ['f97e00', 'fa7fc00000', 'fb7ff8000000000000']) +def test_float_nan(payload): + decoded = loads(unhexlify(payload)) + assert math.isnan(decoded) + + +@pytest.mark.parametrize('payload, expected', [ + ('f4', False), + ('f5', True), + ('f6', None), + ('f7', undefined) +]) +def test_special(payload, expected): + decoded = loads(unhexlify(payload)) + assert decoded is expected + + +@pytest.mark.parametrize('payload, expected', [ + ('40', b''), + ('4401020304', b'\x01\x02\x03\x04'), +]) +def test_binary(payload, expected): + decoded = loads(unhexlify(payload)) + assert decoded == expected + + +@pytest.mark.parametrize('payload, expected', [ + ('60', u''), + ('6161', u'a'), + ('6449455446', u'IETF'), + ('62225c', u'\"\\'), + ('62c3bc', u'\u00fc'), + ('63e6b0b4', u'\u6c34') +]) +def test_string(payload, expected): + decoded = loads(unhexlify(payload)) + assert decoded == expected + + +@pytest.mark.parametrize('payload, expected', [ + ('80', []), + ('83010203', [1, 2, 3]), + ('8301820203820405', [1, [2, 3], [4, 5]]), + ('98190102030405060708090a0b0c0d0e0f101112131415161718181819', list(range(1, 26))) +]) +def test_array(payload, expected): + decoded = loads(unhexlify(payload)) + assert decoded == expected + + +@pytest.mark.parametrize('payload, expected', [ + ('a0', {}), + ('a201020304', {1: 2, 3: 4}) +]) +def test_map(payload, expected): + decoded = loads(unhexlify(payload)) + assert decoded == expected + + +@pytest.mark.parametrize('payload, expected', [ + ('a26161016162820203', {'a': 1, 'b': [2, 3]}), + ('826161a161626163', ['a', {'b': 'c'}]), + ('a56161614161626142616361436164614461656145', + {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D', 'e': 'E'}) +]) +def test_mixed_array_map(payload, expected): + decoded = loads(unhexlify(payload)) + assert decoded == expected + + +@pytest.mark.parametrize('payload, expected', [ + ('5f42010243030405ff', b'\x01\x02\x03\x04\x05'), + ('7f657374726561646d696e67ff', 'streaming'), + ('9fff', []), + ('9f018202039f0405ffff', [1, [2, 3], [4, 5]]), + ('9f01820203820405ff', [1, [2, 3], [4, 5]]), + ('83018202039f0405ff', [1, [2, 3], [4, 5]]), + ('83019f0203ff820405', [1, [2, 3], [4, 5]]), + ('9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff', list(range(1, 26))), + ('bf61610161629f0203ffff', {'a': 1, 'b': [2, 3]}), + ('826161bf61626163ff', ['a', {'b': 'c'}]), + ('bf6346756ef563416d7421ff', {'Fun': True, 'Amt': -2}), +]) +def test_streaming(payload, expected): + decoded = loads(unhexlify(payload)) + assert decoded == expected + + +@pytest.mark.parametrize('payload, expected', [ + ('e0', 0), + ('e2', 2), + ('f3', 19), + ('f820', 32), + ('e0', CBORSimpleValue(0)), + ('e2', CBORSimpleValue(2)), + ('f3', CBORSimpleValue(19)), + ('f820', CBORSimpleValue(32)) +]) +def test_simple_value(payload, expected): + decoded = loads(unhexlify(payload)) + assert decoded == expected + + +# +# Tests for extension tags +# + + +@pytest.mark.parametrize('payload, expected', [ + ('c074323031332d30332d32315432303a30343a30305a', + datetime(2013, 3, 21, 20, 4, 0, tzinfo=timezone.utc)), + ('c0781b323031332d30332d32315432303a30343a30302e3338303834315a', + datetime(2013, 3, 21, 20, 4, 0, 380841, tzinfo=timezone.utc)), + ('c07819323031332d30332d32315432323a30343a30302b30323a3030', + datetime(2013, 3, 21, 22, 4, 0, tzinfo=timezone(timedelta(hours=2)))), + ('c11a514b67b0', datetime(2013, 3, 21, 20, 4, 0, tzinfo=timezone.utc)), + ('c11a514b67b0', datetime(2013, 3, 21, 22, 4, 0, tzinfo=timezone(timedelta(hours=2)))) +], ids=['datetime/utc', 'datetime+micro/utc', 'datetime/eet', 'timestamp/utc', 'timestamp/eet']) +def test_datetime(payload, expected): + decoded = loads(unhexlify(payload)) + assert decoded == expected + + +def test_bad_datetime(): + exc = pytest.raises(CBORDecodeError, loads, unhexlify('c06b303030302d3132332d3031')) + assert str(exc.value).endswith('invalid datetime string: 0000-123-01') + + +def test_fraction(): + decoded = loads(unhexlify('c48221196ab3')) + assert decoded == Decimal('273.15') + + +def test_bigfloat(): + decoded = loads(unhexlify('c5822003')) + assert decoded == Decimal('1.5') + + +def test_rational(): + decoded = loads(unhexlify('d81e820205')) + assert decoded == Fraction(2, 5) + + +def test_regex(): + decoded = loads(unhexlify('d8236d68656c6c6f2028776f726c6429')) + expr = re.compile(u'hello (world)') + assert decoded == expr + + +def test_mime(): + decoded = loads(unhexlify( + 'd824787b436f6e74656e742d547970653a20746578742f706c61696e3b20636861727365743d2269736f2d38' + '3835392d3135220a4d494d452d56657273696f6e3a20312e300a436f6e74656e742d5472616e736665722d45' + '6e636f64696e673a2071756f7465642d7072696e7461626c650a0a48656c6c6f203d413475726f')) + assert isinstance(decoded, Message) + assert decoded.get_payload() == 'Hello =A4uro' + + +def test_uuid(): + decoded = loads(unhexlify('d825505eaffac8b51e480581277fdcc7842faf')) + assert decoded == UUID(hex='5eaffac8b51e480581277fdcc7842faf') + + +def test_bad_shared_reference(): + exc = pytest.raises(CBORDecodeError, loads, unhexlify('d81d05')) + assert str(exc.value).endswith('shared reference 5 not found') + + +def test_uninitialized_shared_reference(): + fp = BytesIO(unhexlify('d81d00')) + decoder = CBORDecoder(fp) + decoder._shareables.append(None) + exc = pytest.raises(CBORDecodeError, decoder.decode) + assert str(exc.value).endswith('shared value 0 has not been initialized') + + +def test_cyclic_array(): + decoded = loads(unhexlify('d81c81d81d00')) + assert decoded == [decoded] + + +def test_cyclic_map(): + decoded = loads(unhexlify('d81ca100d81d00')) + assert decoded == {0: decoded} + + +def test_unhandled_tag(): + """ + Test that a tag is simply ignored and its associated value returned if there is no special + handling available for it. + + """ + decoded = loads(unhexlify('d917706548656c6c6f')) + assert decoded == CBORTag(6000, u'Hello') + + +def test_premature_end_of_stream(): + """ + Test that the decoder detects a situation where read() returned fewer than expected bytes. + + """ + exc = pytest.raises(CBORDecodeError, loads, unhexlify('437879')) + exc.match('premature end of stream \(expected to read 3 bytes, got 2 instead\)') + + +def test_tag_hook(): + def reverse(decoder, tag, fp, shareable_index=None): + return tag.value[::-1] + + decoded = loads(unhexlify('d917706548656c6c6f'), tag_hook=reverse) + assert decoded == u'olleH' + + +def test_tag_hook_cyclic(): + class DummyType(object): + def __init__(self, value): + self.value = value + + def unmarshal_dummy(decoder, tag, shareable_index=None): + instance = DummyType.__new__(DummyType) + decoder.set_shareable(shareable_index, instance) + instance.value = decoder.decode_from_bytes(tag.value) + return instance + + decoded = loads(unhexlify('D81CD90BB849D81CD90BB843D81D00'), tag_hook=unmarshal_dummy) + assert isinstance(decoded, DummyType) + assert decoded.value.value is decoded + + +def test_object_hook(): + class DummyType(object): + def __init__(self, state): + self.state = state + + payload = unhexlify('A2616103616205') + decoded = loads(payload, object_hook=lambda decoder, value: DummyType(value)) + assert isinstance(decoded, DummyType) + assert decoded.state == {'a': 3, 'b': 5} + + +def test_error_major_type(): + exc = pytest.raises(CBORDecodeError, loads, b'') + assert str(exc.value).startswith('error reading major type at index 0: ') + + +def test_load_from_file(tmpdir): + path = tmpdir.join('testdata.cbor') + path.write_binary(b'\x82\x01\x0a') + with path.open('rb') as fp: + obj = load(fp) + + assert obj == [1, 10] + + +@pytest.mark.skipif(sys.version_info < (3, 0), reason="No exception with python 2.7") +def test_nested_exception(): + exc = pytest.raises((CBORDecodeError, TypeError), loads, unhexlify('A1D9177082010201')) + exc.match(r"error decoding value at index 8: " + r"(unhashable type: 'CBORTag'|'CBORTag' objects are unhashable)") + + +def test_set(): + payload = unhexlify('d9010283616361626161') + value = loads(payload) + assert type(value) is set + assert value == set([u'a', u'b', u'c']) diff --git a/mercurial/thirdparty/cbor/tests/test_encoder.py b/mercurial/thirdparty/cbor/tests/test_encoder.py new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/tests/test_encoder.py @@ -0,0 +1,318 @@ +import re +from binascii import unhexlify +from collections import OrderedDict +from datetime import datetime, timedelta, date +from decimal import Decimal +from email.mime.text import MIMEText +from fractions import Fraction +from uuid import UUID + +import pytest + +from cbor2.compat import timezone +from cbor2.encoder import dumps, CBOREncodeError, dump, shareable_encoder +from cbor2.types import CBORTag, undefined, CBORSimpleValue + + +@pytest.mark.parametrize('value, expected', [ + (0, '00'), + (1, '01'), + (10, '0a'), + (23, '17'), + (24, '1818'), + (100, '1864'), + (1000, '1903e8'), + (1000000, '1a000f4240'), + (1000000000000, '1b000000e8d4a51000'), + (18446744073709551615, '1bffffffffffffffff'), + (18446744073709551616, 'c249010000000000000000'), + (-18446744073709551616, '3bffffffffffffffff'), + (-18446744073709551617, 'c349010000000000000000'), + (-1, '20'), + (-10, '29'), + (-100, '3863'), + (-1000, '3903e7') +]) +def test_integer(value, expected): + expected = unhexlify(expected) + assert dumps(value) == expected + + +@pytest.mark.parametrize('value, expected', [ + (1.1, 'fb3ff199999999999a'), + (1.0e+300, 'fb7e37e43c8800759c'), + (-4.1, 'fbc010666666666666'), + (float('inf'), 'f97c00'), + (float('nan'), 'f97e00'), + (float('-inf'), 'f9fc00') +]) +def test_float(value, expected): + expected = unhexlify(expected) + assert dumps(value) == expected + + +@pytest.mark.parametrize('value, expected', [ + (b'', '40'), + (b'\x01\x02\x03\x04', '4401020304'), +]) +def test_bytestring(value, expected): + expected = unhexlify(expected) + assert dumps(value) == expected + + +def test_bytearray(): + expected = unhexlify('4401020304') + assert dumps(bytearray(b'\x01\x02\x03\x04')) == expected + + +@pytest.mark.parametrize('value, expected', [ + (u'', '60'), + (u'a', '6161'), + (u'IETF', '6449455446'), + (u'"\\', '62225c'), + (u'\u00fc', '62c3bc'), + (u'\u6c34', '63e6b0b4') +]) +def test_string(value, expected): + expected = unhexlify(expected) + assert dumps(value) == expected + + +@pytest.mark.parametrize('value, expected', [ + (False, 'f4'), + (True, 'f5'), + (None, 'f6'), + (undefined, 'f7') +], ids=['false', 'true', 'null', 'undefined']) +def test_special(value, expected): + expected = unhexlify(expected) + assert dumps(value) == expected + + +@pytest.mark.parametrize('value, expected', [ + (CBORSimpleValue(0), 'e0'), + (CBORSimpleValue(2), 'e2'), + (CBORSimpleValue(19), 'f3'), + (CBORSimpleValue(32), 'f820') +]) +def test_simple_value(value, expected): + expected = unhexlify(expected) + assert dumps(value) == expected + + +# +# Tests for extension tags +# + +@pytest.mark.parametrize('value, as_timestamp, expected', [ + (datetime(2013, 3, 21, 20, 4, 0, tzinfo=timezone.utc), False, + 'c074323031332d30332d32315432303a30343a30305a'), + (datetime(2013, 3, 21, 20, 4, 0, 380841, tzinfo=timezone.utc), False, + 'c0781b323031332d30332d32315432303a30343a30302e3338303834315a'), + (datetime(2013, 3, 21, 22, 4, 0, tzinfo=timezone(timedelta(hours=2))), False, + 'c07819323031332d30332d32315432323a30343a30302b30323a3030'), + (datetime(2013, 3, 21, 20, 4, 0), False, 'c074323031332d30332d32315432303a30343a30305a'), + (datetime(2013, 3, 21, 20, 4, 0, tzinfo=timezone.utc), True, 'c11a514b67b0'), + (datetime(2013, 3, 21, 22, 4, 0, tzinfo=timezone(timedelta(hours=2))), True, 'c11a514b67b0') +], ids=['datetime/utc', 'datetime+micro/utc', 'datetime/eet', 'naive', 'timestamp/utc', + 'timestamp/eet']) +def test_datetime(value, as_timestamp, expected): + expected = unhexlify(expected) + assert dumps(value, datetime_as_timestamp=as_timestamp, timezone=timezone.utc) == expected + + +def test_date(): + expected = unhexlify('c074323031332d30332d32315430303a30303a30305a') + assert dumps(date(2013, 3, 21), timezone=timezone.utc) == expected + + +def test_naive_datetime(): + """Test that naive datetimes are gracefully rejected when no timezone has been set.""" + exc = pytest.raises(CBOREncodeError, dumps, datetime(2013, 3, 21)) + exc.match('naive datetime encountered and no default timezone has been set') + + +@pytest.mark.parametrize('value, expected', [ + (Decimal('14.123'), 'c4822219372b'), + (Decimal('NaN'), 'f97e00'), + (Decimal('Infinity'), 'f97c00'), + (Decimal('-Infinity'), 'f9fc00') +], ids=['normal', 'nan', 'inf', 'neginf']) +def test_decimal(value, expected): + expected = unhexlify(expected) + assert dumps(value) == expected + + +def test_rational(): + expected = unhexlify('d81e820205') + assert dumps(Fraction(2, 5)) == expected + + +def test_regex(): + expected = unhexlify('d8236d68656c6c6f2028776f726c6429') + assert dumps(re.compile(u'hello (world)')) == expected + + +def test_mime(): + expected = unhexlify( + 'd824787b436f6e74656e742d547970653a20746578742f706c61696e3b20636861727365743d2269736f2d38' + '3835392d3135220a4d494d452d56657273696f6e3a20312e300a436f6e74656e742d5472616e736665722d456' + 'e636f64696e673a2071756f7465642d7072696e7461626c650a0a48656c6c6f203d413475726f') + message = MIMEText(u'Hello \u20acuro', 'plain', 'iso-8859-15') + assert dumps(message) == expected + + +def test_uuid(): + expected = unhexlify('d825505eaffac8b51e480581277fdcc7842faf') + assert dumps(UUID(hex='5eaffac8b51e480581277fdcc7842faf')) == expected + + +def test_custom_tag(): + expected = unhexlify('d917706548656c6c6f') + assert dumps(CBORTag(6000, u'Hello')) == expected + + +def test_cyclic_array(): + """Test that an array that contains itself can be serialized with value sharing enabled.""" + expected = unhexlify('d81c81d81c81d81d00') + a = [[]] + a[0].append(a) + assert dumps(a, value_sharing=True) == expected + + +def test_cyclic_array_nosharing(): + """Test that serializing a cyclic structure w/o value sharing will blow up gracefully.""" + a = [] + a.append(a) + exc = pytest.raises(CBOREncodeError, dumps, a) + exc.match('cyclic data structure detected but value sharing is disabled') + + +def test_cyclic_map(): + """Test that a dict that contains itself can be serialized with value sharing enabled.""" + expected = unhexlify('d81ca100d81d00') + a = {} + a[0] = a + assert dumps(a, value_sharing=True) == expected + + +def test_cyclic_map_nosharing(): + """Test that serializing a cyclic structure w/o value sharing will fail gracefully.""" + a = {} + a[0] = a + exc = pytest.raises(CBOREncodeError, dumps, a) + exc.match('cyclic data structure detected but value sharing is disabled') + + +@pytest.mark.parametrize('value_sharing, expected', [ + (False, '828080'), + (True, 'd81c82d81c80d81d01') +], ids=['nosharing', 'sharing']) +def test_not_cyclic_same_object(value_sharing, expected): + """Test that the same shareable object can be included twice if not in a cyclic structure.""" + expected = unhexlify(expected) + a = [] + b = [a, a] + assert dumps(b, value_sharing=value_sharing) == expected + + +def test_unsupported_type(): + exc = pytest.raises(CBOREncodeError, dumps, lambda: None) + exc.match('cannot serialize type function') + + +def test_default(): + class DummyType(object): + def __init__(self, state): + self.state = state + + def default_encoder(encoder, value): + encoder.encode(value.state) + + expected = unhexlify('820305') + obj = DummyType([3, 5]) + serialized = dumps(obj, default=default_encoder) + assert serialized == expected + + +def test_default_cyclic(): + class DummyType(object): + def __init__(self, value=None): + self.value = value + + @shareable_encoder + def default_encoder(encoder, value): + state = encoder.encode_to_bytes(value.value) + encoder.encode(CBORTag(3000, state)) + + expected = unhexlify('D81CD90BB849D81CD90BB843D81D00') + obj = DummyType() + obj2 = DummyType(obj) + obj.value = obj2 + serialized = dumps(obj, value_sharing=True, default=default_encoder) + assert serialized == expected + + +def test_dump_to_file(tmpdir): + path = tmpdir.join('testdata.cbor') + with path.open('wb') as fp: + dump([1, 10], fp) + + assert path.read_binary() == b'\x82\x01\x0a' + + +@pytest.mark.parametrize('value, expected', [ + (OrderedDict([(b'a', b''), (b'b', b'')]), 'A2416140416240'), + (OrderedDict([(b'b', b''), (b'a', b'')]), 'A2416140416240'), + (OrderedDict([(u'a', u''), (u'b', u'')]), 'a2616160616260'), + (OrderedDict([(u'b', u''), (u'a', u'')]), 'a2616160616260'), + (OrderedDict([(b'00001', u''), (b'002', u'')]), 'A2433030326045303030303160'), + (OrderedDict([(255, 0), (2, 0)]), 'a2020018ff00') +], ids=['bytes in order', 'bytes out of order', 'text in order', + 'text out of order', 'byte length', 'integer keys']) +def test_ordered_map(value, expected): + expected = unhexlify(expected) + assert dumps(value, canonical=True) == expected + + +@pytest.mark.parametrize('value, expected', [ + (3.5, 'F94300'), + (100000.0, 'FA47C35000'), + (3.8, 'FB400E666666666666'), + (float('inf'), 'f97c00'), + (float('nan'), 'f97e00'), + (float('-inf'), 'f9fc00'), + (float.fromhex('0x1.0p-24'), 'f90001'), + (float.fromhex('0x1.4p-24'), 'fa33a00000'), + (float.fromhex('0x1.ff8p-63'), 'fa207fc000') +], ids=['float 16', 'float 32', 'float 64', 'inf', 'nan', '-inf', + 'float 16 minimum positive subnormal', 'mantissa o/f to 32', + 'exponent o/f to 32']) +def test_minimal_floats(value, expected): + expected = unhexlify(expected) + assert dumps(value, canonical=True) == expected + + +def test_tuple_key(): + assert dumps({(2, 1): u''}) == unhexlify('a182020160') + + +@pytest.mark.parametrize('frozen', [False, True], ids=['set', 'frozenset']) +def test_set(frozen): + value = {u'a', u'b', u'c'} + if frozen: + value = frozenset(value) + + serialized = dumps(value) + assert len(serialized) == 10 + assert serialized.startswith(unhexlify('d9010283')) + + +@pytest.mark.parametrize('frozen', [False, True], ids=['set', 'frozenset']) +def test_canonical_set(frozen): + value = {u'y', u'x', u'aa', u'a'} + if frozen: + value = frozenset(value) + + serialized = dumps(value, canonical=True) + assert serialized == unhexlify('d9010284616161786179626161') diff --git a/mercurial/thirdparty/cbor/tests/test_types.py b/mercurial/thirdparty/cbor/tests/test_types.py new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/tests/test_types.py @@ -0,0 +1,36 @@ +import pytest + +from cbor2.types import CBORTag, CBORSimpleValue + + +def test_tag_repr(): + assert repr(CBORTag(600, 'blah')) == "CBORTag(600, 'blah')" + + +def test_tag_equals(): + tag1 = CBORTag(500, ['foo']) + tag2 = CBORTag(500, ['foo']) + tag3 = CBORTag(500, ['bar']) + assert tag1 == tag2 + assert not tag1 == tag3 + assert not tag1 == 500 + + +def test_simple_value_repr(): + assert repr(CBORSimpleValue(1)) == "CBORSimpleValue(1)" + + +def test_simple_value_equals(): + tag1 = CBORSimpleValue(1) + tag2 = CBORSimpleValue(1) + tag3 = CBORSimpleValue(21) + assert tag1 == tag2 + assert tag1 == 1 + assert not tag1 == tag3 + assert not tag1 == 21 + assert not tag2 == "21" + + +def test_simple_value_too_big(): + exc = pytest.raises(TypeError, CBORSimpleValue, 256) + assert str(exc.value) == 'simple value too big' diff --git a/mercurial/thirdparty/cbor/tox.ini b/mercurial/thirdparty/cbor/tox.ini new file mode 100644 --- /dev/null +++ b/mercurial/thirdparty/cbor/tox.ini @@ -0,0 +1,12 @@ +[tox] +envlist = py27, py33, py34, py35, py36, pypy, pypy3, flake8 +skip_missing_interpreters = true + +[testenv] +commands = python -m pytest {posargs} +extras = test + +[testenv:flake8] +deps = flake8 +commands = flake8 cbor2 tests +skip_install = true