feat(test): 添加单元测试框架并完善国际化支持

- 新增 CMake 测试框架配置,支持 safe_string、string_ext 和 path_manager 模块的单元测试
- 实现 Windows API Mock 机制,便于测试编码转换函数
- 添加 error_code 模块的字符串表示函数,支持英文错误日志
- 在 UI 回调函数中集成国际化翻译,覆盖新建、编辑、导入导出等操作提示
- 扩展 string_list 功能,新增重复路径检查函数
- 更新翻译文件,同步所有用户界面的中英文文本
This commit is contained in:
2026-04-30 11:38:05 +08:00
parent ceed90aea8
commit cf19a37a97
20 changed files with 1778 additions and 55 deletions
+354
View File
@@ -0,0 +1,354 @@
/*
* string_ext.c 单元测试
* 测试字符串扩展函数和 StringList 操作
*/
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <string.h>
#include <stdlib.h>
#include "utils/string_ext.h"
#include "mock_windows.h"
/* ==================== Mock 计数器 ==================== */
int mock_MultiByteToWideChar_call_count = 0;
int mock_WideCharToMultiByte_call_count = 0;
static int mock_MB2WC_return = 0;
static int mock_WC2MB_return = 0;
void mock_set_MultiByteToWideChar_return(int ret) {
mock_MB2WC_return = ret;
}
void mock_set_WideCharToMultiByte_return(int ret) {
mock_WC2MB_return = ret;
}
/* ==================== Mock 实现 ==================== */
int mock_MultiByteToWideChar(
UINT CodePage,
DWORD dwFlags,
LPCSTR lpMultiByteStr,
int cbMultiByte,
LPWSTR lpWideCharStr,
int cchWideChar)
{
(void)CodePage;
(void)dwFlags;
mock_MultiByteToWideChar_call_count++;
if (!lpMultiByteStr || cbMultiByte == 0)
return 0;
int len = (cbMultiByte == -1) ? strlen(lpMultiByteStr) : cbMultiByte;
/* 简单 ASCII 转宽字符 */
if (lpWideCharStr && cchWideChar > 0) {
int copy_len = (cchWideChar < len + 1) ? cchWideChar - 1 : len;
for (int i = 0; i < copy_len; i++) {
lpWideCharStr[i] = (wchar_t)lpMultiByteStr[i];
}
lpWideCharStr[copy_len] = L'\0';
}
return mock_MB2WC_return > 0 ? mock_MB2WC_return : (len + 1);
}
int mock_WideCharToMultiByte(
UINT CodePage,
DWORD dwFlags,
LPCWSTR lpWideCharStr,
int cchWideChar,
LPSTR lpMultiByteStr,
int cbMultiByte,
LPCSTR lpDefaultChar,
LPBOOL lpUsedDefaultChar)
{
(void)CodePage;
(void)dwFlags;
(void)lpDefaultChar;
(void)lpUsedDefaultChar;
mock_WideCharToMultiByte_call_count++;
if (!lpWideCharStr || cchWideChar == 0)
return 0;
int len = (cchWideChar == -1) ? wcslen(lpWideCharStr) : cchWideChar;
if (lpMultiByteStr && cbMultiByte > 0) {
int copy_len = (cbMultiByte < len + 1) ? cbMultiByte - 1 : len;
for (int i = 0; i < copy_len; i++) {
lpMultiByteStr[i] = (char)lpWideCharStr[i];
}
lpMultiByteStr[copy_len] = '\0';
}
return mock_WC2MB_return > 0 ? mock_WC2MB_return : (len + 1);
}
/* ==================== StringList 测试 ==================== */
static void test_init_string_list(void **state)
{
(void)state;
StringList list;
list.items = (void *)0x1234; /* 初始化前设置垃圾值 */
list.count = 999;
list.capacity = 999;
init_string_list(&list);
/* init_string_list 将 items 设置为 NULLcount 和 capacity 设为 0 */
assert_null(list.items);
assert_int_equal(list.count, 0);
assert_int_equal(list.capacity, 0);
/* clear_string_list 对 NULL items 应该安全处理 */
clear_string_list(&list);
}
static void test_add_string_list_single(void **state)
{
(void)state;
StringList list;
init_string_list(&list);
add_string_list(&list, "C:\\Windows");
assert_int_equal(list.count, 1);
assert_string_equal(string_list_get(&list, 0), "C:\\Windows");
clear_string_list(&list);
}
static void test_add_string_list_multiple(void **state)
{
(void)state;
StringList list;
init_string_list(&list);
add_string_list(&list, "C:\\Windows");
add_string_list(&list, "C:\\Program Files");
add_string_list(&list, "D:\\Tools");
assert_int_equal(list.count, 3);
assert_string_equal(string_list_get(&list, 0), "C:\\Windows");
assert_string_equal(string_list_get(&list, 1), "C:\\Program Files");
assert_string_equal(string_list_get(&list, 2), "D:\\Tools");
clear_string_list(&list);
}
static void test_string_list_get_out_of_bounds(void **state)
{
(void)state;
StringList list;
init_string_list(&list);
add_string_list(&list, "test");
assert_null(string_list_get(&list, -1));
assert_null(string_list_get(&list, 1));
assert_null(string_list_get(&list, 100));
clear_string_list(&list);
}
static void test_string_list_get_null_list(void **state)
{
(void)state;
assert_null(string_list_get(NULL, 0));
}
static void test_string_list_set_normal(void **state)
{
(void)state;
StringList list;
init_string_list(&list);
add_string_list(&list, "original");
int result = string_list_set(&list, 0, "modified");
assert_int_equal(result, 0);
assert_string_equal(string_list_get(&list, 0), "modified");
clear_string_list(&list);
}
static void test_string_list_set_out_of_bounds(void **state)
{
(void)state;
StringList list;
init_string_list(&list);
add_string_list(&list, "test");
int result = string_list_set(&list, 5, "modified");
assert_int_equal(result, -1);
clear_string_list(&list);
}
static void test_string_list_set_null_list(void **state)
{
(void)state;
int result = string_list_set(NULL, 0, "test");
assert_int_equal(result, -1);
}
static void test_clear_string_list(void **state)
{
(void)state;
StringList list;
init_string_list(&list);
add_string_list(&list, "item1");
add_string_list(&list, "item2");
add_string_list(&list, "item3");
clear_string_list(&list);
assert_int_equal(list.count, 0);
assert_null(list.items);
}
/* ==================== 编码转换测试 ==================== */
static void test_utf8_to_wide_normal(void **state)
{
(void)state;
const char *utf8_str = "Hello";
wchar_t *result = utf8_to_wide(utf8_str);
if (result) {
assert_true(wcscmp(result, L"Hello") == 0);
free(result);
}
}
static void test_utf8_to_wide_null(void **state)
{
(void)state;
wchar_t *result = utf8_to_wide(NULL);
assert_null(result);
}
static void test_wide_to_utf8_normal(void **state)
{
(void)state;
const wchar_t *wide_str = L"World";
char *result = wide_to_utf8(wide_str);
if (result) {
assert_string_equal(result, "World");
free(result);
}
}
static void test_wide_to_utf8_null(void **state)
{
(void)state;
char *result = wide_to_utf8(NULL);
assert_null(result);
}
/* ==================== stristr 测试 ==================== */
static void test_stristr_found(void **state)
{
(void)state;
const char *haystack = "The quick brown fox";
const char *needle = "quick";
char *result = stristr(haystack, needle);
assert_non_null(result);
assert_ptr_equal(result, haystack + 4); /* "quick" 在 "The " 之后 */
}
static void test_stristr_not_found(void **state)
{
(void)state;
const char *haystack = "The quick brown fox";
const char *needle = "jumps";
char *result = stristr(haystack, needle);
assert_null(result);
}
static void test_stristr_case_insensitive(void **state)
{
(void)state;
const char *haystack = "The QUICK brown fox";
const char *needle = "quick";
char *result = stristr(haystack, needle);
assert_non_null(result);
}
static void test_stristr_null_haystack(void **state)
{
(void)state;
char *result = stristr(NULL, "test");
assert_null(result);
}
static void test_stristr_null_needle(void **state)
{
(void)state;
char *result = stristr("test", NULL);
assert_null(result);
}
static void test_stristr_empty_needle(void **state)
{
(void)state;
const char *haystack = "The quick brown fox";
const char *needle = "";
char *result = stristr(haystack, needle);
/* 空字符串应该返回原字符串首地址 */
assert_non_null(result);
assert_ptr_equal(result, haystack);
}
/* ==================== 主函数 ==================== */
int main(void)
{
const struct CMUnitTest tests[] = {
/* StringList 测试 */
cmocka_unit_test(test_init_string_list),
cmocka_unit_test(test_add_string_list_single),
cmocka_unit_test(test_add_string_list_multiple),
cmocka_unit_test(test_string_list_get_out_of_bounds),
cmocka_unit_test(test_string_list_get_null_list),
cmocka_unit_test(test_string_list_set_normal),
cmocka_unit_test(test_string_list_set_out_of_bounds),
cmocka_unit_test(test_string_list_set_null_list),
cmocka_unit_test(test_clear_string_list),
/* 编码转换测试 */
cmocka_unit_test(test_utf8_to_wide_normal),
cmocka_unit_test(test_utf8_to_wide_null),
cmocka_unit_test(test_wide_to_utf8_normal),
cmocka_unit_test(test_wide_to_utf8_null),
/* stristr 测试 */
cmocka_unit_test(test_stristr_found),
cmocka_unit_test(test_stristr_not_found),
cmocka_unit_test(test_stristr_case_insensitive),
cmocka_unit_test(test_stristr_null_haystack),
cmocka_unit_test(test_stristr_null_needle),
cmocka_unit_test(test_stristr_empty_needle),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}