Files
ghostty-rs/scripts/live-dbus-test.sh
T
ddidderr 30863e1f0b test(gtk): live D-Bus/portal comparison against the original Ghostty
Adds scripts/live-dbus-test.sh (`just live-dbus`): each scenario runs
the installed Ghostty 1.3.1 and the Rust build on a private
dbus-run-session bus inside a private headless weston (the user's
session bus is never written to), with Python/Gio mocks for
org.freedesktop.Notifications, the GlobalShortcuts/Settings portal and
org.freedesktop.systemd1, plus a real xdg-desktop-portal run. Scenarios:
single-instance (+detect), +new-window with arguments, notifications,
global shortcuts, color-scheme settings, cgroup scope creation, URL
opening. tests/live_dbus.rs compares the Rust build against goldens
recorded from the original (skips when tools or a fresh production
build are missing).

Two Rust bugs found and fixed (no upstream deviation):
- GlobalShortcuts CreateSession/BindShortcuts payloads used the wrong
  D-Bus signatures ((v), double-boxed values), which a real portal
  rejects; signature tests added.
- CreateSession was sent twice at startup; the config is now bound only
  after the D-Bus connection is set, as in Zig.

All nine scenarios now behave identically to the original
(docs/rewrite/notes/live-dbus.md).

Test Plan:
- just live-dbus
- cargo test --test live_dbus (needs target/vp/production/ghostty)
2026-10-12 18:49:53 +02:00

