// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/threading/sequence_bound.h"

#include <functional>
#include <memory>
#include <string_view>
#include <utility>

#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

namespace {

class EventLogger {
 public:
  EventLogger() = default;

  void AddEvent(std::string_view event) {
    AutoLock guard(lock_);
    events_.push_back(std::string(event));
  }
  std::vector<std::string> TakeEvents() {
    AutoLock guard(lock_);
    return std::exchange(events_, {});
  }

 private:
  Lock lock_;
  std::vector<std::string> events_ GUARDED_BY(lock_);
};

// Helpers for writing type tests against both `SequenceBound<T>` and
// `SequenceBound<std::unique_ptr<T>`. The tricky part here is that the
// constructor and emplace both need to accept variadic args; however,
// construction of the actual `T` depends on the storage strategy.  The
// `Wrapper` template provides this layer of indirection to construct the
// managed `T` while still passing through all the other remaining
// `SequenceBound` APIs.
struct DirectVariation {
  static constexpr bool kManagingTaskRunnerConstructsT = true;

  template <typename T>
  class Wrapper : public SequenceBound<T> {
   public:
    template <typename... Args>
    explicit Wrapper(scoped_refptr<SequencedTaskRunner> task_runner,
                     Args&&... args)
        : SequenceBound<T>(std::move(task_runner),
                           std::forward<Args>(args)...) {}

    template <typename... Args>
    void WrappedEmplace(scoped_refptr<SequencedTaskRunner> task_runner,
                        Args&&... args) {
      this->emplace(std::move(task_runner), std::forward<Args>(args)...);
    }

    using SequenceBound<T>::SequenceBound;
    using SequenceBound<T>::operator=;

   private:
    using SequenceBound<T>::emplace;
  };
};

struct UniquePtrVariation {
  static constexpr bool kManagingTaskRunnerConstructsT = false;

  template <typename T>
  struct Wrapper : public SequenceBound<std::unique_ptr<T>> {
   public:
    template <typename... Args>
    explicit Wrapper(scoped_refptr<SequencedTaskRunner> task_runner,
                     Args&&... args)
        : SequenceBound<std::unique_ptr<T>>(
              std::move(task_runner),
              std::make_unique<T>(std::forward<Args>(args)...)) {}

    template <typename... Args>
    void WrappedEmplace(scoped_refptr<SequencedTaskRunner> task_runner,
                        Args&&... args) {
      this->emplace(std::move(task_runner),
                    std::make_unique<T>(std::forward<Args>(args)...));
    }

    using SequenceBound<std::unique_ptr<T>>::SequenceBound;
    using SequenceBound<std::unique_ptr<T>>::operator=;

   private:
    using SequenceBound<std::unique_ptr<T>>::emplace;
  };
};

// Helper macros since using the name directly is otherwise quite unwieldy.
#define SEQUENCE_BOUND_T typename TypeParam::template Wrapper
// Try to catch tests that inadvertently use SequenceBound<T> directly instead
// of SEQUENCE_BOUND_T, as that bypasses the point of having a typed test.
#define SequenceBound PleaseUseSequenceBoundT

template <typename Variation>
class SequenceBoundTest : public ::testing::Test {
 public:
  void TearDown() override {
    // Make sure that any objects owned by `SequenceBound` have been destroyed
    // to avoid tripping leak detection.
    task_environment_.RunUntilIdle();
  }

  // Helper for tests that want to synchronize on a `SequenceBound` which has
  // already been `Reset()`: a null `SequenceBound` has no `SequencedTaskRunner`
  // associated with it, so the usual `FlushPostedTasksForTesting()` helper does
  // not work.
  void FlushPostedTasks() {
    RunLoop run_loop;
    background_task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
    run_loop.Run();
  }

  test::TaskEnvironment task_environment_;

  // Task runner to use for SequenceBound's managed `T`.
  scoped_refptr<SequencedTaskRunner> background_task_runner_ =
      ThreadPool::CreateSequencedTaskRunner({});

  // Defined as part of the test fixture so that tests using `EventLogger` do
  // not need to explicitly synchronize on `Reset() to avoid use-after-frees;
  // instead, tests should rely on `TearDown()` to drain and run any
  // already-posted cleanup tasks.
  EventLogger logger_;
};

using Variations = ::testing::Types<DirectVariation, UniquePtrVariation>;
TYPED_TEST_SUITE(SequenceBoundTest, Variations);

class Base {
 public:
  explicit Base(EventLogger& logger) : logger_(logger) {
    logger_->AddEvent("constructed Base");
  }
  virtual ~Base() { logger_->AddEvent("destroyed Base"); }

 protected:
  EventLogger& GetLogger() { return *logger_; }

 private:
  const raw_ref<EventLogger> logger_;
};

class Derived : public Base {
 public:
  explicit Derived(EventLogger& logger) : Base(logger) {
    GetLogger().AddEvent("constructed Derived");
  }

  ~Derived() override { GetLogger().AddEvent("destroyed Derived"); }

  void SetValue(int value) {
    GetLogger().AddEvent(StringPrintf("set Derived to %d", value));
  }
};

class Leftmost {
 public:
  explicit Leftmost(EventLogger& logger) : logger_(logger) {
    logger_->AddEvent("constructed Leftmost");
  }
  virtual ~Leftmost() { logger_->AddEvent("destroyed Leftmost"); }

