| // 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); |
| } |
| } |
| |
| // TODO(crbug.com/40245687): Maybe use the nocompile harness here instead |
| // of being "clever"... |
| TYPED_TEST(SequenceBoundTest, NoCompileTests) { |
| // TODO(crbug.com/40245687): 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(crbug.com/40245687): Test calling Then() before calling |
| // WithArgs(). |
| // |
| // Given: |
| // class C { |
| // void F(int); |
| // }; |
| // |
| // Then: |
| // SequenceBound<C> s(...); |
| // s.AsyncCall(&C::F).Then(...).WithArgs(...); |
| // |
| // should not compile. |
| // |
| // TODO(crbug.com/40245687): Add no-compile tests for converting |
| // between SequenceBound<T> and SequenceBound<std::unique_ptr<T>>. |
| } |
| #undef SequenceBound |
| |
| class SequenceBoundDeathTest : public ::testing::Test { |
| protected: |
| void TearDown() override { |
| // Make sure that any objects owned by `SequenceBound` have been destroyed |
| // to avoid tripping leak detection. |
| RunLoop run_loop; |
| task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| // Death tests use fork(), which can interact (very) poorly with threads. |
| test::SingleThreadTaskEnvironment task_environment_; |
| scoped_refptr<SequencedTaskRunner> task_runner_ = |
| SequencedTaskRunner::GetCurrentDefault(); |
| }; |
| |
| TEST_F(SequenceBoundDeathTest, AsyncCallIntArgNoWithArgsShouldCheck) { |
| SequenceBound<IntArgIntReturn> s(task_runner_); |
| EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&IntArgIntReturn::Method), ""); |
| } |
| |
| TEST_F(SequenceBoundDeathTest, AsyncCallIntReturnNoThenShouldCheck) { |
| { |
| SequenceBound<NoArgsIntReturn> s(task_runner_); |
| EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&NoArgsIntReturn::Method), ""); |
| } |
| |
| { |
| SequenceBound<IntArgIntReturn> s(task_runner_); |
| EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&IntArgIntReturn::Method).WithArgs(0), |
| ""); |
| } |
| } |
| |
| } // namespace |
| |
| } // namespace base |