| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #include <gtest/gtest.h> |
| |
| #include <csignal> |
| |
| #include "celix_compiler.h" |
| #include "celix_utils.h" |
| #include "celix_threads.h" |
| |
| class ThreadsTestSuite : public ::testing::Test { |
| public: |
| #ifdef __APPLE__ |
| const double ALLOWED_ERROR_MARGIN = 0.2; |
| #else |
| const double ALLOWED_ERROR_MARGIN= 0.1; |
| #endif |
| }; |
| |
| //----------------------TEST THREAD FUNCTION DECLARATIONS---------------------- |
| static void * thread_test_func_create(void *); |
| static void * thread_test_func_exit(void *); |
| static void * thread_test_func_detach(void *); |
| static void * thread_test_func_self(void *); |
| static void * thread_test_func_lock(void *); |
| static void * thread_test_func_cond_wait(void *); |
| static void * thread_test_func_cond_broadcast(void *); |
| static int thread_test_func_recur_lock(celix_thread_mutex_t*, int); |
| static void * thread_test_func_kill(void*); |
| |
| static void * thread_test_func_once(void*); |
| static void thread_test_func_once_init(); |
| static int thread_test_func_once_init_times_called = 0; |
| |
| struct func_param{ |
| int i, i2; |
| celix_thread_mutex_t mu, mu2; |
| celix_thread_cond_t cond, cond2; |
| celix_thread_once_t once_control; |
| celix_thread_rwlock_t rwlock; |
| }; |
| |
| //----------------------CELIX THREADS TESTS---------------------- |
| |
| TEST_F(ThreadsTestSuite, CreateTest) { |
| celix_thread_t thread; |
| int ret; |
| char* testStr = nullptr; |
| |
| ret = celixThread_create(&thread, nullptr, &thread_test_func_create, |
| &testStr); |
| EXPECT_EQ(CELIX_SUCCESS, ret); |
| celixThread_join(thread, nullptr); |
| |
| EXPECT_STREQ("SUCCESS", testStr); |
| |
| free(testStr); |
| } |
| |
| TEST_F(ThreadsTestSuite, ExitTest) { |
| int ret, *status; |
| celix_thread_t thread; |
| |
| ret = celixThread_create(&thread, nullptr, &thread_test_func_exit, nullptr); |
| EXPECT_EQ(CELIX_SUCCESS, ret); |
| celixThread_join(thread, (void**) &status); |
| EXPECT_EQ(666, *status); |
| free(status); |
| } |
| |
| TEST_F(ThreadsTestSuite, DetachTest) { |
| int ret; |
| celix_thread_t thread; |
| |
| celixThread_create(&thread, nullptr, thread_test_func_detach, nullptr); |
| ret = celixThread_detach(thread); |
| EXPECT_EQ(CELIX_SUCCESS, ret); |
| } |
| |
| TEST_F(ThreadsTestSuite, SelfTest) { |
| celix_thread_t thread; |
| celix_thread_t thread2; |
| |
| celixThread_create(&thread, nullptr, thread_test_func_self, &thread2); |
| celixThread_join(thread, nullptr); |
| EXPECT_TRUE(celixThread_equals(thread, thread2)); |
| } |
| |
| TEST_F(ThreadsTestSuite, InitializedTest) { |
| celix_thread_t thread{}; |
| EXPECT_FALSE(celixThread_initialized(thread)); |
| |
| celixThread_create(&thread, nullptr, thread_test_func_detach, nullptr); |
| EXPECT_TRUE(celixThread_initialized(thread)); |
| celixThread_join(thread, nullptr); |
| } |
| |
| TEST_F(ThreadsTestSuite, OnceTest) { |
| int *status; |
| celix_thread_t thread; |
| celix_thread_t thread2; |
| struct func_param* params = (struct func_param*) calloc(1,sizeof(struct func_param)); |
| |
| celixThread_create(&thread, nullptr, &thread_test_func_once, params); |
| celixThread_join(thread, (void**) &status); |
| |
| celixThread_create(&thread2, nullptr, &thread_test_func_once, params); |
| celixThread_join(thread2, (void**) &status); |
| |
| free(params); |
| |
| EXPECT_EQ(1, thread_test_func_once_init_times_called); |
| } |
| |
| //----------------------CELIX THREADS KILL TESTS---------------------- |
| |
| TEST_F(ThreadsTestSuite, KillTest){ |
| //setup signal handler to ignore SIGUSR1 |
| struct sigaction sigact; |
| struct sigaction sigactold; |
| memset(&sigact, 0, sizeof(sigact)); |
| sigact.sa_handler = [](int) {/*nop*/};; |
| sigaction(SIGUSR1, &sigact, &sigactold); |
| |
| |
| int * ret; |
| celix_thread_t thread; |
| celixThread_create(&thread, nullptr, thread_test_func_kill, nullptr); |
| sleep(1); |
| |
| celixThread_kill(thread, SIGUSR1); |
| celixThread_join(thread, (void**)&ret); |
| |
| EXPECT_EQ(-1, *ret); // -1 returned from usleep (interrupted by signal) |
| free(ret); |
| |
| sigaction(SIGUSR1, &sigactold, nullptr); |
| } |
| |
| //----------------------CELIX THREADS MUTEX TESTS---------------------- |
| |
| TEST_F(ThreadsTestSuite, CreateMutexTest) { |
| celix_thread_mutex_t mu; |
| EXPECT_EQ(CELIX_SUCCESS, celixThreadMutex_create(&mu, nullptr)); |
| EXPECT_EQ(CELIX_SUCCESS, celixThreadMutex_destroy(&mu)); |
| } |
| |
| |
| TEST_F(ThreadsTestSuite, LockTest) { |
| celix_thread_t thread; |
| struct func_param * params = (struct func_param*) calloc(1, |
| sizeof(struct func_param)); |
| |
| celixThreadMutex_create(¶ms->mu, nullptr); |
| |
| celixThreadMutex_lock(¶ms->mu); |
| celixThread_create(&thread, nullptr, thread_test_func_lock, params); |
| |
| sleep(1); |
| |
| EXPECT_EQ(0, params->i); |
| |
| //possible race condition, not perfect test |
| celixThreadMutex_unlock(¶ms->mu); |
| |
| sleep(1); |
| |
| celixThreadMutex_lock(¶ms->mu2); |
| EXPECT_EQ(666, params->i); |
| celixThreadMutex_unlock(¶ms->mu2); |
| celixThread_join(thread, nullptr); |
| free(params); |
| } |
| |
| TEST_F(ThreadsTestSuite, AttrCreateTest) { |
| celix_thread_mutexattr_t mu_attr; |
| EXPECT_EQ(CELIX_SUCCESS, celixThreadMutexAttr_create(&mu_attr)); |
| EXPECT_EQ(CELIX_SUCCESS, celixThreadMutexAttr_destroy(&mu_attr)); |
| } |
| |
| TEST_F(ThreadsTestSuite, AttrSettypeTest) { |
| celix_thread_mutex_t mu; |
| celix_thread_mutexattr_t mu_attr; |
| celixThreadMutexAttr_create(&mu_attr); |
| |
| //test recursive mutex |
| celixThreadMutexAttr_settype(&mu_attr, CELIX_THREAD_MUTEX_RECURSIVE); |
| celixThreadMutex_create(&mu, &mu_attr); |
| //if program doesnt deadlock: success! also check factorial of 10, for reasons unknown |
| EXPECT_EQ(3628800, thread_test_func_recur_lock(&mu, 10)); |
| celixThreadMutex_destroy(&mu); |
| |
| //test deadlock check mutex |
| celixThreadMutexAttr_settype(&mu_attr, CELIX_THREAD_MUTEX_ERRORCHECK); |
| celixThreadMutex_create(&mu, &mu_attr); |
| //get deadlock error |
| celixThreadMutex_lock(&mu); |
| EXPECT_TRUE(celixThreadMutex_lock(&mu) != CELIX_SUCCESS); |
| //do not get deadlock error |
| celixThreadMutex_unlock(&mu); |
| EXPECT_EQ(CELIX_SUCCESS, celixThreadMutex_lock(&mu)); |
| //get unlock error |
| celixThreadMutex_unlock(&mu); |
| EXPECT_TRUE(celixThreadMutex_unlock(&mu) != CELIX_SUCCESS); |
| |
| celixThreadMutex_destroy(&mu); |
| celixThreadMutexAttr_destroy(&mu_attr); |
| } |
| |
| //----------------------CELIX THREAD CONDITIONS TESTS---------------------- |
| |
| TEST_F(ThreadsTestSuite, InitCondTest) { |
| celix_thread_cond_t cond; |
| EXPECT_EQ(CELIX_SUCCESS, celixThreadCondition_init(&cond, nullptr)); |
| EXPECT_EQ(CELIX_SUCCESS, celixThreadCondition_destroy(&cond)); |
| } |
| |
| //test wait and signal |
| TEST_F(ThreadsTestSuite, WaitCondTest) { |
| celix_thread_t thread; |
| struct func_param * param = (struct func_param*) calloc(1, |
| sizeof(struct func_param)); |
| celixThreadMutex_create(¶m->mu, nullptr); |
| celixThreadCondition_init(¶m->cond, nullptr); |
| |
| celixThreadMutex_lock(¶m->mu); |
| |
| sleep(2); |
| |
| celixThread_create(&thread, nullptr, thread_test_func_cond_wait, param); |
| EXPECT_EQ(0, param->i); |
| |
| celixThreadCondition_wait(¶m->cond, ¶m->mu); |
| EXPECT_EQ(666, param->i); |
| celixThreadMutex_unlock(¶m->mu); |
| |
| celixThread_join(thread, nullptr); |
| free(param); |
| } |
| |
| //test wait and broadcast on multiple threads |
| TEST_F(ThreadsTestSuite, CondBroadcastTest) { |
| celix_thread_t thread; |
| celix_thread_t thread2; |
| struct func_param * param = (struct func_param*) calloc(1,sizeof(struct func_param)); |
| celixThreadMutex_create(¶m->mu, nullptr); |
| celixThreadMutex_create(¶m->mu2, nullptr); |
| celixThreadCondition_init(¶m->cond, nullptr); |
| |
| celixThread_create(&thread, nullptr, thread_test_func_cond_broadcast, param); |
| celixThread_create(&thread2, nullptr, thread_test_func_cond_broadcast, param); |
| |
| sleep(1); |
| celixThreadMutex_lock(¶m->mu); |
| EXPECT_EQ(0, param->i); |
| celixThreadMutex_unlock(¶m->mu); |
| |
| celixThreadMutex_lock(¶m->mu); |
| celixThreadCondition_broadcast(¶m->cond); |
| celixThreadMutex_unlock(¶m->mu); |
| sleep(1); |
| celixThreadMutex_lock(¶m->mu); |
| EXPECT_EQ(2, param->i); |
| celixThreadMutex_unlock(¶m->mu); |
| |
| celixThread_join(thread, nullptr); |
| celixThread_join(thread2, nullptr); |
| free(param); |
| } |
| |
| TEST_F(ThreadsTestSuite, CondTimedWaitTest) { |
| celix_thread_mutex_t mutex; |
| celix_thread_cond_t cond; |
| |
| auto status = celixThreadMutex_create(&mutex, nullptr); |
| ASSERT_EQ(status, CELIX_SUCCESS); |
| status = celixThreadCondition_init(&cond, nullptr); |
| ASSERT_EQ(status, CELIX_SUCCESS); |
| |
| //Test with nullptr abstime |
| status = celixThreadCondition_waitUntil(&cond, &mutex, nullptr); |
| ASSERT_EQ(status, CELIX_ILLEGAL_ARGUMENT); |
| |
| //Test with valid abstime |
| auto start = celixThreadCondition_getTime(); |
| auto targetEnd = celixThreadCondition_getDelayedTime(0.01); |
| celixThreadMutex_lock(&mutex); |
| status = celixThreadCondition_waitUntil(&cond, &mutex, &targetEnd); |
| ASSERT_EQ(status, ETIMEDOUT); |
| celixThreadMutex_unlock(&mutex); |
| auto end = celixThreadCondition_getTime(); |
| EXPECT_NEAR(celix_difftime(&start, &end), 0.01, ALLOWED_ERROR_MARGIN); |
| |
| start = celixThreadCondition_getTime(); |
| celixThreadMutex_lock(&mutex); |
| status = celixThreadCondition_waitUntil(&cond, &mutex, &start); |
| ASSERT_EQ(status, ETIMEDOUT); |
| celixThreadMutex_unlock(&mutex); |
| end = celixThreadCondition_getTime(); |
| EXPECT_NEAR(celix_difftime(&start, &end), 0.0, ALLOWED_ERROR_MARGIN); |
| } |
| |
| //----------------------CELIX READ-WRITE LOCK TESTS---------------------- |
| |
| TEST_F(ThreadsTestSuite, CreateRwLockTest) { |
| celix_thread_rwlock_t alock; |
| celix_status_t status; |
| status = celixThreadRwlock_create(&alock, nullptr); |
| if (status != CELIX_SUCCESS) { |
| fprintf(stderr, "Found error '%s'\n", strerror(status)); |
| } |
| EXPECT_EQ(CELIX_SUCCESS, status); |
| status = celixThreadRwlock_destroy(&alock); |
| EXPECT_EQ(CELIX_SUCCESS, status); |
| } |
| |
| TEST_F(ThreadsTestSuite, ReadLockTest) { |
| int status; |
| celix_thread_rwlock_t lock; |
| memset(&lock, 0x00, sizeof(celix_thread_rwlock_t)); |
| //struct func_param * param = (struct func_param*) calloc(1,sizeof(struct func_param)); |
| |
| celixThreadRwlock_create(&lock, nullptr); |
| |
| status = celixThreadRwlock_readLock(&lock); |
| EXPECT_EQ(0, status); |
| status = celixThreadRwlock_readLock(&lock); |
| EXPECT_EQ(0, status); |
| status = celixThreadRwlock_unlock(&lock); |
| EXPECT_EQ(0, status); |
| status = celixThreadRwlock_unlock(&lock); |
| EXPECT_EQ(0, status); |
| |
| celixThreadRwlock_destroy(&lock); |
| } |
| |
| TEST_F(ThreadsTestSuite, WriteLockTest) { |
| int status; |
| celix_thread_rwlock_t lock; |
| celixThreadRwlock_create(&lock, nullptr); |
| |
| status = celixThreadRwlock_writeLock(&lock); |
| EXPECT_EQ(0, status); |
| status = celixThreadRwlock_writeLock(&lock); |
| //EDEADLK ErNo: Resource deadlock avoided |
| EXPECT_EQ(EDEADLK, status); |
| |
| celixThreadRwlock_unlock(&lock); |
| } |
| |
| TEST_F(ThreadsTestSuite, RwLockAttrTest) { |
| celix_thread_rwlockattr_t attr; |
| celixThreadRwlockAttr_create(&attr); |
| celixThreadRwlockAttr_destroy(&attr); |
| } |
| |
| TEST_F(ThreadsTestSuite, TssTest) { |
| celix_tss_key_t key; |
| celix_status_t status = celix_tss_create(&key, [](void* ptr) { free(ptr); }); |
| EXPECT_EQ(CELIX_SUCCESS, status); |
| |
| int* value = (int*)malloc(sizeof(int)); |
| *value = 123; |
| status = celix_tss_set(key, value); |
| EXPECT_EQ(CELIX_SUCCESS, status); |
| |
| value = (int*)celix_tss_get(key); |
| EXPECT_EQ(123, *value); |
| |
| status = celix_tss_delete(key); |
| EXPECT_EQ(CELIX_SUCCESS, status); |
| } |
| |
| static void * thread_test_func_create(void * arg) { |
| char ** test_str = (char**) arg; |
| *test_str = strdup("SUCCESS"); |
| |
| return nullptr; |
| } |
| |
| static void * thread_test_func_exit(void *) { |
| int *pi = (int*) calloc(1, sizeof(int)); |
| *pi = 666; |
| |
| celixThread_exit(pi); |
| return nullptr; |
| } |
| |
| static void * thread_test_func_detach(void *) { |
| return nullptr; |
| } |
| |
| static void * thread_test_func_self(void * arg) { |
| *((celix_thread*) arg) = celixThread_self(); |
| return nullptr; |
| } |
| |
| static void * thread_test_func_once(void * arg) { |
| struct func_param *param = (struct func_param *) arg; |
| celixThread_once(¶m->once_control, thread_test_func_once_init); |
| return nullptr; |
| } |
| |
| static void thread_test_func_once_init() { |
| thread_test_func_once_init_times_called += 1; |
| } |
| |
| static void * thread_test_func_lock(void *arg) { |
| struct func_param *param = (struct func_param *) arg; |
| |
| celixThreadMutex_lock(¶m->mu2); |
| celixThreadMutex_lock(¶m->mu); |
| param->i = 666; |
| celixThreadMutex_unlock(¶m->mu); |
| celixThreadMutex_unlock(¶m->mu2); |
| |
| return nullptr; |
| } |
| |
| static void * thread_test_func_cond_wait(void *arg) { |
| struct func_param *param = (struct func_param *) arg; |
| |
| celixThreadMutex_lock(¶m->mu); |
| |
| param->i = 666; |
| |
| celixThreadCondition_signal(¶m->cond); |
| celixThreadMutex_unlock(¶m->mu); |
| return nullptr; |
| } |
| |
| static void * thread_test_func_cond_broadcast(void *arg) { |
| struct func_param *param = (struct func_param *) arg; |
| |
| celixThreadMutex_lock(¶m->mu); |
| celixThreadCondition_wait(¶m->cond, ¶m->mu); |
| celixThreadMutex_unlock(¶m->mu); |
| celixThreadMutex_lock(¶m->mu); |
| param->i++; |
| celixThreadMutex_unlock(¶m->mu); |
| return nullptr; |
| } |
| |
| static int thread_test_func_recur_lock(celix_thread_mutex_t *mu, int i) { |
| int temp; |
| if (i == 1) { |
| return 1; |
| } else { |
| celixThreadMutex_lock(mu); |
| temp = thread_test_func_recur_lock(mu, i - 1); |
| temp *= i; |
| celixThreadMutex_unlock(mu); |
| return temp; |
| } |
| } |
| |
| static void * thread_test_func_kill(void* arg CELIX_UNUSED){ |
| int * ret = (int*) malloc(sizeof(*ret)); |
| //sleep for about a minute, or until a kill signal (USR1) is received |
| *ret = usleep(60000000); |
| return ret; |
| } |