. "$BST_LIB" # BST-TIMEOUT: 300 # 114 cases, ~110 execs, no loops and no corpus. MEASURED at # # elapsed=72s on fra2 (2026-08-26, # # ==BST-SUITE i2c-tools elapsed=72s bound=300s cases=114==) # # and declared at ~4x that. Nothing here waits: every tool # # call either fails in argv parsing or is refused at # # open(2)/ioctl(2) and returns at once. # i2c-tools -- an I2C/SMBus userland on a machine that exposes no I2C adapter. # # WHAT THIS MACHINE CAN AND CANNOT TEST. i2c-tools is five programs sharing # tools/i2cbusses.c -- i2cdetect, i2cget, i2cset, i2cdump, i2ctransfer -- plus # eeprog and the static libi2c. All of them are i2c-dev clients: each resolves a # bus to /dev/i2c/ or /dev/i2c-, opens it, and drives it with the I2C_FUNCS # / I2C_SLAVE / I2C_SMBUS / I2C_RDWR ioctls of linux/i2c-dev.h. Whether this # kernel registers the i2c-dev chardev at all is NOT assumed here: the probe below # reads the live system for it, and every gated case runs the real assertion the # moment an adapter appears. # # WHAT IS THEREFORE ASSERTED FOR REAL, and it is most of all six programs: every # option each tool parses, all six usage texts, the version banner each prints and # the stream it prints it on, the whole of the bus/address/mode/length/bank/range # validation lattice, and the documented exit status of each path. Those are # arithmetic on argv and calls to fprintf; they contain no device 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, WITH ITS OWN CONTROL. open(2) # on a path that is not an i2c adapter SUCCEEDS when the path is an ordinary file, # so staging one at /dev/i2c- carries every tool past the open into a real # ioctl(I2C_FUNCS) on a real descriptor. The kernel answers ENOTTY and upstream # reports it with strerror, so one case covers argv parsing, the open, the ioctl, # the errno and the diagnostic -- on a machine with no adapter whatever. The # control is the SAME argv against a bus number with no file staged, which must # report ENOENT instead: two spellings one integer apart, and the only thing that # differs between them is whether open(2) reached the ioctl. # # EVERY EXPECTATION HERE WAS READ OFF A NATIVE x86-64 BUILD of i2c-tools 4.3 # during the port -- the exit statuses, the message wording, and which stream each # goes to -- rather than transcribed from the source. # # TWO UPSTREAM BEHAVIOURS ARE PINNED RATHER THAN PATCHED, because they are # i2c-tools' on every architecture and a future change to either should be visible # rather than silent (i2c-tools is a third-party public repository; see # no-public-upstream-reports.md). eeprog reports open(2) failing and # ioctl(I2C_FUNCS) failing with the SAME message -- "unable to open eeprom device # file (check that the file exists and that it's readable)" -- so its diagnostic # cannot distinguish a missing path from a path that is not an adapter. And # eeprog's -h prints the usage and exits 1, not 0. require i2cdetect i2c-tools/present W=${TMPDIR:-/tmp}/bst-i2c.$$ out=$W.out; err=$W.err : > "$out" # ------------------------------------------------------------------ probe ---- # Read the live system for an adapter, by every route the tools themselves use, # and by the two the kernel would have to provide first. Nothing below is # hardcoded: `adapter` is set only from what this machine actually reports, so # the gated cases run their real assertion if an adapter ever appears. adapter=""; adapter_src="" # 1) /proc/bus/i2c -- gather_i2c_busses() reads this before anything else. if [ -r /proc/bus/i2c ]; then _n=$(sed -n 's/^i2c-\([0-9][0-9]*\).*/\1/p' /proc/bus/i2c 2>/dev/null | head -1) [ -n "$_n" ] && { adapter=$_n; adapter_src=/proc/bus/i2c; } fi # 2) /sys/class/i2c-dev/i2c-N -- its sysfs fallback. if [ -z "$adapter" ]; then for d in /sys/class/i2c-dev/i2c-*; do [ -e "$d" ] || continue adapter=${d##*/i2c-}; adapter_src=/sys/class/i2c-dev; break done fi # 3) the two paths open_i2c_dev() actually tries, as character devices. if [ -z "$adapter" ]; then for d in /dev/i2c-* /dev/i2c/*; do [ -c "$d" ] || continue adapter=${d##*[-/]}; adapter_src=/dev; break done fi # ...and the two facts that decide whether an adapter COULD be there, reported # whatever the answer, so a run that skips says what it saw rather than that it # looked. if grep -q '[0-9] i2c$' /proc/devices 2>/dev/null; then chardev=yes; else chardev=no; fi [ -d /sys/bus/i2c ] && sysbus=yes || sysbus=no printf '#diag: i2c-tools probe: adapter=%s src=%s i2c-dev-chardev=%s /sys/bus/i2c=%s\n' \ "${adapter:-none}" "${adapter_src:-none}" "$chardev" "$sysbus" # A bus number nothing resolves to, chosen by looking rather than by assuming -- # the nonexistent-bus cases below are only meaningful against a bus that really # is absent. free="" for n in 99 98 97 96 95 94 93 92 91 90; do [ -e "/dev/i2c-$n" ] && continue [ -e "/dev/i2c/$n" ] && continue free=$n; break done if [ -z "$free" ]; then bst_notok i2c-tools/free-bus "every candidate bus number 90-99 already exists in /dev; the absent-bus assertions cannot be posed" free=99 else bst_ok i2c-tools/free-bus fi # ---------------------------------------------------------------- identity --- # Every tool prints its banner on STDERR and exits 0. Both halves are asserted: # check_out fails the case unless the status is 0, and the needle is the whole # banner including the version this image built. check_out i2c-tools/version-detect "i2cdetect version 4.3" i2cdetect -V check_out i2c-tools/version-get "i2cget version 4.3" i2cget -V check_out i2c-tools/version-set "i2cset version 4.3" i2cset -V check_out i2c-tools/version-dump "i2cdump version 4.3" i2cdump -V check_out i2c-tools/version-transfer "i2ctransfer version 4.3" i2ctransfer -V # stdout carries nothing for -V: the banner is on stderr, and a tool that moved # it would still pass the case above. Assert the split itself. check_eq i2c-tools/version-stream "" "$(i2cdetect -V 2>/dev/null)" # update-alternatives owns all five names at priority 100; the real binaries are # .i2c-tools. eeprog is deliberately NOT under alternatives (the recipe's # ALTERNATIVE list names only the five), so it resolves to itself. for t in i2cdetect i2cdump i2cget i2cset i2ctransfer; do p=$(command -v $t 2>/dev/null) if [ -n "$p" ] && [ "$(readlink "$p")" = "$t.i2c-tools" ]; then bst_ok i2c-tools/alternative-$t else bst_notok i2c-tools/alternative-$t "want $p -> $t.i2c-tools, got '$(readlink "$p" 2>/dev/null)'" fi done # ------------------------------------------------------------- bus listing --- # `i2cdetect -l` is the one command the ticket names that has a defined answer on # a machine with no adapter: it walks the same two sources the probe did and # prints one line per bus, so with none present it prints NOTHING and exits 0. # Asserted as a value against what the probe found, not as a fixed string -- an # adapter appearing turns this into the positive assertion by itself. lrc=0 lout=$(i2cdetect -l 2>"$err") || lrc=$? check_eq i2c-tools/list-rc 0 "$lrc" if [ -z "$adapter" ]; then check_eq i2c-tools/list-empty "" "$lout" # ...and it must not have complained on the way: an empty list is the # correct answer here, not an error suppressed into silence. check_eq i2c-tools/list-silent "" "$(cat "$err")" else check_out i2c-tools/list-names "i2c-$adapter" printf '%s\n' "$lout" fi # ------------------------------------------------------- argument validation -- # No arguments. i2cdetect and i2cdump name the missing operand first; i2cget, # i2cset and i2ctransfer go straight to the usage. All five exit 1. check_rc i2c-tools/noargs-detect-rc 1 sh -c "i2cdetect > '$out' 2>&1" check_out i2c-tools/noargs-detect "Error: No i2c-bus specified!" cat "$out" check_out i2c-tools/usage-detect "Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]" cat "$out" check_rc i2c-tools/noargs-get-rc 1 sh -c "i2cget > '$out' 2>&1" check_out i2c-tools/usage-get "Usage: i2cget [-f] [-y] [-a] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE [LENGTH]]]" cat "$out" check_rc i2c-tools/noargs-set-rc 1 sh -c "i2cset > '$out' 2>&1" check_out i2c-tools/usage-set "Usage: i2cset [-f] [-y] [-m MASK] [-r] [-a] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]" cat "$out" check_rc i2c-tools/noargs-dump-rc 1 sh -c "i2cdump > '$out' 2>&1" check_out i2c-tools/noargs-dump "Error: No i2c-bus specified!" cat "$out" check_out i2c-tools/usage-dump "Usage: i2cdump [-f] [-y] [-r first-last] [-a] I2CBUS ADDRESS [MODE [BANK [BANKREG]]]" cat "$out" check_rc i2c-tools/noargs-transfer-rc 1 sh -c "i2ctransfer > '$out' 2>&1" check_out i2c-tools/usage-transfer "Usage: i2ctransfer [-f] [-y] [-v] [-V] [-a] I2CBUS DESC [DATA] [DESC [DATA]]..." cat "$out" # An option none of them takes. The diagnostic quotes the offending argv entry, # so this also shows argv reaching the tool intact. for t in i2cdetect i2cget i2cset i2cdump i2ctransfer; do sh -c "$t -Z > '$out' 2>&1"; rc=$? check_eq i2c-tools/badopt-$t-rc 1 "$rc" check_out i2c-tools/badopt-$t 'Error: Unsupported option "-Z"!' cat "$out" done # Bus number above the 0xFFFFF ceiling lookup_i2c_bus() enforces. This is a # range check, not an open, so it fires on every tool that resolves a bus. check_rc i2c-tools/busrange-detect-rc 1 sh -c "i2cdetect -y 0x100000 > '$out' 2>&1" check_out i2c-tools/busrange-detect "Error: I2C bus out of range!" cat "$out" check_rc i2c-tools/busrange-transfer-rc 1 sh -c "i2ctransfer -y 0x100000 r1@0x50 > '$out' 2>&1" check_out i2c-tools/busrange-transfer "Error: I2C bus out of range!" cat "$out" # A bus given by NAME rather than by number takes the other half of # lookup_i2c_bus(): it gathers the adapters and matches names. With none present # nothing can match, and that is the message -- which is a statement about this # machine, so it is gated on the probe. if [ -z "$adapter" ]; then check_rc i2c-tools/busname-detect-rc 1 sh -c "i2cdetect -y no-such-bus-name > '$out' 2>&1" check_out i2c-tools/busname-detect "Error: I2C bus name doesn't match any bus present!" cat "$out" check_rc i2c-tools/busname-transfer-rc 1 sh -c "i2ctransfer -y no-such-bus-name r1@0x50 > '$out' 2>&1" check_out i2c-tools/busname-transfer "Error: I2C bus name doesn't match any bus present!" cat "$out" else bst_skip i2c-tools/busname-detect "adapter $adapter is present (via $adapter_src), so a name lookup could match a real bus; the no-match message is not this machine's answer" bst_skip i2c-tools/busname-transfer "adapter $adapter is present (via $adapter_src); see i2c-tools/busname-detect" fi # The chip-address lattice. parse_i2c_address() is shared by i2cget/i2cset/ # i2cdump, and -a is what widens 0x08-0x77 to 0x00-0x7f. The pair below is a # DIFFERENTIAL on that one flag: same address, same bus, and the only thing that # changes the outcome is -a -- which carries the second run past the address # check and into the open. A build that ignored -a would pass neither. check_rc i2c-tools/addr-range-rc 1 sh -c "i2cget -y $free 0x05 > '$out' 2>&1" check_out i2c-tools/addr-range "Error: Chip address out of range (0x08-0x77)!" cat "$out" check_rc i2c-tools/addr-range-all-rc 1 sh -c "i2cget -y -a $free 0x05 > '$out' 2>&1" check_out i2c-tools/addr-range-all "Error: Could not open file" cat "$out" check_rc i2c-tools/addr-nan-rc 1 sh -c "i2cget -y $free zz > '$out' 2>&1" check_out i2c-tools/addr-nan "Error: Chip address is not a number!" cat "$out" # i2cget's data-address / mode / length lattice, one case per refusal, each with # the message that distinguishes it from its neighbours. check_rc i2c-tools/get-daddr-rc 1 sh -c "i2cget -y $free 0x50 0x100 > '$out' 2>&1" check_out i2c-tools/get-daddr "Error: Data address invalid!" cat "$out" check_rc i2c-tools/get-mode-rc 1 sh -c "i2cget -y $free 0x50 0x00 z > '$out' 2>&1" check_out i2c-tools/get-mode "Error: Invalid mode!" cat "$out" check_rc i2c-tools/get-pec-rc 1 sh -c "i2cget -y $free 0x50 0x00 ip > '$out' 2>&1" check_out i2c-tools/get-pec "Error: PEC not supported for I2C block data!" cat "$out" check_rc i2c-tools/get-len-mode-rc 1 sh -c "i2cget -y $free 0x50 0x00 b 4 > '$out' 2>&1" check_out i2c-tools/get-len-mode "Error: Length only valid for I2C block data!" cat "$out" check_rc i2c-tools/get-len-rc 1 sh -c "i2cget -y $free 0x50 0x00 i 0 > '$out' 2>&1" check_out i2c-tools/get-len "Error: Length invalid!" cat "$out" # i2cset validates the value and names the offending mode character, which # i2cget's equivalent message does not -- two different strings for the same # class of mistake, and each is asserted where it belongs. check_rc i2c-tools/set-mode-rc 1 sh -c "i2cset -y $free 0x50 0x00 0x00 z > '$out' 2>&1" check_out i2c-tools/set-mode "Error: Invalid mode 'z'!" cat "$out" check_rc i2c-tools/set-value-rc 1 sh -c "i2cset -y $free 0x50 0x00 0x100 > '$out' 2>&1" check_out i2c-tools/set-value "Error: Data value out of range!" cat "$out" # i2cdump: the missing-address path, the mode lattice, the bank range and the # -r range parser -- whose two refusals are the only ones in the package that # print no usage text after them. check_rc i2c-tools/dump-noaddr-rc 1 sh -c "i2cdump -y $free > '$out' 2>&1" check_out i2c-tools/dump-noaddr "Error: No address specified!" cat "$out" check_rc i2c-tools/dump-mode-rc 1 sh -c "i2cdump -y $free 0x50 z > '$out' 2>&1" check_out i2c-tools/dump-mode "Error: Invalid mode!" cat "$out" check_rc i2c-tools/dump-bank-rc 1 sh -c "i2cdump -y $free 0x50 b 99 > '$out' 2>&1" check_out i2c-tools/dump-bank "Error: bank out of range!" cat "$out" check_rc i2c-tools/dump-range-rc 1 sh -c "i2cdump -y -r 5-2 $free 0x50 > '$out' 2>&1" check_out i2c-tools/dump-range "Error: Invalid range parameter!" cat "$out" # ...and with no MODE operand it says so on stderr and carries on to the open, # which is a note rather than a refusal and is asserted as such. check_out i2c-tools/dump-nosize "No size specified (using byte-data access)" cat "$out" # ------------------------------------------------ the absent bus, per tool --- # The ticket's explicit ask: every tool handed a bus that does not exist, with # the documented status. All five resolve the number, fail at open(2) with # ENOENT, and exit 1 -- naming BOTH paths they tried, which is what shows the # /dev/i2c/ fallback is compiled in and not just the /dev/i2c- form. for c in "detect:i2cdetect -y $free" \ "get:i2cget -y $free 0x50" \ "set:i2cset -y $free 0x50 0x00 0x00" \ "dump:i2cdump -y $free 0x50" \ "transfer:i2ctransfer -y $free w1@0x50 0x64"; do id=${c%%:*}; cmd=${c#*:} sh -c "$cmd > '$out' 2>&1"; rc=$? check_eq i2c-tools/noent-$id-rc 1 "$rc" check_out i2c-tools/noent-$id "Error: Could not open file \`/dev/i2c-$free' or \`/dev/i2c/$free': No such file or directory" cat "$out" done # ------------------------------------------- the deepest reachable point ----- # Stage an ordinary file where the tools look for the bus. open(2) then SUCCEEDS # and every one of them runs a real ioctl(I2C_FUNCS) on a real descriptor, which # this kernel answers ENOTTY. The staging is probed, not assumed: if /dev cannot # be written the cases skip with that reason rather than reporting a verdict # nothing produced. node=/dev/i2c-$free staged=no # `touch`, not `: > "$node"`: a redirection that fails is FATAL to the suite # shell -- dash reports it and exits, and the 2>/dev/null cannot suppress it # because the failing redirect is processed first. That is the "suite ends # early while reporting a clean pass" failure the packager README exists to # prevent, and on a read-only /dev it would fire on every run. touch is an # ordinary command, so its failure is a status this can test. MEASURED: the # redirect form killed the suite at this line on the build-host oracle. if [ ! -e "$node" ] && touch "$node" 2>/dev/null && [ -f "$node" ]; then staged=yes; fi if [ "$staged" = yes ]; then bst_ok i2c-tools/stage-node for c in "detect:i2cdetect -y $free" \ "func:i2cdetect -F $free" \ "get:i2cget -y $free 0x50" \ "set:i2cset -y $free 0x50 0x00 0x00" \ "dump:i2cdump -y $free 0x50" \ "transfer:i2ctransfer -y $free r1@0x50"; do id=${c%%:*}; cmd=${c#*:} sh -c "$cmd > '$out' 2>&1"; rc=$? check_eq i2c-tools/enotty-$id-rc 1 "$rc" check_out i2c-tools/enotty-$id \ "Error: Could not get the adapter functionality matrix: Inappropriate ioctl for device" cat "$out" done # THE CONTROL. The same tool and the same argv shape against a bus with no # file staged must report ENOENT, not ENOTTY. Without this pair the cases # above are consistent with a tool that never opened anything. ctl=$((free - 1)) if [ ! -e "/dev/i2c-$ctl" ] && [ ! -e "/dev/i2c/$ctl" ]; then sh -c "i2cget -y $ctl 0x50 > '$out' 2>&1" if grep -q 'No such file or directory' "$out" && ! grep -q 'Inappropriate ioctl' "$out"; then bst_ok i2c-tools/enotty-control else bst_notok i2c-tools/enotty-control "bus $ctl has no node staged but did not report ENOENT: $(_bst_1line "$(cat "$out")")" fi else bst_skip i2c-tools/enotty-control "bus $ctl also resolves, so it cannot serve as the no-node control" fi rm -f "$node" else bst_skip i2c-tools/stage-node "cannot create $node (/dev is not writable here), so the ioctl depth cases have no vehicle" for id in detect func get set dump transfer; do bst_skip i2c-tools/enotty-$id "no node could be staged at $node; see i2c-tools/stage-node" done bst_skip i2c-tools/enotty-control "no node could be staged; see i2c-tools/stage-node" fi # ------------------------------------------------------------------ eeprog --- # eeprog is the sixth program and the only one outside update-alternatives. Its # whole argv path is reachable without a device through -d (dummy), which parses # everything and then declines to open. check_rc i2c-tools/eeprog-noargs-rc 1 sh -c "eeprog > '$out' 2>&1" check_out i2c-tools/eeprog-usage "Usage: eeprog [-fqxdh] [-16|-8] [ -r addr[:count] | -w addr ] /dev/i2c-N i2c-address" cat "$out" check_out i2c-tools/eeprog-banner "eeprog 0.7.5, a 24Cxx EEPROM reader/writer" cat "$out" # -h is a documented option and still exits 1. Pinned as it is, not as it should be. check_rc i2c-tools/eeprog-help-rc 1 sh -c "eeprog -h > '$out' 2>&1" check_rc i2c-tools/eeprog-dummy-rc 0 sh -c "eeprog -d -r 0:1 /dev/i2c-$free 0x50 > '$out' 2>&1" check_out i2c-tools/eeprog-dummy "Dummy mode selected, nothing done." cat "$out" # Not dummy: it opens, and on this machine it cannot. eeprog reports open(2) and # ioctl(2) failing identically, so only the one message is asserted -- see the # header. check_rc i2c-tools/eeprog-open-rc 1 sh -c "eeprog -f -q -r 0:1 /dev/i2c-$free 0x50 > '$out' 2>&1" check_out i2c-tools/eeprog-open "unable to open eeprom device file" cat "$out" # ------------------------------------------------------- the shipped library -- # The port's other half. libi2c.so is not built on this arm (brintos.conf is a # static-only world and wasmld refuses a -shared side-module link handed native # wasm inputs), so the deliverable is the archive plus the public header, and # BOTH must be here -- the archive alone would be unusable and the header alone # would be a promise. if [ -f /usr/lib/libi2c.a ]; then bst_ok i2c-tools/libi2c-a else bst_notok i2c-tools/libi2c-a "/usr/lib/libi2c.a is not installed"; fi if [ -f /usr/include/i2c/smbus.h ]; then bst_ok i2c-tools/smbus-h else bst_notok i2c-tools/smbus-h "/usr/include/i2c/smbus.h is not installed"; fi check_out i2c-tools/smbus-h-api "i2c_smbus_read_byte_data" cat /usr/include/i2c/smbus.h # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape i2c-tools/no-shared "/usr/lib/libi2c.so" "/usr/lib/libi2c.so.0" # ------------------------------------------------------- adapter-gated cases -- # Each of these runs the real assertion the moment an adapter exists. if [ -n "$adapter" ]; then check i2c-tools/funcs i2cdetect -F "$adapter" check i2c-tools/scan i2cdetect -y -r "$adapter" else bst_skip i2c-tools/funcs "no I2C adapter on this machine: /proc/bus/i2c absent, /sys/class/i2c-dev empty, no /dev/i2c-* or /dev/i2c/* character device (i2c-dev chardev in /proc/devices: $chardev, /sys/bus/i2c: $sysbus) -- there is no adapter whose functionality matrix could be read" bst_skip i2c-tools/scan "no I2C adapter to probe; the address lattice and the open/ioctl path are covered by the noent-* and enotty-* cases above" fi # The man pages ship in i2c-tools-doc and this image installs no -doc packages. # Not a defect, and it flips green by itself if that ever changes. if [ -f /usr/share/man/man8/i2cdetect.8 ] || [ -f /usr/share/man/man8/i2cdetect.8.gz ]; then check_out i2c-tools/man-page "i2cdetect" sh -c "zcat -f /usr/share/man/man8/i2cdetect.8* 2>/dev/null | head -40" else bst_skip i2c-tools/man-page "i2cdetect.8 ships in i2c-tools-doc; this image installs no -doc packages" fi # The five perl scripts of i2c-tools-misc are deliberately not installed: every # one of them decodes an SPD/EDID EEPROM through an i2c bus, and i2c-stub-from-dump # needs the i2c-stub kernel module. Assert they stayed out, so a packaging change # that ships them -- and with them a runtime dependency on perl-module-strict and # perl-module-vars, which this image does not otherwise carry -- is caught here. misc="" for f in /usr/bin/decode-dimms /usr/bin/decode-edid /usr/bin/decode-vaio \ /usr/bin/ddcmon /usr/sbin/i2c-stub-from-dump; do [ -e "$f" ] && misc="$misc $f" done if [ -n "$misc" ]; then bst_notok i2c-tools/no-misc "i2c-tools-misc files are installed:$misc" else bst_ok i2c-tools/no-misc fi rm -f "$out" "$err" "$W".* 2>/dev/null