121 lines
5.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Live D-Bus / portal comparison of original Ghostty and the Rust build.
#
# scripts/live-dbus-test.sh [scenario...]
#
# Scenarios: single-instance single-instance-detect notifications shortcuts settings cgroup openuri openurl-click real-portal
# (default: all). Each scenario runs once per binary on a *private* session
# bus (dbus-run-session with its own config: only our service dir, no user
# services) inside a private headless weston; mock services
# (scripts/live-dbus/mock.py) answer org.freedesktop.Notifications,
# org.freedesktop.systemd1 and the portal interfaces Ghostty uses. The
# user's session bus, portal and desktop are never touched.
#
# The original's normalized result (D-Bus traffic + observable effects) is the
# oracle; the Rust result must be identical. Exit status 1 on any difference,
# 0 when everything matches or the needed tools are missing (skip).
#
# env: RUST_BIN (default target/vp/production/ghostty: debug builds use a different app id), REF_BIN (default /usr/bin/ghostty),
# OUT (default target/live-dbus), KEEP=1 keeps the weston log.
set -u
ROOT=$(cd "$(dirname "$0")/.." && pwd)
RUST_BIN=${RUST_BIN:-$ROOT/target/vp/production/ghostty}
REF_BIN=${REF_BIN:-/usr/bin/ghostty}
OUT=${OUT:-$ROOT/target/live-dbus}
LD=$ROOT/scripts/live-dbus
SCEN=${*:-single-instance single-instance-detect notifications shortcuts settings cgroup openuri openurl-click real-portal}
for t in weston dbus-run-session busctl python3 xdotool; do
command -v $t >/dev/null || { echo "live-dbus: SKIP ($t missing)"; exit 0; }
done
python3 -c 'import gi; gi.require_version("Gio","2.0")' 2>/dev/null || { echo "live-dbus: SKIP (python gi missing)"; exit 0; }
[ -x "$RUST_BIN" ] || [ "${LABELS:-}" = ref ] || { echo "live-dbus: SKIP (no rust build at $RUST_BIN)"; exit 0; }
mkdir -p "$OUT"
export XDG_RUNTIME_DIR=${VP_RUNTIME_DIR:-/tmp/vp-runtime-$UID}
mkdir -p -m700 "$XDG_RUNTIME_DIR" /tmp/.X11-unix 2>/dev/null
WORK=$(mktemp -d "$OUT/work.XXXX")
TAG=ldb$$
SOCK=ldb$$
cat >"$WORK/weston.ini" <<INI
[core]
shell=desktop-shell.so
xwayland=true
[output]
name=headless
mode=1200x800
[shell]
panel-position=none
background-color=0xff202020
INI
__EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json XWAYLAND_NO_GLAMOR=1 \
timeout 3000 weston --debug --backend=headless --renderer=gl --socket=$SOCK -c "$WORK/weston.ini" \
>"$WORK/weston.log" 2>&1 &
WPID=$!
cleanup() {
kill $WPID 2>/dev/null
for p in /proc/[0-9]*; do
grep -qa "LDB_TAG=$TAG" "$p/environ" 2>/dev/null && kill -9 "${p#/proc/}" 2>/dev/null
done
wait 2>/dev/null
[ -n "${KEEP:-}" ] || rm -rf "$WORK"
}
trap cleanup EXIT
for _ in $(seq 50); do [ -S "$XDG_RUNTIME_DIR/$SOCK" ] && break; sleep 0.1; done
[ -S "$XDG_RUNTIME_DIR/$SOCK" ] || { echo "live-dbus: SKIP (weston did not start)"; cat "$WORK/weston.log"; exit 0; }
sleep 1
XDISPLAY=$(grep -o "display :[0-9]*" "$WORK/weston.log" | tail -1 | sed 's/display //')
rc=0
RUST_ONLY=" openurl-click " # the original cannot be driven (no input injection): golden file only
cut_bus() { sed '/^== bus traffic/,$d' "$1"; }
run_label() { # scenario label bin
local s=$1 label=$2 bin=$3
local d=$OUT/$s/$label; rm -rf "$d"; mkdir -p "$d"
local run=$WORK/run-$s-$label; mkdir -p "$run/services"
# Private bus: only our service dir (activation must start the binary
# under test, not /usr/bin/ghostty), no user services.
cat >"$run/session.conf" <<CONF
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig><type>session</type><keep_umask/><listen>unix:tmpdir=$XDG_RUNTIME_DIR</listen><auth>EXTERNAL</auth>
<servicedir>$run/services</servicedir>
<policy context="default"><allow send_destination="*" eavesdrop="true"/><allow eavesdrop="true"/><allow own="*"/></policy>
</busconfig>
CONF
printf '[D-BUS Service]\nName=com.mitchellh.ghostty\nExec=%s --gtk-single-instance=true --initial-window=false\n' "$bin" \
>"$run/services/com.mitchellh.ghostty.service"
LDB_LABEL=$label LDB_TAG=$TAG WAYLAND_DISPLAY=$SOCK DISPLAY=$XDISPLAY LDB_BIN=$bin LDB_RUN=$run LDB_ROOT=$ROOT \
timeout 240 dbus-run-session --config-file="$run/session.conf" -- \
bash "$LD/scenario.sh" "$s" "$bin" "$d" >"$d/driver.log" 2>&1
LDB_RUN=$run LDB_ROOT=$ROOT python3 "$LD/normalize.py" "$d" >"$d/result.txt" 2>"$d/normalize.err"
}
for s in $SCEN; do
golden=$LD/golden/$s.txt
mkdir -p "$OUT/$s"
have_ref=0
case "$RUST_ONLY" in *" $s "*) ;; *) [ -x "$REF_BIN" ] && [ "${LABELS:-ref rust}" != rust ] && have_ref=1 ;; esac
if [ $have_ref = 1 ]; then run_label "$s" ref "$REF_BIN"; fi
if [ -x "$RUST_BIN" ] && [ "${LABELS:-rust}" != ref ]; then
if [ $have_ref = 1 ] || [ -f "$golden" ] || [ -n "${UPDATE_GOLDEN:-}" ]; then
run_label "$s" rust "$RUST_BIN"
fi
fi
[ "${LABELS:-}" = ref ] && continue
ok=1
if grep -qs '^ERROR' "$OUT"/$s/*/facts.txt; then echo "live-dbus: FAIL $s (scenario error, see $OUT/$s/*/facts.txt)"; rc=1; continue; fi
if [ $have_ref = 1 ] && [ -f "$OUT/$s/rust/result.txt" ]; then
if diff -u "$OUT/$s/ref/result.txt" "$OUT/$s/rust/result.txt" >"$OUT/$s/diff.txt"; then :; else
echo "live-dbus: FAIL $s (original vs Rust)"; cat "$OUT/$s/diff.txt"; ok=0; fi
if [ -n "${UPDATE_GOLDEN:-}" ] || [ ! -f "$golden" ]; then cut_bus "$OUT/$s/ref/result.txt" >"$golden"; fi
fi
if [ -f "$golden" ] && [ -f "$OUT/$s/rust/result.txt" ]; then
if [ -n "${UPDATE_GOLDEN:-}" ] && [ $have_ref = 0 ]; then cut_bus "$OUT/$s/rust/result.txt" >"$golden"; fi
if ! diff -u "$golden" <(cut_bus "$OUT/$s/rust/result.txt") >"$OUT/$s/diff-golden.txt"; then
echo "live-dbus: FAIL $s (Rust vs recorded original, golden/$s.txt)"; cat "$OUT/$s/diff-golden.txt"; ok=0; fi
fi
if [ $ok = 1 ]; then
echo "live-dbus: PASS $s$([ $have_ref = 1 ] && echo ' (original + golden)' || echo ' (golden only)')"
else rc=1; fi
done
exit $rc