///////////////////////////////////////////////////////////////////////////////
// Name:        tests/log/logtest.cpp
// Purpose:     wxLog unit test
// Author:      Vadim Zeitlin
// Created:     2009-07-07
// RCS-ID:      $Id$
// Copyright:   (c) 2009 Vadim Zeitlin <vadim@wxwidgets.org>
///////////////////////////////////////////////////////////////////////////////

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

#include "testprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/log.h"
    #include "wx/filefn.h"
#endif // WX_PRECOMP

#include "wx/scopeguard.h"

#if WXWIN_COMPATIBILITY_2_8
    // we override deprecated DoLog() and DoLogString() in this test, suppress
    // warnings about it
    #if wxCHECK_VISUALC_VERSION(7)
        #pragma warning(disable: 4996)
    #endif // VC++ 7+
#endif // WXWIN_COMPATIBILITY_2_8

// all calls to wxLogXXX() functions from this file will use this log component
#define wxLOG_COMPONENT "test"

// ----------------------------------------------------------------------------
// test loggers
// ----------------------------------------------------------------------------

// base class for all test loggers which simply store all logged messages for
// future examination in the test code
class TestLogBase : public wxLog
{
public:
    TestLogBase() { }

    const wxString& GetLog(wxLogLevel level) const
    {
        return m_logs[level];
    }

    const wxLogRecordInfo& GetInfo(wxLogLevel level) const
    {
        return m_logsInfo[level];
    }

    void Clear()
    {
        for ( unsigned n = 0; n < WXSIZEOF(m_logs); n++ )
        {
            m_logs[n].clear();
            m_logsInfo[n] = wxLogRecordInfo();
        }
    }

protected:
    wxString m_logs[wxLOG_Trace + 1];
    wxLogRecordInfo m_logsInfo[wxLOG_Trace + 1];

    wxDECLARE_NO_COPY_CLASS(TestLogBase);
};

// simple log sink which just stores the messages logged for each level
class TestLog : public TestLogBase
{
public:
    TestLog() { }

protected:
    virtual void DoLogRecord(wxLogLevel level,
                             const wxString& msg,
                             const wxLogRecordInfo& info)
    {
        m_logs[level] = msg;
        m_logsInfo[level] = info;
    }

private:
    wxDECLARE_NO_COPY_CLASS(TestLog);
};

#if WXWIN_COMPATIBILITY_2_8

// log sink overriding the old DoLogXXX() functions should still work too

// this one overrides DoLog(char*)
class CompatTestLog : public TestLogBase
{
public:
    CompatTestLog() { }

protected:
    virtual void DoLog(wxLogLevel level, const char *str, time_t WXUNUSED(t))
    {
        m_logs[level] = str;
    }

    // get rid of the warning about hiding the other overload
    virtual void DoLog(wxLogLevel WXUNUSED(level),
                       const wchar_t *WXUNUSED(str),
                       time_t WXUNUSED(t))
    {
    }

private:
    wxDECLARE_NO_COPY_CLASS(CompatTestLog);
};

// and this one overload DoLogString(wchar_t*)
class CompatTestLog2 : public wxLog
{
public:
    CompatTestLog2() { }

    const wxString& Get() const { return m_msg; }

protected:
    virtual void DoLogString(const wchar_t *msg, time_t WXUNUSED(t))
    {
        m_msg = msg;
    }

    // get rid of the warning
    virtual void DoLogString(const char *WXUNUSED(msg), time_t WXUNUSED(t))
    {
    }

private:
    wxString m_msg;

    wxDECLARE_NO_COPY_CLASS(CompatTestLog2);
};

#endif // WXWIN_COMPATIBILITY_2_8

// ----------------------------------------------------------------------------
// test class
// ----------------------------------------------------------------------------

class LogTestCase : public CppUnit::TestCase
{
public:
    LogTestCase() { }

