[PATCH v2] tests: add test-compositor

Marek Chalupa mchqwerty at gmail.com
Thu Aug 21 07:52:16 PDT 2014


This patch introduces a set of functions that can create a display
and clients for tests.
On server side the user can use functions:
  display_create()
  display_destroy()
  create_client()
  display_run()
  display_resume()
and on client side the user can use:
  client_connect()
  client_disconnect()
  stop_display()

The stop_display() and display_resume() are functions that serve as a barrier
and also allow the display to take some action after the display_run() was called,
because after the display is stopped, it can run arbitrary code until it calls
display_resume().

client_connect() function connects to wayland display and creates a proxy to
test_compositor global object, so it can ask for stopping the display later
using stop_display().

An example:

  void
  client_main()
  {
        /* or client can use wl_display_connect(NULL)
         * and do all the stuff manually */
        struct client *c = client_connect();

        /* do some stuff, ... */

        /* stop the display so that it can
         * do some other stuff */
        stop_display(c, 1);

        /* ... */

        client_disconnect(c);
  }

  TEST(dummy_tst)
  {
       struct display *d = display_create();

       /* set up the display */
       wl_global_create(d->wl_display, ...);

       /* ... */

       create_client(d, client_main);
       display_run();

       /* if we are here, the display has been stopped
        * and we can do some code, i. e. create another global or so */
       wl_global_create(d->wl_display, ...);

       /* ... */

       display_resume(d); /* resume display and clients */

       display_destroy(d);
  }

v2:
  added/changed message in few asserts that were not clear
  fixed codying style issues and typo
  client_create_with_name: fixed a condition in an assert
  get_socket_name: use also pid
  check_error: fix errno -> err

Signed-off-by: Marek Chalupa <mchqwerty at gmail.com>
---
 Makefile.am             |   3 +-
 tests/test-compositor.c | 467 ++++++++++++++++++++++++++++++++++++++++++++++++
 tests/test-compositor.h |  97 ++++++++++
 3 files changed, 566 insertions(+), 1 deletion(-)
 create mode 100644 tests/test-compositor.c
 create mode 100644 tests/test-compositor.h

diff --git a/Makefile.am b/Makefile.am
index fee19ab..dee95c2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -142,7 +142,8 @@ check_LTLIBRARIES = libtest-runner.la
 libtest_runner_la_SOURCES =			\
 	tests/test-runner.c			\
 	tests/test-runner.h			\
-	tests/test-helpers.c
+	tests/test-helpers.c			\
+	tests/test-compositor.c
 libtest_runner_la_LIBADD =			\
 	libwayland-util.la			\
 	libwayland-client.la			\
