1 /*
2  * PROJECT:     ReactOS API tests
3  * LICENSE:     LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+)
4  * PURPOSE:     debugging and analysis of message states
5  * COPYRIGHT:   Copyright 2019 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6  */
7 
8 #include "precomp.h"
9 #include "undocuser.h"
10 #include "winxx.h"
11 #include <strsafe.h>
12 
13 static void MsgDumpPrintf(LPCSTR fmt, ...)
14 {
15     static char s_szText[1024];
16     va_list va;
17     va_start(va, fmt);
18     StringCbVPrintfA(s_szText, sizeof(s_szText), fmt, va);
19     trace("%s", s_szText);
20     va_end(va);
21 }
22 #define MSGDUMP_TPRINTF MsgDumpPrintf
23 static char s_prefix[16] = "";
24 #define MSGDUMP_PREFIX s_prefix
25 #include "msgdump.h"    /* msgdump.h needs MSGDUMP_TPRINTF and MSGDUMP_PREFIX */
26 
27 typedef enum STAGE_TYPE
28 {
29     STAGE_TYPE_SEQUENCE,
30     STAGE_TYPE_COUNTING
31 } STAGE_TYPE;
32 
33 typedef struct STAGE
34 {
35     INT nLine;
36     UINT uParentMsg;
37     INT nLevel;
38     STAGE_TYPE nType;
39     INT iFirstAction;
40     INT nCount;
41     UINT uMessages[10];
42     INT iActions[10];
43     INT nCounters[10];
44 } STAGE;
45 
46 /* variables */
47 static INT s_iStage;
48 static INT s_iStep;
49 static INT s_nLevel;
50 static BOOL s_bNextStage;
51 static INT s_nCounters[10];
52 static UINT s_msgStack[32];
53 static const STAGE *s_pStages;
54 static INT s_cStages;
55 
56 /* macros */
57 #define TIMEOUT_TIMER   999
58 #define TOTAL_TIMEOUT   (5 * 1000)
59 #define WIDTH           300
60 #define HEIGHT          200
61 #define PARENT_MSG      s_msgStack[s_nLevel - 1]
62 
63 static void DoInitialize(const STAGE *pStages, INT cStages)
64 {
65     s_iStage = s_iStep = s_nLevel = 0;
66     s_bNextStage = FALSE;
67     ZeroMemory(s_nCounters, sizeof(s_nCounters));
68     ZeroMemory(s_msgStack, sizeof(s_msgStack));
69     s_pStages = pStages;
70     s_cStages = cStages;
71 }
72 
73 static void DoFinish(void)
74 {
75     ok_int(s_iStage, s_cStages);
76     if (s_iStage != s_cStages)
77     {
78         skip("Some stage(s) skipped (Step: %d)\n", s_iStep);
79     }
80 }
81 
82 typedef enum ACTION
83 {
84     ACTION_ZERO = 0,
85     ACTION_FIRSTMINMAX,
86     ACTION_NCCREATE,
87     ACTION_SHOW,
88     ACTION_IME_SETCONTEXT_OPEN,
89     ACTION_IME_NOTIFY_OPEN,
90     ACTION_DESTROY,
91     ACTION_IME_SETCONTEXT_CLOSE,
92     ACTION_IME_NOTIFY_CLOSE,
93     ACTION_HIDE,
94     ACTION_DEACTIVATE,
95     ACTION_ACTIVATE
96 } ACTION;
97 
98 static void DoAction(HWND hwnd, INT iAction, WPARAM wParam, LPARAM lParam)
99 {
100     RECT rc;
101     switch (iAction)
102     {
103         case ACTION_ZERO:
104             /* does nothing */
105             break;
106         case ACTION_FIRSTMINMAX:
107             GetWindowRect(hwnd, &rc);
108             ok_long(rc.right - rc.left, 0);
109             ok_long(rc.bottom - rc.top, 0);
110             ok_int(IsWindowVisible(hwnd), FALSE);
111             break;
112         case ACTION_NCCREATE:
113             GetWindowRect(hwnd, &rc);
114             ok_long(rc.right - rc.left, WIDTH);
115             ok_long(rc.bottom - rc.top, HEIGHT);
116             ok_int(IsWindowVisible(hwnd), FALSE);
117             break;
118         case ACTION_SHOW:
119             ShowWindow(hwnd, SW_SHOWNORMAL);
120             break;
121         case ACTION_IME_SETCONTEXT_OPEN:
122             ok(wParam == 1, "Step %d: wParam was %p\n", s_iStep, (void *)wParam);
123             ok(lParam == 0xC000000F, "Step %d: lParam was %p\n", s_iStep, (void *)lParam);
124             break;
125         case ACTION_IME_NOTIFY_OPEN:
126             ok(wParam == 2, "Step %d: wParam was %p\n", s_iStep, (void *)wParam);
127             ok(lParam == 0, "Step %d: lParam was %p\n", s_iStep, (void *)lParam);
128             break;
129         case ACTION_DESTROY:
130             DestroyWindow(hwnd);
131             break;
132         case ACTION_IME_SETCONTEXT_CLOSE:
133             ok(wParam == 0, "Step %d: wParam was %p\n", s_iStep, (void *)wParam);
134             ok(lParam == 0xC000000F, "Step %d: lParam was %p\n", s_iStep, (void *)lParam);
135             break;
136         case ACTION_IME_NOTIFY_CLOSE:
137             ok(wParam == 1, "Step %d: wParam was %p\n", s_iStep, (void *)wParam);
138             ok(lParam == 0, "Step %d: lParam was %p\n", s_iStep, (void *)lParam);
139             break;
140         case ACTION_HIDE:
141             ShowWindow(hwnd, SW_HIDE);
142             break;
143         case ACTION_DEACTIVATE:
144             SetForegroundWindow(GetDesktopWindow());
145             break;
146         case ACTION_ACTIVATE:
147             SetForegroundWindow(hwnd);
148             break;
149     }
150 }
151 
152 static void NextStage(HWND hwnd)
153 {
154     INT i, iAction;
155     const STAGE *pStage = &s_pStages[s_iStage];
156 
157     if (pStage->nType == STAGE_TYPE_COUNTING)
158     {
159         /* check counters */
160         for (i = 0; i < pStage->nCount; ++i)
161         {
162             if (pStage->nCounters[i] > 0)
163             {
164                 ok(pStage->nCounters[i] == s_nCounters[i],
165                    "Line %d: s_nCounters[%d] expected %d but %d.\n",
166                    pStage->nLine, i, pStage->nCounters[i], s_nCounters[i]);
167             }
168         }
169     }
170 
171     /* go to next stage */
172     ++s_iStage;
173     if (s_iStage >= s_cStages)
174     {
175         DestroyWindow(hwnd);
176         return;
177     }
178     trace("Stage %d (Line %d)\n", s_iStage, s_pStages[s_iStage].nLine);
179 
180     s_iStep = 0;
181     ZeroMemory(s_nCounters, sizeof(s_nCounters));
182 
183     iAction = s_pStages[s_iStage].iFirstAction;
184     if (iAction)
185         PostMessage(hwnd, WM_COMMAND, iAction, 0);
186 }
187 
188 static void DoStage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
189 {
190     INT i, iAction;
191     const STAGE *pStage;
192     s_bNextStage = FALSE;
193 
194     if (s_iStage >= s_cStages)
195         return;
196 
197     pStage = &s_pStages[s_iStage];
198     switch (pStage->nType)
199     {
200         case STAGE_TYPE_SEQUENCE:
201             if (pStage->uMessages[s_iStep] == uMsg)
202             {
203                 ok_int(1, 1);
204                 ok(s_nLevel == pStage->nLevel,
205                    "Line %d, Step %d: Level expected %d but %d.\n",
206                    pStage->nLine, s_iStep, pStage->nLevel, s_nLevel);
207                 ok(PARENT_MSG == pStage->uParentMsg,
208                    "Line %d, Step %d: PARENT_MSG expected %u but %u.\n",
209                    pStage->nLine, s_iStep, pStage->uParentMsg, PARENT_MSG);
210 
211                 iAction = pStage->iActions[s_iStep];
212                 if (iAction)
213                     DoAction(hwnd, iAction, wParam, lParam);
214 
215                 ++s_iStep;
216                 if (s_iStep >= pStage->nCount)
217                     s_bNextStage = TRUE;
218             }
219             break;
220         case STAGE_TYPE_COUNTING:
221             for (i = 0; i < pStage->nCount; ++i)
222             {
223                 if (pStage->uMessages[i] == uMsg)
224                 {
225                     ok_int(1, 1);
226                     ok(s_nLevel == pStage->nLevel,
227                        "Line %d: Level expected %d but %d.\n",
228                        pStage->nLine, pStage->nLevel, s_nLevel);
229                     ok(PARENT_MSG == pStage->uParentMsg,
230                        "Line %d: PARENT_MSG expected %u but %u.\n",
231                        pStage->nLine, pStage->uParentMsg, PARENT_MSG);
232 
233                     iAction = pStage->iActions[i];
234                     if (iAction)
235                         DoAction(hwnd, iAction, wParam, lParam);
236 
237                     ++s_nCounters[i];
238 
239                     if (i == pStage->nCount - 1)
240                         s_bNextStage = TRUE;
241                     break;
242                 }
243             }
244             break;
245     }
246 
247     if (s_bNextStage)
248     {
249         NextStage(hwnd);
250     }
251 }
252 
253 static LRESULT CALLBACK
254 InnerWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
255 {
256     switch (uMsg)
257     {
258         case WM_COMMAND:
259             DoAction(hwnd, LOWORD(wParam), 0, 0);
260             break;
261         case WM_TIMER:
262             KillTimer(hwnd, (UINT)wParam);
263             if (wParam == TIMEOUT_TIMER)
264                 DestroyWindow(hwnd);
265             break;
266         case WM_DESTROY:
267             PostQuitMessage(0);
268             break;
269         case WM_NCCREATE:
270             SetTimer(hwnd, TIMEOUT_TIMER, TOTAL_TIMEOUT, NULL);
271             /* FALL THROUGH */
272         default:
273             return DefWindowProc(hwnd, uMsg, wParam, lParam);
274     }
275     return 0;
276 }
277 
278 static void DoBuildPrefix(void)
279 {
280     DWORD Flags = InSendMessageEx(NULL);
281     INT i = 0;
282 
283     if (Flags & ISMEX_CALLBACK)
284         s_prefix[i++] = 'C';
285     if (Flags & ISMEX_NOTIFY)
286         s_prefix[i++] = 'N';
287     if (Flags & ISMEX_REPLIED)
288         s_prefix[i++] = 'R';
289     if (Flags & ISMEX_SEND)
290         s_prefix[i++] = 'S';
291     if (i == 0)
292         s_prefix[i++] = 'P';
293 
294     s_prefix[i++] = ':';
295     s_prefix[i++] = ' ';
296     s_prefix[i] = 0;
297 }
298 
299 static const STAGE s_GeneralStages[] =
300 {
301     /* Stage 0 */
302     {
303         __LINE__, WM_NULL, 1, STAGE_TYPE_SEQUENCE, 0,
304         4,
305         { WM_GETMINMAXINFO, WM_NCCREATE, WM_NCCALCSIZE, WM_CREATE },
306         { ACTION_FIRSTMINMAX, ACTION_NCCREATE, 0, 0 },
307     },
308     /* Stage 1 */
309     {
310         __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, ACTION_SHOW,
311         6,
312         { WM_SHOWWINDOW, WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGING,
313           WM_ACTIVATEAPP, WM_NCACTIVATE, WM_ACTIVATE },
314     },
315     {
316         __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, ACTION_DESTROY,
317         6,
318         { WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED, WM_NCACTIVATE,
319           WM_ACTIVATE, WM_ACTIVATEAPP, WM_KILLFOCUS },
320     },
321     {
322         __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, 0,
323         2,
324         { WM_DESTROY, WM_NCDESTROY },
325     },
326 };
327 
328 static LRESULT CALLBACK
329 WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
330 {
331     LRESULT lResult;
332 
333     /* Skip asynchronous WM_TIMER messages */
334     if (uMsg == WM_TIMER) return InnerWindowProc(hwnd, uMsg, wParam, lParam);
335 
336     /* build s_prefix */
337     DoBuildPrefix();
338 
339     /* message dump */
340     MD_msgdump(hwnd, uMsg, wParam, lParam);
341 
342     ++s_nLevel;
343     s_msgStack[s_nLevel] = uMsg;
344     {
345         /* do inner task */
346         DoStage(hwnd, uMsg, wParam, lParam);
347         lResult = InnerWindowProc(hwnd, uMsg, wParam, lParam);
348     }
349     --s_nLevel;
350 
351     /* message return */
352     StringCbCopyA(s_prefix, sizeof(s_prefix), "R: ");
353     MD_msgresult(hwnd, uMsg, wParam, lParam, lResult);
354     return lResult;
355 }
356 
357 static void General_DoTest(void)
358 {
359     WNDCLASSA wc;
360     HWND hwnd;
361     MSG msg;
362     static const char s_szName[] = "MessageStateAnalyzerGeneral";
363 
364     trace("General_DoTest\n");
365     DoInitialize(s_GeneralStages, ARRAYSIZE(s_GeneralStages));
366 
367     /* register window class */
368     ZeroMemory(&wc, sizeof(wc));
369     wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
370     wc.lpfnWndProc = WindowProc;
371     wc.hInstance = GetModuleHandleA(NULL);
372     wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
373     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
374     wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
375     wc.lpszClassName = s_szName;
376     if (!RegisterClassA(&wc))
377     {
378         skip("RegisterClassW failed.\n");
379         return;
380     }
381 
382     /* create a window */
383     hwnd = CreateWindowA(s_szName, s_szName, WS_OVERLAPPEDWINDOW,
384                          0, 0, WIDTH, HEIGHT, NULL, NULL,
385                          GetModuleHandleW(NULL), NULL);
386     if (!hwnd)
387     {
388         skip("CreateWindowW failed.\n");
389         return;
390     }
391 
392     /* message loop */
393     while (GetMessageA(&msg, NULL, 0, 0))
394     {
395         TranslateMessage(&msg);
396         DispatchMessageA(&msg);
397     }
398 
399     ok_int(UnregisterClassA(s_szName, GetModuleHandleA(NULL)), TRUE);
400 
401     DoFinish();
402 }
403 
404 static const STAGE s_IMEStages[] =
405 {
406     /* Stage 0 */
407     {
408         __LINE__, WM_NULL, 1, STAGE_TYPE_SEQUENCE, 0,
409         4,
410         { WM_GETMINMAXINFO, WM_NCCREATE, WM_NCCALCSIZE, WM_CREATE },
411         { ACTION_FIRSTMINMAX, ACTION_NCCREATE, 0, 0 },
412     },
413     /* Stage 1 */
414     // show
415     {
416         __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, ACTION_SHOW,
417         6,
418         { WM_SHOWWINDOW, WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGING,
419           WM_ACTIVATEAPP, WM_NCACTIVATE, WM_ACTIVATE },
420     },
421     {
422         __LINE__, WM_ACTIVATE, 3, STAGE_TYPE_SEQUENCE, 0,
423         1,
424         { WM_IME_SETCONTEXT },
425         { ACTION_IME_SETCONTEXT_OPEN },
426     },
427     {
428         __LINE__, WM_IME_SETCONTEXT, 4, STAGE_TYPE_SEQUENCE, 0,
429         1,
430         { WM_IME_NOTIFY },
431         { ACTION_IME_NOTIFY_OPEN },
432     },
433     // hide
434     {
435         __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, ACTION_HIDE,
436         8,
437         { WM_SHOWWINDOW, WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED,
438           WM_NCACTIVATE, WM_ACTIVATE, WM_ACTIVATEAPP, WM_KILLFOCUS,
439           WM_IME_SETCONTEXT },
440         { 0, 0, 0, 0, 0, 0, 0, ACTION_IME_SETCONTEXT_CLOSE }
441     },
442     {
443         __LINE__, WM_IME_SETCONTEXT, 3, STAGE_TYPE_SEQUENCE, 0,
444         1,
445         { WM_IME_NOTIFY },
446         { ACTION_IME_NOTIFY_CLOSE }
447     },
448     // show again
449     {
450         __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, 3,
451         6,
452         { WM_SHOWWINDOW, WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGING,
453           WM_ACTIVATEAPP, WM_NCACTIVATE, WM_ACTIVATE },
454     },
455     {
456         __LINE__, WM_ACTIVATE, 3, STAGE_TYPE_SEQUENCE, 0,
457         1,
458         { WM_IME_SETCONTEXT },
459         { ACTION_IME_SETCONTEXT_OPEN },
460     },
461     {
462         __LINE__, WM_IME_SETCONTEXT, 4, STAGE_TYPE_SEQUENCE, 0,
463         1,
464         { WM_IME_NOTIFY },
465         { ACTION_IME_NOTIFY_OPEN },
466     },
467     // deactivate
468     {
469         __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, ACTION_DEACTIVATE,
470         4,
471         { WM_NCACTIVATE, WM_ACTIVATE, WM_ACTIVATEAPP, WM_KILLFOCUS },
472     },
473     {
474         __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, 0,
475         1,
476         { WM_IME_SETCONTEXT },
477         { ACTION_IME_SETCONTEXT_CLOSE }
478     },
479     {
480         __LINE__, WM_IME_SETCONTEXT, 3, STAGE_TYPE_SEQUENCE, 0,
481         1,
482         { WM_IME_NOTIFY },
483         { ACTION_IME_NOTIFY_CLOSE }
484     },
485     // activate
486     {
487         __LINE__, WM_ACTIVATE, 3, STAGE_TYPE_SEQUENCE, ACTION_ACTIVATE,
488         1,
489         { WM_IME_SETCONTEXT },
490         { ACTION_IME_SETCONTEXT_OPEN }
491     },
492     {
493         __LINE__, WM_IME_SETCONTEXT, 4, STAGE_TYPE_SEQUENCE, 0,
494         1,
495         { WM_IME_NOTIFY },
496         { ACTION_IME_NOTIFY_OPEN },
497     },
498     // destroy
499     {
500         __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, ACTION_DESTROY,
501         2,
502         { WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED },
503     },
504     {
505         __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, 0,
506         1,
507         { WM_IME_SETCONTEXT },
508         { ACTION_IME_SETCONTEXT_CLOSE }
509     },
510     {
511         __LINE__, WM_IME_SETCONTEXT, 3, STAGE_TYPE_SEQUENCE, 0,
512         1,
513         { WM_IME_NOTIFY },
514         { ACTION_IME_NOTIFY_CLOSE }
515     },
516     {
517         __LINE__, WM_COMMAND, 2, STAGE_TYPE_SEQUENCE, 0,
518         2,
519         { WM_DESTROY, WM_NCDESTROY },
520     },
521 };
522 
523 static void IME_DoTest(void)
524 {
525     WNDCLASSA wc;
526     HWND hwnd;
527     MSG msg;
528     static const char s_szName[] = "MessageStateAnalyzerIME";
529 
530     trace("IME_DoTest\n");
531     DoInitialize(s_IMEStages, ARRAYSIZE(s_IMEStages));
532 
533     /* register window class */
534     ZeroMemory(&wc, sizeof(wc));
535     wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
536     wc.lpfnWndProc = WindowProc;
537     wc.hInstance = GetModuleHandleA(NULL);
538     wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
539     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
540     wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
541     wc.lpszClassName = s_szName;
542     if (!RegisterClassA(&wc))
543     {
544         skip("RegisterClassW failed.\n");
545         return;
546     }
547 
548     /* create a window */
549     hwnd = CreateWindowA(s_szName, s_szName, WS_OVERLAPPEDWINDOW,
550                          0, 0, WIDTH, HEIGHT, NULL, NULL,
551                          GetModuleHandleW(NULL), NULL);
552     if (!hwnd)
553     {
554         skip("CreateWindowW failed.\n");
555         return;
556     }
557 
558     /* message loop */
559     while (GetMessageA(&msg, NULL, 0, 0))
560     {
561         TranslateMessage(&msg);
562         DispatchMessageA(&msg);
563     }
564 
565     ok_int(UnregisterClassA(s_szName, GetModuleHandleA(NULL)), TRUE);
566 
567     DoFinish();
568 }
569 
570 START_TEST(MessageStateAnalyzer)
571 {
572     General_DoTest();
573     IME_DoTest();
574 }
575