| // 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 <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/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<std::string> TakeEvents() { |
| AutoLock guard(lock_); |
| return std::exchange(events_, {}); |
| } |
| |
| private: |
| Lock lock_; |
| std::vector<std::string> 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<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_; |
| }; |
| |
| 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) { |
| 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<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. |
| TEST_F(SequenceBoundTest, SequenceValidation) { |
| class Validator { |
| public: |
| explicit Validator(scoped_refptr<SequencedTaskRunner> 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<SequencedTaskRunner> task_runner_; |
| }; |
| |
| SequenceBound<Validator> 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<BoxedValue> 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<BoxedValue> 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> derived_old(background_task_runner_, |
| std::ref(logger_)); |
| SequenceBound<Derived> 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> 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<Base>`; 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<MultiplyDerived> multiply_derived(background_task_runner_, |
| std::ref(logger_)); |
| SequenceBound<Leftmost> 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<Leftmost>`; 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<MultiplyDerived> multiply_derived(background_task_runner_, |
| std::ref(logger_)); |
| SequenceBound<Rightmost> 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<Rightmost>`; 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> derived_old(background_task_runner_, |
| std::ref(logger_)); |
| SequenceBound<Derived> 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<Derived>(); |
| FlushPostedTasks(); |
| EXPECT_THAT(logger_.TakeEvents(), |
| ::testing::ElementsAre("constructed Base", "constructed Derived", |
| "destroyed Derived", "destroyed Base")); |
| } |
| |
| TEST_F(SequenceBoundTest, MoveAssignmentUpcastsToBase) { |
| SequenceBound<Derived> derived(background_task_runner_, std::ref(logger_)); |
| SequenceBound<Base> base; |
| |
| base = std::move(derived); |
| 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(); |
| FlushPostedTasks(); |
| EXPECT_THAT(logger_.TakeEvents(), |
| ::testing::ElementsAre("constructed Base", "constructed Derived", |
| "destroyed Derived", "destroyed Base")); |
| } |
| |
| TEST_F(SequenceBoundTest, MoveAssignmentUpcastsToLeftmost) { |
| SequenceBound<MultiplyDerived> multiply_derived(background_task_runner_, |
| std::ref(logger_)); |
| SequenceBound<Leftmost> 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<Leftmost>`; 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<MultiplyDerived> multiply_derived(background_task_runner_, |
| std::ref(logger_)); |
| SequenceBound<Rightmost> 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<Rightmost>`; 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<MultiplyDerived> 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<MultiplyDerived> 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<BoxedValue> value1; |
| // Should not crash. |
| SequenceBound<BoxedValue> value2(std::move(value1)); |
| } |
| |
| TEST_F(SequenceBoundTest, MoveAssignmentFromNull) { |
| SequenceBound<BoxedValue> value1; |
| SequenceBound<BoxedValue> value2; |
| // Should not crash. |
| value2 = std::move(value1); |
| } |
| |
| TEST_F(SequenceBoundTest, MoveAssignmentFromSelf) { |
| SequenceBound<BoxedValue> value; |
| // Cheat to avoid clang self-move warning. |
| auto& value2 = value; |
| // Should not crash. |
| value2 = std::move(value); |
| } |
| |
| TEST_F(SequenceBoundTest, ResetNullSequenceBound) { |
| SequenceBound<BoxedValue> value; |
| // Should not crash. |
| value.Reset(); |
| } |
| |
| TEST_F(SequenceBoundTest, ConstructWithLvalue) { |
| int lvalue = 99; |
| SequenceBound<BoxedValue> 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<BoxedValue> 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<BoxedValue> 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<BoxedValue> 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<EmptyClass> value(background_task_runner_); |
| // Test passes if SequenceBound constructor does not crash in AlignedAlloc(). |
| } |
| |
| TEST_F(SequenceBoundTest, SelfMoveAssign) { |
| class EmptyClass {}; |
| SequenceBound<EmptyClass> 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<BoxedValue> 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<BoxedValue> 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<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. |
| SequenceBound<BoxedValue> 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<int> method_called_with_; |
| const raw_ptr<int> const_method_called_with_; |
| |
| raw_ptr<RunLoop> loop_ = nullptr; |
| }; |
| |
| class IntArgIntReturn { |
| public: |
| int Method(int x) { return -x; } |
| int ConstMethod(int x) const { return -x; } |
| }; |
| |
| } // namespace |
| |
| TEST_F(SequenceBoundTest, AsyncCallNoArgsNoThen) { |
| SequenceBound<NoArgsVoidReturn> 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<IntArgVoidReturn> 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<NoArgsVoidReturn> 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<NoArgsIntReturn> 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<IntArgVoidReturn> 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<IntArgIntReturn> 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<NoArgsVoidReturn> 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<RunLoop> loop_ = nullptr; |
| const raw_ptr<bool> called_ = nullptr; |
| }; |
| |
| TEST_F(SequenceBoundTest, AsyncCallIgnoreResultNoArgs) { |
| { |
| RunLoop loop; |
| SequenceBound<IgnoreResultTestHelperWithNoArgs> s(background_task_runner_, |
| &loop, nullptr); |
| s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::ConstMethod)); |
| loop.Run(); |
| } |
| |
| { |
| RunLoop loop; |
| SequenceBound<IgnoreResultTestHelperWithNoArgs> s(background_task_runner_, |
| &loop, nullptr); |
| s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::Method)); |
| loop.Run(); |
| } |
| } |
| |
| TEST_F(SequenceBoundTest, AsyncCallIgnoreResultThen) { |
| { |
| RunLoop loop; |
| bool called = false; |
| SequenceBound<IgnoreResultTestHelperWithNoArgs> 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<IgnoreResultTestHelperWithNoArgs> 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<RunLoop> loop_ = nullptr; |
| int& value_; |
| }; |
| |
| TEST_F(SequenceBoundTest, AsyncCallIgnoreResultWithArgs) { |
| { |
| RunLoop loop; |
| int result = 0; |
| SequenceBound<IgnoreResultTestHelperWithArgs> 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<IgnoreResultTestHelperWithArgs> 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<IgnoreResultTestHelperWithArgs> 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<IgnoreResultTestHelperWithArgs> 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<C> 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<C> 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<SequencedTaskRunner> task_runner_ = |
| SequencedTaskRunnerHandle::Get(); |
| }; |
| |
| 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 |