/* ------------------------------------------------------------------------- * 文件名: 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; }