Fix wxFileSystemWatcher::Remove() in wxMSW.
[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 // 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
20 #include "wx/evtloop.h"
21 #include "wx/filename.h"
22 #include "wx/filefn.h"
23 #include "wx/stdpaths.h"
24 #include "wx/fswatcher.h"
25
26 #include "testfile.h"
27
28 // ----------------------------------------------------------------------------
29 // local functions
30 // ----------------------------------------------------------------------------
31
32 // class generating file system events
33 class EventGenerator
34 {
35 public:
36 static EventGenerator& Get()
37 {
38 if (!ms_instance)
39 ms_instance = new EventGenerator(GetWatchDir());
40
41 return *ms_instance;
42 }
43
44 EventGenerator(const wxFileName& path) : m_base(path)
45 {
46 m_old = wxFileName();
47 m_file = RandomName();
48 m_new = RandomName();
49 }
50
51 // operations
52 bool CreateFile()
53 {
54 wxFile file(m_file.GetFullPath(), wxFile::write);
55 return file.IsOpened() && m_file.FileExists();
56 }
57
58 bool RenameFile()
59 {
60 CPPUNIT_ASSERT(m_file.FileExists());
61
62 wxLogDebug("Renaming %s=>%s", m_file.GetFullPath(), m_new.GetFullPath());
63
64 bool ret = wxRenameFile(m_file.GetFullPath(), m_new.GetFullPath());
65 if (ret)
66 {
67 m_old = m_file;
68 m_file = m_new;
69 m_new = RandomName();
70 }
71
72 return ret;
73 }
74
75 bool DeleteFile()
76 {
77 CPPUNIT_ASSERT(m_file.FileExists());
78
79 bool ret = wxRemoveFile(m_file.GetFullPath());
80 if (ret)
81 {
82 m_old = m_file;
83 m_file = m_new;
84 m_new = RandomName();
85 }
86
87 return ret;
88 }
89
90 bool TouchFile()
91 {
92 return m_file.Touch();
93 }
94
95 bool ReadFile()
96 {
97 wxFile f(m_file.GetFullPath());
98 CPPUNIT_ASSERT(f.IsOpened());
99
100 char buf[1];
101 ssize_t count = f.Read(buf, sizeof(buf));
102 CPPUNIT_ASSERT(count > 0);
103
104 return true;
105 }
106
107 bool ModifyFile()
108 {
109 CPPUNIT_ASSERT(m_file.FileExists());
110
111 wxFile file(m_file.GetFullPath(), wxFile::write_append);
112 CPPUNIT_ASSERT(file.IsOpened());
113
114 CPPUNIT_ASSERT(file.Write("Words of Wisdom, Lloyd. Words of wisdom\n"));
115 return file.Close();
116 }
117
118 // helpers
119 wxFileName RandomName(int length = 10)
120 {
121 return RandomName(m_base, length);
122 }
123
124 // static helpers
125 static const wxFileName& GetWatchDir()
126 {
127 static wxFileName dir;
128
129 if (dir.DirExists())
130 return dir;
131
132 wxString tmp = wxStandardPaths::Get().GetTempDir();
133 dir.AssignDir(tmp);
134
135 // XXX look for more unique name? there is no function to generate
136 // unique filename, the file always get created...
137 dir.AppendDir("fswatcher_test");
138 CPPUNIT_ASSERT(!dir.DirExists());
139 CPPUNIT_ASSERT(dir.Mkdir());
140
141 return dir;
142 }
143
144 static void RemoveWatchDir()
145 {
146 wxFileName dir = GetWatchDir();
147 CPPUNIT_ASSERT(dir.DirExists());
148
149 // just to be really sure we know what we remove
150 CPPUNIT_ASSERT_EQUAL( "fswatcher_test", dir.GetDirs().Last() );
151
152 // FIXME-VC6: using non-static Rmdir() results in ICE
153 CPPUNIT_ASSERT( wxFileName::Rmdir(dir.GetFullPath(), wxPATH_RMDIR_RECURSIVE) );
154 }
155
156 static wxFileName RandomName(const wxFileName& base, int length = 10)
157 {
158 static int ALFA_CNT = 'z' - 'a';
159
160 wxString s;
161 for (int i = 0 ; i < length; ++i)
162 {
163 char c = 'a' + (rand() % ALFA_CNT);
164 s += c;
165 }
166
167 return wxFileName(base.GetFullPath(), s);
168 }
169
170 public:
171 wxFileName m_base; // base dir for doing operations
172 wxFileName m_file; // current file name
173 wxFileName m_old; // previous file name
174 wxFileName m_new; // name after renaming
175
176 protected:
177 static EventGenerator* ms_instance;
178 };
179
180 EventGenerator* EventGenerator::ms_instance = 0;
181
182
183 // custom event handler
184 class EventHandler : public wxEvtHandler
185 {
186 public:
187 enum { WAIT_DURATION = 3 };
188
189 EventHandler() :
190 eg(EventGenerator::Get()), m_loop(0), m_count(0), m_watcher(0)
191 {
192 m_loop = new wxEventLoop();
193 Connect(wxEVT_IDLE, wxIdleEventHandler(EventHandler::OnIdle));
194 Connect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(
195 EventHandler::OnFileSystemEvent));
196 }
197
198 virtual ~EventHandler()
199 {
200 delete m_watcher;
201 if (m_loop)
202 {
203 if (m_loop->IsRunning())
204 m_loop->Exit();
205 delete m_loop;
206 }
207 }
208
209 void Exit()
210 {
211 m_loop->Exit();
212 }
213
214 // sends idle event, so we get called in a moment
215 void SendIdle()
216 {
217 wxIdleEvent* e = new wxIdleEvent();
218 QueueEvent(e);
219 }
220
221 void Run()
222 {
223 SendIdle();
224 m_loop->Run();
225 }
226
227 void OnIdle(wxIdleEvent& /*evt*/)
228 {
229 bool more = Action();
230 m_count++;
231
232 if (more)
233 {
234 SendIdle();
235 }
236 }
237
238 // returns whether we should produce more idle events
239 virtual bool Action()
240 {
241 switch (m_count)
242 {
243 case 0:
244 CPPUNIT_ASSERT(Init());
245 break;
246 case 1:
247 GenerateEvent();
248 break;
249 case 2:
250 // actual test
251 CheckResult();
252 Exit();
253 break;
254
255 // TODO a mechanism that will break the loop in case we
256 // don't receive a file system event
257 // this below doesn't quite work, so all tests must pass :-)
258 #if 0
259 case 2:
260 m_loop.Yield();
261 m_loop.WakeUp();
262 CPPUNIT_ASSERT(KeepWaiting());
263 m_loop.Yield();
264 break;
265 case 3:
266 break;
267 case 4:
268 CPPUNIT_ASSERT(AfterWait());
269 break;
270 #endif
271 } // switch (m_count)
272
273 return m_count <= 0;
274 }
275
276 virtual bool Init()
277 {
278 // test we're good to go
279 CPPUNIT_ASSERT(wxEventLoopBase::GetActive());
280
281 // XXX only now can we construct Watcher, because we need
282 // active loop here
283 m_watcher = new wxFileSystemWatcher();
284 m_watcher->SetOwner(this);
285
286 // add dir to be watched
287 wxFileName dir = EventGenerator::GetWatchDir();
288 CPPUNIT_ASSERT(m_watcher->Add(dir, wxFSW_EVENT_ALL));
289
290 return true;
291 }
292
293 virtual bool KeepWaiting()
294 {
295 // did we receive event already?
296 if (!tested)
297 {
298 // well, let's wait a bit more
299 wxSleep(WAIT_DURATION);
300 }
301
302 return true;
303 }
304
305 virtual bool AfterWait()
306 {
307 // fail if still no events
308 WX_ASSERT_MESSAGE
309 (
310 ("No events during %d seconds!", static_cast<int>(WAIT_DURATION)),
311 tested
312 );
313
314 return true;
315 }
316
317 virtual void OnFileSystemEvent(wxFileSystemWatcherEvent& evt)
318 {
319 wxLogDebug("--- %s ---", evt.ToString());
320 m_lastEvent = wxDynamicCast(evt.Clone(), wxFileSystemWatcherEvent);
321 m_events.Add(m_lastEvent);
322
323 // test finished
324 SendIdle();
325 tested = true;
326 }
327
328 virtual void CheckResult()
329 {
330 CPPUNIT_ASSERT_MESSAGE( "No events received", !m_events.empty() );
331
332 const wxFileSystemWatcherEvent * const e = m_events.front();
333
334 WX_ASSERT_EQUAL_MESSAGE
335 (
336 (
337 "Extra events received, first is of type %x, for path=\"%s\","
338 "last is of type %x, path=\"%s\"",
339 e->GetChangeType(),
340 e->GetPath().GetFullPath(),
341 m_events.back()->GetChangeType(),
342 m_events.back()->GetPath().GetFullPath()
343 ),
344 1, m_events.size()
345 );
346
347 // this is our "reference event"
348 const wxFileSystemWatcherEvent expected = ExpectedEvent();
349
350 CPPUNIT_ASSERT_EQUAL( expected.GetChangeType(), e->GetChangeType() );
351
352 CPPUNIT_ASSERT_EQUAL((int)wxEVT_FSWATCHER, e->GetEventType());
353
354 // XXX this needs change
355 CPPUNIT_ASSERT_EQUAL(wxEVT_CATEGORY_UNKNOWN, e->GetEventCategory());
356
357 CPPUNIT_ASSERT_EQUAL(expected.GetPath(), e->GetPath());
358 CPPUNIT_ASSERT_EQUAL(expected.GetNewPath(), e->GetNewPath());
359 }
360
361 virtual void GenerateEvent() = 0;
362
363 virtual wxFileSystemWatcherEvent ExpectedEvent() = 0;
364
365
366 protected:
367 EventGenerator& eg;
368 wxEventLoopBase* m_loop; // loop reference
369 int m_count; // idle events count
370
371 wxFileSystemWatcher* m_watcher;
372 bool tested; // indicates, whether we have already passed the test
373
374 #include "wx/arrimpl.cpp"
375 WX_DEFINE_ARRAY_PTR(wxFileSystemWatcherEvent*, wxArrayEvent);
376 wxArrayEvent m_events;
377 wxFileSystemWatcherEvent* m_lastEvent;
378 };
379
380
381 // ----------------------------------------------------------------------------
382 // test class
383 // ----------------------------------------------------------------------------
384
385 class FileSystemWatcherTestCase : public CppUnit::TestCase
386 {
387 public:
388 FileSystemWatcherTestCase() { }
389
390 virtual void setUp();
391 virtual void tearDown();
392
393 protected:
394 wxEventLoopBase* m_loop;
395
396 private:
397 CPPUNIT_TEST_SUITE( FileSystemWatcherTestCase );
398 CPPUNIT_TEST( TestEventCreate );
399 CPPUNIT_TEST( TestEventDelete );
400
401 // kqueue-based implementation doesn't collapse create/delete pairs in
402 // renames and doesn't detect neither modifications nor access to the
403 // files reliably currently so disable these tests
404 //
405 // FIXME: fix the code and reenable them
406 #ifndef wxHAS_KQUEUE
407 CPPUNIT_TEST( TestEventRename );
408 CPPUNIT_TEST( TestEventModify );
409
410 // MSW implementation doesn't detect file access events currently
411 #ifndef __WXMSW__
412 CPPUNIT_TEST( TestEventAccess );
413 #endif // __WXMSW__
414 #endif // !wxHAS_KQUEUE
415
416 CPPUNIT_TEST( TestNoEventsAfterRemove );
417 CPPUNIT_TEST_SUITE_END();
418
419 void TestEventCreate();
420 void TestEventDelete();
421 void TestEventRename();
422 void TestEventModify();
423 void TestEventAccess();
424
425 void TestNoEventsAfterRemove();
426
427 DECLARE_NO_COPY_CLASS(FileSystemWatcherTestCase)
428 };
429
430 // the test currently hangs under OS X for some reason and this prevents tests
431 // ran by buildbot from completing so disable it until someone has time to
432 // debug it
433 //
434 // FIXME: debug and fix this!
435 #ifndef __WXOSX__
436 // register in the unnamed registry so that these tests are run by default
437 CPPUNIT_TEST_SUITE_REGISTRATION( FileSystemWatcherTestCase );
438 #endif
439
440 // also include in its own registry so that these tests can be run alone
441 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( FileSystemWatcherTestCase,
442 "FileSystemWatcherTestCase" );
443
444 void FileSystemWatcherTestCase::setUp()
445 {
446 wxLog::AddTraceMask(wxTRACE_FSWATCHER);
447 EventGenerator::Get().GetWatchDir();
448 }
449
450 void FileSystemWatcherTestCase::tearDown()
451 {
452 EventGenerator::Get().RemoveWatchDir();
453 }
454
455 // ----------------------------------------------------------------------------
456 // TestEventCreate
457 // ----------------------------------------------------------------------------
458 void FileSystemWatcherTestCase::TestEventCreate()
459 {
460 wxLogDebug("TestEventCreate()");
461
462 class EventTester : public EventHandler
463 {
464 public:
465 virtual void GenerateEvent()
466 {
467 CPPUNIT_ASSERT(eg.CreateFile());
468 }
469
470 virtual wxFileSystemWatcherEvent ExpectedEvent()
471 {
472 wxFileSystemWatcherEvent event(wxFSW_EVENT_CREATE);
473 event.SetPath(eg.m_file);
474 event.SetNewPath(eg.m_file);
475 return event;
476 }
477 };
478
479 EventTester tester;
480
481 wxLogTrace(wxTRACE_FSWATCHER, "TestEventCreate tester created()");
482
483 tester.Run();
484 }
485
486 // ----------------------------------------------------------------------------
487 // TestEventDelete
488 // ----------------------------------------------------------------------------
489 void FileSystemWatcherTestCase::TestEventDelete()
490 {
491 wxLogDebug("TestEventDelete()");
492
493 class EventTester : public EventHandler
494 {
495 public:
496 virtual void GenerateEvent()
497 {
498 CPPUNIT_ASSERT(eg.DeleteFile());
499 }
500
501 virtual wxFileSystemWatcherEvent ExpectedEvent()
502 {
503 wxFileSystemWatcherEvent event(wxFSW_EVENT_DELETE);
504 event.SetPath(eg.m_old);
505
506 // CHECK maybe new path here could be NULL or sth?
507 event.SetNewPath(eg.m_old);
508 return event;
509 }
510 };
511
512 // we need to create a file now, so we can delete it
513 EventGenerator::Get().CreateFile();
514
515 EventTester tester;
516 tester.Run();
517 }
518
519 // ----------------------------------------------------------------------------
520 // TestEventRename
521 // ----------------------------------------------------------------------------
522 void FileSystemWatcherTestCase::TestEventRename()
523 {
524 wxLogDebug("TestEventRename()");
525
526 class EventTester : public EventHandler
527 {
528 public:
529 virtual void GenerateEvent()
530 {
531 CPPUNIT_ASSERT(eg.RenameFile());
532 }
533
534 virtual wxFileSystemWatcherEvent ExpectedEvent()
535 {
536 wxFileSystemWatcherEvent event(wxFSW_EVENT_RENAME);
537 event.SetPath(eg.m_old);
538 event.SetNewPath(eg.m_file);
539 return event;
540 }
541 };
542
543 // need a file to rename later
544 EventGenerator::Get().CreateFile();
545
546 EventTester tester;
547 tester.Run();
548 }
549
550 // ----------------------------------------------------------------------------
551 // TestEventModify
552 // ----------------------------------------------------------------------------
553 void FileSystemWatcherTestCase::TestEventModify()
554 {
555 wxLogDebug("TestEventModify()");
556
557 class EventTester : public EventHandler
558 {
559 public:
560 virtual void GenerateEvent()
561 {
562 CPPUNIT_ASSERT(eg.ModifyFile());
563 }
564
565 virtual wxFileSystemWatcherEvent ExpectedEvent()
566 {
567 wxFileSystemWatcherEvent event(wxFSW_EVENT_MODIFY);
568 event.SetPath(eg.m_file);
569 event.SetNewPath(eg.m_file);
570 return event;
571 }
572 };
573
574 // we need to create a file to modify
575 EventGenerator::Get().CreateFile();
576
577 EventTester tester;
578 tester.Run();
579 }
580
581 // ----------------------------------------------------------------------------
582 // TestEventAccess
583 // ----------------------------------------------------------------------------
584 void FileSystemWatcherTestCase::TestEventAccess()
585 {
586 wxLogDebug("TestEventAccess()");
587
588 class EventTester : public EventHandler
589 {
590 public:
591 virtual void GenerateEvent()
592 {
593 CPPUNIT_ASSERT(eg.ReadFile());
594 }
595
596 virtual wxFileSystemWatcherEvent ExpectedEvent()
597 {
598 wxFileSystemWatcherEvent event(wxFSW_EVENT_ACCESS);
599 event.SetPath(eg.m_file);
600 event.SetNewPath(eg.m_file);
601 return event;
602 }
603 };
604
605 // we need to create a file to read from it and write sth to it
606 EventGenerator::Get().CreateFile();
607 EventGenerator::Get().ModifyFile();
608
609 EventTester tester;
610 tester.Run();
611 }
612
613 void FileSystemWatcherTestCase::TestNoEventsAfterRemove()
614 {
615 class EventTester : public EventHandler,
616 public wxTimer
617 {
618 public:
619 EventTester()
620 {
621 // We need to use an inactivity timer as we never get any file
622 // system events in this test, so we consider that the test is
623 // finished when this 1s timeout expires instead of, as usual,
624 // stopping after getting the file system events.
625 Start(1000, true);
626 }
627
628 virtual void GenerateEvent()
629 {
630 m_watcher->Remove(EventGenerator::GetWatchDir());
631 CPPUNIT_ASSERT(eg.CreateFile());
632 }
633
634 virtual void CheckResult()
635 {
636 CPPUNIT_ASSERT( m_events.empty() );
637 }
638
639 virtual wxFileSystemWatcherEvent ExpectedEvent()
640 {
641 CPPUNIT_FAIL( "Shouldn't be called" );
642
643 return wxFileSystemWatcherEvent(wxFSW_EVENT_ERROR);
644 }
645
646 virtual void Notify()
647 {
648 SendIdle();
649 }
650 };
651
652 EventTester tester;
653 tester.Run();
654 }