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 #include "shelltest.h"
9 #include <shlwapi.h>
10 #include <stdio.h>
11 
12 #define WM_SHELL_NOTIFY (WM_USER + 100)
13 #define WM_GET_NOTIFY_FLAGS (WM_USER + 101)
14 #define WM_CLEAR_FLAGS (WM_USER + 102)
15 
16 static WCHAR s_dir1[MAX_PATH];  // "%TEMP%\\WatchDir1"
17 static WCHAR s_dir2[MAX_PATH];  // "%TEMP%\\WatchDir1\\Dir2"
18 static WCHAR s_dir3[MAX_PATH];  // "%TEMP%\\WatchDir1\\Dir3"
19 static WCHAR s_file1[MAX_PATH]; // "%TEMP%\\WatchDir1\\File1.txt"
20 static WCHAR s_file2[MAX_PATH]; // "%TEMP%\\WatchDir1\\File2.txt"
21 
22 static HWND s_hwnd = NULL;
23 static const WCHAR s_szName[] = L"SHChangeNotify testcase";
24 
25 typedef enum TYPE
26 {
27     TYPE_RENAMEITEM,
28     TYPE_CREATE,
29     TYPE_DELETE,
30     TYPE_MKDIR,
31     TYPE_RMDIR,
32     TYPE_UPDATEDIR,
33     TYPE_UPDATEITEM,
34     TYPE_RENAMEFOLDER,
35     TYPE_FREESPACE
36 } TYPE;
37 
38 typedef void (*ACTION)(void);
39 
40 typedef struct TEST_ENTRY
41 {
42     INT line;
43     LONG event;
44     LPCVOID item1;
45     LPCVOID item2;
46     LPCSTR pattern;
47     ACTION action;
48 } TEST_ENTRY;
49 
50 static BOOL
51 DoCreateEmptyFile(LPCWSTR pszFileName)
52 {
53     FILE *fp = _wfopen(pszFileName, L"wb");
54     fclose(fp);
55     return fp != NULL;
56 }
57 
58 static void
59 DoAction1(void)
60 {
61     ok_int(CreateDirectoryW(s_dir2, NULL), TRUE);
62 }
63 
64 static void
65 DoAction2(void)
66 {
67     ok_int(RemoveDirectoryW(s_dir2), TRUE);
68 }
69 
70 static void
71 DoAction3(void)
72 {
73     ok_int(MoveFileExW(s_dir2, s_dir3, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING), TRUE);
74 }
75 
76 static void
77 DoAction4(void)
78 {
79     ok_int(DoCreateEmptyFile(s_file1), TRUE);
80 }
81 
82 static void
83 DoAction5(void)
84 {
85     ok_int(MoveFileExW(s_file1, s_file2, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING), TRUE);
86 }
87 
88 static void
89 DoAction6(void)
90 {
91     ok_int(DeleteFileW(s_file2), TRUE);
92 }
93 
94 static void
95 DoAction7(void)
96 {
97     DeleteFileW(s_file1);
98     DeleteFileW(s_file2);
99     ok_int(RemoveDirectoryW(s_dir3), TRUE);
100 }
101 
102 static void
103 DoAction8(void)
104 {
105     ok_int(RemoveDirectoryW(s_dir1), TRUE);
106 }
107 
108 static const TEST_ENTRY s_TestEntries[] = {
109     {__LINE__, SHCNE_MKDIR, s_dir1, NULL, "000100000", NULL},
110     {__LINE__, SHCNE_MKDIR, s_dir2, NULL, "000100000", NULL},
111     {__LINE__, SHCNE_RMDIR, s_dir2, NULL, "000010000", NULL},
112     {__LINE__, SHCNE_MKDIR, s_dir2, NULL, "000100000", DoAction1},
113     {__LINE__, SHCNE_RMDIR, s_dir2, NULL, "000010000", NULL},
114     {__LINE__, SHCNE_RMDIR, s_dir2, NULL, "000010000", DoAction2},
115     {__LINE__, SHCNE_MKDIR, s_dir2, NULL, "000100000", DoAction1},
116     {__LINE__, SHCNE_RENAMEFOLDER, s_dir2, s_dir3, "000000010", NULL},
117     {__LINE__, SHCNE_RENAMEFOLDER, s_dir2, s_dir3, "000000010", DoAction3},
118     {__LINE__, SHCNE_CREATE, s_file1, NULL, "010000000", NULL},
119     {__LINE__, SHCNE_CREATE, s_file1, s_file2, "010000000", NULL},
120     {__LINE__, SHCNE_CREATE, s_file1, NULL, "010000000", DoAction4},
121     {__LINE__, SHCNE_RENAMEITEM, s_file1, s_file2, "100000000", NULL},
122     {__LINE__, SHCNE_RENAMEITEM, s_file1, s_file2, "100000000", DoAction5},
123     {__LINE__, SHCNE_RENAMEITEM, s_file1, s_file2, "100000000", NULL},
124     {__LINE__, SHCNE_UPDATEITEM, s_file1, NULL, "000000100", NULL},
125     {__LINE__, SHCNE_UPDATEITEM, s_file2, NULL, "000000100", NULL},
126     {__LINE__, SHCNE_UPDATEITEM, s_file1, s_file2, "000000100", NULL},
127     {__LINE__, SHCNE_UPDATEITEM, s_file2, s_file1, "000000100", NULL},
128     {__LINE__, SHCNE_DELETE, s_file1, NULL, "001000000", NULL},
129     {__LINE__, SHCNE_DELETE, s_file2, NULL, "001000000", NULL},
130     {__LINE__, SHCNE_DELETE, s_file2, s_file1, "001000000", NULL},
131     {__LINE__, SHCNE_DELETE, s_file1, s_file2, "001000000", NULL},
132     {__LINE__, SHCNE_DELETE, s_file2, NULL, "001000000", DoAction6},
133     {__LINE__, SHCNE_DELETE, s_file2, NULL, "001000000", NULL},
134     {__LINE__, SHCNE_DELETE, s_file1, NULL, "001000000", NULL},
135     {__LINE__, SHCNE_UPDATEDIR, s_file1, NULL, "000001000", NULL},
136     {__LINE__, SHCNE_UPDATEDIR, s_file2, NULL, "000001000", NULL},
137     {__LINE__, SHCNE_UPDATEDIR, s_file1, s_file2, "000001000", NULL},
138     {__LINE__, SHCNE_UPDATEDIR, s_file2, s_file1, "000001000", NULL},
139     {__LINE__, SHCNE_UPDATEDIR, s_dir1, NULL, "000001000", NULL},
140     {__LINE__, SHCNE_UPDATEDIR, s_dir2, NULL, "000001000", NULL},
141     {__LINE__, SHCNE_UPDATEDIR, s_dir1, s_dir2, "000001000", NULL},
142     {__LINE__, SHCNE_UPDATEDIR, s_dir2, s_dir1, "000001000", NULL},
143     {__LINE__, SHCNE_RMDIR, s_dir1, NULL, "000010000", NULL},
144     {__LINE__, SHCNE_RMDIR, s_dir2, NULL, "000010000", NULL},
145     {__LINE__, SHCNE_RMDIR, s_dir3, NULL, "000010000", NULL},
146     {__LINE__, SHCNE_RMDIR, s_dir1, s_dir2, "000010000", NULL},
147     {__LINE__, SHCNE_RMDIR, s_dir1, s_dir3, "000010000", NULL},
148     {__LINE__, SHCNE_RMDIR, s_dir2, s_dir1, "000010000", NULL},
149     {__LINE__, SHCNE_RMDIR, s_dir2, s_dir3, "000010000", NULL},
150     {__LINE__, SHCNE_RMDIR, s_dir3, NULL, "000010000", NULL},
151     {__LINE__, SHCNE_RMDIR, s_dir3, NULL, "000010000", DoAction7},
152     {__LINE__, SHCNE_RMDIR, s_dir1, NULL, "000010000", NULL},
153     {__LINE__, SHCNE_RMDIR, s_dir1, NULL, "000010000", DoAction8},
154 };
155 
156 LPCSTR PatternFromFlags(DWORD flags)
157 {
158     static char s_buf[TYPE_FREESPACE + 1 + 1];
159     DWORD i;
160     for (i = 0; i <= TYPE_FREESPACE; ++i)
161     {
162         s_buf[i] = (char)('0' + !!(flags & (1 << i)));
163     }
164     s_buf[i] = 0;
165     return s_buf;
166 }
167 
168 static void
169 DoTestEntry(const TEST_ENTRY *entry)
170 {
171     if (entry->action)
172     {
173         (*entry->action)();
174     }
175 
176     SHChangeNotify(entry->event, SHCNF_PATHW | SHCNF_FLUSH, entry->item1, entry->item2);
177 
178     DWORD flags = SendMessageW(s_hwnd, WM_GET_NOTIFY_FLAGS, 0, 0);
179     LPCSTR pattern = PatternFromFlags(flags);
180 
181     ok(lstrcmpA(pattern, entry->pattern) == 0, "Line %d: pattern mismatch '%s'\n", entry->line, pattern);
182 
183     SendMessageW(s_hwnd, WM_CLEAR_FLAGS, 0, 0);
184 }
185 
186 static BOOL
187 DoInit(HWND hwnd)
188 {
189     WCHAR szTemp[MAX_PATH], szPath[MAX_PATH];
190 
191     GetTempPathW(_countof(szTemp), szTemp);
192     GetLongPathNameW(szTemp, szPath, _countof(szPath));
193 
194     lstrcpyW(s_dir1, szPath);
195     PathAppendW(s_dir1, L"WatchDir1");
196     CreateDirectoryW(s_dir1, NULL);
197 
198     lstrcpyW(s_dir2, s_dir1);
199     PathAppendW(s_dir2, L"Dir2");
200 
201     lstrcpyW(s_dir3, s_dir1);
202     PathAppendW(s_dir3, L"Dir3");
203 
204     lstrcpyW(s_file1, s_dir1);
205     PathAppendW(s_file1, L"File1.txt");
206 
207     lstrcpyW(s_file2, s_dir1);
208     PathAppendW(s_file2, L"File2.txt");
209 
210     return TRUE;
211 }
212 
213 static void
214 DoEnd(HWND hwnd)
215 {
216     DeleteFileW(s_file1);
217     DeleteFileW(s_file2);
218     RemoveDirectoryW(s_dir3);
219     RemoveDirectoryW(s_dir2);
220     RemoveDirectoryW(s_dir1);
221 
222     SendMessageW(s_hwnd, WM_COMMAND, IDOK, 0);
223 }
224 
225 static BOOL CALLBACK
226 PropEnumProcEx(HWND hwnd, LPWSTR lpszString, HANDLE hData, ULONG_PTR dwData)
227 {
228     if (HIWORD(lpszString))
229         trace("Prop: '%S' --> %p\n", lpszString, hData);
230     else
231         trace("Prop: '%u' --> %p\n", LOWORD(lpszString), hData);
232     return TRUE;
233 }
234 
235 static void
236 DoPropTest(HWND hwnd)
237 {
238     EnumPropsExW(hwnd, PropEnumProcEx, 0);
239 }
240 
241 START_TEST(SHChangeNotify)
242 {
243     WCHAR szPath[MAX_PATH];
244     GetModuleFileNameW(NULL, szPath, _countof(szPath));
245     PathRemoveFileSpecW(szPath);
246     PathAppendW(szPath, L"shell-notify.exe");
247 
248     if (!PathFileExistsW(szPath))
249     {
250         trace("szPath: %S\n", szPath);
251         PathRemoveFileSpecW(szPath);
252         PathAppendW(szPath, L"testdata\\shell-notify.exe");
253 
254         if (!PathFileExistsW(szPath))
255         {
256             trace("szPath: %S\n", szPath);
257             skip("shell-notify.exe not found\n");
258             return;
259         }
260     }
261 
262     HINSTANCE hinst = ShellExecuteW(NULL, NULL, szPath, NULL, NULL, SW_SHOWNORMAL);
263     if ((INT_PTR)hinst <= 32)
264     {
265         skip("Unable to run shell-notify.exe.\n");
266         return;
267     }
268 
269     for (int i = 0; i < 10; ++i)
270     {
271         s_hwnd = FindWindowW(s_szName, s_szName);
272         if (s_hwnd)
273             break;
274 
275         Sleep(100);
276     }
277 
278     if (!s_hwnd)
279     {
280         skip("Unable to find window.\n");
281         return;
282     }
283 
284     if (!DoInit(s_hwnd))
285     {
286         skip("Unable to initialize.\n");
287         return;
288     }
289 
290     DoPropTest(s_hwnd);
291 
292     for (size_t i = 0; i < _countof(s_TestEntries); ++i)
293     {
294         DoTestEntry(&s_TestEntries[i]);
295     }
296 
297     DoEnd(s_hwnd);
298 }
299