#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 #define THREAD_STACK_SIZE 768 using namespace utest::v1; // The counter type used accross all the tests // It is internall ysynchronized so read typedef SynchronizedIntegral counter_t; // Tasks with different functions to test on threads void increment(counter_t* counter) { (*counter)++; } void increment_with_yield(counter_t* counter) { Thread::yield(); (*counter)++; } void increment_with_wait(counter_t* counter) { Thread::wait(100); (*counter)++; } void increment_with_child(counter_t* counter) { Thread child(osPriorityNormal, THREAD_STACK_SIZE/2); child.start(callback(increment, counter)); child.join(); } 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(osPriorityNormal, THREAD_STACK_SIZE); child.start(callback(increment, counter)); child.terminate(); } (*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() { 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() { counter_t counter(0); Thread *threads[N]; for (int i = 0; i < N; i++) { threads[i] = new Thread(osPriorityNormal, THREAD_STACK_SIZE); threads[i]->start(callback(F, &counter)); } for (int i = 0; i < N; i++) { threads[i]->join(); delete threads[i]; } 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); 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 Thread(osPriorityNormal, THREAD_STACK_SIZE); thread->start(callback(self_terminate, thread)); thread->join(); delete thread; } void signal_wait() { osEvent evt = Thread::signal_wait(0x1); TEST_ASSERT_EQUAL(osEventSignal, evt.status); TEST_ASSERT_EQUAL(0x1, evt.value.signals); } void signal_wait_tout() { osEvent evt = Thread::signal_wait(0x2, 50); TEST_ASSERT_EQUAL(osEventTimeout, evt.status); } void signal_wait_multibit() { osEvent evt = Thread::signal_wait(0x1 | 0x2, 50); TEST_ASSERT_EQUAL(osEventSignal, evt.status); TEST_ASSERT_EQUAL(0x3, evt.value.signals); } void signal_wait_multibit_tout() { osEvent evt = Thread::signal_wait(0x1 | 0x2, 50); TEST_ASSERT_EQUAL(osEventTimeout, evt.status); } /** Testing thread signal: wait Given two threads (A & B) are started when thread A executes @a signal_wait(0x1) and thread B execute @a signal_set(0x1) then thread A exits the wait and continues execution Testing thread signal: timeout Given two threads (A & B) are started when thread A executes @a signal_wait(0x1 | 0x2, 50) with a timeout of 50ms and thread B execute @a signal_set(0x2) then thread A keeps waiting for correct signal until it timeouts Testing thread signal: multi-bit Given two threads (A & B) are started when thread A executes @a signal_wait(0x1 | 0x2) and thread B execute @a signal_set(0x1 | 0x2) then thread A exits the wait and continues execution Testing thread signal: multi-bit timeout Given two threads (A & B) are started when thread A executes @a signal_wait(0x1, 50) with a timeout of 50ms and thread B execute @a signal_set(0x2) then thread A keeps waiting for correct signal until it timeouts */ template void test_thread_signal() { Thread t_wait; t_wait.start(callback(F)); Thread::yield(); Thread::State state = t_wait.get_state(); TEST_ASSERT_EQUAL(Thread::WaitingThreadFlag, state); int32_t res = t_wait.signal_set(S); t_wait.join(); } void signal_clr() { Thread::yield(); int32_t sig = Thread::signal_clr(0x1); TEST_ASSERT_EQUAL(0x1, sig); /* Signal cleared we should get timeout */ osEvent evt = Thread::signal_wait(0x1, 0); TEST_ASSERT_EQUAL(osOK, evt.status); } /** Testing thread signals: signal clear Given two threads (A & B) are started when thread A executes @a signal_set(0x1) and thread B execute @a signal_clr(0x1) and thread B execute @a signal_wait(0x1, 0) then thread B @a signal_wait status should be osOK indicating a timeout */ void test_thread_signal_clr() { Thread t_wait; t_wait.start(callback(signal_clr)); int32_t res = t_wait.signal_set(0x1); TEST_ASSERT_EQUAL(0x1, res); t_wait.join(); } void thread_wait_signal() { Thread::signal_wait(0x1); } void stack_info() { Thread::signal_wait(0x1); thread_wait_signal(); Thread::signal_wait(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() { Thread t(osPriorityNormal, THREAD_STACK_SIZE); t.start(callback(stack_info)); Thread::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.signal_set(0x1); Thread::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.signal_set(0x1); Thread::yield(); TEST_ASSERT_EQUAL(THREAD_STACK_SIZE, t.free_stack() + t.used_stack()); TEST_ASSERT(last_stack >= t.used_stack()); t.signal_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() { uint32_t start = us_ticker_read(); Thread::wait(150); TEST_ASSERT_UINT32_WITHIN(50000, 150000, us_ticker_read() - start); } /** 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() { const char tname[] = "Amazing thread"; Thread t(osPriorityNormal, THREAD_STACK_SIZE, NULL, tname); t.start(callback(thread_wait_signal)); TEST_ASSERT_EQUAL(strcmp(tname, t.get_name()), 0); t.signal_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() { Thread t; 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() { Thread::wait(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() { Thread t; t.start(callback(test_delay_thread)); Thread::yield(); TEST_ASSERT_EQUAL(Thread::WaitingDelay, t.get_state()); t.join(); TEST_ASSERT_EQUAL(Thread::Deleted, t.get_state()); } void test_signal_thread() { Thread::signal_wait(0x1); } /** Testing thread states: wait signal Given the thread is running when thread waits for a signal then its state, as reported by @a get_state, is @a WaitingSignal */ void test_signal() { Thread t; t.start(callback(test_signal_thread)); Thread::yield(); TEST_ASSERT_EQUAL(Thread::WaitingThreadFlag, t.get_state()); t.signal_set(0x1); } void test_evt_flag_thread(osEventFlagsId_t evtflg) { osEventFlagsWait(evtflg, 0x1, osFlagsWaitAny, osWaitForever); } /** 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() { Thread t; mbed_rtos_storage_event_flags_t evtflg_mem; osEventFlagsAttr_t evtflg_attr; osEventFlagsId_t evtflg; evtflg_attr.cb_mem = &evtflg_mem; evtflg_attr.cb_size = sizeof(evtflg_mem); evtflg = osEventFlagsNew(&evtflg_attr); TEST_ASSERT_NOT_EQUAL(NULL, evtflg); t.start(callback(test_evt_flag_thread, evtflg)); Thread::yield(); TEST_ASSERT_EQUAL(Thread::WaitingEventFlag, t.get_state()); osEventFlagsSet(evtflg, 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() { Thread t; Mutex mutex; mutex.lock(); t.start(callback(test_mutex_thread, &mutex)); Thread::yield(); TEST_ASSERT_EQUAL(Thread::WaitingMutex, t.get_state()); mutex.unlock(); } void test_semaphore_thread(Semaphore *sem) { sem->wait(); } /** 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() { Thread t; Semaphore sem; t.start(callback(test_semaphore_thread, &sem)); Thread::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() { Thread t; Queue queue; t.start(callback(test_msg_get_thread, &queue)); Thread::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() { Thread t; Queue queue; queue.put((int32_t *)0xE1EE7); t.start(callback(test_msg_put_thread, &queue)); Thread::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() { Thread t(osPriorityNormal); t.start(callback(thread_wait_signal)); TEST_ASSERT_EQUAL(osPriorityNormal, t.get_priority()); t.set_priority(osPriorityHigh); TEST_ASSERT_EQUAL(osPriorityHigh, t.get_priority()); t.signal_set(0x1); t.join(); } utest::v1::status_t test_setup(const size_t number_of_cases) { GREENTEA_SETUP(15, "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<3, 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<3, 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 signals: wait", test_thread_signal<0x1, signal_wait>, DEFAULT_HANDLERS}, {"Testing thread signals: timeout", test_thread_signal<0x1, signal_wait_tout>, DEFAULT_HANDLERS}, {"Testing thread signals: multi-bit", test_thread_signal<0x3, signal_wait_multibit>, DEFAULT_HANDLERS}, {"Testing thread signals: multi-bit timeout", test_thread_signal<0x1, signal_wait_multibit_tout>, DEFAULT_HANDLERS}, {"Testing thread signals: signal clear", test_thread_signal_clr, 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 signal", test_signal, 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); }