///////////////////////////////////////////////////////////////////////////////
// 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(int types = wxFSW_EVENT_ALL) :
        eg(EventGenerator::Get()), m_loop(0), m_count(0), m_watcher(0),
        m_eventTypes(types)
    {
        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, m_eventTypes));

        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();

        // 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());

        // Under MSW extra modification events are sometimes reported after a
        // rename and we just can't get rid of them, so ignore them in this
        // test if they do happen.
        if ( e->GetChangeType() == wxFSW_EVENT_RENAME &&
                m_events.size() == 2 )
        {
            const wxFileSystemWatcherEvent* const e2 = m_events.back();
            if ( e2->GetChangeType() == wxFSW_EVENT_MODIFY &&
                    e2->GetPath() == e->GetNewPath() )
            {
                // This is a modify event for the new file, ignore it.
                return;
            }
        }

        WX_ASSERT_EQUAL_MESSAGE
        (
            (
                "Extra events received, last one is of type %x, path=\"%s\" "
                "(the original event was for \"%s\" (\"%s\")",
                m_events.back()->GetChangeType(),
                m_events.back()->GetPath().GetFullPath(),
                e->GetPath().GetFullPath(),
                e->GetNewPath().GetFullPath()
            ),
            1, m_events.size()
        );

    }

    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;
    int m_eventTypes;  // Which event-types to watch. Normally all of them
    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 );
#if !defined(__VISUALC__) || wxCHECK_VISUALC_VERSION(7)
        CPPUNIT_TEST( TestTrees );
#endif

        // 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 __WINDOWS__
        CPPUNIT_TEST( TestEventAccess );
#endif // __WINDOWS__
#endif // !wxHAS_KQUEUE

#ifdef wxHAS_INOTIFY
        CPPUNIT_TEST( TestEventAttribute );
        CPPUNIT_TEST( TestSingleWatchtypeEvent );
#endif // wxHAS_INOTIFY

        CPPUNIT_TEST( TestNoEventsAfterRemove );
    CPPUNIT_TEST_SUITE_END();

    void TestEventCreate();
    void TestEventDelete();
    void TestEventRename();
    void TestEventModify();
    void TestEventAccess();
#ifdef wxHAS_INOTIFY
    void TestEventAttribute();
    void TestSingleWatchtypeEvent();
#endif // wxHAS_INOTIFY
#if !defined(__VISUALC__) || wxCHECK_VISUALC_VERSION(7)
    void TestTrees();   // Visual C++ 6 can't build this
#endif
    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();
}

#ifdef wxHAS_INOTIFY
// ----------------------------------------------------------------------------
// TestEventAttribute
// ----------------------------------------------------------------------------
void FileSystemWatcherTestCase::TestEventAttribute()
{
    wxLogDebug("TestEventAttribute()");

    class EventTester : public EventHandler
    {
    public:
        virtual void GenerateEvent()
        {
            CPPUNIT_ASSERT(eg.TouchFile());
        }

        virtual wxFileSystemWatcherEvent ExpectedEvent()
        {
            wxFileSystemWatcherEvent event(wxFSW_EVENT_ATTRIB);
            event.SetPath(eg.m_file);
            event.SetNewPath(eg.m_file);
            return event;
        }
    };

    // we need to create a file to touch
    EventGenerator::Get().CreateFile();

    EventTester tester;
    tester.Run();
}

// ----------------------------------------------------------------------------
// TestSingleWatchtypeEvent: Watch only wxFSW_EVENT_ACCESS
// ----------------------------------------------------------------------------
void FileSystemWatcherTestCase::TestSingleWatchtypeEvent()
{
    wxLogDebug("TestSingleWatchtypeEvent()");

    class EventTester : public EventHandler
    {
    public:
        // We could pass wxFSW_EVENT_CREATE or MODIFY instead, but not RENAME or
        // DELETE as the event path fields would be wrong in CheckResult()
        EventTester() : EventHandler(wxFSW_EVENT_ACCESS) {}

        virtual void GenerateEvent()
        {
            // As wxFSW_EVENT_ACCESS is passed to the ctor only ReadFile() will
            // generate an event. Without it they all will, and the test fails
            CPPUNIT_ASSERT(eg.CreateFile());
            CPPUNIT_ASSERT(eg.ModifyFile());
            CPPUNIT_ASSERT(eg.ReadFile());
        }

        virtual wxFileSystemWatcherEvent ExpectedEvent()
        {
            wxFileSystemWatcherEvent event(wxFSW_EVENT_ACCESS);
            event.SetPath(eg.m_file);
            event.SetNewPath(eg.m_file);
            return event;
        }
    };

    EventTester tester;
    tester.Run();
}
#endif // wxHAS_INOTIFY

