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]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b5d9535467
commit
78a844968c
Executable
+234
@@ -0,0 +1,234 @@
|
||||
#!/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 ]]
|
||||
Reference in New Issue
Block a user