/* 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; using namespace std::chrono; struct test_data { Semaphore *sem; uint32_t data; }; #define TEST_ASSERT_DURATION_WITHIN(delta, expected, actual) \ do { \ using ct = std::common_type_t<decltype(delta), decltype(expected), decltype(actual)>; \ TEST_ASSERT_INT_WITHIN(ct(delta).count(), ct(expected).count(), ct(actual).count()); \ } while (0) #if defined(MBED_CONF_RTOS_PRESENT) #define THREAD_DELAY 30ms #define SEMAPHORE_SLOTS 2 #define SEM_CHANGES 100 #define SHORT_WAIT 5ms #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(rtos::Kernel::Clock::duration const *delay) { const auto 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 rtos::Kernel::Clock::duration t1_delay = THREAD_DELAY * 1; const rtos::Kernel::Clock::duration t2_delay = THREAD_DELAY * 2; const rtos::Kernel::Clock::duration 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 acquire 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(30ms); TEST_ASSERT_FALSE(acquired); } /** Test timeout Given thread and a semaphore with no tokens available when a thread calls @a try_acquire_for with a timeout of 30ms then the thread is blocked for up to 30ms 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_DURATION_WITHIN(5ms, 30ms, timer.elapsed_time()); } #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 the main thread calls @a acquire then the main thread is blocked when callback calls @a release on the semaphore then the main thread is unblocked */ void test_semaphore_acquire() { Semaphore sem(0); struct test_data data; data.sem = &sem; data.data = 0; Ticker t1; t1.attach(callback(test_ticker_release, &data), 3ms); 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 callback tries to acquire the semaphore, it fails. */ void test_semaphore_try_acquire() { Semaphore sem(0); Ticker t1; t1.attach(callback(test_ticker_try_acquire, &sem), 3ms); ThisThread::sleep_for(4ms); t1.detach(); } /** Test semaphore try timeout Given a semaphore with no tokens available when the main thread calls @a try_acquire_for with 3ms timeout then the main thread is blocked for 3ms and timeouts after */ void test_semaphore_try_timeout() { Semaphore sem(0); bool res; res = sem.try_acquire_for(3ms); 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 the main thread calls @a try_acquire_for with 10ms timeout then the main thread is blocked for up to 10ms when callback call @a release on the semaphore after 3ms then the main thread is unblocked. */ void test_semaphore_try_acquire_timeout() { Semaphore sem(0); bool res; Ticker t1; t1.attach(callback(test_ticker_semaphore_release, &sem), 3ms); res = sem.try_acquire_for(10ms); 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 try_acquire 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 try_acquire with timeout of 0ms then the thread returns immediately without acquiring a token */ template<int T> void test_no_timeout() { Semaphore sem(T); Timer timer; timer.start(); bool acquired = sem.try_acquire(); TEST_ASSERT_EQUAL(T > 0, acquired); TEST_ASSERT_DURATION_WITHIN(5ms, 0ms, timer.elapsed_time()); } /** Test multiple tokens wait Given a thread and a semaphore initialized with 5 tokens when thread calls @a try_acquire 6 times in a loop 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