Rtos API example

mbed-os/TESTS/mbedmicro-rtos-mbed/mail/main.cpp

Committer:
marcozecchini
Date:
2019-02-23
Revision:
0:9fca2b23d0ba

File content as of revision 0:9fca2b23d0ba:

/* mbed Microcontroller Library
 * Copyright (c) 2017 ARM Limited
 *
 * 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.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include "rtos.h"

#if defined(MBED_RTOS_SINGLE_THREAD)
  #error [NOT_SUPPORTED] test not supported
#endif

using namespace utest::v1;

#define THREAD_STACK_SIZE   320 /* larger stack cause out of heap memory on some 16kB RAM boards in multi thread test*/
#define QUEUE_SIZE          16
#define THREAD_1_ID         1
#define THREAD_2_ID         2
#define THREAD_3_ID         3
#define QUEUE_PUT_DELAY_1   5
#define QUEUE_PUT_DELAY_2   50
#define QUEUE_PUT_DELAY_3   100
#define DATA_BASE           100


typedef struct {
    uint16_t data;
    uint8_t thread_id;
} mail_t;


template<uint8_t thread_id, uint32_t wait_ms, uint32_t send_count>
void send_thread(Mail<mail_t, QUEUE_SIZE> *m)
{
    uint32_t data = thread_id * DATA_BASE;

    for (uint32_t i = 0; i < send_count; i++) {
        mail_t *mail = m->alloc();
        mail->thread_id = thread_id;
        mail->data = data++;
        m->put(mail);
        Thread::wait(wait_ms);
    }
}

template<uint8_t thread_id, uint32_t queue_size, uint32_t wait_ms>
void receive_thread(Mail<mail_t, queue_size> *m)
{
    int result_counter = 0;
    uint32_t data = thread_id * DATA_BASE;

    Thread::wait(wait_ms);
    for (uint32_t i = 0; i < queue_size; i++) {
        osEvent evt = m->get();
        if (evt.status == osEventMail) {
            mail_t *mail = (mail_t*)evt.value.p;
            const uint8_t id = mail->thread_id;

            // verify thread id
            TEST_ASSERT_TRUE(id == thread_id);
            // verify sent data
            TEST_ASSERT_TRUE(mail->data == data++);

            m->free(mail);
            result_counter++;
        }
    }
    TEST_ASSERT_EQUAL(queue_size, result_counter);
}

/** Test single thread Mail usage and order

    Given mailbox and one additional thread
    When messages are put in to the Mail box by this thread
    Then messages are received in main thread in the same order as was sent and the data sent is valid
 */
void test_single_thread_order(void)
{
    uint16_t data = DATA_BASE;
    int result_counter = 0;
    Mail<mail_t, QUEUE_SIZE> mail_box;

    // mail send thread creation
    Thread thread(osPriorityNormal, THREAD_STACK_SIZE);
    thread.start(callback(send_thread<THREAD_1_ID, QUEUE_PUT_DELAY_1, QUEUE_SIZE>, &mail_box));

    // wait for some mail to be collected
    Thread::wait(10);

    for (uint32_t i = 0; i < QUEUE_SIZE; i++) {
        // mail receive (main thread)
        osEvent evt = mail_box.get();
        if (evt.status == osEventMail) {
            mail_t *mail = (mail_t*)evt.value.p;
            const uint8_t id = mail->thread_id;

            // verify thread id
            TEST_ASSERT_TRUE(id == THREAD_1_ID);
            // verify sent data
            TEST_ASSERT_TRUE(mail->data == data++);
            mail_box.free(mail);

            result_counter++;
        }
    }
    TEST_ASSERT_EQUAL(QUEUE_SIZE, result_counter);
}

/** Test multi thread Mail usage and order

    Given mailbox and three additional threads
    When messages are put in to the Mail box by these threads
    Then messages are received in main thread in the same per thread order as was sent and the data sent is valid
 */
