xref: /aosp_15_r20/external/wayland/patches/0002-client-Add-message-observer-interface.diff (revision 84e872a0dc482bffdb63672969dd03a827d67c73)
1From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2From: Lloyd Pique <[email protected]>
3Date: Thu, 10 Mar 2022 17:44:32 -0800
4Subject: [PATCH 2/6] client: Add message observer interface
5
6Client message observers 2/6
7
8Introduce a client message observer interface, strongly resembling the server
9protocol logger interface added in commit 450f06e2.
10
11This means a new pair of public API functions:
12
13* wl_display_create_client_observer(): allows a client to register an observer
14  function, which is called for messages that are received or sent.
15
16* wl_client_observer_destroy() which destroys the observer created by the prior
17  function.
18
19With these changes, a client can set and clear an observer at run-time, and can
20use it to log client messages to a location other than stderr.
21
22The existing protocol-logger-test has also been revised and extended to demonstrate
23using the new API for test use, to validate the sequence of messages sent and
24received by the client, on top of the existing checks to do the same for the
25server messages.
26
27Signed-off-by: Lloyd Pique <[email protected]>
28
29diff --git a/src/wayland-client-core.h b/src/wayland-client-core.h
30index ce91a6f..2aa72a4 100644
31--- a/src/wayland-client-core.h
32+++ b/src/wayland-client-core.h
33@@ -285,6 +285,104 @@ wl_display_read_events(struct wl_display *display);
34 void
35 wl_log_set_handler_client(wl_log_func_t handler);
36
37+/**
38+ * The message type.
39+ */
40+enum wl_client_message_type {
41+	/** The message is a request */
42+	WL_CLIENT_MESSAGE_REQUEST,
43+
44+	/** The message is an event */
45+	WL_CLIENT_MESSAGE_EVENT,
46+};
47+
48+/**
49+ * The message discard reason codes.
50+ */
51+enum wl_client_message_discarded_reason {
52+	/** The message was handled normally, and not discarded. */
53+	WL_CLIENT_MESSAGE_NOT_DISCARDED = 0,
54+
55+	/** The target was not alive at dispatch time */
56+	WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH,
57+
58+	/** The target had no listener or dispatcher */
59+	WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH,
60+};
61+
62+/**
63+ * The structure used to communicate details about an observed message to the
64+ * registered observers.
65+ */
66+struct wl_client_observed_message {
67+	/** The target for the message */
68+	struct wl_proxy *proxy;
69+
70+	/** The message opcode */
71+	int message_opcode;
72+
73+	/** The protocol message structure */
74+	const struct wl_message *message;
75+
76+	/** The count of arguments to the message */
77+	int arguments_count;
78+
79+	/** The argument array for the messagge */
80+	const union wl_argument *arguments;
81+
82+	/** The discard reason code */
83+	enum wl_client_message_discarded_reason discarded_reason;
84+
85+	/**
86+	 * The discard reason string, or NULL if the event was not discarded.
87+	 *
88+	 * This string is only for convenience for a observer that does
89+	 * logging. The string values should not be considered stable, and
90+	 * are not localized.
91+	 */
92+	const char *discarded_reason_str;
93+};
94+
95+/**
96+ * The signature for a client message observer function, as registered with
97+ * wl_display_add_client_observer().
98+ *
99+ * \param user_data \c user_data pointer given when the observer was
100+ *                  registered with \c wl_display_create_client_observer
101+ * \param type      type of message
102+ * \param message   details for the message
103+ */
104+typedef void (*wl_client_message_observer_func_t)(
105+	void *user_data, enum wl_client_message_type type,
106+	const struct wl_client_observed_message *message);
107+
108+/** \class wl_client_observer
109+ *
110+ * \brief Represents a client message observer
111+ *
112+ * A client observer allows the client to observe all request and event
113+ * message traffic to and from the client. For events, the observer is
114+ * also given a discard reason if the event wasn't handled.
115+ *
116+ * The typical use for the observer is to allow the client implementation to
117+ * do its own debug logging, as the default when setting WAYLAND_DEBUG is to
118+ * log to stderr.
119+ *
120+ * With this runtime call, the client can also enable and disable the observer
121+ * at any time.
122+ *
123+ * The protocol-logger-test.c file has an example of a logger implementation.
124+ */
125+struct wl_client_observer;
126+
127+struct wl_client_observer *
128+wl_display_create_client_observer(struct wl_display *display,
129+				  wl_client_message_observer_func_t observer,
130+				  void *user_data);
131+
132+void
133+wl_client_observer_destroy(struct wl_client_observer *observer);
134+
135 #ifdef  __cplusplus
136 }
137 #endif
138diff --git a/src/wayland-client.c b/src/wayland-client.c
139index ae47307..04b4f60 100644
140--- a/src/wayland-client.c
141+++ b/src/wayland-client.c
142@@ -109,10 +109,19 @@ struct wl_display {
143 	int reader_count;
144 	uint32_t read_serial;
145 	pthread_cond_t reader_cond;
146+
147+	struct wl_list observers;
148 };
149
150 /** \endcond */
151
152+struct wl_client_observer {
153+	struct wl_list link;
154+	struct wl_display *display;
155+	wl_client_message_observer_func_t func;
156+	void *user_data;
157+};
158+
159 static int debug_client = 0;
160
161 /**
162@@ -151,6 +160,28 @@ adjust_closure_args_for_logging(struct wl_closure *closure, bool send)
163 	}
164 }
165
166+/**
167+ * Maps the \c discard_reason to a string suitable for logging.
168+ *
169+ * \param discarded_reason  reason for discard
170+ * \return A string describing the reason, or NULL.
171+ *
172+ */
173+static const char *
174+get_discarded_reason_str(
175+	enum wl_client_message_discarded_reason discarded_reason)
176+{
177+	switch (discarded_reason) {
178+	case WL_CLIENT_MESSAGE_NOT_DISCARDED:
179+		return NULL;
180+	case WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH:
181+		return "dead proxy on dispatch";
182+	case WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH:
183+		return "no listener on dispatch";
184+	}
185+	return NULL;
186+}
187+
188 /**
189  * This function helps log closures from the client, assuming logging is
190  * enabled.
191@@ -158,16 +189,18 @@ adjust_closure_args_for_logging(struct wl_closure *closure, bool send)
192  * \param closure    closure for the message
193  * \param proxy      proxy for the message
194  * \param send       true if this is closure is for a request
195- * \param discarded  true if this is message is being discarded
196- *
197+ * \param discarded_reason  reason if the message is being discarded, or
198+ *                          WL_CLIENT_MESSAGE_NOT_DISCARDED
199  */
200 static void
201 closure_log(struct wl_closure *closure, struct wl_proxy *proxy, bool send,
202-	    bool discarded)
203+	    enum wl_client_message_discarded_reason discarded_reason)
204 {
205+	struct wl_display *display = proxy->display;
206+	const char *discarded_reason_str;
207 	struct wl_closure adjusted_closure = { 0 };
208
209-	if (!debug_client)
210+	if (!debug_client && wl_list_empty(&display->observers))
211 		return;
212
213 	// Note: The real closure has extra data (referenced by its args
214@@ -178,8 +211,30 @@ closure_log(struct wl_closure *closure, struct wl_proxy *proxy, bool send,
215 	// Adjust the closure arguments.
216 	adjust_closure_args_for_logging(&adjusted_closure, send);
217
218-	wl_closure_print(&adjusted_closure, &proxy->object, send,
219-			 discarded ? "" : NULL);
220+	discarded_reason_str = get_discarded_reason_str(discarded_reason);
221+
222+	if (debug_client)
223+		wl_closure_print(&adjusted_closure, &proxy->object, send,
224+				 discarded_reason_str);
225+
226+	if (!wl_list_empty(&display->observers)) {
227+		enum wl_client_message_type type =
228+			send ? WL_CLIENT_MESSAGE_REQUEST
229+			     : WL_CLIENT_MESSAGE_EVENT;
230+		struct wl_client_observer *observer;
231+		struct wl_client_observed_message message;
232+
233+		message.proxy = proxy;
234+		message.message_opcode = adjusted_closure.opcode;
235+		message.message = adjusted_closure.message;
236+		message.arguments_count = adjusted_closure.count;
237+		message.arguments = adjusted_closure.args;
238+		message.discarded_reason = discarded_reason;
239+		message.discarded_reason_str = discarded_reason_str;
240+		wl_list_for_each(observer, &display->observers, link) {
241+			observer->func(observer->user_data, type, &message);
242+		}
243+	}
244 }
245
246 /**
247@@ -952,7 +1007,7 @@ wl_proxy_marshal_array_flags(struct wl_proxy *proxy, uint32_t opcode,
248 		goto err_unlock;
249 	}
250
251-	closure_log(closure, proxy, true, false);
252+	closure_log(closure, proxy, true, WL_CLIENT_MESSAGE_NOT_DISCARDED);
253
254 	if (wl_closure_send(closure, proxy->display->connection)) {
255 		wl_log("Error sending request: %s\n", strerror(errno));
256@@ -1259,6 +1314,7 @@ wl_display_connect_to_fd(int fd)
257 	pthread_mutex_init(&display->mutex, NULL);
258 	pthread_cond_init(&display->reader_cond, NULL);
259 	display->reader_count = 0;
260+	wl_list_init(&display->observers);
261
262 	if (wl_map_insert_at(&display->objects, 0, 0, NULL) == -1)
263 		goto err_connection;
264@@ -1388,6 +1444,7 @@ wl_display_disconnect(struct wl_display *display)
265 	wl_map_release(&display->objects);
266 	wl_event_queue_release(&display->default_queue);
267 	wl_event_queue_release(&display->display_queue);
268+	wl_list_remove(&display->observers);
269 	pthread_mutex_destroy(&display->mutex);
270 	pthread_cond_destroy(&display->reader_cond);
271 	close(display->fd);
272@@ -1663,25 +1720,29 @@ dispatch_event(struct wl_display *display, struct wl_event_queue *queue)
273 	proxy = closure->proxy;
274 	proxy_destroyed = !!(proxy->flags & WL_PROXY_FLAG_DESTROYED);
275 	if (proxy_destroyed) {
276-		closure_log(closure, proxy, false, true);
277-		destroy_queued_closure(closure);
278-		return;
279-	}
280-
281-	pthread_mutex_unlock(&display->mutex);
282+		closure_log(closure, proxy, false,
283+			    WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH);
284+	} else if (proxy->dispatcher) {
285+		closure_log(closure, proxy, false,
286+			    WL_CLIENT_MESSAGE_NOT_DISCARDED);
287
288-	if (proxy->dispatcher) {
289-		closure_log(closure, proxy, false, false);
290+		pthread_mutex_unlock(&display->mutex);
291 		wl_closure_dispatch(closure, proxy->dispatcher,
292 				    &proxy->object, opcode);
293+		pthread_mutex_lock(&display->mutex);
294 	} else if (proxy->object.implementation) {
295-		closure_log(closure, proxy, false, false);
296+		closure_log(closure, proxy, false,
297+			    WL_CLIENT_MESSAGE_NOT_DISCARDED);
298+
299+		pthread_mutex_unlock(&display->mutex);
300 		wl_closure_invoke(closure, WL_CLOSURE_INVOKE_CLIENT,
301 				  &proxy->object, opcode, proxy->user_data);
302+		pthread_mutex_lock(&display->mutex);
303+	} else {
304+		closure_log(closure, proxy, false,
305+			    WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH);
306 	}
307
308-	pthread_mutex_lock(&display->mutex);
309-
310 	destroy_queued_closure(closure);
311 }
312
313@@ -2538,3 +2599,64 @@ wl_log_set_handler_client(wl_log_func_t handler)
314 {
315 	wl_log_handler = handler;
316 }
317+
318+/** Creates an client message observer.
319+ *
320+ * Note that the observer can potentially start receiving traffic immediately
321+ * after being created, and even before this call returns.
322+ *
323+ * \param display    client display to register with
324+ * \param func       function to call when client messages are observed
325+ * \param user_data  \c user_data pointer to pass to the observer
326+ *
327+ * \return The created observer, or NULL.
328+ *
329+ * \sa wl_client_observer_destroy
330+ *
331+ * \memberof wl_display
332+ */
333+
334+WL_EXPORT struct wl_client_observer *
335+wl_display_create_client_observer(struct wl_display *display,
336+				  wl_client_message_observer_func_t func,
337+				  void *user_data)
338+{
339+	struct wl_client_observer *observer;
340+
341+	observer = malloc(sizeof *observer);
342+	if (!observer)
343+		return NULL;
344+
345+	observer->display = display;
346+	observer->func = func;
347+	observer->user_data = user_data;
348+
349+	pthread_mutex_lock(&display->mutex);
350+
351+	wl_list_insert(&display->observers, &observer->link);
352+
353+	pthread_mutex_unlock(&display->mutex);
354+
355+	return observer;
356+}
357+
358+/** Destroys a client message obsever.
359+ *
360+ * This function destroys a client message observer, and removes it from the
361+ * display it was added to with \c wl_display_create_client_observer.
362+ *
363+ * \param observer observer to destroy.
364+ *
365+ * \memberof wl_client_observer
366+ */
367+WL_EXPORT void
368+wl_client_observer_destroy(struct wl_client_observer *observer)
369+{
370+	pthread_mutex_lock(&observer->display->mutex);
371+
372+	wl_list_remove(&observer->link);
373+
374+	pthread_mutex_unlock(&observer->display->mutex);
375+
376+	free(observer);
377+}
378diff --git a/tests/protocol-logger-test.c b/tests/protocol-logger-test.c
379index a0ebd22..3b9dc3e 100644
380--- a/tests/protocol-logger-test.c
381+++ b/tests/protocol-logger-test.c
382@@ -29,12 +29,15 @@
383 #include <string.h>
384 #include <stdio.h>
385 #include <sys/un.h>
386+#include <time.h>
387 #include <unistd.h>
388
389 #include "wayland-client.h"
390 #include "wayland-server.h"
391 #include "test-runner.h"
392
393+#define ARRAY_LENGTH(a) (sizeof(a) / sizeof(a)[0])
394+
395 /* Ensure the connection doesn't fail due to lack of XDG_RUNTIME_DIR. */
396 static const char *
397 require_xdg_runtime_dir(void)
398@@ -45,57 +48,146 @@ require_xdg_runtime_dir(void)
399 	return val;
400 }
401
402+struct expected_compositor_message {
403+	enum wl_protocol_logger_type type;
404+	const char *class;
405+	int opcode;
406+	const char *message_name;
407+	int args_count;
408+};
409+
410 struct compositor {
411 	struct wl_display *display;
412 	struct wl_event_loop *loop;
413-	int message;
414+	struct wl_protocol_logger *logger;
415+
416+	struct expected_compositor_message *expected_msg;
417+	int expected_msg_count;
418+	int actual_msg_count;
419 	struct wl_client *client;
420 };
421
422-struct message {
423-	enum wl_protocol_logger_type type;
424+struct expected_client_message {
425+	enum wl_client_message_type type;
426+	enum wl_client_message_discarded_reason discarded_reason;
427 	const char *class;
428 	int opcode;
429 	const char *message_name;
430 	int args_count;
431-} messages[] = {
432-	{
433-		.type = WL_PROTOCOL_LOGGER_REQUEST,
434-		.class = "wl_display",
435-		.opcode = 0,
436-		.message_name = "sync",
437-		.args_count = 1,
438-	},
439-	{
440-		.type = WL_PROTOCOL_LOGGER_EVENT,
441-		.class = "wl_callback",
442-		.opcode = 0,
443-		.message_name = "done",
444-		.args_count = 1,
445-	},
446-	{
447-		.type = WL_PROTOCOL_LOGGER_EVENT,
448-		.class = "wl_display",
449-		.opcode = 1,
450-		.message_name = "delete_id",
451-		.args_count = 1,
452-	},
453 };
454
455+struct client {
456+	struct wl_display *display;
457+	struct wl_callback *cb;
458+	struct wl_client_observer *sequence_observer;
459+
460+	struct expected_client_message *expected_msg;
461+	int expected_msg_count;
462+	int actual_msg_count;
463+};
464+
465+#define ASSERT_LT(arg1, arg2, ...)                                            \
466+	if (arg1 >= arg2)                                                     \
467+		fprintf(stderr, __VA_ARGS__);                                 \
468+	assert(arg1 < arg2)
469+
470+#define ASSERT_EQ(arg1, arg2, ...)                                            \
471+	if (arg1 != arg2)                                                     \
472+		fprintf(stderr, __VA_ARGS__);                                 \
473+	assert(arg1 == arg2)
474+
475+#define ASSERT_STR_EQ(arg1, arg2, ...)                                        \
476+	if (strcmp(arg1, arg2) != 0)                                          \
477+		fprintf(stderr, __VA_ARGS__);                                 \
478+	assert(strcmp(arg1, arg2) == 0)
479+
480 static void
481-logger_func(void *user_data, enum wl_protocol_logger_type type,
482-	    const struct wl_protocol_logger_message *message)
483+compositor_sequence_observer_func(
484+	void *user_data, enum wl_protocol_logger_type actual_type,
485+	const struct wl_protocol_logger_message *actual_msg)
486 {
487 	struct compositor *c = user_data;
488-	struct message *msg = &messages[c->message++];
489+	struct expected_compositor_message *expected_msg;
490+	int actual_msg_count = c->actual_msg_count++;
491+	char details_msg[256];
492+
493+	c->client = wl_resource_get_client(actual_msg->resource);
494+
495+	if (!c->expected_msg)
496+		return;
497+
498+	ASSERT_LT(actual_msg_count, c->expected_msg_count,
499+		  "actual count %d exceeds expected count %d\n",
500+		  actual_msg_count, c->expected_msg_count);
501+
502+	expected_msg = &c->expected_msg[actual_msg_count];
503+
504+	snprintf(details_msg, sizeof details_msg,
505+		 "compositor msg %d of %d actual [%d, '%s', %d, '%s', %d] vs "
506+		 "expected [%d, '%s', %d, '%s', %d]\n",
507+		 c->actual_msg_count, c->expected_msg_count, actual_type,
508+		 wl_resource_get_class(actual_msg->resource),
509+		 actual_msg->message_opcode, actual_msg->message->name,
510+		 actual_msg->arguments_count, expected_msg->type,
511+		 expected_msg->class, expected_msg->opcode,
512+		 expected_msg->message_name, expected_msg->args_count);
513
514-	assert(msg->type == type);
515-	assert(strcmp(msg->class, wl_resource_get_class(message->resource)) == 0);
516-	assert(msg->opcode == message->message_opcode);
517-	assert(strcmp(msg->message_name, message->message->name) == 0);
518-	assert(msg->args_count == message->arguments_count);
519+	ASSERT_EQ(expected_msg->type, actual_type, "type mismatch: %s",
520+		  details_msg);
521+	ASSERT_STR_EQ(expected_msg->class,
522+		      wl_resource_get_class(actual_msg->resource),
523+		      "class mismatch: %s", details_msg);
524+	ASSERT_EQ(expected_msg->opcode, actual_msg->message_opcode,
525+		  "opcode mismatch: %s", details_msg);
526+	ASSERT_STR_EQ(expected_msg->message_name, actual_msg->message->name,
527+		      "message name mismatch: %s", details_msg);
528+	ASSERT_EQ(expected_msg->args_count, actual_msg->arguments_count,
529+		  "arg count mismatch: %s", details_msg);
530+}
531+
532+static void
533+client_sequence_observer_func(
534+	void *user_data, enum wl_client_message_type actual_type,
535+	const struct wl_client_observed_message *actual_msg)
536+{
537+	struct client *c = user_data;
538+	struct expected_client_message *expected_msg;
539+	int actual_msg_count = c->actual_msg_count++;
540+	char details_msg[256];
541+
542+	if (!c->expected_msg)
543+		return;
544+
545+	ASSERT_LT(actual_msg_count, c->expected_msg_count,
546+		  "actual count %d exceeds expected count %d\n",
547+		  actual_msg_count, c->expected_msg_count);
548+	expected_msg = &c->expected_msg[actual_msg_count];
549
550-	c->client = wl_resource_get_client(message->resource);
551+	snprintf(details_msg, sizeof details_msg,
552+		 "client msg %d of %d actual [%d, %d, '%s', %d, '%s', %d] vs "
553+		 "expected [%d, %d, '%s', %d, '%s', %d]\n",
554+		 c->actual_msg_count, c->expected_msg_count, actual_type,
555+		 actual_msg->discarded_reason,
556+		 wl_proxy_get_class(actual_msg->proxy),
557+		 actual_msg->message_opcode, actual_msg->message->name,
558+		 actual_msg->arguments_count, expected_msg->type,
559+		 expected_msg->discarded_reason, expected_msg->class,
560+		 expected_msg->opcode, expected_msg->message_name,
561+		 expected_msg->args_count);
562+
563+	ASSERT_EQ(expected_msg->type, actual_type, "type mismatch: %s",
564+		  details_msg);
565+	ASSERT_EQ(expected_msg->discarded_reason, actual_msg->discarded_reason,
566+		  "discarded reason mismatch: %s", details_msg);
567+	ASSERT_STR_EQ(expected_msg->class,
568+		      wl_proxy_get_class(actual_msg->proxy),
569+		      "class mismatch: %s", details_msg);
570+	ASSERT_EQ(expected_msg->opcode, actual_msg->message_opcode,
571+		  "opcode mismatch: %s", details_msg);
572+	ASSERT_STR_EQ(expected_msg->message_name, actual_msg->message->name,
573+		      "message name mismatch: %s", details_msg);
574+	ASSERT_EQ(expected_msg->args_count, actual_msg->arguments_count,
575+		  "arg count mismatch: %s", details_msg);
576 }
577
578 static void
579@@ -108,41 +200,236 @@ static const struct wl_callback_listener callback_listener = {
580 	callback_done,
581 };
582
583+static void
584+logger_setup(struct compositor *compositor, struct client *client)
585+{
586+	const char *socket;
587+
588+	require_xdg_runtime_dir();
589+
590+	compositor->display = wl_display_create();
591+	compositor->loop = wl_display_get_event_loop(compositor->display);
592+	socket = wl_display_add_socket_auto(compositor->display);
593+
594+	compositor->logger = wl_display_add_protocol_logger(
595+		compositor->display, compositor_sequence_observer_func,
596+		compositor);
597+
598+	client->display = wl_display_connect(socket);
599+	client->sequence_observer = wl_display_create_client_observer(
600+		client->display, client_sequence_observer_func, client);
601+}
602+
603+static void
604+logger_teardown(struct compositor *compositor, struct client *client)
605+{
606+	wl_client_observer_destroy(client->sequence_observer);
607+	wl_display_disconnect(client->display);
608+
609+	wl_client_destroy(compositor->client);
610+	wl_protocol_logger_destroy(compositor->logger);
611+	wl_display_destroy(compositor->display);
612+}
613+
614 TEST(logger)
615 {
616 	test_set_timeout(1);
617
618-	const char *socket;
619+	struct expected_compositor_message compositor_messages[] = {
620+		{
621+			.type = WL_PROTOCOL_LOGGER_REQUEST,
622+			.class = "wl_display",
623+			.opcode = 0,
624+			.message_name = "sync",
625+			.args_count = 1,
626+		},
627+		{
628+			.type = WL_PROTOCOL_LOGGER_EVENT,
629+			.class = "wl_callback",
630+			.opcode = 0,
631+			.message_name = "done",
632+			.args_count = 1,
633+		},
634+		{
635+			.type = WL_PROTOCOL_LOGGER_EVENT,
636+			.class = "wl_display",
637+			.opcode = 1,
638+			.message_name = "delete_id",
639+			.args_count = 1,
640+		},
641+	};
642+	struct expected_client_message client_messages[] = {
643+		{
644+			.type = WL_CLIENT_MESSAGE_REQUEST,
645+			.discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED,
646+			.class = "wl_display",
647+			.opcode = 0,
648+			.message_name = "sync",
649+			.args_count = 1,
650+		},
651+		{
652+			.type = WL_CLIENT_MESSAGE_EVENT,
653+			.discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED,
654+			.class = "wl_display",
655+			.opcode = 1,
656+			.message_name = "delete_id",
657+			.args_count = 1,
658+		},
659+		{
660+			.type = WL_CLIENT_MESSAGE_EVENT,
661+			.discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED,
662+			.class = "wl_callback",
663+			.opcode = 0,
664+			.message_name = "done",
665+			.args_count = 1,
666+		},
667+	};
668 	struct compositor compositor = { 0 };
669-	struct {
670-		struct wl_display *display;
671-		struct wl_callback *cb;
672-	} client;
673-	struct wl_protocol_logger *logger;
674+	struct client client = { 0 };
675
676-	require_xdg_runtime_dir();
677+	logger_setup(&compositor, &client);
678
679-	compositor.display = wl_display_create();
680-	compositor.loop = wl_display_get_event_loop(compositor.display);
681-	socket = wl_display_add_socket_auto(compositor.display);
682+	compositor.expected_msg = &compositor_messages[0];
683+	compositor.expected_msg_count = ARRAY_LENGTH(compositor_messages);
684
685-	logger = wl_display_add_protocol_logger(compositor.display,
686-						logger_func, &compositor);
687+	client.expected_msg = &client_messages[0];
688+	client.expected_msg_count = ARRAY_LENGTH(client_messages);
689
690-	client.display = wl_display_connect(socket);
691 	client.cb = wl_display_sync(client.display);
692 	wl_callback_add_listener(client.cb, &callback_listener, NULL);
693 	wl_display_flush(client.display);
694
695-	while (compositor.message < 3) {
696+	while (compositor.actual_msg_count < compositor.expected_msg_count) {
697 		wl_event_loop_dispatch(compositor.loop, -1);
698 		wl_display_flush_clients(compositor.display);
699 	}
700
701-	wl_display_dispatch(client.display);
702-	wl_display_disconnect(client.display);
703+	while (client.actual_msg_count < client.expected_msg_count) {
704+		wl_display_dispatch(client.display);
705+	}
706+
707+	logger_teardown(&compositor, &client);
708+}
709+
710+TEST(client_discards_if_dead_on_dispatch)
711+{
712+	test_set_timeout(1);
713+
714+	struct expected_client_message client_messages[] = {
715+		{
716+			.type = WL_CLIENT_MESSAGE_REQUEST,
717+			.discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED,
718+			.class = "wl_display",
719+			.opcode = 0,
720+			.message_name = "sync",
721+			.args_count = 1,
722+		},
723+		{
724+			.type = WL_CLIENT_MESSAGE_EVENT,
725+			.discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED,
726+			.class = "wl_display",
727+			.opcode = 1,
728+			.message_name = "delete_id",
729+			.args_count = 1,
730+		},
731+		{
732+			.type = WL_CLIENT_MESSAGE_EVENT,
733+			.discarded_reason =
734+				WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH,
735+			.class = "wl_callback",
736+			.opcode = 0,
737+			.message_name = "done",
738+			.args_count = 1,
739+		},
740+	};
741+	struct compositor compositor = { 0 };
742+	struct client client = { 0 };
743+
744+	logger_setup(&compositor, &client);
745+
746+	compositor.expected_msg_count = 3;
747+
748+	client.expected_msg = &client_messages[0];
749+	client.expected_msg_count = ARRAY_LENGTH(client_messages);
750+
751+	client.cb = wl_display_sync(client.display);
752+	wl_callback_add_listener(client.cb, &callback_listener, NULL);
753+	wl_display_flush(client.display);
754+
755+	while (compositor.actual_msg_count < compositor.expected_msg_count) {
756+		wl_event_loop_dispatch(compositor.loop, -1);
757+		wl_display_flush_clients(compositor.display);
758+	}
759+
760+	wl_display_prepare_read(client.display);
761+	wl_display_read_events(client.display);
762+
763+	// To get a WL_CLIENT_MESSAGE_DISCARD_DEAD_PROXY_ON_DISPATCH, we
764+	// destroy the callback after reading client events, but before
765+	// dispatching them.
766+	wl_callback_destroy(client.cb);
767+
768+	while (client.actual_msg_count < client.expected_msg_count) {
769+		wl_display_dispatch(client.display);
770+	}
771+
772+	logger_teardown(&compositor, &client);
773+}
774+
775+TEST(client_discards_if_no_listener_on_dispatch)
776+{
777+	test_set_timeout(1);
778+
779+	struct expected_client_message client_messages[] = {
780+		{
781+			.type = WL_CLIENT_MESSAGE_REQUEST,
782+			.discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED,
783+			.class = "wl_display",
784+			.opcode = 0,
785+			.message_name = "sync",
786+			.args_count = 1,
787+		},
788+		{
789+			.type = WL_CLIENT_MESSAGE_EVENT,
790+			.discarded_reason = WL_CLIENT_MESSAGE_NOT_DISCARDED,
791+			.class = "wl_display",
792+			.opcode = 1,
793+			.message_name = "delete_id",
794+			.args_count = 1,
795+		},
796+		{
797+			.type = WL_CLIENT_MESSAGE_EVENT,
798+			.discarded_reason =
799+				WL_CLIENT_MESSAGE_DISCARD_NO_LISTENER_ON_DISPATCH,
800+			.class = "wl_callback",
801+			.opcode = 0,
802+			.message_name = "done",
803+			.args_count = 1,
804+		},
805+	};
806+	struct compositor compositor = { 0 };
807+	struct client client = { 0 };
808+
809+	logger_setup(&compositor, &client);
810+
811+	compositor.expected_msg_count = 3;
812+
813+	client.expected_msg = &client_messages[0];
814+	client.expected_msg_count = ARRAY_LENGTH(client_messages);
815+
816+	client.cb = wl_display_sync(client.display);
817+	wl_display_flush(client.display);
818+
819+	while (compositor.actual_msg_count < compositor.expected_msg_count) {
820+		wl_event_loop_dispatch(compositor.loop, -1);
821+		wl_display_flush_clients(compositor.display);
822+	}
823+
824+	while (client.actual_msg_count < client.expected_msg_count) {
825+		wl_display_dispatch(client.display);
826+	}
827+
828+	wl_callback_destroy(client.cb);
829
830-	wl_client_destroy(compositor.client);
831-	wl_protocol_logger_destroy(logger);
832-	wl_display_destroy(compositor.display);
833+	logger_teardown(&compositor, &client);
834 }
835