1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: tests/fswatcher/fswatchertest.cpp
3 // Purpose: wxFileSystemWatcher unit test
4 // Author: Bartosz Bekier
7 // Copyright: (c) 2009 Bartosz Bekier
8 ///////////////////////////////////////////////////////////////////////////////
10 // ----------------------------------------------------------------------------
12 // ----------------------------------------------------------------------------
24 #include "wx/evtloop.h"
25 #include "wx/filename.h"
26 #include "wx/filefn.h"
27 #include "wx/stdpaths.h"
28 #include "wx/fswatcher.h"
32 // ----------------------------------------------------------------------------
34 // ----------------------------------------------------------------------------
36 // class generating file system events
40 static EventGenerator
& Get()
43 ms_instance
= new EventGenerator(GetWatchDir());
48 EventGenerator(const wxFileName
& path
) : m_base(path
)
51 m_file
= RandomName();
58 wxFile
file(m_file
.GetFullPath(), wxFile::write
);
59 return file
.IsOpened() && m_file
.FileExists();
64 CPPUNIT_ASSERT(m_file
.FileExists());
66 wxLogDebug("Renaming %s=>%s", m_file
.GetFullPath(), m_new
.GetFullPath());
68 bool ret
= wxRenameFile(m_file
.GetFullPath(), m_new
.GetFullPath());
81 CPPUNIT_ASSERT(m_file
.FileExists());
83 bool ret
= wxRemoveFile(m_file
.GetFullPath());
96 return m_file
.Touch();
101 wxFile
f(m_file
.GetFullPath());
102 CPPUNIT_ASSERT(f
.IsOpened());
105 ssize_t count
= f
.Read(buf
, sizeof(buf
));
106 CPPUNIT_ASSERT(count
> 0);
113 CPPUNIT_ASSERT(m_file
.FileExists());
115 wxFile
file(m_file
.GetFullPath(), wxFile::write_append
);
116 CPPUNIT_ASSERT(file
.IsOpened());
118 CPPUNIT_ASSERT(file
.Write("Words of Wisdom, Lloyd. Words of wisdom\n"));
123 wxFileName
RandomName(int length
= 10)
125 return RandomName(m_base
, length
);
129 static const wxFileName
& GetWatchDir()
131 static wxFileName dir
;
136 wxString tmp
= wxStandardPaths::Get().GetTempDir();
139 // XXX look for more unique name? there is no function to generate
140 // unique filename, the file always get created...
141 dir
.AppendDir("fswatcher_test");
142 CPPUNIT_ASSERT(!dir
.DirExists());
143 CPPUNIT_ASSERT(dir
.Mkdir());
148 static void RemoveWatchDir()
150 wxFileName dir
= GetWatchDir();
151 CPPUNIT_ASSERT(dir
.DirExists());
153 // just to be really sure we know what we remove
154 CPPUNIT_ASSERT_EQUAL( "fswatcher_test", dir
.GetDirs().Last() );
156 // FIXME-VC6: using non-static Rmdir() results in ICE
157 CPPUNIT_ASSERT( wxFileName::Rmdir(dir
.GetFullPath(), wxPATH_RMDIR_RECURSIVE
) );
160 static wxFileName
RandomName(const wxFileName
& base
, int length
= 10)
162 static int ALFA_CNT
= 'z' - 'a';
165 for (int i
= 0 ; i
< length
; ++i
)
167 char c
= 'a' + (rand() % ALFA_CNT
);
171 return wxFileName(base
.GetFullPath(), s
);
175 wxFileName m_base
; // base dir for doing operations
176 wxFileName m_file
; // current file name
177 wxFileName m_old
; // previous file name
178 wxFileName m_new
; // name after renaming
181 static EventGenerator
* ms_instance
;
184 EventGenerator
* EventGenerator::ms_instance
= 0;
187 // custom event handler
188 class EventHandler
: public wxEvtHandler
191 enum { WAIT_DURATION
= 3 };
194 eg(EventGenerator::Get()), m_loop(0), m_count(0), m_watcher(0)
196 m_loop
= new wxEventLoop();
197 Connect(wxEVT_IDLE
, wxIdleEventHandler(EventHandler::OnIdle
));
198 Connect(wxEVT_FSWATCHER
, wxFileSystemWatcherEventHandler(
199 EventHandler::OnFileSystemEvent
));
202 virtual ~EventHandler()
207 if (m_loop
->IsRunning())
218 // sends idle event, so we get called in a moment
221 wxIdleEvent
* e
= new wxIdleEvent();
231 void OnIdle(wxIdleEvent
& /*evt*/)
233 bool more
= Action();
242 // returns whether we should produce more idle events
243 virtual bool Action()
248 CPPUNIT_ASSERT(Init());
259 // TODO a mechanism that will break the loop in case we
260 // don't receive a file system event
261 // this below doesn't quite work, so all tests must pass :-)
266 CPPUNIT_ASSERT(KeepWaiting());
272 CPPUNIT_ASSERT(AfterWait());
275 } // switch (m_count)
282 // test we're good to go
283 CPPUNIT_ASSERT(wxEventLoopBase::GetActive());
285 // XXX only now can we construct Watcher, because we need
287 m_watcher
= new wxFileSystemWatcher();
288 m_watcher
->SetOwner(this);
290 // add dir to be watched
291 wxFileName dir
= EventGenerator::GetWatchDir();
292 CPPUNIT_ASSERT(m_watcher
->Add(dir
, wxFSW_EVENT_ALL
));
297 virtual bool KeepWaiting()
299 // did we receive event already?
302 // well, let's wait a bit more
303 wxSleep(WAIT_DURATION
);
309 virtual bool AfterWait()
311 // fail if still no events
314 ("No events during %d seconds!", static_cast<int>(WAIT_DURATION
)),
321 virtual void OnFileSystemEvent(wxFileSystemWatcherEvent
& evt
)
323 wxLogDebug("--- %s ---", evt
.ToString());
324 m_lastEvent
= wxDynamicCast(evt
.Clone(), wxFileSystemWatcherEvent
);
325 m_events
.Add(m_lastEvent
);
332 virtual void CheckResult()
334 CPPUNIT_ASSERT_MESSAGE( "No events received", !m_events
.empty() );
336 const wxFileSystemWatcherEvent
* const e
= m_events
.front();
338 // this is our "reference event"
339 const wxFileSystemWatcherEvent expected
= ExpectedEvent();
341 CPPUNIT_ASSERT_EQUAL( expected
.GetChangeType(), e
->GetChangeType() );
343 CPPUNIT_ASSERT_EQUAL((int)wxEVT_FSWATCHER
, e
->GetEventType());
345 // XXX this needs change
346 CPPUNIT_ASSERT_EQUAL(wxEVT_CATEGORY_UNKNOWN
, e
->GetEventCategory());
348 CPPUNIT_ASSERT_EQUAL(expected
.GetPath(), e
->GetPath());
349 CPPUNIT_ASSERT_EQUAL(expected
.GetNewPath(), e
->GetNewPath());
351 // Under MSW extra modification events are sometimes reported after a
352 // rename and we just can't get rid of them, so ignore them in this
353 // test if they do happen.
354 if ( e
->GetChangeType() == wxFSW_EVENT_RENAME
&&
355 m_events
.size() == 2 )
357 const wxFileSystemWatcherEvent
* const e2
= m_events
.back();
358 if ( e2
->GetChangeType() == wxFSW_EVENT_MODIFY
&&
359 e2
->GetPath() == e
->GetNewPath() )
361 // This is a modify event for the new file, ignore it.
366 WX_ASSERT_EQUAL_MESSAGE
369 "Extra events received, last one is of type %x, path=\"%s\" "
370 "(the original event was for \"%s\" (\"%s\")",
371 m_events
.back()->GetChangeType(),
372 m_events
.back()->GetPath().GetFullPath(),
373 e
->GetPath().GetFullPath(),
374 e
->GetNewPath().GetFullPath()
381 virtual void GenerateEvent() = 0;
383 virtual wxFileSystemWatcherEvent
ExpectedEvent() = 0;
388 wxEventLoopBase
* m_loop
; // loop reference
389 int m_count
; // idle events count
391 wxFileSystemWatcher
* m_watcher
;
392 bool tested
; // indicates, whether we have already passed the test
394 #include "wx/arrimpl.cpp"
395 WX_DEFINE_ARRAY_PTR(wxFileSystemWatcherEvent
*, wxArrayEvent
);
396 wxArrayEvent m_events
;
397 wxFileSystemWatcherEvent
* m_lastEvent
;
401 // ----------------------------------------------------------------------------
403 // ----------------------------------------------------------------------------
405 class FileSystemWatcherTestCase
: public CppUnit::TestCase
408 FileSystemWatcherTestCase() { }
410 virtual void setUp();
411 virtual void tearDown();
414 wxEventLoopBase
* m_loop
;
417 CPPUNIT_TEST_SUITE( FileSystemWatcherTestCase
);
418 CPPUNIT_TEST( TestEventCreate
);
419 CPPUNIT_TEST( TestEventDelete
);
421 // FIXME: Currently this test fails under Windows.
423 CPPUNIT_TEST( TestTrees
);
424 #endif // __WINDOWS__
426 // kqueue-based implementation doesn't collapse create/delete pairs in
427 // renames and doesn't detect neither modifications nor access to the
428 // files reliably currently so disable these tests
430 // FIXME: fix the code and reenable them
432 CPPUNIT_TEST( TestEventRename
);
433 CPPUNIT_TEST( TestEventModify
);
435 // MSW implementation doesn't detect file access events currently
437 CPPUNIT_TEST( TestEventAccess
);
438 #endif // __WINDOWS__
439 #endif // !wxHAS_KQUEUE
441 CPPUNIT_TEST( TestNoEventsAfterRemove
);
442 CPPUNIT_TEST_SUITE_END();
444 void TestEventCreate();
445 void TestEventDelete();
446 void TestEventRename();
447 void TestEventModify();
448 void TestEventAccess();
451 #endif // __WINDOWS__
453 void TestNoEventsAfterRemove();
455 DECLARE_NO_COPY_CLASS(FileSystemWatcherTestCase
)
458 // the test currently hangs under OS X for some reason and this prevents tests
459 // ran by buildbot from completing so disable it until someone has time to
462 // FIXME: debug and fix this!
464 // register in the unnamed registry so that these tests are run by default
465 CPPUNIT_TEST_SUITE_REGISTRATION( FileSystemWatcherTestCase
);
468 // also include in its own registry so that these tests can be run alone
469 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( FileSystemWatcherTestCase
,
470 "FileSystemWatcherTestCase" );
472 void FileSystemWatcherTestCase::setUp()
474 wxLog::AddTraceMask(wxTRACE_FSWATCHER
);
475 EventGenerator::Get().GetWatchDir();
478 void FileSystemWatcherTestCase::tearDown()
480 EventGenerator::Get().RemoveWatchDir();
483 // ----------------------------------------------------------------------------
485 // ----------------------------------------------------------------------------
486 void FileSystemWatcherTestCase::TestEventCreate()
488 wxLogDebug("TestEventCreate()");
490 class EventTester
: public EventHandler
493 virtual void GenerateEvent()
495 CPPUNIT_ASSERT(eg
.CreateFile());
498 virtual wxFileSystemWatcherEvent
ExpectedEvent()
500 wxFileSystemWatcherEvent
event(wxFSW_EVENT_CREATE
);
501 event
.SetPath(eg
.m_file
);
502 event
.SetNewPath(eg
.m_file
);
509 wxLogTrace(wxTRACE_FSWATCHER
, "TestEventCreate tester created()");
514 // ----------------------------------------------------------------------------
516 // ----------------------------------------------------------------------------
517 void FileSystemWatcherTestCase::TestEventDelete()
519 wxLogDebug("TestEventDelete()");
521 class EventTester
: public EventHandler
524 virtual void GenerateEvent()
526 CPPUNIT_ASSERT(eg
.DeleteFile());
529 virtual wxFileSystemWatcherEvent
ExpectedEvent()
531 wxFileSystemWatcherEvent
event(wxFSW_EVENT_DELETE
);
532 event
.SetPath(eg
.m_old
);
534 // CHECK maybe new path here could be NULL or sth?
535 event
.SetNewPath(eg
.m_old
);
540 // we need to create a file now, so we can delete it
541 EventGenerator::Get().CreateFile();
547 // ----------------------------------------------------------------------------
549 // ----------------------------------------------------------------------------
550 void FileSystemWatcherTestCase::TestEventRename()
552 wxLogDebug("TestEventRename()");
554 class EventTester
: public EventHandler
557 virtual void GenerateEvent()
559 CPPUNIT_ASSERT(eg
.RenameFile());
562 virtual wxFileSystemWatcherEvent
ExpectedEvent()
564 wxFileSystemWatcherEvent
event(wxFSW_EVENT_RENAME
);
565 event
.SetPath(eg
.m_old
);
566 event
.SetNewPath(eg
.m_file
);
571 // need a file to rename later
572 EventGenerator::Get().CreateFile();
578 // ----------------------------------------------------------------------------
580 // ----------------------------------------------------------------------------
581 void FileSystemWatcherTestCase::TestEventModify()
583 wxLogDebug("TestEventModify()");
585 class EventTester
: public EventHandler
588 virtual void GenerateEvent()
590 CPPUNIT_ASSERT(eg
.ModifyFile());
593 virtual wxFileSystemWatcherEvent
ExpectedEvent()
595 wxFileSystemWatcherEvent
event(wxFSW_EVENT_MODIFY
);
596 event
.SetPath(eg
.m_file
);
597 event
.SetNewPath(eg
.m_file
);
602 // we need to create a file to modify
603 EventGenerator::Get().CreateFile();
609 // ----------------------------------------------------------------------------
611 // ----------------------------------------------------------------------------
612 void FileSystemWatcherTestCase::TestEventAccess()
614 wxLogDebug("TestEventAccess()");
616 class EventTester
: public EventHandler
619 virtual void GenerateEvent()
621 CPPUNIT_ASSERT(eg
.ReadFile());
624 virtual wxFileSystemWatcherEvent
ExpectedEvent()
626 wxFileSystemWatcherEvent
event(wxFSW_EVENT_ACCESS
);
627 event
.SetPath(eg
.m_file
);
628 event
.SetNewPath(eg
.m_file
);
633 // we need to create a file to read from it and write sth to it
634 EventGenerator::Get().CreateFile();
635 EventGenerator::Get().ModifyFile();
641 // ----------------------------------------------------------------------------
643 // ----------------------------------------------------------------------------
645 void FileSystemWatcherTestCase::TestTrees()
647 class TreeTester
: public EventHandler
649 const size_t subdirs
;
653 TreeTester() : subdirs(5), files(3) {}
655 void GrowTree(wxFileName dir
)
657 CPPUNIT_ASSERT(dir
.Mkdir());
659 // Create a branch of 5 numbered subdirs, each containing 3
661 for ( unsigned d
= 0; d
< subdirs
; ++d
)
663 dir
.AppendDir(wxString::Format("subdir%u", d
+1));
664 CPPUNIT_ASSERT(dir
.Mkdir());
666 const wxString prefix
= dir
.GetPathWithSep();
667 for ( unsigned f
= 0; f
< files
; ++f
)
669 // Just create the files.
670 wxFile(prefix
+ wxString::Format("file%u", f
+1),
676 void RmDir(wxFileName dir
)
678 CPPUNIT_ASSERT(dir
.DirExists());
680 CPPUNIT_ASSERT(dir
.Rmdir(wxPATH_RMDIR_RECURSIVE
));
683 void WatchDir(wxFileName dir
)
685 CPPUNIT_ASSERT(m_watcher
);
687 // Store the initial count; there may already be some watches
688 const int initial
= m_watcher
->GetWatchedPathsCount();
691 CPPUNIT_ASSERT_EQUAL(initial
+ 1,
692 m_watcher
->GetWatchedPathsCount());
695 void RemoveSingleWatch(wxFileName dir
)
697 CPPUNIT_ASSERT(m_watcher
);
699 const int initial
= m_watcher
->GetWatchedPathsCount();
701 m_watcher
->Remove(dir
);
702 CPPUNIT_ASSERT_EQUAL(initial
- 1,
703 m_watcher
->GetWatchedPathsCount());
706 void WatchTree(const wxFileName
& dir
)
708 CPPUNIT_ASSERT(m_watcher
);
711 treeitems
= (subdirs
*files
) + subdirs
+ 1; // +1 for the trunk
713 // Store the initial count; there may already be some watches
714 const int initial
= m_watcher
->GetWatchedPathsCount();
718 m_watcher
->AddTree(dir
);
719 const int plustree
= m_watcher
->GetWatchedPathsCount();
721 CPPUNIT_ASSERT_EQUAL(initial
+ treeitems
, plustree
);
723 m_watcher
->RemoveTree(dir
);
724 CPPUNIT_ASSERT_EQUAL(initial
, m_watcher
->GetWatchedPathsCount());
727 void RemoveAllWatches()
729 CPPUNIT_ASSERT(m_watcher
);
731 m_watcher
->RemoveAll();
732 CPPUNIT_ASSERT_EQUAL(0, m_watcher
->GetWatchedPathsCount());
735 virtual void GenerateEvent()
737 // We don't use this function for events. Just run the tests
739 wxFileName watchdir
= EventGenerator::GetWatchDir();
740 CPPUNIT_ASSERT(watchdir
.DirExists());
742 wxFileName
treedir(watchdir
);
743 treedir
.AppendDir("treetrunk");
744 CPPUNIT_ASSERT(!treedir
.DirExists());
746 wxFileName
singledir(watchdir
);
747 singledir
.AppendDir("single");
748 CPPUNIT_ASSERT(!singledir
.DirExists());
749 CPPUNIT_ASSERT(singledir
.Mkdir());
754 RemoveSingleWatch(singledir
);
755 // Add it back again, ready to test RemoveAll()
767 virtual wxFileSystemWatcherEvent
ExpectedEvent()
769 CPPUNIT_FAIL("Shouldn't be called");
771 return wxFileSystemWatcherEvent(wxFSW_EVENT_ERROR
);
774 virtual void CheckResult()
776 // Do nothing. We override this to prevent receiving events in
784 #endif // __WINDOWS__
789 // We can't define this class locally inside TestNoEventsAfterRemove() for some
790 // reason with g++ 4.0 under OS X 10.5, it results in the following mysterious
793 // /var/tmp//ccTkNCkc.s:unknown:Non-global symbol:
794 // __ZThn80_ZN25FileSystemWatcherTestCase23TestNoEventsAfterRemoveEvEN11EventTester6NotifyEv.eh
795 // can't be a weak_definition
797 // So define this class outside the function instead.
798 class NoEventsAfterRemoveEventTester
: public EventHandler
,
802 NoEventsAfterRemoveEventTester()
804 // We need to use an inactivity timer as we never get any file
805 // system events in this test, so we consider that the test is
806 // finished when this 1s timeout expires instead of, as usual,
807 // stopping after getting the file system events.
811 virtual void GenerateEvent()
813 m_watcher
->Remove(EventGenerator::GetWatchDir());
814 CPPUNIT_ASSERT(eg
.CreateFile());
817 virtual void CheckResult()
819 CPPUNIT_ASSERT( m_events
.empty() );
822 virtual wxFileSystemWatcherEvent
ExpectedEvent()
824 CPPUNIT_FAIL( "Shouldn't be called" );
826 return wxFileSystemWatcherEvent(wxFSW_EVENT_ERROR
);
829 virtual void Notify()
835 } // anonymous namespace
837 void FileSystemWatcherTestCase::TestNoEventsAfterRemove()
839 NoEventsAfterRemoveEventTester tester
;