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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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