// ----------------------------------------------------------------------------
// TestTrees
// ----------------------------------------------------------------------------

#if !defined(__VISUALC__) || wxCHECK_VISUALC_VERSION(7)
void FileSystemWatcherTestCase::TestTrees()
{
    class TreeTester : public EventHandler
    {
        const size_t subdirs;
        const size_t files;

    public:
        TreeTester() : subdirs(5), files(3) {}

        void GrowTree(wxFileName dir
#ifdef __UNIX__
                      , bool withSymlinks = false
#endif
                      )
        {
            CPPUNIT_ASSERT(dir.Mkdir());
            // Now add a subdir with an easy name to remember in WatchTree()
            dir.AppendDir("child");
            CPPUNIT_ASSERT(dir.Mkdir());
            wxFileName child(dir);  // Create a copy to which to symlink

            // Create a branch of 5 numbered subdirs, each containing 3
            // numbered files
            for ( unsigned d = 0; d < subdirs; ++d )
            {
                dir.AppendDir(wxString::Format("subdir%u", d+1));
                CPPUNIT_ASSERT(dir.Mkdir());

                const wxString prefix = dir.GetPathWithSep();
                const wxString ext[] = { ".txt", ".log", "" };
                for ( unsigned f = 0; f < files; ++f )
                {
                    // Just create the files.
                    wxFile(prefix + wxString::Format("file%u", f+1) + ext[f],
                           wxFile::write);
                }
#if defined(__UNIX__)
                if ( withSymlinks )
                {
                    // Create a symlink to a files, and another to 'child'
                    CPPUNIT_ASSERT_EQUAL(0,
                        symlink(wxString(prefix + "file1").c_str(),
                        wxString(prefix + "file.lnk").c_str()));
                    CPPUNIT_ASSERT_EQUAL(0,
                        symlink(child.GetFullPath().c_str(),
                        wxString(prefix + "dir.lnk").c_str()));
                }
#endif // __UNIX__
            }
        }

        void RmDir(wxFileName dir)
        {
            CPPUNIT_ASSERT(dir.DirExists());

            CPPUNIT_ASSERT(dir.Rmdir(wxPATH_RMDIR_RECURSIVE));
        }

        void WatchDir(wxFileName dir)
        {
            CPPUNIT_ASSERT(m_watcher);

            // Store the initial count; there may already be some watches
            const int initial = m_watcher->GetWatchedPathsCount();

            m_watcher->Add(dir);
            CPPUNIT_ASSERT_EQUAL(initial + 1,
                                 m_watcher->GetWatchedPathsCount());
        }

        void RemoveSingleWatch(wxFileName dir)
        {
            CPPUNIT_ASSERT(m_watcher);

            const int initial = m_watcher->GetWatchedPathsCount();

            m_watcher->Remove(dir);
            CPPUNIT_ASSERT_EQUAL(initial - 1,
                                 m_watcher->GetWatchedPathsCount());
        }

        void WatchTree(const wxFileName& dir)
        {
            CPPUNIT_ASSERT(m_watcher);

            size_t treeitems = 1; // the trunk
#ifndef __WINDOWS__
            // When there's no file mask, wxMSW sets a single watch
            // on the trunk which is implemented recursively.
            // wxGTK always sets an additional watch for each subdir
            treeitems += subdirs + 1; // +1 for 'child'
#endif // __WINDOWS__

            // Store the initial count; there may already be some watches
            const int initial = m_watcher->GetWatchedPathsCount();

            GrowTree(dir);

            m_watcher->AddTree(dir);
            const int plustree = m_watcher->GetWatchedPathsCount();

            CPPUNIT_ASSERT_EQUAL(initial + treeitems, plustree);

            m_watcher->RemoveTree(dir);
            CPPUNIT_ASSERT_EQUAL(initial, m_watcher->GetWatchedPathsCount());

            // Now test the refcount mechanism by watching items more than once
            wxFileName child(dir);
            child.AppendDir("child");
            m_watcher->AddTree(child);
            // Check some watches were added; we don't care about the number
            CPPUNIT_ASSERT(initial < m_watcher->GetWatchedPathsCount());
            // Now watch the whole tree and check that the count is the same
            // as it was the first time, despite also adding 'child' separately
            // Except that in wxMSW this isn't true: each watch will be a
            // single, recursive dir; so fudge the count
            size_t fudge = 0;
#ifdef __WINDOWS__
            fudge = 1;
#endif // __WINDOWS__
            m_watcher->AddTree(dir);
            CPPUNIT_ASSERT_EQUAL(plustree + fudge, m_watcher->GetWatchedPathsCount());
            m_watcher->RemoveTree(child);
            CPPUNIT_ASSERT(initial < m_watcher->GetWatchedPathsCount());
            m_watcher->RemoveTree(dir);
            CPPUNIT_ASSERT_EQUAL(initial, m_watcher->GetWatchedPathsCount());
#if defined(__UNIX__)
            // Finally, test a tree containing internal symlinks
            RmDir(dir);
            GrowTree(dir, true /* test symlinks */);

            // Without the DontFollowLink() call AddTree() would now assert
            // (and without the assert, it would infinitely loop)
            wxFileName fn = dir;
            fn.DontFollowLink();
            CPPUNIT_ASSERT(m_watcher->AddTree(fn));
            CPPUNIT_ASSERT(m_watcher->RemoveTree(fn));

            // Regrow the tree without symlinks, ready for the next test
            RmDir(dir);
            GrowTree(dir, false);
#endif // __UNIX__
        }

        void WatchTreeWithFilespec(const wxFileName& dir)
        {
            CPPUNIT_ASSERT(m_watcher);
            CPPUNIT_ASSERT(dir.DirExists()); // Was built in WatchTree()

            // Store the initial count; there may already be some watches
            const int initial = m_watcher->GetWatchedPathsCount();

            // When we use a filter, both wxMSW and wxGTK implementations set
            // an additional watch for each subdir (+1 for the root dir itself
            // and another +1 for "child").
            const size_t treeitems = subdirs + 2;
            m_watcher->AddTree(dir, wxFSW_EVENT_ALL, "*.txt");

            const int plustree = m_watcher->GetWatchedPathsCount();
            CPPUNIT_ASSERT_EQUAL(initial + treeitems, plustree);

            // RemoveTree should try to remove only those files that were added
            m_watcher->RemoveTree(dir);
            CPPUNIT_ASSERT_EQUAL(initial, m_watcher->GetWatchedPathsCount());
        }

        void RemoveAllWatches()
        {
            CPPUNIT_ASSERT(m_watcher);

            m_watcher->RemoveAll();
            CPPUNIT_ASSERT_EQUAL(0, m_watcher->GetWatchedPathsCount());
        }

        virtual void GenerateEvent()
        {
            // We don't use this function for events. Just run the tests

            wxFileName watchdir = EventGenerator::GetWatchDir();
            CPPUNIT_ASSERT(watchdir.DirExists());

            wxFileName treedir(watchdir);
            treedir.AppendDir("treetrunk");
            CPPUNIT_ASSERT(!treedir.DirExists());

            wxFileName singledir(watchdir);
            singledir.AppendDir("single");
            CPPUNIT_ASSERT(!singledir.DirExists());
            CPPUNIT_ASSERT(singledir.Mkdir());

            WatchDir(singledir);
            WatchTree(treedir);
            // Now test adding and removing a tree using a filespec
            // wxMSW uses the generic method to add matching files; which fails
            // as it doesn't support adding files :/ So disable the test
#ifndef __WINDOWS__
            WatchTreeWithFilespec(treedir);
#endif // __WINDOWS__

            RemoveSingleWatch(singledir);
            // Add it back again, ready to test RemoveAll()
            WatchDir(singledir);

            RemoveAllWatches();

            // Clean up
            RmDir(singledir);
            RmDir(treedir);

            Exit();
        }

        virtual wxFileSystemWatcherEvent ExpectedEvent()
        {
            CPPUNIT_FAIL("Shouldn't be called");

            return wxFileSystemWatcherEvent(wxFSW_EVENT_ERROR);
        }

        virtual void CheckResult()
        {
            // Do nothing. We override this to prevent receiving events in
            // ExpectedEvent()
        }
    };

    TreeTester tester;
    tester.Run();
}
#endif // !defined(__VISUALC__) || wxCHECK_VISUALC_VERSION(7)


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();
}
