. "$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"