/* mbed Microcontroller Library * Copyright (c) 2017 ARM Limited * * Licensed 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 "mbed.h" #include "greentea-client/test_env.h" #include "unity.h" #include "utest.h" #include "rtos.h" #include "SynchronizedIntegral.h" #include "LockGuard.h" #if defined(MBED_RTOS_SINGLE_THREAD) #error [NOT_SUPPORTED] test not supported #endif #if !DEVICE_USTICKER #error [NOT_SUPPORTED] test not supported #endif #define THREAD_STACK_SIZE 512 #if defined(__CORTEX_A9) #define PARALLEL_THREAD_STACK_SIZE 512 #elif defined(__CORTEX_M23) || defined(__CORTEX_M33) #define PARALLEL_THREAD_STACK_SIZE 512 #elif defined(__ARM_FM) #define PARALLEL_THREAD_STACK_SIZE 512 #elif defined(TARGET_CY8CKIT_062_WIFI_BT_PSA) #define PARALLEL_THREAD_STACK_SIZE 512 #else #define PARALLEL_THREAD_STACK_SIZE 384 #endif #define CHILD_THREAD_STACK_SIZE 384 using namespace utest::v1; // The counter type used accross all the tests // It is internall ysynchronized so read typedef SynchronizedIntegral counter_t; template class ParallelThread : public Thread { public: ParallelThread() : Thread(P, S) { } }; // Tasks with different functions to test on threads void increment(counter_t *counter) { (*counter)++; } void increment_with_yield(counter_t *counter) { ThisThread::yield(); (*counter)++; } void increment_with_wait(counter_t *counter) { ThisThread::sleep_for(100); (*counter)++; } void increment_with_child(counter_t *counter) { Thread *child = new (std::nothrow) Thread(osPriorityNormal, CHILD_THREAD_STACK_SIZE); char *dummy = new (std::nothrow) char[CHILD_THREAD_STACK_SIZE]; delete[] dummy; // Don't fail test due to lack of memory. Call function directly instead if (!child || !dummy) { increment(counter); delete child; return; } child->start(callback(increment, counter)); child->join(); delete child; } void increment_with_murder(counter_t *counter) { { // take ownership of the counter mutex so it prevent the child to // modify counter. LockGuard lock(counter->internal_mutex()); Thread *child = new (std::nothrow) Thread(osPriorityNormal, CHILD_THREAD_STACK_SIZE); char *dummy = new (std::nothrow) char[CHILD_THREAD_STACK_SIZE]; delete[] dummy; // Don't fail test due to lack of memory. if (!child || !dummy) { delete child; goto end; } child->start(callback(increment, counter)); child->terminate(); delete child; } end: (*counter)++; } void self_terminate(Thread *self) { self->terminate(); // Code should not get here TEST_ASSERT(0); } // Tests that spawn tasks in different configurations /** Template for tests: single thread, with yield, with wait, with child, with murder Testing single thread Given single thread is started when the thread increments the counter then the final value of the counter is equal to 1 Testing single thread with yield Given single thread is started when the thread yields and then increments the counter then the final value of the counter is equal to 1 Testing single thread with wait Given single thread is started when the thread waits for 100ms and then increments the counter then the final value of the counter is equal to 1 Testing single thread with child Given single thread is started when the thread spawns another thread that increments the counter then the final value of the counter is equal to 1 Testing serial threads with murder Given single thread is started when the parent thread is holding a lock and the parent thread spawns a child thread that waits for the lock before incrementing the counter and the parent terminates the child before releasing the lock and the parent increments the counter then the final value of the counter is equal to 1 */ template void test_single_thread() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); counter_t counter(0); Thread thread(osPriorityNormal, THREAD_STACK_SIZE); thread.start(callback(F, &counter)); thread.join(); TEST_ASSERT_EQUAL(counter, 1); } /** Template for tests: parallel threads, with yield, with wait, with child, with murder Testing parallel threads Given multiple threads are started in parallel when each of the threads increments the counter then the final value of the counter is equal to number of threads Testing parallel threads with yield Given multiple threads are started in parallel when each of the threads yields and then increments the counter then the final value of the counter is equal to number of threads Testing parallel threads with wait Given multiple threads are started in parallel when each of the threads waits for 100ms and then increments the counter then the final value of the counter is equal to number of threads Testing parallel threads with child Given multiple threads are started in parallel when each of the threads spawns another thread that increments the counter then the final value of the counter is equal to number of parallel threads Testing parallel threads with murder Given multiple threads are started in parallel when the parent thread is holding a lock and the parent thread spawns a child thread that waits for the lock before incrementing the counter and the parent terminates the child before releasing the lock and the parent increments the counter then the final value of the counter is equal to number of parallel threads */ template void test_parallel_threads() { char *dummy = new (std::nothrow) char[PARALLEL_THREAD_STACK_SIZE * N]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); counter_t counter(0); ParallelThread threads[N]; for (int i = 0; i < N; i++) { threads[i].start(callback(F, &counter)); } for (int i = 0; i < N; i++) { threads[i].join(); } TEST_ASSERT_EQUAL(counter, N); } /** Template for tests: serial threads, with yield, with wait, with child, with murder Testing serial threads Given multiple threads are started serially when each of the threads increments the counter then the final value of the counter is equal to number of threads Testing serial threads with yield Given multiple threads are started serially when each of the threads yields and then increments the counter then the final value of the counter is equal to number of threads Testing serial threads with wait Given multiple threads are started serially when each of the threads waits for 100ms and then increments the counter then the final value of the counter is equal to number of threads Testing serial threads with child Given multiple threads are started serially when each of the threads spawns another thread that increments the counter then the final value of the counter is equal to number of serial threads Testing serial threads with murder Given multiple threads are started serially when the parent thread is holding a lock and the parent thread spawns a child thread that waits for the lock before incrementing the counter and the parent terminates the child before releasing the lock and the parent increments the counter then the final value of the counter is equal to number of serial threads */ template void test_serial_threads() { counter_t counter(0); char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); for (int i = 0; i < N; i++) { Thread thread(osPriorityNormal, THREAD_STACK_SIZE); thread.start(callback(F, &counter)); thread.join(); } TEST_ASSERT_EQUAL(counter, N); } /** Testing thread self terminate Given the thread is running when the thread calls @a terminate on its self then the thread terminates execution cleanly */ void test_self_terminate() { Thread *thread = new (std::nothrow) Thread(osPriorityNormal, THREAD_STACK_SIZE); char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; // Don't fail test due to lack of memory. if (!thread || !dummy) { goto end; } thread->start(callback(self_terminate, thread)); thread->join(); end: delete thread; } void flags_wait() { uint32_t flags = ThisThread::flags_wait_all(0x1); TEST_ASSERT_EQUAL(0x1, flags); } void flags_wait_tout() { uint32_t flags = ThisThread::flags_wait_all_for(0x2, 50); TEST_ASSERT_EQUAL(0x1, flags); } void flags_wait_multibit() { uint32_t flags = ThisThread::flags_wait_all(0x1 | 0x2); TEST_ASSERT_EQUAL(0x3, flags); } void flags_wait_multibit_any() { uint32_t flags = ThisThread::flags_wait_any(0x1 | 0x2); TEST_ASSERT_NOT_EQUAL(0x0, flags); } void flags_wait_multibit_tout() { uint32_t flags = ThisThread::flags_wait_all_for(0x1 | 0x2, 50); TEST_ASSERT_NOT_EQUAL(0x3, flags); } /** Testing thread flags: wait Given two threads (A & B) are started when thread A executes @a flags_wait_all(0x1) and thread B execute @a flags_set(0x1) then thread A exits the wait and continues execution Testing thread flags: timeout Given two threads (A & B) are started when thread A executes @a flags_wait_all_for(0x1 | 0x2, 50) with a timeout of 50ms and thread B execute @a flags_set(0x2) then thread A keeps waiting for correct flags until it timeouts Testing thread flags: multi-bit Given two threads (A & B) are started when thread A executes @a flags_wait_all(0x1 | 0x2) and thread B execute @a flags_set(0x1 | 0x2) then thread A exits the wait and continues execution Testing thread flags: multi-bit any Given two threads (A & B) are started when thread A executes @a flags_wait_any(0x1 | 0x2) and thread B execute @a flags_set(0x1) then thread A exits the wait and continues execution Testing thread flags: multi-bit timeout Given two threads (A & B) are started when thread A executes @a flags_wait_all_for(0x1, 50) with a timeout of 50ms and thread B execute @a flags_set(0x2) then thread A keeps waiting for correct flags until it timeouts */ template void test_thread_flags_set() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); Thread t_wait(osPriorityNormal, THREAD_STACK_SIZE); t_wait.start(callback(F)); ThisThread::yield(); Thread::State state = t_wait.get_state(); TEST_ASSERT_EQUAL(Thread::WaitingThreadFlag, state); int32_t res = t_wait.flags_set(S); t_wait.join(); } void flags_clear() { ThisThread::yield(); int32_t sig = ThisThread::flags_clear(0x1); TEST_ASSERT_EQUAL(0x1, sig); /* Flags cleared we should get timeout */ uint32_t flags = ThisThread::flags_wait_all_for(0x1, 0); TEST_ASSERT_EQUAL(0, flags); } /** Testing thread flags: flags clear Given two threads (A & B) are started when thread A executes @a flags_set(0x1) and thread B execute @a flags_clear(0x1) and thread B execute @a flags_wait_all_for(0x1, 0) then thread B @a flags_wait_all_for return should be 0 indicating no flags set */ void test_thread_flags_clear() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); Thread t_wait(osPriorityNormal, THREAD_STACK_SIZE); t_wait.start(callback(flags_clear)); int32_t res = t_wait.flags_set(0x1); TEST_ASSERT_EQUAL(0x1, res); t_wait.join(); } void thread_wait_flags() { ThisThread::flags_wait_all(0x1); } void stack_info() { ThisThread::flags_wait_all(0x1); thread_wait_flags(); ThisThread::flags_wait_all(0x1); } /** Testing thread stack info Given the thread is running when a function is called from the thread context then the stack usage goes up and the reported stack size is as requested in the constructor and the sum of free and used stack sizes is equal to the total stack size when the function returns then the stack usage goes down and the reported stack size is as requested in the constructor and the sum of free and used stack sizes is equal to the total stack size */ void test_thread_stack_info() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); Thread t(osPriorityNormal, THREAD_STACK_SIZE); t.start(callback(stack_info)); ThisThread::yield(); TEST_ASSERT_EQUAL(THREAD_STACK_SIZE, t.stack_size()); TEST_ASSERT_EQUAL(THREAD_STACK_SIZE, t.free_stack() + t.used_stack()); uint32_t last_stack = t.used_stack(); t.flags_set(0x1); ThisThread::yield(); TEST_ASSERT_EQUAL(THREAD_STACK_SIZE, t.free_stack() + t.used_stack()); TEST_ASSERT(last_stack <= t.used_stack()); last_stack = t.used_stack(); t.flags_set(0x1); ThisThread::yield(); TEST_ASSERT_EQUAL(THREAD_STACK_SIZE, t.free_stack() + t.used_stack()); TEST_ASSERT(last_stack >= t.used_stack()); t.flags_set(0x1); t.join(); } /** Testing thread wait aka delay Given the thread is running when the @a wait function is called then the thread sleeps for given amount of time */ void test_thread_wait() { Timer timer; timer.start(); ThisThread::sleep_for(150); TEST_ASSERT_UINT32_WITHIN(50000, 150000, timer.read_us()); } /** Testing thread name Given a thread is started with a specified name when the name is queried using @a get_name then the returned name is as set */ void test_thread_name() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); const char tname[] = "Amazing thread"; Thread t(osPriorityNormal, THREAD_STACK_SIZE, NULL, tname); t.start(callback(thread_wait_flags)); TEST_ASSERT_EQUAL(strcmp(tname, t.get_name()), 0); t.flags_set(0x1); t.join(); } void test_deleted_thread() { } /** Testing thread states: deleted Given the thread is not started then its state, as reported by @a get_state, is @a Deleted when the thread is started and finishes executing then its state, as reported by @a get_state, is @a Deleted */ void test_deleted() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); Thread t(osPriorityNormal, THREAD_STACK_SIZE); TEST_ASSERT_EQUAL(Thread::Deleted, t.get_state()); t.start(callback(test_deleted_thread)); t.join(); TEST_ASSERT_EQUAL(Thread::Deleted, t.get_state()); } void test_delay_thread() { ThisThread::sleep_for(50); } /** Testing thread states: wait delay Given the thread is running when thread calls @a wait then its state, as reported by @a get_state, is @a WaitingDelay */ void test_delay() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); Thread t(osPriorityNormal, THREAD_STACK_SIZE); t.start(callback(test_delay_thread)); ThisThread::yield(); TEST_ASSERT_EQUAL(Thread::WaitingDelay, t.get_state()); t.join(); TEST_ASSERT_EQUAL(Thread::Deleted, t.get_state()); } void test_thread_flags_thread() { ThisThread::flags_wait_all(0x1); } /** Testing thread states: wait flags Given the thread is running when thread waits for flags then its state, as reported by @a get_state, is @a WaitingThreadFlag */ void test_thread_flags() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); Thread t(osPriorityNormal, THREAD_STACK_SIZE); t.start(callback(test_thread_flags_thread)); ThisThread::yield(); TEST_ASSERT_EQUAL(Thread::WaitingThreadFlag, t.get_state()); t.flags_set(0x1); } void test_evt_flag_thread(EventFlags *evtflg) { evtflg->wait_any(0x1); } /** Testing thread states: wait evt flag Given the thread is running when thread waits for an event flag then its state, as reported by @a get_state, is @a WaitingEventFlag */ void test_evt_flag() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); Thread t(osPriorityNormal, THREAD_STACK_SIZE); EventFlags evtflg; t.start(callback(test_evt_flag_thread, &evtflg)); ThisThread::yield(); TEST_ASSERT_EQUAL(Thread::WaitingEventFlag, t.get_state()); evtflg.set(0x1); } void test_mutex_thread(Mutex *mutex) { mutex->lock(); } /** Testing thread states: wait mutex Given the thread is running when thread waits for a mutex then its state, as reported by @a get_state, is @a WaitingMutex */ void test_mutex() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); Thread t(osPriorityNormal, THREAD_STACK_SIZE); Mutex mutex; mutex.lock(); t.start(callback(test_mutex_thread, &mutex)); ThisThread::yield(); TEST_ASSERT_EQUAL(Thread::WaitingMutex, t.get_state()); mutex.unlock(); } void test_semaphore_thread(Semaphore *sem) { sem->acquire(); } /** Testing thread states: wait semaphore Given the thread is running when thread waits for a semaphore then its state, as reported by @a get_state, is @a WaitingSemaphore */ void test_semaphore() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); Thread t(osPriorityNormal, THREAD_STACK_SIZE); Semaphore sem; t.start(callback(test_semaphore_thread, &sem)); ThisThread::yield(); TEST_ASSERT_EQUAL(Thread::WaitingSemaphore, t.get_state()); sem.release(); } void test_msg_get_thread(Queue *queue) { queue->get(); } /** Testing thread states: wait message get Given the thread is running when thread tries to get a message from an empty queue then its state, as reported by @a get_state, is @a WaitingMessageGet */ void test_msg_get() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); Thread t(osPriorityNormal, THREAD_STACK_SIZE); Queue queue; t.start(callback(test_msg_get_thread, &queue)); ThisThread::yield(); TEST_ASSERT_EQUAL(Thread::WaitingMessageGet, t.get_state()); queue.put((int32_t *)0xE1EE7); } void test_msg_put_thread(Queue *queue) { queue->put((int32_t *)0xDEADBEEF, osWaitForever); } /** Testing thread states: wait message put Given the thread is running when thread tries to put a message into a full queue then its state, as reported by @a get_state, is @a WaitingMessagePut */ void test_msg_put() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); Thread t(osPriorityNormal, THREAD_STACK_SIZE); Queue queue; queue.put((int32_t *)0xE1EE7); t.start(callback(test_msg_put_thread, &queue)); ThisThread::yield(); TEST_ASSERT_EQUAL(Thread::WaitingMessagePut, t.get_state()); queue.get(); } /** Utility function that places some date on the stack */ void use_some_stack() { volatile uint32_t stack_filler[10] = {0xDEADBEEF}; } /** Testing thread with external stack memory Given external buffer is supplied as stack to a thread when the thread executes then the supplies buffer is used as a stack */ void test_thread_ext_stack() { char stack[512]; Thread t(osPriorityNormal, sizeof(stack), (unsigned char *)stack); memset(&stack, 0, sizeof(stack)); t.start(callback(use_some_stack)); t.join(); /* If buffer was used as a stack it was cleared with pattern and some data were placed in it */ for (unsigned i = 0; i < sizeof(stack); i++) { if (stack[i] != 0) { return; } } TEST_FAIL_MESSAGE("External stack was not used."); } /** Testing thread priority operations Given thread running with osPriorityNormal when new priority is set using @a set_priority then priority is changed and can be retrieved using @a get_priority */ void test_thread_prio() { char *dummy = new (std::nothrow) char[THREAD_STACK_SIZE]; delete[] dummy; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough memory to run test"); Thread t(osPriorityNormal, THREAD_STACK_SIZE); t.start(callback(thread_wait_flags)); TEST_ASSERT_EQUAL(osPriorityNormal, t.get_priority()); t.set_priority(osPriorityHigh); TEST_ASSERT_EQUAL(osPriorityHigh, t.get_priority()); t.flags_set(0x1); t.join(); } utest::v1::status_t test_setup(const size_t number_of_cases) { GREENTEA_SETUP(20, "default_auto"); return verbose_test_setup_handler(number_of_cases); } #define DEFAULT_HANDLERS NULL,NULL,greentea_case_setup_handler,greentea_case_teardown_handler,greentea_case_failure_abort_handler // Test cases. It's spelled out rather than constructed with macro because // macros don't play nicely with the templates (extra comma). static const case_t cases[] = { {"Testing single thread", test_single_thread, DEFAULT_HANDLERS}, {"Testing parallel threads", test_parallel_threads<3, increment>, DEFAULT_HANDLERS}, {"Testing serial threads", test_serial_threads<10, increment>, DEFAULT_HANDLERS}, {"Testing single thread with yield", test_single_thread, DEFAULT_HANDLERS}, {"Testing parallel threads with yield", test_parallel_threads<3, increment_with_yield>, DEFAULT_HANDLERS}, {"Testing serial threads with yield", test_serial_threads<10, increment_with_yield>, DEFAULT_HANDLERS}, {"Testing single thread with wait", test_single_thread, DEFAULT_HANDLERS}, {"Testing parallel threads with wait", test_parallel_threads<3, increment_with_wait>, DEFAULT_HANDLERS}, {"Testing serial threads with wait", test_serial_threads<10, increment_with_wait>, DEFAULT_HANDLERS}, {"Testing single thread with child", test_single_thread, DEFAULT_HANDLERS}, {"Testing parallel threads with child", test_parallel_threads<2, increment_with_child>, DEFAULT_HANDLERS}, {"Testing serial threads with child", test_serial_threads<10, increment_with_child>, DEFAULT_HANDLERS}, {"Testing single thread with murder", test_single_thread, DEFAULT_HANDLERS}, {"Testing parallel threads with murder", test_parallel_threads<2, increment_with_murder>, DEFAULT_HANDLERS}, {"Testing serial threads with murder", test_serial_threads<10, increment_with_murder>, DEFAULT_HANDLERS}, {"Testing thread self terminate", test_self_terminate, DEFAULT_HANDLERS}, {"Testing thread flags: wait", test_thread_flags_set<0x1, flags_wait>, DEFAULT_HANDLERS}, {"Testing thread flags: timeout", test_thread_flags_set<0x1, flags_wait_tout>, DEFAULT_HANDLERS}, {"Testing thread flags: multi-bit all", test_thread_flags_set<0x3, flags_wait_multibit>, DEFAULT_HANDLERS}, {"Testing thread flags: multi-bit all timeout", test_thread_flags_set<0x1, flags_wait_multibit_tout>, DEFAULT_HANDLERS}, {"Testing thread flags: multi-bit any", test_thread_flags_set<0x1, flags_wait_multibit_any>, DEFAULT_HANDLERS}, {"Testing thread flags: flags clear", test_thread_flags_clear, DEFAULT_HANDLERS}, {"Testing thread stack info", test_thread_stack_info, DEFAULT_HANDLERS}, {"Testing thread wait", test_thread_wait, DEFAULT_HANDLERS}, {"Testing thread name", test_thread_name, DEFAULT_HANDLERS}, {"Testing thread states: deleted", test_deleted, DEFAULT_HANDLERS}, {"Testing thread states: wait delay", test_delay, DEFAULT_HANDLERS}, {"Testing thread states: wait thread flags", test_thread_flags, DEFAULT_HANDLERS}, {"Testing thread states: wait event flag", test_evt_flag, DEFAULT_HANDLERS}, {"Testing thread states: wait mutex", test_mutex, DEFAULT_HANDLERS}, {"Testing thread states: wait semaphore", test_semaphore, DEFAULT_HANDLERS}, {"Testing thread states: wait message get", test_msg_get, DEFAULT_HANDLERS}, {"Testing thread states: wait message put", test_msg_put, DEFAULT_HANDLERS}, {"Testing thread with external stack memory", test_thread_ext_stack, DEFAULT_HANDLERS}, {"Testing thread priority ops", test_thread_prio, DEFAULT_HANDLERS} }; Specification specification(test_setup, cases); int main() { return !Harness::run(specification); }