. "$BST_LIB" # BST-TIMEOUT: 900 # TWO python3 starts: a bare `import' whose STDERR is the # # assertion, and the probe, which takes ONE start for 111 # # measurements. # # # # 400s per start is the budget the neighbouring python3 # # suites use on this arm, and this package adds nothing to # # it: hexbytes is 40 lines of pure Python with no socket, no # # subprocess, no device and no file, so the probe's whole # # cost after the interpreter is up is arithmetic on byte # # strings of at most 32 bytes. # # # # A suite-timeout red is a batch-wide cost, so if the two # # starts prove slower than that on target the RIGHT fix is # # this number, not fewer cases. # # # # 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-hexbytes 1.0.0 (meta-openembedded meta-python/recipes-devtools/python, # python3-hexbytes_1.0.0.bb, `inherit pypi setuptools3', PyPI sdist) on the # wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE ACTUALLY IS, AND THEREFORE WHY THIS SUITE IS EXHAUSTIVE. # ============================================================================ # # `hexbytes' is a `bytes' SUBCLASS with three behaviours and forty lines of # code, spread over three files: `__init__.py' (10 lines), `main.py' (the # class), `_utils.py' (the conversion). It accepts more constructor inputs than # `bytes' does, it prints itself 0x-prefixed at a console, and its # `__getitem__' keeps a slice inside the subclass. # # There is no socket, no subprocess, no device, no terminal and no file # anywhere in it. Every fixture below is a literal, every answer is exact, and # nothing here needs a human -- WHICH IS WHY THIS PORT CLOSES ON ITS SUITE with # no BRINTOS-780 follow-on. # # Because the library is this small the suite is EXHAUSTIVE rather than # representative: every branch of `_utils.to_bytes' has a case, both branches # of `hexstr_to_bytes' have a case, and every arm of `HexBytes.__getitem__' has # a case. Every expected value below was MEASURED by running this suite's own # probe under CPython 3.12.3 against this port's own built # python3-hexbytes_1.0.0-r0_wasm32.deb, unpacked onto PYTHONPATH -- not derived # from reading the source. Three of those measurements contradicted what # reading the source had predicted, and each is called out where it lands. # # ============================================================================ # THE PORT TURNS ON ONE LINE, AND IT IS AN IMPORT. # ============================================================================ # # hexbytes/__init__.py line 1 is # # from importlib.metadata import version as __version # # executed at import time, so there is no `import hexbytes' that does not go # through `importlib.metadata'. That module imports `email' and `zipfile' at # module scope, and OE splits both OUT of python3-core -- into python3-email # and python3-compression. meta-python's recipe declares no RDEPENDS at all, so # the built .deb's control stanza reads `Depends: python3-core' and nothing # else, and on an image without those two splits this package does not import. # # python3-hexbytes_%.bbappend declares them; `pkg-depends-exact' is the case # that pins the corrected line, and `email-importable' / `zipfile-importable' # are the two that say why it is not decoration. # # `binascii' is the fourth name the code needs -- hexstr_to_bytes calls # unhexlify -- and it needs no declaration: this image's interpreter links every # stdlib extension module statically (python3_%.bbappend, wasm32 has no dynamic # loader), so binascii is a BUILTIN and cannot be split out. `binascii-builtin' # asserts that rather than assuming it. # # ============================================================================ # FOUR THINGS A CALLER WILL GET WRONG. THESE ARE THE POINT OF THE SUITE. # ============================================================================ # # THE PREFIXED FORM IS repr() AND ONLY repr(). `bytes' defines its own # __str__, which calls bytes_repr directly rather than dispatching # through type(self).__repr__, and HexBytes does not override it. # So str(HexBytes(b'\x0a')), '{}'.format(...) and f'{...}' all # answer b'\n' -- the escaped bytes repr this package exists to # replace -- while repr() and !r answer HexBytes('0x0a'). The one # console-facing feature is reachable only through repr, and # `str-not-prefixed', `format-not-prefixed', `fstring-not-prefixed' # and `repr-conversion-prefixed' pin all four spellings. # MEASURED; reading the class had predicted the opposite. # # AN ODD-LENGTH HEX STRING IS PADDED, NOT REFUSED. hexstr_to_bytes left-pads # with a '0' and decodes, so HexBytes('0x123') is b'\x01\x23' -- # not a raise, and not the truncated b'\x12' a naive unhexlify # would give. THE TICKET'S ACCEPTANCE CRITERIA NAMED THIS AS ONE OF # THREE INPUTS THAT RAISE; measurement says it is an accepted input # with a defined value, and `odd-*' below pins the value. # # A NON-ASCII CHARACTER REACHES A DEAD except-CLAUSE. hexstr_to_bytes wraps # `padded_hex.encode("ascii")' in `except UnicodeDecodeError', but # str.encode raises UnicodeENcodeError -- siblings, neither one the # other's base -- so the handler can never fire and its crafted # "may only contain [0-9a-fA-F] characters" message is unreachable # code. `non-ascii-encode-error' and `unicode-encode-not-decode' # are that pair. It is a diagnostics defect, not a contract break: # UnicodeEncodeError is still a ValueError, which # `non-ascii-is-valueerror' asserts. # # A NON-HEX ASCII CHARACTER RAISES binascii.Error, not the bare ValueError the # module's own except-clause is written for. binascii.Error is a # ValueError subclass, so a caller catching ValueError is right # either way -- `non-hex-char' pins the class and # `non-hex-is-valueerror' pins the relationship. # # ============================================================================ # WHY __getitem__ WORKS, WHICH IS NOT WHY IT LOOKS LIKE IT WORKS. # ============================================================================ # # result = super().__getitem__(key) # if hasattr(result, "hex"): # return type(self)(result) # else: # return result # # The discriminator is `hasattr(result, "hex")', NOT the type of the key. It # gives the documented answers -- a slice stays a HexBytes, an index is an int # -- only because bytes has .hex and int does not. `int-has-no-hex' is the case # that says so, and it is not pedantry: float ALREADY has .hex(), so the # premise is narrower than it looks, and the day int grows one every index on # this class starts answering a one-byte HexBytes instead of an integer. # # `type(self)' rather than `HexBytes' means a subclass keeps its own type # through a slice, which `subclass-slice-type' measures. # # ============================================================================ # STRUCTURE # ============================================================================ # # 1. what is on the rootfs -- files, dist-info, the dpkg stanza # 2. the import -- the three splits, and `import hexbytes' # 3. the probe -- 111 measurements from ONE interpreter start # # 111 measurements over two python3 starts. Every one of them is a literal # compared against a literal. P=python3-hexbytes bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKG=$SITE/hexbytes DI=$SITE/hexbytes-1.0.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-hexbytes.list PROBE=$BST_DATA/python3-hexbytes-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-hexbytes-suite.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # --- 1. what is on the rootfs ---------------------------------------------- # # hexbytes is a PACKAGE (a directory), unlike the single-module python3 ports # next door, so the shape assertions are about a directory and its three # modules rather than about one .py file. check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKG" check $P/init-module test -s "$PKG/__init__.py" check $P/main-module test -s "$PKG/main.py" check $P/utils-module test -s "$PKG/_utils.py" check $P/not-a-flat-module test ! -e "$SITE/hexbytes.py" # py.typed is the PEP 561 marker; it is data, and it is the one non-.py file # the package ships. Asserted because `pkg-only-python' below has to tolerate # it by name rather than by accident. check $P/py-typed test -f "$PKG/py.typed" check $P/pycache-init test -s "$PKG/__pycache__/__init__.cpython-312.pyc" check $P/pycache-main test -s "$PKG/__pycache__/main.cpython-312.pyc" check $P/pycache-utils test -s "$PKG/__pycache__/_utils.cpython-312.pyc" # No compiled artifact of any kind. The sdist has no ext_modules, so a .so here # would mean something built one -- and this image's interpreter exports no Py* # API and has no dynamic loader, so it could never be imported (BRINTOS-1476). check $P/no-so test ! -e "$PKG/_utils.so" check $P/no-c test ! -e "$PKG/main.c" cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" check_eq $P/pkg-no-bin "" \ "$(grep -E '^/usr/(s?bin)/' "$W/pkgf" 2>/dev/null)" # Everything shipped is a .py, a .pyc, dist-info metadata, or py.typed. A # fourth kind of file would mean the wheel carried something setup.py does not # declare, which is how python3-reedsolo ended up owning /tests. _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; */py.typed) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # The sdist ships tests/core/ and setup.py does NOT declare it, so this package # must not carry those files onto the image -- the failure python3-reedsolo has, # where a stale egg-info top_level.txt put a top-level `tests' in the wheel. # # ASSERTED AS OWNERSHIP, NOT AS ABSENCE, and the difference is this batch. A # `test ! -d $SITE/tests' would be red here for a reason that has nothing to do # with this package: python3-reedsolo (BRINTOS-2283) is a sibling on this same # image and it DOES own that directory. What is this port's business is that no # path in ITS OWN dpkg file list is under tests/ -- which is the thing that # could regress, and which stays true whoever else is on the image. check_eq $P/pkg-owns-no-tests "" \ "$(grep -E '/site-packages/tests(/|$)' "$W/pkgf" 2>/dev/null)" check_eq $P/pkg-owns-only-hexbytes "" \ "$(grep -E '^/usr/lib/python3\.12/site-packages/' "$W/pkgf" 2>/dev/null \ | grep -vE '^/usr/lib/python3\.12/site-packages/(hexbytes|hexbytes-1\.0\.0\.dist-info)(/|$)')" check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-name "Name: hexbytes" cat "$DI/METADATA" check_out $P/dist-info-version "Version: 1.0.0" cat "$DI/METADATA" check $P/dist-info-record test -s "$DI/RECORD" check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/dist-info-tag-py3 "Tag: py3-none-any" cat "$DI/WHEEL" check_eq $P/dist-info-no-py2-tag "" \ "$(grep '^Tag: py2' "$DI/WHEEL" 2>/dev/null)" # top_level.txt is `hexbytes' and NOTHING ELSE. This is the file that put a # top-level `tests' on the image for python3-reedsolo; here it is correct, and # the case is what keeps it that way. check_out $P/dist-info-top-level "hexbytes" cat "$DI/top_level.txt" check_eq $P/dist-info-top-level-only "hexbytes" \ "$(cat "$DI/top_level.txt" 2>/dev/null | tr -d '\r' | grep -v '^$' | tr '\n' ',' | sed 's/,$//')" # The dist-info is not decoration here: __init__.py reads its own version out of # it through importlib.metadata AT IMPORT TIME, so a strip of dist-info would # not merely lose metadata, it would break the import. `version-attr' below is # the other end of that. check $P/dist-info-is-load-bearing test -f "$DI/METADATA" sed -n '/^Package: python3-hexbytes$/,/^$/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: 1.0.0-r0" cat "$W/st" # THE CORRECTED DEPENDS LINE, VERBATIM. meta-python's recipe declares no # RDEPENDS, which yields `Depends: python3-core' alone; python3-hexbytes_%. # bbappend adds the two splits importlib.metadata needs. dpkg sorts the list, # so this is the whole line in the order it is written on the image. check_out $P/pkg-depends-exact \ "Depends: python3-compression, python3-core, python3-email" cat "$W/st" # --- 2. the import --------------------------------------------------------- # # Each of the three names is imported ON ITS OWN, one interpreter start each, # so a future split that moved one of them names which one rather than failing # `import hexbytes' with a single unattributable red. check $P/email-importable python3 -c 'import email' check $P/zipfile-importable python3 -c 'import zipfile' check $P/metadata-importable python3 -c 'import importlib.metadata' check $P/import python3 -c 'import hexbytes' check_eq $P/import-quiet "" \ "$(python3 -c 'import hexbytes' 2>&1)" if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "$PROBE did not ship" rm -rf "$W" exit 0 fi bst_ok $P/probe-present # --- 3. the probe ---------------------------------------------------------- bst_probe $P/probe-run "$W/probe" 600 python3 "$PROBE" OUT="$W/probe" pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } if [ -s "$OUT" ]; then bst_ok $P/probe-output else bst_notok $P/probe-output "the probe emitted nothing -- python3 did not start, or died before its first line; every CASE below is unscorable" fi check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "111" "$(pv emitted_count)" # The four names the package's import chain rests on, measured from inside the # interpreter that will actually run it rather than from the shell. check_eq $P/binascii-builtin "yes" "$(pv binascii_builtin)" check_eq $P/email-in-chain "yes" "$(pv email_importable)" check_eq $P/zipfile-in-chain "yes" "$(pv zipfile_importable)" check_eq $P/metadata-in-chain "yes" "$(pv importlib_metadata_importable)" # Identity. check_eq $P/module-answered "$PKG/__init__.py" "$(pv module_file)" check_eq $P/is-package "yes" "$(pv is_package)" check_eq $P/main-answered "$PKG/main.py" "$(pv main_file)" check_eq $P/utils-answered "$PKG/_utils.py" "$(pv utils_file)" check_eq $P/all-exports "HexBytes" "$(pv all_exports)" # __version__ comes from the dist-info, not from a literal in the source. This # case and `dist-info-metadata' above are the two ends of the same fact. check_eq $P/version-attr "1.0.0" "$(pv version_attr)" check_eq $P/is-bytes-subclass "yes" "$(pv hexbytes_is_bytes_subclass)" check_eq $P/mro "HexBytes,bytes,object" "$(pv hexbytes_mro)" # --- construction: one case per branch of _utils.to_bytes ------------------ # # The branch order is bytes, str, bytearray, bool, int, memoryview, and it is # load-bearing twice: `bool' precedes `int' because isinstance(True, int) is # true, and `bytes' comes first so a bytes SUBCLASS passes through untouched. check_eq $P/from-bytes "1234" "$(pv from_bytes)" check_eq $P/from-bytes-empty "" "$(pv from_bytes_empty)" check_eq $P/from-bytes-high "ff00ff" "$(pv from_bytes_high)" check_eq $P/from-bytearray "1234" "$(pv from_bytearray)" check_eq $P/from-memoryview "1234" "$(pv from_memoryview)" check_eq $P/from-hexbytes "1234" "$(pv from_hexbytes)" check_eq $P/from-str-prefixed "1234" "$(pv from_str_prefixed)" check_eq $P/from-str-unprefixed "1234" "$(pv from_str_unprefixed)" check_eq $P/from-str-upper-prefix "1234" "$(pv from_str_upper_prefix)" check_eq $P/from-str-upper-digits "abcdef" "$(pv from_str_upper_digits)" check_eq $P/from-str-mixed-case "abcdef" "$(pv from_str_mixed_case)" check_eq $P/from-str-empty "" "$(pv from_str_empty)" check_eq $P/from-str-bare-prefix "" "$(pv from_str_bare_prefix)" check_eq $P/from-str-long \ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" \ "$(pv from_str_long)" # bool before int: b'\x01', not hex(True). check_eq $P/from-bool-true "01" "$(pv from_bool_true)" check_eq $P/from-bool-false "00" "$(pv from_bool_false)" # An int goes to_bytes(hex(val)) and comes back through the odd-length padding, # which is why 0 is one byte and 256 is two. check_eq $P/from-int-0 "00" "$(pv from_int_0)" check_eq $P/from-int-1 "01" "$(pv from_int_1)" check_eq $P/from-int-15 "0f" "$(pv from_int_15)" check_eq $P/from-int-16 "10" "$(pv from_int_16)" check_eq $P/from-int-255 "ff" "$(pv from_int_255)" check_eq $P/from-int-256 "0100" "$(pv from_int_256)" check_eq $P/from-int-4096 "1000" "$(pv from_int_4096)" check_eq $P/from-int-negative "!ValueError" "$(pv from_int_negative)" # --- the odd-length string is PADDED, which the ticket expected to raise ---- check_eq $P/odd-unprefixed "0123" "$(pv from_str_odd_unprefixed)" check_eq $P/odd-prefixed "0123" "$(pv from_str_odd_prefixed)" check_eq $P/odd-single "0f" "$(pv from_str_odd_single)" check_eq $P/odd-prefixed-single "0f" "$(pv from_str_odd_prefixed_single)" # --- the error paths ------------------------------------------------------- check_eq $P/non-hex-char "!Error" "$(pv err_non_hex_char)" check_eq $P/non-hex-is-valueerror "yes" "$(pv err_non_hex_char_is_valueerror)" check_eq $P/non-hex-caught "yes" "$(pv err_non_hex_valueerror_caught)" check_eq $P/non-ascii-encode-error "!UnicodeEncodeError" "$(pv err_non_ascii)" check_eq $P/non-ascii-caught "yes" "$(pv err_non_ascii_valueerror_caught)" check_eq $P/non-ascii-is-valueerror "yes" "$(pv unicode_encode_is_valueerror)" # The dead handler: str.encode raises the ENcode error, the module catches the # DEcode one, and neither is the other's base. check_eq $P/unicode-encode-not-decode "no" "$(pv unicode_encode_is_decode_subclass)" check_eq $P/err-none "!TypeError" "$(pv err_none)" check_eq $P/err-float "!TypeError" "$(pv err_float)" check_eq $P/err-list "!TypeError" "$(pv err_list)" check_eq $P/err-tuple "!TypeError" "$(pv err_tuple)" check_eq $P/err-dict "!TypeError" "$(pv err_dict)" check_eq $P/err-none-message \ "Cannot convert None of type to bytes" \ "$(pv err_none_message)" # --- interoperation with bytes --------------------------------------------- # # The whole point of a bytes subclass is that everything taking bytes takes it. # Equality, hashing and dict lookup are the three that a caller relies on # without thinking about, so they are asserted rather than assumed. check_eq $P/eq-bytes "yes" "$(pv eq_bytes)" check_eq $P/eq-bytes-reversed "yes" "$(pv eq_bytes_reversed)" check_eq $P/isinstance-bytes "yes" "$(pv isinstance_bytes)" check_eq $P/hash-equals-bytes "yes" "$(pv hash_equals_bytes)" check_eq $P/dict-key-interop "yes" "$(pv dict_key_interop)" check_eq $P/len-answers "2" "$(pv len_answers)" check_eq $P/in-operator "yes" "$(pv in_operator)" # Concatenation and join DROP the subclass -- bytes.__add__ and bytes.join both # answer a plain bytes, and neither is overridden here. So the prefixed repr # does not survive an ordinary byte-string operation, which is the same # narrowness the str() cases below are about. check_eq $P/bytes-of-hexbytes "bytes" "$(pv bytes_of_hexbytes_type)" check_eq $P/concat-type "bytes" "$(pv concat_type)" check_eq $P/concat-value "123456" "$(pv concat_value)" check_eq $P/join-type "bytes" "$(pv join_type)" # --- representation: the prefixed form is repr() and only repr() ----------- check_eq $P/repr-empty "HexBytes('0x')" "$(pv repr_empty)" check_eq $P/repr-single "HexBytes('0x0a')" "$(pv repr_single)" check_eq $P/repr-leading-zero "HexBytes('0x00ff')" "$(pv repr_leading_zero)" check_eq $P/repr-long "HexBytes('0x000102030405060708090a0b0c0d0e0f')" \ "$(pv repr_long)" check_eq $P/repr-lowercases "HexBytes('0xabcdef')" "$(pv repr_uppercase_input)" # MEASURED, and the opposite of what reading the class predicts: bytes defines # its own __str__, which calls bytes_repr directly instead of dispatching # through type(self).__repr__. check_eq $P/str-not-prefixed "b'\n'" "$(pv str_of_hexbytes)" check_eq $P/format-not-prefixed "b'\n'" "$(pv format_of_hexbytes)" check_eq $P/fstring-not-prefixed "b'\n'" "$(pv fstring_of_hexbytes)" check_eq $P/repr-conversion-prefixed "HexBytes('0x0a')" \ "$(pv repr_conversion_of_hexbytes)" check_eq $P/bytes-repr-reachable "b'\n'" "$(pv bytes_repr_still_available)" # hex() is NOT overridden: bytes.hex(), so lowercase and NO prefix. The prefix # belongs to repr alone, and this pair is what keeps the two from being # confused for each other. check_eq $P/hex-no-prefix "abcdef" "$(pv hex_no_prefix)" check_eq $P/hex-empty "" "$(pv hex_empty)" # --- slicing and indexing -------------------------------------------------- # # int-has-no-hex is the premise the whole of __getitem__ rests on, and # float-has-hex is why that premise is narrower than it looks. check_eq $P/int-has-no-hex "no" "$(pv int_has_hex)" check_eq $P/float-has-hex "yes" "$(pv float_has_hex)" check_eq $P/bytes-has-hex "yes" "$(pv bytes_has_hex)" check_eq $P/index-type "int" "$(pv index_type)" check_eq $P/index-value "0" "$(pv index_value)" check_eq $P/index-middle-value "51" "$(pv index_middle_value)" check_eq $P/index-negative-type "int" "$(pv index_negative_type)" check_eq $P/index-negative-value "255" "$(pv index_negative_value)" check_eq $P/index-out-of-range "!IndexError" "$(pv index_out_of_range)" check_eq $P/slice-type "HexBytes" "$(pv slice_type)" check_eq $P/slice-value "0011" "$(pv slice_value)" check_eq $P/slice-whole-type "HexBytes" "$(pv slice_whole_type)" check_eq $P/slice-empty-type "HexBytes" "$(pv slice_empty_type)" check_eq $P/slice-empty-repr "HexBytes('0x')" "$(pv slice_empty_repr)" check_eq $P/slice-step-type "HexBytes" "$(pv slice_step_type)" check_eq $P/slice-step-value "0022446688aaccee" "$(pv slice_step_value)" check_eq $P/slice-negative-type "HexBytes" "$(pv slice_negative_type)" check_eq $P/slice-negative-value "eeff" "$(pv slice_negative_value)" check_eq $P/slice-repr "HexBytes('0x0011')" "$(pv slice_repr_is_hexbytes)" check_eq $P/slice-of-slice-type "HexBytes" "$(pv slice_of_slice_type)" # A bool IS a SupportsIndex, so hb[True] indexes rather than reaching the bool # arm of to_bytes. The one place this package's two bool special cases could be # confused for each other. check_eq $P/index-by-bool-type "int" "$(pv index_by_bool_type)" check_eq $P/index-by-bool-value "17" "$(pv index_by_bool_value)" # Iteration goes through bytes.__iter__, NOT this __getitem__, so it yields # ints and constructs no HexBytes -- a loop over a HexBytes costs what a loop # over bytes costs. check_eq $P/iter-element-type "int" "$(pv iter_element_type)" check_eq $P/iter-first-three "0,17,34" "$(pv iter_first_three)" # type(self), not HexBytes: a subclass keeps its own type through a slice. check_eq $P/subclass-slice-type "_Sub" "$(pv subclass_slice_type)" check_eq $P/subclass-index-type "int" "$(pv subclass_index_type)" # --- the private helpers --------------------------------------------------- # # Not the interface, but the whole of the logic -- so each is measured # directly, and a change in to_bytes that repr and equality happen to mask # still moves a case here. check_eq $P/to-bytes-type "bytes" "$(pv to_bytes_type)" check_eq $P/to-bytes-str "1234" "$(pv to_bytes_str)" check_eq $P/to-bytes-passthrough "yes" "$(pv to_bytes_passthrough_identity)" check_eq $P/hexstr-prefixed "1234" "$(pv hexstr_to_bytes_prefixed)" check_eq $P/hexstr-unprefixed "1234" "$(pv hexstr_to_bytes_unprefixed)" check_eq $P/hexstr-odd "0123" "$(pv hexstr_to_bytes_odd)" check_eq $P/hexstr-upper-prefix "1234" "$(pv hexstr_to_bytes_upper_prefix)" check_eq $P/utils-not-exported "yes" "$(pv utils_not_exported)" check_eq $P/to-bytes-not-reexported "yes" "$(pv to_bytes_not_on_package)" rm -rf "$W"