| // 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_.emplace_back(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); | |
| } | |
| } | |
| #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 |