. "$BST_LIB" # BST-TIMEOUT: 3400 # # About sixty runs of som-program-nrf91 and som-test-nrf91, each bounded by # bst_run; the slowest are the pty-board runs. 731 s in a brintOS guest # (core-image-brintos-manufacturing 2026-10-08, Developer mode, 2026-10-12), # 172-178 s on a Mac, and 2254 s on the superproject's HardwareJS under Node # (core-image-brintos-nrf-sdk, 2026-10-13), where a pty-board run took 10-229 s # and the three flight-mode runs the longest (166-229 s). The bound is 1.5 # times that last figure, under the runner's 3600 s cap; a pty-board run is # bounded at 600 s. # # What goes wrong, on purpose. The 2026-10-10 review of PR #1980, and the # 2026-10-12 review of its follow-up, found these faults in som-program-nrf91, # som-test-nrf91 and the cases that were to catch them; each case below poses # one of them, and each was first run against the scripts without the fix, # where it went red: # * an interrupt (HUP, INT, TERM) is honoured between steps, never inside an # openocd session, and a write it lands on is let finish, then reported: # its "do not reset" message and its retry session are kept -- also when # the shell acts on it only after the session is over, as BusyBox ash did; # a stop after the gate lets the halted core run again, however late; # * a failed write's retry session blocks the next run that would reset the # chip (--ignore-retry overrides; --check reports it), takes a name no # older one has, and writes the UICR-free image kept in it; # * the halt gate lasts the watchdog's own timeout and 3 s more, colour # codes or not, --gate lengthens it, and an unconfirmed watchdog is warned # about; # * the nRF91's own watchdog (WDT0) is read by the probe's own Tcl: one that # counts while the core is halted makes the gate as long as its timeout, # or is refused; a reload register the flash loader does not feed is # warned about; # * the backup is taken only with --backup; # * a dry run touches no hardware, the console included, and --check opens # the console only to read the modem; a run lets go of it when it ends; # * som-test-nrf91: a SIM that wants a PIN, or does not answer, is a FAIL, # also under --suite lte, which reads the modem's mode too; a busy SIM is # asked again; a flight-mode check restores normal mode however late the # modem answers, reads it back, and restores it with a reader of its own # after a hangup; identity numbers are masked in --send replies, in the # transcript and in results; a HUP or TERM cleans up, and so does a closed # stdout; two runs never share a directory. # # Stand-ins at the process boundary only: an openocd that answers as the board # did (data/som-nrf91-openocd.sh, first on PATH), running the probe's own Tcl # under tclsh or expect where there is one (data/som-nrf91-probe.tcl); shims in # front of grep, sed, mktemp and mv for the cases that need them # (data/som-nrf91-shim.sh); and for the console a pty board run by expect # (data/som-nrf91-board.exp), which spawns the tool on the pty's other end -- so # a Ctrl-C reaches it as at a bench. A suite's commands run as background jobs, # which ignore SIGINT, so INT is tested only through that pty; HUP and TERM are # sent by the stand-ins. Cases assert on what the tool said and left behind, # not on its exit status alone: a script killed by an untrapped TERM exits 143 # too. bst_ok som-nrf91-faults/suite-canary # first line: absorbs brintos/linux#96, which drops it require som-program-nrf91 som-nrf91-faults/program-present require som-test-nrf91 som-nrf91-faults/test-present W=${TMPDIR:-/tmp}/bst-som-nrf91-faults.$$ rm -rf "$W" if mkdir -p "$W/stub" "$W/late" "$W/mkt" "$W/mvs" "$W/sd" "$W/tmp" 2>/dev/null; then bst_ok som-nrf91-faults/workdir else bst_notok som-nrf91-faults/workdir "cannot create working directory $W" exit 0 fi TS=$(command -v som-test-nrf91) # Fixtures: good.hex is 16 bytes at 0x0 plus a UICR record from the 1.2-114 # hello_mcuboot release; board.cfg stands in for the board config, which the # stand-in openocd never reads. printf '%s\n' ':10000000000102030405060708090A0B0C0D0E0F78' ':0200000400FFFB' \ ':10813000008000000000020001000000FFFFFA5074' ':00000001FF' > "$W/good.hex" echo '# stand-in board config' > "$W/board.cfg" echo 'not a tty' > "$W/plainfile" sed -n p "$BST_DATA/som-nrf91-openocd.sh" > "$W/stub/openocd" 2>/dev/null chmod +x "$W/stub/openocd" 2>/dev/null # The shims: grep and sed in $W/late send a signal a session left for after # its end; mktemp in $W/mkt notes the names it hands out; mv in $W/mvs sends # itself a signal first. for c in late/grep late/sed mkt/mktemp mvs/mv; do sed -n p "$BST_DATA/som-nrf91-shim.sh" > "$W/$c" 2>/dev/null chmod +x "$W/$c" 2>/dev/null done # A Tcl for the probe's own Tcl, if this machine has one that runs a script. TCLSH="" printf 'puts [expr {6 * 7}]\n' > "$W/t.tcl" for t in tclsh expect; do command -v "$t" >/dev/null 2>&1 || continue if [ "$("$t" "$W/t.tcl" 2>/dev/null)" = 42 ]; then TCLSH=$(command -v "$t"); break; fi done # prun [VAR=value...] COMMAND ARGS...: run against the stand-in openocd, in a # fresh session directory; sets $out and $rc (bst_run) and $nses. $W/tmp is the # run's TMPDIR, kept between runs, so a case can leave a retry session in it. # A backup goes to an empty $W/bk: the default, /tmp, is the station's own, and # the stand-in's pages of zeros there would read as a board's backup. # PRE= puts the shims in ahead of the rest of PATH for this run # (cleared after it: bash keeps a VAR=value given before a function call). prun() { rm -rf "$W/sd" "$W/bk"; mkdir -p "$W/sd/ses" "$W/bk"; : >"$W/sd/count" bst_run 120 env PATH="${PRE:+$PRE:}$W/stub:$PATH" NRF_STUB_DIR="$W/sd" TMPDIR="$W/tmp" SOM_BACKUP_DIR="$W/bk" \ SOM_BOARD_CFG="$W/board.cfg" NRF_STUB_TCLSH="$TCLSH" NRF_STUB_TCL="$BST_DATA/som-nrf91-probe.tcl" "$@" nses=$(grep -c "" "$W/sd/count" 2>/dev/null); nses=${nses:-0} PRE= } fresh_tmp() { rm -rf "$W/tmp"; mkdir -p "$W/tmp"; } # has_out ...: ok iff $out holds every needle. has_out() { _id=$1; shift for _n; do case "$out" in *"$_n"*) ;; *) bst_notok "som-nrf91-faults/$_id" "rc=$rc, no '$_n' in: '$(_bst_1line "$out")'"; return 1 ;; esac done bst_ok "som-nrf91-faults/$_id" } # runses : ok iff session is the one that lets the core run again. runses() { [ "$(sed -n p "$W/sd/ses/$1.tcl" 2>/dev/null | tr '\n' ';')" = 'init;catch {reset run};shutdown;' ]; } # --- 1. som-program-nrf91 against the stand-in openocd ------------------------- # A dry run first: it runs no openocd (the stand-in is on PATH only because # the script asks that one be installed), and touches nothing -- not the # console either, which used to get `stty` before the dry-run exit (a plain # file stands in for a console it must not open). bst_run 60 env PATH="$W/stub:$PATH" SOM_BOARD_CFG="$W/board.cfg" som-program-nrf91 --dry-run --console "$W/plainfile" --image "$W/good.hex" case "$rc:$out" in 0:*"backup: off (--backup to take one)"*"PASS -- resolved"*) bst_ok som-nrf91-faults/dry-run-no-console ;; *) bst_notok som-nrf91-faults/dry-run-no-console "rc=$rc: '$(_bst_1line "$out")'" ;; esac for g in 0 601 0x10; do bst_run 60 env PATH="$W/stub:$PATH" SOM_BOARD_CFG="$W/board.cfg" som-program-nrf91 --dry-run --no-console --image "$W/good.hex" --gate "$g" case "$rc:$out" in 1:*"FAIL -- "*"--gate"*) bst_ok som-nrf91-faults/gate-refuses-$g ;; *) bst_notok som-nrf91-faults/gate-refuses-$g "rc=$rc: '$(_bst_1line "$out")'" ;; esac done STUB_IDS="shims-reached probe-tcl wdt0-off check-leaves-console backup-off-by-default backup-on-request no-backup-accepted sig-term-write-fail retry-image-uicr-free second-signal-ignored sig-term-write-late sig-term-write-late-pass sig-term-write-pass sig-term-probe sig-hup-gate sig-hup-gate-late sig-term-backup retry-blocks-reset retry-ignore retry-check-reports retry-name-taken wdt0-pauses wdt0-rr-warn wdt0-halt-gate wdt0-halt-too-long wdt0-unreadable wdt0-nonsecure gate-option-long gate-option-short wdt-unconfirmed-warn" : >"$W/sd/count" if NRF_STUB_DIR="$W/sd" "$W/stub/openocd" stub-check >/dev/null 2>&1 && [ -s "$W/sd/count" ] \ && [ "$(PATH="$W/stub:$PATH" command -v openocd)" = "$W/stub/openocd" ]; then bst_ok som-nrf91-faults/stub-reached # The cases that use the shims need them run ahead of the real commands. sh_bad="" for c in late/grep late/sed mkt/mktemp mvs/mv; do [ "$(PATH="$W/${c%/*}:$PATH" "${c#*/}" --som-shim 2>&1)" = SHIM-OK ] || sh_bad="$sh_bad ${c#*/}" done if [ -z "$sh_bad" ]; then bst_ok som-nrf91-faults/shims-reached else bst_notok som-nrf91-faults/shims-reached "PATH does not reach the shim for:$sh_bad (a shell that runs its own applet first?)"; fi # The probe's own Tcl, run against the stand-in chip: its UICR compare, and # WDT0 off, as on the board. prun som-program-nrf91 --check --no-console --image "$W/good.hex" if [ -n "$TCLSH" ]; then has_out probe-tcl "SOM stub-tcl=ran" "uicr: the image's 14 set UICR bytes match the chip (2 left unprogrammed" "PASS -- check" else bst_skip som-nrf91-faults/probe-tcl "no tclsh or expect to run the probe's Tcl: the stand-in makes up its answers"; fi has_out wdt0-off "wdt0: off" # --check opens the console only to read the modem, and nothing here asks # for that: a plain file named as the console is never opened. prun som-program-nrf91 --check --console "$W/plainfile" --image "$W/good.hex" case "$rc:$out" in 0:*"PASS -- check"*) bst_ok som-nrf91-faults/check-leaves-console ;; *) bst_notok som-nrf91-faults/check-leaves-console "rc=$rc: '$(_bst_1line "$out")'" ;; esac # The backup is off unless asked for (the review's answer); --no-backup # stays accepted and changes nothing. fresh_tmp prun som-program-nrf91 --no-console --image "$W/good.hex" if [ "$rc" = 0 ] && [ "$nses" = 3 ] && ! grep -q dump_image "$W"/sd/ses/*.tcl 2>/dev/null; then bst_ok som-nrf91-faults/backup-off-by-default else bst_notok som-nrf91-faults/backup-off-by-default "rc=$rc, $nses session(s), want 3 and no dump_image: '$(_bst_1line "$out")'"; fi prun som-program-nrf91 --no-console --backup --image "$W/good.hex" b=$(grep -l dump_image "$W"/sd/ses/*.tcl 2>/dev/null | sed -n 1p); w=$(grep -l "flash write_image" "$W"/sd/ses/*.tcl 2>/dev/null | sed -n 1p) if [ "$rc" = 0 ] && [ "$nses" = 4 ] && [ -n "$b" ] && [ -n "$w" ] && [ "$(basename "$b" .tcl)" -lt "$(basename "$w" .tcl)" ] \ && [ -n "$(ls "$W"/bk/nrf91-flash-*.pages 2>/dev/null)" ]; then bst_ok som-nrf91-faults/backup-on-request else bst_notok som-nrf91-faults/backup-on-request "rc=$rc, $nses session(s), backup '$b' write '$w': '$(_bst_1line "$out")'"; fi prun som-program-nrf91 --no-console --no-backup --image "$W/good.hex" if [ "$rc" = 0 ] && ! grep -q dump_image "$W"/sd/ses/*.tcl 2>/dev/null; then bst_ok som-nrf91-faults/no-backup-accepted else bst_notok som-nrf91-faults/no-backup-accepted "rc=$rc: '$(_bst_1line "$out")'"; fi # A TERM during the write: the session runs to its end; a failed write # keeps its retry session (write.log in it, a plain `halt' first) and says # NOT reset; nothing runs after it. fresh_tmp prun NRF_STUB_SIGNAL=TERM som-program-nrf91 --no-console --image "$W/good.hex" rt=$(ls -d "$W"/tmp/som-nrf91-retry-* 2>/dev/null | sed -n 1p) if [ -n "$rt" ] && [ -s "$rt/write.log" ] && [ "$(sed -n 2p "$rt/retry.tcl" 2>/dev/null)" = halt ] && [ "$nses" = 3 ]; then has_out sig-term-write-fail "TERM: letting the write session finish" "NOT reset" "stopped by TERM during the write step" else bst_notok som-nrf91-faults/sig-term-write-fail "rc=$rc, $nses session(s), retry '${rt:-none}': '$(_bst_1line "$out")'"; fi # The retry writes the image kept beside it, which has no UICR record: the # original HEX's would write the write-once UICR bank. wl=$(grep 'flash write_image' "${rt:-$W/none}/retry.tcl" 2>/dev/null) case "$wl" in *" $rt/image.hex"*) if grep -q '^:10000000000102' "$rt/image.hex" 2>/dev/null && ! grep -q '^:10813000' "$rt/image.hex" 2>/dev/null; then bst_ok som-nrf91-faults/retry-image-uicr-free else bst_notok som-nrf91-faults/retry-image-uicr-free "$rt/image.hex: '$(_bst_1line "$(sed -n p "$rt/image.hex" 2>/dev/null)")'"; fi ;; *) bst_notok som-nrf91-faults/retry-image-uicr-free "the retry does not write the image kept in ${rt:-its session}: '$(_bst_1line "$wl")'" ;; esac # A second signal, as a second Ctrl-C would send it to the command in the # foreground too, does not cut short the saving of the retry session: from # the first one on, the commands the run starts ignore them. (The mv shim # sends itself the signal.) fresh_tmp PRE=$W/mvs prun NRF_STUB_SIGNAL=TERM NRF_STUB_MVSIG=TERM som-program-nrf91 --no-console --image "$W/good.hex" rt=$(ls -d "$W"/tmp/som-nrf91-retry-* 2>/dev/null | sed -n 1p) if [ -n "$rt" ] && [ -s "$rt/retry.tcl" ] && [ -s "$rt/write.log" ] && [ -z "$(ls -d "$W"/tmp/som-program-nrf91.* 2>/dev/null)" ]; then has_out second-signal-ignored "NOT reset" "$rt/retry.tcl" else bst_notok som-nrf91-faults/second-signal-ignored "rc=$rc, left in TMPDIR: '$(ls "$W/tmp" 2>/dev/null | tr '\n' ' ')': '$(_bst_1line "$out")'"; fi # The same, with the signal acted on only once the session is over, as # BusyBox ash did with a Ctrl-C (2026-10-12): the stand-in leaves it, and # the script's first grep or sed after the session sends it. Still no reset # after the failed write: its own report, and nothing else. fresh_tmp PRE=$W/late prun NRF_STUB_SIGNAL=TERM NRF_STUB_SIGNAL_LATE=1 som-program-nrf91 --no-console --image "$W/good.hex" rt=$(ls -d "$W"/tmp/som-nrf91-retry-* 2>/dev/null | sed -n 1p) if [ -n "$rt" ] && [ "$(sed -n 2p "$rt/retry.tcl" 2>/dev/null)" = halt ] && [ "$nses" = 3 ]; then has_out sig-term-write-late "NOT reset" "stopped by TERM during the write step: erase/write/verify failed" else bst_notok som-nrf91-faults/sig-term-write-late "rc=$rc, $nses session(s) (want 3: none after the write), retry '${rt:-none}': '$(_bst_1line "$out")'"; fi # A passed write with the signal acted on late: reported as written. fresh_tmp PRE=$W/late prun NRF_STUB_SIGNAL=TERM NRF_STUB_SIGNAL_LATE=1 NRF_STUB_SIGNAL_RESULT=pass som-program-nrf91 --no-console --image "$W/good.hex" if [ "$nses" = 3 ] && [ -z "$(ls -d "$W"/tmp/som-nrf91-retry-* 2>/dev/null)" ]; then has_out sig-term-write-late-pass "stopped by TERM during the write step; the image was written and verified" else bst_notok som-nrf91-faults/sig-term-write-late-pass "rc=$rc, $nses session(s), want 3 and no retry session: '$(_bst_1line "$out")'"; fi fresh_tmp prun NRF_STUB_SIGNAL=TERM NRF_STUB_SIGNAL_RESULT=pass som-program-nrf91 --no-console --image "$W/good.hex" if [ -z "$(ls -d "$W"/tmp/som-nrf91-retry-* 2>/dev/null)" ]; then case "$out" in *"NOT reset"*) bst_notok som-nrf91-faults/sig-term-write-pass "a passed write was reported NOT reset: '$(_bst_1line "$out")'" ;; *) has_out sig-term-write-pass "stopped by TERM during the write step" "written and verified" ;; esac else bst_notok som-nrf91-faults/sig-term-write-pass "a passed write left a retry session: '$(_bst_1line "$out")'"; fi # A TERM during the probe, on a chip an earlier write left erased: the run # stops there, and resets nothing -- a reset would lock that chip. fresh_tmp prun NRF_STUB_SIGNAL=TERM NRF_STUB_SIGNAL_AT=probe NRF_STUB_ERASED=1 som-program-nrf91 --no-console --image "$W/good.hex" case "$rc:$nses:$out" in 143:1:*"stopped by TERM during the probe step; nothing was written ["*) bst_ok som-nrf91-faults/sig-term-probe ;; *) bst_notok som-nrf91-faults/sig-term-probe "rc=$rc, $nses session(s), want 143, 1 and no reset: '$(_bst_1line "$out")'" ;; esac # A HUP during the gate: the gate finishes, nothing is written, and the # halted core is let run again, by a session of its own after the gate's. fresh_tmp prun NRF_STUB_SIGNAL=HUP NRF_STUB_SIGNAL_AT=gate som-program-nrf91 --no-console --image "$W/good.hex" if [ "$nses" = 3 ] && runses 3; then has_out sig-hup-gate "stopped by HUP during the gate step; nothing was written; the core was let run again" else bst_notok som-nrf91-faults/sig-hup-gate "rc=$rc, $nses session(s), want 3, the last one 'init; catch {reset run}; shutdown': '$(_bst_1line "$out")'"; fi # The same, acted on only once the gate's session is over: the core is let # run again all the same (the script used to wait for the gate's verdict). fresh_tmp PRE=$W/late prun NRF_STUB_SIGNAL=HUP NRF_STUB_SIGNAL_AT=gate NRF_STUB_SIGNAL_LATE=1 som-program-nrf91 --no-console --image "$W/good.hex" if [ "$nses" = 3 ] && runses 3; then has_out sig-hup-gate-late "stopped by HUP during the gate step; nothing was written; the core was let run again" else bst_notok som-nrf91-faults/sig-hup-gate-late "rc=$rc, $nses session(s), want 3, the last one 'init; catch {reset run}; shutdown': '$(_bst_1line "$out")'"; fi # A TERM during the backup: nothing written, the partial backup removed, # the core let run again. fresh_tmp prun NRF_STUB_SIGNAL=TERM NRF_STUB_SIGNAL_AT=backup som-program-nrf91 --no-console --backup --image "$W/good.hex" if [ -z "$(ls "$W/bk" 2>/dev/null)" ] && [ "$nses" = 4 ] && runses 4; then has_out sig-term-backup "stopped by TERM during the backup step; nothing was written; the core was let run again" else bst_notok som-nrf91-faults/sig-term-backup "rc=$rc, $nses session(s), left: '$(ls "$W/bk" 2>/dev/null)': '$(_bst_1line "$out")'"; fi # A retry session left by a failed write blocks a run that would reset the # chip; --ignore-retry overrides, --check only reports it. fresh_tmp; mkdir -p "$W/tmp/som-nrf91-retry-1" prun som-program-nrf91 --no-console --image "$W/good.hex" case "$rc:$nses:$out" in 1:1:*"retry session is still in"*"nothing was written"*) bst_ok som-nrf91-faults/retry-blocks-reset ;; *) bst_notok som-nrf91-faults/retry-blocks-reset "rc=$rc, $nses session(s), want 1 and a refusal: '$(_bst_1line "$out")'" ;; esac prun som-program-nrf91 --no-console --ignore-retry --image "$W/good.hex" case "$rc:$out" in 0:*"PASS -- wrote"*) bst_ok som-nrf91-faults/retry-ignore ;; *) bst_notok som-nrf91-faults/retry-ignore "rc=$rc: '$(_bst_1line "$out")'" ;; esac prun som-program-nrf91 --check --no-console --image "$W/good.hex" case "$rc:$out" in 0:*"retry: a failed write's session is still in"*"PASS -- check"*) bst_ok som-nrf91-faults/retry-check-reports ;; *) bst_notok som-nrf91-faults/retry-check-reports "rc=$rc: '$(_bst_1line "$out")'" ;; esac # A PID comes round again. A run whose own PID names an older retry session # (the shell execs it in place), let run beside it by --ignore-retry, keeps # a session of its own, which writes its own image; the older one is left. fresh_tmp prun NRF_STUB_WRITE_FAIL=1 sh -c 'mkdir -p "$TMPDIR/som-nrf91-retry-$$" && echo older > "$TMPDIR/som-nrf91-retry-$$/image.hex" && exec som-program-nrf91 --no-console --ignore-retry --image "$1"' sh "$W/good.hex" new=$(ls -d "$W"/tmp/som-nrf91-retry-*-1 2>/dev/null | sed -n 1p) if [ -n "$new" ] && [ "$(ls -d "$W"/tmp/som-nrf91-retry-* | grep -c '')" = 2 ] \ && grep -q " $new/image.hex" "$new/retry.tcl" 2>/dev/null && grep -q '^:10000000000102' "$new/image.hex" 2>/dev/null \ && [ "$(sed -n p "${new%-1}/image.hex" 2>/dev/null)" = older ] && [ ! -e "${new%-1}/retry.tcl" ]; then has_out retry-name-taken "NOT reset" "$new/retry.tcl" else bst_notok som-nrf91-faults/retry-name-taken "rc=$rc, sessions: '$(ls -R "$W/tmp" 2>/dev/null | tr '\n' ' ')': '$(_bst_1line "$out")'"; fi fresh_tmp # WDT0, as som-nrf91-probe.tcl's registers set it. Running, pausing while # the core is halted, and fed by the flash loader's RR[0]: reported, and no # more. Another reload register enabled: a warning. Counting while the core # is halted: a gate as long as its timeout and 3 s, in which the stand-in's # WDT0 then fires, and a timeout longer than any gate is refused at once. # Not readable: a warning. In the non-secure alias: read there. prun NRF_STUB_WDT0="1 327679 1 1" som-program-nrf91 --check --no-console --image "$W/good.hex" # (Needles split, so the lint's eye for a timeout(1) command is not caught # by the message's own words.) case "$out" in *WARN:*) bst_notok som-nrf91-faults/wdt0-pauses "a warning for a WDT0 the loader feeds: '$(_bst_1line "$out")'" ;; *) has_out wdt0-pauses "wdt0: running, " "10 s, pauses while the core is halted; reload registers 0x01" "PASS -- check" ;; esac prun NRF_STUB_WDT0="1 327679 1 3" som-program-nrf91 --check --no-console --image "$W/good.hex" has_out wdt0-rr-warn "WARN: WDT0 reloads only when every enabled reload register is written" "with 0x03 enabled" prun NRF_STUB_WDT0="1 983039 9 1" som-program-nrf91 --no-console --image "$W/good.hex" if [ "$rc" = 1 ] && [ "$nses" = 2 ] && grep -q 'i < 33}' "$W/sd/ses/2.tcl" 2>/dev/null; then has_out wdt0-halt-gate "keeps counting while the core is halted" "the gate holds at least 33 s" "the halt gate failed: reset at 30 s" "WDT0) runs and keeps counting" "nothing was written" else bst_notok som-nrf91-faults/wdt0-halt-gate "rc=$rc, $nses session(s), want 1, 2 (probe, gate) and a 33 s gate: '$(_bst_1line "$out")'"; fi prun NRF_STUB_WDT0="1 22937599 9 1" som-program-nrf91 --no-console --image "$W/good.hex" case "$rc:$nses:$out" in 1:1:*"longer than the longest halt gate (600 s)"*"nothing was written"*) bst_ok som-nrf91-faults/wdt0-halt-too-long ;; *) bst_notok som-nrf91-faults/wdt0-halt-too-long "rc=$rc, $nses session(s), want 1 and a refusal: '$(_bst_1line "$out")'" ;; esac prun NRF_STUB_WDT0=unreadable som-program-nrf91 --check --no-console --image "$W/good.hex" has_out wdt0-unreadable "wdt0: not read" "WARN: the nRF91's own watchdog (WDT0) could not be read" if [ -n "$TCLSH" ]; then prun NRF_STUB_WDT0="1 327679 1 1 ns" som-program-nrf91 --check --no-console --image "$W/good.hex" has_out wdt0-nonsecure "wdt0: running, " "10 s, pauses while the core is halted" else bst_skip som-nrf91-faults/wdt0-nonsecure "no tclsh or expect to run the probe's Tcl, which picks the alias"; fi # The gate's length: --gate only lengthens it; with no console the # watchdog is unknown, and the run says so. prun som-program-nrf91 --no-console --gate 30 --image "$W/good.hex" if grep -q 'i < 30}' "$W"/sd/ses/2.tcl 2>/dev/null; then has_out gate-option-long "held halted 30 s" else bst_notok som-nrf91-faults/gate-option-long "the gate: '$(_bst_1line "$(sed -n p "$W/sd/ses/2.tcl" 2>/dev/null)")'"; fi prun som-program-nrf91 --no-console --gate 5 --image "$W/good.hex" if grep -q 'i < 12}' "$W"/sd/ses/2.tcl 2>/dev/null; then bst_ok som-nrf91-faults/gate-option-short else bst_notok som-nrf91-faults/gate-option-short "the gate: '$(_bst_1line "$(sed -n p "$W/sd/ses/2.tcl" 2>/dev/null)")'"; fi has_out wdt-unconfirmed-warn "WARN: the watchdog state is unknown (no console)" else for id in stub-reached $STUB_IDS; do bst_notok som-nrf91-faults/$id "the stand-in openocd in $W/stub did not run, so som-program-nrf91 was not started" done fi # --- 2. som-test-nrf91, without a board ------------------------------------------ # The SIM verdict, taken out of the installed script and run on its own. eval "$(sed -n -e '/^cfun_name()/,/^}/p' -e '/^sim_classify()/,/^}/p' "$TS")" if command -v sim_classify >/dev/null 2>&1; then for c in "0|READY|1|PASS ready" "0|SIM PIN|1|FAIL locked" "1|ERROR|1|SKIP none" "1|ERROR|0|SKIP off" \ "1|+CME ERROR: 10|1|SKIP none" "1|+CME ERROR: 13|1|FAIL failed" "1|+CME ERROR: 14|1|RETRY busy" \ "2||1|FAIL unknown"; do a=${c%%|*}; r=${c#*|}; v=${r%%|*}; r=${r#*|}; f=${r%%|*}; want=${r#*|} got=$(sim_classify "$a" "$v" "$f") case "$got" in "$want "*) bst_ok "som-nrf91-faults/sim-classify-$a-$(echo "$v" | tr -c 'A-Za-z0-9\n' '_')-$f" ;; *) bst_notok "som-nrf91-faults/sim-classify-$a-$(echo "$v" | tr -c 'A-Za-z0-9\n' '_')-$f" "want '$want ...', got '$got'" ;; esac done else bst_notok som-nrf91-faults/sim-classify "som-test-nrf91 has no sim_classify()" fi # Two runs in the same second get two directories, private ones; a --out # another run holds is refused. bst_run 60 som-test-nrf91 --console "$W/no-such-tty" --quick; o1=$(printf '%s\n' "$out" | sed -n 's/.*out: *//p') bst_run 60 som-test-nrf91 --console "$W/no-such-tty" --quick; o2=$(printf '%s\n' "$out" | sed -n 's/.*out: *//p') case "$o1" in /tmp/som-test-*.??????) if [ -n "$o2" ] && [ "$o1" != "$o2" ]; then case "$(ls -ld "$o1" 2>/dev/null)" in drwx------*) bst_ok som-nrf91-faults/outdir-unique ;; *) bst_notok som-nrf91-faults/outdir-unique "$o1 is not private: '$(ls -ld "$o1" 2>/dev/null)'" ;; esac else bst_notok som-nrf91-faults/outdir-unique "two runs share '$o1'"; fi ;; *) bst_notok som-nrf91-faults/outdir-unique "the run directory '$o1' is not mktemp's" ;; esac case "$o1" in /tmp/som-test-*) rm -rf "$o1" ;; esac case "$o2" in /tmp/som-test-*) rm -rf "$o2" ;; esac mkdir -p "$W/held/.work" bst_run 60 som-test-nrf91 --console "$W/no-such-tty" --out "$W/held" case "$rc:$out" in 2:*"is in use"*) bst_ok som-nrf91-faults/outdir-lock ;; *) bst_notok som-nrf91-faults/outdir-lock "rc=$rc: '$(_bst_1line "$out")'" ;; esac # A stdout closed under the run does not end it before its clean-up: PIPE is # caught. (The reader of the pipe is gone a second before the run starts.) bst_run 60 sh -c '{ sleep 1; exec som-test-nrf91 --console "$1" --quick --out "$2"; } | true' sh "$W/no-such-tty" "$W/p" if [ -d "$W/p" ] && [ ! -e "$W/p/.work" ] && grep -q '^rig.console' "$W/p/results.tsv" 2>/dev/null; then bst_ok som-nrf91-faults/stdout-closed-cleans else bst_notok som-nrf91-faults/stdout-closed-cleans "rc=$rc, the run left: '$(ls -a "$W/p" 2>/dev/null | tr '\n' ' ')'"; fi # --- 3. both tools against a pty board ---------------------------------------------- PTY_IDS="pty-watchdog-order pty-gate-from-timeout pty-int-write pty-timeout-unreported pty-port-released pty-send-masks pty-send-keeps-nothing pty-send-masks-imsi pty-send-identity pty-transcript-masked pty-sim-busy-retry pty-lte-sim-pin pty-lte-reads-cfun pty-cfun-restore-late pty-cfun-restore-retry pty-hangup-restores pty-hup-cleans pty-term-cleans pty-result-masked" spawn_ok=0 if command -v expect >/dev/null 2>&1; then bst_run 30 expect -c 'spawn -noecho true; expect eof; puts SPAWN-OK' case "$out" in *SPAWN-OK*) spawn_ok=1 ;; esac fi # board MODES TOOL ARGS...: the tool on a pty board; BENV adds VAR=value words # to its environment, PRE a directory of shims ahead of PATH (both cleared # after the run). Sets $out and $rc; the board's log is $W/recv. board() { _bm=$1; shift rm -f "$W/recv"; rm -rf "$W/sd" "$W/bk"; mkdir -p "$W/sd/ses" "$W/bk"; : >"$W/sd/count" bst_run 600 env PATH="${PRE:+$PRE:}$W/stub:$PATH" NRF_STUB_DIR="$W/sd" TMPDIR="$W/tmp" SOM_BACKUP_DIR="$W/bk" SOM_BOARD_CFG="$W/board.cfg" \ NRF_STUB_TCLSH="$TCLSH" NRF_STUB_TCL="$BST_DATA/som-nrf91-probe.tcl" ${BENV:-} \ expect "$BST_DATA/som-nrf91-board.exp" "$_bm" "$W/recv" "$@" PRE=; BENV= } if [ "$spawn_ok" -eq 0 ]; then for id in $PTY_IDS; do bst_skip som-nrf91-faults/$id "expect cannot spawn here (brintos/hardwarejs#50 on the site's dev.5 runtime; Developer mode runs the image's own)" done else # The watchdog step, in order, and a gate as long as the timeout it # reports -- in colour codes here, as a shell may print it. (nohup: see # pty-port-released.) fresh_tmp board good,wdt=30000,wdtcolor,nohup som-program-nrf91 --image "$W/good.hex" heard=$(grep -v '^$' "$W/recv" 2>/dev/null | tr '\n' '|') released=$out case "$heard:$out" in "watchdog status|watchdog disable|watchdog status|version|:"*"PASS -- wrote"*"TOOL-RC=0"*) bst_ok som-nrf91-faults/pty-watchdog-order ;; *) bst_notok som-nrf91-faults/pty-watchdog-order "heard '$heard': '$(_bst_1line "$out")'" ;; esac if grep -q 'i < 33}' "$W"/sd/ses/2.tcl 2>/dev/null; then has_out pty-gate-from-timeout "watchdog: disabled (its timeout: 30000 ms)" "held halted 33 s" else bst_notok som-nrf91-faults/pty-gate-from-timeout "the gate: '$(_bst_1line "$(sed -n p "$W/sd/ses/2.tcl" 2>/dev/null)")'"; fi # A real Ctrl-C on the tool's terminal, a second into a write that fails: # the write session runs to its end (its log ends as the stand-in's # session does), and the retry session is kept. This board's shell gives # no watchdog timeout, which the run warns of. fresh_tmp BENV="NRF_STUB_SLOW=4 NRF_STUB_WRITE_FAIL=1" board good,int,wdtnotimeout som-program-nrf91 --image "$W/good.hex" has_out pty-timeout-unreported "watchdog: disabled" "WARN: the board's shell did not say the watchdog's timeout" rt=$(ls -d "$W"/tmp/som-nrf91-retry-* 2>/dev/null | sed -n 1p) if [ -n "$rt" ] && [ "$(sed -n 2p "$rt/retry.tcl" 2>/dev/null)" = halt ] && grep -q '^SOM result=fail' "$rt/write.log" 2>/dev/null; then has_out pty-int-write "INT: letting the write session finish" "NOT reset" "stopped by INT during the write step" "TOOL-RC=130" else bst_notok som-nrf91-faults/pty-int-write "no retry session, or its write session was cut short: '$(_bst_1line "$(sed -n p "${rt:-$W/none}/write.log" 2>/dev/null)")': '$(_bst_1line "$out")'"; fi fresh_tmp # som-test-nrf91 --send: the IMEI is masked, unless --identity, and # without --out nothing is kept: every directory mktemp gave it is gone. PRE=$W/mkt board good som-test-nrf91 --send "at AT+CGSN=1" case "$out" in *490154203237518*) bst_notok som-nrf91-faults/pty-send-masks "the IMEI in full: '$(_bst_1line "$out")'" ;; *) has_out pty-send-masks '+CGSN: "***********7518"' ;; esac made=$(sed -n p "$W/sd/mktemp.log" 2>/dev/null) left=""; for d in $made; do [ -e "$d" ] && left="$left $d"; done if [ -z "$made" ]; then bst_notok som-nrf91-faults/pty-send-keeps-nothing "the run made no directory through mktemp (is the shim in $W/mkt reached?)" elif [ -z "$left" ]; then bst_ok som-nrf91-faults/pty-send-keeps-nothing else bst_notok som-nrf91-faults/pty-send-keeps-nothing "left:$left"; fi # An IMSI may have 14 digits. board good som-test-nrf91 --send "at AT+CIMI" case "$out" in *31041012345678*) bst_notok som-nrf91-faults/pty-send-masks-imsi "the IMSI in full: '$(_bst_1line "$out")'" ;; *) has_out pty-send-masks-imsi '***********5678' ;; esac board good som-test-nrf91 --identity --send "at AT+CGSN=1" has_out pty-send-identity '+CGSN: "490154203237518"' # A quick run never asks for the IMEI, but the board logs it in a client id: # the transcript masks it all the same. board good,nohup som-test-nrf91 --quick --out "$W/q" released="$released $out" if [ -s "$W/q/transcript.txt" ] && ! grep -q -E '[0-9]{14}' "$W/q/transcript.txt"; then bst_ok som-nrf91-faults/pty-transcript-masked else bst_notok som-nrf91-faults/pty-transcript-masked "'$(_bst_1line "$(grep -E '[0-9]{14}' "$W/q/transcript.txt" 2>/dev/null)")'"; fi # Each of the two runs above let go of the console as it ended: no reader # of its own was left on it. (They start with HUP ignored, as under nohup, # so that the tool's exit, as its pty's session leader, does not hang a # leftover reader up with it.) case "$released" in *"PORT-HELD=1"*) bst_notok som-nrf91-faults/pty-port-released "a reader outlived its run: '$(_bst_1line "$(printf '%s\n' "$released" | grep -E 'TOOL-RC|PORT-HELD')")'" ;; *"PORT-HELD=0"*"PORT-HELD=0"*) bst_ok som-nrf91-faults/pty-port-released ;; *) bst_notok som-nrf91-faults/pty-port-released "the board did not report on its console: '$(_bst_1line "$released")'" ;; esac # A SIM still busy at the first AT+CPIN? is asked once more. board good,simbusy som-test-nrf91 --quick --out "$W/sb" if [ "$(grep -c -x 'at AT+CPIN?' "$W/recv" 2>/dev/null)" = 2 ]; then has_out pty-sim-busy-retry "at.sim PASS -- READY" else bst_notok som-nrf91-faults/pty-sim-busy-retry "the board heard AT+CPIN? $(grep -c -x 'at AT+CPIN?' "$W/recv" 2>/dev/null) time(s), want 2: '$(_bst_1line "$out")'"; fi # A SIM that wants a PIN is a FAIL under --suite lte too. board pin som-test-nrf91 --suite lte --out "$W/l" has_out pty-lte-sim-pin "lte.sim FAIL -- SIM needs: SIM PIN" "TOOL-RC=1" # --suite lte reads the modem's mode as well: with the modem off, the SIM's # ERROR is not taken for a missing SIM. board cfun0 som-test-nrf91 --suite lte --out "$W/l0" has_out pty-lte-reads-cfun "lte.sim SKIP -- modem not in normal mode (+CFUN: 0, modem off)" # AT+CFUN=4's OK comes 16 s late: normal mode is restored all the same. # (--suite at,lte: under lte alone the old script never reached the check.) board nosim,cfun4late som-test-nrf91 --suite at,lte --allow-writes --out "$W/c" heard=$(grep 'CFUN=[14]' "$W/recv" 2>/dev/null | tr '\n' '|') case "$heard:$out" in "at AT+CFUN=4|at AT+CFUN=1|"*:*"BOARD-CFUN=1"*) has_out pty-cfun-restore-late "normal mode (1) is restored" ;; *) bst_notok som-nrf91-faults/pty-cfun-restore-late "heard '$heard': '$(_bst_1line "$out")'" ;; esac # The first AT+CFUN=1 is refused: the restore reads the mode back, and # sends it again. board nosim,cfun1fails som-test-nrf91 --suite at,lte --allow-writes --out "$W/c1" heard=$(grep 'CFUN=[14]' "$W/recv" 2>/dev/null | tr '\n' '|') case "$heard:$out" in "at AT+CFUN=4|at AT+CFUN=1|at AT+CFUN=1|:"*"BOARD-CFUN=1"*) has_out pty-cfun-restore-retry "normal mode (1) is restored" ;; *) bst_notok som-nrf91-faults/pty-cfun-restore-retry "heard '$heard': '$(_bst_1line "$out")'" ;; esac # A hangup in flight mode: the run's reader goes with it, and the restore, # reading with a new one, is answered at the first try. board nosim,hupflight som-test-nrf91 --suite at,lte --allow-writes --out "$W/c2" heard=$(grep 'CFUN=[14]' "$W/recv" 2>/dev/null | tr '\n' '|') case "$heard:$out" in "at AT+CFUN=4|at AT+CFUN=1|:"*"normal mode restored"*"TOOL-RC=129"*"BOARD-CFUN=1"*) bst_ok som-nrf91-faults/pty-hangup-restores ;; *) bst_notok som-nrf91-faults/pty-hangup-restores "heard '$heard': '$(_bst_1line "$out")'" ;; esac # A HUP while the board is silent: the run cleans up behind it. board silent,hup som-test-nrf91 --out "$W/h" if [ ! -e "$W/h/.work" ]; then has_out pty-hup-cleans "TOOL-RC=129" else bst_notok som-nrf91-faults/pty-hup-cleans "the run left $W/h/.work: '$(_bst_1line "$out")'"; fi # The same for a TERM, which exits 143. board silent,term som-test-nrf91 --out "$W/t" if [ ! -e "$W/t/.work" ]; then has_out pty-term-cleans "TOOL-RC=143" else bst_notok som-nrf91-faults/pty-term-cleans "the run left $W/t/.work: '$(_bst_1line "$out")'"; fi # A check's detail can quote the board: zephyr.rtc's quotes the client # id's log line here, and the IMEI in it is masked, on screen and in # results.tsv. board good,rtcleak som-test-nrf91 --suite zephyr --out "$W/z" if grep -q -E '[0-9]{14}' "$W/z/results.tsv" 2>/dev/null; then bst_notok som-nrf91-faults/pty-result-masked "results.tsv: '$(_bst_1line "$(grep -E '[0-9]{14}' "$W/z/results.tsv")")'" else case "$out" in *490154203237518*) bst_notok som-nrf91-faults/pty-result-masked "the IMEI in full: '$(_bst_1line "$out")'" ;; *) has_out pty-result-masked "zephyr.rtc FAIL -- no answer from the RTC: " "nrf-***********7518" ;; esac fi fi rm -rf "$W"