Newer
Older
mbed-os / events / tests / TESTS / events / equeue / main.cpp
@George Psimenos George Psimenos on 28 Jul 2020 30 KB Restructure events directory & move tests
/* 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 "events/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)
{
    uint8_t *d = reinterpret_cast<uint8_t *>(p);
    if (*d < 255) {
        (*d)++;
    }
}

static void sloth_func(void *p)
{
    ThisThread::sleep_for(10);
    (*(reinterpret_cast<uint8_t *>(p)))++;
}

struct indirect {
    uint8_t *touched;
    uint8_t buffer[7];
};

static void indirect_func(void *p)
{
    struct indirect *i = reinterpret_cast<struct indirect *>(p);
    (*i->touched)++;
}

struct timing {
    unsigned tick;
    unsigned delay;
};

static void timing_func(void *p)
{
    struct timing *timing = reinterpret_cast<struct timing *>(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<struct fragment *>(p);
    timing_func(&fragment->timing);

    struct fragment *nfragment = reinterpret_cast<struct fragment *>(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<struct cancel *>(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<struct nest *>(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<int *>(p)) = ms;
}

struct ethread {
    equeue_t *q;
    int ms;
};

static void ethread_dispatch(void *p)
{
    struct ethread *t = reinterpret_cast<struct ethread *>(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<struct count_and_queue *>(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<struct indirect *>(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<struct indirect *>(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<struct indirect *>(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<struct indirect *>(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 <int N>
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<struct cancel *>(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<struct nest *>(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<struct nest *>(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(&q2);
    equeue_destroy(&q1);
}

/** 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<int N>
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<struct timing *>(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<int N>
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<struct fragment *>(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<int N>
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<struct timing *>(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);
}

struct user_allocated_event {
    struct equeue_event e;
    uint8_t touched;
};

/** Test that equeue executes user allocated events passed by equeue_post.
 *
 *  Given queue is initialized and its size is set to store one event at max in its internal memory.
 *  When post events allocated in queues internal memory (what is done by calling equeue_call).
 *  Then only one event can be posted due to queue memory size.
 *  When post user allocated events.
 *  Then number of posted events is not limited by queue memory size.
 *  When both queue allocaded and user allocated events are posted and equeue_dispatch is called.
 *  Then both types of events are executed properly.
 */
static void test_equeue_user_allocated_event_post()
{
    equeue_t q;
    int err = equeue_create(&q, EQUEUE_EVENT_SIZE);
    TEST_ASSERT_EQUAL_INT(0, err);

    uint8_t touched = 0;
    user_allocated_event e1 = { { 0, 0, 0, NULL, NULL, NULL, 0, -1, NULL, NULL }, 0 };
    user_allocated_event e2 = { { 0, 0, 0, NULL, NULL, NULL, 10,  10, NULL, NULL }, 0 };
    user_allocated_event e3 = { { 0, 0, 0, NULL, NULL, NULL, 10,  10, NULL, NULL }, 0 };
    user_allocated_event e4 = { { 0, 0, 0, NULL, NULL, NULL, 10,  10, NULL, NULL }, 0 };
    user_allocated_event e5 = { { 0, 0, 0, NULL, NULL, NULL, 0, -1, NULL, NULL }, 0 };

    TEST_ASSERT_NOT_EQUAL(0, equeue_call_every(&q, 10, simple_func, &touched));
    TEST_ASSERT_EQUAL_INT(0, equeue_call_every(&q, 10, simple_func, &touched));
    TEST_ASSERT_EQUAL_INT(0, equeue_call_every(&q, 10, simple_func, &touched));

    equeue_post_user_allocated(&q, simple_func, &e1.e);
    equeue_post_user_allocated(&q, simple_func, &e2.e);
    equeue_post_user_allocated(&q, simple_func, &e3.e);
    equeue_post_user_allocated(&q, simple_func, &e4.e);
    equeue_post_user_allocated(&q, simple_func, &e5.e);
    equeue_cancel_user_allocated(&q, &e3.e);
    equeue_cancel_user_allocated(&q, &e3.e);

    equeue_dispatch(&q, 11);

    TEST_ASSERT_EQUAL_UINT8(1, touched);
    TEST_ASSERT_EQUAL_UINT8(1, e1.touched);
    TEST_ASSERT_EQUAL_UINT8(1, e2.touched);
    TEST_ASSERT_EQUAL_UINT8(0, e3.touched);
    TEST_ASSERT_EQUAL_UINT8(1, e4.touched);
    TEST_ASSERT_EQUAL_UINT8(1, e5.touched);

    e3.e.target = 10; // set target as it's modified by equeue_call
    e3.e.period = 10; // set period as it's reset by equeue_cancel
    equeue_post_user_allocated(&q, simple_func, &e3.e);
    equeue_dispatch(&q, 101);

    TEST_ASSERT_EQUAL_UINT8(11, touched);
    TEST_ASSERT_EQUAL_UINT8(1, e1.touched);
    TEST_ASSERT_EQUAL_UINT8(11, e2.touched);
    TEST_ASSERT_EQUAL_UINT8(10, e3.touched);
    TEST_ASSERT_EQUAL_UINT8(11, e4.touched);
    TEST_ASSERT_EQUAL_UINT8(1, e5.touched);

    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),
    Case("user allocated event test", test_equeue_user_allocated_event_post)

};

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