/* * Copyright (c) 2018 Intel Corporation * Copyright (c) 2023 Meta * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include static void *child_func(void *p1) { sem_t *sem = (sem_t *)p1; zassert_equal(sem_post(sem), 0, "sem_post failed"); return NULL; } static void semaphore_test(sem_t *sem) { pthread_t thread1, thread2; int val, ret; struct timespec abstime; /* TESTPOINT: Check if sema value is less than * CONFIG_SEM_VALUE_MAX */ zassert_equal(sem_init(sem, 0, (CONFIG_SEM_VALUE_MAX + 1)), -1, "value larger than %d\n", CONFIG_SEM_VALUE_MAX); zassert_equal(errno, EINVAL); zassert_equal(sem_init(sem, 0, 0), 0, "sem_init failed"); /* TESTPOINT: Check if semaphore value is as set */ zassert_equal(sem_getvalue(sem, &val), 0); zassert_equal(val, 0); /* TESTPOINT: Check if sema is acquired when it * is not available */ zassert_equal(sem_trywait(sem), -1); zassert_equal(errno, EAGAIN); ret = pthread_create(&thread1, NULL, child_func, sem); zassert_equal(ret, 0, "Thread creation failed"); zassert_equal(clock_gettime(CLOCK_REALTIME, &abstime), 0, "clock_gettime failed"); abstime.tv_sec += 5; /* TESPOINT: Wait for 5 seconds and acquire sema given * by thread1 */ zassert_equal(sem_timedwait(sem, &abstime), 0); /* TESTPOINT: Semaphore is already acquired, check if * no semaphore is available */ zassert_equal(sem_timedwait(sem, &abstime), -1); zassert_equal(errno, ETIMEDOUT); zassert_equal(sem_destroy(sem), 0, "semaphore is not destroyed"); /* TESTPOINT: Initialize sema with 1 */ zassert_equal(sem_init(sem, 0, 1), 0, "sem_init failed"); zassert_equal(sem_getvalue(sem, &val), 0); zassert_equal(val, 1); zassert_equal(sem_destroy(sem), -1, "acquired semaphore" " is destroyed"); zassert_equal(errno, EBUSY); /* TESTPOINT: take semaphore which is initialized with 1 */ zassert_equal(sem_trywait(sem), 0); zassert_equal(pthread_create(&thread2, NULL, child_func, sem), 0, "Thread creation failed"); /* TESTPOINT: Wait and acquire semaphore till thread2 gives */ zassert_equal(sem_wait(sem), 0, "sem_wait failed"); /* Make sure the threads are terminated */ zassert_ok(pthread_join(thread1, NULL)); zassert_ok(pthread_join(thread2, NULL)); } ZTEST(semaphore, test_semaphore) { sem_t sema; /* TESTPOINT: Call sem_post with invalid kobject */ zassert_equal(sem_post(NULL), -1, "sem_post of" " invalid semaphore object didn't fail"); zassert_equal(errno, EINVAL); /* TESTPOINT: sem_destroy with invalid kobject */ zassert_equal(sem_destroy(NULL), -1, "invalid" " semaphore is destroyed"); zassert_equal(errno, EINVAL); semaphore_test(&sema); } int nsem_get_ref_count(sem_t *sem); size_t nsem_get_list_len(void); static void *nsem_open_func(void *p) { const char *name = (char *)p; for (int i = 0; i < CONFIG_TEST_SEM_N_LOOPS; i++) { zassert_not_null(sem_open(name, 0, 0, 0), "%s is NULL", name); k_msleep(1); } /* Unlink after finished opening */ zassert_ok(sem_unlink(name)); return NULL; } static void *nsem_close_func(void *p) { sem_t *sem = (sem_t *)p; /* Make sure that we have enough ref_count's initially */ k_msleep(CONFIG_TEST_SEM_N_LOOPS >> 1); for (int i = 0; i < CONFIG_TEST_SEM_N_LOOPS; i++) { zassert_ok(sem_close(sem)); k_msleep(1); } /* Close the last `sem` */ zassert_ok(sem_close(sem)); return NULL; } ZTEST(semaphore, test_named_semaphore) { pthread_t thread1, thread2; sem_t *sem1, *sem2, *different_sem1; /* If `name` is invalid */ sem1 = sem_open(NULL, 0, 0, 0); zassert_equal(errno, EINVAL); zassert_equal_ptr(sem1, SEM_FAILED); zassert_equal(nsem_get_list_len(), 0); /* Attempt to open a named sem that doesn't exist */ sem1 = sem_open("sem1", 0, 0, 0); zassert_equal(errno, ENOENT); zassert_equal_ptr(sem1, SEM_FAILED); zassert_equal(nsem_get_list_len(), 0); /* Name exceeds CONFIG_SEM_NAMELEN_MAX */ char name_too_long[CONFIG_SEM_NAMELEN_MAX + 2]; for (size_t i = 0; i < sizeof(name_too_long) - 1; i++) { name_too_long[i] = 'a'; } name_too_long[sizeof(name_too_long) - 1] = '\0'; sem1 = sem_open(name_too_long, 0, 0, 0); zassert_equal(errno, ENAMETOOLONG, "\"%s\" should be longer than %d", name_too_long, CONFIG_SEM_NAMELEN_MAX); zassert_equal_ptr(sem1, SEM_FAILED); zassert_equal(nsem_get_list_len(), 0); /* `value` greater than CONFIG_SEM_VALUE_MAX */ sem1 = sem_open("sem1", O_CREAT, 0, (CONFIG_SEM_VALUE_MAX + 1)); zassert_equal(errno, EINVAL); zassert_equal_ptr(sem1, SEM_FAILED); zassert_equal(nsem_get_list_len(), 0); /* Open named sem */ sem1 = sem_open("sem1", O_CREAT, 0, 0); zassert_equal(nsem_get_ref_count(sem1), 2); zassert_equal(nsem_get_list_len(), 1); sem2 = sem_open("sem2", O_CREAT, 0, 0); zassert_equal(nsem_get_ref_count(sem2), 2); zassert_equal(nsem_get_list_len(), 2); /* Open created named sem repeatedly */ for (size_t i = 1; i <= CONFIG_TEST_SEM_N_LOOPS; i++) { sem_t *new_sem1, *new_sem2; /* oflags are ignored (except when both O_CREAT & O_EXCL are set) */ new_sem1 = sem_open("sem1", i % 2 == 0 ? O_CREAT : 0, 0, 0); zassert_not_null(new_sem1); zassert_equal_ptr(new_sem1, sem1); /* Should point to the same sem */ new_sem2 = sem_open("sem2", i % 2 == 0 ? O_CREAT : 0, 0, 0); zassert_not_null(new_sem2); zassert_equal_ptr(new_sem2, sem2); /* ref_count should increment */ zassert_equal(nsem_get_ref_count(sem1), 2 + i); zassert_equal(nsem_get_ref_count(sem2), 2 + i); /* Should reuse the same named sem instead of creating another one */ zassert_equal(nsem_get_list_len(), 2); } /* O_CREAT and O_EXCL are set and the named semaphore already exists */ zassert_equal_ptr((sem_open("sem1", O_CREAT | O_EXCL, 0, 0)), SEM_FAILED); zassert_equal(errno, EEXIST); zassert_equal(nsem_get_list_len(), 2); zassert_equal(sem_close(NULL), -1); zassert_equal(errno, EINVAL); zassert_equal(nsem_get_list_len(), 2); /* Close sem */ for (size_t i = CONFIG_TEST_SEM_N_LOOPS; /* close until one left, required by the test later */ i >= 1; i--) { zassert_ok(sem_close(sem1)); zassert_equal(nsem_get_ref_count(sem1), 2 + i - 1); zassert_ok(sem_close(sem2)); zassert_equal(nsem_get_ref_count(sem2), 2 + i - 1); zassert_equal(nsem_get_list_len(), 2); } /* If `name` is invalid */ zassert_equal(sem_unlink(NULL), -1); zassert_equal(errno, EINVAL); zassert_equal(nsem_get_list_len(), 2); /* Attempt to unlink a named sem that doesn't exist */ zassert_equal(sem_unlink("sem3"), -1); zassert_equal(errno, ENOENT); zassert_equal(nsem_get_list_len(), 2); /* Name exceeds CONFIG_SEM_NAMELEN_MAX */ char long_sem_name[CONFIG_SEM_NAMELEN_MAX + 2]; for (int i = 0; i < CONFIG_SEM_NAMELEN_MAX + 1; i++) { long_sem_name[i] = 'a'; } long_sem_name[CONFIG_SEM_NAMELEN_MAX + 1] = '\0'; zassert_equal(sem_unlink(long_sem_name), -1); zassert_equal(errno, ENAMETOOLONG); zassert_equal(nsem_get_list_len(), 2); /* Unlink sem1 when it is still being used */ zassert_equal(nsem_get_ref_count(sem1), 2); zassert_ok(sem_unlink("sem1")); /* sem won't be destroyed */ zassert_equal(nsem_get_ref_count(sem1), 1); zassert_equal(nsem_get_list_len(), 2); /* Create another sem with the name of an unlinked sem */ different_sem1 = sem_open("sem1", O_CREAT, 0, 0); zassert_not_null(different_sem1); /* The created sem will be a different instance */ zassert(different_sem1 != sem1, ""); zassert_equal(nsem_get_list_len(), 3); /* Destruction of sem1 will be postponed until all references to the semaphore have been * destroyed by calls to sem_close() */ zassert_ok(sem_close(sem1)); zassert_equal(nsem_get_list_len(), 2); /* Closing a linked sem won't destroy the sem */ zassert_ok(sem_close(sem2)); zassert_equal(nsem_get_ref_count(sem2), 1); zassert_equal(nsem_get_list_len(), 2); /* Instead the sem will be destroyed upon call to sem_unlink() */ zassert_ok(sem_unlink("sem2")); zassert_equal(nsem_get_list_len(), 1); /* What we have left open here is `different_sem` as "sem1", which has a ref_count of 2 */ zassert_equal(nsem_get_ref_count(different_sem1), 2); /* Stress test: open & close "sem1" repeatedly */ zassert_ok(pthread_create(&thread1, NULL, nsem_open_func, "sem1")); zassert_ok(pthread_create(&thread2, NULL, nsem_close_func, different_sem1)); /* Make sure the threads are terminated */ zassert_ok(pthread_join(thread1, NULL)); zassert_ok(pthread_join(thread2, NULL)); /* All named semaphores should be destroyed here */ zassert_equal(nsem_get_list_len(), 0); /* Create a new named sem to be used in the normal semaphore test */ sem1 = sem_open("nsem", O_CREAT, 0, 0); zassert_equal(nsem_get_list_len(), 1); zassert_equal(nsem_get_ref_count(sem1), 2); /* Run the semaphore test with the created named semaphore */ semaphore_test(sem1); /* List length and ref_count shouldn't change after the test */ zassert_equal(nsem_get_list_len(), 1); zassert_equal(nsem_get_ref_count(sem1), 2); /* Unless it is unlinked and closed */ sem_unlink("nsem"); sem_close(sem1); zassert_equal(nsem_get_list_len(), 0); } 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(semaphore, NULL, NULL, before, NULL, NULL);