. "$BST_LIB" # BST-TIMEOUT: 300 # C-Kermit 3.02 -- the communications program and its scripting language. # # TWO HALVES, AND THE SECOND ONE ONLY EXISTS HERE BECAUSE THE PORT TURNED IT # ON. meta-oe compiles this package -DNONET, which on a guest whose serial line # refuses open() until a host WebSerial port attaches # (linux-in-the-browser#60) would leave kermit with no reachable transport at # all; ckermit_%.bbappend drops that flag and adds the -DTCPSOCKET upstream's # own linux makefile passes. So every ckermit/net-* case below is also the # assertion that the divergence is still in the recipe: a build that quietly # went back to meta-oe's flags answers `?No keywords match - host` and reds # them all. # # The language half is driven from data/ckermit-selftest.ksc, written in the # language it tests, which prints tagged R lines this file compares against # values scored on an x86_64 control build of the same tarball with the same # flag list. A mismatch here is a target finding, not a mis-written assertion. # # EVERY kermit INVOCATION REDIRECTS STDIN FROM /dev/null AND PASSES -y # /dev/null. Without the first, kermit falls through to its interactive command # parser and waits forever -- under the runner that is a suite-timeout with no # case to name. Without the second it reads ~/.kermrc, so what the suite # measures would depend on the image's home directory. # # ONE socat EXEC, AND IT IS THE FIRST THING THIS FILE DOES. socat is a 54 MB # image on a guest with 938 MB of RAM that does not return memory on process # exit (brintos/linux#21) and refuses to stage a large exec under fragmentation # (brintos/linux#44) -- measured here, a second socat exec already fails # `Cannot allocate memory` with 868 MB nominally free. So the whole network # group runs against ONE forking listener started before anything else, and # every client is kermit, which at 9.8 MB stages repeatedly. An earlier draft # stood up four listeners and retried each client ten times; it grew the guest # 2918 MiB/min and was killed with no case reported at all. require kermit ckermit/present require socat ckermit/socat-present D=${TMPDIR:-/tmp}/bst-ckermit.$$ rm -rf "$D"; mkdir -p "$D" || { bst_notok ckermit/workdir "cannot create $D"; exit 0; } K="kermit -y /dev/null" T="" if command -v timeout >/dev/null 2>&1; then T="timeout 60"; else bst_notok ckermit/timeout-available "no timeout(1) on this image; kermit runs are bounded by the suite alone" fi # --- the payloads, built before the listener so its stdin is never racing ------ # Every byte value 0..256: ONE awk process and LC_ALL=C pinned, for the reasons # 88-socat-net.test records -- 256 forked subshells is slower here than every # assertion in this file combined, and under a UTF-8 locale awk's %c emits the # UTF-8 encoding of 128..255, so the "256-byte" payload is silently 384 bytes # and stops covering the high half at all. LC_ALL=C awk 'BEGIN{for(i=0;i<256;i++) printf "%c", i}' > "$D/bin.src" # 12000 bytes of fixed text: several transfer blocks, so a short write shows up # as a length rather than rounding to "close enough". awk 'BEGIN{for(n=1;n<=200;n++) printf "%04d the quick brown fox jumps over the lazy dog 0123456789\n", n}' > "$D/txt.src" printf 'one\ntwo\nthree\n' > "$D/eol.src" BIN_N=$(wc -c < "$D/bin.src" | tr -d ' ') TXT_N=$(wc -c < "$D/txt.src" | tr -d ' ') # --- the single listener ------------------------------------------------------ # `fork` serves each of kermit's three connections from a child of the ONE socat # that was exec'd, appending them to a single sink in the order kermit sends # them. The segments are then split back out by their known lengths, so three # independent verdicts come out of one exec. $T socat -u TCP4-LISTEN:22412,bind=127.0.0.1,reuseaddr,fork FILE:"$D/sink.out",create,append 2>"$D/socat.err" & # WAIT FOR THE SOCKET, NOT FOR A CLOCK. A TCP listener leaves no filesystem # artifact to poll, so the obvious `sleep 3` is what an earlier draft did -- and # a 54 MB socat takes longer than that to load on this guest, so every network # case reported `Connection refused` against a listener that was merely still # starting. /proc/net/tcp is the artifact: it costs no exec, and a port in state # 0A is the same fact the client is about to depend on. wait_listen() { _hex=$(printf '%04X' "$1"); _n=0 while [ "$_n" -lt 60 ]; do grep -qi ":$_hex " /proc/net/tcp 2>/dev/null && return 0 grep -qi ":$_hex " /proc/net/tcp6 2>/dev/null && return 0 _n=$((_n+1)); sleep 1 done return 1 } socat_err=$(cat "$D/socat.err" 2>/dev/null) if wait_listen 22412; then bst_ok ckermit/net-listener listener=1 elif bst_execfail "$socat_err"; then bst_unrun ckermit/net-listener "$socat_err" listener=0 else bst_notok ckermit/net-listener "socat never reached LISTEN on 127.0.0.1:22412 within 60s: $(_bst_1line "$socat_err")" listener=0 fi # --- one kermit run drives every network case --------------------------------- cat > "$D/net.ksc" <<'KSC' set host 127.0.0.1 22412 /raw-socket if fail exit 1 echo N1-CONNECT ok transmit /binary \$(CK_BIN) if success echo N2-BIN sent close set host 127.0.0.1 22412 /raw-socket transmit /binary \$(CK_TXT) if success echo N3-TXT sent close set transmit prompt 0 set transmit echo off set host 127.0.0.1 22412 /raw-socket transmit \$(CK_EOL) if success echo N4-EOL sent close set host 127.0.0.1 22419 /raw-socket if fail echo N5-REFUSED as-expected exit 0 KSC if [ "$listener" -eq 1 ]; then net_out=$(CK_BIN="$D/bin.src" CK_TXT="$D/txt.src" CK_EOL="$D/eol.src" \ $T $K -C "take $D/net.ksc" &1); net_rc=$? else net_out=""; net_rc=0 fi # n -- one verdict per tagged line the network script printed. # The listener's own failure is carried forward rather than re-derived, so a # guest that could not stage socat reports the platform gap on every case # instead of five lookalike reds about kermit. n() { if [ "$listener" -eq 0 ]; then bst_notok "ckermit/$1" "$BST_UNRUN_TAG (brintos/linux#44): the socat listener could not be staged, so this assertion never ran" return fi case "$net_out" in *"No keywords match"*) bst_notok "ckermit/$1" "SET HOST is not compiled in -- the -DNONET/-DTCPSOCKET divergence is not in the recipe: $(_bst_1line "$net_out")" ;; *"$2"*) bst_ok "ckermit/$1" ;; *) bst_notok "ckermit/$1" "rc=$net_rc $(_bst_1line "$net_out")" ;; esac } n net-set-host N1-CONNECT n net-transmit-binary N2-BIN n net-transmit-text N3-TXT n net-transmit-eol N4-EOL # The negative control for the whole group: a port nobody listens on must FAIL. # Without it, a kermit that reported success for every SET HOST would pass every # case above. n net-refused N5-REFUSED # --- byte-for-byte at the far end --------------------------------------------- # The ticket's criterion, and the only assertion here that reads the far end's # file rather than kermit's own account of what it did. seg() { _case=$1; _src=$2; _skip=$3; _len=$4 if [ "$listener" -eq 0 ] || [ ! -s "$D/sink.out" ]; then bst_notok "ckermit/$_case" "$BST_UNRUN_TAG (brintos/linux#44): no listener sink, so the comparison never ran" return fi dd if="$D/sink.out" of="$D/$_case.got" bs=1 skip="$_skip" count="$_len" 2>/dev/null if cmp -s "$_src" "$D/$_case.got"; then bst_ok "ckermit/$_case" else bst_notok "ckermit/$_case" "$(wc -c < "$_src" | tr -d ' ') bytes in, $(wc -c < "$D/$_case.got" 2>/dev/null | tr -d ' ') out of a $(wc -c < "$D/sink.out" 2>/dev/null | tr -d ' ')-byte sink"; fi } seg net-binary-identical "$D/bin.src" 0 "$BIN_N" seg net-text-identical "$D/txt.src" "$BIN_N" "$TXT_N" # TRANSMIT's TEXT mode does not promise the same bytes: it terminates each line # with CR by design, so this asserts the TRANSLATED form. A reader "fixing" the # expectation to match the source file has broken the case, not repaired it. if [ "$listener" -eq 1 ] && [ -s "$D/sink.out" ]; then eol_got=$(dd if="$D/sink.out" bs=1 skip=$((BIN_N + TXT_N)) 2>/dev/null | od -An -c | tr -s ' ' | tr -d ' \n') check_eq ckermit/net-text-cr-eol "one\rtwo\rthree\r" "$eol_got" else bst_notok ckermit/net-text-cr-eol "$BST_UNRUN_TAG (brintos/linux#44): no listener sink, so the comparison never ran" fi # --- the language, from the payload ------------------------------------------- if [ -f "$BST_DATA/ckermit-selftest.ksc" ]; then bst_ok ckermit/payload-present else bst_notok ckermit/payload-present "$BST_DATA/ckermit-selftest.ksc is not on the image" fi script_out=$(CK_WORK="$D" $T $K -C "take $BST_DATA/ckermit-selftest.ksc" &1); script_rc=$? # The whole-payload gate first, so a payload that died at case 3 says so once # rather than as forty lookalike reds. if bst_execfail "$script_out"; then bst_unrun ckermit/script-all-ok "$script_out" else case "$script_out" in *ALL-CKERMIT-OK*) if [ "$script_rc" -eq 0 ]; then bst_ok ckermit/script-all-ok else bst_notok ckermit/script-all-ok "reached the end but exited rc=$script_rc"; fi ;; *ALL-CKERMIT-NO-WORKDIR*) bst_notok ckermit/script-all-ok "FOPEN could not write in $D -- the payload stopped at its own guard" ;; *) bst_notok ckermit/script-all-ok "rc=$script_rc $(_bst_1line "$script_out")" ;; esac fi # r -- pull one tagged line out of the payload's output and # compare its value. Split from check_out deliberately: a payload that printed # nothing at all would make every `contains` assertion fail with no value to # show, and check_eq's `got:''` is what says "the line was never printed". r() { check_eq "ckermit/$1" "$2" "$(printf '%s\n' "$script_out" | sed -n "s/^R[0-9][0-9b]*-$1 //p" | head -1)" } r define 42 r arith-mul 42 r arith-sub 42 r arith-div 43 r arith-mod 16 r increment 47 r decrement 42 r while-sum 15 r while-continue 30 r for-step 25 r if-then greater r if-streq equal r if-strneq differs r switch two r switch-default other r macro-return 42 r macro-do 42-OK r macro-nested 50 r macro-loop 720 r array-elem 16 r array-dim 5 r flength 16 r fupper MIXEDCASE r flower mixedcase r fsubstring commun r findex 14 r frepeat abababab r fword gamma r fmax 71 r fmin 17 r feval 14 r fcharacter A r fcode 65 r fopen-write ok r fopen-read ok r fread-count 3 r fread-last gamma r fsize 17 r if-exist present r environ set r version-major 9 # --- the channel-guard patch, driven on target -------------------------------- # A failed FOPEN leaves the channel variable at -1, and upstream's file i/o entry # points check only the upper bound before indexing z_file[channel]: FWRITE, # FREAD and FSEEK segfault the whole interpreter. The port patches all twelve # entry points; these three cases are that patch, asserted where it runs. Each # runs in its own kermit process so a regression costs one case rather than the # rest of the file. guard() { _case=$1; _cmd=$2 printf '%s\n' 'fopen /write \%c /no-such-dir-here/x.txt' "$_cmd" 'echo SURVIVED' 'exit 0' > "$D/guard.ksc" _out=$($T $K -C "take $D/guard.ksc" &1); _rc=$? case "$_out" in *SURVIVED*) bst_ok "ckermit/$_case" ;; *) if [ "$_rc" -ge 128 ]; then bst_notok "ckermit/$_case" "kermit died on signal $((_rc-128)) writing to a failed channel -- the ckuus7.c channel guard is not in this build" else bst_notok "ckermit/$_case" "rc=$_rc $(_bst_1line "$_out")" fi ;; esac } guard io-guard-fwrite 'fwrite /line \%c alpha' guard io-guard-fread 'fread /line \%c \%l' guard io-guard-fseek 'fseek \%c 0' rm -rf "$D"