diff --git a/src/wayland-client-core.h b/src/wayland-client-core.h index a4ca4e5d..efca7a00 100644 --- a/src/wayland-client-core.h +++ b/src/wayland-client-core.h @@ -302,6 +302,110 @@ void wl_display_set_max_buffer_size(struct wl_display *display, size_t max_buffer_size); +/** + * The message type. + */ +enum wl_client_message_type { + /** The message is a request */ + WL_CLIENT_MESSAGE_REQUEST, + + /** The message is an event */ + WL_CLIENT_MESSAGE_EVENT, +}; + +/** + * The message discard reason codes. + */ +enum wl_client_message_discarded_reason { + /** The message was handled normally, and not discarded. */ + WL_CLIENT_MESSAGE_NOT_DISCARDED = 0, + + /** The target was not alive at dispatch time */ + WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH, + + /** The target had no listener or dispatcher */ + WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH, +}; + +/** + * The structure used to communicate details about an observed message to the + * registered observers. + */ +struct wl_client_observed_message { + /** The target for the message */ + struct wl_proxy *proxy; + + /** The message opcode */ + int message_opcode; + + /** The protocol message structure */ + const struct wl_message *message; + + /** The count of arguments to the message */ + int arguments_count; + + /** The argument array for the messagge */ + const union wl_argument *arguments; + + /** The discard reason code */ + enum wl_client_message_discarded_reason discarded_reason; + + /** + * The discard reason string, or NULL if the event was not discarded. + * + * This string is only for convenience for a observer that does + * logging. The string values should not be considered stable, and + * are not localized. + */ + const char *discarded_reason_str; + + /** + * The queue name, or NULL if the event is not associated with a + * particular queue. + */ + const char *queue_name; +}; + +/** + * The signature for a client message observer function, as registered with + * wl_display_add_client_observer(). + * + * \param user_data \c user_data pointer given when the observer was + * registered with \c wl_display_create_client_observer + * \param type type of message + * \param message details for the message + */ +typedef void (*wl_client_message_observer_func_t)( + void *user_data, enum wl_client_message_type type, + const struct wl_client_observed_message *message); + +/** \class wl_client_observer + * + * \brief Represents a client message observer + * + * A client observer allows the client to observe all request and event + * message traffic to and from the client. For events, the observer is + * also given a discard reason if the event wasn't handled. + * + * The typical use for the observer is to allow the client implementation to + * do its own debug logging, as the default when setting WAYLAND_DEBUG is to + * log to stderr. + * + * Via the client observer interfaces, the client can also enable and disable + * the observer at any time. + * + * The protocol-logger-test.c file has an example of a logger implementation. + */ +struct wl_client_observer; + +struct wl_client_observer * +wl_display_create_client_observer(struct wl_display *display, + wl_client_message_observer_func_t observer, + void *user_data); + +void +wl_client_observer_destroy(struct wl_client_observer *observer); + #ifdef __cplusplus } #endif diff --git a/src/wayland-client.c b/src/wayland-client.c index d88a4725..8377bc44 100644 --- a/src/wayland-client.c +++ b/src/wayland-client.c @@ -109,10 +109,19 @@ struct wl_display { int reader_count; uint32_t read_serial; pthread_cond_t reader_cond; + + struct wl_list observers; }; /** \endcond */ +struct wl_client_observer { + struct wl_list link; + struct wl_display *display; + wl_client_message_observer_func_t func; + void *user_data; +}; + static int debug_client = 0; /** @@ -153,6 +162,28 @@ adjust_closure_args_for_logging(struct wl_closure *closure, bool send) } } +/** + * Maps the \c discard_reason to a string suitable for logging. + * + * \param discarded_reason reason for discard + * \return A string describing the reason, or NULL. + * + */ +static const char * +get_discarded_reason_str( + enum wl_client_message_discarded_reason discarded_reason) +{ + switch (discarded_reason) { + case WL_CLIENT_MESSAGE_NOT_DISCARDED: + return NULL; + case WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH: + return "dead proxy on dispatch"; + case WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH: + return "no listener on dispatch"; + } + return NULL; +} + /** * This function helps log closures from the client, assuming logging is * enabled. @@ -160,16 +191,22 @@ adjust_closure_args_for_logging(struct wl_closure *closure, bool send) * \param closure closure for the message * \param proxy proxy for the message * \param send true if this is closure is for a request - * \param discarded true if this is message is being discarded + * \param discarded_reason reason if the message is being discarded, or + * WL_CLIENT_MESSAGE_NOT_DISCARDED * \param queue_name name for the queue for the message * */ static void closure_log(struct wl_closure *closure, struct wl_proxy *proxy, bool send, - bool discarded, const char *queue_name) + enum wl_client_message_discarded_reason discarded_reason, const char *queue_name) { + struct wl_display *display = proxy->display; + const char *discarded_reason_str; struct wl_closure adjusted_closure = { 0 }; + if (!debug_client && wl_list_empty(&display->observers)) + return; + // Note: The real closure has extra data (referenced by its args // immediately following the structure in memory, but we don't // need to duplicate that. @@ -178,8 +215,31 @@ closure_log(struct wl_closure *closure, struct wl_proxy *proxy, bool send, // Adjust the closure arguments. adjust_closure_args_for_logging(&adjusted_closure, send); - wl_closure_print(&adjusted_closure, &proxy->object, send, - discarded ? "" : NULL, queue_name); + discarded_reason_str = get_discarded_reason_str(discarded_reason); + + if (debug_client) + wl_closure_print(&adjusted_closure, &proxy->object, send, + discarded_reason_str, queue_name); + + if (!wl_list_empty(&display->observers)) { + enum wl_client_message_type type = + send ? WL_CLIENT_MESSAGE_REQUEST + : WL_CLIENT_MESSAGE_EVENT; + struct wl_client_observer *observer; + struct wl_client_observed_message message; + + message.proxy = proxy; + message.message_opcode = adjusted_closure.opcode; + message.message = adjusted_closure.message; + message.arguments_count = adjusted_closure.count; + message.arguments = adjusted_closure.args; + message.discarded_reason = discarded_reason; + message.discarded_reason_str = discarded_reason_str; + message.queue_name = queue_name; + wl_list_for_each(observer, &display->observers, link) { + observer->func(observer->user_data, type, &message); + } + } } /** @@ -965,6 +1025,8 @@ wl_proxy_marshal_array_flags(struct wl_proxy *proxy, uint32_t opcode, struct wl_proxy *new_proxy = NULL; const struct wl_message *message; struct wl_display *disp = proxy->display; + struct wl_event_queue *queue = NULL; + const char *queue_name = NULL; pthread_mutex_lock(&disp->mutex); @@ -990,18 +1052,12 @@ wl_proxy_marshal_array_flags(struct wl_proxy *proxy, uint32_t opcode, goto err_unlock; } - if (debug_client) { - struct wl_event_queue *queue; - const char *queue_name = NULL; + queue = wl_proxy_get_queue(proxy); + if (queue) + queue_name = wl_event_queue_get_name(queue); - queue = wl_proxy_get_queue(proxy); - if (queue) - queue_name = wl_event_queue_get_name(queue); - - closure_log(closure, proxy, true, false, - queue_name); - - } + closure_log(closure, proxy, true, WL_CLIENT_MESSAGE_NOT_DISCARDED, + queue_name); if (wl_closure_send(closure, proxy->display->connection)) { wl_log("Error sending request for %s.%s: %s\n", @@ -1311,6 +1367,7 @@ wl_display_connect_to_fd(int fd) pthread_mutex_init(&display->mutex, NULL); pthread_cond_init(&display->reader_cond, NULL); display->reader_count = 0; + wl_list_init(&display->observers); if (wl_map_insert_at(&display->objects, 0, 0, NULL) == -1) goto err_connection; @@ -1442,6 +1499,7 @@ wl_display_disconnect(struct wl_display *display) free(display->default_queue.name); wl_event_queue_release(&display->display_queue); free(display->display_queue.name); + wl_list_remove(&display->observers); pthread_mutex_destroy(&display->mutex); pthread_cond_destroy(&display->reader_cond); close(display->fd); @@ -1714,30 +1772,32 @@ dispatch_event(struct wl_display *display, struct wl_event_queue *queue) proxy = closure->proxy; proxy_destroyed = !!(proxy->flags & WL_PROXY_FLAG_DESTROYED); - if (debug_client) { - bool discarded = proxy_destroyed || - !(proxy->dispatcher || proxy->object.implementation); - - closure_log(closure, proxy, false, discarded, queue->name); - } - if (proxy_destroyed) { - destroy_queued_closure(closure); - return; - } + closure_log(closure, proxy, false, + WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH, + queue->name); + } else if (proxy->dispatcher) { + closure_log(closure, proxy, false, + WL_CLIENT_MESSAGE_NOT_DISCARDED, queue->name); - pthread_mutex_unlock(&display->mutex); - - if (proxy->dispatcher) { + pthread_mutex_unlock(&display->mutex); wl_closure_dispatch(closure, proxy->dispatcher, &proxy->object, opcode); + pthread_mutex_lock(&display->mutex); } else if (proxy->object.implementation) { + closure_log(closure, proxy, false, + WL_CLIENT_MESSAGE_NOT_DISCARDED, queue->name); + + pthread_mutex_unlock(&display->mutex); wl_closure_invoke(closure, WL_CLOSURE_INVOKE_CLIENT, &proxy->object, opcode, proxy->user_data); + pthread_mutex_lock(&display->mutex); + } else { + closure_log(closure, proxy, false, + WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH, + queue->name); } - pthread_mutex_lock(&display->mutex); - destroy_queued_closure(closure); } @@ -2665,3 +2725,64 @@ wl_log_set_handler_client(wl_log_func_t handler) { wl_log_handler = handler; } + +/** Creates an client message observer. + * + * Note that the observer can potentially start receiving traffic immediately + * after being created, and even before this call returns. + * + * \param display client display to register with + * \param func function to call when client messages are observed + * \param user_data \c user_data pointer to pass to the observer + * + * \return The created observer, or NULL. + * + * \sa wl_client_observer_destroy + * + * \memberof wl_display + */ + +WL_EXPORT struct wl_client_observer * +wl_display_create_client_observer(struct wl_display *display, + wl_client_message_observer_func_t func, + void *user_data) +{ + struct wl_client_observer *observer; + + observer = malloc(sizeof *observer); + if (!observer) + return NULL; + + observer->display = display; + observer->func = func; + observer->user_data = user_data; + + pthread_mutex_lock(&display->mutex); + + wl_list_insert(&display->observers, &observer->link); + + pthread_mutex_unlock(&display->mutex); + + return observer; +} + +/** Destroys a client message obsever. + * + * This function destroys a client message observer, and removes it from the + * display it was added to with \c wl_display_create_client_observer. + * + * \param observer observer to destroy. + * + * \memberof wl_client_observer + */ +WL_EXPORT void +wl_client_observer_destroy(struct wl_client_observer *observer) +{ + pthread_mutex_lock(&observer->display->mutex); + + wl_list_remove(&observer->link); + + pthread_mutex_unlock(&observer->display->mutex); + + free(observer); +} diff --git a/tests/protocol-logger-test.c b/tests/protocol-logger-test.c index a0ebd22a..88f3d83c 100644 --- a/tests/protocol-logger-test.c +++ b/tests/protocol-logger-test.c @@ -29,12 +29,15 @@ #include #include #include +#include #include #include "wayland-client.h" #include "wayland-server.h" #include "test-runner.h" +#define ARRAY_LENGTH(a) (sizeof(a) / sizeof(a)[0]) + /* Ensure the connection doesn't fail due to lack of XDG_RUNTIME_DIR. */ static const char * require_xdg_runtime_dir(void) @@ -45,57 +48,162 @@ require_xdg_runtime_dir(void) return val; } -struct compositor { - struct wl_display *display; - struct wl_event_loop *loop; - int message; - struct wl_client *client; -}; - -struct message { +struct expected_compositor_message { enum wl_protocol_logger_type type; const char *class; int opcode; const char *message_name; int args_count; -} messages[] = { - { - .type = WL_PROTOCOL_LOGGER_REQUEST, - .class = "wl_display", - .opcode = 0, - .message_name = "sync", - .args_count = 1, - }, - { - .type = WL_PROTOCOL_LOGGER_EVENT, - .class = "wl_callback", - .opcode = 0, - .message_name = "done", - .args_count = 1, - }, - { - .type = WL_PROTOCOL_LOGGER_EVENT, - .class = "wl_display", - .opcode = 1, - .message_name = "delete_id", - .args_count = 1, - }, }; +struct compositor { + struct wl_display *display; + struct wl_event_loop *loop; + struct wl_protocol_logger *logger; + + struct expected_compositor_message *expected_msg; + int expected_msg_count; + int actual_msg_count; + struct wl_client *client; +}; + +struct expected_client_message { + enum wl_client_message_type type; + enum wl_client_message_discarded_reason discarded_reason; + const char *queue_name; + const char *class; + int opcode; + const char *message_name; + int args_count; +}; + +struct client { + struct wl_display *display; + struct wl_callback *cb; + struct wl_client_observer *sequence_observer; + + struct expected_client_message *expected_msg; + int expected_msg_count; + int actual_msg_count; +}; + +static int +safe_strcmp(const char *s1, const char *s2) { + if (s1 == NULL && s2 == NULL) + return 0; + if (s1 == NULL && s2 != NULL) + return 1; + if (s1 != NULL && s2 == NULL) + return -1; + return strcmp(s1, s2); +} + +#define ASSERT_LT(arg1, arg2, ...) \ + if (arg1 >= arg2) \ + fprintf(stderr, __VA_ARGS__); \ + assert(arg1 < arg2) + +#define ASSERT_EQ(arg1, arg2, ...) \ + if (arg1 != arg2) \ + fprintf(stderr, __VA_ARGS__); \ + assert(arg1 == arg2) + +#define ASSERT_STR_EQ(arg1, arg2, ...) \ + if (safe_strcmp(arg1, arg2) != 0) \ + fprintf(stderr, __VA_ARGS__); \ + assert(safe_strcmp(arg1, arg2) == 0) + static void -logger_func(void *user_data, enum wl_protocol_logger_type type, - const struct wl_protocol_logger_message *message) +compositor_sequence_observer_func( + void *user_data, enum wl_protocol_logger_type actual_type, + const struct wl_protocol_logger_message *actual_msg) { struct compositor *c = user_data; - struct message *msg = &messages[c->message++]; + struct expected_compositor_message *expected_msg; + int actual_msg_count = c->actual_msg_count++; + char details_msg[256]; - assert(msg->type == type); - assert(strcmp(msg->class, wl_resource_get_class(message->resource)) == 0); - assert(msg->opcode == message->message_opcode); - assert(strcmp(msg->message_name, message->message->name) == 0); - assert(msg->args_count == message->arguments_count); + c->client = wl_resource_get_client(actual_msg->resource); - c->client = wl_resource_get_client(message->resource); + if (!c->expected_msg) + return; + + ASSERT_LT(actual_msg_count, c->expected_msg_count, + "actual count %d exceeds expected count %d\n", + actual_msg_count, c->expected_msg_count); + + expected_msg = &c->expected_msg[actual_msg_count]; + + snprintf(details_msg, sizeof details_msg, + "compositor msg %d of %d actual [%d, '%s', %d, '%s', %d] vs " + "expected [%d, '%s', %d, '%s', %d]\n", + c->actual_msg_count, c->expected_msg_count, actual_type, + wl_resource_get_class(actual_msg->resource), + actual_msg->message_opcode, actual_msg->message->name, + actual_msg->arguments_count, expected_msg->type, + expected_msg->class, expected_msg->opcode, + expected_msg->message_name, expected_msg->args_count); + + ASSERT_EQ(expected_msg->type, actual_type, "type mismatch: %s", + details_msg); + ASSERT_STR_EQ(expected_msg->class, + wl_resource_get_class(actual_msg->resource), + "class mismatch: %s", details_msg); + ASSERT_EQ(expected_msg->opcode, actual_msg->message_opcode, + "opcode mismatch: %s", details_msg); + ASSERT_STR_EQ(expected_msg->message_name, actual_msg->message->name, + "message name mismatch: %s", details_msg); + ASSERT_EQ(expected_msg->args_count, actual_msg->arguments_count, + "arg count mismatch: %s", details_msg); +} + +static void +client_sequence_observer_func( + void *user_data, enum wl_client_message_type actual_type, + const struct wl_client_observed_message *actual_msg) +{ + struct client *c = user_data; + struct expected_client_message *expected_msg; + int actual_msg_count = c->actual_msg_count++; + char details_msg[256]; + + if (!c->expected_msg) + return; + + ASSERT_LT(actual_msg_count, c->expected_msg_count, + "actual count %d exceeds expected count %d\n", + actual_msg_count, c->expected_msg_count); + expected_msg = &c->expected_msg[actual_msg_count]; + + snprintf(details_msg, sizeof details_msg, + "client msg %d of %d actual [%d, %d, '%s', '%s', %d, '%s', %d] vs " + "expected [%d, %d, '%s', '%s', %d, '%s', %d]\n", + c->actual_msg_count, c->expected_msg_count, actual_type, + actual_msg->discarded_reason, + actual_msg->queue_name ? actual_msg->queue_name : "NULL", + wl_proxy_get_class(actual_msg->proxy), + actual_msg->message_opcode, actual_msg->message->name, + actual_msg->arguments_count, expected_msg->type, + expected_msg->discarded_reason, + expected_msg->queue_name ? expected_msg->queue_name : "NULL", + expected_msg->class, expected_msg->opcode, + expected_msg->message_name, expected_msg->args_count); + + ASSERT_EQ(expected_msg->type, actual_type, "type mismatch: %s", + details_msg); + ASSERT_EQ(expected_msg->discarded_reason, actual_msg->discarded_reason, + "discarded reason mismatch: %s", details_msg); + ASSERT_STR_EQ(expected_msg->queue_name, actual_msg->queue_name, + "queue name mismatch: %s", details_msg); + ASSERT_STR_EQ(expected_msg->class, + wl_proxy_get_class(actual_msg->proxy), + "class mismatch: %s", details_msg); + ASSERT_EQ(expected_msg->opcode, actual_msg->message_opcode, + "opcode mismatch: %s", details_msg); + ASSERT_STR_EQ(expected_msg->message_name, actual_msg->message->name, + "message name mismatch: %s", details_msg); + ASSERT_EQ(expected_msg->args_count, actual_msg->arguments_count, + "arg count mismatch: %s", details_msg); } static void @@ -108,41 +216,245 @@ static const struct wl_callback_listener callback_listener = { callback_done, }; +static void +logger_setup(struct compositor *compositor, struct client *client) +{ + const char *socket; + + require_xdg_runtime_dir(); + + compositor->display = wl_display_create(); + compositor->loop = wl_display_get_event_loop(compositor->display); + socket = wl_display_add_socket_auto(compositor->display); + + compositor->logger = wl_display_add_protocol_logger( + compositor->display, compositor_sequence_observer_func, + compositor); + + client->display = wl_display_connect(socket); + client->sequence_observer = wl_display_create_client_observer( + client->display, client_sequence_observer_func, client); +} + +static void +logger_teardown(struct compositor *compositor, struct client *client) +{ + wl_client_observer_destroy(client->sequence_observer); + wl_display_disconnect(client->display); + + wl_client_destroy(compositor->client); + wl_protocol_logger_destroy(compositor->logger); + wl_display_destroy(compositor->display); +} + TEST(logger) { test_set_timeout(1); - const char *socket; + struct expected_compositor_message compositor_messages[] = { + { + .type = WL_PROTOCOL_LOGGER_REQUEST, + .class = "wl_display", + .opcode = 0, + .message_name = "sync", + .args_count = 1, + }, + { + .type = WL_PROTOCOL_LOGGER_EVENT, + .class = "wl_callback", + .opcode = 0, + .message_name = "done", + .args_count = 1, + }, + { + .type = WL_PROTOCOL_LOGGER_EVENT, + .class = "wl_display", + .opcode = 1, + .message_name = "delete_id", + .args_count = 1, + }, + }; + struct expected_client_message client_messages[] = { + { + .type = WL_CLIENT_MESSAGE_REQUEST, + .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, + .queue_name = "Default Queue", + .class = "wl_display", + .opcode = 0, + .message_name = "sync", + .args_count = 1, + }, + { + .type = WL_CLIENT_MESSAGE_EVENT, + .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, + .queue_name = "Display Queue", + .class = "wl_display", + .opcode = 1, + .message_name = "delete_id", + .args_count = 1, + }, + { + .type = WL_CLIENT_MESSAGE_EVENT, + .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, + .queue_name = "Default Queue", + .class = "wl_callback", + .opcode = 0, + .message_name = "done", + .args_count = 1, + }, + }; struct compositor compositor = { 0 }; - struct { - struct wl_display *display; - struct wl_callback *cb; - } client; - struct wl_protocol_logger *logger; + struct client client = { 0 }; - require_xdg_runtime_dir(); + logger_setup(&compositor, &client); - compositor.display = wl_display_create(); - compositor.loop = wl_display_get_event_loop(compositor.display); - socket = wl_display_add_socket_auto(compositor.display); + compositor.expected_msg = &compositor_messages[0]; + compositor.expected_msg_count = ARRAY_LENGTH(compositor_messages); - logger = wl_display_add_protocol_logger(compositor.display, - logger_func, &compositor); + client.expected_msg = &client_messages[0]; + client.expected_msg_count = ARRAY_LENGTH(client_messages); - client.display = wl_display_connect(socket); client.cb = wl_display_sync(client.display); wl_callback_add_listener(client.cb, &callback_listener, NULL); wl_display_flush(client.display); - while (compositor.message < 3) { + while (compositor.actual_msg_count < compositor.expected_msg_count) { wl_event_loop_dispatch(compositor.loop, -1); wl_display_flush_clients(compositor.display); } - wl_display_dispatch(client.display); - wl_display_disconnect(client.display); + while (client.actual_msg_count < client.expected_msg_count) { + wl_display_dispatch(client.display); + } - wl_client_destroy(compositor.client); - wl_protocol_logger_destroy(logger); - wl_display_destroy(compositor.display); + logger_teardown(&compositor, &client); +} + +TEST(client_discards_if_dead_on_dispatch) +{ + test_set_timeout(1); + + struct expected_client_message client_messages[] = { + { + .type = WL_CLIENT_MESSAGE_REQUEST, + .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, + .queue_name = "Default Queue", + .class = "wl_display", + .opcode = 0, + .message_name = "sync", + .args_count = 1, + }, + { + .type = WL_CLIENT_MESSAGE_EVENT, + .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, + .queue_name = "Display Queue", + .class = "wl_display", + .opcode = 1, + .message_name = "delete_id", + .args_count = 1, + }, + { + .type = WL_CLIENT_MESSAGE_EVENT, + .discarded_reason = + WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH, + .queue_name = "Default Queue", + .class = "wl_callback", + .opcode = 0, + .message_name = "done", + .args_count = 1, + }, + }; + struct compositor compositor = { 0 }; + struct client client = { 0 }; + + logger_setup(&compositor, &client); + + compositor.expected_msg_count = 3; + + client.expected_msg = &client_messages[0]; + client.expected_msg_count = ARRAY_LENGTH(client_messages); + + client.cb = wl_display_sync(client.display); + wl_callback_add_listener(client.cb, &callback_listener, NULL); + wl_display_flush(client.display); + + while (compositor.actual_msg_count < compositor.expected_msg_count) { + wl_event_loop_dispatch(compositor.loop, -1); + wl_display_flush_clients(compositor.display); + } + + wl_display_prepare_read(client.display); + wl_display_read_events(client.display); + + // To get a WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH, we + // destroy the callback after reading client events, but before + // dispatching them. + wl_callback_destroy(client.cb); + + while (client.actual_msg_count < client.expected_msg_count) { + wl_display_dispatch(client.display); + } + + logger_teardown(&compositor, &client); +} + +TEST(client_discards_if_no_listener_on_dispatch) +{ + test_set_timeout(1); + + struct expected_client_message client_messages[] = { + { + .type = WL_CLIENT_MESSAGE_REQUEST, + .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, + .queue_name = "Default Queue", + .class = "wl_display", + .opcode = 0, + .message_name = "sync", + .args_count = 1, + }, + { + .type = WL_CLIENT_MESSAGE_EVENT, + .discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED, + .queue_name = "Display Queue", + .class = "wl_display", + .opcode = 1, + .message_name = "delete_id", + .args_count = 1, + }, + { + .type = WL_CLIENT_MESSAGE_EVENT, + .discarded_reason = + WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH, + .queue_name = "Default Queue", + .class = "wl_callback", + .opcode = 0, + .message_name = "done", + .args_count = 1, + }, + }; + struct compositor compositor = { 0 }; + struct client client = { 0 }; + + logger_setup(&compositor, &client); + + compositor.expected_msg_count = 3; + + client.expected_msg = &client_messages[0]; + client.expected_msg_count = ARRAY_LENGTH(client_messages); + + client.cb = wl_display_sync(client.display); + wl_display_flush(client.display); + + while (compositor.actual_msg_count < compositor.expected_msg_count) { + wl_event_loop_dispatch(compositor.loop, -1); + wl_display_flush_clients(compositor.display); + } + + while (client.actual_msg_count < client.expected_msg_count) { + wl_display_dispatch(client.display); + } + + wl_callback_destroy(client.cb); + + logger_teardown(&compositor, &client); }