  void SetValue(int value) {
    logger_->AddEvent(StringPrintf("set Leftmost to %d", value));
  }

 private:
  const raw_ref<EventLogger> logger_;
};

class Rightmost : public Base {
 public:
  explicit Rightmost(EventLogger& logger) : Base(logger) {
    GetLogger().AddEvent("constructed Rightmost");
  }

  ~Rightmost() override { GetLogger().AddEvent("destroyed Rightmost"); }

  void SetValue(int value) {
    GetLogger().AddEvent(StringPrintf("set Rightmost to %d", value));
  }
};

class MultiplyDerived : public Leftmost, public Rightmost {
 public:
  explicit MultiplyDerived(EventLogger& logger)
      : Leftmost(logger), Rightmost(logger) {
    GetLogger().AddEvent("constructed MultiplyDerived");
  }

  ~MultiplyDerived() override {
    GetLogger().AddEvent("destroyed MultiplyDerived");
  }
};

class BoxedValue {
 public:
  explicit BoxedValue(int initial_value, EventLogger* logger = nullptr)
      : logger_(logger), value_(initial_value) {
    sequence_checker_.DetachFromSequence();
    AddEventIfNeeded(StringPrintf("constructed BoxedValue = %d", value_));
  }

  BoxedValue(const BoxedValue&) = delete;
  BoxedValue& operator=(const BoxedValue&) = delete;

  ~BoxedValue() {
    EXPECT_TRUE(sequence_checker_.CalledOnValidSequence());
    AddEventIfNeeded(StringPrintf("destroyed BoxedValue = %d", value_));
    if (destruction_callback_)
      std::move(destruction_callback_).Run();
  }

  void set_destruction_callback(OnceClosure callback) {
    EXPECT_TRUE(sequence_checker_.CalledOnValidSequence());
    destruction_callback_ = std::move(callback);
  }

  int value() const {
    EXPECT_TRUE(sequence_checker_.CalledOnValidSequence());
    AddEventIfNeeded(StringPrintf("accessed BoxedValue = %d", value_));
    return value_;
  }
  void set_value(int value) {
    EXPECT_TRUE(sequence_checker_.CalledOnValidSequence());
    AddEventIfNeeded(
        StringPrintf("updated BoxedValue from %d to %d", value_, value));
    value_ = value;
  }

 private:
  void AddEventIfNeeded(std::string_view event) const {
    if (logger_) {
      logger_->AddEvent(event);
    }
  }

  SequenceChecker sequence_checker_;

  mutable raw_ptr<EventLogger> logger_ = nullptr;

  int value_ = 0;
  OnceClosure destruction_callback_;
};

// Smoke test that all interactions with the wrapped object are posted to the
// correct task runner.
class SequenceValidator {
 public:
  explicit SequenceValidator(scoped_refptr<SequencedTaskRunner> task_runner,
                             bool constructs_on_managing_task_runner)
      : task_runner_(std::move(task_runner)) {
    if (constructs_on_managing_task_runner) {
      EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
    }
  }

  ~SequenceValidator() {
    EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
  }

  void ReturnsVoid() const {
    EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
  }

  void ReturnsVoidMutable() {
    EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
  }

  int ReturnsInt() const {
    EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
    return 0;
  }

  int ReturnsIntMutable() {
    EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
    return 0;
  }

 private:
  scoped_refptr<SequencedTaskRunner> task_runner_;
};

TYPED_TEST(SequenceBoundTest, SequenceValidation) {
  SEQUENCE_BOUND_T<SequenceValidator> validator(
      this->background_task_runner_, this->background_task_runner_,
      TypeParam::kManagingTaskRunnerConstructsT);
  validator.AsyncCall(&SequenceValidator::ReturnsVoid);
  validator.AsyncCall(&SequenceValidator::ReturnsVoidMutable);
  validator.AsyncCall(&SequenceValidator::ReturnsInt).Then(BindOnce([](int) {
  }));
  validator.AsyncCall(&SequenceValidator::ReturnsIntMutable)
      .Then(BindOnce([](int) {}));
  validator.AsyncCall(IgnoreResult(&SequenceValidator::ReturnsInt));
  validator.AsyncCall(IgnoreResult(&SequenceValidator::ReturnsIntMutable));
  validator.WrappedEmplace(this->background_task_runner_,
                           this->background_task_runner_,
                           TypeParam::kManagingTaskRunnerConstructsT);
  validator.PostTaskWithThisObject(BindLambdaForTesting(
      [](const SequenceValidator& v) { v.ReturnsVoid(); }));
  validator.PostTaskWithThisObject(BindLambdaForTesting(
      [](SequenceValidator* v) { v->ReturnsVoidMutable(); }));
  validator.Reset();
  this->FlushPostedTasks();
}

TYPED_TEST(SequenceBoundTest, Basic) {
  SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_, 0,
                                     &this->logger_);
  // Construction of `BoxedValue` may be posted to `background_task_runner_`,
  // but the `SequenceBound` itself should immediately be treated as valid /
  // non-null.
  EXPECT_FALSE(value.is_null());
  EXPECT_TRUE(value);
  value.FlushPostedTasksForTesting();
  EXPECT_THAT(this->logger_.TakeEvents(),
              ::testing::ElementsAre("constructed BoxedValue = 0"));

