// 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 "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/thread_pool.h" #include "base/test/bind.h" #include "base/test/task_environment.h" #include "base/threading/sequenced_task_runner_handle.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(StringPiece 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_); }; 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_; // Otherwise, default to using `background_task_runner_` for new tests. 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_; }; 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) { 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(StringPiece 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. TEST_F(SequenceBoundTest, SequenceValidation) { class Validator { public: explicit Validator(scoped_refptr task_runner) : task_runner_(std::move(task_runner)) { EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence()); } ~Validator() { 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_; }; SequenceBound validator(background_task_runner_, background_task_runner_); validator.AsyncCall(&Validator::ReturnsVoid); validator.AsyncCall(&Validator::ReturnsVoidMutable); validator.AsyncCall(&Validator::ReturnsInt).Then(BindOnce([](int) {})); validator.AsyncCall(&Validator::ReturnsIntMutable).Then(BindOnce([](int) {})); validator.AsyncCall(IgnoreResult(&Validator::ReturnsInt)); validator.AsyncCall(IgnoreResult(&Validator::ReturnsIntMutable)); validator.emplace(background_task_runner_, background_task_runner_); validator.PostTaskWithThisObject( BindLambdaForTesting([](const Validator& v) { v.ReturnsVoid(); })); validator.PostTaskWithThisObject( BindLambdaForTesting([](Validator* v) { v->ReturnsVoidMutable(); })); validator.Reset(); FlushPostedTasks(); } TEST_F(SequenceBoundTest, Basic) { SequenceBound value(background_task_runner_, 0, &logger_); // Construction of `BoxedValue` is 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(logger_.TakeEvents(), ::testing::ElementsAre("constructed BoxedValue = 0")); value.AsyncCall(&BoxedValue::set_value).WithArgs(66); value.FlushPostedTasksForTesting(); EXPECT_THAT(logger_.TakeEvents(), ::testing::ElementsAre("updated BoxedValue from 0 to 66")); // Destruction of `BoxedValue` is 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); FlushPostedTasks(); EXPECT_THAT(logger_.TakeEvents(), ::testing::ElementsAre("destroyed BoxedValue = 66")); } TEST_F(SequenceBoundTest, ConstructAndImmediateAsyncCall) { // Calling `AsyncCall` immediately after construction should always work. SequenceBound value(background_task_runner_, 0, &logger_); value.AsyncCall(&BoxedValue::set_value).WithArgs(8); value.FlushPostedTasksForTesting(); EXPECT_THAT(logger_.TakeEvents(), ::testing::ElementsAre("constructed BoxedValue = 0", "updated BoxedValue from 0 to 8")); } TEST_F(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. SequenceBound derived_old(background_task_runner_, std::ref(logger_)); SequenceBound derived_new = std::move(derived_old); EXPECT_TRUE(derived_old.is_null()); EXPECT_FALSE(derived_new.is_null()); derived_new.Reset(); FlushPostedTasks(); EXPECT_THAT(logger_.TakeEvents(), ::testing::ElementsAre("constructed Base", "constructed Derived", "destroyed Derived", "destroyed Base")); } TEST_F(SequenceBoundTest, MoveConstructionUpcastsToBase) { SequenceBound derived(background_task_runner_, std::ref(logger_)); SequenceBound base = std::move(derived); 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(); FlushPostedTasks(); EXPECT_THAT(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. TEST_F(SequenceBoundTest, MoveConstructionUpcastsToLeftmost) { SequenceBound multiply_derived(background_task_runner_, std::ref(logger_)); SequenceBound leftmost_base = std::move(multiply_derived); 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(); FlushPostedTasks(); EXPECT_THAT( logger_.TakeEvents(), ::testing::ElementsAre( "constructed Leftmost", "constructed Base", "constructed Rightmost", "constructed MultiplyDerived", "destroyed MultiplyDerived", "destroyed Rightmost", "destroyed Base", "destroyed Leftmost")); } TEST_F(SequenceBoundTest, MoveConstructionUpcastsToRightmost) { SequenceBound multiply_derived(background_task_runner_, std::ref(logger_)); SequenceBound rightmost_base = std::move(multiply_derived); 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(); FlushPostedTasks(); EXPECT_THAT( logger_.TakeEvents(), ::testing::ElementsAre( "constructed Leftmost", "constructed Base", "constructed Rightmost", "constructed MultiplyDerived", "destroyed MultiplyDerived", "destroyed Rightmost", "destroyed Base", "destroyed Leftmost")); } TEST_F(SequenceBoundTest, MoveAssignment) { SequenceBound derived_old(background_task_runner_, std::ref(logger_)); SequenceBound derived_new; derived_new = std::move(derived_old); 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 = SequenceBound(); FlushPostedTasks(); EXPECT_THAT(logger_.TakeEvents(), ::testing::ElementsAre("constructed Base", "constructed Derived", "destroyed Derived", "destroyed Base")); } TEST_F(SequenceBoundTest, MoveAssignmentUpcastsToBase) { SequenceBound derived(background_task_runner_, std::ref(logger_)); SequenceBound base; base = std::move(derived); 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(); FlushPostedTasks(); EXPECT_THAT(logger_.TakeEvents(), ::testing::ElementsAre("constructed Base", "constructed Derived", "destroyed Derived", "destroyed Base")); } TEST_F(SequenceBoundTest, MoveAssignmentUpcastsToLeftmost) { SequenceBound multiply_derived(background_task_runner_, std::ref(logger_)); SequenceBound leftmost_base; leftmost_base = std::move(multiply_derived); 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(); FlushPostedTasks(); EXPECT_THAT( logger_.TakeEvents(), ::testing::ElementsAre( "constructed Leftmost", "constructed Base", "constructed Rightmost", "constructed MultiplyDerived", "destroyed MultiplyDerived", "destroyed Rightmost", "destroyed Base", "destroyed Leftmost")); } TEST_F(SequenceBoundTest, MoveAssignmentUpcastsToRightmost) { SequenceBound multiply_derived(background_task_runner_, std::ref(logger_)); SequenceBound rightmost_base; rightmost_base = std::move(multiply_derived); 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(); FlushPostedTasks(); EXPECT_THAT( logger_.TakeEvents(), ::testing::ElementsAre( "constructed Leftmost", "constructed Base", "constructed Rightmost", "constructed MultiplyDerived", "destroyed MultiplyDerived", "destroyed Rightmost", "destroyed Base", "destroyed Leftmost")); } TEST_F(SequenceBoundTest, AsyncCallLeftmost) { SequenceBound multiply_derived(background_task_runner_, std::ref(logger_)); multiply_derived.AsyncCall(&Leftmost::SetValue).WithArgs(3); multiply_derived.FlushPostedTasksForTesting(); EXPECT_THAT(logger_.TakeEvents(), ::testing::ElementsAre("constructed Leftmost", "constructed Base", "constructed Rightmost", "constructed MultiplyDerived", "set Leftmost to 3")); } TEST_F(SequenceBoundTest, AsyncCallRightmost) { SequenceBound multiply_derived(background_task_runner_, std::ref(logger_)); multiply_derived.AsyncCall(&Rightmost::SetValue).WithArgs(3); multiply_derived.FlushPostedTasksForTesting(); EXPECT_THAT(logger_.TakeEvents(), ::testing::ElementsAre("constructed Leftmost", "constructed Base", "constructed Rightmost", "constructed MultiplyDerived", "set Rightmost to 3")); } TEST_F(SequenceBoundTest, MoveConstructionFromNull) { SequenceBound value1; // Should not crash. SequenceBound value2(std::move(value1)); } TEST_F(SequenceBoundTest, MoveAssignmentFromNull) { SequenceBound value1; SequenceBound value2; // Should not crash. value2 = std::move(value1); } TEST_F(SequenceBoundTest, MoveAssignmentFromSelf) { SequenceBound value; // Cheat to avoid clang self-move warning. auto& value2 = value; // Should not crash. value2 = std::move(value); } TEST_F(SequenceBoundTest, ResetNullSequenceBound) { SequenceBound value; // Should not crash. value.Reset(); } TEST_F(SequenceBoundTest, ConstructWithLvalue) { int lvalue = 99; SequenceBound value(background_task_runner_, lvalue, &logger_); value.FlushPostedTasksForTesting(); EXPECT_THAT(logger_.TakeEvents(), ::testing::ElementsAre("constructed BoxedValue = 99")); } TEST_F(SequenceBoundTest, PostTaskWithThisObject) { constexpr int kTestValue1 = 42; constexpr int kTestValue2 = 42; SequenceBound value(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(); } TEST_F(SequenceBoundTest, SynchronouslyResetForTest) { SequenceBound value(background_task_runner_, 0); bool destroyed = false; value.AsyncCall(&BoxedValue::set_destruction_callback) .WithArgs(BindLambdaForTesting([&] { destroyed = true; })); value.SynchronouslyResetForTest(); EXPECT_TRUE(destroyed); } TEST_F(SequenceBoundTest, FlushPostedTasksForTesting) { SequenceBound value(background_task_runner_, 0, &logger_); value.AsyncCall(&BoxedValue::set_value).WithArgs(42); value.FlushPostedTasksForTesting(); EXPECT_THAT(logger_.TakeEvents(), ::testing::ElementsAre("constructed BoxedValue = 0", "updated BoxedValue from 0 to 42")); } TEST_F(SequenceBoundTest, SmallObject) { class EmptyClass {}; SequenceBound value(background_task_runner_); // Test passes if SequenceBound constructor does not crash in AlignedAlloc(). } TEST_F(SequenceBoundTest, SelfMoveAssign) { class EmptyClass {}; SequenceBound value(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()); } TEST_F(SequenceBoundTest, Emplace) { SequenceBound value; EXPECT_TRUE(value.is_null()); value.emplace(background_task_runner_, 8); value.AsyncCall(&BoxedValue::value) .Then(BindLambdaForTesting( [&](int actual_value) { EXPECT_EQ(8, actual_value); })); value.FlushPostedTasksForTesting(); } TEST_F(SequenceBoundTest, EmplaceOverExisting) { SequenceBound value(background_task_runner_, 8, &logger_); EXPECT_FALSE(value.is_null()); value.emplace(background_task_runner_, 9, &logger_); value.AsyncCall(&BoxedValue::value) .Then(BindLambdaForTesting( [&](int actual_value) { EXPECT_EQ(9, actual_value); })); value.FlushPostedTasksForTesting(); // 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(logger_.TakeEvents(), ::testing::ElementsAre( "constructed BoxedValue = 8", "destroyed BoxedValue = 8", "constructed BoxedValue = 9", "accessed BoxedValue = 9")); } TEST_F(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. SequenceBound value(another_task_runner, 8); EXPECT_FALSE(value.is_null()); value.emplace(background_task_runner_, 9); { value.PostTaskWithThisObject(BindLambdaForTesting( [another_task_runner, background_task_runner = 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(); } void ConstMethod() const { if (loop_) loop_->Quit(); } void set_loop(RunLoop* loop) { loop_ = loop; } private: 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; if (loop_) loop_->Quit(); } void ConstMethod(int x) const { *const_method_called_with_ = x; if (loop_) loop_->Quit(); } void set_loop(RunLoop* loop) { loop_ = loop; } private: const raw_ptr method_called_with_; const raw_ptr const_method_called_with_; raw_ptr loop_ = nullptr; }; class IntArgIntReturn { public: int Method(int x) { return -x; } int ConstMethod(int x) const { return -x; } }; } // namespace TEST_F(SequenceBoundTest, AsyncCallNoArgsNoThen) { SequenceBound s(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(); } } TEST_F(SequenceBoundTest, AsyncCallIntArgNoThen) { int method_called_with = 0; int const_method_called_with = 0; SequenceBound s( 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); } } TEST_F(SequenceBoundTest, AsyncCallNoArgsVoidThen) { SequenceBound s(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(); } } TEST_F(SequenceBoundTest, AsyncCallNoArgsIntThen) { SequenceBound s(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(); } } TEST_F(SequenceBoundTest, AsyncCallWithArgsVoidThen) { int method_called_with = 0; int const_method_called_with = 0; SequenceBound s( 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); } } TEST_F(SequenceBoundTest, AsyncCallWithArgsIntThen) { SequenceBound s(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(); } } TEST_F(SequenceBoundTest, AsyncCallIsConstQualified) { // Tests that both const and non-const methods may be called through a // const-qualified SequenceBound. const SequenceBound s(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(); } if (called_) { *called_ = true; } return 0; } int Method() { if (loop_) { loop_->Quit(); } if (called_) { *called_ = true; } return 0; } private: const raw_ptr loop_ = nullptr; const raw_ptr called_ = nullptr; }; TEST_F(SequenceBoundTest, AsyncCallIgnoreResultNoArgs) { { RunLoop loop; SequenceBound s(background_task_runner_, &loop, nullptr); s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::ConstMethod)); loop.Run(); } { RunLoop loop; SequenceBound s(background_task_runner_, &loop, nullptr); s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::Method)); loop.Run(); } } TEST_F(SequenceBoundTest, AsyncCallIgnoreResultThen) { { RunLoop loop; bool called = false; SequenceBound s(background_task_runner_, nullptr, &called); s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::ConstMethod)) .Then(BindLambdaForTesting([&] { loop.Quit(); })); loop.Run(); EXPECT_TRUE(called); } { RunLoop loop; bool called = false; SequenceBound s(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 { value_ = arg; if (loop_) { loop_->Quit(); } return arg; } int Method(int arg) { value_ = arg; if (loop_) { loop_->Quit(); } return arg; } private: const raw_ptr loop_ = nullptr; int& value_; }; TEST_F(SequenceBoundTest, AsyncCallIgnoreResultWithArgs) { { RunLoop loop; int result = 0; SequenceBound s(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; SequenceBound s(background_task_runner_, &loop, std::ref(result)); s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::Method)) .WithArgs(06); loop.Run(); EXPECT_EQ(06, result); } } TEST_F(SequenceBoundTest, AsyncCallIgnoreResultWithArgsThen) { { RunLoop loop; int result = 0; SequenceBound s(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; SequenceBound s(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(dcheng): Maybe use the nocompile harness here instead of being // "clever"... TEST_F(SequenceBoundTest, NoCompileTests) { // TODO(dcheng): 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(dcheng): Test calling Then() before calling WithArgs(). // Given: // class C { // void F(int); // }; // // Then: // SequenceBound s(...); // s.AsyncCall(&C::F).Then(...).WithArgs(...); // // should not compile. // } 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_ = SequencedTaskRunnerHandle::Get(); }; 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