void test_multi_thread_order(void)
{
    uint16_t data[4] = { 0, DATA_BASE, DATA_BASE * 2, DATA_BASE * 3 };
    int result_counter = 0;
    Mail<mail_t, QUEUE_SIZE> mail_box;

    // mail send threads creation
    Thread thread1(osPriorityNormal, THREAD_STACK_SIZE);
    Thread thread2(osPriorityNormal, THREAD_STACK_SIZE);
    Thread thread3(osPriorityNormal, THREAD_STACK_SIZE);
    thread1.start(callback(send_thread<THREAD_1_ID, QUEUE_PUT_DELAY_1, 7>, &mail_box));
    thread2.start(callback(send_thread<THREAD_2_ID, QUEUE_PUT_DELAY_2, 5>, &mail_box));
    thread3.start(callback(send_thread<THREAD_3_ID, QUEUE_PUT_DELAY_3, 4>, &mail_box));

    // wait for some mail to be collected
    Thread::wait(10);

    for (uint32_t i = 0; i < QUEUE_SIZE; i++) {
        // mail receive (main thread)
        osEvent evt = mail_box.get();
        if (evt.status == osEventMail) {
            mail_t *mail = (mail_t*)evt.value.p;
            const uint8_t id = mail->thread_id;

            // verify thread id
            TEST_ASSERT_TRUE((id == THREAD_1_ID) || (id == THREAD_2_ID) || (id == THREAD_3_ID));
            // verify sent data
            TEST_ASSERT_TRUE(mail->data == data[id]++);
            mail_box.free(mail);

            result_counter++;
        }
    }
    TEST_ASSERT_EQUAL(QUEUE_SIZE, result_counter);
}

/** Test multi thread multi Mail usage and order

    Given 3 mailbox and three additional threads
    When messages are put in to the mail boxes by main thread
    Then messages are received by threads in the same per mail box order as was sent and the data sent is valid
 */
void test_multi_thread_multi_mail_order(void)
{
    Mail<mail_t, 4> mail_box[4]; /* mail_box[0] not used */
    uint16_t data[4] = { 0, DATA_BASE, DATA_BASE * 2, DATA_BASE * 3 };
    mail_t *mail;
    uint8_t id;

    // mail receive threads creation
    Thread thread1(osPriorityNormal, THREAD_STACK_SIZE);
    Thread thread2(osPriorityNormal, THREAD_STACK_SIZE);
    Thread thread3(osPriorityNormal, THREAD_STACK_SIZE);
    thread1.start(callback(receive_thread<THREAD_1_ID, 4, 0>, mail_box + 1));
    thread2.start(callback(receive_thread<THREAD_2_ID, 4, 10>, mail_box + 2));
    thread3.start(callback(receive_thread<THREAD_3_ID, 4, 100>, mail_box + 3));

    for (uint32_t i = 0; i < 4; i++) {
        id = THREAD_1_ID;
        mail = mail_box[id].alloc();
        mail->thread_id = id;
        mail->data = data[id]++;
        mail_box[id].put(mail);

        id = THREAD_2_ID;
        mail = mail_box[id].alloc();
        mail->thread_id = id;
        mail->data = data[id]++;
        mail_box[id].put(mail);

        id = THREAD_3_ID;
        mail = mail_box[id].alloc();
        mail->thread_id = id;
        mail->data = data[id]++;
        mail_box[id].put(mail);

        Thread::wait(i * 10);
    }

    thread1.join();
    thread2.join();
    thread3.join();
}

/** Test message memory deallocation with block out of the scope

    Given an empty mailbox
    When try to free out of the scope memory block
    Then it return appropriate error code
 */
void test_free_wrong()
{
    osStatus status;
    Mail<uint32_t, 4> mail_box;
    uint32_t *mail, data;

    mail = &data;
    status = mail_box.free(mail);
    TEST_ASSERT_EQUAL(osErrorParameter, status);

    mail = mail_box.alloc();
    TEST_ASSERT_NOT_EQUAL(NULL, mail);

    mail = &data;
    status = mail_box.free(mail);
    TEST_ASSERT_EQUAL(osErrorParameter, status);
}

/** Test message memory deallocation with null block

    Given an empty mailbox
    When try to free null ptr
    Then it return appropriate error code
 */
