# BST-TIMEOUT: 300 # ~50 cases, each an exec of a 3.5 MB wasm binary # # hddtemp on wasm32-hwjs. The tool is two programs sharing a binary: # # * the DRIVE half -- open(2) a block device, probe its bus, issue # HDIO_DRIVE_CMD/SG_IO -- which cannot run here, because # wasm32_defconfig:270 sets "# CONFIG_BLOCK is not set" under that file's # own heading "What we explicitly DO NOT want"; and # # * the DATABASE half -- load_database() and display_supported_drives() in # db.c -- which parses the shipped 29,563-byte drive database into a linked # list of extended regexes and prints it, and which `-b/--drivebase` reaches # WITHOUT OPENING A DEVICE AT ALL: # # if(show_db) { load_database(database_path); display_supported_drives(); exit(0); } # # The database half is therefore the pass criterion, and it is real coverage # rather than a consolation: a real parser, over its own shipped fixture, with # `-f` pointing it at files this suite writes to reach every error path in db.c. # # The drive half is PROBED, never assumed. Every case that cannot run here skips # on hddtemp-probe's live readings with its controls open, so a kernel that grew # a block layer would start running them by itself -- and so a suite that merely # watched hddtemp fail on every device, which would pass identically against a # build miscompiled into rubble, is not what ships. . "$BST_LIB" require hddtemp hddtemp/present W=${TMPDIR:-/tmp}/bst-hddtemp.$$ mkdir -p "$W" 2>/dev/null DB=/usr/share/misc/hddtemp.db # --------------------------------------------------------------------------- # The probe: platform facts as values, with the controls that make them mean # what they say. # --------------------------------------------------------------------------- PROBE_SRC=/usr/libexec/hddtemp-tests/hddtemp-probe probe=$W/hddtemp-probe pout= if [ -f "$PROBE_SRC" ] && cp "$PROBE_SRC" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then bst_ok hddtemp/probe/staged pout=$("$probe" 2>&1) case "$pout" in *"probe end"*) bst_ok hddtemp/probe/ran ;; *) bst_notok hddtemp/probe/ran "probe did not reach its end line -- $(_bst_1line "$pout")" ;; esac else bst_notok hddtemp/probe/staged "cannot stage $PROBE_SRC" bst_notok hddtemp/probe/ran "probe was never staged" fi # pkv : the value of `field=` on the probe's ` ...` line. pkv() { printf '%s\n' "$pout" | awk -v k="$1" -v f="$2" ' $1==k { for (i=2; i<=NF; i++) { n=index($i,"="); if (n>0 && substr($i,1,n-1)==f) { print substr($i,n+1); exit } } }' } # pword : the n-th whitespace field after the key on its line. pword() { printf '%s\n' "$pout" | awk -v k="$1" -v n="$2" '$1==k { print $(n+1); exit }' } # CONTROLS. If these are not open, nothing below is a statement about the block # layer -- it is a statement about this process, and the suite must say so. check_eq hddtemp/probe/control-devnull 0 "$(pkv open-devnull rc)" check_eq hddtemp/probe/control-mknod-chr 0 "$(pkv mknod-chr rc)" check_eq hddtemp/probe/control-open-chr 0 "$(pkv open-chr rc)" controls_open=0 if [ "$(pkv open-devnull rc)" = "0" ] && [ "$(pkv mknod-chr rc)" = "0" ] \ && [ "$(pkv open-chr rc)" = "0" ]; then controls_open=1 fi # --------------------------------------------------------------------------- # The database, read by hddtemp's OWN parser. # --------------------------------------------------------------------------- check hddtemp/db/file-present test -s "$DB" check_eq hddtemp/db/file-size 29563 "$(pkv db size)" bout=$(hddtemp -b 2>&1); brc=$? check_eq hddtemp/db/drivebase-rc 0 "$brc" case "$bout" in *"| Value | Description"*) bst_ok hddtemp/db/header ;; *) bst_notok hddtemp/db/header "no column header -- $(_bst_1line "$bout")" ;; esac # display_supported_drives() prints | %5d | . # The regexp is everything before the FIRST tab and the value sits between the # two pipes after the LAST tab. Splitting on '|' instead would be wrong for the # 38 keys that are alternations and contain a literal '|' of their own -- e.g. # "WDC WD2000JB-(00EV|32EV|34EV|00FU|00GV|55GV|98GV|00KF)A0", which is also the # longest key in the file and so the one that drives the tab-padding column. DB_AWK_COUNT=' { t=0; for (i=length($0); i>=1; i--) if (substr($0,i,1)=="\t") { t=i; break } if (t==0) next rest=substr($0,t+1) if (rest ~ /^\|[ ]*[0-9]+[ ]*\|/) c++ } END { print c+0 }' DB_AWK_LOOKUP=' { f=0; for (i=1; i<=length($0); i++) if (substr($0,i,1)=="\t") { f=i; break } if (f==0) next if (substr($0,1,f-1) != m) next t=0; for (i=length($0); i>=1; i--) if (substr($0,i,1)=="\t") { t=i; break } rest=substr($0,t+1) if (match(rest, /^\|[ ]*[0-9]+[ ]*\|/)) { v=substr(rest,2,RLENGTH-2); gsub(/[ ]/,"",v); print v; exit } }' DB_AWK_HIST=' { t=0; for (i=length($0); i>=1; i--) if (substr($0,i,1)=="\t") { t=i; break } if (t==0) next rest=substr($0,t+1) if (match(rest, /^\|[ ]*[0-9]+[ ]*\|/)) { v=substr(rest,2,RLENGTH-2); gsub(/[ ]/,"",v); n[v]++ } } END { c=0; for (v in n) { printf "%s%s:%s", (c++?" ":""), v, n[v] } printf "\n" }' db_count() { printf '%s\n' "$1" | awk "$DB_AWK_COUNT"; } db_value() { printf '%s\n' "$bout" | awk -v m="$1" "$DB_AWK_LOOKUP"; } check_eq hddtemp/db/entries 364 "$(db_count "$bout")" # The parser's count against the file's own: a parser that silently dropped # entries would agree with nothing here. file_entries=$(grep -cv '^[ ]*#\|^[ ]*$' "$DB" 2>/dev/null) check_eq hddtemp/db/parser-agrees-with-file "$file_entries" "$(db_count "$bout")" # Model-to-attribute mappings, chosen to cover the shape of the file rather than # to be numerous: the head and tail of the list, the "known to have no sensor" # value 0, a wildcard key, all three singleton values, and the longest key. check_eq hddtemp/db/map-first 194 "$(db_value 'ExcelStor Technology J3.0')" check_eq hddtemp/db/map-last 194 "$(db_value 'WDC WD3200SD-01KNB0')" check_eq hddtemp/db/map-no-sensor 0 "$(db_value 'FUJITSU MHM2100AT')" check_eq hddtemp/db/map-wildcard-key 231 "$(db_value 'IBM-DJNA-35.*')" check_eq hddtemp/db/map-singleton-11 11 "$(db_value 'Maxtor 94098U8')" check_eq hddtemp/db/map-singleton-220 220 "$(db_value 'MK4313MAT')" check_eq hddtemp/db/map-singleton-226 226 "$(db_value 'TOSHIBA MK2018GAS')" check_eq hddtemp/db/map-alternation-key 194 \ "$(db_value 'WDC WD2000JB-(00EV|32EV|34EV|00FU|00GV|55GV|98GV|00KF)A0')" # One assertion over the whole parse: the six distinct attribute ids and how # many entries carry each. A mis-parse anywhere in the file moves one of these. check_eq hddtemp/db/value-histogram "0:33 11:1 194:321 220:1 226:1 231:7" \ "$(printf '%s\n' "$bout" | awk "$DB_AWK_HIST" | tr ' ' '\n' | sort -t: -k1,1n | tr '\n' ' ' | sed 's/ $//')" # The description column survives the parse too -- it is a separate strdup() in # parse_db_line() and could be lost without moving any count above. case "$bout" in *"Western Digital Caviar RE 320GB SATA 8MB"*) bst_ok hddtemp/db/description ;; *) bst_notok hddtemp/db/description "last entry's description missing" ;; esac # The long option reaches the same code as -b. lout=$(hddtemp --drivebase 2>&1) check_eq hddtemp/db/long-option-drivebase 364 "$(db_count "$lout")" # The unit column (C/F) is parsed by parse_db_line() but display_supported_drives() # never prints it, so it is asserted against the FILE rather than against -b -- # said plainly, because a reader is entitled to know which of the two answered. check_eq hddtemp/db/file-unit-fahrenheit 1 \ "$(awk '!/^[ \t]*#/ && NF { if ($0 ~ /"[ \t]+[0-9]+[ \t]+F[ \t]+"/) n++ } END { print n+0 }' "$DB")" # --------------------------------------------------------------------------- # The parser's error paths, over files this suite writes. # --------------------------------------------------------------------------- check_rc hddtemp/db/missing-file 2 hddtemp -b -f "$W/no-such.db" mout=$(hddtemp -b -f "$W/no-such.db" 2>&1) case "$mout" in *"hddtemp: can't open"*"No such file or directory"*) bst_ok hddtemp/db/missing-file-msg ;; *) bst_notok hddtemp/db/missing-file-msg "$(_bst_1line "$mout")" ;; esac printf '# a comment\n\n"BST-FAKE-MODEL" 42 C "brintOS synthetic entry"\n' > "$W/one.db" oout=$(hddtemp -b -f "$W/one.db" 2>&1); orc=$? check_eq hddtemp/db/minimal-rc 0 "$orc" check_eq hddtemp/db/minimal-entries 1 "$(db_count "$oout")" check_eq hddtemp/db/minimal-value 42 "$(printf '%s\n' "$oout" | awk -v m='BST-FAKE-MODEL' "$DB_AWK_LOOKUP")" # A malformed line: quoted key and value, then nothing where the unit must be. printf '# header\n"GOOD-ONE" 194 C "fine"\n"BAD-ONE" 42\n' > "$W/bad.db" check_rc hddtemp/db/syntax-error-rc 2 hddtemp -b -f "$W/bad.db" bad=$(hddtemp -b -f "$W/bad.db" 2>&1) case "$bad" in *"syntax error at line 3"*) bst_ok hddtemp/db/syntax-error-line ;; *) bst_notok hddtemp/db/syntax-error-line "want line 3 -- $(_bst_1line "$bad")" ;; esac # MAX_LINE_LEN is 1024 in db.c. awk 'BEGIN { s=""; while (length(s) < 1200) s = s "x"; printf "\"%s\" 194 C \"long\"\n", s }' \ > "$W/long.db" check_rc hddtemp/db/long-line-rc 2 hddtemp -b -f "$W/long.db" lng=$(hddtemp -b -f "$W/long.db" 2>&1) case "$lng" in *"line exceed 1024 characters"*) bst_ok hddtemp/db/long-line-msg ;; *) bst_notok hddtemp/db/long-line-msg "$(_bst_1line "$lng")" ;; esac # load_database() only parses what a '\n' terminates, and its "did I make # progress" test is `s == buff`. Two consequences, both upstream defects, both # asserted here as MEASURED behaviour rather than as what the code ought to do: # # (a) a file with no newline ANYWHERE is reported as a line-length overflow, # whatever its actual length; and # (b) a last line that merely lacks its terminator is dropped in silence, with # a clean exit 0. printf '"NO-NEWLINE-MODEL" 42 C "short line, no terminator"' > "$W/nonl.db" nonl=$(hddtemp -b -f "$W/nonl.db" 2>&1); nonlrc=$? check_eq hddtemp/db/no-trailing-newline-rc 2 "$nonlrc" case "$nonl" in *"line exceed 1024 characters"*) bst_ok hddtemp/db/no-trailing-newline-msg ;; *) bst_notok hddtemp/db/no-trailing-newline-msg "$(_bst_1line "$nonl")" ;; esac printf '"KEPT-MODEL" 194 C "terminated"\n"DROPPED-MODEL" 42 C "not terminated"' > "$W/drop.db" drop=$(hddtemp -b -f "$W/drop.db" 2>&1); droprc=$? check_eq hddtemp/db/dropped-last-line-rc 0 "$droprc" check_eq hddtemp/db/dropped-last-line-entries 1 "$(db_count "$drop")" case "$drop" in *DROPPED-MODEL*) bst_notok hddtemp/db/dropped-last-line "the unterminated entry was parsed after all" ;; *) bst_ok hddtemp/db/dropped-last-line ;; esac # --------------------------------------------------------------------------- # The documented refusal on a device that is not there. # --------------------------------------------------------------------------- NODEV=/dev/bst-hddtemp-no-such-device eout=$(hddtemp "$NODEV" 2>&1); erc=$? check_eq hddtemp/dev/absent-rc 1 "$erc" case "$eout" in *"open: No such file or directory"*) bst_ok hddtemp/dev/absent-msg ;; *) bst_notok hddtemp/dev/absent-msg "$(_bst_1line "$eout")" ;; esac case "$eout" in *"$NODEV"*) bst_ok hddtemp/dev/absent-names-device ;; *) bst_notok hddtemp/dev/absent-names-device "$(_bst_1line "$eout")" ;; esac # A type prefix takes the same path -- the parse of "SATA:" happens before the # open, so a typed device that is absent must refuse identically. tout=$(hddtemp "SATA:$NODEV" 2>&1); trc=$? check_eq hddtemp/dev/absent-typed-rc 1 "$trc" case "$tout" in *"open: No such file or directory"*) bst_ok hddtemp/dev/absent-typed-msg ;; *) bst_notok hddtemp/dev/absent-typed-msg "$(_bst_1line "$tout")" ;; esac # --------------------------------------------------------------------------- # Option parsing: the documented refusals, each with its own exit status. # --------------------------------------------------------------------------- check_out hddtemp/version "hddtemp version 0.3-beta15" hddtemp -v check_out hddtemp/help-usage " Usage: hddtemp [OPTIONS] [TYPE:]DISK1" hddtemp -h check_out hddtemp/help-lists-drivebase "-b --drivebase" hddtemp -h # `case 'h':` in hddtemp.c has neither a break nor an exit, so --help FALLS # THROUGH into `case 'v':` and prints the version banner after the usage. That # is upstream behaviour, and a suite written from a prediction rather than from # the binary would have asserted the opposite. check_out hddtemp/help-falls-through-to-version "hddtemp version 0.3-beta15" hddtemp -h # `case '?':` falls into the same place, so an unknown option prints the usage # and exits 0 -- not the non-zero status the shape of the code suggests. check_rc hddtemp/opt/unknown-rc 0 hddtemp -Z check_rc hddtemp/opt/bad-separator-rc 1 hddtemp -s '' sep=$(hddtemp -s '' 2>&1) case "$sep" in *"ERROR: invalid separator."*) bst_ok hddtemp/opt/bad-separator-msg ;; *) bst_notok hddtemp/opt/bad-separator-msg "$(_bst_1line "$sep")" ;; esac check_rc hddtemp/opt/bad-port-rc 1 hddtemp -p 0 prt=$(hddtemp -p abc 2>&1) case "$prt" in *"ERROR: invalid port number."*) bst_ok hddtemp/opt/bad-port-msg ;; *) bst_notok hddtemp/opt/bad-port-msg "$(_bst_1line "$prt")" ;; esac check_rc hddtemp/opt/bad-interval-rc 1 hddtemp -S 0 itv=$(hddtemp -S 0 2>&1) case "$itv" in *"ERROR: invalid interval."*) bst_ok hddtemp/opt/bad-interval-msg ;; *) bst_notok hddtemp/opt/bad-interval-msg "$(_bst_1line "$itv")" ;; esac check_rc hddtemp/opt/debug-and-daemon-rc 1 hddtemp -D -d dbd=$(hddtemp -D -d 2>&1) case "$dbd" in *"can't use --debug and --daemon"*) bst_ok hddtemp/opt/debug-and-daemon-msg ;; *) bst_notok hddtemp/opt/debug-and-daemon-msg "$(_bst_1line "$dbd")" ;; esac # -b wins over everything: it exits before any disk is collected, so it must # still print the database when a nonexistent device is named beside it. check_eq hddtemp/opt/drivebase-precedes-disk 364 "$(db_count "$(hddtemp -b "$NODEV" 2>&1)")" # --------------------------------------------------------------------------- # The drive half -- asserted against the probe's live readings, never assumed. # --------------------------------------------------------------------------- glob_n=$(pkv diskglob count) check_eq hddtemp/blk/diskglob-count 0 "$glob_n" check_eq hddtemp/blk/proc-partitions "absent" "$(pword path-partitions 2)" check_eq hddtemp/blk/sys-block "absent" "$(pword path-sysblock 2)" check_eq hddtemp/blk/procdev-heading "no" "$(pkv procdev-block heading)" # mknod and open fail INDEPENDENTLY: creating the inode is a filesystem # operation and succeeds on a kernel that can never open the result. Both # readings are reported rather than collapsed, because a suite told only "no # block device" could not tell that apart from "the filesystem refused us" -- # and the char-device control beside it is what makes the pair mean "block # layer" rather than "this process". mk_blk=$(pkv mknod-blk rc); op_blk=$(pkv open-blk rc); op_blk_e=$(pkv open-blk name) if [ "$controls_open" != "1" ]; then bst_notok hddtemp/blk/no-block-layer \ "controls are not open (devnull=$(pkv open-devnull rc) mknod-chr=$(pkv mknod-chr rc) open-chr=$(pkv open-chr rc)) -- this reading is about the process, not the kernel" elif [ "$mk_blk" = "0" ] && [ "$op_blk" != "0" ]; then bst_ok hddtemp/blk/no-block-layer elif [ "$mk_blk" != "0" ] && [ "$op_blk" != "0" ]; then bst_ok hddtemp/blk/no-block-layer else bst_notok hddtemp/blk/no-block-layer \ "a block device node OPENED (mknod=$mk_blk open=$op_blk) -- this kernel has a block layer and the drive cases must stop skipping" fi # With no /dev/[hs]d[a-z] to glob, hddtemp's own no-argument path is reachable # and its refusal is asserted for real. Gated on the glob rather than assumed: # on an image that HAS disks this same invocation reads them, and the case would # be wrong rather than merely inapplicable. if [ "$glob_n" = "0" ]; then check_rc hddtemp/drive/no-args-rc 1 hddtemp noa=$(hddtemp 2>&1) case "$noa" in *"you must specify one drive, at least"*) bst_ok hddtemp/drive/no-args-msg ;; *) bst_notok hddtemp/drive/no-args-msg "$(_bst_1line "$noa")" ;; esac else bst_skip hddtemp/drive/no-args-rc "the disk glob found $glob_n device(s); the no-argument path reads them" bst_skip hddtemp/drive/no-args-msg "the disk glob found $glob_n device(s); the no-argument path reads them" fi # The one thing hddtemp exists to do. Skipped only on the measured absence, with # the controls open -- never by category. if [ "$controls_open" = "1" ] && [ "$glob_n" = "0" ] && [ "$op_blk" != "0" ]; then bst_skip hddtemp/drive/temperature \ "no block layer: CONFIG_BLOCK unset, disk glob 0, /proc/partitions absent, and a block node opens $op_blk_e while the char control opens 0" else dsk=$(printf '%s\n' "$pout" | awk '$1=="diskglob" { print }') tmp_out=$(hddtemp /dev/sda 2>&1); tmp_rc=$? if [ "$tmp_rc" -eq 0 ]; then bst_ok hddtemp/drive/temperature else bst_notok hddtemp/drive/temperature "a block layer is present ($dsk) but hddtemp /dev/sda failed: rc=$tmp_rc $(_bst_1line "$tmp_out")"; fi fi rm -rf "$W"