/******************************************************************************
 *
 * package:     Log4Qt
 * file:        log4qttest.cpp
 * created:     September 2007
 * author:      Martin Heinrich
 *
 * 
 * Copyright 2007 Martin Heinrich
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 ******************************************************************************/

/******************************************************************************
 * Dependencies
 ******************************************************************************/

#include "log4qttest.h"

#include <QtCore/QBuffer>
#include <QtCore/QBitArray>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QMetaEnum>
#include <QtCore/QSettings>
#include <QtCore/QTextStream>
#include <QtCore/QThread>
#include <QtTest/QtTest>
#include "log4qt/basicconfigurator.h"
#include "log4qt/consoleappender.h"
#include "log4qt/dailyrollingfileappender.h"
#include "log4qt/fileappender.h"
#include "log4qt/helpers/configuratorhelper.h"
#include "log4qt/helpers/datetime.h"
#include "log4qt/helpers/factory.h"
#include "log4qt/helpers/initialisationhelper.h"
#include "log4qt/helpers/optionconverter.h"
#include "log4qt/helpers/patternformatter.h"
#include "log4qt/helpers/properties.h"
#include "log4qt/logmanager.h"
#include "log4qt/loggerrepository.h"
#include "log4qt/patternlayout.h"
#include "log4qt/propertyconfigurator.h"
#include "log4qt/rollingfileappender.h"
#include "log4qt/simplelayout.h"
#include "log4qt/ttcclayout.h"
#include "log4qt/varia/denyallfilter.h"
#include "log4qt/varia/levelmatchfilter.h"
#include "log4qt/varia/levelrangefilter.h"
#include "log4qt/varia/stringmatchfilter.h"



using namespace Log4Qt;


/******************************************************************************
 *Declarations
 ******************************************************************************/



/******************************************************************************
 * C helper functions
 ******************************************************************************/


QTEST_MAIN(Log4QtTest)
LOG4QT_DECLARE_STATIC_LOGGER(test_logger, Test::TestLog4Qt)



/******************************************************************************
 * Class implementation: Log4QtTest
 ******************************************************************************/



Log4QtTest::Log4QtTest() :
QObject(),
mSkipLongTests(false),
mTemporaryDirectory(),
mpLoggingEvents(0),
mDefaultProperties(),
mProperties(&mDefaultProperties)
{
    // mSkipLongTests = true;
}


Log4QtTest::~Log4QtTest()
{
}



/******************************************************************************
 * log4qt/helpers                                                             */

void Log4QtTest::initTestCase()
{
    // Logging
    LogManager::resetConfiguration();

    // File system
    QString name = QDir::tempPath() + "/Log4QtTest_"
                   + QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
    if (!mTemporaryDirectory.mkdir(name))
        QFAIL("Creation of temporary directory failed");
    mTemporaryDirectory.setPath(name);
    qDebug() << "Using temporaray directory: " << mTemporaryDirectory.path();
    
    // Appender to track events generated by Log4Qt
    mpLoggingEvents = new Log4Qt::ListAppender(this);
    mpLoggingEvents->retain();
    mpLoggingEvents->setName("Log4QtTest");
    mpLoggingEvents->setConfiguratorList(true);
    resetLogging();
}


void Log4QtTest::cleanupTestCase()
{
    LogManager::resetConfiguration();
    
    if (!deleteDirectoryTree(mTemporaryDirectory.path()))
        QFAIL("Cleanup of temporary directory failed");
    mpLoggingEvents->release();
}


void Log4QtTest::DateTime_compability_data()
{
    QTest::addColumn<QString>("pattern");

    QTest::newRow("date d") << "d";
    QTest::newRow("date dd") << "dd";
    QTest::newRow("date ddd") << "ddd";
    QTest::newRow("date dddd") << "dddd";
    QTest::newRow("date M") << "M";
    QTest::newRow("date MM") << "MM";
    QTest::newRow("date MMM") << "MMM";
    QTest::newRow("date MMMM") << "MMMM";
    QTest::newRow("date YY") << "YY";
    QTest::newRow("date YYYY") << "YYYY";
    QTest::newRow("time h") << "h";
    QTest::newRow("time hh") << "hh";
    QTest::newRow("time H") << "H";
    QTest::newRow("time HH") << "HH";
    QTest::newRow("time m") << "m";
    QTest::newRow("time mm") << "mm";
    QTest::newRow("time s") << "s";
    QTest::newRow("time ss") << "ss";
    QTest::newRow("time z") << "z";
    QTest::newRow("time zz") << "zz";
    QTest::newRow("time a") << "a";
    QTest::newRow("time ap") << "ap";
    QTest::newRow("time A") << "A";
    QTest::newRow("time AP") << "AP";
    QTest::newRow("datetime") << "";
    QTest::newRow("datetime HHh") << "HHh";
    QTest::newRow("datetime 'M'M'd'd'y'yyhh:mm:ss") << "'M'M'd'd'y'yyhh:mm:ss";
    QTest::newRow("datetime M.d.s") << "M.d.s";
    QTest::newRow("datetime YYYY-MM-ddTHH:mm:ss") << "YYYY-MM-ddTHH:mm:ss";
    QTest::newRow("datetime YYYY-MM-ddTHH:mm:ss.zzz") << "YYYY-MM-ddTHH:mm:ss.zzz";
    QTest::newRow("datetime yyyy-MM-dd HH:mm:ss,zzz") << "yyyy-MM-dd HH:mm:ss,zzz";
    QTest::newRow("datetime dd MMM yyyy HH:mm:ss.zzz") << "dd MMM yyyy HH:mm:ss.zzz";
    QTest::newRow("datetime HH:mm:ss.zzz") << "HH:mm:ss.zzz";
    // Quotes are not handled like in JAVA. 'x''x' -> xx not x'x 
    QTest::newRow("datetime 'This is a'''' test'") << "'This is a'''' test'";
    // Qt does not ignore literals outside of quotes x'x' -> xx not x
    QTest::newRow("datetime This 'is a'''' test'") << "This 'is a'''' test'";
    // HH is handled by toString even if not documented
    QTest::newRow("datetime HH:mm AP") << "HH:mm AP";
}


void Log4QtTest::DateTime_compability()
{
    QDateTime reference = QDateTime(QDate(2001, 9, 7), QTime(15, 7, 5, 9));
    QDateTime q_date_time(reference);
    DateTime date_time(reference);

    QFETCH(QString, pattern);
    QCOMPARE(date_time.toString(pattern), q_date_time.toString(pattern));
}


void Log4QtTest::DateTime_week_data()
{
    QTest::addColumn<QDateTime>("datetime");
    QTest::addColumn<QString>("pattern");
    QTest::addColumn<QString>("result");

    QTest::newRow("week 6") << QDateTime(QDate(2001,2,9), QTime(15,7,5,9)) << "w" << "6";
    QTest::newRow("week 06") << QDateTime(QDate(2001,2,9), QTime(15,7,5,9)) << "ww" << "06";
    QTest::newRow("week 36") << QDateTime(QDate(2001,9,7), QTime(15,7,5,9)) << "w" << "36";
}


void Log4QtTest::DateTime_week()
{
    QFETCH(QDateTime, datetime);
    QFETCH(QString, pattern);
    QFETCH(QString, result);

    QCOMPARE(static_cast<DateTime>(datetime).toString(pattern), result);
}


void Log4QtTest::DateTime_milliseconds_data()
{
    QTest::addColumn<QDateTime>("datetime");
    QTest::addColumn<qint64>("milliseconds");

    QTest::newRow("2001-09-07 15:07:05.009") << QDateTime(QDate(2001,9,7), QTime(15,7,5,9), Qt::UTC) << Q_INT64_C(999875225009);
}


void Log4QtTest::DateTime_milliseconds()
{
    QFETCH(QDateTime, datetime);
    QFETCH(qint64, milliseconds);

    QCOMPARE(DateTime(datetime).toMilliSeconds(), milliseconds);
    QCOMPARE(DateTime::fromMilliSeconds(milliseconds).toUTC(), datetime);
}


