mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-05-10 02:09:46 +08:00
feat(test): 添加单元测试框架并完善国际化支持
- 新增 CMake 测试框架配置,支持 safe_string、string_ext 和 path_manager 模块的单元测试 - 实现 Windows API Mock 机制,便于测试编码转换函数 - 添加 error_code 模块的字符串表示函数,支持英文错误日志 - 在 UI 回调函数中集成国际化翻译,覆盖新建、编辑、导入导出等操作提示 - 扩展 string_list 功能,新增重复路径检查函数 - 更新翻译文件,同步所有用户界面的中英文文本
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# PathEditor 单元测试框架
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# 测试子项目配置
|
||||
project(PathEditorTests C)
|
||||
|
||||
# 设置 C 标准(与主项目一致)
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
|
||||
# 包含主项目的头文件路径
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_SOURCE_DIR}/include/core
|
||||
${CMAKE_SOURCE_DIR}/include/ui
|
||||
${CMAKE_SOURCE_DIR}/include/controller
|
||||
${CMAKE_SOURCE_DIR}/include/utils
|
||||
${CMAKE_SOURCE_DIR}/libs/IUP/include
|
||||
${CMAKE_SOURCE_DIR}/libs/lua/include
|
||||
${CMAKE_SOURCE_DIR}/libs/gettext/include
|
||||
)
|
||||
|
||||
# 获取 CMocka(如果系统没有则下载)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
cmocka
|
||||
GIT_REPOSITORY https://git.cryptomilk.org/projects/cmocka.git
|
||||
GIT_TAG cmocka-1.1.5
|
||||
)
|
||||
FetchContent_MakeAvailable(cmocka)
|
||||
|
||||
# 启用测试
|
||||
enable_testing()
|
||||
include(CTest)
|
||||
|
||||
# 添加各测试模块
|
||||
add_subdirectory(unit/safe_string)
|
||||
add_subdirectory(unit/string_ext)
|
||||
add_subdirectory(unit/path_manager)
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* mock_windows.h
|
||||
* Windows API Mock 头文件
|
||||
* 用于单元测试中模拟 Windows API
|
||||
*/
|
||||
#ifndef MOCK_WINDOWS_H
|
||||
#define MOCK_WINDOWS_H
|
||||
|
||||
#ifdef TESTING
|
||||
|
||||
#include <windows.h>
|
||||
#include <wchar.h>
|
||||
|
||||
/* Mock 计数器,用于验证调用 */
|
||||
extern int mock_MultiByteToWideChar_call_count;
|
||||
extern int mock_WideCharToMultiByte_call_count;
|
||||
|
||||
/* 设置 Mock 返回值 */
|
||||
void mock_set_MultiByteToWideChar_return(int ret);
|
||||
void mock_set_WideCharToMultiByte_return(int ret);
|
||||
|
||||
/* Mock MultiByteToWideChar */
|
||||
int mock_MultiByteToWideChar(
|
||||
UINT CodePage,
|
||||
DWORD dwFlags,
|
||||
LPCSTR lpMultiByteStr,
|
||||
int cbMultiByte,
|
||||
LPWSTR lpWideCharStr,
|
||||
int cchWideChar);
|
||||
|
||||
/* Mock WideCharToMultiByte */
|
||||
int mock_WideCharToMultiByte(
|
||||
UINT CodePage,
|
||||
DWORD dwFlags,
|
||||
LPCWSTR lpWideCharStr,
|
||||
int cchWideChar,
|
||||
LPSTR lpMultiByteStr,
|
||||
int cbMultiByte,
|
||||
LPCSTR lpDefaultChar,
|
||||
LPBOOL lpUsedDefaultChar);
|
||||
|
||||
/* 替换宏(在测试源文件中定义) */
|
||||
#ifdef REPLACE_WINDOWS_API
|
||||
#define MultiByteToWideChar mock_MultiByteToWideChar
|
||||
#define WideCharToMultiByte mock_WideCharToMultiByte
|
||||
#endif
|
||||
|
||||
#else
|
||||
/* 非测试模式下为空 */
|
||||
#define REPLACE_WINDOWS_API 0
|
||||
|
||||
#endif /* TESTING */
|
||||
|
||||
#endif /* MOCK_WINDOWS_H */
|
||||
@@ -0,0 +1,20 @@
|
||||
# path_manager 单元测试
|
||||
add_executable(test_path_manager test_path_manager.c
|
||||
${CMAKE_SOURCE_DIR}/src/core/path_manager.c
|
||||
${CMAKE_SOURCE_DIR}/src/utils/string_ext.c
|
||||
${CMAKE_SOURCE_DIR}/src/utils/safe_string.c
|
||||
${CMAKE_SOURCE_DIR}/src/utils/error_code.c
|
||||
)
|
||||
|
||||
target_link_libraries(test_path_manager cmocka)
|
||||
|
||||
target_include_directories(test_path_manager PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src/core
|
||||
${CMAKE_SOURCE_DIR}/src/utils
|
||||
)
|
||||
|
||||
# 定义 TESTING 宏以启用 mock
|
||||
target_compile_definitions(test_path_manager PRIVATE TESTING)
|
||||
|
||||
# 添加测试
|
||||
add_test(NAME path_manager_test COMMAND test_path_manager)
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* path_manager.c 单元测试
|
||||
* 测试路径管理函数
|
||||
*/
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <setjmp.h>
|
||||
#include <cmocka.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "core/path_manager.h"
|
||||
|
||||
/* ==================== Mock 函数 ==================== */
|
||||
|
||||
#ifdef TESTING
|
||||
|
||||
/* Mock is_path_valid - 默认返回 1(有效)*/
|
||||
int is_path_valid_mock_enabled = 0;
|
||||
int is_path_valid_mock_return = 1;
|
||||
|
||||
int is_path_valid(const char *path)
|
||||
{
|
||||
(void)path;
|
||||
if (is_path_valid_mock_enabled) {
|
||||
return is_path_valid_mock_return;
|
||||
}
|
||||
return 1; /* 默认认为路径有效 */
|
||||
}
|
||||
|
||||
/* Mock 日志函数 - 避免链接日志文件依赖 */
|
||||
int log_info_enabled = 0;
|
||||
int log_debug_enabled = 0;
|
||||
int log_warn_enabled = 0;
|
||||
int log_error_enabled = 0;
|
||||
|
||||
void log_info(const char *fmt, ...)
|
||||
{
|
||||
(void)fmt;
|
||||
log_info_enabled++;
|
||||
}
|
||||
|
||||
void log_debug(const char *fmt, ...)
|
||||
{
|
||||
(void)fmt;
|
||||
log_debug_enabled++;
|
||||
}
|
||||
|
||||
void log_warn(const char *fmt, ...)
|
||||
{
|
||||
(void)fmt;
|
||||
log_warn_enabled++;
|
||||
}
|
||||
|
||||
void log_error(const char *fmt, ...)
|
||||
{
|
||||
(void)fmt;
|
||||
log_error_enabled++;
|
||||
}
|
||||
|
||||
#endif /* TESTING */
|
||||
|
||||
/* ==================== path_manager_remove_at 测试 ==================== */
|
||||
|
||||
static void test_remove_at_normal(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "path1");
|
||||
add_string_list(&list, "path2");
|
||||
add_string_list(&list, "path3");
|
||||
|
||||
ErrorCode result = path_manager_remove_at(&list, 1);
|
||||
|
||||
assert_int_equal(result, ERR_OK);
|
||||
assert_int_equal(list.count, 2);
|
||||
assert_string_equal(string_list_get(&list, 0), "path1");
|
||||
assert_string_equal(string_list_get(&list, 1), "path3");
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
static void test_remove_at_first(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "path1");
|
||||
add_string_list(&list, "path2");
|
||||
|
||||
ErrorCode result = path_manager_remove_at(&list, 0);
|
||||
|
||||
assert_int_equal(result, ERR_OK);
|
||||
assert_int_equal(list.count, 1);
|
||||
assert_string_equal(string_list_get(&list, 0), "path2");
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
static void test_remove_at_last(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "path1");
|
||||
add_string_list(&list, "path2");
|
||||
|
||||
ErrorCode result = path_manager_remove_at(&list, 1);
|
||||
|
||||
assert_int_equal(result, ERR_OK);
|
||||
assert_int_equal(list.count, 1);
|
||||
assert_string_equal(string_list_get(&list, 0), "path1");
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
static void test_remove_at_invalid_index(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "path1");
|
||||
|
||||
ErrorCode result = path_manager_remove_at(&list, 5); /* 越界 */
|
||||
|
||||
assert_int_not_equal(result, ERR_OK);
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
static void test_remove_at_null(void **state)
|
||||
{
|
||||
(void)state;
|
||||
ErrorCode result = path_manager_remove_at(NULL, 0);
|
||||
assert_int_equal(result, ERR_NULL_PTR);
|
||||
}
|
||||
|
||||
/* ==================== path_manager_move_up 测试 ==================== */
|
||||
|
||||
static void test_move_up_normal(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "path1");
|
||||
add_string_list(&list, "path2");
|
||||
add_string_list(&list, "path3");
|
||||
|
||||
ErrorCode result = path_manager_move_up(&list, 2);
|
||||
|
||||
assert_int_equal(result, ERR_OK);
|
||||
assert_string_equal(string_list_get(&list, 0), "path1");
|
||||
assert_string_equal(string_list_get(&list, 1), "path3");
|
||||
assert_string_equal(string_list_get(&list, 2), "path2");
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
static void test_move_up_first_element(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "path1");
|
||||
add_string_list(&list, "path2");
|
||||
|
||||
ErrorCode result = path_manager_move_up(&list, 0); /* 第一个元素无法上移 */
|
||||
|
||||
assert_int_equal(result, ERR_INVALID_INDEX);
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
static void test_move_up_invalid_index(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "path1");
|
||||
add_string_list(&list, "path2");
|
||||
|
||||
ErrorCode result = path_manager_move_up(&list, 5); /* 越界 */
|
||||
|
||||
assert_int_not_equal(result, ERR_OK);
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
static void test_move_up_single_element(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "path1");
|
||||
|
||||
ErrorCode result = path_manager_move_up(&list, 0);
|
||||
|
||||
assert_int_equal(result, ERR_INVALID_INDEX);
|
||||
assert_string_equal(string_list_get(&list, 0), "path1");
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
/* ==================== path_manager_move_down 测试 ==================== */
|
||||
|
||||
static void test_move_down_normal(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "path1");
|
||||
add_string_list(&list, "path2");
|
||||
add_string_list(&list, "path3");
|
||||
|
||||
ErrorCode result = path_manager_move_down(&list, 1);
|
||||
|
||||
assert_int_equal(result, ERR_OK);
|
||||
assert_string_equal(string_list_get(&list, 0), "path1");
|
||||
assert_string_equal(string_list_get(&list, 1), "path3");
|
||||
assert_string_equal(string_list_get(&list, 2), "path2");
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
static void test_move_down_last_element(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "path1");
|
||||
add_string_list(&list, "path2");
|
||||
|
||||
ErrorCode result = path_manager_move_down(&list, 1); /* 最后一个元素无法下移 */
|
||||
|
||||
assert_int_equal(result, ERR_INVALID_INDEX);
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
static void test_move_down_invalid_index(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "path1");
|
||||
add_string_list(&list, "path2");
|
||||
|
||||
ErrorCode result = path_manager_move_down(&list, 5); /* 越界 */
|
||||
|
||||
assert_int_not_equal(result, ERR_OK);
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
static void test_move_down_single_element(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "path1");
|
||||
|
||||
ErrorCode result = path_manager_move_down(&list, 0);
|
||||
|
||||
assert_int_equal(result, ERR_INVALID_INDEX);
|
||||
assert_string_equal(string_list_get(&list, 0), "path1");
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
/* ==================== path_manager_clean 测试 ==================== */
|
||||
|
||||
static void test_clean_no_invalid(void **state)
|
||||
{
|
||||
(void)state;
|
||||
is_path_valid_mock_enabled = 1;
|
||||
is_path_valid_mock_return = 1;
|
||||
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "C:\\Windows");
|
||||
add_string_list(&list, "C:\\Program Files");
|
||||
|
||||
int before_count = list.count;
|
||||
ErrorCode result = path_manager_clean(&list);
|
||||
|
||||
assert_int_equal(result, ERR_OK);
|
||||
assert_int_equal(list.count, before_count); /* 没有无效路径,不应删除 */
|
||||
|
||||
is_path_valid_mock_enabled = 0;
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
static void test_clean_with_invalid(void **state)
|
||||
{
|
||||
(void)state;
|
||||
is_path_valid_mock_enabled = 1;
|
||||
is_path_valid_mock_return = 0; /* 所有路径都无效 */
|
||||
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
add_string_list(&list, "C:\\Invalid1");
|
||||
add_string_list(&list, "C:\\Invalid2");
|
||||
add_string_list(&list, "C:\\Invalid3");
|
||||
|
||||
ErrorCode result = path_manager_clean(&list);
|
||||
|
||||
assert_int_equal(result, ERR_OK);
|
||||
assert_int_equal(list.count, 0); /* 所有路径都被删除 */
|
||||
|
||||
is_path_valid_mock_enabled = 0;
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
static void test_clean_empty_list(void **state)
|
||||
{
|
||||
(void)state;
|
||||
StringList list;
|
||||
init_string_list(&list);
|
||||
|
||||
ErrorCode result = path_manager_clean(&list);
|
||||
|
||||
assert_int_equal(result, ERR_OK);
|
||||
assert_int_equal(list.count, 0);
|
||||
|
||||
clear_string_list(&list);
|
||||
}
|
||||
|
||||
/* ==================== 主函数 ==================== */
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const struct CMUnitTest tests[] = {
|
||||
/* remove_at 测试 */
|
||||
cmocka_unit_test(test_remove_at_normal),
|
||||
cmocka_unit_test(test_remove_at_first),
|
||||
cmocka_unit_test(test_remove_at_last),
|
||||
cmocka_unit_test(test_remove_at_invalid_index),
|
||||
cmocka_unit_test(test_remove_at_null),
|
||||
|
||||
/* move_up 测试 */
|
||||
cmocka_unit_test(test_move_up_normal),
|
||||
cmocka_unit_test(test_move_up_first_element),
|
||||
cmocka_unit_test(test_move_up_invalid_index),
|
||||
cmocka_unit_test(test_move_up_single_element),
|
||||
|
||||
/* move_down 测试 */
|
||||
cmocka_unit_test(test_move_down_normal),
|
||||
cmocka_unit_test(test_move_down_last_element),
|
||||
cmocka_unit_test(test_move_down_invalid_index),
|
||||
cmocka_unit_test(test_move_down_single_element),
|
||||
|
||||
/* clean 测试 */
|
||||
cmocka_unit_test(test_clean_no_invalid),
|
||||
cmocka_unit_test(test_clean_with_invalid),
|
||||
cmocka_unit_test(test_clean_empty_list),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# safe_string 单元测试
|
||||
add_executable(test_safe_string test_safe_string.c
|
||||
${CMAKE_SOURCE_DIR}/src/utils/safe_string.c
|
||||
)
|
||||
|
||||
target_link_libraries(test_safe_string cmocka)
|
||||
|
||||
target_include_directories(test_safe_string PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src/utils
|
||||
)
|
||||
|
||||
# 定义 TESTING 宏以启用 mock
|
||||
target_compile_definitions(test_safe_string PRIVATE TESTING)
|
||||
|
||||
# 添加测试
|
||||
add_test(NAME safe_string_test COMMAND test_safe_string)
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* safe_string.c 单元测试
|
||||
* 测试安全字符串操作函数
|
||||
*/
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <setjmp.h>
|
||||
#include <cmocka.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "utils/safe_string.h"
|
||||
|
||||
/* ==================== safe_strcpy 测试 ==================== */
|
||||
|
||||
static void test_safe_strcpy_normal(void **state)
|
||||
{
|
||||
(void)state;
|
||||
char dst[32];
|
||||
const char *src = "Hello, World!";
|
||||
char *result = safe_strcpy(dst, sizeof(dst), src);
|
||||
|
||||
assert_non_null(result);
|
||||
assert_string_equal(dst, src);
|
||||
}
|
||||
|
||||
static void test_safe_strcpy_truncation(void **state)
|
||||
{
|
||||
(void)state;
|
||||
char dst[8];
|
||||
const char *src = "This is a long string";
|
||||
char *result = safe_strcpy(dst, sizeof(dst), src);
|
||||
|
||||
assert_non_null(result);
|
||||
assert_int_equal(strlen(dst), 7); /* 截断到 7 字符 */
|
||||
assert_memory_equal(dst, src, 7); /* 前 7 字符相同 */
|
||||
assert_int_equal(dst[7], '\0');
|
||||
}
|
||||
|
||||
static void test_safe_strcpy_null_dst(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const char *src = "test";
|
||||
char *result = safe_strcpy(NULL, 10, src);
|
||||
assert_null(result);
|
||||
}
|
||||
|
||||
static void test_safe_strcpy_null_src(void **state)
|
||||
{
|
||||
(void)state;
|
||||
char dst[32];
|
||||
char *result = safe_strcpy(dst, sizeof(dst), NULL);
|
||||
assert_null(result);
|
||||
}
|
||||
|
||||
static void test_safe_strcpy_zero_size(void **state)
|
||||
{
|
||||
(void)state;
|
||||
char dst[32];
|
||||
char *result = safe_strcpy(dst, 0, "test");
|
||||
assert_null(result);
|
||||
}
|
||||
|
||||
static void test_safe_strcpy_exact_fit(void **state)
|
||||
{
|
||||
(void)state;
|
||||
char dst[6];
|
||||
const char *src = "12345"; /* 5字符 + 1终止符 = 6 */
|
||||
char *result = safe_strcpy(dst, sizeof(dst), src);
|
||||
|
||||
assert_non_null(result);
|
||||
assert_string_equal(dst, "12345");
|
||||
}
|
||||
|
||||
/* ==================== safe_strcat 测试 ==================== */
|
||||
|
||||
static void test_safe_strcat_normal(void **state)
|
||||
{
|
||||
(void)state;
|
||||
char dst[32] = "Hello";
|
||||
const char *src = ", World!";
|
||||
char *result = safe_strcat(dst, sizeof(dst), src);
|
||||
|
||||
assert_non_null(result);
|
||||
assert_string_equal(dst, "Hello, World!");
|
||||
}
|
||||
|
||||
static void test_safe_strcat_truncation(void **state)
|
||||
{
|
||||
(void)state;
|
||||
char dst[12] = "Hello";
|
||||
const char *src = ", World!"; /* 总长 12,但最后一个位置要放 \0 */
|
||||
char *result = safe_strcat(dst, sizeof(dst), src);
|
||||
|
||||
assert_non_null(result);
|
||||
/* dst 有 11 可用位置 (12-1),"Hello" 占 5,还剩 6 */
|
||||
/* ", World!" 有 9 字符,只能放 6 个字符 + \0 */
|
||||
assert_true(strlen(dst) <= 11);
|
||||
}
|
||||
|
||||
static void test_safe_strcat_null_dst(void **state)
|
||||
{
|
||||
(void)state;
|
||||
char dst[32] = "test";
|
||||
char *result = safe_strcat(NULL, sizeof(dst), "src");
|
||||
assert_null(result);
|
||||
}
|
||||
|
||||
static void test_safe_strcat_null_src(void **state)
|
||||
{
|
||||
(void)state;
|
||||
char dst[32] = "test";
|
||||
char *result = safe_strcat(dst, sizeof(dst), NULL);
|
||||
/* src 为 NULL 时函数返回 NULL */
|
||||
assert_null(result);
|
||||
}
|
||||
|
||||
static void test_safe_strcat_empty_dst(void **state)
|
||||
{
|
||||
(void)state;
|
||||
char dst[32] = "";
|
||||
const char *src = "Hello";
|
||||
char *result = safe_strcat(dst, sizeof(dst), src);
|
||||
|
||||
assert_non_null(result);
|
||||
assert_string_equal(dst, "Hello");
|
||||
}
|
||||
|
||||
/* ==================== safe_strdup 测试 ==================== */
|
||||
|
||||
static void test_safe_strdup_normal(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const char *src = "Hello, World!";
|
||||
char *result = safe_strdup(src);
|
||||
|
||||
assert_non_null(result);
|
||||
assert_string_equal(result, src);
|
||||
assert_ptr_not_equal(result, src); /* 必须是新分配的内存 */
|
||||
|
||||
free(result);
|
||||
}
|
||||
|
||||
static void test_safe_strdup_null(void **state)
|
||||
{
|
||||
(void)state;
|
||||
char *result = safe_strdup(NULL);
|
||||
assert_null(result);
|
||||
}
|
||||
|
||||
static void test_safe_strdup_empty_string(void **state)
|
||||
{
|
||||
(void)state;
|
||||
const char *src = "";
|
||||
char *result = safe_strdup(src);
|
||||
|
||||
assert_non_null(result);
|
||||
assert_string_equal(result, "");
|
||||
|
||||
free(result);
|
||||
}
|
||||
|
||||
static void test_safe_strdup_long_string(void **state)
|
||||
{
|
||||
(void)state;
|
||||
/* 构造一个 510 字符的字符串 */
|
||||
char src[511];
|
||||
for (int i = 0; i < 510; i++) {
|
||||
src[i] = 'A';
|
||||
}
|
||||
src[510] = '\0';
|
||||
|
||||
char *result = safe_strdup(src);
|
||||
|
||||
assert_non_null(result);
|
||||
assert_int_equal(strlen(result), 510);
|
||||
assert_memory_equal(result, src, 510);
|
||||
|
||||
free(result);
|
||||
}
|
||||
|
||||
/* ==================== 主函数 ==================== */
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const struct CMUnitTest tests[] = {
|
||||
/* safe_strcpy 测试 */
|
||||
cmocka_unit_test(test_safe_strcpy_normal),
|
||||
cmocka_unit_test(test_safe_strcpy_truncation),
|
||||
cmocka_unit_test(test_safe_strcpy_null_dst),
|
||||
cmocka_unit_test(test_safe_strcpy_null_src),
|
||||
cmocka_unit_test(test_safe_strcpy_zero_size),
|
||||
cmocka_unit_test(test_safe_strcpy_exact_fit),
|
||||
|
||||
/* safe_strcat 测试 */
|
||||
cmocka_unit_test(test_safe_strcat_normal),
|
||||
cmocka_unit_test(test_safe_strcat_truncation),
|
||||
cmocka_unit_test(test_safe_strcat_null_dst),
|
||||
cmocka_unit_test(test_safe_strcat_null_src),
|
||||
cmocka_unit_test(test_safe_strcat_empty_dst),
|
||||
|
||||
/* safe_strdup 测试 */
|
||||
cmocka_unit_test(test_safe_strdup_normal),
|
||||
cmocka_unit_test(test_safe_strdup_null),
|
||||
cmocka_unit_test(test_safe_strdup_empty_string),
|
||||
cmocka_unit_test(test_safe_strdup_long_string),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# string_ext 单元测试
|
||||
add_executable(test_string_ext test_string_ext.c
|
||||
${CMAKE_SOURCE_DIR}/src/utils/string_ext.c
|
||||
${CMAKE_SOURCE_DIR}/src/utils/safe_string.c
|
||||
)
|
||||
|
||||
target_link_libraries(test_string_ext cmocka)
|
||||
|
||||
target_include_directories(test_string_ext PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src/utils
|
||||
${CMAKE_SOURCE_DIR}/tests/mocks
|
||||
)
|
||||
|
||||
# 定义 TESTING 宏和 REPLACE_WINDOWS_API 以启用 mock
|
||||
target_compile_definitions(test_string_ext PRIVATE TESTING REPLACE_WINDOWS_API)
|
||||
|
||||
# 添加测试
|
||||
add_test(NAME string_ext_test COMMAND test_string_ext)
|
||||
@@ -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 设置为 NULL,count 和 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);
|
||||
}
|
||||
Reference in New Issue
Block a user