Newer
Older
mbed-os / events / tests / TESTS / events / queue / main.cpp
@George Psimenos George Psimenos on 28 Jul 2020 14 KB Restructure events directory & move tests
/* mbed Microcontroller Library
 * Copyright (c) 2017-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.
 */
#include "mbed_events.h"
#include "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"

#if !DEVICE_USTICKER
#error [NOT_SUPPORTED] test not supported
#else

using namespace utest::v1;

// Assume that tolerance is 5% of measured time.
#define DELTA(ms) (ms / 20)

// TEST_EQUEUE_SIZE was reduced below 1024B to fit this test to devices with small RAM (RAM <= 16kB)
// additionally TEST_EQUEUE_SIZE was expressed in EVENTS_EVENT_SIZE to increase readability
// (for more details about EVENTS_EVENT_SIZE see EventQueue constructor)
#define TEST_EQUEUE_SIZE (18*EVENTS_EVENT_SIZE)

// By empirical, we take 80MHz CPU/2ms delay as base tolerance for time left test.
// For higher CPU frequency, tolerance is fixed to 2ms.
// For lower CPU frequency, tolerance is inversely proportional to CPU frequency.
// E.g.:
// 100MHz: 2ms
// 80MHz: 2ms
// 64MHz: 3ms
// 48MHz: 4ms
#define ALLOWED_TIME_LEFT_TOLERANCE_MS   ((SystemCoreClock >= 80000000) ? 2 : ((80000000 * 2 + SystemCoreClock - 1) / SystemCoreClock))

// flag for called
volatile bool touched = false;

// static functions
void func5(int a0, int a1, int a2, int a3, int a4)
{
    touched = true;
    TEST_ASSERT_EQUAL(a0 | a1 | a2 | a3 | a4, 0x1f);
}

void func4(int a0, int a1, int a2, int a3)
{
    touched = true;
    TEST_ASSERT_EQUAL(a0 | a1 | a2 | a3, 0xf);
}

void func3(int a0, int a1, int a2)
{
    touched = true;
    TEST_ASSERT_EQUAL(a0 | a1 | a2, 0x7);
}

void func2(int a0, int a1)
{
    touched = true;
    TEST_ASSERT_EQUAL(a0 | a1, 0x3);
}

void func1(int a0)
{
    touched = true;
    TEST_ASSERT_EQUAL(a0, 0x1);
}

void func0()
{
    touched = true;
}

