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