This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch wl/tests-wlcs
in repository enlightenment.

View the commit online.

commit 1d280c6660f9f4c7a1a01c35009ef39576db8a10
Author: Cedric BAIL <[email protected]>
AuthorDate: Sat Aug 8 19:05:53 2026 -0600

    tests - add a wlcs conformance integration
    
    wlcs is Canonical's cross-compositor Wayland conformance suite, used by Mir
    and KWin among others. A compositor adopts it by providing a shared library
    exporting wlcs_server_integration; the runner dlopen's it and drives the
    compositor through the hooks. In exchange we get several hundred
    third-party protocol tests we did not write.
    
    Subprocess, not in-process. wlcs calls create_server/start/stop/
    destroy_server once per test case. Mir, KWin and Weston all integrate
    in-process because those compositors are libraries with a thin main() and
    can be constructed per test; E is a single large main() with process-global
    state and cannot. So we fork and exec a real Enlightenment.
    
    That fits the contract better than it sounds. start() must not block until
    the mainloop exits - fork+exec is naturally non-blocking. stop() must block
    until teardown is complete - waitpid is naturally blocking. And
    create_client_socket only has to return something wl_display_connect_fd
    can use, which a plain connect() to E's listening socket satisfies.
    
    position_window_absolute was the one hook with no obvious answer: it is
    handed a client-side wl_surface belonging to a connection that is not ours
    and has to move it. The fix is to bind the private wl_test interface on
    that same client's wl_display, so the object reference is valid on the
    connection we send it over, and let the compositor resolve it. All of that
    goes on a private event queue via a proxy wrapper - otherwise our roundtrip
    would dispatch, and swallow, events the test itself is waiting for.
    
    The descriptor is what turns parts of the suite on: wlcs skips every test
    for an extension not listed. Since a stale list silently loses coverage,
    it is checked rather than trusted - the driver below walks the real
    registry and fails if the two disagree, the same discipline as
    globals.expected.
    
    Also included is that driver, e_wlcs_driver, which exercises the hooks
    directly without the runner. It is worth having regardless - a failure
    there points at our shim rather than at several hundred third-party tests -
    and it verifies the interesting behaviour rather than the absence of a
    crash: position_window_absolute is checked against the compositor's own
    view of where the window ended up.
    
    It is also, right now, the only way to run any of this. The packaged wlcs
    (1.1.0-2) links libgtest.so.1.10.0 while the installed gtest is 1.14, so
    /usr/libexec/wlcs/wlcs cannot start. Rebuilding wlcs against the current
    gtest fixes that and requires no change here. Until then the suite itself
    is unverified: the integration is known to be well-formed and to work when
    driven, not known to pass conformance.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01FtoiXoSKUmZb6Aix6U3GZS
---
 src/tests/meson.build        |   1 +
 src/tests/wlcs/e_wlcs.c      | 625 +++++++++++++++++++++++++++++++++++++++++++
 src/tests/wlcs/meson.build   |  46 ++++
 src/tests/wlcs/wlcs_driver.c | 338 +++++++++++++++++++++++
 4 files changed, 1010 insertions(+)

diff --git a/src/tests/meson.build b/src/tests/meson.build
index 0f2a9ce30..cc20842b6 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -8,4 +8,5 @@ executable('timeout', 'timeout.c')
 
 if get_option('wl') == true
   subdir('wayland')
+  subdir('wlcs')
 endif
