[PATCH v2] Add threaded variants of single-threaded malloc tests

Arjun Shankar arjun@redhat.com
Mon Nov 10 14:44:22 GMT 2025


Single-threaded malloc tests exercise only the SINGLE_THREAD_P paths in
the malloc implementation.  This commit runs variants of these tests in
a multi-threaded environment in order to exercise the alternate code
paths in the same test scenarios, thus potentially improving coverage.

$(test)-threaded-main and $(test)-threaded-worker variants are
introduced for most single-threaded malloc tests (with a small number of
exceptions).  The -main variants run the base test in a main thread
while the test environment has an alternate thread running, whereas the
-worker variants run the test in an alternate thread while the main
thread waits on it.

Test support/ infrastructure is extended to be able to allow threaded
execution of these tests without needing to modify the tests themselves,
using a compile-time -D flag to enable threaded execution instead.

Tests that for any reason currently fail are excluded.  They should be
investigated and followed-up on.
---
* v1: https://inbox.sourceware.org/libc-alpha/20251104172806.3931431-1-arjun@redhat.com/

* Changes in v2: addressed Florian's comment:
  - Added missed dependencies to address Hurd testsuite build failures

* For clarity, these are the lines that came from an LLM (and I slightly
  edited them after):

> $(objpfx)%-threaded-main.o: CPPFLAGS += -DTEST_IN_THREAD=THREAD_MAIN
> $(objpfx)%-threaded-main.o: %.c $(before-compile)
> 	$(compile-command.c)
> $(objpfx)%-threaded-worker.o: CPPFLAGS += -DTEST_IN_THREAD=THREAD_WORKER
> $(objpfx)%-threaded-worker.o: %.c $(before-compile)
> 	$(compile-command.c)
---
 malloc/Makefile                          | 53 ++++++++++++++
 support/Makefile                         |  1 +
 support/support_test_in_thread_wrapper.c | 90 ++++++++++++++++++++++++
 support/support_test_main.c              | 25 +++++--
 support/test-driver.c                    |  8 +++
 support/test-driver.h                    | 10 +++
 6 files changed, 181 insertions(+), 6 deletions(-)
 create mode 100644 support/support_test_in_thread_wrapper.c

diff --git a/malloc/Makefile b/malloc/Makefile
index cc012e2921..a2d547477f 100644
--- a/malloc/Makefile
+++ b/malloc/Makefile
@@ -293,10 +293,63 @@ $(addprefix $(objpfx), $(tests-link-with-libpthread)): $(shared-thread-library)
 $(tests-link-with-libpthread:%=$(objpfx)%-malloc-largetcache): \
   $(shared-thread-library)
 
+# tests-threaded-{main,worker} are variants of base malloc tests that are
+# run in a multi-threaded program.  The main variant is run in the main
+# thread while an alternate thread exists, and the worker variant is run in
+# a worker thread while the main thread waits on it.
+
+tests-exclude-threaded = \
+  $(tests-link-with-libpthread) \
+  $(tests-static) \
+  tst-compathooks-off \
+  tst-compathooks-on \
+  tst-interpose-nothread \
+  tst-malloc-usable \
+  tst-malloc-usable-tunables \
+  tst-mallocstate \
+  tst-memalign-2 \
+  tst-mxfast \
+  tst-safe-linking \
+# tests-exclude-threaded
+
+tests-threaded-main = $(addsuffix -threaded-main, \
+				  $(filter-out $(tests-exclude-threaded), \
+					       $(tests)))
+$(addprefix $(objpfx), $(tests-threaded-main)): $(shared-thread-library)
+$(addprefix $(objpfx), $(tests-threaded-main:%=%-malloc-check)): $(shared-thread-library)
+$(addprefix $(objpfx), $(tests-threaded-main:%=%-malloc-hugetlb1)): $(shared-thread-library)
+$(addprefix $(objpfx), $(tests-threaded-main:%=%-malloc-hugetlb2)): $(shared-thread-library)
+$(addprefix $(objpfx), $(tests-threaded-main:%=%-mcheck)): $(shared-thread-library)
+$(addprefix $(objpfx), $(tests-threaded-main:%=%-malloc-largetcache)): $(shared-thread-library)
+
+tests-threaded-worker = $(addsuffix -threaded-worker, \
+				    $(filter-out $(tests-exclude-threaded) \
+						 tst-free-errno \
+						 tst-mallocfork, \
+						 $(tests)))
+$(addprefix $(objpfx), $(tests-threaded-worker)): $(shared-thread-library)
+$(addprefix $(objpfx), $(tests-threaded-worker:%=%-malloc-check)): $(shared-thread-library)
+$(addprefix $(objpfx), $(tests-threaded-worker:%=%-malloc-hugetlb1)): $(shared-thread-library)
+$(addprefix $(objpfx), $(tests-threaded-worker:%=%-malloc-hugetlb2)): $(shared-thread-library)
+$(addprefix $(objpfx), $(tests-threaded-worker:%=%-mcheck)): $(shared-thread-library)
+$(addprefix $(objpfx), $(tests-threaded-worker:%=%-malloc-largetcache)): $(shared-thread-library)
+
+tests += $(tests-threaded-main) $(tests-threaded-worker)
+
 # These should be removed by `make clean'.
 extra-objs = mcheck-init.o libmcheck.a
 others-extras = mcheck-init.o
 
+# Compile threaded-{main,worker} tests from their corresponding base
+# sources, but with -DTEST_IN_THREAD appropriately defined so that support
+# infrastructure runs them in a multi-threaded program.
+$(objpfx)%-threaded-main.o: CPPFLAGS += -DTEST_IN_THREAD=THREAD_MAIN
+$(objpfx)%-threaded-main.o: %.c $(before-compile)
+	$(compile-command.c)
+$(objpfx)%-threaded-worker.o: CPPFLAGS += -DTEST_IN_THREAD=THREAD_WORKER
+$(objpfx)%-threaded-worker.o: %.c $(before-compile)
+	$(compile-command.c)
+
 # Include the cleanup handler.
 aux := set-freeres thread-freeres
 
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..a4d5df0c12
--- /dev/null
+++ b/support/support_test_in_thread_wrapper.c
@@ -0,0 +1,90 @@
+/* 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/>.  */
+
+#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;
+}
+
+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 == 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 == 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..2d76ba4aab 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.  */
+    THREAD_MAIN = 1,
+    THREAD_WORKER = 2,
   };
 
 /* Options provided by the test driver.  */
@@ -78,6 +85,9 @@ extern unsigned int test_verbose;
       printf (__VA_ARGS__);                      \
   } while (0);
 
+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.0



More information about the Libc-alpha mailing list