feat: CSV 导入导出、导入撤销支持及多项 bug 修复

功能:
- 新增 CSV 格式导入导出支持(含 BOM 处理、引号转义、智能标题行检测)
- 导入操作支持撤销/重做
- 保存时 PATH 长度检查与警告
- 深色模式状态持久化(darkmode.txt)
- 提取 get_current_target/push_record 为共享函数,消除控制器层重复代码
- 新增 string_list_insert_at,修复撤销删除时的索引恢复
- 新增 undo_redo、error_code、import_export 单元测试

Bug 修复:
- 修复备份目录对话框和失败原因的硬编码中文字符串
- 提取 get_exe_dir 到 os_env 消除 i18n.c/ui_utils.c 重复定义
- 修复导入撤销 old_sys/old_user 内存管理(push 后置 NULL 防止重复释放)
- 修复 CSV 导出转义与导入解析不一致(移除反斜杠转义,依赖 CSV 引号机制)
- 修正 PATH 长度 8191 限制描述为 "command line safe limit"
This commit is contained in:
2026-05-03 01:52:06 +08:00
parent 720ebb535d
commit cdcfd8e0a7
27 changed files with 1823 additions and 110 deletions
+18
View File
@@ -0,0 +1,18 @@
# error_code 单元测试
add_executable(test_error_code test_error_code.c
${CMAKE_SOURCE_DIR}/src/utils/error_code.c
)
target_link_libraries(test_error_code cmocka)
target_include_directories(test_error_code PRIVATE
${CMAKE_SOURCE_DIR}/include
)
add_custom_command(TARGET test_error_code POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_BINARY_DIR}/_deps/cmocka-build/src/cmocka.dll
$<TARGET_FILE_DIR:test_error_code>
)
add_test(NAME error_code_test COMMAND test_error_code)
+107
View File
@@ -0,0 +1,107 @@
/*
* error_code.c 单元测试
* 测试错误码字符串映射
*/
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <string.h>
#include "utils/error_code.h"
/* ==================== error_code_to_string 测试 ==================== */
static void test_err_ok(void **state)
{
(void)state;
assert_string_equal(error_code_to_string(ERR_OK), "Success");
}
static void test_err_failed(void **state)
{
(void)state;
assert_string_equal(error_code_to_string(ERR_FAILED), "Operation failed");
}
static void test_err_null_ptr(void **state)
{
(void)state;
assert_string_equal(error_code_to_string(ERR_NULL_PTR), "Null pointer error");
}
static void test_err_out_of_memory(void **state)
{
(void)state;
assert_string_equal(error_code_to_string(ERR_OUT_OF_MEMORY), "Out of memory");
}
static void test_err_file_not_found(void **state)
{
(void)state;
assert_string_equal(error_code_to_string(ERR_FILE_NOT_FOUND), "File not found");
}
static void test_err_permission_denied(void **state)
{
(void)state;
assert_string_equal(error_code_to_string(ERR_PERMISSION_DENIED), "Permission denied");
}
static void test_err_invalid_format(void **state)
{
(void)state;
assert_string_equal(error_code_to_string(ERR_INVALID_FORMAT), "Invalid format");
}
static void test_err_registry_failed(void **state)
{
(void)state;
assert_string_equal(error_code_to_string(ERR_REGISTRY_FAILED), "Registry operation failed");
}
static void test_err_not_found(void **state)
{
(void)state;
assert_string_equal(error_code_to_string(ERR_NOT_FOUND), "Item not found");
}
static void test_err_exists(void **state)
{
(void)state;
assert_string_equal(error_code_to_string(ERR_EXISTS), "Item already exists");
}
static void test_err_invalid_index(void **state)
{
(void)state;
assert_string_equal(error_code_to_string(ERR_INVALID_INDEX), "Invalid index");
}
static void test_unknown_error_code(void **state)
{
(void)state;
assert_string_equal(error_code_to_string((ErrorCode)9999), "Unknown error");
assert_string_equal(error_code_to_string((ErrorCode)-99), "Unknown error");
}
/* ==================== 主函数 ==================== */
int main(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_err_ok),
cmocka_unit_test(test_err_failed),
cmocka_unit_test(test_err_null_ptr),
cmocka_unit_test(test_err_out_of_memory),
cmocka_unit_test(test_err_file_not_found),
cmocka_unit_test(test_err_permission_denied),
cmocka_unit_test(test_err_invalid_format),
cmocka_unit_test(test_err_registry_failed),
cmocka_unit_test(test_err_not_found),
cmocka_unit_test(test_err_exists),
cmocka_unit_test(test_err_invalid_index),
cmocka_unit_test(test_unknown_error_code),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}