1 /*
2 * PROJECT: ReactOS api tests
3 * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4 * PURPOSE: Test for SHChangeNotify
5 * COPYRIGHT: Copyright 2020-2024 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
6 */
7
8 // NOTE: This testcase requires shell32_apitest_sub.exe.
9
10 #include "shelltest.h"
11 #include "shell32_apitest_sub.h"
12 #include <assert.h>
13
14 #define NUM_STEP 8
15 #define NUM_CHECKS 12
16 #define INTERVAL 0
17 #define MAX_EVENT_TYPE 6
18
19 static HWND s_hMainWnd = NULL, s_hSubWnd = NULL;
20 static WCHAR s_szSubProgram[MAX_PATH]; // shell32_apitest_sub.exe
21 static HANDLE s_hThread = NULL;
22 static INT s_iStage = -1, s_iStep = -1;
23 static BYTE s_abChecks[NUM_CHECKS] = { 0 }; // Flags for testing
24 static BOOL s_bGotUpdateDir = FALSE; // Got SHCNE_UPDATEDIR?
25
DoCreateFile(LPCWSTR pszFileName)26 static BOOL DoCreateFile(LPCWSTR pszFileName)
27 {
28 HANDLE hFile = ::CreateFileW(pszFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL,
29 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
30 ::CloseHandle(hFile);
31 return hFile != INVALID_HANDLE_VALUE;
32 }
33
DoDeleteDirectory(LPCWSTR pszDir)34 static void DoDeleteDirectory(LPCWSTR pszDir)
35 {
36 WCHAR szPath[MAX_PATH];
37 ZeroMemory(szPath, sizeof(szPath));
38 StringCchCopyW(szPath, _countof(szPath), pszDir); // Double-NULL terminated
39 SHFILEOPSTRUCTW FileOp = { NULL, FO_DELETE, szPath, NULL, FOF_NOCONFIRMATION | FOF_SILENT };
40 SHFileOperation(&FileOp);
41 }
42
GetEventType(LONG lEvent)43 static INT GetEventType(LONG lEvent)
44 {
45 switch (lEvent)
46 {
47 case SHCNE_CREATE: return 0;
48 case SHCNE_DELETE: return 1;
49 case SHCNE_RENAMEITEM: return 2;
50 case SHCNE_MKDIR: return 3;
51 case SHCNE_RMDIR: return 4;
52 case SHCNE_RENAMEFOLDER: return 5;
53 C_ASSERT(5 + 1 == MAX_EVENT_TYPE);
54 default: return -1;
55 }
56 }
57
58 #define FILE_1 L"_TESTFILE_1_.txt"
59 #define FILE_2 L"_TESTFILE_2_.txt"
60 #define DIR_1 L"_TESTDIR_1_"
61 #define DIR_2 L"_TESTDIR_2_"
62
63 static WCHAR s_szDir1[MAX_PATH];
64 static WCHAR s_szDir1InDir1[MAX_PATH];
65 static WCHAR s_szDir2InDir1[MAX_PATH];
66 static WCHAR s_szFile1InDir1InDir1[MAX_PATH];
67 static WCHAR s_szFile1InDir1[MAX_PATH];
68 static WCHAR s_szFile2InDir1[MAX_PATH];
69
DoDeleteFilesAndDirs(void)70 static void DoDeleteFilesAndDirs(void)
71 {
72 ::DeleteFileW(s_szFile1InDir1);
73 ::DeleteFileW(s_szFile2InDir1);
74 ::DeleteFileW(s_szFile1InDir1InDir1);
75 DoDeleteDirectory(s_szDir1InDir1);
76 DoDeleteDirectory(s_szDir2InDir1);
77 DoDeleteDirectory(s_szDir1);
78 }
79
TEST_Quit(void)80 static void TEST_Quit(void)
81 {
82 CloseHandle(s_hThread);
83 s_hThread = NULL;
84
85 PostMessageW(s_hSubWnd, WM_COMMAND, IDNO, 0); // Finish
86 DoWaitForWindow(SUB_CLASSNAME, SUB_CLASSNAME, TRUE, TRUE); // Close sub-windows
87
88 DoDeleteFilesAndDirs();
89 }
90
DoBuildFilesAndDirs(void)91 static void DoBuildFilesAndDirs(void)
92 {
93 WCHAR szPath1[MAX_PATH];
94 SHGetSpecialFolderPathW(NULL, szPath1, CSIDL_PERSONAL, FALSE); // My Documents
95 PathAppendW(szPath1, DIR_1);
96 StringCchCopyW(s_szDir1, _countof(s_szDir1), szPath1);
97
98 PathAppendW(szPath1, DIR_1);
99 StringCchCopyW(s_szDir1InDir1, _countof(s_szDir1InDir1), szPath1);
100 PathRemoveFileSpecW(szPath1);
101
102 PathAppendW(szPath1, DIR_2);
103 StringCchCopyW(s_szDir2InDir1, _countof(s_szDir2InDir1), szPath1);
104 PathRemoveFileSpecW(szPath1);
105
106 PathAppendW(szPath1, DIR_1);
107 PathAppendW(szPath1, FILE_1);
108 StringCchCopyW(s_szFile1InDir1InDir1, _countof(s_szFile1InDir1InDir1), szPath1);
109 PathRemoveFileSpecW(szPath1);
110 PathRemoveFileSpecW(szPath1);
111
112 PathAppendW(szPath1, FILE_1);
113 StringCchCopyW(s_szFile1InDir1, _countof(s_szFile1InDir1), szPath1);
114 PathRemoveFileSpecW(szPath1);
115
116 PathAppendW(szPath1, FILE_2);
117 StringCchCopyW(s_szFile2InDir1, _countof(s_szFile2InDir1), szPath1);
118 PathRemoveFileSpecW(szPath1);
119
120 #define TRACE_PATH(path) trace(#path ": %ls\n", path)
121 TRACE_PATH(s_szDir1);
122 TRACE_PATH(s_szDir1InDir1);
123 TRACE_PATH(s_szFile1InDir1);
124 TRACE_PATH(s_szFile1InDir1InDir1);
125 TRACE_PATH(s_szFile2InDir1);
126 #undef TRACE_PATH
127
128 DoDeleteFilesAndDirs();
129
130 ::CreateDirectoryW(s_szDir1, NULL);
131 ok_int(!!PathIsDirectoryW(s_szDir1), TRUE);
132
133 DoDeleteDirectory(s_szDir1InDir1);
134 ok_int(!PathIsDirectoryW(s_szDir1InDir1), TRUE);
135 }
136
DoTestEntry(LONG lEvent,LPCITEMIDLIST pidl1,LPCITEMIDLIST pidl2)137 static void DoTestEntry(LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
138 {
139 WCHAR szPath1[MAX_PATH], szPath2[MAX_PATH];
140
141 szPath1[0] = szPath2[0] = 0;
142 SHGetPathFromIDListW(pidl1, szPath1);
143 SHGetPathFromIDListW(pidl2, szPath2);
144
145 trace("(0x%lX, '%ls', '%ls')\n", lEvent, szPath1, szPath2);
146
147 if (lEvent == SHCNE_UPDATEDIR)
148 {
149 trace("Got SHCNE_UPDATEDIR\n");
150 s_bGotUpdateDir = TRUE;
151 return;
152 }
153
154 INT iEventType = GetEventType(lEvent);
155 if (iEventType < 0)
156 return;
157
158 assert(iEventType < MAX_EVENT_TYPE);
159
160 INT i = 0;
161 s_abChecks[i++] |= (lstrcmpiW(szPath1, L"") == 0) << iEventType;
162 s_abChecks[i++] |= (lstrcmpiW(szPath1, s_szDir1) == 0) << iEventType;
163 s_abChecks[i++] |= (lstrcmpiW(szPath1, s_szDir2InDir1) == 0) << iEventType;
164 s_abChecks[i++] |= (lstrcmpiW(szPath1, s_szFile1InDir1) == 0) << iEventType;
165 s_abChecks[i++] |= (lstrcmpiW(szPath1, s_szFile2InDir1) == 0) << iEventType;
166 s_abChecks[i++] |= (lstrcmpiW(szPath1, s_szFile1InDir1InDir1) == 0) << iEventType;
167 s_abChecks[i++] |= (lstrcmpiW(szPath2, L"") == 0) << iEventType;
168 s_abChecks[i++] |= (lstrcmpiW(szPath2, s_szDir1) == 0) << iEventType;
169 s_abChecks[i++] |= (lstrcmpiW(szPath2, s_szDir2InDir1) == 0) << iEventType;
170 s_abChecks[i++] |= (lstrcmpiW(szPath2, s_szFile1InDir1) == 0) << iEventType;
171 s_abChecks[i++] |= (lstrcmpiW(szPath2, s_szFile2InDir1) == 0) << iEventType;
172 s_abChecks[i++] |= (lstrcmpiW(szPath2, s_szFile1InDir1InDir1) == 0) << iEventType;
173 assert(i == NUM_CHECKS);
174 }
175
StringFromChecks(void)176 static LPCSTR StringFromChecks(void)
177 {
178 static char s_sz[2 * NUM_CHECKS + 1];
179
180 char *pch = s_sz;
181 for (INT i = 0; i < NUM_CHECKS; ++i)
182 {
183 WCHAR sz[3];
184 StringCchPrintfW(sz, _countof(sz), L"%02X", s_abChecks[i]);
185 *pch++ = sz[0];
186 *pch++ = sz[1];
187 }
188
189 assert((pch - s_sz) + 1 == sizeof(s_sz));
190
191 *pch = 0;
192 return s_sz;
193 }
194
195 struct TEST_ANSWER
196 {
197 INT lineno;
198 LPCSTR answer;
199 };
200
DoStepCheck(INT iStage,INT iStep,LPCSTR checks)201 static void DoStepCheck(INT iStage, INT iStep, LPCSTR checks)
202 {
203 assert(0 <= iStep);
204 assert(iStep < NUM_STEP);
205
206 assert(0 <= iStage);
207 assert(iStage < NUM_STAGE);
208
209 if (s_bGotUpdateDir)
210 {
211 ok_int(TRUE, TRUE);
212 return;
213 }
214
215 LPCSTR answer = NULL;
216 INT lineno;
217 switch (iStage)
218 {
219 case 0:
220 case 1:
221 case 3:
222 case 6:
223 case 9:
224 {
225 static const TEST_ANSWER c_answers[] =
226 {
227 { __LINE__, "000000010000010000000000" }, // 0
228 { __LINE__, "000000040000000000000400" }, // 1
229 { __LINE__, "000000000200020000000000" }, // 2
230 { __LINE__, "000000000000080000000000" }, // 3
231 { __LINE__, "000000000001010000000000" }, // 4
232 { __LINE__, "000000000002020000000000" }, // 5
233 { __LINE__, "000000000000000020000000" }, // 6
234 { __LINE__, "000010000000100000000000" }, // 7
235 };
236 C_ASSERT(_countof(c_answers) == NUM_STEP);
237 lineno = c_answers[iStep].lineno;
238 answer = c_answers[iStep].answer;
239 break;
240 }
241 case 2:
242 case 4:
243 case 5:
244 case 7:
245 {
246 static const TEST_ANSWER c_answers[] =
247 {
248 { __LINE__, "000000000000000000000000" }, // 0
249 { __LINE__, "000000000000000000000000" }, // 1
250 { __LINE__, "000000000000000000000000" }, // 2
251 { __LINE__, "000000000000000000000000" }, // 3
252 { __LINE__, "000000000000000000000000" }, // 4
253 { __LINE__, "000000000000000000000000" }, // 5
254 { __LINE__, "000000000000000000000000" }, // 6
255 { __LINE__, "000000000000000000000000" }, // 7
256 };
257 C_ASSERT(_countof(c_answers) == NUM_STEP);
258 lineno = c_answers[iStep].lineno;
259 answer = c_answers[iStep].answer;
260 break;
261 }
262 case 8:
263 {
264 static const TEST_ANSWER c_answers[] =
265 {
266 { __LINE__, "000000010000010000000000" }, // 0
267 { __LINE__, "000000040000000000000400" }, // 1
268 { __LINE__, "000000000200020000000000" }, // 2
269 { __LINE__, "000000000000080000000000" }, // 3
270 { __LINE__, "000000000001010000000000" }, // 4 // Recursive case
271 { __LINE__, "000000000002020000000000" }, // 5 // Recursive case
272 { __LINE__, "000000000000000020000000" }, // 6
273 { __LINE__, "000010000000100000000000" }, // 7
274 };
275 C_ASSERT(_countof(c_answers) == NUM_STEP);
276 lineno = c_answers[iStep].lineno;
277 answer = c_answers[iStep].answer;
278 if (iStep == 4 || iStep == 5) // Recursive cases
279 {
280 if (lstrcmpA(checks, "000000000000000000000000") == 0)
281 {
282 trace("Warning! Recursive cases...\n");
283 answer = "000000000000000000000000";
284 }
285 }
286 break;
287 }
288 default:
289 {
290 assert(0);
291 break;
292 }
293 }
294
295 ok(lstrcmpA(checks, answer) == 0,
296 "Line %d: '%s' vs '%s' at Stage %d, Step %d\n", lineno, checks, answer, iStage, iStep);
297 }
298
StageThreadFunc(LPVOID arg)299 static DWORD WINAPI StageThreadFunc(LPVOID arg)
300 {
301 BOOL ret;
302
303 trace("Stage %d\n", s_iStage);
304
305 // 0: Create file1 in dir1
306 s_iStep = 0;
307 trace("Step %d\n", s_iStep);
308 SHChangeNotify(0, SHCNF_PATHW | SHCNF_FLUSH, NULL, NULL);
309 ZeroMemory(s_abChecks, sizeof(s_abChecks));
310 ret = DoCreateFile(s_szFile1InDir1);
311 ok_int(ret, TRUE);
312 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW | SHCNF_FLUSH, s_szFile1InDir1, 0);
313 ::Sleep(INTERVAL);
314 DoStepCheck(s_iStage, s_iStep, StringFromChecks());
315
316 // 1: Rename file1 as file2 in dir1
317 ++s_iStep;
318 trace("Step %d\n", s_iStep);
319 ZeroMemory(s_abChecks, sizeof(s_abChecks));
320 ret = MoveFileW(s_szFile1InDir1, s_szFile2InDir1);
321 ok_int(ret, TRUE);
322 SHChangeNotify(SHCNE_RENAMEITEM, SHCNF_PATHW | SHCNF_FLUSH, s_szFile1InDir1, s_szFile2InDir1);
323 ::Sleep(INTERVAL);
324 DoStepCheck(s_iStage, s_iStep, StringFromChecks());
325
326 // 2: Delete file2 in dir1
327 ++s_iStep;
328 trace("Step %d\n", s_iStep);
329 ZeroMemory(s_abChecks, sizeof(s_abChecks));
330 ret = DeleteFileW(s_szFile2InDir1);
331 ok_int(ret, TRUE);
332 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW | SHCNF_FLUSH, s_szFile2InDir1, NULL);
333 ::Sleep(INTERVAL);
334 DoStepCheck(s_iStage, s_iStep, StringFromChecks());
335
336 // 3: Create dir1 in dir1
337 ++s_iStep;
338 trace("Step %d\n", s_iStep);
339 ZeroMemory(s_abChecks, sizeof(s_abChecks));
340 ret = CreateDirectoryExW(s_szDir1, s_szDir1InDir1, NULL);
341 ok_int(ret, TRUE);
342 SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW | SHCNF_FLUSH, s_szDir1InDir1, NULL);
343 ::Sleep(INTERVAL);
344 DoStepCheck(s_iStage, s_iStep, StringFromChecks());
345
346 // 4: Create file1 in dir1 in dir1
347 ++s_iStep;
348 trace("Step %d\n", s_iStep);
349 ZeroMemory(s_abChecks, sizeof(s_abChecks));
350 ret = DoCreateFile(s_szFile1InDir1InDir1);
351 ok_int(ret, TRUE);
352 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW | SHCNF_FLUSH, s_szFile1InDir1InDir1, NULL);
353 ::Sleep(INTERVAL);
354 DoStepCheck(s_iStage, s_iStep, StringFromChecks());
355
356 // 5: Delete file1 in dir1 in dir1
357 ++s_iStep;
358 trace("Step %d\n", s_iStep);
359 ZeroMemory(s_abChecks, sizeof(s_abChecks));
360 ret = DeleteFileW(s_szFile1InDir1InDir1);
361 ok_int(ret, TRUE);
362 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW | SHCNF_FLUSH, s_szFile1InDir1InDir1, NULL);
363 ::Sleep(INTERVAL);
364 DoStepCheck(s_iStage, s_iStep, StringFromChecks());
365
366 // 6: Rename dir1 as dir2 in dir1
367 ++s_iStep;
368 trace("Step %d\n", s_iStep);
369 ZeroMemory(s_abChecks, sizeof(s_abChecks));
370 ret = ::MoveFileW(s_szDir1InDir1, s_szDir2InDir1);
371 ok_int(ret, TRUE);
372 SHChangeNotify(SHCNE_RENAMEFOLDER, SHCNF_PATHW | SHCNF_FLUSH, s_szDir1InDir1, s_szDir2InDir1);
373 ::Sleep(INTERVAL);
374 DoStepCheck(s_iStage, s_iStep, StringFromChecks());
375
376 // 7: Remove dir2 in dir1
377 ++s_iStep;
378 trace("Step %d\n", s_iStep);
379 ZeroMemory(s_abChecks, sizeof(s_abChecks));
380 ret = RemoveDirectoryW(s_szDir2InDir1);
381 ok_int(ret, TRUE);
382 SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW | SHCNF_FLUSH, s_szDir2InDir1, NULL);
383 ::Sleep(INTERVAL);
384 DoStepCheck(s_iStage, s_iStep, StringFromChecks());
385
386 // 8: Finish
387 ++s_iStep;
388 assert(s_iStep == NUM_STEP);
389 C_ASSERT(NUM_STEP == 8);
390 if (s_iStage + 1 < NUM_STAGE)
391 {
392 ::PostMessage(s_hSubWnd, WM_COMMAND, IDRETRY, 0); // Next stage
393 }
394 else
395 {
396 // Finish
397 ::PostMessage(s_hSubWnd, WM_COMMAND, IDNO, 0);
398 ::PostMessage(s_hMainWnd, WM_COMMAND, IDNO, 0);
399 }
400
401 s_iStep = -1;
402
403 return 0;
404 }
405
406 // WM_COPYDATA
OnCopyData(HWND hwnd,HWND hwndSender,COPYDATASTRUCT * pCopyData)407 static BOOL OnCopyData(HWND hwnd, HWND hwndSender, COPYDATASTRUCT *pCopyData)
408 {
409 if (pCopyData->dwData != 0xBEEFCAFE)
410 return FALSE;
411
412 LPBYTE pbData = (LPBYTE)pCopyData->lpData;
413 LPBYTE pb = pbData;
414
415 LONG cbTotal = pCopyData->cbData;
416 assert(cbTotal >= LONG(sizeof(LONG) + sizeof(DWORD) + sizeof(DWORD)));
417
418 LONG lEvent = *(LONG*)pb;
419 pb += sizeof(lEvent);
420
421 DWORD cbPidl1 = *(DWORD*)pb;
422 pb += sizeof(cbPidl1);
423
424 DWORD cbPidl2 = *(DWORD*)pb;
425 pb += sizeof(cbPidl2);
426
427 LPITEMIDLIST pidl1 = NULL;
428 if (cbPidl1)
429 {
430 pidl1 = (LPITEMIDLIST)CoTaskMemAlloc(cbPidl1);
431 CopyMemory(pidl1, pb, cbPidl1);
432 pb += cbPidl1;
433 }
434
435 LPITEMIDLIST pidl2 = NULL;
436 if (cbPidl2)
437 {
438 pidl2 = (LPITEMIDLIST)CoTaskMemAlloc(cbPidl2);
439 CopyMemory(pidl2, pb, cbPidl2);
440 pb += cbPidl2;
441 }
442
443 assert((pb - pbData) == cbTotal);
444
445 DoTestEntry(lEvent, pidl1, pidl2);
446
447 CoTaskMemFree(pidl1);
448 CoTaskMemFree(pidl2);
449
450 return TRUE;
451 }
452
453 static LRESULT CALLBACK
MainWndProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)454 MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
455 {
456 switch (uMsg)
457 {
458 case WM_CREATE:
459 s_hMainWnd = hwnd;
460 return 0;
461
462 case WM_COMMAND:
463 switch (LOWORD(wParam))
464 {
465 case IDYES: // Start testing
466 {
467 s_iStage = 0;
468 s_bGotUpdateDir = FALSE;
469 s_hThread = ::CreateThread(NULL, 0, StageThreadFunc, hwnd, 0, NULL);
470 if (!s_hThread)
471 {
472 skip("!s_hThread\n");
473 DestroyWindow(hwnd);
474 }
475 break;
476 }
477 case IDRETRY: // New stage
478 {
479 ::CloseHandle(s_hThread);
480 ++s_iStage;
481 s_bGotUpdateDir = FALSE;
482 s_hThread = ::CreateThread(NULL, 0, StageThreadFunc, hwnd, 0, NULL);
483 if (!s_hThread)
484 {
485 skip("!s_hThread\n");
486 DestroyWindow(hwnd);
487 }
488 break;
489 }
490 case IDNO: // Quit
491 {
492 s_iStage = -1;
493 DestroyWindow(hwnd);
494 break;
495 }
496 }
497 break;
498
499 case WM_COPYDATA:
500 if (s_iStage < 0 || s_iStep < 0)
501 break;
502
503 OnCopyData(hwnd, (HWND)wParam, (COPYDATASTRUCT*)lParam);
504 break;
505
506 case WM_DESTROY:
507 ::PostQuitMessage(0);
508 break;
509
510 default:
511 return ::DefWindowProcW(hwnd, uMsg, wParam, lParam);
512 }
513 return 0;
514 }
515
TEST_Init(void)516 static BOOL TEST_Init(void)
517 {
518 if (!FindSubProgram(s_szSubProgram, _countof(s_szSubProgram)))
519 {
520 skip("shell32_apitest_sub.exe not found\n");
521 return FALSE;
522 }
523
524 // close the SUB_CLASSNAME windows
525 DoWaitForWindow(SUB_CLASSNAME, SUB_CLASSNAME, TRUE, TRUE);
526
527 // Execute sub program
528 HINSTANCE hinst = ShellExecuteW(NULL, NULL, s_szSubProgram, L"---", NULL, SW_SHOWNORMAL);
529 if ((INT_PTR)hinst <= 32)
530 {
531 skip("Unable to run shell32_apitest_sub.exe.\n");
532 return FALSE;
533 }
534
535 // prepare for files and dirs
536 DoBuildFilesAndDirs();
537
538 // Register main window
539 WNDCLASSW wc = { 0, MainWndProc };
540 wc.hInstance = GetModuleHandleW(NULL);
541 wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
542 wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
543 wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
544 wc.lpszClassName = MAIN_CLASSNAME;
545 if (!RegisterClassW(&wc))
546 {
547 skip("RegisterClassW failed\n");
548 return FALSE;
549 }
550
551 // Create main window
552 HWND hwnd = CreateWindowW(MAIN_CLASSNAME, MAIN_CLASSNAME, WS_OVERLAPPEDWINDOW,
553 CW_USEDEFAULT, CW_USEDEFAULT, 400, 100,
554 NULL, NULL, GetModuleHandleW(NULL), NULL);
555 if (!hwnd)
556 {
557 skip("CreateWindowW failed\n");
558 return FALSE;
559 }
560 ::ShowWindow(hwnd, SW_SHOWNORMAL);
561 ::UpdateWindow(hwnd);
562
563 // Find sub-window
564 s_hSubWnd = DoWaitForWindow(SUB_CLASSNAME, SUB_CLASSNAME, FALSE, FALSE);
565 if (!s_hSubWnd)
566 {
567 skip("Unable to find sub-program window.\n");
568 return FALSE;
569 }
570
571 // Start testing
572 SendMessageW(s_hSubWnd, WM_COMMAND, IDYES, 0);
573
574 return TRUE;
575 }
576
TEST_Main(void)577 static void TEST_Main(void)
578 {
579 if (!TEST_Init())
580 {
581 skip("Unable to start testing.\n");
582 TEST_Quit();
583 return;
584 }
585
586 // Message loop
587 MSG msg;
588 while (GetMessageW(&msg, NULL, 0, 0))
589 {
590 ::TranslateMessage(&msg);
591 ::DispatchMessage(&msg);
592 }
593
594 TEST_Quit();
595 }
596
START_TEST(SHChangeNotify)597 START_TEST(SHChangeNotify)
598 {
599 trace("Please close all Explorer windows before testing.\n");
600 trace("Please don't operate your PC while testing.\n");
601
602 DWORD dwOldTick = GetTickCount();
603 TEST_Main();
604 DWORD dwNewTick = GetTickCount();
605
606 DWORD dwTick = dwNewTick - dwOldTick;
607 trace("SHChangeNotify: Total %lu.%lu sec\n", (dwTick / 1000), (dwTick / 100 % 10));
608 }
609