. "$BST_LIB" # BST-TIMEOUT: 1800 # FOUR python3 starts, and they are the whole cost. # # # # One is a bare `import smbus' at the shell -- the platform # # gate, which cannot be folded into a probe because the # # point of it is what a CALLER on this image gets, not what # # an interpreter that has already imported other things # # reports. One is the main probe, which takes a single # # start for fifty-odd measurements. The other two are one # # call each, and they are separate ON PURPOSE: each asks a # # DIFFERENT dispatch shape of a defect whose uncatchable arm # # is SIGILL (see "the three call shapes" below), so sharing # # a start would let one refusal cost the other fifty # # measurements. # # # # The work inside each start is attribute lookups and a # # handful of syscalls against paths that do not exist: # # microseconds beside an interpreter start on this arm, # # which is tens of seconds before the first line of the # # program runs. Nothing here sleeps and nothing retries. # # # # 1800 is 450s per start -- half of what 99-python3-periphery # # gives each of its two, and roughly seven times the ~60s a # # python3 start has measured at here. A suite-timeout red is # # a batch-wide cost, so if the four 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-smbus 4.3 (meta-openembedded/meta-python # recipes-devtools/python/python3-smbus_4.3.bb, `inherit setuptools3', the # py-smbus/ subdirectory of the i2c-tools 4.3 tarball) on the wasm32-hwjs glibc # image. # # ============================================================================ # WHAT THE PACKAGE IS. # ============================================================================ # # One CPython extension module, `smbus', exposing one type, `SMBus'. Every # method is a thin wrapper over an ioctl(2) on /dev/i2c-: `open' opens the # node, a private helper sets the client address with I2C_SLAVE, and the # fourteen transfer methods drive I2C_SMBUS through libi2c's i2c_smbus_* # entry points. There is no Python in it at all -- the sdist's py-smbus/ # directory is smbusmodule.c and a setup.py. # # ============================================================================ # THIS FILE HAS NOT BEEN RUN. Every expectation below is a MEASUREMENT of an # x86-64 control, not of this image. # ============================================================================ # # It was authored during a batched Epic's recipe phase, which forbids building # the image, so the target has never executed a line of it. What that phase # CAN do is settle every expectation that is not a property of this arm, and # that was done rather than guessed: the same 4.3 sdist was built on x86-64 # against a natively compiled libi2c with the same CPython 3.12, and every # value the cases below compare against is that control's OUTPUT. Three of # them are not what the source reads like and would have been predicted wrong: # # * the module's own `__name__' is `SMBus', not `smbus' -- PyModuleDef.m_name # is the string "SMBus" and the import machinery does not overwrite it; # * the type's `__module__' is `builtins', because tp_name is the bare # "SMBus" with no dot for CPython to split on; # * `pec' is NOT validated as a boolean despite its own docstring saying so: # PyObject_IsTrue accepts any truthy object, so `pec = "x"' reaches the # ioctl rather than raising TypeError. # # TWO expectations remain genuinely arm-dependent and are marked as predictions # where they appear: whether the module imports at all, and whether a call # reaches it once it has. Both are platform questions this suite is the first # thing to answer, and both are structured below so that the answer is one case # rather than fifty. # # ============================================================================ # THE ABSENCE OF THE HARDWARE IS THE ASSERTION, NOT AN OBSTACLE TO IT. # ============================================================================ # # There is no I2C adapter behind this browser guest and there cannot be one, so # no transfer here will ever reach a device. That is not a gap in the coverage; # it is what makes the coverage possible. A bus that was never opened holds # fd = -1, so EVERY documented method takes its ioctl(2) to a bad descriptor and # comes back EBADF -- which is the answer a caller on this machine really gets, # and is a result rather than something skipped as unreachable. The suite # asserts that answer for all fifteen entry points, which is the ticket's # "raises the documented error rather than segfaults" in the form the package # actually has it. # # Three further things are assertable with no adapter at all, and they are where # a wasm32 miscompile would show: # # * THE ONE PURELY COMPUTATIONAL REFUSAL. SMBus_open snprintf()s the bus # number into char[16]; "/dev/i2c-" is nine characters, so seven digits # overflow and raise OverflowError while six fit and reach open(2). The # pair is a differential separated by one digit of arithmetic. # * THE BLOCK-ARGUMENT LATTICE. SMBus_list_to_data refuses a non-list, a # non-integer element and a list longer than 32 -- all before any ioctl -- # and accepts exactly 32, which it writes into `union i2c_smbus_data.block', # a 34-byte member. `v-block-32' is a memory-safety assertion rather than a # validation one: a union laid out short on this arm would corrupt the frame # there and nowhere else. # * THE LAYOUT ITSELF, at build time rather than here. sizeof(union # i2c_smbus_data) == 34 and sizeof(struct i2c_smbus_ioctl_data) == 12 with # its four members at 0/1/4/8, asserted against the target UAPI headers on # the wasm32 arm; the x86-64 control answers 16 for the second, so the # agreement is not an accident of the host. Recorded in the test plan at # BRINTOS-2423. # # WHAT THE TICKET ASKED FOR AND THE SOURCE DOES NOT HAVE, narrowed here rather # than asserted away. The ticket asks that an address outside 0x03-0x77 and a # negative register raise before any ioctl. Neither check exists in # smbusmodule.c: `i' accepts any C int and SMBus_set_addr hands it straight to # I2C_SLAVE, so `v-negative-register' asserts what the module DOES -- the # ordinary EBADF -- and the range check is recorded as absent rather than # invented. The ticket also asks for `SMBus('/dev/i2c-0')'; the format is # "|i:__init__", so a str raises TypeError and never reaches open(2). That is # the smbus2 API, not this one. # # ============================================================================ # THE THREE CALL SHAPES, WHICH IS THIS PORT'S REAL CONTRIBUTION. # ============================================================================ # # `linux-in-the-browser#186' -- open 2026-09-07 -- records the runtime's F152 # open-world dispatch consult REFUSING every main-side vectorcall of a # METH_NOARGS entry in a dlopen'd CPython extension: xxhash and lz4 both trap at # the same site_sig, while crc32c, whose whole API takes arguments, is 76 of 76 # green. The issue states its own open question outright -- separating the # shapes "needs a probe that calls a METH_NOARGS and a METH_VARARGS method on # the same extension" -- and says the METH_NOARGS reading rests on two samples # and is not proven. # # smbus IS that probe, by construction rather than by contrivance. # SMBus_methods[] declares `close' METH_NOARGS and the other fourteen entries # METH_VARARGS; the type adds a tp_new/tp_init pair and a PyGetSetDef. So one # extension, one dlopen, four dispatch shapes, and each asked in isolation: # # ctor-dispatch tp_new + tp_init in the main probe, as its gate # dispatch-varargs METH_VARARGS in the main probe, as its gate # noargs-close METH_NOARGS its own start # pec-read/write getset getter and setter its own start # # The refusal has two arms and only one is survivable in-process: the catchable # one raises `RuntimeError: unreachable', the other is SIGILL with rc=132. That # is why the last two get starts of their own, and why the probes emit and FLUSH # an `*_attempted' line before the call -- so that the uncatchable arm still # leaves evidence that the call was reached, rather than looking like a probe # that died during its own start. # # WHAT A RED HERE MEANS. If either isolated probe traps, its `probe-run' case # goes red carrying the rc and the last line the probe printed, and that red is # ATTRIBUTED to linux-in-the-browser#186 -- it is the regression signal that # flips green by itself the day that issue closes, and the measurement the issue # asked for either way. The main probe's two gates behave the other way round: # a refusal there SKIPS the fifty cases downstream with the reason attached, # because fifty reds naming one platform fact bury it rather than report it. # # ============================================================================ # WHERE libi2c WENT, WHICH IS NOT WHERE THE TICKET EXPECTED. # ============================================================================ # # The ticket asks that the extension "resolve against the shipped libi2c rather # than a bundled copy". It carries a copy, on both arms of the shared-libs # conversion: # # * STATIC ARM -- `libi2c.a', no shared object. `-li2c' under -flto absorbs # the archive whole. # * CONVERTED ARM -- libi2c.so.0 ships beside the archive, but the recipe # links the archive by path in place of `-li2c'. A side module records no # needed list of its own, so a module linked against libi2c.so would import # the i2c_smbus_* functions with nothing to provide them: python3 does not # name libi2c, and the dlopen of the module binds against the runtime, # libc.so and the main only. Measured: `import smbus' refused with # `unresolved import env.i2c_smbus_access'. # # Either way all fifteen i2c_smbus_* functions are DEFINED inside # smbus.cpython-312.so and re-exported from it, the module's dylink.0 section # declares no NEEDED entry, and the .deb Depends on nothing but python3-core. # `so-carries-libi2c' asserts that shape. The day the extension can import from # libi2c.so instead, this assertion is what changes. # # BRINTOS-WORKAROUND: hwjs-cc#128 revisit — a side module records no needed list of its own, so the extension carries libi2c.a on the converted arm too and this suite asserts the bundled copy there. P=python3-smbus # ============================================================================ # The suite proper. # ============================================================================ # # `suite-canary' is emitted FIRST and unconditionally. A suite that dies before # its first result line is indistinguishable from one that was never scheduled, # so this one line is what separates "ran and found nothing" from "did not run". bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages SO=$SITE/smbus.cpython-312.so DI=$SITE/smbus-1.1.dist-info PKGLIST=/var/lib/dpkg/info/python3-smbus.list PROBE=$BST_DATA/python3-smbus-probe.py NOARGS_PROBE=$BST_DATA/python3-smbus-noargs-probe.py GETSET_PROBE=$BST_DATA/python3-smbus-getset-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-smbus.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ---------------------------------------------------------------- packaging -- # WHERE THE PACKAGING SAYS THE MODULE LANDS. These run before any import, so a # red here says the .deb is wrong rather than that the module misbehaves -- which # matters more than usual for this port, since a missing .so and an unloadable # one both end at the same ImportError. check $P/site-packages-dir test -d "$SITE" check $P/extension-present test -f "$SO" check $P/extension-nonempty test -s "$SO" # IT IS AN EXTENSION AND NOTHING ELSE. py-smbus/ has no .py, so a `smbus.py' or # a `smbus/' package directory here would mean a release that grew a # pure-Python half -- which would change what this port is and wants noticing. check $P/no-py-module test ! -f "$SITE/smbus.py" check $P/no-py-package test ! -d "$SITE/smbus" # THE SONAME CARRIES NO PLATFORM TRIPLE. Upstream names an extension # `smbus.cpython-312-.so'; poky's python3 reports an empty EXT_SUFFIX # triple on this arm, so the file is plain `smbus.cpython-312.so'. The import # resolves through importlib.machinery.EXTENSION_SUFFIXES, so the two must # agree -- and if a future poky restored the triple, this case reds before the # import does and says why. check_eq $P/extension-name "smbus.cpython-312.so" \ "$(cd "$SITE" 2>/dev/null && echo smbus.cpython-312*.so)" # NO EXECUTABLE. setup.py declares no console_scripts, so nothing lands in # /usr/bin. Asserted so a future release that started shipping one is noticed. check $P/no-bin-script test ! -e /usr/bin/smbus # ------------------------------------------------------------------ libi2c -- # WHERE libi2c WENT. See the header. On both arms all fifteen i2c_smbus_* # definitions are inside this .so and re-exported from it. The names are # strings in the module itself, which is what makes this answerable on an image # carrying no wasm tooling. # # THE NAMES ARE EXTRACTED, NOT GREPPED FOR IN PLACE, and that is not a style # choice. `grep -q ' FAILS on this file under a UTF-8 locale -- # measured on the built artifact, where the bytes are present nine times over # and grep still exits 1, because the surrounding binary is not valid multibyte # input. It matches under `grep -a', which is not POSIX. So the module is run # through `tr' once into a token-per-line file, which costs one pass instead of # fourteen, is locale-proof, and lets each name be matched WHOLE: a substring # test would let `i2c_smbus_read_byte_data' satisfy `i2c_smbus_read_byte'. cat "$SO" | tr -c 'A-Za-z0-9_' '\n' > "$W/syms" 2>/dev/null || : > "$W/syms" # The extraction itself worked. Without this case an empty $W/syms would report # every name below as missing and read as a port that lost its library. check $P/so-symbols-readable grep -q '^PyInit_smbus$' "$W/syms" _missing="" for _f in access write_quick read_byte write_byte read_byte_data \ write_byte_data read_word_data write_word_data process_call \ read_block_data write_block_data read_i2c_block_data \ write_i2c_block_data block_process_call; do grep -q "^i2c_smbus_$_f\$" "$W/syms" || _missing="$_missing i2c_smbus_$_f" done check_eq $P/so-carries-libi2c "" "$_missing" # --------------------------------------------------------------- dist-info --- check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" # 1.1, not 4.3. setup.py in py-smbus/ carries the MODULE's own version, which # upstream has never moved; the recipe's 4.3 is the i2c-tools tarball's. Both # are asserted -- this one here, the recipe's in the dpkg stanza below -- because # a reader who saw only one of them would call the other a packaging error. check_out $P/dist-info-version "Version: 1.1" cat "$DI/METADATA" check_out $P/dist-info-name "Name: smbus" cat "$DI/METADATA" check_out $P/dist-info-summary "Python bindings for Linux SMBus access" cat "$DI/METADATA" check $P/dist-info-wheel test -f "$DI/WHEEL" # NOT PURELIB, stated by the packaging itself. `Root-Is-Purelib: false' is the # wheel's own claim that it contains something architecture-specific -- the # opposite of what every pure-Python port here asserts, and the one-line summary # of why this port is different. check_out $P/dist-info-not-purelib "Root-Is-Purelib: false" cat "$DI/WHEEL" # THE WHEEL TAG IS WRONG, AND IT IS UPSTREAM SETUPTOOLS RATHER THAN THIS PORT. # bdist_wheel computes the tag from the BUILD HOST and the cross build never # tells it otherwise, so the wheel says `cp312-cp312-linux_x86_64' for a wasm32 # module. Nothing reads it at runtime -- the .deb is what installs the file and # dpkg has the right architecture -- so this is recorded rather than repaired. # The case pins the current value so that a poky which started setting the tag # correctly is a visible change and not a silent one. check_out $P/dist-info-tag-is-host "Tag: cp312-cp312-linux_x86_64" cat "$DI/WHEEL" check_out $P/dist-info-top-level "smbus" cat "$DI/top_level.txt" check $P/dist-info-record test -s "$DI/RECORD" # ------------------------------------------------------------ the .deb file --- # The installed file list, read once into $W. The copy is what the loop below # redirects from: a stdin redirect the top-level shell performs on the virtiofs # root makes dup2(2) return 513 and ENDS the suite silently (brintos/linux#73), # and $W is on tmpfs. cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" # EXACTLY ONE SHARED OBJECT SHIPS. Everything else in the package is dist-info # metadata. A second .so would mean the build produced something this port did # not expect and did not measure. check_eq $P/pkg-one-so "1" "$(grep -c '\.so$' "$W/pkgf" 2>/dev/null)" # Every payload path is under site-packages and is the .so or a dist-info file. _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 *.so|*.py|*.pyc) ;; */METADATA|*/WHEEL|*/RECORD|*/INSTALLER|*/LICENSE|*/LICENSE.*) ;; */top_level.txt|*/entry_points.txt) ;; *) _bad="$_bad $_f" ;; esac done < "$W/pkgf" check_eq $P/pkg-only-expected "" "$_bad" # ------------------------------------------------------------- dpkg status --- sed -n '/^Package: python3-smbus$/,/^$/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: 4.3" cat "$W/st" # THE DEPENDENCY SET. The recipe DEPENDS on i2c-tools at BUILD time and the # extension links libi2c's archive. The runtime dependency set is python3-core # alone, because the archive went into the module and its dylink.0 declares no # NEEDED entry for the shlibs pass to derive. Asserted by name, because a # control stanza that had lost it would surface as an ImportError that looked # like this port's fault. 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" # The package's architecture is this arm's, which is the half the wrong wheel # tag above might otherwise be read as contradicting. check_out $P/pkg-arch "Architecture: wasm32" cat "$W/st" # ------------------------------------------------------------------ import --- # THE PLATFORM GATE, and the first of four python3 starts. This is a runtime # dlopen(3) of a side module in site-packages whose CPython API imports must # bind against a main that was linked whole-program -- BRINTOS-2046's work -- and # whose import-suspend verdicts must be seeded from that main's published # partition, which is BRINTOS-2323's, generalised to every extension recipe by # BRINTOS-2365's global class. This recipe's do_compile confirms the seeding # happened ("BRINTOS-2365: seeding this recipe's -shared links from 1275 # coroutine export(s) of the dynamic main"), and this line is where that is # tested rather than reported. check $P/import python3 -c 'import smbus' # ------------------------------------------------------------------- probe --- if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "probe missing at $PROBE" exit 0 fi bst_ok $P/probe-present # THE SECOND START, and the one that carries the suite. Bounded at 450s inside # the 1800s suite budget so a hung probe is reported as a probe overrun with the # last line it printed, rather than as a suite timeout that says nothing about # where it stopped. # # The probe EXITS 0 even when the import or a dispatch gate fails, so this case # passing means "the probe ran", not "the module works" -- `import-ok' and the # two gate cases below are what carry that. `-u' because the probe's evidence # is the lines it managed to flush before anything went wrong. bst_probe $P/probe-run "$W/probe" 450 python3 -u "$PROBE" OUT="$W/probe" # pv : the value the probe emitted for , or empty. `head -n 1' # because a key is emitted once and a duplicate would otherwise concatenate; # `cut -f2-' because a value may legitimately contain `='. pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } # Every case below reads a value the probe printed, so a probe that stopped # early would otherwise turn into a run of empty-string mismatches that all name # the wrong thing. check_eq $P/probe-complete "yes" "$(pv probe_done)" # The behavioural half, named once so the three gates below can skip it without # each repeating the list. Kept in the order the probe emits them. BEHAVIOURAL="module-answered is-extension module-name module-doc public-names has-smbus-type type-name type-module methods-complete pec-descriptor doc-open doc-close doc-read-byte ctor-noarg m-read-byte m2-read-byte m-write-quick m-write-byte m-read-byte-data m-write-byte-data m-read-word-data m-write-word-data m-process-call m-read-block-data m-write-block-data m-block-process-call m-read-i2c-block-data m-write-i2c-block-data open-0 ctor-0 ctor-kw-0 ctor-str ctor-negative open-negative open-6digit open-7digit open-7digit-msg v-block-notalist v-block-tuple v-block-33 v-block-str-element v-i2c-block-33 v-block-process-33 v-block-32 v-block-empty v-block-element-300 v-no-args v-str-addr v-too-few v-too-many v-huge-addr v-negative-register probe-count" skip_behavioural() { for _c in $BEHAVIOURAL; do bst_skip $P/$_c "$1"; done } # ------------------------------------------------------- the gates' verdicts -- # ONE case per platform fact, each carrying its own evidence. A refusal at any # of the three ends the behavioural half as SKIPS rather than as fifty reds: # they would all be the same fact, and fifty copies of a fact bury it. _imp=$(pv import_ok) check_eq $P/import-ok "yes" "$_imp" if [ "$_imp" != "yes" ]; then skip_behavioural "smbus.cpython-312.so did not load: $(pv import_error)" exit 0 fi # GATE 1 -- constructing the type. tp_new and tp_init are slot calls the # interpreter makes into the module through a function pointer. With no # argument SMBus_init takes the `bus < 0' arm and opens nothing, so the only # thing this can fail on is the dispatch itself. _ctor=$(pv dispatch_ctor) check_eq $P/ctor-dispatch "ok" "$_ctor" if [ "$_ctor" != "ok" ]; then skip_behavioural "SMBus() did not construct ($_ctor): $(pv ctor_noarg)" exit 0 fi # GATE 2 -- a METH_VARARGS method. crc32c's whole API is argument-taking and it # is fully green, so this is EXPECTED to pass; it is a gate rather than an # ordinary case because everything after it is meaningless if it does not. _va=$(pv dispatch_varargs) check_eq $P/varargs-dispatch "ok" "$_va" if [ "$_va" != "ok" ]; then skip_behavioural "a METH_VARARGS call was refused ($(pv m_read_byte)) -- linux-in-the-browser#186" exit 0 fi check_eq $P/probe-count "58" "$(pv emitted_count)" # ---------------------------------------------------------------- identity --- # WHICH COPY ANSWERED. Without this every measurement below would be a # statement about whatever was first on sys.path. check_eq $P/module-answered "$SO" "$(pv module_file)" check_eq $P/is-extension "yes" "$(pv is_extension)" # `SMBus', not `smbus' -- see the header. Measured on the control. check_eq $P/module-name "SMBus" "$(pv module_name)" check_eq $P/module-doc "This module defines an object type that allows SMBus transactions" \ "$(pv module_doc_line1)" # The module's whole public surface is the one type. A second name appearing # would be a release that grew an API. check_eq $P/public-names "SMBus" "$(pv public_names)" check_eq $P/has-smbus-type "yes" "$(pv has_smbus_type)" check_eq $P/type-name "SMBus" "$(pv type_name)" # `builtins' -- see the header. tp_name has no dot for CPython to split on. check_eq $P/type-module "builtins" "$(pv type_module)" # EVERY DOCUMENTED METHOD IS PRESENT, asserted as the whole list rather than one # `hasattr' per name: the ticket asks for six of them by name, and the list is # what also catches a method that has gone MISSING between releases. check_eq $P/methods-complete \ "block_process_call,close,open,pec,process_call,read_block_data,read_byte,read_byte_data,read_i2c_block_data,read_word_data,write_block_data,write_byte,write_byte_data,write_i2c_block_data,write_quick,write_word_data" \ "$(pv methods)" check_eq $P/pec-descriptor "getset_descriptor" "$(pv pec_descriptor)" # THE DOCUMENTED SIGNATURES, from the module's own PyDoc_STRVARs. This is what # the ticket's "with the documented signature" can mean on a C extension: the # functions take *args and carry no introspectable signature, so the docstring # IS the declaration. check_eq $P/doc-open "open(bus)" "$(pv doc_open)" check_eq $P/doc-close "close()" "$(pv doc_close)" check_eq $P/doc-read-byte "read_byte(addr) -> result" "$(pv doc_read_byte)" # ------------------------------------------- the documented error contract --- # EVERY METHOD ON A BUS THAT WAS NEVER OPENED, and each is a RESULT: fd is -1, # so the ioctl goes to a bad descriptor and EBADF comes back. errno 9 is # asserted alongside the class, because OSError alone would also be satisfied by # an unrelated failure on the way in. check_eq $P/ctor-noarg "ok" "$(pv ctor_noarg)" for _pair in \ "m-read-byte:m_read_byte" \ "m2-read-byte:m2_read_byte" \ "m-write-quick:m_write_quick" \ "m-write-byte:m_write_byte" \ "m-read-byte-data:m_read_byte_data" \ "m-write-byte-data:m_write_byte_data" \ "m-read-word-data:m_read_word_data" \ "m-write-word-data:m_write_word_data" \ "m-process-call:m_process_call" \ "m-read-block-data:m_read_block_data" \ "m-write-block-data:m_write_block_data" \ "m-block-process-call:m_block_process_call" \ "m-read-i2c-block-data:m_read_i2c_block_data" \ "m-write-i2c-block-data:m_write_i2c_block_data"; do check_eq $P/${_pair%%:*} "OSError:9" "$(pv ${_pair#*:})" done # ------------------------------------------------------------ opening a bus --- # WHAT open(2) SAYS ON A MACHINE WITH NO ADAPTER. This is the one expectation # the x86-64 control could NOT settle -- that host HAS /dev/i2c-0 and answered # EACCES -- so it is derived from the node's absence instead, and ADJUDICATED # rather than asserted: the suite reads /dev first and says which world it is # in. A guest that grew an i2c node is a different machine from the one this # port was written for, and saying so is worth more than a red that reads like a # defect in the module. if [ -e /dev/i2c-0 ]; then bst_skip $P/open-0 "/dev/i2c-0 EXISTS on this guest: ENOENT is not the expectation any more, got $(pv open_0)" bst_skip $P/ctor-0 "/dev/i2c-0 EXISTS on this guest, got $(pv ctor_0)" bst_skip $P/ctor-kw-0 "/dev/i2c-0 EXISTS on this guest, got $(pv ctor_kw_0)" else check_eq $P/open-0 "FileNotFoundError:2" "$(pv open_0)" check_eq $P/ctor-0 "FileNotFoundError:2" "$(pv ctor_0)" # The keyword spelling reaches the same PyArg_ParseTupleAndKeywords kwlist, # which is the only thing separating it from the positional one above. check_eq $P/ctor-kw-0 "FileNotFoundError:2" "$(pv ctor_kw_0)" fi # A PATH IS NOT A BUS NUMBER -- the ticket's own wording, narrowed to what the # format string does. See the header. check_eq $P/ctor-str "TypeError" "$(pv ctor_str)" # A NEGATIVE bus is not refused: SMBus_init only opens when bus >= 0, so the # constructor is quiet and open() builds "/dev/i2c--5" and fails on the path. check_eq $P/ctor-negative "ok" "$(pv ctor_negative)" check_eq $P/open-negative "FileNotFoundError:2" "$(pv open_negative)" # THE DIFFERENTIAL. Six digits fit in char[16] and reach open(2); seven do not # and raise before any syscall. One digit of arithmetic is the only thing # between the two, which is what makes the pair an assertion about the arm # rather than about the filesystem. check_eq $P/open-6digit "FileNotFoundError:2" "$(pv open_6digit)" check_eq $P/open-7digit "OverflowError" "$(pv open_7digit)" # The message, not only the class: a huge ADDRESS raises OverflowError too, and # only the text says which of the two checks fired. check_eq $P/open-7digit-msg "Bus number is invalid." "$(pv open_7digit_msg)" # ---------------------------------------------------- argument validation ---- # WHAT IS VALIDATED, WHICH IS LESS THAN THE TICKET ASSUMES -- see the header. # These fire before any ioctl, so they hold on any machine and are pure # statements about this arm's arithmetic and type checks. check_eq $P/v-block-notalist "TypeError" "$(pv v_block_notalist)" # A TUPLE IS NOT A LIST. PyList_Check, not PySequence_Check -- so the natural # spelling of the argument is refused, which is worth pinning because a future # release that relaxed it would change the API. check_eq $P/v-block-tuple "TypeError" "$(pv v_block_tuple)" check_eq $P/v-block-33 "OverflowError" "$(pv v_block_33)" check_eq $P/v-block-str-element "TypeError" "$(pv v_block_str_element)" check_eq $P/v-i2c-block-33 "OverflowError" "$(pv v_i2c_block_33)" check_eq $P/v-block-process-33 "OverflowError" "$(pv v_block_process_33)" # THE BOUNDARY, and a memory-safety assertion rather than a validation one -- # see the header. 32 bytes of 0xff go into a 34-byte union member and come back # out as an ordinary EBADF. check_eq $P/v-block-32 "OSError:9" "$(pv v_block_32)" # An EMPTY list is accepted despite the message saying "at least one": the # length check is `> 32' only. Recorded as what it does. check_eq $P/v-block-empty "OSError:9" "$(pv v_block_empty)" # An element out of byte range is not refused either -- it is cast to __u8. check_eq $P/v-block-element-300 "OSError:9" "$(pv v_block_element_300)" check_eq $P/v-no-args "TypeError" "$(pv v_no_args)" check_eq $P/v-str-addr "TypeError" "$(pv v_str_addr)" check_eq $P/v-too-few "TypeError" "$(pv v_too_few)" check_eq $P/v-too-many "TypeError" "$(pv v_too_many)" # `i' IS range-checked, by PyArg_ParseTuple rather than by the module: an # address past INT_MAX overflows the conversion. This is the only address check # that exists -- the 0x03-0x77 range the ticket asks for is not one of them. check_eq $P/v-huge-addr "OverflowError" "$(pv v_huge_addr)" check_eq $P/v-negative-register "OSError:9" "$(pv v_negative_register)" # ============================================================================ # THE TWO ISOLATED DISPATCH SHAPES. Last, and one start each -- see the header. # ============================================================================ # ------------------------------------------------------- METH_NOARGS: close -- if [ ! -f "$NOARGS_PROBE" ]; then bst_notok $P/noargs-probe-present "probe missing at $NOARGS_PROBE" else bst_ok $P/noargs-probe-present # A red on THIS case is the uncatchable arm of linux-in-the-browser#186: # SIGILL, rc=132, with `noargs_attempted=yes' the last line the probe flushed. # It is attributed there and is the regression signal that flips green by # itself the day the issue closes. bst_probe $P/noargs-probe-run "$W/noargs" 450 python3 -u "$NOARGS_PROBE" NOUT="$W/noargs" nv() { grep "^$1=" "$NOUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } check_eq $P/noargs-reached "yes" "$(nv noargs_attempted)" _nerr=$(nv noargs_error) case "$_nerr" in # The catchable arm: `RuntimeError: unreachable'. A skip carrying the issue # rather than a red, so that one platform fact is reported once -- the # `noargs-probe-run' case above is where it shows as a failure if it takes # the other arm. RuntimeError*) bst_skip $P/noargs-close "METH_NOARGS dispatch refused (linux-in-the-browser#186): $_nerr" bst_skip $P/noargs-close-twice "same refusal" ;; *) check_eq $P/noargs-close "None" "$(nv noargs_result)" check_eq $P/noargs-close-twice "None" "$(nv noargs_twice)" ;; esac fi # --------------------------------------------------------- getset: pec ------ if [ ! -f "$GETSET_PROBE" ]; then bst_notok $P/getset-probe-present "probe missing at $GETSET_PROBE" else bst_ok $P/getset-probe-present bst_probe $P/getset-probe-run "$W/getset" 450 python3 -u "$GETSET_PROBE" GOUT="$W/getset" gv() { grep "^$1=" "$GOUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } check_eq $P/getset-reached "yes" "$(gv getset_attempted)" _gread=$(gv pec_read) case "$_gread" in RuntimeError*) bst_skip $P/pec-read "getset dispatch refused (linux-in-the-browser#186): $_gread" bst_skip $P/pec-write-same "same refusal" bst_skip $P/pec-write-true "same refusal" bst_skip $P/pec-write-str "same refusal" ;; *) # SMBus_new sets pec = 0 and the getter returns Py_False without touching # the device, so False is the answer whatever is attached. check_eq $P/pec-read "ok:False" "$_gread" # The setter compares before it acts: writing the value it already holds # is a no-op that returns, and writing the other one reaches ioctl(2) on # fd -1. The pair is what says the setter arrived and took its branch. check_eq $P/pec-write-same "ok:None" "$(gv pec_write_same)" check_eq $P/pec-write-true "OSError:9" "$(gv pec_write_true)" # NOT a TypeError, despite the docstring -- see the header. A str is # truthy, so PyObject_IsTrue returns 1 and the write proceeds. check_eq $P/pec-write-str "OSError:9" "$(gv pec_write_str)" ;; esac fi rm -rf "$W" 2>/dev/null