From a749e3493d13a7f924a62d4ac4f8a270b074a01c Mon Sep 17 00:00:00 2001 From: Joshua DeWeese Date: Tue, 1 Apr 2025 12:42:56 -0400 Subject: [PATCH] sys/isrpipe: add unit tests This patch adds unit tests for the isrpipe module. --- tests/unittests/tests-isrpipe/Makefile | 1 + .../unittests/tests-isrpipe/Makefile.include | 2 + .../tests-isrpipe-read_timeout.c | 95 +++++++ tests/unittests/tests-isrpipe/tests-isrpipe.c | 243 ++++++++++++++++++ tests/unittests/tests-isrpipe/tests-isrpipe.h | 50 ++++ 5 files changed, 391 insertions(+) create mode 100644 tests/unittests/tests-isrpipe/Makefile create mode 100644 tests/unittests/tests-isrpipe/Makefile.include create mode 100644 tests/unittests/tests-isrpipe/tests-isrpipe-read_timeout.c create mode 100644 tests/unittests/tests-isrpipe/tests-isrpipe.c create mode 100644 tests/unittests/tests-isrpipe/tests-isrpipe.h diff --git a/tests/unittests/tests-isrpipe/Makefile b/tests/unittests/tests-isrpipe/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/tests/unittests/tests-isrpipe/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-isrpipe/Makefile.include b/tests/unittests/tests-isrpipe/Makefile.include new file mode 100644 index 0000000000..ab62c17b64 --- /dev/null +++ b/tests/unittests/tests-isrpipe/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE += isrpipe +USEMODULE += isrpipe_read_timeout diff --git a/tests/unittests/tests-isrpipe/tests-isrpipe-read_timeout.c b/tests/unittests/tests-isrpipe/tests-isrpipe-read_timeout.c new file mode 100644 index 0000000000..8acbde90bf --- /dev/null +++ b/tests/unittests/tests-isrpipe/tests-isrpipe-read_timeout.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2025 Prime Controls, Inc.(R) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include +#include + +#include "embUnit/embUnit.h" + +#include "isrpipe/read_timeout.h" +#include "tests-isrpipe.h" + +static void test_read_timeout(void) +{ + uint8_t buffer[2] = {0}; + uint8_t read_buf[2]; + isrpipe_t pipe; + int res; + + /* prep the pipe */ + isrpipe_init(&pipe, buffer, ARRAY_SIZE(buffer)); + + /* test timeout */ + res = isrpipe_read_timeout(&pipe, read_buf, ARRAY_SIZE(read_buf), 1); + TEST_ASSERT_EQUAL_INT(-ETIMEDOUT, res); + + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&pipe, 1)); + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&pipe, 2)); + + /* pipe now holds 1, 2 */ + + /* test successful read */ + res = isrpipe_read_timeout(&pipe, read_buf, ARRAY_SIZE(read_buf), 1); + TEST_ASSERT_EQUAL_INT(ARRAY_SIZE(read_buf), res); + + /* pipe now empty */ + + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&pipe, 3)); + + /* pipe now holds 3 */ + + /* test partial read */ + res = isrpipe_read_timeout(&pipe, read_buf, ARRAY_SIZE(read_buf), 1); + TEST_ASSERT_EQUAL_INT(1, res); +} + +static void test_read_all_timeout(void) +{ + uint8_t buffer[2] = {0}; + uint8_t read_buf[2]; + isrpipe_t pipe; + int res; + + /* prep the pipe */ + isrpipe_init(&pipe, buffer, ARRAY_SIZE(buffer)); + + /* test timeout */ + res = isrpipe_read_all_timeout(&pipe, read_buf, ARRAY_SIZE(read_buf), 1); + TEST_ASSERT_EQUAL_INT(-ETIMEDOUT, res); + + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&pipe, 1)); + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&pipe, 2)); + + /* pipe now holds 1, 2 */ + + /* test successful read */ + res = isrpipe_read_all_timeout(&pipe, read_buf, ARRAY_SIZE(read_buf), 1); + TEST_ASSERT_EQUAL_INT(ARRAY_SIZE(read_buf), res); + + /* pipe now empty */ + + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&pipe, 3)); + + /* pipe now holds 3 */ + + /* test timeout - isrpipe_read_all_timeout() does not allow partial reads */ + res = isrpipe_read_all_timeout(&pipe, read_buf, ARRAY_SIZE(read_buf), 1); + TEST_ASSERT_EQUAL_INT(-ETIMEDOUT, res); +} + +Test *tests_isrpipe_read_timeout_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_read_timeout), + new_TestFixture(test_read_all_timeout), + }; + + EMB_UNIT_TESTCALLER(isrpipe_tests, NULL, NULL, fixtures); + + return (Test *)&isrpipe_tests; +} diff --git a/tests/unittests/tests-isrpipe/tests-isrpipe.c b/tests/unittests/tests-isrpipe/tests-isrpipe.c new file mode 100644 index 0000000000..29607275d4 --- /dev/null +++ b/tests/unittests/tests-isrpipe/tests-isrpipe.c @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2025 Prime Controls, Inc.(R) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include + +#include "embUnit/embUnit.h" + +#include "isrpipe.h" +#include "tests-isrpipe.h" + +#include "ztimer.h" + +/* ISR pipe to test */ +static uint8_t _buffer[8]; +static isrpipe_t _pipe; + +/* reader thread's stack */ +static char _reader_stack[THREAD_STACKSIZE_TINY]; + +/* entry point of reader thread */ +static void* _reader_thread(void *arg) +{ + uint8_t *read_buf = arg; + + /* get the size of the read_buffer */ + unsigned read_buf_size = read_buf[0]; + + /* keep reading until read_buf is full */ + while (read_buf_size) { + const int res = isrpipe_read(&_pipe, read_buf, read_buf_size); + read_buf += res; + read_buf_size -= res; + } + + thread_zombify(); + return NULL; +} + +static void setup_up(void) +{ + /* clear buffer contents from previous tests */ + memset(_buffer, 0, sizeof(_buffer)); + + /* prep the pipe */ + isrpipe_init(&_pipe, _buffer, ARRAY_SIZE(_buffer)); +} + +static void test_init(void) +{ + /* statically initialized isrpipe */ + static const isrpipe_t pipe = ISRPIPE_INIT(_buffer); + + /* check that static and runtime init produces the same result */ + TEST_ASSERT_EQUAL_INT(0, memcmp(&_pipe, &pipe, sizeof(isrpipe_t))); +} + +static void test_write_then_read(void) +{ + const uint8_t write_buf[] = {1, 2, 3, 4, 5, 6}; + uint8_t read_buf[2]; + int res; + + res = isrpipe_write(&_pipe, write_buf, ARRAY_SIZE(write_buf)); + TEST_ASSERT_EQUAL_INT(ARRAY_SIZE(write_buf), res); + + /* pipe now holds 1, 2, 3, 4, 5, 6 */ + + res = isrpipe_read(&_pipe, read_buf, 1); + TEST_ASSERT_EQUAL_INT(1, res); + TEST_ASSERT_EQUAL_INT(1, read_buf[0]); + + /* pipe now holds 2, 3, 4, 5, 6 */ + + res = isrpipe_read(&_pipe, read_buf, 2); + TEST_ASSERT_EQUAL_INT(2, res); + TEST_ASSERT_EQUAL_INT(2, read_buf[0]); + TEST_ASSERT_EQUAL_INT(3, read_buf[1]); + + /* pipe now holds 4, 5, 6 */ + + res = isrpipe_read(&_pipe, read_buf, 1); + TEST_ASSERT_EQUAL_INT(1, res); + TEST_ASSERT_EQUAL_INT(4, read_buf[0]); + + /* pipe now holds 5, 6 */ + + res = isrpipe_read(&_pipe, read_buf, 2); + TEST_ASSERT_EQUAL_INT(2, res); + TEST_ASSERT_EQUAL_INT(5, read_buf[0]); + TEST_ASSERT_EQUAL_INT(6, read_buf[1]); + + /* pipe now empty */ +} + +static void test_read_then_write(void) +{ + uint8_t read_buf[3] = {0}; /* init contents to all zeros */ + + thread_t *writer_thread = thread_get_active(); + const unsigned writer_priority = thread_get_priority(writer_thread); + const unsigned reader_priority = 0; + + /* check assumptions */ + TEST_ASSERT(writer_priority > reader_priority); + + /* sneaky way to tell reader thread the size of the read_buf */ + read_buf[0] = ARRAY_SIZE(read_buf); + + /* create reader thread with priority higher than writer thread so that write + can only happen once reading blocks on the empty pipe */ + const int reader_pid = thread_create(_reader_stack, sizeof(_reader_stack), + reader_priority, 0, _reader_thread, read_buf, "reader"); + + /* check assumptions */ + TEST_ASSERT(reader_pid >= 0); + + /* reader thread should have preempted us, but is now blocked by reading + from the empty pipe, which yielded the CPU back to us */ + + /* write to the pipe a byte at a time to demonstrate that the reader is + unblocking with each byte and placing the bytes into read_buf */ + for (unsigned i = 0; i < ARRAY_SIZE(read_buf); i++) { + + /* test that read_buf[i] is not equal to the value we are about to send + through the pipe */ + TEST_ASSERT(i != read_buf[i]); + + /* write a byte, reader thread will then unblock and write it into + read_buf[i] */ + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&_pipe, i)); + + /* check that reader got the byte from the pipe and put it into + read_buf[i]*/ + TEST_ASSERT_EQUAL_INT(i, read_buf[i]); + } + + /* cleanup */ + TEST_ASSERT_EQUAL_INT(1, thread_kill_zombie(reader_pid)); +} + +static void test_overflow(void) +{ + int res; + + /* completely fill the pipe */ + for (unsigned i = 0; i < ARRAY_SIZE(_buffer); i++) { + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&_pipe, i)); + } + + /* pipe is now full */ + + /* isrpipe_write_one() should return -1 for error */ + TEST_ASSERT_EQUAL_INT(-1, isrpipe_write_one(&_pipe, -1)); + + /* isrpipe_write() should return 0 bytes written */ + const uint8_t write_buf[] = {1, 2, 3}; + res = isrpipe_write(&_pipe, write_buf, ARRAY_SIZE(write_buf)); + TEST_ASSERT_EQUAL_INT(0, res); +} + +static void test_underflow(void) +{ + const uint8_t write_buf[] = {1, 2, 3}; + uint8_t read_buf[ARRAY_SIZE(write_buf) + 1]; + int res; + + res = isrpipe_write(&_pipe, write_buf, ARRAY_SIZE(write_buf)); + TEST_ASSERT_EQUAL_INT(ARRAY_SIZE(write_buf), res); + + /* pipe now holds 1, 2, 3 */ + + /* try to read more data than is in pipe */ + res = isrpipe_read(&_pipe, read_buf, ARRAY_SIZE(read_buf)); + TEST_ASSERT_EQUAL_INT(ARRAY_SIZE(write_buf), res); +} + +static void test_write_one(void) +{ + uint8_t read_buf[3]; + int res; + + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&_pipe, 1)); + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&_pipe, 2)); + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&_pipe, 3)); + + /* pipe now holds 1, 2, 3 */ + + TEST_ASSERT_EQUAL_INT(1, isrpipe_read(&_pipe, read_buf, 1)); + TEST_ASSERT_EQUAL_INT(1, read_buf[0]); + + /* pipe now holds 2, 3 */ + + TEST_ASSERT_EQUAL_INT(1, isrpipe_read(&_pipe, read_buf, 1)); + TEST_ASSERT_EQUAL_INT(2, read_buf[0]); + + /* pipe now holds 3 */ + + TEST_ASSERT_EQUAL_INT(1, isrpipe_read(&_pipe, read_buf, 1)); + TEST_ASSERT_EQUAL_INT(3, read_buf[0]); + + /* pipe now empty */ + + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&_pipe, 4)); + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&_pipe, 5)); + TEST_ASSERT_EQUAL_INT(0, isrpipe_write_one(&_pipe, 6)); + + /* pipe now holds 4, 5, 6 */ + + res = isrpipe_read(&_pipe, read_buf, ARRAY_SIZE(read_buf)); + TEST_ASSERT_EQUAL_INT(ARRAY_SIZE(read_buf), res); + TEST_ASSERT_EQUAL_INT(4, read_buf[0]); + TEST_ASSERT_EQUAL_INT(5, read_buf[1]); + TEST_ASSERT_EQUAL_INT(6, read_buf[2]); + + /* pipe now empty */ +} + +Test *tests_isrpipe_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_init), + new_TestFixture(test_write_then_read), + new_TestFixture(test_read_then_write), + new_TestFixture(test_overflow), + new_TestFixture(test_underflow), + new_TestFixture(test_write_one), + }; + + EMB_UNIT_TESTCALLER(isrpipe_tests, setup_up, NULL, fixtures); + + return (Test *)&isrpipe_tests; +} + +void tests_isrpipe(void) +{ + TESTS_RUN(tests_isrpipe_tests()); + TESTS_RUN(tests_isrpipe_read_timeout_tests()); +} diff --git a/tests/unittests/tests-isrpipe/tests-isrpipe.h b/tests/unittests/tests-isrpipe/tests-isrpipe.h new file mode 100644 index 0000000000..7f996f92ff --- /dev/null +++ b/tests/unittests/tests-isrpipe/tests-isrpipe.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 Prime Controls, Inc.(R) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#pragma once + +/** + * @addtogroup unittests + * @{ + * + * @file + * @brief Unittests for ISR pipe + * + * @author Joshua DeWeese + */ + +#include "embUnit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Entry point of the test suite + */ +void tests_isrpipe(void); + +/** + * @brief Generates tests for isrpipe.h + * + * @return embUnit tests if successful, NULL if not. + */ +Test *tests_isrpipe_tests(void); + +/** + * @brief Generates tests for isrpipe/read_timeout.h + * + * @return embUnit tests if successful, NULL if not. + */ +Test *tests_isrpipe_read_timeout_tests(void); + +#ifdef __cplusplus +} +#endif + +/** @} */