// 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 #include #include #include #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 TakeEvents() { AutoLock guard(lock_); return std::exchange(events_, {}); } private: Lock lock_; std::vector events_ GUARDED_BY(lock_); }; // Helpers for writing type tests against both `SequenceBound` and // `SequenceBound`. 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 class Wrapper : public SequenceBound { public: template explicit Wrapper(scoped_refptr task_runner, Args&&... args) : SequenceBound(std::move(task_runner), std::forward(args)...) {} template void WrappedEmplace(scoped_refptr task_runner, Args&&... args) { this->emplace(std::move(task_runner), std::forward(args)...); } using SequenceBound::SequenceBound; using SequenceBound::operator=; private: using SequenceBound::emplace; }; }; struct UniquePtrVariation { static constexpr bool kManagingTaskRunnerConstructsT = false; template struct Wrapper : public SequenceBound> { public: template explicit Wrapper(scoped_refptr task_runner, Args&&... args) : SequenceBound>( std::move(task_runner), std::make_unique(std::forward(args)...)) {} template void WrappedEmplace(scoped_refptr task_runner, Args&&... args) { this->emplace(std::move(task_runner), std::make_unique(std::forward(args)...)); } using SequenceBound>::SequenceBound; using SequenceBound>::operator=; private: using SequenceBound>::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 directly instead // of SEQUENCE_BOUND_T, as that bypasses the point of having a typed test. #define SequenceBound PleaseUseSequenceBoundT template 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 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; 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 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 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 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 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 task_runner_; }; TYPED_TEST(SequenceBoundTest, SequenceValidation) { SEQUENCE_BOUND_T 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 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 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_old(this->background_task_runner_, std::ref(this->logger_)); SEQUENCE_BOUND_T 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(this->background_task_runner_, std::ref(this->logger_)); SEQUENCE_BOUND_T 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`; 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 multiply_derived( this->background_task_runner_, std::ref(this->logger_)); SEQUENCE_BOUND_T 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`; 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 multiply_derived( this->background_task_runner_, std::ref(this->logger_)); SEQUENCE_BOUND_T 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`; 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_old(this->background_task_runner_, std::ref(this->logger_)); SEQUENCE_BOUND_T 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(); 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(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`; 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 multiply_derived( this->background_task_runner_, std::ref(this->logger_)); SEQUENCE_BOUND_T 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`; 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 multiply_derived( this->background_task_runner_, std::ref(this->logger_)); SEQUENCE_BOUND_T 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`; 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 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 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 value1; // Should not crash. SEQUENCE_BOUND_T value2(std::move(value1)); } TYPED_TEST(SequenceBoundTest, MoveAssignmentFromNull) { SEQUENCE_BOUND_T value1; SEQUENCE_BOUND_T value2; // Should not crash. value2 = std::move(value1); } TYPED_TEST(SequenceBoundTest, MoveAssignmentFromSelf) { SEQUENCE_BOUND_T 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 value; // Should not crash. value.Reset(); } TYPED_TEST(SequenceBoundTest, ConstructWithLvalue) { int lvalue = 99; SEQUENCE_BOUND_T 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 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 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 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 value(this->background_task_runner_); // Test passes if SequenceBound constructor does not crash in AlignedAlloc(). } TYPED_TEST(SequenceBoundTest, SelfMoveAssign) { class EmptyClass {}; SEQUENCE_BOUND_T 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 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 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` 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 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 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 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 method_called_with_; mutable raw_ptr const_method_called_with_; mutable raw_ptr 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 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 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 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 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 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 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 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 loop_ = nullptr; mutable raw_ptr called_ = nullptr; }; TYPED_TEST(SequenceBoundTest, AsyncCallIgnoreResultNoArgs) { { RunLoop loop; SEQUENCE_BOUND_T s( this->background_task_runner_, &loop, nullptr); s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::ConstMethod)); loop.Run(); } { RunLoop loop; SEQUENCE_BOUND_T 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 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 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 loop_ = nullptr; mutable raw_ptr value_; }; TYPED_TEST(SequenceBoundTest, AsyncCallIgnoreResultWithArgs) { { RunLoop loop; int result = 0; SEQUENCE_BOUND_T 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 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 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 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 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 s(...); // s.AsyncCall(&C::F).Then(...).WithArgs(...); // // should not compile. // // TODO(https://crbug.com/1382549): Add no-compile tests for converting // between SequenceBound and SequenceBound>. } #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 task_runner_ = SequencedTaskRunner::GetCurrentDefault(); }; TEST_F(SequenceBoundDeathTest, AsyncCallIntArgNoWithArgsShouldCheck) { SequenceBound s(task_runner_); EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&IntArgIntReturn::Method), ""); } TEST_F(SequenceBoundDeathTest, AsyncCallIntReturnNoThenShouldCheck) { { SequenceBound s(task_runner_); EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&NoArgsIntReturn::Method), ""); } { SequenceBound s(task_runner_); EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&IntArgIntReturn::Method).WithArgs(0), ""); } } } // namespace } // namespace base