void test_free_null()
{
    osStatus status;
    Mail<uint32_t, 4> mail_box;
    uint32_t *mail;

    mail = NULL;
    status = mail_box.free(mail);
    TEST_ASSERT_EQUAL(osErrorParameter, status);

    mail = mail_box.alloc();
    TEST_ASSERT_NOT_EQUAL(NULL, mail);

    mail = NULL;
    status = mail_box.free(mail);
    TEST_ASSERT_EQUAL(osErrorParameter, status);
}

/** Test same message memory deallocation twice

    Given an empty mailbox
    Then allocate message memory
    When try to free it second time
    Then it return appropriate error code
 */
void test_free_twice()
{
    osStatus status;
    Mail<uint32_t, 4> mail_box;

    uint32_t *mail = mail_box.alloc();
    TEST_ASSERT_NOT_EQUAL(NULL, mail);

    status = mail_box.free(mail);
    TEST_ASSERT_EQUAL(osOK, status);

    status = mail_box.free(mail);
    TEST_ASSERT_EQUAL(osErrorResource, status);
}

/** Test get from empty mailbox with timeout set

    Given an empty mailbox
    When @a get is called on the mailbox with timeout of 50
    Then mailbox returns status of osOK, but no data after specified amount of time
 */
void test_get_empty_timeout()
{
    Mail<uint32_t, 4> mail_box;
    Timer timer;

    timer.start();
    osEvent evt = mail_box.get(50);
    TEST_ASSERT_UINT32_WITHIN(5000, 50000, timer.read_us());
    TEST_ASSERT_EQUAL(osEventTimeout, evt.status);
}

/** Test get from empty mailbox with 0 timeout

    Given an empty mailbox
    When @a get is called on the mailbox with timeout of 0
    Then mailbox returns status of osOK, but no data
 */
void test_get_empty_no_timeout()
{
    Mail<uint32_t, 4> mail_box;

    osEvent evt = mail_box.get(0);
    TEST_ASSERT_EQUAL(osOK, evt.status);
}

/** Test mail order

    Given an mailbox for uint32_t values
    Then allocate two mails and put them in to mailbox
    When call @a get it returns previously put mails
    Then mails should be in the same order as put
 */
void test_order(void)
{
    osStatus status;
    osEvent evt;
    Mail<int32_t, 4> mail_box;
    const int32_t TEST_VAL1 = 123;
    const int32_t TEST_VAL2 = 456;

    int32_t *mail1 = mail_box.alloc();
    TEST_ASSERT_NOT_EQUAL(NULL, mail1);

    *mail1 = TEST_VAL1;
    status = mail_box.put(mail1);
    TEST_ASSERT_EQUAL(osOK, status);

    int32_t *mail2 = mail_box.alloc();
    TEST_ASSERT_NOT_EQUAL(NULL, mail2);

    *mail2 = TEST_VAL2;
    status = mail_box.put(mail2);
    TEST_ASSERT_EQUAL(osOK, status);


    evt = mail_box.get();
    TEST_ASSERT_EQUAL(evt.status, osEventMail);

    mail1 = (int32_t*)evt.value.p;
    TEST_ASSERT_EQUAL(TEST_VAL1, *mail1);

    evt = mail_box.get();
    TEST_ASSERT_EQUAL(evt.status, osEventMail);

    mail2 = (int32_t*)evt.value.p;
    TEST_ASSERT_EQUAL(TEST_VAL2, *mail2);


    status = mail_box.free(mail1);
    TEST_ASSERT_EQUAL(osOK, status);

    status = mail_box.free(mail2);
    TEST_ASSERT_EQUAL(osOK, status);
}

/** Test Mail box max size limit

    Given an Mail box with max size of 4 elements
    When call @a alloc four times it returns memory blocks
    Then the memory blocks should be valid
    When call @a alloc one more time it returns memory blocks
    Then the memory blocks should be not valid (NULL - no memory available)
 */