  value.AsyncCall(&BoxedValue::set_value).WithArgs(66);
  value.FlushPostedTasksForTesting();
  EXPECT_THAT(this->logger_.TakeEvents(),
              ::testing::ElementsAre("updated BoxedValue from 0 to 66"));

  // Destruction of `BoxedValue` may be posted to `background_task_runner_`, but
  // the `SequenceBound` itself should immediately be treated as valid /
  // non-null.
  value.Reset();
  EXPECT_TRUE(value.is_null());
  EXPECT_FALSE(value);
  this->FlushPostedTasks();
  EXPECT_THAT(this->logger_.TakeEvents(),
              ::testing::ElementsAre("destroyed BoxedValue = 66"));
}

TYPED_TEST(SequenceBoundTest, ConstructAndImmediateAsyncCall) {
  // Calling `AsyncCall` immediately after construction should always work.
  SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_, 0,
                                     &this->logger_);
  value.AsyncCall(&BoxedValue::set_value).WithArgs(8);
  value.FlushPostedTasksForTesting();
  EXPECT_THAT(this->logger_.TakeEvents(),
              ::testing::ElementsAre("constructed BoxedValue = 0",
                                     "updated BoxedValue from 0 to 8"));
}

TYPED_TEST(SequenceBoundTest, MoveConstruction) {
  // std::ref() is required here: internally, the async work is bound into the
  // standard base callback infrastructure, which requires the explicit use of
  // `std::cref()` and `std::ref()` when passing by reference.
  SEQUENCE_BOUND_T<Derived> derived_old(this->background_task_runner_,
                                        std::ref(this->logger_));
  SEQUENCE_BOUND_T<Derived> derived_new = std::move(derived_old);
  // NOLINTNEXTLINE(bugprone-use-after-move)
  EXPECT_TRUE(derived_old.is_null());
  EXPECT_FALSE(derived_new.is_null());
  derived_new.Reset();
  this->FlushPostedTasks();
  EXPECT_THAT(this->logger_.TakeEvents(),
              ::testing::ElementsAre("constructed Base", "constructed Derived",
                                     "destroyed Derived", "destroyed Base"));
}

TYPED_TEST(SequenceBoundTest, MoveConstructionUpcastsToBase) {
  SEQUENCE_BOUND_T<Derived> derived(this->background_task_runner_,
                                    std::ref(this->logger_));
  SEQUENCE_BOUND_T<Base> base = std::move(derived);
  // NOLINTNEXTLINE(bugprone-use-after-move)
  EXPECT_TRUE(derived.is_null());
  EXPECT_FALSE(base.is_null());

  // The original `Derived` object is now owned by `SequencedBound<Base>`; make
  // sure `~Derived()` still runs when it is reset.
  base.Reset();
  this->FlushPostedTasks();
  EXPECT_THAT(this->logger_.TakeEvents(),
              ::testing::ElementsAre("constructed Base", "constructed Derived",
                                     "destroyed Derived", "destroyed Base"));
}

// Classes with multiple-derived bases may need pointer adjustments when
// upcasting. These tests rely on sanitizers to catch potential mistakes.
TYPED_TEST(SequenceBoundTest, MoveConstructionUpcastsToLeftmost) {
  SEQUENCE_BOUND_T<MultiplyDerived> multiply_derived(
      this->background_task_runner_, std::ref(this->logger_));
  SEQUENCE_BOUND_T<Leftmost> leftmost_base = std::move(multiply_derived);
  // NOLINTNEXTLINE(bugprone-use-after-move)
  EXPECT_TRUE(multiply_derived.is_null());
  EXPECT_FALSE(leftmost_base.is_null());

  // The original `MultiplyDerived` object is now owned by
  // `SequencedBound<Leftmost>`; make sure all the expected destructors
  // still run when it is reset.
  leftmost_base.Reset();
  this->FlushPostedTasks();
  EXPECT_THAT(
      this->logger_.TakeEvents(),
      ::testing::ElementsAre(
          "constructed Leftmost", "constructed Base", "constructed Rightmost",
          "constructed MultiplyDerived", "destroyed MultiplyDerived",
          "destroyed Rightmost", "destroyed Base", "destroyed Leftmost"));
}

TYPED_TEST(SequenceBoundTest, MoveConstructionUpcastsToRightmost) {
  SEQUENCE_BOUND_T<MultiplyDerived> multiply_derived(
      this->background_task_runner_, std::ref(this->logger_));
  SEQUENCE_BOUND_T<Rightmost> rightmost_base = std::move(multiply_derived);
  // NOLINTNEXTLINE(bugprone-use-after-move)
  EXPECT_TRUE(multiply_derived.is_null());
  EXPECT_FALSE(rightmost_base.is_null());

  // The original `MultiplyDerived` object is now owned by
  // `SequencedBound<Rightmost>`; make sure all the expected destructors
  // still run when it is reset.
  rightmost_base.Reset();
  this->FlushPostedTasks();
  EXPECT_THAT(
      this->logger_.TakeEvents(),
      ::testing::ElementsAre(
          "constructed Leftmost", "constructed Base", "constructed Rightmost",
          "constructed MultiplyDerived", "destroyed MultiplyDerived",
          "destroyed Rightmost", "destroyed Base", "destroyed Leftmost"));
}

