#!/bin/sh # Run libotr's upstream test suite -- every entry upstream names in tests/test_list # -- and report one PASS:/FAIL:/SKIP: line each, which is the protocol # ptest-runner reads. # # THIS IS A TAP PARSER, AND IT HAS TO BE. The exit status of this suite carries # no information: all eleven unit programmes end `return 0` unconditionally and # never call libtap's exit_status(), and each of the eight regression entries is # a bash wrapper whose own tap.sh EXIT trap returns ITS test count (zero -- the # wrapper calls no ok()) and discards the client's status. A harness keying on # `$? -eq 0` would therefore report 19/19 green with every assertion inside them # failing. Upstream does not do that either: tests/run.sh is # `prove --merge --exec '' - < test_list`, and prove is a TAP CONSUMER that reads # the plan and the ok/not ok stream. What follows is prove's verdict rule, which # is the suite's real semantics: # # * a `1..N` plan line must appear (before or after the points); # * the number of test points executed must equal N; # * no point may be `not ok`; # * `1..0 # SKIP` is a skipped file, not a failed one; # * a non-zero wait status fails the file. (Checked for completeness -- on this # suite it is structurally always zero, per the paragraph above.) # # This cannot be `prove` itself: that is perl with Test::Harness, and this image # ships perl without it. The command line is reimplemented; the verdict rule is # upstream's. # # WHY THE PAYLOAD IS COPIED RATHER THAN USED IN PLACE: the deployed rootfs is # read-only at browser runtime, and the entries write beside themselves and into # /tmp (test_privkey mkstemps /tmp/libotr-testing-XXXXXX; the regression client # binds /tmp/otr-test-{alice,bob}.sock). Upstream also runs every entry with the # tests/ directory as the working directory -- that is what makes the relative # paths in test_list resolve -- so the staged tree keeps upstream's own shape and # this script chdirs into a writable copy of it. set -u pdir=$(cd "$(dirname "$0")" && pwd) [ -f "$pdir/test_list" ] || { echo "FAIL: libotr-ptest-setup (no test_list; the ptest package is incomplete)" exit 1 } work=$(mktemp -d "${TMPDIR:-/tmp}/libotr-ptest.XXXXXX" 2>/dev/null) \ || { work="${TMPDIR:-/tmp}/libotr-ptest.$$"; mkdir -p "$work"; } [ -d "$work" ] || { echo "FAIL: libotr-ptest-setup (no writable scratch dir)"; exit 1; } # Everything but this script and the ptest metadata: the staged tree IS upstream's # tests/ directory, so a whole-directory copy reproduces it exactly. cp -a "$pdir/." "$work/" 2>/dev/null || { echo "FAIL: libotr-ptest-setup (cannot stage the suite into $work)" rm -rf "$work" exit 1 } # Assertion counts across the whole run, reported at the end: a suite that ran # every file but far fewer points inside them is a real regression that per-file # verdicts alone would not show. # # THEY ARE SPLIT IN TWO, BECAUSE ONLY HALF OF THIS SUITE IS COUNTABLE. libtap # offers two plan modes and libotr uses both, and TAP itself tells them apart by # WHERE the plan line falls: # # plan_tests(N) prints `1..N` BEFORE the points -- the eleven unit files. # Their totals are fixed properties of the source: 5 + 48 + 38 # + 10 + 22 + 1 + 11 + 5 + 23 + 13 + 13 = 189 assertions, and # that number is pinnable and worth pinning. # plan_no_plan() prints `1..N` AFTER them -- the eight regression files, whose # client sends `rand() % 50` messages of `rand() % 600` bytes. # Their totals are RANDOM per run: one file alone measured 262, # 262 and 278 points on three consecutive native runs, and the # whole-suite total was 3454 here against prove's 3832 on the # same tree. Pinning that number would red the suite on nothing. # # So the fixed half is reported separately from the dynamic half, and 80-libotr # pins the first exactly while bounding the second from below. tot_ok=0 tot_notok=0 fixed_pts=0 dyn_pts=0 while read -r entry; do [ -n "$entry" ] || continue out="$work/.bst-tap.out" ( cd "$work" && ./"$entry" ) > "$out" 2>&1 status=$? # Parse the TAP stream. Only the two canonical point forms are counted, and # `not ok` is tested BEFORE `ok` because the former contains the latter as a # prefix of its own third word -- reversing these two cases silently scores # every failure as a pass. plan="" skip_all=0 n_ok=0 n_notok=0 plan_at_head=0 while IFS= read -r line; do case "$line" in "1..0 # SKIP"*|"1..0 #SKIP"*|"1..0 # Skip"*) plan=0; skip_all=1 ;; "1.."[0-9]*) plan=${line#1..} plan=${plan%% *} # Before any point => plan_tests(N), a fixed plan. After them => # plan_no_plan(), a count this run happened to produce. [ $((n_ok + n_notok)) -eq 0 ] && plan_at_head=1 ;; "not ok"*) n_notok=$((n_notok + 1)) ;; "ok "*|"ok") n_ok=$((n_ok + 1)) ;; esac done < "$out" ran=$((n_ok + n_notok)) tot_ok=$((tot_ok + n_ok)) tot_notok=$((tot_notok + n_notok)) if [ "$plan_at_head" = 1 ]; then fixed_pts=$((fixed_pts + ran)) else dyn_pts=$((dyn_pts + ran)) fi if [ "$skip_all" = 1 ]; then echo "SKIP: $entry" continue fi # The verdict, in prove's order. Each clause is a DIFFERENT failure and they # are kept apart on purpose: a missing plan means the programme died before # it announced itself (test_instag returns -ENOMEM from main when # readlink("/proc/self/exe") fails, printing its plan and nothing else); a # short count means it died partway; a `not ok` means the library is wrong. if [ -z "$plan" ]; then echo "FAIL: $entry" elif [ "$n_notok" -gt 0 ]; then echo "FAIL: $entry" elif [ "$ran" != "$plan" ]; then echo "FAIL: $entry" elif [ "$status" != 0 ]; then echo "FAIL: $entry" else echo "PASS: $entry" fi done < "$pdir/test_list" # Not a ptest verdict line -- ptest-runner ignores anything that is not # PASS:/FAIL:/SKIP: -- but the assertion tally the on-target suite asserts against # the native reference. See the header for why fixed and dynamic are separate. echo "libotr-tap-points: ok=$tot_ok not_ok=$tot_notok fixed=$fixed_pts dynamic=$dyn_pts" cd / rm -rf "$work" # The per-test verdicts above are the report; ptest-runner tallies them itself and # a non-zero exit here would be counted a second time as a harness failure. exit 0