|
| 1 | +/* SPDX-License-Identifier: LicenseRef-MSLA |
| 2 | + * SPDX-FileCopyrightText: Silicon Laboratories Inc. https://www.silabs.com |
| 3 | + */ |
| 4 | + |
| 5 | +#include <stdint.h> |
| 6 | +#include <string.h> |
| 7 | +#include <stddef.h> |
| 8 | + |
| 9 | +#include "unity.h" |
| 10 | + |
| 11 | +#include "ZW_SendDataAppl.h" /* public API + ts_param_t */ |
| 12 | +#include "zw_frame_buffer.h" /* zw_frame_buffer_element_t */ |
| 13 | +#include "ZW_transport_api.h" /* TRANSMIT_COMPLETE_*, TX_STATUS_TYPE */ |
| 14 | +#include "node_queue.h" /* en_queue_state, QS_IDLE */ |
| 15 | + |
| 16 | +/* |
| 17 | + * Test-seam declarations |
| 18 | + * |
| 19 | + * These are compiled into ZW_SendDataAppl.c only when -DUNIT_TEST is set |
| 20 | + * (which libs2/test/CMakeLists.txt sets globally with add_definitions). |
| 21 | + * -------------------------------------------------------------------------*/ |
| 22 | +void ZW_SendDataAppl_set_lock_ll(uint8_t v); |
| 23 | +void ZW_SendDataAppl_set_resend_counter(uint8_t v); |
| 24 | +uint8_t ZW_SendDataAppl_get_lock_ll(void); |
| 25 | +void *ZW_SendDataAppl_alloc_session(void); |
| 26 | +void ZW_SendDataAppl_push_session(void *s); |
| 27 | +void *ZW_SendDataAppl_list_head(void); |
| 28 | +void ZW_SendDataAppl_trigger_fail_for_test(void); |
| 29 | + |
| 30 | +typedef struct { |
| 31 | + void *next; |
| 32 | + zw_frame_buffer_element_t *fb; |
| 33 | + void *user; |
| 34 | + ZW_SendDataAppl_Callback_t callback; |
| 35 | +} test_session_t; |
| 36 | + |
| 37 | +static int app_cb_count; |
| 38 | +static uint8_t app_cb_status; |
| 39 | +static void *app_cb_user; |
| 40 | + |
| 41 | +static void spy_app_callback(uint8_t status, void *user, TX_STATUS_TYPE *tx) |
| 42 | +{ |
| 43 | + (void)tx; |
| 44 | + app_cb_count++; |
| 45 | + app_cb_status = status; |
| 46 | + app_cb_user = user; |
| 47 | +} |
| 48 | + |
| 49 | +static int fb_free_count; |
| 50 | +static zw_frame_buffer_element_t *fb_free_ptr; |
| 51 | + |
| 52 | +void __wrap_zw_frame_buffer_free(zw_frame_buffer_element_t *e) |
| 53 | +{ |
| 54 | + fb_free_count++; |
| 55 | + fb_free_ptr = e; |
| 56 | +} |
| 57 | + |
| 58 | +static int pp_count; |
| 59 | +static process_event_t pp_last_event; |
| 60 | + |
| 61 | +#define EXPECTED_SEND_NEXT_LL ((process_event_t)1) |
| 62 | + |
| 63 | +int __wrap_process_post(struct process *p, process_event_t ev, void *data) |
| 64 | +{ |
| 65 | + (void)p; |
| 66 | + (void)data; |
| 67 | + pp_count++; |
| 68 | + pp_last_event = ev; |
| 69 | + return 0; |
| 70 | +} |
| 71 | + |
| 72 | +enum en_queue_state __wrap_get_queue_state(void) |
| 73 | +{ |
| 74 | + return QS_IDLE; |
| 75 | +} |
| 76 | + |
| 77 | +void __wrap_process_exit(struct process *p) { (void)p; } |
| 78 | +void __wrap_process_start(struct process *p, const char *arg) { |
| 79 | + (void)p; |
| 80 | + (void)arg; |
| 81 | +} |
| 82 | + |
| 83 | +void __wrap_etimer_stop(struct etimer *t) { (void)t; } |
| 84 | +void __wrap_etimer_set(struct etimer *t, unsigned long i) { (void)t; (void)i; } |
| 85 | +int __wrap_etimer_expired(struct etimer *t) { (void)t; return 1; } |
| 86 | + |
| 87 | +void __wrap_sec0_abort_all_tx_sessions(void) {} |
| 88 | +void __wrap_ima_send_data_done(uint16_t n, uint8_t s, TX_STATUS_TYPE *t) |
| 89 | +{ |
| 90 | + (void)n; (void)s; (void)t; |
| 91 | +} |
| 92 | + |
| 93 | +static void reset_spies(void) |
| 94 | +{ |
| 95 | + app_cb_count = 0; |
| 96 | + app_cb_status = 0xFF; |
| 97 | + app_cb_user = NULL; |
| 98 | + |
| 99 | + fb_free_count = 0; |
| 100 | + fb_free_ptr = NULL; |
| 101 | + |
| 102 | + pp_count = 0; |
| 103 | + pp_last_event = 0xFF; |
| 104 | +} |
| 105 | + |
| 106 | +/* ------------------------------------------------------------------------- |
| 107 | + * ZW_SendDataAppl_init() sets lock=FALSE, lock_ll=FALSE, clears the list |
| 108 | + * and reinitialises the memb pool. It also (re)starts the Contiki process, |
| 109 | + * which is stubbed to a no-op here. |
| 110 | + * -------------------------------------------------------------------------*/ |
| 111 | +void setUp(void) |
| 112 | +{ |
| 113 | + reset_spies(); |
| 114 | + ZW_SendDataAppl_init(); |
| 115 | +} |
| 116 | + |
| 117 | +void tearDown(void) {} |
| 118 | + |
| 119 | +void test_ts_should_pop_session_and_advance_when_transmit_complete_fail_and_resend_zero(void) |
| 120 | +{ |
| 121 | + static zw_frame_buffer_element_t fb_a, fb_b; |
| 122 | + int sentinel = 0xBEEF; |
| 123 | + |
| 124 | + test_session_t *session_a = (test_session_t *)ZW_SendDataAppl_alloc_session(); |
| 125 | + test_session_t *session_b = (test_session_t *)ZW_SendDataAppl_alloc_session(); |
| 126 | + TEST_ASSERT_NOT_NULL_MESSAGE(session_a, "memb_alloc failed — session pool exhausted"); |
| 127 | + TEST_ASSERT_NOT_NULL_MESSAGE(session_b, "memb_alloc failed — session pool exhausted"); |
| 128 | + |
| 129 | + session_a->fb = &fb_a; |
| 130 | + session_a->user = &sentinel; |
| 131 | + session_a->callback = spy_app_callback; |
| 132 | + |
| 133 | + session_b->fb = &fb_b; |
| 134 | + session_b->user = NULL; |
| 135 | + session_b->callback = NULL; |
| 136 | + |
| 137 | + /* head = A, A->next = B */ |
| 138 | + ZW_SendDataAppl_push_session(session_b); |
| 139 | + ZW_SendDataAppl_push_session(session_a); |
| 140 | + |
| 141 | + TEST_ASSERT_EQUAL_PTR_MESSAGE(session_a, ZW_SendDataAppl_list_head(), |
| 142 | + "Pre-condition: A must be at the head before the failure"); |
| 143 | + |
| 144 | + ZW_SendDataAppl_set_lock_ll(1); |
| 145 | + ZW_SendDataAppl_set_resend_counter(0); |
| 146 | + |
| 147 | + ZW_SendDataAppl_trigger_fail_for_test(); |
| 148 | + |
| 149 | + TEST_ASSERT_EQUAL_PTR_MESSAGE(session_b, ZW_SendDataAppl_list_head(), |
| 150 | + "Session B must be the head after session A is popped on FAIL"); |
| 151 | + |
| 152 | + TEST_ASSERT_EQUAL_INT_MESSAGE(1, fb_free_count, |
| 153 | + "Exactly one buffer must be freed (session A's)"); |
| 154 | + TEST_ASSERT_EQUAL_PTR_MESSAGE(&fb_a, fb_free_ptr, |
| 155 | + "The freed buffer must be session A's, not B's"); |
| 156 | + |
| 157 | + TEST_ASSERT_EQUAL_INT_MESSAGE(1, app_cb_count, |
| 158 | + "Application callback must be called exactly once"); |
| 159 | + TEST_ASSERT_EQUAL_UINT8_MESSAGE(TRANSMIT_COMPLETE_FAIL, app_cb_status, |
| 160 | + "Application callback must receive TRANSMIT_COMPLETE_FAIL"); |
| 161 | + TEST_ASSERT_EQUAL_PTR_MESSAGE(&sentinel, app_cb_user, |
| 162 | + "Application callback must receive the original user pointer"); |
| 163 | + |
| 164 | + TEST_ASSERT_EQUAL_INT_MESSAGE(1, pp_count, |
| 165 | + "SEND_EVENT_SEND_NEXT_LL must be posted after A is removed"); |
| 166 | + TEST_ASSERT_EQUAL_MESSAGE(EXPECTED_SEND_NEXT_LL, pp_last_event, |
| 167 | + "The posted event must be SEND_EVENT_SEND_NEXT_LL"); |
| 168 | +} |
| 169 | + |
| 170 | +void test_ts_should_not_pop_session_when_resend_is_needed() |
| 171 | +{ |
| 172 | + static zw_frame_buffer_element_t fb; |
| 173 | + |
| 174 | + test_session_t *s = (test_session_t *)ZW_SendDataAppl_alloc_session(); |
| 175 | + TEST_ASSERT_NOT_NULL(s); |
| 176 | + s->fb = &fb; |
| 177 | + s->user = NULL; |
| 178 | + s->callback = spy_app_callback; |
| 179 | + ZW_SendDataAppl_push_session(s); |
| 180 | + |
| 181 | + ZW_SendDataAppl_set_lock_ll(1); |
| 182 | + ZW_SendDataAppl_set_resend_counter(1); |
| 183 | + |
| 184 | + ZW_SendDataAppl_trigger_fail_for_test(); |
| 185 | + |
| 186 | + TEST_ASSERT_EQUAL_PTR_MESSAGE(s, ZW_SendDataAppl_list_head(), |
| 187 | + "Session must remain in send_data_list when resend_counter > 0"); |
| 188 | + |
| 189 | + TEST_ASSERT_EQUAL_INT_MESSAGE(0, fb_free_count, |
| 190 | + "zw_frame_buffer_free must NOT be called when resend_counter > 0"); |
| 191 | + |
| 192 | + TEST_ASSERT_EQUAL_INT_MESSAGE(0, app_cb_count, |
| 193 | + "Application callback must NOT be called when resend_counter > 0"); |
| 194 | + |
| 195 | + TEST_ASSERT_EQUAL_INT_MESSAGE(0, pp_count, |
| 196 | + "process_post must NOT be called when resend_counter > 0"); |
| 197 | + |
| 198 | + TEST_ASSERT_EQUAL_UINT8_MESSAGE(0, ZW_SendDataAppl_get_lock_ll(), |
| 199 | + "lock_ll must be FALSE after the callback (retry path)"); |
| 200 | +} |
0 commit comments