diff --git a/tests/test-compositor.c b/tests/test-compositor.c
new file mode 100644
index 0000000..2f54276
--- /dev/null
+++ b/tests/test-compositor.c
@@ -0,0 +1,467 @@
+/*
+ * Copyright (c) 2014 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+#include "test-compositor.h"
+
+/* --- Protocol --- */
+struct test_compositor;
+
+static const struct wl_message tc_requests[] = {
+	/* this request serves as a barrier for synchronizing*/
+	{ "stop_display", "u", NULL }
+};
+
+static const struct wl_message tc_events[] = {
+	{ "display_resumed", "", NULL }
+};
+
+const struct wl_interface test_compositor_interface = {
+	"test", 1,
+	1, tc_requests,
+	1, tc_events
+};
+
+struct test_compositor_interface {
+	void (*stop_display)(struct wl_client *client,
+			     struct wl_resource *resource,
+			     uint32_t num);
+};
+
+struct test_compositor_listener {
+	void (*display_resumed)(void *data, struct test_compositor *tc);
+
+};
+
+enum {
+	STOP_DISPLAY = 0
+};
+
+enum {
+	DISPLAY_RESUMED = 0
+};
+
+/* Since tests can run parallely, we need unique socket names
+ * for each test, otherwise the test can fail on wl_display_add_socket. */
+static const char *
+get_socket_name(void)
+{
+	struct timeval tv;
+	static char retval[64];
+
+	gettimeofday(&tv, NULL);
+	snprintf(retval, sizeof retval, "wayland-test-%d-%ld%ld",
+		 getpid(), tv.tv_sec, tv.tv_usec);
+
+	return retval;
+}
+
+/**
+ * Check client's state and terminate display when all clients exited
+ */
+static void
+client_destroyed(struct wl_listener *listener, void *data)
+{
+	struct display *d;
+	struct client_info *ci;
+	siginfo_t status;
+
+	ci = wl_container_of(listener, ci, destroy_listener);
+	d = ci->display;
+
+	assert(waitid(P_PID, ci->pid, &status, WEXITED) != -1);
+
+	switch (status.si_code) {
+	case CLD_KILLED:
+	case CLD_DUMPED:
+		fprintf(stderr, "Client '%s' was killed by signal %d\n",
+			ci->name, status.si_status);
+		ci->exit_code = status.si_status;
+		break;
+	case CLD_EXITED:
+		if (status.si_status != EXIT_SUCCESS)
+			fprintf(stderr, "Client '%s' exited with code %d\n",
+				ci->name, status.si_status);
+
+		ci->exit_code = status.si_status;
+		break;
+	}
+
+	++d->clients_terminated_no;
+	if (d->clients_no == d->clients_terminated_no) {
+		wl_display_terminate(d->wl_display);
+	}
+
+	/* the clients are not removed from the list, because
+	 * at the end of the test we check the exit codes of all
+	 * clients. In the case that the test would go through
+	 * the clients list manually, zero out the wl_client as a sign
+	 * that the client is not running anymore */
+	ci->wl_client = NULL;
+}
+
+static void
+run_client(void (*client_main)(void), int wayland_sock, int client_pipe)
+{
+	char s[8];
+	int can_continue = 0;
+
+	/* Wait until display signals that client can continue */
+	assert(read(client_pipe, &can_continue, sizeof(int)) == sizeof(int));
+
+	if (can_continue == 0)
+		abort(); /* error in parent */
+
+	/* for wl_display_connect() */
+	snprintf(s, sizeof s, "%d", wayland_sock);
+	setenv("WAYLAND_SOCKET", s, 0);
+
+	client_main();
+}
+
+static struct client_info *
+display_create_client(struct display *d,
+		      void (*client_main)(void),
+		      const char *name)
+{
+	int pipe_cli[2];
+	int sock_wayl[2];
+	pid_t pid;
+	int can_continue = 0;
+	struct client_info *cl;
+
+	assert(pipe(pipe_cli) == 0 && "Failed creating pipe");
+	assert(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_wayl) == 0
+	       && "Failed creating socket pair");
+
+	pid = fork();
+	assert(pid != -1 && "Fork failed");
+
+	if (pid == 0) {
+		close(sock_wayl[1]);
+		close(pipe_cli[1]);
+
+		run_client(client_main, sock_wayl[0], pipe_cli[0]);
+
+		close(sock_wayl[0]);
+		close(pipe_cli[0]);
+
+		exit(0);
+	}
+
+	close(sock_wayl[0]);
+	close(pipe_cli[0]);
+
+	cl = calloc(1, sizeof(struct client_info));
+	assert(cl && "Out of memory");
+
+	wl_list_insert(&d->clients, &cl->link);
+
+	cl->display = d;
+	cl->name = name;
+	cl->pid = pid;
+	cl->pipe = pipe_cli[1];
+	cl->destroy_listener.notify = &client_destroyed;
+
+	cl->wl_client = wl_client_create(d->wl_display, sock_wayl[1]);
+	if (!cl->wl_client) {
+		/* abort the client */
+		write(cl->pipe, &can_continue, sizeof(int));
+		assert(0 && "Couldn't create wayland client");
+	}
+
+	wl_client_add_destroy_listener(cl->wl_client,
+				       &cl->destroy_listener);
+
+	++d->clients_no;
+
+	return cl;
+}
+
+struct client_info *
+client_create_with_name(struct display *d, void (*client_main)(void),
+			const char *name)
+{
+	int can_continue = 1;
+	struct client_info *cl = display_create_client(d, client_main, name);
+
+	/* let the show begin! */
+	assert(write(cl->pipe, &can_continue, sizeof(int)) == sizeof(int));
+
+	return cl;
+}
+
+/* wfr = waiting for resume */
+struct wfr {
+	struct wl_resource *resource;
+	struct wl_list link;
+};
+
+static void
+handle_stop_display(struct wl_client *client,
+		    struct wl_resource *resource, uint32_t num)
+{
+	struct display *d = wl_resource_get_user_data(resource);
+	struct wfr *wfr;
+
+	assert(d->wfr_num < num
+	       && "test error: Too many clients sent stop_display request");
+
+	++d->wfr_num;
+
+	wfr = malloc(sizeof *wfr);
+	if (!wfr) {
+		wl_client_post_no_memory(client);
+		assert(0 && "Out of memory");
+	}
+
+	wfr->resource = resource;
+	wl_list_insert(&d->waiting_for_resume, &wfr->link);
+
+	if (d->wfr_num == num)
+		wl_display_terminate(d->wl_display);
+}
+
+static const struct test_compositor_interface tc_implementation = {
+	handle_stop_display
+};
+
+static void
+tc_bind(struct wl_client *client, void *data,
+	uint32_t ver, uint32_t id)
+{
+	struct wl_resource *res;
+
+	res = wl_resource_create(client, &test_compositor_interface, ver, id);
+	if (!res) {
+		wl_client_post_no_memory(client);
+		assert(0 && "Out of memory");
+	}
+
+	wl_resource_set_implementation(res, &tc_implementation, data, NULL);
+}
+
+struct display *
+display_create(void)
+{
+	struct display *d = NULL;
+	struct wl_global *g;
+	const char *socket_name;
+	int stat = 0;
+
+	d = calloc(1, sizeof *d);
+	assert(d && "Out of memory");
+
+	d->wl_display = wl_display_create();
+	assert(d->wl_display && "Creating display failed");
+
+	/* hope the path won't be longer than 108 ... */
+	socket_name = get_socket_name();
+	stat = wl_display_add_socket(d->wl_display, socket_name);
+	assert(stat == 0 && "Failed adding socket");
+
+	wl_list_init(&d->clients);
+	d->clients_no = d->clients_terminated_no = 0;
+
+	wl_list_init(&d->waiting_for_resume);
+	d->wfr_num = 0;
+
+	g = wl_global_create(d->wl_display, &test_compositor_interface,
+			     1, d, tc_bind);
+	assert(g && "Creating test global failed");
+
+	return d;
+}
+
+void
+display_run(struct display *d)
+{
+	assert(d->wfr_num == 0
+	       && "test error: Have waiting clients. Use display_resume.");
+	wl_display_run(d->wl_display);
+}
+
+void
+display_resume(struct display *d)
+{
+	struct wfr *wfr, *next;
+
+	assert(d->wfr_num > 0 && "test error: No clients waiting.");
+
+	wl_list_for_each_safe(wfr, next, &d->waiting_for_resume, link) {
+		wl_resource_post_event(wfr->resource, DISPLAY_RESUMED);
+		wl_list_remove(&wfr->link);
+		free(wfr);
+	}
+
+	assert(wl_list_empty(&d->waiting_for_resume));
+	d->wfr_num = 0;
+
+	wl_display_run(d->wl_display);
+}
+
+void
+display_destroy(struct display *d)
+{
+	struct client_info *cl, *next;
+	int failed = 0;
+
+	assert(d->wfr_num == 0
+	       && "test error: Didn't you forget to call display_resume?");
+
+	wl_list_for_each_safe(cl, next, &d->clients, link) {
+		assert(cl->wl_client == NULL);
+
+		if (cl->exit_code != 0) {
+			++failed;
+			fprintf(stderr, "Client '%s' failed\n", cl->name);
+		}
+
+		close(cl->pipe);
+		free(cl);
+	}
+
+	wl_display_destroy(d->wl_display);
+	free(d);
+
+	if (failed) {
+		fprintf(stderr, "%d child(ren) failed\n", failed);
+		abort();
+	}
+}
+
+/*
+ * --- Client helper functions ---
+ */
+static void
+handle_display_resumed(void *data, struct test_compositor *tc)
+{
+	struct client *c = data;
+
+	c->display_stopped = 0;
+}
+
+static const struct test_compositor_listener tc_listener = {
+	handle_display_resumed
+};
+
+static void
+registry_handle_globals(void *data, struct wl_registry *registry,
+			uint32_t id, const char *intf, uint32_t ver)
+{
+	struct client *c = data;
+
+	if (strcmp(intf, "test") != 0)
+		return;
+
+	c->tc = wl_registry_bind(registry, id, &test_compositor_interface, ver);
+	assert(c->tc && "Failed binding to registry");
+
+	wl_proxy_add_listener((struct wl_proxy *) c->tc,
+			      (void *) &tc_listener, c);
+}
+
+static const struct wl_registry_listener registry_listener =
+{
+	registry_handle_globals,
+	NULL
+};
+
+struct client *client_connect()
+{
+	struct wl_registry *reg;
+	struct client *c = calloc(1, sizeof *c);
+	assert(c && "Out of memory");
+
+	c->wl_display = wl_display_connect(NULL);
+	assert(c->wl_display && "Failed connecting to display");
+
+	/* create test_compositor proxy. Do it with temporary
+	 * registry so that client can define it's own listener later */
+	reg = wl_display_get_registry(c->wl_display);
+	assert(reg);
+	wl_registry_add_listener(reg, &registry_listener, c);
+	wl_display_roundtrip(c->wl_display);
+	assert(c->tc);
+
+	wl_registry_destroy(reg);
+
+	return c;
+}
+
+static void
+check_error(struct wl_display *display)
+{
+	uint32_t ec, id;
+	const struct wl_interface *intf;
+	int err;
+
+	err = wl_display_get_error(display);
+	/* write out message about protocol error */
+	if (err == EPROTO) {
+		ec = wl_display_get_protocol_error(display, &intf, &id);
+		fprintf(stderr, "Client: Got protocol error %u on interface %s"
+				" (object %u)\n", ec, intf->name, id);
+	}
+
+	if (err) {
+		fprintf(stderr, "Client error: %s\n", strerror(err));
+		abort();
+	}
+}
+
+void
+client_disconnect(struct client *c)
+{
+	/* check for errors */
+	check_error(c->wl_display);
+
+	wl_proxy_destroy((struct wl_proxy *) c->tc);
+	wl_display_disconnect(c->wl_display);
+}
+
+/* num is number of clients that requests to stop display.
+ * Display is stopped after it recieve num STOP_DISPLAY requests */
+int
+stop_display(struct client *c, int num)
+{
+	int n = 0;
+
+	c->display_stopped = 1;
+	wl_proxy_marshal((struct wl_proxy *) c->tc, STOP_DISPLAY, num);
+
+	while (c->display_stopped && n >= 0) {
+		n = wl_display_dispatch(c->wl_display);
+	}
+
+	return n;
+}
diff --git a/tests/test-compositor.h b/tests/test-compositor.h
new file mode 100644
index 0000000..c41b17b
--- /dev/null
+++ b/tests/test-compositor.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2014 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <unistd.h>
+
+#include "wayland-server.h"
+#include "wayland-client.h"
+
+/* info about a client on server side */
+struct client_info {
+	struct display *display;
+	struct wl_client *wl_client;
+	struct wl_listener destroy_listener;
+	const char *name; /* for debugging */
+
+	int pipe;
+	pid_t pid;
+	int exit_code;
+
+	struct wl_list link;
+	void *data; /* for arbitrary use */
+};
+
+struct display {
+	struct wl_display *wl_display;
+
+	struct wl_list clients;
+	uint32_t clients_no;
+	uint32_t clients_terminated_no;
+
+	/* list of clients waiting for display_resumed event */
+	struct wl_list waiting_for_resume;
+	uint32_t wfr_num;
+};
+
+/* This is a helper structure for clients.
+ * Instead of calling wl_display_connect() and all the other stuff,
+ * client can use client_connect and it will return this structure
+ * filled. */
+struct client {
+	struct wl_display *wl_display;
+	struct test_compositor *tc;
+
+	int display_stopped;
+};
+
+struct client *client_connect(void);
+void client_disconnect(struct client *);
+int stop_display(struct client *, int);
+
+/**
+ * Usual workflow:
+ *
+ *    d = display_create();
+ *
+ *    wl_global_create(d->wl_display, ...);
+ *    ... other setups ...
+ *
+ *    client_create(d, client_main);
+ *    client_create(d, client_main2);
+ *
+ *    display_run(d);
+ *    display_destroy(d);
+ */
+struct display *display_create(void);
+void display_destroy(struct display *d);
+void display_run(struct display *d);
+
+/* After n clients called stop_display(..., n), the display
+ * is stopped and can process the code after display_run().
+ * This function rerun the display again and send display_resumed
+ * event to waiting clients, so the clients will stop waiting and continue */
+void display_resume(struct display *d);
+
+struct client_info *client_create_with_name(struct display *d,
+					    void (*client_main)(void),
+					    const char *name);
+#define client_create(d, c) client_create_with_name((d), (c), (#c))
-- 
2.0.4



More information about the wayland-devel mailing list