Files
Classroom_Desktop/tools/lab/wonderink-print-check
T
ekklesiaandClaude Opus 5.5 940f63e0e5 fix: a picture prints as the picture, where and as large as the teacher asks
The black bars above and below a printed picture were neither in the
picture nor in the layout. A PNG or JPEG in a tab of its own is shown by
the engine on a viewer page whose background is rgb(14, 14, 14), and Qt
prints page backgrounds by default; 0.2.6.1 printed that page.

A picture is now taken as itself — drawn onto white, capped at 300 dpi
across Letter — and laid out on the printer's paper by the preview, with
Size (as large as fits down to 25%), Position (nine squares), Paper
(automatic, upright, sideways) and Fill the whole page, which says it
cuts the edges off and is never the default. The preview is the PDF that
prints, laid out again after each change. Worksheet PDFs and lesson pages
get no picture controls and are unchanged.

Page backgrounds are no longer printed for lesson pages either, as in
Chrome; Blink darkens light text when it does that, so a white-on-black
page prints readable grey on white. "Colour" is "Color" in the print
interface and the manual.

Lab: 135 print checks, now including eight picture layouts measured on
the job file CUPS kept — byte-identical to the preview, within 1 point of
where asked, no ink anywhere else. Printed from the real session with the
pointer: 50% top left, Elmer-66, the same. Not yet on paper.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-23 21:41:15 -05:00

240 lines
12 KiB
Bash
Executable File

