重构代码的架构设计,增加测试单元,提高代码可靠性
This commit is contained in:
108
tests/CMakeLists.txt
Normal file
108
tests/CMakeLists.txt
Normal file
@@ -0,0 +1,108 @@
|
||||
# tests 子工程最低 CMake 版本要求(与主工程保持一致)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# 预留:测试通用源码列表(当前未直接使用,可用于后续统一链接)
|
||||
set(DTU_TEST_COMMON_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/ascii.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/key.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/menu.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/display.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/TCP/tcp.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/thread_utils.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/remoteDisplay.c"
|
||||
)
|
||||
|
||||
# 封装测试目标创建逻辑:
|
||||
# - add_executable: 生成测试可执行文件
|
||||
# - target_include_directories: 注入项目头文件路径
|
||||
# - target_link_libraries: 按平台链接系统库
|
||||
# - add_test: 注册到 CTest,支持 ctest 统一执行
|
||||
function(add_dtu_test test_name)
|
||||
add_executable(${test_name} ${ARGN})
|
||||
# 测试目标可见的头文件目录
|
||||
target_include_directories(${test_name} PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/include"
|
||||
"${CMAKE_SOURCE_DIR}/src"
|
||||
"${CMAKE_SOURCE_DIR}/src/TCP"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv"
|
||||
)
|
||||
# 平台差异:Windows 需要 ws2_32,非 Windows 使用 pthread
|
||||
if(WIN32)
|
||||
target_link_libraries(${test_name} PRIVATE ws2_32)
|
||||
else()
|
||||
target_link_libraries(${test_name} PRIVATE pthread)
|
||||
endif()
|
||||
# 将测试程序注册为 CTest 用例,名称与目标名一致
|
||||
add_test(NAME ${test_name} COMMAND ${test_name})
|
||||
endfunction()
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Smoke 测试:验证测试框架本身可用
|
||||
# ------------------------------------------------------------
|
||||
add_dtu_test(test_smoke tests_smoke.c)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# P0:高价值纯逻辑测试
|
||||
# ------------------------------------------------------------
|
||||
add_dtu_test(
|
||||
test_p0_remote_display
|
||||
test_p0_remote_display.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_draw.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_text.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/ascii.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/key.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/TCP/tcp.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/thread_utils.c"
|
||||
)
|
||||
add_dtu_test(
|
||||
test_p0_utf8_next
|
||||
test_p0_utf8_next.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_draw.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_text.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/ascii.c"
|
||||
)
|
||||
add_dtu_test(
|
||||
test_p0_utf8_hz12_get
|
||||
test_p0_utf8_hz12_get.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/ascii.c"
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# P1:业务核心计算/状态流转测试
|
||||
# ------------------------------------------------------------
|
||||
add_dtu_test(
|
||||
test_p1_key
|
||||
test_p1_key.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/key.c"
|
||||
)
|
||||
add_dtu_test(
|
||||
test_p1_lcd_basic
|
||||
test_p1_lcd_basic.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_draw.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_text.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/ascii.c"
|
||||
)
|
||||
add_dtu_test(
|
||||
test_p1_menu
|
||||
test_p1_menu.c
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/display.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_draw.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/lcd_text.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/lcd/ascii.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/Drv/key.c"
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# P2:集成测试(网络回环等)
|
||||
# ------------------------------------------------------------
|
||||
add_dtu_test(
|
||||
test_p2_tcp_loopback
|
||||
test_p2_tcp_loopback.c
|
||||
"${CMAKE_SOURCE_DIR}/src/TCP/tcp.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/thread_utils.c"
|
||||
)
|
||||
66
tests/test_common.h
Normal file
66
tests/test_common.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/* 统一测试辅助头:
|
||||
* - 提供轻量断言宏,失败时打印上下文并返回 1 终止当前测试程序
|
||||
* - 所有测试文件可直接 include 本头,减少重复样板代码
|
||||
*/
|
||||
#ifndef DTU_TEST_COMMON_H
|
||||
#define DTU_TEST_COMMON_H
|
||||
|
||||
/* 标准库依赖:
|
||||
* - stdio.h : fprintf
|
||||
* - stdlib.h : 通用工具(当前宏未直接使用,预留)
|
||||
* - string.h : strcmp
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* 断言:表达式为真
|
||||
* 失败输出:失败表达式 + 文件 + 行号
|
||||
* 失败返回:1(约定 main 返回非 0 表示测试失败)
|
||||
*/
|
||||
#define ASSERT_TRUE(expr) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
fprintf(stderr, "ASSERT_TRUE failed: %s (%s:%d)\n", #expr, __FILE__,\
|
||||
__LINE__); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* 断言:两个整数值相等(按 int 比较) */
|
||||
#define ASSERT_EQ_INT(expected, actual) \
|
||||
do { \
|
||||
int _exp = (int)(expected); \
|
||||
int _act = (int)(actual); \
|
||||
if (_exp != _act) { \
|
||||
fprintf(stderr, \
|
||||
"ASSERT_EQ_INT failed: expected=%d actual=%d (%s:%d)\n", \
|
||||
_exp, _act, __FILE__, __LINE__); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* 断言:两个无符号 32 位值相等(按 unsigned int 比较) */
|
||||
#define ASSERT_EQ_U32(expected, actual) \
|
||||
do { \
|
||||
unsigned int _exp = (unsigned int)(expected); \
|
||||
unsigned int _act = (unsigned int)(actual); \
|
||||
if (_exp != _act) { \
|
||||
fprintf(stderr, \
|
||||
"ASSERT_EQ_U32 failed: expected=%u actual=%u (%s:%d)\n", \
|
||||
_exp, _act, __FILE__, __LINE__); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* 断言:两个 C 字符串内容相等(区分大小写) */
|
||||
#define ASSERT_STREQ(expected, actual) \
|
||||
do { \
|
||||
if (strcmp((expected), (actual)) != 0) { \
|
||||
fprintf(stderr, "ASSERT_STREQ failed: expected=\"%s\" actual=\"%s\" (%s:%d)\n", \
|
||||
(expected), (actual), __FILE__, __LINE__); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
77
tests/test_p0_remote_display.c
Normal file
77
tests/test_p0_remote_display.c
Normal file
@@ -0,0 +1,77 @@
|
||||
/* P0 测试:remoteDisplay 协议核心
|
||||
* 目标:
|
||||
* - 校验 CRC 纯函数行为(calc_crc)
|
||||
* - 校验 RemoDispBus 帧解析边界(parse_frame)
|
||||
*/
|
||||
#include <stdint.h>
|
||||
|
||||
#include "test_common.h"
|
||||
/* 直接包含 .c 以访问 static 函数(calc_crc / parse_frame) */
|
||||
#include "../src/remoteDisplay.c"
|
||||
|
||||
/* 用例1:CRC 计算
|
||||
* - 空数据长度应返回 0
|
||||
* - 常规 payload 应返回逐字节异或结果
|
||||
*/
|
||||
static int test_calc_crc(void)
|
||||
{
|
||||
const uint8_t payload[] = {0x12, 0x34, 0x56};
|
||||
ASSERT_EQ_INT(0x00, calc_crc(NULL, 0));
|
||||
ASSERT_EQ_INT((0x12 ^ 0x34 ^ 0x56), calc_crc(payload, 3));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 用例2:合法帧解析
|
||||
* 验证:
|
||||
* - parse_frame 返回成功
|
||||
* - cmd/data/data_len/consume 与期望一致
|
||||
*/
|
||||
static int test_parse_frame_ok(void)
|
||||
{
|
||||
uint8_t cmd = 0;
|
||||
const uint8_t* data = NULL;
|
||||
unsigned int data_len = 0;
|
||||
unsigned int consume = 0;
|
||||
const uint8_t payload[] = {0x10, 0x20};
|
||||
const uint8_t frame[] = {0xAA, 0x03, 0x00, 0x02, 0x10, 0x20, (uint8_t)(0x10 ^ 0x20)};
|
||||
|
||||
ASSERT_EQ_INT(1, parse_frame(frame, sizeof(frame), &cmd, &data, &data_len, &consume));
|
||||
ASSERT_EQ_INT(0x03, cmd);
|
||||
ASSERT_EQ_INT(2, (int)data_len);
|
||||
ASSERT_EQ_INT((int)sizeof(frame), (int)consume);
|
||||
ASSERT_EQ_INT(payload[0], data[0]);
|
||||
ASSERT_EQ_INT(payload[1], data[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 用例3:非法输入集合
|
||||
* - TAG 非客户端 TAG
|
||||
* - 帧长度不足(截断)
|
||||
* - CRC 错误
|
||||
* 期望均解析失败(返回 0)
|
||||
*/
|
||||
static int test_parse_frame_invalid_cases(void)
|
||||
{
|
||||
uint8_t cmd = 0;
|
||||
const uint8_t* data = NULL;
|
||||
unsigned int data_len = 0;
|
||||
unsigned int consume = 0;
|
||||
const uint8_t bad_tag[] = {0xBB, 0x03, 0x00, 0x00, 0x00};
|
||||
const uint8_t short_buf[] = {0xAA, 0x03, 0x00, 0x01};
|
||||
const uint8_t bad_crc[] = {0xAA, 0x02, 0x00, 0x01, 0x55, 0x00};
|
||||
|
||||
ASSERT_EQ_INT(0, parse_frame(bad_tag, sizeof(bad_tag), &cmd, &data, &data_len, &consume));
|
||||
ASSERT_EQ_INT(0, parse_frame(short_buf, sizeof(short_buf), &cmd, &data, &data_len, &consume));
|
||||
ASSERT_EQ_INT(0, parse_frame(bad_crc, sizeof(bad_crc), &cmd, &data, &data_len, &consume));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 测试入口:按顺序执行各子用例,任一失败即返回非 0 */
|
||||
int main(void)
|
||||
{
|
||||
if (test_calc_crc() != 0) return 1;
|
||||
if (test_parse_frame_ok() != 0) return 1;
|
||||
if (test_parse_frame_invalid_cases() != 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
14
tests/test_p0_utf8_hz12_get.c
Normal file
14
tests/test_p0_utf8_hz12_get.c
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "../src/Drv/lcd/ascii.h"
|
||||
#include "test_common.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const uint8_t* hit = utf8_hz12_get(0x4F60u); /* 你 */
|
||||
const uint8_t* miss_low = utf8_hz12_get(0x0001u);
|
||||
const uint8_t* miss_high = utf8_hz12_get(0x10FFFFu);
|
||||
|
||||
ASSERT_TRUE(hit != NULL);
|
||||
ASSERT_TRUE(miss_low == NULL);
|
||||
ASSERT_TRUE(miss_high == NULL);
|
||||
return 0;
|
||||
}
|
||||
27
tests/test_p0_utf8_next.c
Normal file
27
tests/test_p0_utf8_next.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "../src/Drv/lcd/lcd_text.h"
|
||||
#include "test_common.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uint32_t unicode = 0;
|
||||
const unsigned char ascii[] = "A";
|
||||
const unsigned char u2[] = {0xC2, 0xA2, 0x00}; /* U+00A2 */
|
||||
const unsigned char u3[] = {0xE4, 0xBD, 0xA0, 0x00}; /* U+4F60 */
|
||||
const unsigned char invalid[] = {0xF0, 0x00}; /* 4-byte start not supported */
|
||||
const unsigned char truncated2[] = {0xC2, 0x00};
|
||||
const unsigned char truncated3[] = {0xE4, 0xBD, 0x00};
|
||||
|
||||
ASSERT_EQ_INT(1, utf8_next(ascii, &unicode));
|
||||
ASSERT_EQ_U32(0x41u, unicode);
|
||||
|
||||
ASSERT_EQ_INT(2, utf8_next(u2, &unicode));
|
||||
ASSERT_EQ_U32(0x00A2u, unicode);
|
||||
|
||||
ASSERT_EQ_INT(3, utf8_next(u3, &unicode));
|
||||
ASSERT_EQ_U32(0x4F60u, unicode);
|
||||
|
||||
ASSERT_EQ_INT(0, utf8_next(invalid, &unicode));
|
||||
ASSERT_EQ_INT(0, utf8_next(truncated2, &unicode));
|
||||
ASSERT_EQ_INT(0, utf8_next(truncated3, &unicode));
|
||||
return 0;
|
||||
}
|
||||
16
tests/test_p1_key.c
Normal file
16
tests/test_p1_key.c
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "../src/Drv/key.h"
|
||||
#include "test_common.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Key_Init();
|
||||
ASSERT_EQ_INT(EN_KEY_FLAG_NULL, g_tRemoteKey.byKeyValid);
|
||||
ASSERT_EQ_INT(KEY_NONE, g_tRemoteKey.byKeyValue);
|
||||
|
||||
g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NEW;
|
||||
g_tRemoteKey.byKeyValue = KEY_U;
|
||||
ASSERT_EQ_INT(KEY_U, Key_Read());
|
||||
ASSERT_EQ_INT(EN_KEY_FLAG_NULL, g_tRemoteKey.byKeyValid);
|
||||
ASSERT_EQ_INT(KEY_NONE, Key_Read());
|
||||
return 0;
|
||||
}
|
||||
289
tests/test_p1_lcd_basic.c
Normal file
289
tests/test_p1_lcd_basic.c
Normal file
@@ -0,0 +1,289 @@
|
||||
/* -------------------------------------------------------------------------
|
||||
* 文件名: test_p1_lcd_basic.c
|
||||
* 作用:
|
||||
* 对 LCD 基础模块进行单元测试,覆盖以下三个子模块:
|
||||
* 1) lcd.c : 显存初始化、像素读写、边界保护
|
||||
* 2) lcd_draw.c : 填充、反显、水平线/垂直线绘制
|
||||
* 3) lcd_text.c : UTF-8 解码、字符串显示(ASCII/中文/换行)与边界返回值
|
||||
*
|
||||
* 测试策略:
|
||||
* - 每个 test_* 函数仅验证一个明确行为域,失败即立即返回非 0。
|
||||
* - 使用 ASSERT_* 宏统一断言风格,失败时打印文件+行号。
|
||||
* - main() 串行执行所有子用例,便于定位首个失败点。
|
||||
*
|
||||
* 设计说明:
|
||||
* - 本文件只依赖公开头文件,不包含被测 .c,实现黑盒+灰盒结合验证。
|
||||
* - 通过“像素点结果”验证绘图函数,不依赖外设或真实 LCD 硬件。
|
||||
* ------------------------------------------------------------------------- */
|
||||
#include "../src/Drv/lcd/lcd.h"
|
||||
#include "../src/Drv/lcd/lcd_draw.h"
|
||||
#include "../src/Drv/lcd/lcd_text.h"
|
||||
#include "test_common.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 用例: test_lcd_init_and_pixel_rw
|
||||
* 目标:
|
||||
* - 验证 Lcd_Init() 会把显存清零(黑色)
|
||||
* - 验证 Lcd_SetPixel/Lcd_GetPixel 的基本读写正确性
|
||||
* - 验证越界写入/读取均返回错误码
|
||||
*
|
||||
* 覆盖函数:
|
||||
* - Lcd_Init
|
||||
* - Lcd_SetPixel
|
||||
* - Lcd_GetPixel
|
||||
* ------------------------------------------------------------------------- */
|
||||
static int test_lcd_init_and_pixel_rw(void)
|
||||
{
|
||||
/* 初始化后显存应为全黑(0x00) */
|
||||
Lcd_Init();
|
||||
/* 左上角像素校验 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(0, 0));
|
||||
/* 右下角像素校验,确认全屏范围都被正确初始化 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(LCD_SIZE_X - 1, LCD_SIZE_Y - 1));
|
||||
|
||||
/* 在合法坐标写白点并回读 */
|
||||
ASSERT_EQ_INT(LCD_OK, Lcd_SetPixel(1, 1, LCD_COLOR_WHITE));
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(1, 1));
|
||||
|
||||
/* 非法颜色应返回错误码,且不改变原有像素值 */
|
||||
ASSERT_EQ_INT(LCD_ERR_INVALID_COLOR, Lcd_SetPixel(1, 1, 0x7F));
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(1, 1));
|
||||
|
||||
/* 越界写入应返回错误码:x == LCD_SIZE_X 已越界 */
|
||||
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_SetPixel(LCD_SIZE_X, 1, LCD_COLOR_WHITE));
|
||||
/* 越界写入应返回错误码:y == LCD_SIZE_Y 已越界 */
|
||||
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_SetPixel(1, LCD_SIZE_Y, LCD_COLOR_WHITE));
|
||||
/* 越界读取应返回错误码 */
|
||||
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_GetPixel(LCD_SIZE_X, 1));
|
||||
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_GetPixel(1, LCD_SIZE_Y));
|
||||
|
||||
/* 附加点位校验,确保越界写没有污染邻近合法区域 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(2, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 用例: test_fillrect_and_invert
|
||||
* 目标:
|
||||
* - 验证 Lcd_FillRect 的闭区间填充行为(包含 right_x/bottom_y)与错误码
|
||||
* - 验证 Lcd_Invert 的半开区间行为([min, max))与错误码
|
||||
* - 验证 Lcd_Invert 支持反向坐标输入(起点大于终点)
|
||||
*
|
||||
* 覆盖函数:
|
||||
* - Lcd_FillRect
|
||||
* - Lcd_Invert
|
||||
* ------------------------------------------------------------------------- */
|
||||
static int test_fillrect_and_invert(void)
|
||||
{
|
||||
Lcd_Init();
|
||||
|
||||
/* 填充 3x3 白色区域:x=[2,4], y=[2,4](闭区间) */
|
||||
ASSERT_EQ_INT(LCD_OK, Lcd_FillRect(2, 2, 4, 4, LCD_COLOR_WHITE));
|
||||
/* 左上角在填充区内 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(2, 2));
|
||||
/* 右下角也在填充区内,验证 FillRect 含边界 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(4, 4));
|
||||
/* 区域外点保持黑色 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(5, 5));
|
||||
|
||||
/* Invert 采用 [min, max) 半开区间:会翻转 x=2..3, y=2..3 */
|
||||
ASSERT_EQ_INT(LCD_OK, Lcd_Invert(2, 2, 4, 4));
|
||||
/* 区域内像素从白翻转成 ~白 */
|
||||
ASSERT_EQ_INT((uint8_t)~LCD_COLOR_WHITE, Lcd_GetPixel(2, 2));
|
||||
ASSERT_EQ_INT((uint8_t)~LCD_COLOR_WHITE, Lcd_GetPixel(3, 3));
|
||||
/* x=4/y=4 在半开区间外,不应被翻转 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(4, 4));
|
||||
|
||||
/* 反向坐标也应可处理:再次翻转回白色 */
|
||||
ASSERT_EQ_INT(LCD_OK, Lcd_Invert(4, 4, 2, 2));
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(2, 2));
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(3, 3));
|
||||
|
||||
/* 越界参数应返回错误码 */
|
||||
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_FillRect(0, 0, LCD_SIZE_X, 4, LCD_COLOR_WHITE));
|
||||
ASSERT_EQ_INT(LCD_ERR_INVALID_COLOR, Lcd_FillRect(0, 0, 1, 1, 0x7F));
|
||||
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_Invert(0, 0, LCD_SIZE_X + 1, 4));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 用例: test_lineh_linev
|
||||
* 目标:
|
||||
* - 验证 Lcd_LineH/Lcd_LineV 的绘制范围与线宽、错误码
|
||||
* - 验证水平/垂直线的“结束坐标不包含”语义
|
||||
* - 验证 Lcd_LineH 的反向坐标能力
|
||||
*
|
||||
* 覆盖函数:
|
||||
* - Lcd_LineH
|
||||
* - Lcd_LineV
|
||||
* ------------------------------------------------------------------------- */
|
||||
static int test_lineh_linev(void)
|
||||
{
|
||||
Lcd_Init();
|
||||
|
||||
/* 水平线:x=[10,14), y=[20,22);线宽=2 */
|
||||
ASSERT_EQ_INT(LCD_OK, Lcd_LineH(10, 14, 20, 2, LCD_COLOR_WHITE));
|
||||
/* 起点应该被绘制 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(10, 20));
|
||||
/* 范围内末端附近点(x=13,y=21)应为白 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(13, 21));
|
||||
/* xEnd 不包含:x=14 不应被画 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(14, 20)); /* xEnd 不包含 */
|
||||
|
||||
/* 垂直线:x=[40,42), y=[30,35);线宽=2 */
|
||||
ASSERT_EQ_INT(LCD_OK, Lcd_LineV(30, 35, 40, 2, LCD_COLOR_WHITE));
|
||||
/* 起点应被绘制 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(40, 30));
|
||||
/* 范围内末端附近点应为白 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(41, 34));
|
||||
/* yEnd 不包含:y=35 不应被画 */
|
||||
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(40, 35)); /* yEnd 不包含 */
|
||||
|
||||
/* 水平线支持反向 X:函数内部应自动交换起止坐标 */
|
||||
ASSERT_EQ_INT(LCD_OK, Lcd_LineH(14, 10, 50, 1, LCD_COLOR_WHITE));
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(10, 50));
|
||||
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(13, 50));
|
||||
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(14, 50));
|
||||
|
||||
ASSERT_EQ_INT(LCD_ERR_INVALID_COLOR, Lcd_LineH(10, 14, 20, 1, 0x7F));
|
||||
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_LineH(10, 14, LCD_SIZE_Y, 1, LCD_COLOR_WHITE));
|
||||
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_LineV(10, 14, LCD_SIZE_X, 1, LCD_COLOR_WHITE));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 工具函数: count_white_in_rect
|
||||
* 作用:
|
||||
* 统计指定闭区间矩形内白色像素数量。
|
||||
* 用途:
|
||||
* 文本渲染(尤其字形)不便逐像素写期望值时,用“白点数 > 0”验证
|
||||
* “确实有内容被渲染”这一最小正确性。
|
||||
*
|
||||
* 参数:
|
||||
* x0,y0 : 左上角(包含)
|
||||
* x1,y1 : 右下角(包含)
|
||||
*
|
||||
* 返回:
|
||||
* 区域内等于 LCD_COLOR_WHITE 的像素个数。
|
||||
* ------------------------------------------------------------------------- */
|
||||
static int count_white_in_rect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
|
||||
{
|
||||
int count = 0;
|
||||
for (uint16_t y = y0; y <= y1; y++) {
|
||||
for (uint16_t x = x0; x <= x1; x++) {
|
||||
if (Lcd_GetPixel(x, y) == LCD_COLOR_WHITE) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 用例: test_utf8_next_decode
|
||||
* 目标:
|
||||
* - 覆盖 utf8_next 对 1/2/3 字节 UTF-8 的解码路径
|
||||
* - 验证不支持或非法输入时返回 0
|
||||
*
|
||||
* 覆盖函数:
|
||||
* - utf8_next
|
||||
* ------------------------------------------------------------------------- */
|
||||
static int test_utf8_next_decode(void)
|
||||
{
|
||||
uint32_t unicode = 0;
|
||||
const unsigned char ascii[] = "A"; /* 单字节 ASCII */
|
||||
const unsigned char u2[] = {0xC2, 0xA2, 0x00}; /* 两字节: U+00A2 */
|
||||
const unsigned char u3[] = {0xE4, 0xBD, 0xA0, 0x00}; /* 三字节: U+4F60 */
|
||||
const unsigned char invalid[] = {0xF0, 0x00}; /* 四字节起始(当前实现不支持) */
|
||||
const unsigned char trunc2[] = {0xC2, 0x00}; /* 两字节截断 */
|
||||
const unsigned char trunc3[] = {0xE4, 0xBD, 0x00}; /* 三字节截断 */
|
||||
|
||||
/* ASCII: 返回长度 1,码点 0x41 */
|
||||
ASSERT_EQ_INT(1, utf8_next(ascii, &unicode));
|
||||
ASSERT_EQ_U32(0x41u, unicode);
|
||||
|
||||
/* 两字节 UTF-8 */
|
||||
ASSERT_EQ_INT(2, utf8_next(u2, &unicode));
|
||||
ASSERT_EQ_U32(0x00A2u, unicode);
|
||||
|
||||
/* 三字节 UTF-8 */
|
||||
ASSERT_EQ_INT(3, utf8_next(u3, &unicode));
|
||||
ASSERT_EQ_U32(0x4F60u, unicode);
|
||||
|
||||
/* 非支持输入返回 0 */
|
||||
ASSERT_EQ_INT(0, utf8_next(invalid, &unicode));
|
||||
ASSERT_EQ_INT(0, utf8_next(trunc2, &unicode));
|
||||
ASSERT_EQ_INT(0, utf8_next(trunc3, &unicode));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 用例: test_showstr_ascii_utf8_newline_and_bounds
|
||||
* 目标:
|
||||
* - 验证 Lcd_ShowStr 对 ASCII/中文渲染均生效
|
||||
* - 验证换行逻辑(行高 + 行间距)正确
|
||||
* - 验证越界起始坐标时返回错误码
|
||||
*
|
||||
* 覆盖函数:
|
||||
* - Lcd_ShowStr
|
||||
* ------------------------------------------------------------------------- */
|
||||
static int test_showstr_ascii_utf8_newline_and_bounds(void)
|
||||
{
|
||||
uint8_t text_a[] = "A";
|
||||
uint8_t text_cn[] = "你";
|
||||
uint8_t text_nl[] = "A\nB";
|
||||
uint8_t text_cn_overflow_x[] = "你你";
|
||||
uint8_t text_nl_overflow_y[] = "A\nB";
|
||||
|
||||
/* ASCII 渲染: 返回 0 且字形区域应存在白点 */
|
||||
Lcd_Init();
|
||||
ASSERT_EQ_INT(0, Lcd_ShowStr(0, 0, text_a));
|
||||
ASSERT_TRUE(count_white_in_rect(0, 0, 6, 11) > 0);
|
||||
|
||||
/* 中文渲染: 13x12 区域内应存在白点 */
|
||||
Lcd_Init();
|
||||
ASSERT_EQ_INT(0, Lcd_ShowStr(10, 10, text_cn));
|
||||
ASSERT_TRUE(count_white_in_rect(10, 10, 22, 21) > 0);
|
||||
|
||||
/* 换行渲染: "A\nB" 中 B 应下移到 y = 12 + rowSpace(2) = 14 */
|
||||
Lcd_Init();
|
||||
ASSERT_EQ_INT(0, Lcd_ShowStr(0, 0, text_nl));
|
||||
ASSERT_TRUE(count_white_in_rect(0, 0, 6, 11) > 0); /* A */
|
||||
ASSERT_TRUE(count_white_in_rect(0, 14, 6, 25) > 0); /* B: 12 高 + 2 行距 */
|
||||
|
||||
/* 起始越界:x 方向越界应返回 -1 */
|
||||
ASSERT_EQ_INT(-1, Lcd_ShowStr(LCD_SIZE_X - 6, 0, text_a));
|
||||
/* 起始越界:y 方向越界应返回 -2 */
|
||||
ASSERT_EQ_INT(-2, Lcd_ShowStr(0, LCD_SIZE_Y - 12, text_a));
|
||||
|
||||
/* 中文第 2 个字符越过右边界时应返回 -1 */
|
||||
Lcd_Init();
|
||||
ASSERT_EQ_INT(-1, Lcd_ShowStr(LCD_SIZE_X - 13, 0, text_cn_overflow_x));
|
||||
|
||||
/* 换行后若超出底部边界,应返回 -2 */
|
||||
Lcd_Init();
|
||||
ASSERT_EQ_INT(-2, Lcd_ShowStr(0, LCD_SIZE_Y - 13, text_nl_overflow_y));
|
||||
|
||||
/* 预留接口目前为桩函数,返回 0 */
|
||||
ASSERT_EQ_INT(0, Lcd_ShowTest(0, 0, text_a));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 测试入口:
|
||||
* 按固定顺序执行所有子用例,任一失败立即返回 1。
|
||||
* 约定:
|
||||
* 返回 0 表示该测试程序通过。
|
||||
* ------------------------------------------------------------------------- */
|
||||
int main(void)
|
||||
{
|
||||
if (test_lcd_init_and_pixel_rw() != 0) return 1;
|
||||
if (test_fillrect_and_invert() != 0) return 1;
|
||||
if (test_lineh_linev() != 0) return 1;
|
||||
if (test_utf8_next_decode() != 0) return 1;
|
||||
if (test_showstr_ascii_utf8_newline_and_bounds() != 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
28
tests/test_p1_menu.c
Normal file
28
tests/test_p1_menu.c
Normal file
@@ -0,0 +1,28 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "test_common.h"
|
||||
#include "../src/Drv/menu.c"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uint8_t menu_num[4] = {0};
|
||||
tagPMenuItem first[4] = {0};
|
||||
tagPMenuItem index[4] = {0};
|
||||
uint8_t max_len;
|
||||
|
||||
Menu_Init();
|
||||
ASSERT_TRUE(g_tMenuCtrl.by0LevelNum > 0);
|
||||
ASSERT_TRUE(g_tMenuCtrl.ptHead != NULL);
|
||||
ASSERT_TRUE(g_tMenuCtrl.ptCurrent != NULL);
|
||||
|
||||
ASSERT_EQ_INT(3, utf8_len_cal((uint8_t*)"ABC"));
|
||||
ASSERT_EQ_INT(2, utf8_len_cal((uint8_t*)"你"));
|
||||
|
||||
first[0] = g_tMenuCtrl.ptHead;
|
||||
index[0] = g_tMenuCtrl.ptHead;
|
||||
max_len = Menu_charLenCal(0, menu_num, first, index);
|
||||
ASSERT_TRUE(max_len > 0);
|
||||
ASSERT_TRUE(menu_num[1] > 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
81
tests/test_p2_tcp_loopback.c
Normal file
81
tests/test_p2_tcp_loopback.c
Normal file
@@ -0,0 +1,81 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "../src/TCP/tcp.h"
|
||||
#include "../src/thread_utils.h"
|
||||
#include "test_common.h"
|
||||
|
||||
typedef struct {
|
||||
int port;
|
||||
volatile int ready;
|
||||
volatile int done;
|
||||
int result;
|
||||
} loopback_ctx_t;
|
||||
|
||||
static void server_fn(void* arg)
|
||||
{
|
||||
loopback_ctx_t* ctx = (loopback_ctx_t*)arg;
|
||||
int server = TcpServer_Listen((uint16_t)ctx->port);
|
||||
char buf[32] = {0};
|
||||
int client;
|
||||
int n;
|
||||
|
||||
if (server == TCP_INVALID_SOCKET) {
|
||||
ctx->result = 1;
|
||||
ctx->done = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->ready = 1;
|
||||
client = TcpServer_Accept(server);
|
||||
if (client == TCP_INVALID_SOCKET) {
|
||||
ctx->result = 2;
|
||||
TcpServer_Close(server);
|
||||
ctx->done = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
n = TcpClient_Recv(client, buf, sizeof(buf));
|
||||
if (n <= 0 || strncmp(buf, "ping", 4) != 0) {
|
||||
ctx->result = 3;
|
||||
} else {
|
||||
TcpClient_Send(client, "pong", 4);
|
||||
ctx->result = 0;
|
||||
}
|
||||
|
||||
TcpClient_Close(client);
|
||||
TcpServer_Close(server);
|
||||
ctx->done = 1;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
loopback_ctx_t ctx;
|
||||
thread_handle_t th;
|
||||
int client;
|
||||
char recv_buf[8] = {0};
|
||||
int n;
|
||||
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
ctx.port = 7013;
|
||||
|
||||
ASSERT_EQ_INT(0, Tcp_Init());
|
||||
ASSERT_EQ_INT(0, Thread_Create(server_fn, &ctx, &th));
|
||||
|
||||
while (!ctx.ready) {
|
||||
/* wait */
|
||||
}
|
||||
|
||||
client = TcpClient_Connect("127.0.0.1", (uint16_t)ctx.port);
|
||||
ASSERT_TRUE(client != TCP_INVALID_SOCKET);
|
||||
ASSERT_EQ_INT(4, TcpClient_Send(client, "ping", 4));
|
||||
|
||||
n = TcpClient_Recv(client, recv_buf, sizeof(recv_buf));
|
||||
ASSERT_EQ_INT(4, n);
|
||||
ASSERT_TRUE(strncmp(recv_buf, "pong", 4) == 0);
|
||||
TcpClient_Close(client);
|
||||
|
||||
Thread_Join(th);
|
||||
ASSERT_EQ_INT(0, ctx.result);
|
||||
Tcp_Cleanup();
|
||||
return 0;
|
||||
}
|
||||
10
tests/tests_smoke.c
Normal file
10
tests/tests_smoke.c
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "test_common.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
ASSERT_TRUE(1);
|
||||
ASSERT_EQ_INT(4, 2 + 2);
|
||||
ASSERT_EQ_U32(0x12345678, 0x12345678);
|
||||
ASSERT_STREQ("Hello, World!", "Hello, World!");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user