Inital commit
CI / macOS (push) Has been cancelled
CI / Windows / Clang (push) Has been cancelled
CI / macOS (push) Has been cancelled
CI / Windows / Clang (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
find_package(Unity REQUIRED)
|
||||
find_package(CMock REQUIRED)
|
||||
|
||||
set(MOCK_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/mocks")
|
||||
file(MAKE_DIRECTORY "${MOCK_GEN_DIR}")
|
||||
|
||||
# Generate a CMock mock from a header and attach it to a target.
|
||||
# Usage: cmock_generate_mock(<target> <absolute-path-to-header>)
|
||||
# CMock names generated files Mock<Name>.h/.c (capital M, no separator)
|
||||
function(cmock_generate_mock target header)
|
||||
if (NOT RUBY_EXECUTABLE)
|
||||
message(FATAL_ERROR "Ruby is required for CMock generation")
|
||||
endif()
|
||||
get_filename_component(name "${header}" NAME_WE)
|
||||
get_filename_component(header_dir "${header}" DIRECTORY)
|
||||
set(mock_src "${MOCK_GEN_DIR}/Mock${name}.c")
|
||||
set(mock_hdr "${MOCK_GEN_DIR}/Mock${name}.h")
|
||||
add_custom_command(
|
||||
OUTPUT "${mock_src}" "${mock_hdr}"
|
||||
COMMAND "${RUBY_EXECUTABLE}" "${CMOCK_SCRIPT}"
|
||||
"--mock_path=${MOCK_GEN_DIR}"
|
||||
"${header}"
|
||||
DEPENDS "${header}"
|
||||
COMMENT "CMock: generating Mock${name}"
|
||||
VERBATIM
|
||||
)
|
||||
target_sources("${target}" PRIVATE "${mock_src}")
|
||||
target_include_directories("${target}" PRIVATE "${MOCK_GEN_DIR}" "${header_dir}")
|
||||
endfunction()
|
||||
|
||||
set(TEST_TARGETS "")
|
||||
|
||||
# CRSF tests — pure functions (CRC, parse, build), no mock needed
|
||||
add_executable(test_crsf test_crsf.c)
|
||||
target_include_directories(test_crsf PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
target_link_libraries(test_crsf PRIVATE celrs_crsf Unity::Unity)
|
||||
target_compile_features(test_crsf PRIVATE c_std_23)
|
||||
add_test(NAME test_crsf COMMAND test_crsf)
|
||||
list(APPEND TEST_TARGETS test_crsf)
|
||||
|
||||
# Serial tests — mocks log_write.h for any logging calls
|
||||
add_executable(test_serial test_serial.c)
|
||||
target_include_directories(test_serial PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
target_link_libraries(test_serial PRIVATE celrs_serial Unity::Unity CMock::CMock)
|
||||
target_compile_features(test_serial PRIVATE c_std_23)
|
||||
cmock_generate_mock(test_serial "${CMAKE_SOURCE_DIR}/celrs/log_write.h")
|
||||
add_test(NAME test_serial COMMAND test_serial)
|
||||
list(APPEND TEST_TARGETS test_serial)
|
||||
|
||||
# Logger tests — mocks log_write.h so output calls are intercepted
|
||||
add_executable(test_logger test_logger.c)
|
||||
target_include_directories(test_logger PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
target_link_libraries(test_logger PRIVATE celrs_logger Unity::Unity CMock::CMock)
|
||||
target_compile_features(test_logger PRIVATE c_std_23)
|
||||
cmock_generate_mock(test_logger "${CMAKE_SOURCE_DIR}/celrs/log_write.h")
|
||||
add_test(NAME test_logger COMMAND test_logger)
|
||||
list(APPEND TEST_TARGETS test_logger)
|
||||
|
||||
# 'check' builds all suites and runs CTest with full Unity output.
|
||||
# USES_TERMINAL keeps ANSI colors alive through Ninja's output buffering.
|
||||
add_custom_target(check
|
||||
COMMAND ${CMAKE_CTEST_COMMAND}
|
||||
--test-dir "${CMAKE_BINARY_DIR}"
|
||||
--output-on-failure
|
||||
--progress
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
USES_TERMINAL
|
||||
DEPENDS ${TEST_TARGETS}
|
||||
)
|
||||
|
||||
if (ENABLE_COVERAGE)
|
||||
set(COVERAGE_DIR "${CMAKE_BINARY_DIR}/coverage")
|
||||
add_custom_target(coverage
|
||||
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${COVERAGE_DIR}"
|
||||
COMMAND ${GCOVR_EXE}
|
||||
--gcov-executable "${GCOV_EXECUTABLE}"
|
||||
--root "${CMAKE_SOURCE_DIR}"
|
||||
--filter "${CMAKE_SOURCE_DIR}/celrs/"
|
||||
--exclude "${CMAKE_SOURCE_DIR}/tests/"
|
||||
--exclude ".*Mock.*"
|
||||
--exclude ".*unity.*"
|
||||
--exclude ".*cmock.*"
|
||||
--html-details "${COVERAGE_DIR}/index.html"
|
||||
--txt
|
||||
--print-summary
|
||||
"${CMAKE_BINARY_DIR}"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
USES_TERMINAL
|
||||
DEPENDS ${TEST_TARGETS}
|
||||
COMMENT "Coverage report: ${COVERAGE_DIR}/index.html"
|
||||
)
|
||||
endif()
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "unity.h"
|
||||
#include "celrs/crsf.h"
|
||||
#include <string.h>
|
||||
|
||||
void setUp(void) {}
|
||||
void tearDown(void) {}
|
||||
|
||||
/* CRC tests */
|
||||
void test_crc_empty(void) {
|
||||
uint8_t data[1] = {0};
|
||||
TEST_ASSERT_EQUAL_UINT8(0x00, cel_crsf_crc(data, 0));
|
||||
}
|
||||
|
||||
void test_crc_single_byte(void) {
|
||||
uint8_t data[1] = {0x01};
|
||||
uint8_t crc = cel_crsf_crc(data, 1);
|
||||
TEST_ASSERT_TRUE(crc != 0); /* non-trivial */
|
||||
}
|
||||
|
||||
void test_crc_known_value(void) {
|
||||
/* CRSF heartbeat frame data (dest+src+type+size+payload):
|
||||
* {0x10, 0x80, 0x03, 0x02, 0x80, 0x01}
|
||||
* Known CRC for this sequence */
|
||||
uint8_t data[6] = {0x10, 0x80, 0x03, 0x02, 0x80, 0x01};
|
||||
uint8_t crc = cel_crsf_crc(data, 6);
|
||||
TEST_ASSERT_TRUE(crc != 0);
|
||||
/* Verify idempotency */
|
||||
uint8_t crc2 = cel_crsf_crc(data, 6);
|
||||
TEST_ASSERT_EQUAL_UINT8(crc, crc2);
|
||||
}
|
||||
|
||||
/* Frame parse tests */
|
||||
void test_parse_invalid_header(void) {
|
||||
cel_crsf_frame frame;
|
||||
uint8_t buf[8] = {0x00, 0x10, 0x80, 0x03, 0x02, 0x80, 0x01, 0x00};
|
||||
TEST_ASSERT_EQUAL_INT(-1, cel_crsf_frame_parse(&frame, buf, 8));
|
||||
}
|
||||
|
||||
void test_parse_too_short(void) {
|
||||
cel_crsf_frame frame;
|
||||
uint8_t buf[2] = {0xC8, 0x10};
|
||||
TEST_ASSERT_EQUAL_INT(-1, cel_crsf_frame_parse(&frame, buf, 2));
|
||||
}
|
||||
|
||||
void test_parse_null_frame(void) {
|
||||
uint8_t buf[8] = {0xC8, 0x10, 0x80, 0x03, 0x02, 0x80, 0x01, 0x00};
|
||||
TEST_ASSERT_EQUAL_INT(-1, cel_crsf_frame_parse(NULL, buf, 8));
|
||||
}
|
||||
|
||||
void test_parse_null_buf(void) {
|
||||
cel_crsf_frame frame;
|
||||
TEST_ASSERT_EQUAL_INT(-1, cel_crsf_frame_parse(&frame, NULL, 8));
|
||||
}
|
||||
|
||||
/* Frame build tests */
|
||||
void test_build_heartbeat(void) {
|
||||
uint8_t dst[256];
|
||||
uint8_t payload[2] = {0x80, 0x01};
|
||||
size_t len = cel_crsf_frame_build(dst, 0x00, 0x80, 0x03, payload, 2);
|
||||
|
||||
TEST_ASSERT_GREATER_THAN(0, len);
|
||||
TEST_ASSERT_EQUAL_UINT8(CEL_CRSF_FRAME_HEADER, dst[0]);
|
||||
TEST_ASSERT_EQUAL_UINT8(0x00, dst[1]); /* destination */
|
||||
TEST_ASSERT_EQUAL_UINT8(0x80, dst[2]); /* source */
|
||||
TEST_ASSERT_EQUAL_UINT8(0x03, dst[3]); /* type: heartbeat */
|
||||
TEST_ASSERT_EQUAL_UINT8(0x02, dst[4]); /* size */
|
||||
TEST_ASSERT_EQUAL_UINT8(0x80, dst[5]); /* payload[0] */
|
||||
TEST_ASSERT_EQUAL_UINT8(0x01, dst[6]); /* payload[1] */
|
||||
}
|
||||
|
||||
void test_build_roundtrip(void) {
|
||||
uint8_t dst[256];
|
||||
uint8_t payload[4] = {0xAA, 0xBB, 0xCC, 0xDD};
|
||||
size_t len = cel_crsf_frame_build(dst, 0x10, 0x80, 0x01, payload, 4);
|
||||
|
||||
/* Parse the built frame back */
|
||||
cel_crsf_frame frame;
|
||||
TEST_ASSERT_EQUAL_INT(0, cel_crsf_frame_parse(&frame, dst, len));
|
||||
TEST_ASSERT_EQUAL_UINT8(0x10, frame.destination);
|
||||
TEST_ASSERT_EQUAL_UINT8(0x80, frame.source);
|
||||
TEST_ASSERT_EQUAL_UINT8(0x01, frame.type);
|
||||
TEST_ASSERT_EQUAL_UINT8(4, frame.size);
|
||||
TEST_ASSERT_EQUAL_UINT8(0xAA, frame.payload[0]);
|
||||
TEST_ASSERT_EQUAL_UINT8(0xDD, frame.payload[3]);
|
||||
}
|
||||
|
||||
void test_build_null_dst(void) {
|
||||
uint8_t payload[2] = {0x01, 0x02};
|
||||
TEST_ASSERT_EQUAL_UINT(0, cel_crsf_frame_build(NULL, 0x00, 0x80, 0x03, payload, 2));
|
||||
}
|
||||
|
||||
void test_build_null_payload(void) {
|
||||
uint8_t dst[256];
|
||||
size_t len = cel_crsf_frame_build(dst, 0x10, 0x80, 0x03, NULL, 0);
|
||||
TEST_ASSERT_GREATER_THAN(0, len);
|
||||
/* Should still have valid CRC for empty payload */
|
||||
cel_crsf_frame frame;
|
||||
TEST_ASSERT_EQUAL_INT(0, cel_crsf_frame_parse(&frame, dst, len));
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_crc_empty);
|
||||
RUN_TEST(test_crc_single_byte);
|
||||
RUN_TEST(test_crc_known_value);
|
||||
RUN_TEST(test_parse_invalid_header);
|
||||
RUN_TEST(test_parse_too_short);
|
||||
RUN_TEST(test_parse_null_frame);
|
||||
RUN_TEST(test_parse_null_buf);
|
||||
RUN_TEST(test_build_heartbeat);
|
||||
RUN_TEST(test_build_roundtrip);
|
||||
RUN_TEST(test_build_null_dst);
|
||||
RUN_TEST(test_build_null_payload);
|
||||
return UNITY_END();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "unity.h"
|
||||
#include "celrs/logger.h"
|
||||
#include "Mocklog_write.h"
|
||||
|
||||
void setUp(void) { Mocklog_write_Init(); cel_logger_set_level(CEL_LOG_DEBUG); }
|
||||
void tearDown(void) { Mocklog_write_Verify(); Mocklog_write_Destroy(); }
|
||||
|
||||
void test_log_debug_emits_at_debug_level(void) {
|
||||
cel_log_write_Expect("[DEBUG] hello");
|
||||
cel_log_debug("hello");
|
||||
}
|
||||
|
||||
void test_log_info_emits_at_debug_level(void) {
|
||||
cel_log_write_Expect("[INFO] world");
|
||||
cel_log_info("world");
|
||||
}
|
||||
|
||||
void test_log_warn_emits_at_warn_level(void) {
|
||||
cel_logger_set_level(CEL_LOG_WARN);
|
||||
cel_log_write_Expect("[WARN] alert");
|
||||
cel_log_warn("alert");
|
||||
}
|
||||
|
||||
void test_log_err_emits_at_warn_level(void) {
|
||||
cel_logger_set_level(CEL_LOG_WARN);
|
||||
cel_log_write_Expect("[ERROR] fatal");
|
||||
cel_log_err("fatal");
|
||||
}
|
||||
|
||||
void test_log_debug_suppressed_at_info_level(void) {
|
||||
cel_logger_set_level(CEL_LOG_INFO);
|
||||
cel_log_debug("silent");
|
||||
}
|
||||
|
||||
void test_log_info_suppressed_at_warn_level(void) {
|
||||
cel_logger_set_level(CEL_LOG_WARN);
|
||||
cel_log_info("silent");
|
||||
}
|
||||
|
||||
void test_log_warn_suppressed_at_error_level(void) {
|
||||
cel_logger_set_level(CEL_LOG_ERROR);
|
||||
cel_log_warn("silent");
|
||||
}
|
||||
|
||||
void test_log_none_suppresses_all(void) {
|
||||
cel_logger_set_level(CEL_LOG_NONE);
|
||||
cel_log_debug("silent");
|
||||
cel_log_info("silent");
|
||||
cel_log_warn("silent");
|
||||
cel_log_err("silent");
|
||||
}
|
||||
|
||||
void test_level_can_be_raised_then_lowered(void) {
|
||||
cel_logger_set_level(CEL_LOG_ERROR);
|
||||
cel_log_info("silent");
|
||||
cel_logger_set_level(CEL_LOG_DEBUG);
|
||||
cel_log_write_Expect("[INFO] now visible");
|
||||
cel_log_info("now visible");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_log_debug_emits_at_debug_level);
|
||||
RUN_TEST(test_log_info_emits_at_debug_level);
|
||||
RUN_TEST(test_log_warn_emits_at_warn_level);
|
||||
RUN_TEST(test_log_err_emits_at_warn_level);
|
||||
RUN_TEST(test_log_debug_suppressed_at_info_level);
|
||||
RUN_TEST(test_log_info_suppressed_at_warn_level);
|
||||
RUN_TEST(test_log_warn_suppressed_at_error_level);
|
||||
RUN_TEST(test_log_none_suppresses_all);
|
||||
RUN_TEST(test_level_can_be_raised_then_lowered);
|
||||
return UNITY_END();
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "unity.h"
|
||||
#include "celrs/serial.h"
|
||||
#include "Mocklog_write.h"
|
||||
|
||||
void setUp(void) { Mocklog_write_Init(); }
|
||||
void tearDown(void) { Mocklog_write_Verify(); Mocklog_write_Destroy(); }
|
||||
|
||||
void test_open_valid_path(void) {
|
||||
cel_serial_port* port = cel_serial_open("COM3", 400000);
|
||||
TEST_ASSERT_NOT_NULL(port);
|
||||
cel_serial_close(port);
|
||||
}
|
||||
|
||||
void test_open_null_path(void) {
|
||||
TEST_ASSERT_NULL(cel_serial_open(NULL, 400000));
|
||||
}
|
||||
|
||||
void test_open_preserves_path(void) {
|
||||
cel_serial_port* port = cel_serial_open("/dev/ttyUSB0", 400000);
|
||||
TEST_ASSERT_NOT_NULL(port);
|
||||
/* path is stored internally; verify by roundtrip behavior */
|
||||
cel_serial_close(port);
|
||||
}
|
||||
|
||||
void test_close_null(void) {
|
||||
/* Should not crash */
|
||||
cel_serial_close(NULL);
|
||||
}
|
||||
|
||||
void test_read_returns_zero_stub(void) {
|
||||
cel_serial_port* port = cel_serial_open("COM3", 400000);
|
||||
TEST_ASSERT_NOT_NULL(port);
|
||||
uint8_t buf[16];
|
||||
/* Stub implementation returns 0 */
|
||||
size_t n = cel_serial_read(port, buf, sizeof(buf), 100);
|
||||
TEST_ASSERT_EQUAL_UINT(0, n);
|
||||
cel_serial_close(port);
|
||||
}
|
||||
|
||||
void test_write_returns_zero_stub(void) {
|
||||
cel_serial_port* port = cel_serial_open("COM3", 400000);
|
||||
TEST_ASSERT_NOT_NULL(port);
|
||||
uint8_t buf[4] = {0xC8, 0x10, 0x80, 0x03};
|
||||
/* Stub implementation returns 0 */
|
||||
size_t n = cel_serial_write(port, buf, sizeof(buf));
|
||||
TEST_ASSERT_EQUAL_UINT(0, n);
|
||||
cel_serial_close(port);
|
||||
}
|
||||
|
||||
void test_flush_no_crash(void) {
|
||||
cel_serial_port* port = cel_serial_open("COM3", 400000);
|
||||
TEST_ASSERT_NOT_NULL(port);
|
||||
cel_serial_flush(port); /* should not crash */
|
||||
cel_serial_close(port);
|
||||
}
|
||||
|
||||
void test_flush_null(void) {
|
||||
cel_serial_flush(NULL); /* should not crash */
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_open_valid_path);
|
||||
RUN_TEST(test_open_null_path);
|
||||
RUN_TEST(test_open_preserves_path);
|
||||
RUN_TEST(test_close_null);
|
||||
RUN_TEST(test_read_returns_zero_stub);
|
||||
RUN_TEST(test_write_returns_zero_stub);
|
||||
RUN_TEST(test_flush_no_crash);
|
||||
RUN_TEST(test_flush_null);
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in New Issue
Block a user