feat(rt/posix): Added FreeRTOS-Plus-POSIX message queues implementation

Note: The current mq_open() implementation is changed to match
      the POSIX standard.
This commit is contained in:
Jakob Hasse
2024-02-01 09:19:46 +08:00
parent 5448703663
commit bddc1e95ef
30 changed files with 2760 additions and 4 deletions

View File

@@ -0,0 +1,9 @@
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
components/rt/test_apps/posix_rt_test:
enable:
- if: IDF_TARGET in ["esp32", "esp32s2", "esp32c3", "esp32p4"]
reason: covers all major arch types, xtensa vs riscv, single vs dual-core
depends_components:
- rt
- freertos

View File

@@ -0,0 +1,7 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
project(posix_test)

View File

@@ -0,0 +1,2 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-P4 | ESP32-S2 |
| ----------------- | ----- | -------- | -------- | -------- |

View File

@@ -0,0 +1,4 @@
idf_component_register(SRCS "main.c" "mqueue_test.cpp"
INCLUDE_DIRS "."
PRIV_REQUIRES rt unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <time.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
#include "esp_log.h"
static const char *TAG = "Linux_sim";
void setUp(void)
{
unity_utils_set_leak_level(0);
unity_utils_record_free_mem();
}
void tearDown(void)
{
unity_utils_evaluate_leaks();
}
void app_main(void)
{
// The following code avoids memory leaks from initializing libc structures in clock_gettime() calls.
struct timespec tp;
clock_gettime(CLOCK_REALTIME, &tp);
(void) tp;
ESP_LOGI(TAG, "Running FreeRTOS Linux test app");
unity_run_menu();
}

View File

@@ -0,0 +1,288 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <thread>
#include "unity.h"
#include "mqueue.h"
// Note: implementation does not support mode argument
// Note: implementation does not support message priorities (msg_prio arguments)
struct MessageQueueFixture {
MessageQueueFixture(const char *name = "/test_queue",
long int maxmsg = 10,
long int msgsize = sizeof(int),
int oflag = O_CREAT | O_WRONLY)
: name(name) {
struct mq_attr conf = {
.mq_flags = 0, // ignored by mq_open
.mq_maxmsg = maxmsg,
.mq_msgsize = msgsize,
.mq_curmsgs = 0 // ignored by mq_open
};
mq = mq_open(name, oflag, S_IRUSR, &conf);
TEST_ASSERT_NOT_EQUAL((mqd_t) -1, mq);
}
~MessageQueueFixture() {
TEST_ASSERT_EQUAL(0, mq_close(mq));
TEST_ASSERT_EQUAL(0, mq_unlink(name));
}
const char *name;
mqd_t mq;
};
TEST_CASE("open with invalid message queue name fails", "[mqueue]")
{
struct mq_attr conf = {
.mq_flags = 0, // ignored by mq_open
.mq_maxmsg = 10,
.mq_msgsize = sizeof(int),
.mq_curmsgs = 0 // ignored by mq_open
};
// 255 + 2 characters, excl. '\0'
#define TOO_LONG_NAME "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf));
TEST_ASSERT_EQUAL(EINVAL, errno);
TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("my_queue", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf));
TEST_ASSERT_EQUAL(EINVAL, errno);
TEST_ASSERT_EQUAL((mqd_t) -1, mq_open(TOO_LONG_NAME, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf));
TEST_ASSERT_EQUAL(EINVAL, errno);
}
TEST_CASE("name / is valid", "[mqueue]")
{
mqd_t mq = mq_open("/", O_CREAT | O_WRONLY, S_IRUSR, NULL);
TEST_ASSERT_NOT_EQUAL((mqd_t) -1, mq);
TEST_ASSERT_EQUAL(0, mq_close(mq));
TEST_ASSERT_EQUAL(0, mq_unlink("/"));
}
TEST_CASE("open without create flag fails", "[mqueue]")
{
TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("/my_queue", O_WRONLY));
TEST_ASSERT_EQUAL(ENOENT, errno);
}
TEST_CASE("open mq_maxmsg <= 0", "[mqueue]")
{
struct mq_attr conf = {
.mq_flags = 0, // ignored by mq_open
.mq_maxmsg = 0,
.mq_msgsize = sizeof(int),
.mq_curmsgs = 0 // ignored by mq_open
};
TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("/my_queue", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf));
TEST_ASSERT_EQUAL(EINVAL, errno);
conf.mq_maxmsg = -1;
TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("/my_queue", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf));
TEST_ASSERT_EQUAL(EINVAL, errno);
}
TEST_CASE("open mq_msgsize <= 0", "[mqueue]")
{
struct mq_attr conf = {
.mq_flags = 0, // ignored by mq_open
.mq_maxmsg = 10,
.mq_msgsize = 0,
.mq_curmsgs = 0 // ignored by mq_open
};
TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("/my_queue", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf));
TEST_ASSERT_EQUAL(EINVAL, errno);
conf.mq_msgsize = -1;
TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("/my_queue", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf));
TEST_ASSERT_EQUAL(EINVAL, errno);
}
TEST_CASE("creating already existing message queue fails", "[mqueue]")
{
MessageQueueFixture mq_fix;
TEST_ASSERT_EQUAL((mqd_t) -1, mq_open(mq_fix.name, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR , NULL));
TEST_ASSERT_EQUAL(EEXIST, errno);
}
TEST_CASE("open with default flags", "[mqueue]")
{
mqd_t mq = mq_open("/my_queue", O_CREAT | O_WRONLY, S_IRUSR, NULL);
TEST_ASSERT_NOT_EQUAL((mqd_t) -1, mq);
struct mq_attr default_conf;
TEST_ASSERT_EQUAL(0, mq_getattr(mq, &default_conf));
TEST_ASSERT_EQUAL(10, default_conf.mq_maxmsg);
TEST_ASSERT_EQUAL(128, default_conf.mq_msgsize);
TEST_ASSERT_EQUAL(0, mq_close(mq));
TEST_ASSERT_EQUAL(0, mq_unlink("/my_queue"));
}
TEST_CASE("open propagates attributes correctly", "[mqueue]")
{
MessageQueueFixture mq_fix("/test", 4, 8);
struct mq_attr conf;
TEST_ASSERT_EQUAL(0, mq_getattr(mq_fix.mq, &conf));
TEST_ASSERT_EQUAL(4, conf.mq_maxmsg);
TEST_ASSERT_EQUAL(8, conf.mq_msgsize);
}
TEST_CASE("message queue does not exceed queue size", "[mqueue]")
{
MessageQueueFixture mq_fix("/test", 1, sizeof(int));
int to_send = 47;
TEST_ASSERT_EQUAL(0, mq_send(mq_fix.mq, (const char*) &to_send, sizeof(to_send), 0));
int to_send_2 = 48;
int received = 0;
const struct timespec zero_time = {0, 0};
TEST_ASSERT_EQUAL(-1, mq_timedsend(mq_fix.mq, (const char*) &to_send_2, sizeof(to_send_2), 0, &zero_time));
TEST_ASSERT_EQUAL(ETIMEDOUT, errno);
TEST_ASSERT_EQUAL(4, mq_receive(mq_fix.mq, (char*) &received, sizeof(received), nullptr));
TEST_ASSERT_EQUAL(47, received);
}
TEST_CASE("sending on invalid mq descriptor fails", "[mqueue]")
{
int to_send = 47;
TEST_ASSERT_EQUAL(-1, mq_send(nullptr, (char*) &to_send, sizeof(to_send), 0));
TEST_ASSERT_EQUAL(EBADF, errno);
}
TEST_CASE("sending with invalid timeout fails", "[mqueue]")
{
MessageQueueFixture mq_fix("/test", 1, sizeof(int));
int to_send = 47;
struct timespec invalid_time = {0, 1'000'000'000};
TEST_ASSERT_EQUAL(-1, mq_timedsend(mq_fix.mq, (char*) &to_send, sizeof(to_send), 0, &invalid_time));
TEST_ASSERT_EQUAL(EINVAL, errno);
invalid_time.tv_nsec = -1;
TEST_ASSERT_EQUAL(-1, mq_timedsend(mq_fix.mq, (char*) &to_send, sizeof(to_send), 0, &invalid_time));
TEST_ASSERT_EQUAL(EINVAL, errno);
}
TEST_CASE("sending with invalid message size fails", "[mqueue]")
{
MessageQueueFixture mq_fix("/test", 1, sizeof(int));
int to_send = 47;
TEST_ASSERT_EQUAL(-1, mq_send(mq_fix.mq, (char*) &to_send, sizeof(to_send) + 1, 0));
TEST_ASSERT_EQUAL(EMSGSIZE, errno);
}
TEST_CASE("sending does not exceed queue size", "[mqueue]")
{
MessageQueueFixture mq_fix("/test", 1, sizeof(int));
int to_send = 47;
TEST_ASSERT_EQUAL(0, mq_send(mq_fix.mq, (const char*) &to_send, sizeof(to_send), 0));
int to_send_2 = 48;
int received = 0;
const struct timespec zero_time = {0, 0};
TEST_ASSERT_EQUAL(-1, mq_timedsend(mq_fix.mq, (const char*) &to_send_2, sizeof(to_send_2), 0, &zero_time));
TEST_ASSERT_EQUAL(ETIMEDOUT, errno);
TEST_ASSERT_EQUAL(4, mq_receive(mq_fix.mq, (char*) &received, sizeof(received), nullptr));
TEST_ASSERT_EQUAL(47, received);
}
TEST_CASE("nonblocking send works", "[mqueue]")
{
MessageQueueFixture mq_fix("/test", 1, sizeof(int), O_CREAT | O_WRONLY | O_NONBLOCK);
int to_send = 47;
int received = 0;
TEST_ASSERT_EQUAL(0, mq_send(mq_fix.mq, (char*) &to_send, sizeof(to_send), 0));
int to_send_2 = 48;
const struct timespec zero_time = {0, 0};
TEST_ASSERT_EQUAL(-1, mq_timedsend(mq_fix.mq, (char*) &to_send_2, sizeof(to_send_2), 0, &zero_time));
TEST_ASSERT_EQUAL(EAGAIN, errno);
TEST_ASSERT_EQUAL(-1, mq_send(mq_fix.mq, (char*) &to_send_2, sizeof(to_send_2), 0));
TEST_ASSERT_EQUAL(EAGAIN, errno);
TEST_ASSERT_EQUAL(4, mq_receive(mq_fix.mq, (char*) &received, sizeof(received), nullptr));
TEST_ASSERT_EQUAL(47, received);
}
TEST_CASE("receiving on invalid mq descriptor fails", "[mqueue]")
{
int received;
TEST_ASSERT_EQUAL(-1, mq_receive(nullptr, (char*) &received, sizeof(received), nullptr));
TEST_ASSERT_EQUAL(EBADF, errno);
}
TEST_CASE("receiving with invalid timeout fails", "[mqueue]")
{
MessageQueueFixture mq_fix("/test", 1, sizeof(int));
int received;
struct timespec invalid_time = {0, 1'000'000'000};
TEST_ASSERT_EQUAL(-1, mq_timedreceive(mq_fix.mq, (char*) &received, sizeof(received), 0, &invalid_time));
TEST_ASSERT_EQUAL(EINVAL, errno);
invalid_time.tv_nsec = -1;
TEST_ASSERT_EQUAL(-1, mq_timedreceive(mq_fix.mq, (char*) &received, sizeof(received), 0, &invalid_time));
TEST_ASSERT_EQUAL(EINVAL, errno);
}
TEST_CASE("receiving with invalid message size fails", "[mqueue]")
{
MessageQueueFixture mq_fix("/test", 1, sizeof(int));
int received;
TEST_ASSERT_EQUAL(-1, mq_send(mq_fix.mq, (char*) &received, sizeof(received) + 1, 0));
TEST_ASSERT_EQUAL(EMSGSIZE, errno);
}
TEST_CASE("receive on empty message queue times out", "[mqueue]")
{
MessageQueueFixture mq_fix("/test", 1, sizeof(int));
int received;
const struct timespec zero_time = {0, 0};
TEST_ASSERT_EQUAL(-1, mq_timedreceive(mq_fix.mq, (char*) &received, sizeof(received), 0, &zero_time));
TEST_ASSERT_EQUAL(ETIMEDOUT, errno);
}
TEST_CASE("nonblocking receive works", "[mqueue]")
{
MessageQueueFixture mq_fix("/test", 1, sizeof(int), O_CREAT | O_WRONLY | O_NONBLOCK);
int received;
const struct timespec zero_time = {0, 0};
TEST_ASSERT_EQUAL(-1, mq_timedreceive(mq_fix.mq, (char*) &received, sizeof(received), 0, &zero_time));
TEST_ASSERT_EQUAL(EAGAIN, errno);
TEST_ASSERT_EQUAL(-1, mq_receive(mq_fix.mq, (char*) &received, sizeof(received), nullptr));
TEST_ASSERT_EQUAL(EAGAIN, errno);
}
TEST_CASE("queue sends and receives multiple messages", "[mqueue]")
{
MessageQueueFixture mq_fix("/test", 2, sizeof(int), O_CREAT | O_WRONLY | O_NONBLOCK);
int received;
int to_send = 47;
TEST_ASSERT_EQUAL(0, mq_send(mq_fix.mq, (char*) &to_send, sizeof(to_send), 0));
to_send = 48;
TEST_ASSERT_EQUAL(0, mq_send(mq_fix.mq, (char*) &to_send, sizeof(to_send), 0));
TEST_ASSERT_EQUAL(4, mq_receive(mq_fix.mq, (char*) &received, sizeof(received), nullptr));
TEST_ASSERT_EQUAL(47, received);
TEST_ASSERT_EQUAL(4, mq_receive(mq_fix.mq, (char*) &received, sizeof(received), nullptr));
TEST_ASSERT_EQUAL(48, received);
}

View File

@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.generic
@pytest.mark.esp32
@pytest.mark.esp32c3
@pytest.mark.esp32p4
@pytest.mark.esp32s2
def test_rt_mqueue(dut: Dut) -> None:
dut.run_all_single_board_cases()

View File

@@ -0,0 +1,2 @@
# This "default" configuration is appended to all other configurations
CONFIG_ESP_TASK_WDT_INIT=n