/* mbed Microcontroller Library * Copyright (c) 2017 ARM Limited * SPDX-License-Identifier: Apache-2.0 * * 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. */ #if !DEVICE_USTICKER #error [NOT_SUPPORTED] UsTicker need to be enabled for this test. #else #include "mbed.h" #include "greentea-client/test_env.h" #include "unity.h" #include "utest.h" #include "rtos.h" using namespace utest::v1; struct test_data { Semaphore *sem; uint32_t data; }; #if defined(MBED_CONF_RTOS_PRESENT) #define THREAD_DELAY 30 #define SEMAPHORE_SLOTS 2 #define SEM_CHANGES 100 #define SHORT_WAIT 5 #define THREAD_STACK_SIZE 320 /* larger stack cause out of heap memory on some 16kB RAM boards in multi thread test*/ Semaphore two_slots(SEMAPHORE_SLOTS); volatile int change_counter = 0; volatile int sem_counter = 0; volatile bool sem_defect = false; void test_thread(int const *delay) { const int thread_delay = *delay; while (true) { two_slots.acquire(); sem_counter++; const bool sem_lock_failed = sem_counter > SEMAPHORE_SLOTS; if (sem_lock_failed) { sem_defect = true; } ThisThread::sleep_for(thread_delay); sem_counter--; change_counter++; two_slots.release(); } } /* Test multiple threads Given 3 threads started with different delays and a semaphore with 2 tokens when each thread runs it tries to acquire a token then no more than two threads should be able to access protected region */ void test_multi() { const int t1_delay = THREAD_DELAY * 1; const int t2_delay = THREAD_DELAY * 2; const int t3_delay = THREAD_DELAY * 3; Thread t1(osPriorityNormal, THREAD_STACK_SIZE); Thread t2(osPriorityNormal, THREAD_STACK_SIZE); Thread t3(osPriorityNormal, THREAD_STACK_SIZE); t1.start(callback(test_thread, &t1_delay)); t2.start(callback(test_thread, &t2_delay)); t3.start(callback(test_thread, &t3_delay)); while (true) { if (change_counter >= SEM_CHANGES or sem_defect == true) { t1.terminate(); t2.terminate(); t3.terminate(); break; } } } void single_thread(struct test_data *data) { data->sem->acquire(); data->data++; } /** Test single thread Given a two threads A & B and a semaphore (with count of 0) and a counter (equals to 0) when thread B calls @a wait then thread B waits for a token to become available then the counter is equal to 0 when thread A calls @a release on the semaphore then thread B acquires a token and increments the counter then the counter equals to 1 */ void test_single_thread() { Thread t(osPriorityNormal, THREAD_STACK_SIZE); Semaphore sem(0); struct test_data data; osStatus res; data.sem = &sem; data.data = 0; res = t.start(callback(single_thread, &data)); TEST_ASSERT_EQUAL(osOK, res); ThisThread::sleep_for(SHORT_WAIT); TEST_ASSERT_EQUAL(Thread::WaitingSemaphore, t.get_state()); TEST_ASSERT_EQUAL(0, data.data); res = sem.release(); TEST_ASSERT_EQUAL(osOK, res); ThisThread::sleep_for(SHORT_WAIT); TEST_ASSERT_EQUAL(1, data.data); t.join(); } void timeout_thread(Semaphore *sem) { bool acquired = sem->try_acquire_for(30); TEST_ASSERT_FALSE(acquired); } /** Test timeout Given thread and a semaphore with no tokens available when thread calls @a wait on the semaphore with timeout of 10ms then the thread waits for 10ms and timeouts after */ void test_timeout() { Thread t(osPriorityNormal, THREAD_STACK_SIZE); Semaphore sem(0); osStatus res; Timer timer; timer.start(); res = t.start(callback(timeout_thread, &sem)); TEST_ASSERT_EQUAL(osOK, res); ThisThread::sleep_for(SHORT_WAIT); TEST_ASSERT_EQUAL(Thread::WaitingSemaphore, t.get_state()); t.join(); TEST_ASSERT_UINT32_WITHIN(5000, 30000, timer.read_us()); } #endif void test_ticker_release(struct test_data *data) { osStatus res; data->data++; res = data->sem->release(); TEST_ASSERT_EQUAL(osOK, res); } /** Test semaphore acquire Given a semaphore with no tokens available and ticker with the callback registered when callbacks update the test data and release semaphore which will unblock the main thread which is blocked on semaphore acquire. */ void test_semaphore_acquire() { Semaphore sem(0); struct test_data data; data.sem = &sem; data.data = 0; Ticker t1; t1.attach_us(callback(test_ticker_release, &data), 3000); sem.acquire(); t1.detach(); TEST_ASSERT_EQUAL(1, data.data); } void test_ticker_try_acquire(Semaphore *sem) { osStatus res; res = sem->try_acquire(); TEST_ASSERT_FALSE(res); } /** Test semaphore try acquire Given a semaphore with no tokens available and ticker with the callback registered when callbacks try to acquire the semaphore will fail. */ void test_semaphore_try_acquire() { Semaphore sem(0); Ticker t1; t1.attach_us(callback(test_ticker_try_acquire, &sem), 3000); ThisThread::sleep_for(3); t1.detach(); } /** Test semaphore try timeout Given a semaphore with no tokens available when the main thread calls @a wait on the semaphore with try timeout of 5ms then the main thread waits for 5ms and timeouts after */ void test_semaphore_try_timeout() { Semaphore sem(0); bool res; res = sem.try_acquire_for(5); TEST_ASSERT_FALSE(res); } void test_ticker_semaphore_release(struct Semaphore *sem) { osStatus res; res = sem->release(); TEST_ASSERT_EQUAL(osOK, res); } /** Test semaphore try acquire timeout Given a semaphore with no tokens available and ticker with the callback registered when callbacks release the semaphore will unblock the main thread which is waiting for semaphore with a timeout. */ void test_semaphore_try_acquire_timeout() { Semaphore sem(0); bool res; Ticker t1; t1.attach_us(callback(test_ticker_semaphore_release, &sem), 3000); res = sem.try_acquire_for(30); t1.detach(); TEST_ASSERT_TRUE(res); } /** Test no timeouts Test 1 token no timeout Given thread and a semaphore with one token available when thread calls @a wait on the semaphore with timeout of 0ms then the thread acquires the token immediately Test 0 tokens no timeout Given thread and a semaphore with no tokens available when thread calls @a wait on the semaphore with timeout of 0ms then the thread returns immediately without acquiring a token */ template void test_no_timeout() { Semaphore sem(T); Timer timer; timer.start(); bool acquired = sem.try_acquire(); TEST_ASSERT_EQUAL(T > 0, acquired); TEST_ASSERT_UINT32_WITHIN(5000, 0, timer.read_us()); } /** Test multiple tokens wait Given a thread and a semaphore initialized with 5 tokens when thread calls @a wait 6 times on the semaphore then the token counts goes to zero */ void test_multiple_tokens_wait() { Semaphore sem(5); for (int i = 5; i >= 0; i--) { bool acquired = sem.try_acquire(); TEST_ASSERT_EQUAL(i > 0, acquired); } } /** Test multiple tokens release Given a thread and a semaphore initialized with zero tokens and max of 5 when thread calls @a release 6 times on the semaphore then the token count should be equal to 5 and last release call should fail */ void test_multiple_tokens_release() { Semaphore sem(0, 5); for (int i = 5; i > 0; i--) { osStatus stat = sem.release(); TEST_ASSERT_EQUAL(osOK, stat); } osStatus stat = sem.release(); TEST_ASSERT_EQUAL(osErrorResource, stat); } utest::v1::status_t test_setup(const size_t number_of_cases) { GREENTEA_SETUP(10, "default_auto"); return verbose_test_setup_handler(number_of_cases); } Case cases[] = { Case("Test 1 token no timeout", test_no_timeout<1>), Case("Test 0 tokens no timeout", test_no_timeout<0>), Case("Test multiple tokens wait", test_multiple_tokens_wait), Case("Test multiple tokens release", test_multiple_tokens_release), Case("Test semaphore acquire", test_semaphore_acquire), Case("Test semaphore try acquire", test_semaphore_try_acquire), Case("Test semaphore try timeout", test_semaphore_try_timeout), Case("Test semaphore try acquire timeout", test_semaphore_try_acquire_timeout), #if defined(MBED_CONF_RTOS_PRESENT) Case("Test single thread", test_single_thread), Case("Test timeout", test_timeout), Case("Test multiple threads", test_multi) #endif }; Specification specification(test_setup, cases); int main() { return !Harness::run(specification); } #endif // !DEVICE_USTICKER