123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- #include <wil/filesystem.h>
- #include <wil/registry.h>
- #include <wil/resource.h>
- #include <memory> // For shared_event_watcher
- #include <wil/resource.h>
- #include "common.h"
- TEST_CASE("EventWatcherTests::Construction", "[resource][event_watcher]")
- {
- SECTION("Create unique_event_watcher_nothrow without event")
- {
- auto watcher = wil::make_event_watcher_nothrow([]{});
- REQUIRE(watcher != nullptr);
- }
- SECTION("Create unique_event_watcher_nothrow with unique_event_nothrow")
- {
- wil::unique_event_nothrow eventToPass;
- FAIL_FAST_IF_FAILED(eventToPass.create(wil::EventOptions::None));
- auto watcher = wil::make_event_watcher_nothrow(wistd::move(eventToPass), []{});
- REQUIRE(watcher != nullptr);
- REQUIRE(eventToPass.get() == nullptr); // move construction must take it
- }
- SECTION("Create unique_event_watcher_nothrow with handle")
- {
- wil::unique_event_nothrow eventToDupe;
- FAIL_FAST_IF_FAILED(eventToDupe.create(wil::EventOptions::None));
- auto watcher = wil::make_event_watcher_nothrow(eventToDupe.get(), []{});
- REQUIRE(watcher != nullptr);
- REQUIRE(eventToDupe.get() != nullptr); // handle duped in this case
- }
- #ifdef WIL_ENABLE_EXCEPTIONS
- SECTION("Create unique_event_watcher_nothrow with unique_event")
- {
- wil::unique_event eventToPass(wil::EventOptions::None);
- auto watcher = wil::make_event_watcher_nothrow(wistd::move(eventToPass), []{});
- REQUIRE(watcher != nullptr);
- REQUIRE(eventToPass.get() == nullptr); // move construction must take it
- }
- SECTION("Create unique_event_watcher without event")
- {
- auto watcher = wil::make_event_watcher([]{});
- }
- SECTION("Create unique_event_watcher with unique_event_nothrow")
- {
- wil::unique_event_nothrow eventToPass;
- THROW_IF_FAILED(eventToPass.create(wil::EventOptions::None));
- auto watcher = wil::make_event_watcher(wistd::move(eventToPass), []{});
- REQUIRE(eventToPass.get() == nullptr); // move construction must take it
- }
- SECTION("Create unique_event_watcher with unique_event")
- {
- wil::unique_event eventToPass(wil::EventOptions::None);
- auto watcher = wil::make_event_watcher(wistd::move(eventToPass), []{});
- REQUIRE(eventToPass.get() == nullptr); // move construction must take it
- }
- SECTION("Create unique_event_watcher with handle")
- {
- wil::unique_event eventToDupe(wil::EventOptions::None);
- auto watcher = wil::make_event_watcher(eventToDupe.get(), []{});
- REQUIRE(eventToDupe.get() != nullptr); // handle duped in this case
- }
- SECTION("Create unique_event_watcher shared watcher")
- {
- wil::shared_event_watcher sharedWatcher = wil::make_event_watcher([]{});
- }
- #endif
- }
- static auto make_event(wil::EventOptions options = wil::EventOptions::None)
- {
- wil::unique_event_nothrow result;
- FAIL_FAST_IF_FAILED(result.create(options));
- return result;
- }
- TEST_CASE("EventWatcherTests::VerifyDelivery", "[resource][event_watcher]")
- {
- auto notificationReceived = make_event();
- int volatile countObserved = 0;
- auto watcher = wil::make_event_watcher_nothrow([&]
- {
- countObserved = countObserved + 1;
- notificationReceived.SetEvent();
- });
- REQUIRE(watcher != nullptr);
- watcher.SetEvent();
- REQUIRE(notificationReceived.wait(5000)); // 5 second max wait
- watcher.SetEvent();
- REQUIRE(notificationReceived.wait(5000)); // 5 second max wait
- REQUIRE(countObserved == 2);
- }
- TEST_CASE("EventWatcherTests::VerifyLastChangeObserved", "[resource][event_watcher]")
- {
- wil::EventOptions const eventOptions[] =
- {
- wil::EventOptions::None,
- wil::EventOptions::ManualReset,
- wil::EventOptions::Signaled,
- wil::EventOptions::ManualReset | wil::EventOptions::Signaled,
- };
- for (auto const &eventOption : eventOptions)
- {
- auto allChangesMade = make_event(wil::EventOptions::ManualReset); // ManualReset to avoid hang in case where 2 callbacks are generated (a test failure).
- auto processedChange = make_event();
- DWORD volatile stateToObserve = 0;
- DWORD volatile lastObservedState = 0;
- int volatile countObserved = 0;
- auto watcher = wil::make_event_watcher_nothrow(make_event(eventOption), [&]
- {
- allChangesMade.wait();
- countObserved = countObserved + 1;
- lastObservedState = stateToObserve;
- processedChange.SetEvent();
- });
- REQUIRE(watcher != nullptr);
- stateToObserve = 1;
- watcher.SetEvent();
- stateToObserve = 2;
- watcher.SetEvent();
- allChangesMade.SetEvent();
- REQUIRE(processedChange.wait(5000));
- REQUIRE((countObserved == 1 || countObserved == 2)); // ensure the race worked how we wanted it to
- REQUIRE(lastObservedState == stateToObserve);
- }
- }
- #define ROOT_KEY_PAIR HKEY_CURRENT_USER, L"Software\\Microsoft\\RegistryWatcherTest"
- TEST_CASE("RegistryWatcherTests::Construction", "[registry][registry_watcher]")
- {
- SECTION("Create unique_registry_watcher_nothrow with string")
- {
- auto watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind){});
- REQUIRE(watcher);
- }
- SECTION("Create unique_registry_watcher_nothrow with unique_hkey")
- {
- wil::unique_hkey keyToMove;
- REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32(RegCreateKeyExW(ROOT_KEY_PAIR, 0, nullptr, 0, KEY_NOTIFY, nullptr, &keyToMove, nullptr)));
- auto watcher = wil::make_registry_watcher_nothrow(wistd::move(keyToMove), true, [&](wil::RegistryChangeKind){});
- REQUIRE(watcher);
- REQUIRE(keyToMove.get() == nullptr); // ownership is transferred
- }
- SECTION("Create unique_registry_watcher_nothrow with handle")
- {
- // construct with just an open registry key
- wil::unique_hkey rootKey;
- REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32(RegCreateKeyExW(ROOT_KEY_PAIR, 0, nullptr, 0, KEY_NOTIFY, nullptr, &rootKey, nullptr)));
- auto watcher = wil::make_registry_watcher_nothrow(rootKey.get(), L"", true, [&](wil::RegistryChangeKind){});
- REQUIRE(watcher);
- }
- #ifdef WIL_ENABLE_EXCEPTIONS
- SECTION("Create unique_registry_watcher with string")
- {
- REQUIRE_NOTHROW(wil::make_registry_watcher(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind){}));
- }
- SECTION("Create unique_registry_watcher with unique_hkey")
- {
- wil::unique_hkey keyToMove;
- THROW_IF_FAILED(HRESULT_FROM_WIN32(RegCreateKeyExW(ROOT_KEY_PAIR, 0, nullptr, 0, KEY_NOTIFY, nullptr, &keyToMove, nullptr)));
- REQUIRE_NOTHROW(wil::make_registry_watcher(wistd::move(keyToMove), true, [&](wil::RegistryChangeKind){}));
- REQUIRE(keyToMove.get() == nullptr); // ownership is transferred
- }
- #endif
- }
- void 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)
- {
- wil::unique_hkey key;
- REQUIRE(RegOpenKeyExW(hKey, lpSubKey, 0, KEY_WRITE, &key) == ERROR_SUCCESS);
- REQUIRE(RegSetValueExW(key.get(), lpValueName, 0, dwType, static_cast<BYTE const*>(lpData), cbData) == ERROR_SUCCESS);
- }
- TEST_CASE("RegistryWatcherTests::VerifyDelivery", "[registry][registry_watcher]")
- {
- RegDeleteTreeW(ROOT_KEY_PAIR); // So that we get the 'Modify' event
- auto notificationReceived = make_event();
- int volatile countObserved = 0;
- auto volatile observedChangeType = wil::RegistryChangeKind::Delete;
- auto watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind changeType)
- {
- countObserved = countObserved + 1;
- observedChangeType = changeType;
- notificationReceived.SetEvent();
- });
- REQUIRE(watcher);
- DWORD value = 1;
- SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
- REQUIRE(notificationReceived.wait(5000));
- REQUIRE(observedChangeType == wil::RegistryChangeKind::Modify);
- value++;
- SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
- REQUIRE(notificationReceived.wait(5000));
- REQUIRE(countObserved == 2);
- REQUIRE(observedChangeType == wil::RegistryChangeKind::Modify);
- }
- TEST_CASE("RegistryWatcherTests::VerifyLastChangeObserved", "[registry][registry_watcher]")
- {
- RegDeleteTreeW(ROOT_KEY_PAIR);
- auto allChangesMade = make_event(wil::EventOptions::ManualReset); // ManualReset for the case where both registry operations result in a callback.
- auto processedChange = make_event();
- DWORD volatile stateToObserve = 0;
- DWORD volatile lastObservedState = 0;
- DWORD volatile lastObservedValue = 0;
- int volatile countObserved = 0;
- auto watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&, called = false](wil::RegistryChangeKind) mutable
- {
- // This callback may be called more than once (since we modify the key twice), but we're holding references to
- // local variables. Therefore, bail out if this is not the first time we're called
- if (called)
- {
- return;
- }
- called = true;
- allChangesMade.wait();
- countObserved = countObserved + 1;
- lastObservedState = stateToObserve;
- DWORD value, cbValue = sizeof(value);
- RegGetValueW(ROOT_KEY_PAIR, L"value", RRF_RT_REG_DWORD, nullptr, &value, &cbValue);
- lastObservedValue = value;
- processedChange.SetEvent();
- });
- REQUIRE(watcher);
- DWORD value;
- // make 2 changes and verify that only the last gets observed
- stateToObserve = 1;
- value = 0;
- SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
- stateToObserve = 2;
- value = 1;
- SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
- allChangesMade.SetEvent();
- REQUIRE(processedChange.wait(5000));
- REQUIRE(countObserved >= 1); // Sometimes 2 events are observed, see if this can be eliminated.
- REQUIRE(lastObservedState == stateToObserve);
- REQUIRE(lastObservedValue == 1);
- }
- TEST_CASE("RegistryWatcherTests::VerifyDeleteBehavior", "[registry][registry_watcher]")
- {
- auto notificationReceived = make_event();
- int volatile countObserved = 0;
- auto volatile observedChangeType = wil::RegistryChangeKind::Modify;
- auto watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind changeType)
- {
- countObserved = countObserved + 1;
- observedChangeType = changeType;
- notificationReceived.SetEvent();
- });
- REQUIRE(watcher);
- RegDeleteTreeW(ROOT_KEY_PAIR); // delete the key to signal the watcher with the special error case
- REQUIRE(notificationReceived.wait(5000));
- REQUIRE(countObserved == 1);
- REQUIRE(observedChangeType == wil::RegistryChangeKind::Delete);
- }
- TEST_CASE("RegistryWatcherTests::VerifyResetInCallback", "[registry][registry_watcher]")
- {
- auto notificationReceived = make_event();
- wil::unique_registry_watcher_nothrow watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, TRUE, [&](wil::RegistryChangeKind)
- {
- watcher.reset();
- DWORD value = 2;
- SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
- notificationReceived.SetEvent();
- });
- REQUIRE(watcher);
- DWORD value = 1;
- SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
- REQUIRE(notificationReceived.wait(5000));
- }
- // Stress test, disabled by default
- TEST_CASE("RegistryWatcherTests::VerifyResetInCallbackStress", "[LocalOnly][registry][registry_watcher][stress]")
- {
- for (DWORD value = 0; value < 10000; ++value)
- {
- wil::srwlock lock;
- auto notificationReceived = make_event();
- wil::unique_registry_watcher_nothrow watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, TRUE, [&](wil::RegistryChangeKind)
- {
- {
- auto al = lock.lock_exclusive();
- watcher.reset(); // get m_refCount to 1 to ensure the Release happens on the background thread
- }
- ++value;
- SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
- notificationReceived.SetEvent();
- });
- REQUIRE(watcher);
- SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
- notificationReceived.wait();
- {
- auto al = lock.lock_exclusive();
- watcher.reset();
- }
- }
- }
- TEST_CASE("RegistryWatcherTests::VerifyResetAfterDelete", "[registry][registry_watcher]")
- {
- auto notificationReceived = make_event();
- int volatile countObserved = 0;
- auto volatile observedChangeType = wil::RegistryChangeKind::Modify;
- wil::unique_registry_watcher_nothrow watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind changeType)
- {
- countObserved = countObserved + 1;
- observedChangeType = changeType;
- notificationReceived.SetEvent();
- watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind changeType)
- {
- countObserved = countObserved + 1;
- observedChangeType = changeType;
- notificationReceived.SetEvent();
- });
- REQUIRE(watcher);
- });
- REQUIRE(watcher);
- RegDeleteTreeW(ROOT_KEY_PAIR); // delete the key to signal the watcher with the special error case
- notificationReceived.wait();
- REQUIRE(countObserved == 1);
- REQUIRE(observedChangeType == wil::RegistryChangeKind::Delete);
- // wait for the reset to finish. The constructor creates the registry key
- notificationReceived.wait(300);
- DWORD value = 1;
- SetRegistryValue(ROOT_KEY_PAIR, L"value", REG_DWORD, &value, sizeof(value));
- notificationReceived.wait();
- REQUIRE(countObserved == 2);
- REQUIRE(observedChangeType == wil::RegistryChangeKind::Modify);
- }
- TEST_CASE("RegistryWatcherTests::VerifyCallbackFinishesBeforeFreed", "[registry][registry_watcher]")
- {
- auto notificationReceived = make_event();
- auto deleteNotification = make_event();
- int volatile deleteObserved = 0;
- auto watcher = wil::make_registry_watcher_nothrow(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind)
- {
- notificationReceived.SetEvent();
- // ensure that the callback is still being executed while the watcher is reset().
- deleteNotification.wait(200);
- deleteObserved = deleteObserved + 1;
- notificationReceived.SetEvent();
- });
- RegDeleteTreeW(ROOT_KEY_PAIR); // delete the key to signal the watcher with the special error case
- REQUIRE(notificationReceived.wait(5000));
- watcher.reset();
- deleteNotification.SetEvent();
- REQUIRE(notificationReceived.wait(5000));
- REQUIRE(deleteObserved == 1);
- }
- TEST_CASE("FileSystemWatcherTests::Construction", "[resource][folder_watcher]")
- {
- SECTION("Create unique_folder_watcher_nothrow with valid path")
- {
- auto watcher = wil::make_folder_watcher_nothrow(L"C:\\Windows\\System32", true, wil::FolderChangeEvents::All, []{});
- REQUIRE(watcher);
- }
- SECTION("Create unique_folder_watcher_nothrow with invalid path")
- {
- auto watcher = wil::make_folder_watcher_nothrow(L"X:\\invalid path", true, wil::FolderChangeEvents::All, []{});
- REQUIRE(!watcher);
- }
- #ifdef WIL_ENABLE_EXCEPTIONS
- SECTION("Create unique_folder_watcher with valid path")
- {
- REQUIRE_NOTHROW(wil::make_folder_watcher(L"C:\\Windows\\System32", true, wil::FolderChangeEvents::All, []{}));
- }
- SECTION("Create unique_folder_watcher with invalid path")
- {
- REQUIRE_THROWS(wil::make_folder_watcher(L"X:\\invalid path", true, wil::FolderChangeEvents::All, []{}));
- }
- #endif
- }
- TEST_CASE("FileSystemWatcherTests::VerifyDelivery", "[resource][folder_watcher]")
- {
- witest::TestFolder folder;
- REQUIRE(folder);
- auto notificationEvent = make_event();
- int observedCount = 0;
- auto watcher = wil::make_folder_watcher_nothrow(folder.Path(), true, wil::FolderChangeEvents::All, [&]
- {
- ++observedCount;
- notificationEvent.SetEvent();
- });
- REQUIRE(watcher);
- witest::TestFile file(folder.Path(), L"file.txt");
- REQUIRE(file);
- REQUIRE(notificationEvent.wait(5000));
- REQUIRE(observedCount == 1);
- witest::TestFile file2(folder.Path(), L"file2.txt");
- REQUIRE(file2);
- REQUIRE(notificationEvent.wait(5000));
- REQUIRE(observedCount == 2);
- }
- TEST_CASE("FolderChangeReaderTests::Construction", "[resource][folder_change_reader]")
- {
- SECTION("Create folder_change_reader_nothrow with valid path")
- {
- auto reader = wil::make_folder_change_reader_nothrow(L"C:\\Windows\\System32", true, wil::FolderChangeEvents::All, [](auto, auto) {});
- REQUIRE(reader);
- }
- SECTION("Create folder_change_reader_nothrow with invalid path")
- {
- auto reader = wil::make_folder_change_reader_nothrow(L"X:\\invalid path", true, wil::FolderChangeEvents::All, [](auto, auto) {});
- REQUIRE(!reader);
- }
- #ifdef WIL_ENABLE_EXCEPTIONS
- SECTION("Create folder_change_reader with valid path")
- {
- REQUIRE_NOTHROW(wil::make_folder_change_reader(L"C:\\Windows\\System32", true, wil::FolderChangeEvents::All, [](auto, auto) {}));
- }
- SECTION("Create folder_change_reader with invalid path")
- {
- REQUIRE_THROWS(wil::make_folder_change_reader(L"X:\\invalid path", true, wil::FolderChangeEvents::All, [](auto, auto) {}));
- }
- #endif
- }
- TEST_CASE("FolderChangeReaderTests::VerifyDelivery", "[resource][folder_change_reader]")
- {
- witest::TestFolder folder;
- REQUIRE(folder);
- auto notificationEvent = make_event();
- wil::FolderChangeEvent observedEvent;
- wchar_t observedFileName[MAX_PATH] = L"";
- auto reader = wil::make_folder_change_reader_nothrow(folder.Path(), true, wil::FolderChangeEvents::All,
- [&](wil::FolderChangeEvent event, PCWSTR fileName)
- {
- observedEvent = event;
- StringCchCopyW(observedFileName, ARRAYSIZE(observedFileName), fileName);
- notificationEvent.SetEvent();
- });
- REQUIRE(reader);
- witest::TestFile testFile(folder.Path(), L"file.txt");
- REQUIRE(testFile);
- REQUIRE(notificationEvent.wait(5000));
- REQUIRE(observedEvent == wil::FolderChangeEvent::Added);
- REQUIRE(wcscmp(observedFileName, L"file.txt") == 0);
- witest::TestFile testFile2(folder.Path(), L"file2.txt");
- REQUIRE(testFile2);
- REQUIRE(notificationEvent.wait(5000));
- REQUIRE(observedEvent == wil::FolderChangeEvent::Added);
- REQUIRE(wcscmp(observedFileName, L"file2.txt") == 0);
- }
|