1
2 #include <wil/filesystem.h>
3 #include <wil/registry.h>
4 #include <wil/resource.h>
5
6 #include <memory> // For shared_event_watcher
7 #include <wil/resource.h>
8
9 #include "common.h"
10
11 TEST_CASE("EventWatcherTests::Construction", "[resource][event_watcher]")
12 {
13 SECTION("Create unique_event_watcher_nothrow without event")
14 {
__anon64b017ed0102null15 auto watcher = wil::make_event_watcher_nothrow([]{});
16 REQUIRE(watcher != nullptr);
17 }
18
19 SECTION("Create unique_event_watcher_nothrow with unique_event_nothrow")
20 {
21 wil::unique_event_nothrow eventToPass;
22 FAIL_FAST_IF_FAILED(eventToPass.create(wil::EventOptions::None));
__anon64b017ed0202null23 auto watcher = wil::make_event_watcher_nothrow(wistd::move(eventToPass), []{});
24 REQUIRE(watcher != nullptr);
25 REQUIRE(eventToPass.get() == nullptr); // move construction must take it
26 }
27
28 SECTION("Create unique_event_watcher_nothrow with handle")
29 {
30 wil::unique_event_nothrow eventToDupe;
31 FAIL_FAST_IF_FAILED(eventToDupe.create(wil::EventOptions::None));
__anon64b017ed0302null32 auto watcher = wil::make_event_watcher_nothrow(eventToDupe.get(), []{});
33 REQUIRE(watcher != nullptr);
34 REQUIRE(eventToDupe.get() != nullptr); // handle duped in this case
35 }
36
37 #ifdef WIL_ENABLE_EXCEPTIONS
38 SECTION("Create unique_event_watcher_nothrow with unique_event")
39 {
40 wil::unique_event eventToPass(wil::EventOptions::None);
__anon64b017ed0402null41 auto watcher = wil::make_event_watcher_nothrow(wistd::move(eventToPass), []{});
42 REQUIRE(watcher != nullptr);
43 REQUIRE(eventToPass.get() == nullptr); // move construction must take it
44 }
45
46 SECTION("Create unique_event_watcher without event")
47 {
__anon64b017ed0502null48 auto watcher = wil::make_event_watcher([]{});
49 }
50
51 SECTION("Create unique_event_watcher with unique_event_nothrow")
52 {
53 wil::unique_event_nothrow eventToPass;
54 THROW_IF_FAILED(eventToPass.create(wil::EventOptions::None));
__anon64b017ed0602null55 auto watcher = wil::make_event_watcher(wistd::move(eventToPass), []{});
56 REQUIRE(eventToPass.get() == nullptr); // move construction must take it
57 }
58
59 SECTION("Create unique_event_watcher with unique_event")
60 {
61 wil::unique_event eventToPass(wil::EventOptions::None);
__anon64b017ed0702null62 auto watcher = wil::make_event_watcher(wistd::move(eventToPass), []{});
63 REQUIRE(eventToPass.get() == nullptr); // move construction must take it
64 }
65
66 SECTION("Create unique_event_watcher with handle")
67 {
68 wil::unique_event eventToDupe(wil::EventOptions::None);
__anon64b017ed0802null69 auto watcher = wil::make_event_watcher(eventToDupe.get(), []{});
70 REQUIRE(eventToDupe.get() != nullptr); // handle duped in this case
71 }
72
73 SECTION("Create unique_event_watcher shared watcher")
74 {
__anon64b017ed0902null75 wil::shared_event_watcher sharedWatcher = wil::make_event_watcher([]{});
76 }
77 #endif
78 }
79
make_event(wil::EventOptions options=wil::EventOptions::None)80 static auto make_event(wil::EventOptions options = wil::EventOptions::None)
81 {
82 wil::unique_event_nothrow result;
83 FAIL_FAST_IF_FAILED(result.create(options));
84 return result;
85 }
86
87 TEST_CASE("EventWatcherTests::VerifyDelivery", "[resource][event_watcher]")
88 {
89 auto notificationReceived = make_event();
90
91 int volatile countObserved = 0;
92 auto watcher = wil::make_event_watcher_nothrow([&]
__anon64b017ed0a02null93 {
94 countObserved++;
95 notificationReceived.SetEvent();
96 });
97 REQUIRE(watcher != nullptr);
98
99 watcher.SetEvent();
100 REQUIRE(notificationReceived.wait(5000)); // 5 second max wait
101
102 watcher.SetEvent();
103 REQUIRE(notificationReceived.wait(5000)); // 5 second max wait
104 REQUIRE(countObserved == 2);
105 }
106
107 TEST_CASE("EventWatcherTests::VerifyLastChangeObserved", "[resource][event_watcher]")
108 {
109 wil::EventOptions const eventOptions[] =
110 {
111 wil::EventOptions::None,
112 wil::EventOptions::ManualReset,
113 wil::EventOptions::Signaled,
114 wil::EventOptions::ManualReset | wil::EventOptions::Signaled,
115 };
116
117 for (auto const &eventOption : eventOptions)
118 {
119 auto allChangesMade = make_event(wil::EventOptions::ManualReset); // ManualReset to avoid hang in case where 2 callbacks are generated (a test failure).
120 auto processedChange = make_event();
121
122 DWORD volatile stateToObserve = 0;
123 DWORD volatile lastObservedState = 0;
124 int volatile countObserved = 0;
125 auto watcher = wil::make_event_watcher_nothrow(make_event(eventOption), [&]
__anon64b017ed0b02null126 {
127 allChangesMade.wait();
128 countObserved++;
129 lastObservedState = stateToObserve;
130 processedChange.SetEvent();
131 });
132 REQUIRE(watcher != nullptr);
133
134 stateToObserve = 1;
135 watcher.SetEvent();
136 stateToObserve = 2;
137 watcher.SetEvent();
138
139 allChangesMade.SetEvent();
140 REQUIRE(processedChange.wait(5000));
141
142 REQUIRE((countObserved == 1 || countObserved == 2)); // ensure the race worked how we wanted it to
143 REQUIRE(lastObservedState == stateToObserve);
144 }
145 }
146
147 #define ROOT_KEY_PAIR HKEY_CURRENT_USER, L"Software\\Microsoft\\RegistryWatcherTest"
148
149 TEST_CASE("RegistryWatcherTests::Construction", "[registry][registry_watcher]")
150 {
151 SECTION("Create unique_registry_watcher_nothrow with string")
152 {
__anon64b017ed0c02(wil::RegistryChangeKind)153 auto watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind){});
154 REQUIRE(watcher);
155 }
156
157 SECTION("Create unique_registry_watcher_nothrow with unique_hkey")
158 {
159 wil::unique_hkey keyToMove;
160 REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32(RegCreateKeyExW(ROOT_KEY_PAIR, 0, nullptr, 0, KEY_NOTIFY, nullptr, &keyToMove, nullptr)));
161
__anon64b017ed0d02(wil::RegistryChangeKind)162 auto watcher = wil::make_registry_watcher_nothrow(wistd::move(keyToMove), true, [&](wil::RegistryChangeKind){});
163 REQUIRE(watcher);
164 REQUIRE(keyToMove.get() == nullptr); // ownership is transferred
165 }
166
167 SECTION("Create unique_registry_watcher_nothrow with handle")
168 {
169 // construct with just an open registry key
170 wil::unique_hkey rootKey;
171 REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32(RegCreateKeyExW(ROOT_KEY_PAIR, 0, nullptr, 0, KEY_NOTIFY, nullptr, &rootKey, nullptr)));
172
__anon64b017ed0e02(wil::RegistryChangeKind)173 auto watcher = wil::make_registry_watcher_nothrow(rootKey.get(), L"", true, [&](wil::RegistryChangeKind){});
174 REQUIRE(watcher);
175 }
176
177 #ifdef WIL_ENABLE_EXCEPTIONS
178 SECTION("Create unique_registry_watcher with string")
179 {
__anon64b017ed0f02(wil::RegistryChangeKind)180 REQUIRE_NOTHROW(wil::make_registry_watcher(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind){}));
181 }
182
183 SECTION("Create unique_registry_watcher with unique_hkey")
184 {
185 wil::unique_hkey keyToMove;
186 THROW_IF_FAILED(HRESULT_FROM_WIN32(RegCreateKeyExW(ROOT_KEY_PAIR, 0, nullptr, 0, KEY_NOTIFY, nullptr, &keyToMove, nullptr)));
187
__anon64b017ed1002(wil::RegistryChangeKind)188 REQUIRE_NOTHROW(wil::make_registry_watcher(wistd::move(keyToMove), true, [&](wil::RegistryChangeKind){}));
189 REQUIRE(keyToMove.get() == nullptr); // ownership is transferred
190 }
191 #endif
192 }
193
SetRegistryValue(_In_ HKEY hKey,_In_opt_ LPCWSTR lpSubKey,_In_opt_ LPCWSTR lpValueName,_In_ DWORD dwType,_In_reads_bytes_opt_ (cbData)LPCVOID lpData,_In_ DWORD cbData)194 void SetRegistryValue(
195 _In_ HKEY hKey,
196 _In_opt_ LPCWSTR lpSubKey,
197 _In_opt_ LPCWSTR lpValueName,
198 _In_ DWORD dwType,
199 _In_reads_bytes_opt_(cbData) LPCVOID lpData,
200 _In_ DWORD cbData)
201 {
202 wil::unique_hkey key;
203 REQUIRE(RegOpenKeyExW(hKey, lpSubKey, 0, KEY_WRITE, &key) == ERROR_SUCCESS);
204 REQUIRE(RegSetValueExW(key.get(), lpValueName, 0, dwType, static_cast<BYTE const*>(lpData), cbData) == ERROR_SUCCESS);
205 }
206
207 TEST_CASE("RegistryWatcherTests::VerifyDelivery", "[registry][registry_watcher]")
208 {
209 RegDeleteTreeW(ROOT_KEY_PAIR); // So that we get the 'Modify' event
210 auto notificationReceived = make_event();
211
212 int volatile countObserved = 0;
213 auto volatile observedChangeType = wil::RegistryChangeKind::Delete;
214 auto watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind changeType)
__anon64b017ed1102(wil::RegistryChangeKind changeType) 215 {
216 countObserved++;
217 observedChangeType = changeType;
218 notificationReceived.SetEvent();
219 });
220 REQUIRE(watcher);
221
222 DWORD value = 1;
223 SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
224 REQUIRE(notificationReceived.wait(5000));
225 REQUIRE(observedChangeType == wil::RegistryChangeKind::Modify);
226
227 value++;
228 SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
229 REQUIRE(notificationReceived.wait(5000));
230 REQUIRE(countObserved == 2);
231 REQUIRE(observedChangeType == wil::RegistryChangeKind::Modify);
232 }
233
234 TEST_CASE("RegistryWatcherTests::VerifyLastChangeObserved", "[registry][registry_watcher]")
235 {
236 RegDeleteTreeW(ROOT_KEY_PAIR);
237 auto allChangesMade = make_event(wil::EventOptions::ManualReset); // ManualReset for the case where both registry operations result in a callback.
238 auto processedChange = make_event();
239
240 DWORD volatile stateToObserve = 0;
241 DWORD volatile lastObservedState = 0;
242 DWORD volatile lastObservedValue = 0;
243 int volatile countObserved = 0;
244 auto watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&, called = false](wil::RegistryChangeKind) mutable
__anon64b017ed1202(wil::RegistryChangeKind) 245 {
246 // This callback may be called more than once (since we modify the key twice), but we're holding references to
247 // local variables. Therefore, bail out if this is not the first time we're called
248 if (called)
249 {
250 return;
251 }
252 called = true;
253
254 allChangesMade.wait();
255 countObserved++;
256 lastObservedState = stateToObserve;
257 DWORD value, cbValue = sizeof(value);
258 RegGetValueW(ROOT_KEY_PAIR, L"value", RRF_RT_REG_DWORD, nullptr, &value, &cbValue);
259 lastObservedValue = value;
260 processedChange.SetEvent();
261 });
262 REQUIRE(watcher);
263
264 DWORD value;
265 // make 2 changes and verify that only the last gets observed
266 stateToObserve = 1;
267 value = 0;
268 SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
269
270 stateToObserve = 2;
271 value = 1;
272 SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
273
274 allChangesMade.SetEvent();
275 REQUIRE(processedChange.wait(5000));
276
277 REQUIRE(countObserved >= 1); // Sometimes 2 events are observed, see if this can be eliminated.
278 REQUIRE(lastObservedState == stateToObserve);
279 REQUIRE(lastObservedValue == 1);
280 }
281
282 TEST_CASE("RegistryWatcherTests::VerifyDeleteBehavior", "[registry][registry_watcher]")
283 {
284 auto notificationReceived = make_event();
285
286 int volatile countObserved = 0;
287 auto volatile observedChangeType = wil::RegistryChangeKind::Modify;
288 auto watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind changeType)
__anon64b017ed1302(wil::RegistryChangeKind changeType) 289 {
290 countObserved++;
291 observedChangeType = changeType;
292 notificationReceived.SetEvent();
293 });
294 REQUIRE(watcher);
295
296 RegDeleteTreeW(ROOT_KEY_PAIR); // delete the key to signal the watcher with the special error case
297 REQUIRE(notificationReceived.wait(5000));
298 REQUIRE(countObserved == 1);
299 REQUIRE(observedChangeType == wil::RegistryChangeKind::Delete);
300 }
301
302 TEST_CASE("RegistryWatcherTests::VerifyResetInCallback", "[registry][registry_watcher]")
303 {
304 auto notificationReceived = make_event();
305
306 wil::unique_registry_watcher_nothrow watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, TRUE, [&](wil::RegistryChangeKind)
__anon64b017ed1402(wil::RegistryChangeKind) 307 {
308 watcher.reset();
309 DWORD value = 2;
310 SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
311 notificationReceived.SetEvent();
312 });
313 REQUIRE(watcher);
314
315 DWORD value = 1;
316 SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
317 REQUIRE(notificationReceived.wait(5000));
318 }
319
320 // Stress test, disabled by default
321 TEST_CASE("RegistryWatcherTests::VerifyResetInCallbackStress", "[!hide][registry][registry_watcher][stress]")
322 {
323 for (DWORD value = 0; value < 10000; ++value)
324 {
325 wil::srwlock lock;
326 auto notificationReceived = make_event();
327
328 wil::unique_registry_watcher_nothrow watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, TRUE, [&](wil::RegistryChangeKind)
__anon64b017ed1502(wil::RegistryChangeKind) 329 {
330 {
331 auto al = lock.lock_exclusive();
332 watcher.reset(); // get m_refCount to 1 to ensure the Release happens on the background thread
333 }
334 ++value;
335 SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
336 notificationReceived.SetEvent();
337 });
338 REQUIRE(watcher);
339
340 SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
341 notificationReceived.wait();
342
343 {
344 auto al = lock.lock_exclusive();
345 watcher.reset();
346 }
347 }
348 }
349
350 TEST_CASE("RegistryWatcherTests::VerifyResetAfterDelete", "[registry][registry_watcher]")
351 {
352 auto notificationReceived = make_event();
353
354 int volatile countObserved = 0;
355 auto volatile observedChangeType = wil::RegistryChangeKind::Modify;
356 wil::unique_registry_watcher_nothrow watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind changeType)
__anon64b017ed1602(wil::RegistryChangeKind changeType) 357 {
358 countObserved++;
359 observedChangeType = changeType;
360 notificationReceived.SetEvent();
361 watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind changeType)
362 {
363 countObserved++;
364 observedChangeType = changeType;
365 notificationReceived.SetEvent();
366 });
367 REQUIRE(watcher);
368 });
369 REQUIRE(watcher);
370
371 RegDeleteTreeW(ROOT_KEY_PAIR); // delete the key to signal the watcher with the special error case
372 notificationReceived.wait();
373 REQUIRE(countObserved == 1);
374 REQUIRE(observedChangeType == wil::RegistryChangeKind::Delete);
375
376 // wait for the reset to finish. The constructor creates the registry key
377 notificationReceived.wait(300);
378 DWORD value = 1;
379 SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
380
381 notificationReceived.wait();
382 REQUIRE(countObserved == 2);
383 REQUIRE(observedChangeType == wil::RegistryChangeKind::Modify);
384 }
385
386 TEST_CASE("RegistryWatcherTests::VerifyCallbackFinishesBeforeFreed", "[registry][registry_watcher]")
387 {
388 auto notificationReceived = make_event();
389 auto deleteNotification = make_event();
390
391 int volatile deleteObserved = 0;
392 auto watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind)
__anon64b017ed1802(wil::RegistryChangeKind) 393 {
394 notificationReceived.SetEvent();
395 // ensure that the callback is still being executed while the watcher is reset().
396 deleteNotification.wait(200);
397 deleteObserved++;
398 notificationReceived.SetEvent();
399 });
400
401 RegDeleteTreeW(ROOT_KEY_PAIR); // delete the key to signal the watcher with the special error case
402 REQUIRE(notificationReceived.wait(5000));
403
404 watcher.reset();
405 deleteNotification.SetEvent();
406 REQUIRE(notificationReceived.wait(5000));
407 REQUIRE(deleteObserved == 1);
408 }
409
410 TEST_CASE("FileSystemWatcherTests::Construction", "[resource][folder_watcher]")
411 {
412 SECTION("Create unique_folder_watcher_nothrow with valid path")
413 {
__anon64b017ed1902null414 auto watcher = wil::make_folder_watcher_nothrow(L"C:\\Windows\\System32", true, wil::FolderChangeEvents::All, []{});
415 REQUIRE(watcher);
416 }
417
418 SECTION("Create unique_folder_watcher_nothrow with invalid path")
419 {
__anon64b017ed1a02null420 auto watcher = wil::make_folder_watcher_nothrow(L"X:\\invalid path", true, wil::FolderChangeEvents::All, []{});
421 REQUIRE(!watcher);
422 }
423
424 #ifdef WIL_ENABLE_EXCEPTIONS
425 SECTION("Create unique_folder_watcher with valid path")
426 {
__anon64b017ed1b02null427 REQUIRE_NOTHROW(wil::make_folder_watcher(L"C:\\Windows\\System32", true, wil::FolderChangeEvents::All, []{}));
428 }
429
430 SECTION("Create unique_folder_watcher with invalid path")
431 {
__anon64b017ed1c02null432 REQUIRE_THROWS(wil::make_folder_watcher(L"X:\\invalid path", true, wil::FolderChangeEvents::All, []{}));
433 }
434 #endif
435 }
436
437 TEST_CASE("FileSystemWatcherTests::VerifyDelivery", "[resource][folder_watcher]")
438 {
439 witest::TestFolder folder;
440 REQUIRE(folder);
441
442 auto notificationEvent = make_event();
443 int observedCount = 0;
444 auto watcher = wil::make_folder_watcher_nothrow(folder.Path(), true, wil::FolderChangeEvents::All, [&]
__anon64b017ed1d02null445 {
446 ++observedCount;
447 notificationEvent.SetEvent();
448 });
449 REQUIRE(watcher);
450
451 witest::TestFile file(folder.Path(), L"file.txt");
452 REQUIRE(file);
453 REQUIRE(notificationEvent.wait(5000));
454 REQUIRE(observedCount == 1);
455
456 witest::TestFile file2(folder.Path(), L"file2.txt");
457 REQUIRE(file2);
458 REQUIRE(notificationEvent.wait(5000));
459 REQUIRE(observedCount == 2);
460 }
461
462 TEST_CASE("FolderChangeReaderTests::Construction", "[resource][folder_change_reader]")
463 {
464 SECTION("Create folder_change_reader_nothrow with valid path")
465 {
__anon64b017ed1e02(auto, auto) 466 auto reader = wil::make_folder_change_reader_nothrow(L"C:\\Windows\\System32", true, wil::FolderChangeEvents::All, [](auto, auto) {});
467 REQUIRE(reader);
468 }
469
470 SECTION("Create folder_change_reader_nothrow with invalid path")
471 {
__anon64b017ed1f02(auto, auto) 472 auto reader = wil::make_folder_change_reader_nothrow(L"X:\\invalid path", true, wil::FolderChangeEvents::All, [](auto, auto) {});
473 REQUIRE(!reader);
474 }
475
476 #ifdef WIL_ENABLE_EXCEPTIONS
477 SECTION("Create folder_change_reader with valid path")
478 {
__anon64b017ed2002(auto, auto) 479 REQUIRE_NOTHROW(wil::make_folder_change_reader(L"C:\\Windows\\System32", true, wil::FolderChangeEvents::All, [](auto, auto) {}));
480 }
481
482 SECTION("Create folder_change_reader with invalid path")
483 {
__anon64b017ed2102(auto, auto) 484 REQUIRE_THROWS(wil::make_folder_change_reader(L"X:\\invalid path", true, wil::FolderChangeEvents::All, [](auto, auto) {}));
485 }
486 #endif
487 }
488
489 TEST_CASE("FolderChangeReaderTests::VerifyDelivery", "[resource][folder_change_reader]")
490 {
491 witest::TestFolder folder;
492 REQUIRE(folder);
493
494 auto notificationEvent = make_event();
495 wil::FolderChangeEvent observedEvent;
496 wchar_t observedFileName[MAX_PATH] = L"";
497 auto reader = wil::make_folder_change_reader_nothrow(folder.Path(), true, wil::FolderChangeEvents::All,
498 [&](wil::FolderChangeEvent event, PCWSTR fileName)
__anon64b017ed2202(wil::FolderChangeEvent event, PCWSTR fileName) 499 {
500 observedEvent = event;
501 StringCchCopyW(observedFileName, ARRAYSIZE(observedFileName), fileName);
502 notificationEvent.SetEvent();
503 });
504 REQUIRE(reader);
505
506 witest::TestFile testFile(folder.Path(), L"file.txt");
507 REQUIRE(testFile);
508 REQUIRE(notificationEvent.wait(5000));
509 REQUIRE(observedEvent == wil::FolderChangeEvent::Added);
510 REQUIRE(wcscmp(observedFileName, L"file.txt") == 0);
511
512 witest::TestFile testFile2(folder.Path(), L"file2.txt");
513 REQUIRE(testFile2);
514 REQUIRE(notificationEvent.wait(5000));
515 REQUIRE(observedEvent == wil::FolderChangeEvent::Added);
516 REQUIRE(wcscmp(observedFileName, L"file2.txt") == 0);
517 }
518