void Log4QtTest::PatternFormatter_data()
{
    QTest::addColumn<LoggingEvent>("event");
    QTest::addColumn<QString>("pattern");
    QTest::addColumn<QString>("result");
    QTest::addColumn<int>("event_count");

    // Create end of line
    QString eol;
#ifdef Q_OS_WIN32
    eol = QLatin1String("\r\n");
#else
    eol = QLatin1String("\n");
#endif // Q_OS_WIN32
    // Prepare event data
    int relative_offset = 17865;
    qint64 relative_timestamp = InitialisationHelper::startTime() + relative_offset;
    QString relative_string = QString::number(relative_offset);
    qint64 absolute_timestamp =
    DateTime(QDateTime(QDate(2001,9,7), QTime(15,7,5,9))).toMilliSeconds();
    QHash<QString, QString> properties;
    properties.insert("A", "a");
    properties.insert("B", "b");
    properties.insert("C", "c");

    QTest::newRow("Default conversion")
    << LoggingEvent(test_logger(),
                    Level(Level::DEBUG_INT),
                    "This is the message")
    << "%m%n"
    << "This is the message" + eol
    << 0;
    QTest::newRow("TTCC conversion")
    << LoggingEvent(test_logger(),
                    Level(Level::DEBUG_INT),
                    "This is the message",
                    "NDC",
                    properties,
                    "main",
                    relative_timestamp)
    << "%r [%t] %p %c %x - %m%n"
    << relative_string + " [main] DEBUG Test::TestLog4Qt NDC - This is the message" + eol
    << 0;
    QTest::newRow("Java class documentation example 1")
    << LoggingEvent(test_logger(),
                    Level(Level::WARN_INT),
                    "This is the message",
                    "NDC",
                    properties,
                    "main",
                    relative_timestamp)
    << "%-5p [%t]: %m%n"
    << "WARN  [main]: This is the message" + eol
    << 0;
    QTest::newRow("Java class documentation example 2")
    << LoggingEvent(test_logger(),
                    Level(Level::INFO_INT),
                    "This is the message",
                    "NDC",
                    properties,
                    "main",
                    relative_timestamp)
    << "%r [%t] %-5p %c %x - %m%n"
    << relative_string + " [main] INFO  Test::TestLog4Qt NDC - This is the message" + eol
    << 0;
    QTest::newRow("Java class documentation example 2")
    << LoggingEvent(test_logger(),
                    Level(Level::INFO_INT),
                    "This is the message",
                    "NDC",
                    properties,
                    "main",
                    relative_timestamp)
    << "%-6r [%15.15t] %-5p %30.30c %x - %m%n"
    << relative_string + "  [           main] INFO                Test::TestLog4Qt NDC - This is the message" + eol
    << 0;
    QTest::newRow("Java class documentation example 2, truncating")
    << LoggingEvent(test_logger(),
                    Level(Level::INFO_INT),
                    "This is the message",
                    "NDC",
                    properties,
                    "threadwithextralongname",
                    relative_timestamp)
    << "%-6r [%15.15t] %-5p %30.30c %x - %m%n"
    << relative_string + "  [threadwithextra] INFO                Test::TestLog4Qt NDC - This is the message" + eol
    << 0;
    QTest::newRow("TTCC with ISO date")
    << LoggingEvent(test_logger(),
                    Level(Level::DEBUG_INT),
                    "This is the message",
                    "NDC",
                    properties,
                    "main",
                    absolute_timestamp)
    << "%d{ISO8601} [%t] %p %c %x - %m%n"
    << "2001-09-07 15:07:05.009 [main] DEBUG Test::TestLog4Qt NDC - This is the message" + eol
    << 0;

    resetLogging();
}


void Log4QtTest::PatternFormatter()
{
    QFETCH(LoggingEvent, event);
    QFETCH(QString, pattern);
    QFETCH(QString, result);
    QFETCH(int, event_count);

    Log4Qt::PatternFormatter pattern_formatter(pattern);
    QCOMPARE(pattern_formatter.format(event), result);

    QCOMPARE(mpLoggingEvents->list().count(), event_count);
}


void Log4QtTest::Properties_default_data()
{
    mDefaultProperties.clear();
    mDefaultProperties.setProperty("X", "x");

    mProperties.clear();
    mProperties.setProperty("A", "a");
    mProperties.setProperty("B", "");
    mProperties.setProperty("C", QString());

    QTest::addColumn<QString>("key");
    QTest::addColumn<QString>("value");

    QTest::newRow("Existing value") << "A" << "a";
    QTest::newRow("Empty value") << "B" << "";
    QTest::newRow("Null value") << "C" << "";
    QTest::newRow("Default value") << "X" << "x";
    QTest::newRow("Non existing value") << "D" << QString();
}


void Log4QtTest::Properties_default()
{
    QFETCH(QString, key);
    QFETCH(QString, value);

    QCOMPARE(mProperties.property(key), value);
}


void Log4QtTest::Properties_names()
{
    mDefaultProperties.clear();
    mDefaultProperties.setProperty("X", "x");

    mProperties.clear();
    mProperties.setProperty("A", "a");
    mProperties.setProperty("B", "");

    QStringList property_names = mProperties.propertyNames();
    QCOMPARE(property_names.count(), 3);
    QVERIFY(property_names.contains("A"));
    QVERIFY(property_names.count("B"));
    QVERIFY(property_names.count("X"));
}


void Log4QtTest::Properties_load_device_data()
{
    QTest::addColumn<QByteArray>("buffer");
    QTest::addColumn<int>("property_count");
    QTest::addColumn<QString>("key");
    QTest::addColumn<QString>("value");
    QTest::addColumn<int>("event_count");

    QByteArray buffer;
    QBuffer device(&buffer);
    QTextStream stream(&device);

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "Truth = Beauty" << endl;
    device.close();
    QTest::newRow("Java class documentation example 1a")
    << buffer << 1 << "Truth" << "Beauty" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "         Truth:Beauty" << endl;
    device.close();
    QTest::newRow("Java class documentation example 1b")
    << buffer << 1 << "Truth" << "Beauty" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "Truth        :Beauty" << endl;
    device.close();
    QTest::newRow("Java class documentation example 1c")
    << buffer << 1 << "Truth" << "Beauty" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "fruits                apple, banana, pear, \\" << endl;
    stream << "                      cantaloupe, watermelon, \\" << endl;
    stream << "                      kiwi, mango" << endl;
    device.close();
    QTest::newRow("Java class documentation example 2")
    << buffer << 1 << "fruits"
    << "apple, banana, pear, cantaloupe, watermelon, kiwi, mango" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "cheese" << endl;
    device.close();
    QTest::newRow("Java class documentation example 3")
    << buffer << 1 << "cheese" << "" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "K\\ e\\=\\:y Value" << endl;
    device.close();
    QTest::newRow("Key escape sequences")
    << buffer << 1 << "K e=:y" << "Value" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "Key V\\t\\n\\ra\\\\l\\\"u\\\'e" << endl;
    device.close();
    QTest::newRow("Value escape sequences")
    << buffer << 1 << "Key" << "V\t\n\ra\\l\"u\'e" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "Key\\t Value\\j" << endl;
    device.close();
    QTest::newRow("Invalid escape sequences")
    << buffer << 1 << "Keyt" << "Valuej" << 2;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "Key Valu\\u006fe" << endl;
    device.close();
    QTest::newRow("Unicode escape sequence")
    << buffer << 1 << "Key" << "Valuoe" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "Key Value\\u6f" << endl;
    device.close();
    QTest::newRow("Unicode escape sequence at the end")
    << buffer << 1 << "Key" << "Valueo" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << ": Value" << endl;
    device.close();
    QTest::newRow("Empty key")
    << buffer << 1 << "" << "Value" << 1;

    resetLogging();
}


void Log4QtTest::Properties_load_device()
{
    QFETCH(QByteArray, buffer);
    QFETCH(int, property_count);
    QFETCH(QString, key);
    QFETCH(QString, value);
    QFETCH(int, event_count);

    mpLoggingEvents->clearList();

    QBuffer device(&buffer);
    QTextStream stream(&device);
    device.open(QIODevice::ReadOnly);

    Properties properties;
    properties.load(&device);

    QCOMPARE(properties.count(), property_count);
    QVERIFY(properties.contains(key));
    QCOMPARE(properties.value(key), value);
    QCOMPARE(mpLoggingEvents->list().count(), event_count);
}


void Log4QtTest::Properties_load_settings()
{
    QSettings settings(mTemporaryDirectory.path()
                    + "/PropetiesLoadSettings.ini", QSettings::IniFormat);
    QBitArray bit_array(5, true);

    settings.setValue("A", "a");
    settings.setValue("Group/B", "b");
    settings.setValue("Group/C", true);
    settings.setValue("Group/D", bit_array);
    settings.setValue("Group/Subgroup/E", "e");

    settings.beginGroup("Group");
    Properties properties;
    properties.load(settings);

    QCOMPARE(properties.count(), 3);
    QVERIFY(properties.contains("B"));
    QCOMPARE(properties.value("B"), QString("b"));
    QVERIFY(properties.contains("C"));
    QCOMPARE(properties.value("C"), QString("true"));
    QVERIFY(properties.contains("D"));
    QCOMPARE(properties.value("D"), QString());
}



/******************************************************************************
 * OptionConverter requires Properties                                        */


void Log4QtTest::OptionConverter_boolean_data()
{
    QTest::addColumn<QString>("value");
    QTest::addColumn<bool>("default_value");
    QTest::addColumn<bool>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("true") << "true" << false << true << 0;
    QTest::newRow("enabled") << "enabled" << false << true << 0;
    QTest::newRow("1") << "1" << false << true << 0;
    QTest::newRow("false") << "false" << true << false << 0;
    QTest::newRow("disabled") << "disabled" << true << false << 0;
    QTest::newRow("0") << "0" << true << false << 0;
    QTest::newRow("Case") << "tRuE" << false << true << 0;
    QTest::newRow("Trim") << " true " << false << true << 0;
    QTest::newRow("No Boolean") << "NoBool" << false << false << 1;

    resetLogging();
}