TYPED_TEST(SequenceBoundTest, MoveAssignment) {
  SEQUENCE_BOUND_T<Derived> derived_old(this->background_task_runner_,
                                        std::ref(this->logger_));
  SEQUENCE_BOUND_T<Derived> derived_new;

  derived_new = std::move(derived_old);
  // NOLINTNEXTLINE(bugprone-use-after-move)
  EXPECT_TRUE(derived_old.is_null());
  EXPECT_FALSE(derived_new.is_null());

  // Note that this explicitly avoids using `Reset()` as a basic test that
  // assignment resets any previously-owned object.
  derived_new = SEQUENCE_BOUND_T<Derived>();
  this->FlushPostedTasks();
  EXPECT_THAT(this->logger_.TakeEvents(),
              ::testing::ElementsAre("constructed Base", "constructed Derived",
                                     "destroyed Derived", "destroyed Base"));
}

TYPED_TEST(SequenceBoundTest, MoveAssignmentUpcastsToBase) {
  SEQUENCE_BOUND_T<Derived> derived(this->background_task_runner_,
                                    std::ref(this->logger_));
  SEQUENCE_BOUND_T<Base> base;

  base = std::move(derived);
  // NOLINTNEXTLINE(bugprone-use-after-move)
  EXPECT_TRUE(derived.is_null());
  EXPECT_FALSE(base.is_null());

  // The original `Derived` object is now owned by `SequencedBound<Base>`; make
  // sure `~Derived()` still runs when it is reset.
  base.Reset();
  this->FlushPostedTasks();
  EXPECT_THAT(this->logger_.TakeEvents(),
              ::testing::ElementsAre("constructed Base", "constructed Derived",
                                     "destroyed Derived", "destroyed Base"));
}

TYPED_TEST(SequenceBoundTest, MoveAssignmentUpcastsToLeftmost) {
  SEQUENCE_BOUND_T<MultiplyDerived> multiply_derived(
      this->background_task_runner_, std::ref(this->logger_));
  SEQUENCE_BOUND_T<Leftmost> leftmost_base;

  leftmost_base = std::move(multiply_derived);
  // NOLINTNEXTLINE(bugprone-use-after-move)
  EXPECT_TRUE(multiply_derived.is_null());
  EXPECT_FALSE(leftmost_base.is_null());

  // The original `MultiplyDerived` object is now owned by
  // `SequencedBound<Leftmost>`; make sure all the expected destructors
  // still run when it is reset.
  leftmost_base.Reset();
  this->FlushPostedTasks();
  EXPECT_THAT(
      this->logger_.TakeEvents(),
      ::testing::ElementsAre(
          "constructed Leftmost", "constructed Base", "constructed Rightmost",
          "constructed MultiplyDerived", "destroyed MultiplyDerived",
          "destroyed Rightmost", "destroyed Base", "destroyed Leftmost"));
}

TYPED_TEST(SequenceBoundTest, MoveAssignmentUpcastsToRightmost) {
  SEQUENCE_BOUND_T<MultiplyDerived> multiply_derived(
      this->background_task_runner_, std::ref(this->logger_));
  SEQUENCE_BOUND_T<Rightmost> rightmost_base;

  rightmost_base = std::move(multiply_derived);
  // NOLINTNEXTLINE(bugprone-use-after-move)
  EXPECT_TRUE(multiply_derived.is_null());
  EXPECT_FALSE(rightmost_base.is_null());

  // The original `MultiplyDerived` object is now owned by
  // `SequencedBound<Rightmost>`; make sure all the expected destructors
  // still run when it is reset.
  rightmost_base.Reset();
  this->FlushPostedTasks();
  EXPECT_THAT(
      this->logger_.TakeEvents(),
      ::testing::ElementsAre(
          "constructed Leftmost", "constructed Base", "constructed Rightmost",
          "constructed MultiplyDerived", "destroyed MultiplyDerived",
          "destroyed Rightmost", "destroyed Base", "destroyed Leftmost"));
}

TYPED_TEST(SequenceBoundTest, AsyncCallLeftmost) {
  SEQUENCE_BOUND_T<MultiplyDerived> multiply_derived(
      this->background_task_runner_, std::ref(this->logger_));
  multiply_derived.AsyncCall(&Leftmost::SetValue).WithArgs(3);
  multiply_derived.FlushPostedTasksForTesting();
  EXPECT_THAT(this->logger_.TakeEvents(),
              ::testing::ElementsAre("constructed Leftmost", "constructed Base",
                                     "constructed Rightmost",
                                     "constructed MultiplyDerived",
                                     "set Leftmost to 3"));
}

