/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtGui/QtGui>
#include <QtTest/QtTest>

#include <stddef.h>

QT_USE_NAMESPACE

// this test only works with
//   * GLIBC
//   * MSVC - only debug builds (we need the crtdbg.h helpers)
//   * SYMBIAN
#if (defined(QT_NO_EXCEPTIONS) || (!defined(__GLIBC__) && !defined(Q_CC_MSVC) && !defined(Q_OS_SYMBIAN))) && !defined(Q_MOC_RUN)
    QTEST_NOOP_MAIN
#else

#include "oomsimulator.h"
#if !defined(Q_OS_SYMBIAN)
#include "3rdparty/memcheck.h"
#endif

class tst_ExceptionSafetyObjects: public QObject
{
    Q_OBJECT

public slots:
    void initTestCase();
    void cleanupTestCase();

private slots:
    void objects_data();
    void objects();

    void widgets_data();
    void widgets();

    void vector_data();
    void vector();

    void list_data();
    void list();

    void linkedList_data();
    void linkedList();

private:
    static QtMsgHandler testMessageHandler;
    static void safeMessageHandler(QtMsgType, const char *);
};

// helper structs to create an arbitrary widget
struct AbstractTester
{
    virtual void operator()(QObject *parent) = 0;
};
Q_DECLARE_METATYPE(AbstractTester *)

typedef void (*TestFunction)(QObject*);
Q_DECLARE_METATYPE(TestFunction)

template <typename T>
struct ObjectCreator : public AbstractTester
{
    void operator()(QObject *)
    {
        QScopedPointer<T> ptr(new T);
    }
};

struct BitArrayCreator : public AbstractTester
{
    void operator()(QObject *)
    { QScopedPointer<QBitArray> bitArray(new QBitArray(100, true)); }
};

struct ByteArrayMatcherCreator : public AbstractTester
{
    void operator()(QObject *)
    { QScopedPointer<QByteArrayMatcher> ptr(new QByteArrayMatcher("ralf test",8)); }
};

struct CryptographicHashCreator : public AbstractTester
{
    void operator()(QObject *)
    {
        QScopedPointer<QCryptographicHash> ptr(new QCryptographicHash(QCryptographicHash::Sha1));
        ptr->addData("ralf test",8);
    }
};

struct DataStreamCreator : public AbstractTester
{
    void operator()(QObject *)
    {
        QScopedPointer<QByteArray> arr(new QByteArray("hallo, test"));
        QScopedPointer<QDataStream> ptr(new QDataStream(arr.data(), QIODevice::ReadWrite));
        ptr->writeBytes("ralf test",8);
    }
};

struct DirCreator : public AbstractTester
{
    void operator()(QObject *)
    {
        QDir::cleanPath("../////././");
        QScopedPointer<QDir> ptr(new QDir("."));
        while( ptr->cdUp() )
            ; // just going up
        ptr->count();
        ptr->exists(ptr->path());

        QStringList filters;
        filters << "*.cpp" << "*.cxx" << "*.cc";
        ptr->setNameFilters(filters);
    }
};

void tst_ExceptionSafetyObjects::objects_data()
{
    QTest::addColumn<AbstractTester *>("objectCreator");

#define NEWROW(T) QTest::newRow(#T) << static_cast<AbstractTester *>(new ObjectCreator<T >)
    NEWROW(QObject);
    NEWROW(QBuffer);
    NEWROW(QFile);
    NEWROW(QProcess);
    NEWROW(QSettings);
    NEWROW(QThread);
    NEWROW(QThreadPool);
    NEWROW(QTranslator);
    NEWROW(QFSFileEngine);

#define NEWROW2(T, CREATOR) QTest::newRow(#T) << static_cast<AbstractTester *>(new CREATOR)
    NEWROW2(QBitArray, BitArrayCreator);
    NEWROW2(QByteArrayMatcher, ByteArrayMatcherCreator);
    NEWROW2(QCryptographicHash, CryptographicHashCreator);
    NEWROW2(QDataStream, DataStreamCreator);
    NEWROW2(QDir, DirCreator);

}

