/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: sw=4 ts=4 et :
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "TestHarness.h"

#include "mozilla/CondVar.h"
#include "mozilla/Monitor.h"
#include "mozilla/Mutex.h"
#include "nsAutoLock.h"

using namespace mozilla;

static PRThread*
spawn(void (*run)(void*), void* arg)
{
    return PR_CreateThread(PR_SYSTEM_THREAD,
                           run,
                           arg,
                           PR_PRIORITY_NORMAL,
                           PR_GLOBAL_THREAD,
                           PR_JOINABLE_THREAD,
                           0);
}


#define PASS()                                  \
    do {                                        \
        passed(__FUNCTION__);                   \
        return NS_OK;                           \
    } while (0)

#define FAIL(why)                               \
    do {                                        \
        fail("%s | %s - %s", __FILE__, __FUNCTION__, why); \
        return NS_ERROR_FAILURE;                \
    } while (0)

//-----------------------------------------------------------------------------
// Sanity check: tests that can be done on a single thread
//
static nsresult
Sanity()
{
    Mutex lock("sanity::lock");
    lock.Lock();
    lock.AssertCurrentThreadOwns();
    lock.Unlock();
    
    {
        MutexAutoLock autolock(lock);
        lock.AssertCurrentThreadOwns();
    }

    lock.Lock();
    lock.AssertCurrentThreadOwns();
    {
        MutexAutoUnlock autounlock(lock);
    }
    lock.AssertCurrentThreadOwns();
    lock.Unlock();

    Monitor mon("sanity::monitor");
    mon.Enter();
    mon.AssertCurrentThreadIn();
    mon.Enter();
    mon.AssertCurrentThreadIn();
    mon.Exit();
    mon.AssertCurrentThreadIn();
    mon.Exit();

    {
        MonitorAutoEnter automon(mon);
        mon.AssertCurrentThreadIn();
    }

    PASS();
}

//-----------------------------------------------------------------------------
// Mutex contention tests
//
static Mutex* gLock1;

static void
MutexContention_thread(void* /*arg*/)
{
    for (int i = 0; i < 100000; ++i) {
        gLock1->Lock();
        gLock1->AssertCurrentThreadOwns();
        gLock1->Unlock();
    }
}

static nsresult
MutexContention()
{
    gLock1 = new Mutex("lock1");
    // PURPOSELY not checking for OOM.  YAY!

    PRThread* t1 = spawn(MutexContention_thread, nullptr);
    PRThread* t2 = spawn(MutexContention_thread, nullptr);
    PRThread* t3 = spawn(MutexContention_thread, nullptr);

    PR_JoinThread(t1);
    PR_JoinThread(t2);
    PR_JoinThread(t3);

    delete gLock1;

    PASS();
}

//-----------------------------------------------------------------------------
// Monitor tests
//
static Monitor* gMon1;

static void
MonitorContention_thread(void* /*arg*/)
{
    for (int i = 0; i < 100000; ++i) {
        gMon1->Enter();
        gMon1->AssertCurrentThreadIn();
        gMon1->Exit();
    }
}

static nsresult
MonitorContention()
{
    gMon1 = new Monitor("mon1");

    PRThread* t1 = spawn(MonitorContention_thread, nullptr);
    PRThread* t2 = spawn(MonitorContention_thread, nullptr);
    PRThread* t3 = spawn(MonitorContention_thread, nullptr);

    PR_JoinThread(t1);
    PR_JoinThread(t2);
    PR_JoinThread(t3);

    delete gMon1;

    PASS();
}


static Monitor* gMon2;

static void
MonitorContention2_thread(void* /*arg*/)
{
    for (int i = 0; i < 100000; ++i) {
        gMon2->Enter();
        gMon2->AssertCurrentThreadIn();
        {
            gMon2->Enter();
            gMon2->AssertCurrentThreadIn();
            gMon2->Exit();
        }
        gMon2->AssertCurrentThreadIn();
        gMon2->Exit();
    }
}

static nsresult
MonitorContention2()
{
    gMon2 = new Monitor("mon1");

    PRThread* t1 = spawn(MonitorContention2_thread, nullptr);
    PRThread* t2 = spawn(MonitorContention2_thread, nullptr);
    PRThread* t3 = spawn(MonitorContention2_thread, nullptr);

    PR_JoinThread(t1);
    PR_JoinThread(t2);
    PR_JoinThread(t3);

    delete gMon2;

    PASS();
}


static Monitor* gMon3;
static int32_t gMonFirst;