void Log4QtTest::OptionConverter_boolean()
{
    QFETCH(QString, value);
    QFETCH(bool, default_value);
    QFETCH(bool, result);
    QFETCH(int, event_count);

    mpLoggingEvents->clearList();
    QCOMPARE(OptionConverter::toBoolean(value, default_value), result);
    QCOMPARE(mpLoggingEvents->list().count(), event_count);
}


void Log4QtTest::OptionConverter_filesize_data()
{
    QTest::addColumn<QString>("value");
    QTest::addColumn<bool>("result_ok");
    QTest::addColumn<qint64>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("Int")
    << "135" << true << Q_INT64_C(135) << 0;
    QTest::newRow("Trim")
    << " 135 " << true << Q_INT64_C(135) << 0;
    QTest::newRow("KB")
    << "2KB" << true << Q_INT64_C(2048) << 0;
    QTest::newRow("KB case")
    << "2kb" << true << Q_INT64_C(2048) << 0;
    QTest::newRow("KB trim")
    << " 2KB " << true << Q_INT64_C(2048) << 0;
    QTest::newRow("MB")
    << "100MB" << true << Q_INT64_C(104857600) << 0;
    QTest::newRow("GB")
    << "2GB" << true << Q_INT64_C(2147483648) << 0;
    QTest::newRow("Invalid negative")
    << "-1" << false << Q_INT64_C(0) << 1;
    QTest::newRow("Invalid character")
    << "x" << false << Q_INT64_C(0) << 1;
    QTest::newRow("Invalid character with unit")
    << "xkb" << false << Q_INT64_C(0) << 1;
    QTest::newRow("Invalid additional text")
    << "2KBx" << false << Q_INT64_C(0) << 1;

    resetLogging();
}


void Log4QtTest::OptionConverter_filesize()
{
    QFETCH(QString, value);
    QFETCH(bool, result_ok);
    QFETCH(qint64, result);
    QFETCH(int, event_count);

    mpLoggingEvents->clearList();
    bool ok;
    QCOMPARE(OptionConverter::toFileSize(value, &ok), result);
    QCOMPARE(ok, result_ok);
    QCOMPARE(mpLoggingEvents->list().count(), event_count);
}


void Log4QtTest::OptionConverter_int_data()
{
    QTest::addColumn<QString>("value");
    QTest::addColumn<bool>("result_ok");
    QTest::addColumn<int>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("Positive") << "12" << true << 12 << 0;
    QTest::newRow("Negative") << "-1" << true << -1 << 0;
    QTest::newRow("Trim") << " 12 " << true << 12 << 0;
    QTest::newRow("No integer") << "12x" << false << 0 << 1;

    resetLogging();
}


void Log4QtTest::OptionConverter_int()
{
    QFETCH(QString, value);
    QFETCH(bool, result_ok);
    QFETCH(int, result);
    QFETCH(int, event_count);

    mpLoggingEvents->clearList();
    bool ok;
    QCOMPARE(OptionConverter::toInt(value, &ok), result);
    QCOMPARE(ok, result_ok);
    QCOMPARE(mpLoggingEvents->list().count(), event_count);
}


void Log4QtTest::OptionConverter_level_data()
{
    QTest::addColumn<QString>("value");
    QTest::addColumn<Log4Qt::Level>("default_value");
    QTest::addColumn<Log4Qt::Level>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("Case")
    << "WaRn"
    << Log4Qt::Level(Log4Qt::Level::ERROR_INT)
    << Log4Qt::Level(Log4Qt::Level::WARN_INT)
    << 0;
    QTest::newRow("Trim")
    << " warn "
    << Log4Qt::Level(Log4Qt::Level::ERROR_INT)
    << Log4Qt::Level(Log4Qt::Level::WARN_INT)
    << 0;
    QTest::newRow("Default")
    << "NoLevel"
    << Log4Qt::Level(Log4Qt::Level::ERROR_INT)
    << Log4Qt::Level(Log4Qt::Level::ERROR_INT)
    << 2; // One from Level + one from OptionConverter

    resetLogging();
}


void Log4QtTest::OptionConverter_level()
{
    QFETCH(QString, value);
    QFETCH(Log4Qt::Level, default_value);
    QFETCH(Log4Qt::Level, result);
    QFETCH(int, event_count);

    mpLoggingEvents->clearList();
    QCOMPARE(OptionConverter::toLevel(value, default_value), result);
    QCOMPARE(mpLoggingEvents->list().count(), event_count);
}


void Log4QtTest::OptionConverter_substitution_data()
{
    mDefaultProperties.clear();
    mProperties.clear();

    QTest::addColumn<QString>("key");
    QTest::addColumn<QString>("value");
    QTest::addColumn<QString>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("Existing value")
    << "A" << "a" << "a" << 0;
    QTest::newRow("Existing value")
    << "B" << "b" << "b" << 0;
    QTest::newRow("Empty value")
    << "C" << "" << "" << 0;
    QTest::newRow("Null value")
    << "D" << QString() << "" << 0;
    QTest::newRow("Substitution")
    << "S1" << "begin${A}end" << "beginaend" << 0;
    QTest::newRow("Substitution with two values")
    << "S2" << "begin${A}end${B}" << "beginaendb" << 0;
    QTest::newRow("Substitution recursive")
    << "S3" << "${S1}" << "beginaend" << 0;
    QTest::newRow("Substitution missing bracket")
    << "S4" << "begin${end" << "begin" << 1;
    QTest::newRow("Substitution spare brackets")
    << "S5" << "begin}${A}}end" << "begin}a}end" << 0;
}


void Log4QtTest::OptionConverter_substitution()
{
    QFETCH(QString, key);
    QFETCH(QString, value);
    QFETCH(QString, result);
    QFETCH(int, event_count);

    mpLoggingEvents->clearList();
    mProperties.setProperty(key, value);
    QCOMPARE(OptionConverter::findAndSubst(mProperties, key), result);
    QCOMPARE(mpLoggingEvents->list().count(), event_count);
}


void Log4QtTest::OptionConverter_target_data()
{
    QTest::addColumn<QString>("value");
    QTest::addColumn<bool>("result_ok");
    QTest::addColumn<int>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("stdout cpp")
    << "STDOUT_TARGET" << true << (int)ConsoleAppender::STDOUT_TARGET << 0;
    QTest::newRow("stdout java")
    << "System.out" << true << (int)ConsoleAppender::STDOUT_TARGET << 0;
    QTest::newRow("stderr cpp")
    << "STDERR_TARGET" << true << (int)ConsoleAppender::STDERR_TARGET << 0;
    QTest::newRow("stderr java")
    << "System.err" << true << (int)ConsoleAppender::STDERR_TARGET << 0;
    QTest::newRow("trim")
    << "  STDOUT_TARGET  " << true << (int)ConsoleAppender::STDOUT_TARGET << 0;
    QTest::newRow("error")
    << "Hello" << false << (int)ConsoleAppender::STDOUT_TARGET << 1;
}


void Log4QtTest::OptionConverter_target()
{
    QFETCH(QString, value);
    QFETCH(bool, result_ok);
    QFETCH(int, result);
    QFETCH(int, event_count);

    mpLoggingEvents->clearList();
    bool ok;
    QCOMPARE(OptionConverter::toTarget(value, &ok), result);
    QCOMPARE(ok, result_ok);
    QCOMPARE(mpLoggingEvents->list().count(), event_count);
}



/******************************************************************************
 * Factory requires OptionConverter                                           */


void Log4QtTest::Factory_createAppender_data()
{
    QTest::addColumn<QString>("classname");
    QTest::addColumn<QString>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("ConsoleAppender java")
    << "org.apache.log4j.ConsoleAppender" << "Log4Qt::ConsoleAppender" << 0;
    QTest::newRow("ConsoleAppender cpp")
    << "Log4Qt::ConsoleAppender" << "Log4Qt::ConsoleAppender" << 0;
    QTest::newRow("DailyRollingFileAppender java")
    << "org.apache.log4j.DailyRollingFileAppender" << "Log4Qt::DailyRollingFileAppender" << 0;
    QTest::newRow("DailyRollingFileAppender cpp")
    << "Log4Qt::DailyRollingFileAppender" << "Log4Qt::DailyRollingFileAppender" << 0;
    QTest::newRow("DebugAppender java")
    << "org.apache.log4j.varia.DebugAppender" << "Log4Qt::DebugAppender" << 0;
    QTest::newRow("DebugAppender cpp")
    << "Log4Qt::DebugAppender" << "Log4Qt::DebugAppender" << 0;
    QTest::newRow("FileAppender java")
    << "org.apache.log4j.FileAppender" << "Log4Qt::FileAppender" << 0;
    QTest::newRow("FileAppender cpp")
    << "Log4Qt::FileAppender" << "Log4Qt::FileAppender" << 0;
    QTest::newRow("ListAppender java")
    << "org.apache.log4j.varia.ListAppender" << "Log4Qt::ListAppender" << 0;
    QTest::newRow("ListAppender cpp")
    << "Log4Qt::ListAppender" << "Log4Qt::ListAppender" << 0;
    QTest::newRow("NullAppender java")
    << "org.apache.log4j.varia.NullAppender" << "Log4Qt::NullAppender" << 0;
    QTest::newRow("NullAppender cpp")
    << "Log4Qt::NullAppender" << "Log4Qt::NullAppender" << 0;
    QTest::newRow("RollingFileAppender java")
    << "org.apache.log4j.RollingFileAppender" << "Log4Qt::RollingFileAppender" << 0;
    QTest::newRow("RollingFileAppender cpp")
    << "Log4Qt::RollingFileAppender" << "Log4Qt::RollingFileAppender" << 0;
}


