/*
 * Copyright (C) 2015 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include "Test.h"
#include <wtf/Condition.h>
#include <wtf/Lock.h>
#include <wtf/Vector.h>
#include <wtf/WorkQueue.h>
#include <memory>
#include <string>
#include <thread>

namespace TestWebKitAPI {

using namespace std::literals::chrono_literals;

static const char* simpleTestLabel = "simpleTest";
static const char* longTestLabel = "longTest";
static const char* thirdTestLabel = "thirdTest";
static const char* dispatchAfterLabel = "dispatchAfter";

TEST(WTF_WorkQueue, Simple)
{
    Lock m_lock;
    Condition m_testCompleted;
    Vector<std::string> m_functionCallOrder;

    bool calledSimpleTest = false;
    bool calledLongTest = false;
    bool calledThirdTest = false;

    static const char* simpleTestLabel = "simpleTest";
    static const char* longTestLabel = "longTest";
    static const char* thirdTestLabel = "thirdTest";

    auto queue = WorkQueue::create("com.apple.WebKit.Test.simple");
    int initialRefCount = queue->refCount();
    EXPECT_EQ(1, initialRefCount);

    Locker locker { m_lock };
    queue->dispatch([&](void) {
        m_functionCallOrder.append(simpleTestLabel);
        calledSimpleTest = true;
    });

    queue->dispatch([&](void) {
        m_functionCallOrder.append(longTestLabel);
        std::this_thread::sleep_for(100ns);
        calledLongTest = true;
    });

    queue->dispatch([&](void) {
        Locker locker { m_lock };
        m_functionCallOrder.append(thirdTestLabel);
        calledThirdTest = true;

        EXPECT_TRUE(calledSimpleTest);
        EXPECT_TRUE(calledLongTest);
        EXPECT_TRUE(calledThirdTest);

        m_testCompleted.notifyOne();
    });

    EXPECT_GT(queue->refCount(), 1U);

    m_testCompleted.wait(m_lock);

    EXPECT_TRUE(calledSimpleTest);
    EXPECT_TRUE(calledLongTest);
    EXPECT_TRUE(calledThirdTest);

    EXPECT_EQ(static_cast<size_t>(3), m_functionCallOrder.size());
    EXPECT_STREQ(simpleTestLabel, m_functionCallOrder[0].c_str());
    EXPECT_STREQ(longTestLabel, m_functionCallOrder[1].c_str());
    EXPECT_STREQ(thirdTestLabel, m_functionCallOrder[2].c_str());
}

TEST(WTF_WorkQueue, TwoQueues)
{
    Lock m_lock;
    Condition m_testQueue1Completed, m_testQueue2Completed;
    Vector<std::string> m_functionCallOrder;

    bool calledSimpleTest = false;
    bool calledLongTest = false;
    bool calledThirdTest = false;

    auto queue1 = WorkQueue::create("com.apple.WebKit.Test.twoQueues1");
    auto queue2 = WorkQueue::create("com.apple.WebKit.Test.twoQueues2");

    EXPECT_EQ(1U, queue1->refCount());
    EXPECT_EQ(1U, queue2->refCount());

    Locker locker { m_lock };

    queue1->dispatch([&](void) {
        m_functionCallOrder.append(simpleTestLabel);
        calledSimpleTest = true;
    });

    queue2->dispatch([&](void) {
        std::this_thread::sleep_for(50ms);

        Locker locker { m_lock };

        // Will fail if queue2 took the mutex before queue1.
        EXPECT_TRUE(calledThirdTest);

        m_functionCallOrder.append(longTestLabel);
        calledLongTest = true;
        m_testQueue2Completed.notifyOne();
    });

    queue1->dispatch([&](void) {
        Locker locker { m_lock };
        m_functionCallOrder.append(thirdTestLabel);
        calledThirdTest = true;

        m_testQueue1Completed.notifyOne();
    });

    m_testQueue1Completed.wait(m_lock);

    EXPECT_TRUE(calledSimpleTest);
    EXPECT_FALSE(calledLongTest);
    EXPECT_TRUE(calledThirdTest);

    m_testQueue2Completed.wait(m_lock);

    EXPECT_TRUE(calledSimpleTest);
    EXPECT_TRUE(calledLongTest);
    EXPECT_TRUE(calledThirdTest);

    EXPECT_EQ(static_cast<size_t>(3), m_functionCallOrder.size());
    EXPECT_STREQ(simpleTestLabel, m_functionCallOrder[0].c_str());
    EXPECT_STREQ(thirdTestLabel, m_functionCallOrder[1].c_str());
    EXPECT_STREQ(longTestLabel, m_functionCallOrder[2].c_str());
}

TEST(WTF_WorkQueue, DispatchAfter)
{
    Lock m_lock;
    Condition m_testCompleted, m_dispatchAfterTestCompleted;
    Vector<std::string> m_functionCallOrder;

    bool calledSimpleTest = false;
    bool calledDispatchAfterTest = false;

    auto queue = WorkQueue::create("com.apple.WebKit.Test.dispatchAfter");

    Locker locker { m_lock };

    queue->dispatch([&](void) {
        Locker locker { m_lock };
        m_functionCallOrder.append(simpleTestLabel);
        calledSimpleTest = true;
        m_testCompleted.notifyOne();
    });

    queue->dispatchAfter(500_ms, [&](void) {
        Locker locker { m_lock };
        m_functionCallOrder.append(dispatchAfterLabel);
        calledDispatchAfterTest = true;
        m_dispatchAfterTestCompleted.notifyOne();
    });

    m_testCompleted.wait(m_lock);

    EXPECT_TRUE(calledSimpleTest);
    EXPECT_FALSE(calledDispatchAfterTest);

    m_dispatchAfterTestCompleted.wait(m_lock);

    EXPECT_TRUE(calledSimpleTest);
    EXPECT_TRUE(calledDispatchAfterTest);

    EXPECT_EQ(static_cast<size_t>(2), m_functionCallOrder.size());
    EXPECT_STREQ(simpleTestLabel, m_functionCallOrder[0].c_str());
    EXPECT_STREQ(dispatchAfterLabel, m_functionCallOrder[1].c_str());
}

TEST(WTF_WorkQueue, DestroyOnSelf)
{
    Lock lock;
    Condition dispatchAfterTestStarted;
    Condition dispatchAfterTestCompleted;
    bool started = false;
    bool completed = false;

    {
        Locker locker { lock };
        {
            auto queue = WorkQueue::create("com.apple.WebKit.Test.dispatchAfter");
            queue->dispatchAfter(500_ms, [&](void) {
                Locker locker { lock };
                dispatchAfterTestStarted.wait(lock, [&] {
                    return started;
                });
                completed = true;
                dispatchAfterTestCompleted.notifyOne();
            });
        }
        started = true;
        dispatchAfterTestStarted.notifyOne();
    }
    {
        Locker locker { lock };
        dispatchAfterTestCompleted.wait(lock, [&] {
            return completed;
        });
        WTF::sleep(100_ms);
    }
}

TEST(WTF_WorkQueue, DispatchSync)
{
    auto queue = WorkQueue::create("com.apple.WebKit.Test.dispatchSync");
    std::atomic<bool> firstAsyncTaskRan = false;
    std::atomic<bool> secondAsyncTaskRan = false;
    std::atomic<bool> firstSyncTaskTaskRan = false;
    std::atomic<bool> secondSyncTaskTaskRan = false;
    queue->dispatch([&] {
        EXPECT_FALSE(firstAsyncTaskRan);
        EXPECT_FALSE(firstSyncTaskTaskRan);
        EXPECT_FALSE(secondAsyncTaskRan);
        EXPECT_FALSE(secondSyncTaskTaskRan);
        firstAsyncTaskRan = true;
    });
    queue->dispatchSync([&] {
        EXPECT_TRUE(firstAsyncTaskRan);
        EXPECT_FALSE(firstSyncTaskTaskRan);
        EXPECT_FALSE(secondAsyncTaskRan);
        EXPECT_FALSE(secondSyncTaskTaskRan);
        firstSyncTaskTaskRan = true;
    });
    EXPECT_TRUE(firstSyncTaskTaskRan);
    queue->dispatch([&] {
        EXPECT_TRUE(firstAsyncTaskRan);
        EXPECT_TRUE(firstSyncTaskTaskRan);
        EXPECT_FALSE(secondAsyncTaskRan);
        EXPECT_FALSE(secondSyncTaskTaskRan);
        secondAsyncTaskRan = true;
    });
    queue->dispatchSync([&] {
        EXPECT_TRUE(firstAsyncTaskRan);
        EXPECT_TRUE(firstSyncTaskTaskRan);
        EXPECT_TRUE(secondAsyncTaskRan);
        EXPECT_FALSE(secondSyncTaskTaskRan);
        secondSyncTaskTaskRan = true;
    });
    EXPECT_TRUE(secondSyncTaskTaskRan);
}

// Tests that the Function passed to WorkQueue::dispatch is destructed on the thread that
// runs the Function. It is a common pattern to capture a owning reference into a Function
// and dispatch that to a queue to ensure ordering (or thread affinity) of the object destruction.
TEST(WTF_WorkQueue, DestroyDispatchedOnDispatchQueue)
{
    std::atomic<size_t> counter = 0;
    class DestructionWorkQueueTester {
    public:
        DestructionWorkQueueTester(std::atomic<size_t>& counter)
            : m_counter(counter)
        {
        }
        ~DestructionWorkQueueTester()
        {
            EXPECT_NE(m_createdInThread, Thread::current().uid());
            m_counter++;
        }
    private:
        uint32_t m_createdInThread = Thread::current().uid();
        std::atomic<size_t>& m_counter;
    };
    constexpr size_t queueCount = 50;
    constexpr size_t iterationCount = 10000;
    RefPtr<WorkQueue> queue[queueCount];
    for (size_t i = 0; i < queueCount; ++i)
        queue[i] = WorkQueue::create("com.apple.WebKit.Test.destroyDispatchedOnDispatchQueue", WorkQueue::QOS::UserInteractive);

    for (size_t i = 0; i < iterationCount; ++i) {
        for (size_t j = 0; j < queueCount; ++j)
            queue[j]->dispatch([instance = std::make_unique<DestructionWorkQueueTester>(counter)]() { }); // NOLINT
    }
    for (size_t j = 0; j < queueCount; ++j)
        queue[j]->dispatchSync([] { });
    EXPECT_EQ(queueCount * iterationCount, counter);
}

namespace {
struct AssertionTestHolder {
    const RefPtr<WorkQueue> queue { WorkQueue::create("com.apple.WebKit.Test.ThreadSafetyAnalysisAssertIsCurrentWorks", WorkQueue::QOS::UserInteractive) };
    size_t counter WTF_GUARDED_BY_CAPABILITY(*queue) { 0 };
    size_t result { 0 }; // This is here to support the result assertion. The compiler doesn't allow us to obtain the `counter` otherwise.

    void testTask()
    {
        assertIsCurrent(*queue); // This is being tested.
        ++counter;
    }
    void computeResult() WTF_REQUIRES_CAPABILITY(*queue) // This is being tested.
    {
        result = ++counter;
    }
    template<typename T> void testTaskThatFailsToCompile()
    {
        ++counter;
    }
};
}

TEST(WTF_WorkQueue, ThreadSafetyAnalysisAssertIsCurrentWorks)
{
    constexpr size_t queueCount = 50;
    constexpr size_t iterationCount = 10000;

    AssertionTestHolder holders[queueCount];
    for (size_t i = 0; i < iterationCount; ++i) {
        for (auto& holder : holders)
            holder.queue->dispatch([&holder] { holder.testTask(); });
    }
// #define TEST_COMPILE_FAILURE
#ifdef TEST_COMPILE_FAILURE
    for (auto& holder : holders)
        holder.queue->dispatchSync([&holder] { holder.testTaskThatFailsToCompile<int>(); });
#endif
    for (auto& holder : holders)
        holder.queue->dispatchSync([&holder] { assertIsCurrent(*holder.queue); holder.computeResult(); });
    for (auto& holder : holders)
        EXPECT_EQ(iterationCount + 1, holder.result);
}

} // namespace TestWebKitAPI