TYPED_TEST(SequenceBoundTest, AsyncCallRightmost) {
  SEQUENCE_BOUND_T<MultiplyDerived> multiply_derived(
      this->background_task_runner_, std::ref(this->logger_));
  multiply_derived.AsyncCall(&Rightmost::SetValue).WithArgs(3);
  multiply_derived.FlushPostedTasksForTesting();
  EXPECT_THAT(this->logger_.TakeEvents(),
              ::testing::ElementsAre("constructed Leftmost", "constructed Base",
                                     "constructed Rightmost",
                                     "constructed MultiplyDerived",
                                     "set Rightmost to 3"));
}

TYPED_TEST(SequenceBoundTest, MoveConstructionFromNull) {
  SEQUENCE_BOUND_T<BoxedValue> value1;
  // Should not crash.
  SEQUENCE_BOUND_T<BoxedValue> value2(std::move(value1));
}

TYPED_TEST(SequenceBoundTest, MoveAssignmentFromNull) {
  SEQUENCE_BOUND_T<BoxedValue> value1;
  SEQUENCE_BOUND_T<BoxedValue> value2;
  // Should not crash.
  value2 = std::move(value1);
}

TYPED_TEST(SequenceBoundTest, MoveAssignmentFromSelf) {
  SEQUENCE_BOUND_T<BoxedValue> value;
  // Cheat to avoid clang self-move warning.
  auto& value2 = value;
  // Should not crash.
  value2 = std::move(value);
}

TYPED_TEST(SequenceBoundTest, ResetNullSequenceBound) {
  SEQUENCE_BOUND_T<BoxedValue> value;
  // Should not crash.
  value.Reset();
}

TYPED_TEST(SequenceBoundTest, ConstructWithLvalue) {
  int lvalue = 99;
  SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_, lvalue,
                                     &this->logger_);
  value.FlushPostedTasksForTesting();
  EXPECT_THAT(this->logger_.TakeEvents(),
              ::testing::ElementsAre("constructed BoxedValue = 99"));
}

TYPED_TEST(SequenceBoundTest, PostTaskWithThisObject) {
  constexpr int kTestValue1 = 42;
  constexpr int kTestValue2 = 42;
  SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_,
                                     kTestValue1);
  value.PostTaskWithThisObject(BindLambdaForTesting(
      [&](const BoxedValue& v) { EXPECT_EQ(kTestValue1, v.value()); }));
  value.PostTaskWithThisObject(
      BindLambdaForTesting([&](BoxedValue* v) { v->set_value(kTestValue2); }));
  value.PostTaskWithThisObject(BindLambdaForTesting(
      [&](const BoxedValue& v) { EXPECT_EQ(kTestValue2, v.value()); }));
  value.FlushPostedTasksForTesting();
}

TYPED_TEST(SequenceBoundTest, SynchronouslyResetForTest) {
  SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_, 0);

  bool destroyed = false;
  value.AsyncCall(&BoxedValue::set_destruction_callback)
      .WithArgs(BindLambdaForTesting([&] { destroyed = true; }));

  value.SynchronouslyResetForTest();
  EXPECT_TRUE(destroyed);
}

TYPED_TEST(SequenceBoundTest, FlushPostedTasksForTesting) {
  SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_, 0,
                                     &this->logger_);

  value.AsyncCall(&BoxedValue::set_value).WithArgs(42);
  value.FlushPostedTasksForTesting();

  EXPECT_THAT(this->logger_.TakeEvents(),
              ::testing::ElementsAre("constructed BoxedValue = 0",
                                     "updated BoxedValue from 0 to 42"));
}

TYPED_TEST(SequenceBoundTest, SmallObject) {
  class EmptyClass {};
  SEQUENCE_BOUND_T<EmptyClass> value(this->background_task_runner_);
  // Test passes if SequenceBound constructor does not crash in AlignedAlloc().
}

TYPED_TEST(SequenceBoundTest, SelfMoveAssign) {
  class EmptyClass {};
  SEQUENCE_BOUND_T<EmptyClass> value(this->background_task_runner_);
  EXPECT_FALSE(value.is_null());
  // Clang has a warning for self-move, so be clever.
  auto& actually_the_same_value = value;
  value = std::move(actually_the_same_value);
  // Note: in general, moved-from objects are in a valid but undefined state.
  // This is merely a test that self-move doesn't result in something bad
  // happening; this is not an assertion that self-move will always have this
  // behavior.
  EXPECT_TRUE(value.is_null());
}

TYPED_TEST(SequenceBoundTest, Emplace) {
  SEQUENCE_BOUND_T<BoxedValue> value;
  EXPECT_TRUE(value.is_null());
  value.WrappedEmplace(this->background_task_runner_, 8);
  value.AsyncCall(&BoxedValue::value)
      .Then(BindLambdaForTesting(
          [&](int actual_value) { EXPECT_EQ(8, actual_value); }));
  value.FlushPostedTasksForTesting();
}