void Log4QtTest::Factory_createAppender()
{
    QFETCH(QString, classname);
    QFETCH(QString, result);
    QFETCH(int, event_count);

    mpLoggingEvents->clearList();
    QObject *p_object = Factory::createAppender(classname);
    QVERIFY(p_object != 0);
    QCOMPARE(QString::fromLatin1(p_object->metaObject()->className()), result);
    delete p_object;
    QCOMPARE(mpLoggingEvents->list().count(), event_count);
}


void Log4QtTest::Factory_createFilter_data()
{
    QTest::addColumn<QString>("classname");
    QTest::addColumn<QString>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("DenyAllFilter java")
    << "org.apache.log4j.varia.DenyAllFilter" << "Log4Qt::DenyAllFilter" << 0;
    QTest::newRow("DenyAllFilter cpp")
    << "Log4Qt::DenyAllFilter" << "Log4Qt::DenyAllFilter" << 0;
    QTest::newRow("LevelMatchFilter java")
    << "org.apache.log4j.varia.LevelMatchFilter" << "Log4Qt::LevelMatchFilter" << 0;
    QTest::newRow("LevelMatchFilter cpp")
    << "Log4Qt::LevelMatchFilter" << "Log4Qt::LevelMatchFilter" << 0;
    QTest::newRow("LevelRangeFilter java")
    << "org.apache.log4j.varia.LevelRangeFilter" << "Log4Qt::LevelRangeFilter" << 0;
    QTest::newRow("LevelRangeFilter cpp")
    << "Log4Qt::LevelRangeFilter" << "Log4Qt::LevelRangeFilter" << 0;
    QTest::newRow("StringMatchFilter java")
    << "org.apache.log4j.varia.StringMatchFilter" << "Log4Qt::StringMatchFilter" << 0;
    QTest::newRow("StringMatchFilter cpp")
    << "Log4Qt::StringMatchFilter" << "Log4Qt::StringMatchFilter" << 0;
}


void Log4QtTest::Factory_createFilter()
{
    QFETCH(QString, classname);
    QFETCH(QString, result);
    QFETCH(int, event_count);

    mpLoggingEvents->clearList();
    QObject *p_object = Factory::createFilter(classname);
    QVERIFY(p_object != 0);
    QCOMPARE(QString::fromLatin1(p_object->metaObject()->className()), result);
    delete p_object;
    QCOMPARE(mpLoggingEvents->list().count(), event_count);
}


void Log4QtTest::Factory_createLayout_data()
{
    QTest::addColumn<QString>("classname");
    QTest::addColumn<QString>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("PatternLayout java")
    << "org.apache.log4j.PatternLayout" << "Log4Qt::PatternLayout" << 0;
    QTest::newRow("PatternLayout cpp")
    << "Log4Qt::PatternLayout" << "Log4Qt::PatternLayout" << 0;
    QTest::newRow("SimpleLayout java")
    << "org.apache.log4j.SimpleLayout" << "Log4Qt::SimpleLayout" << 0;
    QTest::newRow("SimpleLayout cpp")
    << "Log4Qt::SimpleLayout" << "Log4Qt::SimpleLayout" << 0;
    QTest::newRow("TTCCLayout java")
    << "org.apache.log4j.TTCCLayout" << "Log4Qt::TTCCLayout" << 0;
    QTest::newRow("TTCCLayout cpp")
    << "Log4Qt::TTCCLayout" << "Log4Qt::TTCCLayout" << 0;
}


void Log4QtTest::Factory_createLayout()
{
    QFETCH(QString, classname);
    QFETCH(QString, result);
    QFETCH(int, event_count);

    mpLoggingEvents->clearList();
    QObject *p_object = Factory::createLayout(classname);
    QVERIFY(p_object != 0);
    QCOMPARE(QString::fromLatin1(p_object->metaObject()->className()), result);
    delete p_object;
    QCOMPARE(mpLoggingEvents->list().count(), event_count);
}


void Log4QtTest::Factory_setObjectProperty_data()
{
    QTest::addColumn<QString>("appenderclass");
    QTest::addColumn<QString>("property");
    QTest::addColumn<QString>("value");
    QTest::addColumn<QString>("result_value");
    QTest::addColumn<int>("event_count");

    QTest::newRow("Bool")
    << "Log4Qt::FileAppender"
    << "immediateFlush" << "false"
    << "false" << 0;
    QTest::newRow("Int")
    << "Log4Qt::ListAppender"
    << "maxCount" << "10"
    << "10" << 0;
    QTest::newRow("QString")
    << "Log4Qt::FileAppender"
    << "file" << "C:\\tmp\\mylog.txt"
    << "C:\\tmp\\mylog.txt" << 0;
    QTest::newRow("Null object")
    << "Log4Qt::NullAppender"
    << "maxCount" << "10"
    << "" << 1;
    QTest::newRow("Empty property string")
    << "Log4Qt::NullAppender"
    << "" << "10"
    << "" << 1;
    QTest::newRow("Property does not exist")
    << "Log4Qt::NullAppender"
    << "Colour" << "10"
    << "" << 1;
    QTest::newRow("Property not writable")
    << "Log4Qt::NullAppender"
    << "isClosed" << "10"
    << "" << 1;
    QTest::newRow("Property of wrong type")
    << "Log4Qt::RollingFileAppender"
    << "maximumFileSize" << "7"
    << "" << 1; 
}


void Log4QtTest::Factory_setObjectProperty()
{
    QFETCH(QString, appenderclass);
    QFETCH(QString, property);
    QFETCH(QString, value);
    QFETCH(QString, result_value);
    QFETCH(int, event_count);

    mpLoggingEvents->clearList();
    QObject *p_object = Factory::createAppender(appenderclass);

    Factory::setObjectProperty(p_object, property, value);
    QCOMPARE(mpLoggingEvents->list().count(), event_count);
    if (mpLoggingEvents->list().count() == 0)
        QCOMPARE(p_object->property(property.toLatin1()).toString(), result_value);

    delete p_object;
}



/******************************************************************************
 * log4qt/varia                                                               */


void Log4QtTest::ListAppender()
{
    Log4Qt::ListAppender appender;

    // Store messages
    QCOMPARE(appender.list().count(), 0);
    appender.doAppend(LoggingEvent(test_logger(), Level::WARN_INT, "Message1"));
    appender.doAppend(LoggingEvent(test_logger(), Level::WARN_INT, "Message2"));
    appender.doAppend(LoggingEvent(test_logger(), Level::WARN_INT, "Message3"));
    QCOMPARE(appender.list().count(), 3);

    // Delete oldest, if max is set
    appender.setMaxCount(2);
    QCOMPARE(appender.list().count(), 2);
    QCOMPARE(appender.list().at(0).message(), QString("Message2"));
    QCOMPARE(appender.list().at(1).message(), QString("Message3"));

    // Ignore new ones added
    appender.doAppend(LoggingEvent(test_logger(), Level::WARN_INT, "Message4"));
    QCOMPARE(appender.list().count(), 2);
    QCOMPARE(appender.list().at(0).message(), QString("Message2"));
    QCOMPARE(appender.list().at(1).message(), QString("Message3"));

    // Clear
    appender.clearList();
    QCOMPARE(appender.list().count(), 0);
}


void Log4QtTest::DenyAllFilter()
{
    Log4Qt::DenyAllFilter filter;
    LoggingEvent event(test_logger(), Level::WARN_INT, "Message");
    QCOMPARE(filter.decide(event), Filter::DENY);
}


void Log4QtTest::LevelMatchFilter_data()
{
    QTest::addColumn<QString>("filter_level");
    QTest::addColumn<bool>("accept_on_match");
    QTest::addColumn<QString>("event_level");
    QTest::addColumn<QString>("result");

    QTest::newRow("No match, No accept") << "WARN" << false << "TRACE" << "NEUTRAL";
    QTest::newRow("No match, Accept") << "WARN" << true << "TRACE" << "NEUTRAL";
    QTest::newRow("Match, No accept") << "WARN" << false << "WARN" << "DENY";
    QTest::newRow("Match, Accept") << "WARN" << true << "WARN" << "ACCEPT";
}


