[Xcb] [PATCH RFC 1/2] Add some tests for thread hangs
Uli Schlachter
psychon at znc.in
Thu Apr 10 07:25:15 PDT 2014
This requires a working X server and thus is more of an integration test than a
unit test. And having threads in unit tests is ugly anyway since they are
supposed to be reproducible.
Thus:
NOT-Signed-Off-by!
(For what the result is: Once the thread_break_connection() got the connection
into an error state, all threads but one exit. The one thread is
thread_wait_for_event() and it is stuck in xcb_wait_for_event())
---
tests/Makefile.am | 2 +-
tests/check_all.c | 1 +
tests/check_suites.h | 1 +
tests/check_threads.c | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 207 insertions(+), 1 deletion(-)
create mode 100644 tests/check_threads.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index ceef722..eb2db92 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -10,7 +10,7 @@ LDADD = @CHECK_LIBS@ $(top_builddir)/src/libxcb.la
if HAVE_CHECK
TESTS = check_all
check_PROGRAMS = check_all
-check_all_SOURCES = check_all.c check_suites.h check_public.c
+check_all_SOURCES = check_all.c check_suites.h check_public.c check_threads.c
check-local: check-TESTS
$(RM) CheckLog.html
diff --git a/tests/check_all.c b/tests/check_all.c
index 4393422..314e5dc 100644
--- a/tests/check_all.c
+++ b/tests/check_all.c
@@ -12,6 +12,7 @@ int main(void)
{
int nf;
SRunner *sr = srunner_create(public_suite());
+ srunner_add_suite(sr, thread_suite());
srunner_set_xml(sr, "CheckLog_xcb.xml");
srunner_run_all(sr, CK_NORMAL);
nf = srunner_ntests_failed(sr);
diff --git a/tests/check_suites.h b/tests/check_suites.h
index 499f1af..f887bc6 100644
--- a/tests/check_suites.h
+++ b/tests/check_suites.h
@@ -2,3 +2,4 @@
void suite_add_test(Suite *s, TFun tf, const char *name);
Suite *public_suite(void);
+Suite *thread_suite(void);
diff --git a/tests/check_threads.c b/tests/check_threads.c
new file mode 100644
index 0000000..6aaaead
--- /dev/null
+++ b/tests/check_threads.c
@@ -0,0 +1,204 @@
+/* Copyright (C) 2014 Uli Schlachter.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the names of the authors or their
+ * institutions shall not be used in advertising or otherwise to promote the
+ * sale, use or other dealings in this Software without prior written
+ * authorization from the authors.
+ */
+
+#include <check.h>
+#include <pthread.h>
+#include <string.h>
+#include <stdlib.h>
+#include "check_suites.h"
+#include "xcb.h"
+#include "xcbext.h"
+
+/* An extension definition for an extension that (hopefully) does never exist */
+static xcb_extension_t no_such_extension = {
+ "NO_SUCH_EXTENSION", 0
+};
+
+/* threads {{{ */
+
+static void *thread_wait_for_event(void *arg)
+{
+ xcb_connection_t *conn = arg;
+ xcb_generic_event_t *event;
+
+ while (!xcb_connection_has_error(conn) && (event = xcb_wait_for_event(conn))) {
+ free(event);
+ }
+
+ return NULL;
+}
+
+static void *thread_poll_for_event(void *arg)
+{
+ xcb_connection_t *conn = arg;
+ xcb_generic_event_t *event;
+
+ while (!xcb_connection_has_error(conn) && (event = xcb_poll_for_event(conn))) {
+ free(event);
+ }
+
+ return NULL;
+}
+
+static void *thread_send_noops(void *arg)
+{
+ xcb_connection_t *conn = arg;
+
+ while (!xcb_connection_has_error(conn)) {
+ xcb_no_operation(conn);
+ }
+
+ return NULL;
+}
+
+static void *thread_send_big_noops(void *arg)
+{
+ xcb_connection_t *conn = arg;
+ static const xcb_protocol_request_t xcb_req = {
+ 2, /* count */
+ 0, /* ext */
+ XCB_NO_OPERATION, /* opcode */
+ 1 /* isvoid */
+ };
+ struct iovec xcb_parts[4];
+ xcb_no_operation_request_t xcb_out;
+ /* The idea here is to fill up xcb's output buffer and causing threads
+ * to wait for another writing thread. We do this by sending a really,
+ * really big NoOperation request
+ */
+ size_t request_length = xcb_get_maximum_request_length(conn);
+ char *buffer;
+
+ /* Did things already go downhill? */
+ if (request_length == 0)
+ return NULL;
+
+ /* Round request length down to a multiple of 4 */
+ request_length = (request_length - sizeof(xcb_out)) & ~0x3;
+
+ buffer = malloc(request_length);
+ ck_assert_ptr_ne(buffer, NULL);
+ memset(buffer, 0, request_length);
+ xcb_out.pad0 = 0;
+
+ while (!xcb_connection_has_error(conn)) {
+ xcb_parts[2].iov_base = &xcb_out;
+ xcb_parts[2].iov_len = sizeof(xcb_out);
+ xcb_parts[3].iov_base = buffer;
+ xcb_parts[3].iov_len = request_length;
+
+ xcb_send_request(conn, 0, &xcb_parts[2], &xcb_req);
+ }
+
+ free(buffer);
+
+ return NULL;
+}
+
+static void *thread_sync(void *arg)
+{
+ xcb_connection_t *conn = arg;
+
+ while (!xcb_connection_has_error(conn)) {
+ free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL));
+ }
+
+ return NULL;
+}
+
+static void *thread_break_connection(void *arg)
+{
+ int i;
+ struct iovec parts[3];
+ xcb_connection_t *conn = arg;
+ xcb_get_input_focus_request_t request = { 0 };
+ const xcb_protocol_request_t xcb_req = {
+ 1, /* count */
+ &no_such_extension, /* extension */
+ 42, /* opcode */
+ 0 /* isvoid */
+ };
+
+ /* First wait a bit */
+ for (i = 0; i < 10; i++)
+ free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL));
+
+ /* Then break the connection by using an extension that doesn't exist */
+ parts[2].iov_base = &request;
+ parts[2].iov_len = sizeof(request);
+
+ xcb_send_request(conn, 0, &parts[2], &xcb_req);
+
+ return NULL;
+}
+
+/* }}} */
+
+static xcb_connection_t *conn = NULL;
+
+void connect_display(void)
+{
+ putenv("DISPLAY=:0"); /* XXX: Really, really ugly */
+ conn = xcb_connect(NULL, NULL);
+ ck_assert_int_eq(xcb_connection_has_error(conn), 0);
+}
+
+void disconnect_display(void)
+{
+ xcb_disconnect(conn);
+ conn = NULL;
+}
+
+START_TEST(threads)
+{
+ pthread_t threads[6];
+
+ ck_assert_int_eq(pthread_create(&threads[0], NULL, thread_wait_for_event, conn), 0);
+ ck_assert_int_eq(pthread_create(&threads[1], NULL, thread_poll_for_event, conn), 0);
+ ck_assert_int_eq(pthread_create(&threads[2], NULL, thread_send_noops, conn), 0);
+ ck_assert_int_eq(pthread_create(&threads[3], NULL, thread_send_big_noops, conn), 0);
+ ck_assert_int_eq(pthread_create(&threads[4], NULL, thread_sync, conn), 0);
+ ck_assert_int_eq(pthread_create(&threads[5], NULL, thread_break_connection, conn), 0);
+
+ mark_point();
+
+ ck_assert_int_eq(pthread_join(threads[0], NULL), 0);
+ ck_assert_int_eq(pthread_join(threads[1], NULL), 0);
+ ck_assert_int_eq(pthread_join(threads[2], NULL), 0);
+ ck_assert_int_eq(pthread_join(threads[3], NULL), 0);
+ ck_assert_int_eq(pthread_join(threads[4], NULL), 0);
+ ck_assert_int_eq(pthread_join(threads[5], NULL), 0);
+}
+END_TEST
+
+Suite *thread_suite(void)
+{
+ Suite *s = suite_create("Thread safety tests");
+ TCase *tc = tcase_create("Some thread magic");
+ tcase_add_checked_fixture(tc, connect_display, disconnect_display);
+ tcase_add_test(tc, threads);
+ suite_add_tcase(s, tc);
+ return s;
+}
--
1.9.1
More information about the Xcb
mailing list