#!/bin/bash # Temporary workspace restore: do a teacher's recent tabs come back — and # only for a while, and never with their sign-in? # # Each scenario launches the INSTALLED classroom-wonderink one or more times # (tools/lab/wonderink-workspace-rig.py), against the lab's stand-in for # WonderInk's start page and a second origin standing in for its public # resource files, and is judged by the tabs each launch opens and by the # kept file itself. Time between launches is made to pass by moving the # time of last use in the file; time within a launch is real, with the # limit shortened by the test-only override where a scenario needs it. # # tools/lab/wonderink-workspace-check [SCENARIO...] set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" NAME="${LAB_NAME:-classroom-v2-lab}" COMMAND="${WONDERINK:-/usr/local/bin/classroom-wonderink}" DIR=/opt/wonderink-check SITE=http://127.0.0.1:8094 SLIDE=http://localhost:8095/img/ark-wide.png OTHER=http://localhost:8095/img/ark-tall.png KEPT=/home/teacher/.local/share/ekklesia-classroom/wonderink-workspace.json SHEETS="/home/teacher/Downloads/Wonder Ink" lab() { docker exec -i "$NAME" bash -c "$1"; } lab "mkdir -p $DIR" for file in wonderink-workspace-rig.py wonderink-lesson-site.py wonderink-picture-measure.py; do docker cp "$ROOT/tools/lab/$file" "$NAME:$DIR/$file" >/dev/null done lab "chmod -R a+rX $DIR; id teacher >/dev/null 2>&1 || useradd -m teacher; install -d -o teacher -m 700 /tmp/teacher-rt" lab "test -f $DIR/www/ark-wide.webp || { mkdir -p $DIR/www; QT_QPA_PLATFORM=offscreen \ QTWEBENGINE_CHROMIUM_FLAGS='--no-sandbox --disable-gpu' python3 $DIR/wonderink-picture-measure.py make $DIR/www; } chmod -R a+r $DIR/www" lab "pkill -f '[w]onderink-lesson-site.py'; : > $DIR/site.log; chmod a+rw $DIR/site.log" docker exec -d "$NAME" python3 "$DIR/wonderink-lesson-site.py" "$DIR/www" 8094 8095 "$DIR/site.log" lab 'for _ in $(seq 20); do curl -s -o /dev/null http://127.0.0.1:8094/app && exit 0; sleep 0.3; done; exit 1' || { echo "the lesson site did not start" >&2 exit 1 } passed=0 failed=0 out="" judge() { if eval "$2"; then printf ' \033[32mPASS\033[0m %s\n' "$1" passed=$((passed + 1)) else printf ' \033[31mFAIL\033[0m %s\n' "$1" failed=$((failed + 1)) fi } # launch [limit=SECONDS] -- STEP... one Wonder Ink; its output appended to $out launch() { local env="" [[ "$1" == limit=* ]] && { env="CLASSROOM_WONDERINK_TEST_WORKSPACE_SECONDS=${1#limit=}"; shift; } [[ "$1" == -- ]] && shift out+=$'\n'"=== launch $*"$'\n' out+=$(lab "su teacher -c 'cd /tmp; XDG_RUNTIME_DIR=/tmp/teacher-rt QT_QPA_PLATFORM=offscreen \ QTWEBENGINE_CHROMIUM_FLAGS=\"--no-sandbox --disable-gpu\" $env \ timeout 180 python3 $DIR/wonderink-workspace-rig.py $COMMAND $SITE $* 2>&1'; echo \"exit=\$?\"") } # The time of last use in the kept file, made SECONDS older (negative: the future). age() { lab "su teacher -c \"python3 -c 'import json,sys; p=sys.argv[1]; d=json.load(open(p)); d[\\\"last_activity\\\"]-=float(sys.argv[2]); json.dump(d, open(p, \\\"w\\\"))' $KEPT $1\"" } said() { grep -q -- "$1" <<< "$out"; } nth() { grep -o -- "$1.*" <<< "$out" | sed -n "${2}p"; } kept_file() { lab "test -e $KEPT"; } # The age a STATE line reports for the kept file. age_of() { grep -o 'age=[0-9]*' <<< "$1" | cut -d= -f2; } clean() { lab "rm -f $KEPT $KEPT.new; rm -rf \"$SHEETS\"; install -d -o teacher \"$SHEETS\""; out=""; } # Every launch ended by itself with 0, except one the scenario killed on purpose. no_crash() { ! said Traceback && ! said Segmentation && \ ! awk '/\] CRASH/ {killed=1} /^exit=/ {if ($0 != "exit=0" && !(killed && $0 == "exit=137")) bad=1; killed=0} END {exit !bad}' <<< "$out" } THREE="touch open:$SLIDE open:$OTHER front:1 touch" RESTORED="tabs=\\['/app', '$SLIDE', '$OTHER'\\] active=1" FRESH="tabs=\\['/app'\\] active=0 kept\\[none\\]" scenarios=("$@") [[ ${#scenarios[@]} -gt 0 ]] || scenarios=(restore-5min restore-90min expire-2h1m expire-next-day expire-next-week reopen-does-not-refresh activity-refreshes background-ignored preview-ignored printing-counts closed-stays-closed reordered worksheet worksheet-gone crash-then-restore restore-crashes corrupt future-format half-written privacy restored-tabs-print) for scenario in "${scenarios[@]}"; do echo "$scenario" clean before=$failed case "$scenario" in restore-5min|restore-90min) minutes=${scenario#restore-}; minutes=${minutes%min} launch -- $THREE state quit age $((minutes * 60)) launch -- state front:0 state quit judge "three tabs, the middle one in front, closed; $minutes minutes later: all three back" \ "nth STATE 2 | grep -q \"$RESTORED\"" judge "in the same order, the same one in front" "nth START 2 | grep -q 'tabs=3'" judge "the tabs behind wait to load until they are shown (a PDF behind opened at 10%)" \ "nth STATE 2 | grep -q 'unshown=2'" judge "and one clicked loads: the start page, asking to sign in" \ "nth STATE 3 | grep -q \"pages=\\['Signed out'\\] unshown=1\"" ;; expire-2h1m|expire-next-day|expire-next-week) case "$scenario" in expire-2h1m) seconds=$((2 * 3600 + 60)); label="two hours and a minute" ;; expire-next-day) seconds=$((24 * 3600)); label="the next day" ;; *) seconds=$((7 * 24 * 3600)); label="the next week" ;; esac launch -- $THREE quit age $seconds launch -- state quit judge "$label: a fresh Wonder Ink, one tab on the start page" "nth STATE 1 | grep -q \"$FRESH\"" judge "the old workspace is gone, and says why" "said 'starting fresh (expired)' && ! kept_file" ;; reopen-does-not-refresh) launch limit=20 -- $THREE quit launch limit=20 -- wait:8 quit launch limit=20 -- wait:8 quit launch limit=20 -- state quit judge "reopened twice, nobody touching it: restored both times" \ "nth START 2 | grep -q 'tabs=3' && nth START 3 | grep -q 'tabs=3'" judge "but reopening never renewed it: fresh once the limit had passed" \ "nth STATE 1 | grep -q \"$FRESH\"" ;; activity-refreshes) launch -- $THREE quit age 600 launch -- wait:4 state touch quit # The time of last use is written at most every 30 s, and at quit. restored=$(age_of "$(nth STATE 1)"); touched=$(age_of "$(nth QUIT 2)") judge "restoring it did not count as using it (age $restored s, from ten minutes)" "(( restored >= 600 ))" judge "a click on the restored tabs does (age $touched s at quit)" "(( touched <= 5 ))" ;; background-ignored) launch -- "touch open:$SITE/app?busy=1 touch" quit age 600 launch -- wait:20 state quit judge "a page busy by itself for 20 s (a request a second, an animation) is not activity (age $(age_of "$(nth STATE 1)") s)" \ "(( \$(age_of \"\$(nth STATE 1)\") >= 615 ))" judge "and it really was busy" "[[ \$(lab 'grep -c beacon $DIR/site.log') -gt 10 ]]" ;; preview-ignored) launch -- $THREE quit age 600 launch -- preview:15 state quit judge "a print preview left open 15 s untouched is not activity (age $(age_of "$(nth STATE 1)") s)" \ "(( \$(age_of \"\$(nth STATE 1)\") >= 610 ))" ;; printing-counts) launch -- $THREE quit age 600 before_jobs=$(lab 'lpstat -W all -o 2>/dev/null | wc -l') launch -- front:2 print state quit judge "pressing Print is using Wonder Ink (age $(age_of "$(nth STATE 1)") s)" "(( \$(age_of \"\$(nth STATE 1)\") <= 5 ))" judge "and the restored picture tab printed" "(( \$(lab 'lpstat -W all -o | wc -l') > before_jobs ))" ;; closed-stays-closed) launch -- $THREE close:1 touch quit launch -- state quit judge "a tab closed before closing Wonder Ink stays closed" \ "nth STATE 1 | grep -q \"tabs=\\['/app', '$OTHER'\\]\"" ;; reordered) launch -- $THREE move:0:2 touch quit launch -- state quit judge "tabs dragged into another order come back in that order" \ "nth STATE 1 | grep -q \"tabs=\\['$SLIDE', '$OTHER', '/app'\\]\"" ;; worksheet) launch -- touch worksheet:Colour-the-Ark open:$SLIDE front:1 touch quit launch -- state quit judge "a worksheet comes back as a worksheet, where it was, in front" \ "nth STATE 1 | grep -q \"tabs=\\['/app', 'worksheet:Colour-the-Ark.pdf', '$SLIDE'\\] active=1\"" ;; worksheet-gone) launch -- touch worksheet:Colour-the-Ark open:$SLIDE touch quit lab "rm -f \"$SHEETS/Colour-the-Ark.pdf\"" launch -- state quit judge "a worksheet whose file has gone is left out, and nothing else is" \ "nth STATE 1 | grep -q \"tabs=\\['/app', '$SLIDE'\\]\"" ;; crash-then-restore) launch -- $THREE wait:2 crash launch -- state quit judge "killed outright: the tabs come back all the same" "nth STATE 1 | grep -q \"$RESTORED\"" ;; restore-crashes) launch -- $THREE quit launch -- crash launch -- crash launch -- state quit judge "a restore that crashes is tried once more" "nth START 2 | grep -q 'tabs=3' && nth START 3 | grep -q 'tabs=3 attempts=2'" judge "and then abandoned: fresh, without asking anybody" \ "said 'starting fresh (it failed to restore 2 times)' && nth STATE 1 | grep -q \"tabs=\\['/app'\\]\"" ;; corrupt) lab "su teacher -c 'mkdir -p $(dirname $KEPT); echo \"{not json\" > $KEPT'" launch -- state quit judge "an unreadable file: fresh, and Wonder Ink opens" "said 'starting fresh (unreadable)' && nth STATE 1 | grep -q \"$FRESH\"" ;; future-format) launch -- $THREE quit lab "su teacher -c \"python3 -c 'import json,sys; p=sys.argv[1]; d=json.load(open(p)); d[\\\"format\\\"]=99; json.dump(d, open(p, \\\"w\\\"))' $KEPT\"" launch -- state quit judge "a file from a later version: fresh" "said 'starting fresh (from another version)' && nth STATE 1 | grep -q \"$FRESH\"" ;; half-written) launch -- $THREE quit lab "su teacher -c 'echo \"{\\\"format\\\": 1, \\\"last_act\" > $KEPT.new'" launch -- state quit judge "a write cut off half way leaves the last whole one, which is restored" "nth STATE 1 | grep -q \"$RESTORED\"" ;; privacy) launch -- signin touch state open:$SLIDE open:$OTHER touch quit judge "signed in on the stand-in, whose sign-in is Basic auth as WonderInk's is" \ "nth STATE 1 | grep -q \"pages=\\['Signed in as teacher'\\]\"" judge "the kept file has no password, Basic-auth credential, cookie or header in it" \ "kept_file && ! lab \"grep -qiE 'hunter2|Basic|dGVhY2hlc|teacher@|rrf|cookie|x-rrf|authoriz|token' $KEPT\"" judge "only addresses, titles and times: the fields it is meant to have" \ "lab \"python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); sys.exit(0 if set(d)=={\\\"format\\\",\\\"last_activity\\\",\\\"active\\\",\\\"attempts\\\",\\\"tabs\\\"} and all(set(t)=={\\\"kind\\\",\\\"where\\\",\\\"title\\\"} for t in d[\\\"tabs\\\"]) else 1)' $KEPT\"" launch -- front:0 state quit judge "and the restored start page is signed out: the site asks again" \ "said 'restored 3 recent tabs' && nth STATE 2 | grep -q \"pages=\\['Signed out'\\]\"" ;; restored-tabs-print) launch -- touch worksheet:Colour-the-Ark open:$SLIDE touch quit before_jobs=$(lab 'lpstat -W all -o 2>/dev/null | wc -l') launch -- front:1 print front:2 print state quit judge "a restored worksheet tab and a restored picture tab both print" \ "(( \$(lab 'lpstat -W all -o | wc -l') >= before_jobs + 2 ))" ;; esac judge "Wonder Ink never crashed by itself" no_crash [[ "$failed" -eq "$before" ]] || \ { echo "$out" | grep -E 'STATE|START|QUIT|CRASH|PRINTED|exit=|Traceback|starting|restored|TEST' | tail -14 | sed 's/^/ /'; } done echo echo "$passed passed, $failed failed" [[ "$failed" -eq 0 ]]