I'm trying to write a unit test for my FileWatcher
class.
FileWatcher
derives from a Thread class and uses WaitForMultipleObjects
to wait on two handles in its thread procedure:
- The handle returned from
FindFirstChangeNotification
- A handle for an Event that lets me cancel the above wait.
So basically FileWatcher
is waiting for whatever comes first: a file change or I tell it to stop watching.
Now, when trying to write code that tests this class I need to wait for it to start waiting.
Peusdo Code:
FileWatcher.Wait(INFINITE)
ChangeFile()
// Verify that FileWatcher works (with some other event - unimportant...)
Problem is that there's a race condition. I need to first make sure that FileWatcher has started waiting (i.e. that its thread is now blocked on WaitForMultipleObjects
) before I can trigger the file change in line #2. I don't want to use Sleeps because, well, it seems hacky and is bound to give me problems when debugging.
I'm familiar with SignalObjectAndWait
, but it doesn't really solve my problem, because I need it to "SignalObjectAndWaitOnMultipleObjects"...
Any ideas?
Edit
To clarify a bit, here's a simplified version of the FileWatcher
class:
// Inherit from this class, override OnChange, and call Start() to turn on monitoring.
class FileChangeWatcher : public Utils::Thread
{
public:
// File must exist before constructing this instance
FileChangeWatcher(const std::string& filename);
virtual int Run();
virtual void OnChange() = 0;
};
It inherits from Thread
and implements the thread function, which looks something like this (very simplified):
_changeEvent = ::FindFirstChangeNotificationW(wfn.c_str(), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE);
HANDLE events[2] = { _changeEvent, m_hStopEvent };
DWORD hWaitDone = WAIT_OBJECT_0;
while (hWaitDone == WAIT_OBJECT_0)
{
hWaitDone = ::WaitForMultipleObjects(2, events, FALSE, INFINITE);
if (hWaitDone == WAIT_OBJECT_0)
OnChange();
else
return Thread::THREAD_ABORTED;
}
return THREAD_FINISHED;
Notice that the thread function waits on two handles, one - the change notification, and the other - the "stop thread" event (inherited from Thread).
Now the code that tests this class looks like this:
class TestFileWatcher : public FileChangeWatcher
{
public:
bool Changed;
Event evtDone;
TestFileWatcher(const std::string& fname) : FileChangeWatcher(fname) { Changed = false; }
virtual void OnChange()
{
Changed = true;
evtDone.Set();
}
};
And is invoked from a CPPUnit test:
std::string tempFile = TempFilePath();
StringToFile("Hello, file", tempFile);
TestFileWatcher tfw(tempFile);
tfw.Start();
::Sleep(100); // Ugly, but we have to wait for monitor to kick in in worker thread
StringToFile("Modify me", tempFile);
tfw.evtDone.Wait(INFINITE);
CPPUNIT_ASSERT(tfw.Changed);
The idea is to get rid of that Sleep in the middle.