diff --git a/tests/posix/threads_base/CMakeLists.txt b/tests/posix/threads_base/CMakeLists.txt new file mode 100644 index 000000000000..e6f2ff73a02b --- /dev/null +++ b/tests/posix/threads_base/CMakeLists.txt @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(posix_threads_base) + +FILE(GLOB app_sources src/*.c) + +target_sources(app PRIVATE ${app_sources}) + +target_compile_options(app PRIVATE -U_POSIX_C_SOURCE -D_POSIX_C_SOURCE=200809L) diff --git a/tests/posix/threads_base/prj.conf b/tests/posix/threads_base/prj.conf new file mode 100644 index 000000000000..174f0ce0e8d7 --- /dev/null +++ b/tests/posix/threads_base/prj.conf @@ -0,0 +1,13 @@ +CONFIG_POSIX_API=y +CONFIG_ZTEST=y + +CONFIG_POSIX_AEP_CHOICE_BASE=y +CONFIG_POSIX_THREADS=y +CONFIG_THREAD_NAME=y +CONFIG_DYNAMIC_THREAD=y +CONFIG_THREAD_STACK_INFO=y +CONFIG_DYNAMIC_THREAD_POOL_SIZE=3 +CONFIG_POSIX_THREAD_THREADS_MAX=3 +CONFIG_HEAP_MEM_POOL_SIZE=4096 +CONFIG_POSIX_THREAD_KEYS_MAX=6 +CONFIG_TEST_EXTRA_STACK_SIZE=4096 diff --git a/tests/posix/common/src/cond.c b/tests/posix/threads_base/src/cond.c similarity index 92% rename from tests/posix/common/src/cond.c rename to tests/posix/threads_base/src/cond.c index e269b1f9d117..85e6f2114875 100644 --- a/tests/posix/common/src/cond.c +++ b/tests/posix/threads_base/src/cond.c @@ -13,7 +13,7 @@ * * @details Exactly CONFIG_MAX_PTHREAD_COND_COUNT can be in use at once. */ -ZTEST(cond, test_cond_resource_exhausted) +ZTEST(posix_threads_base, test_cond_resource_exhausted) { size_t i; pthread_cond_t m[CONFIG_MAX_PTHREAD_COND_COUNT + 1]; @@ -37,7 +37,7 @@ ZTEST(cond, test_cond_resource_exhausted) * * @details Demonstrate that condition variables may be used over and over again. */ -ZTEST(cond, test_cond_resource_leak) +ZTEST(posix_threads_base, test_cond_resource_leak) { pthread_cond_t cond; @@ -47,7 +47,7 @@ ZTEST(cond, test_cond_resource_leak) } } -ZTEST(cond, test_pthread_condattr) +ZTEST(posix_threads_base, test_pthread_condattr) { pthread_condattr_t att = {0}; diff --git a/tests/posix/common/src/key.c b/tests/posix/threads_base/src/key.c similarity index 88% rename from tests/posix/common/src/key.c rename to tests/posix/threads_base/src/key.c index 693f4dccaa9d..42fa79e9b878 100644 --- a/tests/posix/common/src/key.c +++ b/tests/posix/threads_base/src/key.c @@ -9,7 +9,7 @@ #include #include -#define N_THR 2 +#define N_THR 2 #define N_KEY 2 #define BUFFSZ 48 @@ -74,7 +74,7 @@ static void make_keys(void) * multiple keys. */ -ZTEST(key, test_key_1toN_thread) +ZTEST(posix_threads_base, test_key_1toN_thread) { void *retval; pthread_t newthread[N_THR]; @@ -95,7 +95,7 @@ ZTEST(key, test_key_1toN_thread) zassert_ok(pthread_key_delete(key), "attempt to delete key failed"); } -ZTEST(key, test_key_Nto1_thread) +ZTEST(posix_threads_base, test_key_Nto1_thread) { pthread_t newthread; @@ -113,7 +113,7 @@ ZTEST(key, test_key_Nto1_thread) } } -ZTEST(key, test_key_resource_leak) +ZTEST(posix_threads_base, test_key_resource_leak) { pthread_key_t key; @@ -123,7 +123,7 @@ ZTEST(key, test_key_resource_leak) } } -ZTEST(key, test_correct_key_is_deleted) +ZTEST(posix_threads_base, test_correct_key_is_deleted) { pthread_key_t key; size_t j = CONFIG_POSIX_THREAD_KEYS_MAX - 1; @@ -162,34 +162,21 @@ static void *setspecific_thread(void *count) return NULL; } -ZTEST(key, test_thread_specific_data_deallocation) +ZTEST(posix_threads_base, test_thread_specific_data_deallocation) { pthread_t thread; static int alloc_count_t0; static int alloc_count_t1; zassert_ok(pthread_create(&thread, NULL, setspecific_thread, &alloc_count_t0), - "attempt to create thread failed"); + "attempt to create thread failed"); zassert_ok(pthread_join(thread, NULL), "failed to join thread"); printk("first thread allocated %d keys", alloc_count_t0); zassert_ok(pthread_create(&thread, NULL, setspecific_thread, &alloc_count_t1), - "attempt to create thread failed"); + "attempt to create thread failed"); zassert_ok(pthread_join(thread, NULL), "failed to join thread"); printk("second thread allocated %d keys", alloc_count_t1); - zassert_equal(alloc_count_t0, alloc_count_t1, - "failed to deallocate thread specific data"); -} - -static void before(void *arg) -{ - ARG_UNUSED(arg); - - if (!IS_ENABLED(CONFIG_DYNAMIC_THREAD)) { - /* skip redundant testing if there is no thread pool / heap allocation */ - ztest_test_skip(); - } + zassert_equal(alloc_count_t0, alloc_count_t1, "failed to deallocate thread specific data"); } - -ZTEST_SUITE(key, NULL, NULL, before, NULL, NULL); diff --git a/tests/posix/threads_base/src/main.c b/tests/posix/threads_base/src/main.c new file mode 100644 index 000000000000..3ea7a0de3e98 --- /dev/null +++ b/tests/posix/threads_base/src/main.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2023, Meta + * Copyright (c) 2024, Marvin Ouma + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +extern void before(void *arg); +extern void after(void *arg); + +ZTEST_SUITE(posix_threads_base, NULL, NULL, before, after, NULL); diff --git a/tests/posix/common/src/mutex.c b/tests/posix/threads_base/src/mutex.c similarity index 92% rename from tests/posix/common/src/mutex.c rename to tests/posix/threads_base/src/mutex.c index 40987d50297a..bca25ca1d0bb 100644 --- a/tests/posix/common/src/mutex.c +++ b/tests/posix/threads_base/src/mutex.c @@ -85,7 +85,7 @@ static void test_mutex_common(int type, void *(*entry)(void *arg)) zassert_ok(pthread_mutex_destroy(&mutex), "Destroying mutex is failed"); } -ZTEST(mutex, test_mutex_prioceiling_stubs) +ZTEST(posix_threads_base, test_mutex_prioceiling_stubs) { #ifdef CONFIG_POSIX_THREAD_PRIO_PROTECT zassert_equal(pthread_mutex_getprioceiling(NULL, NULL), ENOSYS); @@ -104,7 +104,7 @@ ZTEST(mutex, test_mutex_prioceiling_stubs) * and pthread_mutex_lock are tested with mutex type being * normal. */ -ZTEST(mutex, test_mutex_normal) +ZTEST(posix_threads_base, test_mutex_normal) { test_mutex_common(PTHREAD_MUTEX_NORMAL, normal_mutex_entry); } @@ -116,7 +116,7 @@ ZTEST(mutex, test_mutex_normal) * twice and unlocked for the same number of time. * */ -ZTEST(mutex, test_mutex_recursive) +ZTEST(posix_threads_base, test_mutex_recursive) { test_mutex_common(PTHREAD_MUTEX_RECURSIVE, recursive_mutex_entry); } @@ -126,7 +126,7 @@ ZTEST(mutex, test_mutex_recursive) * * @details Exactly CONFIG_MAX_PTHREAD_MUTEX_COUNT can be in use at once. */ -ZTEST(mutex, test_mutex_resource_exhausted) +ZTEST(posix_threads_base, test_mutex_resource_exhausted) { size_t i; pthread_mutex_t m[CONFIG_MAX_PTHREAD_MUTEX_COUNT + 1]; @@ -150,7 +150,7 @@ ZTEST(mutex, test_mutex_resource_exhausted) * * @details Demonstrate that mutexes may be used over and over again. */ -ZTEST(mutex, test_mutex_resource_leak) +ZTEST(posix_threads_base, test_mutex_resource_leak) { pthread_mutex_t m; @@ -189,7 +189,7 @@ static void *test_mutex_timedlock_fn(void *arg) } /** @brief Test to verify @ref pthread_mutex_timedlock returns ETIMEDOUT */ -ZTEST(mutex, test_mutex_timedlock) +ZTEST(posix_threads_base, test_mutex_timedlock) { void *ret; pthread_t th; @@ -215,15 +215,3 @@ ZTEST(mutex, test_mutex_timedlock) zassert_ok(pthread_mutex_destroy(&mutex)); } - -static void before(void *arg) -{ - ARG_UNUSED(arg); - - if (!IS_ENABLED(CONFIG_DYNAMIC_THREAD)) { - /* skip redundant testing if there is no thread pool / heap allocation */ - ztest_test_skip(); - } -} - -ZTEST_SUITE(mutex, NULL, NULL, before, NULL, NULL); diff --git a/tests/posix/common/src/mutex_attr.c b/tests/posix/threads_base/src/mutex_attr.c similarity index 83% rename from tests/posix/common/src/mutex_attr.c rename to tests/posix/threads_base/src/mutex_attr.c index d5f70083d412..c4740ab40809 100644 --- a/tests/posix/common/src/mutex_attr.c +++ b/tests/posix/threads_base/src/mutex_attr.c @@ -9,7 +9,7 @@ #include -ZTEST(mutex_attr, test_pthread_mutexattr_init) +ZTEST(posix_threads_base, test_pthread_mutexattr_init) { pthread_mutexattr_t attr; @@ -22,7 +22,7 @@ ZTEST(mutex_attr, test_pthread_mutexattr_init) zassert_ok(pthread_mutexattr_destroy(&attr)); } -ZTEST(mutex_attr, test_pthread_mutexattr_destroy) +ZTEST(posix_threads_base, test_pthread_mutexattr_destroy) { pthread_mutexattr_t attr; @@ -42,5 +42,3 @@ ZTEST(mutex_attr, test_pthread_mutexattr_destroy) zassert_equal(EINVAL, pthread_mutexattr_destroy(&attr)); } } - -ZTEST_SUITE(mutex_attr, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/posix/common/src/pthread.c b/tests/posix/threads_base/src/pthread.c similarity index 95% rename from tests/posix/common/src/pthread.c rename to tests/posix/threads_base/src/pthread.c index 035730ee4dc7..b953c9fd5aef 100644 --- a/tests/posix/common/src/pthread.c +++ b/tests/posix/threads_base/src/pthread.c @@ -226,7 +226,7 @@ static void *thread_top_term(void *p1) /* Test the internal priority conversion functions */ int zephyr_to_posix_priority(int z_prio, int *policy); int posix_to_zephyr_priority(int priority, int policy); -ZTEST(pthread, test_pthread_priority_conversion) +ZTEST(posix_threads_base, test_pthread_priority_conversion) { /* * ZEPHYR [-CONFIG_NUM_COOP_PRIORITIES, -1] @@ -256,7 +256,7 @@ ZTEST(pthread, test_pthread_priority_conversion) } } -ZTEST(pthread, test_pthread_execution) +ZTEST(posix_threads_base, test_pthread_execution) { int i, ret; pthread_t newthread[N_THR_E]; @@ -346,7 +346,7 @@ ZTEST(pthread, test_pthread_execution) printk("Barrier test OK\n"); } -ZTEST(pthread, test_pthread_termination) +ZTEST(posix_threads_base, test_pthread_termination) { int32_t i, ret; pthread_t newthread[N_THR_T] = {0}; @@ -376,7 +376,7 @@ ZTEST(pthread, test_pthread_termination) zassert_equal(ret, ESRCH, "cancelled a terminated thread!"); } -ZTEST(pthread, test_pthread_tryjoin) +ZTEST(posix_threads_base, test_pthread_tryjoin) { pthread_t th = {0}; int sleep_duration_ms = 200; @@ -396,7 +396,7 @@ ZTEST(pthread, test_pthread_tryjoin) zassert_ok(pthread_tryjoin_np(th, &retval)); } -ZTEST(pthread, test_pthread_timedjoin) +ZTEST(posix_threads_base, test_pthread_timedjoin) { pthread_t th = {0}; int sleep_duration_ms = 200; @@ -444,7 +444,7 @@ static void *create_thread1(void *p1) return NULL; } -ZTEST(pthread, test_pthread_descriptor_leak) +ZTEST(posix_threads_base, test_pthread_descriptor_leak) { pthread_t pthread1; @@ -456,7 +456,7 @@ ZTEST(pthread, test_pthread_descriptor_leak) } } -ZTEST(pthread, test_pthread_equal) +ZTEST(posix_threads_base, test_pthread_equal) { zassert_true(pthread_equal(pthread_self(), pthread_self())); zassert_false(pthread_equal(pthread_self(), (pthread_t)4242)); @@ -484,7 +484,7 @@ static void *test_pthread_cleanup_entry(void *arg) return NULL; } -ZTEST(pthread, test_pthread_cleanup) +ZTEST(posix_threads_base, test_pthread_cleanup) { pthread_t th; @@ -522,7 +522,7 @@ static void *test_pthread_cancel_fn(void *arg) return NULL; } -ZTEST(pthread, test_pthread_testcancel) +ZTEST(posix_threads_base, test_pthread_testcancel) { pthread_t th; @@ -550,22 +550,10 @@ static void *test_pthread_setschedprio_fn(void *arg) return NULL; } -ZTEST(pthread, test_pthread_setschedprio) +ZTEST(posix_threads_base, test_pthread_setschedprio) { pthread_t th; zassert_ok(pthread_create(&th, NULL, test_pthread_setschedprio_fn, NULL)); zassert_ok(pthread_join(th, NULL)); } - -static void before(void *arg) -{ - ARG_UNUSED(arg); - - if (!IS_ENABLED(CONFIG_DYNAMIC_THREAD)) { - /* skip redundant testing if there is no thread pool / heap allocation */ - ztest_test_skip(); - } -} - -ZTEST_SUITE(pthread, NULL, NULL, before, NULL, NULL); diff --git a/tests/posix/threads_base/src/pthread_attr.c b/tests/posix/threads_base/src/pthread_attr.c new file mode 100644 index 000000000000..2e033c14caa4 --- /dev/null +++ b/tests/posix/threads_base/src/pthread_attr.c @@ -0,0 +1,579 @@ +/* + * 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_entry(const pthread_attr_t *attrp, bool expect_success, + bool joinable, void *(*entry)(void *arg), void *arg) +{ + pthread_t th; + + if (!joinable) { + detached_thread_has_finished = false; + } + + if (expect_success) { + zassert_ok(pthread_create(&th, attrp, entry, arg)); + } else { + zassert_not_ok(pthread_create(&th, attrp, entry, arg)); + 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 void create_thread_common(const pthread_attr_t *attrp, bool expect_success, bool joinable) +{ + create_thread_common_entry(attrp, expect_success, joinable, thread_entry, + UINT_TO_POINTER(joinable)); +} + +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(posix_threads_base, 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(posix_threads_base, 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(posix_threads_base, 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(posix_threads_base, 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(posix_threads_base, 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(posix_threads_base, 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(posix_threads_base, 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(posix_threads_base, test_pthread_attr_getscope) +{ + int contentionscope = BIOS_FOOD; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_getscope(NULL, NULL), EINVAL); + zassert_equal(pthread_attr_getscope(NULL, &contentionscope), EINVAL); + zassert_equal(pthread_attr_getscope(&uninit_attr, &contentionscope), + EINVAL); + } + zassert_equal(pthread_attr_getscope(&attr, NULL), EINVAL); + } + + zassert_ok(pthread_attr_getscope(&attr, &contentionscope)); + zassert_equal(contentionscope, PTHREAD_SCOPE_SYSTEM); +} + +ZTEST(posix_threads_base, test_pthread_attr_setscope) +{ + int contentionscope = BIOS_FOOD; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_setscope(NULL, PTHREAD_SCOPE_SYSTEM), EINVAL); + zassert_equal(pthread_attr_setscope(NULL, contentionscope), EINVAL); + zassert_equal(pthread_attr_setscope((pthread_attr_t *)&uninit_attr, + contentionscope), + EINVAL); + } + zassert_equal(pthread_attr_setscope(&attr, 3), EINVAL); + } + + zassert_equal(pthread_attr_setscope(&attr, PTHREAD_SCOPE_PROCESS), ENOTSUP); + zassert_ok(pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM)); + zassert_ok(pthread_attr_getscope(&attr, &contentionscope)); + zassert_equal(contentionscope, PTHREAD_SCOPE_SYSTEM); +} + +ZTEST(posix_threads_base, test_pthread_attr_getinheritsched) +{ + int inheritsched = BIOS_FOOD; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_getinheritsched(NULL, NULL), EINVAL); + zassert_equal(pthread_attr_getinheritsched(NULL, &inheritsched), EINVAL); + zassert_equal(pthread_attr_getinheritsched(&uninit_attr, &inheritsched), + EINVAL); + } + zassert_equal(pthread_attr_getinheritsched(&attr, NULL), EINVAL); + } + + zassert_ok(pthread_attr_getinheritsched(&attr, &inheritsched)); + zassert_equal(inheritsched, PTHREAD_INHERIT_SCHED); +} + +static void *inheritsched_entry(void *arg) +{ + int prio; + int inheritsched; + int pprio = POINTER_TO_INT(arg); + + zassert_ok(pthread_attr_getinheritsched(&attr, &inheritsched)); + + prio = k_thread_priority_get(k_current_get()); + + if (inheritsched == PTHREAD_INHERIT_SCHED) { + /* + * There will be numerical overlap between posix priorities in different scheduler + * policies so only check the Zephyr priority here. The posix policy and posix + * priority are derived from the Zephyr priority in any case. + */ + zassert_equal(prio, pprio, "actual priority: %d, expected priority: %d", prio, + pprio); + return NULL; + } + + /* inheritsched == PTHREAD_EXPLICIT_SCHED */ + int act_prio; + int exp_prio; + int act_policy; + int exp_policy; + struct sched_param param; + + /* get the actual policy, param, etc */ + zassert_ok(pthread_getschedparam(pthread_self(), &act_policy, ¶m)); + act_prio = param.sched_priority; + + /* get the expected policy, param, etc */ + zassert_ok(pthread_attr_getschedpolicy(&attr, &exp_policy)); + zassert_ok(pthread_attr_getschedparam(&attr, ¶m)); + exp_prio = param.sched_priority; + + /* compare actual vs expected */ + zassert_equal(act_policy, exp_policy, "actual policy: %d, expected policy: %d", act_policy, + exp_policy); + zassert_equal(act_prio, exp_prio, "actual priority: %d, expected priority: %d", act_prio, + exp_prio); + + return NULL; +} + +static void test_pthread_attr_setinheritsched_common(bool inheritsched) +{ + int prio; + int policy; + struct sched_param param; + + extern int zephyr_to_posix_priority(int priority, int *policy); + + prio = k_thread_priority_get(k_current_get()); + zassert_not_equal(prio, K_LOWEST_APPLICATION_THREAD_PRIO); + + /* + * values affected by inheritsched are policy / priority / contentionscope + * + * we only support PTHREAD_SCOPE_SYSTEM, so no need to set contentionscope + */ + prio = K_LOWEST_APPLICATION_THREAD_PRIO; + param.sched_priority = zephyr_to_posix_priority(prio, &policy); + + zassert_ok(pthread_attr_setschedpolicy(&attr, policy)); + zassert_ok(pthread_attr_setschedparam(&attr, ¶m)); + zassert_ok(pthread_attr_setinheritsched(&attr, inheritsched)); + create_thread_common_entry(&attr, true, true, inheritsched_entry, + UINT_TO_POINTER(k_thread_priority_get(k_current_get()))); +} + +ZTEST(posix_threads_base, test_pthread_attr_setinheritsched) +{ + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_setinheritsched(NULL, PTHREAD_EXPLICIT_SCHED), + EINVAL); + zassert_equal(pthread_attr_setinheritsched(NULL, PTHREAD_INHERIT_SCHED), + EINVAL); + zassert_equal(pthread_attr_setinheritsched((pthread_attr_t *)&uninit_attr, + PTHREAD_INHERIT_SCHED), + EINVAL); + } + zassert_equal(pthread_attr_setinheritsched(&attr, 3), EINVAL); + } + + /* valid cases */ + test_pthread_attr_setinheritsched_common(PTHREAD_INHERIT_SCHED); + test_pthread_attr_setinheritsched_common(PTHREAD_EXPLICIT_SCHED); +} + +ZTEST(posix_threads_base, 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(posix_threads_base, 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(posix_threads_base, 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); + } + } +} + +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; +} + +void after(void *arg) +{ + ARG_UNUSED(arg); + + if (attr_valid) { + (void)pthread_attr_destroy(&attr); + attr_valid = false; + } +} diff --git a/tests/posix/threads_base/testcase.yaml b/tests/posix/threads_base/testcase.yaml new file mode 100644 index 000000000000..ab2594bf1649 --- /dev/null +++ b/tests/posix/threads_base/testcase.yaml @@ -0,0 +1,25 @@ +common: + tags: + - posix + - threads_base + min_ram: 64 + timeout: 240 + # 1 tier0 platform per supported architecture + platform_key: + - arch + - simulation + filter: not CONFIG_NATIVE_LIBC +tests: + portability.posix.threads_base: {} + portability.posix.threads_base.minimal: + extra_configs: + - CONFIG_MINIMAL_LIBC=y + portability.posix.threads_base.newlib: + filter: TOOLCHAIN_HAS_NEWLIB == 1 + extra_configs: + - CONFIG_NEWLIB_LIBC=y + portability.posix.threads_base.picolibc: + tags: picolibc + filter: CONFIG_PICOLIBC_SUPPORTED + extra_configs: + - CONFIG_PICOLIBC=y