// create and destructs an object, and lets each and every allocation
// during construction and destruction fail.
template <typename T>
static void doOOMTest(T &testFunc, QObject *parent, int start=0)
{
    int currentOOMIndex = start;
    bool caught = false;
    bool done = false;

    AllocFailer allocFailer(0);
    int allocCountBefore = allocFailer.currentAllocIndex();

    do {
        allocFailer.reactivateAt(++currentOOMIndex);

        caught = false;

        try {
            testFunc(parent);
        } catch (const std::bad_alloc &) {
            caught = true;
        } catch (const std::exception &ex) {
            if (strcmp(ex.what(), "autotest swallow") != 0)
                throw;
            caught = true;
        }

        if (!caught) {
            void *buf = malloc(42);
            if (buf) {
                // we got memory here - oom test is over.
                free(buf);
                done = true;
            }
        }

        // if we get a FAIL, stop executing now
        if (QTest::currentTestFailed())
            done = true;

//#define REALLY_VERBOSE
#ifdef REALLY_VERBOSE
    fprintf(stderr, " OOM Index: %d\n", currentOOMIndex);
#endif


    } while (caught || !done);

    allocFailer.deactivate();

//#define VERBOSE
#ifdef VERBOSE
    fprintf(stderr, "OOM Test done, checked allocs: %d (range %d - %d)\n", currentOOMIndex,
                allocCountBefore, allocFailer.currentAllocIndex());
#else
    Q_UNUSED(allocCountBefore);
#endif
}

static int alloc1Failed = 0;
static int alloc2Failed = 0;
static int alloc3Failed = 0;
static int alloc4Failed = 0;
static int malloc1Failed = 0;
static int malloc2Failed = 0;

// Tests that new, new[] and malloc() fail at least once during OOM testing.
class SelfTestObject : public QObject
{
public:
    SelfTestObject(QObject *parent = 0)
        : QObject(parent)
    {
        try { delete new int; } catch (const std::bad_alloc &) { ++alloc1Failed; throw; }
        try { delete [] new double[5]; } catch (const std::bad_alloc &) { ++alloc2Failed; throw ;}
        void *buf = malloc(42);
        if (buf)
            free(buf);
        else
            ++malloc1Failed;
    }

    ~SelfTestObject()
    {
        try { delete new int; } catch (const std::bad_alloc &) { ++alloc3Failed; }
        try { delete [] new double[5]; } catch (const std::bad_alloc &) { ++alloc4Failed; }
        void *buf = malloc(42);
        if (buf)
            free(buf);
        else
            ++malloc2Failed = true;
    }
};

QtMsgHandler tst_ExceptionSafetyObjects::testMessageHandler;

void tst_ExceptionSafetyObjects::safeMessageHandler(QtMsgType type, const char *msg)
{
    // this temporarily suspends OOM testing while handling a message
    int currentIndex = mallocFailIndex;
    AllocFailer allocFailer(0);
    allocFailer.deactivate();
    (*testMessageHandler)(type, msg);
    allocFailer.reactivateAt(currentIndex);
}

void tst_ExceptionSafetyObjects::initTestCase()
{
    testMessageHandler = qInstallMsgHandler(safeMessageHandler);

    QVERIFY(AllocFailer::initialize());

    // sanity check whether OOM simulation works
    AllocFailer allocFailer(0);

    // malloc fail index is 0 -> this malloc should fail.
    void *buf = malloc(42);
    allocFailer.deactivate();
    QVERIFY(!buf);

    // malloc fail index is 1 - second malloc should fail.
    allocFailer.reactivateAt(1);
    buf = malloc(42);
    void *buf2 = malloc(42);
    allocFailer.deactivate();

    QVERIFY(buf);
    free(buf);
    QVERIFY(!buf2);

#ifdef Q_OS_SYMBIAN
    // temporary workaround for INC138398
    std::new_handler nh_func = std::set_new_handler(0);
    (void) std::set_new_handler(nh_func);
#endif

    ObjectCreator<SelfTestObject> *selfTest = new ObjectCreator<SelfTestObject>;
    doOOMTest(*selfTest, 0);
    delete selfTest;
    QCOMPARE(alloc1Failed, 1);
    QCOMPARE(alloc2Failed, 1);
    QCOMPARE(alloc3Failed, 2);
    QCOMPARE(alloc4Failed, 3);
    QCOMPARE(malloc1Failed, 1);
    QCOMPARE(malloc2Failed, 1);
}

