# BST-TIMEOUT: 300 # MEASURED: elapsed 8 s for 76 cases (2026-08-18, local # # functional verify, idle host). # # 300 is derived from the probe's WORST case, not from that 8 s, and the gap # between the two is the point. THE FIRST VERSION OF THIS FILE SET 120 AND WAS # KILLED BY IT after 18 cases -- not because the suite is slow (it is not; the # same suite now runs in 8 s) but because the probe hung on its very first act # and burned the whole budget in one call. A bound sized from a healthy run # would have been just as wrong; what it has to cover is the unhealthy one. # # The unhealthy one is bounded by construction: the probe caps its blocking # section at 150 s (run_bounded, which uses waitpid+nanosleep and therefore # holds even where alarm(2) is ENOSYS -- as it is on a kernel without the # brintos/linux#77 fix, where the per-call 5 s alarm guard does nothing at # all). 150 s of blocking section plus # the forked shell cases is the ceiling; 300 is 2x that, and ~37x the measured # 8 s, which is the headroom the nightly needs on a loaded host. # # . "$BST_LIB" is the first executable line; everything above is the reader's # brief, and it is long because the shape of this suite is not obvious from its # assertions. . "$BST_LIB" # libsocketcan 0.0.12 -- Pengutronix's userspace control-plane library for # SocketCAN (meta-oe recipes-extended/socketcan). What ships is # /usr/lib/libsocketcan.a, /usr/lib/pkgconfig/libsocketcan.pc and two headers, # /usr/include/libsocketcan.h and /usr/include/can_netlink.h. There is no CLI, # no daemon, no check_PROGRAMS and no tests/ directory at any tag, so on an # image with no compiler nothing that ships can drive a line of it -- hence the # probe, built in the recipe against the archive that ships. # # --------------------------------------------------------------------------- # THIS IMAGE HAS NO CAN DEVICE, AND THAT IS THE WHOLE DESIGN PROBLEM. # # arch/wasm32/configs/wasm32_defconfig sets CONFIG_NET=y and has no CONFIG_CAN # line at all: the CAN protocol family is not built, no `can` rtnl_link_ops kind # is registered, and vcan does not exist to be created. All seventeen public # functions take an interface name and address a CAN netdev. There is none. # # The naive suite writes itself and is worthless. Point all seventeen at "can0", # watch all seventeen fail with ENODEV, report 17 red, attribute them to the # missing kernel option, ship. A libsocketcan.a miscompiled into rubble would # produce THE SAME SEVENTEEN FAILURES, because none of them would have reached a # line of library code that does anything. The suite would assert nothing about # the port while looking like a thorough one. # # So the probe drives the API three ways and this file scores the three apart. # What separates them is not the return value -- almost everything returns -1 -- # but WHERE the library gave up, which the probe reports as a stderr class: # # E group, an interface name that cannot exist (bstnocan0). if_nametoindex # returns 0 and the library gives up before composing a message. NO # NETLINK TRAFFIC HAPPENS. class `nodev`, errno ENODEV. This is the # ticket's stated fallback case: the library refuses a bad name # cleanly rather than hanging or crashing. # # L group, `lo` -- a real interface that is really not a CAN device. THE NAME # RESOLVES, so the library goes all the way: open_nl_sock, bind, # getsockname, RTM_GETLINK composition, sendto, a BLOCKING recvmsg # loop, the NLMSG_OK walk, the ifinfomsg decode and the nested-rtattr # parse. It then returns -1 because `lo` carries no # IFLA_LINKINFO/IFLA_INFO_DATA to decode -- having done the entire # netlink transaction. THIS IS THE PROOF THE PORT REACHES NETLINK, and # it is the assertion the ticket asks for. # # V group, a CAN-kind netdev. Unreachable here. Scored `skip`, see below. # # WHY L01-L08 RETURNING -1 WITH NO STDERR AT ALL IS A STRONG RESULT, not a weak # one. The library's receive loop is `while (!done && recvmsg(...) > 0)`, and # `done` is set ONLY when a reply arrives AND its IFLA_IFNAME matches the name # asked for. There is no other way out of that loop. So if netlink were not # working the call would not return at all -- it would sit in recvmsg until the # probe's fork bound killed the whole blocking section, and these cases would be # ABSENT rather than wrong, with probe-bound red to say so. # # That is why err=none is asserted rather than dismissed as "nothing happened": # the message's absence, together with the case existing at all, is what proves # the transaction completed and the name matched. Note the evidence runs through # probe-bound and NOT through the per-call `timeout=` column, which is inert # on a kernel without the brintos/linux#77 fix -- alarm(2) is ENOSYS there, so # nothing ever sets it. An argument resting on the alarm guard would be wrong # on such a kernel, and the fork bound is what carries it instead, on either # kernel. # # --------------------------------------------------------------------------- # THE ORACLE IS A NATIVE x86-64 RUN OF THE SAME PROBE AGAINST THE SAME 0.0.12 # # Every value pinned below was read off a native build of # libsocketcan_probe.c against libsocketcan 0.0.12 at SRCREV 077def39 -- the # recipe's own source -- in two configurations, because privilege changes two of # the answers and only one of the two matches the target: # # ORACLE A unprivileged, host network namespace. # ORACLE B root with CAP_NET_ADMIN in a private user+network namespace, with # a real `ip link add dev vcan0 type vcan` present. # # The target runs its suites as root, so ORACLE B is the one pinned for the L # setters: they answer EOPNOTSUPP there and EPERM under ORACLE A, because the # kernel checks the capability before it checks the link kind. Pinning the # unprivileged value would have made every L setter red on target for a reason # that has nothing to do with wasm32. # # THE STRUCT SIZES AND OFFSETS ARE THE ASSERTION A TARGET-ONLY RUN CANNOT MAKE. # can_bittiming, can_bittiming_const, can_clock, can_ctrlmode, can_berr_counter # and can_device_stats are copied verbatim into and out of netlink attribute # payloads -- they are uapi wire structs of fixed-width __u32/__u16, so a wasm32 # build and an x86-64 build MUST lay them out identically. Writing a bitrate and # reading it back would prove the writer and the reader agree; it would not # prove they are RIGHT, because a port that placed sample_point at bitrate's # offset would round-trip through its own mistake and report every value # correct. Only the pinned numbers catch that, and they are checked against the # declarations rather than merely copied: can_bittiming is 8 x __u32 = 32 at # 0,4,8,...,28; can_bittiming_const is a 16-byte char[] name + 8 x __u32 = 48; # can_berr_counter is 2 x __u16 = 4 at 0,2. # # --------------------------------------------------------------------------- # WHAT THE vcan RUN CORRECTED, AND WHY THE V GROUP IS `skip` AND NOT `not ok` # # ORACLE B was built to demonstrate the bring-up the ticket describes: create a # virtual CAN interface, set a bitrate and a restart interval, read both back. # IT DOES NOT WORK, AND CANNOT. vcan registers no can_bittiming_const, so the # kernel answers EOPNOTSUPP to IFLA_CAN_BITTIMING and IFLA_CAN_RESTART_MS, and # emits IFLA_LINKINFO carrying kind "vcan" with NO IFLA_INFO_DATA -- leaving # every CAN getter reporting "no link data found". Bit timing is a property of a # controller driver and vcan has no controller, so NO purely virtual CAN device # exercises those paths; only real silicon does. What a vcan does honour is # can_do_start/can_do_stop, which return 0 and really flip IFF_UP. # # So the V group's expectations below are the MEASURED vcan answers, not an # idealised bring-up. And they are scored `skip` on an image with no CAN device # rather than `not ok`, which is the narrower and more honest claim: a red would # assert "this assertion is known good and merely blocked", and on this image it # has never been executed. The E and L groups are what hold this port, and they # are expected fully green -- unlike the neighbouring 79-libnfnetlink.test, # whose transport protocol really is absent from this kernel, libsocketcan's # NETLINK_ROUTE is registered unconditionally under CONFIG_NET. # # --------------------------------------------------------------------------- # FORK BUDGET. The scoring is ONE awk pass over the probe's output into shell # variables, not a fork per case. On this guest a fork is expensive enough that # the per-case idiom is what makes a suite time out rather than the work. R=${BST_ROOT:-} PROBE=$R/usr/libexec/libsocketcan-tests/libsocketcan-probe ARCHIVE=$R/usr/lib/libsocketcan.a PCFILE=$R/usr/lib/pkgconfig/libsocketcan.pc INC=$R/usr/include # The version the recipe pins. Asserted rather than read back from the library, # so a recipe bumped without updating this suite is a red here instead of a # silent change of subject -- the values above are only the oracle for THIS # version. WANT_VERSION=0.0.12 if [ ! -f "$ARCHIVE" ]; then bst_skip libsocketcan/present "libsocketcan not installed on this image" exit 0 fi bst_ok libsocketcan/present # --- the package is whole ----------------------------------------------------- check libsocketcan/staticlib test -s "$ARCHIVE" check libsocketcan/pkgconfig test -s "$PCFILE" check libsocketcan/hdr-main test -s "$INC/libsocketcan.h" check libsocketcan/hdr-can-netlink test -s "$INC/can_netlink.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 libsocketcan/no-shared "$R/usr/lib/libsocketcan.so" "$R/usr/lib/libsocketcan.so.2" # The .pc must name the version the recipe pinned, or a consumer resolving # libsocketcan through pkg-config gets a different answer from the one this # suite pinned its values against. pcver=$(sed -n 's/^Version: *//p' "$PCFILE" 2>/dev/null | head -1) check_eq libsocketcan/pkgconfig-version "$WANT_VERSION" "$pcver" # The .pc's link line is what a consumer -- canutils above all -- resolves # through; -lsocketcan is the whole of it, since the library has no dependency. check_out libsocketcan/pkgconfig-libs "-lsocketcan" cat "$PCFILE" # The archive carries the API. grep rather than nm: the image ships no binutils # nm, and the names are present as plain bytes. -a where the grep supports it -- # on this arm the archive members are LLVM IR bitcode (ThinLTO), so a grep that # suppresses matches in a file containing NULs reports nothing; GNU grep needs # -a, busybox grep as shipped does not. Probing keeps this measuring # libsocketcan rather than measuring the image's grep. if echo x | grep -aq x 2>/dev/null; then _grepq="grep -aq"; else _grepq="grep -q"; fi # TEN of the seventeen, chosen to cover both halves of the API (the four the # ticket names first) rather than to be exhaustive -- see the fork-budget note. for sym in can_do_start can_do_stop can_do_restart can_set_bitrate \ can_set_bittiming can_set_ctrlmode can_set_restart_ms \ can_get_state can_get_bittiming can_get_link_stats; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_ok "libsocketcan/sym-$sym" else bst_notok "libsocketcan/sym-$sym" "$sym absent from $ARCHIVE" fi done # --- the probe ---------------------------------------------------------------- if [ ! -x "$PROBE" ]; then bst_skip libsocketcan/probe-runs "$PROBE absent from the image" exit 0 fi tmp=${TMPDIR:-/tmp} pout=$tmp/bst-libsocketcan-out.$$ "$PROBE" > "$pout" 2>&1 prc=$? if [ "$prc" -eq 0 ]; then bst_ok libsocketcan/probe-runs elif bst_execfail "$(head -c 200 "$pout" 2>/dev/null)"; then bst_unrun libsocketcan/probe-runs "$(head -c 200 "$pout" 2>/dev/null)" rm -f "$pout" exit 0 else bst_notok libsocketcan/probe-runs "probe exited $prc: $(_bst_1line "$(head -c 300 "$pout")")" fi # PROBE-END is the terminator. Without it a probe killed partway through would # leave every unreached case reporting its default and the suite would score a # truncated run as a clean one. if grep -q '^PROBE-END$' "$pout" 2>/dev/null; then bst_ok libsocketcan/probe-complete else bst_notok libsocketcan/probe-complete "probe output has no PROBE-END terminator (last line: $(tail -1 "$pout" 2>/dev/null))" fi # ONE awk pass over the probe's output, turning every line into a shell variable # V_. This is the whole of the process cost of scoring ~70 cases. The ID is # matched in field 1 so a line's position never matters. eval "$(awk ' function q(v) { gsub(/'"'"'/, "'"'"'\\'"'"''"'"'", v); return "'"'"'" v "'"'"'" } ($1 ~ /^[ANSELVX][0-9][0-9]$/ || $1 == "BOUND") && seen[$1]++ == 0 { id = $1; v = $0; sub(/^[^ ]+ ?/, "", v); print "V_" id "=" q(v) } ' "$pout")" # --- the instrument's own control -------------------------------------------- # Every `timeout=` reading below rests on alarm(2) being able to interrupt a # blocking syscall on this arm. A01 measures that directly, with a poll() on an # empty pipe that can only end by signal or by its own timeout -- the two are # distinguishable, which is the case. A01 failing does NOT by itself mean the # port is broken: it means the per-call timeout column is decorative. It is # scored on its own, and never folded into the cases it underwrites, so the two # findings stay separable. case "$V_A01" in guard=interrupts) bst_ok libsocketcan/alarm-guard ;; guard=nosignal*) # EXPECTED GREEN on a kernel with the brintos/linux#77 fix. On a kernel # without it alarm(2) is unavailable (A02 below says in which way), so # SIGALRM never arrives and the per-call `timeout=` column means only "the # call returned", not "the call was timed". A red here is NOT a # libsocketcan defect and is not scored as one: on a kernel with the fix it # is a regression, and on a kernel pin without it the case reads red by # design. What keeps the run finite either way is the probe's fork bound, # scored separately as probe-bound. bst_notok libsocketcan/alarm-guard \ "PLATFORM-NO-ALARM (brintos/linux#77): SIGALRM did not interrupt a blocking poll() within 3s ($V_A01) -- a regression on a kernel with the alarm(2) fix, red by design on a kernel pin without it; a platform fault and not a libsocketcan defect; the probe's fork bound is what makes the run finite" ;; *) bst_notok libsocketcan/alarm-guard "unexpected A01: '$V_A01'" ;; esac # WHY the guard does or does not work, which A01 alone cannot say: alarm() # unimplemented, alarm() fine but SIGALRM undelivered, and SIGALRM delivered but # not interrupting are three faults in three different places. This case also # pins the specific trap that produced a whole column of wrong errnos on the # first target run: alarm() failing SETS ERRNO, so a caller that cancels its # alarm before reading the errno it cares about publishes alarm()'s ENOSYS # instead. The probe now reads errno on the line after each call; this case is # what makes a regression of that ordering visible rather than silent. # EXPECTED GREEN (errno=0/0) on a kernel with the brintos/linux#77 fix; the # ENOSYS arm below is what it reads on a kernel pin without the fix. case "$V_A02" in alarm_rc=*errno=0/0) bst_ok libsocketcan/alarm-available ;; alarm_rc=*errno=38/ENOSYS) bst_notok libsocketcan/alarm-available \ "PLATFORM-NO-ALARM (brintos/linux#77): alarm(2) returns with errno=ENOSYS ($V_A02), which is why A01 reports no signal -- a regression on a kernel with the fix that wires it, red by design on a kernel pin without it. Unwired, syscall-table index [37] is NULL (no __ARCH_WANT_SYS_ALARM) and glibc calls __NR_alarm directly, so this is glibc-only; musl is unaffected" ;; *) bst_notok libsocketcan/alarm-available "alarm(2) failed unexpectedly: '$V_A02'" ;; esac # The outer bound. The probe runs everything that can block in a forked child # and kills it at 150 s, because the alarm guard cannot be trusted to bound a # call on a platform whose signal delivery is what A01 is measuring. `killed=no` # means the blocking section ran to completion; `killed=yes` means it did not, # and the L/X/V cases below are then partial rather than absent -- which is the # distinction this case exists to make visible, since a missing case and a # never-run case look identical in a summary line. case "$V_BOUND" in killed=no) bst_ok libsocketcan/probe-bound ;; killed=yes*) bst_notok libsocketcan/probe-bound \ "the probe's blocking section was killed at its deadline ($V_BOUND) -- a netlink call did not return, and the L/X/V cases below are truncated" ;; fork=failed*) bst_notok libsocketcan/probe-bound "the probe could not fork its bounded section: $V_BOUND" ;; *) bst_notok libsocketcan/probe-bound "unexpected BOUND: '$V_BOUND'" ;; esac # --- the netlink substrate ---------------------------------------------------- # Opened by the PROBE, not the library. This is the control that makes every # result below mean something: green here with the V group absent says "this # kernel has no CAN", a defconfig fact with a one-line fix. Red here would say # "AF_NETLINK is unavailable", a different fault in a different place, and would # make any CAN-shaped conclusion wrong. Without it the two are indistinguishable # from this suite's output. check_eq libsocketcan/nl-socket "rc=0 errno=0/0" "$V_N01" check_eq libsocketcan/nl-bind "rc=0 errno=0/0 pid=nonzero" "$V_N02" # if_nametoindex is the gate all seventeen entry points pass through first, so # both its answers are pinned here rather than inferred further down. check_eq libsocketcan/if-lo-resolves "idx=nonzero" "$V_N03" check_eq libsocketcan/if-absent-rejected "idx=zero" "$V_N04" # The two calls open_nl_sock() makes and IGNORES the return of. They are scored # because they explain the errno every other case would otherwise have to # tolerate silently (see the L-group note), and because an ignored failure is # exactly the kind of thing that is invisible until someone asks. The socket # still works without them -- they only size the send/receive buffers -- so a # red here costs the port nothing and is reported rather than skipped, so it # flips green by itself if the option is ever wired. for pair in "N05 sndbuf SO_SNDBUF" "N06 rcvbuf SO_RCVBUF"; do set -- $pair eval "_got=\$V_$1" case "$_got" in rc=0*) bst_ok "libsocketcan/setsockopt-$2" ;; *errno=38/ENOSYS) bst_notok "libsocketcan/setsockopt-$2" \ "PLATFORM-NO-SETSOCKOPT-BUF: setsockopt(SOL_SOCKET, $3) is ENOSYS on this arm ($_got) -- libsocketcan ignores the return, so the netlink socket is unaffected and the port is not; reported so an ignored failure does not stay invisible" ;; *) bst_notok "libsocketcan/setsockopt-$2" "setsockopt(SOL_SOCKET, $3) failed unexpectedly: '$_got'" ;; esac done # N07/N08: the send and the receive, sampled for the leftover errno the L group # deliberately no longer asserts. Both are expected to succeed -- they are the # library's own sequence by hand -- so the scored part is the CALL, and the errno # is carried in the message for the reader rather than pinned, since which call # leaves ENOSYS is exactly what these exist to find out and pinning today's # answer would freeze a diagnosis in progress. case "$V_N07" in rc=ok*) bst_ok libsocketcan/nl-send ;; *) bst_notok libsocketcan/nl-send "a hand-rolled RTM_GETLINK send failed: '$V_N07'" ;; esac case "$V_N08" in rc=ok*) bst_ok libsocketcan/nl-recvmsg ;; *) bst_notok libsocketcan/nl-recvmsg "a hand-rolled recvmsg of the RTM_GETLINK reply failed: '$V_N08'" ;; esac # N09 is the localiser: the library's prologue walked step by step with errno # printed after every call and NOT reset between them, so the first non-zero # entry names the call that leaves it. Reported rather than asserted -- it is a # diagnostic trace, and pinning it would freeze a value whose whole purpose is # to change when the platform does. The case passes as long as the trace was # produced; what it carries is the reading. case "$V_N09" in trace=*[1-9]*) bst_ok libsocketcan/errno-trace bst_skip libsocketcan/errno-trace-clean \ "a call in libsocketcan's prologue leaves errno set although every one of them succeeds ($V_N09) -- harmless to the port, since the library assigns errno itself on both paths where it means anything, and the reason this suite scores rc and the failure stage rather than errno on the paths where it does not" ;; trace=*) bst_ok libsocketcan/errno-trace bst_ok libsocketcan/errno-trace-clean ;; *) bst_notok libsocketcan/errno-trace "the prologue trace did not run: '$V_N09'" ;; esac # --- the wire structs (ORACLE A and B agree; both are x86-64) ----------------- check_eq libsocketcan/sizeof-bittiming "size=32" "$V_S01" check_eq libsocketcan/sizeof-bittiming-const "size=48" "$V_S02" check_eq libsocketcan/sizeof-clock "size=4" "$V_S03" check_eq libsocketcan/sizeof-ctrlmode "size=8" "$V_S04" check_eq libsocketcan/sizeof-berr-counter "size=4" "$V_S05" check_eq libsocketcan/sizeof-device-stats "size=24" "$V_S06" check_eq libsocketcan/offsets-bittiming "off=0,4,8,12,16,20,24,28" "$V_S07" check_eq libsocketcan/offsets-ctrlmode "off=0,4" "$V_S08" check_eq libsocketcan/offsets-berr-counter "off=0,2" "$V_S09" check_eq libsocketcan/offsets-device-stats "off=0,4,8,12,16,20" "$V_S10" # The enum the state getter writes through its int*, pinned because consumers # switch on it and it travels in a netlink attribute. check_eq libsocketcan/enum-can-state "state=0,1,2,3,4,5" "$V_S11" # --- E group: a name that cannot exist --------------------------------------- # All seventeen, including the two that mutate. Every one must stop at # if_nametoindex: rc=-1, errno ENODEV, class `nodev`, and NO netlink traffic. # timeout=no is the "does not hang" half of the ticket's requirement and # `nodev` is the "returns the documented negative errno" half; asserting the # whole line at once is what keeps a call that started hanging from passing on # its return code alone. _e_want="rc=-1 errno=19/ENODEV err=nodev timeout=no" for n in 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17; do eval "_got=\$V_E$n" check_eq "libsocketcan/absent-if-E$n" "$_e_want" "$_got" done # --- L group: the netlink round trip against a real non-CAN interface --------- # # WHAT THE errno COLUMN MEANS HERE, AND WHY IT IS ASSERTED IN FULL. # # libsocketcan assigns errno on exactly two paths: if_nametoindex leaves ENODEV # (the E group), and the NLMSG_ERROR decode assigns `errno = -err->error` (the L # setters). On every other path it returns without touching errno at all. The # probe zeroes errno immediately before each call, so `errno=0/0` below is not a # leftover being pinned -- it is the assertion that the library did NOT set # errno on that path, which is a property of libsocketcan and a real thing to # check. # # That reading was only earned on the third attempt, and the two wrong ones are # worth recording because they were wrong in a direction that flatters the # investigator. The target first reported ENOSYS in this column on every case # whose errno the library does not assign, and it was twice attributed to the # platform -- first to setsockopt(SO_SNDBUF/SO_RCVBUF), which open_nl_sock() # calls and ignores the return of, and then to send()/recvmsg(). Both fitted the # evidence perfectly. Both were false: N05..N09 measure every call in the # library's prologue and every one is clean on target, with N09 walking the whole # sequence printing errno after each step. # # It was the probe. alarm(2) was ENOSYS on the kernel of that run (A02, a # kernel without the brintos/linux#77 fix), and the probe brackets each call # with alarm(5)/alarm(0) -- so the LEADING alarm left errno at ENOSYS going # into a call that never overwrote it. The trailing alarm was found and fixed # first, which is exactly why the residue # was so convincing afterwards: it had turned the E group and the L setters # green, and the cases still red were precisely the ones with no # library-assigned errno. # # N05..N09 stay, though nothing now depends on them, because they are the # controls that cleared the platform. Deleting them would leave the file # asserting the conclusion with none of the evidence that reached it, and would # let the same three rounds be spent again. # # L01-L08, the CAN-attribute getters. Each completes the transaction and then # finds no IFLA_LINKINFO to decode, so it returns -1 silently. See the header # note on why the silence is the evidence. _l_get_want="rc=-1 errno=0/0 err=none timeout=no" for n in 01 02 03 04 05 06 07 08; do eval "_got=\$V_L$n" check_eq "libsocketcan/lo-roundtrip-L$n" "$_l_get_want" "$_got" done # L09, can_get_link_stats -- THE ONE CALL THAT SUCCEEDS. IFLA_STATS64 is decoded # before the CAN-attribute branch, so this returns 0 with real counters on any # interface. It is the only case in the suite that proves the netlink path by # succeeding rather than by failing in the right place. check_eq libsocketcan/lo-link-stats "rc=0 errno=0/0 err=none timeout=no" "$V_L09" # L10-L14, the setters. These compose IFLA_LINKINFO{IFLA_INFO_KIND="can"} and # block for the kernel's ACK; the kernel parses the message, finds `lo` has no # `can` link ops, and answers NLMSG_ERROR/EOPNOTSUPP. That errno is not # something the library can invent -- reaching it requires a real rtnetlink to # have read and rejected our composed attributes -- so this is the strongest # evidence in the suite that the SEND path is correct, as distinct from the dump # path L01-L09 exercise. ORACLE B (root); unprivileged it is EPERM. _l_set_want="rc=-1 errno=95/EOPNOTSUPP err=rtnlerr timeout=no" for n in 10 11 12 13 14; do eval "_got=\$V_L$n" check_eq "libsocketcan/lo-setter-L$n" "$_l_set_want" "$_got" done # L15, can_do_restart -- gated before it sends anything. It calls can_get_state # first and refuses when that fails, which is the library's own precondition # and not a netlink outcome; scored separately so it is not read as one. check_eq libsocketcan/lo-restart-gated "rc=-1 errno=0/0 err=gated timeout=no" "$V_L15" # L16/L17 are can_do_stop/can_do_start, deliberately NOT RUN against `lo`. They # are the only two entry points that send no IFLA_LINKINFO, so the kernel treats # them as a plain `ip link set lo up|down` and they WOULD SUCCEED -- taking the # loopback interface down underneath the rest of the suite. Asserting the # not-run marker keeps that decision visible: if a future probe starts driving # them here, this goes red rather than the image quietly losing lo mid-run. check_eq libsocketcan/lo-mutators-not-run-L16 "rc=na errno=0/0 err=notrun timeout=no" "$V_L16" check_eq libsocketcan/lo-mutators-not-run-L17 "rc=na errno=0/0 err=notrun timeout=no" "$V_L17" # --- X group: the success case, cross-checked against a second source --------- # can_get_link_stats is the one call that returns data, so it is the one worth # proving returns REAL data. /proc/net/dev is an entirely separate path to the # same counters. The two are read at different instants, so the assertion is a # sandwich -- procfs, library, procfs -- and the library's number must lie in # the range. A zeroed buffer from a do-nothing implementation would satisfy that # only if the counter were 0 at both ends, which is why the probe first sends one # datagram to the loopback discard port to move it off zero. check_eq libsocketcan/stats-call "rc=0 errno=0/0 err=none timeout=no" "$V_X01" check_eq libsocketcan/stats-oracle "procfs=readable" "$V_X02" # `yes-but-zero` is a pass but a weaker one: it means the nudge did not move # lo's counter (no IP stack, or lo down), so the range check was vacuous. It is # reported distinctly rather than folded into `yes` so the difference is legible # in the transcript instead of being invisible. case "$V_X04" in agree=yes) bst_ok libsocketcan/stats-agree ;; agree=yes-but-zero) bst_ok libsocketcan/stats-agree bst_skip libsocketcan/stats-agree-nonzero \ "lo rx_packets is 0 at both reads ($V_X00), so the range check is vacuous -- the counters agree but on nothing" ;; *) bst_notok libsocketcan/stats-agree \ "netlink and /proc/net/dev disagree: $V_X03 ($V_X04)" ;; esac case "$V_X04" in agree=yes) bst_ok libsocketcan/stats-agree-nonzero ;; esac # --- V group: the bring-up path, unreachable without a CAN subsystem ---------- # `skip` and not `not ok`, deliberately. A red would assert that these # expectations are known correct and merely blocked; on this image they have # never been executed. They were validated on x86-64 against a real vcan0, and # the values below are what THAT measured -- vcan honours start/stop and refuses # bit timing, which is not the bring-up the ticket anticipated. See the header. case "$V_V00" in present=no|"") bst_skip libsocketcan/can-device \ "no CAN-kind netdev on this image: wasm32_defconfig has no CONFIG_CAN, so the CAN family is not built and not even vcan can be created -- the bring-up path has nothing to address" ;; present=yes*) bst_ok libsocketcan/can-device # Measured against vcan0 (ORACLE B). start/stop really flip IFF_UP; bit # timing and restart-ms are refused because vcan registers no # can_bittiming_const, and every CAN getter reports no link data because # vcan emits IFLA_LINKINFO with no IFLA_INFO_DATA. On real CAN silicon # these four differ, and that is a finding to record, not a regression. check_eq libsocketcan/can-stop "rc=0 errno=0/0 err=none timeout=no" "$V_V01" check_eq libsocketcan/can-start "rc=0 errno=0/0 err=none timeout=no" "$V_V04" check_eq libsocketcan/can-stop-again "rc=0 errno=0/0 err=none timeout=no" "$V_V11" check_eq libsocketcan/can-set-bitrate-vcan "rc=-1 errno=95/EOPNOTSUPP err=rtnlerr timeout=no" "$V_V02" check_eq libsocketcan/can-set-restart-vcan "rc=-1 errno=95/EOPNOTSUPP err=rtnlerr timeout=no" "$V_V03" check_eq libsocketcan/can-get-bittiming-vcan "rc=-1 errno=0/0 err=nolinkdata timeout=no" "$V_V05" check_eq libsocketcan/can-get-restart-vcan "rc=-1 errno=0/0 err=nolinkdata timeout=no" "$V_V07" check_eq libsocketcan/can-get-state-vcan "rc=-1 errno=0/0 err=nolinkdata timeout=no" "$V_V09" ;; *) bst_notok libsocketcan/can-device "unreadable V00 from the probe: '$V_V00'" ;; esac rm -f "$pout"