    virtual void setUp();
    virtual void tearDown();

private:
    CPPUNIT_TEST_SUITE( LogTestCase );
        CPPUNIT_TEST( Functions );
        CPPUNIT_TEST( Null );
        CPPUNIT_TEST( Component );
#if wxDEBUG_LEVEL
        CPPUNIT_TEST( Trace );
#endif // wxDEBUG_LEVEL
#if WXWIN_COMPATIBILITY_2_8
        CPPUNIT_TEST( CompatLogger );
        CPPUNIT_TEST( CompatLogger2 );
#endif // WXWIN_COMPATIBILITY_2_8
        CPPUNIT_TEST( SysError );
    CPPUNIT_TEST_SUITE_END();

    void Functions();
    void Null();
    void Component();
#if wxDEBUG_LEVEL
    void Trace();
#endif // wxDEBUG_LEVEL
#if WXWIN_COMPATIBILITY_2_8
    void CompatLogger();
    void CompatLogger2();
#endif // WXWIN_COMPATIBILITY_2_8
    void SysError();

    TestLog *m_log;
    wxLog *m_logOld;
    bool m_logWasEnabled;

    wxDECLARE_NO_COPY_CLASS(LogTestCase);
};

// register in the unnamed registry so that these tests are run by default
CPPUNIT_TEST_SUITE_REGISTRATION( LogTestCase );

// also include in its own registry so that these tests can be run alone
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( LogTestCase, "LogTestCase" );

void LogTestCase::setUp()
{
    m_logOld = wxLog::SetActiveTarget(m_log = new TestLog);
    m_logWasEnabled = wxLog::EnableLogging();
}

void LogTestCase::tearDown()
{
    delete wxLog::SetActiveTarget(m_logOld);
    wxLog::EnableLogging(m_logWasEnabled);
}

void LogTestCase::Functions()
{
    wxLogMessage("Message");
    CPPUNIT_ASSERT_EQUAL( "Message", m_log->GetLog(wxLOG_Message) );

    wxLogError("Error %d", 17);
    CPPUNIT_ASSERT_EQUAL( "Error 17", m_log->GetLog(wxLOG_Error) );

    wxLogDebug("Debug");
#if wxDEBUG_LEVEL
    CPPUNIT_ASSERT_EQUAL( "Debug", m_log->GetLog(wxLOG_Debug) );
#else
    CPPUNIT_ASSERT_EQUAL( "", m_log->GetLog(wxLOG_Debug) );
#endif
}

void LogTestCase::Null()
{
    {
        wxLogNull noLog;
        wxLogWarning("%s warning", "Not important");

        CPPUNIT_ASSERT_EQUAL( "", m_log->GetLog(wxLOG_Warning) );
    }

    wxLogWarning("%s warning", "Important");
    CPPUNIT_ASSERT_EQUAL( "Important warning", m_log->GetLog(wxLOG_Warning) );
}

void LogTestCase::Component()
{
    wxLogMessage("Message");
    CPPUNIT_ASSERT_EQUAL( wxLOG_COMPONENT,
                          m_log->GetInfo(wxLOG_Message).component );

    // completely disable logging for this component
    wxLog::SetComponentLevel("test/ignore", wxLOG_FatalError);

    // but enable it for one of its subcomponents
    wxLog::SetComponentLevel("test/ignore/not", wxLOG_Max);

    #undef wxLOG_COMPONENT
    #define wxLOG_COMPONENT "test/ignore"

    // this shouldn't be output as this component is ignored
    wxLogError("Error");
    CPPUNIT_ASSERT_EQUAL( "", m_log->GetLog(wxLOG_Error) );

    // and so are its subcomponents
    #undef wxLOG_COMPONENT
    #define wxLOG_COMPONENT "test/ignore/sub/subsub"
    wxLogError("Error");
    CPPUNIT_ASSERT_EQUAL( "", m_log->GetLog(wxLOG_Error) );

    // but one subcomponent is not
    #undef wxLOG_COMPONENT
    #define wxLOG_COMPONENT "test/ignore/not"
    wxLogError("Error");
    CPPUNIT_ASSERT_EQUAL( "Error", m_log->GetLog(wxLOG_Error) );

    // restore the original value
    #undef wxLOG_COMPONENT
    #define wxLOG_COMPONENT "test"
}

