// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/base/platform/condition-variable.h"

#include "src/base/platform/platform.h"
#include "src/base/platform/time.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace v8 {
namespace base {

TEST(ConditionVariable, WaitForAfterNofityOnSameThread) {
  for (int n = 0; n < 10; ++n) {
    Mutex mutex;
    ConditionVariable cv;

    LockGuard<Mutex> lock_guard(&mutex);

    cv.NotifyOne();
    EXPECT_FALSE(cv.WaitFor(&mutex, TimeDelta::FromMicroseconds(n)));

    cv.NotifyAll();
    EXPECT_FALSE(cv.WaitFor(&mutex, TimeDelta::FromMicroseconds(n)));
  }
}


namespace {

class ThreadWithMutexAndConditionVariable FINAL : public Thread {
 public:
  ThreadWithMutexAndConditionVariable()
      : Thread(Options("ThreadWithMutexAndConditionVariable")),
        running_(false),
        finished_(false) {}
  virtual ~ThreadWithMutexAndConditionVariable() {}

  virtual void Run() OVERRIDE {
    LockGuard<Mutex> lock_guard(&mutex_);
    running_ = true;
    cv_.NotifyOne();
    while (running_) {
      cv_.Wait(&mutex_);
    }
    finished_ = true;
    cv_.NotifyAll();
  }

  bool running_;
  bool finished_;
  ConditionVariable cv_;
  Mutex mutex_;
};

}  // namespace


TEST(ConditionVariable, MultipleThreadsWithSeparateConditionVariables) {
  static const int kThreadCount = 128;
  ThreadWithMutexAndConditionVariable threads[kThreadCount];

  for (int n = 0; n < kThreadCount; ++n) {
    LockGuard<Mutex> lock_guard(&threads[n].mutex_);
    EXPECT_FALSE(threads[n].running_);
    EXPECT_FALSE(threads[n].finished_);
    threads[n].Start();
    // Wait for nth thread to start.
    while (!threads[n].running_) {
      threads[n].cv_.Wait(&threads[n].mutex_);
    }
  }

  for (int n = kThreadCount - 1; n >= 0; --n) {
    LockGuard<Mutex> lock_guard(&threads[n].mutex_);
    EXPECT_TRUE(threads[n].running_);
    EXPECT_FALSE(threads[n].finished_);
  }

  for (int n = 0; n < kThreadCount; ++n) {
    LockGuard<Mutex> lock_guard(&threads[n].mutex_);
    EXPECT_TRUE(threads[n].running_);
    EXPECT_FALSE(threads[n].finished_);
    // Tell the nth thread to quit.
    threads[n].running_ = false;
    threads[n].cv_.NotifyOne();
  }

  for (int n = kThreadCount - 1; n >= 0; --n) {
    // Wait for nth thread to quit.
    LockGuard<Mutex> lock_guard(&threads[n].mutex_);
    while (!threads[n].finished_) {
      threads[n].cv_.Wait(&threads[n].mutex_);
    }
    EXPECT_FALSE(threads[n].running_);
    EXPECT_TRUE(threads[n].finished_);
  }

  for (int n = 0; n < kThreadCount; ++n) {
    threads[n].Join();
    LockGuard<Mutex> lock_guard(&threads[n].mutex_);
    EXPECT_FALSE(threads[n].running_);
    EXPECT_TRUE(threads[n].finished_);
  }
}


namespace {

class ThreadWithSharedMutexAndConditionVariable FINAL : public Thread {
 public:
  ThreadWithSharedMutexAndConditionVariable()
      : Thread(Options("ThreadWithSharedMutexAndConditionVariable")),
        running_(false),
        finished_(false),
        cv_(NULL),
        mutex_(NULL) {}
  virtual ~ThreadWithSharedMutexAndConditionVariable() {}

  virtual void Run() OVERRIDE {
    LockGuard<Mutex> lock_guard(mutex_);
    running_ = true;
    cv_->NotifyAll();
    while (running_) {
      cv_->Wait(mutex_);
    }
    finished_ = true;
    cv_->NotifyAll();
  }

