// Copyright 2013 The Chromium 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 "base/sequence_checker.h"

#include <stddef.h>

#include <memory>
#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/single_thread_task_runner.h"
#include "base/test/sequenced_worker_pool_owner.h"
#include "base/threading/thread.h"
#include "testing/gtest/include/gtest/gtest.h"

// Duplicated from base/sequence_checker.h so that we can be good citizens
// there and undef the macro.
#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON))
#define ENABLE_SEQUENCE_CHECKER 1
#else
#define ENABLE_SEQUENCE_CHECKER 0
#endif

namespace base {

namespace {

const size_t kNumWorkerThreads = 3;

// Simple class to exercise the basics of SequenceChecker.
// DoStuff should verify that it's called on a valid sequenced thread.
// SequenceCheckedObject can be destroyed on any thread (like WeakPtr).
class SequenceCheckedObject {
 public:
  SequenceCheckedObject() {}
  ~SequenceCheckedObject() {}

  // Verifies that it was called on the same thread as the constructor.
  void DoStuff() {
    DCHECK(sequence_checker_.CalledOnValidSequencedThread());
  }

  void DetachFromSequence() {
    sequence_checker_.DetachFromSequence();
  }

 private:
  SequenceChecker sequence_checker_;

  DISALLOW_COPY_AND_ASSIGN(SequenceCheckedObject);
};

class SequenceCheckerTest : public testing::Test {
 public:
  SequenceCheckerTest() : other_thread_("sequence_checker_test_other_thread") {}

  void SetUp() override {
    other_thread_.Start();
    ResetPool();
  }

  void TearDown() override {
    other_thread_.Stop();
  }

 protected:
  base::Thread* other_thread() { return &other_thread_; }

  const scoped_refptr<SequencedWorkerPool>& pool() {
    return pool_owner_->pool();
  }

  void PostDoStuffToWorkerPool(SequenceCheckedObject* sequence_checked_object,
                               const std::string& token_name) {
    pool()->PostNamedSequencedWorkerTask(
        token_name,
        FROM_HERE,
        base::Bind(&SequenceCheckedObject::DoStuff,
                   base::Unretained(sequence_checked_object)));
  }

  void PostDoStuffToOtherThread(
      SequenceCheckedObject* sequence_checked_object) {
    other_thread()->task_runner()->PostTask(
        FROM_HERE, base::Bind(&SequenceCheckedObject::DoStuff,
                              base::Unretained(sequence_checked_object)));
  }

  void PostDeleteToOtherThread(
      std::unique_ptr<SequenceCheckedObject> sequence_checked_object) {
    other_thread()->message_loop()->task_runner()->DeleteSoon(
        FROM_HERE, sequence_checked_object.release());
  }

  // Destroys the SequencedWorkerPool instance, blocking until it is fully shut
  // down, and creates a new instance.
  void ResetPool() {
    pool_owner_.reset(new SequencedWorkerPoolOwner(kNumWorkerThreads, "test"));
  }

  void MethodOnDifferentThreadDeathTest();
  void DetachThenCallFromDifferentThreadDeathTest();
  void DifferentSequenceTokensDeathTest();
  void WorkerPoolAndSimpleThreadDeathTest();
  void TwoDifferentWorkerPoolsDeathTest();

 private:
  MessageLoop message_loop_;  // Needed by SequencedWorkerPool to function.
  base::Thread other_thread_;
  std::unique_ptr<SequencedWorkerPoolOwner> pool_owner_;
};

TEST_F(SequenceCheckerTest, CallsAllowedOnSameThread) {
  std::unique_ptr<SequenceCheckedObject> sequence_checked_object(
      new SequenceCheckedObject);

  // Verify that DoStuff doesn't assert.
  sequence_checked_object->DoStuff();

  // Verify that the destructor doesn't assert.
  sequence_checked_object.reset();
}

TEST_F(SequenceCheckerTest, DestructorAllowedOnDifferentThread) {
  std::unique_ptr<SequenceCheckedObject> sequence_checked_object(
      new SequenceCheckedObject);

  // Verify the destructor doesn't assert when called on a different thread.
  PostDeleteToOtherThread(std::move(sequence_checked_object));
  other_thread()->Stop();
}

TEST_F(SequenceCheckerTest, DetachFromSequence) {
  std::unique_ptr<SequenceCheckedObject> sequence_checked_object(
      new SequenceCheckedObject);

  // Verify that DoStuff doesn't assert when called on a different thread after
  // a call to DetachFromSequence.
  sequence_checked_object->DetachFromSequence();

  PostDoStuffToOtherThread(sequence_checked_object.get());
  other_thread()->Stop();
}

TEST_F(SequenceCheckerTest, SameSequenceTokenValid) {
  std::unique_ptr<SequenceCheckedObject> sequence_checked_object(
      new SequenceCheckedObject);

  sequence_checked_object->DetachFromSequence();
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "A");
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "A");
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "A");
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "A");
  pool()->FlushForTesting();

  PostDeleteToOtherThread(std::move(sequence_checked_object));
  other_thread()->Stop();
}

TEST_F(SequenceCheckerTest, DetachSequenceTokenValid) {
  std::unique_ptr<SequenceCheckedObject> sequence_checked_object(
      new SequenceCheckedObject);

  sequence_checked_object->DetachFromSequence();
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "A");
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "A");
  pool()->FlushForTesting();

  sequence_checked_object->DetachFromSequence();
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "B");
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "B");
  pool()->FlushForTesting();

  PostDeleteToOtherThread(std::move(sequence_checked_object));
  other_thread()->Stop();
}

#if GTEST_HAS_DEATH_TEST || !ENABLE_SEQUENCE_CHECKER

