diff options
author | Marek Chalupa <mchqwerty@gmail.com> | 2014-08-21 16:52:16 +0200 |
---|---|---|
committer | Pekka Paalanen <pekka.paalanen@collabora.co.uk> | 2014-08-22 12:34:33 +0300 |
commit | 85d08e8bd6d84694e2032f6fdb312f33036bfdda (patch) | |
tree | 81d9b1bc8ac7d8998c97c3d4a8a7cfd38a01c638 /tests | |
parent | ded9bb1f8b30c5c38bc8170ef99f33857302121a (diff) |
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>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/test-compositor.c | 472 | ||||
-rw-r--r-- | tests/test-compositor.h | 97 |
2 files changed, 569 insertions, 0 deletions
diff --git a/tests/test-compositor.c b/tests/test-compositor.c new file mode 100644 index 0000000..3248e2d --- /dev/null +++ b/tests/test-compositor.c @@ -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; +} 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)) |