Skip to content

Commit 4418620

Browse files
committed
test: add unit tests for send_data_callback_func FAIL path
Before the fix, TRANSMIT_COMPLETE_FAIL with resend_counter==0 left the session in send_data_list, causing every subsequent SEND_EVENT_SEND_NEXT_LL to re-drive the same stuck session through Transport Service. The implementation adds UNIT_TEST-guarded seam functions to ZW_SendDataAppl.c that expose private/static states. The test uses wraps to replace process_post and zw_frame_buffer_free. Choose this solution over CMock for sake of simplicity. Relates-to: ZGW-3457 Origin: #47 Signed-off-by: Laudin Molina Troconis <laudin.molinatroconis@silabs.com>
1 parent 69e1881 commit 4418620

3 files changed

Lines changed: 245 additions & 0 deletions

File tree

libs2/test/CMakeLists.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,34 @@ if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "C51")
148148
${CMAKE_SOURCE_DIR}/Z-Wave/include)
149149
endif()
150150

151+
# ZW_SendDataAppl.c is compiled directly into the test (not taken from the
152+
# pre-built zipgateway-lib) so that the -DUNIT_TEST flag (set globally by
153+
# add_definitions at the top of this file) is in effect. This is required
154+
# to compile the UNIT_TEST seam functions that expose private state.
155+
#
156+
# The remaining zipgateway-lib symbols that ZW_SendDataAppl.c needs are
157+
# provided either by linking zipgateway-lib or by --wrap stubs in the test.
158+
# ---------------------------------------------------------------------------
159+
if (${CMAKE_PROJECT_NAME} MATCHES "zipgateway")
160+
add_unity_test(NAME test_send_data_callback
161+
FILES test_send_data_callback.c
162+
${CMAKE_SOURCE_DIR}/src/transport/ZW_SendDataAppl.c
163+
${CMAKE_SOURCE_DIR}/test/zipgateway_main_stubs.c
164+
LIBRARIES zipgateway-lib)
165+
166+
set_target_properties(test_send_data_callback PROPERTIES LINK_FLAGS
167+
"-Wl,-wrap=process_post \
168+
-Wl,-wrap=process_exit \
169+
-Wl,-wrap=process_start \
170+
-Wl,-wrap=get_queue_state \
171+
-Wl,-wrap=zw_frame_buffer_free \
172+
-Wl,-wrap=etimer_stop \
173+
-Wl,-wrap=etimer_set \
174+
-Wl,-wrap=etimer_expired \
175+
-Wl,-wrap=sec0_abort_all_tx_sessions \
176+
-Wl,-wrap=ima_send_data_done")
177+
endif()
178+
151179
add_definitions( -DRANDLEN=64 )
152180
add_unity_test(NAME test_ctr_dbrg FILES test_ctr_dbrg.c ../crypto/ctr_drbg/ctr_drbg.c ../crypto/aes/aes.c)
153181

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/* SPDX-License-Identifier: LicenseRef-MSLA
2+
* SPDX-FileCopyrightText: Silicon Laboratories Inc. https://www.silabs.com
3+
*/
4+
5+
#include <stddef.h>
6+
#include <stdint.h>
7+
#include <string.h>
8+
9+
#include "unity.h"
10+
11+
#include "ZW_SendDataAppl.h" /* public API + ts_param_t */
12+
#include "ZW_transport_api.h" /* TRANSMIT_COMPLETE_*, TX_STATUS_TYPE */
13+
#include "node_queue.h" /* en_queue_state, QS_IDLE */
14+
#include "zw_frame_buffer.h" /* zw_frame_buffer_element_t */
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+
(void)tx;
43+
app_cb_count++;
44+
app_cb_status = status;
45+
app_cb_user = user;
46+
}
47+
48+
static int fb_free_count;
49+
static zw_frame_buffer_element_t *fb_free_ptr;
50+
51+
void __wrap_zw_frame_buffer_free(zw_frame_buffer_element_t *e) {
52+
fb_free_count++;
53+
fb_free_ptr = e;
54+
}
55+
56+
static int pp_count;
57+
static process_event_t pp_last_event;
58+
59+
#define EXPECTED_SEND_NEXT_LL ((process_event_t)1)
60+
61+
int __wrap_process_post(struct process *p, process_event_t ev, void *data) {
62+
(void)p;
63+
(void)data;
64+
pp_count++;
65+
pp_last_event = ev;
66+
return 0;
67+
}
68+
69+
enum en_queue_state __wrap_get_queue_state(void) { return QS_IDLE; }
70+
71+
void __wrap_process_exit(struct process *p) { (void)p; }
72+
void __wrap_process_start(struct process *p, const char *arg) {
73+
(void)p;
74+
(void)arg;
75+
}
76+
77+
void __wrap_etimer_stop(struct etimer *t) { (void)t; }
78+
void __wrap_etimer_set(struct etimer *t, unsigned long i) {
79+
(void)t;
80+
(void)i;
81+
}
82+
int __wrap_etimer_expired(struct etimer *t) {
83+
(void)t;
84+
return 1;
85+
}
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+
(void)n;
90+
(void)s;
91+
(void)t;
92+
}
93+
94+
static void reset_spies(void) {
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+
reset_spies();
113+
ZW_SendDataAppl_init();
114+
}
115+
116+
void tearDown(void) {}
117+
118+
void test_ts_should_pop_session_and_advance_when_transmit_complete_fail_and_resend_zero(void) {
119+
static zw_frame_buffer_element_t fb_a, fb_b;
120+
int sentinel = 0xBEEF;
121+
122+
test_session_t *session_a = (test_session_t *)ZW_SendDataAppl_alloc_session();
123+
test_session_t *session_b = (test_session_t *)ZW_SendDataAppl_alloc_session();
124+
TEST_ASSERT_NOT_NULL_MESSAGE(session_a, "memb_alloc failed — session pool exhausted");
125+
TEST_ASSERT_NOT_NULL_MESSAGE(session_b, "memb_alloc failed — session pool exhausted");
126+
127+
session_a->fb = &fb_a;
128+
session_a->user = &sentinel;
129+
session_a->callback = spy_app_callback;
130+
131+
session_b->fb = &fb_b;
132+
session_b->user = NULL;
133+
session_b->callback = NULL;
134+
135+
/* head = A, A->next = B */
136+
ZW_SendDataAppl_push_session(session_b);
137+
ZW_SendDataAppl_push_session(session_a);
138+
139+
TEST_ASSERT_EQUAL_PTR_MESSAGE(session_a, ZW_SendDataAppl_list_head(),
140+
"Pre-condition: A must be at the head before the failure");
141+
142+
ZW_SendDataAppl_set_lock_ll(1);
143+
ZW_SendDataAppl_set_resend_counter(0);
144+
145+
ZW_SendDataAppl_trigger_fail_for_test();
146+
147+
TEST_ASSERT_EQUAL_PTR_MESSAGE(session_b, ZW_SendDataAppl_list_head(),
148+
"Session B must be the head after session A is popped on FAIL");
149+
150+
TEST_ASSERT_EQUAL_INT_MESSAGE(1, fb_free_count, "Exactly one buffer must be freed (session A's)");
151+
TEST_ASSERT_EQUAL_PTR_MESSAGE(&fb_a, fb_free_ptr,
152+
"The freed buffer must be session A's, not B's");
153+
154+
TEST_ASSERT_EQUAL_INT_MESSAGE(1, app_cb_count,
155+
"Application callback must be called exactly once");
156+
TEST_ASSERT_EQUAL_UINT8_MESSAGE(TRANSMIT_COMPLETE_FAIL, app_cb_status,
157+
"Application callback must receive TRANSMIT_COMPLETE_FAIL");
158+
TEST_ASSERT_EQUAL_PTR_MESSAGE(&sentinel, app_cb_user,
159+
"Application callback must receive the original user pointer");
160+
161+
TEST_ASSERT_EQUAL_INT_MESSAGE(1, pp_count,
162+
"SEND_EVENT_SEND_NEXT_LL must be posted after A is removed");
163+
TEST_ASSERT_EQUAL_MESSAGE(EXPECTED_SEND_NEXT_LL, pp_last_event,
164+
"The posted event must be SEND_EVENT_SEND_NEXT_LL");
165+
}
166+
167+
void test_ts_should_not_pop_session_when_resend_is_needed() {
168+
static zw_frame_buffer_element_t fb;
169+
170+
test_session_t *s = (test_session_t *)ZW_SendDataAppl_alloc_session();
171+
TEST_ASSERT_NOT_NULL(s);
172+
s->fb = &fb;
173+
s->user = NULL;
174+
s->callback = spy_app_callback;
175+
ZW_SendDataAppl_push_session(s);
176+
177+
ZW_SendDataAppl_set_lock_ll(1);
178+
ZW_SendDataAppl_set_resend_counter(1);
179+
180+
ZW_SendDataAppl_trigger_fail_for_test();
181+
182+
TEST_ASSERT_EQUAL_PTR_MESSAGE(s, ZW_SendDataAppl_list_head(),
183+
"Session must remain in send_data_list when resend_counter > 0");
184+
185+
TEST_ASSERT_EQUAL_INT_MESSAGE(0, fb_free_count,
186+
"zw_frame_buffer_free must NOT be called when resend_counter > 0");
187+
188+
TEST_ASSERT_EQUAL_INT_MESSAGE(0, app_cb_count,
189+
"Application callback must NOT be called when resend_counter > 0");
190+
191+
TEST_ASSERT_EQUAL_INT_MESSAGE(0, pp_count,
192+
"process_post must NOT be called when resend_counter > 0");
193+
194+
TEST_ASSERT_EQUAL_UINT8_MESSAGE(0, ZW_SendDataAppl_get_lock_ll(),
195+
"lock_ll must be FALSE after the callback (retry path)");
196+
}

