/* * 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, K_SECONDS(10)) == 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, K_SECONDS(10)), "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, K_SECONDS(10)), "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, K_SECONDS(10)), "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, K_SECONDS(10)), "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);