blob: 7ad85931e59d92f56e72925095f41ea1ed07c86e [file] [log] [blame]
// 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