void tst_ExceptionSafetyObjects::cleanupTestCase()
{
    qInstallMsgHandler(testMessageHandler);
}

void tst_ExceptionSafetyObjects::objects()
{
    QFETCH(AbstractTester *, objectCreator);

    doOOMTest(*objectCreator, 0);
    
    delete objectCreator;
}

template <typename T>
struct WidgetCreator : public AbstractTester
{
    void operator()(QObject *parent)
    {
        Q_ASSERT(!parent || parent->isWidgetType());
        QScopedPointer<T> ptr(parent ? new T(static_cast<QWidget *>(parent)) : new T);
    }
};

// QSizeGrip doesn't have a default constructor - always pass parent (even though it might be 0)
template <> struct WidgetCreator<QSizeGrip> : public AbstractTester
{
    void operator()(QObject *parent)
    {
        Q_ASSERT(!parent || parent->isWidgetType());
        QScopedPointer<QSizeGrip> ptr(new QSizeGrip(static_cast<QWidget *>(parent)));
    }
};

// QDesktopWidget doesn't need a parent.
template <> struct WidgetCreator<QDesktopWidget> : public AbstractTester
{
    void operator()(QObject *parent)
    {
        Q_ASSERT(!parent || parent->isWidgetType());
        QScopedPointer<QDesktopWidget> ptr(new QDesktopWidget());
    }
};
void tst_ExceptionSafetyObjects::widgets_data()
{
#ifdef Q_OS_SYMBIAN
    // Initialise the S60 rasteriser, which crashes if started while out of memory
    QImage image(20, 20, QImage::Format_RGB32); 
    QPainter p(&image); 
    p.drawText(0, 15, "foo"); 
#endif

    QTest::addColumn<AbstractTester *>("widgetCreator");

#undef NEWROW
#define NEWROW(T) QTest::newRow(#T) << static_cast<AbstractTester *>(new WidgetCreator<T >)

    NEWROW(QWidget);

    NEWROW(QButtonGroup);
    NEWROW(QDesktopWidget);
    NEWROW(QCheckBox);
    NEWROW(QComboBox);
    NEWROW(QCommandLinkButton);
    NEWROW(QDateEdit);
    NEWROW(QDateTimeEdit);
    NEWROW(QDial);
    NEWROW(QDoubleSpinBox);
    NEWROW(QFocusFrame);
    NEWROW(QFontComboBox);
    NEWROW(QFrame);
    NEWROW(QGroupBox);
    NEWROW(QLCDNumber);
    NEWROW(QLabel);
    NEWROW(QLCDNumber);
    NEWROW(QLineEdit);
    NEWROW(QMenu);
    NEWROW(QPlainTextEdit);
    NEWROW(QProgressBar);
    NEWROW(QPushButton);
    NEWROW(QRadioButton);
    NEWROW(QScrollArea);
    NEWROW(QScrollBar);
    NEWROW(QSizeGrip);
    NEWROW(QSlider);
    NEWROW(QSpinBox);
    NEWROW(QSplitter);
    NEWROW(QStackedWidget);
    NEWROW(QStatusBar);
    NEWROW(QTabBar);
    NEWROW(QTabWidget);
    NEWROW(QTextBrowser);
    NEWROW(QTextEdit);
    NEWROW(QTimeEdit);
    NEWROW(QToolBox);
    NEWROW(QToolButton);
    NEWROW(QStatusBar);
    NEWROW(QToolBar);
    NEWROW(QMenuBar);
    NEWROW(QMainWindow);
    NEWROW(QWorkspace);
    NEWROW(QColumnView);
    NEWROW(QListView);
    NEWROW(QListWidget);
    NEWROW(QTableView);
    NEWROW(QTableWidget);
    NEWROW(QTreeView);
    NEWROW(QTreeWidget);
}