#define SIMPLE_POSTS_TEST(i, ...)                           \
void simple_posts_test##i() {                               \
    EventQueue queue(TEST_EQUEUE_SIZE);                     \
                                                            \
    touched = false;                                        \
    queue.call(func##i,##__VA_ARGS__);                      \
    queue.dispatch(0);                                      \
    TEST_ASSERT(touched);                                   \
                                                            \
    touched = false;                                        \
    queue.call_in(1, func##i,##__VA_ARGS__);                \
    queue.dispatch(2);                                      \
    TEST_ASSERT(touched);                                   \
                                                            \
    touched = false;                                        \
    queue.call_every(1, func##i,##__VA_ARGS__);             \
    queue.dispatch(2);                                      \
    TEST_ASSERT(touched);                                   \
}

SIMPLE_POSTS_TEST(5, 0x01, 0x02, 0x04, 0x08, 0x010)
SIMPLE_POSTS_TEST(4, 0x01, 0x02, 0x04, 0x08)
SIMPLE_POSTS_TEST(3, 0x01, 0x02, 0x04)
SIMPLE_POSTS_TEST(2, 0x01, 0x02)
SIMPLE_POSTS_TEST(1, 0x01)
SIMPLE_POSTS_TEST(0)


void time_func(Timer *t, int ms)
{
    TEST_ASSERT_INT_WITHIN(DELTA(ms), ms, t->read_ms());
    t->reset();
}

template <int N>
void call_in_test()
{
    Timer tickers[N];

    EventQueue queue(TEST_EQUEUE_SIZE);

    for (int i = 0; i < N; i++) {
        tickers[i].start();
        queue.call_in((i + 1) * 100, time_func, &tickers[i], (i + 1) * 100);
    }

    queue.dispatch(N * 100);
}

template <int N>
void call_every_test()
{
    Timer tickers[N];

    EventQueue queue(TEST_EQUEUE_SIZE);

    for (int i = 0; i < N; i++) {
        tickers[i].start();
        queue.call_every((i + 1) * 100, time_func, &tickers[i], (i + 1) * 100);
    }

    queue.dispatch(N * 100);
}

void allocate_failure_test()
{
    EventQueue queue(TEST_EQUEUE_SIZE);
    int id;

    for (int i = 0; i < 100; i++) {
        id = queue.call((void (*)())0);
    }

    TEST_ASSERT(!id);
}

void no()
{
    TEST_ASSERT(false);
}

template <int N>
void cancel_test1()
{
    EventQueue queue(TEST_EQUEUE_SIZE);

    int ids[N];

    for (int i = 0; i < N; i++) {
        ids[i] = queue.call_in(1000, no);
    }

    for (int i = N - 1; i >= 0; i--) {
        queue.cancel(ids[i]);
    }

    queue.dispatch(0);
}


// Testing the dynamic arguments to the event class
unsigned counter = 0;

void count5(unsigned a0, unsigned a1, unsigned a2, unsigned a3, unsigned a5)
{
    counter += a0 + a1 + a2 + a3 + a5;
}

void count4(unsigned a0, unsigned a1, unsigned a2, unsigned a3)
{
    counter += a0 + a1 + a2 + a3;
}

void count3(unsigned a0, unsigned a1, unsigned a2)
{
    counter += a0 + a1 + a2;
}

void count2(unsigned a0, unsigned a1)
{
    counter += a0 + a1;
}

void count1(unsigned a0)
{
    counter += a0;
}

void count0()
{
    counter += 0;
}

void event_class_test()
{
    counter = 0;
    EventQueue queue(TEST_EQUEUE_SIZE);

    Event<void(int, int, int, int, int)> e5(&queue, count5);
    Event<void(int, int, int, int)> e4(&queue, count5, 1);
    Event<void(int, int, int)> e3(&queue, count5, 1, 1);
    Event<void(int, int)> e2(&queue, count5, 1, 1, 1);
    Event<void(int)> e1(&queue, count5, 1, 1, 1, 1);
    Event<void()> e0(&queue, count5, 1, 1, 1, 1, 1);

    e5.post(1, 1, 1, 1, 1);
    e4.post(1, 1, 1, 1);
    e3.post(1, 1, 1);
    e2.post(1, 1);
    e1.post(1);
    e0.post();

    queue.dispatch(0);

    TEST_ASSERT_EQUAL(counter, 30);
}

void event_class_helper_test()
{
    counter = 0;
    EventQueue queue(TEST_EQUEUE_SIZE);

    Event<void()> e5 = queue.event(count5, 1, 1, 1, 1, 1);
    Event<void()> e4 = queue.event(count4, 1, 1, 1, 1);
    Event<void()> e3 = queue.event(count3, 1, 1, 1);
    Event<void()> e2 = queue.event(count2, 1, 1);
    Event<void()> e1 = queue.event(count1, 1);
    Event<void()> e0 = queue.event(count0);

    e5.post();
    e4.post();
    e3.post();
    e2.post();
    e1.post();
    e0.post();

    queue.dispatch(0);

    TEST_ASSERT_EQUAL(counter, 15);
}

void event_inference_test()
{
    counter = 0;
    EventQueue queue(TEST_EQUEUE_SIZE);

    queue.event(count5, 1, 1, 1, 1, 1).post();
    queue.event(count5, 1, 1, 1, 1).post(1);
    queue.event(count5, 1, 1, 1).post(1, 1);
    queue.event(count5, 1, 1).post(1, 1, 1);
    queue.event(count5, 1).post(1, 1, 1, 1);
    queue.event(count5).post(1, 1, 1, 1, 1);

    queue.event(callback(count5), 1, 1, 1, 1, 1).post();
    queue.event(callback(count5), 1, 1, 1, 1).post(1);
    queue.event(callback(count5), 1, 1, 1).post(1, 1);
    queue.event(callback(count5), 1, 1).post(1, 1, 1);
    queue.event(callback(count5), 1).post(1, 1, 1, 1);
    queue.event(callback(count5)).post(1, 1, 1, 1, 1);

    queue.dispatch(0);

    TEST_ASSERT_EQUAL(counter, 60);
}

int timeleft_events[2];

void check_time_left(EventQueue *queue, int index, int expected)
{
    const int event_id = timeleft_events[index];
    TEST_ASSERT_INT_WITHIN(ALLOWED_TIME_LEFT_TOLERANCE_MS, expected, queue->time_left(event_id));
    touched = true;
}

void time_left(EventQueue *queue, int index)
{
    const int event_id = timeleft_events[index];
    TEST_ASSERT_EQUAL(0, queue->time_left(event_id));
}

void time_left_test()
{
    EventQueue queue(TEST_EQUEUE_SIZE);

    // Enque check events
    TEST_ASSERT(queue.call_in(50, check_time_left, &queue, 0, 100 - 50));
    TEST_ASSERT(queue.call_in(200, check_time_left, &queue, 1, 200 - 200));

    // Enque events to be checked
    timeleft_events[0] = queue.call_in(100, time_left, &queue, 0);
    timeleft_events[1] = queue.call_in(200, time_left, &queue, 1);
    TEST_ASSERT(timeleft_events[0]);
    TEST_ASSERT(timeleft_events[1]);

    queue.dispatch(300);

    // Ensure check was called
    TEST_ASSERT(touched);
    touched = false;

    int id = queue.call(func0);
    TEST_ASSERT(id);
    TEST_ASSERT_EQUAL(0, queue.time_left(id));
    queue.dispatch(10);

    // Test invalid event id
    TEST_ASSERT_EQUAL(-1, queue.time_left(0));
}

void f5(int a1, int a2, int a3, int a4, int a5)
{
    touched = true;
}

class EventTest {
public:
    EventTest() : counter() {}
    void f0()
    {
        counter++;
    }
    void f1(int a)
    {
        counter += a;
    }
    void f5(int a, int b, int c, int d, int e)
    {
        counter += a + b + c + d + e;
    }
    uint32_t counter;
};

/** Test that queue executes both dynamic and user allocated events.
 *
 *  Given queue is initialized and its size is set to store three Event at max in its internal memory.
 *      When post queue allocated event.
 *      Then only three event can be posted due to queue memory size.
 *      When post user allocated evens.
 *      Then number of posted events is not limited by queue memory size.
 *      When both Event and UserAllocatedEvent are posted and queue dispatch is called.
 *      Then both types of events are executed properly.
 *
 */
void mixed_dynamic_static_events_queue_test()
{
    {
        EventQueue queue(9 * EVENTS_EVENT_SIZE);

        EventTest e1_test;
        Event<void()> e1 = queue.event(&e1_test, &EventTest::f0);
        e1.delay(10);
        e1.period(10);
        int id1 =  e1.post();
        TEST_ASSERT_NOT_EQUAL(0, id1);
        EventTest e2_test;
        Event<void()> e2 = queue.event(&e2_test, &EventTest::f1, 3);
        e2.period(10);
        int id2 = e2.post();
        TEST_ASSERT_NOT_EQUAL(0, id2);
        EventTest e3_test;
        Event<void()> e3 = queue.event(&e3_test, &EventTest::f5, 1, 2, 3, 4, 5);
        e3.period(10);
        int id3 = e3.post();
        TEST_ASSERT_NOT_EQUAL(0, id3);


        auto ue0 = make_user_allocated_event(func0);
        EventTest ue1_test;
        auto ue1 = make_user_allocated_event(&ue1_test, &EventTest::f0);
        EventTest ue2_test;
        auto ue2 = make_user_allocated_event(&ue2_test, &EventTest::f1, 3);
        EventTest ue3_test;
        auto ue3 = make_user_allocated_event(&ue3_test, &EventTest::f5, 1, 2, 3, 4, 5);
        EventTest ue4_test;
        auto ue4 = make_user_allocated_event(&ue4_test, &EventTest::f5, 1, 2, 3, 4, 5);

        touched = false;

        ue0.call_on(&queue);
        TEST_ASSERT_EQUAL(false, ue0.try_call());
        ue1.call_on(&queue);
        TEST_ASSERT_EQUAL(false, ue1.try_call());
        ue2.period(10);
        ue2.call_on(&queue);
        TEST_ASSERT_EQUAL(false, ue2.try_call());
        ue3.period(10);
        ue3.delay(50);
        ue3.call_on(&queue);
        TEST_ASSERT_EQUAL(false, ue3.try_call());
        ue4.call_on(&queue);
        ue4.cancel();
        TEST_ASSERT_EQUAL(true, ue4.try_call());
        ue4.cancel();
        e2.cancel();

        queue.dispatch(101);

        TEST_ASSERT_EQUAL(true, touched);
        TEST_ASSERT_EQUAL(1, ue1_test.counter);
        TEST_ASSERT_EQUAL(33, ue2_test.counter);
        TEST_ASSERT_EQUAL(90, ue3_test.counter);
        TEST_ASSERT_EQUAL(0, ue4_test.counter);
        TEST_ASSERT_EQUAL(10, e1_test.counter);
        TEST_ASSERT_EQUAL(0, e2_test.counter);
        TEST_ASSERT_EQUAL(165, e3_test.counter);

        // user allocated event have to be canceled(removed from the queue) before destruction
        // cancel all periodic user events
        ue2.cancel();
        ue3.cancel();
    }
}


static EventQueue g_queue(0);
static auto ue0 = g_queue.make_user_allocated_event(func0);
static EventTest test1;
static auto ue1 = make_user_allocated_event(&test1, &EventTest::f0);
static EventTest test2;
static auto ue2 = g_queue.make_user_allocated_event(&test2, &EventTest::f1, 3);
static EventTest test3;
static auto ue3 = make_user_allocated_event(&test3, &EventTest::f5, 1, 2, 3, 4, 5);
static EventTest test4;
static auto ue4 = g_queue.make_user_allocated_event(&test4, &EventTest::f5, 1, 2, 3, 4, 5);


/** Test that static queue executes user allocated events.
 *
 *  Given static queue is initialized
 *      When post user allocated evens.
 *      Then UserAllocatedEvent are posted and dispatched without any error.
 */
void static_events_queue_test()
{
    // check that no dynamic event can be posted
    Event<void()> e0 = g_queue.event(func0);
    TEST_ASSERT_EQUAL(0, e0.post());

    ue0.delay(100);
    ue0.period(200);

    ue1.delay(100);
    ue1.period(200);

    ue2.delay(100);
    ue2.period(200);

    ue3.delay(100);
    ue3.period(200);

    ue4.delay(100);
    ue4.period(200);

    ue0.call();
    TEST_ASSERT_EQUAL(false, ue0.try_call());
    ue1.call_on(&g_queue);
    TEST_ASSERT_EQUAL(false, ue1.try_call());
    ue2();
    TEST_ASSERT_EQUAL(false, ue2.try_call());
    ue3.call_on(&g_queue);
    TEST_ASSERT_EQUAL(false, ue3.try_call());
    ue4.call();
    ue4.cancel();
    ue4.cancel();
    TEST_ASSERT_EQUAL(true, ue4.try_call());
    g_queue.cancel(&ue4);
    g_queue.cancel(&ue4);

    g_queue.dispatch(400);

    TEST_ASSERT_EQUAL(2, test1.counter);
    TEST_ASSERT_EQUAL(6, test2.counter);
    TEST_ASSERT_EQUAL(30, test3.counter);
    TEST_ASSERT_EQUAL(0, test4.counter);

    ue4.delay(1);
    TEST_ASSERT_EQUAL(true, ue4.try_call());
    g_queue.dispatch(1);

    TEST_ASSERT_EQUAL(2, test1.counter);
    TEST_ASSERT_EQUAL(6, test2.counter);
    TEST_ASSERT_EQUAL(30, test3.counter);
    TEST_ASSERT_EQUAL(15, test4.counter);
}

// Test setup
utest::v1::status_t test_setup(const size_t number_of_cases)
{
    GREENTEA_SETUP(20, "default_auto");
    return verbose_test_setup_handler(number_of_cases);
}

const Case cases[] = {
    Case("Testing calls with 5 args", simple_posts_test5),
    Case("Testing calls with 4 args", simple_posts_test4),
    Case("Testing calls with 3 args", simple_posts_test3),
    Case("Testing calls with 2 args", simple_posts_test2),
    Case("Testing calls with 1 args", simple_posts_test1),
    Case("Testing calls with 0 args", simple_posts_test0),

    Case("Testing call_in",    call_in_test<20>),
    Case("Testing call_every", call_every_test<20>),

    Case("Testing allocate failure", allocate_failure_test),

    Case("Testing event cancel 1", cancel_test1<20>),
    Case("Testing the event class", event_class_test),
    Case("Testing the event class helpers", event_class_helper_test),
    Case("Testing the event inference", event_inference_test),

    Case("Testing time_left", time_left_test),
    Case("Testing mixed dynamic & static events queue", mixed_dynamic_static_events_queue_test),
    Case("Testing static events queue", static_events_queue_test)

};

Specification specification(test_setup, cases);

int main()
{
    return !Harness::run(specification);
}

#endif // !DEVICE_USTICKER