1153 lines
37 KiB
C++
1153 lines
37 KiB
C++
|
|
// 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 <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(StringPiece event) {
|
||
|
|
AutoLock guard(lock_);
|
||
|
|
events_.push_back(std::string(event));
|
||
|
|
}
|
||
|
|
std::vector<std::string> TakeEvents() {
|
||
|
|
AutoLock guard(lock_);
|
||
|
|
return std::exchange(events_, {});
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
Lock lock_;
|
||
|
|
std::vector<std::string> events_ GUARDED_BY(lock_);
|
||
|
|
};
|
||
|
|
|
||
|
|
// 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(StringPiece event) const {
|
||
|
|
if (logger_) {
|
||
|
|
logger_->AddEvent(event);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
SequenceChecker sequence_checker_;
|
||
|
|
|
||
|
|
mutable raw_ptr<EventLogger> logger_ = nullptr;
|
||
|
|
|
||
|
|
int value_ = 0;
|
||
|
|
OnceClosure destruction_callback_;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Smoke test that all interactions with the wrapped object are posted to the
|
||
|
|
// correct task runner.
|
||
|
|
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(https://crbug.com/1382549): Maybe use the nocompile harness here instead
|
||
|
|
// of being "clever"...
|
||
|
|
TYPED_TEST(SequenceBoundTest, NoCompileTests) {
|
||
|
|
// TODO(https://crbug.com/1382549): 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(https://crbug.com/1382549): 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(https://crbug.com/1382549): 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
|