重构代码的架构设计,增加测试单元,提高代码可靠性

This commit is contained in:
2026-03-23 20:40:04 +08:00
parent c2ce221691
commit a4bf0962b2
31 changed files with 2084 additions and 703 deletions

108
tests/CMakeLists.txt Normal file
View 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
View 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

View 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"
/* 用例1CRC 计算
* - 空数据长度应返回 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;
}

View 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
View 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
View 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
View 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
View 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;
}

View 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
View 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;
}