void Log4QtTest::LevelMatchFilter()
{
    QFETCH(QString, filter_level);
    QFETCH(bool, accept_on_match);
    QFETCH(QString, event_level);
    QFETCH(QString, result);

    Log4Qt::LevelMatchFilter filter;
    filter.setLevelToMatch(Level::fromString(filter_level));
    filter.setAcceptOnMatch(accept_on_match);
    LoggingEvent
                    event(test_logger(), Level::fromString(event_level),
                                    "Message");

    QString decision =
                    enumValueToKey(&filter, "Decision", filter.decide(event));
    QCOMPARE(decision, result);
}


void Log4QtTest::LevelRangeFilter_data()
{
    QTest::addColumn<QString>("filter_min_level");
    QTest::addColumn<QString>("filter_max_level");
    QTest::addColumn<bool>("accept_on_match");
    QTest::addColumn<QString>("event_level");
    QTest::addColumn<QString>("result");

    QTest::newRow("Too low, No accept") << "DEBUG" << "ERROR" << false << "TRACE" << "DENY";
    QTest::newRow("Too high, No accept") << "DEBUG" << "ERROR" << false << "FATAL" << "DENY";
    QTest::newRow("Min, No accept") << "DEBUG" << "ERROR" << false << "DEBUG" << "NEUTRAL";
    QTest::newRow("Inside, No accept") << "DEBUG" << "ERROR" << false << "WARN" << "NEUTRAL";
    QTest::newRow("Max, No accept") << "DEBUG" << "ERROR" << false << "ERROR" << "NEUTRAL";
    QTest::newRow("Min not initialised, No accept") << "" << "ERROR" << false << "TRACE" << "NEUTRAL";
    QTest::newRow("Max not initialised, No accept") << "DEBUG" << "" << false << "FATAL" << "NEUTRAL";
    QTest::newRow("Too low, Accept") << "DEBUG" << "ERROR" << true << "TRACE" << "DENY";
    QTest::newRow("Too high, Accept") << "DEBUG" << "ERROR" << true << "FATAL" << "DENY";
    QTest::newRow("Min, Accept") << "DEBUG" << "ERROR" << true << "DEBUG" << "ACCEPT";
    QTest::newRow("Inside, Accept") << "DEBUG" << "ERROR" << true << "WARN" << "ACCEPT";
    QTest::newRow("Max, Accept") << "DEBUG" << "ERROR" << true << "ERROR" << "ACCEPT";
    QTest::newRow("Min not initialised, Accept") << "" << "ERROR" << true << "TRACE" << "ACCEPT";
    QTest::newRow("Max not initialised, Accept") << "DEBUG" << "" << true << "FATAL" << "ACCEPT";
}


void Log4QtTest::LevelRangeFilter()
{
    QFETCH(QString, filter_min_level);
    QFETCH(QString, filter_max_level);
    QFETCH(bool, accept_on_match);
    QFETCH(QString, event_level);
    QFETCH(QString, result);

    Log4Qt::LevelRangeFilter filter;
    if (!filter_min_level.isEmpty())
        filter.setLevelMin(Level::fromString(filter_min_level));
    if (!filter_max_level.isEmpty())
        filter.setLevelMax(Level::fromString(filter_max_level));
    filter.setAcceptOnMatch(accept_on_match);
    LoggingEvent
                    event(test_logger(), Level::fromString(event_level),
                                    "Message");

    QString decision =
                    enumValueToKey(&filter, "Decision", filter.decide(event));
    QCOMPARE(decision, result);
}


void Log4QtTest::StringMatchFilter_data()
{
    QTest::addColumn<QString>("filter_string");
    QTest::addColumn<bool>("accept_on_match");
    QTest::addColumn<QString>("event_string");
    QTest::addColumn<QString>("result");

    QTest::newRow("No match, No accept") << "MESSAGE" << false << "This is a message" << "NEUTRAL";
    QTest::newRow("Match, No accept") << "This" << false << "This is a message" << "DENY";
    QTest::newRow("No match, Accept") << "MESSAGE" << true << "This is a message" << "NEUTRAL";
    QTest::newRow("Match, Accept") << "This" << true << "This is a message" << "ACCEPT";
    QTest::newRow("Empty message, No accept") << "This" << false << "" << "NEUTRAL";
    QTest::newRow("Empty message, Accept") << "This" << true << "" << "NEUTRAL";
    QTest::newRow("Empty filter, No accept") << "" << false << "This is a message" << "NEUTRAL";
    QTest::newRow("Empty filter, Accept") << "" << true << "This is a message" << "NEUTRAL";
}


void Log4QtTest::StringMatchFilter()
{
    QFETCH(QString, filter_string);
    QFETCH(bool, accept_on_match);
    QFETCH(QString, event_string);
    QFETCH(QString, result);

    Log4Qt::StringMatchFilter filter;
    filter.setStringToMatch(filter_string);
    filter.setAcceptOnMatch(accept_on_match);
    LoggingEvent event(test_logger(), Level::WARN_INT, event_string);

    QString decision =
                    enumValueToKey(&filter, "Decision", filter.decide(event));
    QCOMPARE(decision, result);
}



/******************************************************************************
 * log4qt                                                                     */


void Log4QtTest::AppenderSkeleton_threshold()
{
    resetLogging();

    Log4Qt::ListAppender *p_appender = new Log4Qt::ListAppender();
    Log4Qt::Logger *p_logger = test_logger();
    p_logger->addAppender(p_appender);

    // Threshold
    p_appender->setThreshold(Level::ERROR_INT);
    p_appender->doAppend(LoggingEvent(p_logger, Level::WARN_INT, "Warn"));
    p_appender->doAppend(LoggingEvent(p_logger, Level::ERROR_INT, "Error"));
    p_appender->doAppend(LoggingEvent(p_logger, Level::FATAL_INT, "Fatal"));
    QCOMPARE(p_appender->list().count(), 2);
    QCOMPARE(p_appender->list().at(0).level(), Level(Level::ERROR_INT));
    QCOMPARE(p_appender->list().at(1).level(), Level(Level::FATAL_INT));
}


void Log4QtTest::AppenderSkeleton_filter_data()
{
    resetLogging();

    QTest::addColumn<QString>("filter1_level");
    QTest::addColumn<bool>("filter1_accept");
    QTest::addColumn<QString>("filter2_level");
    QTest::addColumn<bool>("filter2_accept");
    QTest::addColumn<QString>("event_level");
    QTest::addColumn<int>("event_count");

    QTest::newRow("Single filter, NEUTRAL")
    << "WARN" << true << "" << true << "TRACE" << 1;
    QTest::newRow("Single filter, ACCEPT")
    << "WARN" << true << "" << true << "WARN" << 1;
    QTest::newRow("Single filter, DENY")
    << "WARN" << false << "" << true << "WARN" << 0;

    QTest::newRow("Double filter, NEUTRAL NEUTRAL")
    << "WARN" << true << "WARN" << true << "TRACE" << 1;
    QTest::newRow("Double filter, NEUTRAL ACCEPT")
    << "WARN" << true << "TRACE" << true << "TRACE" << 1;
    QTest::newRow("Double filter, NEUTRAL DENY")
    << "WARN" << true << "TRACE" << false << "TRACE" << 0;

    QTest::newRow("Double filter, ACCEPT NEUTRAL")
    << "WARN" << true << "TRACE" << true << "WARN" << 1;
    QTest::newRow("Double filter, ACCEPT ACCEPT")
    << "WARN" << true << "WARN" << true << "WARN" << 1;
    QTest::newRow("Double filter, ACCEPT DENY")
    << "WARN" << true << "WARN" << false << "WARN" << 1;

    QTest::newRow("Double filter, DENY NEUTRAL")
    << "WARN" << false << "TRACE" << true << "WARN" << 0;
    QTest::newRow("Double filter, DENY ACCEPT")
    << "WARN" << false << "WARN" << true << "WARN" << 0;
    QTest::newRow("Double filter, DENY DENY")
    << "WARN" << false << "WARN" << false << "WARN" << 0;
}


void Log4QtTest::AppenderSkeleton_filter()
{
    QFETCH(QString, filter1_level);
    QFETCH(bool, filter1_accept);
    QFETCH(QString, filter2_level);
    QFETCH(bool, filter2_accept);
    QFETCH(QString, event_level);
    QFETCH(int, event_count);

    Log4Qt::ListAppender appender;

    if (!filter1_level.isEmpty())
    {
        Log4Qt::LevelMatchFilter *p_filter = new Log4Qt::LevelMatchFilter();
        p_filter->setLevelToMatch(Level::fromString(filter1_level));
        p_filter->setAcceptOnMatch(filter1_accept);
        appender.addFilter(p_filter);
    }
    if (!filter2_level.isEmpty())
    {
        Log4Qt::LevelMatchFilter *p_filter = new Log4Qt::LevelMatchFilter();
        p_filter->setLevelToMatch(Level::fromString(filter2_level));
        p_filter->setAcceptOnMatch(filter2_accept);
        appender.addFilter(p_filter);
    }

    appender.doAppend(LoggingEvent(test_logger(),
                    Level::fromString(event_level), "Message"));
    QCOMPARE(appender.list().count(), event_count);
}