diff --git a/src/tests/wlcs/e_wlcs.c b/src/tests/wlcs/e_wlcs.c
new file mode 100644
index 000000000..5a9330b9d
--- /dev/null
+++ b/src/tests/wlcs/e_wlcs.c
@@ -0,0 +1,625 @@
+/* wlcs integration for Enlightenment.
+ *
+ * wlcs (https://github.com/canonical/wlcs) is a third-party conformance suite
+ * that any compositor can adopt by providing this shared library. The runner
+ * dlopen's it, looks up wlcs_server_integration, and drives the compositor
+ * through the hooks below.
+ *
+ * Subprocess, not in-process
+ * --------------------------
+ * wlcs calls create_server/start/stop/destroy_server once per *test case*.
+ * Mir, KWin and Weston all run their integrations in-process because those
+ * compositors are built as libraries with a thin main(); they construct and
+ * tear down a compositor per test. E is a single large main() with
+ * process-global state throughout and cannot be initialised repeatedly, so we
+ * fork and exec a real Enlightenment instead.
+ *
+ * That fits the contract better than it might sound: wlcs requires start() to
+ * not block until the mainloop exits (fork+exec is naturally non-blocking) and
+ * requires stop() to block until teardown is complete (waitpid is naturally
+ * blocking). And create_client_socket only has to hand back something
+ * wl_display_connect_fd can use, which a plain connect() to the compositor's
+ * listening socket satisfies.
+ *
+ * The part that needs the test module
+ * -----------------------------------
+ * position_window_absolute is handed a *client-side* wl_surface belonging to a
+ * connection that is not ours, and has to ask the compositor to move it. We
+ * cannot look that up from here. Instead we bind the private wl_test interface
+ * on that same client's wl_display - so the object is valid on the connection
+ * we send it over - and let the compositor resolve it. See
+ * src/protocol/wl-test.xml.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <limits.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include <wayland-client.h>
+#include <wlcs/display_server.h>
+#include <wlcs/pointer.h>
+#include <wlcs/touch.h>
+
+#include "wl-test-client-protocol.h"
+
+#ifndef E_WLCS_DEFAULT_BIN
+# define E_WLCS_DEFAULT_BIN "enlightenment"
+#endif
+
+#define STARTUP_TIMEOUT_MS 20000
+
+typedef struct
+{
+   WlcsDisplayServer base;
+
+   char runtime_dir[PATH_MAX];
+   char socket_path[PATH_MAX];
+   pid_t pid;
+
+   /* Our own connection, used for input injection. Input is not associated
+    * with any particular client, so it cannot ride on a test client's
+    * connection the way position_window_absolute does. */
+   struct wl_display *ctrl;
+   struct wl_registry *ctrl_registry;
+   struct wl_test *ctrl_test;
+} E_Server;
+
+typedef struct
+{
+   WlcsPointer base;
+   E_Server *server;
+   int x, y;
+} E_Pointer;
+
+typedef struct
+{
+   WlcsTouch base;
+   E_Server *server;
+} E_Touch;
+
+/* ------------------------------------------------------------------ utils */
+
+static void
+_msleep(int ms)
+{
+   struct timespec ts = { ms / 1000, (long)(ms % 1000) * 1000000L };
+
+   nanosleep(&ts, NULL);
+}
+
+static void
+_registry_global(void *data, struct wl_registry *reg, uint32_t id,
+                 const char *iface, uint32_t version)
+{
+   struct wl_test **out = data;
+
+   (void)version;
+   if (!strcmp(iface, "wl_test") && !*out)
+     *out = wl_registry_bind(reg, id, &wl_test_interface, 1);
+}
+
+static void
+_registry_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 =
+{
+   _registry_global, _registry_global_remove
+};
+
+/* --------------------------------------------------------------- lifecycle */
+
+static int
+_connect_fd(E_Server *s)
+{
+   struct sockaddr_un addr;
+   int fd;
+
+   fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+   if (fd < 0) return -1;
+
+   memset(&addr, 0, sizeof(addr));
+   addr.sun_family = AF_UNIX;
+   snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", s->socket_path);
+
+   if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+     {
+        close(fd);
+        return -1;
+     }
+   return fd;
+}
+
+static int
+_wait_for_socket(E_Server *s)
+{
+   int waited = 0;
+
+   /* Poll, never sleep a fixed amount: a hardcoded delay is racy in both
+    * directions, and a compositor that died wants noticing straight away
+    * rather than after the full timeout. */
+   while (waited < STARTUP_TIMEOUT_MS)
+     {
+        struct stat st;
+        int status;
+
+        if (waitpid(s->pid, &status, WNOHANG) == s->pid)
+          {
+             fprintf(stderr, "e_wlcs: compositor exited during startup\n");
+             s->pid = 0;
+             return -1;
+          }
+
+        if ((stat(s->socket_path, &st) == 0) && S_ISSOCK(st.st_mode))
+          {
+             /* The socket file appears at listen() time, which can be before
+              * the compositor is actually serving. A real connect closes
+              * that gap. */
+             int fd = _connect_fd(s);
+
+             if (fd >= 0)
+               {
+                  close(fd);
+                  return 0;
+               }
+          }
+
+        _msleep(50);
+        waited += 50;
+     }
+
+   fprintf(stderr, "e_wlcs: no usable socket at %s after %dms\n",
+           s->socket_path, STARTUP_TIMEOUT_MS);
+   return -1;
+}
+
+static void
+_server_start(WlcsDisplayServer *server)
+{
+   E_Server *s = (E_Server *)server;
+   const char *bin = getenv("E_WLCS_BIN") ?: E_WLCS_DEFAULT_BIN;
+   char prefix[PATH_MAX], buf[PATH_MAX];
+   const char *slash;
+   pid_t pid;
+
+   snprintf(s->runtime_dir, sizeof(s->runtime_dir), "%s/e-wlcs-XXXXXX",
+            getenv("TMPDIR") ?: "/tmp");
+   if (!mkdtemp(s->runtime_dir))
+     {
+        fprintf(stderr, "e_wlcs: mkdtemp failed: %s\n", strerror(errno));
+        return;
+     }
+   chmod(s->runtime_dir, 0700);
+
+   /* A private XDG_RUNTIME_DIR means the compositor under test owns the only
+    * socket in it, so we know its name without having to discover it: E picks
+    * its own via ecore_wl2_display_create(NULL) and never exports
+    * WAYLAND_DISPLAY. With the directory to itself it is always wayland-0. */
+   snprintf(s->socket_path, sizeof(s->socket_path), "%s/wayland-0",
+            s->runtime_dir);
+
+   /* Derive the install prefix from the binary path. E resolves its data
+    * through eina_prefix, which reads E_BIN_DIR/E_LIB_DIR/E_DATA_DIR/
+    * E_LOCALE_DIR *before* deriving them from E_PREFIX - so inheriting a
+    * developer's live session would silently point the compositor under test
+    * at /usr/share/enlightenment. Pin all of them. */
+   slash = strrchr(bin, '/');
+   if (slash)
+     {
+        snprintf(buf, sizeof(buf), "%.*s", (int)(slash - bin), bin);
+        slash = strrchr(buf, '/');
+        snprintf(prefix, sizeof(prefix), "%.*s",
+                 slash ? (int)(slash - buf) : (int)strlen(buf), buf);
+     }
+   else
+     snprintf(prefix, sizeof(prefix), "/usr");
+
+   pid = fork();
+   if (pid < 0)
+     {
+        fprintf(stderr, "e_wlcs: fork failed: %s\n", strerror(errno));
+        return;
+     }
+
+   if (pid == 0)
+     {
+        char path[PATH_MAX];
+
+        setenv("XDG_RUNTIME_DIR", s->runtime_dir, 1);
+        setenv("HOME", s->runtime_dir, 1);
+        setenv("E_WL_FORCE", "buffer", 1);
+        setenv("E_CONF_PROFILE", "wltest", 1);
+        setenv("E_CONF_PROFILE_NOSAVE", "1", 1);
+        setenv("E_MODULE_FORCE_LOAD", "wl_test", 1);
+
+        setenv("E_PREFIX", prefix, 1);
+        snprintf(path, sizeof(path), "%s/bin", prefix);
+        setenv("E_BIN_DIR", path, 1);
+        snprintf(path, sizeof(path), "%s/lib", prefix);
+        setenv("E_LIB_DIR", path, 1);
+        snprintf(path, sizeof(path), "%s/share/enlightenment", prefix);
+        setenv("E_DATA_DIR", path, 1);
+        snprintf(path, sizeof(path), "%s/share/locale", prefix);
+        setenv("E_LOCALE_DIR", path, 1);
+
+        /* The compositor's own chatter would otherwise be interleaved with
+         * the test report. Keep it unless someone asks for it. */
+        if (!getenv("E_WLCS_VERBOSE"))
+          {
+             int devnull = open("/dev/null", O_WRONLY);
+
+             if (devnull >= 0)
+               {
+                  dup2(devnull, STDOUT_FILENO);
+                  dup2(devnull, STDERR_FILENO);
+                  if (devnull > STDERR_FILENO) close(devnull);
+               }
+          }
+
+        execlp(bin, bin, (char *)NULL);
+        _exit(127);
+     }
+
+   s->pid = pid;
+
+   if (_wait_for_socket(s) < 0)
+     {
+        if (s->pid)
+          {
+             kill(s->pid, SIGKILL);
+             waitpid(s->pid, NULL, 0);
+             s->pid = 0;
+          }
+        return;
+     }
+
+   /* Our own connection for input injection. */
+   s->ctrl = wl_display_connect_to_fd(_connect_fd(s));
+   if (!s->ctrl)
+     {
+        fprintf(stderr, "e_wlcs: could not open the control connection\n");
+        return;
+     }
+   s->ctrl_registry = wl_display_get_registry(s->ctrl);
+   wl_registry_add_listener(s->ctrl_registry, &_registry_listener,
+                            &s->ctrl_test);
+   wl_display_roundtrip(s->ctrl);
+
+   if (!s->ctrl_test)
+     fprintf(stderr, "e_wlcs: no wl_test global - was E built with "
+                     "-Dtests=true?\n");
+}
+
+static void
+_server_stop(WlcsDisplayServer *server)
+{
+   E_Server *s = (E_Server *)server;
+   char buf[PATH_MAX];
+   int waited = 0;
+
+   if (s->ctrl)
+     {
+        wl_display_disconnect(s->ctrl);
+        s->ctrl = NULL;
+        s->ctrl_registry = NULL;
+        s->ctrl_test = NULL;
+     }
+
+   /* wlcs requires this to block until teardown is complete, so that one
+    * test's compositor cannot leak into the next. */
+   if (s->pid)
+     {
+        kill(s->pid, SIGTERM);
+        while (waited < 5000)
+          {
+             if (waitpid(s->pid, NULL, WNOHANG) == s->pid) break;
+             _msleep(50);
+             waited += 50;
+          }
+        if (waited >= 5000)
+          {
+             kill(s->pid, SIGKILL);
+             waitpid(s->pid, NULL, 0);
+          }
+        s->pid = 0;
+     }
+
+   if (s->runtime_dir[0])
+     {
+        /* E forks helpers that keep writing into HOME as they wind down, so
+         * a single pass can lose the race. */
+        snprintf(buf, sizeof(buf), "rm -rf '%s' 2>/dev/null", s->runtime_dir);
+        for (waited = 0; waited < 10; waited++)
+          {
+             if (system(buf) == 0) break;
+             _msleep(100);
+          }
+        s->runtime_dir[0] = 0;
+     }
+}
+
+static int
+_server_create_client_socket(WlcsDisplayServer *server)
+{
+   return _connect_fd((E_Server *)server);
+}
+
+/* --------------------------------------------------------- window position */
+
+static void
+_server_position_window_absolute(WlcsDisplayServer *server,
+                                 struct wl_display *client,
+                                 struct wl_surface *surface, int x, int y)
+{
+   struct wl_event_queue *queue;
+   struct wl_display *wrapper;
+   struct wl_registry *registry;
+   struct wl_test *tester = NULL;
+
+   (void)server;
+
+   /* The surface belongs to this client, not to us, so bind wl_test on the
+    * client's own connection: the object reference is then valid on the
+    * connection we send it over.
+    *
+    * Everything goes on a private event queue via a proxy wrapper. Without
+    * that, our roundtrip would dispatch - and swallow - events the test
+    * itself is waiting for on the default queue. */
+   queue = wl_display_create_queue(client);
+   if (!queue) return;
+
+   wrapper = wl_proxy_create_wrapper(client);
+   if (!wrapper)
+     {
+        wl_event_queue_destroy(queue);
+        return;
+     }
+   wl_proxy_set_queue((struct wl_proxy *)wrapper, queue);
+
+   registry = wl_display_get_registry(wrapper);
+   wl_proxy_wrapper_destroy(wrapper);
+   if (!registry)
+     {
+        wl_event_queue_destroy(queue);
+        return;
+     }
+
+   wl_registry_add_listener(registry, &_registry_listener, &tester);
+   wl_display_roundtrip_queue(client, queue);
+
+   if (tester)
+     {
+        wl_test_move_surface(tester, surface, x, y);
+        /* wl_test.sync, not wl_display.sync: it answers from an ecore job, so
+         * one main loop iteration has run and the move has actually been
+         * applied by the time we return. */
+        wl_test_sync(tester);
+        wl_display_roundtrip_queue(client, queue);
+        wl_test_destroy(tester);
+     }
+   else
+     fprintf(stderr, "e_wlcs: no wl_test on the client connection; "
+                     "cannot position window\n");
+
+   wl_registry_destroy(registry);
+   wl_event_queue_destroy(queue);
+}
+
+/* ---------------------------------------------------------------- pointer */
+
+static void
+_pointer_move_absolute(WlcsPointer *pointer, wl_fixed_t x, wl_fixed_t y)
+{
+   E_Pointer *p = (E_Pointer *)pointer;
+
+   p->x = wl_fixed_to_int(x);
+   p->y = wl_fixed_to_int(y);
+   if (!p->server->ctrl_test) return;
+   wl_test_pointer_warp(p->server->ctrl_test, p->x, p->y);
+   wl_display_roundtrip(p->server->ctrl);
+}
+
+static void
+_pointer_move_relative(WlcsPointer *pointer, wl_fixed_t dx, wl_fixed_t dy)
+{
+   E_Pointer *p = (E_Pointer *)pointer;
+
+   /* The compositor only takes absolute positions, so the current location is
+    * tracked here. */
+   p->x += wl_fixed_to_int(dx);
+   p->y += wl_fixed_to_int(dy);
+   if (!p->server->ctrl_test) return;
+   wl_test_pointer_warp(p->server->ctrl_test, p->x, p->y);
+   wl_display_roundtrip(p->server->ctrl);
+}
+
+static void
+_pointer_button(E_Pointer *p, int button, int pressed)
+{
+   if (!p->server->ctrl_test) return;
+   wl_test_pointer_button(p->server->ctrl_test, (uint32_t)button, pressed);
+   wl_display_roundtrip(p->server->ctrl);
+}
+
+static void
+_pointer_button_up(WlcsPointer *pointer, int button)
+{
+   _pointer_button((E_Pointer *)pointer, button, 0);
+}
+
+static void
+_pointer_button_down(WlcsPointer *pointer, int button)
+{
+   _pointer_button((E_Pointer *)pointer, button, 1);
+}
+
+static void
+_pointer_destroy(WlcsPointer *pointer)
+{
+   free(pointer);
+}
+
+static WlcsPointer *
+_server_create_pointer(WlcsDisplayServer *server)
+{
+   E_Pointer *p = calloc(1, sizeof(*p));
+
+   if (!p) return NULL;
+   p->server = (E_Server *)server;
+   p->base.version = WLCS_POINTER_VERSION;
+   p->base.move_absolute = _pointer_move_absolute;
+   p->base.move_relative = _pointer_move_relative;
+   p->base.button_up = _pointer_button_up;
+   p->base.button_down = _pointer_button_down;
+   p->base.destroy = _pointer_destroy;
+   return &p->base;
+}
+
+/* ------------------------------------------------------------------ touch */
+
+static void
+_touch_down(WlcsTouch *touch, wl_fixed_t x, wl_fixed_t y)
+{
+   E_Touch *t = (E_Touch *)touch;
+
+   if (!t->server->ctrl_test) return;
+   wl_test_touch_down(t->server->ctrl_test, 0,
+                      wl_fixed_to_int(x), wl_fixed_to_int(y));
+   wl_display_roundtrip(t->server->ctrl);
+}
+
+static void
+_touch_move(WlcsTouch *touch, wl_fixed_t x, wl_fixed_t y)
+{
+   E_Touch *t = (E_Touch *)touch;
+
+   if (!t->server->ctrl_test) return;
+   wl_test_touch_move(t->server->ctrl_test, 0,
+                      wl_fixed_to_int(x), wl_fixed_to_int(y));
+   wl_display_roundtrip(t->server->ctrl);
+}
+
+static void
+_touch_up(WlcsTouch *touch)
+{
+   E_Touch *t = (E_Touch *)touch;
+
+   if (!t->server->ctrl_test) return;
+   wl_test_touch_up(t->server->ctrl_test, 0);
+   wl_display_roundtrip(t->server->ctrl);
+}
+
+static void
+_touch_destroy(WlcsTouch *touch)
+{
+   free(touch);
+}
+
+static WlcsTouch *
+_server_create_touch(WlcsDisplayServer *server)
+{
+   E_Touch *t = calloc(1, sizeof(*t));
+
+   if (!t) return NULL;
+   t->server = (E_Server *)server;
+   t->base.version = WLCS_TOUCH_VERSION;
+   t->base.touch_down = _touch_down;
+   t->base.touch_move = _touch_move;
+   t->base.touch_up = _touch_up;
+   t->base.destroy = _touch_destroy;
+   return &t->base;
+}
+
+/* ------------------------------------------------------------- descriptor */
+
+/* What we tell wlcs we support. wlcs *skips* every test for an extension not
+ * listed here, so this is the switch that turns parts of the suite on: as
+ * Track A lands protocols, they get added and more of the suite runs.
+ *
+ * It has to be kept in step with what E actually advertises. That is checked,
+ * not trusted: test_descriptor.c walks the real registry and fails if the two
+ * disagree, the same discipline as globals.expected.
+ */
+static const WlcsExtensionDescriptor _extensions[] =
+{
+   { "wl_compositor", 4 },
+   { "wl_subcompositor", 1 },
+   { "wl_shm", 1 },
+   { "wl_seat", 4 },
+   { "wl_output", 2 },
+   { "wl_data_device_manager", 3 },
+   { "xdg_wm_base", 1 },
+   { "zxdg_shell_v6", 1 },
+   { "wl_shell", 1 },
+};
+
+static const WlcsIntegrationDescriptor _descriptor =
+{
+   1,
+   sizeof(_extensions) / sizeof(_extensions[0]),
+   _extensions
+};
+
+static const WlcsIntegrationDescriptor *
+_server_get_descriptor(const WlcsDisplayServer *server)
+{
+   (void)server;
+   return &_descriptor;
+}
+
+/* ------------------------------------------------------------ integration */
+
+static WlcsDisplayServer *
+_create_server(int argc, const char **argv)
+{
+   E_Server *s;
+
+   (void)argc; (void)argv;
+
+   s = calloc(1, sizeof(*s));
+   if (!s) return NULL;
+
+   s->base.version = 3;
+   s->base.start = _server_start;
+   s->base.stop = _server_stop;
+   s->base.create_client_socket = _server_create_client_socket;
+   s->base.position_window_absolute = _server_position_window_absolute;
+   s->base.create_pointer = _server_create_pointer;
+   s->base.create_touch = _server_create_touch;
+   s->base.get_descriptor = _server_get_descriptor;
+   /* start_on_this_thread is deliberately NULL: it is for compositors whose
+    * mainloop must own the calling thread, which only helps an in-process
+    * integration. wlcs requires one of {start, start_on_this_thread} and
+    * prefers start when both are present. */
+   s->base.start_on_this_thread = NULL;
+
+   return &s->base;
+}
+
+static void
+_destroy_server(WlcsDisplayServer *server)
+{
+   E_Server *s = (E_Server *)server;
+
+   if (s->pid || s->runtime_dir[0]) _server_stop(server);
+   free(s);
+}
+
+WlcsServerIntegration const wlcs_server_integration =
+{
+   1,
+   _create_server,
+   _destroy_server,
+};
diff --git a/src/tests/wlcs/meson.build b/src/tests/wlcs/meson.build
new file mode 100644
index 000000000..c4a1ec30f
--- /dev/null
+++ b/src/tests/wlcs/meson.build
@@ -0,0 +1,46 @@
+dep_wlcs = dependency('wlcs', required: false)
+
+if dep_wlcs.found()
+  wlcs_proto_src = []
+  foreach p: ['../../protocol/wl-test.xml',
+              '@0@/stable/xdg-shell/xdg-shell.xml'.format(dir_wayland_protocols)]
+    wlcs_proto_src += gen_scanner_client.process(p)
+    wlcs_proto_src += gen_scanner_impl.process(p)
+  endforeach
+
+  e_bin_path = join_paths(get_option('prefix'), get_option('bindir'),
+                          'enlightenment')
+
+  # The integration the wlcs runner dlopen's. name_prefix is cleared so the
+  # file is e_wlcs.so, which is what gets passed to the runner.
+  e_wlcs = shared_module('e_wlcs',
+    ['e_wlcs.c', wlcs_proto_src],
+    name_prefix : '',
+    dependencies: [dep_wlcs, dependency('wayland-client')],
+    c_args      : ['-DE_WLCS_DEFAULT_BIN="@0@"'.format(e_bin_path)],
+  )
+
+  # Drives the integration's own hooks, without the wlcs runner.
+  #
+  # Worth having on its own terms - it isolates "is our shim correct" from
+  # "is the suite happy" - and right now it is the only way to check any of
+  # this: the packaged wlcs runner links libgtest.so.1.10.0 while the system
+  # has gtest 1.14, so /usr/libexec/wlcs/wlcs cannot start at all. Rebuilding
+  # wlcs against the current gtest fixes that; this test does not depend on it.
+  wlcs_driver = executable('e_wlcs_driver',
+    ['wlcs_driver.c', wlcs_proto_src],
+    dependencies: [dep_wlcs, dependency('wayland-client'), cc.find_library('dl')],
+  )
+
+  wlcs_env = environment()
+  wlcs_env.set('E_WLCS_BIN', e_bin_path)
+  wlcs_env.set('E_WLCS_INTEGRATION', e_wlcs.full_path())
+
+  test('wlcs-integration',
+    wlcs_driver,
+    env    : wlcs_env,
+    timeout: 180,
+  )
+else
+  message('wlcs not found; the conformance integration will not be built')
+endif
diff --git a/src/tests/wlcs/wlcs_driver.c b/src/tests/wlcs/wlcs_driver.c
new file mode 100644
index 000000000..907ed310c
--- /dev/null
+++ b/src/tests/wlcs/wlcs_driver.c
@@ -0,0 +1,338 @@
+/* Drives the wlcs integration's hooks directly.
+ *
+ * This is not a substitute for running the conformance suite - it proves our
+ * shim behaves, not that E is conformant. It is worth having anyway, because
+ * it separates "our integration is correct" from "the suite is happy", and
+ * because a failure here points at our code rather than at several hundred
+ * third-party tests.
+ *
+ * It is also, at the moment, the only way to exercise any of this: the
+ * packaged wlcs runner links libgtest.so.1.10.0 while the installed gtest is
+ * 1.14, so /usr/libexec/wlcs/wlcs will not start. Rebuilding wlcs against the
+ * current gtest fixes that and changes nothing here.
+ *
+ * What it checks, in order:
+ *   - the integration exports wlcs_server_integration and can be dlopen'd,
+ *     exactly as the runner loads it;
+ *   - start() leaves the server able to hand out client sockets;
+ *   - create_client_socket() gives a connectable fd;
+ *   - the advertised descriptor matches what the compositor really has, so
+ *     wlcs neither skips tests it could run nor runs tests it cannot;
+ *   - position_window_absolute() actually moves a window - verified against
+ *     the compositor's own view, not merely by nothing crashing;
+ *   - pointer and touch injection survive a round trip;
+ *   - stop() is idempotent-safe and destroy_server() cleans up.
+ */
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#include <wayland-client.h>
+#include <wlcs/display_server.h>
+#include <wlcs/pointer.h>
+#include <wlcs/touch.h>
+
+#include "wl-test-client-protocol.h"
+#include "xdg-shell-client-protocol.h"
+
+#define W 160
+#define H 120
+
+#define FAIL(fmt, ...) \
+  do { fprintf(stderr, "wlcs-driver: " fmt "\n", ##__VA_ARGS__); return 1; } while (0)
+
+static struct wl_compositor *compositor;
+static struct wl_shm *shm;
+static struct xdg_wm_base *wm_base;
+static struct wl_test *tester;
+
+/* interface -> advertised version, as read from the real registry */
+static struct { char name[64]; uint32_t version; } seen[64];
+static int seen_count;
+
+static int configured;
+static int sync_done;
+static int info_valid;
+static int32_t info_x, info_y;
+
+static void
+_registry_global(void *data, struct wl_registry *reg, uint32_t id,
+                 const char *iface, uint32_t version)
+{
+   (void)data;
+
+   if (seen_count < (int)(sizeof(seen) / sizeof(seen[0])))
+     {
+        snprintf(seen[seen_count].name, sizeof(seen[0].name), "%s", iface);
+        seen[seen_count].version = version;
+        seen_count++;
+     }
+
+   if (!strcmp(iface, "wl_compositor"))
+     compositor = wl_registry_bind(reg, id, &wl_compositor_interface, 4);
+   else if (!strcmp(iface, "wl_shm"))
+     shm = wl_registry_bind(reg, id, &wl_shm_interface, 1);
+   else if (!strcmp(iface, "xdg_wm_base"))
+     wm_base = wl_registry_bind(reg, id, &xdg_wm_base_interface, 1);
+   else if (!strcmp(iface, "wl_test"))
+     tester = wl_registry_bind(reg, id, &wl_test_interface, 1);
+}
+
+static void
+_registry_global_remove(void *d, struct wl_registry *r, uint32_t id)
+{
+   (void)d; (void)r; (void)id;
+}
+
+static const struct wl_registry_listener _registry_listener =
+{
+   _registry_global, _registry_global_remove
+};
+
+static void
+_tester_client_serial(void *d, struct wl_test *t, uint32_t serial)
+{
+   (void)d; (void)t; (void)serial;
+}
+
+static void
+_tester_surface_info(void *d, struct wl_test *t, struct wl_surface *s,
+                     int32_t x, int32_t y, int32_t w, int32_t h,
+                     uint32_t visible, uint32_t focused)
+{
+   (void)d; (void)t; (void)s; (void)w; (void)h; (void)visible; (void)focused;
+   info_valid = 1;
+   info_x = x;
+   info_y = y;
+}
+
+static void
+_tester_surface_unknown(void *d, struct wl_test *t, struct wl_surface *s)
+{
+   (void)d; (void)t; (void)s;
+   info_valid = 0;
+}
+
+static void
+_tester_sync_done(void *d, struct wl_test *t)
+{
+   (void)d; (void)t;
+   sync_done = 1;
+}
+
+static const struct wl_test_listener _tester_listener =
+{
+   _tester_client_serial, _tester_surface_info, _tester_surface_unknown,
+   _tester_sync_done
+};
+
+static void
+_xdg_surface_configure(void *d, struct xdg_surface *s, uint32_t serial)
+{
+   (void)d;
+   xdg_surface_ack_configure(s, serial);
+   configured = 1;
+}
+
+static const struct xdg_surface_listener _xdg_surface_listener =
+{
+   _xdg_surface_configure
+};
+
+static void
+_toplevel_configure(void *d, struct xdg_toplevel *t, int32_t w, int32_t h, struct wl_array *st)
+{
+   (void)d; (void)t; (void)w; (void)h; (void)st;
+}
+
+static void
+_toplevel_close(void *d, struct xdg_toplevel *t)
+{
+   (void)d; (void)t;
+}
+
+static const struct xdg_toplevel_listener _toplevel_listener =
+{
+   _toplevel_configure, _toplevel_close
+};
+
+static struct wl_buffer *
+make_buffer(void)
+{
+   int fd, stride = W * 4, size = stride * H;
+   void *map;
+   struct wl_shm_pool *pool;
+   struct wl_buffer *buf;
+
+   fd = shm_open("/e-wlcs-driver-shm", O_RDWR | O_CREAT | O_EXCL, 0600);
+   if (fd < 0) return NULL;
+   shm_unlink("/e-wlcs-driver-shm");
+   if (ftruncate(fd, size) < 0) { close(fd); return NULL; }
+   map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+   if (map == MAP_FAILED) { close(fd); return NULL; }
+   memset(map, 0xff, size);
+
+   pool = wl_shm_create_pool(shm, fd, size);
+   buf = wl_shm_pool_create_buffer(pool, 0, W, H, stride, WL_SHM_FORMAT_ARGB8888);
+   wl_shm_pool_destroy(pool);
+   close(fd);
+   return buf;
+}
+
+int
+main(void)
+{
+   const char *path = getenv("E_WLCS_INTEGRATION");
+   WlcsServerIntegration const *integration;
+   WlcsDisplayServer *server;
+   const WlcsIntegrationDescriptor *desc;
+   struct wl_display *disp;
+   struct wl_registry *reg;
+   struct wl_surface *surface;
+   struct xdg_surface *xdg_surface;
+   struct xdg_toplevel *toplevel;
+   struct wl_buffer *buffer;
+   WlcsPointer *pointer;
+   WlcsTouch *touch;
+   void *handle;
+   size_t i;
+   int fd, rc = 1;
+
+   if (!path) FAIL("E_WLCS_INTEGRATION is unset");
+
+   /* dlopen rather than link: this is how the runner loads us, so it also
+    * checks the symbol is actually exported from the shared object. */
+   handle = dlopen(path, RTLD_NOW);
+   if (!handle) FAIL("dlopen(%s): %s", path, dlerror());
+
+   integration = dlsym(handle, "wlcs_server_integration");
+   if (!integration) FAIL("no wlcs_server_integration symbol: %s", dlerror());
+   if (!integration->create_server || !integration->destroy_server)
+     FAIL("integration is missing create_server/destroy_server");
+
+   server = integration->create_server(0, NULL);
+   if (!server) FAIL("create_server returned NULL");
+
+   if (!server->start) FAIL("no start hook");
+   if (!server->stop) FAIL("no stop hook");
+   if (!server->create_client_socket) FAIL("no create_client_socket hook");
+   if (!server->position_window_absolute) FAIL("no position_window_absolute hook");
+   if (!server->create_pointer) FAIL("no create_pointer hook");
+   if (!server->create_touch) FAIL("no create_touch hook");
+   if (!server->get_descriptor) FAIL("no get_descriptor hook (wlcs v2)");
+
+   server->start(server);
+
+   fd = server->create_client_socket(server);
+   if (fd < 0) FAIL("create_client_socket failed -- did the compositor start?");
+
+   disp = wl_display_connect_to_fd(fd);
+   if (!disp) FAIL("wl_display_connect_to_fd on the returned socket failed");
+
+   reg = wl_display_get_registry(disp);
+   wl_registry_add_listener(reg, &_registry_listener, NULL);
+   if (wl_display_roundtrip(disp) < 0) FAIL("registry roundtrip failed");
+
+   if (!compositor) FAIL("no wl_compositor over the wlcs socket");
+   if (!shm) FAIL("no wl_shm");
+   if (!wm_base) FAIL("no xdg_wm_base");
+   if (!tester) FAIL("no wl_test -- built without -Dtests=true?");
+   wl_test_add_listener(tester, &_tester_listener, NULL);
+
+   /* The descriptor decides which parts of the suite run at all: wlcs skips
+    * every test for an extension not listed. If it drifts from reality we
+    * either silently lose coverage or run tests that cannot pass, and both
+    * failures are quiet. So check it against the registry we just read. */
+   desc = server->get_descriptor(server);
+   if (!desc) FAIL("get_descriptor returned NULL");
+   for (i = 0; i < desc->num_extensions; i++)
+     {
+        const WlcsExtensionDescriptor *e = &desc->supported_extensions[i];
+        int found = 0, j;
+
+        for (j = 0; j < seen_count; j++)
+          {
+             if (strcmp(seen[j].name, e->name)) continue;
+             found = 1;
+             if (seen[j].version < e->version)
+               FAIL("descriptor claims %s v%u but the compositor advertises "
+                    "v%u -- wlcs would run tests that cannot pass",
+                    e->name, e->version, seen[j].version);
+             break;
+          }
+        if (!found)
+          FAIL("descriptor claims %s but the compositor does not advertise it",
+               e->name);
+     }
+
+   /* Map a window so there is something to position. */
+   surface = wl_compositor_create_surface(compositor);
+   xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, surface);
+   xdg_surface_add_listener(xdg_surface, &_xdg_surface_listener, NULL);
+   toplevel = xdg_surface_get_toplevel(xdg_surface);
+   xdg_toplevel_add_listener(toplevel, &_toplevel_listener, NULL);
+   xdg_toplevel_set_title(toplevel, "wlcs-driver");
+   wl_surface_commit(surface);
+   while (!configured)
+     if (wl_display_dispatch(disp) < 0) FAIL("dispatch failed awaiting configure");
+
+   buffer = make_buffer();
+   if (!buffer) FAIL("could not create an shm buffer");
+   wl_surface_attach(surface, buffer, 0, 0);
+   wl_surface_damage(surface, 0, 0, W, H);
+   wl_surface_commit(surface);
+   if (wl_display_roundtrip(disp) < 0) FAIL("roundtrip after mapping failed");
+
+   /* The hook that needed the test module. Checked against the compositor's
+    * own view rather than against nothing having crashed. */
+   server->position_window_absolute(server, disp, surface, 210, 140);
+
+   sync_done = 0;
+   wl_test_sync(tester);
+   while (!sync_done)
+     if (wl_display_dispatch(disp) < 0) FAIL("dispatch failed awaiting sync");
+
+   info_valid = 0;
+   wl_test_get_surface_info(tester, surface);
+   if (wl_display_roundtrip(disp) < 0) FAIL("surface_info roundtrip failed");
+   if (!info_valid) FAIL("compositor has no E_Client for the mapped surface");
+   if ((info_x != 210) || (info_y != 140))
+     FAIL("position_window_absolute(210,140) but the compositor believes "
+          "(%d,%d)", info_x, info_y);
+
+   /* Input. There is no client-visible assertion to make here without a
+    * focused pointer surface, so this checks the transport: the requests are
+    * accepted and the connection is still alive afterwards. */
+   pointer = server->create_pointer(server);
+   if (!pointer) FAIL("create_pointer returned NULL");
+   pointer->move_absolute(pointer, wl_fixed_from_int(240), wl_fixed_from_int(180));
+   pointer->move_relative(pointer, wl_fixed_from_int(10), wl_fixed_from_int(10));
+   pointer->button_down(pointer, 0x110 /* BTN_LEFT */);
+   pointer->button_up(pointer, 0x110);
+   pointer->destroy(pointer);
+
+   touch = server->create_touch(server);
+   if (!touch) FAIL("create_touch returned NULL");
+   touch->touch_down(touch, wl_fixed_from_int(250), wl_fixed_from_int(190));
+   touch->touch_move(touch, wl_fixed_from_int(260), wl_fixed_from_int(200));
+   touch->touch_up(touch);
+   touch->destroy(touch);
+
+   if (wl_display_roundtrip(disp) < 0)
+     FAIL("connection died during input injection");
+
+   printf("wlcs-driver: ok (%zu extensions declared, window positioned at "
+          "%d,%d, pointer and touch accepted)\n",
+          desc->num_extensions, info_x, info_y);
+   rc = 0;
+
+   wl_display_disconnect(disp);
+   server->stop(server);
+   integration->destroy_server(server);
+   dlclose(handle);
+   return rc;
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to