[PATCH 7/7] selftests/futex: Migrate robust_list to harness

From: Wake Liu

Date: Mon Jun 01 2026 - 02:52:54 EST


Migrate robust_list test to the kselftest harness framework,
removing mixed legacy ksft_* API usages and passing test metadata
to cloned child functions via child_args struct.

Also, fix an out-of-bounds waitpid bug during multithreaded tests.

Signed-off-by: Wake Liu <wakel@xxxxxxxxxx>
---
.../selftests/futex/functional/robust_list.c | 166 +++++++++---------
1 file changed, 85 insertions(+), 81 deletions(-)

diff --git a/tools/testing/selftests/futex/functional/robust_list.c b/tools/testing/selftests/futex/functional/robust_list.c
index e7d1254e18ca..d0b461962586 100644
--- a/tools/testing/selftests/futex/functional/robust_list.c
+++ b/tools/testing/selftests/futex/functional/robust_list.c
@@ -25,7 +25,7 @@
#define _GNU_SOURCE

#include "futextest.h"
-#include "../../kselftest_harness.h"
+#include "kselftest_harness.h"

#include <errno.h>
#include <pthread.h>
@@ -35,11 +35,11 @@
#include <stddef.h>
#include <sys/mman.h>
#include <sys/wait.h>
+#include <stdlib.h>
+#include <string.h>

#define STACK_SIZE (1024 * 1024)
-
#define FUTEX_TIMEOUT 3
-
#define SLEEP_US 100

