. "$BST_LIB" # BST-TIMEOUT: 1200 # THREE python3 starts, and they are the whole cost. Every # # assertion below either reads the filesystem/dpkg with # # shell builtins or greps the output of one of those three. # # # # They are the package-level `import pyroute2' (whose # # FAILURE is the assertion), a second start for `import # # ctypes' (the cause, from the interpreter's own side), and # # the probe -- which takes ONE start for ninety # # measurements, including the whole netlink conversation. # # # # The netlink work itself is microseconds: roughly a dozen # # AF_NETLINK sockets opened and closed, each carrying one # # dump or one request. Nothing here sleeps and nothing here # # waits on a timer. # # # # 1200 is 400s per start, the same budget 99-python3-jdcal # # and 99-python3-async-timeout set for their three, and # # roughly six times the ~60s a python3 start has measured # # at here. # # # # 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-pyroute2 0.7.10 (meta-openembedded/meta-python # recipes-devtools/python/python3-pyroute2_0.7.10.bb, `inherit setuptools3 pypi # ptest', PyPI sdist) on the wasm32-hwjs glibc image. NO bbappend and NO patch: # the stock recipe builds and packages correctly as it stands -- `bitbake # python3-pyroute2' completed 1622 of 1622 tasks with zero ERROR lines against a # warm sstate, 2026-09-08, producing exactly python3-pyroute2, -dev and -dbg. # # ============================================================================ # THE HEADLINE: `import pyroute2' CANNOT WORK ON THIS IMAGE -- AND THE NETLINK # CORE UNDERNEATH IT WORKS COMPLETELY. # ============================================================================ # # Both halves are findings, and reporting either one alone misdescribes the # port. # # THE BLOCK. This image's python3 is ONE STATIC BINARY built with _ctypes # CONFIGURED OUT: ffi_closure_alloc cannot be linked on this arm (hwjs-cc#15 -- # libffi packages the name, the coroutinize pass REFUSEs the function, and # wasmld FATALs the link of anything reaching it), and CPython reaches it # unconditionally outside Darwin. Working FFI is a separate milestone # (linux-in-the-browser#180). pyroute2/__init__.py line 16 is # # from pyroute2.cli.console import Console # # which reaches pyroute2/ndb/main.py, whose line 282 is `import ctypes'. So the # package import dies with # # ModuleNotFoundError: No module named '_ctypes' # # THE BLOCK IS ONE LINE DEEP. Not one module under pyroute2/netlink/ imports # ctypes at module scope, and neither does pyroute2/iproute/linux.py. The ctypes # users are ndb.main, netns, inotify, ethtool, ext.rawsocket and # iproute/windows -- none of which the rtnetlink path touches. EVERY submodule # nevertheless fails to import, because importing `pyroute2.anything' runs the # parent package's __init__ first. That is why the honest package-level failure # HIDES a working core, and why this suite does not stop at it. # # THE BYPASS, AND WHY IT IS AN INSTRUMENT RATHER THAN A WORKAROUND. The probe # stubs the parent package (a module object carrying only __path__) and imports # `pyroute2.iproute' underneath it. Nothing installs that stub, nothing ships # it, and a caller on this image really does have to wait for ctypes: the suite # asserts the package-level failure separately, as a RED WITNESS, so the gap # cannot go quiet. 99-python3-periphery uses the same instrument for the same # reason -- three of its seven interfaces are sound and hidden behind one # ctypes-reaching sibling. # # WHAT THE BYPASS BUYS is the whole subject of the port: `IPRoute()' opens # AF_NETLINK, and every rtnetlink case below runs for real against this arm's # kernel. # # ============================================================================ # THE POINT OF THIS PORT: THERE IS NO C LIBRARY BETWEEN THE TEST AND AF_NETLINK. # ============================================================================ # # pyroute2 builds and parses the netlink messages ITSELF, in Python, byte for # byte -- 442 packaged files and not one `.so' (asserted below off the package's # own file list). So a red in the rtnetlink cases is the KERNEL's or the SOCKET # LAYER's, and cannot be a binding's. That is a stronger instrument than any # `ip'/`ifconfig' suite on this image: busybox `ip' is rtnetlink-based too, but # a failure there could always be busybox's. # # THE ARM SUPPORTS THIS. arch/wasm32/configs/wasm32_defconfig carries # CONFIG_NET=y, and its own comment states the other side of it -- "rtnetlink # core (busybox `ip` is rtnetlink-based; rtnetlink is unconditional core under # NET)". The syscall table (arch/wasm32/kernel/syscall_table.c) registers every # member this needs: socket [41], connect [42], sendto [44], recvfrom [45], # sendmsg [46], recvmsg [47], bind [49], getsockname [51], setsockopt [54], # getsockopt [55], plus poll [7] and select [23]. None of them is the epoll # family, which is unwired here (brintos/linux#53) and which this port does not # reach. # # ============================================================================ # NetNS IS BLOCKED TWICE OVER, AND THE TWO ARE ASSERTED SEPARATELY. # ============================================================================ # # GAP 1 -- pyroute2/netns/__init__.py line 82 is `import ctypes', so NetNS is # unreachable here for the SAME reason the package import is. The probe asserts # that directly (`netns-import-refused'). # # GAP 2 -- even with ctypes working, this arm's defconfig carries # `# CONFIG_NET_NS is not set', naming the same class of arch work PID_NS and # USER_NS wait on. `netns.create()' would call `libc.unshare(CLONE_NEWNET)'; # unshare IS wired ([272], GR-402), so the refusal would come from the kernel's # own check_unshare_flags rather than from an unregistered syscall. # # THEY ARE ASSERTED SEPARATELY ON PURPOSE, exactly as 99-python3-file-magic # separates its two: a reader who saw only the ctypes failure would conclude # that FFI is the whole of what NetNS waits on, and would be wrong by one gap. # Closing hwjs-cc#15 alone moves the failure from the import to the unshare and # changes the message rather than making NetNS work. # # `proc-ns-net-absent' is the kernel's own side of gap 2, READ rather than # inferred: with CONFIG_NET_NS unset the net namespace type is not registered # and /proc/self/ns/net does not exist. # # NOT YET MEASURED ON TARGET. This port's first on-target run is the batch # verify. Gap 2's two cases -- `netns-gap2-proc-ns-net' and the CONFIG_NET_NS # reasoning behind it -- are PREDICTIONS derived from the defconfig, not # results: the x86-64 control has CONFIG_NET_NS=y and could not measure them. # Gap 1 IS measured (see the control note below). # # ============================================================================ # WHERE THE EXPECTED VALUES CAME FROM. # ============================================================================ # # MEASURED BEFOREHAND ON AN x86-64 CONTROL, run against THIS BUILD's own .deb # payload -- the bytes that ship, extracted from # python3-pyroute2_0.7.10-r0_wasm32.deb -- with `_ctypes' forced to raise # ModuleNotFoundError through a sys.meta_path hook, which is this image's # condition exactly. That control established, as RESULTS rather than guesses: # the package import failure and its precise chain; the netlink core importing # and working with ctypes blocked; NetNS blocked at gap 1; version 0.7.10; # NetlinkError carrying code 19 for a bad index AND for a bad ifname; OSError # for a malformed address; and the sysfs/rtnetlink cross-check agreeing across # eight interfaces. # # THE WRITE PATH WAS MEASURED IN A PRIVATE user+net NAMESPACE (`unshare -Urn'), # which is root over a FRESH `lo' and touches nothing on the shared build host. # Add, independent read-back, delete, independent re-read, and link down/up all # ran green there. # # TWO VALUES THE CONTROL COULD NOT SUPPLY, and neither is guessed: # - `lo' carrying 127.0.0.1/8. A fresh namespace's `lo' is unconfigured, so # the control could not show it. It is not an assumption either: # 99-net-tools.test on THIS image already asserts `ifconfig lo' reporting # 127.0.0.1, so the fact is measured -- just by a different suite. # - The interface set. The target has `lo' and the virtio-net `eth0' # (CONFIG_VIRTIO_NET=y; 41 suites on this image assert eth0). The suite # asserts those two by name and cross-checks the whole set against sysfs, # rather than pinning a count that a future netdev would red. # # ============================================================================ # WHAT THIS SUITE CHANGES ON THE GUEST. # ============================================================================ # # It runs on an image SHARED with the rest of its batch, so the write cases are # constrained twice over. They touch `lo' and NOTHING ELSE -- never eth0, which # carries the guest's connectivity and the verify's own transport. Every # mutation is wrapped in try/finally in the probe, so the address is removed and # `lo' is brought back UP whatever raises. `restore-ok' is emitted last from # those finally blocks and is asserted below, so a probe that died mid-mutation # SAYS so rather than leaving a quiet mess for the next suite to trip over. # # The link-state cases are written to be correct whichever way `lo' starts: # after `set down' it must not be UP, and after `set up' it must be. That pair # is the observed flag change, and it does not depend on the guest's boot state # the way a raw before/after flag comparison would. # # ============================================================================ # PACKAGING. # ============================================================================ # # The recipe declares `RDEPENDS:${PN} += ' -- an APPEND, so the `python3-core' # that setuptools3-base contributes survives; measured on the built .deb, the # control stanza carries it alongside the eleven declared names, and all twelve # are asserted below. python3-ctypes is among them and RESOLVES AND INSTALLS # even though `import ctypes' fails -- the pure-Python half is what that package # ships, so the dependency is satisfiable while the C module is absent. That # pair is worth holding together, and 99-python3-file-magic makes the same point. # # The recipe DOES `inherit ptest', but DISTRO_FEATURES on this distro is # `ipv4 x11 wayland' (conf/distro/brintos.conf) with no `ptest', so no -ptest # package is produced: the build emitted exactly three .debs. Asserted below # rather than assumed. # # TWO top-level packages ship, not one: `pyroute2' and `pr2modules' (the legacy # compatibility shim), which is what dist-info's top_level.txt declares. And # FOUR CLI entry points land in /usr/bin -- pyroute2-cli, pyroute2-dhcp-client, # pyroute2-test-platform and ss2. Every one of them imports the package at # startup, so all four are dead on this image for the ctypes reason; the suite # asserts they were INSTALLED (the packaging is correct) without claiming they # run. P=python3-pyroute2 # The canary is what tells a reader the suite STARTED, as distinct from a suite # that produced nothing because the interpreter never came up. bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKGDIR=$SITE/pyroute2 DI=$SITE/pyroute2-0.7.10.dist-info PKGLIST=/var/lib/dpkg/info/python3-pyroute2.list PROBE=$BST_DATA/python3-pyroute2-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-pyroute2.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- on the disk --- check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKGDIR" check $P/init-file test -s "$PKGDIR/__init__.py" check $P/no-flat-module test ! -e "$SITE/pyroute2.py" # The netlink core, which is what this port is actually for. check $P/core-netlink-dir test -d "$PKGDIR/netlink" check $P/core-rtnl-dir test -d "$PKGDIR/netlink/rtnl" check $P/core-iproute-dir test -d "$PKGDIR/iproute" check $P/core-iproute-linux test -s "$PKGDIR/iproute/linux.py" # The legacy compatibility shim -- the SECOND top-level package, which a suite # looking only for `pyroute2' would miss entirely. check $P/pr2modules-dir test -d "$SITE/pr2modules" check $P/pycache test -s "$PKGDIR/__pycache__/__init__.cpython-312.pyc" # The four CLI entry points. They are asserted as INSTALLED, not as working: # each imports the package at startup and so is dead here for the ctypes reason. for _c in pyroute2-cli pyroute2-dhcp-client pyroute2-test-platform ss2; do check $P/cli-installed-$_c test -x /usr/bin/$_c done # ------------------------------------------------------- what dpkg shipped --- cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" # PURE PYTHON, ASSERTED FROM THE PACKAGE'S OWN FILE LIST. This is the claim the # whole port rests on: no C library stands between the test and AF_NETLINK. # This image's python3 has an empty lib-dynload and no dynamic loader # (BRINTOS-1476), so a release that grew a compiled extension could not be # imported whatever it built to -- it would land here first. check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" # Every packaged file is Python, a bytecode cache, dist-info metadata, or one of # the four CLI scripts -- named explicitly, because a blanket rule would stop # catching anything. _bad= while IFS= read -r _p; do [ -d "$_p" ] && continue case "$_p" in *.py|*.pyc) continue ;; *.dist-info/*) continue ;; /usr/bin/pyroute2-cli|/usr/bin/pyroute2-dhcp-client) continue ;; /usr/bin/pyroute2-test-platform|/usr/bin/ss2) continue ;; esac _bad="$_bad $_p" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # ------------------------------------------------------------- dist-info ---- check $P/dist-info-dir test -d "$DI" check_out $P/dist-info-name "Name: pyroute2" cat "$DI/METADATA" check_out $P/dist-info-version "Version: 0.7.10" cat "$DI/METADATA" check_out $P/dist-info-summary "Summary: Python Netlink library" cat "$DI/METADATA" # The wheel metadata's own statement that there is nothing arch-specific here -- # the packaging side of "pure Python". 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" # BOTH top-level packages, which is the fact a one-package assumption would miss. check_out $P/dist-info-top-level-pyroute2 "pyroute2" cat "$DI/top_level.txt" check_out $P/dist-info-top-level-pr2modules "pr2modules" cat "$DI/top_level.txt" # Dual-licensed, and both files ship. check $P/license-gpl test -s "$DI/LICENSE.GPL-2.0-or-later" check $P/license-apache test -s "$DI/LICENSE.Apache-2.0" # --------------------------------------------------------- what dpkg says ---- dpkg -s python3-pyroute2 > "$W/st" 2>/dev/null || : > "$W/st" check_out $P/pkg-installed "Status: install ok installed" cat "$W/st" grep '^Depends:' "$W/st" > "$W/dep" 2>/dev/null || : > "$W/dep" # The eleven the recipe declares... for _d in python3-ctypes python3-io python3-json python3-fcntl python3-logging \ python3-multiprocessing python3-pickle python3-pkgutil python3-pprint \ python3-shell python3-unixadmin; do check_out $P/pkg-depends-$_d "$_d" cat "$W/dep" done # ...and the one it does NOT, which arrives from `setuptools3-base'. The recipe # uses `+=' rather than `=', so this survives; asserted because nothing in the # recipe text says it should be there. check_out $P/pkg-depends-core "python3-core" cat "$W/dep" # THE WHOLE STANZA, in dpkg's own order, so a dependency APPEARING is caught as # well as one going missing. check_eq $P/pkg-depends-exact \ "Depends: python3-core, python3-ctypes, python3-fcntl, python3-io, python3-json, python3-logging, python3-multiprocessing, python3-pickle, python3-pkgutil, python3-pprint, python3-shell, python3-unixadmin" \ "$(cat "$W/dep")" # The recipe DOES `inherit ptest', but this distro's DISTRO_FEATURES is # "ipv4 x11 wayland" with no `ptest', so the split does not exist here. check_eq $P/no-ptest-package "" \ "$(dpkg -s python3-pyroute2-ptest 2>/dev/null | grep '^Status: install ok installed')" # ------------------------------------------- the import, and why it refuses --- # THE HONEST ANSWER, ASSERTED AS A RED WITNESS. This case fails on the day # ctypes starts working, which is the day this whole file should be re-read. check_eq $P/pkg-import-refused \ "ModuleNotFoundError: No module named '_ctypes'" \ "$(python3 -c 'import pyroute2' 2>&1 | tail -1)" # ...and the cause, from the interpreter's own side rather than by inference. check_eq $P/ctypes-refused \ "ModuleNotFoundError: No module named '_ctypes'" \ "$(python3 -c 'import ctypes' 2>&1 | tail -1)" # ------------------------------------------------------------- the probe ----- check $P/probe-present test -s "$PROBE" bst_probe $P/probe-run "$W/probe" 900 python3 "$PROBE" OUT="$W/probe" pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } if ! test -s "$OUT"; then bst_notok $P/probe-output "the probe emitted nothing -- python3 did not start, or died before its first line; every CASE below is unscorable" else bst_ok $P/probe-output fi check_eq $P/probe-complete "yes" "$(pv probe_done)" # 90 = the 79 the x86-64 control emitted with eth0 ABSENT, plus the eleven eth0 # adds here: three sysfs_eth0_* and the nine-key link_eth0_* block that replaces # the single `link_eth0_present=no'. The arithmetic was checked against the # control's own count rather than asserted. check_eq $P/probe-count "90" "$(pv emitted_count)" # --------------------------------------------- the block, from the probe ----- check_eq $P/probe-pkg-import-refused \ "ModuleNotFoundError: No module named '_ctypes'" "$(pv pkg_import)" check_eq $P/probe-ctypes-refused \ "ModuleNotFoundError: No module named '_ctypes'" "$(pv ctypes_import)" # _ctypes is not a builtin either. On an arm where every extension module must # be linked into the interpreter, "not builtin" IS "not anywhere". check_eq $P/probe-ctypes-not-builtin "absent" "$(pv ctypes_builtin)" # The stdlib the core needs. If one of these were missing the port would be # blocked by MORE than ctypes, and this suite would be reporting a smaller gap # than the image really has. for _m in socket struct os errno select threading json io logging pickle \ pprint fcntl multiprocessing pkgutil time; do check_eq $P/stdlib-$_m "ok" "$(pv stdlib_$_m)" done # ------------------------------------------------------ the netlink floor ---- # The address family this entire port stands on: DEFINED in the interpreter... check_eq $P/af-netlink-defined "yes" "$(pv af_netlink_defined)" check_eq $P/af-netlink-value "16" "$(pv af_netlink_value)" # ...and ACCEPTED by the kernel. Two cases, because a constant the header # defines proves nothing about a socket the kernel will open -- and on this arm # that distinction is the difference between a wired syscall and an unwired one. check_eq $P/af-netlink-socket "ok" "$(pv af_netlink_socket)" # --------------------------------------- the core, with the package blocked -- # THE SECOND HALF OF THE HEADLINE. The package import above is dead; these say # the netlink core underneath it is not. check_eq $P/core-import-iproute "ok" "$(pv core_iproute_import)" check_eq $P/core-import-exceptions "ok" "$(pv core_exceptions_import)" check_eq $P/core-version "0.7.10" "$(pv core_version)" check_eq $P/core-iproute-class "IPRoute" "$(pv iproute_class)" # ------------------------------------------------------- NetNS: two gaps ----- # GAP 1, MEASURED: the netns module reaches ctypes at its own line 82. check_eq $P/netns-gap1-import-refused \ "ModuleNotFoundError: No module named '_ctypes'" "$(pv netns_import)" # GAP 2, PREDICTED, NOT YET MEASURED: `# CONFIG_NET_NS is not set' in this arm's # defconfig means the net namespace type is not registered, so this link does # not exist. The x86-64 control has CONFIG_NET_NS=y and could not measure it. # If this reds, the defconfig has changed and gap 2 has closed -- re-read the # header rather than "fixing" the case. check_eq $P/netns-gap2-proc-ns-net "absent" "$(pv proc_ns_net)" # ------------------------------------------------------- the link dump ------- # rtnetlink's idea of which interfaces exist must be sysfs's. Two independent # windows onto the same kernel state: a cached or synthesised netlink answer # would agree with neither. check_eq $P/links-match-sysfs "yes" "$(pv links_match_sysfs)" check_eq $P/link-lo-present "yes" "$(pv link_lo_present)" check_eq $P/link-lo-index "1" "$(pv link_lo_index)" check_eq $P/link-lo-mtu "65536" "$(pv link_lo_mtu)" check_eq $P/link-lo-address "00:00:00:00:00:00" "$(pv link_lo_address)" # Each field against sysfs individually, so a mismatch names the FIELD rather # than only the interface. check_eq $P/link-lo-index-matches "yes" "$(pv link_lo_index_matches)" check_eq $P/link-lo-mtu-matches "yes" "$(pv link_lo_mtu_matches)" check_eq $P/link-lo-address-matches "yes" "$(pv link_lo_address_matches)" # The virtio-net interface (CONFIG_VIRTIO_NET=y). Its MTU and MAC are the # guest's own and are NOT pinned -- the cross-check against sysfs is what says # they are right, without inventing a value a future rig would red on. check_eq $P/link-eth0-present "yes" "$(pv link_eth0_present)" check_eq $P/link-eth0-index-matches "yes" "$(pv link_eth0_index_matches)" check_eq $P/link-eth0-mtu-matches "yes" "$(pv link_eth0_mtu_matches)" check_eq $P/link-eth0-address-matches "yes" "$(pv link_eth0_address_matches)" # link_lookup is the API most callers use to turn a name into an index... check_eq $P/lookup-lo "1" "$(pv lookup_lo)" # ...and it must answer EMPTY for a name that is not there, rather than raising # or inventing an index. check_eq $P/lookup-absent "" "$(pv lookup_absent)" # --------------------------------------------------------------- addresses --- # 127.0.0.1/8 on lo. 99-net-tools.test asserts the same fact through `ifconfig'; # this reads it from rtnetlink, which is a different path to the same kernel. check_eq $P/addr-lo-loopback "yes" "$(pv addr_lo_has_loopback)" # ----------------------------------------------------------- the write path --- # THE CASE A CACHED OR SYNTHESISED ANSWER CANNOT FAKE: written through one # netlink socket, read back through a DIFFERENT one, removed, and confirmed gone # through a third. Every read is a fresh IPRoute() for exactly that reason. check_eq $P/addr-add "ok" "$(pv addr_add)" check_eq $P/addr-add-readback "yes" "$(pv addr_after_add_present)" # An "add" that REPLACED rather than added would pass a naive presence check. check_eq $P/addr-add-keeps-loopback "yes" "$(pv addr_after_add_keeps_loopback)" check_eq $P/addr-del "ok" "$(pv addr_del)" check_eq $P/addr-del-readback "no" "$(pv addr_after_del_present)" # ...and the interface is back exactly where it started. check_eq $P/addr-restored "yes" "$(pv addr_restored)" # ------------------------------------------------------------ link up/down --- # Written to be correct whichever way `lo' starts on this guest: after `set # down' it must not be UP, and after `set up' it must be. That PAIR is the # observed flag change, and unlike a raw before/after comparison it does not # depend on the boot state. check_eq $P/link-set-down "ok" "$(pv link_set_down)" check_eq $P/link-down-observed "no" "$(pv lo_up_after_down)" check_eq $P/link-set-up "ok" "$(pv link_set_up)" check_eq $P/link-up-observed "yes" "$(pv lo_up_after_up)" # THE GUEST IS SHARED WITH THE REST OF THE BATCH. This is emitted last, from the # probe's finally blocks, and says every mutation above was undone. A red here # is a mess the NEXT suite will trip over, so it is worth its own case. check_eq $P/restore-ok "yes" "$(pv restore_ok)" # ------------------------------------------------------------------ routes --- check_eq $P/routes-have-loopback "yes" "$(pv routes_have_loopback)" # The loopback route lives in the `local' table (255). Asserting the TABLE, not # just the prefix, is what distinguishes a real routing table from a list that # merely mentions 127. check_eq $P/routes-local-table "yes" "$(pv routes_local_table)" # ------------------------------------------------------------------ errors --- # THE KERNEL'S OWN ERRNO, carried up as NetlinkError. This is the case that says # the request really reached the kernel: pyroute2 did not invent 19. check_eq $P/err-bad-index-class "NetlinkError" "$(pv err_bad_index)" check_eq $P/err-bad-index-errno "19" "$(pv err_bad_index_code)" check_eq $P/err-bad-ifname-class "NetlinkError" "$(pv err_bad_ifname)" check_eq $P/err-bad-ifname-errno "19" "$(pv err_bad_ifname_code)" # A MALFORMED REQUEST IS REFUSED BEFORE IT IS SENT -- a different and WEAKER # claim than the two above: pyroute2's own encoder rejects it and the kernel # never sees it. Both are worth having; one says the library validates, the # other says the kernel does. check_eq $P/err-bad-address-class "OSError" "$(pv err_bad_address)"