diff --git a/protocol/weston-test.xml b/protocol/weston-test.xml index 845b1d412..717e95dcf 100644 --- a/protocol/weston-test.xml +++ b/protocol/weston-test.xml @@ -95,6 +95,23 @@ + + + + + + + + Request that the compositor pauses execution at a certain point. When + execution is paused, the compositor will signal the shared semaphore + to the client. + + + + diff --git a/tests/meson.build b/tests/meson.build index 9dd90f2b5..ae0d5de6b 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -15,6 +15,7 @@ lib_test_runner = static_library( dep_libweston_private_h_deps, dep_wayland_client, dep_libdl, + dep_threads, ], include_directories: common_inc, install: false, @@ -46,6 +47,7 @@ lib_test_client = static_library( dep_libweston_private, dep_libdrm_headers, dep_pixman, + dep_threads, dependency('cairo'), ], install: false, @@ -60,6 +62,7 @@ dep_test_client = declare_dependency( dep_test_runner, dep_pixman, dep_libdrm_headers, + dep_threads, dependency('libudev', version: '>= 136'), ] ) diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index bb8e5c635..654e50882 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -27,6 +27,7 @@ #include "config.h" +#include #include #include #include @@ -2085,3 +2086,162 @@ color_rgb888(pixman_color_t *tmp, uint8_t r, uint8_t g, uint8_t b) return tmp; } + +/** + * Asks the server to wait for a specified breakpoint the next time it occurs, + * synchronized as part of the protocol stream. + * + * \param client Client structure + * \param suite_data Test suite data structure + * \param breakpoint Breakpoint to stop at + * \param proxy Optional breakpoint-specific object to filter by + */ +void +client_push_breakpoint(struct client *client, + struct wet_testsuite_data *suite_data, + enum weston_test_breakpoint breakpoint, + struct wl_proxy *proxy) +{ + weston_test_client_break(client->test->weston_test, breakpoint, + proxy ? wl_proxy_get_id(proxy) : 0); +} + +/** + * Waits for the server's next breakpoint and returns control to the client. + * Must have had a corresponding client_push_breakpoint() call made before. + * + * May only be called after a weston_test.breakpoint request has been issued, + * or within a break, before client_release_breakpoint() has been called. + * + * \param client Client structure + * \param suite_data Test suite data structure + * \return Information about the active breakpoint + */ +struct wet_test_active_breakpoint * +client_wait_breakpoint(struct client *client, + struct wet_testsuite_data *suite_data) +{ + struct wet_test_active_breakpoint *active_bp; + + assert(suite_data); + assert(!suite_data->breakpoints.in_client_break); + + wl_display_flush(client->wl_display); + wet_test_wait_sem(&suite_data->breakpoints.client_break); + + active_bp = suite_data->breakpoints.active_bp; + assert(active_bp); + suite_data->breakpoints.in_client_break = true; + return active_bp; +} + +static void * +get_resource_data_from_proxy(struct wet_testsuite_data *suite_data, + struct wl_proxy *proxy) +{ + struct wl_resource *resource; + + assert(suite_data->breakpoints.in_client_break); + + if (!proxy) + return NULL; + + resource = wl_client_get_object(suite_data->wl_client, + wl_proxy_get_id(proxy)); + assert(resource); + return wl_resource_get_user_data(resource); +} + +/** + * Asks the server to wait for a specified breakpoint the next time it occurs, + * inserted immediately into the wait list with no synchronization to the + * protocol stream. + * + * Must only be called between client_wait_breakpoint() and + * client_release_breakpoint(). + * + * \param client Client structure + * \param suite_data Test suite data structure + * \param breakpoint Breakpoint to stop at + * \param proxy Optional breakpoint-specific object to filter by + */ +void +client_insert_breakpoint(struct client *client, + struct wet_testsuite_data *suite_data, + enum weston_test_breakpoint breakpoint, + struct wl_proxy *proxy) +{ + struct wet_test_pending_breakpoint *bp; + + assert(suite_data->breakpoints.in_client_break); + + bp = xzalloc(sizeof(*bp)); + bp->breakpoint = breakpoint; + bp->resource = get_resource_data_from_proxy(suite_data, proxy); + wl_list_insert(&suite_data->breakpoints.list, &bp->link); +} + +/** + * Removes a specified breakpoint from the server's breakpoint list, + * with no synchronization to the protocol stream. + * + * Must only be called between client_wait_breakpoint() and + * client_release_breakpoint(). + * + * \param client Client structure + * \param suite_data Test suite data structure + * \param breakpoint Breakpoint to remove + * \param proxy Optional breakpoint-specific object to filter by + */ +void +client_remove_breakpoint(struct client *client, + struct wet_testsuite_data *suite_data, + enum weston_test_breakpoint breakpoint, + struct wl_proxy *proxy) +{ + struct wet_test_pending_breakpoint *bp, *tmp; + void *resource = get_resource_data_from_proxy(suite_data, proxy); + + assert(suite_data->breakpoints.in_client_break); + + wl_list_for_each_safe(bp, tmp, &suite_data->breakpoints.list, + link) { + if (bp->breakpoint != breakpoint) + continue; + if (bp->resource != resource) + continue; + assert(bp != suite_data->breakpoints.active_bp->template_); + wl_list_remove(&bp->link); + free(bp); + return; + } + + assert(!"couldn't find breakpoint to remove"); +} + +/** + * Continues server execution after a breakpoint. + * + * \param client Client structure + * \param suite_data Test suite data structure + * \param active_bp Data structure returned from client_wait_breakpoint() + */ +void +client_release_breakpoint(struct client *client, + struct wet_testsuite_data *suite_data, + struct wet_test_active_breakpoint *active_bp) +{ + assert(suite_data->breakpoints.active_bp == active_bp); + + if (active_bp->rearm_on_release) { + wl_list_insert(&suite_data->breakpoints.list, + &active_bp->template_->link); + } else { + free(active_bp->template_); + } + + free(active_bp); + suite_data->breakpoints.active_bp = NULL; + suite_data->breakpoints.in_client_break = false; + wet_test_post_sem(&suite_data->breakpoints.server_release); +} diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 818d703dd..184f6a1b9 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -39,6 +39,7 @@ #include "weston-test-client-protocol.h" #include "viewporter-client-protocol.h" #include "weston-output-capture-client-protocol.h" +#include "weston-testsuite-data.h" struct client { struct wl_display *wl_display; @@ -320,4 +321,42 @@ fill_image_with_color(pixman_image_t *image, const pixman_color_t *color); pixman_color_t * color_rgb888(pixman_color_t *tmp, uint8_t r, uint8_t g, uint8_t b); +/* Helper to wait for the next breakpoint and execute inside it; as this is a + * for loop, continue/break/etc will not affect an enclosing scope! */ +#define RUN_INSIDE_BREAKPOINT(client_, suite_data_) \ + for (struct wet_test_active_breakpoint *breakpoint = \ + client_wait_breakpoint(client_, suite_data_); \ + suite_data_->breakpoints.in_client_break; \ + client_release_breakpoint(client_, suite_data_, breakpoint)) + +/* Specifies that the currently-executing breakpoint should be rearmed */ +#define REARM_BREAKPOINT(breakpoint_) breakpoint_->rearm_on_release = true + +void +client_push_breakpoint(struct client *client, + struct wet_testsuite_data *suite_data, + enum weston_test_breakpoint breakpoint, + struct wl_proxy *proxy); + +struct wet_test_active_breakpoint * +client_wait_breakpoint(struct client *client, + struct wet_testsuite_data *suite_data); + +void +client_insert_breakpoint(struct client *client, + struct wet_testsuite_data *suite_data, + enum weston_test_breakpoint breakpoint, + struct wl_proxy *proxy); + +void +client_remove_breakpoint(struct client *client, + struct wet_testsuite_data *suite_data, + enum weston_test_breakpoint breakpoint, + struct wl_proxy *proxy); + +void +client_release_breakpoint(struct client *client, + struct wet_testsuite_data *suite_data, + struct wet_test_active_breakpoint *active_bp); + #endif diff --git a/tests/weston-test-runner.h b/tests/weston-test-runner.h index 03f399639..bd9c10a70 100644 --- a/tests/weston-test-runner.h +++ b/tests/weston-test-runner.h @@ -29,6 +29,7 @@ #include "config.h" +#include #include #include @@ -143,6 +144,16 @@ struct weston_test_entry { } \ TEST_BEGIN(name, struct weston_compositor *compositor) +/** Get test suite data structure + * + * This returns the shared test suite data structure, to be used in + * any test which is declared with TEST(), TEST_P(), or PLUGIN_TEST(). + * + * \return Test suite data structure + * \ingroup testharness + */ +#define TEST_GET_SUITE_DATA() _wet_suite_data + void testlog(const char *fmt, ...) WL_PRINTF(1, 2); diff --git a/tests/weston-test.c b/tests/weston-test.c index c25358120..89a6dfd7e 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -85,9 +86,56 @@ struct weston_test_output { struct wl_list link; }; +static void +maybe_breakpoint(struct weston_test *test, + enum weston_test_breakpoint breakpoint, + void *resource) +{ + struct wet_test_pending_breakpoint *bp, *tmp; + struct wet_testsuite_data *tsd = weston_compositor_get_test_data(test->compositor); + + wl_list_for_each_safe(bp, tmp, &tsd->breakpoints.list, link) { + struct wet_test_active_breakpoint *active_bp; + + if (breakpoint != bp->breakpoint) + continue; + if (bp->resource && resource != bp->resource) + continue; + + /* Remove this breakpoint from the list; ownership passes to + * the active breakpoint */ + wl_list_remove(&bp->link); + + /* The active breakpoint and the pending one which triggered it + * are now owned by the client */ + active_bp = xzalloc(sizeof(*active_bp)); + active_bp->compositor = test->compositor; + active_bp->resource = resource; + active_bp->template_ = bp; + + /* Wake the client with the active breakpoint, and wait for it + * to return control */ + tsd->breakpoints.active_bp = active_bp; + wet_test_post_sem(&tsd->breakpoints.client_break); + wet_test_wait_sem(&tsd->breakpoints.server_release); + + /* Only ever trigger a single breakpoint at a time */ + return; + } +} + static void output_repaint_listener(struct wl_listener *listener, void *data) { + struct weston_test_output *to = + container_of(listener, struct weston_test_output, + repaint_listener); + struct weston_head *head; + + wl_list_for_each(head, &to->output->head_list, output_link) { + maybe_breakpoint(to->test, WESTON_TEST_BREAKPOINT_POST_REPAINT, + head); + } } static void @@ -501,6 +549,28 @@ send_touch(struct wl_client *client, struct wl_resource *resource, } } +static void +client_break(struct wl_client *client, struct wl_resource *resource, + uint32_t _breakpoint, uint32_t resource_id) +{ + struct weston_test *test = wl_resource_get_user_data(resource); + struct wet_testsuite_data *tsd = weston_compositor_get_test_data(test->compositor); + struct wet_test_pending_breakpoint *bp; + enum weston_test_breakpoint breakpoint = _breakpoint; + + bp = calloc(1, sizeof(*bp)); + bp->breakpoint = breakpoint; + + if (resource_id != 0) { + struct wl_resource *resource = + wl_client_get_object(client, resource_id); + assert(resource); + bp->resource = wl_resource_get_user_data(resource); + } + + wl_list_insert(&tsd->breakpoints.list, &bp->link); +} + static const struct weston_test_interface test_implementation = { move_surface, move_pointer, @@ -511,12 +581,24 @@ static const struct weston_test_interface test_implementation = { device_release, device_add, send_touch, + client_break, }; +static void +destroy_test(struct wl_resource *resource) +{ + struct weston_test *test = wl_resource_get_user_data(resource); + struct wet_testsuite_data *tsd = weston_compositor_get_test_data(test->compositor); + + assert(tsd->wl_client); + tsd->wl_client = NULL; +} + static void bind_test(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct weston_test *test = data; + struct wet_testsuite_data *tsd = weston_compositor_get_test_data(test->compositor); struct wl_resource *resource; resource = wl_resource_create(client, &weston_test_interface, 1, id); @@ -526,8 +608,12 @@ bind_test(struct wl_client *client, void *data, uint32_t version, uint32_t id) } wl_resource_set_implementation(resource, - &test_implementation, test, NULL); + &test_implementation, test, + destroy_test); + /* There can only be one wl_client bound */ + assert(!tsd->wl_client); + tsd->wl_client = client; notify_pointer_position(test, resource); } @@ -613,6 +699,23 @@ create_client_thread(struct weston_test *test, struct wet_testsuite_data *data) data->thread_event_pipe = pipefd[1]; + wl_list_init(&data->breakpoints.list); + + ret = sem_init(&data->breakpoints.client_break, 0, 0); + if (ret != 0) { + weston_log("Creating breakpoint semaphore failed: %s (%d)\n", + strerror(errno), errno); + goto out_source; + } + + ret = sem_init(&data->breakpoints.server_release, 0, 0); + if (ret != 0) { + weston_log("Creating release semaphore failed: %s (%d)\n", + strerror(errno), errno); + goto out_source; + } + + /* Ensure we don't accidentally get signals to the thread. */ sigfillset(&blocked); sigdelset(&blocked, SIGSEGV); @@ -686,10 +789,12 @@ handle_compositor_destroy(struct wl_listener *listener, void *weston_compositor) { struct weston_compositor *compositor = weston_compositor; + struct wet_testsuite_data *data; struct weston_test *test; struct weston_output *output; test = wl_container_of(listener, test, destroy_listener); + data = weston_compositor_get_test_data(test->compositor); wl_list_remove(&test->destroy_listener.link); wl_list_remove(&test->output_created_listener.link); @@ -708,6 +813,8 @@ handle_compositor_destroy(struct wl_listener *listener, if (test->is_seat_initialized) test_seat_release(test); + data->wl_client = NULL; + wl_list_remove(&test->layer.view_list.link); wl_list_remove(&test->layer.link); diff --git a/tests/weston-testsuite-data.h b/tests/weston-testsuite-data.h index 601c5e69f..59a25afd5 100644 --- a/tests/weston-testsuite-data.h +++ b/tests/weston-testsuite-data.h @@ -26,6 +26,10 @@ #ifndef WESTON_TESTSUITE_DATA_H #define WESTON_TESTSUITE_DATA_H +#include +#include +#include + /** Standard return codes * * Both Autotools and Meson use these codes as test program exit codes @@ -57,6 +61,98 @@ enum test_type { TEST_TYPE_CLIENT, }; +/** Safely handle posting a semaphore to wake a waiter + * + * \ingroup testharness_private + */ +static inline void wet_test_post_sem(sem_t *sem) +{ + int ret = sem_post(sem); + assert(ret == 0); /* only fails on programming errors */ +} + +/** Safely handle waiting on a semaphore + * + * \ingroup testharness_private + */ +static inline void wet_test_wait_sem(sem_t *sem) +{ + int ret; + + do { + ret = sem_wait(sem); + } while (ret != 0 && errno == EINTR); + assert(ret == 0); /* all other failures are programming errors */ +} + +/** An individual breakpoint set for the server + * + * This breakpoint data is created and placed in a list by either the server + * (when handling protocol messages) or the client (when directly manipulating + * the list during a breakpoint). + * + * It must be freed by the client. + * + * \ingroup testharness_private + */ +struct wet_test_pending_breakpoint { + /** breakpoint type - enum weston_test_breakpoint from protocol */ + uint32_t breakpoint; + /** type-specific resource to filter on (optional) */ + void *resource; + struct wl_list link; /**< wet_testsuite_breakpoints.list */ +}; + +/** Information about the server's active breakpoint + * + * This breakpoint data is created by the server and passed to the client when + * the server enters a breakpoint. + * + * It must be freed by the client. + * + * \ingroup testharness_private + */ +struct wet_test_active_breakpoint { + /** libweston compositor instance in use */ + struct weston_compositor *compositor; + /** type-specific pointer to resource which triggered this breakpoint */ + void *resource; + /** on release, reinsert the template to trigger next time */ + bool rearm_on_release; + /** client's original breakpoint request */ + struct wet_test_pending_breakpoint *template_; +}; + +/** Client/compositor synchronisation for breakpoint state + * + * Manages the set of active breakpoints placed for the server, as well as + * signalling the pausing/continuing of server actions. + * + * \ingroup testharness_private + */ +struct wet_testsuite_breakpoints { + /** signalled by the server when it reaches a breakpoint */ + sem_t client_break; + /** signalled by the client to resume server execution */ + sem_t server_release; + + /** Pushed by the server when a breakpoint is triggered, immediately + * before it signals the client_break semaphore. Client consumes this + * and takes ownership after the wait succeeds. */ + struct wet_test_active_breakpoint *active_bp; + + /** client-internal state; set by consuming active_bp, cleared by + * signalling server_release */ + bool in_client_break; + + /** list of pending breakpoints: owned by the server during normal + * execution (ordinarily added to by a protocol request, and + * traversed to find a possible breakpoint to trigger), and owned by + * the client wtihin a breakpoint (pending breakpoints may be added + * or removed). Members are wet_test_pending_breakpoint.link */ + struct wl_list list; +}; + /** Test harness specific data for running tests * * \ingroup testharness_private @@ -64,6 +160,8 @@ enum test_type { struct wet_testsuite_data { void (*run)(struct wet_testsuite_data *); + void *wl_client; + /* test definitions */ const struct weston_test_entry *tests; unsigned tests_count; @@ -73,6 +171,7 @@ struct wet_testsuite_data { /* client thread control */ int thread_event_pipe; + struct wet_testsuite_breakpoints breakpoints; /* informational run state */ int fixture_iteration;