TYPED_TEST(SequenceBoundTest, EmplaceOverExisting) {
  SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_, 8,
                                     &this->logger_);
  EXPECT_FALSE(value.is_null());
  value.WrappedEmplace(this->background_task_runner_, 9, &this->logger_);
  value.AsyncCall(&BoxedValue::value)
      .Then(BindLambdaForTesting(
          [&](int actual_value) { EXPECT_EQ(9, actual_value); }));
  value.FlushPostedTasksForTesting();

  if constexpr (TypeParam::kManagingTaskRunnerConstructsT) {
    // Both the replaced `BoxedValue` and the current `BoxedValue` should
    // live on the same sequence: make sure the replaced `BoxedValue` was
    // destroyed before the current `BoxedValue` was constructed.
    EXPECT_THAT(this->logger_.TakeEvents(),
                ::testing::ElementsAre(
                    "constructed BoxedValue = 8", "destroyed BoxedValue = 8",
                    "constructed BoxedValue = 9", "accessed BoxedValue = 9"));
  } else {
    // When `SequenceBound` manages a `std::unique_ptr<T>`, `T` is constructed
    // on the current sequence so construction of the new managed instance will
    // happen before the previously-managed instance is destroyed on the
    // managing task runner.
    EXPECT_THAT(this->logger_.TakeEvents(),
                ::testing::ElementsAre(
                    "constructed BoxedValue = 8", "constructed BoxedValue = 9",
                    "destroyed BoxedValue = 8", "accessed BoxedValue = 9"));
  }
}

TYPED_TEST(SequenceBoundTest, EmplaceOverExistingWithTaskRunnerSwap) {
  scoped_refptr<SequencedTaskRunner> another_task_runner =
      ThreadPool::CreateSequencedTaskRunner({});
  // No `EventLogger` here since destruction of the old `BoxedValue` and
  // construction of the new `BoxedValue` take place on different sequences and
  // can arbitrarily race.
  SEQUENCE_BOUND_T<BoxedValue> value(another_task_runner, 8);
  EXPECT_FALSE(value.is_null());
  value.WrappedEmplace(this->background_task_runner_, 9);
  {
    value.PostTaskWithThisObject(BindLambdaForTesting(
        [another_task_runner,
         background_task_runner =
             this->background_task_runner_](const BoxedValue& boxed_value) {
          EXPECT_FALSE(another_task_runner->RunsTasksInCurrentSequence());
          EXPECT_TRUE(background_task_runner->RunsTasksInCurrentSequence());
          EXPECT_EQ(9, boxed_value.value());
        }));
    value.FlushPostedTasksForTesting();
  }
}

namespace {

class NoArgsVoidReturn {
 public:
  void Method() {
    if (loop_) {
      loop_->Quit();
      loop_ = nullptr;
    }
  }
  void ConstMethod() const {
    if (loop_) {
      loop_->Quit();
      loop_ = nullptr;
    }
  }

  void set_loop(RunLoop* loop) { loop_ = loop; }

 private:
  mutable raw_ptr<RunLoop> loop_ = nullptr;
};

class NoArgsIntReturn {
 public:
  int Method() { return 123; }
  int ConstMethod() const { return 456; }
};

class IntArgVoidReturn {
 public:
  IntArgVoidReturn(int* method_called_with, int* const_method_called_with)
      : method_called_with_(method_called_with),
        const_method_called_with_(const_method_called_with) {}

  void Method(int x) {
    *method_called_with_ = x;
    method_called_with_ = nullptr;
    if (loop_) {
      loop_->Quit();
      loop_ = nullptr;
    }
  }
  void ConstMethod(int x) const {
    *const_method_called_with_ = x;
    const_method_called_with_ = nullptr;
    if (loop_) {
      loop_->Quit();
      loop_ = nullptr;
    }
  }

  void set_loop(RunLoop* loop) { loop_ = loop; }

 private:
  raw_ptr<int> method_called_with_;
  mutable raw_ptr<int> const_method_called_with_;
  mutable raw_ptr<RunLoop> loop_ = nullptr;
};

class IntArgIntReturn {
 public:
  int Method(int x) { return -x; }
  int ConstMethod(int x) const { return -x; }
};

}  // namespace

TYPED_TEST(SequenceBoundTest, AsyncCallNoArgsNoThen) {
  SEQUENCE_BOUND_T<NoArgsVoidReturn> s(this->background_task_runner_);

  {
    RunLoop loop;
    s.AsyncCall(&NoArgsVoidReturn::set_loop).WithArgs(&loop);
    s.AsyncCall(&NoArgsVoidReturn::Method);
    loop.Run();
  }

  {
    RunLoop loop;
    s.AsyncCall(&NoArgsVoidReturn::set_loop).WithArgs(&loop);
    s.AsyncCall(&NoArgsVoidReturn::ConstMethod);
    loop.Run();
  }
}

TYPED_TEST(SequenceBoundTest, AsyncCallIntArgNoThen) {
  int method_called_with = 0;
  int const_method_called_with = 0;
  SEQUENCE_BOUND_T<IntArgVoidReturn> s(this->background_task_runner_,
                                       &method_called_with,
                                       &const_method_called_with);

  {
    RunLoop loop;
    s.AsyncCall(&IntArgVoidReturn::set_loop).WithArgs(&loop);
    s.AsyncCall(&IntArgVoidReturn::Method).WithArgs(123);
    loop.Run();
    EXPECT_EQ(123, method_called_with);
  }

  {
    RunLoop loop;
    s.AsyncCall(&IntArgVoidReturn::set_loop).WithArgs(&loop);
    s.AsyncCall(&IntArgVoidReturn::ConstMethod).WithArgs(456);
    loop.Run();
    EXPECT_EQ(456, const_method_called_with);
  }
}