src/transport/ZW_SendDataAppl.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,3 +912,24 @@ PROCESS_THREAD(ZW_SendDataAppl_process, ev, data)
912912
}
913913
PROCESS_END();
914914
}
915+
916+
#ifdef UNIT_TEST
917+
void ZW_SendDataAppl_set_lock_ll(uint8_t value) { lock_ll = value; }
918+
void ZW_SendDataAppl_set_resend_counter(uint8_t value) { resend_counter = value; }
919+
uint8_t ZW_SendDataAppl_get_lock_ll(void) { return lock_ll; }
920+
921+
void *ZW_SendDataAppl_alloc_session(void) { return memb_alloc(&session_memb); }
922+
void ZW_SendDataAppl_push_session(void *s) { list_push(send_data_list, s); }
923+
void *ZW_SendDataAppl_list_head(void) { return list_head(send_data_list); }
924+
925+
/*
926+
* Drive send_data_callback_func(TRANSMIT_COMPLETE_FAIL, NULL) directly.
927+
* This is the same call the emergency timer makes when the NCP misses a
928+
* callback. Exposing it here lets tests trigger the FAIL path without
929+
* needing a running Contiki scheduler.
930+
*/
931+
void ZW_SendDataAppl_trigger_fail_for_test(void)
932+
{
933+
send_data_callback_func(TRANSMIT_COMPLETE_FAIL, NULL);
934+
}
935+
#endif /* UNIT_TEST */

0 commit comments

Comments
 (0)