https://git.reactos.org/?p=reactos.git;a=commitdiff;h=ba516ff0f5c83f86d76b13...
commit ba516ff0f5c83f86d76b1383dbc58b6ef03f9031 Author: Hermès Bélusca-Maïto hermes.belusca-maito@reactos.org AuthorDate: Sat Feb 5 18:44:02 2022 +0100 Commit: Hermès Bélusca-Maïto hermes.belusca-maito@reactos.org CommitDate: Tue Feb 8 15:58:00 2022 +0100
[KERNEL32_APITESTS] Add a test showing thread locale/LangID sync with console (#4301)
This test shows how a console program's thread's language ID changes, or not, when changing the console output code page (or indirectly when starting on/connecting to a new console), depending on whether or not the console is running on a CJK/on a non-CJK system, and the selected code page is a CJK/a non-CJK one.
By that, the test indirectly tests the (non-exposed) consoles GetLangId + SetThreadLocale() calls being made under the hood. --- modules/rostests/apitests/kernel32/ConsoleCP.c | 215 +++++++++++++++++++++---- 1 file changed, 184 insertions(+), 31 deletions(-)
diff --git a/modules/rostests/apitests/kernel32/ConsoleCP.c b/modules/rostests/apitests/kernel32/ConsoleCP.c index 6a997681ac5..31fc45767f3 100644 --- a/modules/rostests/apitests/kernel32/ConsoleCP.c +++ b/modules/rostests/apitests/kernel32/ConsoleCP.c @@ -1,9 +1,9 @@ /* * PROJECT: ReactOS api tests - * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: Tests for i18n console. * COPYRIGHT: Copyright 2017-2020 Katayama Hirofumi MZ - * Copyright 2020 Hermes Belusca-Maito + * Copyright 2020-2022 Hermès Bélusca-Maïto */
#include "precomp.h" @@ -24,30 +24,189 @@ static const WCHAR u9580[] = {0x9580, 0}; /* 門 */ static const WCHAR space[] = {L' ', 0}; static const WCHAR ideograph_space = (WCHAR)0x3000; /* fullwidth space */ static const WCHAR s_str[] = {L'A', 0x9580, 'B', 0}; -static LCID lcidJapanese = MAKELCID(MAKELANGID(LANG_JAPANESE, SUBLANG_DEFAULT), SORT_DEFAULT); -static LCID lcidRussian = MAKELCID(MAKELANGID(LANG_RUSSIAN , SUBLANG_DEFAULT), SORT_DEFAULT); +static const LCID lcidJapanese = MAKELCID(MAKELANGID(LANG_JAPANESE, SUBLANG_DEFAULT), SORT_DEFAULT); +static const LCID lcidRussian = MAKELCID(MAKELANGID(LANG_RUSSIAN , SUBLANG_DEFAULT), SORT_DEFAULT); + +static UINT s_uOEMCP; static BOOL s_bIs8Plus;
-static BOOL IsCJKCodePage(void) +static BOOL IsCJKCodePage(_In_ UINT CodePage) { - switch (GetOEMCP()) + switch (CodePage) { - case 936: // Chinese PRC case 932: // Japanese case 949: // Korean case 1361: // Korean (Johab) + case 936: // Chinese PRC case 950: // Taiwan return TRUE; } return FALSE; }
+static __inline +LANGID MapCJKCPToLangId(_In_ UINT CodePage) +{ + switch (CodePage) + { + case 932: // Japanese (Shift-JIS) + return MAKELANGID(LANG_JAPANESE, SUBLANG_DEFAULT); + case 949: // Korean (Hangul/Wansung) + // case 1361: // Korean (Johab) + return MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN); + case 936: // Chinese PRC (Chinese Simplified) + return MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED); + case 950: // Taiwan (Chinese Traditional) + return MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL); + default: + return MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); + } +} + +static BOOL ChangeOutputCP_( + _In_ const char* file, + _In_ int line, + _In_ UINT CodePage) +{ + BOOL bSuccess; + + /* Validate the code page */ + bSuccess = IsValidCodePage(CodePage); + if (!bSuccess) + { + skip_(file, line)("Code page %d not available\n", CodePage); + return FALSE; + } + + /* Set the new code page */ + SetLastError(0xdeadbeef); + bSuccess = SetConsoleOutputCP(CodePage); + if (!bSuccess) + skip_(file, line)("SetConsoleOutputCP(%d) failed with last error %lu\n", CodePage, GetLastError()); + return bSuccess; +} + +#define ChangeOutputCP(CodePage) \ + ChangeOutputCP_(__FILE__, __LINE__, CodePage) + + +#define cmpThreadLangId(file, line, ExpectedLangId) \ +do { \ + LANGID ThreadLangId; \ + ThreadLangId = LANGIDFROMLCID(NtCurrentTeb()->CurrentLocale); \ + trace_((file), (line))("Thread LangId %d, expecting %d...\n", \ + ThreadLangId, (ExpectedLangId)); \ + ok_((file), (line))(ThreadLangId == (ExpectedLangId), \ + "Thread LangId %d, expected %d\n", \ + ThreadLangId, (ExpectedLangId)); \ +} while (0) + +static BOOL +doTest_CP_ThreadLang_( + _In_ const char* file, + _In_ int line, + _In_ UINT CodePage, + _In_ LANGID ExpectedLangId) +{ + UINT newcp; + + /* Verify and set the new code page */ + if (!ChangeOutputCP_(file, line, CodePage)) + { + skip_(file, line)("Code page %d expected to be valid!\n", CodePage); + return FALSE; + } + + newcp = GetConsoleOutputCP(); + ok_(file, line)(newcp == CodePage, "Console output CP is %d, expected %d\n", newcp, CodePage); + + /* Verify that the thread lang ID is the expected one */ + cmpThreadLangId(file, line, ExpectedLangId); + return TRUE; +} + +#define doTest_CP_ThreadLang(...) \ + doTest_CP_ThreadLang_(__FILE__, __LINE__, ##__VA_ARGS__) + +static VOID test_CP_ThreadLang(VOID) +{ + /* Save the initial current thread locale. It is (re)initialized after + * attaching to a console. Don't use GetThreadLocale() as the latter + * can return a replacement value in case CurrentLocale is 0. */ + LANGID ThreadLangId = LANGIDFROMLCID(NtCurrentTeb()->CurrentLocale); + UINT oldcp = GetConsoleOutputCP(); + + if (IsCJKCodePage(s_uOEMCP)) + { + /* We are on a CJK system */ + + /* Is the console in CJK? If so, current thread should be in CJK language */ + if (!IsCJKCodePage(oldcp)) + { + skip("CJK system but console CP not in CJK\n"); + } + else + { + /* Check that the thread lang ID matches what the console set */ + LANGID LangId = MapCJKCPToLangId(oldcp); + cmpThreadLangId(__FILE__, __LINE__, LangId); + } + + /* Set the code page to OEM USA (non-CJK codepage that is supported). + * Verify that the thread lang ID has changed to non-CJK language. */ + doTest_CP_ThreadLang(437, MapCJKCPToLangId(437)); + + /* Set the code page to the default system CJK codepage. + * Check that the thread lang ID matches what the console set. */ + doTest_CP_ThreadLang(s_uOEMCP, MapCJKCPToLangId(s_uOEMCP)); + } + else + { + /* We are on a non-CJK system */ + + /* Code pages: Japanese, Korean, Chinese Simplified/Traditional */ + UINT CJKCodePages[] = {932, 949, 936, 950}; + UINT newcp; + USHORT i; + + /* Switch to a different code page (OEM USA) than the current one. + * In such setup, the current thread lang ID should not change. */ + newcp = (s_uOEMCP == 437 ? 850 : 437); + doTest_CP_ThreadLang(newcp, ThreadLangId); + + /* Try switching to a CJK codepage, if possible, but + * the thread lang ID should not change either... */ + + /* Retry as long as no valid CJK codepage has been found */ + for (i = 0; i < ARRAYSIZE(CJKCodePages); ++i) + { + newcp = CJKCodePages[i]; + if (IsValidCodePage(newcp)) + break; // Found a valid one. + } + if (i >= ARRAYSIZE(CJKCodePages)) + { + /* No valid CJK code pages on the system */ + skip("CJK system but console CP not in CJK\n"); + } + else + { + /* Verify that the thread lang ID remains the same */ + doTest_CP_ThreadLang(newcp, ThreadLangId); + } + } + + /* Restore code page */ + SetConsoleOutputCP(oldcp); +} + + /* Russian Code Page 855 */ // NOTE that CP 866 can also be used static void test_cp855(HANDLE hConOut) { BOOL ret; - DWORD oldcp; + UINT oldcp; int n; DWORD len; COORD c; @@ -56,19 +215,11 @@ static void test_cp855(HANDLE hConOut) WCHAR str[32]; WORD attrs[16];
- if (!IsValidCodePage(855)) - { - skip("Codepage 855 not available\n"); - return; - } - /* Set code page */ oldcp = GetConsoleOutputCP(); - SetLastError(0xdeadbeef); - ret = SetConsoleOutputCP(855); - if (!ret) + if (!ChangeOutputCP(855)) { - skip("SetConsoleOutputCP failed with last error %lu\n", GetLastError()); + skip("Codepage 855 not available\n"); return; }
@@ -269,7 +420,7 @@ static void test_cp855(HANDLE hConOut) static void test_cp932(HANDLE hConOut) { BOOL ret; - DWORD oldcp; + UINT oldcp; int n; DWORD len; COORD c, buffSize; @@ -280,19 +431,11 @@ static void test_cp932(HANDLE hConOut) CHAR_INFO buff[16]; SMALL_RECT sr;
- if (!IsValidCodePage(932)) - { - skip("Codepage 932 not available\n"); - return; - } - /* Set code page */ oldcp = GetConsoleOutputCP(); - SetLastError(0xdeadbeef); - ret = SetConsoleOutputCP(932); - if (!ret) + if (!ChangeOutputCP(932)) { - skip("SetConsoleOutputCP failed with last error %lu\n", GetLastError()); + skip("Codepage 932 not available\n"); return; }
@@ -1364,6 +1507,7 @@ static void test_cp932(HANDLE hConOut) SetConsoleOutputCP(oldcp); }
+ START_TEST(ConsoleCP) { HANDLE hConIn, hConOut; @@ -1382,9 +1526,18 @@ START_TEST(ConsoleCP) ok(hConIn != INVALID_HANDLE_VALUE, "Opening ConIn\n"); ok(hConOut != INVALID_HANDLE_VALUE, "Opening ConOut\n");
+ /* Retrieve the system OEM code page */ + s_uOEMCP = GetOEMCP(); + trace("Running on %s system (codepage %d)\n", + IsCJKCodePage(s_uOEMCP) ? "CJK" : "Non-CJK", + s_uOEMCP); + + /* Test thread lang ID syncing with console code page */ + test_CP_ThreadLang(); + if (IsValidLocale(lcidRussian, LCID_INSTALLED)) { - if (!IsCJKCodePage()) + if (!IsCJKCodePage(s_uOEMCP)) test_cp855(hConOut); else skip("Russian testcase is skipped because of CJK\n"); @@ -1396,7 +1549,7 @@ START_TEST(ConsoleCP)
if (IsValidLocale(lcidJapanese, LCID_INSTALLED)) { - if (IsCJKCodePage()) + if (IsCJKCodePage(s_uOEMCP)) test_cp932(hConOut); else skip("Japanese testcase is skipped because of not CJK\n");