mirror of
https://gitlab.freedesktop.org/wayland/wayland.git
synced 2025-10-29 05:40:16 -04:00
tests: add test-compositor
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
[Pekka Paalanen: added test-compositor.h to SOURCES, added
WL_HIDE_DEPRECATED to get rid of deprecated defs and lots of warnings,
fixed one unchecked return value from write().]
Signed-off-by: Marek Chalupa <mchqwerty@gmail.com>
Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
This commit is contained in:
parent
ded9bb1f8b
commit
85d08e8bd6
3 changed files with 572 additions and 1 deletions
|
|
@ -142,7 +142,9 @@ 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.h \
|
||||
tests/test-compositor.c
|
||||
libtest_runner_la_LIBADD = \
|
||||
libwayland-util.la \
|
||||
libwayland-client.la \
|
||||
|
|
|
|||
472
tests/test-compositor.c
Normal file
472
tests/test-compositor.c
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
#define WL_HIDE_DEPRECATED
|
||||
|
||||
#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) {
|
||||
int ret;
|
||||
|
||||
/* abort the client */
|
||||
ret = write(cl->pipe, &can_continue, sizeof(int));
|
||||
assert(ret == sizeof(int) && "aborting the client failed");
|
||||
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, ®istry_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;
|
||||
}
|
||||
97
tests/test-compositor.h
Normal file
97
tests/test-compositor.h
Normal file
|
|
@ -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))
|
||||
Loading…
Add table
Add a link
Reference in a new issue