TYPED_TEST(SequenceBoundTest, AsyncCallNoArgsVoidThen) {
  SEQUENCE_BOUND_T<NoArgsVoidReturn> s(this->background_task_runner_);

  {
    RunLoop loop;
    s.AsyncCall(&NoArgsVoidReturn::Method).Then(BindLambdaForTesting([&]() {
      loop.Quit();
    }));
    loop.Run();
  }

  {
    RunLoop loop;
    s.AsyncCall(&NoArgsVoidReturn::ConstMethod)
        .Then(BindLambdaForTesting([&]() { loop.Quit(); }));
    loop.Run();
  }
}

TYPED_TEST(SequenceBoundTest, AsyncCallNoArgsIntThen) {
  SEQUENCE_BOUND_T<NoArgsIntReturn> s(this->background_task_runner_);

  {
    RunLoop loop;
    s.AsyncCall(&NoArgsIntReturn::Method)
        .Then(BindLambdaForTesting([&](int result) {
          EXPECT_EQ(123, result);
          loop.Quit();
        }));
    loop.Run();
  }

  {
    RunLoop loop;
    s.AsyncCall(&NoArgsIntReturn::ConstMethod)
        .Then(BindLambdaForTesting([&](int result) {
          EXPECT_EQ(456, result);
          loop.Quit();
        }));
    loop.Run();
  }
}

TYPED_TEST(SequenceBoundTest, AsyncCallWithArgsVoidThen) {
  int method_called_with = 0;
  int const_method_called_with = 0;
  SEQUENCE_BOUND_T<IntArgVoidReturn> s(this->background_task_runner_,
                                       &method_called_with,
                                       &const_method_called_with);

  {
    RunLoop loop;
    s.AsyncCall(&IntArgVoidReturn::Method)
        .WithArgs(123)
        .Then(BindLambdaForTesting([&] { loop.Quit(); }));
    loop.Run();
    EXPECT_EQ(123, method_called_with);
  }

  {
    RunLoop loop;
    s.AsyncCall(&IntArgVoidReturn::ConstMethod)
        .WithArgs(456)
        .Then(BindLambdaForTesting([&] { loop.Quit(); }));
    loop.Run();
    EXPECT_EQ(456, const_method_called_with);
  }
}

TYPED_TEST(SequenceBoundTest, AsyncCallWithArgsIntThen) {
  SEQUENCE_BOUND_T<IntArgIntReturn> s(this->background_task_runner_);

  {
    RunLoop loop;
    s.AsyncCall(&IntArgIntReturn::Method)
        .WithArgs(123)
        .Then(BindLambdaForTesting([&](int result) {
          EXPECT_EQ(-123, result);
          loop.Quit();
        }));
    loop.Run();
  }

  {
    RunLoop loop;
    s.AsyncCall(&IntArgIntReturn::ConstMethod)
        .WithArgs(456)
        .Then(BindLambdaForTesting([&](int result) {
          EXPECT_EQ(-456, result);
          loop.Quit();
        }));
    loop.Run();
  }
}

TYPED_TEST(SequenceBoundTest, AsyncCallIsConstQualified) {
  // Tests that both const and non-const methods may be called through a
  // const-qualified SequenceBound.
  const SEQUENCE_BOUND_T<NoArgsVoidReturn> s(this->background_task_runner_);
  s.AsyncCall(&NoArgsVoidReturn::ConstMethod);
  s.AsyncCall(&NoArgsVoidReturn::Method);
}

class IgnoreResultTestHelperWithNoArgs {
 public:
  explicit IgnoreResultTestHelperWithNoArgs(RunLoop* loop, bool* called)
      : loop_(loop), called_(called) {}

  int ConstMethod() const {
    if (loop_) {
      loop_->Quit();
      loop_ = nullptr;
    }
    if (called_) {
      *called_ = true;
      called_ = nullptr;
    }
    return 0;
  }

  int Method() {
    if (loop_) {
      loop_->Quit();
      loop_ = nullptr;
    }
    if (called_) {
      *called_ = true;
      called_ = nullptr;
    }
    return 0;
  }

 private:
  mutable raw_ptr<RunLoop> loop_ = nullptr;
  mutable raw_ptr<bool> called_ = nullptr;
};

TYPED_TEST(SequenceBoundTest, AsyncCallIgnoreResultNoArgs) {
  {
    RunLoop loop;
    SEQUENCE_BOUND_T<IgnoreResultTestHelperWithNoArgs> s(
        this->background_task_runner_, &loop, nullptr);
    s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::ConstMethod));
    loop.Run();
  }

  {
    RunLoop loop;
    SEQUENCE_BOUND_T<IgnoreResultTestHelperWithNoArgs> s(
        this->background_task_runner_, &loop, nullptr);
    s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::Method));
    loop.Run();
  }
}