#if wxDEBUG_LEVEL

namespace
{

const char *TEST_MASK = "test";

// this is a test vararg function (a real one, not a variadic-template-like as
// wxVLogTrace(), so care should be taken with its arguments)
void TraceTest(const char *format, ...)
{
    va_list argptr;
    va_start(argptr, format);
    wxVLogTrace(TEST_MASK, format, argptr);
    va_end(argptr);
}

} // anonymous namespace

void LogTestCase::Trace()
{
    // we use wxLogTrace() or wxVLogTrace() from inside TraceTest()
    // interchangeably here, it shouldn't make any difference

    wxLogTrace(TEST_MASK, "Not shown");
    CPPUNIT_ASSERT_EQUAL( "", m_log->GetLog(wxLOG_Trace) );

    wxLog::AddTraceMask(TEST_MASK);
    TraceTest("Shown");
    CPPUNIT_ASSERT_EQUAL( wxString::Format("(%s) Shown", TEST_MASK),
                          m_log->GetLog(wxLOG_Trace) );

    wxLog::RemoveTraceMask(TEST_MASK);
    m_log->Clear();

    TraceTest("Not shown again");
    CPPUNIT_ASSERT_EQUAL( "", m_log->GetLog(wxLOG_Trace) );
}

#endif // wxDEBUG_LEVEL

#if WXWIN_COMPATIBILITY_2_8

void LogTestCase::CompatLogger()
{
    CompatTestLog log;
    wxLog * const logOld = wxLog::SetActiveTarget(&log);
    wxON_BLOCK_EXIT1( wxLog::SetActiveTarget, logOld );

    wxLogError("Old error");
    CPPUNIT_ASSERT_EQUAL( "Old error", log.GetLog(wxLOG_Error) );
}

void LogTestCase::CompatLogger2()
{
    CompatTestLog2 log;
    wxLog * const logOld = wxLog::SetActiveTarget(&log);
    wxON_BLOCK_EXIT1( wxLog::SetActiveTarget, logOld );

    wxLogWarning("Old warning");
    CPPUNIT_ASSERT_EQUAL( "Old warning", log.Get() );
}

#endif // WXWIN_COMPATIBILITY_2_8

void LogTestCase::SysError()
{
    wxString s;

    wxLogSysError(17, "Error");
    CPPUNIT_ASSERT( m_log->GetLog(wxLOG_Error).StartsWith("Error (", &s) );
    WX_ASSERT_MESSAGE( ("Error message is \"(%s\"", s), s.StartsWith("error 17") );

    // The last error code seems to be set somewhere in MinGW CRT as its value
    // is just not what we expect (ERROR_INVALID_PARAMETER instead of 0 and 0
    // instead of ERROR_FILE_NOT_FOUND) so exclude the tests which rely on last
    // error being preserved for this compiler.
#ifndef __MINGW32__
    wxLogSysError("Success");
    CPPUNIT_ASSERT( m_log->GetLog(wxLOG_Error).StartsWith("Success (", &s) );
    WX_ASSERT_MESSAGE( ("Error message is \"(%s\"", s), s.StartsWith("error 0") );

    wxOpen("no-such-file", 0, 0);
    wxLogSysError("Not found");
    CPPUNIT_ASSERT( m_log->GetLog(wxLOG_Error).StartsWith("Not found (", &s) );
    WX_ASSERT_MESSAGE( ("Error message is \"(%s\"", s), s.StartsWith("error 2") );
#endif // __MINGW32__
}