Ich versuche, einen Unit-Test für meine FileWatcher
Klasse.
FileWatcher
leitet sich von einer Thread-Klasse ab und verwendet WaitForMultipleObjects
auf zwei Handles in seiner Thread-Prozedur zu warten:
- Der Handle, der von
FindFirstChangeNotification
- Ein Handle für ein Ereignis, mit dem ich die obige Wartezeit abbrechen kann.
Also grundsätzlich FileWatcher
wartet auf das, was zuerst kommt: eine Datei-Änderung oder ich sage ihm, dass er die Überwachung beenden soll.
Wenn ich nun versuche, Code zu schreiben, der diese Klasse testet, muss ich warten, bis sie zu warten beginnt.
Peusdo Code:
FileWatcher.Wait(INFINITE)
ChangeFile()
// Verify that FileWatcher works (with some other event - unimportant...)
Das Problem ist, dass es eine Wettlaufbedingung gibt. Ich muss zuerst sicherstellen, dass FileWatcher angefangen hat zu warten (d.h., dass sein Thread jetzt blockiert ist auf WaitForMultipleObjects
), bevor ich die Dateiänderung in Zeile #2 auslösen kann. Ich möchte Sleeps nicht verwenden, weil es, nun ja, umständlich erscheint und beim Debuggen zu Problemen führen wird.
Ich bin vertraut mit SignalObjectAndWait
, aber das löst mein Problem nicht wirklich, denn ich brauche es für "SignalObjectAndWaitOnMultipleObjects"...
Irgendwelche Ideen?
bearbeiten
Um das zu verdeutlichen, hier eine vereinfachte Version der FileWatcher
Klasse:
// 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;
};
Sie erbt von Thread
und implementiert die Thread-Funktion, die in etwa wie folgt aussieht (stark vereinfacht):
_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;
Beachten Sie, dass die Thread-Funktion auf zwei Handles wartet, einen - die Änderungsmeldung - und den anderen - das Ereignis "Thread anhalten" (geerbt von Thread).
Der Code, der diese Klasse testet, sieht wie folgt aus:
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();
}
};
Und wird von einem CPPUnit-Test aufgerufen:
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);
Die Idee ist, den Schlaf in der Mitte loszuwerden.