TYPED_TEST(SequenceBoundTest, AsyncCallIgnoreResultThen) {
  {
    RunLoop loop;
    bool called = false;
    SEQUENCE_BOUND_T<IgnoreResultTestHelperWithNoArgs> s(
        this->background_task_runner_, nullptr, &called);
    s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::ConstMethod))
        .Then(BindLambdaForTesting([&] { loop.Quit(); }));
    loop.Run();
    EXPECT_TRUE(called);
  }

  {
    RunLoop loop;
    bool called = false;
    SEQUENCE_BOUND_T<IgnoreResultTestHelperWithNoArgs> s(
        this->background_task_runner_, nullptr, &called);
    s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::Method))
        .Then(BindLambdaForTesting([&] { loop.Quit(); }));
    loop.Run();
    EXPECT_TRUE(called);
  }
}

class IgnoreResultTestHelperWithArgs {
 public:
  IgnoreResultTestHelperWithArgs(RunLoop* loop, int& value)
      : loop_(loop), value_(&value) {}

  int ConstMethod(int arg) const {
    if (value_) {
      *value_ = arg;
      value_ = nullptr;
    }
    if (loop_) {
      loop_->Quit();
      loop_ = nullptr;
    }
    return arg;
  }

  int Method(int arg) {
    if (value_) {
      *value_ = arg;
      value_ = nullptr;
    }
    if (loop_) {
      loop_->Quit();
      loop_ = nullptr;
    }
    return arg;
  }

 private:
  mutable raw_ptr<RunLoop> loop_ = nullptr;
  mutable raw_ptr<int> value_;
};

TYPED_TEST(SequenceBoundTest, AsyncCallIgnoreResultWithArgs) {
  {
    RunLoop loop;
    int result = 0;
    SEQUENCE_BOUND_T<IgnoreResultTestHelperWithArgs> s(
        this->background_task_runner_, &loop, std::ref(result));
    s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::ConstMethod))
        .WithArgs(60);
    loop.Run();
    EXPECT_EQ(60, result);
  }

  {
    RunLoop loop;
    int result = 0;
    SEQUENCE_BOUND_T<IgnoreResultTestHelperWithArgs> s(
        this->background_task_runner_, &loop, std::ref(result));
    s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::Method))
        .WithArgs(06);
    loop.Run();
    EXPECT_EQ(06, result);
  }
}

TYPED_TEST(SequenceBoundTest, AsyncCallIgnoreResultWithArgsThen) {
  {
    RunLoop loop;
    int result = 0;
    SEQUENCE_BOUND_T<IgnoreResultTestHelperWithArgs> s(
        this->background_task_runner_, nullptr, std::ref(result));
    s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::ConstMethod))
        .WithArgs(60)
        .Then(BindLambdaForTesting([&] { loop.Quit(); }));
    loop.Run();
    EXPECT_EQ(60, result);
  }

  {
    RunLoop loop;
    int result = 0;
    SEQUENCE_BOUND_T<IgnoreResultTestHelperWithArgs> s(
        this->background_task_runner_, nullptr, std::ref(result));
    s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::Method))
        .WithArgs(06)
        .Then(BindLambdaForTesting([&] { loop.Quit(); }));
    loop.Run();
    EXPECT_EQ(06, result);
  }
}

// TODO(https://crbug.com/1382549): Maybe use the nocompile harness here instead
// of being "clever"...
TYPED_TEST(SequenceBoundTest, NoCompileTests) {
  // TODO(https://crbug.com/1382549): Test calling WithArgs() on a method that
  // takes no arguments.
  //
  // Given:
  //   class C {
  //     void F();
  //   };
  //
  // Then:
  //   SequenceBound<C> s(...);
  //   s.AsyncCall(&C::F).WithArgs(...);
  //
  // should not compile.
  //
  // TODO(https://crbug.com/1382549): Test calling Then() before calling
  // WithArgs().
  //
  // Given:
  //   class C {
  //     void F(int);
  //   };
  //
  // Then:
  //   SequenceBound<C> s(...);
  //   s.AsyncCall(&C::F).Then(...).WithArgs(...);
  //
  // should not compile.
  //
  // TODO(https://crbug.com/1382549): Add no-compile tests for converting
  // between SequenceBound<T> and SequenceBound<std::unique_ptr<T>>.
}
#undef SequenceBound

class SequenceBoundDeathTest : public ::testing::Test {
 protected:
  void TearDown() override {
    // Make sure that any objects owned by `SequenceBound` have been destroyed
    // to avoid tripping leak detection.
    RunLoop run_loop;
    task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
    run_loop.Run();
  }

  // Death tests use fork(), which can interact (very) poorly with threads.
  test::SingleThreadTaskEnvironment task_environment_;
  scoped_refptr<SequencedTaskRunner> task_runner_ =
      SequencedTaskRunner::GetCurrentDefault();
};

TEST_F(SequenceBoundDeathTest, AsyncCallIntArgNoWithArgsShouldCheck) {
  SequenceBound<IntArgIntReturn> s(task_runner_);
  EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&IntArgIntReturn::Method), "");
}

TEST_F(SequenceBoundDeathTest, AsyncCallIntReturnNoThenShouldCheck) {
  {
    SequenceBound<NoArgsIntReturn> s(task_runner_);
    EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&NoArgsIntReturn::Method), "");
  }

  {
    SequenceBound<IntArgIntReturn> s(task_runner_);
    EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&IntArgIntReturn::Method).WithArgs(0),
                              "");
  }
}

}  // namespace

}  // namespace base