void Log4QtTest::BasicConfigurator()
{
    LogManager::resetConfiguration();
    resetLogging();
    QVERIFY(Log4Qt::BasicConfigurator::configure());

    Logger *p_logger = LogManager::rootLogger();
    QCOMPARE(p_logger->appenders().count(), 1);
    ConsoleAppender *p_appender =
                    qobject_cast<ConsoleAppender *>(p_logger->appenders().at(0));
    QCOMPARE(p_appender != 0, true);
    QVERIFY(p_appender->isActive());
    QVERIFY(!p_appender->isClosed());
    QCOMPARE(p_appender->target(), QString::fromLatin1("STDOUT_TARGET"));
    PatternLayout *p_layout =
                    qobject_cast<PatternLayout *>(p_appender->layout());
    QVERIFY(p_layout != 0);
    QCOMPARE(p_layout->conversionPattern(), QString("%r [%t] %p %c %x - %m%n"));

    // Log4Qt::Logger *logger = LogManager::rootLogger();
    // logger->trace("Trace message"); //Disabled by default
    // logger->debug("Debug message");
    // logger->info("Info message");
    // logger->warn("Warn message");
    // logger->error("Error message");
    // logger->fatal("Fatal message");
}


void Log4QtTest::FileAppender()
{
    resetLogging();

    QString dir(mTemporaryDirectory.path() + "/FileAppender");
    QString file("/log");

    Log4Qt::FileAppender appender1(new SimpleLayout(), dir + file, false);
    appender1.setName("Fileappender1");
    appender1.activateOptions();
    appender1.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                    QString("Message 0")));
    appender1.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                    QString("Message 1")));
    appender1.close();
    QStringList expected;
    QString result;
    expected << "log";
    if (!validateDirContents(dir, expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected << "DEBUG - Message 0" << "DEBUG - Message 1";
    if (!validateFileContents(dir + file, expected, result))
        QFAIL(qPrintable(result));

    Log4Qt::FileAppender appender2(new SimpleLayout(), dir + file, false);
    appender2.setName("Fileappender2");
    appender2.activateOptions();
    appender2.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                    QString("Message 2")));
    appender2.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                    QString("Message 3")));
    appender2.close();
    expected.clear();
    expected << "log";
    if (!validateDirContents(dir, expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected << "DEBUG - Message 2" << "DEBUG - Message 3";
    if (!validateFileContents(dir + file, expected, result))
        QFAIL(qPrintable(result));

    Log4Qt::FileAppender appender3(new SimpleLayout(), dir + file, true);
    appender3.setName("Fileappender3");
    appender3.activateOptions();
    appender3.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                    QString("Message 4")));
    appender3.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                    QString("Message 5")));
    appender3.close();
    expected.clear();
    expected << "log";
    if (!validateDirContents(dir, expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected
        << "DEBUG - Message 2" << "DEBUG - Message 3"
        << "DEBUG - Message 4" << "DEBUG - Message 5";
    if (!validateFileContents(dir + file, expected, result))
        QFAIL(qPrintable(result));

    QCOMPARE(mpLoggingEvents->list().count(), 0);
}


void Log4QtTest::DailyRollingFileAppender()
{
    resetLogging();

    if (mSkipLongTests)
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
        QSKIP("Skipping long running test");
#else
        QSKIP("Skipping long running test", SkipSingle);
#endif
    qDebug() << "The test is time based and takes approximately 3 minutes ...";

    QString dir(mTemporaryDirectory.path() + "/DailyRollingFileAppender");
    QString file("/log");

    // Using a RollingFileAppender with 2 files history and 3 messages per file
    Log4Qt::DailyRollingFileAppender appender;
    appender.setName("DailyRollingFileAppender");
    appender.setFile(dir + file);
    appender.setLayout(new SimpleLayout());
    appender.setDatePattern(DailyRollingFileAppender::MINUTELY_ROLLOVER);

    // Start on a full minute
    QDateTime now = QDateTime::currentDateTime();
    QTest::qSleep((60 - now.time().second()) * 1000);
    appender.activateOptions();

    qDebug() << "   1 / 7";
    appender.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                    QString("Message 0")));
    int i;
    for (i = 1; i < 7; i++)
    {
        QTest::qSleep(21 * 1000);
        qDebug() << "  " << i + 1 << "/" << 7;
        appender.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                        QString("Message %1").arg(i)));
    }

    QCOMPARE(mpLoggingEvents->list().count(), 0);

    // Validate directory
    QStringList expected;
    QString result;
    expected << "log" << "log"
                    + dailyRollingFileAppenderSuffix(now.addSecs(60)) << "log"
                    + dailyRollingFileAppenderSuffix(now.addSecs(120));
    if (!validateDirContents(dir, expected, result))
        QFAIL(qPrintable(result));

    // Validate files
    expected.clear();
    expected << "DEBUG - Message 6";
    if (!validateFileContents(dir + file, expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected << "DEBUG - Message 0" << "DEBUG - Message 1"
                    << "DEBUG - Message 2";
    if (!validateFileContents(dir + file
                    + dailyRollingFileAppenderSuffix(now.addSecs(60)),
                    expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected << "DEBUG - Message 3" << "DEBUG - Message 4"
                    << "DEBUG - Message 5";
    if (!validateFileContents(dir + file
                    + dailyRollingFileAppenderSuffix(now.addSecs(120)),
                    expected, result))
        QFAIL(qPrintable(result));
}


void Log4QtTest::LoggingEvent_stream_data()
{
    QTest::addColumn<LoggingEvent>("original");

    qint64 timestamp =
    DateTime(QDateTime(QDate(2001,9,7), QTime(15,7,5,9))).toMilliSeconds();
    QHash<QString, QString> properties;
    properties.insert("A", "a");
    properties.insert("B", "b");
    properties.insert("C", "c");

    QTest::newRow("Empty logging event")
    << LoggingEvent();
    QTest::newRow("Logging event")
    << LoggingEvent(test_logger(),
                    Level(Level::WARN_INT),
                    "This is a message",
                    "NDC",
                    properties,
                    "main",
                    timestamp);
    QTest::newRow("Logging no logger")
    << LoggingEvent(0,
                    Level(Level::WARN_INT),
                    "This is a message",
                    "NDC",
                    properties,
                    "main",
                    timestamp);

    resetLogging();
}


void Log4QtTest::LoggingEvent_stream()
{
    QFETCH(LoggingEvent, original);

    QByteArray array;
    QBuffer buffer(&array);
    buffer.open(QIODevice::WriteOnly);
    QDataStream stream(&buffer);
    stream << original;
    buffer.close();

    buffer.open(QIODevice::ReadOnly);
    LoggingEvent streamed;
    stream >> streamed;
    buffer.close();

    QCOMPARE(original.level(), streamed.level());
    QCOMPARE(original.loggerName(), streamed.loggerName());
    QCOMPARE(original.message(), streamed.message());
    QCOMPARE(original.ndc(), streamed.ndc());
    QCOMPARE(original.properties().count(), streamed.properties().count());
    QStringList keys = original.properties().keys();
    QString key;
    Q_FOREACH(key, keys)
    {
        QVERIFY(streamed.properties().contains(key));
        QCOMPARE(original.properties().value(key),
                        streamed.properties().value(key));
    }
    QCOMPARE(original.sequenceNumber(), streamed.sequenceNumber());
    QCOMPARE(original.threadName(), streamed.threadName());
    QCOMPARE(original.timeStamp(), streamed.timeStamp());

    QCOMPARE(mpLoggingEvents->list().count(), 0);
}


void Log4QtTest::LogManager_configureLogLogger()
{
    resetLogging();
    LogManager::logLogger()->removeAppender(mpLoggingEvents);

    LogManager::resetConfiguration();
    Log4Qt::Logger *p_logger = LogManager::logLogger();
    QCOMPARE(p_logger->appenders().count(), 2);
    ConsoleAppender *p_appender;
    TTCCLayout *p_layout;

    p_appender = qobject_cast<ConsoleAppender *>(p_logger->appenders().at(0));
    QCOMPARE(p_appender != 0, true);
    QVERIFY(p_appender->isActive());
    QVERIFY(!p_appender->isClosed());
    QCOMPARE(p_appender->target(), QString::fromLatin1("STDOUT_TARGET"));
    p_layout = qobject_cast<TTCCLayout *>(p_appender->layout());
    QVERIFY(p_layout != 0);

    p_appender = qobject_cast<ConsoleAppender *>(p_logger->appenders().at(1));
    QCOMPARE(p_appender != 0, true);
    QVERIFY(p_appender->isActive());
    QVERIFY(!p_appender->isClosed());
    QCOMPARE(p_appender->target(), QString::fromLatin1("STDERR_TARGET"));
    p_layout = qobject_cast<TTCCLayout *>(p_appender->layout());
    QVERIFY(p_layout != 0);

}


void Log4QtTest::PropertyConfigurator_missing_appender()
{
    LogManager::resetConfiguration();
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();
    
    mProperties.setProperty("log4j.logger.MissingAppender", 
                            "INHERITED, A");
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(mpLoggingEvents->list().count(), 1);
}


void Log4QtTest::PropertyConfigurator_unknown_appender_class()
{
    LogManager::resetConfiguration();
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();
    
    mProperties.setProperty("log4j.logger.UnknownAppender", 
                            "INHERITED, A");
    mProperties.setProperty("log4j.appender.A",
                            "org.apache.log4j.UnknownAppender");
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1); 
    QCOMPARE(mpLoggingEvents->list().count(), 2); // Warning from Factory, Error PropertyConfigurator
}


void Log4QtTest::PropertyConfigurator_missing_layout()
{
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();
    
    mProperties.setProperty("log4j.logger.MissingLayout", 
                            "INHERITED, A");
    mProperties.setProperty("log4j.appender.A",
                            "org.apache.log4j.ConsoleAppender");
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(mpLoggingEvents->list().count(), 1);
}


void Log4QtTest::PropertyConfigurator_unknown_layout_class()
{
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();
    
    mProperties.setProperty("log4j.logger.UnknownLayout", 
                            "INHERITED, A");
    mProperties.setProperty("log4j.appender.A",
                            "org.apache.log4j.ConsoleAppender");
    mProperties.setProperty("log4j.appender.A.layout",
                            "org.apache.log4j.UnknownLayout");
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(mpLoggingEvents->list().count(), 2); // Warning from Factory, Error PropertyConfigurator
}


void Log4QtTest::PropertyConfigurator_reset()
{
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    // - Create a logger with an appender
    // - If the reset flag is not set, configure must leave the appender
    // - If the reset flag is set to an invalid value, configure must raise an
    //   error and leave the appender
    // - If the reset flag is set, configure must remove the appender
    
    const QLatin1String key_reset("log4j.reset");    
    test_logger()->addAppender(new Log4Qt::ListAppender);
    mProperties.setProperty(key_reset, 
                            "false");
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(mpLoggingEvents->list().count(), 0);
    QCOMPARE(test_logger()->appenders().count(), 1);
    
    mProperties.setProperty(key_reset, 
                            "No boolean");
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(mpLoggingEvents->list().count(), 1);
    QCOMPARE(test_logger()->appenders().count(), 1);

    resetLogging();
    mProperties.setProperty(key_reset, 
                            "true");
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(mpLoggingEvents->list().count(), 0);
    QCOMPARE(test_logger()->appenders().count(), 0);
}


void Log4QtTest::PropertyConfigurator_debug()
{
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    // - Set the log logger level to INFO
    // - If debug is not set, configure must leave the log logger level 
    //   unaltered
    // - If debug is set, but with no valid level string, configure must set 
    //   the log logger level to DEBUG
    // - If debug is set to the level TRACE, configure must set the log logger
    //   level to TRACE

    const QLatin1String key_debug("log4j.Debug");    
    Logger *p_logger = LogManager::logLogger();
    p_logger->setLevel(Level::INFO_INT);
    
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(mpLoggingEvents->list().count(), 0);
    QCOMPARE(p_logger->level(), Level(Level::INFO_INT));
    
    mProperties.setProperty(key_debug, 
                            "true");
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    // QCOMPARE(mpLoggingEvents->list().count(), 1); // Warning from Level::fromString() & several debug messages
    QCOMPARE(p_logger->level(), Level(Level::DEBUG_INT));
    
    mpLoggingEvents->clearList();
    mProperties.setProperty(key_debug, 
                            "TRACE");
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    // QCOMPARE(mpLoggingEvents->list().count(), 1); // Warning from PropertyConfigurator & several debug/trace messages
    QCOMPARE(p_logger->level(), Level(Level::TRACE_INT));
}


void Log4QtTest::PropertyConfigurator_threshold()
{
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    // - Set the repository threshold to INFO
    // - If the threshold is not set, configure must leave the repository 
    //   threshold unaltered
    // - If the threshold is set to an invalid value, configure must raise an
    //   error and set the threshold to ALL
    // - If the threshold is set to WARN, configure must set the repository 
    //   threshold to WARN

    const QLatin1String key_threshold("log4j.threshold");    
    LogManager::loggerRepository()->setThreshold(Level::INFO_INT);
    
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(mpLoggingEvents->list().count(), 0);
    QCOMPARE(LogManager::loggerRepository()->threshold(), Level(Level::INFO_INT));
    
    mProperties.setProperty(key_threshold, 
                            "Not a value");
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(mpLoggingEvents->list().count(), 2); // Warning by Level, Error from OptionConverter
    QCOMPARE(LogManager::loggerRepository()->threshold(), Level(Level::ALL_INT));
    
    mpLoggingEvents->clearList();
    mProperties.setProperty(key_threshold, 
                            "WARN");
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(mpLoggingEvents->list().count(), 0);
    QCOMPARE(LogManager::loggerRepository()->threshold(), Level(Level::WARN_INT));
}


void Log4QtTest::PropertyConfigurator_handleQtMessages()
{
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();
    
    // - Set handle Qt messages to true
    // - If handle Qt messages is not set, configure must leave handle Qt 
    //   messages unaltered
    // - If handle Qt messages is set to an invalid value, configure must raise
    //   an error and set handle Qt messages to false
    // - If handle Qt messages is true, configure must set handle Qt messages
    //   to true

    const QLatin1String key_handle_qt_messages("log4j.handleQtMessages");
    LogManager::setHandleQtMessages(true);
    
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(mpLoggingEvents->list().count(), 0);
    QCOMPARE(LogManager::handleQtMessages(), true);
    
    mProperties.setProperty(key_handle_qt_messages, 
                            "No boolean");
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(mpLoggingEvents->list().count(), 1);
    QCOMPARE(LogManager::handleQtMessages(), false);
    
    mpLoggingEvents->clearList();
    mProperties.setProperty(key_handle_qt_messages, 
                            "true");
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(mpLoggingEvents->list().count(), 0);
    QCOMPARE(LogManager::handleQtMessages(), true);
}


void Log4QtTest::PropertyConfigurator_example()
{
    LogManager::resetConfiguration();
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    QString file(mTemporaryDirectory.path() + "/RollingFileAppender/log");

    // Based on the JavaDoc example:
    // - A1: JavaDoc uses SyslogAppender, which is not available on all platforms
    // - A2: JavaDoc does not set a file, which causes error on activation
    // - A2: JavaDoc uses default values for file size and backup index. Use
    //       different values.
    // - A2 Layout: ContextPrinting uses default enabled. Use disabled instead.
    // - root: JavaDoc uses default level DEBUG. Use INFO instead.
    // - SECURITY: JavaDoc uses INHERIT. Use INHERITED instead
    // - log4j.logger.class.of.the.day: JavaDoc uses INHERIT. Use INHERITED 
    //   instead
    
    // Appender A1: ConsoleAppender with PatternLayout
    mProperties.setProperty("log4j.appender.A1",
                    "org.apache.log4j.ConsoleAppender");
    mProperties.setProperty("log4j.appender.A1.Target", "System.Out");
    mProperties.setProperty("log4j.appender.A1.layout",
                    "org.apache.log4j.PatternLayout");
    mProperties.setProperty("log4j.appender.A1.layout.ConversionPattern",
                    "%-4r %-5p %c{2} %M.%L %x - %m\n");
    // Appender A2: RollingFileAppender with TTCCLayout
    mProperties.setProperty("log4j.appender.A2",
                    "org.apache.log4j.RollingFileAppender");
    mProperties.setProperty("log4j.appender.A2.File", file);
    mProperties.setProperty("log4j.appender.A2.MaxFileSize", "13MB");
    mProperties.setProperty("log4j.appender.A2.MaxBackupIndex", "7");
    mProperties.setProperty("log4j.appender.A2.layout",
                    "org.apache.log4j.TTCCLayout");
    mProperties.setProperty("log4j.appender.A2.layout.ContextPrinting",
                    "disabled");
    mProperties.setProperty("log4j.appender.A2.layout.DateFormat", "ISO8601");
    // Root Logger: Uses A2
    mProperties.setProperty("log4j.rootLogger", "INFO, A2");
    // Logger SECURITY: Uses A1
    mProperties.setProperty("log4j.logger.SECURITY", "INHERITED, A1");
    mProperties.setProperty("log4j.additivity.SECURITY", "false");
    // Logger SECURITY.access:
    mProperties.setProperty("log4j.logger.SECURITY.access", "WARN");
    // Logger class.of.the.day:
    mProperties.setProperty("log4j.logger.class.of.the.day", "INHERITED");

    // No warnings, no errors expected
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(mpLoggingEvents->list().count(), 0);

    // Root logger
    Logger *p_logger;    
    p_logger = LogManager::rootLogger();
    QCOMPARE(p_logger->level(), Level(Level::INFO_INT));
    QCOMPARE(p_logger->appenders().count(), 1);
    Log4Qt::RollingFileAppender *p_a2 = 
        qobject_cast<Log4Qt::RollingFileAppender *>(p_logger->appenders().at(0));
    QVERIFY(p_a2 != 0);
    QCOMPARE(p_a2->file(), file);
    QCOMPARE(p_a2->maximumFileSize(), Q_INT64_C(13*1024*1024));
    QCOMPARE(p_a2->maxBackupIndex(), 7);
    Log4Qt::TTCCLayout *p_a2layout = 
        qobject_cast<Log4Qt::TTCCLayout *>(p_a2->layout());
    QVERIFY(p_a2layout != 0);
    QCOMPARE(p_a2layout->contextPrinting(), false);
    QCOMPARE(p_a2layout->dateFormat(), QString::fromLatin1("ISO8601"));
    
    // Logger SECURITY
    QVERIFY(LogManager::exists("SECURITY"));
    p_logger = LogManager::logger("SECURITY");
    QCOMPARE(p_logger->level(), Level(Level::NULL_INT));
    QCOMPARE(p_logger->appenders().count(), 1);
    Log4Qt::ConsoleAppender *p_a1 = 
        qobject_cast<Log4Qt::ConsoleAppender *>(p_logger->appenders().at(0));
    QVERIFY(p_a1 != 0);
    QCOMPARE(p_a1->target(), QString::fromLatin1("STDOUT_TARGET"));
    Log4Qt::PatternLayout *p_a1layout = 
        qobject_cast<Log4Qt::PatternLayout *>(p_a1->layout());
    QVERIFY(p_a1layout != 0);
    QCOMPARE(p_a1layout->conversionPattern(), QString::fromLatin1("%-4r %-5p %c{2} %M.%L %x - %m\n"));
    
    // Logger SECURITY::access
    QVERIFY(LogManager::exists("SECURITY::access"));
    p_logger = LogManager::logger("SECURITY::access");
    QCOMPARE(p_logger->level(), Level(Level::WARN_INT));
    
    // Logger class::of::the::day
    QVERIFY(LogManager::exists("class::of::the::day"));
}


void Log4QtTest::RollingFileAppender()
{
    resetLogging();

    QString dir(mTemporaryDirectory.path() + "/RollingFileAppender");
    QString file("/log");

    // Using a RollingFileAppender with 2 files history and 3 messages per file
    Log4Qt::RollingFileAppender appender;
    appender.setName("RollingFileAppender");
    appender.setFile(dir + file);
    appender.setLayout(new SimpleLayout());
    appender.setMaxBackupIndex(2);
    appender.setMaximumFileSize(40);
    appender.activateOptions();

    // Output 9 messages 
    int i;
    for (i = 0; i < 10; i++)
        appender.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                        QString("Message %1").arg(i)));

    // No warnings or errors expected
    QCOMPARE(mpLoggingEvents->list().count(), 0);

    // Validate diretcory
    QStringList expected;
    QString result;
    expected << "log" << "log.1" << "log.2";
    if (!validateDirContents(dir, expected, result))
        QFAIL(qPrintable(result));

    // Validate files
    expected.clear();
    expected << "DEBUG - Message 9";
    if (!validateFileContents(dir + file, expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected << "DEBUG - Message 6" << "DEBUG - Message 7"
                    << "DEBUG - Message 8";
    if (!validateFileContents(dir + file + ".1", expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected << "DEBUG - Message 3" << "DEBUG - Message 4"
                    << "DEBUG - Message 5";
    if (!validateFileContents(dir + file + ".2", expected, result))
        QFAIL(qPrintable(result));

    QCOMPARE(mpLoggingEvents->list().count(), 0);
}


QString Log4QtTest::dailyRollingFileAppenderSuffix(const QDateTime &rDateTime)
{
    QString result(".");
    result += QString::number(rDateTime.date().year()).rightJustified(4, '0');
    result += '-';
    result += QString::number(rDateTime.date().month()).rightJustified(2, '0');
    result += '-';
    result += QString::number(rDateTime.date().day()).rightJustified(2, '0');
    result += '-';
    result += QString::number(rDateTime.time().hour()).rightJustified(2, '0');
    result += '-';
    result += QString::number(rDateTime.time().minute()).rightJustified(2, '0');
    return result;
}


QString Log4QtTest::enumValueToKey(QObject *pObject,
                                   const char* pEnumeration,
                                   int value)
{
    Q_ASSERT(pObject);
    Q_ASSERT(!QString(pEnumeration).isEmpty());

    int i = pObject->metaObject()->indexOfEnumerator(pEnumeration);
    Q_ASSERT(i >= 0);
    QMetaEnum enumerator = pObject->metaObject()->enumerator(i);
    Q_ASSERT(enumerator.isValid());
    QString result = enumerator.valueToKey(value);
    Q_ASSERT(!result.isNull());
    return result;
}


void Log4QtTest::resetLogging()
{
    Log4Qt::Logger *p_logger;

    // Log4Qt logger
    p_logger = LogManager::logLogger();
    p_logger->setAdditivity(false);
    p_logger->setLevel(Level::WARN_INT);
    p_logger->removeAllAppenders();

    // Log4QtTest appender
    p_logger->addAppender(mpLoggingEvents);
    mpLoggingEvents->clearList();
    mpLoggingEvents->clearFilters();
    mpLoggingEvents->setThreshold(Level::WARN_INT);

    // Test logger
    p_logger = test_logger();
    p_logger->setAdditivity(true);
    p_logger->setLevel(Level::NULL_INT);
}


bool Log4QtTest::compareStringLists(const QStringList &rActual,
                                    const QStringList &rExpected,
                                    const QString &rEntry,
                                    const QString &rEntries,
                                    QString &rResult)
{
    QString tab("   ");
    QString eol("\n");

    // Generate content string
    QString content;
    int i;
    content += tab + "Actual: " + rEntries + ": "
                    + QString::number(rActual.count()) + eol;
    for (i = 0; i < rActual.count(); i++)
        content += tab + tab + '\'' + rActual.at(i) + '\'' + eol;
    content += tab + "Expected: " + rEntries + ": "
                    + QString::number(rExpected.count()) + eol;
    for (i = 0; i < rExpected.count(); i++)
        content += tab + tab + '\'' + rExpected.at(i) + '\'' + eol;

    // Check count
    if (rActual.count() != rExpected.count())
    {
        rResult = tab + "Compared " + rEntry + " counts are not the same" + eol;
        rResult += content;
        return false;
    }

    // Check entries
    for (i = 0; i < rActual.count(); i++)
    {
        if (rActual.at(i) != rExpected.at(i))
        {
            rResult = tab + rEntry + " " + QString::number(i + 1)
                            + " is not the same" + eol;
            rResult += content;
            return false;
        }
    }

    rResult.clear();
    return true;
}


bool Log4QtTest::deleteDirectoryTree(const QString &rName)
{
    QFileInfo file_info(rName);
    if (!file_info.exists())
        return true;
    if (file_info.isDir())
    {
        QDir d(rName);
        QStringList members = d.entryList(QDir::Dirs | QDir::Files
                        | QDir::NoDotAndDotDot | QDir::NoSymLinks
                        | QDir::Hidden, QDir::Name | QDir::DirsFirst);
        QString member;
        Q_FOREACH(member, members)
        if (!deleteDirectoryTree(rName + '/' + member))
        return false;
        if (d.rmdir(rName))
            return true;
        qDebug() << "Unable to remove directory: " << rName;
        return false;
    }
    else
    {
        QFile f(rName);
        if (f.remove())
            return true;
        qDebug() << "Unable to remove file: " << rName << "("
                        << f.errorString() << ")";
        return false;
    }
}


bool Log4QtTest::validateDirContents(const QString &rName,
                                     const QStringList &rExpected,
                                     QString &rResult)
{
    QDir dir(rName);
    if (!dir.exists())
    {
        rResult = QString("The dir '%1' does not exist").arg(rName);
        return false;
    }

    QStringList actual = dir.entryList(QDir::Dirs | QDir::Files
                    | QDir::NoDotAndDotDot | QDir::NoSymLinks | QDir::Hidden,
                    QDir::Name | QDir::DirsFirst);
    if (!compareStringLists(actual, rExpected, "Entry", "Entries", rResult))
    {
        QString error =
                        "The directory contents validation failed.\n   '%1'\n%2";
        rResult = error.arg(rName, rResult);
        return false;
    }

    return true;
}


bool Log4QtTest::validateFileContents(const QString &rName,
                                      const QStringList &rExpected,
                                      QString &rResult)
{
    QFile file(rName);
    if (!file.exists())
    {
        rResult = QString("The expected file '%1' does not exist (%2)").arg(rName).arg(file.errorString());
        return false;
    }
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        rResult = QString("The expected file '%1' cannot be opened (%2)").arg(rName).arg(file.errorString());
        return false;
    }

    QStringList actual;
    QTextStream textstream(&file);
    QString line = textstream.readLine();
    while (!line.isNull())
    {
        actual << line;
        line = textstream.readLine();
    }
    if (!compareStringLists(actual, rExpected, "Line", "Lines", rResult))
    {
        QString error = "The file contents validation failed.\n   '%1'\n%2";
        rResult = error.arg(rName, rResult);
        return false;
    }

    return true;
}



/******************************************************************************
 * Implementation: Operators, Helper
 ******************************************************************************/