void tst_ExceptionSafetyObjects::widgets()
{
    QFETCH(AbstractTester *, widgetCreator);

    doOOMTest(*widgetCreator, 0, 00000);

    QWidget parent;
    doOOMTest(*widgetCreator, &parent, 00000);

    delete widgetCreator;

    // if the test reaches here without crashing, we passed :)
    QVERIFY(true);
}

struct Integer
{
    Integer(int value = 42)
        : ptr(new int(value))
    {
        ++instanceCount;
    }

    Integer(const Integer &other)
        : ptr(new int(*other.ptr))
    {
        ++instanceCount;
    }

    Integer &operator=(const Integer &other)
    {
        int *newPtr = new int(*other.ptr);
        delete ptr;
        ptr = newPtr;
        return *this;
    }

    ~Integer()
    {
        --instanceCount;
        delete ptr;
    }

    int value() const
    {
        return *ptr;
    }

    int *ptr;
    static int instanceCount;
};

int Integer::instanceCount = 0;

struct IntegerMoveable
    {
    IntegerMoveable(int value = 42)
        : val(value)
    {
        delete new int;
        ++instanceCount;
    }

    IntegerMoveable(const IntegerMoveable &other)
        : val(other.val)
    {
        delete new int;
        ++instanceCount;
    }

    IntegerMoveable &operator=(const IntegerMoveable &other)
    {
        delete new int;
        val = other.val;
        return *this;
    }

    ~IntegerMoveable()
    {
        --instanceCount;
    }

    int value() const
    {
        return val;
    }

    int val;
    static int instanceCount;
    };

int IntegerMoveable::instanceCount = 0;
Q_DECLARE_TYPEINFO(IntegerMoveable, Q_MOVABLE_TYPE);

template <typename T, template<typename> class Container>
void containerInsertTest(QObject*)
{
    Container<T> container;

    // insert an item in an empty container
    try {
        container.insert(container.begin(), 41);
    } catch (...) {
        QVERIFY(container.isEmpty());
        QCOMPARE(T::instanceCount, 0);
        return;
    }

    QCOMPARE(container.size(), 1);
    QCOMPARE(T::instanceCount, 1);

    // insert an item before another item
    try {
        container.insert(container.begin(), 42);
    } catch (...) {
        QCOMPARE(container.size(), 1);
        QCOMPARE(container.first().value(), 41);
        QCOMPARE(T::instanceCount, 1);
        return;
    }

    QCOMPARE(T::instanceCount, 2);

    // insert an item in between
    try {
        container.insert(container.begin() + 1, 43);
    } catch (...) {
        QCOMPARE(container.size(), 2);
        QCOMPARE(container.first().value(), 41);
        QCOMPARE((container.begin() + 1)->value(), 42);
        QCOMPARE(T::instanceCount, 2);
        return;
    }

    QCOMPARE(T::instanceCount, 3);
}

template <typename T, template<typename> class Container>
void containerAppendTest(QObject*)
{
    Container<T> container;

    // append to an empty container
    try {
        container.append(42);
    } catch (...) {
        QCOMPARE(container.size(), 0);
        QCOMPARE(T::instanceCount, 0);
        return;
    }

    // append to a container with one item
    try {
        container.append(43);
    } catch (...) {
        QCOMPARE(container.size(), 1);
        QCOMPARE(container.first().value(), 42);
        QCOMPARE(T::instanceCount, 1);
        return;
    }

    Container<T> container2;

    try {
        container2.append(44);
    } catch (...) {
        // don't care
        return;
    }
    QCOMPARE(T::instanceCount, 3);

    // append another container with one item
    try {
        container += container2;
    } catch (...) {
        QCOMPARE(container.size(), 2);
        QCOMPARE(container.first().value(), 42);
        QCOMPARE((container.begin() + 1)->value(), 43);
        QCOMPARE(T::instanceCount, 3);
        return;
    }

    QCOMPARE(T::instanceCount, 4);
}