  bool running_;
  bool finished_;
  ConditionVariable* cv_;
  Mutex* mutex_;
};

}  // namespace


TEST(ConditionVariable, MultipleThreadsWithSharedSeparateConditionVariables) {
  static const int kThreadCount = 128;
  ThreadWithSharedMutexAndConditionVariable threads[kThreadCount];
  ConditionVariable cv;
  Mutex mutex;

  for (int n = 0; n < kThreadCount; ++n) {
    threads[n].mutex_ = &mutex;
    threads[n].cv_ = &cv;
  }

  // Start all threads.
  {
    LockGuard<Mutex> lock_guard(&mutex);
    for (int n = 0; n < kThreadCount; ++n) {
      EXPECT_FALSE(threads[n].running_);
      EXPECT_FALSE(threads[n].finished_);
      threads[n].Start();
    }
  }

  // Wait for all threads to start.
  {
    LockGuard<Mutex> lock_guard(&mutex);
    for (int n = kThreadCount - 1; n >= 0; --n) {
      while (!threads[n].running_) {
        cv.Wait(&mutex);
      }
    }
  }

  // Make sure that all threads are running.
  {
    LockGuard<Mutex> lock_guard(&mutex);
    for (int n = 0; n < kThreadCount; ++n) {
      EXPECT_TRUE(threads[n].running_);
      EXPECT_FALSE(threads[n].finished_);
    }
  }

  // Tell all threads to quit.
  {
    LockGuard<Mutex> lock_guard(&mutex);
    for (int n = kThreadCount - 1; n >= 0; --n) {
      EXPECT_TRUE(threads[n].running_);
      EXPECT_FALSE(threads[n].finished_);
      // Tell the nth thread to quit.
      threads[n].running_ = false;
    }
    cv.NotifyAll();
  }

  // Wait for all threads to quit.
  {
    LockGuard<Mutex> lock_guard(&mutex);
    for (int n = 0; n < kThreadCount; ++n) {
      while (!threads[n].finished_) {
        cv.Wait(&mutex);
      }
    }
  }

  // Make sure all threads are finished.
  {
    LockGuard<Mutex> lock_guard(&mutex);
    for (int n = kThreadCount - 1; n >= 0; --n) {
      EXPECT_FALSE(threads[n].running_);
      EXPECT_TRUE(threads[n].finished_);
    }
  }

  // Join all threads.
  for (int n = 0; n < kThreadCount; ++n) {
    threads[n].Join();
  }
}


namespace {

class LoopIncrementThread FINAL : public Thread {
 public:
  LoopIncrementThread(int rem, int* counter, int limit, int thread_count,
                      ConditionVariable* cv, Mutex* mutex)
      : Thread(Options("LoopIncrementThread")),
        rem_(rem),
        counter_(counter),
        limit_(limit),
        thread_count_(thread_count),
        cv_(cv),
        mutex_(mutex) {
    EXPECT_LT(rem, thread_count);
    EXPECT_EQ(0, limit % thread_count);
  }

  virtual void Run() OVERRIDE {
    int last_count = -1;
    while (true) {
      LockGuard<Mutex> lock_guard(mutex_);
      int count = *counter_;
      while (count % thread_count_ != rem_ && count < limit_) {
        cv_->Wait(mutex_);
        count = *counter_;
      }
      if (count >= limit_) break;
      EXPECT_EQ(*counter_, count);
      if (last_count != -1) {
        EXPECT_EQ(last_count + (thread_count_ - 1), count);
      }
      count++;
      *counter_ = count;
      last_count = count;
      cv_->NotifyAll();
    }
  }

 private:
  const int rem_;
  int* counter_;
  const int limit_;
  const int thread_count_;
  ConditionVariable* cv_;
  Mutex* mutex_;
};

}  // namespace


TEST(ConditionVariable, LoopIncrement) {
  static const int kMaxThreadCount = 16;
  Mutex mutex;
  ConditionVariable cv;
  for (int thread_count = 1; thread_count < kMaxThreadCount; ++thread_count) {
    int limit = thread_count * 10;
    int counter = 0;

    // Setup the threads.
    Thread** threads = new Thread* [thread_count];
    for (int n = 0; n < thread_count; ++n) {
      threads[n] = new LoopIncrementThread(n, &counter, limit, thread_count,
                                           &cv, &mutex);
    }

    // Start all threads.
    for (int n = thread_count - 1; n >= 0; --n) {
      threads[n]->Start();
    }

    // Join and cleanup all threads.
    for (int n = 0; n < thread_count; ++n) {
      threads[n]->Join();
      delete threads[n];
    }
    delete[] threads;

    EXPECT_EQ(limit, counter);
  }
}

}  // namespace base
}  // namespace v8
