. "$BST_LIB" require s-nail s-nail/present # Every invocation below passes -:/ (load no resource file at all) and points # MAILRC at /dev/null, so what is asserted is s-nail's own behaviour and not # whatever /etc/nail.rc happens to carry on this image. HOME is moved to the # writable tmpfs because the rootfs is read-only at browser runtime and s-nail # writes .mailrc-adjacent state next to it. tmp=${TMPDIR:-/tmp} MAILRC=/dev/null; export MAILRC HOME=$tmp; export HOME box=$tmp/bst-snail-rt.$$ multi=$tmp/bst-snail-multi.$$ empty=$tmp/bst-snail-empty.$$ rm -f "$box" "$multi" "$empty" SUBJ='BRINTOS Round Trip 7' BODY='BODY-hello-42' HDR='X-Brintos-Arm: wasm32-hwjs' # THE PIPELINE CASE, AND IT IS ALSO THE COMPOSE HALF OF THE ROUND TRIP. A # recipient that names a file rather than an address makes s-nail append the # composed message to that mbox instead of handing it to an MTA -- which is what # lets this suite assert a real compose->store->read cycle with no SMTP peer # anywhere. expandaddr=+file is what unlocks the file target; s-nail refuses it # by default, and a port that quietly ignored the variable would deliver # nothing here rather than deliver wrongly. deliver() { # deliver [extra-header] if [ -n "${4:-}" ]; then printf '%s\n' "$2" | s-nail -:/ -s "$1" -C "$4" -S expandaddr=+file -. "$3" else printf '%s\n' "$2" | s-nail -:/ -s "$1" -S expandaddr=+file -. "$3" fi } # An exit status is not evidence that a message was stored. A binary that did # nothing at all and returned 0 satisfies `deliver` and every rc-only assertion # built on it, so the delivery case requires the mbox to exist and be non-empty # afterwards -- which is the difference between "s-nail ran" and "s-nail wrote". deliver_ok() { deliver "$1" "$2" "$3" "${4:-}" || return $? [ -s "$3" ] } # Read a stored message back THROUGH S-NAIL rather than with cat: the point of # the round trip is that s-nail's own reader parses what s-nail's own writer # produced. `print 1` renders message 1 with its headers; -# is the batch mode # that makes the reader consume a command stream on stdin. readback() { printf 'print 1\nquit\n' | s-nail -:/ -# -f "$1"; } check s-nail/deliver-file deliver_ok "$SUBJ" "$BODY" "$box" "$HDR" # mbox format itself: a stored message opens with a "From " envelope line. A # writer that emitted the headers but not the separator produces a file every # reader mis-parses, and the header assertions below would still pass on it. check_out s-nail/mbox-from 'From ' head -n 1 "$box" rt=$(readback "$box" 2>&1) check_out s-nail/rt-subject "Subject: $SUBJ" printf '%s\n' "$rt" check_out s-nail/rt-body "$BODY" printf '%s\n' "$rt" check_out s-nail/rt-customhdr "$HDR" printf '%s\n' "$rt" check_out s-nail/rt-useragent 'User-Agent: s-nail' printf '%s\n' "$rt" check_out s-nail/rt-date 'Date: ' printf '%s\n' "$rt" # A body of several lines, asserted line by line and in order -- a reader that # folds or reorders the body would still satisfy a single-line body check. printf 'l1-alpha\nl2-beta\nl3-gamma\n' | s-nail -:/ -s 'Multi' -S expandaddr=+file -. "$multi" >/dev/null 2>&1 mrt=$(readback "$multi" 2>&1) check_out s-nail/multiline-1 'l1-alpha' printf '%s\n' "$mrt" check_out s-nail/multiline-2 'l2-beta' printf '%s\n' "$mrt" check_out s-nail/multiline-3 'l3-gamma' printf '%s\n' "$mrt" check_eq s-nail/multiline-order 'l1-alpha l2-beta l3-gamma' \ "$(printf '%s\n' "$mrt" | sed -n 's/^\(l[123]-[a-z]*\)$/\1/p' | tr '\n' ' ' | sed 's/ $//')" # Appending a second message to an existing mbox -- the operation a script that # logs mail performs on every run, and the one that goes wrong when a writer # truncates instead of appends. deliver 'Subj B' 'BODY-second' "$box" >/dev/null 2>&1 check_eq s-nail/append-count 2 "$(grep -c '^From ' "$box")" check_out s-nail/summary-subjects 'Subj B' \ sh -c 'printf "headers\nquit\n" | s-nail -:/ -# -f "$1"' sh "$box" # The has-mail exit status, both directions. Scripts branch on this, so the # false direction matters as much as the true one: a port that always returned 0 # would pass the first of these two and fail the second. : > "$empty" check_rc s-nail/has-mail 0 s-nail -:/ -e -f "$box" check_rc s-nail/no-mail 1 s-nail -:/ -e -f "$empty" # mailx(1) is the name an operator and a shell script reach for, and it is the # reason this package is in the image at all -- the recipe registers s-nail as # the mailx alternative, so assert the ALTERNATIVE actually resolved rather than # trusting the recipe line that asked for it. if command -v mailx >/dev/null 2>&1; then check_out s-nail/mailx-alt 's-nail v14.9.24' mailx -:/ -Xversion -Xx else bst_notok s-nail/mailx-alt 'mailx alternative not resolved on this image' fi # The evaluators, scored against values recorded from a native s-nail 14.9.24 # x86_64 control (2026-08-12) rather than from this arm. These are computed # answers: a wasm32 build that started, echoed and delivered mail correctly can # still get integer width or string indexing wrong, and nothing above would see # it. check_eq s-nail/version-major 14 "$(s-nail -:/ -X 'echo $version-major' -Xx 2>&1)" check_eq s-nail/csop-length 14 "$(s-nail -:/ -X 'csop length brintos-wasm32' -Xx 2>&1)" check_eq s-nail/csop-find 8 "$(s-nail -:/ -X 'csop find brintos-wasm32 wasm' -Xx 2>&1)" check_eq s-nail/csop-substring 'wasm32' "$(s-nail -:/ -X 'csop substring brintos-wasm32 8' -Xx 2>&1)" check_out s-nail/vexpr-add '0x2A' s-nail -:/ -X 'vexpr + 40 2' -Xx check_out s-nail/vexpr-mul '0x2A' s-nail -:/ -X 'vexpr * 6 7' -Xx check_out s-nail/vexpr-pbase '0xFF' s-nail -:/ -X 'vexpr pbase 16 255' -Xx rm -f "$box" "$multi" "$empty"