/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** 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 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtTest/QtTest>

#include <qcoreapplication.h>
#include <qmutex.h>
#include <qthread.h>
#include <qwaitcondition.h>
#include <qthreadstorage.h>
#include <qdir.h>
#include <qfileinfo.h>

#ifdef Q_OS_UNIX
#include <pthread.h>
#endif
#ifdef Q_OS_WIN
#ifndef Q_OS_WINCE
#include <process.h>
#endif
#include <windows.h>
#endif

class tst_QThreadStorage : public QObject
{
    Q_OBJECT
private slots:
    void initTestCase();
    void hasLocalData();
    void localData();
    void localData_const();
    void setLocalData();
    void autoDelete();
    void adoptedThreads();
    void ensureCleanupOrder();
#ifndef QT_NO_PROCESS
    void crashOnExit();
#endif
    void leakInDestructor();
    void resetInDestructor();
    void valueBased();

private:
    QString m_crashOnExit;
};

class Pointer
{
public:
    static int count;
    inline Pointer() { ++count; }
    inline ~Pointer() { --count; }
};
int Pointer::count = 0;

void tst_QThreadStorage::initTestCase()
{
    const QString crashOnExitDir = QFINDTESTDATA("crashonexit");
    QVERIFY2(!crashOnExitDir.isEmpty(),
             qPrintable(QString::fromLatin1("Could not find 'crashonexit' starting from '%1'")
                        .arg(QDir::toNativeSeparators(QDir::currentPath()))));
    m_crashOnExit = crashOnExitDir + QStringLiteral("/crashonexit");
#ifdef Q_OS_WIN
    m_crashOnExit += QStringLiteral(".exe");
#endif
    QVERIFY2(QFileInfo(m_crashOnExit).isExecutable(),
             qPrintable(QDir::toNativeSeparators(m_crashOnExit) + QStringLiteral(" does not exist or is not executable.")));
}

void tst_QThreadStorage::hasLocalData()
{
    QThreadStorage<Pointer *> pointers;
    QVERIFY(!pointers.hasLocalData());
    pointers.setLocalData(new Pointer);
    QVERIFY(pointers.hasLocalData());
    pointers.setLocalData(0);
    QVERIFY(!pointers.hasLocalData());
}

void tst_QThreadStorage::localData()
{
    QThreadStorage<Pointer*> pointers;
    Pointer *p = new Pointer;
    QVERIFY(!pointers.hasLocalData());
    pointers.setLocalData(p);
    QVERIFY(pointers.hasLocalData());
    QCOMPARE(pointers.localData(), p);
    pointers.setLocalData(0);
    QCOMPARE(pointers.localData(), (Pointer *)0);
    QVERIFY(!pointers.hasLocalData());
}

void tst_QThreadStorage::localData_const()
{
    QThreadStorage<Pointer *> pointers;
    const QThreadStorage<Pointer *> &const_pointers = pointers;
    Pointer *p = new Pointer;
    QVERIFY(!pointers.hasLocalData());
    pointers.setLocalData(p);
    QVERIFY(pointers.hasLocalData());
    QCOMPARE(const_pointers.localData(), p);
    pointers.setLocalData(0);
    QCOMPARE(const_pointers.localData(), (Pointer *)0);
    QVERIFY(!pointers.hasLocalData());
}

void tst_QThreadStorage::setLocalData()
{
    QThreadStorage<Pointer *> pointers;
    QVERIFY(!pointers.hasLocalData());
    pointers.setLocalData(new Pointer);
    QVERIFY(pointers.hasLocalData());
    pointers.setLocalData(0);
    QVERIFY(!pointers.hasLocalData());
}

class Thread : public QThread
{
public:
    QThreadStorage<Pointer *> &pointers;

    QMutex mutex;
    QWaitCondition cond;

    Thread(QThreadStorage<Pointer *> &p)
        : pointers(p)
    { }

    void run()
    {
        pointers.setLocalData(new Pointer);

        QMutexLocker locker(&mutex);
        cond.wakeOne();
        cond.wait(&mutex);
    }
};

void tst_QThreadStorage::autoDelete()
{
    QThreadStorage<Pointer *> pointers;
    QVERIFY(!pointers.hasLocalData());

    Thread thread(pointers);
    int c = Pointer::count;
    {
        QMutexLocker locker(&thread.mutex);
        thread.start();
        thread.cond.wait(&thread.mutex);
        // QCOMPARE(Pointer::count, c + 1);
        thread.cond.wakeOne();
    }
    thread.wait();
    QCOMPARE(Pointer::count, c);
}

