Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion helper.mk
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ docker_arch_i386=i386
docker_arch?=$(docker_arch_${target_debian_arch})

packages?=make cmake time file git sudo
packages+=build-essential pkg-config bison flex python
packages+=build-essential pkg-config bison flex python ruby
packages+=libusb-1.0-0-dev libssl-dev libxml2-dev libjson-c-dev
packages+=doxygen xsltproc plantuml roffit
packages+=radvd parprouted bridge-utils net-tools zip unzip
Expand Down
142 changes: 142 additions & 0 deletions libs2/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,148 @@ if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "C51")
target_link_libraries(new_test_t2 zipgateway-lib)
endif()
add_test(test_transport_service2 new_test_t2)

set(CMOCK_RB "" CACHE FILEPATH "Path to CMock lib/cmock.rb used for local mock regeneration")
set(CMOCK_VERSION "2.4.6" CACHE STRING "CMock version used for regeneration")
set(CMOCK_ARCHIVE "${CMAKE_BINARY_DIR}/tools/cmock-v${CMOCK_VERSION}.tar.gz")
set(CMOCK_ARCHIVE_SHA256 "7b4b0a1ca9ecd4d461e3a442b4b2c98c37e451d8966e636483142fdf49667ed7"
CACHE STRING "Expected SHA256 for CMock archive")
set(CMOCK_ROOT "${CMAKE_BINARY_DIR}/tools/CMock-${CMOCK_VERSION}")
set(CMOCK_RB_DEFAULT "${CMOCK_ROOT}/lib/cmock.rb")
if(CMOCK_RB STREQUAL "")
if(NOT EXISTS "${CMOCK_RB_DEFAULT}")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/tools")
if(NOT EXISTS "${CMOCK_ARCHIVE}")
message(STATUS "Downloading CMock v${CMOCK_VERSION} for mock regeneration")
file(DOWNLOAD
"https://github.com/ThrowTheSwitch/CMock/archive/refs/tags/v${CMOCK_VERSION}.tar.gz"
"${CMOCK_ARCHIVE}"
SHOW_PROGRESS
STATUS CMOCK_DOWNLOAD_STATUS
TLS_VERIFY ON)
list(GET CMOCK_DOWNLOAD_STATUS 0 CMOCK_DOWNLOAD_CODE)
list(GET CMOCK_DOWNLOAD_STATUS 1 CMOCK_DOWNLOAD_MESSAGE)
if(NOT CMOCK_DOWNLOAD_CODE EQUAL 0)
message(FATAL_ERROR "Failed to download CMock archive: ${CMOCK_DOWNLOAD_MESSAGE}")
endif()
endif()

file(SHA256 "${CMOCK_ARCHIVE}" CMOCK_ARCHIVE_SHA256_ACTUAL)
if(NOT CMOCK_ARCHIVE_SHA256_ACTUAL STREQUAL CMOCK_ARCHIVE_SHA256)
message(FATAL_ERROR
"CMock archive SHA256 mismatch. Expected ${CMOCK_ARCHIVE_SHA256}, got ${CMOCK_ARCHIVE_SHA256_ACTUAL}")
endif()