static void
MonitorSyncSanity_thread(void* /*arg*/)
{
    gMon3->Enter();
    gMon3->AssertCurrentThreadIn();
    if (gMonFirst) {
        gMonFirst = 0;
        gMon3->Wait();
        gMon3->Enter();
    } else {
        gMon3->Notify();
        gMon3->Enter();
    }
    gMon3->AssertCurrentThreadIn();
    gMon3->Exit();
    gMon3->AssertCurrentThreadIn();
    gMon3->Exit();
}

static nsresult
MonitorSyncSanity()
{
    gMon3 = new Monitor("monitor::syncsanity");
   
    for (int32_t i = 0; i < 10000; ++i) {
        gMonFirst = 1;
        PRThread* ping = spawn(MonitorSyncSanity_thread, nullptr);
        PRThread* pong = spawn(MonitorSyncSanity_thread, nullptr);
        PR_JoinThread(ping);
        PR_JoinThread(pong);
    }

    delete gMon3;

    PASS();
}

//-----------------------------------------------------------------------------
// Condvar tests
//
static Mutex* gCvlock1;
static CondVar* gCv1;
static int32_t gCvFirst;

static void
CondVarSanity_thread(void* /*arg*/)
{
    gCvlock1->Lock();
    gCvlock1->AssertCurrentThreadOwns();
    if (gCvFirst) {
        gCvFirst = 0;
        gCv1->Wait();
    } else {
        gCv1->Notify();
    }
    gCvlock1->AssertCurrentThreadOwns();
    gCvlock1->Unlock();
}

static nsresult
CondVarSanity()
{
    gCvlock1 = new Mutex("cvlock1");
    gCv1 = new CondVar(*gCvlock1, "cvlock1");

    for (int32_t i = 0; i < 10000; ++i) {
        gCvFirst = 1;
        PRThread* ping = spawn(CondVarSanity_thread, nullptr);
        PRThread* pong = spawn(CondVarSanity_thread, nullptr);
        PR_JoinThread(ping);
        PR_JoinThread(pong);
    }

    delete gCv1;
    delete gCvlock1;

    PASS();
}

//-----------------------------------------------------------------------------
// AutoLock tests
//
static nsresult
AutoLock()
{
    Mutex l1("autolock");
    MutexAutoLock autol1(l1);

    l1.AssertCurrentThreadOwns();

    {
        Mutex l2("autolock2");
        MutexAutoLock autol2(l2);

        l1.AssertCurrentThreadOwns();
        l2.AssertCurrentThreadOwns();
    }

    l1.AssertCurrentThreadOwns();

    PASS();
}

//-----------------------------------------------------------------------------
// AutoUnlock tests
//
static nsresult
AutoUnlock()
{
    Mutex l1("autounlock");
    Mutex l2("autounlock2");

    l1.Lock();
    l1.AssertCurrentThreadOwns();

    {
        MutexAutoUnlock autol1(l1);
        {
            l2.Lock();
            l2.AssertCurrentThreadOwns();

            MutexAutoUnlock autol2(l2);
        }
        l2.AssertCurrentThreadOwns();
        l2.Unlock();
    }
    l1.AssertCurrentThreadOwns();

    l1.Unlock();

    PASS();
}

//-----------------------------------------------------------------------------
// AutoMonitor tests
//
static nsresult
AutoMonitor()
{
    Monitor m1("automonitor");
    Monitor m2("automonitor2");

    m1.Enter();
    m1.AssertCurrentThreadIn();
    {
        MonitorAutoEnter autom1(m1);
        m1.AssertCurrentThreadIn();

        m2.Enter();
        m2.AssertCurrentThreadIn();
        {
            MonitorAutoEnter autom2(m2);
            m1.AssertCurrentThreadIn();
            m2.AssertCurrentThreadIn();
        }
        m2.AssertCurrentThreadIn();
        m2.Exit();

        m1.AssertCurrentThreadIn();
    }
    m1.AssertCurrentThreadIn();
    m1.Exit();

    PASS();
}

//-----------------------------------------------------------------------------

int
main(int argc, char** argv)
{
    ScopedXPCOM xpcom("Synchronization (" __FILE__ ")");
    if (xpcom.failed())
        return 1;

    int rv = 0;

    if (NS_FAILED(Sanity()))
        rv = 1;
    if (NS_FAILED(MutexContention()))
        rv = 1;
    if (NS_FAILED(MonitorContention()))
        rv = 1;
    if (NS_FAILED(MonitorContention2()))
        rv = 1;
    if (NS_FAILED(MonitorSyncSanity()))
        rv = 1;
    if (NS_FAILED(CondVarSanity()))
        rv = 1;
    if (NS_FAILED(AutoLock()))
        rv = 1;
    if (NS_FAILED(AutoUnlock()))
        rv = 1;
    if (NS_FAILED(AutoMonitor()))
        rv = 1;

    return rv;
}
