diff -Nru construct-2.10.56+dfsg1/construct/core.py construct-2.10.58+dfsg1/construct/core.py --- construct-2.10.56+dfsg1/construct/core.py 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/construct/core.py 2021-01-27 22:32:16.000000000 +0000 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -import struct, io, binascii, itertools, collections, pickle, sys, os, tempfile, hashlib, importlib, imp +import struct, io, binascii, itertools, collections, pickle, sys, os, tempfile, hashlib, importlib from construct.lib import * from construct.expr import * @@ -351,7 +351,10 @@ r""" Build an object into a closed binary file. See build(). """ - with open(filename, 'wb') as f: + # Open the file for reading as well as writing. This allows builders to + # read back the stream just written. For example. RawCopy does this. + # See issue #888. + with open(filename, 'w+b') as f: self.build_stream(obj, f, **contextkw) def _build(self, obj, stream, context, path): @@ -439,7 +442,8 @@ f.write(source) modulename = hexlify(hashlib.sha1(source.encode()).digest()).decode() - module = imp.new_module(modulename) + module_spec = importlib.machinery.ModuleSpec(modulename, None) + module = importlib.util.module_from_spec(module_spec) c = compile(source, '', 'exec') exec(c, module.__dict__) @@ -987,7 +991,7 @@ Parses into an integer. Builds from an integer into specified byte count and endianness. Size is determined by `struct` module according to specified format string. :param endianity: string, character like: < > = - :param format: string, character like: f d B H L Q b h l q + :param format: string, character like: f d B H L Q b h l q e :raises StreamError: requested reading negative amount, could not read enough bytes, requested writing different amount than actual data, or could not write all bytes :raises FormatFieldError: wrong format string, or struct.(un)pack complained about the value @@ -1064,7 +1068,7 @@ :param length: integer or context lambda, number of bytes in the field :param signed: bool, whether the value is signed (two's complement), default is False (unsigned) - :param swapped: bool, whether to swap byte order (little endian), default is False (big endian) + :param swapped: bool or context lambda, whether to swap byte order (little endian), default is False (big endian) :raises StreamError: requested reading negative amount, could not read enough bytes, requested writing different amount than actual data, or could not write all bytes :raises IntegerError: lenght is negative, given a negative value when field is not signed, or not an integer @@ -1089,13 +1093,11 @@ self.swapped = swapped def _parse(self, stream, context, path): - length = self.length - if callable(length): - length = length(context) + length = evaluate(self.length, context) if length < 0: raise IntegerError("length must be non-negative", path=path) data = stream_read(stream, length, path) - if self.swapped: + if evaluate(self.swapped, context): data = data[::-1] return bytes2integer(data, self.signed) @@ -1104,28 +1106,26 @@ raise IntegerError("value %r is not an integer" % (obj,), path=path) if obj < 0 and not self.signed: raise IntegerError("value %r is negative, but field is not signed" % (obj,), path=path) - length = self.length - if callable(length): - length = length(context) + length = evaluate(self.length, context) if length < 0: raise IntegerError("length must be non-negative", path=path) data = integer2bytes(obj, length) - if self.swapped: + if evaluate(self.swapped, context): data = data[::-1] stream_write(stream, data, length, path) return obj def _sizeof(self, context, path): try: - length = self.length - if callable(length): - length = length(context) - return length + return evaluate(self.length, context) except (KeyError, AttributeError): raise SizeofError("cannot calculate size, key not found in context", path=path) def _emitparse(self, code): - return "bytes2integer(read_bytes(io, %s)%s, %s)" % (self.length, "[::-1]" if self.swapped else "", self.signed) + if callable(self.swapped): + return "bytes2integer(read_bytes(io, %s)[::-1] if %s else read_bytes(io, %s), %s)" % (self.length, self.swapped, self.length, self.signed, ) + else: + return "bytes2integer(read_bytes(io, %s)%s, %s)" % (self.length, "[::-1]" if self.swapped else "", self.signed, ) def _emitprimitivetype(self, ksy, bitwise): if bitwise: @@ -1133,6 +1133,7 @@ assert not self.swapped return "b%s" % (8*self.length, ) else: + assert not callable(self.swapped) return "%s%s%s" % ("s" if self.signed else "u", self.length, "le" if self.swapped else "be", ) @@ -1148,7 +1149,7 @@ :param length: integer or context lambda, number of bits in the field :param signed: bool, whether the value is signed (two's complement), default is False (unsigned) - :param swapped: bool, whether to swap byte order (little endian), default is False (big endian) + :param swapped: bool or context lambda, whether to swap byte order (little endian), default is False (big endian) :raises StreamError: requested reading negative amount, could not read enough bytes, requested writing different amount than actual data, or could not write all bytes :raises IntegerError: lenght is negative, given a negative value when field is not signed, or not an integer @@ -1173,13 +1174,11 @@ self.swapped = swapped def _parse(self, stream, context, path): - length = self.length - if callable(length): - length = length(context) + length = evaluate(self.length, context) if length < 0: raise IntegerError("length must be non-negative", path=path) data = stream_read(stream, length, path) - if self.swapped: + if evaluate(self.swapped, context): if length & 7: raise IntegerError("little-endianness is only defined for multiples of 8 bits", path=path) data = swapbytes(data) @@ -1190,13 +1189,11 @@ raise IntegerError("value %r is not an integer" % (obj,), path=path) if obj < 0 and not self.signed: raise IntegerError("value %r is negative, but field is not signed" % (obj,), path=path) - length = self.length - if callable(length): - length = length(context) + length = evaluate(self.length, context) if length < 0: raise IntegerError("length must be non-negative", path=path) data = integer2bits(obj, length) - if self.swapped: + if evaluate(self.swapped, context): if length & 7: raise IntegerError("little-endianness is only defined for multiples of 8 bits", path=path) data = swapbytes(data) @@ -1205,15 +1202,15 @@ def _sizeof(self, context, path): try: - length = self.length - if callable(length): - length = length(context) - return length + return evaluate(self.length, context) except (KeyError, AttributeError): raise SizeofError("cannot calculate size, key not found in context", path=path) def _emitparse(self, code): - return "bits2integer(read_bytes(io, %s)%s, %s)" % (self.length, "[::-1]" if self.swapped else "", self.signed, ) + if callable(self.swapped): + return "bits2integer(read_bytes(io, %s)[::-1] if %s else read_bytes(io, %s), %s)" % (self.length, self.swapped, self.length, self.signed, ) + else: + return "bits2integer(read_bytes(io, %s)%s, %s)" % (self.length, "[::-1]" if self.swapped else "", self.signed, ) def _emitprimitivetype(self, ksy, bitwise): assert not self.signed @@ -4243,6 +4240,12 @@ Object is a dictionary with either "data" or "value" keys, or both. + When building, if both the "value" and data "keys" are present, then the + "data" key is used, and the "value" key ignored. This is undesirable in the + case that you parse some data for the purpose of modifying it and writing + it back; in this case, delete the "data" key when modifying the "value" key + to correctly rebuild the former. + :param subcon: Construct instance :raises StreamError: stream is not seekable and tellable diff -Nru construct-2.10.56+dfsg1/construct/lib/containers.py construct-2.10.58+dfsg1/construct/lib/containers.py --- construct-2.10.56+dfsg1/construct/lib/containers.py 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/construct/lib/containers.py 2021-01-27 22:32:16.000000000 +0000 @@ -1,5 +1,6 @@ from construct.lib.py3compat import * import re +import collections globalPrintFullStrings = False @@ -55,7 +56,7 @@ return decorator -class Container(dict): +class Container(collections.OrderedDict): r""" Generic ordered dictionary that allows both key and attribute access, and preserves key order by insertion. Adding keys is preferred using \*\*entrieskw (requires Python 3.6). Equality does NOT check item order. Also provides regex searching. @@ -82,20 +83,12 @@ text = u'utf8 decoded string...' (total 22) value = 123 """ - __slots__ = ["__keys_order__", "__recursion_lock__"] + __slots__ = ["__recursion_lock__"] def __getattr__(self, name): try: if name in self.__slots__: - try: - return object.__getattribute__(self, name) - except AttributeError as e: - if name == "__keys_order__": - r = [] - object.__setattr__(self, "__keys_order__", r) - return r - else: - raise e + return object.__getattribute__(self, name) else: return self[name] except KeyError: @@ -119,63 +112,12 @@ except KeyError: raise AttributeError(name) - def __setitem__(self, key, value): - if key not in self: - self.__keys_order__.append(key) - dict.__setitem__(self, key, value) - - def __delitem__(self, key): - """Removes an item from the Container in linear time O(n).""" - if key in self: - self.__keys_order__.remove(key) - dict.__delitem__(self, key) - - def __init__(self, *args, **entrieskw): - self.__keys_order__ = [] - for arg in args: - if isinstance(arg, dict): - for k,v in arg.items(): - self[k] = v - else: - for k,v in arg: - self[k] = v - for k,v in entrieskw.items(): - self[k] = v - def __call__(self, **entrieskw): """Chains adding new entries to the same container. See ctor.""" for k,v in entrieskw.items(): self[k] = v return self - def keys(self): - return iter(self.__keys_order__) - - def values(self): - return (self[k] for k in self.__keys_order__) - - def items(self): - return ((k, self[k]) for k in self.__keys_order__) - - __iter__ = keys - - def clear(self): - """Removes all items.""" - dict.clear(self) - self.__keys_order__ = [] - - def pop(self, key): - """Removes and returns the value for a given key, raises KeyError if not found.""" - val = dict.pop(self, key) - self.__keys_order__.remove(key) - return val - - def popitem(self): - """Removes and returns the last key and value from order.""" - k = self.__keys_order__.pop() - v = dict.pop(self, k) - return k, v - def update(self, seqordict): """Appends items from another dict/Container or list-of-tuples.""" if isinstance(seqordict, dict): @@ -183,12 +125,6 @@ for k,v in seqordict: self[k] = v - def __getstate__(self): - return self.__keys_order__ - - def __setstate__(self, state): - self.__keys_order__ = state - def copy(self): return Container(self) diff -Nru construct-2.10.56+dfsg1/construct/lib/__init__.py construct-2.10.58+dfsg1/construct/lib/__init__.py --- construct-2.10.56+dfsg1/construct/lib/__init__.py 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/construct/lib/__init__.py 2021-01-27 22:32:16.000000000 +0000 @@ -8,7 +8,6 @@ 'bits2bytes', 'bits2integer', 'byte2int', - 'bytes', 'bytes2bits', 'bytes2integer', 'bytes2integers', diff -Nru construct-2.10.56+dfsg1/construct/lib/py3compat.py construct-2.10.58+dfsg1/construct/lib/py3compat.py --- construct-2.10.56+dfsg1/construct/lib/py3compat.py 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/construct/lib/py3compat.py 2021-01-27 22:32:16.000000000 +0000 @@ -11,14 +11,15 @@ unicodestringtype = str bytestringtype = bytes -INT2BYTE_CACHE = {i:bytes((i,)) for i in range(256)} +INT2BYTE_CACHE = {i:bytes([i]) for i in range(256)} def int2byte(character): """Converts (0 through 255) integer into b'...' character.""" return INT2BYTE_CACHE[character] +BYTE2INT_CACHE = {bytes([i]):i for i in range(256)} def byte2int(character): """Converts b'...' character into (0 through 255) integer.""" - return ord(character) + return BYTE2INT_CACHE[character] def str2bytes(string): """Converts '...' string into b'...' string. On PY2 they are equivalent. On PY3 its utf8 encoded.""" @@ -28,7 +29,7 @@ """Converts b'...' string into '...' string. On PY2 they are equivalent. On PY3 its utf8 decoded.""" return string.decode("utf8") -ITERATEBYTES_CACHE = {i:bytes((i,)) for i in range(256)} +ITERATEBYTES_CACHE = {i:bytes([i]) for i in range(256)} def iteratebytes(data): """Iterates though b'...' string yielding b'...' characters.""" return (ITERATEBYTES_CACHE[i] for i in data) @@ -51,9 +52,6 @@ if isinstance(data, str): return repr(data) -import builtins -bytes = builtins.bytes - def integers2bytes(ints): """Converts integer generator into bytes.""" return bytes(ints) diff -Nru construct-2.10.56+dfsg1/construct/version.py construct-2.10.58+dfsg1/construct/version.py --- construct-2.10.56+dfsg1/construct/version.py 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/construct/version.py 2021-01-27 22:32:16.000000000 +0000 @@ -1,3 +1,3 @@ -version = (2,10,56) -version_string = "2.10.56" -release_date = "2020.01.28" +version = (2,10,58) +version_string = "2.10.58" +release_date = "2021.01.27" diff -Nru construct-2.10.56+dfsg1/debian/changelog construct-2.10.58+dfsg1/debian/changelog --- construct-2.10.56+dfsg1/debian/changelog 2020-11-19 14:18:40.000000000 +0000 +++ construct-2.10.58+dfsg1/debian/changelog 2021-01-29 06:21:42.000000000 +0000 @@ -1,8 +1,16 @@ -construct (2.10.56+dfsg1-1~cloud0) focal-wallaby; urgency=medium +construct (2.10.58+dfsg1-1~cloud0) focal-wallaby; urgency=medium - * New upstream release for the Ubuntu Cloud Archive. + * New update for the Ubuntu Cloud Archive. - -- Openstack Ubuntu Testing Bot Thu, 19 Nov 2020 14:18:40 +0000 + -- Openstack Ubuntu Testing Bot Fri, 29 Jan 2021 06:21:42 +0000 + +construct (2.10.58+dfsg1-1) unstable; urgency=medium + + * New upstream version 2.10.58+dfsg1 + * d/contol: debhelper-compat to 13 (from 12) + * d/control: bump Standard-Version to 4.5.1. No changes needed. + + -- Henry-Nicolas Tourneur Thu, 28 Jan 2021 18:45:09 +0000 construct (2.10.56+dfsg1-1) unstable; urgency=medium diff -Nru construct-2.10.56+dfsg1/debian/control construct-2.10.58+dfsg1/debian/control --- construct-2.10.56+dfsg1/debian/control 2020-10-16 13:04:34.000000000 +0000 +++ construct-2.10.58+dfsg1/debian/control 2021-01-28 18:45:09.000000000 +0000 @@ -3,14 +3,14 @@ Priority: optional Maintainer: Debian Python Team Uploaders: Henry-Nicolas Tourneur -Build-Depends: debhelper-compat (= 12), +Build-Depends: debhelper-compat (= 13), dh-python, python3-all (>= 3.6), python3-arrow, python3-numpy, python3-ruamel.yaml, python3-setuptools -Standards-Version: 4.5.0 +Standards-Version: 4.5.1 X-Python3-Version: >= 3.6 Rules-Requires-Root: no Testsuite: autopkgtest-pkg-python diff -Nru construct-2.10.56+dfsg1/docs/advanced.rst construct-2.10.58+dfsg1/docs/advanced.rst --- construct-2.10.56+dfsg1/docs/advanced.rst 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/docs/advanced.rst 2021-01-27 22:32:16.000000000 +0000 @@ -21,7 +21,7 @@ Short <--> Int16ub Int <--> Int32ub Long <--> Int64ub - Half <--> Float16b (Python 3.6 and above only) + Half <--> Float16b Single <--> Float32b Double <--> Float64b @@ -30,7 +30,7 @@ >>> VarInt.build(1234567890) b'\xd2\x85\xd8\xcc\x04' -Long integers (or those of particularly odd sizes) can be encoded using a fixed-sized `BytesInteger`. Here is a 128-bit integer. +Long integers (or those of particularly odd sizes) can be encoded using a `BytesInteger`. Here is a 128-bit integer. >>> BytesInteger(16).build(255) b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff' diff -Nru construct-2.10.56+dfsg1/docs/compilation.rst construct-2.10.58+dfsg1/docs/compilation.rst --- construct-2.10.56+dfsg1/docs/compilation.rst 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/docs/compilation.rst 2021-01-27 22:32:16.000000000 +0000 @@ -26,6 +26,8 @@ Sizeof is applied during compilation (not during parsing and building) +Lambdas (unlike this expressions) are not compilable. + Exceptions do not include `path` information Struct Sequence FocusedSeq Union LazyStruct do not support `_subcons _stream` context entries diff -Nru construct-2.10.56+dfsg1/docs/extending.rst construct-2.10.58+dfsg1/docs/extending.rst --- construct-2.10.56+dfsg1/docs/extending.rst 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/docs/extending.rst 2021-01-27 22:32:16.000000000 +0000 @@ -70,7 +70,6 @@ self.name = subcon.name self.subcon = subcon self.flagbuildnone = subcon.flagbuildnone - self.flagembedded = subcon.flagembedded def _parse(self, stream, context, path): obj = self.subcon._parse(stream, context, path) diff -Nru construct-2.10.56+dfsg1/docs/intro.rst construct-2.10.58+dfsg1/docs/intro.rst --- construct-2.10.56+dfsg1/docs/intro.rst 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/docs/intro.rst 2021-01-27 22:32:16.000000000 +0000 @@ -64,7 +64,7 @@ Requirements -------------- -Construct should run on CPython 3.6 3.7 3.8 and PyPy 3.5 implementations. Recommended is newest CPython and PyPy because they support ordered keyword arguments, and also PyPy achieves much better performance. Therefore PyPy would be most recommended, despite the 3.5 version. +Construct should run on CPython 3.6 3.7 3.8 3.9 and PyPy implementations. PyPy achieves much better performance. Therefore PyPy would be somewhat recommended. Following modules are needed only if you want to use certain features: diff -Nru construct-2.10.56+dfsg1/docs/meta.rst construct-2.10.58+dfsg1/docs/meta.rst --- construct-2.10.56+dfsg1/docs/meta.rst 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/docs/meta.rst 2021-01-27 22:32:16.000000000 +0000 @@ -213,4 +213,8 @@ >>> lambda this: this.value in (1, 2, 3) +Indexing (square brackets) do not work in this expressions. Use a lambda: + +>>> lambda this: this.list[this.index] + Lambdas (unlike this expressions) are not compilable. diff -Nru construct-2.10.56+dfsg1/docs/transition210.rst construct-2.10.58+dfsg1/docs/transition210.rst --- construct-2.10.56+dfsg1/docs/transition210.rst 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/docs/transition210.rst 2021-01-27 22:32:16.000000000 +0000 @@ -6,10 +6,14 @@ Overall ========== -Dropped support for Python 2.7 and 3.5 (pypy 3.5 still supported) +Dropped support for Python 2.7 and 3.5 (pypy is also supported) Bytes GreedyBytes can build from bytearrays (not just bytes) Embedded and EmbeddedSwitch were permanently removed Exceptions always display path information + +build_file() opens a file for both reading and writing + +BytesInteger BitsInteger can take lambda for swapped parameter diff -Nru construct-2.10.56+dfsg1/docs/tunneling.rst construct-2.10.58+dfsg1/docs/tunneling.rst --- construct-2.10.56+dfsg1/docs/tunneling.rst 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/docs/tunneling.rst 2021-01-27 22:32:16.000000000 +0000 @@ -38,6 +38,14 @@ >>> d.parse(b"\x01") b'\x01\x00\x00\x00\x00\x00\x00\x00' +In case that endianness is determined at parse/build time, you can pass endianness (`swapped` parameter) by the context: + +>>> d = BytesInteger(2, swapped=this.swapped) +>>> d.build(1, swapped=True) +b'\x01\x00' +>>> d = BitsInteger(16, swapped=this.swapped) +>>> d.build(1, swapped=True) +b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' Working with bytes subsets -------------------------------------------- diff -Nru construct-2.10.56+dfsg1/.github/workflows/main.yml construct-2.10.58+dfsg1/.github/workflows/main.yml --- construct-2.10.56+dfsg1/.github/workflows/main.yml 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/.github/workflows/main.yml 2021-01-27 22:32:16.000000000 +0000 @@ -7,7 +7,7 @@ runs-on: ubuntu-latest strategy: matrix: - python-version: [ '3.6', '3.7', '3.8', 'pypy3' ] + python-version: [ '3.6', '3.7', '3.8', '3.9', 'pypy3' ] name: Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v1 diff -Nru construct-2.10.56+dfsg1/Makefile construct-2.10.58+dfsg1/Makefile --- construct-2.10.56+dfsg1/Makefile 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/Makefile 2021-01-27 22:32:16.000000000 +0000 @@ -26,6 +26,7 @@ apt-get install python3.6 python3-sphinx --upgrade python3.6 -m pip install pytest pytest-benchmark pytest-cov twine --upgrade python3.6 -m pip install enum34 numpy arrow ruamel.yaml --upgrade + version: ./version-increment diff -Nru construct-2.10.56+dfsg1/README.rst construct-2.10.58+dfsg1/README.rst --- construct-2.10.56+dfsg1/README.rst 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/README.rst 2021-01-27 22:32:16.000000000 +0000 @@ -1,4 +1,4 @@ -Construct 2.9 +Construct 2.10 =================== Construct is a powerful **declarative** and **symmetrical** parser and builder for binary data. diff -Nru construct-2.10.56+dfsg1/.readthedocs.yml construct-2.10.58+dfsg1/.readthedocs.yml --- construct-2.10.56+dfsg1/.readthedocs.yml 1970-01-01 00:00:00.000000000 +0000 +++ construct-2.10.58+dfsg1/.readthedocs.yml 2021-01-27 22:32:16.000000000 +0000 @@ -0,0 +1,17 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - method: pip + path: . diff -Nru construct-2.10.56+dfsg1/tests/declarativeunittest.py construct-2.10.58+dfsg1/tests/declarativeunittest.py --- construct-2.10.56+dfsg1/tests/declarativeunittest.py 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/tests/declarativeunittest.py 2021-01-27 22:32:16.000000000 +0000 @@ -3,7 +3,7 @@ skip = pytest.mark.skip skipif = pytest.mark.skipif -import os, math, random, collections, itertools, io, hashlib, binascii +import os, math, random, collections, itertools, io, hashlib, binascii, tempfile from construct import * from construct.lib import * diff -Nru construct-2.10.56+dfsg1/tests/lib/test_containers_dict.py construct-2.10.58+dfsg1/tests/lib/test_containers_dict.py --- construct-2.10.56+dfsg1/tests/lib/test_containers_dict.py 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/tests/lib/test_containers_dict.py 2021-01-27 22:32:16.000000000 +0000 @@ -100,7 +100,7 @@ assert c.popitem() == ("c",3) assert c.popitem() == ("b",2) assert c.popitem() == ("a",1) - assert raises(c.popitem) == IndexError + assert raises(c.popitem) == KeyError def test_update_dict(): c = Container(a=1)(b=2)(c=3)(d=4) diff -Nru construct-2.10.56+dfsg1/tests/lib/test_py3compat.py construct-2.10.58+dfsg1/tests/lib/test_py3compat.py --- construct-2.10.56+dfsg1/tests/lib/test_py3compat.py 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/tests/lib/test_py3compat.py 2021-01-27 22:32:16.000000000 +0000 @@ -2,9 +2,6 @@ from construct.lib.py3compat import * -def test_version(): - assert PY2 or PY3 - def test_int_byte(): assert int2byte(5) == b"\x05" assert int2byte(255) == b"\xff" @@ -30,6 +27,7 @@ assert bytes() == b'' assert bytes(2) == b'\x00\x00' assert bytes([1,2]) == b'\x01\x02' + assert bytes((1,)) == b'\x01' def test_bytes_integers(): assert bytes2integers(b'abc')[0] == 97 diff -Nru construct-2.10.56+dfsg1/tests/test_core.py construct-2.10.58+dfsg1/tests/test_core.py --- construct-2.10.56+dfsg1/tests/test_core.py 2020-01-28 18:55:29.000000000 +0000 +++ construct-2.10.58+dfsg1/tests/test_core.py 2021-01-27 22:32:16.000000000 +0000 @@ -136,6 +136,9 @@ d = BytesInteger(4, signed=True, swapped=False) common(d, b"\x01\x02\x03\x04", 0x01020304, 4) common(d, b"\xff\xff\xff\xff", -1, 4) + d = BytesInteger(4, signed=False, swapped=this.swapped) + common(d, b"\x01\x02\x03\x04", 0x01020304, 4, swapped=False) + common(d, b"\x04\x03\x02\x01", 0x01020304, 4, swapped=True) assert raises(BytesInteger(this.missing).sizeof) == SizeofError assert raises(BytesInteger(4, signed=False).build, -1) == IntegerError common(BytesInteger(0), b"", 0, 0) @@ -147,6 +150,9 @@ common(d, b"\x01\x01\x01\x01\x01\x01\x01\x01", -1, 8) d = BitsInteger(16, swapped=True) common(d, b"\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01", 0xff00, 16) + d = BitsInteger(16, swapped=this.swapped) + common(d, b"\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00", 0xff00, 16, swapped=False) + common(d, b"\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01", 0xff00, 16, swapped=True) assert raises(BitsInteger(this.missing).sizeof) == SizeofError assert raises(BitsInteger(8, signed=False).build, -1) == IntegerError common(BitsInteger(0), b"", 0, 0) @@ -930,6 +936,13 @@ d = Struct("a"/RawCopy(Byte), "check"/Check(this.a.value == 255)) assert d.build(dict(a=dict(value=255))) == b"\xff" +def test_rawcopy_issue_888(): + # If you use build_file() on a RawCopy that has only a value defined, then + # RawCopy._build may also attempt to read from the file, which won't work + # if build_file opened the file for writing only. + d = RawCopy(Byte) + d.build_file(dict(value=0), filename="example_888") + def test_byteswapped(): d = ByteSwapped(Bytes(5)) common(d, b"12345", b"54321", 5) @@ -1157,6 +1170,49 @@ assert st.parse(b"\x00\x00\x00") == Container(vals=[0, 0])(checksum=0) assert raises(st.parse, b"\x00\x00\x01") == ChecksumError +def test_checksum_warnings_issue_841(): + + class ChecksumWarning(Warning): + pass + class Checksum2(Construct): + def __init__(self, checksumfield, hashfunc, bytesfunc): + super().__init__() + self.checksumfield = checksumfield + self.hashfunc = hashfunc + self.bytesfunc = bytesfunc + self.flagbuildnone = True + + def _parse(self, stream, context, path): + hash1 = self.checksumfield._parsereport(stream, context, path) + hash2 = self.hashfunc(self.bytesfunc(context)) + if hash1 != hash2: + import warnings + warnings.warn( + "wrong checksum, read %r, computed %r, path %s" % ( + hash1 if not isinstance(hash1,bytestringtype) else binascii.hexlify(hash1), + hash2 if not isinstance(hash2,bytestringtype) else binascii.hexlify(hash2), + path), + ChecksumWarning + ) + return hash1 + + def _build(self, obj, stream, context, path): + hash2 = self.hashfunc(self.bytesfunc(context)) + self.checksumfield._build(hash2, stream, context, path) + return hash2 + + def _sizeof(self, context, path): + return self.checksumfield._sizeof(context, path) + + d = Struct( + "fields" / RawCopy(Struct( + "a" / Byte, + "b" / Byte, + )), + "checksum" / Checksum2(Bytes(64), lambda data: hashlib.sha512(data).digest(), this.fields.data), + ) + d.parse(bytes(66)) + def test_compressed_zlib(): zeros = bytes(10000) d = Compressed(GreedyBytes, "zlib")