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