mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-05-09 18:02:52 +08:00
feat(test): 添加单元测试框架并完善国际化支持
- 新增 CMake 测试框架配置,支持 safe_string、string_ext 和 path_manager 模块的单元测试 - 实现 Windows API Mock 机制,便于测试编码转换函数 - 添加 error_code 模块的字符串表示函数,支持英文错误日志 - 在 UI 回调函数中集成国际化翻译,覆盖新建、编辑、导入导出等操作提示 - 扩展 string_list 功能,新增重复路径检查函数 - 更新翻译文件,同步所有用户界面的中英文文本
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(PathEditor VERSION 3.0 LANGUAGES C)
|
||||
|
||||
# 选项:是否构建测试
|
||||
option(BUILD_TESTS "Build unit tests" OFF)
|
||||
|
||||
# 启用资源编译器以处理 .rc 文件
|
||||
enable_language(RC)
|
||||
|
||||
@@ -18,6 +21,7 @@ set(SOURCES
|
||||
src/utils/safe_string.c
|
||||
src/utils/logger.c
|
||||
src/utils/i18n.c
|
||||
src/utils/error_code.c
|
||||
src/ui/ui_utils.c
|
||||
src/ui/dialogs.c
|
||||
src/ui/main_window.c
|
||||
@@ -123,3 +127,9 @@ add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/locale"
|
||||
COMMENT "Copying locale directory to build directory..."
|
||||
)
|
||||
|
||||
# 测试支持
|
||||
if(BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <iup.h>
|
||||
#include "core/app_context.h"
|
||||
#include "utils/i18n.h"
|
||||
|
||||
// 内部辅助函数声明(供各 callbacks_*.c 文件共享)
|
||||
// 这些函数不对外暴露,仅在 controller 层内部使用
|
||||
|
||||
@@ -15,6 +15,7 @@ typedef enum {
|
||||
ERR_INVALID_INDEX = -10 // 无效索引
|
||||
} ErrorCode;
|
||||
|
||||
// 获取错误码的字符串表示(英文,用于日志)
|
||||
const char* error_code_to_string(ErrorCode code);
|
||||
|
||||
#endif // ERROR_CODE_H
|
||||
|
||||
@@ -27,4 +27,7 @@ char *wide_to_utf8(const wchar_t *wstr);
|
||||
wchar_t *utf8_to_wide(const char *str);
|
||||
char *stristr(const char *haystack, const char *needle);
|
||||
|
||||
// 检查字符串列表中是否存在指定路径(不区分大小写)
|
||||
int string_list_contains(const StringList *list, const char *str);
|
||||
|
||||
#endif // STRING_EXT_H
|
||||
|
||||
+286
-2
@@ -6,8 +6,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PathEditor 3.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-26 10:00+0800\n"
|
||||
"PO-Revision-Date: 2026-03-26 19:59+0800\n"
|
||||
"POT-Creation-Date: 2026-04-29 16:30+0800\n"
|
||||
"PO-Revision-Date: 2026-04-29 16:30+0800\n"
|
||||
"Last-Translator: LHY <3364451258@qq.com>\n"
|
||||
"Language-Team: English\n"
|
||||
"Language: en_US\n"
|
||||
@@ -103,3 +103,287 @@ msgstr "Chinese (Simplified)"
|
||||
#: src/ui/dialogs.c
|
||||
msgid "English"
|
||||
msgstr "English"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Administrator privileges are required to save changes!"
|
||||
msgstr "Administrator privileges are required to save changes!"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Backup Settings"
|
||||
msgstr "Backup Settings"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Would you like to customize the backup directory?"
|
||||
msgstr "Would you like to customize the backup directory?"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Use Default"
|
||||
msgstr "Use Default"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Custom Directory"
|
||||
msgstr "Custom Directory"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Skip Backup"
|
||||
msgstr "Skip Backup"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "No directory selected, will use default backup path."
|
||||
msgstr "No directory selected, will use default backup path."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Confirm"
|
||||
msgstr "Confirm"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Are you sure you want to skip backup?"
|
||||
msgstr "Are you sure you want to skip backup?"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Skipping backup may cause inability to recover!"
|
||||
msgstr "Skipping backup may cause inability to recover!"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Skip Anyway"
|
||||
msgstr "Skip Anyway"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Go Back"
|
||||
msgstr "Go Back"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Continue Saving"
|
||||
msgstr "Continue Saving"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Success"
|
||||
msgstr "Success"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Both system and user PATH environment variables have been updated!"
|
||||
msgstr "Both system and user PATH environment variables have been updated!"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Info"
|
||||
msgstr "Info"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "System variables saved successfully, but user variables failed to save."
|
||||
msgstr "System variables saved successfully, but user variables failed to save."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "User variables saved successfully, but system variables failed to save."
|
||||
msgstr "User variables saved successfully, but system variables failed to save."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Failed to save!"
|
||||
msgstr "Failed to save!"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Unable to open system environment variable registry key, please try running as administrator."
|
||||
msgstr "Unable to open system environment variable registry key, please try running as administrator."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Usage Instructions"
|
||||
msgstr "Usage Instructions"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "This program is used to edit system environment variable PATH."
|
||||
msgstr "This program is used to edit system environment variable PATH."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Must run as"
|
||||
msgstr "Must run as"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Administrator"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "to save changes."
|
||||
msgstr "to save changes."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Operations:"
|
||||
msgstr "Operations:"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "New: Add new path to end of list."
|
||||
msgstr "New: Add new path to end of list."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Edit: Modify selected path."
|
||||
msgstr "Edit: Modify selected path."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Browse: Select directory from file system to add."
|
||||
msgstr "Browse: Select directory from file system to add."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Delete: Remove selected path."
|
||||
msgstr "Delete: Remove selected path."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Up/Down: Adjust path priority."
|
||||
msgstr "Up/Down: Adjust path priority."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Import/Export: Backup and restore PATH configuration."
|
||||
msgstr "Import/Export: Backup and restore PATH configuration."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Click"
|
||||
msgstr "Click"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "to save changes and apply."
|
||||
msgstr "to save changes and apply."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Note: Some running programs may need to restart to recognize new environment variables."
|
||||
msgstr "Note: Some running programs may need to restart to recognize new environment variables."
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Author: LHY"
|
||||
msgstr "Author: LHY"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Email: 3364451258@qq.com"
|
||||
msgstr "Email: 3364451258@qq.com"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "GitHub: https://github.com/LHY0125/PathEditor"
|
||||
msgstr "GitHub: https://github.com/LHY0125/PathEditor"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Don't forget to star my project!"
|
||||
msgstr "Don't forget to star my project!"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Administrator privileges are required to import PATH!"
|
||||
msgstr "Administrator privileges are required to import PATH!"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "No valid paths found in file!"
|
||||
msgstr "No valid paths found in file!"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Import Options"
|
||||
msgstr "Import Options"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Please select import target:"
|
||||
msgstr "Please select import target:"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "System Variables Only"
|
||||
msgstr "System Variables Only"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "User Variables Only"
|
||||
msgstr "User Variables Only"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Import All"
|
||||
msgstr "Import All"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Import to System"
|
||||
msgstr "Import to System"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Import to User"
|
||||
msgstr "Import to User"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Successfully imported %d paths!"
|
||||
msgstr "Successfully imported %d paths!"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Import Success"
|
||||
msgstr "Import Success"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Import failed, please check if the file format is correct!"
|
||||
msgstr "Import failed, please check if the file format is correct!"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Export successful!"
|
||||
msgstr "Export successful!"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "System variables: %d"
|
||||
msgstr "System variables: %d"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "User variables: %d"
|
||||
msgstr "User variables: %d"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Save location: %s"
|
||||
msgstr "Save location: %s"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Export Success"
|
||||
msgstr "Export Success"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Export failed!"
|
||||
msgstr "Export failed!"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "Confirm Cleanup"
|
||||
msgstr "Confirm Cleanup"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "This operation will remove all"
|
||||
msgstr "This operation will remove all"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "invalid paths"
|
||||
msgstr "invalid paths"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "and"
|
||||
msgstr "and"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "duplicate paths"
|
||||
msgstr "duplicate paths"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "from the current list."
|
||||
msgstr "from the current list."
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "Are you sure you want to continue?"
|
||||
msgstr "Are you sure you want to continue?"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "Cleanup completed! Removed %d invalid or duplicate paths."
|
||||
msgstr "Cleanup completed! Removed %d invalid or duplicate paths."
|
||||
|
||||
#: src/controller/callbacks_basic.c
|
||||
msgid "New Environment Variable"
|
||||
msgstr "New Environment Variable"
|
||||
|
||||
#: src/controller/callbacks_basic.c
|
||||
msgid "Please enter a path:"
|
||||
msgstr "Please enter a path:"
|
||||
|
||||
#: src/controller/callbacks_basic.c
|
||||
msgid "Edit Environment Variable"
|
||||
msgstr "Edit Environment Variable"
|
||||
|
||||
#: src/controller/callbacks_basic.c
|
||||
msgid "Edit path:"
|
||||
msgstr "Edit path:"
|
||||
|
||||
#: src/controller/callbacks_basic.c
|
||||
msgid "Please select an item to delete first"
|
||||
msgstr "Please select an item to delete first"
|
||||
|
||||
#: src/controller/callbacks_basic.c
|
||||
msgid "This path already exists and will not be added again."
|
||||
msgstr "This path already exists and will not be added again."
|
||||
+286
-2
@@ -6,8 +6,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PathEditor 3.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-26 10:00+0800\n"
|
||||
"PO-Revision-Date: 2026-03-26 19:58+0800\n"
|
||||
"POT-Creation-Date: 2026-04-29 16:30+0800\n"
|
||||
"PO-Revision-Date: 2026-04-29 16:30+0800\n"
|
||||
"Last-Translator: LHY <3364451258@qq.com>\n"
|
||||
"Language-Team: Chinese (Simplified)\n"
|
||||
"Language: zh_CN\n"
|
||||
@@ -103,3 +103,287 @@ msgstr "中文 (简体中文)"
|
||||
#: src/ui/dialogs.c
|
||||
msgid "English"
|
||||
msgstr "English"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Administrator privileges are required to save changes!"
|
||||
msgstr "需要管理员权限才能保存更改!"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Backup Settings"
|
||||
msgstr "备份设置"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Would you like to customize the backup directory?"
|
||||
msgstr "是否自定义备份目录?"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Use Default"
|
||||
msgstr "使用默认"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Custom Directory"
|
||||
msgstr "自定义目录"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Skip Backup"
|
||||
msgstr "跳过备份"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "No directory selected, will use default backup path."
|
||||
msgstr "未选择目录,将使用默认备份路径。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Confirm"
|
||||
msgstr "确认"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Are you sure you want to skip backup?"
|
||||
msgstr "确定跳过备份吗?"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Skipping backup may cause inability to recover!"
|
||||
msgstr "跳过备份可能导致无法恢复!"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Skip Anyway"
|
||||
msgstr "确定跳过"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Go Back"
|
||||
msgstr "返回备份"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Continue Saving"
|
||||
msgstr "继续保存"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Success"
|
||||
msgstr "成功"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Both system and user PATH environment variables have been updated!"
|
||||
msgstr "系统和用户 PATH 环境变量均已更新!"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Info"
|
||||
msgstr "提示"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "System variables saved successfully, but user variables failed to save."
|
||||
msgstr "系统变量保存成功,但用户变量保存失败。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "User variables saved successfully, but system variables failed to save."
|
||||
msgstr "用户变量保存成功,但系统变量保存失败。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Failed to save!"
|
||||
msgstr "保存失败!"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Unable to open system environment variable registry key, please try running as administrator."
|
||||
msgstr "无法打开系统环境变量注册表键,请尝试以管理员身份运行。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Usage Instructions"
|
||||
msgstr "使用说明"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "This program is used to edit system environment variable PATH."
|
||||
msgstr "本程序用于编辑系统环境变量 PATH。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Must run as"
|
||||
msgstr "必须以"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Administrator"
|
||||
msgstr "管理员身份"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "to save changes."
|
||||
msgstr "运行才能保存更改。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Operations:"
|
||||
msgstr "操作说明:"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "New: Add new path to end of list."
|
||||
msgstr "新建:添加新路径到列表末尾。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Edit: Modify selected path."
|
||||
msgstr "编辑:修改选中的路径。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Browse: Select directory from file system to add."
|
||||
msgstr "浏览:从文件系统选择目录添加。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Delete: Remove selected path."
|
||||
msgstr "删除:移除选中的路径。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Up/Down: Adjust path priority."
|
||||
msgstr "上移/下移:调整路径优先级。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Import/Export: Backup and restore PATH configuration."
|
||||
msgstr "导入/导出:备份和恢复 PATH 配置。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Click"
|
||||
msgstr "点击"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "to save changes and apply."
|
||||
msgstr "保存更改并生效。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Note: Some running programs may need to restart to recognize new environment variables."
|
||||
msgstr "注意:某些正在运行的程序可能需要重启才能识别新的环境变量。"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Author: LHY"
|
||||
msgstr "作者:LHY"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Email: 3364451258@qq.com"
|
||||
msgstr "邮箱:3364451258@qq.com"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "GitHub: https://github.com/LHY0125/PathEditor"
|
||||
msgstr "GitHub:https://github.com/LHY0125/PathEditor"
|
||||
|
||||
#: src/controller/callbacks_sys.c
|
||||
msgid "Don't forget to star my project!"
|
||||
msgstr "记得给我的项目点个star!"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Administrator privileges are required to import PATH!"
|
||||
msgstr "需要管理员权限才能导入 PATH!"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "No valid paths found in file!"
|
||||
msgstr "文件中没有找到有效的路径!"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Import Options"
|
||||
msgstr "导入选项"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Please select import target:"
|
||||
msgstr "请选择导入目标:"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "System Variables Only"
|
||||
msgstr "仅系统变量"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "User Variables Only"
|
||||
msgstr "仅用户变量"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Import All"
|
||||
msgstr "全部导入"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Import to System"
|
||||
msgstr "导入到系统变量"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Import to User"
|
||||
msgstr "导入到用户变量"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Successfully imported %d paths!"
|
||||
msgstr "成功导入 %d 个路径!"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Import Success"
|
||||
msgstr "导入成功"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Import failed, please check if the file format is correct!"
|
||||
msgstr "导入失败,请检查文件格式是否正确!"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Export successful!"
|
||||
msgstr "成功导出!"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "System variables: %d"
|
||||
msgstr "系统变量: %d 个"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "User variables: %d"
|
||||
msgstr "用户变量: %d 个"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Save location: %s"
|
||||
msgstr "保存位置: %s"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Export Success"
|
||||
msgstr "导出成功"
|
||||
|
||||
#: src/controller/callbacks_io.c
|
||||
msgid "Export failed!"
|
||||
msgstr "导出失败!"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "Confirm Cleanup"
|
||||
msgstr "确认清理"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "This operation will remove all"
|
||||
msgstr "此操作将移除当前列表中所有"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "invalid paths"
|
||||
msgstr "无效路径"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "and"
|
||||
msgstr "和"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "duplicate paths"
|
||||
msgstr "重复路径"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "from the current list."
|
||||
msgstr "。"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "Are you sure you want to continue?"
|
||||
msgstr "确定要继续吗?"
|
||||
|
||||
#: src/controller/callbacks_nav.c
|
||||
msgid "Cleanup completed! Removed %d invalid or duplicate paths."
|
||||
msgstr "清理完成!共移除了 %d 个无效或重复路径。"
|
||||
|
||||
#: src/controller/callbacks_basic.c
|
||||
msgid "New Environment Variable"
|
||||
msgstr "新建环境变量"
|
||||
|
||||
#: src/controller/callbacks_basic.c
|
||||
msgid "Please enter a path:"
|
||||
msgstr "请输入路径:"
|
||||
|
||||
#: src/controller/callbacks_basic.c
|
||||
msgid "Edit Environment Variable"
|
||||
msgstr "编辑环境变量"
|
||||
|
||||
#: src/controller/callbacks_basic.c
|
||||
msgid "Edit path:"
|
||||
msgstr "编辑路径:"
|
||||
|
||||
#: src/controller/callbacks_basic.c
|
||||
msgid "Please select an item to delete first"
|
||||
msgstr "请先选择要删除的项"
|
||||
|
||||
#: src/controller/callbacks_basic.c
|
||||
msgid "This path already exists and will not be added again."
|
||||
msgstr "该路径已存在,不会重复添加。"
|
||||
@@ -18,11 +18,19 @@ int btn_new_cb(Ihandle *self)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(self);
|
||||
char buffer[PATH_BUFFER_SIZE] = "";
|
||||
if (custom_input_dialog("新建环境变量", "请输入路径:", buffer, sizeof(buffer)))
|
||||
if (custom_input_dialog(_("New Environment Variable"), _("Please enter a path:"), buffer, sizeof(buffer)))
|
||||
{
|
||||
if (strlen(buffer) > 0)
|
||||
{
|
||||
StringList *raw_data = get_current_raw_data(dlg);
|
||||
|
||||
// 检查是否已存在重复路径
|
||||
if (string_list_contains(raw_data, buffer))
|
||||
{
|
||||
IupMessage(_("Warning"), _("This path already exists and will not be added again."));
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
add_string_list(raw_data, buffer);
|
||||
|
||||
Ihandle *current_list = get_current_list(dlg);
|
||||
@@ -51,7 +59,7 @@ int btn_edit_cb(Ihandle *self)
|
||||
char buffer[PATH_BUFFER_SIZE];
|
||||
safe_strcpy(buffer, sizeof(buffer), string_list_get(raw_data, selected - 1));
|
||||
|
||||
if (custom_input_dialog("编辑环境变量", "编辑路径:", buffer, sizeof(buffer)))
|
||||
if (custom_input_dialog(_("Edit Environment Variable"), _("Edit path:"), buffer, sizeof(buffer)))
|
||||
{
|
||||
if (strlen(buffer) > 0)
|
||||
{
|
||||
@@ -91,6 +99,15 @@ int btn_browse_cb(Ihandle *self)
|
||||
if (value)
|
||||
{
|
||||
StringList *raw_data = get_current_raw_data(dlg);
|
||||
|
||||
// 检查是否已存在重复路径
|
||||
if (string_list_contains(raw_data, value))
|
||||
{
|
||||
IupMessage(_("Warning"), _("This path already exists and will not be added again."));
|
||||
IupDestroy(filedlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
add_string_list(raw_data, value);
|
||||
|
||||
Ihandle *current_list = get_current_list(dlg);
|
||||
@@ -113,7 +130,7 @@ int btn_del_cb(Ihandle *self)
|
||||
|
||||
if (selected == 0)
|
||||
{
|
||||
IupMessage("提示", "请先选择要删除的项");
|
||||
IupMessage(_("Info"), _("Please select an item to delete first"));
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ int btn_import_cb(Ihandle *self)
|
||||
|
||||
if (!check_admin())
|
||||
{
|
||||
IupMessage("错误", "需要管理员权限才能导入 PATH!");
|
||||
IupMessage(_("Error"), _("Administrator privileges are required to import PATH!"));
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ int btn_import_cb(Ihandle *self)
|
||||
|
||||
if (!has_system && !has_user)
|
||||
{
|
||||
IupMessage("错误", "文件中没有找到有效的路径!");
|
||||
IupMessage(_("Error"), _("No valid paths found in file!"));
|
||||
clear_string_list(&imported.system);
|
||||
clear_string_list(&imported.user);
|
||||
IupDestroy(filedlg);
|
||||
@@ -62,14 +62,14 @@ int btn_import_cb(Ihandle *self)
|
||||
int choice = 0;
|
||||
if (has_system && has_user)
|
||||
{
|
||||
choice = IupAlarm("导入选项", "请选择导入目标:",
|
||||
"仅系统变量", "仅用户变量", "全部导入");
|
||||
choice = IupAlarm(_("Import Options"), _("Please select import target:"),
|
||||
_("System Variables Only"), _("User Variables Only"), _("Import All"));
|
||||
}
|
||||
else if (has_system)
|
||||
{
|
||||
// TXT 文件导入时,让用户选择目标(系统变量或用户变量)
|
||||
choice = IupAlarm("导入选项", "请选择导入目标:",
|
||||
"导入到系统变量", "导入到用户变量", NULL);
|
||||
// TXT file import: let user choose target (system or user)
|
||||
choice = IupAlarm(_("Import Options"), _("Please select import target:"),
|
||||
_("Import to System"), _("Import to User"), NULL);
|
||||
// IupAlarm 返回 1 或 2,转换为 1(系统) 或 2(用户)
|
||||
}
|
||||
else
|
||||
@@ -108,8 +108,8 @@ int btn_import_cb(Ihandle *self)
|
||||
clear_string_list(&imported.user);
|
||||
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg), "成功导入 %d 个路径!", total_imported);
|
||||
IupMessage("导入成功", msg);
|
||||
snprintf(msg, sizeof(msg), _("Successfully imported %d paths!"), total_imported);
|
||||
IupMessage(_("Import Success"), msg);
|
||||
|
||||
Ihandle *lbl_status = IupGetDialogChild(dlg, CTRL_LBL_STATUS);
|
||||
if (lbl_status)
|
||||
@@ -118,7 +118,7 @@ int btn_import_cb(Ihandle *self)
|
||||
else
|
||||
{
|
||||
log_error("Import failed: error code %d", import_result);
|
||||
IupMessage("错误", "导入失败,请检查文件格式是否正确!");
|
||||
IupMessage(_("Error"), _("Import failed, please check if the file format is correct!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,14 +171,14 @@ int btn_export_cb(Ihandle *self)
|
||||
if (export_result == ERR_OK)
|
||||
{
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "成功导出!\n系统变量: %d 个\n用户变量: %d 个\n\n保存位置: %s",
|
||||
snprintf(msg, sizeof(msg), _("Export successful!\nSystem variables: %d\nUser variables: %d\n\nSave location: %s"),
|
||||
data.system.count, data.user.count, filepath);
|
||||
IupMessage("导出成功", msg);
|
||||
IupMessage(_("Export Success"), msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error("Export failed: error code %d", export_result);
|
||||
IupMessage("错误", "导出失败!");
|
||||
IupMessage(_("Error"), _("Export failed!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ int btn_clean_cb(Ihandle *self)
|
||||
if (!raw_data || raw_data->count == 0)
|
||||
return IUP_DEFAULT;
|
||||
|
||||
if (IupAlarm("确认清理", "此操作将移除当前列表中所有【无效路径】和【重复路径】。\n确定要继续吗?", "确定", "取消", NULL) != 1)
|
||||
if (IupAlarm(_("Confirm Cleanup"), _("This operation will remove all 【invalid paths】 and 【duplicate paths】 from the current list.\nAre you sure you want to continue?"), _("Confirm"), _("Cancel"), NULL) != 1)
|
||||
{
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
@@ -74,8 +74,8 @@ int btn_clean_cb(Ihandle *self)
|
||||
sync_string_list_to_ui(current_list, raw_data);
|
||||
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof(msg), "清理完成!共移除了 %d 个无效或重复路径。", removed);
|
||||
IupMessage("提示", msg);
|
||||
snprintf(msg, sizeof(msg), _("Cleanup completed! Removed %d invalid or duplicate paths."), removed);
|
||||
IupMessage(_("Info"), msg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ int btn_ok_cb(Ihandle *self)
|
||||
|
||||
if (!check_admin())
|
||||
{
|
||||
IupMessage("错误", "需要管理员权限才能保存更改!");
|
||||
IupMessage(_("Error"), _("Administrator privileges are required to save changes!"));
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
@@ -32,11 +32,11 @@ int btn_ok_cb(Ihandle *self)
|
||||
char custom_backup_dir[MAX_PATH] = "";
|
||||
int do_backup = 1; // 是否执行备份
|
||||
|
||||
int backup_choice = IupAlarm("备份设置",
|
||||
"是否自定义备份目录?\n\n"
|
||||
"选择「使用默认」将备份到 %APPDATA%/PathEditor/backups/\n"
|
||||
"选择「自定义目录」可选择其他位置",
|
||||
"使用默认", "自定义目录", "跳过备份");
|
||||
int backup_choice = IupAlarm(_("Backup Settings"),
|
||||
_("Would you like to customize the backup directory?\n\n"
|
||||
"Select 'Use Default' to backup to %%APPDATA%%/PathEditor/backups/\n"
|
||||
"Select 'Custom Directory' to choose another location"),
|
||||
_("Use Default"), _("Custom Directory"), _("Skip Backup"));
|
||||
|
||||
if (backup_choice == 2) // 自定义目录
|
||||
{
|
||||
@@ -56,13 +56,13 @@ int btn_ok_cb(Ihandle *self)
|
||||
|
||||
if (strlen(custom_backup_dir) == 0)
|
||||
{
|
||||
IupMessage("提示", "未选择目录,将使用默认备份路径。");
|
||||
IupMessage(_("Hint"), _("No directory selected, will use default backup path."));
|
||||
}
|
||||
}
|
||||
else if (backup_choice == 3) // 跳过备份
|
||||
{
|
||||
int skip_confirm = IupAlarm("确认", "确定跳过备份吗?\n跳过备份可能导致无法恢复!",
|
||||
"确定跳过", "返回备份", NULL);
|
||||
int skip_confirm = IupAlarm(_("Confirm"), _("Are you sure you want to skip backup?\nSkipping backup may cause inability to recover!"),
|
||||
_("Skip Anyway"), _("Go Back"), NULL);
|
||||
if (skip_confirm != 1)
|
||||
{
|
||||
// 用户反悔,重新询问
|
||||
@@ -89,7 +89,7 @@ int btn_ok_cb(Ihandle *self)
|
||||
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "备份失败!原因:%s\n\n是否继续保存?\n(继续保存可能导致无法恢复)", reason);
|
||||
int choice = IupAlarm("警告", msg, "继续保存", "取消", NULL);
|
||||
int choice = IupAlarm(_("Warning"), msg, _("Continue Saving"), _("Cancel"), NULL);
|
||||
if (choice != 1)
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
@@ -104,22 +104,22 @@ int btn_ok_cb(Ihandle *self)
|
||||
{
|
||||
log_info("Saved system paths: %d, user paths: %d", ctx->sys_paths.count, ctx->user_paths.count);
|
||||
SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, NULL);
|
||||
IupMessage("成功", "系统和用户 PATH 环境变量均已更新!");
|
||||
IupMessage(_("Success"), _("Both system and user PATH environment variables have been updated!"));
|
||||
if (lbl_status)
|
||||
IupSetAttribute(lbl_status, "TITLE", lua_config_get_string("status", "saved"));
|
||||
}
|
||||
else if (sys_ok == ERR_OK)
|
||||
{
|
||||
IupMessage("提示", "系统变量保存成功,但用户变量保存失败。");
|
||||
IupMessage(_("Info"), _("System variables saved successfully, but user variables failed to save."));
|
||||
}
|
||||
else if (user_ok == ERR_OK)
|
||||
{
|
||||
IupMessage("提示", "用户变量保存成功,但系统变量保存失败。");
|
||||
IupMessage(_("Info"), _("User variables saved successfully, but system variables failed to save."));
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error("Failed to save paths: sys=%d, user=%d", sys_ok, user_ok);
|
||||
IupMessage("错误", "保存失败!");
|
||||
IupMessage(_("Error"), _("Failed to save!"));
|
||||
if (lbl_status)
|
||||
IupSetAttribute(lbl_status, "TITLE", lua_config_get_string("status", "error"));
|
||||
}
|
||||
@@ -146,7 +146,7 @@ void load_all_paths(void)
|
||||
if (load_system_paths(&ctx->sys_paths) != ERR_OK)
|
||||
{
|
||||
log_error("Failed to load system paths");
|
||||
IupMessage("错误", "无法打开系统环境变量注册表键,请尝试以管理员身份运行。");
|
||||
IupMessage(_("Error"), _("Unable to open system environment variable registry key, please try running as administrator."));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -182,23 +182,23 @@ int btn_lang_cb(Ihandle *self)
|
||||
// 按钮回调:帮助
|
||||
int btn_help_cb(Ihandle *self)
|
||||
{
|
||||
IupMessage("使用说明",
|
||||
"1. 本程序用于编辑系统环境变量 PATH。\n"
|
||||
"2. 必须以【管理员身份】运行才能保存更改。\n"
|
||||
"3. 操作说明:\n"
|
||||
" - 新建:添加新路径到列表末尾。\n"
|
||||
" - 编辑:修改选中的路径。\n"
|
||||
" - 浏览:从文件系统选择目录添加。\n"
|
||||
" - 删除:移除选中的路径。\n"
|
||||
" - 上移/下移:调整路径优先级。\n"
|
||||
" - 导入/导出:备份和恢复 PATH 配置。\n"
|
||||
"4. 点击【确定】保存更改并生效。\n"
|
||||
"5. 注意:某些正在运行的程序可能需要重启才能识别新的环境变量。\n\n"
|
||||
"--------------------------------------------------\n"
|
||||
"作者:LHY\n"
|
||||
"邮箱:3364451258@qq.com\n"
|
||||
"GitHub:https://github.com/LHY0125/PathEditor\n"
|
||||
"记得给我的项目点个star!");
|
||||
IupMessage(_("Usage Instructions"),
|
||||
_("1. This program is used to edit system environment variable PATH.\n"
|
||||
"2. Must run as 【Administrator】 to save changes.\n"
|
||||
"3. Operations:\n"
|
||||
" - New: Add new path to end of list.\n"
|
||||
" - Edit: Modify selected path.\n"
|
||||
" - Browse: Select directory from file system to add.\n"
|
||||
" - Delete: Remove selected path.\n"
|
||||
" - Up/Down: Adjust path priority.\n"
|
||||
" - Import/Export: Backup and restore PATH configuration.\n"
|
||||
"4. Click 【OK】 to save changes and apply.\n"
|
||||
"5. Note: Some running programs may need to restart to recognize new environment variables.\n\n"
|
||||
"--------------------------------------------------\n"
|
||||
"Author: LHY\n"
|
||||
"Email: 3364451258@qq.com\n"
|
||||
"GitHub: https://github.com/LHY0125/PathEditor\n"
|
||||
"Don't forget to star my project!"));
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "utils/error_code.h"
|
||||
#include <string.h>
|
||||
|
||||
const char* error_code_to_string(ErrorCode code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case ERR_OK:
|
||||
return "Success";
|
||||
case ERR_FAILED:
|
||||
return "Operation failed";
|
||||
case ERR_NULL_PTR:
|
||||
return "Null pointer error";
|
||||
case ERR_OUT_OF_MEMORY:
|
||||
return "Out of memory";
|
||||
case ERR_FILE_NOT_FOUND:
|
||||
return "File not found";
|
||||
case ERR_PERMISSION_DENIED:
|
||||
return "Permission denied";
|
||||
case ERR_INVALID_FORMAT:
|
||||
return "Invalid format";
|
||||
case ERR_REGISTRY_FAILED:
|
||||
return "Registry operation failed";
|
||||
case ERR_NOT_FOUND:
|
||||
return "Item not found";
|
||||
case ERR_EXISTS:
|
||||
return "Item already exists";
|
||||
case ERR_INVALID_INDEX:
|
||||
return "Invalid index";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
// 注意:error_code_to_message 需要链接 intl 库,
|
||||
// 如果不需要在错误码模块使用国际化,可以直接使用 error_code_to_string
|
||||
const char* error_code_to_message(ErrorCode code)
|
||||
{
|
||||
return error_code_to_string(code);
|
||||
}
|
||||
@@ -126,4 +126,17 @@ void clear_string_list(StringList *list)
|
||||
list->items = NULL;
|
||||
list->count = 0;
|
||||
list->capacity = 0;
|
||||
}
|
||||
|
||||
// 检查字符串列表中是否存在指定路径(不区分大小写)
|
||||
int string_list_contains(const StringList *list, const char *str)
|
||||
{
|
||||
if (!list || !str)
|
||||
return 0;
|
||||
for (int i = 0; i < list->count; i++)
|
||||
{
|
||||
if (list->items[i] && _stricmp(list->items[i], str) == 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -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