blob: 790200804ad57c2ed3ec81628dd7ab6e038be093 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/sequence_bound.h"
#include <functional>
#include <utility>
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
class SequenceBoundTest : public ::testing::Test {
public:
// Helpful values that our test classes use.
enum Value {
kInitialValue = 0,
kDifferentValue = 1,
// Values used by the Derived class.
kDerivedCtorValue = 111,
kDerivedDtorValue = 222,
// Values used by the Other class.
kOtherCtorValue = 333,
kOtherDtorValue = 444,
};
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();
}
// Do-nothing base class, just so we can test assignment of derived classes.
// It introduces a virtual destructor, so that casting derived classes to
// Base should still use the appropriate (virtual) destructor.
class Base {
public:
virtual ~Base() {}
};
// Handy class to set an int ptr to different values, to verify that things
// are being run properly.
class Derived : public Base {
public:
Derived(Value* ptr) : ptr_(ptr) { *ptr_ = kDerivedCtorValue; }
~Derived() override { *ptr_ = kDerivedDtorValue; }
void SetValue(Value value) { *ptr_ = value; }
Value* ptr_;
};
// Another base class, which sets ints to different values.
class Other {
public:
Other(Value* ptr) : ptr_(ptr) { *ptr = kOtherCtorValue; }
virtual ~Other() { *ptr_ = kOtherDtorValue; }
void SetValue(Value value) { *ptr_ = value; }
Value* ptr_;
};
class MultiplyDerived : public Other, public Derived {
public:
MultiplyDerived(Value* ptr1, Value* ptr2) : Other(ptr1), Derived(ptr2) {}
};
// TODO(dcheng): This isn't used, but upcasting to a virtual base class is
// unsafe and is currently unchecked! Add these safety checks back in.
struct VirtuallyDerived : public virtual Base {};
base::test::TaskEnvironment task_environment_;
scoped_refptr<base::SequencedTaskRunner> task_runner_ =
base::SequencedTaskRunnerHandle::Get();
Value value_ = kInitialValue;
};
class BoxedValue {
public:
explicit BoxedValue(int initial_value) : value_(initial_value) {}
~BoxedValue() {
if (destruction_callback_)
std::move(destruction_callback_).Run();
}
void set_destruction_callback(base::OnceClosure callback) {
destruction_callback_ = std::move(callback);
}
int value() const { return value_; }
void set_value(int value) { value_ = value; }
private:
int value_ = 0;
base::OnceClosure destruction_callback_;
DISALLOW_COPY_AND_ASSIGN(BoxedValue);
};
#if defined(OS_IOS) && !TARGET_OS_SIMULATOR
#define MAYBE_ConstructAsyncCallReset FLAKY_ConstructAsyncCallReset
#else
#define MAYBE_ConstructAsyncCallReset ConstructAsyncCallReset
#endif
// https://crbug.com/899779 tracks test flakiness on iOS.
TEST_F(SequenceBoundTest, MAYBE_ConstructAsyncCallReset) {
auto derived = SequenceBound<Derived>(task_runner_, &value_);
EXPECT_FALSE(derived.is_null());
EXPECT_TRUE(derived);
// Nothing should happen until we run the message loop.
EXPECT_EQ(value_, kInitialValue);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDerivedCtorValue);
// Post now that the object has been constructed.
derived.AsyncCall(&Derived::SetValue).WithArgs(kDifferentValue);
EXPECT_EQ(value_, kDerivedCtorValue);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDifferentValue);
// Reset it, and make sure that destruction is posted. The owner should
// report that it is null immediately.
derived.Reset();
EXPECT_TRUE(derived.is_null());
EXPECT_FALSE(derived);
EXPECT_EQ(value_, kDifferentValue);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDerivedDtorValue);
}
TEST_F(SequenceBoundTest, PostBeforeConstruction) {
// Construct an object and post a message to it, before construction has been
// run on |task_runner_|.
auto derived = SequenceBound<Derived>(task_runner_, &value_);
derived.AsyncCall(&Derived::SetValue).WithArgs(kDifferentValue);
EXPECT_EQ(value_, kInitialValue);
// Both construction and SetValue should run.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDifferentValue);
}
TEST_F(SequenceBoundTest, MoveConstructionFromSameClass) {
// Verify that we can move-construct with the same class.
auto derived_old = SequenceBound<Derived>(task_runner_, &value_);
auto derived_new = std::move(derived_old);
EXPECT_TRUE(derived_old.is_null());
EXPECT_FALSE(derived_new.is_null());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDerivedCtorValue);
// Verify that |derived_new| owns the object now, and that the virtual
// destructor is called.
derived_new.Reset();
EXPECT_EQ(value_, kDerivedCtorValue);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDerivedDtorValue);
}
TEST_F(SequenceBoundTest, MoveConstructionFromDerivedClass) {
// Verify that we can move-construct to a base class from a derived class.
auto derived = SequenceBound<Derived>(task_runner_, &value_);
SequenceBound<Base> base(std::move(derived));
EXPECT_TRUE(derived.is_null());
EXPECT_FALSE(base.is_null());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDerivedCtorValue);
// Verify that |base| owns the object now, and that destruction still destroys
// Derived properly.
base.Reset();
EXPECT_EQ(value_, kDerivedCtorValue);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDerivedDtorValue);
}
TEST_F(SequenceBoundTest, MultiplyDerivedDestructionWorksLeftSuper) {
// Verify that everything works when we're casting around in ways that might
// change the address. We cast to the left side of MultiplyDerived and then
// reset the owner. ASAN will catch free() errors.
Value value2 = kInitialValue;
auto mderived =
SequenceBound<MultiplyDerived>(task_runner_, &value_, &value2);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kOtherCtorValue);
EXPECT_EQ(value2, kDerivedCtorValue);
SequenceBound<Other> other = std::move(mderived);
other.Reset();
base::RunLoop().RunUntilIdle();
// Both destructors should have run.
EXPECT_EQ(value_, kOtherDtorValue);
EXPECT_EQ(value2, kDerivedDtorValue);
}
TEST_F(SequenceBoundTest, MultiplyDerivedDestructionWorksRightSuper) {
// Verify that everything works when we're casting around in ways that might
// change the address. We cast to the right side of MultiplyDerived and then
// reset the owner. ASAN will catch free() errors.
Value value2 = kInitialValue;
auto mderived =
SequenceBound<MultiplyDerived>(task_runner_, &value_, &value2);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kOtherCtorValue);
EXPECT_EQ(value2, kDerivedCtorValue);
SequenceBound<Base> base = std::move(mderived);
base.Reset();
base::RunLoop().RunUntilIdle();
// Both destructors should have run.
EXPECT_EQ(value_, kOtherDtorValue);
EXPECT_EQ(value2, kDerivedDtorValue);
}
TEST_F(SequenceBoundTest, MoveAssignmentFromSameClass) {
// Test move-assignment using the same classes.
auto derived_old = SequenceBound<Derived>(task_runner_, &value_);
SequenceBound<Derived> derived_new;
derived_new = std::move(derived_old);
EXPECT_TRUE(derived_old.is_null());
EXPECT_FALSE(derived_new.is_null());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDerivedCtorValue);
// Verify that |derived_new| owns the object now. Also verifies that move
// assignment from the same class deletes the outgoing object.
derived_new = SequenceBound<Derived>();
EXPECT_EQ(value_, kDerivedCtorValue);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDerivedDtorValue);
}
TEST_F(SequenceBoundTest, MoveAssignmentFromDerivedClass) {
// Move-assignment from a derived class to a base class.
auto derived = SequenceBound<Derived>(task_runner_, &value_);
SequenceBound<Base> base;
base = std::move(derived);
EXPECT_TRUE(derived.is_null());
EXPECT_FALSE(base.is_null());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDerivedCtorValue);
// Verify that |base| owns the object now, and that destruction still destroys
// Derived properly.
base.Reset();
EXPECT_EQ(value_, kDerivedCtorValue);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDerivedDtorValue);
}
TEST_F(SequenceBoundTest, MoveAssignmentFromDerivedClassDestroysOldObject) {
// Verify that move-assignment from a derived class runs the dtor of the
// outgoing object.
auto derived = SequenceBound<Derived>(task_runner_, &value_);
Value value1 = kInitialValue;
Value value2 = kInitialValue;
auto mderived =
SequenceBound<MultiplyDerived>(task_runner_, &value1, &value2);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDerivedCtorValue);
// Assign |mderived|, and verify that the original object in |derived| is
// destroyed properly.
derived = std::move(mderived);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value_, kDerivedDtorValue);
// Delete |derived|, since it has pointers to local vars.
derived.Reset();
base::RunLoop().RunUntilIdle();
}
TEST_F(SequenceBoundTest, MultiplyDerivedPostToLeftBaseClass) {
// Cast and call methods on the left base class.
Value value1 = kInitialValue;
Value value2 = kInitialValue;
auto mderived =
SequenceBound<MultiplyDerived>(task_runner_, &value1, &value2);
// Cast to Other, the left base.
SequenceBound<Other> other(std::move(mderived));
other.AsyncCall(&Other::SetValue).WithArgs(kDifferentValue);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value1, kDifferentValue);
EXPECT_EQ(value2, kDerivedCtorValue);
other.Reset();
base::RunLoop().RunUntilIdle();
}
TEST_F(SequenceBoundTest, MultiplyDerivedPostToRightBaseClass) {
// Cast and call methods on the right base class.
Value value1 = kInitialValue;
Value value2 = kInitialValue;
auto mderived =
SequenceBound<MultiplyDerived>(task_runner_, &value1, &value2);
SequenceBound<Derived> derived(std::move(mderived));
derived.AsyncCall(&Derived::SetValue).WithArgs(kDifferentValue);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(value1, kOtherCtorValue);
EXPECT_EQ(value2, kDifferentValue);
derived.Reset();
base::RunLoop().RunUntilIdle();
}
TEST_F(SequenceBoundTest, MoveConstructionFromNullWorks) {
// Verify that this doesn't crash.
SequenceBound<Derived> derived1;
SequenceBound<Derived> derived2(std::move(derived1));
}
TEST_F(SequenceBoundTest, MoveAssignmentFromNullWorks) {
// Verify that this doesn't crash.
SequenceBound<Derived> derived1;
SequenceBound<Derived> derived2;
derived2 = std::move(derived1);
}
TEST_F(SequenceBoundTest, ResetOnNullObjectWorks) {
// Verify that this doesn't crash.
SequenceBound<Derived> derived;
derived.Reset();
}
TEST_F(SequenceBoundTest, LvalueConstructionParameter) {
// Note here that |value_ptr| is an lvalue, while |&value| would be an rvalue.
Value value = kInitialValue;
Value* value_ptr = &value;
SequenceBound<Derived> derived(task_runner_, value_ptr);
{
derived.AsyncCall(&Derived::SetValue).WithArgs(kDifferentValue);
base::RunLoop run_loop;
task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
EXPECT_EQ(value, kDifferentValue);
}
{
derived.Reset();
base::RunLoop run_loop;
task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
EXPECT_EQ(value, kDerivedDtorValue);
}
}
TEST_F(SequenceBoundTest, PostTaskWithThisObject) {
constexpr int kTestValue1 = 42;
constexpr int kTestValue2 = 42;
base::SequenceBound<BoxedValue> value(task_runner_, kTestValue1);
base::RunLoop loop;
value.PostTaskWithThisObject(
FROM_HERE, base::BindLambdaForTesting([&](const BoxedValue& v) {
EXPECT_EQ(kTestValue1, v.value());
}));
value.PostTaskWithThisObject(
FROM_HERE, base::BindLambdaForTesting(
[&](BoxedValue* v) { v->set_value(kTestValue2); }));
value.PostTaskWithThisObject(
FROM_HERE, base::BindLambdaForTesting([&](const BoxedValue& v) {
EXPECT_EQ(kTestValue2, v.value());
loop.Quit();
}));
loop.Run();
}
TEST_F(SequenceBoundTest, SynchronouslyResetForTest) {
base::SequenceBound<BoxedValue> value(task_runner_, 0);
bool destroyed = false;
value.AsyncCall(&BoxedValue::set_destruction_callback)
.WithArgs(base::BindLambdaForTesting([&] { destroyed = true; }));
value.SynchronouslyResetForTest();
EXPECT_TRUE(destroyed);
}
TEST_F(SequenceBoundTest, SmallObject) {
class EmptyClass {};
SequenceBound<EmptyClass> value(task_runner_);
// Test passes if SequenceBound constructor does not crash in AlignedAlloc().
}
TEST_F(SequenceBoundTest, SelfMoveAssign) {
class EmptyClass {};
SequenceBound<EmptyClass> value(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());
}
namespace {
class NoArgsVoidReturn {
public:
void Method() {
if (loop_)
loop_->Quit();
}
void ConstMethod() const {
if (loop_)
loop_->Quit();
}
void set_loop(RunLoop* loop) { loop_ = loop; }
private:
RunLoop* loop_ = nullptr;
};
class NoArgsIntReturn {
public:
int Method() { return 123; }
int ConstMethod() const { return 456; }
};
class IntArgVoidReturn {
public:
IntArgVoidReturn(int* method_called_with, int* const_method_called_with)
: method_called_with_(method_called_with),
const_method_called_with_(const_method_called_with) {}
void Method(int x) {
*method_called_with_ = x;
if (loop_)
loop_->Quit();
}
void ConstMethod(int x) const {
*const_method_called_with_ = x;
if (loop_)
loop_->Quit();
}
void set_loop(RunLoop* loop) { loop_ = loop; }
private:
int* const method_called_with_;
int* const const_method_called_with_;
RunLoop* loop_ = nullptr;
};
class IntArgIntReturn {
public:
int Method(int x) { return -x; }
int ConstMethod(int x) const { return -x; }
};
} // namespace
TEST_F(SequenceBoundTest, AsyncCallNoArgsNoThen) {
SequenceBound<NoArgsVoidReturn> s(task_runner_);
{
RunLoop loop;
s.AsyncCall(&NoArgsVoidReturn::set_loop).WithArgs(&loop);
s.AsyncCall(&NoArgsVoidReturn::Method);
loop.Run();
}
{
RunLoop loop;
s.AsyncCall(&NoArgsVoidReturn::set_loop).WithArgs(&loop);
s.AsyncCall(&NoArgsVoidReturn::ConstMethod);
loop.Run();
}
}
TEST_F(SequenceBoundTest, AsyncCallIntArgNoThen) {
int method_called_with = 0;
int const_method_called_with = 0;
SequenceBound<IntArgVoidReturn> s(task_runner_, &method_called_with,
&const_method_called_with);
{
RunLoop loop;
s.AsyncCall(&IntArgVoidReturn::set_loop).WithArgs(&loop);
s.AsyncCall(&IntArgVoidReturn::Method).WithArgs(123);
loop.Run();
EXPECT_EQ(123, method_called_with);
}
{
RunLoop loop;
s.AsyncCall(&IntArgVoidReturn::set_loop).WithArgs(&loop);
s.AsyncCall(&IntArgVoidReturn::ConstMethod).WithArgs(456);
loop.Run();
EXPECT_EQ(456, const_method_called_with);
}
}
TEST_F(SequenceBoundTest, AsyncCallNoArgsVoidThen) {
SequenceBound<NoArgsVoidReturn> s(task_runner_);
{
RunLoop loop;
s.AsyncCall(&NoArgsVoidReturn::Method).Then(BindLambdaForTesting([&]() {
loop.Quit();
}));
loop.Run();
}
{
RunLoop loop;
s.AsyncCall(&NoArgsVoidReturn::ConstMethod)
.Then(BindLambdaForTesting([&]() { loop.Quit(); }));
loop.Run();
}
}
TEST_F(SequenceBoundTest, AsyncCallNoArgsIntThen) {
SequenceBound<NoArgsIntReturn> s(task_runner_);
{
RunLoop loop;
s.AsyncCall(&NoArgsIntReturn::Method)
.Then(BindLambdaForTesting([&](int result) {
EXPECT_EQ(123, result);
loop.Quit();
}));
loop.Run();
}
{
RunLoop loop;
s.AsyncCall(&NoArgsIntReturn::ConstMethod)
.Then(BindLambdaForTesting([&](int result) {
EXPECT_EQ(456, result);
loop.Quit();
}));
loop.Run();
}
}
TEST_F(SequenceBoundTest, AsyncCallWithArgsVoidThen) {
int method_called_with = 0;
int const_method_called_with = 0;
SequenceBound<IntArgVoidReturn> s(task_runner_, &method_called_with,
&const_method_called_with);
{
RunLoop loop;
s.AsyncCall(&IntArgVoidReturn::Method)
.WithArgs(123)
.Then(BindLambdaForTesting([&] { loop.Quit(); }));
loop.Run();
EXPECT_EQ(123, method_called_with);
}
{
RunLoop loop;
s.AsyncCall(&IntArgVoidReturn::ConstMethod)
.WithArgs(456)
.Then(BindLambdaForTesting([&] { loop.Quit(); }));
loop.Run();
EXPECT_EQ(456, const_method_called_with);
}
}
TEST_F(SequenceBoundTest, AsyncCallWithArgsIntThen) {
SequenceBound<IntArgIntReturn> s(task_runner_);
{
RunLoop loop;
s.AsyncCall(&IntArgIntReturn::Method)
.WithArgs(123)
.Then(BindLambdaForTesting([&](int result) {
EXPECT_EQ(-123, result);
loop.Quit();
}));
loop.Run();
}
{
RunLoop loop;
s.AsyncCall(&IntArgIntReturn::ConstMethod)
.WithArgs(456)
.Then(BindLambdaForTesting([&](int result) {
EXPECT_EQ(-456, result);
loop.Quit();
}));
loop.Run();
}
}
TEST_F(SequenceBoundTest, AsyncCallIsConstQualified) {
// Tests that both const and non-const methods may be called through a
// const-qualified SequenceBound.
const SequenceBound<NoArgsVoidReturn> s(task_runner_);
s.AsyncCall(&NoArgsVoidReturn::ConstMethod);
s.AsyncCall(&NoArgsVoidReturn::Method);
}
class IgnoreResultTestHelperWithNoArgs {
public:
explicit IgnoreResultTestHelperWithNoArgs(RunLoop* loop, bool* called)
: loop_(loop), called_(called) {}
int ConstMethod() const {
if (loop_) {
loop_->Quit();
}
if (called_) {
*called_ = true;
}
return 0;
}
int Method() {
if (loop_) {
loop_->Quit();
}
if (called_) {
*called_ = true;
}
return 0;
}
private:
RunLoop* const loop_ = nullptr;
bool* const called_ = nullptr;
};
TEST_F(SequenceBoundTest, AsyncCallIgnoreResultNoArgs) {
{
RunLoop loop;
SequenceBound<IgnoreResultTestHelperWithNoArgs> s(task_runner_, &loop,
nullptr);
s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::ConstMethod));
loop.Run();
}
{
RunLoop loop;
SequenceBound<IgnoreResultTestHelperWithNoArgs> s(task_runner_, &loop,
nullptr);
s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::Method));
loop.Run();
}
}
TEST_F(SequenceBoundTest, AsyncCallIgnoreResultThen) {
{
RunLoop loop;
bool called = false;
SequenceBound<IgnoreResultTestHelperWithNoArgs> s(task_runner_, nullptr,
&called);
s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::ConstMethod))
.Then(BindLambdaForTesting([&] { loop.Quit(); }));
loop.Run();
EXPECT_TRUE(called);
}
{
RunLoop loop;
bool called = false;
SequenceBound<IgnoreResultTestHelperWithNoArgs> s(task_runner_, nullptr,
&called);
s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::Method))
.Then(BindLambdaForTesting([&] { loop.Quit(); }));
loop.Run();
EXPECT_TRUE(called);
}
}
class IgnoreResultTestHelperWithArgs {
public:
IgnoreResultTestHelperWithArgs(RunLoop* loop, int& value)
: loop_(loop), value_(value) {}
int ConstMethod(int arg) const {
value_ = arg;
if (loop_) {
loop_->Quit();
}
return arg;
}
int Method(int arg) {
value_ = arg;
if (loop_) {
loop_->Quit();
}
return arg;
}
private:
RunLoop* const loop_ = nullptr;
int& value_;
};
TEST_F(SequenceBoundTest, AsyncCallIgnoreResultWithArgs) {
{
RunLoop loop;
int result = 0;
SequenceBound<IgnoreResultTestHelperWithArgs> s(task_runner_, &loop,
std::ref(result));
s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::ConstMethod))
.WithArgs(60);
loop.Run();
EXPECT_EQ(60, result);
}
{
RunLoop loop;
int result = 0;
SequenceBound<IgnoreResultTestHelperWithArgs> s(task_runner_, &loop,
std::ref(result));
s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::Method))
.WithArgs(06);
loop.Run();
EXPECT_EQ(06, result);
}
}
TEST_F(SequenceBoundTest, AsyncCallIgnoreResultWithArgsThen) {
{
RunLoop loop;
int result = 0;
SequenceBound<IgnoreResultTestHelperWithArgs> s(task_runner_, nullptr,
std::ref(result));
s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::ConstMethod))
.WithArgs(60)
.Then(BindLambdaForTesting([&] { loop.Quit(); }));
loop.Run();
EXPECT_EQ(60, result);
}
{
RunLoop loop;
int result = 0;
SequenceBound<IgnoreResultTestHelperWithArgs> s(task_runner_, nullptr,
std::ref(result));
s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::Method))
.WithArgs(06)
.Then(BindLambdaForTesting([&] { loop.Quit(); }));
loop.Run();
EXPECT_EQ(06, result);
}
}
// TODO(dcheng): Maybe use the nocompile harness here instead of being
// "clever"...
TEST_F(SequenceBoundTest, NoCompileTests) {
// TODO(dcheng): Test calling WithArgs() on a method that takes no arguments.
// Given:
// class C {
// void F();
// };
//
// Then:
// SequenceBound<C> s(...);
// s.AsyncCall(&C::F).WithArgs(...);
//
// should not compile.
//
// TODO(dcheng): Test calling Then() before calling WithArgs().
// Given:
// class C {
// void F(int);
// };
//
// Then:
// SequenceBound<C> s(...);
// s.AsyncCall(&C::F).Then(...).WithArgs(...);
//
// should not compile.
//
}
class SequenceBoundDeathTest : public ::testing::Test {
protected:
void TearDown() override {
// Make sure that any objects owned by `SequenceBound` have been destroyed
// to avoid tripping leak detection.
RunLoop run_loop;
task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
// Death tests use fork(), which can interact (very) poorly with threads.
test::SingleThreadTaskEnvironment task_environment_;
scoped_refptr<SequencedTaskRunner> task_runner_ =
base::SequencedTaskRunnerHandle::Get();
};
TEST_F(SequenceBoundDeathTest, AsyncCallIntArgNoWithArgsShouldCheck) {
SequenceBound<IntArgIntReturn> s(task_runner_);
EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&IntArgIntReturn::Method), "");
}
TEST_F(SequenceBoundDeathTest, AsyncCallIntReturnNoThenShouldCheck) {
{
SequenceBound<NoArgsIntReturn> s(task_runner_);
EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&NoArgsIntReturn::Method), "");
}
{
SequenceBound<IntArgIntReturn> s(task_runner_);
EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&IntArgIntReturn::Method).WithArgs(0),
"");
}
}
} // namespace base