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;