bool threadStorageOk;
void testAdoptedThreadStorageWin(void *p)
{
    QThreadStorage<Pointer *>  *pointers = reinterpret_cast<QThreadStorage<Pointer *> *>(p);
    if (pointers->hasLocalData()) {
        threadStorageOk = false;
        return;
    }

    Pointer *pointer = new Pointer();
    pointers->setLocalData(pointer);

    if (pointers->hasLocalData() == false) {
        threadStorageOk = false;
        return;
    }

    if (pointers->localData() != pointer) {
        threadStorageOk = false;
        return;
    }
    QObject::connect(QThread::currentThread(), SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
}
void *testAdoptedThreadStorageUnix(void *pointers)
{
    testAdoptedThreadStorageWin(pointers);
    return 0;
}
void tst_QThreadStorage::adoptedThreads()
{
    QTestEventLoop::instance(); // Make sure the instance is created in this thread.
    QThreadStorage<Pointer *> pointers;
    int c = Pointer::count;
    threadStorageOk = true;
    {
#ifdef Q_OS_UNIX
        pthread_t thread;
        const int state = pthread_create(&thread, 0, testAdoptedThreadStorageUnix, &pointers);
        QCOMPARE(state, 0);
        pthread_join(thread, 0);
#elif defined Q_OS_WIN && !defined(Q_OS_WINRT)
        HANDLE thread;
#if defined(Q_OS_WINCE)
        thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)testAdoptedThreadStorageWin, &pointers, 0, NULL);
#else
        thread = (HANDLE)_beginthread(testAdoptedThreadStorageWin, 0, &pointers);
#endif
        QVERIFY(thread);
        WaitForSingleObject(thread, INFINITE);
#endif
    }
    QVERIFY(threadStorageOk);

    QTestEventLoop::instance().enterLoop(2);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(Pointer::count, c);
}

QBasicAtomicInt cleanupOrder = Q_BASIC_ATOMIC_INITIALIZER(0);

class First
{
public:
    ~First()
    {
        order = cleanupOrder.fetchAndAddRelaxed(1);
    }
    static int order;
};
int First::order = -1;

class Second
{
public:
    ~Second()
    {
        order = cleanupOrder.fetchAndAddRelaxed(1);
    }
    static int order;
};
int Second::order = -1;

void tst_QThreadStorage::ensureCleanupOrder()
{
    class Thread : public QThread
    {
    public:
        QThreadStorage<First *> &first;
        QThreadStorage<Second *> &second;

        Thread(QThreadStorage<First *> &first,
               QThreadStorage<Second *> &second)
            : first(first), second(second)
        { }

        void run()
        {
            // set in reverse order, but shouldn't matter, the data
            // will be deleted in the order the thread storage objects
            // were created
            second.setLocalData(new Second);
            first.setLocalData(new First);
        }
    };

    QThreadStorage<Second *> second;
    QThreadStorage<First *> first;
    Thread thread(first, second);
    thread.start();
    thread.wait();

    QVERIFY(First::order < Second::order);
}

#ifndef QT_NO_PROCESS
static inline bool runCrashOnExit(const QString &binary, QString *errorMessage)
{
    const int timeout = 60000;
    QProcess process;
    process.start(binary);
    if (!process.waitForStarted()) {
        *errorMessage = QString::fromLatin1("Could not start '%1': %2").arg(binary, process.errorString());
        return false;
    }
    if (!process.waitForFinished(timeout)) {
        process.kill();
        *errorMessage = QString::fromLatin1("Timeout (%1ms) waiting for %2.").arg(timeout).arg(binary);
        return false;
    }
    if (process.exitStatus() != QProcess::NormalExit) {
        *errorMessage = binary + QStringLiteral(" crashed.");
        return false;
    }
    return true;
}

void tst_QThreadStorage::crashOnExit()
{
    QString errorMessage;
    QVERIFY2(runCrashOnExit(m_crashOnExit, &errorMessage),
             qPrintable(errorMessage));
}
#endif

// S stands for thread Safe.
class SPointer
{
public:
    static QBasicAtomicInt count;
    inline SPointer() { count.ref(); }
    inline ~SPointer() { count.deref(); }
    inline SPointer(const SPointer & /* other */) { count.ref(); }
};
QBasicAtomicInt SPointer::count = Q_BASIC_ATOMIC_INITIALIZER(0);

Q_GLOBAL_STATIC(QThreadStorage<SPointer *>, threadStoragePointers1)
Q_GLOBAL_STATIC(QThreadStorage<SPointer *>, threadStoragePointers2)

class ThreadStorageLocalDataTester
{
public:
    SPointer member;
    inline ~ThreadStorageLocalDataTester() {
        QVERIFY(!threadStoragePointers1()->hasLocalData());
        QVERIFY(!threadStoragePointers2()->hasLocalData());
        threadStoragePointers2()->setLocalData(new SPointer);
        threadStoragePointers1()->setLocalData(new SPointer);
        QVERIFY(threadStoragePointers1()->hasLocalData());
        QVERIFY(threadStoragePointers2()->hasLocalData());
    }
};


