Files
msime-windows/server/tests/src/test_config_template_merge.cpp
T
fanlusky 54e5c68f94 refactor(server): clear the Server and test /W4 warnings
Most of them come from logging that is compiled out. The disabled DIAG_LOGF /
FANY_IPC_LOG* macros expanded to `((void)0)`, so every HRESULT and parameter
that exists only to be logged looked unreferenced (C4189/C4100). They now
expand to `((void)sizeof(DiscardLogArgs(__VA_ARGS__)))`: the arguments stay in
an unevaluated context, so nothing is computed and no side effect runs, but the
names count as used.

The rest:

- C4100: commented out the parameter names that are genuinely unused
  (WinMain's hPrevInstance/nCmdShow, AdjustWndPosition's hwnd/height, the
  WebView2 callbacks' sender, and the no-op logging sinks).
- C4189/C4101: removed the dead locals and the unused catch bindings left over
  with the log statements.
- C4244: made the DIP-to-pixel and int-to-float conversions explicit in
  globals.h, ime_windows.cpp and the menu placement.
- C4456: renamed the shadowing `hwnd` and `lock` locals.
- C4005: guarded the tests' `#define NOMINMAX` with #ifndef; the test targets
  already define it on the command line.

Release builds clean and ctest passes for the server, the tests and msimeui.
Warnings under vendor/ are untouched: those are submodules with their own
repositories.
2026-09-12 00:55:57 +08:00

186 lines
9.5 KiB
C++

#ifndef NOMINMAX
#define NOMINMAX
#endif
#include "config/ime_config.h"
#include "tests/includes/test_framework.h"
#include <type_traits>
#include <fstream>
#include <iterator>
TEST_CASE(font_fallback_upgrade_preserves_legacy_fonts_and_order)
{
const std::string next = "[appearance]\nfallback_fonts = [\"Noto Sans SC\", \"Microsoft YaHei\"]\n";
const std::string old = "[appearance]\nfont = \"SimSun\"\ndefault_font = \"DengXian\"\n";
const auto migrated = MergeConfigIntoTemplate(next, old, old);
REQUIRE(migrated.find("[\"SimSun\", \"DengXian\"]") != std::string::npos);
const std::string custom = "[appearance]\nfallback_fonts = [\n \"Font#1\", # comment\n \"Font]2\"\n]\n";
REQUIRE(MergeConfigIntoTemplate(next, custom, next).find("Font]2") != std::string::npos);
REQUIRE(MergeConfigIntoTemplate(next, "[appearance]\nfallback_fonts = []\n", next).find("fallback_fonts = []") !=
std::string::npos);
}
TEST_CASE(shortcut_config_upgrade_preserves_word_selection_and_adds_defaults)
{
const std::string old_config =
"[input]\nword_to_character = true\n\n[keybindings]\nswitch_language_shift = false\n";
const std::string new_template =
"[input]\nword_to_character = false\nword_to_character_keys = \"brackets\"\n\n"
"[keybindings]\nswitch_language_shift = true\ntoggle_character_set_ctrl_shift_f = true\n";
const auto merged = MergeConfigIntoTemplate(new_template, old_config, "");
REQUIRE(merged.find("word_to_character = true") != std::string::npos);
REQUIRE(merged.find("word_to_character_keys = \"brackets\"") != std::string::npos);
REQUIRE(merged.find("switch_language_shift = false") != std::string::npos);
REQUIRE(merged.find("toggle_character_set_ctrl_shift_f = true") != std::string::npos);
}
TEST_CASE(candidate_key_config_rejects_invalid_groups_without_changing_state)
{
const auto keys = GetConfiguredWordToCharacterKeys();
const bool enabled = GetConfiguredWordToCharacterEnabled();
const bool brackets = GetConfiguredPagingBracketsEnabled();
const bool minus = GetConfiguredPagingMinusEqualEnabled();
REQUIRE(!SetConfiguredWordToCharacterKeys("unsupported"));
REQUIRE_EQ(GetConfiguredWordToCharacterKeys(), keys);
REQUIRE_EQ(GetConfiguredWordToCharacterEnabled(), enabled);
REQUIRE_EQ(GetConfiguredPagingBracketsEnabled(), brackets);
REQUIRE_EQ(GetConfiguredPagingMinusEqualEnabled(), minus);
}
TEST_CASE(config_merge_keeps_customized_values_and_adds_new_keys)
{
const std::string template_text = "[general]\n"
"# 悬浮工具栏\n"
"floating_toolbar = true\n"
"cloud_candidates = true\n"
"\n"
"[input]\n"
"schema = \"shuangpin\" # 可选:quanpin/shuangpin\n";
const std::string user_text = "[general]\n"
"floating_toolbar = false\n"
"\n"
"[input]\n"
"schema = \"quanpin\"\n";
const std::string baseline_text = "[general]\n"
"floating_toolbar = true\n"
"\n"
"[input]\n"
"schema = \"shuangpin\"\n";
const std::string expected = "[general]\n"
"# 悬浮工具栏\n"
"floating_toolbar = false\n"
"cloud_candidates = true\n"
"\n"
"[input]\n"
"schema = \"quanpin\" # 可选:quanpin/shuangpin\n";
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, baseline_text), expected);
}
TEST_CASE(config_merge_lets_new_defaults_win_for_untouched_keys)
{
const std::string template_text = "[appearance]\npage_size = 9\nfont_size = 18\n";
const std::string user_text = "[appearance]\npage_size = 8\nfont_size = 20\n";
const std::string baseline_text = "[appearance]\npage_size = 8\nfont_size = 16\n";
// page_size 仍是旧默认值 -> 跟随新默认值;font_size 被用户改过 -> 保留。
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, baseline_text),
"[appearance]\npage_size = 9\nfont_size = 20\n");
}
TEST_CASE(config_merge_drops_keys_and_sections_absent_from_template)
{
const std::string template_text = "[general]\nkept = true\n";
const std::string user_text = "[general]\nkept = false\nretired = 1\n\n[gone]\nvalue = \"x\"\n";
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, std::string()), "[general]\nkept = false\n");
}
TEST_CASE(config_merge_keeps_all_user_values_without_a_baseline)
{
// 从没有 config.base.toml 的旧版本升级时,无法区分「用户改的」和「旧默认值」。
const std::string template_text = "[voice_input]\nasr_token = \"<YOUR_OWN_ASR_TOKEN>\"\nlanguage = \"zh-cn\"\n";
const std::string user_text = "[voice_input]\nasr_token = \"sk-real-token\"\nlanguage = \"en\"\n";
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, std::string()),
"[voice_input]\nasr_token = \"sk-real-token\"\nlanguage = \"en\"\n");
}
TEST_CASE(config_merge_handles_multiline_string_values)
{
const std::string template_text = "[ai_assistant]\n"
"prompt = \"\"\"new\ndefault prompt\"\"\"\n"
"model = \"v2\"\n";
const std::string user_text = "[ai_assistant]\n"
"prompt = \"\"\"my\nown prompt\"\"\"\n"
"model = \"v1\"\n";
const std::string baseline_text = "[ai_assistant]\n"
"prompt = \"\"\"old\ndefault prompt\"\"\"\n"
"model = \"v1\"\n";
const std::string expected = "[ai_assistant]\n"
"prompt = \"\"\"my\nown prompt\"\"\"\n"
"model = \"v2\"\n";
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, baseline_text), expected);
}
TEST_CASE(config_merge_ignores_assignments_inside_multiline_strings)
{
const std::string template_text = "[ai_assistant]\n"
"prompt = \"\"\"say hi\"\"\"\n"
"model = \"v2\"\n";
// prompt 正文里的 model = ... 只是提示词内容,不能被当成一个键。
const std::string user_text = "[ai_assistant]\n"
"prompt = \"\"\"say hi\nmodel = \"hijacked\"\n\"\"\"\n";
const std::string expected = "[ai_assistant]\n"
"prompt = \"\"\"say hi\nmodel = \"hijacked\"\n\"\"\"\n"
"model = \"v2\"\n";
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, std::string()), expected);
}
TEST_CASE(config_merge_ignores_commented_out_keys)
{
const std::string template_text = "[input]\n# schema = \"wubi\"\nschema = \"shuangpin\"\n";
const std::string user_text = "[input]\n# schema = \"quanpin\"\nschema = \"wubi\"\n";
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, std::string()),
"[input]\n# schema = \"wubi\"\nschema = \"wubi\"\n");
}
TEST_CASE(configured_voice_input_is_handed_out_as_a_snapshot)
{
// LoadImeConfig rewrites the whole voice config on the IPC worker thread (asr_tokens / polish_tokens are cleared
// and refilled) while the voice control thread and the low-level keyboard hook read it on every keystroke. Handing
// out a reference lets those readers walk strings and maps that are being rewritten, so this accessor must return a
// snapshot by value.
REQUIRE(!std::is_reference_v<decltype(GetConfiguredVoiceInput())>);
REQUIRE((std::is_same_v<decltype(GetConfiguredVoiceInput()), VoiceInputConfig>));
const VoiceInputConfig snapshot = GetConfiguredVoiceInput();
REQUIRE_EQ(snapshot.commit_mode, VoiceInputConfig().commit_mode);
}
// 读取安装器真正分发的模板,防止开发配置齐全、安装模板漏项导致升级后丢失设置。
TEST_CASE(shipped_translation_settings_survive_template_upgrade)
{
std::ifstream input(MSIME_DEFAULT_CONFIG_PATH, std::ios::binary);
REQUIRE(static_cast<bool>(input));
const std::string installed((std::istreambuf_iterator<char>(input)), {});
const std::string configured =
"[custom_translation]\nenabled = true\nendpoint = \"https://translation.example/translate\"\n"
"api_key = \"test-translation-key\"\n[tencent_tmt]\ntarget_language = \"ja\"\n";
const std::string legacy_template = "[tencent_tmt]\nenabled = true\n";
// 同时覆盖旧版本缺少这些字段,以及新版本已经包含这些字段的升级。
for (const auto &baseline : {legacy_template, installed})
{
const auto merged = toml::parse(MergeConfigIntoTemplate(installed, configured, baseline));
REQUIRE(merged["custom_translation"]["enabled"].value_or(false));
REQUIRE_EQ(merged["custom_translation"]["endpoint"].value_or(std::string()),
std::string("https://translation.example/translate"));
REQUIRE_EQ(merged["custom_translation"]["api_key"].value_or(std::string()),
std::string("test-translation-key"));
REQUIRE_EQ(merged["tencent_tmt"]["target_language"].value_or(std::string()), std::string("ja"));
}
}