#!/bin/bash # Wonder Ink's printing: which printers, which options, what is sent. # # CUPS is not on the development host, so lpstat, lpoptions, lp and # pdfinfo are stand-ins that answer with the output the real ones gave in # the lab — Elmer's own PPD options included. Whether a job really goes # through Elmer's filters is tools/lab/wonderink-print-check's question. set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT mkdir -p "$WORK/bin" cat > "$WORK/bin/lpstat" <&2; exit 1; } case "\$1" in -e) cat "$WORK/printers" 2>/dev/null ;; -d) cat "$WORK/default" 2>/dev/null || echo "no system default destination" ;; esac EOF # lpoptions -p NAME -l, as the real one printed it for Elmer's PPD. cat > "$WORK/bin/lpoptions" </dev/null EOF cat > "$WORK/bin/lp" < "$WORK/lp-args" if [[ -f "$WORK/lp-refuses" ]]; then echo 'lp: Destination "Elmer" is not accepting jobs.' >&2 exit 1 fi echo "request id is Elmer-42 (1 file(s))" EOF cat > "$WORK/bin/pdfinfo" <<'EOF' #!/bin/bash [[ "$(head -c 5 "$1" 2>/dev/null)" == "%PDF-" ]] || { echo "Syntax Error" >&2; exit 1; } echo "Producer: test" echo "Pages: $(grep -c '^page' "$1")" EOF chmod +x "$WORK"/bin/* printf 'Elmer\n' > "$WORK/printers" echo "system default destination: Elmer" > "$WORK/default" cat > "$WORK/options-Elmer" <<'EOF' PrintoutMode/Print Quality: Draft Draft.Gray *Normal Normal.Gray High High.Gray PageSize/Page Size: Custom.WIDTHxHEIGHT *Letter A4 11x17 A3 A5 B5 Env10 EnvC5 EnvDL EnvISOB5 EnvMonarch Executive Legal Duplex/Double-Sided Printing: DuplexNoTumble DuplexTumble *None ColorModel/Color Mode: FromPrintoutMode *Color Grayscale PrinterResolution/Resolution: *FromPrintoutMode 300x300dpi 600x600dpi 1200x600dpi 1200x1200dpi EOF cat > "$WORK/options-Office" <<'EOF' PageSize/Media Size: Letter *A4 Legal EOF PATH="$WORK/bin:$PATH" WORK="$WORK" PYTHONPATH="$ROOT/lib" python3 - "$ROOT" <<'PY' import os import re import sys from pathlib import Path import classroom_printing as printing root = Path(sys.argv[1]) work = Path(os.environ["WORK"]) passed = failed = 0 def check(label, condition, detail=""): global passed, failed if condition: print(f" ok {label}") passed += 1 else: print(f" FAIL {label}") if detail: print(f" {detail}") failed += 1 def lp_args(): return (work / "lp-args").read_text().splitlines() print("1. which printers a teacher is offered") check("Elmer, and Elmer is the default", printing.printers() == (["Elmer"], "Elmer")) (work / "printers").write_text("Elmer\nOffice\n") (work / "default").write_text("system default destination: Office\n") check("the system default is preselected, whichever it is", printing.printers() == (["Elmer", "Office"], "Office")) (work / "default").write_text("no system default destination\n") check("with no default, the first printer", printing.printers() == (["Elmer", "Office"], "Elmer")) (work / "default").write_text("system default destination: Gone\n") check("a default that is not in the list is not offered", printing.printers() == (["Elmer", "Office"], "Elmer")) (work / "printers").write_text("") check("no printers at all is an empty list, not an error", printing.printers() == ([], None)) (work / "cups-down").touch() check("and so is CUPS not running", printing.printers() == ([], None)) (work / "cups-down").unlink() (work / "printers").write_text("Elmer\n") (work / "default").write_text("system default destination: Elmer\n") print() print("2. what each printer's driver offers") elmer = printing.options("Elmer") check("Elmer's paper is Letter", elmer["paper"] == "Letter", elmer) # Measured: this driver ignores print-color-mode and prints in colour. check("black and white is asked for in the driver's own terms", elmer["gray"] == ("ColorModel", "Grayscale"), elmer) office = printing.options("Office") check("an A4 printer is A4", office["paper"] == "A4", office) check("a printer with no grey choice offers none", office["gray"] is None, office) unknown = printing.options("Nobody") check("a printer that says nothing is Letter, colour only", unknown == {"paper": "Letter", "gray": None}, unknown) print() print("3. a teacher's page list") for text, expected in (("", None), ("all", None), (" ", None), ("2", "2"), ("1-3", "1-3"), ("1, 3, 5-6", "1,3,5-6"), ("3-3", "3"), ("1 - 2", "1-2"), ("6", "6")): try: got = printing.parse_pages(text, 6) except ValueError as error: got = f"refused: {error}" check(f"{text!r} is {expected!r}", got == expected, got) for text in ("7", "0", "3-1", "1-9", "one", "1;lp -d x", "-2", "1--2", "2,", "1.5"): try: printing.parse_pages(text, 6) check(f"{text!r} is refused", False) except ValueError as error: check(f"{text!r} is refused, and says why: {error}", bool(str(error))) print() print("4. what is handed to CUPS") pdf = work / "Colour the Ark.pdf" pdf.write_text("%PDF-1.4\npage\npage\n") check("a PDF with two pages has two", printing.page_count(pdf) == 2) check("something that is not a PDF has none", printing.page_count(work / "printers") == 0) ok, answer = printing.submit(pdf, "Elmer") args = lp_args() check("a job is accepted, with its id", (ok, answer) == (True, "Elmer-42"), answer) check("to Elmer, one copy, named for the lesson", args[:6] == ["-d", "Elmer", "-n", "1", "-t", "Wonder Ink"], args) check("the file comes after --, so no name can be read as an option", args[-2:] == ["--", str(pdf)], args) check("and nothing else is asked for", "-P" not in args and "-o" not in args, args) printing.submit(pdf, "Elmer", copies=3, pages="2-3", gray=("ColorModel", "Grayscale"), title="Noah") args = lp_args() check("copies, pages and black and white arrive as lp options", args[args.index("-n") + 1] == "3" and args[args.index("-P") + 1] == "2-3" and args[args.index("-o") + 1] == "ColorModel=Grayscale" and args[args.index("-t") + 1] == "Noah", args) printing.submit(pdf, "Elmer", copies=500) check(f"copies are capped at {printing.MAX_COPIES}", lp_args()[lp_args().index("-n") + 1] == str(printing.MAX_COPIES)) printing.submit(pdf, "Elmer", copies=0) check("and never fewer than one", lp_args()[lp_args().index("-n") + 1] == "1") (work / "lp-refuses").touch() ok, answer = printing.submit(pdf, "Elmer") check("a refused job is a failure, in CUPS's own words", not ok and answer == 'Destination "Elmer" is not accepting jobs.', answer) print() print("5. Wonder Ink prints through this, and only this") ink = (root / "scripts/classroom-wonderink").read_text() code = "\n".join(line for line in ink.splitlines() if not line.lstrip().startswith("#")) # The 0.2.2 crash, and the blank worksheet, both came from Qt's print path. check("no QPrinter", "QPrinter(" not in code and "QPrintDialog" not in code) check("no QWebEngineView.print()", not re.search(r"\bview\.print\(", code)) check("a lesson page becomes a PDF by printToPdf", "printToPdf(" in code) check("which is given up on if the engine never answers", "PREPARE_TIMEOUT_MS" in code and "self._timer.start(PREPARE_TIMEOUT_MS)" in code) check("the preview submits with classroom_printing.submit", "classroom_printing.submit(" in code) check("there is no file chooser in it", "QFileDialog" not in code) check("a worksheet tab is printed from its file", "self._pdf_views[view] = resolved" in code) check("an inline PDF is fetched as a file first", "view.page().download(view.url())" in code) check("an exception in a handler is logged, not fatal", "sys.excepthook = keep_going" in code) check("the module is packaged", "lib/classroom_printing.py" in (root / "tools/build-release.sh").read_text()) print() print(f"{passed} passed, {failed} failed") sys.exit(1 if failed else 0) PY