This is an automated email from the git hooks/post-receive script.
git pushed a commit to reference refs/pull/145/head
in repository enlightenment.
View the commit online.
commit f74a3188e50c12e6e1c7a3cb4583fedfec2eb807
Author: Cedric BAIL <[email protected]>
AuthorDate: Fri Aug 7 23:14:23 2026 -0600
tests - run a headless compositor and check its advertised globals
The first actual Wayland test, plus the harness the rest will hang off.
run-nested.sh starts a throwaway Enlightenment and runs a client against
it. It uses E_WL_FORCE=buffer: the wl_buffer backend is an in-memory
ecore_evas with no X server, no DRM, no GL and no login session, which
E's own --help calls "Invisible memory buffer. (Debugging/testing)".
E_TEST_BACKEND=x11 switches to a window when you want to watch.
Three things it has to get right:
- a private XDG_RUNTIME_DIR, so the compositor under test owns the only
socket in it. E picks its socket name via ecore_wl2_display_create()
and never exports WAYLAND_DISPLAY, so the name has to be discovered;
isolating the directory makes that an unambiguous glob rather than a
guess.
- E_PREFIX *and* E_BIN_DIR/E_LIB_DIR/E_DATA_DIR/E_LOCALE_DIR. Setting
E_PREFIX alone is not enough: e_prefix.c exports the four _DIR vars,
so running this from inside a live E session inherits that session's
values, and eina_prefix checks <P>_<SUFFIX>_DIR before deriving from
<P>_PREFIX. Miss this and the test binary silently runs against
/usr/share/enlightenment - which looks fine until the two trees
disagree.
- polling for the socket rather than sleeping. A fixed delay is racy in
both directions; the xwayland module already demonstrates that.
The test itself is e_test_globals: walk the registry, print
"interface<TAB>version" sorted, diff against globals.expected. Not
wayland-info, whose output format is not a contract and which would be
another CI dependency.
That file is the point. Any change to E's advertised protocol surface now
has to be made deliberately, lands in the same commit as the code, and
shows up in review as a diff - and a version that regresses fails the
build. It already earned itself: it is what caught wl_buffer advertising
no wl_output.
Note the tests run against an installed tree, so 'ninja install' has to
have happened. run-nested.sh says so plainly rather than failing oddly.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01FtoiXoSKUmZb6Aix6U3GZS
---
src/tests/wayland/check-globals.sh | 42 ++++++++++
src/tests/wayland/e_test_globals.c | 107 ++++++++++++++++++++++++
src/tests/wayland/globals.expected | 16 ++++
src/tests/wayland/meson.build | 34 ++++++++
src/tests/wayland/run-nested.sh | 165 +++++++++++++++++++++++++++++++++++++
5 files changed, 364 insertions(+)
diff --git a/src/tests/wayland/check-globals.sh b/src/tests/wayland/check-globals.sh
new file mode 100755
index 000000000..432f415ce
--- /dev/null
+++ b/src/tests/wayland/check-globals.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+# Compare the compositor's advertised globals against globals.expected.
+#
+# check-globals.sh <run-nested.sh> <e_test_globals> <globals.expected>
+#
+# A failure here is either a regression (a global disappeared or lost a
+# version) or an intentional change that has not been recorded yet. For the
+# latter, update globals.expected in the same commit as the protocol change --
+# that file is the reviewable record of E's Wayland protocol surface.
+
+set -eu
+
+if [ $# -ne 3 ]; then
+ echo "usage: $0 <run-nested.sh> <e_test_globals> <globals.expected>" >&2
+ exit 2
+fi
+
+RUNNER=$1
+DUMPER=$2
+EXPECTED=$3
+
+ACTUAL=$(mktemp "${TMPDIR:-/tmp}/e-globals.XXXXXX")
+trap 'rm -f "$ACTUAL"' EXIT INT TERM
+
+"$RUNNER" "$DUMPER" >"$ACTUAL"
+
+if diff -u "$EXPECTED" "$ACTUAL"; then
+ exit 0
+fi
+
+cat >&2 <<EOF
+
+check-globals: advertised globals do not match $EXPECTED
+
+ '-' lines are expected but missing -> a regression, or a global that moved
+ '+' lines are advertised but new -> update globals.expected in the same
+ commit that added them
+
+To refresh the file after an intentional change:
+ $RUNNER $DUMPER > $EXPECTED
+EOF
+exit 1
diff --git a/src/tests/wayland/e_test_globals.c b/src/tests/wayland/e_test_globals.c
new file mode 100644
index 000000000..75b0f473b
--- /dev/null
+++ b/src/tests/wayland/e_test_globals.c
@@ -0,0 +1,107 @@
+/* Dump every global the compositor advertises, one "interface<TAB>version"
+ * per line, sorted.
+ *
+ * This exists rather than shelling out to wayland-info because wayland-info's
+ * output format is not a stable contract and it is an extra dependency in CI.
+ * The output here is compared against src/tests/wayland/globals.expected, so
+ * any change to E's advertised protocol surface has to be made deliberately,
+ * in the same commit, and shows up in review as a diff of that file.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-client.h>
+
+typedef struct
+{
+ char *iface;
+ uint32_t version;
+} Global;
+
+static Global *globals = NULL;
+static size_t globals_count = 0;
+static size_t globals_alloc = 0;
+
+static void
+_global_add(void *data, struct wl_registry *reg, uint32_t id,
+ const char *iface, uint32_t version)
+{
+ (void)data; (void)reg; (void)id;
+
+ if (globals_count == globals_alloc)
+ {
+ globals_alloc = globals_alloc ? globals_alloc * 2 : 32;
+ globals = realloc(globals, globals_alloc * sizeof(*globals));
+ if (!globals)
+ {
+ fprintf(stderr, "e_test_globals: out of memory\n");
+ exit(1);
+ }
+ }
+ globals[globals_count].iface = strdup(iface);
+ globals[globals_count].version = version;
+ globals_count++;
+}
+
+static void
+_global_remove(void *data, struct wl_registry *reg, uint32_t id)
+{
+ (void)data; (void)reg; (void)id;
+}
+
+static const struct wl_registry_listener _registry_listener =
+{
+ _global_add,
+ _global_remove
+};
+
+static int
+_global_cmp(const void *a, const void *b)
+{
+ const Global *ga = a, *gb = b;
+ int r = strcmp(ga->iface, gb->iface);
+
+ if (r) return r;
+ if (ga->version < gb->version) return -1;
+ if (ga->version > gb->version) return 1;
+ return 0;
+}
+
+int
+main(void)
+{
+ struct wl_display *disp;
+ struct wl_registry *reg;
+ size_t i;
+
+ disp = wl_display_connect(NULL);
+ if (!disp)
+ {
+ fprintf(stderr, "e_test_globals: cannot connect to WAYLAND_DISPLAY=%s\n",
+ getenv("WAYLAND_DISPLAY") ?: "(unset)");
+ return 1;
+ }
+
+ reg = wl_display_get_registry(disp);
+ wl_registry_add_listener(reg, &_registry_listener, NULL);
+
+ /* Two roundtrips: the first delivers the registry's initial globals, the
+ * second catches anything the compositor advertises in response to that
+ * first batch being processed. */
+ if ((wl_display_roundtrip(disp) < 0) || (wl_display_roundtrip(disp) < 0))
+ {
+ fprintf(stderr, "e_test_globals: roundtrip failed\n");
+ return 1;
+ }
+
+ qsort(globals, globals_count, sizeof(*globals), _global_cmp);
+ for (i = 0; i < globals_count; i++)
+ printf("%s\t%u\n", globals[i].iface, globals[i].version);
+
+ for (i = 0; i < globals_count; i++) free(globals[i].iface);
+ free(globals);
+ wl_registry_destroy(reg);
+ wl_display_disconnect(disp);
+
+ return 0;
+}
diff --git a/src/tests/wayland/globals.expected b/src/tests/wayland/globals.expected
new file mode 100644
index 000000000..33367355e
--- /dev/null
+++ b/src/tests/wayland/globals.expected
@@ -0,0 +1,16 @@
+action_route 1
+efl_aux_hints 1
+wl_compositor 4
+wl_data_device_manager 3
+wl_output 2
+wl_seat 4
+wl_shell 1
+wl_shm 1
+wl_subcompositor 1
+xdg_wm_base 1
+zwp_e_session_recovery 1
+zwp_pointer_constraints_v1 1
+zwp_relative_pointer_manager_v1 1
+zxdg_exporter_v1 1
+zxdg_importer_v1 1
+zxdg_shell_v6 1
diff --git a/src/tests/wayland/meson.build b/src/tests/wayland/meson.build
new file mode 100644
index 000000000..6b8d23d96
--- /dev/null
+++ b/src/tests/wayland/meson.build
@@ -0,0 +1,34 @@
+# Wayland compositor tests.
+#
+# Every test here is an out-of-process Wayland *client* driven against a
+# throwaway Enlightenment started by run-nested.sh. E is a single large
+# main() rather than a library, so it cannot be constructed per test case the
+# way Weston's or KWin's suites construct theirs; the compositor is a
+# subprocess and the tests talk to it over the wire.
+#
+# Because the compositor has to be started from an installed tree, these
+# tests require 'ninja install' to have run. run-nested.sh says so plainly if
+# the binary is missing.
+
+e_test_bin = join_paths(get_option('prefix'), get_option('bindir'),
+ 'enlightenment')
+
+run_nested = files('run-nested.sh')
+check_globals = files('check-globals.sh')
+
+wl_test_env = environment()
+wl_test_env.set('E_TEST_BIN', e_test_bin)
+
+e_test_globals = executable('e_test_globals',
+ 'e_test_globals.c',
+ dependencies: [dependency('wayland-client')],
+)
+
+# The golden-globals test. Cheap, and the one test every protocol branch has
+# to touch: it makes E's advertised protocol surface a reviewable file.
+test('wl-globals',
+ find_program('check-globals.sh'),
+ args: [run_nested, e_test_globals, files('globals.expected')],
+ env: wl_test_env,
+ timeout: 120,
+)
diff --git a/src/tests/wayland/run-nested.sh b/src/tests/wayland/run-nested.sh
new file mode 100755
index 000000000..3ace564d6
--- /dev/null
+++ b/src/tests/wayland/run-nested.sh
@@ -0,0 +1,165 @@
+#!/bin/sh
+# Run a Wayland client against a throwaway Enlightenment compositor.
+#
+# run-nested.sh <client> [args...]
+#
+# Environment:
+# E_TEST_BIN path to the installed enlightenment binary (required)
+# E_TEST_BACKEND buffer (default) | x11 | wl -- see E_WL_FORCE
+# E_TEST_TIMEOUT seconds to wait for E's socket (default 20)
+# E_TEST_KEEP set to 1 to keep the temp dir and print E's log path
+# WAYLAND_DEBUG passed through to the client
+#
+# Exits with the client's exit status, or 1 if the compositor never came up.
+#
+# Two things make this safe to run repeatedly on a developer machine:
+#
+# * a private XDG_RUNTIME_DIR, so the compositor under test owns the *only*
+# socket in it. E picks its socket name via ecore_wl2_display_create(NULL)
+# and never exports WAYLAND_DISPLAY (e_comp_wl.c, the e_env_set call is
+# commented out), so we have to discover the name rather than assume it.
+# Isolating the directory turns discovery into an unambiguous glob.
+#
+# * E_CONF_PROFILE + E_CONF_PROFILE_NOSAVE, so a test run cannot write to
+# the developer's real E configuration.
+#
+# The default backend is "buffer" (E_WL_FORCE=buffer, the wl_buffer module):
+# an in-memory ecore_evas with no X server, no DRM, no GL and no login
+# session. E's own --help text describes it as "Invisible memory buffer.
+# (Debugging/testing)". Use E_TEST_BACKEND=x11 when you want to watch.
+
+set -eu
+
+if [ $# -lt 1 ]; then
+ echo "usage: $0 <client> [args...]" >&2
+ exit 2
+fi
+
+E_BIN=${E_TEST_BIN:-}
+if [ -z "$E_BIN" ] || [ ! -x "$E_BIN" ]; then
+ echo "run-nested.sh: E_TEST_BIN is unset or not executable: '${E_BIN}'" >&2
+ echo "run-nested.sh: the tests run against an *installed* tree." >&2
+ echo "run-nested.sh: run 'ninja -C build install' first." >&2
+ exit 1
+fi
+
+BACKEND=${E_TEST_BACKEND:-buffer}
+TIMEOUT=${E_TEST_TIMEOUT:-20}
+
+# Pin every path E resolves through eina_prefix.
+#
+# Setting E_PREFIX alone is NOT enough. e_prefix.c exports E_BIN_DIR,
+# E_LIB_DIR, E_DATA_DIR and E_LOCALE_DIR into the environment, so a developer
+# running this from inside a live Enlightenment session inherits that
+# session's values -- and eina_prefix checks <P>_<SUFFIX>_DIR *before* falling
+# back to deriving from <P>_PREFIX (eina_prefix.c, _get_env_var). The result
+# is our binary silently running against /usr/share/enlightenment data: it
+# looks like it works right up until the two trees disagree.
+E_PREFIX=$(cd "$(dirname "$E_BIN")/.." && pwd)
+if [ ! -d "$E_PREFIX/share/enlightenment" ]; then
+ echo "run-nested.sh: no share/enlightenment under '$E_PREFIX'" >&2
+ echo "run-nested.sh: run 'ninja -C build install' first." >&2
+ exit 1
+fi
+
+RUNDIR=$(mktemp -d "${TMPDIR:-/tmp}/e-wl-test.XXXXXX")
+chmod 0700 "$RUNDIR"
+
+E_PID=""
+E_LOG="$RUNDIR/enlightenment.log"
+
+cleanup() {
+ status=$?
+ if [ -n "$E_PID" ] && kill -0 "$E_PID" 2>/dev/null; then
+ kill -TERM "$E_PID" 2>/dev/null || :
+ # Give it a moment to go down cleanly, then insist.
+ i=0
+ while [ $i -lt 20 ] && kill -0 "$E_PID" 2>/dev/null; do
+ i=$((i + 1))
+ sleep 0.1
+ done
+ kill -KILL "$E_PID" 2>/dev/null || :
+ wait "$E_PID" 2>/dev/null || :
+ fi
+ if [ "${E_TEST_KEEP:-0}" = "1" ]; then
+ echo "run-nested.sh: kept $RUNDIR (compositor log: $E_LOG)" >&2
+ else
+ rm -rf "$RUNDIR"
+ fi
+ exit $status
+}
+trap cleanup EXIT INT TERM
+
+# The compositor's own environment. Deliberately not exported to this shell,
+# so the client below is launched with WAYLAND_DISPLAY pointing at the nested
+# compositor and nothing else inherited by accident.
+XDG_RUNTIME_DIR="$RUNDIR" \
+E_PREFIX="$E_PREFIX" \
+E_BIN_DIR="$E_PREFIX/bin" \
+E_LIB_DIR="$E_PREFIX/lib" \
+E_DATA_DIR="$E_PREFIX/share/enlightenment" \
+E_LOCALE_DIR="$E_PREFIX/share/locale" \
+E_WL_FORCE="$BACKEND" \
+E_CONF_PROFILE=wltest \
+E_CONF_PROFILE_NOSAVE=1 \
+HOME="$RUNDIR" \
+ "$E_BIN" >"$E_LOG" 2>&1 &
+E_PID=$!
+
+# Poll for the socket. Never a fixed sleep: a hardcoded delay is racy in both
+# directions on fast and slow machines (the same bug this tree already has in
+# the xwayland module, tracked as E-16e).
+SOCKET=""
+elapsed=0
+while [ "$(printf '%s' "$elapsed" | cut -d. -f1)" -lt "$TIMEOUT" ]; do
+ if ! kill -0 "$E_PID" 2>/dev/null; then
+ echo "run-nested.sh: compositor exited before creating a socket" >&2
+ echo "--- $E_LOG ---" >&2
+ cat "$E_LOG" >&2 || :
+ exit 1
+ fi
+ for s in "$RUNDIR"/wayland-*; do
+ case "$s" in
+ *.lock) continue ;;
+ *'wayland-*') continue ;;
+ esac
+ [ -S "$s" ] || continue
+ SOCKET=$(basename "$s")
+ break
+ done
+ [ -n "$SOCKET" ] && break
+ sleep 0.1
+ elapsed=$(awk "BEGIN {print $elapsed + 0.1}")
+done
+
+if [ -z "$SOCKET" ]; then
+ echo "run-nested.sh: no wayland socket in $RUNDIR after ${TIMEOUT}s" >&2
+ echo "--- $E_LOG ---" >&2
+ cat "$E_LOG" >&2 || :
+ exit 1
+fi
+
+# The socket file exists as soon as the listen() happens, which can be before
+# the compositor is actually serving. One connect attempt loop closes that gap.
+i=0
+while [ $i -lt 50 ]; do
+ if XDG_RUNTIME_DIR="$RUNDIR" WAYLAND_DISPLAY="$SOCKET" \
+ "${E_TEST_PROBE:-true}" >/dev/null 2>&1; then
+ break
+ fi
+ i=$((i + 1))
+ sleep 0.1
+done
+
+set +e
+XDG_RUNTIME_DIR="$RUNDIR" WAYLAND_DISPLAY="$SOCKET" "$@"
+client_status=$?
+set -e
+
+if [ $client_status -ne 0 ]; then
+ echo "run-nested.sh: client '$1' exited $client_status" >&2
+ echo "--- compositor log ---" >&2
+ cat "$E_LOG" >&2 || :
+fi
+
+exit $client_status
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.