. "$BST_LIB" # logwarn is a log-file scanner that reports the interesting lines added SINCE # THE LAST RUN. That "since" is the whole package: it keeps a per-logfile state # file recording the log's inode, line number and byte offset, and every # invocation resumes from there. So unlike the pure text filters already on this # image (grep, sed, awk), logwarn's correctness is not a property of a single # process -- it is a property of what one process leaves on disk for the next # one to read. Every case below that matters asserts across TWO invocations. # # What that makes it a probe of, on this arm: # # 1. INODE IDENTITY ON THE virtiofs ROOT. logwarn decides "has this log been # rotated?" by comparing st_ino against the inode it recorded last time. If # this filesystem reported zero, unstable or recycled inode numbers, rotation # detection would silently mis-fire -- and it would mis-fire by SKIPPING # lines, which reads as a quiet success rather than an error. logwarn/inode-* # below pins that property directly, before any case depends on it, so a red # there explains every rotation red under it. # # 2. THE COMPRESSED PATH GOES THROUGH popen(3), WHICH IS ENOSYS HERE. A rotated # log may be compressed, and scan_file() handles that by sniffing the magic # bytes and re-opening the file through popen("gunzip -c ''"). glibc's # popen is __posix_spawn(_PATH_BSHELL, ...), and posix_spawn is unwired on # wasm32-hwjs -- brintos/linux#14. So logwarn/gz-direct and logwarn/gz-resume # are RED on this image, and they are left FAILING ON PURPOSE rather than # skipped, each asserting the value a WORKING platform gives, so they flip # green by themselves when the platform fix lands and there is nothing to # rewrite at that point. That the gap is popen's and not this port's is # proven ON THIS IMAGE by the other 46 cases, every one of which fork+execs # logwarn from the shell, and by 88-logrotate, whose compress and postrotate # cases pass by fork+execv'ing gzip and /bin/sh directly. # # 3. fseek ON A PIPE, and the fallback that exists for it. When resuming inside # a COMPRESSED file logwarn cannot seek, so it falls back to re-reading and # discarding state->line-1 lines. Both halves are exercised # (logwarn/gz-direct reads whole, logwarn/gz-resume skips), because the # fallback is what a platform with a different pipe-seek errno would take # silently and get wrong. # # 4. THE DEFAULT STATE DIRECTORY IS PART OF THE PORT. The meta-oe recipe compiles # in -DDEFAULT_STATE_DIR="/var/lib/logwarn" and creates that directory in # do_install:append. logwarn/default-state-dir-* run logwarn with NO -d and NO # -f, which is how an operator actually runs it, and check that the state # genuinely lands there. # # EVERY EXPECTATION BELOW WAS MEASURED against a native x86_64 build of the same # 1.0.14 tarball the recipe pins (sha256 8dbfcf9b...), driven through the same # probes, rather than read off the manual. Two of them are worth knowing: # # - -v and -h write to STDERR, not stdout. check_out folds 2>&1 so the cases # read normally, but a case that pinned stdout alone would fail for the # wrong reason. # - THE DEFAULT MATCH IS "YES". A line matching NEITHER a positive nor a # negative pattern IS a match (logwarn.1: "A line that matches neither a # positive nor a negative pattern is considered a match"), so the natural # reading of `logwarn app.log ERROR` -- grep for ERROR -- is wrong: it prints # every line. -p inverts that default and is what the grep-like cases use. # Getting this backwards would produce a suite that passes on a broken port, # so both polarities are asserted against each other below. require logwarn logwarn/present D=${TMPDIR:-/tmp}/.bst-logwarn rm -rf "$D"; mkdir -p "$D" # The fixture every matching case uses. Four lines, two of which carry ERROR, in # alternating positions so that an off-by-one in line accounting is visible in # the -l case rather than hidden by adjacency. mklog() { printf 'alpha info ok\nbravo ERROR disk\ncharlie warn\ndelta ERROR net\n' > "$1" } # A fresh state directory per case: these tests are ABOUT persistence, so sharing # one directory between them would make every case depend on the order the # runner happened to execute them in. fresh() { rm -rf "$D/$1"; mkdir -p "$D/$1"; printf '%s' "$D/$1" } # The state file's name is NOT the log's basename: state_file_name() strips the # leading slashes off the log's path and turns every remaining one into an # underscore, so /tmp/x/a.log becomes tmp_x_a.log inside the state directory. # Reproduced here rather than assumed, because the cases that hand-write a state # file (logwarn/gz-resume) have to name it exactly as logwarn will look for it -- # a near-miss would simply be ignored and the case would silently test the # no-state path instead. sfname() { printf '%s' "$1" | sed 's|^/*||; s|/|_|g'; } # --- identity and packaging --------------------------------------------------- # First, and on purpose: if these are green and the behavioural cases are red, # the binary is installed and running and the fault is in what it DID. check_out logwarn/version "logwarn version 1.0.14" logwarn -v check_rc logwarn/version-rc 0 logwarn -v check_out logwarn/help "logwarn [-d dir | -f file]" logwarn -h check_rc logwarn/help-rc 0 logwarn -h check_eq logwarn/resolves-to-port "/usr/bin/logwarn" "$(command -v logwarn)" # Nothing on this image shadows it -- asserted as an absence, which is the only # falsifiable form of the claim. if busybox --list 2>/dev/null | grep -qx logwarn; then bst_notok logwarn/no-busybox-applet "busybox provides a logwarn applet that would shadow this port" else bst_ok logwarn/no-busybox-applet fi # The recipe's do_install:append creates this, and the binary has its path # compiled in as DEFAULT_STATE_DIR. A missing directory here is a packaging # failure that would only otherwise surface as a confusing runtime error. if [ -d /var/lib/logwarn ]; then bst_ok logwarn/state-dir-shipped else bst_notok logwarn/state-dir-shipped "/var/lib/logwarn is not on the image" fi # The recipe splits the Nagios plug-in into its own logwarn-nagios package, which # this image does not install. Pinned so that a packaging change which starts # dragging bash+coreutils onto the image (that package's RDEPENDS) is noticed. if [ -e /usr/lib/nagios/plugins/check_logwarn ]; then bst_notok logwarn/nagios-split "check_logwarn is present; logwarn-nagios should not be installed on this image" else bst_ok logwarn/nagios-split fi # --- match polarity ----------------------------------------------------------- # The two cases that define the tool. They use the SAME log and the SAME pattern # and differ only in -p, so together they prove the default-match rule rather # than merely exercising it. L=$D/match.log; mklog "$L" s=$(fresh m1); out=$(logwarn -d "$s" -z "$L" ERROR 2>&1); rc=$? n=$(printf '%s\n' "$out" | grep -c .) if [ "$rc" -eq 1 ] && [ "$n" -eq 4 ]; then bst_ok logwarn/default-match-is-yes else bst_notok logwarn/default-match-is-yes "rc=$rc lines=$n want rc=1 lines=4: $(_bst_1line "$out")"; fi s=$(fresh m2); out=$(logwarn -d "$s" -z -p "$L" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "bravo ERROR disk delta ERROR net" ]; then bst_ok logwarn/positive-with-p else bst_notok logwarn/positive-with-p "rc=$rc got:'$(_bst_1line "$out")'"; fi # A negative pattern suppresses; everything else still matches by default. s=$(fresh m3); out=$(logwarn -d "$s" -z "$L" '!ERROR' 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "alpha info ok charlie warn" ]; then bst_ok logwarn/negative-pattern else bst_notok logwarn/negative-pattern "rc=$rc got:'$(_bst_1line "$out")'"; fi # No match at all is rc 0 with empty output -- the "nothing interesting happened" # path an operator's cron job actually keys on. s=$(fresh m4); out=$(logwarn -d "$s" -z -p "$L" NOSUCH 2>&1); rc=$? if [ "$rc" -eq 0 ] && [ -z "$out" ]; then bst_ok logwarn/no-match-rc0 else bst_notok logwarn/no-match-rc0 "rc=$rc want 0, out:'$(_bst_1line "$out")'"; fi # Case folding, both directions, so the flag is shown to DO something rather # than merely be accepted. s=$(fresh m5); out=$(logwarn -d "$s" -z -p -c "$L" error 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "bravo ERROR disk delta ERROR net" ]; then bst_ok logwarn/case-insensitive else bst_notok logwarn/case-insensitive "rc=$rc got:'$(_bst_1line "$out")'"; fi s=$(fresh m6); out=$(logwarn -d "$s" -z -p "$L" error 2>&1); rc=$? if [ "$rc" -eq 0 ] && [ -z "$out" ]; then bst_ok logwarn/case-sensitive-default else bst_notok logwarn/case-sensitive-default "rc=$rc out:'$(_bst_1line "$out")'"; fi # A real extended regular expression, not a literal: this is the one case that # exercises glibc's regcomp/regexec alternation on this arm. s=$(fresh m7); out=$(logwarn -d "$s" -z -p "$L" 'ERROR|warn' 2>&1); rc=$? n=$(printf '%s\n' "$out" | grep -c .) if [ "$rc" -eq 1 ] && [ "$n" -eq 3 ]; then bst_ok logwarn/ere-alternation else bst_notok logwarn/ere-alternation "rc=$rc lines=$n want 3: $(_bst_1line "$out")"; fi # Line numbers come from the LOG, not from the output, so the fixture's # alternating layout makes an off-by-one visible. s=$(fresh m8); out=$(logwarn -d "$s" -z -p -l "$L" ERROR 2>&1) if [ "$out" = "2:bravo ERROR disk 4:delta ERROR net" ]; then bst_ok logwarn/line-numbers else bst_notok logwarn/line-numbers "got:'$(_bst_1line "$out")'"; fi # -q suppresses the text but NOT the verdict: rc 1 still says "there were # matches", which is how logwarn is used as a pure test in a shell script. s=$(fresh m9); out=$(logwarn -d "$s" -z -p -q "$L" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ -z "$out" ]; then bst_ok logwarn/quiet-still-signals else bst_notok logwarn/quiet-still-signals "rc=$rc want 1, out:'$(_bst_1line "$out")'"; fi s=$(fresh m10); out=$(logwarn -d "$s" -z -p -M 1 "$L" ERROR 2>&1) check_eq logwarn/max-print "bravo ERROR disk" "$out" s=$(fresh m11); out=$(logwarn -d "$s" -z -p -N 1 "$L" ERROR 2>&1) check_eq logwarn/max-messages "bravo ERROR disk" "$out" # --- persistence: the reason the package exists ------------------------------- # One state directory deliberately shared across this block, because here the # ORDER IS THE TEST. S=$(fresh persist) P=$D/persist.log; mklog "$P" out=$(logwarn -d "$S" -z -p "$P" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ -n "$out" ]; then bst_ok logwarn/first-run-reports else bst_notok logwarn/first-run-reports "rc=$rc out:'$(_bst_1line "$out")'"; fi SF=$S/$(sfname "$P") if [ -f "$SF" ]; then bst_ok logwarn/state-file-created else bst_notok logwarn/state-file-created "expected $SF; dir holds: $(ls "$S" 2>&1 | tr '\n' ' ')"; fi # Its documented shape. Asserted field by field because a state file that parses # but carries a zero INODENUM is exactly how a broken stat(2) would present. miss="" for k in INODENUM LINENUM POSITION MATCHING; do grep -q "^$k=" "$SF" 2>/dev/null || miss="$miss $k" done check_eq logwarn/state-file-format "" "$miss" sino=$(sed -n 's/^INODENUM="\([0-9]*\)"/\1/p' "$SF" 2>/dev/null) lino=$(ls -i "$P" 2>/dev/null | awk '{print $1}') if [ -n "$sino" ] && [ "$sino" = "$lino" ] && [ "$sino" != "0" ]; then bst_ok logwarn/state-records-real-inode else bst_notok logwarn/state-records-real-inode "state INODENUM='$sino' but ls -i says '$lino'" fi # THE CENTRAL ASSERTION: a second run over an unchanged log reports nothing. out=$(logwarn -d "$S" -p "$P" ERROR 2>&1); rc=$? if [ "$rc" -eq 0 ] && [ -z "$out" ]; then bst_ok logwarn/second-run-silent else bst_notok logwarn/second-run-silent "rc=$rc out:'$(_bst_1line "$out")'"; fi # ...and an appended line is reported ONCE, alone -- not the whole file again. printf 'echo ERROR late\n' >> "$P" out=$(logwarn -d "$S" -p "$P" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "echo ERROR late" ]; then bst_ok logwarn/reports-only-new else bst_notok logwarn/reports-only-new "rc=$rc got:'$(_bst_1line "$out")'"; fi out=$(logwarn -d "$S" -p "$P" ERROR 2>&1); rc=$? if [ "$rc" -eq 0 ] && [ -z "$out" ]; then bst_ok logwarn/each-line-once else bst_notok logwarn/each-line-once "rc=$rc out:'$(_bst_1line "$out")'"; fi # -i initialises to "up to date": no output now, and the NEXT run starts from # here. Both halves asserted -- a -i that printed nothing but also recorded # nothing would pass the first half alone. S2=$(fresh init); I=$D/init.log; mklog "$I" out=$(logwarn -d "$S2" -i "$I" 2>&1); rc=$? if [ "$rc" -eq 0 ] && [ -z "$out" ]; then bst_ok logwarn/init-silent else bst_notok logwarn/init-silent "rc=$rc out:'$(_bst_1line "$out")'"; fi printf 'post-init ERROR here\n' >> "$I" out=$(logwarn -d "$S2" -p "$I" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "post-init ERROR here" ]; then bst_ok logwarn/init-starts-at-end else bst_notok logwarn/init-starts-at-end "rc=$rc got:'$(_bst_1line "$out")'"; fi # -a is the anti-flood flag: a MISSING state file is treated as "up to date" # instead of replaying the entire log. S3=$(fresh auto); A=$D/auto.log; mklog "$A" out=$(logwarn -d "$S3" -a -p "$A" ERROR 2>&1); rc=$? if [ "$rc" -eq 0 ] && [ -z "$out" ]; then bst_ok logwarn/auto-init-no-flood else bst_notok logwarn/auto-init-no-flood "rc=$rc out:'$(_bst_1line "$out")'"; fi printf 'after-auto ERROR x\n' >> "$A" out=$(logwarn -d "$S3" -p "$A" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "after-auto ERROR x" ]; then bst_ok logwarn/auto-init-then-new else bst_notok logwarn/auto-init-then-new "rc=$rc got:'$(_bst_1line "$out")'"; fi # -f names the state file directly rather than a directory holding it. F=$D/explicit.state; E=$D/explicit.log; mklog "$E" logwarn -f "$F" -z -p "$E" ERROR >/dev/null 2>&1 if [ -f "$F" ]; then bst_ok logwarn/explicit-state-file else bst_notok logwarn/explicit-state-file "-f did not create $F"; fi # Truncation in place: when the log is SHORTER than the recorded offset the tool # must rescan from the beginning instead of seeking past the end. A platform # whose stat(2) reported a stale size would fail exactly here. S4=$(fresh trunc); T=$D/trunc.log; mklog "$T" logwarn -d "$S4" -z -p "$T" ERROR >/dev/null 2>&1 printf 'tiny ERROR\n' > "$T" out=$(logwarn -d "$S4" -p "$T" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "tiny ERROR" ]; then bst_ok logwarn/truncation-rescans else bst_notok logwarn/truncation-rescans "rc=$rc got:'$(_bst_1line "$out")'"; fi # --- error paths -------------------------------------------------------------- S5=$(fresh err) out=$(logwarn -d "$S5" -z -p "$D/nosuch.log" ERROR 2>&1); rc=$? case "$out" in *"No such file or directory"*) hit=1 ;; *) hit=0 ;; esac if [ "$rc" -eq 2 ] && [ "$hit" -eq 1 ]; then bst_ok logwarn/missing-log-rc2 else bst_notok logwarn/missing-log-rc2 "rc=$rc want 2: $(_bst_1line "$out")"; fi # -n makes a missing log a non-event, which is what a cron job over a log that # has not been created yet needs. S6=$(fresh errn) out=$(logwarn -d "$S6" -n -z -p "$D/nosuch.log" ERROR 2>&1); rc=$? if [ "$rc" -eq 0 ] && [ -z "$out" ]; then bst_ok logwarn/missing-log-with-n else bst_notok logwarn/missing-log-with-n "rc=$rc out:'$(_bst_1line "$out")'"; fi S7=$(fresh errre) out=$(logwarn -d "$S7" -z -p "$L" '[unclosed' 2>&1); rc=$? case "$out" in *"invalid regular expression"*) hit=1 ;; *) hit=0 ;; esac if [ "$rc" -eq 2 ] && [ "$hit" -eq 1 ]; then bst_ok logwarn/bad-regex-rc2 else bst_notok logwarn/bad-regex-rc2 "rc=$rc want 2: $(_bst_1line "$out")"; fi out=$(logwarn -X 2>&1); rc=$? case "$out" in *"invalid option"*) hit=1 ;; *) hit=0 ;; esac if [ "$rc" -eq 2 ] && [ "$hit" -eq 1 ]; then bst_ok logwarn/bad-option-rc2 else bst_notok logwarn/bad-option-rc2 "rc=$rc want 2: $(_bst_1line "$out")"; fi # --- stdin, and the compressed path (fork+exec) ------------------------------- # `-` reads standard input: no state file is consulted for the content, so this # is the one mode that is a pure filter. out=$(printf 'x ERROR one\ny fine\n' | logwarn -f "$D/stdin.state" -z -p - ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "x ERROR one" ]; then bst_ok logwarn/stdin else bst_notok logwarn/stdin "rc=$rc got:'$(_bst_1line "$out")'"; fi # ---- RED: popen(3) is ENOSYS on wasm32-hwjs (brintos/linux#14) ---------------- # logwarn sniffs the gzip magic and re-opens the file through # popen("gunzip -c ..."); glibc routes that through __posix_spawn, which is # unwired on this arm, so the call returns NULL with errno 38 and logwarn reports # logwarn: can't invoke "gunzip -c ''": Function not implemented # Both cases below are left FAILING on purpose (not skipped) and assert what a # working platform gives, so they turn green by themselves when #14 lands. # The `gzip absent` branch is a genuine skip -- a missing decompressor would be a # fixture problem rather than a platform verdict, and the two must not be conflated. G=$D/scan.log printf 'z1 ERROR one\nz2 ERROR two\n' > "$G" if command -v gzip >/dev/null 2>&1 && gzip -c "$G" > "$G.gz" 2>/dev/null; then S8=$(fresh gz) out=$(logwarn -d "$S8" -z -p "$G.gz" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "z1 ERROR one z2 ERROR two" ]; then bst_ok logwarn/gz-direct elif bst_execfail "$out"; then bst_unrun logwarn/gz-direct "$out" else bst_notok logwarn/gz-direct "rc=$rc got:'$(_bst_1line "$out")'"; fi # Resuming INSIDE a compressed file cannot seek, so logwarn re-reads and # discards state->line-1 lines instead. This is that fallback, and it is the # half a platform with different pipe semantics would get silently wrong. S9=$(fresh gzresume) { echo "# logwarn 1.0.14 state for \"$G.gz\"" echo "INODENUM=\"$(ls -i "$G.gz" | awk '{print $1}')\"" echo 'LINENUM="2"' echo 'POSITION="13"' echo 'MATCHING="true"' } > "$S9/$(sfname "$G.gz")" out=$(logwarn -d "$S9" -p "$G.gz" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "z2 ERROR two" ]; then bst_ok logwarn/gz-resume elif bst_execfail "$out"; then bst_unrun logwarn/gz-resume "$out" else bst_notok logwarn/gz-resume "rc=$rc want 'z2 ERROR two', got:'$(_bst_1line "$out")'"; fi else bst_skip logwarn/gz-direct "gzip not available to build the fixture" bst_skip logwarn/gz-resume "gzip not available to build the fixture" fi # --- rotation: inode identity on this filesystem ------------------------------ # Pinned BEFORE the rotation cases, because every one of them depends on it and # a red here explains all of them at once. logwarn's whole rotation mechanism is # "st_ino changed => the file I knew was renamed away". R=$D/rot.log; printf 'r1 ERROR one\n' > "$R" i1=$(ls -i "$R" | awk '{print $1}'); i2=$(ls -i "$R" | awk '{print $1}') if [ -n "$i1" ] && [ "$i1" = "$i2" ] && [ "$i1" != "0" ]; then bst_ok logwarn/inode-stable else bst_notok logwarn/inode-stable "st_ino not stable/nonzero across two stats: '$i1' vs '$i2'" fi # The rotation fixture uses mv, NOT gzip, and that is deliberate rather than # incidental: mv carries the ORIGINAL inode into the rotated name, which forces # the new log to receive a different one. Compressing instead FREES the old # inode, and a filesystem that immediately recycles it hands the new log the very # inode the state file records -- logwarn then correctly concludes "same file" # and the case would fail for a reason that is neither logwarn's nor the port's. # (Measured on the x86_64 control, where ext4 recycled it every time.) S10=$(fresh rot) logwarn -d "$S10" -z -p "$R" ERROR >/dev/null 2>&1 printf 'r2 ERROR unconsumed\n' >> "$R" mv "$R" "$R.0" printf 'r3 ERROR new\n' > "$R" rino=$(ls -i "$R" | awk '{print $1}') if [ "$rino" != "$i1" ]; then bst_ok logwarn/rotation-new-inode else bst_notok logwarn/rotation-new-inode "new log reused the rotated file's inode ($rino); rotation is undetectable"; fi # Both halves in one verdict: the line left unread in the ROTATED file, and the # line in the NEW one, in that order. A tool that only picked up the new file # would lose r2 -- silently, which is the failure mode that matters. out=$(logwarn -d "$S10" -p "$R" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "r2 ERROR unconsumed r3 ERROR new" ]; then bst_ok logwarn/rotation-spans-both else bst_notok logwarn/rotation-spans-both "rc=$rc got:'$(_bst_1line "$out")'"; fi # --- multi-line messages and repeat suppression ------------------------------- # Without -m every line is its own message. With it, a message starts at firstpat # and its continuation lines belong to it -- so a matching FIRST line drags its # indented body along, which is what a Java stack trace in a log looks like. ML=$D/ml.log printf 'JOB start a\n detail a1\n detail a2\nJOB start b ERROR\n detail b1\n' > "$ML" S11=$(fresh ml) out=$(logwarn -d "$S11" -z -p -m '^JOB' "$ML" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "JOB start b ERROR detail b1" ]; then bst_ok logwarn/multiline else bst_notok logwarn/multiline "rc=$rc got:'$(_bst_1line "$out")'"; fi S12=$(fresh mlL) out=$(logwarn -d "$S12" -z -p -m '^JOB' -L 1 "$ML" ERROR 2>&1) check_eq logwarn/multiline-maxlines "JOB start b ERROR" "$out" # -T suppresses a repeating message until it has occurred num times within secs. # The window is an hour and all four lines are scanned in one run, so this is # deterministic despite being nominally about time: occurrences 1 and 2 are # swallowed and the third is what gets reported. TL=$D/t.log printf 'e1 ERROR\ne2 ERROR\ne3 ERROR\ne4 ERROR\n' > "$TL" S13=$(fresh sup) out=$(logwarn -d "$S13" -z -p "$TL" -T 3/3600 ERROR 2>&1) check_eq logwarn/suppress-until-threshold "e3 ERROR" "$out" # --- the default state directory, i.e. how it is actually run ----------------- # No -d and no -f: the compiled-in DEFAULT_STATE_DIR is used. This is the only # block that writes outside $TMPDIR, and it is the one that proves the recipe's # do_install:append directory is not merely present but USABLE. DL=$D/default.log; mklog "$DL" DSF=/var/lib/logwarn/$(sfname "$DL") rm -f "$DSF" 2>/dev/null out=$(logwarn -z -p "$DL" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "bravo ERROR disk delta ERROR net" ]; then bst_ok logwarn/default-state-dir-run else bst_notok logwarn/default-state-dir-run "rc=$rc got:'$(_bst_1line "$out")'"; fi if [ -f "$DSF" ]; then bst_ok logwarn/default-state-dir-written else bst_notok logwarn/default-state-dir-written "no state file at $DSF after a default-dir run"; fi # And it resumes from there, which is the pair of the case above: writing the # state is only half of "the default directory works". printf 'default-dir ERROR new\n' >> "$DL" out=$(logwarn -p "$DL" ERROR 2>&1); rc=$? if [ "$rc" -eq 1 ] && [ "$out" = "default-dir ERROR new" ]; then bst_ok logwarn/default-state-dir-resumes else bst_notok logwarn/default-state-dir-resumes "rc=$rc got:'$(_bst_1line "$out")'"; fi rm -f "$DSF" 2>/dev/null rm -rf "$D"