void SequenceCheckerTest::MethodOnDifferentThreadDeathTest() {
  std::unique_ptr<SequenceCheckedObject> sequence_checked_object(
      new SequenceCheckedObject);

  // DoStuff should assert in debug builds only when called on a
  // different thread.
  PostDoStuffToOtherThread(sequence_checked_object.get());
  other_thread()->Stop();
}

#if ENABLE_SEQUENCE_CHECKER
TEST_F(SequenceCheckerTest, MethodNotAllowedOnDifferentThreadDeathTestInDebug) {
  // The default style "fast" does not support multi-threaded tests.
  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
  ASSERT_DEATH({
    MethodOnDifferentThreadDeathTest();
  }, "");
}
#else
TEST_F(SequenceCheckerTest, MethodAllowedOnDifferentThreadDeathTestInRelease) {
  MethodOnDifferentThreadDeathTest();
}
#endif  // ENABLE_SEQUENCE_CHECKER

void SequenceCheckerTest::DetachThenCallFromDifferentThreadDeathTest() {
  std::unique_ptr<SequenceCheckedObject> sequence_checked_object(
      new SequenceCheckedObject);

  // DoStuff doesn't assert when called on a different thread
  // after a call to DetachFromSequence.
  sequence_checked_object->DetachFromSequence();
  PostDoStuffToOtherThread(sequence_checked_object.get());
  other_thread()->Stop();

  // DoStuff should assert in debug builds only after moving to
  // another thread.
  sequence_checked_object->DoStuff();
}

#if ENABLE_SEQUENCE_CHECKER
TEST_F(SequenceCheckerTest, DetachFromSequenceDeathTestInDebug) {
  // The default style "fast" does not support multi-threaded tests.
  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
  ASSERT_DEATH({
    DetachThenCallFromDifferentThreadDeathTest();
  }, "");
}
#else
TEST_F(SequenceCheckerTest, DetachFromThreadDeathTestInRelease) {
  DetachThenCallFromDifferentThreadDeathTest();
}
#endif  // ENABLE_SEQUENCE_CHECKER

void SequenceCheckerTest::DifferentSequenceTokensDeathTest() {
  std::unique_ptr<SequenceCheckedObject> sequence_checked_object(
      new SequenceCheckedObject);

  sequence_checked_object->DetachFromSequence();
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "A");
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "A");
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "B");
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "B");
  pool()->FlushForTesting();

  PostDeleteToOtherThread(std::move(sequence_checked_object));
  other_thread()->Stop();
}

#if ENABLE_SEQUENCE_CHECKER
TEST_F(SequenceCheckerTest, DifferentSequenceTokensDeathTestInDebug) {
  // The default style "fast" does not support multi-threaded tests.
  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
  ASSERT_DEATH({
    DifferentSequenceTokensDeathTest();
  }, "");
}
#else
TEST_F(SequenceCheckerTest, DifferentSequenceTokensDeathTestInRelease) {
  DifferentSequenceTokensDeathTest();
}
#endif  // ENABLE_SEQUENCE_CHECKER

void SequenceCheckerTest::WorkerPoolAndSimpleThreadDeathTest() {
  std::unique_ptr<SequenceCheckedObject> sequence_checked_object(
      new SequenceCheckedObject);

  sequence_checked_object->DetachFromSequence();
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "A");
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "A");
  pool()->FlushForTesting();

  PostDoStuffToOtherThread(sequence_checked_object.get());
  other_thread()->Stop();
}

#if ENABLE_SEQUENCE_CHECKER
TEST_F(SequenceCheckerTest, WorkerPoolAndSimpleThreadDeathTestInDebug) {
  // The default style "fast" does not support multi-threaded tests.
  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
  ASSERT_DEATH({
    WorkerPoolAndSimpleThreadDeathTest();
  }, "");
}
#else
TEST_F(SequenceCheckerTest, WorkerPoolAndSimpleThreadDeathTestInRelease) {
  WorkerPoolAndSimpleThreadDeathTest();
}
#endif  // ENABLE_SEQUENCE_CHECKER

void SequenceCheckerTest::TwoDifferentWorkerPoolsDeathTest() {
  std::unique_ptr<SequenceCheckedObject> sequence_checked_object(
      new SequenceCheckedObject);

  sequence_checked_object->DetachFromSequence();
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "A");
  PostDoStuffToWorkerPool(sequence_checked_object.get(), "A");
  pool()->FlushForTesting();

  SequencedWorkerPoolOwner second_pool_owner(kNumWorkerThreads, "test2");
  second_pool_owner.pool()->PostNamedSequencedWorkerTask(
      "A",
      FROM_HERE,
      base::Bind(&SequenceCheckedObject::DoStuff,
                 base::Unretained(sequence_checked_object.get())));
  second_pool_owner.pool()->FlushForTesting();
}

#if ENABLE_SEQUENCE_CHECKER
TEST_F(SequenceCheckerTest, TwoDifferentWorkerPoolsDeathTestInDebug) {
  // The default style "fast" does not support multi-threaded tests.
  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
  ASSERT_DEATH({
    TwoDifferentWorkerPoolsDeathTest();
  }, "");
}
#else
TEST_F(SequenceCheckerTest, TwoDifferentWorkerPoolsDeathTestInRelease) {
  TwoDifferentWorkerPoolsDeathTest();
}
#endif  // ENABLE_SEQUENCE_CHECKER

#endif  // GTEST_HAS_DEATH_TEST || !ENABLE_SEQUENCE_CHECKER

}  // namespace

}  // namespace base

// Just in case we ever get lumped together with other compilation units.
#undef ENABLE_SEQUENCE_CHECKER