void test_max_size()
{
    osStatus status;
    Mail<uint32_t, 4> mail_box;
    const uint32_t TEST_VAL = 123;

    // 1 OK
    uint32_t *mail1 = mail_box.alloc();
    TEST_ASSERT_NOT_EQUAL(NULL, mail1);

    // 2 OK
    uint32_t *mail2 = mail_box.alloc();
    TEST_ASSERT_NOT_EQUAL(NULL, mail2);

    // 3 OK
    uint32_t *mail3 = mail_box.alloc();
    TEST_ASSERT_NOT_EQUAL(NULL, mail3);

    // 4 OK
    uint32_t *mail4 = mail_box.alloc();
    TEST_ASSERT_NOT_EQUAL(NULL, mail4);

    // 5 KO
    uint32_t *mail5 = mail_box.alloc();
    TEST_ASSERT_EQUAL(NULL, mail5);


    status = mail_box.free(mail1);
    TEST_ASSERT_EQUAL(osOK, status);

    status = mail_box.free(mail2);
    TEST_ASSERT_EQUAL(osOK, status);

    status = mail_box.free(mail3);
    TEST_ASSERT_EQUAL(osOK, status);

    status = mail_box.free(mail4);
    TEST_ASSERT_EQUAL(osOK, status);
}

/** Test mailbox of T type data

    Given an mailbox with T memory block type
    When allocate/put/get/free memory block
    Then all operations should succeed
 */
template<typename T>
void test_data_type(void)
{
    osStatus status;
    Mail<T, 4> mail_box;
    const T TEST_VAL = 123;

    T *mail = mail_box.alloc();
    TEST_ASSERT_NOT_EQUAL(NULL, mail);

    *mail = TEST_VAL;
    status = mail_box.put(mail);
    TEST_ASSERT_EQUAL(osOK, status);

    osEvent evt = mail_box.get();
    TEST_ASSERT_EQUAL(evt.status, osEventMail);

    mail = (T*)evt.value.p;
    TEST_ASSERT_EQUAL(TEST_VAL, *mail);


    status = mail_box.free(mail);
    TEST_ASSERT_EQUAL(osOK, status);
}

/** Test calloc - memory block allocation with resetting

    Given an empty Mail box
    When call @a calloc it returns allocated memory block
    Then the memory block should be valid and filled with zeros
 */
void test_calloc()
{
    Mail<uint32_t, 1> mail_box;

    uint32_t *mail = mail_box.calloc();
    TEST_ASSERT_NOT_EQUAL(NULL, mail);
    TEST_ASSERT_EQUAL(0, *mail);
}

/** Test mail empty

    Given a mail of uint32_t data
    before data is inserted the mail should be empty
    after data is inserted the mail shouldn't be empty
 */
void test_mail_empty()
{
    Mail<mail_t, 1> m;

    mail_t *mail = m.alloc();

    TEST_ASSERT_EQUAL(true,  m.empty());

    m.put(mail);

    TEST_ASSERT_EQUAL(false, m.empty());
}

/** Test mail empty

    Given a mail of uint32_t data with size of 1
    before data is inserted the mail shouldn't be full
    after data is inserted the mail should be full
 */
void test_mail_full()
{
    Mail<mail_t, 1> m;

    mail_t *mail = m.alloc();

    TEST_ASSERT_EQUAL(false,  m.full());

    m.put(mail);

    TEST_ASSERT_EQUAL(true, m.full());
}

utest::v1::status_t test_setup(const size_t number_of_cases)
{
    GREENTEA_SETUP(10, "default_auto");
    return verbose_test_setup_handler(number_of_cases);
}

Case cases[] = {
    Case("Test calloc", test_calloc),
    Case("Test message type uint8", test_data_type<uint8_t>),
    Case("Test message type uint16", test_data_type<uint16_t>),
    Case("Test message type uint32", test_data_type<uint32_t>),
    Case("Test mailbox max size", test_max_size),
    Case("Test message send order", test_order),
    Case("Test get with timeout on empty mailbox", test_get_empty_timeout),
    Case("Test get without timeout on empty mailbox", test_get_empty_no_timeout),
    Case("Test message free twice", test_free_twice),
    Case("Test null message free", test_free_null),
    Case("Test invalid message free", test_free_wrong),
    Case("Test message send/receive single thread and order", test_single_thread_order),
    Case("Test message send/receive multi-thread and per thread order", test_multi_thread_order),
    Case("Test message send/receive multi-thread, multi-Mail and per thread order", test_multi_thread_multi_mail_order),
    Case("Test mail empty", test_mail_empty),
    Case("Test mail full", test_mail_full)
};

Specification specification(test_setup, cases);

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