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