diff --git a/tests/subsys/modem/modem_pipe/CMakeLists.txt b/tests/subsys/modem/modem_pipe/CMakeLists.txt new file mode 100644 index 00000000000..9d160a67720 --- /dev/null +++ b/tests/subsys/modem/modem_pipe/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) 2023 Trackunit Corporation +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(modem_pipe_test) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/subsys/modem/modem_pipe/prj.conf b/tests/subsys/modem/modem_pipe/prj.conf new file mode 100644 index 00000000000..ab3228738b9 --- /dev/null +++ b/tests/subsys/modem/modem_pipe/prj.conf @@ -0,0 +1,8 @@ +# Copyright (c) 2023 Trackunit Corporation +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_NO_OPTIMIZATIONS=y +CONFIG_MODEM_MODULES=y +CONFIG_MODEM_PIPE=y +CONFIG_EVENTS=y +CONFIG_ZTEST=y diff --git a/tests/subsys/modem/modem_pipe/src/main.c b/tests/subsys/modem/modem_pipe/src/main.c new file mode 100644 index 00000000000..07cc93decfe --- /dev/null +++ b/tests/subsys/modem/modem_pipe/src/main.c @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2023 Trackunit Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/*************************************************************************************************/ +/* Dependencies */ +/*************************************************************************************************/ +#include +#include +#include +#include +#include + +#define TEST_MODEM_PIPE_EVENT_OPENED_BIT 0 +#define TEST_MODEM_PIPE_EVENT_TRANSMIT_IDLE_BIT 1 +#define TEST_MODEM_PIPE_EVENT_RECEIVE_READY_BIT 2 +#define TEST_MODEM_PIPE_EVENT_CLOSED_BIT 3 +#define TEST_MODEM_PIPE_NOTIFY_TIMEOUT K_MSEC(10) +#define TEST_MODEM_PIPE_WAIT_TIMEOUT K_MSEC(20) + +/*************************************************************************************************/ +/* Fake modem_pipe backend */ +/*************************************************************************************************/ +struct modem_backend_fake { + struct modem_pipe pipe; + + struct k_work_delayable opened_dwork; + struct k_work_delayable transmit_idle_dwork; + struct k_work_delayable closed_dwork; + + const uint8_t *transmit_buffer; + size_t transmit_buffer_size; + + uint8_t *receive_buffer; + size_t receive_buffer_size; + + uint8_t synchronous : 1; + uint8_t open_called : 1; + uint8_t transmit_called : 1; + uint8_t receive_called : 1; + uint8_t close_called : 1; +}; + +static void modem_backend_fake_opened_handler(struct k_work *item) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(item); + struct modem_backend_fake *backend = + CONTAINER_OF(dwork, struct modem_backend_fake, opened_dwork); + + modem_pipe_notify_opened(&backend->pipe); +} + +static int modem_backend_fake_open(void *data) +{ + struct modem_backend_fake *backend = data; + + backend->open_called = true; + + if (backend->synchronous) { + modem_pipe_notify_opened(&backend->pipe); + } else { + k_work_schedule(&backend->opened_dwork, TEST_MODEM_PIPE_NOTIFY_TIMEOUT); + } + + return 0; +} + +static void modem_backend_fake_transmit_idle_handler(struct k_work *item) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(item); + struct modem_backend_fake *backend = + CONTAINER_OF(dwork, struct modem_backend_fake, transmit_idle_dwork); + + modem_pipe_notify_transmit_idle(&backend->pipe); +} + +static int modem_backend_fake_transmit(void *data, const uint8_t *buf, size_t size) +{ + struct modem_backend_fake *backend = data; + + backend->transmit_called = true; + backend->transmit_buffer = buf; + backend->transmit_buffer_size = size; + + if (backend->synchronous) { + modem_pipe_notify_transmit_idle(&backend->pipe); + } else { + k_work_schedule(&backend->transmit_idle_dwork, TEST_MODEM_PIPE_NOTIFY_TIMEOUT); + } + + return size; +} + +static int modem_backend_fake_receive(void *data, uint8_t *buf, size_t size) +{ + struct modem_backend_fake *backend = data; + + backend->receive_called = true; + backend->receive_buffer = buf; + backend->receive_buffer_size = size; + return size; +} + +static void modem_backend_fake_closed_handler(struct k_work *item) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(item); + struct modem_backend_fake *backend = + CONTAINER_OF(dwork, struct modem_backend_fake, closed_dwork); + + modem_pipe_notify_closed(&backend->pipe); +} + +static int modem_backend_fake_close(void *data) +{ + struct modem_backend_fake *backend = data; + + backend->close_called = true; + + if (backend->synchronous) { + modem_pipe_notify_closed(&backend->pipe); + } else { + k_work_schedule(&backend->closed_dwork, TEST_MODEM_PIPE_NOTIFY_TIMEOUT); + } + + return 0; +} + +static struct modem_pipe_api modem_backend_fake_api = { + .open = modem_backend_fake_open, + .transmit = modem_backend_fake_transmit, + .receive = modem_backend_fake_receive, + .close = modem_backend_fake_close, +}; + +static struct modem_pipe *modem_backend_fake_init(struct modem_backend_fake *backend) +{ + k_work_init_delayable(&backend->opened_dwork, + modem_backend_fake_opened_handler); + k_work_init_delayable(&backend->transmit_idle_dwork, + modem_backend_fake_transmit_idle_handler); + k_work_init_delayable(&backend->closed_dwork, + modem_backend_fake_closed_handler); + + modem_pipe_init(&backend->pipe, backend, &modem_backend_fake_api); + return &backend->pipe; +} + +static void modem_backend_fake_reset(struct modem_backend_fake *backend) +{ + backend->transmit_buffer = NULL; + backend->transmit_buffer_size = 0; + backend->receive_buffer = NULL; + backend->transmit_buffer_size = 0; + backend->open_called = false; + backend->transmit_called = false; + backend->receive_called = false; + backend->close_called = false; +} + +static void modem_backend_fake_set_sync(struct modem_backend_fake *backend, bool sync) +{ + backend->synchronous = sync; +} + +/*************************************************************************************************/ +/* Instances */ +/*************************************************************************************************/ +static struct modem_backend_fake test_backend; +static struct modem_pipe *test_pipe; +static uint32_t test_user_data; +static atomic_t test_state; +static uint8_t test_buffer[4]; +static size_t test_buffer_size = sizeof(test_buffer); + +/*************************************************************************************************/ +/* Callbacks */ +/*************************************************************************************************/ +static void modem_pipe_fake_handler(struct modem_pipe *pipe, enum modem_pipe_event event, + void *user_data) +{ + __ASSERT(pipe == test_pipe, "Incorrect pipe provided with callback"); + __ASSERT(user_data == (void *)&test_user_data, "Incorrect user data ptr"); + + switch (event) { + case MODEM_PIPE_EVENT_OPENED: + atomic_set_bit(&test_state, TEST_MODEM_PIPE_EVENT_OPENED_BIT); + break; + + case MODEM_PIPE_EVENT_RECEIVE_READY: + atomic_set_bit(&test_state, TEST_MODEM_PIPE_EVENT_RECEIVE_READY_BIT); + break; + + case MODEM_PIPE_EVENT_TRANSMIT_IDLE: + atomic_set_bit(&test_state, TEST_MODEM_PIPE_EVENT_TRANSMIT_IDLE_BIT); + break; + + case MODEM_PIPE_EVENT_CLOSED: + atomic_set_bit(&test_state, TEST_MODEM_PIPE_EVENT_CLOSED_BIT); + break; + } +} + +static void test_reset(void) +{ + modem_backend_fake_reset(&test_backend); + atomic_set(&test_state, 0); +} + +static void *modem_backend_fake_setup(void) +{ + test_pipe = modem_backend_fake_init(&test_backend); + return NULL; +} + +static void modem_backend_fake_before(void *f) +{ + modem_backend_fake_set_sync(&test_backend, false); + modem_pipe_attach(test_pipe, modem_pipe_fake_handler, &test_user_data); + test_reset(); +} + +static void modem_backend_fake_after(void *f) +{ + __ASSERT(modem_pipe_close(test_pipe) == 0, "Failed to close pipe"); + modem_pipe_release(test_pipe); +} + +/* Opening pipe shall raise events OPENED and TRANSMIT_IDLE */ +static void test_pipe_open(void) +{ + zassert_ok(modem_pipe_open(test_pipe), "Failed to open pipe"); + zassert_true(test_backend.open_called, "open was not called"); + zassert_equal(atomic_get(&test_state), + BIT(TEST_MODEM_PIPE_EVENT_OPENED_BIT) | + BIT(TEST_MODEM_PIPE_EVENT_TRANSMIT_IDLE_BIT), + "Unexpected state %u", atomic_get(&test_state)); +} + +/* Re-opening pipe shall have no effect */ +static void test_pipe_reopen(void) +{ + zassert_ok(modem_pipe_open(test_pipe), "Failed to re-open pipe"); + zassert_false(test_backend.open_called, "open was called"); + zassert_equal(atomic_get(&test_state), 0, + "Unexpected state %u", atomic_get(&test_state)); +} + +/* Closing pipe shall raise event CLOSED */ +static void test_pipe_close(void) +{ + zassert_ok(modem_pipe_close(test_pipe), "Failed to close pipe"); + zassert_true(test_backend.close_called, "close was not called"); + zassert_equal(atomic_get(&test_state), BIT(TEST_MODEM_PIPE_EVENT_CLOSED_BIT), + "Unexpected state %u", atomic_get(&test_state)); +} + +/* Re-closing pipe shall have no effect */ +static void test_pipe_reclose(void) +{ + zassert_ok(modem_pipe_close(test_pipe), "Failed to re-close pipe"); + zassert_false(test_backend.close_called, "close was called"); + zassert_equal(atomic_get(&test_state), 0, + "Unexpected state %u", atomic_get(&test_state)); +} + +static void test_pipe_async_transmit(void) +{ + zassert_equal(modem_pipe_transmit(test_pipe, test_buffer, test_buffer_size), + test_buffer_size, "Failed to transmit"); + zassert_true(test_backend.transmit_called, "transmit was not called"); + zassert_equal(test_backend.transmit_buffer, test_buffer, "Incorrect buffer"); + zassert_equal(test_backend.transmit_buffer_size, test_buffer_size, + "Incorrect buffer size"); + zassert_equal(atomic_get(&test_state), 0, "Unexpected state %u", + atomic_get(&test_state)); + k_sleep(TEST_MODEM_PIPE_WAIT_TIMEOUT); + zassert_equal(atomic_get(&test_state), BIT(TEST_MODEM_PIPE_EVENT_TRANSMIT_IDLE_BIT), + "Unexpected state %u", atomic_get(&test_state)); +} + +static void test_pipe_sync_transmit(void) +{ + zassert_equal(modem_pipe_transmit(test_pipe, test_buffer, test_buffer_size), + test_buffer_size, "Failed to transmit"); + zassert_true(test_backend.transmit_called, "transmit was not called"); + zassert_equal(test_backend.transmit_buffer, test_buffer, "Incorrect buffer"); + zassert_equal(test_backend.transmit_buffer_size, test_buffer_size, + "Incorrect buffer size"); + zassert_equal(atomic_get(&test_state), BIT(TEST_MODEM_PIPE_EVENT_TRANSMIT_IDLE_BIT), + "Unexpected state %u", atomic_get(&test_state)); +} + +static void test_pipe_attach_receive_not_ready_transmit_idle(void) +{ + modem_pipe_attach(test_pipe, modem_pipe_fake_handler, &test_user_data); + zassert_equal(atomic_get(&test_state), BIT(TEST_MODEM_PIPE_EVENT_TRANSMIT_IDLE_BIT), + "Unexpected state %u", atomic_get(&test_state)); +} + +static void test_pipe_attach_receive_ready_transmit_idle(void) +{ + modem_pipe_attach(test_pipe, modem_pipe_fake_handler, &test_user_data); + zassert_equal(atomic_get(&test_state), + BIT(TEST_MODEM_PIPE_EVENT_TRANSMIT_IDLE_BIT) | + BIT(TEST_MODEM_PIPE_EVENT_RECEIVE_READY_BIT), + "Unexpected state %u", atomic_get(&test_state)); +} + +static void test_pipe_receive(void) +{ + zassert_equal(modem_pipe_receive(test_pipe, test_buffer, test_buffer_size), + test_buffer_size, "Failed to receive"); + zassert_true(test_backend.receive_called, "receive was not called"); + zassert_equal(test_backend.receive_buffer, test_buffer, "Incorrect buffer"); + zassert_equal(test_backend.receive_buffer_size, test_buffer_size, + "Incorrect buffer size"); + zassert_equal(atomic_get(&test_state), 0, "Unexpected state %u", + atomic_get(&test_state)); +} + +static void test_pipe_notify_receive_ready(void) +{ + modem_pipe_notify_receive_ready(test_pipe); + zassert_equal(atomic_get(&test_state), BIT(TEST_MODEM_PIPE_EVENT_RECEIVE_READY_BIT), + "Unexpected state %u", atomic_get(&test_state)); +} + +ZTEST(modem_pipe, test_async_open_close) +{ + test_pipe_open(); + test_reset(); + test_pipe_reopen(); + test_reset(); + test_pipe_close(); + test_reset(); + test_pipe_reclose(); +} + +ZTEST(modem_pipe, test_sync_open_close) +{ + modem_backend_fake_set_sync(&test_backend, true); + test_pipe_open(); + test_reset(); + test_pipe_reopen(); + test_reset(); + test_pipe_close(); + test_reset(); + test_pipe_reclose(); +} + +ZTEST(modem_pipe, test_async_transmit) +{ + test_pipe_open(); + test_reset(); + test_pipe_async_transmit(); +} + +ZTEST(modem_pipe, test_sync_transmit) +{ + modem_backend_fake_set_sync(&test_backend, true); + test_pipe_open(); + test_reset(); + test_pipe_sync_transmit(); +} + +ZTEST(modem_pipe, test_attach) +{ + test_pipe_open(); + + /* + * Attaching pipe shall reinvoke TRANSMIT IDLE, but not RECEIVE READY as + * receive is not ready. + */ + test_reset(); + test_pipe_attach_receive_not_ready_transmit_idle(); + + /* + * Notify receive ready and expect receive ready to be re-invoked every + * time the pipe is attached to. + */ + test_reset(); + test_pipe_notify_receive_ready(); + test_reset(); + test_pipe_attach_receive_ready_transmit_idle(); + test_reset(); + test_pipe_attach_receive_ready_transmit_idle(); + + /* + * Receiving data from the pipe shall clear the receive ready state, stopping + * the invocation of receive ready on attach. + */ + test_reset(); + test_pipe_receive(); + test_reset(); + test_pipe_attach_receive_not_ready_transmit_idle(); +} + +ZTEST_SUITE(modem_pipe, NULL, modem_backend_fake_setup, modem_backend_fake_before, + modem_backend_fake_after, NULL); diff --git a/tests/subsys/modem/modem_pipe/testcase.yaml b/tests/subsys/modem/modem_pipe/testcase.yaml new file mode 100644 index 00000000000..39a8c8e6c2c --- /dev/null +++ b/tests/subsys/modem/modem_pipe/testcase.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2023 Trackunit Corporation +# SPDX-License-Identifier: Apache-2.0 + +tests: + modem.modem_pipe: + tags: modem_pipe + harness: ztest + platform_allow: native_sim