/* * PROJECT: ReactOS API tests * LICENSE: LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+) * PURPOSE: debugging and analysis of message states * COPYRIGHT: Copyright 2019 Katayama Hirofumi MZ */ #include "precomp.h" #include "undocuser.h" #include "winxx.h" #include static void MsgDumpPrintf(LPCSTR fmt, ...) { static char s_szText[1024]; va_list va; va_start(va, fmt); StringCbVPrintfA(s_szText, sizeof(s_szText), fmt, va); trace("%s", s_szText); va_end(va); } #define MSGDUMP_TPRINTF MsgDumpPrintf static char s_prefix[16] = ""; #define MSGDUMP_PREFIX s_prefix #include "msgdump.h" /* msgdump.h needs MSGDUMP_TPRINTF and MSGDUMP_PREFIX */ typedef enum STAGE_TYPE { STAGE_TYPE_SEQUENCE, STAGE_TYPE_COUNTING } STAGE_TYPE; typedef struct STAGE { INT nLine; UINT uParentMsg; INT nLevel; STAGE_TYPE nType; INT iFirstAction; INT nCount; UINT uMessages[10]; INT iActions[10]; INT nCounters[10]; } STAGE; /* variables */ static INT s_iStage; static INT s_iStep; static INT s_nLevel; static BOOL s_bNextStage; static INT s_nCounters[10]; static UINT s_msgStack[32]; static const STAGE *s_pStages; static INT s_cStages; /* macros */ #define TIMEOUT_TIMER 999 #define TOTAL_TIMEOUT (5 * 1000) #define WIDTH 300 #define HEIGHT 200 #define PARENT_MSG s_msgStack[s_nLevel - 1] static void DoInitialize(const STAGE *pStages, INT cStages) { s_iStage = s_iStep = s_nLevel = 0; s_bNextStage = FALSE; ZeroMemory(s_nCounters, sizeof(s_nCounters)); ZeroMemory(s_msgStack, sizeof(s_msgStack)); s_pStages = pStages; s_cStages = cStages; } static void DoFinish(void) { ok_int(s_iStage, s_cStages); if (s_iStage != s_cStages) { skip("Some stage(s) skipped (Step: %d)\n", s_iStep); } } typedef enum ACTION { ACTION_ZERO = 0, ACTION_FIRSTMINMAX, ACTION_NCCREATE, ACTION_SHOW, ACTION_IME_SETCONTEXT_OPEN, ACTION_IME_NOTIFY_OPEN, ACTION_DESTROY, ACTION_IME_SETCONTEXT_CLOSE, ACTION_IME_NOTIFY_CLOSE, ACTION_HIDE, ACTION_DEACTIVATE, ACTION_ACTIVATE } ACTION; static void DoAction(HWND hwnd, INT iAction, WPARAM wParam, LPARAM lParam) { RECT rc; switch (iAction) { case ACTION_ZERO: /* does nothing */ break; case ACTION_FIRSTMINMAX: GetWindowRect(hwnd, &rc); ok_long(rc.right - rc.left, 0); ok_long(rc.bottom - rc.top, 0); ok_int(IsWindowVisible(hwnd), FALSE); break; case ACTION_NCCREATE: GetWindowRect(hwnd, &rc); ok_long(rc.right - rc.left, WIDTH); ok_long(rc.bottom - rc.top, HEIGHT); ok_int(IsWindowVisible(hwnd), FALSE); break; case ACTION_SHOW: ShowWindow(hwnd, SW_SHOWNORMAL); break; case ACTION_IME_SETCONTEXT_OPEN: ok(wParam == 1, "Step %d: wParam was %p\n", s_iStep, (void *)wParam); ok(lParam == 0xC000000F, "Step %d: lParam was %p\n", s_iStep, (void *)lParam); break; case ACTION_IME_NOTIFY_OPEN: ok(wParam == 2, "Step %d: wParam was %p\n", s_iStep, (void *)wParam); ok(lParam == 0, "Step %d: lParam was %p\n", s_iStep, (void *)lParam); break; case ACTION_DESTROY: DestroyWindow(hwnd); break; case ACTION_IME_SETCONTEXT_CLOSE: ok(wParam == 0, "Step %d: wParam was %p\n", s_iStep, (void *)wParam); ok(lParam == 0xC000000F, "Step %d: lParam was %p\n", s_iStep, (void *)lParam); break; case ACTION_IME_NOTIFY_CLOSE: ok(wParam == 1, "Step %d: wParam was %p\n", s_iStep, (void *)wParam); ok(lParam == 0, "Step %d: lParam was %p\n", s_iStep, (void *)lParam); break; case ACTION_HIDE: ShowWindow(hwnd, SW_HIDE); break; case ACTION_DEACTIVATE: SetForegroundWindow(GetDesktopWindow()); break; case ACTION_ACTIVATE: SetForegroundWindow(hwnd); break; } } static void NextStage(HWND hwnd) { INT i, iAction; const STAGE *pStage = &s_pStages[s_iStage]; if (pStage->nType == STAGE_TYPE_COUNTING) { /* check counters */ for (i = 0; i < pStage->nCount; ++i) { if (pStage->nCounters[i] > 0) { ok(pStage->nCounters[i] == s_nCounters[i], "Line %d: s_nCounters[%d] expected %d but %d.\n", pStage->nLine, i, pStage->nCounters[i], s_nCounters[i]); } } } /* go to next stage */ ++s_iStage; if (s_iStage >= s_cStages) { DestroyWindow(hwnd); return; } trace("Stage %d (Line %d)\n", s_iStage, s_pStages[s_iStage].nLine); s_iStep = 0; ZeroMemory(s_nCounters, sizeof(s_nCounters)); iAction = s_pStages[s_iStage].iFirstAction; if (iAction) PostMessage(hwnd, WM_COMMAND, iAction, 0); } static void DoStage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { INT i, iAction; const STAGE *pStage; s_bNextStage = FALSE; if (s_iStage >= s_cStages) return; pStage = &s_pStages[s_iStage]; switch (pStage->nType) { case STAGE_TYPE_SEQUENCE: if (pStage->uMessages[s_iStep] == uMsg) { ok_int(1, 1); ok(s_nLevel == pStage->nLevel, "Line %d, Step %d: Level expected %d but %d.\n", pStage->nLine, s_iStep, pStage->nLevel, s_nLevel); ok(PARENT_MSG == pStage->uParentMsg, "Line %d, Step %d: PARENT_MSG expected %u but %u.\n", pStage->nLine, s_iStep, pStage->uParentMsg, PARENT_MSG); iAction = pStage->iActions[s_iStep]; if (iAction) DoAction(hwnd, iAction, wParam, lParam); ++s_iStep; if (s_iStep >= pStage->nCount) s_bNextStage = TRUE; } break; case STAGE_TYPE_COUNTING: for (i = 0; i < pStage->nCount; ++i) { if (pStage->uMessages[i] == uMsg) { ok_int(1, 1); ok(s_nLevel == pStage->nLevel, "Line %d: Level expected %d but %d.\n", pStage->nLine, pStage->nLevel, s_nLevel); ok(PARENT_MSG == pStage->uParentMsg, "Line %d: PARENT_MSG expected %u but %u.\n", pStage->nLine, pStage->uParentMsg, PARENT_MSG); iAction = pStage->iActions[i]; if (iAction) DoAction(hwnd, iAction, wParam, lParam); ++s_nCounters[i]; if (i == pStage->nCount - 1) s_bNextStage = TRUE; break; } } break; } if (s_bNextStage) { NextStage(hwnd); } } static LRESULT CALLBACK InnerWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_COMMAND: DoAction(hwnd, LOWORD(wParam), 0, 0); break; case WM_TIMER: KillTimer(hwnd, (UINT)wParam); if (wParam == TIMEOUT_TIMER) DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; case WM_NCCREATE: SetTimer(hwnd, TIMEOUT_TIMER, TOTAL_TIMEOUT, NULL); /* FALL THROUGH */ default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; } static void DoBuildPrefix(void) { DWORD Flags = InSendMessageEx(NULL); INT i = 0; if (Flags & ISMEX_CALLBACK) s_prefix[i++] = 'C'; if (Flags & ISMEX_NOTIFY) s_prefix[i++] = 'N'; if (Flags & ISMEX_REPLIED) s_prefix[i++] = 'R'; if (Flags & ISMEX_SEND) s_prefix[i++] = 'S'; if (i == 0) s_prefix[i++] = 'P'; s_prefix[i++] = ':'; s_prefix[i++] = ' '; s_prefix[i] = 0; } static const STAGE s_GeneralStages[] = { /* Stage 0 */ { __LINE__, WM_NULL, 1, STAGE_TYPE_SEQUENCE, 0, 4, { WM_GETMINMAXINFO, WM_NCCREATE, WM_NCCALCSIZE, WM_CREATE }, { ACTION_FIRSTMINMAX, ACTION_NCCREATE, 0, 0 }, }, /* Stage 1 */ { __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, ACTION_SHOW, 6, { WM_SHOWWINDOW, WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGING, WM_ACTIVATEAPP, WM_NCACTIVATE, WM_ACTIVATE }, }, { __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, ACTION_DESTROY, 6, { WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED, WM_NCACTIVATE, WM_ACTIVATE, WM_ACTIVATEAPP, WM_KILLFOCUS }, }, { __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, 0, 2, { WM_DESTROY, WM_NCDESTROY }, }, }; static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT lResult; /* Skip asynchronous WM_TIMER messages */ if (uMsg == WM_TIMER) return InnerWindowProc(hwnd, uMsg, wParam, lParam); /* build s_prefix */ DoBuildPrefix(); /* message dump */ MD_msgdump(hwnd, uMsg, wParam, lParam); ++s_nLevel; s_msgStack[s_nLevel] = uMsg; { /* do inner task */ DoStage(hwnd, uMsg, wParam, lParam); lResult = InnerWindowProc(hwnd, uMsg, wParam, lParam); } --s_nLevel; /* message return */ StringCbCopyA(s_prefix, sizeof(s_prefix), "R: "); MD_msgresult(hwnd, uMsg, wParam, lParam, lResult); return lResult; } static void General_DoTest(void) { WNDCLASSA wc; HWND hwnd; MSG msg; static const char s_szName[] = "MessageStateAnalyzerGeneral"; trace("General_DoTest\n"); DoInitialize(s_GeneralStages, ARRAYSIZE(s_GeneralStages)); /* register window class */ ZeroMemory(&wc, sizeof(wc)); wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wc.lpfnWndProc = WindowProc; wc.hInstance = GetModuleHandleA(NULL); wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); wc.lpszClassName = s_szName; if (!RegisterClassA(&wc)) { skip("RegisterClassW failed.\n"); return; } /* create a window */ hwnd = CreateWindowA(s_szName, s_szName, WS_OVERLAPPEDWINDOW, 0, 0, WIDTH, HEIGHT, NULL, NULL, GetModuleHandleW(NULL), NULL); if (!hwnd) { skip("CreateWindowW failed.\n"); return; } /* message loop */ while (GetMessageA(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessageA(&msg); } ok_int(UnregisterClassA(s_szName, GetModuleHandleA(NULL)), TRUE); DoFinish(); } static const STAGE s_IMEStages[] = { /* Stage 0 */ { __LINE__, WM_NULL, 1, STAGE_TYPE_SEQUENCE, 0, 4, { WM_GETMINMAXINFO, WM_NCCREATE, WM_NCCALCSIZE, WM_CREATE }, { ACTION_FIRSTMINMAX, ACTION_NCCREATE, 0, 0 }, }, /* Stage 1 */ // show { __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, ACTION_SHOW, 6, { WM_SHOWWINDOW, WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGING, WM_ACTIVATEAPP, WM_NCACTIVATE, WM_ACTIVATE }, }, { __LINE__, WM_ACTIVATE, 3, STAGE_TYPE_SEQUENCE, 0, 1, { WM_IME_SETCONTEXT }, { ACTION_IME_SETCONTEXT_OPEN }, }, { __LINE__, WM_IME_SETCONTEXT, 4, STAGE_TYPE_SEQUENCE, 0, 1, { WM_IME_NOTIFY }, { ACTION_IME_NOTIFY_OPEN }, }, // hide { __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, ACTION_HIDE, 8, { WM_SHOWWINDOW, WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED, WM_NCACTIVATE, WM_ACTIVATE, WM_ACTIVATEAPP, WM_KILLFOCUS, WM_IME_SETCONTEXT }, { 0, 0, 0, 0, 0, 0, 0, ACTION_IME_SETCONTEXT_CLOSE } }, { __LINE__, WM_IME_SETCONTEXT, 3, STAGE_TYPE_SEQUENCE, 0, 1, { WM_IME_NOTIFY }, { ACTION_IME_NOTIFY_CLOSE } }, // show again { __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, 3, 6, { WM_SHOWWINDOW, WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGING, WM_ACTIVATEAPP, WM_NCACTIVATE, WM_ACTIVATE }, }, { __LINE__, WM_ACTIVATE, 3, STAGE_TYPE_SEQUENCE, 0, 1, { WM_IME_SETCONTEXT }, { ACTION_IME_SETCONTEXT_OPEN }, }, { __LINE__, WM_IME_SETCONTEXT, 4, STAGE_TYPE_SEQUENCE, 0, 1, { WM_IME_NOTIFY }, { ACTION_IME_NOTIFY_OPEN }, }, // deactivate { __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, ACTION_DEACTIVATE, 4, { WM_NCACTIVATE, WM_ACTIVATE, WM_ACTIVATEAPP, WM_KILLFOCUS }, }, { __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, 0, 1, { WM_IME_SETCONTEXT }, { ACTION_IME_SETCONTEXT_CLOSE } }, { __LINE__, WM_IME_SETCONTEXT, 3, STAGE_TYPE_SEQUENCE, 0, 1, { WM_IME_NOTIFY }, { ACTION_IME_NOTIFY_CLOSE } }, // activate { __LINE__, WM_ACTIVATE, 3, STAGE_TYPE_SEQUENCE, ACTION_ACTIVATE, 1, { WM_IME_SETCONTEXT }, { ACTION_IME_SETCONTEXT_OPEN } }, { __LINE__, WM_IME_SETCONTEXT, 4, STAGE_TYPE_SEQUENCE, 0, 1, { WM_IME_NOTIFY }, { ACTION_IME_NOTIFY_OPEN }, }, // destroy { __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, ACTION_DESTROY, 2, { WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED }, }, { __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, 0, 1, { WM_IME_SETCONTEXT }, { ACTION_IME_SETCONTEXT_CLOSE } }, { __LINE__, WM_IME_SETCONTEXT, 3, STAGE_TYPE_SEQUENCE, 0, 1, { WM_IME_NOTIFY }, { ACTION_IME_NOTIFY_CLOSE } }, { __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, 0, 2, { WM_DESTROY, WM_NCDESTROY }, }, }; static void IME_DoTest(void) { WNDCLASSA wc; HWND hwnd; MSG msg; static const char s_szName[] = "MessageStateAnalyzerIME"; trace("IME_DoTest\n"); DoInitialize(s_IMEStages, ARRAYSIZE(s_IMEStages)); /* register window class */ ZeroMemory(&wc, sizeof(wc)); wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wc.lpfnWndProc = WindowProc; wc.hInstance = GetModuleHandleA(NULL); wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); wc.lpszClassName = s_szName; if (!RegisterClassA(&wc)) { skip("RegisterClassW failed.\n"); return; } /* create a window */ hwnd = CreateWindowA(s_szName, s_szName, WS_OVERLAPPEDWINDOW, 0, 0, WIDTH, HEIGHT, NULL, NULL, GetModuleHandleW(NULL), NULL); if (!hwnd) { skip("CreateWindowW failed.\n"); return; } /* message loop */ while (GetMessageA(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessageA(&msg); } ok_int(UnregisterClassA(s_szName, GetModuleHandleA(NULL)), TRUE); DoFinish(); } START_TEST(MessageStateAnalyzer) { General_DoTest(); IME_DoTest(); }