From c06b11b386396376a5872a0eef82e1dd92a4d7d3 Mon Sep 17 00:00:00 2001 From: Szymon Kwiatkowski <44876530+Tharazi97@users.noreply.github.com> Date: Thu, 18 Jul 2019 14:50:19 +0300 Subject: [PATCH] Add Unittest equeue tests --- TESTS/events/equeue/main.cpp | 1026 +++++++++++++++++++++++ UNITTESTS/events/equeue/test_equeue.cpp | 997 ++++++++++++++++++++++ UNITTESTS/events/equeue/unittest.cmake | 24 + UNITTESTS/stubs/EqueuePosix_stub.c | 117 +++ 4 files changed, 2164 insertions(+) create mode 100644 TESTS/events/equeue/main.cpp create mode 100644 UNITTESTS/events/equeue/test_equeue.cpp create mode 100644 UNITTESTS/events/equeue/unittest.cmake create mode 100644 UNITTESTS/stubs/EqueuePosix_stub.c diff --git a/TESTS/events/equeue/main.cpp b/TESTS/events/equeue/main.cpp new file mode 100644 index 00000000000..c5365ee4eed --- /dev/null +++ b/TESTS/events/equeue/main.cpp @@ -0,0 +1,1026 @@ +/* mbed Microcontroller Library + * Copyright (c) 2019 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. + */ + +#ifndef MBED_EXTENDED_TESTS +#error [NOT_SUPPORTED] When running on CI this test is disabled due to limiting testing time. +#else + +#include "utest/utest.h" +#include "unity/unity.h" +#include "greentea-client/test_env.h" + +#include "equeue.h" +#include "mbed.h" + +using namespace utest::v1; + +#define TEST_EQUEUE_SIZE (4*EVENTS_EVENT_SIZE) +#define TEST_THREAD_STACK_SIZE 512 +#define DISPATCH_INFINITE -1 + +// Test functions +static void pass_func(void *eh) +{ +} + +static void simple_func(void *p) +{ + (*(reinterpret_cast(p)))++; +} + +static void sloth_func(void *p) +{ + ThisThread::sleep_for(10); + (*(reinterpret_cast(p)))++; +} + +struct indirect { + uint8_t *touched; + uint8_t buffer[7]; +}; + +static void indirect_func(void *p) +{ + struct indirect *i = reinterpret_cast(p); + (*i->touched)++; +} + +struct timing { + unsigned tick; + unsigned delay; +}; + +static void timing_func(void *p) +{ + struct timing *timing = reinterpret_cast(p); + unsigned tick = equeue_tick(); + + unsigned t1 = timing->delay; + unsigned t2 = tick - timing->tick; + TEST_ASSERT_UINT_WITHIN(10, t2, t1); + + timing->tick = tick; +} + +struct fragment { + equeue_t *q; + size_t size; + struct timing timing; +}; + +static void fragment_func(void *p) +{ + struct fragment *fragment = reinterpret_cast(p); + timing_func(&fragment->timing); + + struct fragment *nfragment = reinterpret_cast(equeue_alloc(fragment->q, fragment->size)); + TEST_ASSERT_NOT_NULL(nfragment); + + *nfragment = *fragment; + equeue_event_delay(nfragment, fragment->timing.delay); + + int id = equeue_post(nfragment->q, fragment_func, nfragment); + TEST_ASSERT_NOT_EQUAL(0, id); +} + +struct cancel { + equeue_t *q; + int id; +}; + +static void cancel_func(void *p) +{ + struct cancel *ccel = reinterpret_cast(p); + equeue_cancel(ccel->q, ccel->id); +} + +struct nest { + equeue_t *q; + void (*cb)(void *); + void *data; +}; + +static void nest_func(void *p) +{ + struct nest *nst = reinterpret_cast(p); + equeue_call(nst->q, nst->cb, nst->data); + + ThisThread::sleep_for(10); +} + +static void multithread_thread(equeue_t *p) +{ + equeue_dispatch(p, DISPATCH_INFINITE); +} + +static void background_func(void *p, int ms) +{ + *(reinterpret_cast(p)) = ms; +} + +struct ethread { + equeue_t *q; + int ms; +}; + +static void ethread_dispatch(void *p) +{ + struct ethread *t = reinterpret_cast(p); + equeue_dispatch(t->q, t->ms); +} + +struct count_and_queue { + int p; + equeue_t *q; +}; + +static void simple_breaker(void *p) +{ + struct count_and_queue *caq = reinterpret_cast(p); + equeue_break(caq->q); + ThisThread::sleep_for(10); + caq->p++; +} + +// Simple call tests + +/** Test that equeue executes function passed by equeue_call. + * + * Given queue is initialized. + * When the event is scheduled and after that equeue_dispatch is called. + * Then function passed by equeue_call is executed properly. + */ +static void test_equeue_simple_call() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched = 0; + equeue_call(&q, simple_func, &touched); + equeue_dispatch(&q, 0); + TEST_ASSERT_EQUAL_UINT8(1, touched); + + equeue_dispatch(&q, 10); + TEST_ASSERT_EQUAL_UINT8(1, touched); + + equeue_destroy(&q); +} + +/** Test that equeue executes function passed by equeue_call_in. + * + * Given queue is initialized. + * When the event is scheduled and after that equeue_dispatch is called. + * Then function passed by equeue_call_in is executed properly. + */ +static void test_equeue_simple_call_in() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched = 0; + int id = equeue_call_in(&q, 10, simple_func, &touched); + TEST_ASSERT_NOT_EQUAL(0, id); + + equeue_dispatch(&q, 15); + TEST_ASSERT_EQUAL_UINT8(1, touched); + + equeue_dispatch(&q, 10); + TEST_ASSERT_EQUAL_UINT8(1, touched); + + equeue_destroy(&q); +} + +/** Test that equeue executes function passed by equeue_call_every. + * + * Given queue is initialized. + * When the event is scheduled and after that equeue_dispatch is called. + * Then function passed by equeue_call_every is executed properly. + */ +static void test_equeue_simple_call_every() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched = 0; + int id = equeue_call_every(&q, 10, simple_func, &touched); + TEST_ASSERT_NOT_EQUAL(0, id); + + equeue_dispatch(&q, 15); + TEST_ASSERT_EQUAL_UINT8(1, touched); + + equeue_destroy(&q); +} + +/** Test that equeue executes function passed by equeue_post. + * + * Given queue is initialized. + * When the event is posted and after that equeue_dispatch is called. + * Then function passed by equeue_post is executed properly. + */ +static void test_equeue_simple_post() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched = 0; + struct indirect *i = reinterpret_cast(equeue_alloc(&q, sizeof(struct indirect))); + TEST_ASSERT_NOT_NULL(i); + + i->touched = &touched; + int id = equeue_post(&q, indirect_func, i); + TEST_ASSERT_NOT_EQUAL(0, id); + + equeue_dispatch(&q, 0); + TEST_ASSERT_EQUAL_UINT8(1, *i->touched); + + equeue_destroy(&q); +} + + +// Misc tests + +/** Test that equeue executes events attached to its events destructors by equeue_event_dtor. + * + * Given queue is initialized. + * When equeue events are being destroyed by equeue_dispatch, equeue_cancel, or equeue_destroy. + * Then functions attached to equeue events destructors are executed properly. + */ +static void test_equeue_destructor() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched = 0; + struct indirect *e; + int ids[3]; + + for (int i = 0; i < 3; i++) { + e = reinterpret_cast(equeue_alloc(&q, sizeof(struct indirect))); + TEST_ASSERT_NOT_NULL(e); + + e->touched = &touched; + equeue_event_dtor(e, indirect_func); + int id = equeue_post(&q, pass_func, e); + TEST_ASSERT_NOT_EQUAL(0, id); + } + + equeue_dispatch(&q, 0); + TEST_ASSERT_EQUAL_UINT8(3, touched); + + touched = 0; + for (int i = 0; i < 3; i++) { + e = reinterpret_cast(equeue_alloc(&q, sizeof(struct indirect))); + TEST_ASSERT_NOT_NULL(e); + + e->touched = &touched; + equeue_event_dtor(e, indirect_func); + ids[i] = equeue_post(&q, pass_func, e); + TEST_ASSERT_NOT_EQUAL(0, ids[i]); + } + + for (int i = 0; i < 3; i++) { + equeue_cancel(&q, ids[i]); + } + TEST_ASSERT_EQUAL_UINT8(3, touched); + + equeue_dispatch(&q, 0); + + touched = 0; + for (int i = 0; i < 3; i++) { + e = reinterpret_cast(equeue_alloc(&q, sizeof(struct indirect))); + TEST_ASSERT_NOT_NULL(e); + + e->touched = &touched; + equeue_event_dtor(e, indirect_func); + int id = equeue_post(&q, pass_func, e); + TEST_ASSERT_NOT_EQUAL(0, id); + } + + equeue_destroy(&q); + TEST_ASSERT_EQUAL_UINT8(3, touched); +} + +/** Test that equeue_alloc returns 0 when equeue can not be allocated. + * + * Given queue is initialized. + * When equeue_alloc is called and equeue can not be allocated + * Then function equeue_alloc returns NULL. + */ +static void test_equeue_allocation_failure() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + void *p = equeue_alloc(&q, 2 * TEST_EQUEUE_SIZE); + TEST_ASSERT_NULL(p); + + for (int i = 0; i < 100; i++) { + p = equeue_alloc(&q, 0); + } + TEST_ASSERT_NULL(p); + + equeue_destroy(&q); +} + +/** Test that equeue does not execute evenets that has been canceled. + * + * Given queue is initialized. + * When events are canceled by equeue_cancel. + * Then they are not executed by calling equeue_dispatch. + */ +template +static void test_equeue_cancel() +{ + equeue_t q; + int err = equeue_create(&q, (N * EVENTS_EVENT_SIZE)); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched = 0; + int ids[N]; + + for (int i = 0; i < N; i++) { + ids[i] = equeue_call(&q, simple_func, &touched); + TEST_ASSERT_NOT_EQUAL(0, ids[i]); + } + + for (int i = N - 1; i >= 0; i--) { + equeue_cancel(&q, ids[i]); + } + + equeue_dispatch(&q, 0); + TEST_ASSERT_EQUAL_UINT(0, touched); + + equeue_destroy(&q); +} + +/** Test that events can be cancelled by function executed by equeue_dispatch. + * + * Given queue is initialized. + * When event is cancelled by another event while dispatching. + * Then event that was cancelled is not being executed. + */ +static void test_equeue_cancel_inflight() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched = 0; + + int id = equeue_call(&q, simple_func, &touched); + equeue_cancel(&q, id); + + equeue_dispatch(&q, 0); + TEST_ASSERT_EQUAL_UINT8(0, touched); + + id = equeue_call(&q, simple_func, &touched); + equeue_cancel(&q, id); + + equeue_dispatch(&q, 0); + TEST_ASSERT_EQUAL_UINT8(0, touched); + + struct cancel *ccel = reinterpret_cast(equeue_alloc(&q, sizeof(struct cancel))); + TEST_ASSERT_NOT_NULL(ccel); + ccel->q = &q; + ccel->id = 0; + + id = equeue_post(&q, cancel_func, ccel); + TEST_ASSERT_NOT_EQUAL(0, id); + + ccel->id = equeue_call(&q, simple_func, &touched); + + equeue_dispatch(&q, 0); + TEST_ASSERT_EQUAL_UINT8(0, touched); + + equeue_destroy(&q); +} + +/** Test that unnecessary canceling events would not affect executing other events. + * + * Given queue is initialized. + * When event is unnecessary canceled by equeue_cancel. + * Then other events are properly executed after calling equeue_dispatch. + */ +static void test_equeue_cancel_unnecessarily() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + int id = equeue_call(&q, pass_func, 0); + for (int i = 0; i < 5; i++) { + equeue_cancel(&q, id); + } + + id = equeue_call(&q, pass_func, 0); + equeue_dispatch(&q, 0); + for (int i = 0; i < 5; i++) { + equeue_cancel(&q, id); + } + + uint8_t touched = 0; + equeue_call(&q, simple_func, &touched); + for (int i = 0; i < 5; i++) { + equeue_cancel(&q, id); + } + + equeue_dispatch(&q, 0); + TEST_ASSERT_EQUAL_UINT8(1, touched); + + equeue_destroy(&q); +} + +/** Test that dispatching events that have 0 ms period time would not end up in infinite loop. + * + * Given queue is initialized. + * When events have 0 ms period time. + * Then dispatching would not end up in infinite loop. + */ +static void test_equeue_loop_protect() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched1 = 0; + equeue_call_every(&q, 0, simple_func, &touched1); + + equeue_dispatch(&q, 0); + TEST_ASSERT_EQUAL_UINT8(1, touched1); + + touched1 = 0; + uint8_t touched2 = 0; + equeue_call_every(&q, 1, simple_func, &touched2); + + equeue_dispatch(&q, 0); + TEST_ASSERT_EQUAL_UINT8(1, touched1); + TEST_ASSERT_EQUAL_UINT8(0, touched2); + + equeue_destroy(&q); +} + +/** Test that equeue_break breaks event queue out of dispatching. + * + * Given queue is initialized. + * When equeue_break is called. + * Then event queue will stop dispatching after finisching current dispatching cycle. + */ +static void test_equeue_break() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched1 = 0; + equeue_call_every(&q, 0, simple_func, &touched1); + + uint8_t touched2 = 0; + equeue_call_every(&q, 5, simple_func, &touched2); + + equeue_break(&q); + equeue_dispatch(&q, DISPATCH_INFINITE); + TEST_ASSERT_EQUAL_UINT8(1, touched1); + TEST_ASSERT_EQUAL_UINT8(0, touched2); + + equeue_destroy(&q); +} + +/** Test that equeue_break function breaks equeue dispatching only once. + * + * Given queue is initialized. + * When equeue_break is called several times. + * Then equeue is stopped only once. + */ +static void test_equeue_break_no_windup() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched = 0; + equeue_call_every(&q, 0, simple_func, &touched); + + equeue_break(&q); + equeue_break(&q); + equeue_dispatch(&q, DISPATCH_INFINITE); + TEST_ASSERT_EQUAL_UINT8(1, touched); + + touched = 0; + equeue_dispatch(&q, 55); + TEST_ASSERT(touched > 1); + + equeue_destroy(&q); +} + +/** Test that function passed by equeue_call_every is being executed periodically. + * + * Given queue is initialized. + * When function is passed by equeue_call_every with specified period. + * Then event is executed (dispatch time/period) times. + */ +static void test_equeue_period() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched = 0; + equeue_call_every(&q, 10, simple_func, &touched); + + equeue_dispatch(&q, 55); + TEST_ASSERT_EQUAL_UINT8(5, touched); + + equeue_destroy(&q); +} + +/** Test that function added to the equeue by other function which already is in equeue executes in the next dispatch, or after the end of execution of the "mother" event. + * + * Given queue is initialized. + * When nested function is added to enqueue. + * Then it is executed in the next dispatch, or after execution of "mother" function. + */ +static void test_equeue_nested() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched = 0; + struct nest *nst = reinterpret_cast(equeue_alloc(&q, sizeof(struct nest))); + TEST_ASSERT_NOT_NULL(nst); + nst->q = &q; + nst->cb = simple_func; + nst->data = &touched; + + int id = equeue_post(&q, nest_func, nst); + TEST_ASSERT_NOT_EQUAL(0, id); + + equeue_dispatch(&q, 5); + TEST_ASSERT_EQUAL_UINT8(0, touched); + + equeue_dispatch(&q, 1); + TEST_ASSERT_EQUAL_UINT8(1, touched); + + touched = 0; + nst = reinterpret_cast(equeue_alloc(&q, sizeof(struct nest))); + TEST_ASSERT_NOT_NULL(nst); + nst->q = &q; + nst->cb = simple_func; + nst->data = &touched; + + id = equeue_post(&q, nest_func, nst); + TEST_ASSERT_NOT_EQUAL(0, id); + + equeue_dispatch(&q, 20); + TEST_ASSERT_EQUAL_UINT8(1, touched); + + equeue_destroy(&q); +} + +/** Test that functions scheduled after slow function would execute according to the schedule if it is possible, if not they would execute right after sloth function. + * + * Given queue is initialized. + * When sloth function is being called before other functions. + * Then if it is possible all functions start according to predefined schedule correctly. + */ +static void test_equeue_sloth() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched1 = 0; + uint8_t touched2 = 0; + uint8_t touched3 = 0; + int id = equeue_call(&q, sloth_func, &touched1); + TEST_ASSERT_NOT_EQUAL(0, id); + + id = equeue_call_in(&q, 5, simple_func, &touched2); + TEST_ASSERT_NOT_EQUAL(0, id); + + id = equeue_call_in(&q, 15, simple_func, &touched3); + TEST_ASSERT_NOT_EQUAL(0, id); + + equeue_dispatch(&q, 20); + TEST_ASSERT_EQUAL_UINT8(1, touched1); + TEST_ASSERT_EQUAL_UINT8(1, touched2); + TEST_ASSERT_EQUAL_UINT8(1, touched3); + + equeue_destroy(&q); +} + +/** Test that equeue can be broken of dispatching from a different thread. + * + * Given queue is initialized. + * When equeue starts dispatching in one thread. + * Then it can be stopped from another thread via equeue_break. + */ +static void test_equeue_multithread() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched = 0; + equeue_call_every(&q, 1, simple_func, &touched); + + Thread t1(osPriorityNormal, TEST_THREAD_STACK_SIZE); + t1.start(callback(multithread_thread, &q)); + ThisThread::sleep_for(10); + equeue_break(&q); + err = t1.join(); + TEST_ASSERT_EQUAL_INT(0, err); + + TEST_ASSERT(touched > 1); + + equeue_destroy(&q); +} + +/** Test that variable referred via equeue_background shows value in ms to the next event. + * + * Given queue is initialized. + * When variable is referred via equeue_background. + * Then it depicts the time in ms to the next event. + */ +static void test_equeue_background() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + int id = equeue_call_in(&q, 20, pass_func, 0); + TEST_ASSERT_NOT_EQUAL(0, id); + + int ms; + equeue_background(&q, background_func, &ms); + TEST_ASSERT_EQUAL_INT(20, ms); + + id = equeue_call_in(&q, 10, pass_func, 0); + TEST_ASSERT_NOT_EQUAL(0, id); + TEST_ASSERT_EQUAL_INT(10, ms); + + id = equeue_call(&q, pass_func, 0); + TEST_ASSERT_NOT_EQUAL(0, id); + TEST_ASSERT_EQUAL_INT(0, ms); + + equeue_dispatch(&q, 0); + TEST_ASSERT_EQUAL_INT(10, ms); + + equeue_destroy(&q); + TEST_ASSERT_EQUAL_INT(-1, ms); +} + +/** Test that when chaining two equeues, events from both equeues execute by calling dispatch only on target. + * + * Given queue is initialized. + * When target chained equeue is dispatched. + * Then events from both chained equeues are executed. + */ +static void test_equeue_chain() +{ + equeue_t q1; + int err = equeue_create(&q1, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + equeue_t q2; + err = equeue_create(&q2, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + equeue_chain(&q2, &q1); + + uint8_t touched1 = 0; + uint8_t touched2 = 0; + + int id1 = equeue_call_in(&q1, 20, simple_func, &touched1); + TEST_ASSERT_NOT_EQUAL(0, id1); + int id2 = equeue_call_in(&q2, 20, simple_func, &touched2); + TEST_ASSERT_NOT_EQUAL(0, id2); + + id1 = equeue_call(&q1, simple_func, &touched1); + TEST_ASSERT_NOT_EQUAL(0, id1); + id2 = equeue_call(&q2, simple_func, &touched2); + TEST_ASSERT_NOT_EQUAL(0, id2); + + id1 = equeue_call_in(&q1, 5, simple_func, &touched1); + TEST_ASSERT_NOT_EQUAL(0, id1); + id2 = equeue_call_in(&q2, 5, simple_func, &touched2); + TEST_ASSERT_NOT_EQUAL(0, id2); + + equeue_cancel(&q1, id1); + equeue_cancel(&q2, id2); + + id1 = equeue_call_in(&q1, 10, simple_func, &touched1); + TEST_ASSERT_NOT_EQUAL(0, id1); + id2 = equeue_call_in(&q2, 10, simple_func, &touched2); + TEST_ASSERT_NOT_EQUAL(0, id2); + + equeue_dispatch(&q1, 30); + + TEST_ASSERT_EQUAL_UINT8(3, touched1); + TEST_ASSERT_EQUAL_UINT8(3, touched2); + + equeue_destroy(&q1); + equeue_destroy(&q2); +} + +/** Test that unchaining equeues makes them work on their own. + * + * Given queue is initialized. + * When equeue is unchained. + * Then it can be only dispatched by calling with reference to it. + */ +static void test_equeue_unchain() +{ + equeue_t q1; + int err = equeue_create(&q1, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + equeue_t q2; + err = equeue_create(&q2, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + equeue_chain(&q2, &q1); + + uint8_t touched1 = 0; + uint8_t touched2 = 0; + int id1 = equeue_call(&q1, simple_func, &touched1); + TEST_ASSERT_NOT_EQUAL(0, id1); + int id2 = equeue_call(&q2, simple_func, &touched2); + TEST_ASSERT_NOT_EQUAL(0, id2); + + equeue_dispatch(&q1, 0); + TEST_ASSERT_EQUAL_UINT8(1, touched1); + TEST_ASSERT_EQUAL_UINT8(1, touched2); + + equeue_chain(&q2, 0); + + touched1 = 0; + touched2 = 0; + + id1 = equeue_call(&q1, simple_func, &touched1); + TEST_ASSERT_NOT_EQUAL(0, id1); + id2 = equeue_call(&q2, simple_func, &touched2); + TEST_ASSERT_NOT_EQUAL(0, id2); + + equeue_dispatch(&q1, 0); + TEST_ASSERT_EQUAL_UINT8(1, touched1); + TEST_ASSERT_EQUAL_UINT8(0, touched2); + + equeue_dispatch(&q2, 0); + TEST_ASSERT_EQUAL_UINT8(1, touched1); + TEST_ASSERT_EQUAL_UINT8(1, touched2); + + equeue_chain(&q1, &q2); + + touched1 = 0; + touched2 = 0; + + id1 = equeue_call(&q1, simple_func, &touched1); + TEST_ASSERT_NOT_EQUAL(0, id1); + id2 = equeue_call(&q2, simple_func, &touched2); + TEST_ASSERT_NOT_EQUAL(0, id2); + + equeue_dispatch(&q2, 0); + TEST_ASSERT_EQUAL_UINT8(1, touched1); + TEST_ASSERT_EQUAL_UINT8(1, touched2); + + equeue_destroy(&q1); + equeue_destroy(&q2); +} + +// Barrage tests + +/** Test that equeue keeps good time at starting events. + * + * Given queue is initialized. + * When equeue is being dispatched. + * Then events happen according to the schedule with an error within a specified range. + */ +template +static void test_equeue_simple_barrage() +{ + equeue_t q; + int err = equeue_create(&q, N * (EQUEUE_EVENT_SIZE + sizeof(struct timing))); + TEST_ASSERT_EQUAL_INT(0, err); + + for (int i = 0; i < N; i++) { + struct timing *timing = reinterpret_cast(equeue_alloc(&q, sizeof(struct timing))); + TEST_ASSERT_NOT_NULL(timing); + + timing->tick = equeue_tick(); + timing->delay = (i + 1) * 100; + equeue_event_delay(timing, timing->delay); + equeue_event_period(timing, timing->delay); + + int id = equeue_post(&q, timing_func, timing); + TEST_ASSERT_NOT_EQUAL(0, id); + } + + equeue_dispatch(&q, N * 100); + + equeue_destroy(&q); +} + +/** Test that equeue keeps good time at starting events when events are added via functions already placed in equeue. + * + * Given queue is initialized. + * When equeue is being dispatched and new events are added via already placed in equeue. + * Then events happen according to the schedule with an error within a specified range. + */ +template +static void test_equeue_fragmenting_barrage() +{ + equeue_t q; + int err = equeue_create(&q, + 2 * N * (EQUEUE_EVENT_SIZE + sizeof(struct fragment) + N * sizeof(int))); + TEST_ASSERT_EQUAL_INT(0, err); + + for (int i = 0; i < N; i++) { + size_t size = sizeof(struct fragment) + i * sizeof(int); + struct fragment *fragment = reinterpret_cast(equeue_alloc(&q, size)); + TEST_ASSERT_NOT_NULL(fragment); + + fragment->q = &q; + fragment->size = size; + fragment->timing.tick = equeue_tick(); + fragment->timing.delay = (i + 1) * 100; + equeue_event_delay(fragment, fragment->timing.delay); + + int id = equeue_post(&q, fragment_func, fragment); + TEST_ASSERT_NOT_EQUAL(0, id); + } + + equeue_dispatch(&q, N * 100); + + equeue_destroy(&q); +} + +/** Test that equeue keeps good time at starting events even if it is working on different thread. + * + * Given queue is initialized. + * When equeue is being dispatched on different thread. + * Then events happen according to the schedule with an error within a specified range. + */ +template +static void test_equeue_multithreaded_barrage() +{ + equeue_t q; + int err = equeue_create(&q, N * (EQUEUE_EVENT_SIZE + sizeof(struct timing))); + TEST_ASSERT_EQUAL_INT(0, err); + + struct ethread t; + t.q = &q; + t.ms = N * 100; + + Thread t1(osPriorityNormal, TEST_THREAD_STACK_SIZE); + + t1.start(callback(ethread_dispatch, &t)); + + for (int i = 0; i < N; i++) { + struct timing *timing = reinterpret_cast(equeue_alloc(&q, sizeof(struct timing))); + TEST_ASSERT_NOT_NULL(timing); + + timing->tick = equeue_tick(); + timing->delay = (i + 1) * 100; + equeue_event_delay(timing, timing->delay); + equeue_event_period(timing, timing->delay); + + int id = equeue_post(&q, timing_func, timing); + TEST_ASSERT_NOT_EQUAL(0, id); + } + + err = t1.join(); + TEST_ASSERT_EQUAL_INT(0, err); + + equeue_destroy(&q); +} + +/** Test that break request flag is cleared when equeue stops dispatching timeouts. + * + * Given queue is initialized. + * When equeue break request flag is called but equeue stops dispatching because of timeout. + * Then next equeue dispatch is not stopped. + */ +static void test_equeue_break_request_cleared_on_timeout() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL(0, err); + + struct count_and_queue pq; + pq.p = 0; + pq.q = &q; + + int id = equeue_call_in(&q, 1, simple_breaker, &pq); + + equeue_dispatch(&q, 10); + + TEST_ASSERT_EQUAL_INT(1, pq.p); + + equeue_cancel(&q, id); + + uint8_t touched = 0; + equeue_call_every(&q, 10, simple_func, &touched); + + equeue_dispatch(&q, 55); + TEST_ASSERT_EQUAL_UINT8(5, touched); + + equeue_destroy(&q); +} + +/** Test that siblings events don't have next pointers. + * + * Given queue is initialized. + * When events are scheduled on the same time. + * Then they are connected via sibling pointers and siblings have their next pointer pointing to NULL. + */ +static void test_equeue_sibling() +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + TEST_ASSERT_EQUAL(0, err); + + int id0 = equeue_call_in(&q, 1, pass_func, 0); + int id1 = equeue_call_in(&q, 1, pass_func, 0); + int id2 = equeue_call_in(&q, 1, pass_func, 0); + + struct equeue_event *e = q.queue; + + for (; e; e = e->next) { + for (struct equeue_event *s = e->sibling; s; s = s->sibling) { + TEST_ASSERT_NULL(s->next); + } + } + equeue_cancel(&q, id0); + equeue_cancel(&q, id1); + equeue_cancel(&q, id2); + equeue_destroy(&q); +} + + +Case cases[] = { + Case("simple call test", test_equeue_simple_call), + Case("simple call in test", test_equeue_simple_call_in), + Case("simple call every test", test_equeue_simple_call_every), + Case("simple post test", test_equeue_simple_post), + + Case("destructor test", test_equeue_destructor), + Case("allocation failure test", test_equeue_allocation_failure), + Case("cancel test", test_equeue_cancel<20>), + Case("cancel inflight test", test_equeue_cancel_inflight), + Case("cancel unnecessarily test", test_equeue_cancel_unnecessarily), + Case("loop protect test", test_equeue_loop_protect), + Case("break test", test_equeue_break), + Case("break no windup test", test_equeue_break_no_windup), + Case("period test", test_equeue_period), + Case("nested test", test_equeue_nested), + Case("sloth test", test_equeue_sloth), + + Case("multithread test", test_equeue_multithread), + + Case("background test", test_equeue_background), + Case("chain test", test_equeue_chain), + Case("unchain test", test_equeue_unchain), + + Case("simple barrage test", test_equeue_simple_barrage<20>), + Case("fragmenting barrage test", test_equeue_fragmenting_barrage<10>), + Case("multithreaded barrage test", test_equeue_multithreaded_barrage<10>), + Case("break request cleared on timeout test", test_equeue_break_request_cleared_on_timeout), + Case("sibling test", test_equeue_sibling) + +}; + +utest::v1::status_t greentea_test_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(40, "default_auto"); + return greentea_test_setup_handler(number_of_cases); +} + +Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); + +int main() +{ + Harness::run(specification); +} + +#endif // MBED_EXTENDED_TESTS diff --git a/UNITTESTS/events/equeue/test_equeue.cpp b/UNITTESTS/events/equeue/test_equeue.cpp new file mode 100644 index 00000000000..dbc3ffa73f5 --- /dev/null +++ b/UNITTESTS/events/equeue/test_equeue.cpp @@ -0,0 +1,997 @@ +/* + * Copyright (c) 2019, Arm Limited and affiliates. + * 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. + */ +#include "gtest/gtest.h" +#include "equeue.h" +#include "mbed.h" +#include +#include + +#define EVENTS_EVENT_SIZE (EQUEUE_EVENT_SIZE - 2*sizeof(void*) + sizeof(mbed::Callback)) +#define TEST_EQUEUE_SIZE 2048 +#define TEST_THREAD_STACK_SIZE 512 +#define DISPATCH_INFINITE -1 +#define ITERATION_TIMES 10 + +extern unsigned int equeue_global_time; + +class TestEqueue : public testing::Test { + virtual void SetUp() + { + } + + virtual void TearDown() + { + } +}; + +// Test functions +static void pass_func(void *eh) +{ +} + +static void simple_func(void *p) +{ + (*(reinterpret_cast(p)))++; +} + +static void sloth_func(void *p) +{ + // adding to equeue_global_time becouse this simulates that this function takes some time + equeue_global_time += 10; + (*(reinterpret_cast(p)))++; +} + +struct indirect { + uint8_t *touched; + uint8_t buffer[7]; +}; + +static void indirect_func(void *p) +{ + struct indirect *i = reinterpret_cast(p); + (*i->touched)++; +} + +struct timing { + unsigned tick; + unsigned delay; +}; + +static void timing_func(void *p) +{ + struct timing *timing = reinterpret_cast(p); + unsigned tick = equeue_tick(); + + unsigned t1 = timing->delay; + unsigned t2 = tick - timing->tick; + EXPECT_TRUE(t2 - 10 < t1 < t2 + 10); + + timing->tick = tick; +} + +struct fragment { + equeue_t *q; + size_t size; + struct timing timing; +}; + +static void fragment_func(void *p) +{ + struct fragment *fragment = reinterpret_cast(p); + timing_func(&fragment->timing); + + struct fragment *nfragment = reinterpret_cast(equeue_alloc(fragment->q, fragment->size)); + ASSERT_TRUE(nfragment != NULL); + + *nfragment = *fragment; + equeue_event_delay(nfragment, fragment->timing.delay); + + int id = equeue_post(nfragment->q, fragment_func, nfragment); + ASSERT_NE(0, id); +} + +struct cancel { + equeue_t *q; + int id; +}; + +static void cancel_func(void *p) +{ + struct cancel *ccel = reinterpret_cast(p); + equeue_cancel(ccel->q, ccel->id); +} + +struct nest { + equeue_t *q; + void (*cb)(void *); + void *data; +}; + +static void nest_func(void *p) +{ + struct nest *nst = reinterpret_cast(p); + equeue_call(nst->q, nst->cb, nst->data); + // adding to equeue_global_time becouse this simulates that this function takes some time + equeue_global_time += 10; +} + +static void *multithread_thread(void *p) +{ + equeue_t *q = reinterpret_cast(p); + equeue_dispatch(q, DISPATCH_INFINITE); + return 0; +} + +static void multithread_func(void *p) +{ + if ((*(reinterpret_cast(p))) < 200) { + (*(reinterpret_cast(p)))++; + } +} + +static void background_func(void *p, int ms) +{ + *(reinterpret_cast(p)) = ms; +} + +struct ethread { + pthread_t thread; + equeue_t *q; + int ms; +}; + +static void *ethread_dispatch(void *p) +{ + struct ethread *t = reinterpret_cast(p); + equeue_dispatch(t->q, t->ms); + return 0; +} + +struct count_and_queue { + int p; + equeue_t *q; +}; + +static void simple_breaker(void *p) +{ + struct count_and_queue *caq = reinterpret_cast(p); + equeue_break(caq->q); + // adding to equeue_global_time becouse this simulates that this function takes some time + equeue_global_time += 10; + caq->p++; +} + +// Simple call tests + +/** Test that equeue executes function passed by equeue_call. + * + * Given queue is initialized. + * When the event is scheduled and after that equeue_dispatch is called. + * Then function passed by equeue_call is executed properly. + */ +TEST_F(TestEqueue, test_equeue_simple_call) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched = 0; + equeue_call(&q, simple_func, &touched); + equeue_dispatch(&q, 0); + EXPECT_EQ(1, touched); + + touched = 0; + equeue_dispatch(&q, 10); + EXPECT_EQ(0, touched); + + equeue_destroy(&q); +} + +/** Test that equeue executes function passed by equeue_call_in. + * + * Given queue is initialized. + * When the event is scheduled and after that equeue_dispatch is called. + * Then function passed by equeue_call_in is executed properly. + */ +TEST_F(TestEqueue, test_equeue_simple_call_in) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched = 0; + int id = equeue_call_in(&q, 10, simple_func, &touched); + ASSERT_NE(0, id); + + equeue_dispatch(&q, 15); + EXPECT_EQ(1, touched); + + touched = 0; + equeue_dispatch(&q, 10); + EXPECT_EQ(0, touched); + + equeue_destroy(&q); +} + +/** Test that equeue executes function passed by equeue_call_every. + * + * Given queue is initialized. + * When the event is scheduled and after that equeue_dispatch is called. + * Then function passed by equeue_call_every is executed properly. + */ + +TEST_F(TestEqueue, test_equeue_simple_call_every) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched = 0; + int id = equeue_call_every(&q, 10, simple_func, &touched); + ASSERT_NE(0, id); + + equeue_dispatch(&q, 15); + EXPECT_EQ(1, touched); + + equeue_destroy(&q); +} + +/** Test that equeue executes function passed by equeue_post. + * + * Given queue is initialized. + * When the event is posted and after that equeue_dispatch is called. + * Then function passed by equeue_post is executed properly. + */ +TEST_F(TestEqueue, test_equeue_simple_post) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched = 0; + struct indirect *i = reinterpret_cast(equeue_alloc(&q, sizeof(struct indirect))); + ASSERT_TRUE(i != NULL); + + i->touched = &touched; + int id = equeue_post(&q, indirect_func, i); + ASSERT_NE(0, id); + + equeue_dispatch(&q, 0); + EXPECT_EQ(1, *i->touched); + + equeue_destroy(&q); +} + +// Misc tests + +/** Test that equeue executes events attached to its events destructors by equeue_event_dtor. + * + * Given queue is initialized. + * When equeue events are being destroyed by equeue_dispatch, equeue_cancel, or equeue_destroy. + * Then functions attached to equeue events destructors are executed properly. + */ +TEST_F(TestEqueue, test_equeue_destructor) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched = 0; + struct indirect *e; + int ids[3]; + + for (int i = 0; i < 3; i++) { + e = reinterpret_cast(equeue_alloc(&q, sizeof(struct indirect))); + ASSERT_TRUE(e != NULL); + + e->touched = &touched; + equeue_event_dtor(e, indirect_func); + int id = equeue_post(&q, pass_func, e); + ASSERT_NE(0, id); + } + + equeue_dispatch(&q, 0); + EXPECT_EQ(3, touched); + + touched = 0; + for (int i = 0; i < 3; i++) { + e = reinterpret_cast(equeue_alloc(&q, sizeof(struct indirect))); + ASSERT_TRUE(e != NULL); + + e->touched = &touched; + equeue_event_dtor(e, indirect_func); + ids[i] = equeue_post(&q, pass_func, e); + ASSERT_NE(0, ids[i]); + } + + for (int i = 0; i < 3; i++) { + equeue_cancel(&q, ids[i]); + } + EXPECT_EQ(3, touched); + + equeue_dispatch(&q, 0); + + touched = 0; + for (int i = 0; i < 3; i++) { + e = reinterpret_cast(equeue_alloc(&q, sizeof(struct indirect))); + ASSERT_TRUE(e); + + e->touched = &touched; + equeue_event_dtor(e, indirect_func); + int id = equeue_post(&q, pass_func, e); + ASSERT_NE(0, id); + } + + equeue_destroy(&q); + EXPECT_EQ(3, touched); +} + +/** Test that equeue_alloc returns 0 when equeue can not be allocated. + * + * Given queue is initialized. + * When equeue_alloc is called and equeue can not be allocated + * Then function equeue_alloc returns NULL. + */ +TEST_F(TestEqueue, test_equeue_allocation_failure) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + void *p = equeue_alloc(&q, 2 * TEST_EQUEUE_SIZE); + EXPECT_TRUE(p == NULL); + + for (int i = 0; i < 100; i++) { + p = equeue_alloc(&q, 0); + } + EXPECT_TRUE(p == NULL); + + equeue_destroy(&q); +} + +/** Test that equeue does not execute evenets that has been canceled. + * + * Given queue is initialized. + * When events are canceled by equeue_cancel. + * Then they are not executed by calling equeue_dispatch. + */ +TEST_F(TestEqueue, test_equeue_cancel) +{ + equeue_t q; + int err = equeue_create(&q, (2 * ITERATION_TIMES * EVENTS_EVENT_SIZE)); + ASSERT_EQ(0, err); + + uint8_t touched = 0; + int ids[2 * ITERATION_TIMES]; + + for (int i = 0; i < 2 * ITERATION_TIMES; i++) { + ids[i] = equeue_call(&q, simple_func, &touched); + ASSERT_NE(0, ids[i]); + } + + for (int i = 2 * ITERATION_TIMES - 1; i >= 0; i--) { + equeue_cancel(&q, ids[i]); + } + + equeue_dispatch(&q, 0); + EXPECT_EQ(0, touched); + + equeue_destroy(&q); +} + +/** Test that events can be cancelled by function executed by equeue_dispatch. + * + * Given queue is initialized. + * When event is cancelled by another event while dispatching. + * Then event that was cancelled is not being executed. + */ +TEST_F(TestEqueue, test_equeue_cancel_inflight) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched = 0; + + int id = equeue_call(&q, simple_func, &touched); + equeue_cancel(&q, id); + + equeue_dispatch(&q, 0); + EXPECT_EQ(0, touched); + + id = equeue_call(&q, simple_func, &touched); + equeue_cancel(&q, id); + + equeue_dispatch(&q, 0); + EXPECT_EQ(0, touched); + + struct cancel *ccel = reinterpret_cast(equeue_alloc(&q, sizeof(struct cancel))); + ASSERT_TRUE(ccel != NULL); + ccel->q = &q; + ccel->id = 0; + + id = equeue_post(&q, cancel_func, ccel); + ASSERT_NE(0, id); + + ccel->id = equeue_call(&q, simple_func, &touched); + + equeue_dispatch(&q, 0); + EXPECT_EQ(0, touched); + + equeue_destroy(&q); +} + +/** Test that unnecessary canceling events would not affect executing other events. + * + * Given queue is initialized. + * When event is unnecessary canceled by equeue_cancel. + * Then other events are properly executed after calling equeue_dispatch. + */ +TEST_F(TestEqueue, test_equeue_cancel_unnecessarily) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + int id = equeue_call(&q, pass_func, 0); + for (int i = 0; i < 5; i++) { + equeue_cancel(&q, id); + } + + id = equeue_call(&q, pass_func, 0); + equeue_dispatch(&q, 0); + for (int i = 0; i < 5; i++) { + equeue_cancel(&q, id); + } + + uint8_t touched = 0; + equeue_call(&q, simple_func, &touched); + for (int i = 0; i < 5; i++) { + equeue_cancel(&q, id); + } + + equeue_dispatch(&q, 0); + EXPECT_EQ(1, touched); + + equeue_destroy(&q); +} + +/** Test that dispatching events that have 0 ms period time would not end up in infinite loop. + * + * Given queue is initialized. + * When events have 0 ms period time. + * Then dispatching would not end up in infinite loop. + */ +TEST_F(TestEqueue, test_equeue_loop_protect) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched1 = 0; + equeue_call_every(&q, 0, simple_func, &touched1); + + equeue_dispatch(&q, 0); + EXPECT_EQ(1, touched1); + + touched1 = 0; + uint8_t touched2 = 0; + equeue_call_every(&q, 1, simple_func, &touched2); + + equeue_dispatch(&q, 0); + EXPECT_EQ(1, touched1); + EXPECT_EQ(0, touched2); + + equeue_destroy(&q); +} + +/** Test that equeue_break breaks event queue out of dispatching. + * + * Given queue is initialized. + * When equeue_break is called. + * Then event queue will stop dispatching after finisching current dispatching cycle. + */ +TEST_F(TestEqueue, test_equeue_break) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched1 = 0; + equeue_call_every(&q, 0, simple_func, &touched1); + + uint8_t touched2 = 0; + equeue_call_every(&q, 5, simple_func, &touched2); + + equeue_break(&q); + equeue_dispatch(&q, DISPATCH_INFINITE); + EXPECT_EQ(1, touched1); + EXPECT_EQ(0, touched2); + + equeue_destroy(&q); +} + +/** Test that equeue_break function breaks equeue dispatching only once. + * + * Given queue is initialized. + * When equeue_break is called several times. + * Then equeue is stopped only once. + */ +TEST_F(TestEqueue, test_equeue_break_no_windup) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched = 0; + equeue_call_every(&q, 0, simple_func, &touched); + + equeue_break(&q); + equeue_break(&q); + equeue_dispatch(&q, DISPATCH_INFINITE); + EXPECT_EQ(1, touched); + + touched = 0; + equeue_dispatch(&q, 55); + EXPECT_TRUE(touched > 1); + + equeue_destroy(&q); +} + +/** Test that function passed by equeue_call_every is being executed periodically. + * + * Given queue is initialized. + * When function is passed by equeue_call_every with specified period. + * Then event is executed (dispatch time/period) times. + */ + +TEST_F(TestEqueue, test_equeue_period) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched = 0; + equeue_call_every(&q, 10, simple_func, &touched); + + equeue_dispatch(&q, 55); + EXPECT_EQ(5, touched); + + equeue_destroy(&q); +} + +/** Test that function added to the equeue by other function which already is in equeue executes in the next dispatch, or after the end of execution of the "mother" event. + * + * Given queue is initialized. + * When nested function is added to enqueue. + * Then it is executed in the next dispatch, or after execution of "mother" function. + */ +TEST_F(TestEqueue, test_equeue_nested) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched = 0; + struct nest *nst = reinterpret_cast(equeue_alloc(&q, sizeof(struct nest))); + ASSERT_TRUE(nst != NULL); + nst->q = &q; + nst->cb = simple_func; + nst->data = &touched; + + int id = equeue_post(&q, nest_func, nst); + ASSERT_NE(0, id); + + equeue_dispatch(&q, 5); + EXPECT_EQ(0, touched); + + equeue_dispatch(&q, 1); + EXPECT_EQ(1, touched); + + touched = 0; + nst = reinterpret_cast(equeue_alloc(&q, sizeof(struct nest))); + ASSERT_TRUE(nst != NULL); + nst->q = &q; + nst->cb = simple_func; + nst->data = &touched; + + id = equeue_post(&q, nest_func, nst); + ASSERT_NE(0, id); + + equeue_dispatch(&q, 20); + EXPECT_EQ(1, touched); + + equeue_destroy(&q); +} + +/** Test that functions scheduled after slow function would execute according to the schedule if it is possible, if not they would execute right after sloth function. + * + * Given queue is initialized. + * When sloth function is being called before other functions. + * Then if it is possible all functions start according to predefined schedule correctly. + */ +TEST_F(TestEqueue, test_equeue_sloth) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched1 = 0; + uint8_t touched2 = 0; + uint8_t touched3 = 0; + int id = equeue_call(&q, sloth_func, &touched1); + ASSERT_NE(0, id); + + id = equeue_call_in(&q, 5, simple_func, &touched2); + ASSERT_NE(0, id); + + id = equeue_call_in(&q, 15, simple_func, &touched3); + ASSERT_NE(0, id); + + equeue_dispatch(&q, 20); + EXPECT_EQ(1, touched1); + EXPECT_EQ(1, touched2); + EXPECT_EQ(1, touched3); + + equeue_destroy(&q); +} + +/** Test that equeue can be broken of dispatching from a different thread. + * + * Given queue is initialized. + * When equeue starts dispatching in one thread. + * Then it can be stopped from another thread via equeue_break. + */ +TEST_F(TestEqueue, test_equeue_multithread) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched = 0; + equeue_call_every(&q, 1, multithread_func, &touched); + + pthread_t thread; + err = pthread_create(&thread, 0, multithread_thread, &q); + ASSERT_EQ(0, err); + + usleep(10000); + equeue_break(&q); + err = pthread_join(thread, 0); + ASSERT_EQ(0, err); + + EXPECT_TRUE(touched > 1); + + equeue_destroy(&q); +} + +/** Test that variable referred via equeue_background shows value in ms to the next event. + * + * Given queue is initialized. + * When variable is referred via equeue_background. + * Then it depicts the time in ms to the next event. + */ +TEST_F(TestEqueue, test_equeue_background) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + int id = equeue_call_in(&q, 20, pass_func, 0); + ASSERT_NE(0, id); + + int ms; + equeue_background(&q, background_func, &ms); + EXPECT_EQ(20, ms); + + id = equeue_call_in(&q, 10, pass_func, 0); + ASSERT_NE(0, id); + EXPECT_EQ(10, ms); + + id = equeue_call(&q, pass_func, 0); + ASSERT_NE(0, id); + EXPECT_EQ(0, ms); + + equeue_dispatch(&q, 0); + EXPECT_EQ(10, ms); + + equeue_destroy(&q); + EXPECT_EQ(-1, ms); +} + +/** Test that when chaining two equeues, events from both equeues execute by calling dispatch only on target. + * + * Given queue is initialized. + * When target chained equeue is dispatched. + * Then events from both chained equeues are executed. + */ +TEST_F(TestEqueue, test_equeue_chain) +{ + equeue_t q1; + int err = equeue_create(&q1, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + equeue_t q2; + err = equeue_create(&q2, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + equeue_chain(&q2, &q1); + + uint8_t touched1 = 0; + uint8_t touched2 = 0; + + int id1 = equeue_call_in(&q1, 20, simple_func, &touched1); + ASSERT_NE(0, id1); + int id2 = equeue_call_in(&q2, 20, simple_func, &touched2); + ASSERT_NE(0, id2); + + id1 = equeue_call(&q1, simple_func, &touched1); + ASSERT_NE(0, id1); + id2 = equeue_call(&q2, simple_func, &touched2); + ASSERT_NE(0, id2); + + id1 = equeue_call_in(&q1, 5, simple_func, &touched1); + ASSERT_NE(0, id1); + id2 = equeue_call_in(&q2, 5, simple_func, &touched2); + ASSERT_NE(0, id2); + + equeue_cancel(&q1, id1); + equeue_cancel(&q2, id2); + + id1 = equeue_call_in(&q1, 10, simple_func, &touched1); + ASSERT_NE(0, id1); + id2 = equeue_call_in(&q2, 10, simple_func, &touched2); + ASSERT_NE(0, id2); + + equeue_dispatch(&q1, 30); + + EXPECT_EQ(3, touched1); + EXPECT_EQ(3, touched2); + + equeue_destroy(&q1); + equeue_destroy(&q2); +} + +/** Test that unchaining equeues makes them work on their own. + * + * Given queue is initialized. + * When equeue is unchained. + * Then it can be only dispatched by calling with reference to it. + */ +TEST_F(TestEqueue, test_equeue_unchain) +{ + equeue_t q1; + int err = equeue_create(&q1, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + equeue_t q2; + err = equeue_create(&q2, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + equeue_chain(&q2, &q1); + + uint8_t touched1 = 0; + uint8_t touched2 = 0; + int id1 = equeue_call(&q1, simple_func, &touched1); + ASSERT_NE(0, id1); + int id2 = equeue_call(&q2, simple_func, &touched2); + ASSERT_NE(0, id2); + + equeue_dispatch(&q1, 0); + EXPECT_EQ(1, touched1); + EXPECT_EQ(1, touched2); + + equeue_chain(&q2, 0); + + touched1 = 0; + touched2 = 0; + + id1 = equeue_call(&q1, simple_func, &touched1); + ASSERT_NE(0, id1); + id2 = equeue_call(&q2, simple_func, &touched2); + ASSERT_NE(0, id2); + + equeue_dispatch(&q1, 0); + EXPECT_EQ(1, touched1); + EXPECT_EQ(0, touched2); + + equeue_dispatch(&q2, 0); + EXPECT_EQ(1, touched1); + EXPECT_EQ(1, touched2); + + equeue_chain(&q1, &q2); + + touched1 = 0; + touched2 = 0; + + id1 = equeue_call(&q1, simple_func, &touched1); + ASSERT_NE(0, id1); + id2 = equeue_call(&q2, simple_func, &touched2); + ASSERT_NE(0, id2); + + equeue_dispatch(&q2, 0); + EXPECT_EQ(1, touched1); + EXPECT_EQ(1, touched2); + + equeue_destroy(&q1); + equeue_destroy(&q2); +} + +// Barrage tests + +/** Test that equeue keeps good time at starting events. + * + * Given queue is initialized. + * When equeue is being dispatched. + * Then events happen according to the schedule with an error within a specified range. + */ +TEST_F(TestEqueue, test_equeue_simple_barrage) +{ + equeue_t q; + int err = equeue_create(&q, 2 * ITERATION_TIMES * (EQUEUE_EVENT_SIZE + sizeof(struct timing))); + ASSERT_EQ(0, err); + + for (int i = 0; i < 2 * ITERATION_TIMES; i++) { + struct timing *timing = reinterpret_cast(equeue_alloc(&q, sizeof(struct timing))); + ASSERT_TRUE(timing != NULL); + + timing->tick = equeue_tick(); + timing->delay = (i + 1) * 100; + equeue_event_delay(timing, timing->delay); + equeue_event_period(timing, timing->delay); + + int id = equeue_post(&q, timing_func, timing); + ASSERT_NE(0, id); + } + + equeue_dispatch(&q, 2 * ITERATION_TIMES * 100); + + equeue_destroy(&q); +} + +/** Test that equeue keeps good time at starting events when events are added via functions already placed in equeue. + * + * Given queue is initialized. + * When equeue is being dispatched and new events are added via already placed in equeue. + * Then events happen according to the schedule with an error within a specified range. + */ +TEST_F(TestEqueue, test_equeue_fragmenting_barrage) +{ + equeue_t q; + int err = equeue_create(&q, + 2 * ITERATION_TIMES * (EQUEUE_EVENT_SIZE + sizeof(struct fragment) + ITERATION_TIMES * sizeof(int))); + ASSERT_EQ(0, err); + + for (int i = 0; i < ITERATION_TIMES; i++) { + size_t size = sizeof(struct fragment) + i * sizeof(int); + struct fragment *fragment = reinterpret_cast(equeue_alloc(&q, size)); + ASSERT_TRUE(fragment != NULL); + + fragment->q = &q; + fragment->size = size; + fragment->timing.tick = equeue_tick(); + fragment->timing.delay = (i + 1) * 100; + equeue_event_delay(fragment, fragment->timing.delay); + + int id = equeue_post(&q, fragment_func, fragment); + ASSERT_NE(0, id); + } + + equeue_dispatch(&q, ITERATION_TIMES * 100); + + equeue_destroy(&q); +} + +/** Test that equeue keeps good time at starting events even if it is working on different thread. + * + * Given queue is initialized. + * When equeue is being dispatched on different thread. + * Then events happen according to the schedule with an error within a specified range. + */ +TEST_F(TestEqueue, test_equeue_multithreaded_barrage) +{ + equeue_t q; + int err = equeue_create(&q, ITERATION_TIMES * (EQUEUE_EVENT_SIZE + sizeof(struct timing))); + ASSERT_EQ(0, err); + + struct ethread t; + t.q = &q; + t.ms = ITERATION_TIMES * 100; + + err = pthread_create(&t.thread, 0, ethread_dispatch, &t); + ASSERT_EQ(0, err); + + for (int i = 0; i < ITERATION_TIMES; i++) { + struct timing *timing = reinterpret_cast(equeue_alloc(&q, sizeof(struct timing))); + ASSERT_TRUE(timing != NULL); + + timing->tick = equeue_tick(); + timing->delay = (i + 1) * 100; + equeue_event_delay(timing, timing->delay); + equeue_event_period(timing, timing->delay); + + int id = equeue_post(&q, timing_func, timing); + ASSERT_NE(0, id); + } + + err = pthread_join(t.thread, 0); + ASSERT_EQ(0, err); + + equeue_destroy(&q); +} + +/** Test that break request flag is cleared when equeue stops dispatching timeouts. + * + * Given queue is initialized. + * When equeue break request flag is called but equeue stops dispatching because of timeout. + * Then next equeue dispatch is not stopped. + */ +TEST_F(TestEqueue, test_equeue_break_request_cleared_on_timeout) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + struct count_and_queue pq; + pq.p = 0; + pq.q = &q; + + int id = equeue_call_in(&q, 1, simple_breaker, &pq); + + equeue_dispatch(&q, 10); + + EXPECT_EQ(1, pq.p); + + equeue_cancel(&q, id); + + uint8_t touched = 0; + equeue_call_every(&q, 10, simple_func, &touched); + + equeue_dispatch(&q, 55); + EXPECT_EQ(5, touched); + + equeue_destroy(&q); +} + +/** Test that siblings events don't have next pointers. + * + * Given queue is initialized. + * When events are scheduled on the same time. + * Then they are connected via sibling pointers and siblings have their next pointer pointing to NULL. + */ +TEST_F(TestEqueue, test_equeue_sibling) +{ + equeue_t q; + int err = equeue_create(&q, TEST_EQUEUE_SIZE); + ASSERT_EQ(0, err); + + int id0 = equeue_call_in(&q, 1, pass_func, 0); + int id1 = equeue_call_in(&q, 1, pass_func, 0); + int id2 = equeue_call_in(&q, 1, pass_func, 0); + + struct equeue_event *e = q.queue; + + for (; e; e = e->next) { + for (struct equeue_event *s = e->sibling; s; s = s->sibling) { + EXPECT_TRUE(s->next == NULL); + } + } + equeue_cancel(&q, id0); + equeue_cancel(&q, id1); + equeue_cancel(&q, id2); + equeue_destroy(&q); +} \ No newline at end of file diff --git a/UNITTESTS/events/equeue/unittest.cmake b/UNITTESTS/events/equeue/unittest.cmake new file mode 100644 index 00000000000..1b97b53b8c0 --- /dev/null +++ b/UNITTESTS/events/equeue/unittest.cmake @@ -0,0 +1,24 @@ + +#################### +# UNIT TESTS +#################### + +list(REMOVE_ITEM unittest-includes ${PROJECT_SOURCE_DIR}/target_h/events ${PROJECT_SOURCE_DIR}/target_h/events/equeue) + +set(unittest-includes ${unittest-includes} + ../events/source + ../events + ../events/internal +) + +set(unittest-sources + ../events/source/equeue.c +) + +set(unittest-test-sources + events/equeue/test_equeue.cpp + stubs/EqueuePosix_stub.c +) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread -DEQUEUE_PLATFORM_POSIX") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -DEQUEUE_PLATFORM_POSIX") diff --git a/UNITTESTS/stubs/EqueuePosix_stub.c b/UNITTESTS/stubs/EqueuePosix_stub.c new file mode 100644 index 00000000000..5c0d55d7cc8 --- /dev/null +++ b/UNITTESTS/stubs/EqueuePosix_stub.c @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019, Arm Limited and affiliates. + * 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. + */ + +#include "internal/equeue_platform.h" + +#if defined(EQUEUE_PLATFORM_POSIX) + +#include + +/* + * Using global variable as a simulation of passing time. Use of sleep functions may cause problems with thread preempting, which can lead to bad timings. + * This problem does not occur on targets. + * This variable is only increased in equeue_sema_wait function and in functions that simulate pass of time. + */ +unsigned int equeue_global_time = 0; + +// Tick operations +void equeue_tick_init(void) {} + +unsigned equeue_tick(void) +{ + return equeue_global_time; +} + + +// Mutex operations +int equeue_mutex_create(equeue_mutex_t *m) +{ + return pthread_mutex_init(m, 0); +} + +void equeue_mutex_destroy(equeue_mutex_t *m) +{ + pthread_mutex_destroy(m); +} + +void equeue_mutex_lock(equeue_mutex_t *m) +{ + pthread_mutex_lock(m); +} + +void equeue_mutex_unlock(equeue_mutex_t *m) +{ + pthread_mutex_unlock(m); +} + + +// Semaphore operations +int equeue_sema_create(equeue_sema_t *s) +{ + int err = pthread_mutex_init(&s->mutex, 0); + if (err) { + return err; + } + + err = pthread_cond_init(&s->cond, 0); + if (err) { + return err; + } + + s->signal = false; + return 0; +} + +void equeue_sema_destroy(equeue_sema_t *s) +{ + pthread_cond_destroy(&s->cond); + pthread_mutex_destroy(&s->mutex); +} + +void equeue_sema_signal(equeue_sema_t *s) +{ + pthread_mutex_lock(&s->mutex); + s->signal = true; + pthread_cond_signal(&s->cond); + pthread_mutex_unlock(&s->mutex); +} + +bool equeue_sema_wait(equeue_sema_t *s, int ms) +{ + pthread_mutex_lock(&s->mutex); + if (!s->signal) { + if (ms < 0) { + pthread_cond_wait(&s->cond, &s->mutex); + } else { + for (int i = 0; i < ms; i++) { + equeue_global_time++; + } + // If ms == 0 increase time so functions don't get stuck in infinite loops. + if (ms == 0) { + equeue_global_time++; + } + } + } + + bool signal = s->signal; + s->signal = false; + pthread_mutex_unlock(&s->mutex); + + return signal; +} + +#endif