Skip to content
Draft
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
11 changes: 11 additions & 0 deletions .github/workflows/c-cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,14 @@ jobs:
cmake --build cbuild
working-directory: test

- name: Build stubbed tests
working-directory: stubbed_tests
run: |
mkdir build && cd build
CC=clang CXX=clang++ cmake -DCMAKE_BUILD_TYPE=RelWithDebug ..
cmake --build . -j $(nproc)

- name: Run stubbed tests
working-directory: stubbed_tests/build
run: ./stubbed_tests

3 changes: 1 addition & 2 deletions lib/platform/linux/platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,6 @@ static void * poll_for_indication(void * unused)
// Initially wait for 500ms before any polling
uint32_t wait_before_next_polling_ms = 500;

m_polling_thread_state_request = POLLING_THREAD_RUN;

while (m_polling_thread_state_request != POLLING_THREAD_STOP)
{
usleep(wait_before_next_polling_ms * 1000);
Expand Down Expand Up @@ -378,6 +376,7 @@ bool Platform_init(Platform_get_indication_f get_indication_f,
}

// Start a thread to poll for indication
m_polling_thread_state_request = POLLING_THREAD_RUN;
if (pthread_create(&thread_polling, NULL, poll_for_indication, NULL) != 0)
{
LOGE("Cannot create polling thread\n");
Expand Down
79 changes: 79 additions & 0 deletions stubbed_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
cmake_minimum_required(VERSION 3.18)

project(c-mesh-api-stubbed-tests LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 20)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(TEST_EXTRA_CFLAGS "" CACHE STRING "Extra compiler flags for WPC library and test code. For example '--coverage'.")
set(TEST_EXTRA_LDFLAGS "" CACHE STRING "Extra linker flags for the test executable. For example '--coverage'.")
separate_arguments(TEST_EXTRA_CFLAGS)
separate_arguments(TEST_EXTRA_LDFLAGS)

set(WPC_LIB_DIR "${CMAKE_CURRENT_LIST_DIR}/../lib/")

include(FetchContent)
FetchContent_Declare(
wpc-lib
SOURCE_DIR ${WPC_LIB_DIR}
)
FetchContent_Declare(
fuzztest
GIT_REPOSITORY https://github.com/google/fuzztest/
GIT_TAG 2025-08-05
)
FetchContent_MakeAvailable(wpc-lib fuzztest)

enable_testing()

fuzztest_setup_fuzzing_flags()
add_executable(stubbed_tests
tests/basic_tests.cpp
tests/ul_fuzz_tests.cpp
tests/utils/queued_ul_data_handler.cpp
)
target_include_directories(stubbed_tests PRIVATE tests)

add_subdirectory(serialstub)

target_include_directories(serialstub PRIVATE ${WPC_LIB_DIR}/platform)

##########
# Somewhat hacky way to suppress warnings for fuzztest library. This allows us
# to use stricter compile flags for warnings in our testing code that includes
# files from the fuzztest.
get_target_property(LIB_INCLUDE_DIRS fuzztest_fuzztest INTERFACE_INCLUDE_DIRECTORIES)
if(LIB_INCLUDE_DIRS)
set_target_properties(fuzztest_fuzztest PROPERTIES
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${LIB_INCLUDE_DIRS}"
)
endif()
get_target_property(LIB_INCLUDE_DIRS fuzztest_fuzztest_gtest_main INTERFACE_INCLUDE_DIRECTORIES)
if(LIB_INCLUDE_DIRS)
set_target_properties(fuzztest_fuzztest_gtest_main PROPERTIES
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${LIB_INCLUDE_DIRS}"
)
endif()
##########

target_compile_options(wpc PRIVATE ${TEST_EXTRA_CFLAGS} -Werror -Wall -Wextra)
target_compile_options(wpc_platform PRIVATE ${TEST_EXTRA_CFLAGS} -Werror -Wall -Wextra)
target_compile_options(stubbed_tests PRIVATE ${TEST_EXTRA_CFLAGS} -Wall -Werror -Wextra)
target_link_options(stubbed_tests PRIVATE ${TEST_EXTRA_LDFLAGS})

# "serialstub" should come before "wpc_platform" so that the serial implementation
# from "wpc_platform" is not taken into use.
target_link_libraries(stubbed_tests PRIVATE
GTest::gmock
wpc
serialstub
wpc_platform
)

include(GoogleTest)
link_fuzztest(stubbed_tests)
gtest_discover_tests(stubbed_tests)

48 changes: 48 additions & 0 deletions stubbed_tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Introduction

These tests use a stub implementation for the serial interface and can be run
without a real serial device. See README.md under serialstub for more detils.

There are two types of tests included; regular unit tests and fuzz tests. The
tests use [FuzzTest](https://github.com/google/fuzztest) and
[GTest](https://github.com/google/googletest).

# Requirements
[Clang](https://clang.llvm.org/), since FuzzTest requires it.

# Building
FuzzTest tests can be run as unit tests or in fuzzing mode. For more
information refer to FuzzTest documentation
[here](https://github.com/google/fuzztest/blob/main/doc/quickstart-cmake.md).

Example command to build for unit testing:

```shell
mkdir build && cd build
CC=clang CXX=clang++ cmake -DCMAKE_BUILD_TYPE=RelWithDebug ..
cmake --build . -j $(nproc)
```

# Running the tests

To run unit tests together with fuzz tests in unit testing mode:
```shell
./stubbed_tests
```

Refer to FuzzTest documentation on how to run fuzz tests in fuzzing mode.

## Naming conventions
Test suites that support fuzzing should start with "Fuzz". This way you can
filter tests when running, for example to run only fuzz tests:

```shell
./stubbed_tests --gtest_filter="Fuzz*"
```

And to run non-fuzz tests:

```shell
./stubbed_tests --gtest_filter="-Fuzz*"
```

15 changes: 15 additions & 0 deletions stubbed_tests/serialstub/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
add_library(serialstub STATIC
${CMAKE_CURRENT_LIST_DIR}/serialstub/serial.cpp
${CMAKE_CURRENT_LIST_DIR}/serialstub/serial_stub.cpp
${CMAKE_CURRENT_LIST_DIR}/serialstub/wpc_frame.cpp
${CMAKE_CURRENT_LIST_DIR}/serialstub/misc/crc.cpp
)

target_include_directories(serialstub PUBLIC
${PROJECT_SOURCE_DIR}/platform
${CMAKE_CURRENT_LIST_DIR}
)

set_target_properties(serialstub PROPERTIES LINKER_LANGUAGE CXX)
target_compile_options(serialstub PRIVATE -Wall -Werror -Wextra)

17 changes: 17 additions & 0 deletions stubbed_tests/serialstub/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Introduction

This is a stub serial implementation for the c-mesh-api platform interface
(lib/platform/serial.h). Intended use is to build on top of the linux platform
implementation. Since only serial.h is implemented, the resulting object from
this stub should be linked before the default platform objects so that the
default serial implementation is not taken into use, while rest of the platform
implementation is.

The stub has a wrapper for Dual MCU frames. A handler is assigned for each
frame which is normally sent to the serial device. The handler should return
responses for every call (these are called "confirm" or "indication" in the
DualMCU API.)

For more information, see the Wirepas DualMCU API documentation (for example
[here](https://github.com/wirepas/wm-sdk-5g/blob/rel_1.2.0_5G/libraries/dualmcu/api/DualMcuAPI.md))

37 changes: 37 additions & 0 deletions stubbed_tests/serialstub/serialstub/frame_handler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#pragma once

#include <memory>
#include <vector>
#include <serialstub/wpc_frame.h>

class FrameHandler
{
public:
using ptr = std::shared_ptr<FrameHandler>;

virtual ~FrameHandler() = default;

/**
* \brief Handle a Dual-MCU API frame
*
* Handles a request frame and returns response frames. As an example,
* multiple responses to a single request are possible when responding to
* poll requests.
*
* In this context, a "request" is a frame sent by the application and a
* "response" is a frame sent by the stub back to the application.
*
* In Dual-MCU API specification, "request" messages are the following:
* - request
* - response
* And the "response" messages are the following:
* - confirm
* - indication
*
* \param frame
* Request frame to be handled
* \return Response frames
*/
virtual std::vector<WpcFrame> Handle(const WpcFrame& frame) = 0;
};

13 changes: 13 additions & 0 deletions stubbed_tests/serialstub/serialstub/misc/crc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include "crc.h"

uint16_t Crc::FromBuffer(const uint8_t* const buffer, const size_t length)
{
uint16_t crc = 0xffff;
for (size_t i = 0; i < length; i++)
{
const uint8_t index = buffer[i] ^ (crc >> 8);
crc = crc_ccitt_lut[index] ^ (crc << 8);
}
return crc;
}

48 changes: 48 additions & 0 deletions stubbed_tests/serialstub/serialstub/misc/crc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma once

#include <cstddef>
#include <cstdint>

namespace Crc
{

static const uint16_t crc_ccitt_lut[] =
{
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};

uint16_t FromBuffer(const uint8_t* const buffer, const size_t length);

} // namespace Crc

36 changes: 36 additions & 0 deletions stubbed_tests/serialstub/serialstub/serial.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <serialstub/serial_stub.h>

extern "C" {
#include <serial.h>

int Serial_open([[maybe_unused]] const char * port_name, [[maybe_unused]] unsigned long bitrate)
{
return 0;
}

int Serial_close()
{
return 0;
}

int Serial_read(unsigned char * c, [[maybe_unused]] unsigned int timeout_ms)
{
if (const auto& byte = serial_stub.PopOutByte(); byte.has_value())
{
*c = *byte;
return 1;
}

return 0;
}

int Serial_write(const unsigned char * buffer, unsigned int buffer_size)
{
// This is ok because in practice, c-mesh-api always sends a whole
// frame with a single Serial_write command.
serial_stub.ReadAndHandleFrame(buffer, buffer_size);
return buffer_size;
}

}

Loading