wxWidgets/tests/fswatcher/fswatchertest.cpp
Vadim Zeitlin d3eec3b672 Attempt to work around Mac g++ 4.0 bug in fswatcher unit test.
Don't define the class overriding a virtual base class method inside the test
function as g++ 4.0 under OS X 10.5 fails to compile this for some mysterious
reason.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@67722 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2011-05-10 08:54:20 +00:00

673 lines
17 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: tests/fswatcher/fswatchertest.cpp
// Purpose: wxFileSystemWatcher unit test
// Author: Bartosz Bekier
// Created: 2009-06-11
// RCS-ID: $Id$
// Copyright: (c) 2009 Bartosz Bekier
///////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
#include "testprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include "wx/timer.h"
#endif
#include "wx/evtloop.h"
#include "wx/filename.h"
#include "wx/filefn.h"
#include "wx/stdpaths.h"
#include "wx/fswatcher.h"
#include "testfile.h"
// ----------------------------------------------------------------------------
// local functions
// ----------------------------------------------------------------------------
// class generating file system events
class EventGenerator
{
public:
static EventGenerator& Get()
{
if (!ms_instance)
ms_instance = new EventGenerator(GetWatchDir());
return *ms_instance;
}
EventGenerator(const wxFileName& path) : m_base(path)
{
m_old = wxFileName();
m_file = RandomName();
m_new = RandomName();
}
// operations
bool CreateFile()
{
wxFile file(m_file.GetFullPath(), wxFile::write);
return file.IsOpened() && m_file.FileExists();
}
bool RenameFile()
{
CPPUNIT_ASSERT(m_file.FileExists());
wxLogDebug("Renaming %s=>%s", m_file.GetFullPath(), m_new.GetFullPath());
bool ret = wxRenameFile(m_file.GetFullPath(), m_new.GetFullPath());
if (ret)
{
m_old = m_file;
m_file = m_new;
m_new = RandomName();
}
return ret;
}
bool DeleteFile()
{
CPPUNIT_ASSERT(m_file.FileExists());
bool ret = wxRemoveFile(m_file.GetFullPath());
if (ret)
{
m_old = m_file;
m_file = m_new;
m_new = RandomName();
}
return ret;
}
bool TouchFile()
{
return m_file.Touch();
}
bool ReadFile()
{
wxFile f(m_file.GetFullPath());
CPPUNIT_ASSERT(f.IsOpened());
char buf[1];
ssize_t count = f.Read(buf, sizeof(buf));
CPPUNIT_ASSERT(count > 0);
return true;
}
bool ModifyFile()
{
CPPUNIT_ASSERT(m_file.FileExists());
wxFile file(m_file.GetFullPath(), wxFile::write_append);
CPPUNIT_ASSERT(file.IsOpened());
CPPUNIT_ASSERT(file.Write("Words of Wisdom, Lloyd. Words of wisdom\n"));
return file.Close();
}
// helpers
wxFileName RandomName(int length = 10)
{
return RandomName(m_base, length);
}
// static helpers
static const wxFileName& GetWatchDir()
{
static wxFileName dir;
if (dir.DirExists())
return dir;
wxString tmp = wxStandardPaths::Get().GetTempDir();
dir.AssignDir(tmp);
// XXX look for more unique name? there is no function to generate
// unique filename, the file always get created...
dir.AppendDir("fswatcher_test");
CPPUNIT_ASSERT(!dir.DirExists());
CPPUNIT_ASSERT(dir.Mkdir());
return dir;
}
static void RemoveWatchDir()
{
wxFileName dir = GetWatchDir();
CPPUNIT_ASSERT(dir.DirExists());
// just to be really sure we know what we remove
CPPUNIT_ASSERT_EQUAL( "fswatcher_test", dir.GetDirs().Last() );
// FIXME-VC6: using non-static Rmdir() results in ICE
CPPUNIT_ASSERT( wxFileName::Rmdir(dir.GetFullPath(), wxPATH_RMDIR_RECURSIVE) );
}
static wxFileName RandomName(const wxFileName& base, int length = 10)
{
static int ALFA_CNT = 'z' - 'a';
wxString s;
for (int i = 0 ; i < length; ++i)
{
char c = 'a' + (rand() % ALFA_CNT);
s += c;
}
return wxFileName(base.GetFullPath(), s);
}
public:
wxFileName m_base; // base dir for doing operations
wxFileName m_file; // current file name
wxFileName m_old; // previous file name
wxFileName m_new; // name after renaming
protected:
static EventGenerator* ms_instance;
};
EventGenerator* EventGenerator::ms_instance = 0;
// custom event handler
class EventHandler : public wxEvtHandler
{
public:
enum { WAIT_DURATION = 3 };
EventHandler() :
eg(EventGenerator::Get()), m_loop(0), m_count(0), m_watcher(0)
{
m_loop = new wxEventLoop();
Connect(wxEVT_IDLE, wxIdleEventHandler(EventHandler::OnIdle));
Connect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(
EventHandler::OnFileSystemEvent));
}
virtual ~EventHandler()
{
delete m_watcher;
if (m_loop)
{
if (m_loop->IsRunning())
m_loop->Exit();
delete m_loop;
}
}
void Exit()
{
m_loop->Exit();
}
// sends idle event, so we get called in a moment
void SendIdle()
{
wxIdleEvent* e = new wxIdleEvent();
QueueEvent(e);
}
void Run()
{
SendIdle();
m_loop->Run();
}
void OnIdle(wxIdleEvent& /*evt*/)
{
bool more = Action();
m_count++;
if (more)
{
SendIdle();
}
}
// returns whether we should produce more idle events
virtual bool Action()
{
switch (m_count)
{
case 0:
CPPUNIT_ASSERT(Init());
break;
case 1:
GenerateEvent();
break;
case 2:
// actual test
CheckResult();
Exit();
break;
// TODO a mechanism that will break the loop in case we
// don't receive a file system event
// this below doesn't quite work, so all tests must pass :-)
#if 0
case 2:
m_loop.Yield();
m_loop.WakeUp();
CPPUNIT_ASSERT(KeepWaiting());
m_loop.Yield();
break;
case 3:
break;
case 4:
CPPUNIT_ASSERT(AfterWait());
break;
#endif
} // switch (m_count)
return m_count <= 0;
}
virtual bool Init()
{
// test we're good to go
CPPUNIT_ASSERT(wxEventLoopBase::GetActive());
// XXX only now can we construct Watcher, because we need
// active loop here
m_watcher = new wxFileSystemWatcher();
m_watcher->SetOwner(this);
// add dir to be watched
wxFileName dir = EventGenerator::GetWatchDir();
CPPUNIT_ASSERT(m_watcher->Add(dir, wxFSW_EVENT_ALL));
return true;
}
virtual bool KeepWaiting()
{
// did we receive event already?
if (!tested)
{
// well, let's wait a bit more
wxSleep(WAIT_DURATION);
}
return true;
}
virtual bool AfterWait()
{
// fail if still no events
WX_ASSERT_MESSAGE
(
("No events during %d seconds!", static_cast<int>(WAIT_DURATION)),
tested
);
return true;
}
virtual void OnFileSystemEvent(wxFileSystemWatcherEvent& evt)
{
wxLogDebug("--- %s ---", evt.ToString());
m_lastEvent = wxDynamicCast(evt.Clone(), wxFileSystemWatcherEvent);
m_events.Add(m_lastEvent);
// test finished
SendIdle();
tested = true;
}
virtual void CheckResult()
{
CPPUNIT_ASSERT_MESSAGE( "No events received", !m_events.empty() );
const wxFileSystemWatcherEvent * const e = m_events.front();
WX_ASSERT_EQUAL_MESSAGE
(
(
"Extra events received, first is of type %x, for path=\"%s\","
"last is of type %x, path=\"%s\"",
e->GetChangeType(),
e->GetPath().GetFullPath(),
m_events.back()->GetChangeType(),
m_events.back()->GetPath().GetFullPath()
),
1, m_events.size()
);
// this is our "reference event"
const wxFileSystemWatcherEvent expected = ExpectedEvent();
CPPUNIT_ASSERT_EQUAL( expected.GetChangeType(), e->GetChangeType() );
CPPUNIT_ASSERT_EQUAL((int)wxEVT_FSWATCHER, e->GetEventType());
// XXX this needs change
CPPUNIT_ASSERT_EQUAL(wxEVT_CATEGORY_UNKNOWN, e->GetEventCategory());
CPPUNIT_ASSERT_EQUAL(expected.GetPath(), e->GetPath());
CPPUNIT_ASSERT_EQUAL(expected.GetNewPath(), e->GetNewPath());
}
virtual void GenerateEvent() = 0;
virtual wxFileSystemWatcherEvent ExpectedEvent() = 0;
protected:
EventGenerator& eg;
wxEventLoopBase* m_loop; // loop reference
int m_count; // idle events count
wxFileSystemWatcher* m_watcher;
bool tested; // indicates, whether we have already passed the test
#include "wx/arrimpl.cpp"
WX_DEFINE_ARRAY_PTR(wxFileSystemWatcherEvent*, wxArrayEvent);
wxArrayEvent m_events;
wxFileSystemWatcherEvent* m_lastEvent;
};
// ----------------------------------------------------------------------------
// test class
// ----------------------------------------------------------------------------
class FileSystemWatcherTestCase : public CppUnit::TestCase
{
public:
FileSystemWatcherTestCase() { }
virtual void setUp();
virtual void tearDown();
protected:
wxEventLoopBase* m_loop;
private:
CPPUNIT_TEST_SUITE( FileSystemWatcherTestCase );
CPPUNIT_TEST( TestEventCreate );
CPPUNIT_TEST( TestEventDelete );
// kqueue-based implementation doesn't collapse create/delete pairs in
// renames and doesn't detect neither modifications nor access to the
// files reliably currently so disable these tests
//
// FIXME: fix the code and reenable them
#ifndef wxHAS_KQUEUE
CPPUNIT_TEST( TestEventRename );
CPPUNIT_TEST( TestEventModify );
// MSW implementation doesn't detect file access events currently
#ifndef __WXMSW__
CPPUNIT_TEST( TestEventAccess );
#endif // __WXMSW__
#endif // !wxHAS_KQUEUE
CPPUNIT_TEST( TestNoEventsAfterRemove );
CPPUNIT_TEST_SUITE_END();
void TestEventCreate();
void TestEventDelete();
void TestEventRename();
void TestEventModify();
void TestEventAccess();
void TestNoEventsAfterRemove();
DECLARE_NO_COPY_CLASS(FileSystemWatcherTestCase)
};
// the test currently hangs under OS X for some reason and this prevents tests
// ran by buildbot from completing so disable it until someone has time to
// debug it
//
// FIXME: debug and fix this!
#ifndef __WXOSX__
// register in the unnamed registry so that these tests are run by default
CPPUNIT_TEST_SUITE_REGISTRATION( FileSystemWatcherTestCase );
#endif
// also include in its own registry so that these tests can be run alone
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( FileSystemWatcherTestCase,
"FileSystemWatcherTestCase" );
void FileSystemWatcherTestCase::setUp()
{
wxLog::AddTraceMask(wxTRACE_FSWATCHER);
EventGenerator::Get().GetWatchDir();
}
void FileSystemWatcherTestCase::tearDown()
{
EventGenerator::Get().RemoveWatchDir();
}
// ----------------------------------------------------------------------------
// TestEventCreate
// ----------------------------------------------------------------------------
void FileSystemWatcherTestCase::TestEventCreate()
{
wxLogDebug("TestEventCreate()");
class EventTester : public EventHandler
{
public:
virtual void GenerateEvent()
{
CPPUNIT_ASSERT(eg.CreateFile());
}
virtual wxFileSystemWatcherEvent ExpectedEvent()
{
wxFileSystemWatcherEvent event(wxFSW_EVENT_CREATE);
event.SetPath(eg.m_file);
event.SetNewPath(eg.m_file);
return event;
}
};
EventTester tester;
wxLogTrace(wxTRACE_FSWATCHER, "TestEventCreate tester created()");
tester.Run();
}
// ----------------------------------------------------------------------------
// TestEventDelete
// ----------------------------------------------------------------------------
void FileSystemWatcherTestCase::TestEventDelete()
{
wxLogDebug("TestEventDelete()");
class EventTester : public EventHandler
{
public:
virtual void GenerateEvent()
{
CPPUNIT_ASSERT(eg.DeleteFile());
}
virtual wxFileSystemWatcherEvent ExpectedEvent()
{
wxFileSystemWatcherEvent event(wxFSW_EVENT_DELETE);
event.SetPath(eg.m_old);
// CHECK maybe new path here could be NULL or sth?
event.SetNewPath(eg.m_old);
return event;
}
};
// we need to create a file now, so we can delete it
EventGenerator::Get().CreateFile();
EventTester tester;
tester.Run();
}
// ----------------------------------------------------------------------------
// TestEventRename
// ----------------------------------------------------------------------------
void FileSystemWatcherTestCase::TestEventRename()
{
wxLogDebug("TestEventRename()");
class EventTester : public EventHandler
{
public:
virtual void GenerateEvent()
{
CPPUNIT_ASSERT(eg.RenameFile());
}
virtual wxFileSystemWatcherEvent ExpectedEvent()
{
wxFileSystemWatcherEvent event(wxFSW_EVENT_RENAME);
event.SetPath(eg.m_old);
event.SetNewPath(eg.m_file);
return event;
}
};
// need a file to rename later
EventGenerator::Get().CreateFile();
EventTester tester;
tester.Run();
}
// ----------------------------------------------------------------------------
// TestEventModify
// ----------------------------------------------------------------------------
void FileSystemWatcherTestCase::TestEventModify()
{
wxLogDebug("TestEventModify()");
class EventTester : public EventHandler
{
public:
virtual void GenerateEvent()
{
CPPUNIT_ASSERT(eg.ModifyFile());
}
virtual wxFileSystemWatcherEvent ExpectedEvent()
{
wxFileSystemWatcherEvent event(wxFSW_EVENT_MODIFY);
event.SetPath(eg.m_file);
event.SetNewPath(eg.m_file);
return event;
}
};
// we need to create a file to modify
EventGenerator::Get().CreateFile();
EventTester tester;
tester.Run();
}
// ----------------------------------------------------------------------------
// TestEventAccess
// ----------------------------------------------------------------------------
void FileSystemWatcherTestCase::TestEventAccess()
{
wxLogDebug("TestEventAccess()");
class EventTester : public EventHandler
{
public:
virtual void GenerateEvent()
{
CPPUNIT_ASSERT(eg.ReadFile());
}
virtual wxFileSystemWatcherEvent ExpectedEvent()
{
wxFileSystemWatcherEvent event(wxFSW_EVENT_ACCESS);
event.SetPath(eg.m_file);
event.SetNewPath(eg.m_file);
return event;
}
};
// we need to create a file to read from it and write sth to it
EventGenerator::Get().CreateFile();
EventGenerator::Get().ModifyFile();
EventTester tester;
tester.Run();
}
namespace
{
// We can't define this class locally inside TestNoEventsAfterRemove() for some
// reason with g++ 4.0 under OS X 10.5, it results in the following mysterious
// error:
//
// /var/tmp//ccTkNCkc.s:unknown:Non-global symbol:
// __ZThn80_ZN25FileSystemWatcherTestCase23TestNoEventsAfterRemoveEvEN11EventTester6NotifyEv.eh
// can't be a weak_definition
//
// So define this class outside the function instead.
class NoEventsAfterRemoveEventTester : public EventHandler,
public wxTimer
{
public:
NoEventsAfterRemoveEventTester()
{
// We need to use an inactivity timer as we never get any file
// system events in this test, so we consider that the test is
// finished when this 1s timeout expires instead of, as usual,
// stopping after getting the file system events.
Start(1000, true);
}
virtual void GenerateEvent()
{
m_watcher->Remove(EventGenerator::GetWatchDir());
CPPUNIT_ASSERT(eg.CreateFile());
}
virtual void CheckResult()
{
CPPUNIT_ASSERT( m_events.empty() );
}
virtual wxFileSystemWatcherEvent ExpectedEvent()
{
CPPUNIT_FAIL( "Shouldn't be called" );
return wxFileSystemWatcherEvent(wxFSW_EVENT_ERROR);
}
virtual void Notify()
{
SendIdle();
}
};
} // anonymous namespace
void FileSystemWatcherTestCase::TestNoEventsAfterRemove()
{
NoEventsAfterRemoveEventTester tester;
tester.Run();
}