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 <daniels@collabora.com>
This commit is contained in:
Daniel Stone 2023-06-28 11:45:07 +01:00
parent 68cfc121dc
commit efde2fa0b1
7 changed files with 437 additions and 1 deletions

View File

@ -95,6 +95,23 @@
<arg name="y" type="fixed"/>
<arg name="touch_type" type="uint"/>
</request>
<enum name="breakpoint">
<entry name="post_repaint" value="0"
summary="after output repaint (filter type: wl_output)"/>
</enum>
<request name="client_break">
<description summary="request compositor pause at a certain point">
Request that the compositor pauses execution at a certain point. When
execution is paused, the compositor will signal the shared semaphore
to the client.
</description>
<arg name="breakpoint" type="uint" enum="breakpoint"
summary="event type to wait for" />
<arg name="resource_id" type="uint"
summary="optional Wayland resource ID to filter for (type-specific)"/>
</request>
</interface>
<interface name="weston_test_runner" version="1">

View File

@ -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'),
]
)

View File

@ -27,6 +27,7 @@
#include "config.h"
#include <semaphore.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
@ -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);
}

View File

@ -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

View File

@ -29,6 +29,7 @@
#include "config.h"
#include <semaphore.h>
#include <stdlib.h>
#include <wayland-util.h>
@ -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);

View File

@ -34,6 +34,7 @@
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <libweston/libweston.h>
#include <libweston/weston-log.h>
@ -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);

View File

@ -26,6 +26,10 @@
#ifndef WESTON_TESTSUITE_DATA_H
#define WESTON_TESTSUITE_DATA_H
#include <assert.h>
#include <errno.h>
#include <semaphore.h>
/** 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;