Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
verify/escape-dismiss-test (3.7K)
1 #!/usr/bin/env bash
2 # escape-dismiss-test — behavioral test for the Escape-closes-status-menus arm
3 # in keyboard_group.rs::handle_builtin_binding (7db8c03), driven end to end
4 # through the virtual-keyboard path (fdd1209).
5 #
6 # Setup: a private cce-shadow instance, with status-stub mapped as a status
7 # segment 400px tall — geometrically "expanded", i.e. an open in-surface menu.
8 # The stub subscribes to the `dismiss` topic and shrinks to a bar strip on the
9 # first push, printing one line per push. vkey injects Escape through
10 # zwp_virtual_keyboard_v1. Three assertions, one per gate of the arm:
11 #
12 # 1. Ctrl+Escape while expanded -> no dismiss (modifiers == 0 gate)
13 # 2. plain Escape while expanded -> one dismiss, stub shrinks below
14 # bar_height (the dismissal itself)
15 # 3. plain Escape after shrink -> no dismiss (any_expanded_status_segment
16 # gate, now closed)
17 #
18 # The compositor binary is whatever cce-shadow resolves (installed first, then
19 # the workspace target/release); extra args are passed to `cce-shadow start`,
20 # so `escape-dismiss-test --bin path/to/cce-fx` pins one explicitly.
21
22 set -euo pipefail
23
24 here=$(cd "$(dirname "$0")" && pwd)
25 repo=$(dirname "$here")
26 shadow="$repo/scripts/cce-shadow"
27 out=$(mktemp -d)
28 inst=""
29
30 cleanup() {
31 [ -n "$inst" ] && "$shadow" --instance "$inst" stop >/dev/null 2>&1 || true
32 rm -rf "$out"
33 }
34 trap cleanup EXIT
35
36 fail() {
37 echo "FAIL: $*" >&2
38 echo "--- stub log ---" >&2
39 cat "$out/stub.log" >&2 || true
40 echo "--- compositor log tail ---" >&2
41 [ -n "$inst" ] && "$shadow" --instance "$inst" logs 2>/dev/null | tail -20 >&2 || true
42 exit 1
43 }
44
45 echo "==> building test clients"
46 cargo build --quiet --manifest-path "$here/clients/Cargo.toml"
47 bin="$here/clients/target/debug"
48
49 echo "==> starting shadow instance"
50 inst=$("$shadow" start --new "$@" 2>&1 | sed -n "s/.*claimed instance '\([^']*\)'.*/\1/p")
51 [ -n "$inst" ] || fail "could not claim a shadow instance"
52 sc() { "$shadow" --instance "$inst" "$@"; }
53
54 stub_h() {
55 sc ctl windows --json 2>/dev/null \
56 | jq -r 'select(.app_id == "cce-status-teststub") | .h' | head -1
57 }
58 dismisses() { grep -c '^dismiss' "$out/stub.log" 2>/dev/null || true; }
59
60 # Predicates for wait_for. They must be functions, not `test "$(...)" = x`
61 # inline: a command substitution in wait_for's arguments would be expanded
62 # once at call time, and the loop would poll a constant.
63 stub_h_is() { [ "$(stub_h)" = "$1" ]; }
64 dismiss_count_is() { [ "$(dismisses)" = "$1" ]; }
65
66 wait_for() { # <description> <timeout-seconds> <predicate...>
67 local desc=$1 timeout=$2 i=0
68 shift 2
69 until "$@"; do
70 i=$((i + 1))
71 [ "$i" -ge $((timeout * 5)) ] && fail "timed out waiting for $desc"
72 sleep 0.2
73 done
74 }
75
76 echo "==> mapping expanded status stub"
77 sc run "$bin/status-stub" >"$out/stub.log" 2>&1 &
78 wait_for "stub mapped expanded (h=400)" 10 stub_h_is 400
79
80 echo "==> gate 1: Ctrl+Escape while expanded must not dismiss"
81 sc run "$bin/vkey" mod:4 1 mod:0 >/dev/null
82 sleep 1
83 [ "$(dismisses)" = 0 ] || fail "chorded Escape dismissed the menu (got $(dismisses) dismiss lines)"
84 [ "$(stub_h)" = 400 ] || fail "stub shrank on chorded Escape"
85
86 echo "==> gate 2: plain Escape while expanded must dismiss exactly once"
87 sc run "$bin/vkey" 1 >/dev/null
88 wait_for "dismiss push to reach the stub" 5 dismiss_count_is 1
89 wait_for "stub to shrink below bar_height" 5 stub_h_is 8
90
91 echo "==> gate 3: plain Escape with no expanded segment must stay quiet"
92 sc run "$bin/vkey" 1 >/dev/null
93 sleep 1
94 [ "$(dismisses)" = 1 ] || fail "Escape dismissed with no menu open (got $(dismisses) dismiss lines)"
95
96 echo "PASS: all three gates of the Escape-dismiss arm behave"