/*
 *  Copyright (c) 2016 The WebRTC 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 in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "api/sequence_checker.h"

#include <memory>
#include <utility>

#include "api/function_view.h"
#include "rtc_base/event.h"
#include "rtc_base/platform_thread.h"
#include "rtc_base/task_queue_for_test.h"
#include "test/gtest.h"

namespace webrtc {
namespace {

// This class is dead code, but its purpose is to make sure that
// SequenceChecker is compatible with the RTC_GUARDED_BY and RTC_RUN_ON
// attributes that are checked at compile-time.
class CompileTimeTestForGuardedBy {
 public:
  int CalledOnSequence() RTC_RUN_ON(sequence_checker_) { return guarded_; }

  void CallMeFromSequence() {
    RTC_DCHECK_RUN_ON(&sequence_checker_);
    guarded_ = 41;
  }

 private:
  int guarded_ RTC_GUARDED_BY(sequence_checker_);
  ::webrtc::SequenceChecker sequence_checker_;
};

void RunOnDifferentThread(rtc::FunctionView<void()> run) {
  struct Object {
    static void Run(void* obj) {
      auto* me = static_cast<Object*>(obj);
      me->run();
      me->thread_has_run_event.Set();
    }

    rtc::FunctionView<void()> run;
    rtc::Event thread_has_run_event;
  } object{run};

  rtc::PlatformThread thread(&Object::Run, &object, "thread");
  thread.Start();
  EXPECT_TRUE(object.thread_has_run_event.Wait(1000));
  thread.Stop();
}

}  // namespace

TEST(SequenceCheckerTest, CallsAllowedOnSameThread) {
  SequenceChecker sequence_checker;
  EXPECT_TRUE(sequence_checker.IsCurrent());
}

TEST(SequenceCheckerTest, DestructorAllowedOnDifferentThread) {
  auto sequence_checker = std::make_unique<SequenceChecker>();
  RunOnDifferentThread([&] {
    // Verify that the destructor doesn't assert when called on a different
    // thread.
    sequence_checker.reset();
  });
}

TEST(SequenceCheckerTest, Detach) {
  SequenceChecker sequence_checker;
  sequence_checker.Detach();
  RunOnDifferentThread([&] { EXPECT_TRUE(sequence_checker.IsCurrent()); });
}

TEST(SequenceCheckerTest, DetachFromThreadAndUseOnTaskQueue) {
  SequenceChecker sequence_checker;
  sequence_checker.Detach();
  TaskQueueForTest queue;
  queue.SendTask([&] { EXPECT_TRUE(sequence_checker.IsCurrent()); },
                 RTC_FROM_HERE);
}

TEST(SequenceCheckerTest, DetachFromTaskQueueAndUseOnThread) {
  TaskQueueForTest queue;
  queue.SendTask(
      [] {
        SequenceChecker sequence_checker;
        sequence_checker.Detach();
        RunOnDifferentThread(
            [&] { EXPECT_TRUE(sequence_checker.IsCurrent()); });
      },
      RTC_FROM_HERE);
}

TEST(SequenceCheckerTest, MethodNotAllowedOnDifferentThreadInDebug) {
  SequenceChecker sequence_checker;
  RunOnDifferentThread(
      [&] { EXPECT_EQ(sequence_checker.IsCurrent(), !RTC_DCHECK_IS_ON); });
}

TEST(SequenceCheckerTest, MethodNotAllowedOnDifferentTaskQueueInDebug) {
  SequenceChecker sequence_checker;
  TaskQueueForTest queue;
  queue.SendTask(
      [&] { EXPECT_EQ(sequence_checker.IsCurrent(), !RTC_DCHECK_IS_ON); },
      RTC_FROM_HERE);
}

TEST(SequenceCheckerTest, DetachFromTaskQueueInDebug) {
  SequenceChecker sequence_checker;
  sequence_checker.Detach();

  TaskQueueForTest queue1;
  queue1.SendTask([&] { EXPECT_TRUE(sequence_checker.IsCurrent()); },
                  RTC_FROM_HERE);

  // IsCurrent should return false in debug builds after moving to
  // another task queue.
  TaskQueueForTest queue2;
  queue2.SendTask(
      [&] { EXPECT_EQ(sequence_checker.IsCurrent(), !RTC_DCHECK_IS_ON); },
      RTC_FROM_HERE);
}

class TestAnnotations {
 public:
  TestAnnotations() : test_var_(false) {}

  void ModifyTestVar() {
    RTC_DCHECK_RUN_ON(&checker_);
    test_var_ = true;
  }

 private:
  bool test_var_ RTC_GUARDED_BY(&checker_);
  SequenceChecker checker_;
};

TEST(SequenceCheckerTest, TestAnnotations) {
  TestAnnotations annotations;
  annotations.ModifyTestVar();
}

#if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)

void TestAnnotationsOnWrongQueue() {
  TestAnnotations annotations;
  TaskQueueForTest queue;
  queue.SendTask([&] { annotations.ModifyTestVar(); }, RTC_FROM_HERE);
}

#if RTC_DCHECK_IS_ON
// Note: Ending the test suite name with 'DeathTest' is important as it causes
// gtest to order this test before any other non-death-tests, to avoid potential
// global process state pollution such as shared worker threads being started
// (e.g. a side effect of calling InitCocoaMultiThreading() on Mac causes one or
// two additional threads to be created).
TEST(SequenceCheckerDeathTest, TestAnnotationsOnWrongQueueDebug) {
  ASSERT_DEATH({ TestAnnotationsOnWrongQueue(); }, "");
}
#else
TEST(SequenceCheckerTest, TestAnnotationsOnWrongQueueRelease) {
  TestAnnotationsOnWrongQueue();
}
#endif
#endif  // GTEST_HAS_DEATH_TEST
}  // namespace webrtc