execute_process(
COMMAND ${CMAKE_COMMAND} -E tar xzf "${CMOCK_ARCHIVE}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/tools"
RESULT_VARIABLE CMOCK_EXTRACT_RESULT)
if(NOT CMOCK_EXTRACT_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to extract ${CMOCK_ARCHIVE}")
endif()

file(MAKE_DIRECTORY "${CMOCK_ROOT}/vendor/unity/auto")
file(COPY "${CMAKE_SOURCE_DIR}/libs2/TestFramework/unity/auto/type_sanitizer.rb"
DESTINATION "${CMOCK_ROOT}/vendor/unity/auto")
endif()
set(CMOCK_RB "${CMOCK_RB_DEFAULT}" CACHE FILEPATH "Path to CMock lib/cmock.rb used by regen_mock_zw_transport_api" FORCE)
endif()

set(CMOCK_RUNTIME_C "${CMOCK_ROOT}/src/cmock.c"
CACHE FILEPATH "Path to CMock runtime cmock.c used by unit tests")
if(NOT EXISTS "${CMOCK_RUNTIME_C}")
message(FATAL_ERROR "CMock runtime source not found: ${CMOCK_RUNTIME_C}")
endif()

set(CMOCK_GEN_DIR "${CMAKE_BINARY_DIR}/cmock_gen")
set(CMOCK_INPUT_DIR "${CMAKE_BINARY_DIR}/cmock-input")
set(CMOCK_INPUT_HEADER "${CMOCK_INPUT_DIR}/ZW_transport_api.h")
set(MOCK_ZW_TRANSPORT_API_C "${CMOCK_GEN_DIR}/MockZW_transport_api.c")
set(MOCK_ZW_TRANSPORT_API_H "${CMOCK_GEN_DIR}/MockZW_transport_api.h")

add_custom_command(
OUTPUT "${CMOCK_INPUT_HEADER}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMOCK_INPUT_DIR}"
COMMAND "${CMAKE_C_COMPILER}" -E -P
-DZW_CONTROLLER_BRIDGE
-I"${CMAKE_SOURCE_DIR}/Z-Wave/include"
"${CMAKE_SOURCE_DIR}/Z-Wave/include/ZW_transport_api.h"
-o "${CMOCK_INPUT_HEADER}"
DEPENDS "${CMAKE_SOURCE_DIR}/Z-Wave/include/ZW_transport_api.h"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Preprocessing ZW_transport_api.h for CMock generation")

add_custom_command(
OUTPUT "${MOCK_ZW_TRANSPORT_API_C}" "${MOCK_ZW_TRANSPORT_API_H}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMOCK_GEN_DIR}"
COMMAND ${CMAKE_COMMAND} -E env ruby "${CMOCK_RB}"
-o"${CMAKE_CURRENT_SOURCE_DIR}/cmock/cmock_config.yml"
"${CMOCK_INPUT_HEADER}"
DEPENDS "${CMOCK_INPUT_HEADER}" "${CMAKE_CURRENT_SOURCE_DIR}/cmock/cmock_config.yml"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
COMMENT "Generating MockZW_transport_api in build directory")
set_source_files_properties("${MOCK_ZW_TRANSPORT_API_C}" "${MOCK_ZW_TRANSPORT_API_H}" PROPERTIES GENERATED TRUE)

add_custom_target(regen_mock_zw_transport_api
DEPENDS "${MOCK_ZW_TRANSPORT_API_C}" "${MOCK_ZW_TRANSPORT_API_H}")

add_unity_test(NAME test_t2_zipgw_runtime_nodeid FILES
test_t2_zipgw_runtime_nodeid.c
"${CMOCK_RUNTIME_C}"
"${MOCK_ZW_TRANSPORT_API_C}"
${CMAKE_CURRENT_SOURCE_DIR}/../transport_service/transport_service2.c
${CMAKE_CURRENT_SOURCE_DIR}/../transport_service/transport2_fsm.c)
add_dependencies(test_t2_zipgw_runtime_nodeid regen_mock_zw_transport_api)
target_compile_definitions(test_t2_zipgw_runtime_nodeid PRIVATE ZIPGW)

# Explicitly undefine NEW_TEST_T2 here so this regression test follows the
# production TS code path.
target_compile_options(test_t2_zipgw_runtime_nodeid PRIVATE -U NEW_TEST_T2)
# Generated CMock code may reference legacy Unity detail macros that are
# not available in the Unity variant used by this tree.
target_compile_options(test_t2_zipgw_runtime_nodeid PRIVATE
-include "${CMAKE_CURRENT_SOURCE_DIR}/cmock/cmock_legacy_unity_compat.h")
target_include_directories(test_t2_zipgw_runtime_nodeid PRIVATE
${CMAKE_BINARY_DIR}
"${CMOCK_GEN_DIR}"
"${CMOCK_ROOT}/src"
${CMAKE_CURRENT_SOURCE_DIR}/../transport_service/
${CMAKE_CURRENT_SOURCE_DIR}/../include/
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/transport
${CMAKE_SOURCE_DIR}/src/utls
${CMAKE_SOURCE_DIR}/contiki/core
${CMAKE_SOURCE_DIR}/contiki/core/sys
${CMAKE_SOURCE_DIR}/contiki/platform/linux
${CMAKE_SOURCE_DIR}/Z-Wave/include)
endif()

# ZW_SendDataAppl.c is compiled directly into the test (not taken from the
# pre-built zipgateway-lib) so that the -DUNIT_TEST flag (set globally by
# add_definitions at the top of this file) is in effect. This is required
# to compile the UNIT_TEST seam functions that expose private state.
#
# The remaining zipgateway-lib symbols that ZW_SendDataAppl.c needs are
# provided either by linking zipgateway-lib or by --wrap stubs in the test.
# ---------------------------------------------------------------------------
if (${CMAKE_PROJECT_NAME} MATCHES "zipgateway")
add_unity_test(NAME test_send_data_callback
FILES test_send_data_callback.c
${CMAKE_SOURCE_DIR}/src/transport/ZW_SendDataAppl.c
${CMAKE_SOURCE_DIR}/test/zipgateway_main_stubs.c
LIBRARIES zipgateway-lib)

set_target_properties(test_send_data_callback PROPERTIES LINK_FLAGS
"-Wl,-wrap=process_post \
-Wl,-wrap=process_exit \
-Wl,-wrap=process_start \
-Wl,-wrap=get_queue_state \
-Wl,-wrap=zw_frame_buffer_free \
-Wl,-wrap=etimer_stop \
-Wl,-wrap=etimer_set \
-Wl,-wrap=etimer_expired \
-Wl,-wrap=sec0_abort_all_tx_sessions \
-Wl,-wrap=ima_send_data_done")
endif()

add_definitions( -DRANDLEN=64 )
Expand Down
7 changes: 7 additions & 0 deletions libs2/test/cmock/cmock_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
:cmock:
:mock_path: cmock_gen
:plugins:
- :ignore
- :ignore_arg
- :expect
:treat_externs: :include
16 changes: 16 additions & 0 deletions libs2/test/cmock/cmock_legacy_unity_compat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#ifndef CMOCK_LEGACY_UNITY_COMPAT_H
#define CMOCK_LEGACY_UNITY_COMPAT_H

#ifndef UNITY_SET_DETAIL
#define UNITY_SET_DETAIL(msg) ((void)(msg))
#endif

#ifndef UNITY_CLR_DETAILS
#define UNITY_CLR_DETAILS() ((void)0)
#endif

#ifndef UNITY_SET_DETAILS
#define UNITY_SET_DETAILS(msg1, msg2) ((void)(msg1), (void)(msg2))
#endif

#endif
196 changes: 196 additions & 0 deletions libs2/test/test_send_data_callback.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/* SPDX-License-Identifier: LicenseRef-MSLA
* SPDX-FileCopyrightText: Silicon Laboratories Inc. https://www.silabs.com
*/

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include "unity.h"

#include "ZW_SendDataAppl.h" /* public API + ts_param_t */
#include "ZW_transport_api.h" /* TRANSMIT_COMPLETE_*, TX_STATUS_TYPE */
#include "node_queue.h" /* en_queue_state, QS_IDLE */
#include "zw_frame_buffer.h" /* zw_frame_buffer_element_t */

/*
* Test-seam declarations
*
* These are compiled into ZW_SendDataAppl.c only when -DUNIT_TEST is set
* (which libs2/test/CMakeLists.txt sets globally with add_definitions).
* -------------------------------------------------------------------------*/
void ZW_SendDataAppl_set_lock_ll(uint8_t v);
void ZW_SendDataAppl_set_resend_counter(uint8_t v);
uint8_t ZW_SendDataAppl_get_lock_ll(void);
void *ZW_SendDataAppl_alloc_session(void);
void ZW_SendDataAppl_push_session(void *s);
void *ZW_SendDataAppl_list_head(void);
void ZW_SendDataAppl_trigger_fail_for_test(void);

typedef struct {
void *next;
zw_frame_buffer_element_t *fb;
void *user;
ZW_SendDataAppl_Callback_t callback;
} test_session_t;

static int app_cb_count;
static uint8_t app_cb_status;
static void *app_cb_user;

static void spy_app_callback(uint8_t status, void *user, TX_STATUS_TYPE *tx) {
(void)tx;
app_cb_count++;
app_cb_status = status;
app_cb_user = user;
}

static int fb_free_count;
static zw_frame_buffer_element_t *fb_free_ptr;

void __wrap_zw_frame_buffer_free(zw_frame_buffer_element_t *e) {
fb_free_count++;
fb_free_ptr = e;
}

static int pp_count;
static process_event_t pp_last_event;

#define EXPECTED_SEND_NEXT_LL ((process_event_t)1)

int __wrap_process_post(struct process *p, process_event_t ev, void *data) {
(void)p;
(void)data;
pp_count++;
pp_last_event = ev;
return 0;
}

enum en_queue_state __wrap_get_queue_state(void) { return QS_IDLE; }

void __wrap_process_exit(struct process *p) { (void)p; }
void __wrap_process_start(struct process *p, const char *arg) {
(void)p;
(void)arg;
}

void __wrap_etimer_stop(struct etimer *t) { (void)t; }
void __wrap_etimer_set(struct etimer *t, unsigned long i) {
(void)t;
(void)i;
}
int __wrap_etimer_expired(struct etimer *t) {
(void)t;
return 1;
}

void __wrap_sec0_abort_all_tx_sessions(void) {}
void __wrap_ima_send_data_done(uint16_t n, uint8_t s, TX_STATUS_TYPE *t) {
(void)n;
(void)s;
(void)t;
}

static void reset_spies(void) {
app_cb_count = 0;
app_cb_status = 0xFF;
app_cb_user = NULL;

fb_free_count = 0;
fb_free_ptr = NULL;

pp_count = 0;
pp_last_event = 0xFF;
}

/* -------------------------------------------------------------------------
* ZW_SendDataAppl_init() sets lock=FALSE, lock_ll=FALSE, clears the list
* and reinitialises the memb pool. It also (re)starts the Contiki process,
* which is stubbed to a no-op here.
* -------------------------------------------------------------------------*/
void setUp(void) {
reset_spies();
ZW_SendDataAppl_init();
}

void tearDown(void) {}

void test_ts_should_pop_session_and_advance_when_transmit_complete_fail_and_resend_zero(void) {
static zw_frame_buffer_element_t fb_a, fb_b;
int sentinel = 0xBEEF;

test_session_t *session_a = (test_session_t *)ZW_SendDataAppl_alloc_session();
test_session_t *session_b = (test_session_t *)ZW_SendDataAppl_alloc_session();
TEST_ASSERT_NOT_NULL_MESSAGE(session_a, "memb_alloc failed — session pool exhausted");
TEST_ASSERT_NOT_NULL_MESSAGE(session_b, "memb_alloc failed — session pool exhausted");

session_a->fb = &fb_a;
session_a->user = &sentinel;
session_a->callback = spy_app_callback;

session_b->fb = &fb_b;
session_b->user = NULL;
session_b->callback = NULL;

/* head = A, A->next = B */
ZW_SendDataAppl_push_session(session_b);
ZW_SendDataAppl_push_session(session_a);

TEST_ASSERT_EQUAL_PTR_MESSAGE(session_a, ZW_SendDataAppl_list_head(),
"Pre-condition: A must be at the head before the failure");

ZW_SendDataAppl_set_lock_ll(1);
ZW_SendDataAppl_set_resend_counter(0);

ZW_SendDataAppl_trigger_fail_for_test();

TEST_ASSERT_EQUAL_PTR_MESSAGE(session_b, ZW_SendDataAppl_list_head(),
"Session B must be the head after session A is popped on FAIL");

TEST_ASSERT_EQUAL_INT_MESSAGE(1, fb_free_count, "Exactly one buffer must be freed (session A's)");
TEST_ASSERT_EQUAL_PTR_MESSAGE(&fb_a, fb_free_ptr,
"The freed buffer must be session A's, not B's");

TEST_ASSERT_EQUAL_INT_MESSAGE(1, app_cb_count,
"Application callback must be called exactly once");
TEST_ASSERT_EQUAL_UINT8_MESSAGE(TRANSMIT_COMPLETE_FAIL, app_cb_status,
"Application callback must receive TRANSMIT_COMPLETE_FAIL");
TEST_ASSERT_EQUAL_PTR_MESSAGE(&sentinel, app_cb_user,
"Application callback must receive the original user pointer");

TEST_ASSERT_EQUAL_INT_MESSAGE(1, pp_count,
"SEND_EVENT_SEND_NEXT_LL must be posted after A is removed");
TEST_ASSERT_EQUAL_MESSAGE(EXPECTED_SEND_NEXT_LL, pp_last_event,
"The posted event must be SEND_EVENT_SEND_NEXT_LL");
}

void test_ts_should_not_pop_session_when_resend_is_needed() {
static zw_frame_buffer_element_t fb;

test_session_t *s = (test_session_t *)ZW_SendDataAppl_alloc_session();
TEST_ASSERT_NOT_NULL(s);
s->fb = &fb;
s->user = NULL;
s->callback = spy_app_callback;
ZW_SendDataAppl_push_session(s);

ZW_SendDataAppl_set_lock_ll(1);
ZW_SendDataAppl_set_resend_counter(1);

ZW_SendDataAppl_trigger_fail_for_test();

TEST_ASSERT_EQUAL_PTR_MESSAGE(s, ZW_SendDataAppl_list_head(),
"Session must remain in send_data_list when resend_counter > 0");

TEST_ASSERT_EQUAL_INT_MESSAGE(0, fb_free_count,
"zw_frame_buffer_free must NOT be called when resend_counter > 0");

TEST_ASSERT_EQUAL_INT_MESSAGE(0, app_cb_count,
"Application callback must NOT be called when resend_counter > 0");

TEST_ASSERT_EQUAL_INT_MESSAGE(0, pp_count,
"process_post must NOT be called when resend_counter > 0");

TEST_ASSERT_EQUAL_UINT8_MESSAGE(0, ZW_SendDataAppl_get_lock_ll(),
"lock_ll must be FALSE after the callback (retry path)");
}
Loading
Loading