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 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) 6 */ 7 8 // NOTE: This test program closes the Explorer cabinets before tests. 9 10 #include "shelltest.h" 11 #include <shlwapi.h> 12 #include <stdio.h> 13 #include "SHChangeNotify.h" 14 15 #define DONT_SEND 0x24242424 16 17 static HWND s_hwnd = NULL; 18 static const WCHAR s_szName[] = L"SHChangeNotify testcase"; 19 static WCHAR s_szSubProgram[MAX_PATH]; 20 21 typedef void (*ACTION)(void); 22 23 typedef struct TEST_ENTRY 24 { 25 INT line; 26 DWORD event; 27 LPCVOID item1; 28 LPCVOID item2; 29 LPCSTR pattern; 30 ACTION action; 31 LPCWSTR path1; 32 LPCWSTR path2; 33 } TEST_ENTRY; 34 35 static BOOL 36 DoCreateEmptyFile(LPCWSTR pszFileName) 37 { 38 FILE *fp = _wfopen(pszFileName, L"wb"); 39 fclose(fp); 40 return fp != NULL; 41 } 42 43 static void 44 DoAction1(void) 45 { 46 ok_int(CreateDirectoryW(s_dir2, NULL), TRUE); 47 } 48 49 static void 50 DoAction2(void) 51 { 52 ok_int(RemoveDirectoryW(s_dir2), TRUE); 53 } 54 55 static void 56 DoAction3(void) 57 { 58 ok_int(MoveFileExW(s_dir2, s_dir3, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING), TRUE); 59 } 60 61 static void 62 DoAction4(void) 63 { 64 ok_int(DoCreateEmptyFile(s_file1), TRUE); 65 } 66 67 static void 68 DoAction5(void) 69 { 70 ok_int(MoveFileExW(s_file1, s_file2, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING), TRUE); 71 } 72 73 static void 74 DoAction6(void) 75 { 76 ok_int(DeleteFileW(s_file2), TRUE); 77 } 78 79 static void 80 DoAction7(void) 81 { 82 DeleteFileW(s_file1); 83 DeleteFileW(s_file2); 84 ok_int(RemoveDirectoryW(s_dir3), TRUE); 85 } 86 87 static void 88 DoAction8(void) 89 { 90 BOOL ret = RemoveDirectoryW(s_dir1); 91 ok(ret, "RemoveDirectoryW failed. GetLastError() == %ld\n", GetLastError()); 92 } 93 94 static const TEST_ENTRY s_TestEntriesMode0[] = 95 { 96 {__LINE__, SHCNE_MKDIR, s_dir2, NULL, NULL, DoAction1, NULL, NULL}, 97 {__LINE__, SHCNE_RMDIR, s_dir2, NULL, "00001000", NULL, s_dir2, L""}, 98 {__LINE__, SHCNE_RMDIR, s_dir2, NULL, "00001000", DoAction2, s_dir2, L""}, 99 {__LINE__, SHCNE_MKDIR, s_dir2, NULL, "00010000", DoAction1, s_dir2, L""}, 100 {__LINE__, SHCNE_RENAMEFOLDER, s_dir2, s_dir3, "00000001", NULL, s_dir2, s_dir3}, 101 {__LINE__, SHCNE_RENAMEFOLDER, s_dir2, s_dir3, "00000001", DoAction3, s_dir2, s_dir3}, 102 {__LINE__, SHCNE_CREATE, s_file1, NULL, "01000000", NULL, s_file1, L""}, 103 {__LINE__, SHCNE_CREATE, s_file1, s_file2, "01000000", NULL, s_file1, s_file2}, 104 {__LINE__, SHCNE_CREATE, s_file1, NULL, "01000000", DoAction4, s_file1, L""}, 105 {__LINE__, SHCNE_RENAMEITEM, s_file1, s_file2, "10000000", NULL, s_file1, s_file2}, 106 {__LINE__, SHCNE_RENAMEITEM, s_file1, s_file2, "10000000", DoAction5, s_file1, s_file2}, 107 {__LINE__, SHCNE_RENAMEITEM, s_file1, s_file2, "10000000", NULL, s_file1, s_file2}, 108 {__LINE__, SHCNE_UPDATEITEM, s_file1, NULL, "00000010", NULL, s_file1, L""}, 109 {__LINE__, SHCNE_UPDATEITEM, s_file2, NULL, "00000010", NULL, s_file2, L""}, 110 {__LINE__, SHCNE_DELETE, s_file1, NULL, "00100000", NULL, s_file1, L""}, 111 {__LINE__, SHCNE_DELETE, s_file2, NULL, "00100000", NULL, s_file2, L""}, 112 {__LINE__, SHCNE_DELETE, s_file2, NULL, "00100000", DoAction6, s_file2, L""}, 113 {__LINE__, SHCNE_DELETE, s_file2, NULL, "00100000", NULL, s_file2, L""}, 114 {__LINE__, SHCNE_DELETE, s_file1, NULL, "00100000", NULL, s_file1, L""}, 115 {__LINE__, SHCNE_RMDIR, s_dir2, NULL, "00001000", NULL, s_dir2, L""}, 116 {__LINE__, SHCNE_RMDIR, s_dir3, NULL, "00001000", DoAction7, s_dir3, L""}, 117 {__LINE__, SHCNE_RMDIR, s_dir1, NULL, "00001000", NULL, s_dir1, L""}, 118 {__LINE__, SHCNE_RMDIR, s_dir1, NULL, "00001000", DoAction8, s_dir1, L""}, 119 }; 120 121 #define s_TestEntriesMode1 s_TestEntriesMode0 122 #define s_TestEntriesMode2 s_TestEntriesMode0 123 124 static const TEST_ENTRY s_TestEntriesMode3[] = 125 { 126 {__LINE__, DONT_SEND, s_dir2, NULL, NULL, DoAction1, NULL, NULL}, 127 {__LINE__, DONT_SEND, s_dir2, NULL, "00001000", DoAction2, s_dir2, L""}, 128 {__LINE__, DONT_SEND, s_dir2, NULL, "00010000", DoAction1, s_dir2, L""}, 129 {__LINE__, DONT_SEND, s_dir2, s_dir3, "00000001", DoAction3, s_dir2, s_dir3}, 130 {__LINE__, DONT_SEND, s_file1, NULL, "01000000", DoAction4, s_file1, L""}, 131 {__LINE__, DONT_SEND, s_file1, s_file2, "10000000", DoAction5, s_file1, s_file2}, 132 {__LINE__, DONT_SEND, s_file2, NULL, "00100000", DoAction6, s_file2, L""}, 133 {__LINE__, DONT_SEND, s_dir3, NULL, "00001000", DoAction7, s_dir3, L""}, 134 {__LINE__, SHCNE_MKDIR, s_dir2, NULL, "00000000", NULL, NULL, NULL}, 135 {__LINE__, SHCNE_INTERRUPT | SHCNE_MKDIR, s_dir2, NULL, "00000000", NULL, NULL, NULL}, 136 }; 137 138 static const TEST_ENTRY s_TestEntriesMode4[] = 139 { 140 {__LINE__, DONT_SEND, s_dir2, NULL, NULL, DoAction1, NULL, NULL}, 141 {__LINE__, DONT_SEND, s_dir2, NULL, "00001000", DoAction2, s_dir2, L""}, 142 {__LINE__, DONT_SEND, s_dir2, NULL, "00010000", DoAction1, s_dir2, L""}, 143 {__LINE__, DONT_SEND, s_dir2, s_dir3, "00000001", DoAction3, s_dir2, s_dir3}, 144 {__LINE__, DONT_SEND, s_file1, NULL, "01000000", DoAction4, s_file1, L""}, 145 {__LINE__, DONT_SEND, s_file1, s_file2, "10000000", DoAction5, s_file1, s_file2}, 146 {__LINE__, DONT_SEND, s_file2, NULL, "00100000", DoAction6, s_file2, L""}, 147 {__LINE__, DONT_SEND, s_dir3, NULL, "00001000", DoAction7, s_dir3, L""}, 148 {__LINE__, SHCNE_MKDIR, s_dir2, NULL, "00000000", NULL, NULL, NULL}, 149 {__LINE__, SHCNE_INTERRUPT | SHCNE_MKDIR, s_dir2, NULL, "00000000", NULL, NULL, NULL}, 150 }; 151 152 static const TEST_ENTRY s_TestEntriesMode5[] = 153 { 154 {__LINE__, SHCNE_MKDIR, s_dir2, NULL, "00000000", NULL, NULL, NULL}, 155 {__LINE__, SHCNE_RMDIR, s_dir2, NULL, "00000000", NULL, NULL, NULL}, 156 {__LINE__, SHCNE_MKDIR, s_dir2, NULL, "00000000", DoAction1, NULL, NULL}, 157 {__LINE__, SHCNE_RMDIR, s_dir2, NULL, "00000000", NULL, NULL, NULL}, 158 {__LINE__, SHCNE_RMDIR, s_dir2, NULL, "00000000", DoAction2, NULL, NULL}, 159 {__LINE__, SHCNE_MKDIR, s_dir2, NULL, "00000000", DoAction1, NULL, NULL}, 160 {__LINE__, SHCNE_RENAMEFOLDER, s_dir2, s_dir3, "00000000", NULL, NULL, NULL}, 161 {__LINE__, SHCNE_RENAMEFOLDER, s_dir2, s_dir3, "00000000", DoAction3, NULL, NULL}, 162 {__LINE__, SHCNE_CREATE, s_file1, NULL, "00000000", NULL, NULL, NULL}, 163 {__LINE__, SHCNE_CREATE, s_file1, s_file2, "00000000", NULL, NULL, NULL}, 164 {__LINE__, SHCNE_CREATE, s_file1, NULL, "00000000", DoAction4, NULL, NULL}, 165 {__LINE__, SHCNE_RENAMEITEM, s_file1, s_file2, "00000000", NULL, NULL, NULL}, 166 {__LINE__, SHCNE_RENAMEITEM, s_file1, s_file2, "00000000", DoAction5, NULL, NULL}, 167 {__LINE__, SHCNE_RENAMEITEM, s_file1, s_file2, "00000000", NULL, NULL, NULL}, 168 {__LINE__, SHCNE_UPDATEITEM, s_file1, NULL, "00000000", NULL, NULL, NULL}, 169 {__LINE__, SHCNE_UPDATEITEM, s_file2, NULL, "00000000", NULL, NULL, NULL}, 170 {__LINE__, SHCNE_UPDATEITEM, s_file1, s_file2, "00000000", NULL, NULL, NULL}, 171 {__LINE__, SHCNE_UPDATEITEM, s_file2, s_file1, "00000000", NULL, NULL, NULL}, 172 {__LINE__, SHCNE_DELETE, s_file1, NULL, "00000000", NULL, NULL, NULL}, 173 {__LINE__, SHCNE_DELETE, s_file2, NULL, "00000000", NULL, NULL, NULL}, 174 {__LINE__, SHCNE_DELETE, s_file2, s_file1, "00000000", NULL, NULL, NULL}, 175 {__LINE__, SHCNE_DELETE, s_file1, s_file2, "00000000", NULL, NULL, NULL}, 176 {__LINE__, SHCNE_DELETE, s_file2, NULL, "00000000", DoAction6, NULL, NULL}, 177 {__LINE__, SHCNE_DELETE, s_file2, NULL, "00000000", NULL, NULL, NULL}, 178 {__LINE__, SHCNE_DELETE, s_file1, NULL, "00000000", NULL, NULL, NULL}, 179 {__LINE__, SHCNE_INTERRUPT | SHCNE_RMDIR, s_dir1, NULL, "00000000", NULL, NULL, NULL}, 180 {__LINE__, SHCNE_INTERRUPT | SHCNE_RMDIR, s_dir2, NULL, "00000000", NULL, NULL, NULL}, 181 {__LINE__, SHCNE_INTERRUPT | SHCNE_RMDIR, s_dir3, NULL, "00000000", NULL, NULL, NULL}, 182 {__LINE__, SHCNE_INTERRUPT | SHCNE_RMDIR, s_dir1, s_dir2, "00000000", NULL, NULL, NULL}, 183 {__LINE__, SHCNE_INTERRUPT | SHCNE_RMDIR, s_dir1, s_dir3, "00000000", NULL, NULL, NULL}, 184 {__LINE__, SHCNE_INTERRUPT | SHCNE_RMDIR, s_dir2, s_dir1, "00000000", NULL, NULL, NULL}, 185 {__LINE__, SHCNE_INTERRUPT | SHCNE_RMDIR, s_dir2, s_dir3, "00000000", NULL, NULL, NULL}, 186 {__LINE__, SHCNE_INTERRUPT | SHCNE_RMDIR, s_dir3, NULL, "00000000", NULL, NULL, NULL}, 187 {__LINE__, SHCNE_INTERRUPT | SHCNE_RMDIR, s_dir3, NULL, "00000000", DoAction7, NULL, NULL}, 188 {__LINE__, SHCNE_INTERRUPT | SHCNE_RMDIR, s_dir1, NULL, "00000000", NULL, NULL, NULL}, 189 {__LINE__, SHCNE_INTERRUPT | SHCNE_RMDIR, s_dir1, NULL, "00000000", DoAction8, NULL, NULL}, 190 }; 191 192 LPCSTR PatternFromFlags(DWORD flags) 193 { 194 static char s_buf[TYPE_RENAMEFOLDER + 1 + 1]; 195 DWORD i; 196 for (i = 0; i <= TYPE_RENAMEFOLDER; ++i) 197 { 198 s_buf[i] = (char)('0' + !!(flags & (1 << i))); 199 } 200 s_buf[i] = 0; 201 return s_buf; 202 } 203 204 static BOOL 205 DoGetPaths(LPWSTR pszPath1, LPWSTR pszPath2) 206 { 207 pszPath1[0] = pszPath2[0] = 0; 208 209 WCHAR szText[MAX_PATH * 2]; 210 szText[0] = 0; 211 if (FILE *fp = fopen(TEMP_FILE, "rb")) 212 { 213 fread(szText, 1, sizeof(szText), fp); 214 fclose(fp); 215 } 216 217 LPWSTR pch = wcschr(szText, L'|'); 218 if (pch == NULL) 219 return FALSE; 220 221 *pch = 0; 222 lstrcpynW(pszPath1, szText, MAX_PATH); 223 lstrcpynW(pszPath2, pch + 1, MAX_PATH); 224 return TRUE; 225 } 226 227 static void 228 DoTestEntry(const TEST_ENTRY *entry) 229 { 230 if (entry->action) 231 { 232 (*entry->action)(); 233 } 234 235 if (entry->event != DONT_SEND) 236 { 237 SHChangeNotify(entry->event, SHCNF_PATHW | SHCNF_FLUSH, entry->item1, entry->item2); 238 } 239 else 240 { 241 SHChangeNotify(0, SHCNF_FLUSH, NULL, NULL); 242 } 243 244 DWORD flags = SendMessageW(s_hwnd, WM_GET_NOTIFY_FLAGS, 0, 0); 245 LPCSTR pattern = PatternFromFlags(flags); 246 247 if (entry->pattern) 248 { 249 ok(lstrcmpA(pattern, entry->pattern) == 0 || 250 lstrcmpA(pattern, "00000100") == 0, // SHCNE_UPDATEDIR 251 "Line %d: pattern mismatch '%s'\n", entry->line, pattern); 252 } 253 254 SendMessageW(s_hwnd, WM_SET_PATHS, 0, 0); 255 Sleep(50); 256 257 WCHAR szPath1[MAX_PATH], szPath2[MAX_PATH]; 258 szPath1[0] = szPath2[0] = 0; 259 BOOL bOK = DoGetPaths(szPath1, szPath2); 260 261 if (lstrcmpA(pattern, "00000100") == 0) // SHCNE_UPDATEDIR 262 { 263 if (entry->path1) 264 ok(bOK && lstrcmpiW(s_dir1, szPath1) == 0, 265 "Line %d: path1 mismatch '%S' (%d)\n", entry->line, szPath1, bOK); 266 if (entry->path2) 267 ok(bOK && lstrcmpiW(L"", szPath2) == 0, 268 "Line %d: path2 mismatch '%S' (%d)\n", entry->line, szPath2, bOK); 269 } 270 else 271 { 272 if (entry->path1) 273 ok(bOK && lstrcmpiW(entry->path1, szPath1) == 0, 274 "Line %d: path1 mismatch '%S' (%d)\n", entry->line, szPath1, bOK); 275 if (entry->path2) 276 ok(bOK && lstrcmpiW(entry->path2, szPath2) == 0, 277 "Line %d: path2 mismatch '%S' (%d)\n", entry->line, szPath2, bOK); 278 } 279 280 SendMessageW(s_hwnd, WM_CLEAR_FLAGS, 0, 0); 281 } 282 283 static BOOL 284 DoInit(void) 285 { 286 DoInitPaths(); 287 288 CreateDirectoryW(s_dir1, NULL); 289 290 // close Explorer before tests 291 INT i, nCount = 50; 292 for (i = 0; i < nCount; ++i) 293 { 294 HWND hwnd = FindWindowW(L"CabinetWClass", NULL); 295 if (hwnd == NULL) 296 break; 297 298 PostMessage(hwnd, WM_CLOSE, 0, 0); 299 Sleep(100); 300 } 301 if (i == nCount) 302 skip("Unable to close Explorer cabinet\n"); 303 304 return PathIsDirectoryW(s_dir1); 305 } 306 307 static void 308 DoEnd(HWND hwnd) 309 { 310 DeleteFileW(s_file1); 311 DeleteFileW(s_file2); 312 RemoveDirectoryW(s_dir3); 313 RemoveDirectoryW(s_dir2); 314 RemoveDirectoryW(s_dir1); 315 DeleteFileA(TEMP_FILE); 316 317 SendMessageW(s_hwnd, WM_COMMAND, IDOK, 0); 318 } 319 320 static BOOL 321 GetSubProgramPath(void) 322 { 323 GetModuleFileNameW(NULL, s_szSubProgram, _countof(s_szSubProgram)); 324 PathRemoveFileSpecW(s_szSubProgram); 325 PathAppendW(s_szSubProgram, L"shell32_apitest_sub.exe"); 326 327 if (!PathFileExistsW(s_szSubProgram)) 328 { 329 PathRemoveFileSpecW(s_szSubProgram); 330 PathAppendW(s_szSubProgram, L"testdata\\shell32_apitest_sub.exe"); 331 332 if (!PathFileExistsW(s_szSubProgram)) 333 { 334 return FALSE; 335 } 336 } 337 338 return TRUE; 339 } 340 341 static void 342 JustDoIt(INT nMode) 343 { 344 trace("nMode: %d\n", nMode); 345 SHChangeNotify(0, SHCNF_FLUSH, NULL, NULL); 346 347 if (!DoInit()) 348 { 349 skip("Unable to initialize.\n"); 350 return; 351 } 352 353 WCHAR szParams[8]; 354 wsprintfW(szParams, L"%u", nMode); 355 356 HINSTANCE hinst = ShellExecuteW(NULL, NULL, s_szSubProgram, szParams, NULL, SW_SHOWNORMAL); 357 if ((INT_PTR)hinst <= 32) 358 { 359 skip("Unable to run shell32_apitest_sub.exe.\n"); 360 return; 361 } 362 363 for (int i = 0; i < 15; ++i) 364 { 365 s_hwnd = FindWindowW(s_szName, s_szName); 366 if (s_hwnd) 367 break; 368 369 Sleep(50); 370 } 371 372 if (!s_hwnd) 373 { 374 skip("Unable to find window.\n"); 375 return; 376 } 377 378 switch (nMode) 379 { 380 case 0: 381 case 1: 382 case 2: 383 for (size_t i = 0; i < _countof(s_TestEntriesMode0); ++i) 384 { 385 DoTestEntry(&s_TestEntriesMode0[i]); 386 } 387 break; 388 case 3: 389 for (size_t i = 0; i < _countof(s_TestEntriesMode3); ++i) 390 { 391 DoTestEntry(&s_TestEntriesMode3[i]); 392 } 393 break; 394 case 4: 395 for (size_t i = 0; i < _countof(s_TestEntriesMode4); ++i) 396 { 397 DoTestEntry(&s_TestEntriesMode4[i]); 398 } 399 break; 400 case 5: 401 for (size_t i = 0; i < _countof(s_TestEntriesMode5); ++i) 402 { 403 DoTestEntry(&s_TestEntriesMode5[i]); 404 } 405 break; 406 } 407 408 DoEnd(s_hwnd); 409 410 for (int i = 0; i < 15; ++i) 411 { 412 s_hwnd = FindWindowW(s_szName, s_szName); 413 if (!s_hwnd) 414 break; 415 416 Sleep(50); 417 } 418 } 419 420 START_TEST(SHChangeNotify) 421 { 422 if (!GetSubProgramPath()) 423 { 424 skip("shell32_apitest_sub.exe not found\n"); 425 } 426 427 JustDoIt(0); 428 JustDoIt(1); 429 JustDoIt(2); 430 JustDoIt(3); 431 JustDoIt(4); 432 JustDoIt(5); 433 } 434