void tst_QThreadStorage::leakInDestructor()
{
    class Thread : public QThread
    {
    public:
        QThreadStorage<ThreadStorageLocalDataTester *> &tls;

        Thread(QThreadStorage<ThreadStorageLocalDataTester *> &t) : tls(t) { }

        void run()
        {
            QVERIFY(!tls.hasLocalData());
            tls.setLocalData(new ThreadStorageLocalDataTester);
            QVERIFY(tls.hasLocalData());
        }
    };
    int c = SPointer::count.load();

    QThreadStorage<ThreadStorageLocalDataTester *> tls;

    QVERIFY(!threadStoragePointers1()->hasLocalData());
    QThreadStorage<int *> tls2; //add some more tls to make sure ids are not following each other too much
    QThreadStorage<int *> tls3;
    QVERIFY(!tls2.hasLocalData());
    QVERIFY(!tls3.hasLocalData());
    QVERIFY(!tls.hasLocalData());

    Thread t1(tls);
    Thread t2(tls);
    Thread t3(tls);

    t1.start();
    t2.start();
    t3.start();

    QVERIFY(t1.wait());
    QVERIFY(t2.wait());
    QVERIFY(t3.wait());

    //check all the constructed things have been destructed
    QCOMPARE(int(SPointer::count.load()), c);
}

class ThreadStorageResetLocalDataTester {
public:
    SPointer member;
    ~ThreadStorageResetLocalDataTester();
};

Q_GLOBAL_STATIC(QThreadStorage<ThreadStorageResetLocalDataTester *>, ThreadStorageResetLocalDataTesterTls)

ThreadStorageResetLocalDataTester::~ThreadStorageResetLocalDataTester() {
    //Quite stupid, but WTF::ThreadSpecific<T>::destroy does it.
    ThreadStorageResetLocalDataTesterTls()->setLocalData(this);
}

void tst_QThreadStorage::resetInDestructor()
{
    class Thread : public QThread
    {
    public:
        void run()
        {
            QVERIFY(!ThreadStorageResetLocalDataTesterTls()->hasLocalData());
            ThreadStorageResetLocalDataTesterTls()->setLocalData(new ThreadStorageResetLocalDataTester);
            QVERIFY(ThreadStorageResetLocalDataTesterTls()->hasLocalData());
        }
    };
    int c = SPointer::count.load();

    Thread t1;
    Thread t2;
    Thread t3;
    t1.start();
    t2.start();
    t3.start();
    QVERIFY(t1.wait());
    QVERIFY(t2.wait());
    QVERIFY(t3.wait());

    //check all the constructed things have been destructed
    QCOMPARE(int(SPointer::count.load()), c);
}


void tst_QThreadStorage::valueBased()
{
    struct Thread : QThread {
        QThreadStorage<SPointer> &tlsSPointer;
        QThreadStorage<QString> &tlsString;
        QThreadStorage<int> &tlsInt;

        int someNumber;
        QString someString;
        Thread(QThreadStorage<SPointer> &t1, QThreadStorage<QString> &t2, QThreadStorage<int> &t3)
        : tlsSPointer(t1), tlsString(t2), tlsInt(t3) { }

        void run() {
            /*QVERIFY(!tlsSPointer.hasLocalData());
            QVERIFY(!tlsString.hasLocalData());
            QVERIFY(!tlsInt.hasLocalData());*/
            SPointer pointercopy = tlsSPointer.localData();

            //Default constructed values
            QVERIFY(tlsString.localData().isNull());
            QCOMPARE(tlsInt.localData(), 0);

            //setting
            tlsString.setLocalData(someString);
            tlsInt.setLocalData(someNumber);

            QCOMPARE(tlsString.localData(), someString);
            QCOMPARE(tlsInt.localData(), someNumber);

            //changing
            tlsSPointer.setLocalData(SPointer());
            tlsInt.localData() += 42;
            tlsString.localData().append(QLatin1String(" world"));

            QCOMPARE(tlsString.localData(), (someString + QLatin1String(" world")));
            QCOMPARE(tlsInt.localData(), (someNumber + 42));

            // operator=
            tlsString.localData() = QString::number(someNumber);
            QCOMPARE(tlsString.localData().toInt(), someNumber);
        }
    };

    QThreadStorage<SPointer> tlsSPointer;
    QThreadStorage<QString> tlsString;
    QThreadStorage<int> tlsInt;

    int c = SPointer::count.load();

    Thread t1(tlsSPointer, tlsString, tlsInt);
    Thread t2(tlsSPointer, tlsString, tlsInt);
    Thread t3(tlsSPointer, tlsString, tlsInt);
    t1.someNumber = 42;
    t2.someNumber = -128;
    t3.someNumber = 78;
    t1.someString = "hello";
    t2.someString = "australia";
    t3.someString = "nokia";

    t1.start();
    t2.start();
    t3.start();

    QVERIFY(t1.wait());
    QVERIFY(t2.wait());
    QVERIFY(t3.wait());

    QCOMPARE(c, int(SPointer::count.load()));

}


QTEST_MAIN(tst_QThreadStorage)
#include "tst_qthreadstorage.moc"
