Reactos
at master 604 lines 18 kB view raw
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 19static HWND s_hMainWnd = NULL, s_hSubWnd = NULL; 20static WCHAR s_szSubProgram[MAX_PATH]; // shell32_apitest_sub.exe 21static HANDLE s_hThread = NULL; 22static INT s_iStage = -1, s_iStep = -1; 23static BYTE s_abChecks[NUM_CHECKS] = { 0 }; // Flags for testing 24static BOOL s_bGotUpdateDir = FALSE; // Got SHCNE_UPDATEDIR? 25 26static 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 34static 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 43static 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 63static WCHAR s_szDir1[MAX_PATH]; 64static WCHAR s_szDir1InDir1[MAX_PATH]; 65static WCHAR s_szDir2InDir1[MAX_PATH]; 66static WCHAR s_szFile1InDir1InDir1[MAX_PATH]; 67static WCHAR s_szFile1InDir1[MAX_PATH]; 68static WCHAR s_szFile2InDir1[MAX_PATH]; 69 70static 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 80static 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 91static 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 137static 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 176static 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 195struct TEST_ANSWER 196{ 197 INT lineno; 198 LPCSTR answer; 199}; 200 201static 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 = 0; 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_UNREACHABLE; 289 } 290 291 ok(lstrcmpA(checks, answer) == 0, 292 "Line %d: '%s' vs '%s' at Stage %d, Step %d\n", lineno, checks, answer, iStage, iStep); 293} 294 295static DWORD WINAPI StageThreadFunc(LPVOID arg) 296{ 297 BOOL ret; 298 299 trace("Stage %d\n", s_iStage); 300 301 // 0: Create file1 in dir1 302 s_iStep = 0; 303 trace("Step %d\n", s_iStep); 304 SHChangeNotify(0, SHCNF_PATHW | SHCNF_FLUSH, NULL, NULL); 305 ZeroMemory(s_abChecks, sizeof(s_abChecks)); 306 ret = DoCreateFile(s_szFile1InDir1); 307 ok_int(ret, TRUE); 308 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW | SHCNF_FLUSH, s_szFile1InDir1, 0); 309 ::Sleep(INTERVAL); 310 DoStepCheck(s_iStage, s_iStep, StringFromChecks()); 311 312 // 1: Rename file1 as file2 in dir1 313 ++s_iStep; 314 trace("Step %d\n", s_iStep); 315 ZeroMemory(s_abChecks, sizeof(s_abChecks)); 316 ret = MoveFileW(s_szFile1InDir1, s_szFile2InDir1); 317 ok_int(ret, TRUE); 318 SHChangeNotify(SHCNE_RENAMEITEM, SHCNF_PATHW | SHCNF_FLUSH, s_szFile1InDir1, s_szFile2InDir1); 319 ::Sleep(INTERVAL); 320 DoStepCheck(s_iStage, s_iStep, StringFromChecks()); 321 322 // 2: Delete file2 in dir1 323 ++s_iStep; 324 trace("Step %d\n", s_iStep); 325 ZeroMemory(s_abChecks, sizeof(s_abChecks)); 326 ret = DeleteFileW(s_szFile2InDir1); 327 ok_int(ret, TRUE); 328 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW | SHCNF_FLUSH, s_szFile2InDir1, NULL); 329 ::Sleep(INTERVAL); 330 DoStepCheck(s_iStage, s_iStep, StringFromChecks()); 331 332 // 3: Create dir1 in dir1 333 ++s_iStep; 334 trace("Step %d\n", s_iStep); 335 ZeroMemory(s_abChecks, sizeof(s_abChecks)); 336 ret = CreateDirectoryExW(s_szDir1, s_szDir1InDir1, NULL); 337 ok_int(ret, TRUE); 338 SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW | SHCNF_FLUSH, s_szDir1InDir1, NULL); 339 ::Sleep(INTERVAL); 340 DoStepCheck(s_iStage, s_iStep, StringFromChecks()); 341 342 // 4: Create file1 in dir1 in dir1 343 ++s_iStep; 344 trace("Step %d\n", s_iStep); 345 ZeroMemory(s_abChecks, sizeof(s_abChecks)); 346 ret = DoCreateFile(s_szFile1InDir1InDir1); 347 ok_int(ret, TRUE); 348 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW | SHCNF_FLUSH, s_szFile1InDir1InDir1, NULL); 349 ::Sleep(INTERVAL); 350 DoStepCheck(s_iStage, s_iStep, StringFromChecks()); 351 352 // 5: Delete file1 in dir1 in dir1 353 ++s_iStep; 354 trace("Step %d\n", s_iStep); 355 ZeroMemory(s_abChecks, sizeof(s_abChecks)); 356 ret = DeleteFileW(s_szFile1InDir1InDir1); 357 ok_int(ret, TRUE); 358 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW | SHCNF_FLUSH, s_szFile1InDir1InDir1, NULL); 359 ::Sleep(INTERVAL); 360 DoStepCheck(s_iStage, s_iStep, StringFromChecks()); 361 362 // 6: Rename dir1 as dir2 in dir1 363 ++s_iStep; 364 trace("Step %d\n", s_iStep); 365 ZeroMemory(s_abChecks, sizeof(s_abChecks)); 366 ret = ::MoveFileW(s_szDir1InDir1, s_szDir2InDir1); 367 ok_int(ret, TRUE); 368 SHChangeNotify(SHCNE_RENAMEFOLDER, SHCNF_PATHW | SHCNF_FLUSH, s_szDir1InDir1, s_szDir2InDir1); 369 ::Sleep(INTERVAL); 370 DoStepCheck(s_iStage, s_iStep, StringFromChecks()); 371 372 // 7: Remove dir2 in dir1 373 ++s_iStep; 374 trace("Step %d\n", s_iStep); 375 ZeroMemory(s_abChecks, sizeof(s_abChecks)); 376 ret = RemoveDirectoryW(s_szDir2InDir1); 377 ok_int(ret, TRUE); 378 SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW | SHCNF_FLUSH, s_szDir2InDir1, NULL); 379 ::Sleep(INTERVAL); 380 DoStepCheck(s_iStage, s_iStep, StringFromChecks()); 381 382 // 8: Finish 383 ++s_iStep; 384 assert(s_iStep == NUM_STEP); 385 C_ASSERT(NUM_STEP == 8); 386 if (s_iStage + 1 < NUM_STAGE) 387 { 388 ::PostMessage(s_hSubWnd, WM_COMMAND, IDRETRY, 0); // Next stage 389 } 390 else 391 { 392 // Finish 393 ::PostMessage(s_hSubWnd, WM_COMMAND, IDNO, 0); 394 ::PostMessage(s_hMainWnd, WM_COMMAND, IDNO, 0); 395 } 396 397 s_iStep = -1; 398 399 return 0; 400} 401 402// WM_COPYDATA 403static BOOL OnCopyData(HWND hwnd, HWND hwndSender, COPYDATASTRUCT *pCopyData) 404{ 405 if (pCopyData->dwData != 0xBEEFCAFE) 406 return FALSE; 407 408 LPBYTE pbData = (LPBYTE)pCopyData->lpData; 409 LPBYTE pb = pbData; 410 411 LONG cbTotal = pCopyData->cbData; 412 assert(cbTotal >= LONG(sizeof(LONG) + sizeof(DWORD) + sizeof(DWORD))); 413 414 LONG lEvent = *(LONG*)pb; 415 pb += sizeof(lEvent); 416 417 DWORD cbPidl1 = *(DWORD*)pb; 418 pb += sizeof(cbPidl1); 419 420 DWORD cbPidl2 = *(DWORD*)pb; 421 pb += sizeof(cbPidl2); 422 423 LPITEMIDLIST pidl1 = NULL; 424 if (cbPidl1) 425 { 426 pidl1 = (LPITEMIDLIST)CoTaskMemAlloc(cbPidl1); 427 CopyMemory(pidl1, pb, cbPidl1); 428 pb += cbPidl1; 429 } 430 431 LPITEMIDLIST pidl2 = NULL; 432 if (cbPidl2) 433 { 434 pidl2 = (LPITEMIDLIST)CoTaskMemAlloc(cbPidl2); 435 CopyMemory(pidl2, pb, cbPidl2); 436 pb += cbPidl2; 437 } 438 439 assert((pb - pbData) == cbTotal); 440 441 DoTestEntry(lEvent, pidl1, pidl2); 442 443 CoTaskMemFree(pidl1); 444 CoTaskMemFree(pidl2); 445 446 return TRUE; 447} 448 449static LRESULT CALLBACK 450MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 451{ 452 switch (uMsg) 453 { 454 case WM_CREATE: 455 s_hMainWnd = hwnd; 456 return 0; 457 458 case WM_COMMAND: 459 switch (LOWORD(wParam)) 460 { 461 case IDYES: // Start testing 462 { 463 s_iStage = 0; 464 s_bGotUpdateDir = FALSE; 465 s_hThread = ::CreateThread(NULL, 0, StageThreadFunc, hwnd, 0, NULL); 466 if (!s_hThread) 467 { 468 skip("!s_hThread\n"); 469 DestroyWindow(hwnd); 470 } 471 break; 472 } 473 case IDRETRY: // New stage 474 { 475 ::CloseHandle(s_hThread); 476 ++s_iStage; 477 s_bGotUpdateDir = FALSE; 478 s_hThread = ::CreateThread(NULL, 0, StageThreadFunc, hwnd, 0, NULL); 479 if (!s_hThread) 480 { 481 skip("!s_hThread\n"); 482 DestroyWindow(hwnd); 483 } 484 break; 485 } 486 case IDNO: // Quit 487 { 488 s_iStage = -1; 489 DestroyWindow(hwnd); 490 break; 491 } 492 } 493 break; 494 495 case WM_COPYDATA: 496 if (s_iStage < 0 || s_iStep < 0) 497 break; 498 499 OnCopyData(hwnd, (HWND)wParam, (COPYDATASTRUCT*)lParam); 500 break; 501 502 case WM_DESTROY: 503 ::PostQuitMessage(0); 504 break; 505 506 default: 507 return ::DefWindowProcW(hwnd, uMsg, wParam, lParam); 508 } 509 return 0; 510} 511 512static BOOL TEST_Init(void) 513{ 514 if (!FindSubProgram(s_szSubProgram, _countof(s_szSubProgram))) 515 { 516 skip("shell32_apitest_sub.exe not found\n"); 517 return FALSE; 518 } 519 520 // close the SUB_CLASSNAME windows 521 DoWaitForWindow(SUB_CLASSNAME, SUB_CLASSNAME, TRUE, TRUE); 522 523 // Execute sub program 524 HINSTANCE hinst = ShellExecuteW(NULL, NULL, s_szSubProgram, L"---", NULL, SW_SHOWNORMAL); 525 if ((INT_PTR)hinst <= 32) 526 { 527 skip("Unable to run shell32_apitest_sub.exe.\n"); 528 return FALSE; 529 } 530 531 // prepare for files and dirs 532 DoBuildFilesAndDirs(); 533 534 // Register main window 535 WNDCLASSW wc = { 0, MainWndProc }; 536 wc.hInstance = GetModuleHandleW(NULL); 537 wc.hIcon = LoadIconW(NULL, IDI_APPLICATION); 538 wc.hCursor = LoadCursorW(NULL, IDC_ARROW); 539 wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); 540 wc.lpszClassName = MAIN_CLASSNAME; 541 if (!RegisterClassW(&wc)) 542 { 543 skip("RegisterClassW failed\n"); 544 return FALSE; 545 } 546 547 // Create main window 548 HWND hwnd = CreateWindowW(MAIN_CLASSNAME, MAIN_CLASSNAME, WS_OVERLAPPEDWINDOW, 549 CW_USEDEFAULT, CW_USEDEFAULT, 400, 100, 550 NULL, NULL, GetModuleHandleW(NULL), NULL); 551 if (!hwnd) 552 { 553 skip("CreateWindowW failed\n"); 554 return FALSE; 555 } 556 ::ShowWindow(hwnd, SW_SHOWNORMAL); 557 ::UpdateWindow(hwnd); 558 559 // Find sub-window 560 s_hSubWnd = DoWaitForWindow(SUB_CLASSNAME, SUB_CLASSNAME, FALSE, FALSE); 561 if (!s_hSubWnd) 562 { 563 skip("Unable to find sub-program window.\n"); 564 return FALSE; 565 } 566 567 // Start testing 568 SendMessageW(s_hSubWnd, WM_COMMAND, IDYES, 0); 569 570 return TRUE; 571} 572 573static void TEST_Main(void) 574{ 575 if (!TEST_Init()) 576 { 577 skip("Unable to start testing.\n"); 578 TEST_Quit(); 579 return; 580 } 581 582 // Message loop 583 MSG msg; 584 while (GetMessageW(&msg, NULL, 0, 0)) 585 { 586 ::TranslateMessage(&msg); 587 ::DispatchMessage(&msg); 588 } 589 590 TEST_Quit(); 591} 592 593START_TEST(SHChangeNotify) 594{ 595 trace("Please close all Explorer windows before testing.\n"); 596 trace("Please don't operate your PC while testing.\n"); 597 598 DWORD dwOldTick = GetTickCount(); 599 TEST_Main(); 600 DWORD dwNewTick = GetTickCount(); 601 602 DWORD dwTick = dwNewTick - dwOldTick; 603 trace("SHChangeNotify: Total %lu.%lu sec\n", (dwTick / 1000), (dwTick / 100 % 10)); 604}