Linux: packages tested on Omarchy, Ubuntu and Fedora; ElyMark stays idle (L1.6)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-11 13:29:59 -04:00
1 parent 15106b641f
commit a9f8136e46
11 files changed
+413 -1

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
# Sourced by Linux window tests: inputs, cases and the judge.
# Case: slug, the words it shows, its input.
cases=("ely-doc|Kernel prose|prose.docx" "ely-table|Kernel cell|cells.xlsx" "ely-show|Click to add Title|" "ely-pdf|Packaged PDF|page.pdf"
"ely-tree|hello.txt|repo" "ely-mark|Packaged|note.md")
# Omarchy on Catppuccin; a repo, a note, a PDF.
linux_inputs() { # root, work
local fixtures=$1/crates/ely-ui/tests/fixtures/omarchy current=$2/omarchy/current
mkdir -p "$current/theme"
cp "$fixtures/catppuccin.toml" "$current/theme/colors.toml"
echo catppuccin >"$current/theme.name"
git init -q "$2/repo"
echo hi >"$2/repo/hello.txt"
printf '# Packaged on Linux\n\nEly reads this.\n' >"$2/note.md"
python3 - "$2/page.pdf" <<'PY'
import sys
text = b"BT /F1 36 Tf 72 700 Td (Packaged PDF) Tj ET"
objects = [
b"<< /Type /Catalog /Pages 2 0 R >>",
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R"
b" /Resources << /Font << /F1 5 0 R >> >> >>",
b"<< /Length %d >>\nstream\n%s\nendstream" % (len(text), text),
b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
]
out = bytearray(b"%PDF-1.4\n")
offsets = []
for number, body in enumerate(objects, 1):
offsets.append(len(out))
out += b"%d 0 obj\n%s\nendobj\n" % (number, body)
xref = len(out)
out += b"xref\n0 %d\n0000000000 65535 f \n" % (len(objects) + 1)
out += b"".join(b"%010d 00000 n \n" % offset for offset in offsets)
out += b"trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n" % (len(objects) + 1, xref)
open(sys.argv[1], "wb").write(out)
PY
python3 - "$2" <<'PY'
import sys, zipfile
work = sys.argv[1]
types = '<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/>%s</Types>'
rels = '<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">%s</Relationships>'
rel = '<Relationship Id="%s" Type="http://schemas.openxmlformats.org/%s" Target="%s"/>'
main = "http://schemas.openxmlformats.org/%s"
with zipfile.ZipFile(work + "/prose.docx", "w") as doc:
doc.writestr("[Content_Types].xml", types % '<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>')
doc.writestr("_rels/.rels", rels % rel % ("r1", "officeDocument/2006/relationships/officeDocument", "word/document.xml"))
doc.writestr("word/document.xml", '<?xml version="1.0" encoding="UTF-8"?><w:document xmlns:w="%s"><w:body><w:p><w:r><w:rPr><w:sz w:val="96"/></w:rPr><w:t>Kernel prose</w:t></w:r></w:p></w:body></w:document>' % (main % "wordprocessingml/2006/main"))
sheet = main % "spreadsheetml/2006/main"
with zipfile.ZipFile(work + "/cells.xlsx", "w") as book:
book.writestr("[Content_Types].xml", types % ('<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'
'<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
'<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>'))
book.writestr("_rels/.rels", rels % rel % ("r1", "officeDocument/2006/relationships/officeDocument", "xl/workbook.xml"))
book.writestr("xl/_rels/workbook.xml.rels", rels % (rel % ("r1", "officeDocument/2006/relationships/worksheet", "worksheets/sheet1.xml") + rel % ("r2", "officeDocument/2006/relationships/styles", "styles.xml")))
book.writestr("xl/workbook.xml", '<?xml version="1.0" encoding="UTF-8"?><workbook xmlns="%s" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="Sheet1" sheetId="1" r:id="r1"/></sheets></workbook>' % sheet)
book.writestr("xl/styles.xml", '<?xml version="1.0" encoding="UTF-8"?><styleSheet xmlns="%s"><fonts count="2"><font><sz val="11"/><name val="Liberation Sans"/></font><font><sz val="36"/><name val="Liberation Sans"/></font></fonts><fills count="1"><fill><patternFill patternType="none"/></fill></fills><borders count="1"><border/></borders><cellStyleXfs count="1"><xf/></cellStyleXfs><cellXfs count="2"><xf fontId="0"/><xf fontId="1" applyFont="1"/></cellXfs></styleSheet>' % sheet)
book.writestr("xl/worksheets/sheet1.xml", '<?xml version="1.0" encoding="UTF-8"?><worksheet xmlns="%s"><cols><col min="2" max="2" width="40" customWidth="1"/></cols><sheetData><row r="3" ht="48" customHeight="1"><c r="B3" s="1" t="inlineStr"><is><t>Kernel cell</t></is></c></row></sheetData></worksheet>' % sheet)
PY
}
# Stops an app started with setsid, and all it started.
stop_app() { # pid
kill -- -"$1" 2>/dev/null || true
local found
for _ in $(seq 60); do
found=0
pgrep -s "$1" >/dev/null || found=$?
[ "$found" -eq 1 ] && return 0
[ "$found" -eq 0 ] || fail "pgrep failed with $found"
sleep 0.25
done
fail "processes of session $1 did not exit"
}
# Title bar: Catppuccin is blue-grey, Latte is light.
band() { magick "$1" -crop 100%x3%+0+0 +repage -format "%[fx:int(mean.$2*255)]" info:; }
judge() { # slug, screenshot, words, app log
local text
grep -q 'omarchy catppuccin applied' "$4" || fail "$1 did not apply the Omarchy theme"
case $1 in
ely-doc | ely-table | ely-show)
grep -q 'lok-worker: 1 documents open' "$4" || fail "$1's kernel opened no document"
! grep -q -E 'lok: tile.*failed' "$4" || fail "$1's kernel failed a tile"
;;
esac
local blue red
blue=$(band "$2" b) || fail "magick cannot read $2"
red=$(band "$2" r) || fail "magick cannot read $2"
[ $((blue - red)) -gt 10 ] || fail "$1's title bar is not Catppuccin"
# Dark text, twice the size: Xvfb draws at scale 1.
magick "$2" -colorspace Gray -negate -resize 200% "$2.ocr.png"
text=$(tesseract "$2.ocr.png" - 2>"$2.ocr") || fail "tesseract failed: $(cat "$2.ocr")"
grep -qi "$3" <<<"$text" || fail "$1 shows no '$3': $text"
}
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# deb on Ubuntu, rpm on Fedora: apps draw under Xvfb.
set -euo pipefail
root=$(cd "$(dirname "$0")/../.." && pwd)
dist=${ELY_DIST:-$root/dist}
fail() {
echo "FAIL: $*" >&2
exit 1
}
for tool in docker magick tesseract git python3; do
command -v "$tool" >/dev/null || {
echo "SKIP: no $tool"
exit 0
}
done
version=$(awk -F'"' '/^version = / { print $2; exit }' "$root/Cargo.toml")
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
# shellcheck source=tests/scripts/linux-common.sh
. "$root/tests/scripts/linux-common.sh"
linux_inputs "$root" "$work"
# Xvfb, Mesa's software Vulkan and the X11 capture tools.
docker build -q -t ely-smoke-ubuntu - >/dev/null <<'EOF'
FROM ubuntu:26.04@sha256:f144425ff09be612d6d9ad965196e9cdc23dae1f42110a8a11a3e9a8198759f7
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
xvfb mesa-vulkan-drivers x11-apps x11-utils && rm -rf /var/lib/apt/lists/*
EOF
docker build -q -t ely-smoke-fedora - >/dev/null <<'EOF'
FROM fedora:44@sha256:43b29f65a41eb9c35e1cd5323e3bdf3b655c2357a9f4f1ff2f9c2798e5045d80
RUN dnf install -y -q xorg-x11-server-Xvfb mesa-vulkan-drivers xwd xwininfo && dnf clean all
EOF
# As the work dir's owner: Git and CEF refuse root.
inside='set -euo pipefail
slug=$1 work=$2 file=$3
shift 3
eval "$INSTALL /dist/$file" >/tmp/install.log 2>&1 || { tail -20 /tmp/install.log; exit 1; }
user=$(getent passwd "$OWNER" | cut -d: -f1) || { useradd -u "$OWNER" -m ely; user=ely; }
home=$(getent passwd "$user" | cut -d: -f6)
Xvfb :9 -screen 0 1280x800x24 >/tmp/xvfb.log 2>&1 &
for _ in $(seq 40); do [ -e /tmp/.X11-unix/X9 ] && break; sleep 0.25; done
runuser -u "$user" -- env DISPLAY=:9 ELY_OMARCHY_DIR="$work/omarchy/current" ELY_SMOKE=hold ELY_LOCALE=en-US \
HOME="$home" "/usr/bin/$slug" "$@" 2>"$work/$slug.err" &
pid=$!
id=
for _ in $(seq 120); do
id=$(xwininfo -display :9 -root -children | awk -v class="\"$slug\"" "index(\$0, class) { print \$1; exit }")
[ -n "$id" ] && break
kill -0 "$pid" 2>/dev/null || { echo "$slug quit"; exit 1; }
sleep 0.5
done
[ -n "$id" ] || { echo "$slug showed no window"; exit 1; }
sleep 8
kill -0 "$pid" 2>/dev/null || { echo "$slug quit after showing"; exit 1; }
xwd -display :9 -id "$id" -out "$work/$slug.xwd"
kill "$pid"'
run() { # image, install command, package file, slug, input
local slug=$4 shot=$work/$4.png args=()
[ -f "$dist/$3" ] || fail "no $dist/$3; run packaging/linux/package.sh $slug"
[ -z "$5" ] || args=("$work/$5")
docker run --rm -e INSTALL="$2" -e OWNER="$(id -u)" -v "$dist:/dist:ro" -v "$work:$work" "$1" \
bash -c "$inside" _ "$slug" "$work" "$3" "${args[@]}" ||
fail "$3 does not run in $1: $(tail -n 5 "$work/$slug.err" 2>/dev/null)"
magick "$work/$slug.xwd" "$shot"
judge "$slug" "$shot" "$6" "$work/$slug.err"
echo "linux: $3 shows '$6' in Catppuccin on ${1#ely-smoke-}"
}
for case in "${cases[@]}"; do
IFS='|' read -r slug words input <<<"$case"
run ely-smoke-ubuntu "apt-get update -q && apt-get install -y -q" "${slug}_$version-1_amd64.deb" "$slug" "$input" "$words"
run ely-smoke-fedora "dnf install -y -q" "$slug-$version-1.x86_64.rpm" "$slug" "$input" "$words"
done
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
# Installed apps or AppImages show themed windows. Usage: linux-installed.sh <installed|appimage>
set -euo pipefail
root=$(cd "$(dirname "$0")/../.." && pwd)
mode=${1:?installed or appimage}
fail() {
echo "FAIL: $*" >&2
exit 1
}
[ "$(uname)" = Linux ] && [ -n "${WAYLAND_DISPLAY:-}" ] || {
echo "SKIP: needs a Wayland session"
exit 0
}
for tool in hyprctl grim magick tesseract jq git python3; do
command -v "$tool" >/dev/null || {
echo "SKIP: no $tool"
exit 0
}
done
version=$(awk -F'"' '/^version = / { print $2; exit }' "$root/Cargo.toml")
fixtures=$root/crates/ely-ui/tests/fixtures/omarchy
work=$(mktemp -d)
pid=
trap '[ -z "$pid" ] || stop_app "$pid"; rm -rf "$work"' EXIT
# shellcheck source=tests/scripts/linux-common.sh
. "$root/tests/scripts/linux-common.sh"
linux_inputs "$root" "$work"
current=$work/omarchy/current
program() { # slug
case $mode in
installed) echo "/usr/bin/$1" ;;
appimage)
local name
name=$(awk -F'\t' -v slug="$1" '$1 == slug { print $2 }' "$root/packaging/apps.tsv")
echo "$root/dist/$name-$version-linux-$(uname -m).AppImage"
;;
*) fail "mode is installed or appimage, not $mode" ;;
esac
}
# The one window's capture handle, once mapped.
window() {
local id
for _ in $(seq 120); do
id=$(hyprctl clients -j | jq -r --argjson pid "$pid" \
'[.[] | select(.pid == $pid and .mapped)] | if length == 1 then .[0].stableId else "" end') || {
echo "hyprctl or jq failed"
return 1
}
[ -n "$id" ] && {
echo "$id"
return
}
kill -0 "$pid" 2>/dev/null || {
echo "it quit"
return 1
}
sleep 0.5
done
echo "no single window"
return 1
}
shoot() { # slug, file; the window itself, wherever it is
local id
id=$(window) || fail "$1 showed no window ($id): $(tail -n 5 "$work/$1.err")"
sleep 4
grim -T "$id" "$2" || fail "grim cannot capture $1"
}
check() { # slug, words, args
local slug=$1 words=$2 exe shot=$work/$1.png
shift 2
exe=$(program "$slug")
[ -x "$exe" ] || fail "no $exe"
ELY_OMARCHY_DIR="$current" ELY_SMOKE=hold ELY_LOCALE=en-US HOME="$work" XDG_DATA_HOME="$work/data" \
setsid "$exe" "$@" 2>"$work/$slug.err" &
pid=$!
shoot "$slug" "$shot"
judge "$slug" "$shot" "$words" "$work/$slug.err"
if [ "$slug" = ely-tree ]; then
# Omarchy's swap: the open window follows.
mkdir "$current/next-theme"
cp "$fixtures/catppuccin-latte.toml" "$current/next-theme/colors.toml"
rm -rf "$current/theme"
mv "$current/next-theme" "$current/theme"
echo catppuccin-latte >"$current/theme.name"
sleep 2
shoot "$slug" "$shot"
red=$(band "$shot" r) || fail "magick cannot read $shot"
[ "$red" -gt 180 ] || fail "$slug did not follow Omarchy to Latte"
cp "$fixtures/catppuccin.toml" "$current/theme/colors.toml"
echo catppuccin >"$current/theme.name"
fi
stop_app "$pid"
pid=
echo "linux: $mode $slug shows '$words' in Catppuccin"
}
for case in "${cases[@]}"; do
IFS='|' read -r slug words input <<<"$case"
if [ -n "$input" ]; then
check "$slug" "$words" "$work/$input"
else
check "$slug" "$words"
fi
done
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
# Idle on Linux: ElyMark rereads no theme, settings or file.
set -euo pipefail
root=$(cd "$(dirname "$0")/../.." && pwd)
fail() {
echo "FAIL: $*" >&2
exit 1
}
[ "$(uname)" = Linux ] || {
echo "SKIP: inotify is Linux"
exit 0
}
[ -n "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ] || {
echo "SKIP: no display"
exit 0
}
for tool in inotifywait jq hyprctl grim magick tesseract; do
command -v "$tool" >/dev/null || {
echo "SKIP: no $tool"
exit 0
}
done
[ -f "$root/engines/cef/libcef.so" ] || fail "no engines/cef; run scripts/fetch-cef.sh"
[ -f "$root/engines/content/ely-mark/index.html" ] || fail "no ElyMark page; run scripts/build-mark-content.sh"
app=$(cargo build -q --locked --message-format=json --manifest-path "$root/Cargo.toml" -p ely-mark --bin ely-mark |
jq -r 'select(.reason == "compiler-artifact" and .target.name == "ely-mark") | .executable // empty')
[ -x "$app" ] || fail "Cargo reported no ElyMark binary"
work=$(mktemp -d)
pid=
spy=
trap '[ -z "$spy" ] || kill "$spy" 2>/dev/null; [ -z "$pid" ] || stop_app "$pid"; rm -rf "$work"' EXIT
# shellcheck source=tests/scripts/linux-common.sh
. "$root/tests/scripts/linux-common.sh"
current=$work/omarchy/current
mkdir -p "$current/theme" "$work/data/ElyOffice" "$work/notes"
cp "$root/crates/ely-ui/tests/fixtures/omarchy/catppuccin.toml" "$current/theme/colors.toml"
echo catppuccin >"$current/theme.name"
echo '{"version": 1}' >"$work/data/ElyOffice/settings.json"
note=$work/notes/note.md
printf '# Idle\n\nNothing moves.\n' >"$note"
LD_LIBRARY_PATH="$root/engines/cef" ELY_OMARCHY_DIR="$current" ELY_SMOKE=hold ELY_LOG=info HOME="$work" \
XDG_DATA_HOME="$work/data" setsid "$app" "$note" 2>"$work/app.err" &
pid=$!
seen() { grep -c "$1" "$work/app.err" || true; }
logged() { # pattern, what, count before
local before=${3:-0}
for _ in $(seq 120); do
[ "$(seen "$1")" -gt "$before" ] && return
kill -0 "$pid" 2>/dev/null || fail "ElyMark quit: $(tail -n 5 "$work/app.err")"
sleep 0.25
done
fail "ElyMark missed $2"
}
logged 'mark: watching 1 folders' "its file's folder"
logged 'omarchy catppuccin applied' "the Omarchy theme"
sleep 3
# Four quiet seconds: nothing opens the theme, settings or note.
idle() { # when
inotifywait -q -m -e open --format '%w%f' "$current" "$current/theme" "$work/data/ElyOffice" "$work/notes" \
>"$work/opens" 2>/dev/null &
spy=$!
for _ in 1 2 3 4 5; do
sleep 1
kill -0 "$pid" 2>/dev/null || fail "ElyMark quit while $1: $(tail -n 5 "$work/app.err")"
done
kill "$spy"
spy=
local opens
opens=$(grep -c -E '(colors\.toml|theme\.name|settings\.json|note\.md)$' "$work/opens" || true)
[ "$opens" -eq 0 ] || fail "ElyMark $1 opened files $opens times: $(sort "$work/opens" | uniq -c)"
echo "linux: ElyMark $1 reads nothing"
}
idle "idle after start"
mkdir "$current/next-theme"
cp "$root/crates/ely-ui/tests/fixtures/omarchy/gruvbox.toml" "$current/next-theme/colors.toml"
rm -rf "$current/theme"
mv "$current/next-theme" "$current/theme"
echo gruvbox >"$current/theme.name"
logged 'omarchy gruvbox applied' "Omarchy's theme swap"
echo '{"version": 1, "author": "Tester"}' >"$work/data/ElyOffice/settings.json"
logged 'settings: changed on disk' "a settings write"
changed=$(seen 'changed outside')
printf '# Idle\n\nSomething moved.\n' >"$note"
logged 'changed outside' "an outside edit" "$changed"
# The window shows the new words.
shown=
for _ in $(seq 20); do
kill -0 "$pid" 2>/dev/null || fail "ElyMark quit: $(tail -n 5 "$work/app.err")"
id=$(hyprctl clients -j | jq -r --argjson pid "$pid" '[.[] | select(.pid == $pid and .mapped)][0].stableId // empty')
if [ -n "$id" ]; then
grim -T "$id" "$work/shot.png" 2>"$work/grim.err" || fail "grim failed: $(cat "$work/grim.err")"
magick "$work/shot.png" -colorspace Gray -negate "$work/ocr.png"
text=$(tesseract "$work/ocr.png" - 2>"$work/ocr.err") || fail "tesseract failed: $(cat "$work/ocr.err")"
grep -q 'moved' <<<"$text" && shown=1 && break
fi
sleep 0.5
done
[ -n "$shown" ] || fail "ElyMark does not show the outside edit"
sleep 2
idle "idle after changes"