[PATCH v3 1/2] support: Add support for running tests in a multi-threaded environment

Arjun Shankar arjun@redhat.com
Fri Nov 14 17:31:45 GMT 2025


It can be useful to be able to write a single-threaded test but run it
as part of a multi-threaded program simply to exercise glibc
synchronization code paths, e.g. the malloc implementation.

This commit adds support to enable this kind of testing.  Tests that
define TEST_IN_THREAD, either as TEST_THREAD_MAIN or TEST_THREAD_WORKER,
and then use support infrastructure (by including test-driver.c) will be
accordingly run in either the main thread, or in a second "worker"
thread while the other thread waits.

This can be used in new tests, or to easily make and run copies of
existing tests without modifying the tests themselves.
---
 support/Makefile                         |  1 +
 support/support_test_in_thread_wrapper.c | 96 ++++++++++++++++++++++++
 support/support_test_main.c              | 25 ++++--
 support/test-driver.c                    |  8 ++
 support/test-driver.h                    | 12 +++
 5 files changed, 136 insertions(+), 6 deletions(-)
 create mode 100644 support/support_test_in_thread_wrapper.c

diff --git a/support/Makefile b/support/Makefile
index 2043e4e590..a7b1b39782 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -98,6 +98,7 @@ libsupport-routines = \
   support_test_compare_failure \
   support_test_compare_string \
   support_test_compare_string_wide \
+  support_test_in_thread_wrapper \
   support_test_main \
   support_test_verify_impl \
   support_wait_for_thread_exit \
diff --git a/support/support_test_in_thread_wrapper.c b/support/support_test_in_thread_wrapper.c
new file mode 100644
index 0000000000..56f1d74437
--- /dev/null
+++ b/support/support_test_in_thread_wrapper.c
@@ -0,0 +1,96 @@
+/* Test-in-thread wrapper function for the test driver.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.
+
+   The support_test_in_thread_wrapper function defined here gets called from
+   within support_test_main, but needs to be compiled separately.  This is
+   done in order to avoid linking every test (irrespective of necessity)
+   against pthread_create etc., which has implications for Hurd and
+   statically linked tests.  */
+
+#include <support/test-driver.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/xthread.h>
+
+struct test_thread_args
+{
+  int argc;
+  char **argv;
+  const struct test_config *config;
+  int ret;
+};
+
+static void *
+test_thread (void *closure)
+{
+  struct test_thread_args *args = closure;
+
+  if (args->config->test_function != NULL)
+    args->ret = args->config->test_function ();
+  else if (args->config->test_function_argv != NULL)
+    args->ret = args->config->test_function_argv (args->argc, args->argv);
+  else
+    {
+      printf ("error: no test function defined\n");
+      exit (1);
+    }
+  return NULL;
+}
+
+static pthread_barrier_t barrier;
+
+static void *
+empty_thread (void *closure)
+{
+  /* Make sure that the alternate thread waits (and exists) till the main
+     thread finishes running the test.  */
+  xpthread_barrier_wait (&barrier);
+
+  return NULL;
+}
+
+int
+support_test_in_thread_wrapper (int argc, char **argv,
+                                const struct test_config *config)
+{
+  pthread_t thread;
+  struct test_thread_args closure = {.argc = argc,
+                                     .argv = argv,
+                                     .config = config};
+
+  if (config->test_in_thread == TEST_THREAD_MAIN)
+    {
+      xpthread_barrier_init (&barrier, NULL, 2);
+      thread = xpthread_create (NULL, empty_thread, NULL);
+
+      /* Run the test in the main thread.  */
+      test_thread (&closure);
+
+      /* Signal to the alternate thread that it can return.  */
+      xpthread_barrier_wait (&barrier);
+      xpthread_join (thread);
+    }
+  else /* config->test_in_thread == TEST_THREAD_WORKER.  */
+    {
+      /* Run the test in an alternate thread.  */
+      thread = xpthread_create (NULL, test_thread, &closure);
+      xpthread_join (thread);
+    }
+
+  return closure.ret;
+}
diff --git a/support/support_test_main.c b/support/support_test_main.c
index 1558e27c57..baca343cda 100644
--- a/support/support_test_main.c
+++ b/support/support_test_main.c
@@ -238,14 +238,27 @@ run_test_function (int argc, char **argv, const struct test_config *config)
       exit (1);
     }
 
-  if (config->test_function != NULL)
-    return config->test_function ();
-  else if (config->test_function_argv != NULL)
-    return config->test_function_argv (argc, argv);
+  if (config->test_in_thread == 0)
+    {
+      if (config->test_function != NULL)
+        return config->test_function ();
+      else if (config->test_function_argv != NULL)
+        return config->test_function_argv (argc, argv);
+      else
+        {
+          printf ("error: no test function defined\n");
+          exit (1);
+        }
+    }
   else
     {
-      printf ("error: no test function defined\n");
-      exit (1);
+      if (config->test_in_thread_wrapper != NULL)
+        return config->test_in_thread_wrapper (argc, argv, config);
+      else
+        {
+          printf ("error: no test-in-thread wrapper defined\n");
+          exit (1);
+        }
     }
 }
 
diff --git a/support/test-driver.c b/support/test-driver.c
index 40e2bc0ee2..cb65b9f078 100644
--- a/support/test-driver.c
+++ b/support/test-driver.c
@@ -168,5 +168,13 @@ main (int argc, char **argv)
   test_config.optstring = "+";
 #endif
 
+#ifdef TEST_IN_THREAD
+  test_config.test_in_thread = TEST_IN_THREAD;
+  test_config.test_in_thread_wrapper = support_test_in_thread_wrapper;
+#else
+  test_config.test_in_thread = 0;
+  test_config.test_in_thread_wrapper = NULL;
+#endif
+
   return support_test_main (argc, argv, &test_config);
 }
diff --git a/support/test-driver.h b/support/test-driver.h
index 9291611fbf..19fc601363 100644
--- a/support/test-driver.h
+++ b/support/test-driver.h
@@ -38,6 +38,8 @@ struct test_config
   char no_setvbuf;       /* Boolean flag to disable setvbuf.  */
   char run_command_mode; /* Boolean flag to indicate run-command-mode.  */
   const char *optstring; /* Short command line options.  */
+  int test_in_thread;    /* 0 => no threading, MAIN, WORKER.  */
+  int (*test_in_thread_wrapper) (int, char **, const struct test_config *);
 };
 
 enum
@@ -54,6 +56,11 @@ enum
     /* Used for command line argument parsing.  */
     OPT_DIRECT = 1000,
     OPT_TESTDIR,
+
+    /* Used for TEST_IN_THREAD to run single-threaded tests in a
+       multi-threaded environment.  */
+    TEST_THREAD_MAIN = 1,
+    TEST_THREAD_WORKER = 2,
   };
 
 /* Options provided by the test driver.  */
@@ -78,6 +85,11 @@ extern unsigned int test_verbose;
       printf (__VA_ARGS__);                      \
   } while (0);
 
+/* Tests that define -DTEST_IN_THREAD are run in a thread while an alternate
+   thread exists and waits on it.  */
+extern int support_test_in_thread_wrapper (int argc, char **argv,
+                                           const struct test_config *config);
+
 int support_test_main (int argc, char **argv, const struct test_config *);
 
 __END_DECLS
-- 
2.51.1



More information about the Libc-alpha mailing list