. "$BST_LIB" # BST-TIMEOUT: 150 # 83 cases, ~65 execs, no loops and no corpus. MEASURED at # # elapsed=26s on hz1 (2026-08-25, # # ==BST-SUITE spitools elapsed=26s bound=240s cases=83==) # # and declared at ~6x that. Nothing here waits: every tool # # call either fails in argv parsing or is refused at # # open(2)/ioctl(2) and returns at once, and the one loop is # # the probe's own output echoed back as #diag lines. # spitools -- a spidev userland on a kernel that builds no SPI subsystem. # # WHAT THIS MACHINE CAN AND CANNOT TEST. spi-tools is two programs, spi-config # and spi-pipe, sharing spi-tools.c. Both are spidev clients: each opens a # /dev/spidev. character device and drives it with the SPI_IOC_* ioctls # from linux/spi/spidev.h. This kernel has no SPI subsystem at all -- SPI appears # on none of the 282 lines of arch/wasm32/configs/wasm32_defconfig, absent by # omission rather than by a "# CONFIG_SPI is not set" annotation, because # CONFIG_SPI selects a controller-driver framework and this architecture has no # SPI controller for it to drive. So no spidev driver registers, no character # major 153 appears in /proc/devices, and no /dev/spidev* node exists. # # WHAT IS THEREFORE ASSERTED FOR REAL, and it is most of both programs: every # option either tool parses, the two usage texts, the version banner, the stream # each writes to, and the exit status each documents. Those are arithmetic on # argv and calls to fprintf; they have no device in them and are the same on # every architecture -- so a wasm32 build that got them wrong is a wasm32 build # that is wrong, with no environmental excuse available. # # AND THE DEEPEST REACHABLE POINT IS ASSERTED TOO, which is further in than the # gap suggests. open(2) on a character device that is not a spidev SUCCEEDS, so # handing either tool /dev/null carries it past the open into # Read_spi_configuration() and a real SPI_IOC_RD_MODE ioctl on a real descriptor. # That is spi-tools.c executing, not merely being present, and it is asserted # against the errno the probe reads from the same ioctl rather than against a # transcribed string -- so the assertion follows the kernel if the kernel moves. # # WHAT IS PROBED RATHER THAN ASSUMED. The absence above is read at runtime from # ${libexecdir}/spitools-tests/spitools-probe and every branch below is written # against the live answer, so this suite stays correct the day the image grows a # spidev: the skips turn back into assertions on their own evidence rather than # on a comment. # # AND THE CONTROLS ARE THE POINT OF THE PROBE. "There is no spidev" and "this # process cannot open device nodes" produce the same refusal from both tools, so # two independent controls separate them. /dev/null and /dev/zero are opened; and # a character node is created with mknod(2) at spidev's major (153) beside one at # /dev/null's own (1,3), in the same directory. The filesystem makes either inode # whether or not a driver is registered, so if the (1,3) node opens and the (153) # node does not, the difference is the absent driver and nothing else. Every skip # below is gated on those controls; if they stop holding, this suite reds rather # than quietly skipping -- which is the failure mode a suite full of # environmental skips is otherwise built to have. # # WHY NOT A NATIVE ORACLE FOR THE WHOLE SUITE. The comparison suites next door # (sg3-utils, lvm2) diff byte-for-byte against an x86-64 build, because their # subject is a decoder whose output is a pure function of its input. Half of # spi-tools' output is a function of the ENVIRONMENT instead -- which device # nodes exist, what errno the kernel returns for an ioctl it does not implement # -- and an oracle built on the build host would differ from a correct target run # for reasons that are not defects. So the invariant half (usage, version, the # documented diagnostics and statuses) was read off a native x86-64 build of # spi-tools 1.0.2 during the port and is asserted directly here, and the # environment-dependent half is asserted against the probe. bst_ok spitools/suite-canary # first line: absorbs brintos/linux#96, which drops it require spi-config spitools/present # --- what upstream installs -------------------------------------------------- # # Two programs, from automake's bin_PROGRAMS. A link failure fails the build # here, so this is not aoetools' case of an install loop that swallows one -- it # is checked because the whole argv half below addresses these two names, and a # rename upstream would leave that half skipping quietly rather than reddening. for t in spi-config spi-pipe; do if command -v "$t" >/dev/null 2>&1; then bst_ok "spitools/present/$t" else bst_notok "spitools/present/$t" "not on PATH -- upstream's bin_PROGRAMS installs both spi-config and spi-pipe to \${bindir}" fi done # --- the probe --------------------------------------------------------------- P=/usr/libexec/spitools-tests/spitools-probe if [ ! -x "$P" ]; then bst_notok spitools/probe/present "no probe at $P -- spitools-tests is not installed, so nothing below can be attributed" exit 0 fi PO=$("$P" 2>&1); prc=$? if bst_execfail "$PO"; then bst_unrun spitools/probe/runs "$PO" exit 0 fi case "$PO" in *"probe end"*) bst_ok spitools/probe/runs ;; *) bst_notok spitools/probe/runs "rc=$prc, no 'probe end' -- the probe did not finish, so its values below are partial: $(_bst_1line "$PO")" exit 0 ;; esac # field -- the value of on the probe's # line. Reads from a variable, never a redirect: a top-level stdin # redirect on the virtiofs root makes dup2(2) return 513 and ash exits, ending # the suite silently (brintos/linux#73). field() { printf '%s\n' "$PO" | sed -n "s/^$1 / /p" \ | sed -n "s/.*[ ]$2=\\([^ ]*\\).*/\\1/p" | sed -n 1p } # state -- the third word of a ` present|absent` line. state() { printf '%s\n' "$PO" | sed -n "s/^$1 [^ ]* \\([a-z]*\\)$/\\1/p" | sed -n 1p } # tail_of -- everything after `=` on , kept # whole. strerror texts contain spaces, so `field` cannot carry them. tail_of() { printf '%s\n' "$PO" | sed -n "s/^$1 .*[ ]$2=//p" | sed -n 1p } null_rc=$(field control-dev-null rc) zero_rc=$(field control-dev-zero rc) mkspi_rc=$(field mknod-chr rc); mkspi_name=$(field mknod-chr name) opspi_rc=$(field open-mknod-spidev rc); opspi_name=$(field open-mknod-spidev name) mknull_rc=$(field control-mknod-null rc) opnull_rc=$(field control-open-mknod-null rc); opnull_name=$(field control-open-mknod-null name) dev00_rc=$(field open-spidev00 rc); dev00_name=$(field open-spidev00 name) n_spidev=$(field spidev-glob count) spimajor=$(field spidev-major major) busspi=$(state sysfs-bus-spi) classspidev=$(state sysfs-class-spidev) classmaster=$(state sysfs-class-spi-master) ioctl_rc=$(field ioctl-rd-mode-null rc) ioctl_name=$(field ioctl-rd-mode-null name) ioctl_err=$(tail_of ioctl-rd-mode-null strerror) # Echo what the probe answered, one line at a time, prefixed so it is greppable # and plainly not a result line. Costs nothing and saves the next reader of a # stored run a boot: every branch below is a function of these values, and # without them a stored dump records the verdicts without the evidence. printf '%s\n' "$PO" | while read -r _dl; do [ -n "$_dl" ] && printf '#diag: spitools probe: %s\n' "$_dl" done # --- the controls ------------------------------------------------------------ # # Asserted, not recorded. While they pass, an absent spidev is a fact about the # kernel's SPI subsystem; if they ever fail, it is a fact about this process and # nothing below may be attributed to CONFIG_SPI. check_eq spitools/control/dev-null 0 "$null_rc" check_eq spitools/control/dev-zero 0 "$zero_rc" check_eq spitools/control/mknod 0 "$mknull_rc" check_eq spitools/control/mknod-open 0 "$opnull_rc" controls_ok=no if [ "$null_rc" = 0 ] && [ "$zero_rc" = 0 ] \ && [ "$mknull_rc" = 0 ] && [ "$opnull_rc" = 0 ]; then controls_ok=yes fi # --- the measurement --------------------------------------------------------- # # Recorded as a value with a definite answer required, rather than asserted in # one direction: both outcomes are legitimate results and a suite that demanded # the absence would red on the good day. spidev=unknown if [ "${n_spidev:-0}" -gt 0 ] 2>/dev/null; then spidev=present elif [ "$n_spidev" = 0 ]; then spidev=absent fi case "$spidev" in present|absent) bst_ok spitools/spidev/answer ;; *) bst_notok spitools/spidev/answer "the probe printed no usable spidev-glob line" ;; esac # The three sysfs places an SPI subsystem would show, and /proc/devices' char # major. Recorded rather than asserted absent for the same reason, but they must # AGREE with each other: a kernel with /sys/bus/spi and no spidev major is a # partial SPI build and a different situation from this one, and a suite that # only counted device nodes could not tell them apart. if [ "$busspi" = absent ] && [ "$classspidev" = absent ] \ && [ "$classmaster" = absent ] && [ "$spimajor" = "-1" ]; then bst_ok spitools/spidev/sysfs-coherent elif [ "$busspi" = present ] && [ "$spimajor" != "-1" ]; then bst_ok spitools/spidev/sysfs-coherent else bst_notok spitools/spidev/sysfs-coherent "the four SPI readings disagree: /sys/bus/spi=$busspi /sys/class/spidev=$classspidev /sys/class/spi_master=$classmaster /proc/devices spidev major=$spimajor. This is a partially-present SPI subsystem, which is neither the absence the skips below claim nor a working one" fi # The two mknod readings are the control pair that makes the absence # attributable, and they are a real assertion rather than a record: the # filesystem makes both inodes, so the ONLY thing that can separate them is # whether a driver is registered at the major. if [ "$mkspi_rc" != 0 ]; then bst_notok spitools/spidev/node-open "mknod(2) of a char node at major 153 failed with $mkspi_name while the (1,3) control node was made -- the two differ only in their major, so this is not a filesystem or permission answer and the probe cannot attribute anything" elif [ "$controls_ok" != yes ]; then bst_notok spitools/spidev/node-open "the controls did not pass (dev-null=$null_rc dev-zero=$zero_rc mknod=$mknull_rc mknod-open=$opnull_rc), so this process cannot open character devices either and no spitools failure may be attributed to the absent SPI subsystem" elif [ "$spidev" = absent ] && [ "$opspi_rc" = "-1" ]; then # The reading this image is expected to give: the inode exists, the (1,3) # node beside it opens, and this one does not -- because nothing is # registered at major 153. bst_ok spitools/spidev/node-open elif [ "$spidev" = present ]; then bst_skip spitools/spidev/node-open "this image carries $n_spidev /dev/spidev* node(s), so a synthesized node at major 153 is no longer the interesting question -- assert against the real device instead" else bst_notok spitools/spidev/node-open "a char node at major 153 OPENED (rc=$opspi_rc) on an image with no /dev/spidev* and no spidev major in /proc/devices -- something is registered there that the census cannot see" fi # --- argument parsing, usage text and exit status ---------------------------- # # None of these reaches a device node, so all of them run on their merits on any # kernel. Every status here is upstream's own and is checked exactly: -h and -v # are the only two success exits in the package, and every other path documented # below exits 1. Getting those backwards is precisely what a "the tool ran" # assertion cannot see. # # The tools are invoked by BARE NAME throughout, because both interpolate # argv[0] into their diagnostics -- `usage: spi-config options...`, `spi-config: # no device specified` -- so the strings asserted here are only correct for a # PATH invocation. spitools/argv/argv0-path pins that dependence deliberately by # invoking one of them by absolute path and asserting the path comes back. apr() { # apr _aid=$1; _awant=$2; _aneedle=$3; shift 3 _aout=$("$@" 2>&1); _arc=$? case "$_aout" in *"$_aneedle"*) _ahit=1 ;; *) _ahit=0 ;; esac if bst_execfail "$_aout"; then bst_unrun "$_aid" "$_aout" elif [ "$_arc" -eq "$_awant" ] && [ "$_ahit" -eq 1 ]; then bst_ok "$_aid" else bst_notok "$_aid" "rc=$_arc want=$_awant want:'$_aneedle' got:'$(_bst_1line "$_aout")'"; fi } # -- spi-config: the usage request, which is one of the package's two exit-0 paths apr spitools/argv/config-h 0 'usage: spi-config options...' spi-config -h apr spitools/argv/config-h-device 0 '-d --device= use the given spi-dev character device.' spi-config -h apr spitools/argv/config-h-query 0 '-q --query print the current configuration.' spi-config -h apr spitools/argv/config-h-mode3 0 '3: high idle level, sample on trailing edge.' spi-config -h apr spitools/argv/config-h-wait 0 '-w --wait block keeping the file descriptor open to avoid speed reset.' spi-config -h apr spitools/argv/config-help 0 'usage: spi-config options...' spi-config --help # -- spi-config: the version banner, all four lines, and the version itself apr spitools/argv/config-v 0 'spi-config - 1.0.2' spi-config -v apr spitools/argv/config-v-copy 0 'Copyright (c) 2014-2021 Christophe Blaess. (license GPLv2)' spi-config -v apr spitools/argv/config-v-free 0 'This is free software. You are free to change and redistribute it.' spi-config -v apr spitools/argv/config-v-warr 0 'There is NO WARRANTY, to the extent permitted by law.' spi-config -v apr spitools/argv/config-version 0 'spi-config - 1.0.2' spi-config --version # -- spi-config: every documented validation, short form and long form. # The messages come from spi-tools.c's Parse_* helpers and carry the offending # string, so each is asserted with its argument in it -- a helper that printed # a fixed message would pass a needle that stopped at the colon. apr spitools/argv/config-mode-hi 1 'Error: wrong SPI mode ([0-3]): 4' spi-config -m 4 apr spitools/argv/config-mode-neg 1 'Error: wrong SPI mode ([0-3]): -1' spi-config -m -1 apr spitools/argv/config-mode-nan 1 'Error: wrong SPI mode ([0-3]): abc' spi-config -m abc apr spitools/argv/config-mode-long 1 'Error: wrong SPI mode ([0-3]): 4' spi-config --mode=4 apr spitools/argv/config-lsb 1 'Error: wrong LSB value ([0,1]): 2' spi-config -l 2 apr spitools/argv/config-lsb-long 1 'Error: wrong LSB value ([0,1]): 2' spi-config --lsb=2 apr spitools/argv/config-speed-hi 1 'Error: invalid SPI speed ([0-100000000]): 100000001' spi-config -s 100000001 apr spitools/argv/config-speed-neg 1 'Error: invalid SPI speed ([0-100000000]): -5' spi-config -s -5 apr spitools/argv/config-rdy 1 'Error: wrong SPI-ready value ([0, 1]): 2' spi-config -r 2 apr spitools/argv/config-rdy-long 1 'Error: wrong SPI-ready value ([0, 1]): 2' spi-config --spirdy=2 # spi-config validates -b INLINE rather than through Parse_spi_bits_per_word(), # so its message is a different shape from every other one above -- it is # argv[0]-prefixed and names the correct range. See spitools/argv/pipe-bits for # the same option in spi-pipe, which DOES use the helper and prints a wrong # range; the pair is the assertion, and either alone would look like a typo. apr spitools/argv/config-bits 1 'spi-config: wrong bits per word value [7...]' spi-config -b 6 apr spitools/argv/config-bits-nan 1 'spi-config: wrong bits per word value [7...]' spi-config -b abc # -- spi-config: getopt's own refusals, then upstream's `default:` line. Both # lines are asserted because the second alone would pass if getopt were # silenced, and the first alone would pass if `default:` fell through to the # usage-and-exit-0 path that -h takes. apr spitools/argv/config-badopt 1 "spi-config: invalid option -- 'Z'" spi-config -Z apr spitools/argv/config-badopt2 1 'spi-config: wrong option. Use -h for help.' spi-config -Z apr spitools/argv/config-badlong 1 "spi-config: unrecognized option '--bogus'" spi-config --bogus apr spitools/argv/config-noarg 1 "spi-config: option requires an argument -- 'm'" spi-config -m # -- spi-config: the no-device refusal, and the fact that a VALID option does not # rescue it. -b 7 parses, so this case reaches the device check rather than # the validation above -- which is what distinguishes the two exit-1 paths. apr spitools/argv/config-nodev 1 'spi-config: no device specified (use option -h for help).' spi-config apr spitools/argv/config-q-nodev 1 'spi-config: no device specified (use option -h for help).' spi-config -q apr spitools/argv/config-bits-ok 1 'spi-config: no device specified (use option -h for help).' spi-config -b 7 # -- spi-pipe: the same three families, against a DIFFERENT option table. apr spitools/argv/pipe-h 0 'usage: spi-pipe options...' spi-pipe -h apr spitools/argv/pipe-h-block 0 '-b --blocksize= transfer block size in byte.' spi-pipe -h apr spitools/argv/pipe-h-number 0 '-n --number= number of blocks to transfer (-1 = infinite).' spi-pipe -h apr spitools/argv/pipe-h-bits 0 '-B --bits=[7...] Bits per word.' spi-pipe -h apr spitools/argv/pipe-v 0 'spi-pipe - 1.0.2' spi-pipe -v apr spitools/argv/pipe-v-warr 0 'There is NO WARRANTY, to the extent permitted by law.' spi-pipe -v apr spitools/argv/pipe-mode 1 'Error: wrong SPI mode ([0-3]): 4' spi-pipe -m 4 apr spitools/argv/pipe-lsb 1 'Error: wrong LSB value ([0,1]): 2' spi-pipe -l 2 apr spitools/argv/pipe-speed 1 'Error: invalid SPI speed ([0-100000000]): -5' spi-pipe -s -5 apr spitools/argv/pipe-block0 1 'spi-pipe: wrong blocksize' spi-pipe -b 0 apr spitools/argv/pipe-blockneg 1 'spi-pipe: wrong blocksize' spi-pipe -b -1 apr spitools/argv/pipe-blocknan 1 'spi-pipe: wrong blocksize' spi-pipe -b abc apr spitools/argv/pipe-blocklong 1 'spi-pipe: wrong blocksize' spi-pipe --blocksize=0 apr spitools/argv/pipe-number 1 'spi-pipe: wrong block number' spi-pipe -n -2 apr spitools/argv/pipe-numbernan 1 'spi-pipe: wrong block number' spi-pipe -n abc apr spitools/argv/pipe-numberlong 1 'spi-pipe: wrong block number' spi-pipe --number=-2 apr spitools/argv/pipe-badopt 1 'spi-pipe: wrong option. Use -h for help.' spi-pipe -Z apr spitools/argv/pipe-nodev 1 'spi-pipe: no device specified (use option -h for help).' spi-pipe # -n 0 parses, so like config-bits-ok this reaches the device check and pins that # spi-pipe's buffer allocation -- which happens BEFORE the device check, unlike # spi-config's -- does not change which diagnostic a caller with no -d receives. apr spitools/argv/pipe-n0-nodev 1 'spi-pipe: no device specified (use option -h for help).' spi-pipe -n 0 # -B is spi-pipe's bits-per-word and it routes through spi-tools.c's # Parse_spi_bits_per_word(), whose message names the range [0, 1] -- copied from # Parse_lsb_first() -- while the value it enforces is >= 7. So the same package # rejects a 6-bit word with two different messages, and only one of them is # right. Asserted as it is, with the value in the needle, because the wrong text # is the observable fact; the pairing with spitools/argv/config-bits is what # makes it a finding rather than a transcription error in this file. apr spitools/argv/pipe-bits 1 'Error: wrong bits-per-word value ([0, 1]): 6' spi-pipe -B 6 apr spitools/argv/pipe-bits-long 1 'Error: wrong bits-per-word value ([0, 1]): 6' spi-pipe --bits=6 apr spitools/argv/pipe-bits-nan 1 'Error: wrong bits-per-word value ([0, 1]): abc' spi-pipe -B abc # -- the streams. Both tools write EVERYTHING to stderr, including the usage text # and the version banner, and the only line either sends to stdout is # spi-config's -q result -- which needs a device and is unreachable here. So # `spi-config -v > file` produces an empty file, which is worth pinning # because it is the opposite of what a caller expects and because a build that # got its stdio wrapping wrong would show up here and nowhere else above. vso=$(spi-config -v 2>/dev/null); vse=$(spi-config -v 2>&1 >/dev/null) if bst_execfail "$vse"; then bst_unrun spitools/stream/version-stderr "$vse" else if [ -z "$vso" ]; then bst_ok spitools/stream/version-stdout-empty else bst_notok spitools/stream/version-stdout-empty "spi-config -v wrote to stdout: '$(_bst_1line "$vso")' -- upstream sends all four banner lines to stderr"; fi case "$vse" in *'spi-config - 1.0.2'*) bst_ok spitools/stream/version-stderr ;; *) bst_notok spitools/stream/version-stderr "want the banner on stderr, got:'$(_bst_1line "$vse")'" ;; esac fi hso=$(spi-pipe -h 2>/dev/null); hse=$(spi-pipe -h 2>&1 >/dev/null) if bst_execfail "$hse"; then bst_unrun spitools/stream/usage-stderr "$hse" else if [ -z "$hso" ]; then bst_ok spitools/stream/usage-stdout-empty else bst_notok spitools/stream/usage-stdout-empty "spi-pipe -h wrote to stdout: '$(_bst_1line "$hso")' -- upstream sends the usage text to stderr"; fi case "$hse" in *'usage: spi-pipe options...'*) bst_ok spitools/stream/usage-stderr ;; *) bst_notok spitools/stream/usage-stderr "want the usage on stderr, got:'$(_bst_1line "$hse")'" ;; esac fi # -- argv[0]. Every diagnostic above is only correct for a PATH invocation, # because both tools interpolate argv[0] rather than their own `project` # string (which they use for the version banner alone). Pinned deliberately, # so the dependence is recorded rather than discovered by whoever next runs # one of these tools by absolute path and finds the needles no longer match. apr spitools/argv/argv0-path 1 '/usr/bin/spi-config: no device specified (use option -h for help).' /usr/bin/spi-config apr spitools/argv/argv0-usage 0 'usage: /usr/bin/spi-pipe options...' /usr/bin/spi-pipe -h # --- spi-pipe -r: an undocumented option that dereferences a NULL optarg ------- # # spi-pipe's optstring is "d:s:b:l:m:n:B:rhv" -- `r` with NO colon, so getopt # treats it as taking no argument and leaves optarg at whatever it last held, # which for a first option is NULL. `case 'r':` nonetheless hands optarg to # Parse_spi_ready(), which sscanf()s it. -r is in neither the long-option table # nor the usage text: it is a vestigial paste from spi-config, where -r IS # declared with an argument and works. # # On the x86-64 oracle this is a SIGSEGV (rc 139), reproduced three ways. It is # asserted rather than skipped, and asserted by CLASS rather than by a # transcribed status, because what a NULL sscanf does is a property of the libc # and the architecture and this suite must not encode the build host's answer as # the target's. What it must not do is exit 0: that would mean the tool accepted # an option it cannot have parsed. rout=$(spi-pipe -r &1); rrc=$? if bst_execfail "$rout"; then bst_unrun spitools/defect/pipe-r-null-optarg "$rout" elif [ "$rrc" -ge 128 ]; then bst_ok spitools/defect/pipe-r-null-optarg # died on a signal, as upstream does natively elif [ "$rrc" -ne 0 ]; then bst_ok spitools/defect/pipe-r-null-optarg # this libc returned an error instead of faulting else bst_notok spitools/defect/pipe-r-null-optarg "spi-pipe -r exited 0. optarg is NULL for a no-argument option, so Parse_spi_ready() cannot have parsed anything -- an exit 0 here means the value it wrote into new_config came from uninitialized memory" fi printf '#diag: spitools: spi-pipe -r rc=%s out=%s\n' "$rrc" "$(_bst_1line "$rout")" # And the answer THIS platform gives, pinned exactly, because it is not the # oracle's. On x86-64 glibc the NULL reaches sscanf(3) and the process dies on # SIGSEGV; on wasm32-hwjs glibc it does not fault -- Parse_spi_ready() returns # its parse failure and the diagnostic prints the NULL through printf's own # "(null)" extension, so the tool exits 1 with a message naming a value nobody # passed. Same undefined behaviour, two outcomes, and only one of them is # survivable. Pinned separately from the class assertion above so that a change # in EITHER direction is visible: the class case is what stays correct across # platforms, and this one is what says what this platform actually does. if [ "$rrc" = 1 ]; then case "$rout" in *'Error: wrong SPI-ready value ([0, 1]): (null)'*) bst_ok spitools/defect/pipe-r-null-text ;; *) bst_notok spitools/defect/pipe-r-null-text "spi-pipe -r exited 1 as measured on this arm, but not with the printf '(null)' rendering of the NULL optarg; got:'$(_bst_1line "$rout")'" ;; esac else bst_skip spitools/defect/pipe-r-null-text "spi-pipe -r exited $rrc rather than the 1 measured on wasm32-hwjs glibc 2026-08-25, so this arm no longer returns from the NULL sscanf -- the class assertion above still holds and is the one to read" fi # --- the device paths --------------------------------------------------------- # # open(2) first. A name that does not exist is refused by the kernel before # either tool reaches a line of spi-tools.c, and both report it with perror(), # so the message carries strerror(ENOENT) and is asserted with it. apr spitools/device/config-enoent 1 '/nonexistent-spidev-0.0: No such file or directory' spi-config -d /nonexistent-spidev-0.0 -q apr spitools/device/pipe-enoent 1 '/nonexistent-spidev-0.0: No such file or directory' spi-pipe -d /nonexistent-spidev-0.0 -n 1 # The same for the spidev name a user of this package would actually type. Which # refusal runs is decided by the probe's census, so this is branched rather than # assumed: on an image that grows a spidev, the ENOENT case is simply wrong. if [ "$spidev" = absent ] && [ "$dev00_name" = ENOENT ]; then apr spitools/device/config-spidev00 1 '/dev/spidev0.0: No such file or directory' spi-config -d /dev/spidev0.0 -q elif [ "$spidev" = present ]; then bst_skip spitools/device/config-spidev00 "this image carries $n_spidev /dev/spidev* node(s), so /dev/spidev0.0 no longer refuses with ENOENT -- assert the query output against the real device instead" else bst_notok spitools/device/config-spidev00 "the probe found $n_spidev /dev/spidev* node(s) but open(/dev/spidev0.0) returned $dev00_name, not ENOENT -- the census and the open disagree and neither branch may be taken on that evidence" fi # --- the ioctl surface: the deepest point this image reaches ------------------ # # THIS is the case that separates a working port from a broken one, and it is # the reason the gap above costs less coverage than it looks. open(2) on # /dev/null SUCCEEDS, so both tools carry straight past the device check into # Read_spi_configuration(), whose first act is a real SPI_IOC_RD_MODE ioctl. The # ioctl fails -- /dev/null is not a spidev -- and upstream reports it with # perror("SPI_IOC_RD_MODE"). So the assertion covers argv parsing, the open, the # library call, the ioctl, the errno and the diagnostic, on a kernel with no SPI # subsystem whatever. # # The expected text is built from the PROBE's reading of the same ioctl on the # same device rather than transcribed, so if this kernel ever returns something # other than ENOTTY the assertion follows it instead of going red for the wrong # reason. What is asserted unconditionally is the shape: the tool must name the # ioctl it failed, and it must exit 1. if [ "$ioctl_rc" = "-1" ] && [ -n "$ioctl_err" ]; then apr spitools/ioctl/config-null 1 "SPI_IOC_RD_MODE: $ioctl_err" spi-config -d /dev/null -q apr spitools/ioctl/pipe-null 1 "SPI_IOC_RD_MODE: $ioctl_err" spi-pipe -d /dev/null -n 1 # The same call reached through a directory rather than a character device. # open(O_RDONLY) on a directory succeeds, so this is a second, independent # route to the same ioctl -- present because a build whose open() silently # failed for directories would still pass the two cases above. apr spitools/ioctl/config-dir 1 "SPI_IOC_RD_MODE: $ioctl_err" spi-config -d /tmp -q # And the write path: without -q, spi-config falls through to # Write_spi_configuration(). It never gets there, because the read happens # first -- which is itself the assertion, since a tool that wrote before # reading would report SPI_IOC_WR_MODE here instead. apr spitools/ioctl/config-write 1 "SPI_IOC_RD_MODE: $ioctl_err" spi-config -d /dev/null elif [ "$ioctl_rc" = 0 ]; then bst_notok spitools/ioctl/config-null "SPI_IOC_RD_MODE SUCCEEDED against /dev/null in the probe -- that ioctl number is being answered by something that is not a spidev, and the four cases here cannot be written against it" bst_notok spitools/ioctl/pipe-null "same -- see spitools/ioctl/config-null" bst_notok spitools/ioctl/config-dir "same -- see spitools/ioctl/config-null" bst_notok spitools/ioctl/config-write "same -- see spitools/ioctl/config-null" else bst_notok spitools/ioctl/config-null "the probe printed no usable ioctl-rd-mode-null line (rc='$ioctl_rc' name='$ioctl_name' strerror='$ioctl_err'), so the expected diagnostic cannot be built from the kernel's own answer" bst_notok spitools/ioctl/pipe-null "same -- see spitools/ioctl/config-null" bst_notok spitools/ioctl/config-dir "same -- see spitools/ioctl/config-null" bst_notok spitools/ioctl/config-write "same -- see spitools/ioctl/config-null" fi # The errno itself, recorded with a definite answer required. ENOTTY is what a # character device that does not implement the ioctl returns and is what both # the oracle and this image give; anything else is a finding about the kernel's # ioctl dispatch rather than about spitools, and must not pass silently. case "$ioctl_name" in ENOTTY) bst_ok spitools/ioctl/errno ;; "") bst_notok spitools/ioctl/errno "the probe printed no ioctl-rd-mode-null name field" ;; *) bst_notok spitools/ioctl/errno "SPI_IOC_RD_MODE on /dev/null returned $ioctl_name ($ioctl_err), not the ENOTTY a character device without that ioctl returns -- worth a look at the kernel's ioctl dispatch before reading anything above" ;; esac # --- what genuinely cannot be reached here ------------------------------------ # # Everything past a SUCCESSFUL Read_spi_configuration(), which needs a real # spidev. Skipped only on the probe's confirmed absence and only with the # controls holding; each skip names what it would assert on an image that had # one, so it is a coverage prompt rather than a category. if [ "$spidev" = absent ] && [ "$controls_ok" = yes ]; then bst_skip spitools/bus/query "no /dev/spidev* on this image and none possible: SPI appears on no line of arch/wasm32/configs/wasm32_defconfig, no spidev major is in /proc/devices, /sys/bus/spi is absent, and a synthesized char node at major 153 opens $opspi_name while the (1,3) node beside it opens 0. spi-config -q's one stdout line -- 'mode=%d, lsb=%d, bits=%d, speed=%d, spiready=%d' -- needs four successful SPI_IOC_RD_* ioctls and cannot be reached" bst_skip spitools/bus/set-mode "same absence: writing a configuration needs SPI_IOC_WR_MODE et al on a real spidev, and Read_spi_configuration() refuses before Write_spi_configuration() is called" bst_skip spitools/bus/wait "same absence: spi-config -w holds the descriptor open in pause() after a successful write, so it is unreachable for the same reason, and it would not terminate if it were" bst_skip spitools/bus/transfer "same absence: spi-pipe's transfer loop needs SPI_IOC_MESSAGE(1) on a real spidev; with no device there is nothing to clock data against and its stdin/stdout halves cannot be exercised" bst_skip spitools/bus/restore "same absence: spi-pipe restores the previous configuration on the way out, which only runs after a transfer that cannot happen here" elif [ "$controls_ok" != yes ]; then bst_notok spitools/bus/query "the controls did not pass (dev-null=$null_rc dev-zero=$zero_rc mknod=$mknull_rc mknod-open=$opnull_rc), so nothing may be attributed to an absent SPI subsystem" bst_notok spitools/bus/set-mode "same -- see spitools/bus/query" bst_notok spitools/bus/wait "same -- see spitools/bus/query" bst_notok spitools/bus/transfer "same -- see spitools/bus/query" bst_notok spitools/bus/restore "same -- see spitools/bus/query" else bst_skip spitools/bus/query "this image carries $n_spidev /dev/spidev* node(s), so these five cases are now REACHABLE and need writing against the real device -- spi-config -q's output line is the place to start" bst_skip spitools/bus/set-mode "this image carries a spidev; assert a mode write followed by a query that reads it back" bst_skip spitools/bus/wait "this image carries a spidev; -w now holds a descriptor and needs a bounded driver rather than a plain exec" bst_skip spitools/bus/transfer "this image carries a spidev; assert a loopback transfer if the wiring allows one" bst_skip spitools/bus/restore "this image carries a spidev; assert the configuration is what it was before the transfer" fi