diff --git a/tests/test-classroom-desktop-wallpaper.sh b/tests/test-classroom-desktop-wallpaper.sh new file mode 100755 index 0000000..5dec0f3 --- /dev/null +++ b/tests/test-classroom-desktop-wallpaper.sh @@ -0,0 +1,408 @@ +#!/bin/bash +# Every desktop shows the classroom's wallpaper, whatever it is numbered. +# +# Tiny Pebbles came through the 0.2.2 upgrade with its desktop as +# containment 10 and no wallpaper configured on it, and showed Plasma's own +# purple one. These run the real repair against that file, against a +# machine that was already right, and against the shapes in between, and +# then check that every place naming the wallpaper names the same one. +set -uo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +python3 - "$ROOT" <<'PY' +import importlib.util +import json +import os +import re +import sys +import tempfile +from importlib.machinery import SourceFileLoader +from pathlib import Path + +root = Path(sys.argv[1]) +loader = SourceFileLoader("wallpaper", str(root / "scripts/classroom-desktop-wallpaper")) +spec = importlib.util.spec_from_loader("wallpaper", loader) +module = importlib.util.module_from_spec(spec) +loader.exec_module(module) + +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 + + +IMAGE = "Image=file:///usr/share/backgrounds/ekklesia/classroom-calm.png" + + +def group_texts(text): + """{group name: its lines}, for saying what did and did not change.""" + lines = text.splitlines(keepends=True) + return { + name: "".join(lines[header:end]).rstrip("\n") + for name, header, end in module.groups(lines) + } + + +def containment_ids(text): + return sorted(re.findall(r"^\[Containments\]\[(\d+)\]$", text, re.M)) + + +# A panel built with no screen present takes id 1 and its applets 2 to 9, +# which is how Tiny Pebbles' desktop came to be 10. Its containment is as +# it was read off the machine. +PANEL_1 = """[ActionPlugins][0] +MiddleButton;NoModifier=org.kde.paste +RightButton;NoModifier=org.kde.contextmenu + +[ActionPlugins][1] +RightButton;NoModifier=org.kde.contextmenu + +[Containments][1] +activityId= +formfactor=2 +immutability=1 +lastScreen=0 +location=4 +plugin=org.kde.panel +wallpaperplugin=org.kde.image + +[Containments][1][Applets][2] +immutability=1 +plugin=org.kde.plasma.quicklaunch + +[Containments][1][Applets][2][Configuration][General] +launcherUrls=file:///usr/share/applications/ekklesia-parent-call.desktop + +[Containments][1][Applets][3] +immutability=1 +plugin=org.kde.plasma.panelspacer + +[Containments][1][Applets][4] +immutability=1 +plugin=org.kde.plasma.icontasks + +[Containments][1][Applets][4][Configuration][General] +launchers=applications:ekklesia-wonderink.desktop,applications:google-chrome.desktop,applications:ekklesia-dvd.desktop +sortingStrategy=1 + +[Containments][1][Applets][5] +immutability=1 +plugin=org.kde.plasma.panelspacer + +[Containments][1][Applets][6] +immutability=1 +plugin=org.kde.plasma.networkmanagement + +[Containments][1][Applets][7] +immutability=1 +plugin=org.kde.plasma.volume + +[Containments][1][Applets][8] +immutability=1 +plugin=org.kde.plasma.notifications + +[Containments][1][Applets][9] +immutability=1 +plugin=org.kde.plasma.digitalclock + +[Containments][1][General] +AppletOrder=2;3;4;5;6;7;8;9 +""" + +TINY_PEBBLES = PANEL_1 + """ +[Containments][10] +activityId=62d3d55d-bd31-4fc8-9a7b-a5b9b2b99b60 +formfactor=0 +immutability=1 +lastScreen=0 +location=0 +plugin=org.kde.plasma.folder +wallpaperplugin=org.kde.image + +[Containments][10][General] +positions={"1920x1080":[]} + +[ScreenMapping] +itemsOnDisabledScreens= +""" + +# The healthy machine, as the layout builds it with a screen present and +# as the lab's own session wrote it: desktop 1, panel 2, applets 3 to 10. +# The clock is applet 10 — the number that is Tiny Pebbles' desktop. +RIVER_KIDS = """[ActionPlugins][0] +MiddleButton;NoModifier=org.kde.paste +RightButton;NoModifier=org.kde.contextmenu + +[Containments][1] +ItemGeometries-1920x1080= +ItemGeometriesHorizontal= +activityId=a9db02f6-926f-4650-b05f-de3a9f0b73fe +formfactor=0 +immutability=1 +lastScreen=0 +location=0 +plugin=org.kde.plasma.folder +wallpaperplugin=org.kde.image + +[Containments][1][Wallpaper][org.kde.image][General] +FillMode=2 +Image=file:///usr/share/backgrounds/ekklesia/classroom-calm.png +SlidePaths= + +[Containments][2] +activityId= +formfactor=2 +immutability=1 +lastScreen=0 +location=4 +plugin=org.kde.panel +wallpaperplugin=org.kde.image + +[Containments][2][Applets][10] +immutability=1 +plugin=org.kde.plasma.digitalclock + +[Containments][2][Applets][10][Configuration][Appearance] +showDate=true + +[Containments][2][Applets][3] +immutability=1 +plugin=org.kde.plasma.quicklaunch + +[Containments][2][Applets][5] +immutability=1 +plugin=org.kde.plasma.icontasks + +[Containments][2][General] +AppletOrder=3;4;5;6;7;8;9;10 + +[ScreenMapping] +itemsOnDisabledScreens= +""" + +# What the lab produced when Plasma made a desktop mid-session: the good +# one kept its picture, and the new one, 11, had none. +LATE_DESKTOP = RIVER_KIDS.replace("[ScreenMapping]", """[Containments][11] +activityId=f992593c-2347-4e8d-be82-f80d02e71158 +formfactor=0 +immutability=1 +lastScreen=0 +location=0 +plugin=org.kde.plasma.folder +wallpaperplugin=org.kde.image + +[Containments][11][General] +positions={"1920x1080":[]} + +[ScreenMapping]""") + + +print("1. Tiny Pebbles: a desktop numbered 10, with no wallpaper at all") +check("the desktop is found by what it is, not its number", + module.desktops(TINY_PEBBLES.splitlines(True)) == ["10"], + str(module.desktops(TINY_PEBBLES.splitlines(True)))) +after, changes = module.converge(TINY_PEBBLES) +groups_before, groups_after = group_texts(TINY_PEBBLES), group_texts(after) +wallpaper = groups_after.get("[Containments][10][Wallpaper][org.kde.image][General]", "") +check("it gets the classroom's image", IMAGE in wallpaper, wallpaper) +check("scaled and cropped, as the layout writes it", "FillMode=2" in wallpaper) +check("and it says what it did", changes == ["desktop 10: had no wallpaper configured"], + str(changes)) +check("the wallpaper plugin is still the image one", + "wallpaperplugin=org.kde.image" in groups_after["[Containments][10]"]) +check("one group added and nothing else changed", + set(groups_after) - set(groups_before) + == {"[Containments][10][Wallpaper][org.kde.image][General]"} + and all(groups_after[name] == text for name, text in groups_before.items())) +check("every containment keeps its number", containment_ids(after) == ["1", "10"]) +check("the panel is still containment 1, applets 2 to 9, in order", + groups_after["[Containments][1][General]"].endswith("AppletOrder=2;3;4;5;6;7;8;9") + and groups_after["[Containments][1]"] == groups_before["[Containments][1]"]) +check("the new group sits with its own containment", + after.index("[Containments][10][General]") + < after.index("[Containments][10][Wallpaper]") + < after.index("[ScreenMapping]")) +again, changes_again = module.converge(after) +check("a second run changes nothing", again == after and changes_again == [], str(changes_again)) + + +print("2. River Kids: already right") +after, changes = module.converge(RIVER_KIDS) +check("the file comes back byte for byte", after == RIVER_KIDS) +check("and nothing is reported as changed", changes == [], str(changes)) +check("applet 10 on the panel is not mistaken for a desktop", + module.desktops(RIVER_KIDS.splitlines(True)) == ["1"]) + + +print("3. two desktops, one bare, as the lab made them") +after, changes = module.converge(LATE_DESKTOP) +groups_before, groups_after = group_texts(LATE_DESKTOP), group_texts(after) +check("only the bare one is written", + changes == ["desktop 11: had no wallpaper configured"], str(changes)) +check("the good one is untouched", + groups_after["[Containments][1][Wallpaper][org.kde.image][General]"] + == groups_before["[Containments][1][Wallpaper][org.kde.image][General]"]) +check("and the panel, numbered 2, is untouched", + all(groups_after[name] == text for name, text in groups_before.items() + if name and name.startswith("[Containments][2]"))) +check("the new one has the image", + IMAGE in groups_after.get("[Containments][11][Wallpaper][org.kde.image][General]", "")) + + +print("4. a desktop some other profile left with another picture") +OTHER = """[Containments][4] +formfactor=0 +location=0 +plugin=org.kde.desktopcontainment +wallpaperplugin=org.kde.color + +[Containments][4][Wallpaper][org.kde.image][General] +Image[$e]=file:///usr/share/wallpapers/Next/contents/images/1920x1080.png +SlidePaths=/usr/share/wallpapers/ + +[Containments][7] +formfactor=2 +location=4 +plugin=org.kde.plasma.private.systemtray + +[Containments][9] +formfactor=2 +location=4 +plugin=org.kde.panel +""" +after, changes = module.converge(OTHER) +groups_after = group_texts(after) +check("the plain Desktop containment counts as a desktop", + module.desktops(OTHER.splitlines(True)) == ["4"]) +check("its plugin becomes the image one", + "wallpaperplugin=org.kde.image" in groups_after["[Containments][4]"]) +wallpaper = groups_after["[Containments][4][Wallpaper][org.kde.image][General]"] +check("its image becomes the classroom's, keeping the key's flags", + "Image[$e]=file:///usr/share/backgrounds/ekklesia/classroom-calm.png" in wallpaper, + wallpaper) +check("a missing key is added to the existing group", "FillMode=2" in wallpaper) +check("a key the classroom does not set is left alone", + "SlidePaths=/usr/share/wallpapers/" in wallpaper) +check("a system tray is not a desktop", + groups_after["[Containments][7]"] == group_texts(OTHER)["[Containments][7]"]) +check("nor is a panel", + groups_after["[Containments][9]"] == group_texts(OTHER)["[Containments][9]"]) +check("and it settles", module.converge(after) == (after, [])) + + +print("5. the command, on files") +work = Path(tempfile.mkdtemp()) + + +def run(*args): + return module.main([str(arg) for arg in args]) + + +target = work / "tiny-pebbles" +target.write_text(TINY_PEBBLES) +os.chmod(target, 0o600) +check("--check says it is wrong", run("--check", target) == 1) +check("and changes nothing", target.read_text() == TINY_PEBBLES) +check("the repair succeeds", run(target) == 0) +check("the file is repaired", IMAGE in target.read_text()) +check("the file keeps its permissions", (target.stat().st_mode & 0o777) == 0o600) +check("no temporary file is left behind", sorted(p.name for p in work.iterdir()) == ["tiny-pebbles"]) +stamp = (target.stat().st_ino, target.stat().st_mtime_ns) +check("a second run succeeds", run(target) == 0) +check("and does not write the file at all", + (target.stat().st_ino, target.stat().st_mtime_ns) == stamp) +check("--check then says it is right", run("--check", target) == 0) + +healthy = work / "river-kids" +healthy.write_text(RIVER_KIDS) +stamp = (healthy.stat().st_ino, healthy.stat().st_mtime_ns) +check("a machine that is right is left alone", run(healthy) == 0 + and (healthy.stat().st_ino, healthy.stat().st_mtime_ns) == stamp) + +check("no file at all is not an error", run(work / "absent") == 0) +check("and none is created", not (work / "absent").exists()) + +empty = work / "no-desktop" +empty.write_text(PANEL_1) +check("a file with only a panel is not an error", run(empty) == 0) +check("and is not changed", empty.read_text() == PANEL_1) + + +print("6. never underneath a running shell") +config_home = work / "config" +config_home.mkdir() +live = config_home / module.APPLETSRC +live.write_text(TINY_PEBBLES) +os.environ["XDG_CONFIG_HOME"] = str(config_home) +check("the default file is the one in XDG_CONFIG_HOME", module.default_path() == str(live)) + +proc = work / "proc" +for pid, name in (("1", "systemd"), ("812", "kwin_wayland"), ("self", "python3")): + (proc / pid).mkdir(parents=True) + (proc / pid / "comm").write_text(name + "\n") +check("no plasmashell in the process table is no shell", + module.shell_running(str(proc)) is False) +(proc / "1044").mkdir() +(proc / "1044" / "comm").write_text("plasmashell\n") +check("this user's plasmashell is found", module.shell_running(str(proc)) is True) +check("and the real process table can be read", + module.shell_running() in (True, False)) + +module.shell_running = lambda: True +check("with plasmashell running it refuses", run() == 1) +check("and the shell's file is untouched", live.read_text() == TINY_PEBBLES) +check("--check still answers while it runs", run("--check") == 1) + +module.shell_running = lambda: False +check("with the shell stopped it repairs", run() == 0 and IMAGE in live.read_text()) + + +print("7. one wallpaper, named the same everywhere") +layout = (root / "config/plasma/lookandfeel/contents/layouts/org.kde.plasma.desktop-layout.js").read_text() +defaults = (root / "config/plasma/lookandfeel/contents/defaults").read_text() +build = (root / "tools/build-release.sh").read_text() +dropin = (root / "systemd/user/plasma-plasmashell.service.d/ekklesia-classroom.conf").read_text() +package = root / "config/wallpapers/EkklesiaClassroom" +metadata = json.loads((package / "metadata.json").read_text()) + +check("the layout and the repair name the same file", + f'var WALLPAPER = "{module.WALLPAPER}";' in layout) +check("and the same fill mode", 'desktop.writeConfig("FillMode", 2);' in layout + and ("FillMode", "2") in module.WALLPAPER_KEYS) +check("the packaged file is where they say", f'"$STAGE{module.WALLPAPER}"' in build) + +default = re.search(r"^\[Wallpaper\]\nImage=(.*)$", defaults, re.M) +check("the look and feel's default is a package name, not a path", + default is not None and "/" not in default.group(1), + default.group(1) if default else "no [Wallpaper] Image") +check("the package it names is the classroom's", + default is not None and default.group(1) == metadata["KPlugin"]["Id"] == package.name) +check("the build installs that package's metadata", + f'"$ROOT/config/wallpapers/{package.name}/metadata.json"' in build + and f'WALLPAPER_PACKAGE="$STAGE/usr/share/wallpapers/{package.name}"' in build) +check("its image is a link to the one file, not a copy", + f"ln -s {module.WALLPAPER}" in build) + +check("the repair is packaged", + '"$STAGE/usr/local/bin/classroom-desktop-wallpaper"' in build) +check("it runs before every shell start", + "ExecStartPre=-/usr/local/bin/classroom-desktop-wallpaper" in dropin) +check("and a failure in it cannot stop the shell", + not re.search(r"^ExecStartPre=/", dropin, re.M)) +check("the layout still writes the wallpaper for a fresh desktop", + 'desktop.writeConfig("Image", "file://" + WALLPAPER);' in layout) + +print() +print(f"{passed} passed, {failed} failed") +sys.exit(1 if failed else 0) +PY diff --git a/tools/lab/converged-check b/tools/lab/converged-check index 7f4892f..e731f5d 100755 --- a/tools/lab/converged-check +++ b/tools/lab/converged-check @@ -182,6 +182,12 @@ check "no discover or system settings" 0 \ "$(in_lab "grep -cE 'discover.desktop|systemsettings.desktop' $APPLETS")" check "the packaged wallpaper" 1 \ "$(in_lab "grep -c 'Image=file:///usr/share/backgrounds/ekklesia/classroom-calm.png' $APPLETS")" +# And the one a desktop gets when nothing wrote it, which is what Tiny +# Pebbles showed: a package name, and the package there to be found. +check "a desktop nobody configured defaults to it" EkklesiaClassroom \ + "$(in_lab 'sed -n "/^\[Wallpaper\]/,/^\[/s/^Image=//p" /usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/defaults')" +check "and that package is the same picture" /usr/share/backgrounds/ekklesia/classroom-calm.png \ + "$(in_lab 'readlink -f /usr/share/wallpapers/EkklesiaClassroom/contents/images/*.png')" section "it will not strand a teacher" check "no automatic lock" false "$(session_config --file kscreenlockerrc --group Daemon --key Autolock)" diff --git a/tools/lab/wallpaper-check b/tools/lab/wallpaper-check new file mode 100755 index 0000000..20b8116 --- /dev/null +++ b/tools/lab/wallpaper-check @@ -0,0 +1,238 @@ +#!/bin/bash +# Does every desktop show the classroom's wallpaper — including one Plasma +# makes by itself after the layout has run? +# +# Tiny Pebbles came through the 0.2.2 upgrade with its desktop numbered 10 +# and nothing configured on it, and showed Plasma's own purple wallpaper. +# The layout only reaches the desktops that exist while it runs, and a +# room whose updater reboots it at three in the morning with the +# television off has none then. The lab cannot switch a television on, so +# it makes Plasma create a desktop the other way it does, through a new +# activity: the same code in the shell, and measured to give the same +# bare containment. +# +# tools/lab/wallpaper-check against the running lab session +# +# Each answer is read off the screen, not out of a file: a configuration +# can say anything, and the question is what the room sees. The sky at the +# top of the classroom's picture is pale blue; Plasma's is purple. +set -uo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +LAB="$ROOT/tools/lab/lab" +NAME="${LAB_NAME:-classroom-v2-lab}" +APPLETS=/home/classroom/.config/plasma-org.kde.plasma.desktop-appletsrc + +PASS=0 +FAIL=0 + +check() { + if [[ "$2" == "$3" ]]; then + printf ' \033[32mPASS\033[0m %-58s %s\n' "$1" "$3" + PASS=$((PASS + 1)) + else + printf ' \033[31mFAIL\033[0m %-58s got [%s] want [%s]\n' "$1" "$3" "$2" + FAIL=$((FAIL + 1)) + fi +} + +section() { printf '\n%s\n' "$1"; } +in_lab() { docker exec "$NAME" bash -c "$1" 2>/dev/null; } +# The same, with this script's input handed through — for a program on a +# here-document. Without -i docker gives it nothing to read, and it runs +# on nothing and says nothing. +in_lab_fed() { docker exec -i "$NAME" bash -c "$1"; } +user() { "$LAB" run "$@" 2>/dev/null; } +activities() { + user busctl --user call org.kde.ActivityManager /ActivityManager/Activities \ + org.kde.ActivityManager.Activities "$@" +} + +# What is on the screen at the top middle, where the classroom's picture +# is sky and Plasma's is purple: "classroom", "plasma", or the colour. +screen() { + "$LAB" outer grim -t ppm - 2>/dev/null | python3 -c ' +import sys +data = sys.stdin.buffer.read() +fields, at = [], 0 +while len(fields) < 4: + while data[at:at + 1].isspace(): + at += 1 + end = at + while not data[end:end + 1].isspace(): + end += 1 + fields.append(data[at:end]) + at = end +width, height = int(fields[1]), int(fields[2]) +pixels = data[at + 1:] +total = [0, 0, 0] +count = 0 +for y in range(20, 140, 4): + for x in range(width * 2 // 5, width * 3 // 5, 8): + offset = (y * width + x) * 3 + for channel in range(3): + total[channel] += pixels[offset + channel] + count += 1 +r, g, b = (value / count for value in total) +if b > r + 25 and g > r + 10: + print("classroom") +elif r > g + 25 and b > g + 25: + print("plasma") +else: + print(f"rgb({r:.0f},{g:.0f},{b:.0f})") +' +} + +# The shell writes its file a few seconds after a change, not at once. +settle() { sleep "${1:-35}"; } + +# The shell's file, read as groups rather than grepped, so nothing here +# can pick a line out of the wrong containment. +# +# applets panels each panel's number, its applets' numbers and plugins +# and their order. Not the panel's text: the shell adds +# keys of its own to applets as soon as it runs (popup +# sizes, the volume's migration), and that is Plasma, +# not the repair. tests/ checks the repair's own +# output byte for byte. +# applets desktops the desktop containments as "id:image" or "id:none", +# found by plugin and never by number +applets() { + in_lab_fed "python3 - $1 $APPLETS" <<'PY' +import re, sys +mode, path = sys.argv[1], sys.argv[2] +groups, name = {}, None +for line in open(path).read().splitlines(): + if line.startswith("[") and line.endswith("]"): + name = line + groups[name] = [] + elif name and line: + groups[name].append(line) +def top(cid): + return groups.get(f"[Containments][{cid}]", []) +ids = [m.group(1) for g in groups for m in [re.match(r"^\[Containments\]\[(\d+)\]$", g)] if m] +if mode == "panels": + for c in ids: + if "plugin=org.kde.panel" not in top(c): + continue + applets = sorted( + (int(m.group(1)), line[len("plugin=org.kde.plasma."):]) + for g in groups + for m in [re.match(r"^\[Containments\]\[%s\]\[Applets\]\[(\d+)\]$" % c, g)] if m + for line in groups[g] if line.startswith("plugin=") + ) + order = [l[len("AppletOrder="):] for l in groups.get(f"[Containments][{c}][General]", []) + if l.startswith("AppletOrder=")] + print(f"panel {c}:", " ".join(f"{n}={name}" for n, name in applets), "order", *order) +else: + image = "Image=file:///usr/share/backgrounds/ekklesia/classroom-calm.png" + out = [] + for c in ids: + if {"plugin=org.kde.plasma.folder", "plugin=org.kde.desktopcontainment"} & set(top(c)): + wallpaper = groups.get(f"[Containments][{c}][Wallpaper][org.kde.image][General]", []) + out.append(f"{c}:{'image' if image in wallpaper else 'none'}") + print(" ".join(out)) +PY +} + +panel_groups() { applets panels; } +desktops() { applets desktops; } + +last_repair() { + user journalctl --user -u plasma-plasmashell.service --no-pager -o cat \ + | grep '^classroom-desktop-wallpaper:' | tail -1 +} + +restart_shell() { + user systemctl --user restart plasma-plasmashell.service + for _ in $(seq 1 30); do + in_lab 'pgrep -x plasmashell >/dev/null' && break + sleep 1 + done + sleep 10 +} + +section "the package" +check "the look and feel's default names a package" EkklesiaClassroom \ + "$(in_lab 'sed -n "/^\[Wallpaper\]/,/^\[/s/^Image=//p" /usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/defaults')" +check "which is installed, and is the classroom's picture" /usr/share/backgrounds/ekklesia/classroom-calm.png \ + "$(in_lab 'readlink -f /usr/share/wallpapers/EkklesiaClassroom/contents/images/*.png')" +check "the repair runs before every shell start" 1 \ + "$(in_lab 'grep -c "^ExecStartPre=-/usr/local/bin/classroom-desktop-wallpaper" /etc/systemd/user/plasma-plasmashell.service.d/ekklesia-classroom.conf')" + +section "the session as it came up" +check "the room shows the classroom's picture" classroom "$(screen)" +original=$(activities CurrentActivity | cut -d'"' -f2) + +section "a desktop Plasma makes by itself (Tiny Pebbles' television)" +panels_before=$(panel_groups) +late=$(activities AddActivity s "Late screen" | cut -d'"' -f2) +activities SetCurrentActivity s "$late" >/dev/null +settle +now=$(desktops) +check "Plasma made a second desktop" 2 "$(wc -w <<< "$now")" +check "and wrote no wallpaper into it, as on Tiny Pebbles" 1 "$(grep -o ':none' <<< "$now" | wc -l)" +check "the room still shows the classroom's picture" classroom "$(screen)" + +section "the shell restarted, without a login" +restart_shell +check "the repair wrote it in" "classroom-desktop-wallpaper: the wallpaper is now configured on desktop $(tr ' ' '\n' <<< "$now" | cut -d: -f1 | paste -sd, | sed 's/,/, /g')" \ + "$(last_repair)" +check "every desktop now names the picture" 0 "$(desktops | grep -o ':none' | wc -l)" +check "the panel keeps its numbers, applets and order" "$panels_before" "$(panel_groups)" +check "the room shows the classroom's picture" classroom "$(screen)" +restart_shell +check "a second restart finds nothing to do" \ + "classroom-desktop-wallpaper: desktop $(desktops | tr ' ' '\n' | cut -d: -f1 | paste -sd, | sed 's/,/, /g'): the classroom wallpaper already" \ + "$(last_repair)" +check "and still does" "$panels_before" "$(panel_groups)" + +activities SetCurrentActivity s "$original" >/dev/null +activities RemoveActivity s "$late" >/dev/null +sleep 3 + +section "Tiny Pebbles' own file: desktop 10, panel 1, no wallpaper" +user systemctl --user stop plasma-plasmashell.service +in_lab_fed "python3 - '$original' $APPLETS" <<'PY' +import sys +activity, path = sys.argv[1], sys.argv[2] +applets = ["quicklaunch", "panelspacer", "icontasks", "panelspacer", + "networkmanagement", "volume", "notifications", "digitalclock"] +out = ["[Containments][1]", "activityId=", "formfactor=2", "immutability=1", + "lastScreen=0", "location=4", "plugin=org.kde.panel", + "wallpaperplugin=org.kde.image", ""] +for number, applet in enumerate(applets, start=2): + out += [f"[Containments][1][Applets][{number}]", "immutability=1", + f"plugin=org.kde.plasma.{applet}", ""] +out += ["[Containments][1][Applets][4][Configuration][General]", + "launchers=applications:ekklesia-wonderink.desktop,applications:google-chrome.desktop,applications:ekklesia-dvd.desktop", + "sortingStrategy=1", "", + "[Containments][1][General]", "AppletOrder=2;3;4;5;6;7;8;9", "", + "[Containments][10]", f"activityId={activity}", "formfactor=0", + "immutability=1", "lastScreen=0", "location=0", + "plugin=org.kde.plasma.folder", "wallpaperplugin=org.kde.image", "", + "[Containments][10][General]", 'positions={"1920x1080":[]}', ""] +open(path, "w").write("\n".join(out)) +PY +in_lab "chown classroom:classroom $APPLETS" +panels_before=$(panel_groups) +user systemctl --user start plasma-plasmashell.service +sleep 12 +check "the repair found desktop 10 and wrote it" \ + "classroom-desktop-wallpaper: the wallpaper is now configured on desktop 10" "$(last_repair)" +check "desktop 10 names the picture" "10:image" "$(desktops)" +check "the panel is still containment 1, applets 2 to 9, in order" "$panels_before" "$(panel_groups)" +check "the room shows the classroom's picture" classroom "$(screen)" + +section "and at the next login" +"$LAB" plasma-down >/dev/null +"$LAB" plasma-up >/dev/null +settle 10 +check "the reset left the repair nothing to do" \ + "classroom-desktop-wallpaper: no desktop configuration yet; the layout writes the wallpaper" \ + "$(last_repair)" +check "the layout rebuilt one desktop with the picture" 1 "$(desktops | grep -o ':image' | wc -l)" +check "the room shows the classroom's picture" classroom "$(screen)" + +printf '\n%s passed, %s failed\n' "$PASS" "$FAIL" +[[ "$FAIL" -eq 0 ]] diff --git a/tools/validate-trixie-release.sh b/tools/validate-trixie-release.sh index b1e1d7c..91397ff 100755 --- a/tools/validate-trixie-release.sh +++ b/tools/validate-trixie-release.sh @@ -499,6 +499,71 @@ if [[ -n "$previous" ]]; then grep -q 'var PRESENTATIONS = false;' \ /usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/layouts/org.kde.plasma.desktop-layout.js + # TINY PEBBLES, 0.2.2 -> 0.2.6.4: THE DESKTOP WITH NO WALLPAPER. + # + # The room's desktop was containment 10, made by Plasma after the + # layout had run, with nothing configured on it; it showed Plasma's + # own wallpaper. What a desktop gets when nothing wrote it is the look + # and feel's default, and that has to be a package Plasma can find. + lnf=/usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/defaults + [[ "$(sed -n '/^\[Wallpaper\]/,/^\[/s/^Image=//p' "$lnf")" == EkklesiaClassroom ]] + grep -q '"Id": "EkklesiaClassroom"' /usr/share/wallpapers/EkklesiaClassroom/metadata.json + [[ "$(readlink -f /usr/share/wallpapers/EkklesiaClassroom/contents/images/*.png)" \ + == /usr/share/backgrounds/ekklesia/classroom-calm.png ]] + grep -qx 'ExecStartPre=-/usr/local/bin/classroom-desktop-wallpaper' \ + /etc/systemd/user/plasma-plasmashell.service.d/ekklesia-classroom.conf + + # And the repair the package installed, on that room's own file: the + # panel built with no screen took 1 and its applets 2 to 9. + wallpaper_home=$(mktemp -d) + cat > "$wallpaper_home/plasma-org.kde.plasma.desktop-appletsrc" <<'APPLETS' +[Containments][1] +formfactor=2 +location=4 +plugin=org.kde.panel +wallpaperplugin=org.kde.image + +[Containments][1][Applets][2] +plugin=org.kde.plasma.quicklaunch + +[Containments][1][Applets][9] +plugin=org.kde.plasma.digitalclock + +[Containments][1][General] +AppletOrder=2;3;4;5;6;7;8;9 + +[Containments][10] +activityId=62d3d55d-bd31-4fc8-9a7b-a5b9b2b99b60 +formfactor=0 +immutability=1 +lastScreen=0 +location=0 +plugin=org.kde.plasma.folder +wallpaperplugin=org.kde.image + +[Containments][10][General] +positions={"1920x1080":[]} +APPLETS + applets="$wallpaper_home/plasma-org.kde.plasma.desktop-appletsrc" + panel_before=$(sed -n '/^\[Containments\]\[1\]/,/^$/p' "$applets") + + ! XDG_CONFIG_HOME="$wallpaper_home" classroom-desktop-wallpaper --check >/dev/null + XDG_CONFIG_HOME="$wallpaper_home" classroom-desktop-wallpaper \ + | grep -q 'configured on desktop 10' + sed -n '/^\[Containments\]\[10\]\[Wallpaper\]\[org.kde.image\]\[General\]$/,/^$/p' "$applets" \ + | grep -qx 'Image=file:///usr/share/backgrounds/ekklesia/classroom-calm.png' + [[ "$(sed -n '/^\[Containments\]\[1\]/,/^$/p' "$applets")" == "$panel_before" ]] + [[ "$(grep -c '^\[Containments\]\[[0-9]*\]$' "$applets")" -eq 2 ]] + + # Twice changes nothing, and a room that was already right is not + # written at all. + repaired=$(sha256sum < "$applets") + XDG_CONFIG_HOME="$wallpaper_home" classroom-desktop-wallpaper \ + | grep -q 'the classroom wallpaper already' + [[ "$(sha256sum < "$applets")" == "$repaired" ]] + XDG_CONFIG_HOME="$wallpaper_home" classroom-desktop-wallpaper --check >/dev/null + rm -rf "$wallpaper_home" + # And a room that was carried through the experiment on its own # channel comes home, because that channel is the obsolete one now. sed -i 's/^CHANNEL=.*/CHANNEL=classroom-v2/' /etc/ekklesia-classroom/device.conf