/* * 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 #include #include #include #include #include #include #include #include #include #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 } }; 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 cur_alloc, 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_alloc = get_current_alloc_num(); cur_fds = count_open_fds(); client_main(); /* 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_leaks(cur_alloc, cur_fds); } 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); free(c); } /* 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; }