From 2b611aeb455e8f42a562ed90ab7c50d102fc4ce3 Mon Sep 17 00:00:00 2001 From: Stanislav Motylkov Date: Sun, 7 Jul 2024 01:58:04 +0300 Subject: [PATCH] [SHELL32_APITEST] Add API tests for localized strings CORE-18893 --- .../rostests/apitests/shell32/CMakeLists.txt | 5 +- .../rostests/apitests/shell32/LocaleTests.cpp | 290 ++++++++++++++++++ modules/rostests/apitests/shell32/testlist.c | 2 + 3 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 modules/rostests/apitests/shell32/LocaleTests.cpp diff --git a/modules/rostests/apitests/shell32/CMakeLists.txt b/modules/rostests/apitests/shell32/CMakeLists.txt index c0a6686eecf83..fecfcb81fbc14 100644 --- a/modules/rostests/apitests/shell32/CMakeLists.txt +++ b/modules/rostests/apitests/shell32/CMakeLists.txt @@ -21,6 +21,7 @@ list(APPEND SOURCE Int64ToString.cpp IShellFolderViewCB.cpp ItemIDList.cpp + LocaleTests.cpp OpenAs_RunDLL.cpp PathIsEqualOrSubFolder.cpp PathIsTemporary.cpp @@ -58,7 +59,7 @@ set_target_properties(shell32_apitest ENABLE_EXPORTS TRUE DEFINE_SYMBOL "") -target_link_libraries(shell32_apitest wine uuid ${PSEH_LIB} cpprt atl_classes) +target_link_libraries(shell32_apitest wine uuid ${PSEH_LIB} cpprt cppstl atl_classes) set_module_type(shell32_apitest win32cui) target_compile_definitions(shell32_apitest PRIVATE UNICODE _UNICODE) add_importlibs(shell32_apitest user32 gdi32 shell32 shlwapi ole32 oleaut32 advapi32 shlwapi msvcrt kernel32 ntdll) @@ -67,7 +68,7 @@ add_rostests_file(TARGET shell32_apitest) # shell32_apitest_sub.exe add_executable(shell32_apitest_sub shell32_apitest_sub.cpp) -target_link_libraries(shell32_apitest_sub cpprt atl_classes) +target_link_libraries(shell32_apitest_sub cpprt cppstl atl_classes) set_module_type(shell32_apitest_sub win32gui UNICODE) add_importlibs(shell32_apitest_sub msvcrt kernel32 user32 shell32 shlwapi ole32) add_rostests_file(TARGET shell32_apitest_sub SUBDIR testdata) diff --git a/modules/rostests/apitests/shell32/LocaleTests.cpp b/modules/rostests/apitests/shell32/LocaleTests.cpp new file mode 100644 index 0000000000000..725037f74f19c --- /dev/null +++ b/modules/rostests/apitests/shell32/LocaleTests.cpp @@ -0,0 +1,290 @@ +/* + * PROJECT: ReactOS API tests + * LICENSE: LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+) + * PURPOSE: Formal locale verification tests + * COPYRIGHT: Copyright 2024 Stanislav Motylkov + */ + +#include "shelltest.h" + +#include +#include + +#include +#include + +enum E_MODULE +{ + shell32, + userenv, + syssetup, + mmsys, + explorer_old, +}; + +enum E_STRING +{ + SH32_PROGRAMS, + SH32_STARTUP, + SH32_STARTMENU, + SH32_PROGRAM_FILES, + SH32_PROGRAM_FILES_COMMON, + SH32_ADMINTOOLS, + UENV_STARTMENU, + UENV_PROGRAMS, + UENV_STARTUP, + SYSS_PROGRAMFILES, + SYSS_COMMONFILES, + MMSY_STARTMENU, + EOLD_PROGRAMS, +}; + +typedef struct PART_TEST +{ + E_MODULE eModule; + UINT id; + SIZE_T nParts; +} PART_TEST; + +typedef struct PART +{ + E_STRING Num; + UINT Idx; +} PART; + +typedef struct PART_MATCH +{ + PART p1, p2; +} PART_MATCH; + +LCID curLcid = 0; +std::set langs; +std::map mod; +std::map parts = +{ + { SH32_PROGRAMS, { shell32, 45 /* IDS_PROGRAMS "Start Menu\Programs" */, 2 } }, + { SH32_STARTUP, { shell32, 48 /* IDS_STARTUP "Start Menu\Programs\StartUp" */, 3 } }, + { SH32_STARTMENU, { shell32, 51 /* IDS_STARTMENU "Start Menu" */, 1 } }, + { SH32_PROGRAM_FILES, { shell32, 63 /* IDS_PROGRAM_FILES "Program Files" */, 1 } }, + { SH32_PROGRAM_FILES_COMMON, { shell32, 65 /* IDS_PROGRAM_FILES_COMMON "Program Files\Common Files" */, 2 } }, + { SH32_ADMINTOOLS, { shell32, 67 /* IDS_ADMINTOOLS "Start Menu\Programs\Administrative Tools" */, 3 } }, + { UENV_STARTMENU, { userenv, 11 /* IDS_STARTMENU "Start Menu" */, 1 } }, + { UENV_PROGRAMS, { userenv, 12 /* IDS_PROGRAMS "Start Menu\Programs" */, 2 } }, + { UENV_STARTUP, { userenv, 13 /* IDS_STARTUP "Start Menu\Programs\StartUp" */, 3 } }, + { SYSS_PROGRAMFILES, { syssetup, 3600 /* IDS_PROGRAMFILES "%SystemDrive%\Program Files" */, 2 } }, + { SYSS_COMMONFILES, { syssetup, 3601 /* IDS_COMMONFILES "Common Files" */, 1 } }, + { MMSY_STARTMENU, { mmsys, 5851 /* IDS_STARTMENU "Start Menu" */, 1 } }, + { EOLD_PROGRAMS, { explorer_old, 10 /* IDS_PROGRAMS "Programs" */, 1 } }, +}; + +static PART_MATCH PartMatches[] = +{ + // Start Menu + { { SH32_PROGRAMS, 0 }, { SH32_STARTUP, 0 } }, + { { SH32_PROGRAMS, 0 }, { SH32_STARTMENU, 0 } }, + { { SH32_PROGRAMS, 0 }, { SH32_ADMINTOOLS, 0 } }, + { { SH32_PROGRAMS, 0 }, { UENV_STARTMENU, 0 } }, + { { SH32_PROGRAMS, 0 }, { UENV_PROGRAMS, 0 } }, + { { SH32_PROGRAMS, 0 }, { UENV_STARTUP, 0 } }, + { { SH32_PROGRAMS, 0 }, { MMSY_STARTMENU, 0 } }, + // Programs + { { SH32_PROGRAMS, 1 }, { SH32_STARTUP, 1 } }, + { { SH32_PROGRAMS, 1 }, { SH32_ADMINTOOLS, 1 } }, + { { SH32_PROGRAMS, 1 }, { UENV_PROGRAMS, 1 } }, + { { SH32_PROGRAMS, 1 }, { UENV_STARTUP, 1 } }, + { { SH32_PROGRAMS, 1 }, { EOLD_PROGRAMS, 0 } }, + // StartUp + { { SH32_STARTUP, 2 }, { UENV_STARTUP, 2 } }, + // Program Files + { { SH32_PROGRAM_FILES, 0 }, { SH32_PROGRAM_FILES_COMMON, 0 } }, + { { SH32_PROGRAM_FILES, 0 }, { SYSS_PROGRAMFILES, 1 } }, + // Common Files + { { SH32_PROGRAM_FILES_COMMON, 1 }, { SYSS_COMMONFILES, 0 } }, +}; + +static DWORD CountParts(_In_ LPWSTR str) +{ + DWORD count = 0; + LPWSTR ptr = str; + + if (*ptr == UNICODE_NULL) + return 0; + + while ((ptr = wcschr(ptr, L'\\')) != NULL) + { + count++; + ptr++; + } + + return count + 1; +} + +static LPWSTR GetPart(_In_ LPWSTR str, _In_ SIZE_T num, _Out_ SIZE_T* len) +{ + DWORD count = 0; + LPWSTR ptr = str, next; + + while (count < num && (ptr = wcschr(ptr, L'\\')) != NULL) + { + count++; + ptr++; + } + + next = wcschr(ptr, L'\\'); + *len = next ? next - ptr : wcslen(ptr); + return ptr; +} + +static BOOL CALLBACK find_locale_id_callback( + _In_ HMODULE hModule, _In_ LPCWSTR type, _In_ LPCWSTR name, _In_ LANGID lang, _In_ LPARAM lParam) +{ + langs.insert(lang); + return TRUE; +} + +static void SetLocale(_In_ LCID lcid) +{ + SetThreadLocale(lcid); + SetThreadUILanguage(lcid); + curLcid = lcid; +} + +static void TEST_NumParts(void) +{ + for (auto i = parts.begin(); i != parts.end(); i++) + { + E_MODULE m = i->second.eModule; + + if (!mod[m]) + { + skip("No module for test #%d\n", i->first); + continue; + } + + WCHAR szBuffer[MAX_PATH]; + + LoadStringW(mod[m], i->second.id, szBuffer, _countof(szBuffer)); + ok(i->second.nParts == CountParts(szBuffer), "Locale 0x%lX: Num parts mismatch #%d - expected %lu, got %lu\n", + curLcid, i->first, i->second.nParts, CountParts(szBuffer)); + } +} + +static BOOL LoadPart(_In_ PART* p, _Out_ LPWSTR str, _In_ SIZE_T size) +{ + auto s = parts[p->Num]; + E_MODULE m = s.eModule; + + if (!mod[m]) + return FALSE; + + WCHAR szBuffer[MAX_PATH]; + LPWSTR szPart; + SIZE_T len; + + LoadStringW(mod[m], s.id, szBuffer, _countof(szBuffer)); + szPart = GetPart(szBuffer, p->Idx, &len); + StringCchCopyNW(str, size, szPart, len); + + return TRUE; +} + +static void TEST_PartMatches(void) +{ + for (SIZE_T i = 0; i < _countof(PartMatches); i++) + { + WCHAR szP1[MAX_PATH], szP2[MAX_PATH]; + + if (!LoadPart(&PartMatches[i].p1, szP1, _countof(szP1))) + { + skip("No module for match test #%lu pair 1\n", i); + continue; + } + + if (!LoadPart(&PartMatches[i].p2, szP2, _countof(szP2))) + { + skip("No module for match test #%lu pair 2\n", i); + continue; + } + + ok(wcscmp(szP1, szP2) == 0, "Locale 0x%lX: Mismatching pairs #%u:%u / #%u:%u '%S' vs. '%S'\n", + curLcid, PartMatches[i].p1.Num, PartMatches[i].p1.Idx, PartMatches[i].p2.Num, PartMatches[i].p2.Idx, szP1, szP2); + } +} + +static void TEST_LocaleTests(void) +{ + // Initialization + WCHAR szOldDir[MAX_PATH], szBuffer[MAX_PATH]; + GetCurrentDirectoryW(_countof(szOldDir), szOldDir); + + std::map lib; + + GetModuleFileNameW(NULL, szBuffer, _countof(szBuffer)); + LPWSTR pszFind = StrStrW(szBuffer, L"modules\\rostests\\apitests"); + if (pszFind) + { + WCHAR szNewDir[MAX_PATH]; + + StringCchCopyNW(szNewDir, _countof(szNewDir), szBuffer, pszFind - szBuffer); + SetCurrentDirectoryW(szNewDir); + + lib = { + {shell32, L"dll\\win32\\shell32\\shell32.dll"}, + {userenv, L"dll\\win32\\userenv\\userenv.dll"}, + {syssetup, L"dll\\win32\\syssetup\\syssetup.dll"}, + {mmsys, L"dll\\cpl\\mmsys\\mmsys.cpl"}, + {explorer_old, L"modules\\rosapps\\applications\\explorer-old\\explorer_old.exe"}, + }; + } + else + { + lib = { + {shell32, L"shell32.dll"}, + {userenv, L"userenv.dll"}, + {syssetup, L"syssetup.dll"}, + {mmsys, L"mmsys.cpl"}, + {explorer_old, L"explorer_old.exe"}, + }; + } + + for (auto i = lib.begin(); i != lib.end(); i++) + { + E_MODULE m = i->first; + + mod[m] = LoadLibraryExW(lib[m], NULL, LOAD_LIBRARY_AS_DATAFILE); + if (!mod[m]) + { + trace("Failed to load '%S', error %lu\n", lib[m], GetLastError()); + continue; + } + + EnumResourceLanguagesW(mod[m], (LPCWSTR)RT_STRING, (LPCWSTR)LOCALE_ILANGUAGE, + find_locale_id_callback, NULL); + } + + // Actual tests + for (auto i = langs.begin(); i != langs.end(); i++) + { + SetLocale(MAKELCID(*i, SORT_DEFAULT)); + + TEST_NumParts(); + TEST_PartMatches(); + } + + // Perform cleanup + for (auto i = mod.begin(); i != mod.end(); i++) + { + if (!i->second) + continue; + + FreeLibrary(i->second); + i->second = NULL; + } + + SetCurrentDirectoryW(szOldDir); +} + +START_TEST(LocaleTests) +{ + TEST_LocaleTests(); +} diff --git a/modules/rostests/apitests/shell32/testlist.c b/modules/rostests/apitests/shell32/testlist.c index 9f4fbf34e9e6a..f796039154fcf 100644 --- a/modules/rostests/apitests/shell32/testlist.c +++ b/modules/rostests/apitests/shell32/testlist.c @@ -21,6 +21,7 @@ extern void func_GUIDFromString(void); extern void func_ILCreateFromPath(void); extern void func_Int64ToString(void); extern void func_IShellFolderViewCB(void); +extern void func_LocaleTests(void); extern void func_menu(void); extern void func_OpenAs_RunDLL(void); extern void func_PathIsEqualOrSubFolder(void); @@ -65,6 +66,7 @@ const struct test winetest_testlist[] = { "ILCreateFromPath", func_ILCreateFromPath }, { "Int64ToString", func_Int64ToString }, { "IShellFolderViewCB", func_IShellFolderViewCB }, + { "LocaleTests", func_LocaleTests }, { "menu", func_menu }, { "OpenAs_RunDLL", func_OpenAs_RunDLL }, { "PathIsEqualOrSubFolder", func_PathIsEqualOrSubFolder },