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