545 строки
20 KiB
C++
545 строки
20 KiB
C++
#include "pch.h"
|
|
|
|
#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);
|
|
}
|
|
|
|
#ifdef WIL_ENABLE_EXCEPTIONS
|
|
TEST_CASE("RegistryWatcherTests::VerifyDeleteDuringNotification", "[registry][registry_watcher]")
|
|
{
|
|
wil::unique_event notificationReceived(wil::EventOptions::None);
|
|
wil::unique_event deleteNotification(wil::EventOptions::None);
|
|
|
|
int mockObserved = 0;
|
|
witest::detoured_global_function<&RegNotifyChangeKeyValue> mockRegNotifyChangeKeyValue;
|
|
REQUIRE_SUCCEEDED(mockRegNotifyChangeKeyValue.reset([&](HKEY, BOOL, DWORD, HANDLE event, BOOL) -> LSTATUS {
|
|
if (!mockObserved)
|
|
{
|
|
mockObserved++;
|
|
SetEvent(event);
|
|
// on watcher create just return
|
|
return ERROR_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
mockObserved++;
|
|
notificationReceived.SetEvent();
|
|
deleteNotification.wait();
|
|
return ERROR_SUCCESS;
|
|
}
|
|
}));
|
|
|
|
auto watcher = wil::make_registry_watcher(ROOT_KEY_PAIR, true, [&](wil::RegistryChangeKind) {});
|
|
notificationReceived.wait();
|
|
REQUIRE(mockObserved == 2);
|
|
deleteNotification.SetEvent();
|
|
watcher.reset();
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
}
|