. "$BST_LIB" # BST-TIMEOUT: 240 # ~95 cases; ~12 execs of the CLI plus one probe run, and # # nothing here waits on anything. Every CLI invocation # # either returns inside argv parsing or is refused at # # open(2)/scandir(3) and returns at once; the probe is # # bounded at 60s by bst_probe on top of this. # # switchtec-user 4.2 on wasm32-hwjs glibc -- the CLI and library for Microsemi's # Switchtec PCIe management interface (meta-openembedded/meta-oe, # recipes-support/switchtec-user; one meta-brintos bbappend and one patch). # # --------------------------------------------------------------------------- # THERE IS NO SWITCHTEC DEVICE BEHIND THIS MACHINE, AND THAT IS THE POINT. # # A Switchtec is a PCIe fabric switch reached through the kernel's `switchtec` # character driver, which registers /sys/class/switchtec and /dev/switchtec. # This kernel builds no such driver -- and could not usefully: the driver is a # PCI driver, and this architecture has no PCI. So the enumeration is empty, # permanently and by construction. # # An empty enumeration is a DETERMINISTIC, DOCUMENTED ANSWER rather than a gap # to skip around, which is why this port closes on its own suite with no human # looking at anything. What the package does when it finds nothing is most of # what it does here, and it is asserted rather than described. # # WHAT IS THEREFORE ASSERTED FOR REAL, and it is a large fraction of the # package: every path through cli/commands.c's dispatcher (version, help, an # invalid command, a sub-command reached by both its dashed and underscored # spelling), the three extension sub-commands -- which exist only because # __attribute__((constructor)) ran, so they are also this image's statement # about C constructors -- the whole of the library's enumeration path down to # scandir(3), and the library's device-open path down to fstat(2) and access(2) # on a real descriptor. None of that has a device in it, and a wasm32 build that # got any of it wrong would be wrong with no environmental excuse available. # # AND THE DEEPEST REACHABLE POINT IS DEEPER THAN "there is no device". open(2) # on a character device that is not a Switchtec SUCCEEDS, so handing # switchtec_open() /dev/null carries it past the open into # check_switchtec_device(), which fstat()s the descriptor, builds # /sys/dev/char/:/device/switchtec out of the real rdev and # access()es it. That is libswitchtec executing rather than merely being # present, and the errno it settles on there (ENOTTY) is the library's own # choice rather than one the kernel handed it. Its CALLER never sees that value: # switchtec_open() overwrites errno with ENODEV on every failing arm, so the # refusal is asserted here through the NULL return and through a control that # shows the platform still reporting ENOENT for the same path. # # WHAT IS PROBED RATHER THAN ASSUMED. The absence above is read at runtime from # ${libexecdir}/switchtec-user-tests/switchtec-probe and every branch below is # written against the live answer, so this suite stays correct the day this # image grows a switchtec: the empty-enumeration assertions turn into # non-empty-enumeration assertions on their own evidence rather than on a # comment. # # AND THE CONTROLS ARE THE POINT OF THE PROBE. "There is no /sys/class/switchtec" # and "this process cannot read directories or open device nodes" produce the # same refusal from switchtec_list(), so the probe reports both scandir(3) arms # -- a directory that exists and one that does not -- beside two device-node # opens. Every branch below is gated on those four; 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. bst_ok switchtec/suite-canary # first line: absorbs brintos/linux#96, which drops it tmp=${TMPDIR:-/tmp} nl=' ' hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } require switchtec switchtec/present # --- what the packaging says lands, by name ---------------------------------- bin=/usr/bin/switchtec lib=/usr/lib/libswitchtec.so.4.2 soname=/usr/lib/libswitchtec.so.4 devlink=/usr/lib/libswitchtec.so ar=/usr/lib/libswitchtec.a inc=/usr/include/switchtec if [ -x "$bin" ]; then bst_ok switchtec/file-cli; else bst_notok switchtec/file-cli "not executable: $bin"; fi hasfile switchtec/file-shared "$lib" hasfile switchtec/file-archive "$ar" hasfile switchtec/file-completion /etc/bash_completion.d/switchtec # The soname link and the development link. Upstream's install-bin makes only # the second and points it at the first, which it never creates -- the bbappend # adds libswitchtec.so.4 for exactly this reason, so both of these are # assertions about that fix and go red together if it is dropped. if [ -L "$soname" ] && [ -e "$soname" ]; then bst_ok switchtec/link-soname else bst_notok switchtec/link-soname "not a resolving symlink: $soname -> $(readlink "$soname" 2>/dev/null)"; fi if [ -L "$devlink" ] && [ -e "$devlink" ]; then bst_ok switchtec/link-dev else bst_notok switchtec/link-dev "not a resolving symlink: $devlink -> $(readlink "$devlink" 2>/dev/null)"; fi bst_lib_shape switchtec/lib-shape '/usr/lib/libswitchtec.so*' # The sixteen public headers, by name: the -dev package is `cp -r inc/switchtec` # and nothing narrows it, so a header that stopped shipping would be an upstream # move rather than a packaging accident -- and the fixture below compiles # against switchtec.h, which is the only one a consumer must have. for h in bind diag endian errors fabric gas gas_mrpc log mfg mrpc pci pmon \ portable registers switchtec utils; do hasfile "switchtec/file-header-$h" "$inc/$h.h" done # --- the version banner ------------------------------------------------------ # # print_version() writes `switchtec cli version ' to STDERR and returns # 0; VERSION is build/version.h's, which VERSION-GEN takes from the tree's own # `version' file. do_install asserts that file still says 4.2, so a bump that # moved one and not the other would have reddened the build before this line. # # Both spellings are asserted because do_command() strips leading dashes before # dispatching (`while (*cmd == '-') cmd++'), so `--version' and `version' are # the same code path reached two ways, and a change to that loop would break one # of them silently. vout=$(switchtec --version 2>&1); vrc=$? if bst_execfail "$vout"; then bst_unrun switchtec/version-runs "$vout" else bst_ok switchtec/version-runs check_eq switchtec/version-text "switchtec cli version 4.2" "$vout" if [ "$vrc" -eq 0 ]; then bst_ok switchtec/version-rc else bst_notok switchtec/version-rc "rc=$vrc want=0"; fi # stderr, not stdout: upstream writes the whole of its help and version # output to stderr, which is unusual enough that a reader would assume the # opposite. check_eq switchtec/version-stream-is-stderr "" "$(switchtec --version 2>/dev/null)" fi check_eq switchtec/version-text-undashed "switchtec cli version 4.2" \ "$(switchtec version 2>&1)" # --- `switchtec help' enumerates its sub-commands ---------------------------- hout=$(switchtec help 2>&1); hrc=$? if bst_execfail "$hout"; then bst_unrun switchtec/help-runs "$hout" hout= else bst_ok switchtec/help-runs if [ "$hrc" -eq 0 ]; then bst_ok switchtec/help-rc else bst_notok switchtec/help-rc "rc=$hrc want=0"; fi fi has switchtec/help-banner "switchtec-4.2" "$hout" has switchtec/help-heading "The following are all implemented sub-commands:" "$hout" has switchtec/help-desc "The must be a Switchtec device" "$hout" # The twenty-seven entries of cli/main.c's commands[], as general_help() prints # them -- underscore_to_dash()'d, so `event_wait' appears as `event-wait'. The # list is asserted whole rather than sampled: it is the package's own statement # of what it implements, and a build that dropped a translation unit would drop # entries here and nowhere else visible. for c in list info gui status bw latency events event-wait log-dump log-parse \ test temp port-bind-info port-bind port-unbind stack-bif hard-reset \ fw-update fw-info fw-toggle fw-read fw-img-info evcntr evcntr-setup \ evcntr-show evcntr-del evcntr-wait; do has "switchtec/help-cmd-$c" " $c " "$hout" done has switchtec/help-cmd-version " version " "$hout" has switchtec/help-cmd-help " help " "$hout" # The three extensions are registered by __attribute__((constructor)) at # startup, so this is also an assertion that C constructors run on this arm: # a build whose ctors never fired would print the heading with nothing under it # and every other case in this suite would still pass. has switchtec/help-ext-heading "The following are all installed extensions:" "$hout" for e in mfg fabric diag; do has "switchtec/help-ext-$e" " $e " "$hout" done # --- an invalid command ------------------------------------------------------ # # do_command() dash_to_underscore()s before matching and reports the CONVERTED # spelling, which is worth pinning: the diagnostic does not echo what the user # typed. iout=$(switchtec no-such-command 2>&1); irc=$? if bst_execfail "$iout"; then bst_unrun switchtec/invalid-cmd-runs "$iout" else bst_ok switchtec/invalid-cmd-runs has switchtec/invalid-cmd-text "ERROR: Invalid command 'no_such_command'" "$iout" if [ "$irc" -ne 0 ]; then bst_ok switchtec/invalid-cmd-rc else bst_notok switchtec/invalid-cmd-rc "rc=0, want non-zero"; fi fi # --- the probe --------------------------------------------------------------- P=/usr/libexec/switchtec-user-tests/switchtec-probe if [ ! -x "$P" ]; then bst_notok switchtec/probe/present "no probe at $P -- switchtec-user-tests is not installed, so nothing below can be attributed" exit 0 fi bst_ok switchtec/probe/present bst_probe switchtec/probe/runs "$tmp/switchtec-probe.out" 60 "$P" || { bst_skip switchtec/probe/read "the probe did not complete; its cases cannot be read" exit 0 } PO=$(cat "$tmp/switchtec-probe.out" 2>/dev/null) case "$PO" in *"probe end"*) bst_ok switchtec/probe/complete ;; *) bst_notok switchtec/probe/complete "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 } # --- the four controls, and everything below is gated on them ---------------- null_rc=$(field control-open-devnull rc) zero_rc=$(field control-open-devzero rc) sd_present_n=$(field control-scandir-present n) sd_absent_n=$(field control-scandir-absent n) sd_absent_e=$(field control-scandir-absent errno) check_eq switchtec/control-open-devnull "0" "$null_rc" check_eq switchtec/control-open-devzero "0" "$zero_rc" if [ "${sd_present_n:-0}" -gt 0 ] 2>/dev/null; then bst_ok switchtec/control-scandir-present else bst_notok switchtec/control-scandir-present "scandir(/dev) returned n=$sd_present_n, so a scandir of -1 below says nothing about switchtec"; fi check_eq switchtec/control-scandir-absent-n "-1" "$sd_absent_n" check_eq switchtec/control-scandir-absent-errno "2" "$sd_absent_e" controls_ok=1 [ "$null_rc" = "0" ] || controls_ok=0 [ "$zero_rc" = "0" ] || controls_ok=0 [ "${sd_present_n:-0}" -gt 0 ] 2>/dev/null || controls_ok=0 [ "$sd_absent_n" = "-1" ] || controls_ok=0 # --- the enumeration --------------------------------------------------------- sysdir=$(state sysdir) list_rc=$(field list rc) list_errno=$(field list errno) list_devlist=$(printf '%s\n' "$PO" | sed -n 's/^list-devlist //p' | sed -n 1p) has switchtec/sysclass-readable "/sys/class present" "$PO" if [ "$controls_ok" -ne 1 ]; then bst_skip switchtec/list-empty "a control failed above; switchtec_list()'s answer cannot be attributed" elif [ "$sysdir" = "absent" ]; then # THE EXPECTED SHAPE ON THIS IMAGE. switchtec_list() is scandir(sys_path, # ...) and returns n directly when n <= 0, so an absent /sys/class/switchtec # comes back as scandir's own -1/ENOENT -- NOT as zero devices. The # difference matters: it is what the CLI's exit status below is derived # from, and a reader expecting "0 devices" would call the real answer a bug. bst_ok switchtec/sysdir-absent check_eq switchtec/list-rc "-1" "$list_rc" check_eq switchtec/list-errno "2" "$list_errno" check_eq switchtec/list-devlist-untouched "untouched" "$list_devlist" else # A machine that grew a switchtec driver is a HEALTHIER machine and must not # turn this suite red silently: the assertions become the non-empty ones. bst_ok switchtec/sysdir-present if [ "${list_rc:-0}" -ge 0 ] 2>/dev/null; then bst_ok switchtec/list-rc else bst_notok switchtec/list-rc "/sys/class/switchtec exists and switchtec_list() still returned $list_rc (errno=$list_errno)"; fi fi # --- switchtec_open() on names that are not Switchtec devices ---------------- oa_ret=$(field open-absent ret) oa_errno=$(field open-absent errno) od_ret=$(field open-devnull ret) od_errno=$(field open-devnull errno) isatty_devnull=$(field control-isatty-devnull isatty) # A control for the clobber below: switchtec_open() answers ENODEV for every # failure, so this pins the thing that is NOT true -- an absent name under /dev # is still ENOENT to an ordinary open(2) on this image. Without it, a platform # that began answering ENODEV for everything would leave every assertion below # unchanged and green, which is the one way they could stop meaning anything. absent_dev=/dev/switchtec-no-such-device cat "$absent_dev" >/dev/null 2>"$tmp/switchtec-absent.err" aout=$(cat "$tmp/switchtec-absent.err" 2>/dev/null) has switchtec/control-absent-path-enoent "No such file or directory" "$aout" check_eq switchtec/open-absent-null "null" "$oa_ret" # 19 is ENODEV, and it is switchtec_open()'s OWN answer rather than the one the # syscall underneath produced. lib/switchtec.c dispatches on the spelling of the # name -- anything containing a '/' goes to switchtec_open_by_path() -- and then # discards whatever errno that arm left behind: # # found: # if (!ret) { # errno = ENODEV; # return NULL; # } # # So open(2)'s ENOENT here, and check_switchtec_device()'s ENOTTY below, are # both unobservable through this entry point: every refusal of switchtec_open() # reaches its caller as ENODEV whatever it was. That is upstream behaviour on # every platform, not this image's -- the control above measured the platform # still reporting ENOENT for the same path a moment earlier. check_eq switchtec/open-absent-errno "19" "$oa_errno" check_eq switchtec/open-devnull-null "null" "$od_ret" if [ "$isatty_devnull" = "0" ]; then bst_ok switchtec/open-devnull-not-a-tty # The library's own deeper verdict IS reached here, and only its visibility # is lost to the clobber: /dev/null opens, check_switchtec_device() fstat()s # the descriptor, builds /sys/dev/char/:/device/switchtec out # of the real rdev, access()es it, fails, and sets ENOTTY -- which errno then # becomes ENODEV like any other. It is open-devnull-null that carries the # statement, beside the probe's control-open-devnull rc=0: NULL from a path # whose open(2) succeeded means that access(2) ran and refused. check_eq switchtec/open-devnull-errno "19" "$od_errno" else # isatty(/dev/null) true would divert switchtec_open_by_path() into # switchtec_open_uart(), and the case above would be a different statement. bst_notok switchtec/open-devnull-not-a-tty "isatty(/dev/null)=$isatty_devnull -- switchtec_open() takes the UART arm, so the open-devnull assertions do not apply" fi # --- the CLI's own enumeration, against the probe's --------------------------- # # `switchtec list' is list() in cli/main.c: it calls switchtec_list() and # returns n unchanged when n <= 0, and main() returns that to the shell. So the # expected exit status is DERIVED from the probe's measurement rather than # transcribed -- a negative n arrives at the shell as 256+n. if [ "$controls_ok" -ne 1 ]; then bst_skip switchtec/list-cli "a control failed above; the CLI's exit status cannot be attributed" else lout=$(switchtec list 2>"$tmp/switchtec-list.err"); lrc=$? lerr=$(cat "$tmp/switchtec-list.err" 2>/dev/null) if bst_execfail "$lerr$lout"; then bst_unrun switchtec/list-cli "$lerr$lout" else bst_ok switchtec/list-cli if [ "${list_rc:-0}" -lt 0 ] 2>/dev/null; then want=$((256 + list_rc)) else want=0 fi check_eq switchtec/list-cli-rc "$want" "$lrc" # No device, so no row: the loop that prints them runs zero times, and # upstream prints no header and no "none found" line of its own. check_eq switchtec/list-cli-stdout-empty "" "$lout" check_eq switchtec/list-cli-stderr-empty "" "$lerr" fi fi # --- a sub-command against a device path that does not exist ------------------ # # switchtec_handler() calls switchtec_open(), gets NULL, and reports it with # switchtec_perror(optarg) -- which for a plain errno prints ": # ". argconfig_parse() then exit(1)s on the handler's non-zero # return, so the status is 1 and not the handler's own value. # # The errno it prints is the ENODEV switchtec_open() substituted above, so the # text a user sees for a misspelt path is "No such device" and not the "No such # file or directory" the open(2) actually returned. Asserted as it is, because # it is what the CLI does everywhere -- see the clobber quoted above. d=$absent_dev iout=$(switchtec info "$d" 2>&1); irc=$? if bst_execfail "$iout"; then bst_unrun switchtec/absent-dev-runs "$iout" else bst_ok switchtec/absent-dev-runs check_eq switchtec/absent-dev-text "$d: No such device" "$iout" check_eq switchtec/absent-dev-rc "1" "$irc" fi # The same refusal reached through a second sub-command and a second spelling of # the device, so the assertion is about switchtec_handler() rather than about # `info'. `event-wait' is dashed here and underscored in commands[], which # exercises dash_to_underscore() on a name that needs it. sout=$(switchtec status "$d" 2>&1) check_eq switchtec/absent-dev-text-status "$d: No such device" "$sout" eout=$(switchtec event-wait "$d" 2>&1) has switchtec/absent-dev-dashed-subcmd "$d: No such device" "$eout" # And /dev/null: the deepest reachable point, reached through the CLI rather # than through the fixture, so the diagnostic a user would actually see is # pinned to the probe's errno above rather than to a transcribed string. if [ "$isatty_devnull" = "0" ] && [ "$od_errno" = "19" ]; then nout=$(switchtec info /dev/null 2>&1) has switchtec/devnull-diagnostic "/dev/null: No such device" "$nout" else bst_skip switchtec/devnull-diagnostic "the probe reports errno=$od_errno for switchtec_open(/dev/null), not the ENODEV every refusal is normalised to; the strerror text is pinned to that value" fi rm -f "$tmp/switchtec-probe.out" "$tmp/switchtec-probe.out.err" \ "$tmp/switchtec-list.err" "$tmp/switchtec-absent.err" 2>/dev/null