Files
Classroom_Desktop/tools/lab/wonderink-workspace-check
T
ekklesiaandClaude Opus 5.5 78a844968c test: the lab restores real tabs across real launches, and follows real links
tools/lab/wonderink-workspace-check launches the installed command again
and again against a stand-in for WonderInk's start page whose sign-in is
Basic auth, as WonderInk's is: restored after 5 and 90 minutes; fresh
after two hours and a minute, a day and a week; reopened untouched until
it lapses; a click after a restore counts and the restore does not; a
busy page and an untouched print preview do not; Print does; closed,
reordered, worksheet and missing-worksheet tabs; killed outright; a
restore that crashes twice; corrupt, future-format and half-written
files; the file holding no credential; restored tabs printing. 59 checks.

The print check follows every kind of link on a signed-in page with one
click — new tab in front for links and window.open, nothing for buttons
and anchors, same-tab for no target, Back, closing — and prints a PDF and
a picture reached by a link, and a page after several tabs. 0.2.6.3's
scenarios are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 21:12:17 -05:00

235 lines
12 KiB
Bash
Executable File

#!/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 ]]