Add a unit test for the shared text helpers
contains_ci and narrow/widen were centralized into shared headers (ui/text_match.hpp, util/utf8.hpp) during the cleanup but had no focused test, unlike the project's other pure-logic helpers. Cover the filter contract the Log/Injection panels depend on (empty/null needle matches all, case-insensitivity both directions, boundary substrings, non-matches) and a narrow<->widen round-trip. The non-ASCII round-trips (cafe.exe, CJK) would fail against the old lossy `c & 0x7F` narrowing this cleanup replaced, so the test guards that fix, not just the happy path. Registered as test #34; suite green.
This commit is contained in:
@@ -207,6 +207,12 @@ add_executable(srgb_format_test srgb_format_test.cpp)
|
||||
target_include_directories(srgb_format_test PRIVATE ${CMAKE_SOURCE_DIR}/host/src)
|
||||
add_test(NAME srgb_format_test COMMAND srgb_format_test)
|
||||
|
||||
# Unit test for the host's shared text helpers: contains_ci (the panels' case-insensitive filter)
|
||||
# and narrow/widen (UTF-8 <-> wide). Pure logic; header-only, no window / device.
|
||||
add_executable(text_util_test text_util_test.cpp)
|
||||
target_include_directories(text_util_test PRIVATE ${CMAKE_SOURCE_DIR}/host/src)
|
||||
add_test(NAME text_util_test COMMAND text_util_test)
|
||||
|
||||
# Unit test for the keyed-mutex acquire decision (capture/keyed_mutex.hpp): WAIT_ABANDONED must be
|
||||
# treated as "acquired" so a mirror recovers after a host crash instead of freezing. Header-only.
|
||||
add_executable(keyed_mutex_test keyed_mutex_test.cpp)
|
||||
@@ -405,6 +411,7 @@ coop_output_subdir(tests
|
||||
audio_loopback_test
|
||||
audio_hook_test
|
||||
srgb_format_test
|
||||
text_util_test
|
||||
present_hook_test
|
||||
dx12_present_hook_test
|
||||
d3d9_hook_test
|
||||
|
||||
67
tests/text_util_test.cpp
Normal file
67
tests/text_util_test.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// Unit test for the host's shared text helpers: the case-insensitive filter predicate the Log and
|
||||
// Injection panels use (ui/text_match.hpp) and the UTF-8 <-> wide conversions (util/utf8.hpp) every
|
||||
// panel bridges Win32 strings through. Pure logic; no window / device.
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "ui/text_match.hpp"
|
||||
#include "util/utf8.hpp"
|
||||
|
||||
using namespace coop;
|
||||
|
||||
namespace
|
||||
{
|
||||
int g_failures = 0;
|
||||
void check(bool ok, const char* what)
|
||||
{
|
||||
std::printf("%s %s\n", ok ? " ok:" : "FAIL:", what);
|
||||
if (!ok)
|
||||
{
|
||||
++g_failures;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
// --- contains_ci: the filter contract the panels rely on -------------------------------------
|
||||
// An empty (or null) needle matches everything, so an empty filter box shows every row.
|
||||
check(contains_ci("anything", ""), "empty needle matches");
|
||||
check(contains_ci("anything", nullptr), "null needle matches");
|
||||
check(contains_ci("", ""), "empty needle matches an empty haystack");
|
||||
|
||||
// Case-insensitive both ways (so typing 'error' finds 'ERROR', and 'ERR' finds 'stderr').
|
||||
check(contains_ci("ERROR: disk full", "error"), "lowercase needle finds uppercase text");
|
||||
check(contains_ci("stderr stream", "ERR"), "uppercase needle finds lowercase text");
|
||||
check(contains_ci("MixedCase", "edca"), "case-folded substring across a case boundary");
|
||||
|
||||
// Substring position: start, middle, end, and whole-string.
|
||||
check(contains_ci("game.exe", "game"), "match at the start");
|
||||
check(contains_ci("coop_host.exe", "host"), "match in the middle");
|
||||
check(contains_ci("coop_host.exe", ".exe"), "match at the end");
|
||||
check(contains_ci("exact", "exact"), "whole-string match");
|
||||
|
||||
// Non-matches (so a filter actually hides non-matching rows).
|
||||
check(!contains_ci("game.exe", "xyz"), "no false match");
|
||||
check(!contains_ci("short", "longer than the haystack"), "needle longer than haystack");
|
||||
check(!contains_ci("", "x"), "non-empty needle never matches an empty haystack");
|
||||
|
||||
// ascii_lower folds A-Z only and leaves other bytes untouched.
|
||||
check(ascii_lower("AbC123!") == "abc123!", "ascii_lower folds letters, leaves digits/punct");
|
||||
|
||||
// --- narrow / widen: round-trip + boundary cases ---------------------------------------------
|
||||
check(narrow(L"") == "", "narrow of an empty string is empty");
|
||||
check(widen("") == L"", "widen of an empty string is empty");
|
||||
check(narrow(L"game.exe") == "game.exe", "narrow of ASCII is byte-identical");
|
||||
check(widen("game.exe") == L"game.exe", "widen of ASCII is byte-identical");
|
||||
|
||||
// Round-trip preserves the string (ASCII and a non-ASCII code point that must survive
|
||||
// persist/reload of an override key -- the reason narrow/widen exist instead of a byte mask).
|
||||
for (const wchar_t* s : {L"CoopAllTheThings", L"path\\to\\Game (2).exe", L"café.exe", L"游戏.exe"})
|
||||
{
|
||||
check(widen(narrow(std::wstring(s))) == std::wstring(s), "narrow->widen round-trips");
|
||||
}
|
||||
|
||||
std::printf(g_failures == 0 ? "PASS text_util_test\n" : "FAILED text_util_test (%d)\n", g_failures);
|
||||
return g_failures == 0 ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user