From efde2fa0b1060efeeecef397b7fa29db9a79722d Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 28 Jun 2023 11:45:07 +0100 Subject: [PATCH] tests: Add client<->compositor breakpoint support Add support for clients to request the server insert breakpoints at various points in its processing. These breakpoints are handled internally by semaphores (visible to tests through helpers): when the server reaches the specified point, it will pause execution until the client allows it to restart. A weston_compositor pointer returned at each breakpoint allows the client to reach across the thread boundary and access the server's internal data structures. This can be used to, for example, inspect paint nodes, internal damage, or any other work which is not necessarily client-visible. The majority of tests will not need to use this infrastructure; it is only intended for tightly-coupled tests which can very specifically dictate and anticipate the server's execution flow. Signed-off-by: Daniel Stone --- protocol/weston-test.xml | 17 ++++ tests/meson.build | 3 + tests/weston-test-client-helper.c | 160 ++++++++++++++++++++++++++++++ tests/weston-test-client-helper.h | 39 ++++++++ tests/weston-test-runner.h | 11 ++ tests/weston-test.c | 109 +++++++++++++++++++- tests/weston-testsuite-data.h | 99 ++++++++++++++++++ 7 files changed, 437 insertions(+), 1 deletion(-) diff --git a/protocol/weston-test.xml b/protocol/weston-test.xml index 845b1d41..717e95dc 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 9dd90f2b..ae0d5de6 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 bb8e5c63..654e5088 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 818d703d..184f6a1b 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 03f39963..bd9c10a7 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 c2535812..89a6dfd7 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 601c5e69..59a25afd 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;