template <typename T, template<typename> class Container>
void containerEraseTest(QObject*)
{
    Container<T> container;

    try {
        container.append(42);
        container.append(43);
        container.append(44);
        container.append(45);
        container.append(46);
    } catch (...) {
        // don't care
        return;
    }

    // sanity checks
    QCOMPARE(container.size(), 5);
    QCOMPARE(T::instanceCount, 5);

    // delete the first one
    try {
        container.erase(container.begin());
    } catch (...) {
        QCOMPARE(container.size(), 5);
        QCOMPARE(container.first().value(), 42);
        QCOMPARE(T::instanceCount, 5);
        return;
    }

    QCOMPARE(container.size(), 4);
    QCOMPARE(container.first().value(), 43);
    QCOMPARE(T::instanceCount, 4);

    // delete the last one
    try {
        container.erase(container.end() - 1);
    } catch (...) {
        QCOMPARE(container.size(), 4);
        QCOMPARE(T::instanceCount, 4);
        return;
    }

    QCOMPARE(container.size(), 3);
    QCOMPARE(container.first().value(), 43);
    QCOMPARE((container.begin() + 1)->value(), 44);
    QCOMPARE((container.begin() + 2)->value(), 45);
    QCOMPARE(T::instanceCount, 3);

    // delete the middle one
    try {
        container.erase(container.begin() + 1);
    } catch (...) {
        QCOMPARE(container.size(), 3);
        QCOMPARE(container.first().value(), 43);
        QCOMPARE((container.begin() + 1)->value(), 44);
        QCOMPARE((container.begin() + 2)->value(), 45);
        QCOMPARE(T::instanceCount, 3);
        return;
    }

    QCOMPARE(container.size(), 2);
    QCOMPARE(container.first().value(), 43);
    QCOMPARE((container.begin() + 1)->value(), 45);
    QCOMPARE(T::instanceCount, 2);
}

template <template<typename T> class Container>
static void containerData()
{
    QTest::addColumn<TestFunction>("testFunction");

    QTest::newRow("insert static") << static_cast<TestFunction>(containerInsertTest<Integer, Container>);
    QTest::newRow("append static") << static_cast<TestFunction>(containerAppendTest<Integer, Container>);
    QTest::newRow("erase static") << static_cast<TestFunction>(containerEraseTest<Integer, Container>);
    QTest::newRow("insert moveable") << static_cast<TestFunction>(containerInsertTest<IntegerMoveable, Container>);
    QTest::newRow("append moveable") << static_cast<TestFunction>(containerAppendTest<IntegerMoveable, Container>);
    QTest::newRow("erase moveable") << static_cast<TestFunction>(containerEraseTest<IntegerMoveable, Container>);
}

void tst_ExceptionSafetyObjects::vector_data()
{
    containerData<QVector>();
}

void tst_ExceptionSafetyObjects::vector()
{
    QFETCH(TestFunction, testFunction);

    if (QLatin1String(QTest::currentDataTag()) == QLatin1String("insert static")
        || QLatin1String(QTest::currentDataTag()) == QLatin1String("insert moveable"))
        QSKIP("QVector::insert is currently not strongly exception safe", SkipSingle);

    doOOMTest(testFunction, 0);
}

void tst_ExceptionSafetyObjects::list_data()
{
    containerData<QList>();
}

void tst_ExceptionSafetyObjects::list()
{
    QFETCH(TestFunction, testFunction);

    doOOMTest(testFunction, 0);
}

void tst_ExceptionSafetyObjects::linkedList_data()
{
    containerData<QLinkedList>();
}

void tst_ExceptionSafetyObjects::linkedList()
{
    QFETCH(TestFunction, testFunction);

    doOOMTest(testFunction, 0);
}

QTEST_MAIN(tst_ExceptionSafetyObjects)
#include "tst_exceptionsafety_objects.moc"
#endif // QT_NO_EXCEPTIONS