static pthread_barrier_t barrier, barrier2;
@@ -58,30 +58,46 @@ static int get_robust_list(int pid, struct robust_list_head **head, size_t *len_
* Basic lock struct, contains just the futex word and the robust list element
* Real implementations have also a *prev to easily walk in the list
*/
+typedef _Atomic(unsigned int) atomic_futex_t;
+
struct lock_struct {
- _Atomic(unsigned int) futex;
+ atomic_futex_t futex;
struct robust_list list;
};

+struct child_args {
+ struct __test_metadata *_metadata;
+ void *arg;
+};
+
/*
* Helper function to spawn a child thread. Returns -1 on error, pid on success
*/
-static int create_child(int (*fn)(void *arg), void *arg)
+static int create_child(struct __test_metadata *_metadata, int (*fn)(void *arg), void *arg)
{
char *stack;
pid_t pid;
+ struct child_args *cargs = malloc(sizeof(*cargs));
+
+ if (!cargs)
+ return -1;
+ cargs->_metadata = _metadata;
+ cargs->arg = arg;

stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
- if (stack == MAP_FAILED)
+ if (stack == MAP_FAILED) {
+ free(cargs);
return -1;
+ }

stack += STACK_SIZE;

- pid = clone(fn, stack, CLONE_VM | SIGCHLD, arg);
-
- if (pid == -1)
+ pid = clone(fn, stack, CLONE_VM | SIGCHLD, cargs);
+ if (pid == -1) {
+ free(cargs);
return -1;
+ }

return pid;
}
@@ -110,7 +126,7 @@ static int set_list(struct robust_list_head *head)
*/
static int mutex_lock(struct lock_struct *lock, struct robust_list_head *head, bool error_inject)
{
- _Atomic(unsigned int) *futex = &lock->futex;
+ atomic_futex_t *futex = &lock->futex;
unsigned int zero = 0;
pid_t tid = gettid();
int ret = -1;
@@ -170,21 +186,19 @@ static int mutex_lock(struct lock_struct *lock, struct robust_list_head *head, b
*/
static int child_fn_lock(void *arg)
{
- struct lock_struct *lock = arg;
+ struct child_args *cargs = arg;
+ struct __test_metadata *_metadata = cargs->_metadata;
+ struct lock_struct *lock = cargs->arg;
struct robust_list_head head;
int ret;

+ free(cargs);
+
ret = set_list(&head);
- if (ret) {
- ksft_test_result_fail("set_robust_list error\n");
- return ret;
- }
+ ASSERT_EQ(ret, 0) TH_LOG("set_robust_list error");

ret = mutex_lock(lock, &head, false);
- if (ret) {
- ksft_test_result_fail("mutex_lock error\n");
- return ret;
- }
+ ASSERT_EQ(ret, 0) TH_LOG("mutex_lock error");

pthread_barrier_wait(&barrier);

@@ -207,7 +221,7 @@ static int child_fn_lock(void *arg)
TEST(test_robustness)
{
struct lock_struct lock = { .futex = 0 };
- _Atomic(unsigned int) *futex = &lock.futex;
+ atomic_futex_t *futex = &lock.futex;
struct robust_list_head head;
int ret, pid, wstatus;

@@ -221,7 +235,7 @@ TEST(test_robustness)
ret = pthread_barrier_init(&barrier, NULL, 2);
ASSERT_EQ(ret, 0);

- pid = create_child(&child_fn_lock, &lock);
+ pid = create_child(_metadata, &child_fn_lock, &lock);
ASSERT_NE(pid, -1);

pthread_barrier_wait(&barrier);
@@ -238,9 +252,7 @@ TEST(test_robustness)
wait(&wstatus);
pthread_barrier_destroy(&barrier);

- /* Pass only if the child hasn't return error */
- if (!WEXITSTATUS(wstatus))
- ksft_test_result_pass("%s\n", __func__);
+ EXPECT_EQ(WEXITSTATUS(wstatus), 0) TH_LOG("child failed");
}

/*
@@ -266,8 +278,6 @@ TEST(test_set_robust_list_invalid_size)
ret = set_robust_list(&head, 0);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, EINVAL);
-
- ksft_test_result_pass("%s\n", __func__);
}

/*
@@ -294,20 +304,19 @@ TEST(test_get_robust_list_self)
ASSERT_EQ(ret, 0);
ASSERT_EQ(get_head, &head2);
ASSERT_EQ(head_size, len_ptr);
-
- ksft_test_result_pass("%s\n", __func__);
}

static int child_list(void *arg)
{
- struct robust_list_head *head = arg;
+ struct child_args *cargs = arg;
+ struct __test_metadata *_metadata = cargs->_metadata;
+ struct robust_list_head *head = cargs->arg;
int ret;

+ free(cargs);
+
ret = set_robust_list(head, sizeof(*head));
- if (ret) {
- ksft_test_result_fail("set_robust_list error\n");
- return -1;
- }
+ ASSERT_EQ(ret, 0) TH_LOG("set_robust_list error");

/*
* After setting the list head, wait until the main thread can call
@@ -337,7 +346,7 @@ TEST(test_get_robust_list_child)
ret = pthread_barrier_init(&barrier2, NULL, 2);
ASSERT_EQ(ret, 0);

- tid = create_child(&child_list, &head);
+ tid = create_child(_metadata, &child_list, &head);
ASSERT_NE(tid, -1);

pthread_barrier_wait(&barrier);
@@ -352,28 +361,24 @@ TEST(test_get_robust_list_child)
pthread_barrier_destroy(&barrier);
pthread_barrier_destroy(&barrier2);

- /* Pass only if the child hasn't return error */
- if (!WEXITSTATUS(wstatus))
- ksft_test_result_pass("%s\n", __func__);
+ EXPECT_EQ(WEXITSTATUS(wstatus), 0) TH_LOG("child failed");
}

static int child_fn_lock_with_error(void *arg)
{
- struct lock_struct *lock = arg;
+ struct child_args *cargs = arg;
+ struct __test_metadata *_metadata = cargs->_metadata;
+ struct lock_struct *lock = cargs->arg;
struct robust_list_head head;
int ret;

+ free(cargs);
+
ret = set_list(&head);
- if (ret) {
- ksft_test_result_fail("set_robust_list error\n");
- return -1;
- }
+ ASSERT_EQ(ret, 0) TH_LOG("set_robust_list error");

ret = mutex_lock(lock, &head, true);
- if (ret) {
- ksft_test_result_fail("mutex_lock error\n");
- return -1;
- }
+ ASSERT_EQ(ret, 0) TH_LOG("mutex_lock error");

pthread_barrier_wait(&barrier);

@@ -391,7 +396,7 @@ static int child_fn_lock_with_error(void *arg)
TEST(test_set_list_op_pending)
{
struct lock_struct lock = { .futex = 0 };
- _Atomic(unsigned int) *futex = &lock.futex;
+ atomic_futex_t *futex = &lock.futex;
struct robust_list_head head;
int ret, wstatus;

@@ -401,7 +406,7 @@ TEST(test_set_list_op_pending)
ret = pthread_barrier_init(&barrier, NULL, 2);
ASSERT_EQ(ret, 0);

- ret = create_child(&child_fn_lock_with_error, &lock);
+ ret = create_child(_metadata, &child_fn_lock_with_error, &lock);
ASSERT_NE(ret, -1);

pthread_barrier_wait(&barrier);
@@ -414,21 +419,21 @@ TEST(test_set_list_op_pending)
wait(&wstatus);
pthread_barrier_destroy(&barrier);

- /* Pass only if the child hasn't return error */
- if (!WEXITSTATUS(wstatus))
- ksft_test_result_pass("%s\n", __func__);
- else
- ksft_test_result_fail("%s\n", __func__);
+ EXPECT_EQ(WEXITSTATUS(wstatus), 0) TH_LOG("child failed");
}

#define CHILD_NR 10

static int child_lock_holder(void *arg)
{
- struct lock_struct *locks = arg;
+ struct child_args *cargs = arg;
+ struct __test_metadata *_metadata = cargs->_metadata;
+ struct lock_struct *locks = cargs->arg;
struct robust_list_head head;
int i;

+ free(cargs);
+
set_list(&head);

for (i = 0; i < CHILD_NR; i++) {
@@ -447,22 +452,20 @@ static int child_lock_holder(void *arg)

static int child_wait_lock(void *arg)
{
- struct lock_struct *lock = arg;
+ struct child_args *cargs = arg;
+ struct __test_metadata *_metadata = cargs->_metadata;
+ struct lock_struct *lock = cargs->arg;
struct robust_list_head head;
int ret;

+ free(cargs);
+
pthread_barrier_wait(&barrier2);
ret = mutex_lock(lock, &head, false);
+ ASSERT_EQ(ret, 0) TH_LOG("mutex_lock error");

- if (ret) {
- ksft_test_result_fail("mutex_lock error\n");
- return -1;
- }
-
- if (!(lock->futex & FUTEX_OWNER_DIED)) {
- ksft_test_result_fail("futex not marked with FUTEX_OWNER_DIED\n");
- return -1;
- }
+ ASSERT_TRUE(lock->futex & FUTEX_OWNER_DIED)
+ TH_LOG("futex not marked with FUTEX_OWNER_DIED");

return 0;
}
@@ -482,18 +485,20 @@ TEST(test_robust_list_multiple_elements)
ret = pthread_barrier_init(&barrier2, NULL, CHILD_NR + 1);
ASSERT_EQ(ret, 0);

- pids[0] = create_child(&child_lock_holder, &locks);
+ pids[0] = create_child(_metadata, &child_lock_holder, &locks);
+ ASSERT_NE(pids[0], -1);

/* Wait until the locker thread takes the look */
pthread_barrier_wait(&barrier);

- for (i = 0; i < CHILD_NR; i++)
- pids[i+1] = create_child(&child_wait_lock, &locks[i]);
+ for (i = 0; i < CHILD_NR; i++) {
+ pids[i+1] = create_child(_metadata, &child_wait_lock, &locks[i]);
+ ASSERT_NE(pids[i+1], -1);
+ }

- /* Wait for all children to return */
+ /* Wait for all children to return (holder + all waiters) */
ret = 0;
-
- for (i = 0; i < CHILD_NR; i++) {
+ for (i = 0; i < CHILD_NR + 1; i++) {
waitpid(pids[i], &wstatus, 0);
if (WEXITSTATUS(wstatus))
ret = -1;
@@ -502,22 +507,21 @@ TEST(test_robust_list_multiple_elements)
pthread_barrier_destroy(&barrier);
pthread_barrier_destroy(&barrier2);

- /* Pass only if the child hasn't return error */
- if (!ret)
- ksft_test_result_pass("%s\n", __func__);
+ EXPECT_EQ(ret, 0) TH_LOG("One or more children failed");
}

static int child_circular_list(void *arg)
{
+ struct child_args *cargs = arg;
+ struct __test_metadata *_metadata = cargs->_metadata;
static struct robust_list_head head;
struct lock_struct a, b, c;
int ret;

+ free(cargs);
+
ret = set_list(&head);
- if (ret) {
- ksft_test_result_fail("set_list error\n");
- return -1;
- }
+ ASSERT_EQ(ret, 0) TH_LOG("set_list error");

head.list.next = &a.list;

@@ -539,14 +543,14 @@ static int child_circular_list(void *arg)
TEST(test_circular_list)
{
int wstatus;
+ pid_t pid;

- create_child(child_circular_list, NULL);
+ pid = create_child(_metadata, child_circular_list, NULL);
+ ASSERT_NE(pid, -1);

wait(&wstatus);

- /* Pass only if the child hasn't return error */
- if (!WEXITSTATUS(wstatus))
- ksft_test_result_pass("%s\n", __func__);
+ EXPECT_EQ(WEXITSTATUS(wstatus), 0) TH_LOG("child failed");
}

TEST_HARNESS_MAIN
--
2.54.0.823.g6e5bcc1fc9-goog