#!/bin/bash
# Does Wonder Ink print — and does a job really reach CUPS?
#
# The lab's CUPS gets an "Elmer" built from Elmer's own PPD and options,
# writing to a file instead of socket://10.10.0.155:9100, so every job
# goes through the same filters the real printer's jobs do and none can
# reach a printer. A second queue refuses jobs, to show a teacher is told.
#
# Each scenario runs the INSTALLED classroom-wonderink under a real
# QtWebEngine (offscreen), presses the preview's buttons, and is then
# judged by CUPS's own record of what it was given.
#
# tools/lab/wonderink-print-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}"
lab() { docker exec -i "$NAME" bash -c "$1"; }
# Not /tmp: in the Plasma lab /tmp is a tmpfs, and docker cp writes into
# the layer underneath it, where nothing can see the file. Measured — the
# first run there "failed" every scenario for exactly that reason.
RIG=/opt/wonderink-check/wonderink-print-rig.py
lab 'mkdir -p /opt/wonderink-check'
docker cp "$ROOT/tools/lab/wonderink-print-rig.py" "$NAME:$RIG" >/dev/null
MEASURE=/opt/wonderink-check/wonderink-picture-measure.py
docker cp "$ROOT/tools/lab/wonderink-picture-measure.py" "$NAME:$MEASURE" >/dev/null
lab "chmod -R a+rX /opt/wonderink-check && test -r $RIG" || {
echo "could not put the rig in the lab" >&2
exit 1
}
# The church's printer network is fenced off inside the lab. Checked, not
# assumed: a lab job must never be able to reach the real Elmer.
lab 'ip route replace blackhole 10.10.0.0/16 2>/dev/null
! timeout 3 bash -c "exec 3<>/dev/tcp/10.10.0.155/9100" 2>/dev/null' || {
echo "REFUSING TO CONTINUE: the real Elmer is reachable from the lab" >&2
exit 1
}
lab '
set -e
export DEBIAN_FRONTEND=noninteractive
command -v lpadmin >/dev/null && command -v pdftoppm >/dev/null || \
apt-get install -y -qq cups cups-client poppler-utils >/dev/null 2>&1
dpkg -s foomatic-db-compressed-ppds >/dev/null 2>&1 || \
apt-get install -y -qq foomatic-db-compressed-ppds cups-filters ghostscript >/dev/null 2>&1
grep -q "^FileDevice Yes" /etc/cups/cups-files.conf || echo "FileDevice Yes" >> /etc/cups/cups-files.conf
pkill -x cupsd || true; sleep 1; /usr/sbin/cupsd; sleep 1
cupsctl --debug-logging
: > /var/log/cups/error_log
mkdir -p /var/lab-print && chown lp /var/lab-print
driver="foomatic-db-compressed-ppds:0/ppd/foomatic-ppd/Generic-PCL_6_PCL_XL_Printer-pxlcolor.ppd"
for q in $(lpstat -e 2>/dev/null); do lpadmin -x "$q"; done
lpadmin -p Elmer -E -v file:///var/lab-print/elmer.pxl -m "$driver" \
-D "LAB STAND-IN for Elmer: its PPD, a file for a printer" \
-o PrintoutMode=Normal -o ColorModel=Color 2>/dev/null
lpadmin -d Elmer
lpadmin -p Refusing -E -v file:///dev/null -m "$driver" -D "LAB: refuses jobs" 2>/dev/null
cupsreject Refusing
id teacher >/dev/null 2>&1 || useradd -m teacher
install -d -o teacher -m 700 /tmp/teacher-rt
# An inline PDF, served the way a site shows one rather than sends it. Its
# own port and directory: a lab often has another test server on 8088.
mkdir -p /opt/wonderink-check/www
# CUPS keeps what it was given, so a check can compare the submitted
# file with the one the preview showed, byte for byte.
cupsctl PreserveJobFiles=Yes
' || { echo "could not prepare the lab's CUPS" >&2; exit 1; }
lab "test -f /opt/wonderink-check/www/ark-clear.png || \
QT_QPA_PLATFORM=offscreen python3 $MEASURE make /opt/wonderink-check/www; \
chmod -R a+r /opt/wonderink-check/www; test -f /opt/wonderink-check/www/ark-clear.png" || {
echo "could not make the test pictures" >&2
exit 1
}
# Started detached. A background process started inside a docker exec
# dies with it in the Plasma lab, whose PID 1 is systemd.
lab 'pgrep -f "[h]ttp.server 8089" >/dev/null' || \
docker exec -d -w /opt/wonderink-check/www "$NAME" python3 -m http.server 8089
passed=0
failed=0
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
}
# Runs one scenario; leaves its output in $out and the jobs it caused in $jobs.
run() {
local before
before=$(lab 'lpstat -W all -o 2>/dev/null | wc -l')
out=$(lab "su teacher -c 'cd /tmp; XDG_RUNTIME_DIR=/tmp/teacher-rt QT_QPA_PLATFORM=offscreen \
QTWEBENGINE_CHROMIUM_FLAGS=\"--no-sandbox --disable-gpu\" \
timeout 120 python3 $RIG $COMMAND $1 2>&1'; echo \"exit=\$?\"")
sleep 3
jobs=$(lab "lpstat -W all -o | tail -n +$((before + 1)) | awk '{print \$1}' | tr '\n' ' '")
job_options() { lab "grep -E '\\[Job ${1#*-}\\] argv\\[(4|5)\\]' /var/log/cups/error_log"; }
job_device() { lab "grep -E '\\[Job ${1#*-}\\]' /var/log/cups/error_log | grep -oE 'sDEVICE=[a-z]+' | sort -u"; }
}
count() { wc -w <<< "$jobs"; }
# The file CUPS was handed for a job, kept by PreserveJobFiles.
spool_file() { local id="${1%% *}"; printf '/var/spool/cups/d%05d-001' "${id##*-}"; }
said() { grep -q -- "$1" <<< "$out"; }
exited_cleanly() { said 'exit=0' && ! said 'Traceback' && ! said 'Segmentation' && ! said 'Aborted'; }
# Where a picture goes, measured on the page CUPS was given:
# picture:FILE:SIZE:POSITION:PAPER:FILL[:gray]
PICTURES=(
picture:ark-wide.png:100:centre:auto:fit
picture:ark-wide.png:100:centre:portrait:fit
picture:ark-wide.png:50:top-left:auto:fit
picture:ark-tall.png:100:centre:auto:fit
picture:ark-tall.png:33:bottom-right:portrait:fit
picture:ark-wide.png:100:centre:portrait:fill
picture:ark-wide.jpg:75:right:auto:fit:gray
picture:ark-clear.png:50:top:auto:fit
)
scenarios=("$@")
[[ ${#scenarios[@]} -gt 0 ]] || scenarios=(web pdf pdf-options pdf-viewer-button inline
options bad-range broken cancel page-closes-itself close-window page-prints-again repeat
dark-page "${PICTURES[@]}")
# The inline scenario needs a PDF to serve; the worksheet scenario writes one.
lab 'test -f /opt/wonderink-check/www/ark.pdf' || { run pdf >/dev/null; lab 'cp "/home/teacher/Downloads/Wonder Ink/Colour the Ark.pdf" /opt/wonderink-check/www/ark.pdf; chmod a+r /opt/wonderink-check/www/ark.pdf'; }
for scenario in "${scenarios[@]}"; do
echo "$scenario"
run "$scenario"
judge "Wonder Ink did not crash, and exited by itself" exited_cleanly
# The preview has to SHOW the page, not merely count it. A preview
# that printed the right thing while showing a blank was what the
# first version of this check let through.
if said 'PREVIEW '; then
judge "the preview drew the page it will print" "! grep 'PREVIEW ' <<< \"\$out\" | grep -q 'shown=False'"
fi
case "$scenario" in
web)
judge "the preview showed the lesson, 3 Letter pages, Elmer chosen" \
"said \"pages=3 printers=\\['Elmer', 'Refusing'\\] paper=Letter\""
judge "one job reached CUPS" '[[ $(count) -eq 1 ]]'
judge "Elmer's filters printed it in colour" '[[ "$(job_device $jobs)" == *pxlcolor* ]]'
judge "and nothing was left behind" "said 'scratch_left=\\[\\]'" ;;
pdf|pdf-viewer-button)
judge "the preview showed the worksheet's own 2 pages" "said 'pages=2'"
judge "one job reached CUPS" '[[ $(count) -eq 1 ]]' ;;
pdf-options)
judge "one job" '[[ $(count) -eq 1 ]]'
judge "page 2 only, three copies" \
'[[ "$(job_options $jobs)" == *"argv[4]=\"3\""* && "$(job_options $jobs)" == *page-ranges=2* ]]'
judge "in black and white, by the driver's own option" \
'[[ "$(job_options $jobs)" == *ColorModel=Grayscale* && "$(job_device $jobs)" == *pxlmono* ]]' ;;
inline)
judge "the inline PDF was recognised" "said \"mime='application/pdf'\""
judge "and opened as a worksheet tab" "said 'current_is_pdf=True'"
judge "one job reached CUPS" '[[ $(count) -eq 1 ]]' ;;
options)
judge "pages 2-3, two copies, black and white" \
'[[ "$(job_options $jobs)" == *page-ranges=2-3* && "$(job_options $jobs)" == *"argv[4]=\"2\""* && "$(job_device $jobs)" == *pxlmono* ]]' ;;
bad-range)
judge "a page that does not exist is explained, not printed" "said 'This document has 3 pages'"
judge "and once corrected, one job" '[[ $(count) -eq 1 ]]' ;;
broken)
judge "a refusing printer is reported in CUPS's words" "said 'Refusing did not take the job'"
judge "and no job is claimed" '[[ $(count) -eq 0 ]] && ! said "NOTIFY Sent"' ;;
cancel)
judge "Cancel sends nothing" '[[ $(count) -eq 0 ]]' ;;
page-closes-itself)
judge "the preview says the page closed, and cannot print" \
"said \"status='The page was closed.' print_enabled=False\""
judge "and sends nothing" '[[ $(count) -eq 0 ]]' ;;
close-window)
judge "closing Wonder Ink with the preview open sends nothing" '[[ $(count) -eq 0 ]]' ;;
page-prints-again)
judge "a second print request does not open a second preview" "said 'still one preview=True'"
judge "one job" '[[ $(count) -eq 1 ]]' ;;
repeat)
judge "five prints in a row, five jobs" '[[ $(count) -eq 5 ]]' ;;
dark-page)
judge "one job" '[[ $(count) -eq 1 ]]'
spool=$(spool_file "$jobs")
answer=$(lab "python3 $MEASURE darkness $spool")
dark=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["dark_fraction"])' "$answer")
ink=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["ink_fraction"])' "$answer")
judge "a black page background does not print (dark: $dark of the page)" \
"python3 -c 'import sys; sys.exit(0 if $dark < 0.05 else 1)'"
judge "and its white text still does, darkened ($ink of the page)" \
"python3 -c 'import sys; sys.exit(0 if $ink > 0.005 else 1)'" ;;
picture:*)
IFS=: read -r _ file size position paper fill color <<< "$scenario"
judge "the picture controls appeared, and none for a page" "said 'PICTURE picture_box=True'"
judge "the print interface says Color" "said \"labels='Color','Color'\""
judge "one job reached CUPS" '[[ $(count) -eq 1 ]]'
spool=$(spool_file "$jobs")
judge "CUPS was given exactly the page the preview showed" \
"[[ -n '$spool' ]] && lab 'cmp -s /tmp/rig-out/preview.pdf $spool'"
dims=$(lab "python3 -c 'from PyQt6.QtGui import QImage; i=QImage(\"/opt/wonderink-check/www/$file\"); print(f\"{i.width()}x{i.height()}\")'" 2>/dev/null)
result=$(lab "python3 $MEASURE measure $spool $dims $size $position $paper $fill")
field() { python3 -c "import json,sys; print(json.loads(sys.argv[1])[sys.argv[2]])" "$result" "$1"; }
judge "the paper is turned the way asked ($paper): $(field page)" \
'[[ "$(field page_landscape)" == "$(field wanted_landscape)" ]]'
judge "the picture is where and as large as asked, within 3 points (off by $(field off_by_points))" \
'[[ "$(field off_by_points)" != None ]] && (( $(field off_by_points) <= 3 ))'
judge "no ink anywhere else on the page — no bars ($(field ink_outside_picture) pixels)" \
'[[ "$(field ink_outside_picture)" == 0 ]]'
if [[ "$fill" == fill ]]; then
# The mark is in the picture's left fifth, which filling a
# portrait page with a wide picture cuts off. That is what the
# control says it does; the fit scenarios prove it happens only
# when asked.
judge "Fill cut the edges off, as it says it will" '(( $(field pink_pixels) == 0 ))'
else
judge "the pink corner mark is on the paper — nothing cut off" '(( $(field pink_pixels) > 0 ))'
fi
if [[ "$color" == gray ]]; then
judge "and it went in black and white" '[[ "$(job_device $jobs)" == *pxlmono* ]]'
fi ;;
esac
[[ "$failed" -eq 0 ]] || { echo "$out" | tail -15 | sed 's/^/ /'; }
done
echo
echo "$passed passed, $failed failed"
[[ "$failed" -eq 0 ]]