. "$BST_LIB" # BST-TIMEOUT: 1500 # THREE python3 starts, and they are the whole cost. # # Everything else below reads the filesystem or dpkg with # # shell builtins, or greps the output of one of the three. # # # # The three are: a bare `import'; a second import whose # # STDERR is the assertion (`import-quiet'); and the probe, # # which takes ONE start for a hundred and thirty-one # # measurements. There is no fourth -- pyproject.toml has no # # [project.scripts] table, so this package installs NO # # executable and there is nothing to reach through a # # shebang. # # # # 1500 is 500s per start, against the ~60s a python3 start # # has measured at on this arm, and matches the per-start # # margin 99-python3-pyparsing settled on. # # # # NOTHING HERE IS QUADRATIC IN ANYTHING. The widest field # # in the whole file is 64 bits and the longest fixture is # # the 181-bit main format; every case is a few dozen bits # # packed or unpacked. # # # # NOT MEASURED ON TARGET YET: this port's first on-target # # run is the batch verify, and the measured elapsed replaces # # this note in the test plan rather than in this comment, # # which would cost an image build to keep the shipped suite # # byte-identical. # # python3-bitstruct 8.19.0 (meta-openembedded/meta-python # recipes-devtools/python/python3-bitstruct_8.19.0.bb, `inherit pypi # setuptools3', PyPI sdist, no patches upstream) on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE DOES. # ============================================================================ # # bitstruct is `struct' for BIT fields. A format string -- `u5s3f32b1p2' and the # like -- describes a C bit-field layout whose members need not sit on byte # boundaries, and pack/unpack convert Python values to and from it. It is the # module to reach for when a wire protocol or a register map is specified in # bits rather than in bytes. # # ============================================================================ # THE ONE THING THIS PORT DECIDED. # ============================================================================ # # bitstruct ships its codec twice: the pure-Python `bitstruct/__init__.py', and # `bitstruct.c', a CPython extension built from src/bitstruct/c.c and # bitstream.c answering the same surface. # # THIS IMAGE SHIPS THE PURE-PYTHON HALF AND NO EXTENSION AT ALL. The stock # recipe builds the extension and it LINKS -- measured, 166515 bytes of real # wasm side module with a dylink.0 section, exporting PyInit_c -- but it can # never load: it carries 48 env.Py* / env._Py* imports and 9 GOT.mem Py* data # imports, and this image's python3.12 exports 35 symbols, none of them Py*, # with no libpython3.12.so shipped to bind against instead (BRINTOS-2046, first # measured on python3-bitarray in BRINTOS-2032). So python3-bitstruct_%.bbappend # patches setup.py to honour BITSTRUCT_PURE_PYTHON and sets it for class-target. # # THE DIFFERENCE FROM python3-cbor2 AND python3-crcmod, WHICH DECIDED THE SAME # THING FOR THE SAME REASON. In those two a try/except at import time chooses # between the halves, so declining the accelerator is invisible to a caller. # Here there is NO fallback path, because none is needed: `bitstruct/__init__.py' # does not reference the extension at all, and a caller who wants it writes # `import bitstruct.c as bitstruct' by name. So `import bitstruct' is unaffected # either way, and the absence has to be asserted DIRECTLY -- it cannot be # inferred from any behavioural case in this file. # # THAT IS WHY `pkg-no-so', `no-c-ext-any' AND `c-ext-absent' ARE NOT HYGIENE # CASES. They are the only statement in this suite about which of the two # implementations exists on the image at all. # # ============================================================================ # WHY THE PACKAGING SIGNATURE IS THE WHEEL AND NOT top_level.txt. # ============================================================================ # # 99-python3-cbor2 reads `top_level.txt' as the shortest proof that the # accelerator was not packaged, because cbor2's extension is a TOP-LEVEL module # (`_cbor2') and so appears there. bitstruct's is a SUBMODULE of the package, # so top_level.txt reads `bitstruct' either way -- measured on both .debs, and # copying cbor2's case here would have been a case that cannot fail. # # What does differ is the wheel. Measured on this pin, from the two .debs: # # stock Root-Is-Purelib: false Tag: cp312-cp312-linux_x86_64 # declined Root-Is-Purelib: true Tag: py3-none-any # # so `dist-info-purelib' and `dist-info-tag' say from the packaging side, rather # than from a file check, that the build produced a pure-Python distribution. # (The stock tag naming x86_64 on a wasm32 cross build is setuptools tagging the # wheel for the BUILD host. It is not what this port turns on, and it is noted # here only so it is not read as a finding.) # # ============================================================================ # THE C SOURCES STILL SHIP, AND THAT IS UPSTREAM'S DOING, NOT A LEFTOVER. # ============================================================================ # # `bitstruct/c.c', `bitstream.c' and `bitstream.h' are in the .deb even with the # extension declined -- MANIFEST.in `include's them and setuptools' modern # include_package_data default packages files that sit inside the package # directory. They are ~92 KB of TEXT that nothing reads at runtime. They are # asserted PRESENT below rather than excluded quietly, so that a future release # which stops shipping them is a visible change rather than a silent one, and so # that nobody reads them as an incompletely-declined accelerator. # # ============================================================================ # WHERE THE EXPECTED VALUES CAME FROM. # ============================================================================ # # Every expectation in this file is the OUTPUT of a control run of # data/python3-bitstruct-probe.py -- bitstruct 8.19.0, pure-Python, on CPython # 3.12.13 taken from this build's own python3-native sysroot rather than from a # distro python, which is the same version pair the target runs. They are not # readings of the source. # # ============================================================================ # THE THREE FINDINGS MOST LIKELY TO BE MISREAD AS PORT BUGS. # ============================================================================ # # A FIELD SITS AT THE TOP OF ITS BYTE. `pack('u3', 5)' is 0xa0, not 0x05: bits # are laid down most-significant-first from the start of the buffer and the tail # of the last byte is ZERO FILL. Every per-type literal below is asserted at a # width that straddles a byte boundary for this reason -- at 8 and 16 bits # several wrong implementations agree with the right one. # # calcsize REPORTS THE DECLARED WIDTH, NOT THE SHIPPED ONE. `calcsize('u12')' is # 12 while `len(pack('u12', x))' is 2 bytes. The gap is the tail padding and is # documented; both numbers are pinned so the distinction is recorded rather than # rediscovered. # # `<' AND `>' MEAN TWO DIFFERENT THINGS DEPENDING ON WHERE THEY SIT. As a # PREFIX on a field they are that field's BIT order; as a SUFFIX on the whole # format string they are the BYTE order. They compose -- `/dev/null 2>&1" # The C SOURCES, by contrast, are expected. See the header: MANIFEST.in ships # them and they are inert text. Asserted present so that a release which drops # them is visible, and so they are not mistaken for a half-declined accelerator. check $P/c-source-present test -s "$PKG/c.c" check $P/bitstream-c-present test -s "$PKG/bitstream.c" check $P/bitstream-h-present test -s "$PKG/bitstream.h" # NO EXECUTABLE. pyproject.toml carries no [project.scripts] table, so unlike # python3-cbor2 this package installs nothing under /usr/bin. check $P/no-bin-script sh -c "! ls /usr/bin/bitstruct* >/dev/null 2>&1" # --------------------------------------------------------- the dist-info ------ check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-version "Version: 8.19.0" cat "$DI/METADATA" check_out $P/dist-info-name "Name: bitstruct" cat "$DI/METADATA" check $P/dist-info-wheel test -f "$DI/WHEEL" # THE BBAPPEND'S SIGNATURE IN THE METADATA. With the extension built the wheel # is platlib and its tag is a platform tag; with it declined the wheel is pure # and the tag is py3-none-any. Measured both ways on this pin -- see the header. check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/dist-info-tag "Tag: py3-none-any" cat "$DI/WHEEL" # top_level.txt names `bitstruct' EITHER WAY, because the extension is a # submodule rather than a top-level module. Asserted for completeness, and the # header says why it is not the accelerator signature it is in 99-python3-cbor2. check_eq $P/dist-info-top-level "bitstruct" "$(cat "$DI/top_level.txt" 2>/dev/null)" # RECORD is the wheel's own file manifest, so a .so listed there would have been # installed whatever the filesystem currently looks like. check_eq $P/record-no-so "" \ "$(grep -c '\.so,' "$DI/RECORD" 2>/dev/null | grep -v '^0$')" # ------------------------------------------------------------ the .deb list --- cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" # NO SHARED OBJECT ANYWHERE IN THE PACKAGE. The file checks above name the one # path the extension would take; this one catches it under any name. check_eq $P/pkg-no-so "" \ "$(grep -c '\.so$' "$W/pkgf" 2>/dev/null | grep -v '^0$')" # Everything shipped is Python, metadata, or one of the three inert C sources. # A file that is none of those is a packaging change nobody described. # The list is read from a copy under $TMPDIR: a `while ... done < file' whose # source is on the virtiofs root ENDS THE SUITE (brintos/linux#73). _bad="" while IFS= read -r _f; do case "$_f" in ''|/usr/lib/python3.12/site-packages) continue ;; */) continue ;; esac [ -d "$_f" ] && continue case "$_f" in *.py|*.pyc) ;; */c.c|*/bitstream.c|*/bitstream.h) ;; */METADATA|*/WHEEL|*/RECORD|*/INSTALLER|*/LICENSE|*/LICENSE.txt) ;; */top_level.txt) ;; *) _bad="$_bad $_f" ;; esac done < "$W/pkgf" check_eq $P/pkg-only-python-and-c "" "$_bad" # ------------------------------------------------------------- dpkg status ---- # The package arrives INSTALLED -- it is on the image's IMAGE_INSTALL -- so # there is nothing for this suite to `dpkg -i'. What is worth asserting is that # dpkg agrees it is installed and that its dependency set is what the recipe # declares. sed -n '/^Package: python3-bitstruct$/,/^$/p' /var/lib/dpkg/status > "$W/st" 2>/dev/null check $P/pkg-known test -s "$W/st" check_out $P/pkg-installed "Status: install ok installed" cat "$W/st" check_out $P/pkg-version "Version: 8.19.0" cat "$W/st" # THE DEPENDENCY SET, AND IT IS ONE ENTRY. python3-bitstruct declares # python3-core and nothing else: the module imports binascii, re, struct and # io.BytesIO at module scope, and every one of those is in python3-core or is a # builtin of this interpreter. The recipe carries no DEPENDS and no third-party # RDEPENDS, which is why this port waited on no other package. grep '^Depends:' "$W/st" > "$W/dep" 2>/dev/null || : > "$W/dep" check $P/pkg-depends-line test -s "$W/dep" check_out $P/pkg-depends-python3-core "python3-core" cat "$W/dep" # ----------------------------------------------------------------- import ----- check $P/import python3 -c 'import bitstruct' # AND IT MUST BE SILENT. A build that still shipped an unloadable extension # would not fail this import -- nothing in __init__.py touches it -- but a # loader diagnostic on stderr would still surface here. An empty stderr is # therefore worth asserting even though the import path is independent of it. check_eq $P/import-quiet "" \ "$(python3 -c 'import bitstruct' 2>&1 >/dev/null)" # ------------------------------------------------------------------ probe ----- if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "probe missing at $PROBE" exit 0 fi bst_ok $P/probe-present # 900s for the one start that does a hundred and thirty-one measurements, none # of them wider than 64 bits. On overrun bst_probe names the last line the probe # printed, which names the case it stopped at. bst_probe $P/probe-run "$W/probe" 900 python3 "$PROBE" OUT="$W/probe" # One `grep' per value, split on the FIRST `=' so a value containing one is # safe. Every key the probe emits is [a-z0-9_]+. pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } # THE PROBE RAN TO THE END AND EMITTED EVERYTHING. Without these two, a probe # that died half way would report every case after the death as a mismatch # against an empty string, which reads as a hundred behavioural failures instead # of one truncated transcript. check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "131" "$(pv emitted_count)" # ---------------------------------------------------------------- identity -- check_eq $P/module-answered "$PKG/__init__.py" "$(pv module_file)" check_eq $P/is-package "yes" "$(pv is_package)" check_eq $P/version "8.19.0" "$(pv version)" # THE ACCELERATOR IS NOT IMPORTABLE, asserted from inside the interpreter rather # than off the filesystem. `ModuleNotFoundError' -- not ImportError, and not a # loader diagnostic -- is what a submodule that was never packaged produces, and # it is the difference between "declined at build time" and "shipped and failed # to bind". check_eq $P/c-ext-absent "ModuleNotFoundError: No module named 'bitstruct.c'" \ "$(pv c_ext_import)" check_eq $P/c-ext-not-in-modules "no" "$(pv c_ext_in_modules)" # ------------------------------------------------------------ the API set ---- for _n in pack unpack pack_into unpack_from pack_dict unpack_dict \ pack_into_dict unpack_from_dict calcsize byteswap compile \ compiledformat compiledformatdict error; do check_eq $P/has-$_n "yes" "$(pv has_$_n)" done check_eq $P/error-is-exception "yes" "$(pv error_is_exception)" # ------------------------------------------------------- per-type literals --- # Each is a single field packed ALONE, so the bytes are that field's encoding # and nothing else's. See the header on why they are left-aligned. check_eq $P/u3-5 "a0" "$(pv u3_5)" check_eq $P/u5-17 "88" "$(pv u5_17)" check_eq $P/u12-2748 "abc0" "$(pv u12_2748)" check_eq $P/u17-86421 "a8ca80" "$(pv u17_86421)" check_eq $P/s3-m3 "a0" "$(pv s3_m3)" check_eq $P/s5-m9 "b8" "$(pv s5_m9)" check_eq $P/s12-m1234 "b2e0" "$(pv s12_m1234)" check_eq $P/s17-m54321 "95e780" "$(pv s17_m54321)" # IEEE 754 big-endian at all three widths the format language defines. check_eq $P/f16-1p5 "3e00" "$(pv f16_1p5)" check_eq $P/f32-1p5 "3fc00000" "$(pv f32_1p5)" check_eq $P/f64-m2p25 "c002000000000000" "$(pv f64_m2p25)" check_eq $P/f32-0 "00000000" "$(pv f32_0)" # b8 of True is 0x01 and NOT 0x80: a wider `b' is the one bit zero-extended on # the LEFT, which is the opposite of how the integer types left-align. check_eq $P/b1-true "80" "$(pv b1_true)" check_eq $P/b1-false "00" "$(pv b1_false)" check_eq $P/b8-true "01" "$(pv b8_true)" check_eq $P/r16-dead "dead" "$(pv r16_dead)" check_eq $P/r24-short "010200" "$(pv r24_short)" check_eq $P/t24-abc "616263" "$(pv t24_abc)" check_eq $P/t40-short "6869000000" "$(pv t40_short)" # `p' is zero fill and `P' is one fill, and neither consumes an argument -- # which is the whole reason the two exist as separate types. check_eq $P/p8-alone "00" "$(pv p8_alone)" check_eq $P/cap-p8-alone "ff" "$(pv cap_p8_alone)" check_eq $P/u4p4 "50" "$(pv u4p4_ordering)" check_eq $P/u4-cap-p4 "5f" "$(pv u4cap_p4_ordering)" # ----------------------------------------------- width not a multiple of 8 --- check_eq $P/calcsize-u12 "12" "$(pv calcsize_u12)" check_eq $P/u12-packed-len "2" "$(pv u12_packed_len)" check_eq $P/calcsize-u1s3p4 "8" "$(pv calcsize_u1s3p4)" check_eq $P/calcsize-main "181" "$(pv calcsize_main)" check_eq $P/tail-pad-u12 "fff0" "$(pv tail_pad_u12)" check_eq $P/tail-pad-u9 "ff80" "$(pv tail_pad_u9)" # ------------------------------------------------------------ main corpus ---- # One format carrying every type the language defines, 181 declared bits. check_eq $P/main-packed \ "8dabc95e79fe000006001000000000000585898f7ab400" "$(pv main_packed)" check_eq $P/main-packed-len "23" "$(pv main_packed_len)" check_eq $P/main-unpacked \ "(17, -3, 2748, -54321, 1.5, -2.25, True, 'abc', b'\\xde\\xad')" \ "$(pv main_unpacked)" check_eq $P/main-roundtrip "yes" "$(pv main_roundtrip)" # --------------------------------------------------------- the two orders ---- # A PREFIX is bit order, a SUFFIX is byte order. Both asserted at 12 bits, # because at 8 or 16 several wrong implementations agree with the right one. check_eq $P/default-u12 "abc0" "$(pv default_u12)" check_eq $P/msb-suffix-u12 "abc0" "$(pv msb_suffix_u12)" check_eq $P/lsb-suffix-u12 "bca0" "$(pv lsb_suffix_u12)" check_eq $P/bitorder-lt-u12 "3d50" "$(pv bitorder_lt_u12)" check_eq $P/bitorder-gt-u12 "abc0" "$(pv bitorder_gt_u12)" check_eq $P/bitorder-lt-u5 "68" "$(pv bitorder_lt_u5)" check_eq $P/both-lt-u12 "d530" "$(pv both_lt_u12)" check_eq $P/default-is-msb "yes" "$(pv default_is_msb)" # Two fields is where byte order actually shows: with one there is nothing to # reorder against. check_eq $P/two-field-msb "abcdef" "$(pv two_field_msb)" check_eq $P/two-field-lsb "bcafde" "$(pv two_field_lsb)" # Endianness is STICKY -- a field with no prefix inherits the previous field's. check_eq $P/sticky-endianness "f0f0" "$(pv sticky_endianness)" check_eq $P/sticky-reset "f00f" "$(pv sticky_reset)" check_eq $P/unpack-lsb-suffix "(2748,)" "$(pv unpack_lsb_suffix)" check_eq $P/unpack-bitorder-lt "(2748,)" "$(pv unpack_bitorder_lt)" # byteswap is the third ordering control, and it is a BYTE-level operation on an # already-packed buffer rather than a format modifier. check_eq $P/byteswap-24 "110055443322" "$(pv byteswap_24)" check_eq $P/byteswap-1234 "00020105040309080706" "$(pv byteswap_1234)" # ------------------------------------------------------------- the extremes -- # Both ends of every signed width, the value one past each end, and the round # trip back. The refusals are asserted WITH THEIR MESSAGE, because the message # names the bound and a port that silently truncated would raise nothing at all. check_eq $P/s3-min "80" "$(pv s3_min)" check_eq $P/s3-max "60" "$(pv s3_max)" check_eq $P/s3-min-rt "-4" "$(pv s3_min_rt)" check_eq $P/s3-max-rt "3" "$(pv s3_max_rt)" check_eq $P/s3-under 'Error: "s3" requires -4 <= integer <= 3 (got -5)' "$(pv s3_under)" check_eq $P/s3-over 'Error: "s3" requires -4 <= integer <= 3 (got 4)' "$(pv s3_over)" check_eq $P/s5-min "80" "$(pv s5_min)" check_eq $P/s5-max "78" "$(pv s5_max)" check_eq $P/s5-min-rt "-16" "$(pv s5_min_rt)" check_eq $P/s5-max-rt "15" "$(pv s5_max_rt)" check_eq $P/s5-under 'Error: "s5" requires -16 <= integer <= 15 (got -17)' "$(pv s5_under)" check_eq $P/s5-over 'Error: "s5" requires -16 <= integer <= 15 (got 16)' "$(pv s5_over)" check_eq $P/s12-min "8000" "$(pv s12_min)" check_eq $P/s12-max "7ff0" "$(pv s12_max)" check_eq $P/s12-min-rt "-2048" "$(pv s12_min_rt)" check_eq $P/s12-max-rt "2047" "$(pv s12_max_rt)" check_eq $P/s12-under 'Error: "s12" requires -2048 <= integer <= 2047 (got -2049)' "$(pv s12_under)" check_eq $P/s12-over 'Error: "s12" requires -2048 <= integer <= 2047 (got 2048)' "$(pv s12_over)" check_eq $P/s17-min "800000" "$(pv s17_min)" check_eq $P/s17-max "7fff80" "$(pv s17_max)" check_eq $P/s17-min-rt "-65536" "$(pv s17_min_rt)" check_eq $P/s17-max-rt "65535" "$(pv s17_max_rt)" check_eq $P/s17-under 'Error: "s17" requires -65536 <= integer <= 65535 (got -65537)' "$(pv s17_under)" check_eq $P/s17-over 'Error: "s17" requires -65536 <= integer <= 65535 (got 65536)' "$(pv s17_over)" check_eq $P/u3-max "e0" "$(pv u3_max)" check_eq $P/u5-max "f8" "$(pv u5_max)" check_eq $P/u12-max "fff0" "$(pv u12_max)" check_eq $P/u17-max "ffff80" "$(pv u17_max)" check_eq $P/u3-over 'Error: "u3" requires 0 <= integer <= 7 (got 8)' "$(pv u3_over)" check_eq $P/u5-over 'Error: "u5" requires 0 <= integer <= 31 (got 32)' "$(pv u5_over)" check_eq $P/u12-over 'Error: "u12" requires 0 <= integer <= 4095 (got 4096)' "$(pv u12_over)" check_eq $P/u17-over 'Error: "u17" requires 0 <= integer <= 131071 (got 131072)' "$(pv u17_over)" check_eq $P/u3-negative 'Error: "u3" requires 0 <= integer <= 7 (got -1)' "$(pv u3_negative)" check_eq $P/u5-negative 'Error: "u5" requires 0 <= integer <= 31 (got -1)' "$(pv u5_negative)" check_eq $P/u12-negative 'Error: "u12" requires 0 <= integer <= 4095 (got -1)' "$(pv u12_negative)" check_eq $P/u17-negative 'Error: "u17" requires 0 <= integer <= 131071 (got -1)' "$(pv u17_negative)" # ------------------------------------------------------ pack_dict / unpack ---- check_eq $P/dict-packed \ "8dabc95e79fe000006001000000000000585898f7ab400" "$(pv dict_packed)" # THE ONE THAT MATTERS: the named and positional forms must produce IDENTICAL # bytes. If they ever differ, the name list is being applied in the wrong order # -- and every other dict case here would still pass. check_eq $P/dict-matches-positional "yes" "$(pv dict_matches_positional)" check_eq $P/dict-roundtrip "yes" "$(pv dict_roundtrip)" check_eq $P/dict-unpacked-keys \ "fifth,first,flag,fourth,raw,second,sixth,text,third" \ "$(pv dict_unpacked_keys)" # Padding takes no name, so a format with `p' needs a name list SHORTER than its # field count. Getting this wrong is the commonest pack_dict mistake. check_eq $P/dict-padding-names "5030" "$(pv dict_padding_names)" check_eq $P/dict-missing-name "Error: 'b' not found in data dictionary" \ "$(pv dict_missing_name)" # ------------------------------------------------- pack_into / unpack_from ---- check_eq $P/pack-into-offset4 "0abc00" "$(pv pack_into_offset4)" check_eq $P/unpack-from-offset4 "(2748,)" "$(pv unpack_from_offset4)" check_eq $P/unpack-from-offset0 "(2748,)" "$(pv unpack_from_offset0)" # ------------------------------------------------------------- compile() ----- # A compiled format that disagreed with the one-shot call would make every # cached parse a silent divergence. check_eq $P/compiled-type "CompiledFormat" "$(pv compiled_type)" check_eq $P/compiled-dict-type "CompiledFormatDict" "$(pv compiled_dict_type)" check_eq $P/compiled-matches "yes" "$(pv compiled_matches)" check_eq $P/compiled-calcsize "181" "$(pv compiled_calcsize)" # ------------------------------------------------------------- refusals ------ # Each asserted WITH its message: a port that raised the right class for the # wrong reason would otherwise pass. check_eq $P/bad-char "Error: bad char 'q' in format" "$(pv bad_char)" check_eq $P/zero-width "Error: bad format 'u0'" "$(pv zero_width)" check_eq $P/no-width "Error: bad format 'u'" "$(pv no_width)" # An EMPTY format is legal and produces empty bytes. Reported as a repr rather # than as the empty string, because an empty value is also what pv() yields for # a key that never arrived -- which would make this case pass vacuously. check_eq $P/empty-format "b''" "$(pv empty_format)" check_eq $P/short-data \ "Error: unpack requires at least 24 bits to unpack (got 8)" "$(pv short_data)" check_eq $P/float-bad-size \ "Error: expected float size of 16, 32, or 64 bits (got 24)" "$(pv float_bad_size)" # These two are NOT bitstruct.Error: the wrong argument type reaches the # encoder's own arithmetic first. Pinned as measured rather than as expected -- # a port that "fixed" them into Error would be changing upstream behaviour. check_eq $P/text-not-str \ "AttributeError: 'bytes' object has no attribute 'encode'" "$(pv text_not_str)" check_eq $P/raw-not-bytes \ 'TypeError: can only concatenate str (not "bytes") to str' "$(pv raw_not_bytes)" # allow_truncated turns the short-data refusal into a partial result, which is # the documented way to read a truncated frame. check_eq $P/truncated-allowed "(0,)" "$(pv truncated_allowed)" # ---------------------------------------------------------- text encoding ---- # The width is in BITS and the encoding is UTF-8, so a two-byte character costs # 16 bits of the field rather than 8. check_eq $P/text-utf8-2byte "c3a9c3a9" "$(pv text_utf8_2byte)" check_eq $P/text-roundtrip-ascii "'abc'" "$(pv text_roundtrip_ascii)" check_eq $P/text-too-long "6162" "$(pv text_too_long)" # U+FFFD twice, as CODEPOINTS: this guest runs under the C locale, so asserting # the characters would test the stdout encoding rather than the decoder. check_eq $P/text-errors-replace "65533,65533" "$(pv text_errors_replace)" rm -rf "$W"