Remove all lines containing cvs/svn "$Id$" keyword.
[wxWidgets.git] / tests / fswatcher / fswatchertest.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: tests/fswatcher/fswatchertest.cpp
3 // Purpose: wxFileSystemWatcher unit test
4 // Author: Bartosz Bekier
5 // Created: 2009-06-11
6 // Copyright: (c) 2009 Bartosz Bekier
7 ///////////////////////////////////////////////////////////////////////////////
8
9 // ----------------------------------------------------------------------------
10 // headers
11 // ----------------------------------------------------------------------------
12
13 #include "testprec.h"
14
15 #ifdef __BORLANDC__
16 #pragma hdrstop
17 #endif
18
19 #ifndef WX_PRECOMP
20 #include "wx/timer.h"
21 #endif
22
23 #include "wx/evtloop.h"
24 #include "wx/filename.h"
25 #include "wx/filefn.h"
26 #include "wx/stdpaths.h"
27 #include "wx/fswatcher.h"
28
29 #include "testfile.h"
30
31 // ----------------------------------------------------------------------------
32 // local functions
33 // ----------------------------------------------------------------------------
34
35 // class generating file system events
36 class EventGenerator
37 {
38 public:
39 static EventGenerator& Get()
40 {
41 if (!ms_instance)
42 ms_instance = new EventGenerator(GetWatchDir());
43
44 return *ms_instance;
45 }
46
47 EventGenerator(const wxFileName& path) : m_base(path)
48 {
49 m_old = wxFileName();
50 m_file = RandomName();
51 m_new = RandomName();
52 }
53
54 // operations
55 bool CreateFile()
56 {
57 wxFile file(m_file.GetFullPath(), wxFile::write);
58 return file.IsOpened() && m_file.FileExists();
59 }
60
61 bool RenameFile()
62 {
63 CPPUNIT_ASSERT(m_file.FileExists());
64
65 wxLogDebug("Renaming %s=>%s", m_file.GetFullPath(), m_new.GetFullPath());
66
67 bool ret = wxRenameFile(m_file.GetFullPath(), m_new.GetFullPath());
68 if (ret)
69 {
70 m_old = m_file;
71 m_file = m_new;
72 m_new = RandomName();
73 }
74
75 return ret;
76 }
77
78 bool DeleteFile()
79 {
80 CPPUNIT_ASSERT(m_file.FileExists());
81
82 bool ret = wxRemoveFile(m_file.GetFullPath());
83 if (ret)
84 {
85 m_old = m_file;
86 m_file = m_new;
87 m_new = RandomName();
88 }
89
90 return ret;
91 }
92
93 bool TouchFile()
94 {
95 return m_file.Touch();
96 }
97
98 bool ReadFile()
99 {
100 wxFile f(m_file.GetFullPath());
101 CPPUNIT_ASSERT(f.IsOpened());
102
103 char buf[1];
104 ssize_t count = f.Read(buf, sizeof(buf));
105 CPPUNIT_ASSERT(count > 0);
106
107 return true;
108 }
109
110 bool ModifyFile()
111 {
112 CPPUNIT_ASSERT(m_file.FileExists());
113
114 wxFile file(m_file.GetFullPath(), wxFile::write_append);
115 CPPUNIT_ASSERT(file.IsOpened());
116
117 CPPUNIT_ASSERT(file.Write("Words of Wisdom, Lloyd. Words of wisdom\n"));
118 return file.Close();
119 }
120
121 // helpers
122 wxFileName RandomName(int length = 10)
123 {
124 return RandomName(m_base, length);
125 }
126
127 // static helpers
128 static const wxFileName& GetWatchDir()
129 {
130 static wxFileName dir;
131
132 if (dir.DirExists())
133 return dir;
134
135 wxString tmp = wxStandardPaths::Get().GetTempDir();
136 dir.AssignDir(tmp);
137
138 // XXX look for more unique name? there is no function to generate
139 // unique filename, the file always get created...
140 dir.AppendDir("fswatcher_test");
141 CPPUNIT_ASSERT(!dir.DirExists());
142 CPPUNIT_ASSERT(dir.Mkdir());
143
144 return dir;
145 }
146
147 static void RemoveWatchDir()
148 {
149 wxFileName dir = GetWatchDir();
150 CPPUNIT_ASSERT(dir.DirExists());
151
152 // just to be really sure we know what we remove
153 CPPUNIT_ASSERT_EQUAL( "fswatcher_test", dir.GetDirs().Last() );
154
155 // FIXME-VC6: using non-static Rmdir() results in ICE
156 CPPUNIT_ASSERT( wxFileName::Rmdir(dir.GetFullPath(), wxPATH_RMDIR_RECURSIVE) );
157 }
158
159 static wxFileName RandomName(const wxFileName& base, int length = 10)
160 {
161 static int ALFA_CNT = 'z' - 'a';
162
163 wxString s;
164 for (int i = 0 ; i < length; ++i)
165 {
166 char c = 'a' + (rand() % ALFA_CNT);
167 s += c;
168 }
169
170 return wxFileName(base.GetFullPath(), s);
171 }
172
173 public:
174 wxFileName m_base; // base dir for doing operations
175 wxFileName m_file; // current file name
176 wxFileName m_old; // previous file name
177 wxFileName m_new; // name after renaming
178
179 protected:
180 static EventGenerator* ms_instance;
181 };
182
183 EventGenerator* EventGenerator::ms_instance = 0;
184
185
186 // custom event handler
187 class EventHandler : public wxEvtHandler
188 {
189 public:
190 enum { WAIT_DURATION = 3 };
191
192 EventHandler(int types = wxFSW_EVENT_ALL) :
193 eg(EventGenerator::Get()), m_loop(0), m_count(0), m_watcher(0),
194 m_eventTypes(types)
195 {
196 m_loop = new wxEventLoop();
197 Connect(wxEVT_IDLE, wxIdleEventHandler(EventHandler::OnIdle));
198 Connect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(
199 EventHandler::OnFileSystemEvent));
200 }
201
202 virtual ~EventHandler()
203 {
204 delete m_watcher;
205 if (m_loop)
206 {
207 if (m_loop->IsRunning())
208 m_loop->Exit();
209 delete m_loop;
210 }
211 }
212
213 void Exit()
214 {
215 m_loop->Exit();
216 }
217
218 // sends idle event, so we get called in a moment
219 void SendIdle()
220 {
221 wxIdleEvent* e = new wxIdleEvent();
222 QueueEvent(e);
223 }
224
225 void Run()
226 {
227 SendIdle();
228 m_loop->Run();
229 }
230
231 void OnIdle(wxIdleEvent& /*evt*/)
232 {
233 bool more = Action();
234 m_count++;
235
236 if (more)
237 {
238 SendIdle();
239 }
240 }
241
242 // returns whether we should produce more idle events
243 virtual bool Action()
244 {
245 switch (m_count)
246 {
247 case 0:
248 CPPUNIT_ASSERT(Init());
249 break;
250 case 1:
251 GenerateEvent();
252 break;
253 case 2:
254 // actual test
255 CheckResult();
256 Exit();
257 break;
258
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 :-)
262 #if 0
263 case 2:
264 m_loop.Yield();
265 m_loop.WakeUp();
266 CPPUNIT_ASSERT(KeepWaiting());
267 m_loop.Yield();
268 break;
269 case 3:
270 break;
271 case 4:
272 CPPUNIT_ASSERT(AfterWait());
273 break;
274 #endif
275 } // switch (m_count)
276
277 return m_count <= 0;
278 }
279
280 virtual bool Init()
281 {
282 // test we're good to go
283 CPPUNIT_ASSERT(wxEventLoopBase::GetActive());
284
285 // XXX only now can we construct Watcher, because we need
286 // active loop here
287 m_watcher = new wxFileSystemWatcher();
288 m_watcher->SetOwner(this);
289
290 // add dir to be watched
291 wxFileName dir = EventGenerator::GetWatchDir();
292 CPPUNIT_ASSERT(m_watcher->Add(dir, m_eventTypes));
293
294 return true;
295 }
296
297 virtual bool KeepWaiting()
298 {
299 // did we receive event already?
300 if (!tested)
301 {
302 // well, let's wait a bit more
303 wxSleep(WAIT_DURATION);
304 }
305
306 return true;
307 }
308
309 virtual bool AfterWait()
310 {
311 // fail if still no events
312 WX_ASSERT_MESSAGE
313 (
314 ("No events during %d seconds!", static_cast<int>(WAIT_DURATION)),
315 tested
316 );
317
318 return true;
319 }
320
321 virtual void OnFileSystemEvent(wxFileSystemWatcherEvent& evt)
322 {
323 wxLogDebug("--- %s ---", evt.ToString());
324 m_lastEvent = wxDynamicCast(evt.Clone(), wxFileSystemWatcherEvent);
325 m_events.Add(m_lastEvent);
326
327 // test finished
328 SendIdle();
329 tested = true;
330 }
331
332 virtual void CheckResult()
333 {
334 CPPUNIT_ASSERT_MESSAGE( "No events received", !m_events.empty() );
335
336 const wxFileSystemWatcherEvent * const e = m_events.front();
337
338 // this is our "reference event"
339 const wxFileSystemWatcherEvent expected = ExpectedEvent();
340
341 CPPUNIT_ASSERT_EQUAL( expected.GetChangeType(), e->GetChangeType() );
342
343 CPPUNIT_ASSERT_EQUAL((int)wxEVT_FSWATCHER, e->GetEventType());
344
345 // XXX this needs change
346 CPPUNIT_ASSERT_EQUAL(wxEVT_CATEGORY_UNKNOWN, e->GetEventCategory());
347
348 CPPUNIT_ASSERT_EQUAL(expected.GetPath(), e->GetPath());
349 CPPUNIT_ASSERT_EQUAL(expected.GetNewPath(), e->GetNewPath());
350
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 )
356 {
357 const wxFileSystemWatcherEvent* const e2 = m_events.back();
358 if ( e2->GetChangeType() == wxFSW_EVENT_MODIFY &&
359 e2->GetPath() == e->GetNewPath() )
360 {
361 // This is a modify event for the new file, ignore it.
362 return;
363 }
364 }
365
366 WX_ASSERT_EQUAL_MESSAGE
367 (
368 (
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()
375 ),
376 1, m_events.size()
377 );
378
379 }
380
381 virtual void GenerateEvent() = 0;
382
383 virtual wxFileSystemWatcherEvent ExpectedEvent() = 0;
384
385
386 protected:
387 EventGenerator& eg;
388 wxEventLoopBase* m_loop; // loop reference
389 int m_count; // idle events count
390
391 wxFileSystemWatcher* m_watcher;
392 int m_eventTypes; // Which event-types to watch. Normally all of them
393 bool tested; // indicates, whether we have already passed the test
394
395 #include "wx/arrimpl.cpp"
396 WX_DEFINE_ARRAY_PTR(wxFileSystemWatcherEvent*, wxArrayEvent);
397 wxArrayEvent m_events;
398 wxFileSystemWatcherEvent* m_lastEvent;
399 };
400
401
402 // ----------------------------------------------------------------------------
403 // test class
404 // ----------------------------------------------------------------------------
405
406 class FileSystemWatcherTestCase : public CppUnit::TestCase
407 {
408 public:
409 FileSystemWatcherTestCase() { }
410
411 virtual void setUp();
412 virtual void tearDown();
413
414 protected:
415 wxEventLoopBase* m_loop;
416
417 private:
418 CPPUNIT_TEST_SUITE( FileSystemWatcherTestCase );
419 CPPUNIT_TEST( TestEventCreate );
420 CPPUNIT_TEST( TestEventDelete );
421 #if !defined(__VISUALC__) || wxCHECK_VISUALC_VERSION(7)
422 CPPUNIT_TEST( TestTrees );
423 #endif
424
425 // kqueue-based implementation doesn't collapse create/delete pairs in
426 // renames and doesn't detect neither modifications nor access to the
427 // files reliably currently so disable these tests
428 //
429 // FIXME: fix the code and reenable them
430 #ifndef wxHAS_KQUEUE
431 CPPUNIT_TEST( TestEventRename );
432 CPPUNIT_TEST( TestEventModify );
433
434 // MSW implementation doesn't detect file access events currently
435 #ifndef __WINDOWS__
436 CPPUNIT_TEST( TestEventAccess );
437 #endif // __WINDOWS__
438 #endif // !wxHAS_KQUEUE
439
440 #ifdef wxHAS_INOTIFY
441 CPPUNIT_TEST( TestEventAttribute );
442 CPPUNIT_TEST( TestSingleWatchtypeEvent );
443 #endif // wxHAS_INOTIFY
444
445 CPPUNIT_TEST( TestNoEventsAfterRemove );
446 CPPUNIT_TEST_SUITE_END();
447
448 void TestEventCreate();
449 void TestEventDelete();
450 void TestEventRename();
451 void TestEventModify();
452 void TestEventAccess();
453 #ifdef wxHAS_INOTIFY
454 void TestEventAttribute();
455 void TestSingleWatchtypeEvent();
456 #endif // wxHAS_INOTIFY
457 #if !defined(__VISUALC__) || wxCHECK_VISUALC_VERSION(7)
458 void TestTrees(); // Visual C++ 6 can't build this
459 #endif
460 void TestNoEventsAfterRemove();
461
462 DECLARE_NO_COPY_CLASS(FileSystemWatcherTestCase)
463 };
464
465 // the test currently hangs under OS X for some reason and this prevents tests
466 // ran by buildbot from completing so disable it until someone has time to
467 // debug it
468 //
469 // FIXME: debug and fix this!
470 #ifndef __WXOSX__
471 // register in the unnamed registry so that these tests are run by default
472 CPPUNIT_TEST_SUITE_REGISTRATION( FileSystemWatcherTestCase );
473 #endif
474
475 // also include in its own registry so that these tests can be run alone
476 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( FileSystemWatcherTestCase,
477 "FileSystemWatcherTestCase" );
478
479 void FileSystemWatcherTestCase::setUp()
480 {
481 wxLog::AddTraceMask(wxTRACE_FSWATCHER);
482 EventGenerator::Get().GetWatchDir();
483 }
484
485 void FileSystemWatcherTestCase::tearDown()
486 {
487 EventGenerator::Get().RemoveWatchDir();
488 }
489
490 // ----------------------------------------------------------------------------
491 // TestEventCreate
492 // ----------------------------------------------------------------------------
493 void FileSystemWatcherTestCase::TestEventCreate()
494 {
495 wxLogDebug("TestEventCreate()");
496
497 class EventTester : public EventHandler
498 {
499 public:
500 virtual void GenerateEvent()
501 {
502 CPPUNIT_ASSERT(eg.CreateFile());
503 }
504
505 virtual wxFileSystemWatcherEvent ExpectedEvent()
506 {
507 wxFileSystemWatcherEvent event(wxFSW_EVENT_CREATE);
508 event.SetPath(eg.m_file);
509 event.SetNewPath(eg.m_file);
510 return event;
511 }
512 };
513
514 EventTester tester;
515
516 wxLogTrace(wxTRACE_FSWATCHER, "TestEventCreate tester created()");
517
518 tester.Run();
519 }
520
521 // ----------------------------------------------------------------------------
522 // TestEventDelete
523 // ----------------------------------------------------------------------------
524 void FileSystemWatcherTestCase::TestEventDelete()
525 {
526 wxLogDebug("TestEventDelete()");
527
528 class EventTester : public EventHandler
529 {
530 public:
531 virtual void GenerateEvent()
532 {
533 CPPUNIT_ASSERT(eg.DeleteFile());
534 }
535
536 virtual wxFileSystemWatcherEvent ExpectedEvent()
537 {
538 wxFileSystemWatcherEvent event(wxFSW_EVENT_DELETE);
539 event.SetPath(eg.m_old);
540
541 // CHECK maybe new path here could be NULL or sth?
542 event.SetNewPath(eg.m_old);
543 return event;
544 }
545 };
546
547 // we need to create a file now, so we can delete it
548 EventGenerator::Get().CreateFile();
549
550 EventTester tester;
551 tester.Run();
552 }
553
554 // ----------------------------------------------------------------------------
555 // TestEventRename
556 // ----------------------------------------------------------------------------
557 void FileSystemWatcherTestCase::TestEventRename()
558 {
559 wxLogDebug("TestEventRename()");
560
561 class EventTester : public EventHandler
562 {
563 public:
564 virtual void GenerateEvent()
565 {
566 CPPUNIT_ASSERT(eg.RenameFile());
567 }
568
569 virtual wxFileSystemWatcherEvent ExpectedEvent()
570 {
571 wxFileSystemWatcherEvent event(wxFSW_EVENT_RENAME);
572 event.SetPath(eg.m_old);
573 event.SetNewPath(eg.m_file);
574 return event;
575 }
576 };
577
578 // need a file to rename later
579 EventGenerator::Get().CreateFile();
580
581 EventTester tester;
582 tester.Run();
583 }
584
585 // ----------------------------------------------------------------------------
586 // TestEventModify
587 // ----------------------------------------------------------------------------
588 void FileSystemWatcherTestCase::TestEventModify()
589 {
590 wxLogDebug("TestEventModify()");
591
592 class EventTester : public EventHandler
593 {
594 public:
595 virtual void GenerateEvent()
596 {
597 CPPUNIT_ASSERT(eg.ModifyFile());
598 }
599
600 virtual wxFileSystemWatcherEvent ExpectedEvent()
601 {
602 wxFileSystemWatcherEvent event(wxFSW_EVENT_MODIFY);
603 event.SetPath(eg.m_file);
604 event.SetNewPath(eg.m_file);
605 return event;
606 }
607 };
608
609 // we need to create a file to modify
610 EventGenerator::Get().CreateFile();
611
612 EventTester tester;
613 tester.Run();
614 }
615
616 // ----------------------------------------------------------------------------
617 // TestEventAccess
618 // ----------------------------------------------------------------------------
619 void FileSystemWatcherTestCase::TestEventAccess()
620 {
621 wxLogDebug("TestEventAccess()");
622
623 class EventTester : public EventHandler
624 {
625 public:
626 virtual void GenerateEvent()
627 {
628 CPPUNIT_ASSERT(eg.ReadFile());
629 }
630
631 virtual wxFileSystemWatcherEvent ExpectedEvent()
632 {
633 wxFileSystemWatcherEvent event(wxFSW_EVENT_ACCESS);
634 event.SetPath(eg.m_file);
635 event.SetNewPath(eg.m_file);
636 return event;
637 }
638 };
639
640 // we need to create a file to read from it and write sth to it
641 EventGenerator::Get().CreateFile();
642 EventGenerator::Get().ModifyFile();
643
644 EventTester tester;
645 tester.Run();
646 }
647
648 #ifdef wxHAS_INOTIFY
649 // ----------------------------------------------------------------------------
650 // TestEventAttribute
651 // ----------------------------------------------------------------------------
652 void FileSystemWatcherTestCase::TestEventAttribute()
653 {
654 wxLogDebug("TestEventAttribute()");
655
656 class EventTester : public EventHandler
657 {
658 public:
659 virtual void GenerateEvent()
660 {
661 CPPUNIT_ASSERT(eg.TouchFile());
662 }
663
664 virtual wxFileSystemWatcherEvent ExpectedEvent()
665 {
666 wxFileSystemWatcherEvent event(wxFSW_EVENT_ATTRIB);
667 event.SetPath(eg.m_file);
668 event.SetNewPath(eg.m_file);
669 return event;
670 }
671 };
672
673 // we need to create a file to touch
674 EventGenerator::Get().CreateFile();
675
676 EventTester tester;
677 tester.Run();
678 }
679
680 // ----------------------------------------------------------------------------
681 // TestSingleWatchtypeEvent: Watch only wxFSW_EVENT_ACCESS
682 // ----------------------------------------------------------------------------
683 void FileSystemWatcherTestCase::TestSingleWatchtypeEvent()
684 {
685 wxLogDebug("TestSingleWatchtypeEvent()");
686
687 class EventTester : public EventHandler
688 {
689 public:
690 // We could pass wxFSW_EVENT_CREATE or MODIFY instead, but not RENAME or
691 // DELETE as the event path fields would be wrong in CheckResult()
692 EventTester() : EventHandler(wxFSW_EVENT_ACCESS) {}
693
694 virtual void GenerateEvent()
695 {
696 // As wxFSW_EVENT_ACCESS is passed to the ctor only ReadFile() will
697 // generate an event. Without it they all will, and the test fails
698 CPPUNIT_ASSERT(eg.CreateFile());
699 CPPUNIT_ASSERT(eg.ModifyFile());
700 CPPUNIT_ASSERT(eg.ReadFile());
701 }
702
703 virtual wxFileSystemWatcherEvent ExpectedEvent()
704 {
705 wxFileSystemWatcherEvent event(wxFSW_EVENT_ACCESS);
706 event.SetPath(eg.m_file);
707 event.SetNewPath(eg.m_file);
708 return event;
709 }
710 };
711
712 EventTester tester;
713 tester.Run();
714 }
715 #endif // wxHAS_INOTIFY
716
717 // ----------------------------------------------------------------------------
718 // TestTrees
719 // ----------------------------------------------------------------------------
720
721 #if !defined(__VISUALC__) || wxCHECK_VISUALC_VERSION(7)
722 void FileSystemWatcherTestCase::TestTrees()
723 {
724 class TreeTester : public EventHandler
725 {
726 const size_t subdirs;
727 const size_t files;
728
729 public:
730 TreeTester() : subdirs(5), files(3) {}
731
732 void GrowTree(wxFileName dir
733 #ifdef __UNIX__
734 , bool withSymlinks = false
735 #endif
736 )
737 {
738 CPPUNIT_ASSERT(dir.Mkdir());
739 // Now add a subdir with an easy name to remember in WatchTree()
740 dir.AppendDir("child");
741 CPPUNIT_ASSERT(dir.Mkdir());
742 wxFileName child(dir); // Create a copy to which to symlink
743
744 // Create a branch of 5 numbered subdirs, each containing 3
745 // numbered files
746 for ( unsigned d = 0; d < subdirs; ++d )
747 {
748 dir.AppendDir(wxString::Format("subdir%u", d+1));
749 CPPUNIT_ASSERT(dir.Mkdir());
750
751 const wxString prefix = dir.GetPathWithSep();
752 const wxString ext[] = { ".txt", ".log", "" };
753 for ( unsigned f = 0; f < files; ++f )
754 {
755 // Just create the files.
756 wxFile(prefix + wxString::Format("file%u", f+1) + ext[f],
757 wxFile::write);
758 }
759 #if defined(__UNIX__)
760 if ( withSymlinks )
761 {
762 // Create a symlink to a files, and another to 'child'
763 CPPUNIT_ASSERT_EQUAL(0,
764 symlink(wxString(prefix + "file1").c_str(),
765 wxString(prefix + "file.lnk").c_str()));
766 CPPUNIT_ASSERT_EQUAL(0,
767 symlink(child.GetFullPath().c_str(),
768 wxString(prefix + "dir.lnk").c_str()));
769 }
770 #endif // __UNIX__
771 }
772 }
773
774 void RmDir(wxFileName dir)
775 {
776 CPPUNIT_ASSERT(dir.DirExists());
777
778 CPPUNIT_ASSERT(dir.Rmdir(wxPATH_RMDIR_RECURSIVE));
779 }
780
781 void WatchDir(wxFileName dir)
782 {
783 CPPUNIT_ASSERT(m_watcher);
784
785 // Store the initial count; there may already be some watches
786 const int initial = m_watcher->GetWatchedPathsCount();
787
788 m_watcher->Add(dir);
789 CPPUNIT_ASSERT_EQUAL(initial + 1,
790 m_watcher->GetWatchedPathsCount());
791 }
792
793 void RemoveSingleWatch(wxFileName dir)
794 {
795 CPPUNIT_ASSERT(m_watcher);
796
797 const int initial = m_watcher->GetWatchedPathsCount();
798
799 m_watcher->Remove(dir);
800 CPPUNIT_ASSERT_EQUAL(initial - 1,
801 m_watcher->GetWatchedPathsCount());
802 }
803
804 void WatchTree(const wxFileName& dir)
805 {
806 CPPUNIT_ASSERT(m_watcher);
807
808 size_t treeitems = 1; // the trunk
809 #ifndef __WINDOWS__
810 // When there's no file mask, wxMSW sets a single watch
811 // on the trunk which is implemented recursively.
812 // wxGTK always sets an additional watch for each subdir
813 treeitems += subdirs + 1; // +1 for 'child'
814 #endif // __WINDOWS__
815
816 // Store the initial count; there may already be some watches
817 const int initial = m_watcher->GetWatchedPathsCount();
818
819 GrowTree(dir);
820
821 m_watcher->AddTree(dir);
822 const int plustree = m_watcher->GetWatchedPathsCount();
823
824 CPPUNIT_ASSERT_EQUAL(initial + treeitems, plustree);
825
826 m_watcher->RemoveTree(dir);
827 CPPUNIT_ASSERT_EQUAL(initial, m_watcher->GetWatchedPathsCount());
828
829 // Now test the refcount mechanism by watching items more than once
830 wxFileName child(dir);
831 child.AppendDir("child");
832 m_watcher->AddTree(child);
833 // Check some watches were added; we don't care about the number
834 CPPUNIT_ASSERT(initial < m_watcher->GetWatchedPathsCount());
835 // Now watch the whole tree and check that the count is the same
836 // as it was the first time, despite also adding 'child' separately
837 // Except that in wxMSW this isn't true: each watch will be a
838 // single, recursive dir; so fudge the count
839 size_t fudge = 0;
840 #ifdef __WINDOWS__
841 fudge = 1;
842 #endif // __WINDOWS__
843 m_watcher->AddTree(dir);
844 CPPUNIT_ASSERT_EQUAL(plustree + fudge, m_watcher->GetWatchedPathsCount());
845 m_watcher->RemoveTree(child);
846 CPPUNIT_ASSERT(initial < m_watcher->GetWatchedPathsCount());
847 m_watcher->RemoveTree(dir);
848 CPPUNIT_ASSERT_EQUAL(initial, m_watcher->GetWatchedPathsCount());
849 #if defined(__UNIX__)
850 // Finally, test a tree containing internal symlinks
851 RmDir(dir);
852 GrowTree(dir, true /* test symlinks */);
853
854 // Without the DontFollowLink() call AddTree() would now assert
855 // (and without the assert, it would infinitely loop)
856 wxFileName fn = dir;
857 fn.DontFollowLink();
858 CPPUNIT_ASSERT(m_watcher->AddTree(fn));
859 CPPUNIT_ASSERT(m_watcher->RemoveTree(fn));
860
861 // Regrow the tree without symlinks, ready for the next test
862 RmDir(dir);
863 GrowTree(dir, false);
864 #endif // __UNIX__
865 }
866
867 void WatchTreeWithFilespec(const wxFileName& dir)
868 {
869 CPPUNIT_ASSERT(m_watcher);
870 CPPUNIT_ASSERT(dir.DirExists()); // Was built in WatchTree()
871
872 // Store the initial count; there may already be some watches
873 const int initial = m_watcher->GetWatchedPathsCount();
874
875 // When we use a filter, both wxMSW and wxGTK implementations set
876 // an additional watch for each subdir (+1 for the root dir itself
877 // and another +1 for "child").
878 const size_t treeitems = subdirs + 2;
879 m_watcher->AddTree(dir, wxFSW_EVENT_ALL, "*.txt");
880
881 const int plustree = m_watcher->GetWatchedPathsCount();
882 CPPUNIT_ASSERT_EQUAL(initial + treeitems, plustree);
883
884 // RemoveTree should try to remove only those files that were added
885 m_watcher->RemoveTree(dir);
886 CPPUNIT_ASSERT_EQUAL(initial, m_watcher->GetWatchedPathsCount());
887 }
888
889 void RemoveAllWatches()
890 {
891 CPPUNIT_ASSERT(m_watcher);
892
893 m_watcher->RemoveAll();
894 CPPUNIT_ASSERT_EQUAL(0, m_watcher->GetWatchedPathsCount());
895 }
896
897 virtual void GenerateEvent()
898 {
899 // We don't use this function for events. Just run the tests
900
901 wxFileName watchdir = EventGenerator::GetWatchDir();
902 CPPUNIT_ASSERT(watchdir.DirExists());
903
904 wxFileName treedir(watchdir);
905 treedir.AppendDir("treetrunk");
906 CPPUNIT_ASSERT(!treedir.DirExists());
907
908 wxFileName singledir(watchdir);
909 singledir.AppendDir("single");
910 CPPUNIT_ASSERT(!singledir.DirExists());
911 CPPUNIT_ASSERT(singledir.Mkdir());
912
913 WatchDir(singledir);
914 WatchTree(treedir);
915 // Now test adding and removing a tree using a filespec
916 // wxMSW uses the generic method to add matching files; which fails
917 // as it doesn't support adding files :/ So disable the test
918 #ifndef __WINDOWS__
919 WatchTreeWithFilespec(treedir);
920 #endif // __WINDOWS__
921
922 RemoveSingleWatch(singledir);
923 // Add it back again, ready to test RemoveAll()
924 WatchDir(singledir);
925
926 RemoveAllWatches();
927
928 // Clean up
929 RmDir(singledir);
930 RmDir(treedir);
931
932 Exit();
933 }
934
935 virtual wxFileSystemWatcherEvent ExpectedEvent()
936 {
937 CPPUNIT_FAIL("Shouldn't be called");
938
939 return wxFileSystemWatcherEvent(wxFSW_EVENT_ERROR);
940 }
941
942 virtual void CheckResult()
943 {
944 // Do nothing. We override this to prevent receiving events in
945 // ExpectedEvent()
946 }
947 };
948
949 TreeTester tester;
950 tester.Run();
951 }
952 #endif // !defined(__VISUALC__) || wxCHECK_VISUALC_VERSION(7)
953
954
955 namespace
956 {
957
958 // We can't define this class locally inside TestNoEventsAfterRemove() for some
959 // reason with g++ 4.0 under OS X 10.5, it results in the following mysterious
960 // error:
961 //
962 // /var/tmp//ccTkNCkc.s:unknown:Non-global symbol:
963 // __ZThn80_ZN25FileSystemWatcherTestCase23TestNoEventsAfterRemoveEvEN11EventTester6NotifyEv.eh
964 // can't be a weak_definition
965 //
966 // So define this class outside the function instead.
967 class NoEventsAfterRemoveEventTester : public EventHandler,
968 public wxTimer
969 {
970 public:
971 NoEventsAfterRemoveEventTester()
972 {
973 // We need to use an inactivity timer as we never get any file
974 // system events in this test, so we consider that the test is
975 // finished when this 1s timeout expires instead of, as usual,
976 // stopping after getting the file system events.
977 Start(1000, true);
978 }
979
980 virtual void GenerateEvent()
981 {
982 m_watcher->Remove(EventGenerator::GetWatchDir());
983 CPPUNIT_ASSERT(eg.CreateFile());
984 }
985
986 virtual void CheckResult()
987 {
988 CPPUNIT_ASSERT( m_events.empty() );
989 }
990
991 virtual wxFileSystemWatcherEvent ExpectedEvent()
992 {
993 CPPUNIT_FAIL( "Shouldn't be called" );
994
995 return wxFileSystemWatcherEvent(wxFSW_EVENT_ERROR);
996 }
997
998 virtual void Notify()
999 {
1000 SendIdle();
1001 }
1002 };
1003
1004 } // anonymous namespace
1005
1006 void FileSystemWatcherTestCase::TestNoEventsAfterRemove()
1007 {
1008 NoEventsAfterRemoveEventTester tester;
1009 tester.Run();
1010 }