blob: 468ee56df6995d64ed94de0adbc56d7fcdca8f60 [file] [log] [blame]
/*
* Copyright (c) 2014 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.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-runner.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 },
{ "noop", "", NULL },
};
static const struct wl_message tc_events[] = {
{ "display_resumed", "", NULL }
};
const struct wl_interface test_compositor_interface = {
"test", 1,
2, tc_requests,
1, tc_events
};
struct test_compositor_interface {
void (*stop_display)(struct wl_client *client,
struct wl_resource *resource,
uint32_t num);
void (*noop)(struct wl_client *client,
struct wl_resource *resource);
};
struct test_compositor_listener {
void (*display_resumed)(void *data, struct test_compositor *tc);
};
enum {
STOP_DISPLAY = 0,
TEST_NOOP = 1
};
enum {
DISPLAY_RESUMED = 0
};
/* Since tests can run parallelly, 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;
}
static void
handle_client_destroy(void *data)
{
struct client_info *ci = data;
struct display *d;
siginfo_t status;
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 */
}
/**
* Check client's state and terminate display when all clients exited
*/
static void
client_destroyed(struct wl_listener *listener, void *data)
{
struct client_info *ci;
struct display *d;
struct wl_event_loop *loop;
/* Wait for client in an idle handler to avoid blocking the actual
* client destruction (fd close etc. */
ci = wl_container_of(listener, ci, destroy_listener);
d = ci->display;
loop = wl_display_get_event_loop(d->wl_display);
wl_event_loop_add_idle(loop, handle_client_destroy, ci);
ci->wl_client = NULL;
}
static void
run_client(void (*client_main)(void *data), void *data,
int wayland_sock, int client_pipe)
{
char s[8];
int cur_fds;
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);
cur_fds = count_open_fds();
client_main(data);
/* Clients using wl_display_connect() will end up closing the socket
* passed in through the WAYLAND_SOCKET environment variable. When
* doing this, it clears the environment variable, so if it's been
* unset, then we assume the client consumed the file descriptor and
* do not count it towards leak checking. */
if (!getenv("WAYLAND_SOCKET"))
cur_fds--;
check_fd_leaks(cur_fds);
}
static struct client_info *
display_create_client(struct display *d,
void (*client_main)(void *data),
void *data,
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, data, 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 *data), void *data,
const char *name)
{
int can_continue = 1;
struct client_info *cl = display_create_client(d,
client_main, data,
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 void
handle_noop(struct wl_client *client, struct wl_resource *resource)
{
(void)client;
(void)resource;
}
static const struct test_compositor_interface tc_implementation = {
handle_stop_display,
handle_noop,
};
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_post_resume_events(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;
}
void
display_resume(struct display *d)
{
display_post_resume_events(d);
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);
free(c);
}
/* num is number of clients that requests to stop display.
* Display is stopped after it receives 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;
}
void
noop_request(struct client *c)
{
wl_proxy_marshal((struct wl_proxy *) c->tc, TEST_NOOP);
}