From 9074feabf6f827f6b37a69d77a5e13093799a905 Mon Sep 17 00:00:00 2001 From: Christopher Friedt Date: Sat, 13 Jan 2024 15:26:01 -0500 Subject: [PATCH] tests: posix: separate suite for pthread_t and pthread_attr_t In order to make state more manageable among the other posix api tests, create a separate ZTEST_SUITE() called pthread and another ZTEST_SUITE() called pthread_attr. The tests in the pthread suite mainly operate on the pthread_t type, while tests in the pthread_attr suite mainly operate on the pthread_attr_t type. The pthread_attr testsuite is moved to a separate file (pthread_attr.c). Having separate test suites allows us to narrow the scope of the tests and have a better degree of control over the state. Some effort went in to highlighting undefined behaviour at the API level. This was quite intentional, as we will need to be be able to test / verify that we do in fact have deterministic outcomes when users attempt operations that result in undefined behaviour (e.g. assert). Signed-off-by: Christopher Friedt --- tests/posix/common/src/pthread.c | 562 +---------------------- tests/posix/common/src/pthread_attr.c | 636 ++++++++++++++++++++++++++ 2 files changed, 656 insertions(+), 542 deletions(-) create mode 100644 tests/posix/common/src/pthread_attr.c diff --git a/tests/posix/common/src/pthread.c b/tests/posix/common/src/pthread.c index 0ceadbd14493..02cff3d1e79d 100644 --- a/tests/posix/common/src/pthread.c +++ b/tests/posix/common/src/pthread.c @@ -13,23 +13,14 @@ #define N_THR_E 3 #define N_THR_T 4 #define BOUNCES 64 -#define STACKS (MAX(1024, PTHREAD_STACK_MIN) + CONFIG_TEST_EXTRA_STACK_SIZE) -#define THREAD_PRIORITY 3 #define ONE_SECOND 1 -/* arbitrary number that is also a legal stack size */ -#define OKAY_STACK_SIZE (STACKS + 1) - /* Macros to test invalid states */ #define PTHREAD_CANCEL_INVALID -1 #define SCHED_INVALID -1 #define PRIO_INVALID -1 #define PTHREAD_INVALID -1 -K_THREAD_STACK_ARRAY_DEFINE(stack_e, N_THR_E, STACKS); -K_THREAD_STACK_ARRAY_DEFINE(stack_t, N_THR_T, STACKS); -K_THREAD_STACK_ARRAY_DEFINE(stack_1, 1, 32); - void *thread_top_exec(void *p1); void *thread_top_term(void *p1); @@ -49,8 +40,6 @@ static int barrier_failed; static int barrier_done[N_THR_E]; static int barrier_return[N_THR_E]; -static uint32_t param; - /* First phase bounces execution between two threads using a condition * variable, continuously testing that no other thread is mucking with * the protected state. This ends with all threads going back to @@ -214,13 +203,8 @@ void *thread_top_term(void *p1) } if (id >= 2) { - if (IS_ENABLED(CONFIG_DYNAMIC_THREAD)) { - zassert_false(pthread_detach(self), "failed to set detach state"); - } - ret = pthread_detach(self); - if (id == 2) { - zassert_equal(ret, EINVAL, "re-detached thread!"); - } + zassert_ok(pthread_detach(self), "failed to set detach state"); + zassert_equal(pthread_detach(self), EINVAL, "re-detached thread!"); } printk("Cancelling thread %d\n", id); @@ -231,16 +215,11 @@ void *thread_top_term(void *p1) return NULL; } -ZTEST(posix_apis, test_pthread_execution) +ZTEST(pthread, test_pthread_execution) { - int i, ret, min_prio, max_prio; - int dstate, policy; - pthread_attr_t attr[N_THR_E] = {}; - struct sched_param schedparam, getschedparam; + int i, ret; pthread_t newthread[N_THR_E]; - int schedpolicy = SCHED_FIFO; - void *retval, *stackaddr; - size_t stacksize; + void *retval; int serial_threads = 0; static const char thr_name[] = "thread name"; char thr_name_buf[CONFIG_THREAD_MAX_NAME_LEN]; @@ -252,51 +231,6 @@ ZTEST(posix_apis, test_pthread_execution) zassert_ok(pthread_barrier_init(&barrier, NULL, N_THR_E)); sem_init(&main_sem, 0, 1); - schedparam.sched_priority = CONFIG_NUM_COOP_PRIORITIES - 1; - min_prio = sched_get_priority_min(schedpolicy); - max_prio = sched_get_priority_max(schedpolicy); - - ret = (min_prio < 0 || max_prio < 0 || - schedparam.sched_priority < min_prio || - schedparam.sched_priority > max_prio); - - /* TESTPOINT: Check if scheduling priority is valid */ - zassert_false(ret, - "Scheduling priority outside valid priority range"); - - /* TESTPOINTS: Try setting attributes before init */ - ret = pthread_attr_setschedparam(&attr[0], &schedparam); - zassert_equal(ret, EINVAL, "uninitialized attr set!"); - - ret = pthread_attr_setdetachstate(&attr[0], PTHREAD_CREATE_JOINABLE); - zassert_equal(ret, EINVAL, "uninitialized attr set!"); - - ret = pthread_attr_setschedpolicy(&attr[0], schedpolicy); - zassert_equal(ret, EINVAL, "uninitialized attr set!"); - - /* TESTPOINT: Try setting attribute with empty stack */ - ret = pthread_attr_setstack(&attr[0], 0, STACKS); - zassert_equal(ret, EACCES, "empty stack set!"); - - /* TESTPOINTS: Try getting attributes before init */ - ret = pthread_attr_getschedparam(&attr[0], &getschedparam); - zassert_equal(ret, EINVAL, "uninitialized attr retrieved!"); - - ret = pthread_attr_getdetachstate(&attr[0], &dstate); - zassert_equal(ret, EINVAL, "uninitialized attr retrieved!"); - - ret = pthread_attr_getschedpolicy(&attr[0], &policy); - zassert_equal(ret, EINVAL, "uninitialized attr retrieved!"); - - ret = pthread_attr_getstack(&attr[0], &stackaddr, &stacksize); - zassert_equal(ret, EINVAL, "uninitialized attr retrieved!"); - - ret = pthread_attr_getstacksize(&attr[0], &stacksize); - zassert_equal(ret, EINVAL, "uninitialized attr retrieved!"); - - /* TESTPOINT: Try destroying attr before init */ - ret = pthread_attr_destroy(&attr[0]); - zassert_equal(ret, EINVAL, "uninitialized attr destroyed!"); /* TESTPOINT: Try getting name of NULL thread (aka uninitialized * thread var). @@ -304,60 +238,16 @@ ZTEST(posix_apis, test_pthread_execution) ret = pthread_getname_np(PTHREAD_INVALID, thr_name_buf, sizeof(thr_name_buf)); zassert_equal(ret, ESRCH, "uninitialized getname!"); + for (i = 0; i < N_THR_E; i++) { + ret = pthread_create(&newthread[i], NULL, thread_top_exec, INT_TO_POINTER(i)); + } + /* TESTPOINT: Try setting name of NULL thread (aka uninitialized * thread var). */ ret = pthread_setname_np(PTHREAD_INVALID, thr_name); zassert_equal(ret, ESRCH, "uninitialized setname!"); - /* TESTPOINT: Try creating thread before attr init */ - ret = pthread_create(&newthread[0], &attr[0], - thread_top_exec, NULL); - zassert_equal(ret, EINVAL, "thread created before attr init!"); - - for (i = 0; i < N_THR_E; i++) { - ret = pthread_attr_init(&attr[i]); - if (ret != 0) { - zassert_false(pthread_attr_destroy(&attr[i]), - "Unable to destroy pthread object attrib"); - zassert_false(pthread_attr_init(&attr[i]), - "Unable to create pthread object attrib"); - } - - /* TESTPOINTS: Retrieve set stack attributes and compare */ - pthread_attr_setstack(&attr[i], &stack_e[i][0], STACKS); - stackaddr = NULL; - pthread_attr_getstack(&attr[i], &stackaddr, &stacksize); - zassert_equal_ptr(&stack_e[i][0], stackaddr, - "stack attribute addresses do not match!"); - zassert_equal(STACKS, stacksize, "stack sizes do not match!"); - - pthread_attr_getstacksize(&attr[i], &stacksize); - zassert_equal(STACKS, stacksize, "stack sizes do not match!"); - - pthread_attr_setschedpolicy(&attr[i], schedpolicy); - pthread_attr_getschedpolicy(&attr[i], &policy); - zassert_equal(schedpolicy, policy, - "scheduling policies do not match!"); - - pthread_attr_setschedparam(&attr[i], &schedparam); - pthread_attr_getschedparam(&attr[i], &getschedparam); - zassert_equal(schedparam.sched_priority, - getschedparam.sched_priority, - "scheduling priorities do not match!"); - - if (IS_ENABLED(CONFIG_DYNAMIC_THREAD)) { - ret = pthread_create(&newthread[i], NULL, thread_top_exec, - INT_TO_POINTER(i)); - } else { - ret = pthread_create(&newthread[i], &attr[i], thread_top_exec, - INT_TO_POINTER(i)); - } - - /* TESTPOINT: Check if thread is created successfully */ - zassert_false(ret, "Number of threads exceed max limit"); - } - /* TESTPOINT: Try getting thread name with no buffer */ ret = pthread_getname_np(newthread[0], NULL, sizeof(thr_name_buf)); zassert_equal(ret, EINVAL, "uninitialized getname!"); @@ -416,119 +306,22 @@ ZTEST(posix_apis, test_pthread_execution) printk("Barrier test OK\n"); } -ZTEST(posix_apis, test_pthread_errors_errno) -{ - pthread_attr_t attr; - struct sched_param param; - void *stackaddr; - size_t stacksize; - int policy, detach; - static pthread_once_t key; - - /* TESTPOINT: invoke pthread APIs with NULL */ - zassert_equal(pthread_attr_destroy(NULL), EINVAL, - "pthread destroy NULL error"); - zassert_equal(pthread_attr_getschedparam(NULL, ¶m), EINVAL, - "get scheduling param error"); - zassert_equal(pthread_attr_getstack(NULL, &stackaddr, &stacksize), - EINVAL, "get stack attributes error"); - zassert_equal(pthread_attr_getstacksize(NULL, &stacksize), - EINVAL, "get stack size error"); - zassert_equal(pthread_attr_setschedpolicy(NULL, 2), - EINVAL, "set scheduling policy error"); - zassert_equal(pthread_attr_getschedpolicy(NULL, &policy), - EINVAL, "get scheduling policy error"); - zassert_equal(pthread_attr_setdetachstate(NULL, 0), - EINVAL, "pthread set detach state with NULL error"); - zassert_equal(pthread_attr_getdetachstate(NULL, &detach), - EINVAL, "get detach state error"); - zassert_equal(pthread_detach(PTHREAD_INVALID), ESRCH, "detach with NULL error"); - zassert_equal(pthread_attr_init(NULL), ENOMEM, - "init with NULL error"); - zassert_equal(pthread_attr_setschedparam(NULL, ¶m), EINVAL, - "set sched param with NULL error"); - zassert_equal(pthread_cancel(PTHREAD_INVALID), ESRCH, - "cancel NULL error"); - zassert_equal(pthread_join(PTHREAD_INVALID, NULL), ESRCH, - "join with NULL has error"); - zassert_equal(pthread_once(&key, NULL), EINVAL, - "pthread dynamic package initialization error"); - zassert_equal(pthread_getschedparam(PTHREAD_INVALID, &policy, ¶m), ESRCH, - "get schedparam with NULL error"); - zassert_equal(pthread_setschedparam(PTHREAD_INVALID, policy, ¶m), ESRCH, - "set schedparam with NULL error"); - - attr = (pthread_attr_t){0}; - zassert_equal(pthread_attr_getdetachstate(&attr, &detach), - EINVAL, "get detach state error"); - - /* Initialise thread attribute to ensure won't be return with init error */ - zassert_false(pthread_attr_init(&attr), - "Unable to create pthread object attr"); - zassert_false(pthread_attr_setschedpolicy(&attr, SCHED_FIFO), - "set scheduling policy error"); - zassert_false(pthread_attr_setschedpolicy(&attr, SCHED_RR), "set scheduling policy error"); - zassert_false(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE), - "set detach state error"); - zassert_false(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED), - "set detach state error"); - zassert_equal(pthread_attr_setdetachstate(&attr, 3), - EINVAL, "set detach state error"); - zassert_false(pthread_attr_getdetachstate(&attr, &detach), - "get detach state error"); -} - -ZTEST(posix_apis, test_pthread_termination) +ZTEST(pthread, test_pthread_termination) { int32_t i, ret; - int oldstate, policy; - pthread_attr_t attr[N_THR_T]; - struct sched_param schedparam; - pthread_t newthread[N_THR_T]; + int oldstate; + pthread_t newthread[N_THR_T] = {0}; void *retval; - /* Creating 4 threads with lowest application priority */ + /* Creating 4 threads */ for (i = 0; i < N_THR_T; i++) { - ret = pthread_attr_init(&attr[i]); - if (ret != 0) { - zassert_false(pthread_attr_destroy(&attr[i]), - "Unable to destroy pthread object attrib"); - zassert_false(pthread_attr_init(&attr[i]), - "Unable to create pthread object attrib"); - } - - if (i == 2) { - pthread_attr_setdetachstate(&attr[i], - PTHREAD_CREATE_DETACHED); - } - - schedparam.sched_priority = 2; - pthread_attr_setschedparam(&attr[i], &schedparam); - pthread_attr_setstack(&attr[i], &stack_t[i][0], STACKS); - if (IS_ENABLED(CONFIG_DYNAMIC_THREAD)) { - ret = pthread_create(&newthread[i], NULL, thread_top_term, - INT_TO_POINTER(i)); - } else { - ret = pthread_create(&newthread[i], &attr[i], thread_top_term, - INT_TO_POINTER(i)); - } - - zassert_false(ret, "Not enough space to create new thread"); + zassert_ok(pthread_create(&newthread[i], NULL, thread_top_term, INT_TO_POINTER(i))); } /* TESTPOINT: Try setting invalid cancel state to current thread */ ret = pthread_setcancelstate(PTHREAD_CANCEL_INVALID, &oldstate); zassert_equal(ret, EINVAL, "invalid cancel state set!"); - /* TESTPOINT: Try setting invalid policy */ - ret = pthread_setschedparam(newthread[0], SCHED_INVALID, &schedparam); - zassert_equal(ret, EINVAL, "invalid policy set!"); - - /* TESTPOINT: Try setting invalid priority */ - schedparam.sched_priority = PRIO_INVALID; - ret = pthread_setschedparam(newthread[0], SCHED_RR, &schedparam); - zassert_equal(ret, EINVAL, "invalid priority set!"); - for (i = 0; i < N_THR_T; i++) { pthread_join(newthread[i], &retval); } @@ -540,32 +333,6 @@ ZTEST(posix_apis, test_pthread_termination) /* TESTPOINT: Try canceling a terminated thread */ ret = pthread_cancel(newthread[N_THR_T/2]); zassert_equal(ret, ESRCH, "cancelled a terminated thread!"); - - /* TESTPOINT: Try getting scheduling info from terminated thread */ - ret = pthread_getschedparam(newthread[N_THR_T/2], &policy, &schedparam); - zassert_equal(ret, ESRCH, "got attr from terminated thread!"); -} - -ZTEST(posix_apis, test_pthread_attr_stacksize) -{ - size_t act_size; - pthread_attr_t attr; - const size_t exp_size = OKAY_STACK_SIZE; - - /* TESTPOINT: specify a custom stack size via pthread_attr_t */ - zassert_equal(0, pthread_attr_init(&attr), "pthread_attr_init() failed"); - - if (PTHREAD_STACK_MIN > 0) { - zassert_equal(EINVAL, pthread_attr_setstacksize(&attr, 0), - "pthread_attr_setstacksize() did not fail"); - } - - zassert_equal(0, pthread_attr_setstacksize(&attr, exp_size), - "pthread_attr_setstacksize() failed"); - zassert_equal(0, pthread_attr_getstacksize(&attr, &act_size), - "pthread_attr_getstacksize() failed"); - zassert_equal(exp_size, act_size, "wrong size: act: %zu exp: %zu", - exp_size, act_size); } static void *create_thread1(void *p1) @@ -574,50 +341,14 @@ static void *create_thread1(void *p1) return NULL; } -ZTEST(posix_apis, test_pthread_create_negative) -{ - int ret; - pthread_t pthread1; - pthread_attr_t attr1; - - /* create pthread without attr initialized */ - if (!IS_ENABLED(CONFIG_DYNAMIC_THREAD)) { - ret = pthread_create(&pthread1, NULL, create_thread1, (void *)1); - zassert_equal(ret, EAGAIN, "create thread with NULL successful"); - } - - /* initialized attr without set stack to create thread */ - ret = pthread_attr_init(&attr1); - zassert_false(ret, "attr1 initialized failed"); - - attr1 = (pthread_attr_t){0}; - ret = pthread_create(&pthread1, &attr1, create_thread1, (void *)1); - zassert_equal(ret, EINVAL, "create successful with NULL attr"); - - /* set stack size 0 to create thread */ - pthread_attr_setstack(&attr1, &stack_1, 0); - ret = pthread_create(&pthread1, &attr1, create_thread1, (void *)1); - zassert_equal(ret, EINVAL, "create thread with 0 size"); -} - -ZTEST(posix_apis, test_pthread_descriptor_leak) +ZTEST(pthread, test_pthread_descriptor_leak) { pthread_t pthread1; - pthread_attr_t attr; - - zassert_ok(pthread_attr_init(&attr)); - zassert_ok(pthread_attr_setstack(&attr, &stack_e[0][0], STACKS)); /* If we are leaking descriptors, then this loop will never complete */ for (size_t i = 0; i < CONFIG_MAX_PTHREAD_COUNT * 2; ++i) { - zassert_ok(pthread_create(&pthread1, &attr, create_thread1, NULL), + zassert_ok(pthread_create(&pthread1, NULL, create_thread1, NULL), "unable to create thread %zu", i); - /* - * k_msleep() should not be necessary, but it is added as a workaround - * for #56163 and #58116, which identified race conditions on some - * platforms. - */ - k_msleep(100); zassert_ok(pthread_join(pthread1, NULL), "unable to join thread %zu", i); } } @@ -639,210 +370,13 @@ ZTEST(posix_apis, test_sched_getscheduler) zassert_true((rc == -1 && err == ENOSYS)); } -ZTEST(posix_apis, test_sched_policy) -{ - /* - * TODO: - * 1. assert that _POSIX_PRIORITY_SCHEDULING is defined - * 2. if _POSIX_SPORADIC_SERVER or _POSIX_THREAD_SPORADIC_SERVER are defined, - * also check SCHED_SPORADIC - * 3. SCHED_OTHER is mandatory (but may be equivalent to SCHED_FIFO or SCHED_RR, - * and is implementation defined) - */ - - int pmin; - int pmax; - pthread_t th; - pthread_attr_t attr; - struct sched_param param; - static const int policies[] = { - SCHED_FIFO, - SCHED_RR, - SCHED_OTHER, - SCHED_INVALID, - }; - static const char *const policy_names[] = { - "SCHED_FIFO", - "SCHED_RR", - "SCHED_OTHER", - "SCHED_INVALID", - }; - static const bool policy_enabled[] = { - IS_ENABLED(CONFIG_COOP_ENABLED), - IS_ENABLED(CONFIG_PREEMPT_ENABLED), - IS_ENABLED(CONFIG_PREEMPT_ENABLED), - false, - }; - static int nprio[] = { - CONFIG_NUM_COOP_PRIORITIES, - CONFIG_NUM_PREEMPT_PRIORITIES, - CONFIG_NUM_PREEMPT_PRIORITIES, - 42, - }; - const char *const prios[] = {"pmin", "pmax"}; - - BUILD_ASSERT(!(SCHED_INVALID == SCHED_FIFO || SCHED_INVALID == SCHED_RR || - SCHED_INVALID == SCHED_OTHER), - "SCHED_INVALID is itself invalid"); - - for (int policy = 0; policy < ARRAY_SIZE(policies); ++policy) { - if (!policy_enabled[policy]) { - /* test degenerate cases */ - errno = 0; - zassert_equal(-1, sched_get_priority_min(policies[policy]), - "expected sched_get_priority_min(%s) to fail", - policy_names[policy]); - zassert_equal(EINVAL, errno, "sched_get_priority_min(%s) did not set errno", - policy_names[policy]); - - errno = 0; - zassert_equal(-1, sched_get_priority_max(policies[policy]), - "expected sched_get_priority_max(%s) to fail", - policy_names[policy]); - zassert_equal(EINVAL, errno, "sched_get_priority_max(%s) did not set errno", - policy_names[policy]); - continue; - } - - /* get pmin and pmax for policies[policy] */ - for (int i = 0; i < ARRAY_SIZE(prios); ++i) { - errno = 0; - if (i == 0) { - pmin = sched_get_priority_min(policies[policy]); - param.sched_priority = pmin; - } else { - pmax = sched_get_priority_max(policies[policy]); - param.sched_priority = pmax; - } - - zassert_not_equal(-1, param.sched_priority, - "sched_get_priority_%s(%s) failed: %d", - i == 0 ? "min" : "max", policy_names[policy], errno); - zassert_ok(errno, "sched_get_priority_%s(%s) set errno to %s", - i == 0 ? "min" : "max", policy_names[policy], errno); - } - - /* - * IEEE 1003.1-2008 Section 2.8.4 - * conforming implementations should provide a range of at least 32 priorities - * - * Note: we relax this requirement - */ - zassert_true(pmax > pmin, "pmax (%d) <= pmin (%d)", pmax, pmin, - "%s min/max inconsistency: pmin: %d pmax: %d", policy_names[policy], - pmin, pmax); - - /* - * Getting into the weeds a bit (i.e. whitebox testing), Zephyr - * cooperative threads use [-CONFIG_NUM_COOP_PRIORITIES,-1] and - * preemptive threads use [0, CONFIG_NUM_PREEMPT_PRIORITIES - 1], - * where the more negative thread has the higher priority. Since we - * cannot map those directly (a return value of -1 indicates error), - * we simply map those to the positive space. - */ - zassert_equal(pmin, 0, "unexpected pmin for %s", policy_names[policy]); - zassert_equal(pmax, nprio[policy] - 1, "unexpected pmax for %s", - policy_names[policy]); /* test happy paths */ - - for (int i = 0; i < ARRAY_SIZE(prios); ++i) { - /* create threads with min and max priority levels */ - zassert_ok(pthread_attr_init(&attr), - "pthread_attr_init() failed for %s (%d) of %s", prios[i], - param.sched_priority, policy_names[policy]); - - zassert_ok(pthread_attr_setschedpolicy(&attr, policies[policy]), - "pthread_attr_setschedpolicy() failed for %s (%d) of %s", - prios[i], param.sched_priority, policy_names[policy]); - - zassert_ok(pthread_attr_setschedparam(&attr, ¶m), - "pthread_attr_setschedparam() failed for %s (%d) of %s", - prios[i], param.sched_priority, policy_names[policy]); - - zassert_ok(pthread_attr_setstack(&attr, &stack_e[0][0], STACKS), - "pthread_attr_setstack() failed for %s (%d) of %s", prios[i], - param.sched_priority, policy_names[policy]); - - zassert_ok(pthread_create(&th, &attr, create_thread1, NULL), - "pthread_create() failed for %s (%d) of %s", prios[i], - param.sched_priority, policy_names[policy]); - - zassert_ok(pthread_join(th, NULL), - "pthread_join() failed for %s (%d) of %s", prios[i], - param.sched_priority, policy_names[policy]); - - zassert_ok(pthread_attr_destroy(&attr), - "pthread_attr_destroy() failed for %s (%d) of %s", prios[i], - param.sched_priority, policy_names[policy]); - } - } -} - -ZTEST(posix_apis, test_pthread_equal) +ZTEST(pthread, test_pthread_equal) { zassert_true(pthread_equal(pthread_self(), pthread_self())); zassert_false(pthread_equal(pthread_self(), (pthread_t)4242)); } -/* A 32-bit value to use between threads for validation */ -#define BIOS_FOOD 0xB105F00D - -static void *fun(void *arg) -{ - *((uint32_t *)arg) = BIOS_FOOD; - return NULL; -} - -ZTEST(posix_apis, test_pthread_dynamic_stacks) -{ - pthread_t th; - - if (!IS_ENABLED(CONFIG_DYNAMIC_THREAD)) { - ztest_test_skip(); - } - - zassert_ok(pthread_create(&th, NULL, fun, ¶m)); - zassert_ok(pthread_join(th, NULL)); - zassert_equal(BIOS_FOOD, param); -} - -static void *non_null_retval(void *arg) -{ - ARG_UNUSED(arg); - - return (void *)BIOS_FOOD; -} - -ZTEST(posix_apis, test_pthread_return_val) -{ - pthread_t pth; - void *ret = NULL; - - zassert_ok(pthread_create(&pth, NULL, non_null_retval, NULL)); - zassert_ok(pthread_join(pth, &ret)); - zassert_equal(ret, (void *)BIOS_FOOD); -} - -static void *detached(void *arg) -{ - ARG_UNUSED(arg); - - return NULL; -} - -ZTEST(posix_apis, test_pthread_join_detached) -{ - pthread_t pth; - - zassert_ok(pthread_create(&pth, NULL, detached, NULL)); - zassert_ok(pthread_detach(pth)); - /* note, this was required to be EINVAL previously but is now undefined behaviour */ - zassert_not_equal(0, pthread_join(pth, NULL)); - - /* need to allow this thread to be clean-up by the recycler */ - k_msleep(500); -} - -ZTEST(posix_apis, test_pthread_set_get_concurrency) +ZTEST(pthread, test_pthread_set_get_concurrency) { /* EINVAL if the value specified by new_level is negative */ zassert_equal(EINVAL, pthread_setconcurrency(-42)); @@ -887,7 +421,7 @@ static void *test_pthread_cleanup_entry(void *arg) return NULL; } -ZTEST(posix_apis, test_pthread_cleanup) +ZTEST(pthread, test_pthread_cleanup) { pthread_t th; @@ -895,60 +429,4 @@ ZTEST(posix_apis, test_pthread_cleanup) zassert_ok(pthread_join(th, NULL)); } -ZTEST(posix_apis, test_pthread_attr_getguardsize) -{ - size_t size_after; - pthread_attr_t attr; - const size_t size_before = OKAY_STACK_SIZE; - - attr = (pthread_attr_t){0}; - zassert_equal(pthread_attr_getguardsize(&attr, &size_after), EINVAL); - zassert_ok(pthread_attr_init(&attr)); - zassert_equal(pthread_attr_getguardsize(NULL, NULL), EINVAL); - zassert_equal(pthread_attr_getguardsize(NULL, &size_after), EINVAL); - zassert_equal(pthread_attr_getguardsize(&attr, NULL), EINVAL); - size_after = size_before; - zassert_ok(pthread_attr_getguardsize(&attr, &size_after)); - zassert_not_equal(size_before, size_after); - zassert_equal(size_after, CONFIG_POSIX_PTHREAD_ATTR_GUARDSIZE_DEFAULT); - zassert_ok(pthread_attr_destroy(&attr)); -} - -ZTEST(posix_apis, test_pthread_attr_setguardsize) -{ - size_t size_after; - size_t size_before; - pthread_attr_t attr; - size_t sizes[] = {0, BIT_MASK(CONFIG_POSIX_PTHREAD_ATTR_GUARDSIZE_BITS / 2), - BIT_MASK(CONFIG_POSIX_PTHREAD_ATTR_GUARDSIZE_BITS)}; - - attr = (pthread_attr_t){0}; - zassert_equal(pthread_attr_setguardsize(&attr, 0), EINVAL); - zassert_ok(pthread_attr_init(&attr)); - zassert_ok(pthread_attr_getguardsize(&attr, &size_before)); - zassert_equal(size_before, CONFIG_POSIX_PTHREAD_ATTR_GUARDSIZE_DEFAULT); - zassert_equal(pthread_attr_setguardsize(NULL, SIZE_MAX), EINVAL); - zassert_equal(pthread_attr_setguardsize(NULL, 0), EINVAL); - zassert_equal(pthread_attr_setguardsize(&attr, SIZE_MAX), EINVAL); - for (size_t i = 0; i < ARRAY_SIZE(sizes); ++i) { - size_after = ~sizes[i]; - size_before = sizes[i]; - zassert_ok(pthread_attr_setguardsize(&attr, size_before)); - zassert_ok(pthread_attr_getguardsize(&attr, &size_after)); - zassert_equal(size_before, size_after); - } - zassert_ok(pthread_attr_destroy(&attr)); -} - -ZTEST(posix_apis, test_pthread_attr_large_stacksize) -{ - size_t actual_size; - const size_t expect_size = BIT(CONFIG_POSIX_PTHREAD_ATTR_STACKSIZE_BITS); - pthread_attr_t attr; - - zassert_ok(pthread_attr_init(&attr)); - zassert_ok(pthread_attr_setstacksize(&attr, expect_size)); - zassert_ok(pthread_attr_getstacksize(&attr, &actual_size)); - zassert_equal(actual_size, expect_size); - zassert_ok(pthread_attr_destroy(&attr)); -} +ZTEST_SUITE(pthread, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/posix/common/src/pthread_attr.c b/tests/posix/common/src/pthread_attr.c new file mode 100644 index 000000000000..f2a9b2da22f1 --- /dev/null +++ b/tests/posix/common/src/pthread_attr.c @@ -0,0 +1,636 @@ +/* + * Copyright (c) 2024, Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#define BIOS_FOOD 0xB105F00D +#define SCHED_INVALID 4242 +#define INVALID_DETACHSTATE 7373 + +static bool attr_valid; +static pthread_attr_t attr; +static const pthread_attr_t uninit_attr; +static bool detached_thread_has_finished; + +/* TODO: this should be optional */ +#define STATIC_THREAD_STACK_SIZE (MAX(1024, PTHREAD_STACK_MIN + CONFIG_TEST_EXTRA_STACK_SIZE)) +static K_THREAD_STACK_DEFINE(static_thread_stack, STATIC_THREAD_STACK_SIZE); + +static void *thread_entry(void *arg) +{ + bool joinable = (bool)POINTER_TO_UINT(arg); + + if (!joinable) { + detached_thread_has_finished = true; + } + + return NULL; +} + +static void create_thread_common(const pthread_attr_t *attrp, bool expect_success, bool joinable) +{ + pthread_t th; + + if (!joinable) { + detached_thread_has_finished = false; + } + + if (expect_success) { + zassert_ok(pthread_create(&th, attrp, thread_entry, UINT_TO_POINTER(joinable))); + } else { + zassert_not_ok(pthread_create(&th, attrp, thread_entry, UINT_TO_POINTER(joinable))); + return; + } + + if (joinable) { + zassert_ok(pthread_join(th, NULL), "failed to join joinable thread"); + return; + } + + /* should not be able to join detached thread */ + zassert_not_ok(pthread_join(th, NULL)); + + for (size_t i = 0; i < 10; ++i) { + k_msleep(2 * CONFIG_PTHREAD_RECYCLER_DELAY_MS); + if (detached_thread_has_finished) { + break; + } + } + + zassert_true(detached_thread_has_finished, "detached thread did not seem to finish"); +} + +static inline void can_create_thread(const pthread_attr_t *attrp) +{ + create_thread_common(attrp, true, true); +} + +static inline void cannot_create_thread(const pthread_attr_t *attrp) +{ + create_thread_common(attrp, false, true); +} + +ZTEST(pthread_attr, test_null_attr) +{ + /* + * This test can only succeed when it is possible to call pthread_create() with a NULL + * pthread_attr_t* (I.e. when we have the ability to allocate thread stacks dynamically). + */ + create_thread_common(NULL, IS_ENABLED(CONFIG_DYNAMIC_THREAD) ? true : false, true); +} + +ZTEST(pthread_attr, test_pthread_attr_static_corner_cases) +{ + pthread_attr_t attr1; + + Z_TEST_SKIP_IFDEF(CONFIG_DYNAMIC_THREAD); + + /* + * These tests are specifically for when dynamic thread stacks are disabled, so passing + * a NULL pthread_attr_t* should fail. + */ + cannot_create_thread(NULL); + + /* + * Additionally, without calling pthread_attr_setstack(), thread creation should fail. + */ + zassert_ok(pthread_attr_init(&attr1)); + cannot_create_thread(&attr1); +} + +ZTEST(pthread_attr, test_pthread_attr_init_destroy) +{ + /* attr has already been initialized in before() */ + + if (false) { + /* undefined behaviour */ + zassert_ok(pthread_attr_init(&attr)); + } + + /* cannot destroy an uninitialized attr */ + zassert_equal(pthread_attr_destroy((pthread_attr_t *)&uninit_attr), EINVAL); + + can_create_thread(&attr); + + /* can destroy an initialized attr */ + zassert_ok(pthread_attr_destroy(&attr), "failed to destroy an initialized attr"); + attr_valid = false; + + cannot_create_thread(&attr); + + if (false) { + /* undefined behaviour */ + zassert_ok(pthread_attr_destroy(&attr)); + } + + /* can re-initialize a destroyed attr */ + zassert_ok(pthread_attr_init(&attr)); + /* TODO: pthread_attr_init() should be sufficient to initialize a thread by itself */ + zassert_ok(pthread_attr_setstack(&attr, &static_thread_stack, STATIC_THREAD_STACK_SIZE)); + attr_valid = true; + + can_create_thread(&attr); + + /* note: attr is still valid and is destroyed in after() */ +} + +ZTEST(pthread_attr, test_pthread_attr_getguardsize) +{ + size_t guardsize; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_getguardsize(NULL, NULL), EINVAL); + zassert_equal(pthread_attr_getguardsize(NULL, &guardsize), EINVAL); + zassert_equal(pthread_attr_getguardsize(&uninit_attr, &guardsize), EINVAL); + } + zassert_equal(pthread_attr_getguardsize(&attr, NULL), EINVAL); + } + + guardsize = BIOS_FOOD; + zassert_ok(pthread_attr_getguardsize(&attr, &guardsize)); + zassert_not_equal(guardsize, BIOS_FOOD); +} + +ZTEST(pthread_attr, test_pthread_attr_setguardsize) +{ + size_t guardsize = CONFIG_POSIX_PTHREAD_ATTR_GUARDSIZE_DEFAULT; + size_t sizes[] = {0, BIT_MASK(CONFIG_POSIX_PTHREAD_ATTR_GUARDSIZE_BITS / 2), + BIT_MASK(CONFIG_POSIX_PTHREAD_ATTR_GUARDSIZE_BITS)}; + + /* valid value */ + zassert_ok(pthread_attr_getguardsize(&attr, &guardsize)); + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_setguardsize(NULL, SIZE_MAX), EINVAL); + zassert_equal(pthread_attr_setguardsize(NULL, guardsize), EINVAL); + zassert_equal(pthread_attr_setguardsize((pthread_attr_t *)&uninit_attr, + guardsize), + EINVAL); + } + zassert_equal(pthread_attr_setguardsize(&attr, SIZE_MAX), EINVAL); + } + + ARRAY_FOR_EACH(sizes, i) { + zassert_ok(pthread_attr_setguardsize(&attr, sizes[i])); + guardsize = ~sizes[i]; + zassert_ok(pthread_attr_getguardsize(&attr, &guardsize)); + zassert_equal(guardsize, sizes[i]); + } +} + +ZTEST(pthread_attr, test_pthread_attr_getschedparam) +{ + struct sched_param param = { + .sched_priority = BIOS_FOOD, + }; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_getschedparam(NULL, NULL), EINVAL); + zassert_equal(pthread_attr_getschedparam(NULL, ¶m), EINVAL); + zassert_equal(pthread_attr_getschedparam(&uninit_attr, ¶m), EINVAL); + } + zassert_equal(pthread_attr_getschedparam(&attr, NULL), EINVAL); + } + + /* only check to see that the function succeeds and sets param */ + zassert_ok(pthread_attr_getschedparam(&attr, ¶m)); + zassert_not_equal(BIOS_FOOD, param.sched_priority); +} + +ZTEST(pthread_attr, test_pthread_attr_setschedparam) +{ + struct sched_param param = {0}; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_setschedparam(NULL, NULL), EINVAL); + zassert_equal(pthread_attr_setschedparam(NULL, ¶m), EINVAL); + zassert_equal( + pthread_attr_setschedparam((pthread_attr_t *)&uninit_attr, ¶m), + EINVAL); + } + zassert_equal(pthread_attr_setschedparam(&attr, NULL), EINVAL); + } + + zassert_ok(pthread_attr_setschedparam(&attr, ¶m)); + + can_create_thread(&attr); +} + +ZTEST(pthread_attr, test_pthread_attr_getschedpolicy) +{ + int policy = BIOS_FOOD; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_getschedpolicy(NULL, NULL), EINVAL); + zassert_equal(pthread_attr_getschedpolicy(NULL, &policy), EINVAL); + zassert_equal(pthread_attr_getschedpolicy(&uninit_attr, &policy), EINVAL); + } + zassert_equal(pthread_attr_getschedpolicy(&attr, NULL), EINVAL); + } + + /* only check to see that the function succeeds and sets policy */ + zassert_ok(pthread_attr_getschedpolicy(&attr, &policy)); + zassert_not_equal(BIOS_FOOD, policy); +} + +ZTEST(pthread_attr, test_pthread_attr_setschedpolicy) +{ + int policy = SCHED_OTHER; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_setschedpolicy(NULL, SCHED_INVALID), EINVAL); + zassert_equal(pthread_attr_setschedpolicy(NULL, policy), EINVAL); + zassert_equal( + pthread_attr_setschedpolicy((pthread_attr_t *)&uninit_attr, policy), + EINVAL); + } + zassert_equal(pthread_attr_setschedpolicy(&attr, SCHED_INVALID), EINVAL); + } + + zassert_ok(pthread_attr_setschedpolicy(&attr, SCHED_OTHER)); + /* read back the same policy we just wrote */ + policy = SCHED_INVALID; + zassert_ok(pthread_attr_getschedpolicy(&attr, &policy)); + zassert_equal(policy, SCHED_OTHER); + + can_create_thread(&attr); +} + +ZTEST(pthread_attr, test_pthread_attr_getstack) +{ + void *stackaddr = (void *)BIOS_FOOD; + size_t stacksize = BIOS_FOOD; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_getstack(NULL, NULL, NULL), EINVAL); + zassert_equal(pthread_attr_getstack(NULL, NULL, &stacksize), EINVAL); + zassert_equal(pthread_attr_getstack(NULL, &stackaddr, NULL), EINVAL); + zassert_equal(pthread_attr_getstack(NULL, &stackaddr, &stacksize), EINVAL); + zassert_equal(pthread_attr_getstack(&uninit_attr, &stackaddr, &stacksize), + EINVAL); + } + zassert_equal(pthread_attr_getstack(&attr, NULL, NULL), EINVAL); + zassert_equal(pthread_attr_getstack(&attr, NULL, &stacksize), EINVAL); + zassert_equal(pthread_attr_getstack(&attr, &stackaddr, NULL), EINVAL); + } + + zassert_ok(pthread_attr_getstack(&attr, &stackaddr, &stacksize)); + zassert_not_equal(stackaddr, (void *)BIOS_FOOD); + zassert_not_equal(stacksize, BIOS_FOOD); +} + +ZTEST(pthread_attr, test_pthread_attr_setstack) +{ + void *stackaddr; + size_t stacksize; + void *new_stackaddr; + size_t new_stacksize; + + /* valid values */ + zassert_ok(pthread_attr_getstack(&attr, &stackaddr, &stacksize)); + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_setstack(NULL, NULL, 0), EACCES); + zassert_equal(pthread_attr_setstack(NULL, NULL, stacksize), EINVAL); + zassert_equal(pthread_attr_setstack(NULL, stackaddr, 0), EINVAL); + zassert_equal(pthread_attr_setstack(NULL, stackaddr, stacksize), EINVAL); + zassert_equal(pthread_attr_setstack((pthread_attr_t *)&uninit_attr, + stackaddr, stacksize), + EINVAL); + } + zassert_equal(pthread_attr_setstack(&attr, NULL, 0), EACCES); + zassert_equal(pthread_attr_setstack(&attr, NULL, stacksize), EACCES); + zassert_equal(pthread_attr_setstack(&attr, stackaddr, 0), EINVAL); + } + + /* ensure we can create and join a thread with the default attrs */ + can_create_thread(&attr); + + /* set stack / addr to the current values of stack / addr */ + zassert_ok(pthread_attr_setstack(&attr, stackaddr, stacksize)); + can_create_thread(&attr); + + /* qemu_x86 seems to be unable to set thread stacks to be anything less than 4096 */ + if (!IS_ENABLED(CONFIG_X86)) { + /* + * check we can set a smaller stacksize + * should not require dynamic reallocation + * size may get rounded up to some alignment internally + */ + zassert_ok(pthread_attr_setstack(&attr, stackaddr, stacksize - 1)); + /* ensure we read back the same values as we specified */ + zassert_ok(pthread_attr_getstack(&attr, &new_stackaddr, &new_stacksize)); + zassert_equal(new_stackaddr, stackaddr); + zassert_equal(new_stacksize, stacksize - 1); + can_create_thread(&attr); + } + + if (IS_ENABLED(DYNAMIC_THREAD_ALLOC)) { + /* ensure we can set a dynamic stack */ + k_thread_stack_t *stack; + + stack = k_thread_stack_alloc(2 * stacksize, 0); + zassert_not_null(stack); + + zassert_ok(pthread_attr_setstack(&attr, (void *)stack, 2 * stacksize)); + /* ensure we read back the same values as we specified */ + zassert_ok(pthread_attr_getstack(&attr, &new_stackaddr, &new_stacksize)); + zassert_equal(new_stackaddr, (void *)stack); + zassert_equal(new_stacksize, 2 * stacksize); + can_create_thread(&attr); + } +} + +ZTEST(pthread_attr, test_pthread_attr_getstacksize) +{ + size_t stacksize = BIOS_FOOD; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_getstacksize(NULL, NULL), EINVAL); + zassert_equal(pthread_attr_getstacksize(NULL, &stacksize), EINVAL); + zassert_equal(pthread_attr_getstacksize(&uninit_attr, &stacksize), EINVAL); + } + zassert_equal(pthread_attr_getstacksize(&attr, NULL), EINVAL); + } + + zassert_ok(pthread_attr_getstacksize(&attr, &stacksize)); + zassert_not_equal(stacksize, BIOS_FOOD); +} + +ZTEST(pthread_attr, test_pthread_attr_setstacksize) +{ + size_t stacksize; + size_t new_stacksize; + + /* valid size */ + zassert_ok(pthread_attr_getstacksize(&attr, &stacksize)); + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_setstacksize(NULL, 0), EINVAL); + zassert_equal(pthread_attr_setstacksize(NULL, stacksize), EINVAL); + zassert_equal(pthread_attr_setstacksize((pthread_attr_t *)&uninit_attr, + stacksize), + EINVAL); + } + zassert_equal(pthread_attr_setstacksize(&attr, 0), EINVAL); + } + + /* ensure we can spin up a thread with the default stack size */ + can_create_thread(&attr); + + /* set stack / addr to the current values of stack / addr */ + zassert_ok(pthread_attr_setstacksize(&attr, stacksize)); + /* ensure we can read back the values we just set */ + zassert_ok(pthread_attr_getstacksize(&attr, &new_stacksize)); + zassert_equal(new_stacksize, stacksize); + can_create_thread(&attr); + + /* qemu_x86 seems to be unable to set thread stacks to be anything less than 4096 */ + if (!IS_ENABLED(CONFIG_X86)) { + zassert_ok(pthread_attr_setstacksize(&attr, stacksize - 1)); + /* ensure we can read back the values we just set */ + zassert_ok(pthread_attr_getstacksize(&attr, &new_stacksize)); + zassert_equal(new_stacksize, stacksize - 1); + can_create_thread(&attr); + } + + if (IS_ENABLED(CONFIG_DYNAMIC_THREAD_ALLOC)) { + zassert_ok(pthread_attr_setstacksize(&attr, 2 * stacksize)); + /* ensure we read back the same values as we specified */ + zassert_ok(pthread_attr_getstacksize(&attr, &new_stacksize)); + zassert_equal(new_stacksize, 2 * stacksize); + can_create_thread(&attr); + } +} + +ZTEST(pthread_attr, test_pthread_attr_large_stacksize) +{ + size_t actual_size; + const size_t expect_size = BIT(CONFIG_POSIX_PTHREAD_ATTR_STACKSIZE_BITS); + + zassert_ok(pthread_attr_setstacksize(&attr, expect_size)); + zassert_ok(pthread_attr_getstacksize(&attr, &actual_size)); + zassert_equal(actual_size, expect_size); +} + +ZTEST(pthread_attr, test_pthread_attr_getdetachstate) +{ + int detachstate; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_getdetachstate(NULL, NULL), EINVAL); + zassert_equal(pthread_attr_getdetachstate(NULL, &detachstate), EINVAL); + zassert_equal(pthread_attr_getdetachstate(&uninit_attr, &detachstate), + EINVAL); + } + zassert_equal(pthread_attr_getdetachstate(&attr, NULL), EINVAL); + } + + /* default detachstate is joinable */ + zassert_ok(pthread_attr_getdetachstate(&attr, &detachstate)); + zassert_equal(detachstate, PTHREAD_CREATE_JOINABLE); + can_create_thread(&attr); +} + +ZTEST(pthread_attr, test_pthread_attr_setdetachstate) +{ + int detachstate = PTHREAD_CREATE_JOINABLE; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_setdetachstate(NULL, INVALID_DETACHSTATE), + EINVAL); + zassert_equal(pthread_attr_setdetachstate(NULL, detachstate), EINVAL); + zassert_equal(pthread_attr_setdetachstate((pthread_attr_t *)&uninit_attr, + detachstate), + EINVAL); + } + zassert_equal(pthread_attr_setdetachstate(&attr, INVALID_DETACHSTATE), EINVAL); + } + + /* read back detachstate just written */ + zassert_ok(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); + zassert_ok(pthread_attr_getdetachstate(&attr, &detachstate)); + zassert_equal(detachstate, PTHREAD_CREATE_DETACHED); + create_thread_common(&attr, true, false); +} + +ZTEST(pthread_attr, test_pthread_attr_policy_and_priority_limits) +{ + int pmin = -1; + int pmax = -1; + struct sched_param param; + static const int policies[] = { + SCHED_FIFO, + SCHED_RR, + SCHED_OTHER, + SCHED_INVALID, + }; + static const char *const policy_names[] = { + "SCHED_FIFO", + "SCHED_RR", + "SCHED_OTHER", + "SCHED_INVALID", + }; + static const bool policy_enabled[] = { + CONFIG_NUM_COOP_PRIORITIES > 0, + CONFIG_NUM_PREEMPT_PRIORITIES > 0, + CONFIG_NUM_PREEMPT_PRIORITIES > 0, + false, + }; + static int nprio[] = { + CONFIG_NUM_COOP_PRIORITIES, + CONFIG_NUM_PREEMPT_PRIORITIES, + CONFIG_NUM_PREEMPT_PRIORITIES, + 42, + }; + const char *const prios[] = {"pmin", "pmax"}; + + BUILD_ASSERT(!(SCHED_INVALID == SCHED_FIFO || SCHED_INVALID == SCHED_RR || + SCHED_INVALID == SCHED_OTHER), + "SCHED_INVALID is itself invalid"); + + ARRAY_FOR_EACH(policies, policy) { + /* get pmin and pmax for policies[policy] */ + ARRAY_FOR_EACH(prios, i) { + errno = 0; + if (i == 0) { + pmin = sched_get_priority_min(policies[policy]); + param.sched_priority = pmin; + } else { + pmax = sched_get_priority_max(policies[policy]); + param.sched_priority = pmax; + } + + if (policy == 3) { + /* invalid policy */ + zassert_equal(-1, param.sched_priority); + zassert_equal(errno, EINVAL); + continue; + } + + zassert_not_equal(-1, param.sched_priority, + "sched_get_priority_%s(%s) failed: %d", + i == 0 ? "min" : "max", policy_names[policy], errno); + zassert_ok(errno, "sched_get_priority_%s(%s) set errno to %s", + i == 0 ? "min" : "max", policy_names[policy], errno); + } + + if (policy != 3) { + /* this will not work for SCHED_INVALID */ + + /* + * IEEE 1003.1-2008 Section 2.8.4 + * conforming implementations should provide a range of at least 32 + * priorities + * + * Note: we relax this requirement + */ + zassert_true(pmax > pmin, "pmax (%d) <= pmin (%d)", pmax, pmin, + "%s min/max inconsistency: pmin: %d pmax: %d", + policy_names[policy], pmin, pmax); + + /* + * Getting into the weeds a bit (i.e. whitebox testing), Zephyr + * cooperative threads use [-CONFIG_NUM_COOP_PRIORITIES,-1] and + * preemptive threads use [0, CONFIG_NUM_PREEMPT_PRIORITIES - 1], + * where the more negative thread has the higher priority. Since we + * cannot map those directly (a return value of -1 indicates error), + * we simply map those to the positive space. + */ + zassert_equal(pmin, 0, "unexpected pmin for %s", policy_names[policy]); + zassert_equal(pmax, nprio[policy] - 1, "unexpected pmax for %s", + policy_names[policy]); /* test happy paths */ + } + + /* create threads with min and max priority levels for each policy */ + ARRAY_FOR_EACH(prios, i) { + param.sched_priority = (i == 0) ? pmin : pmax; + + if (!policy_enabled[policy]) { + zassert_not_ok( + pthread_attr_setschedpolicy(&attr, policies[policy])); + zassert_not_ok( + pthread_attr_setschedparam(&attr, ¶m), + "pthread_attr_setschedparam() failed for %s (%d) of %s", + prios[i], param.sched_priority, policy_names[policy]); + continue; + } + + /* set policy */ + zassert_ok(pthread_attr_setschedpolicy(&attr, policies[policy]), + "pthread_attr_setschedpolicy() failed for %s (%d) of %s", + prios[i], param.sched_priority, policy_names[policy]); + + /* set priority */ + zassert_ok(pthread_attr_setschedparam(&attr, ¶m), + "pthread_attr_setschedparam() failed for %s (%d) of %s", + prios[i], param.sched_priority, policy_names[policy]); + + can_create_thread(&attr); + } + } +} + +static void before(void *arg) +{ + ARG_UNUSED(arg); + + zassert_ok(pthread_attr_init(&attr)); + /* TODO: pthread_attr_init() should be sufficient to initialize a thread by itself */ + zassert_ok(pthread_attr_setstack(&attr, &static_thread_stack, STATIC_THREAD_STACK_SIZE)); + attr_valid = true; +} + +static void after(void *arg) +{ + ARG_UNUSED(arg); + + if (attr_valid) { + (void)pthread_attr_destroy(&attr); + attr_valid = false; + } +} + +ZTEST_SUITE(pthread_attr, NULL, NULL, before, after, NULL);