blob: 17189370de61431535f0cca930b0f9ae9622bc15 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 <fbl/function.h>
#include <fbl/vector.h>
#include <unittest/unittest.h>
namespace fbl {
namespace tests {
namespace {
using Closure = void();
using BinaryOp = int(int a, int b);
using MoveOp = fbl::unique_ptr<int>(fbl::unique_ptr<int> value);
// A big object which causes a function target to be heap allocated.
struct Big {
int data[64]{};
};
// An object that looks like an "empty" std::function.
template<typename> struct EmptyFunction;
template<typename R, typename... Args> struct EmptyFunction<R(Args...)> {
R operator()(Args... args) const { return fptr(args...); }
bool operator==(decltype(nullptr)) const { return true; }
R (*fptr)(Args...) = nullptr;
};
constexpr size_t HugeCallableSize = sizeof(Big) + sizeof(void*) * 4;
template <typename ClosureFunction>
bool closure() {
BEGIN_TEST;
// default initialization
ClosureFunction fdefault;
EXPECT_FALSE(!!fdefault);
// nullptr initialization
ClosureFunction fnull(nullptr);
EXPECT_FALSE(!!fnull);
// null function pointer initialization
Closure* fptr = nullptr;
ClosureFunction ffunc(fptr);
EXPECT_FALSE(!!ffunc);
// "empty std::function" initialization
EmptyFunction<Closure> empty;
ClosureFunction fwrapper(empty);
EXPECT_FALSE(!!fwrapper);
// inline callable initialization
int finline_value = 0;
ClosureFunction finline([&finline_value] { finline_value++; });
EXPECT_TRUE(!!finline);
finline();
EXPECT_EQ(1, finline_value);
finline();
EXPECT_EQ(2, finline_value);
// heap callable initialization
int fheap_value = 0;
ClosureFunction fheap([&fheap_value, big = Big() ] { fheap_value++; });
EXPECT_TRUE(!!fheap);
fheap();
EXPECT_EQ(1, fheap_value);
fheap();
EXPECT_EQ(2, fheap_value);
// move initialization of a nullptr
ClosureFunction fnull2(std::move(fnull));
EXPECT_FALSE(!!fnull2);
// move initialization of an inline callable
ClosureFunction finline2(std::move(finline));
EXPECT_TRUE(!!finline2);
EXPECT_FALSE(!!finline);
finline2();
EXPECT_EQ(3, finline_value);
finline2();
EXPECT_EQ(4, finline_value);
// move initialization of a heap callable
ClosureFunction fheap2(std::move(fheap));
EXPECT_TRUE(!!fheap2);
EXPECT_FALSE(!!fheap);
fheap2();
EXPECT_EQ(3, fheap_value);
fheap2();
EXPECT_EQ(4, fheap_value);
// inline mutable lambda
int fmutinline_value = 0;
ClosureFunction fmutinline([&fmutinline_value, x = 1 ]() mutable {
x *= 2;
fmutinline_value = x;
});
EXPECT_TRUE(!!fmutinline);
fmutinline();
EXPECT_EQ(2, fmutinline_value);
fmutinline();
EXPECT_EQ(4, fmutinline_value);
// heap-allocated mutable lambda
int fmutheap_value = 0;
ClosureFunction fmutheap([&fmutheap_value, big = Big(), x = 1 ]() mutable {
x *= 2;
fmutheap_value = x;
});
EXPECT_TRUE(!!fmutheap);
fmutheap();
EXPECT_EQ(2, fmutheap_value);
fmutheap();
EXPECT_EQ(4, fmutheap_value);
// move assignment of non-null
ClosureFunction fnew([] {});
fnew = std::move(finline2);
EXPECT_TRUE(!!fnew);
fnew();
EXPECT_EQ(5, finline_value);
fnew();
EXPECT_EQ(6, finline_value);
// move assignment of null
fnew = std::move(fnull);
EXPECT_FALSE(!!fnew);
// callable assignment with operator=
int fnew_value = 0;
fnew = [&fnew_value] { fnew_value++; };
EXPECT_TRUE(!!fnew);
fnew();
EXPECT_EQ(1, fnew_value);
fnew();
EXPECT_EQ(2, fnew_value);
// callable assignment with SetTarget
fnew.SetTarget([&fnew_value] { fnew_value *= 2; });
EXPECT_TRUE(!!fnew);
fnew();
EXPECT_EQ(4, fnew_value);
fnew();
EXPECT_EQ(8, fnew_value);
// nullptr assignment
fnew = nullptr;
EXPECT_FALSE(!!fnew);
// swap (currently null)
fnew.swap(fheap2);
EXPECT_TRUE(!!fnew);
EXPECT_FALSE(!!fheap);
fnew();
EXPECT_EQ(5, fheap_value);
fnew();
EXPECT_EQ(6, fheap_value);
// swap with self
fnew.swap(fnew);
EXPECT_TRUE(!!fnew);
fnew();
EXPECT_EQ(7, fheap_value);
fnew();
EXPECT_EQ(8, fheap_value);
// swap with non-null
fnew.swap(fmutinline);
EXPECT_TRUE(!!fmutinline);
EXPECT_TRUE(!!fnew);
fmutinline();
EXPECT_EQ(9, fheap_value);
fmutinline();
EXPECT_EQ(10, fheap_value);
fnew();
EXPECT_EQ(8, fmutinline_value);
fnew();
EXPECT_EQ(16, fmutinline_value);
// nullptr comparison operators
EXPECT_TRUE(fnull == nullptr);
EXPECT_FALSE(fnull != nullptr);
EXPECT_TRUE(nullptr == fnull);
EXPECT_FALSE(nullptr != fnull);
EXPECT_FALSE(fnew == nullptr);
EXPECT_TRUE(fnew != nullptr);
EXPECT_FALSE(nullptr == fnew);
EXPECT_TRUE(nullptr != fnew);
// null function pointer assignment
fnew = fptr;
EXPECT_FALSE(!!fnew);
// "empty std::function" assignment
fmutinline = empty;
EXPECT_FALSE(!!fmutinline);
// alloc checking constructor, inline
AllocChecker ac1;
int fcheck_value = 0;
ClosureFunction fcheckinline([&fcheck_value] { fcheck_value++; }, &ac1);
EXPECT_TRUE(!!fcheckinline);
EXPECT_TRUE(ac1.check());
fcheckinline();
EXPECT_EQ(1, fcheck_value);
// alloc checking set target, inline
AllocChecker ac2;
fcheckinline.SetTarget([&fcheck_value] { fcheck_value *= 3; }, &ac2);
EXPECT_TRUE(!!fcheckinline);
EXPECT_TRUE(ac2.check());
fcheckinline();
EXPECT_EQ(3, fcheck_value);
// alloc checking constructor, heap allocated
AllocChecker ac3;
ClosureFunction fcheckheap([&fcheck_value, big = Big() ] { fcheck_value++; }, &ac3);
EXPECT_TRUE(!!fcheckheap);
EXPECT_TRUE(ac3.check());
fcheckheap();
EXPECT_EQ(4, fcheck_value);
// alloc checking set target, heap allocated
AllocChecker ac4;
fcheckheap.SetTarget([&fcheck_value, big = Big() ] { fcheck_value *= 3; }, &ac4);
EXPECT_TRUE(!!fcheckheap);
EXPECT_TRUE(ac4.check());
fcheckheap();
EXPECT_EQ(12, fcheck_value);
END_TEST;
}
template <typename BinaryOpFunction>
bool binary_op() {
BEGIN_TEST;
// default initialization
BinaryOpFunction fdefault;
EXPECT_FALSE(!!fdefault);
// nullptr initialization
BinaryOpFunction fnull(nullptr);
EXPECT_FALSE(!!fnull);
// null function pointer initialization
BinaryOp* fptr = nullptr;
BinaryOpFunction ffunc(fptr);
EXPECT_FALSE(!!ffunc);
// "empty std::function" initialization
EmptyFunction<BinaryOp> empty;
BinaryOpFunction fwrapper(empty);
EXPECT_FALSE(!!fwrapper);
// inline callable initialization
int finline_value = 0;
BinaryOpFunction finline([&finline_value](int a, int b) {
finline_value++;
return a + b;
});
EXPECT_TRUE(!!finline);
EXPECT_EQ(10, finline(3, 7));
EXPECT_EQ(1, finline_value);
EXPECT_EQ(10, finline(3, 7));
EXPECT_EQ(2, finline_value);
// heap callable initialization
int fheap_value = 0;
BinaryOpFunction fheap([&fheap_value, big = Big() ](int a, int b) {
fheap_value++;
return a + b;
});
EXPECT_TRUE(!!fheap);
EXPECT_EQ(10, fheap(3, 7));
EXPECT_EQ(1, fheap_value);
EXPECT_EQ(10, fheap(3, 7));
EXPECT_EQ(2, fheap_value);
// move initialization of a nullptr
BinaryOpFunction fnull2(std::move(fnull));
EXPECT_FALSE(!!fnull2);
// move initialization of an inline callable
BinaryOpFunction finline2(std::move(finline));
EXPECT_TRUE(!!finline2);
EXPECT_FALSE(!!finline);
EXPECT_EQ(10, finline2(3, 7));
EXPECT_EQ(3, finline_value);
EXPECT_EQ(10, finline2(3, 7));
EXPECT_EQ(4, finline_value);
// move initialization of a heap callable
BinaryOpFunction fheap2(std::move(fheap));
EXPECT_TRUE(!!fheap2);
EXPECT_FALSE(!!fheap);
EXPECT_EQ(10, fheap2(3, 7));
EXPECT_EQ(3, fheap_value);
EXPECT_EQ(10, fheap2(3, 7));
EXPECT_EQ(4, fheap_value);
// inline mutable lambda
int fmutinline_value = 0;
BinaryOpFunction fmutinline([&fmutinline_value, x = 1 ](int a, int b) mutable {
x *= 2;
fmutinline_value = x;
return a + b;
});
EXPECT_TRUE(!!fmutinline);
EXPECT_EQ(10, fmutinline(3, 7));
EXPECT_EQ(2, fmutinline_value);
EXPECT_EQ(10, fmutinline(3, 7));
EXPECT_EQ(4, fmutinline_value);
// heap-allocated mutable lambda
int fmutheap_value = 0;
BinaryOpFunction fmutheap([&fmutheap_value, big = Big(), x = 1 ](int a, int b) mutable {
x *= 2;
fmutheap_value = x;
return a + b;
});
EXPECT_TRUE(!!fmutheap);
EXPECT_EQ(10, fmutheap(3, 7));
EXPECT_EQ(2, fmutheap_value);
EXPECT_EQ(10, fmutheap(3, 7));
EXPECT_EQ(4, fmutheap_value);
// move assignment of non-null
BinaryOpFunction fnew([](int a, int b) { return 0; });
fnew = std::move(finline2);
EXPECT_TRUE(!!fnew);
EXPECT_EQ(10, fnew(3, 7));
EXPECT_EQ(5, finline_value);
EXPECT_EQ(10, fnew(3, 7));
EXPECT_EQ(6, finline_value);
// move assignment of null
fnew = std::move(fnull);
EXPECT_FALSE(!!fnew);
// callable assignment with operator=
int fnew_value = 0;
fnew = [&fnew_value](int a, int b) {
fnew_value++;
return a + b;
};
EXPECT_TRUE(!!fnew);
EXPECT_EQ(10, fnew(3, 7));
EXPECT_EQ(1, fnew_value);
EXPECT_EQ(10, fnew(3, 7));
EXPECT_EQ(2, fnew_value);
// callable assignment with SetTarget
fnew.SetTarget([&fnew_value](int a, int b) {
fnew_value *= 2;
return a + b;
});
EXPECT_TRUE(!!fnew);
EXPECT_EQ(10, fnew(3, 7));
EXPECT_EQ(4, fnew_value);
EXPECT_EQ(10, fnew(3, 7));
EXPECT_EQ(8, fnew_value);
// nullptr assignment
fnew = nullptr;
EXPECT_FALSE(!!fnew);
// swap (currently null)
fnew.swap(fheap2);
EXPECT_TRUE(!!fnew);
EXPECT_FALSE(!!fheap);
EXPECT_EQ(10, fnew(3, 7));
EXPECT_EQ(5, fheap_value);
EXPECT_EQ(10, fnew(3, 7));
EXPECT_EQ(6, fheap_value);
// swap with self
fnew.swap(fnew);
EXPECT_TRUE(!!fnew);
EXPECT_EQ(10, fnew(3, 7));
EXPECT_EQ(7, fheap_value);
EXPECT_EQ(10, fnew(3, 7));
EXPECT_EQ(8, fheap_value);
// swap with non-null
fnew.swap(fmutinline);
EXPECT_TRUE(!!fmutinline);
EXPECT_TRUE(!!fnew);
EXPECT_EQ(10, fmutinline(3, 7));
EXPECT_EQ(9, fheap_value);
EXPECT_EQ(10, fmutinline(3, 7));
EXPECT_EQ(10, fheap_value);
EXPECT_EQ(10, fnew(3, 7));
EXPECT_EQ(8, fmutinline_value);
EXPECT_EQ(10, fnew(3, 7));
EXPECT_EQ(16, fmutinline_value);
// nullptr comparison operators
EXPECT_TRUE(fnull == nullptr);
EXPECT_FALSE(fnull != nullptr);
EXPECT_TRUE(nullptr == fnull);
EXPECT_FALSE(nullptr != fnull);
EXPECT_FALSE(fnew == nullptr);
EXPECT_TRUE(fnew != nullptr);
EXPECT_FALSE(nullptr == fnew);
EXPECT_TRUE(nullptr != fnew);
// null function pointer assignment
fnew = fptr;
EXPECT_FALSE(!!fnew);
// "empty std::function" assignment
fmutinline = empty;
EXPECT_FALSE(!!fmutinline);
// alloc checking constructor, inline
AllocChecker ac1;
int fcheck_value = 0;
BinaryOpFunction fcheckinline([&fcheck_value](int a, int b) {
fcheck_value++;
return a + b;
},
&ac1);
EXPECT_TRUE(!!fcheckinline);
EXPECT_TRUE(ac1.check());
EXPECT_EQ(10, fcheckinline(3, 7));
EXPECT_EQ(1, fcheck_value);
// alloc checking set target, inline
AllocChecker ac2;
fcheckinline.SetTarget([&fcheck_value](int a, int b) {
fcheck_value *= 3;
return a + b;
},
&ac2);
EXPECT_TRUE(!!fcheckinline);
EXPECT_TRUE(ac2.check());
EXPECT_EQ(10, fcheckinline(3, 7));
EXPECT_EQ(3, fcheck_value);
// alloc checking constructor, heap allocated
AllocChecker ac3;
BinaryOpFunction fcheckheap([&fcheck_value, big = Big() ](int a, int b) {
fcheck_value++;
return a + b;
},
&ac3);
EXPECT_TRUE(!!fcheckheap);
EXPECT_TRUE(ac3.check());
EXPECT_EQ(10, fcheckheap(3, 7));
EXPECT_EQ(4, fcheck_value);
// alloc checking set target, heap allocated
AllocChecker ac4;
fcheckheap.SetTarget([&fcheck_value, big = Big() ](int a, int b) {
fcheck_value *= 3;
return a + b;
},
&ac4);
EXPECT_TRUE(!!fcheckheap);
EXPECT_TRUE(ac4.check());
EXPECT_EQ(10, fcheckheap(3, 7));
EXPECT_EQ(12, fcheck_value);
END_TEST;
}
bool sized_function_size_bounds() {
BEGIN_TEST;
auto empty = [] {};
fbl::SizedFunction<Closure, sizeof(empty)> fempty(std::move(empty));
static_assert(sizeof(fempty) >= sizeof(empty), "size bounds");
auto small = [ x = 1, y = 2 ] {
(void)x; // suppress unused lambda capture warning
(void)y;
};
fbl::SizedFunction<Closure, sizeof(small)> fsmall(std::move(small));
static_assert(sizeof(fsmall) >= sizeof(small), "size bounds");
fsmall = [] {};
auto big = [ big = Big(), x = 1 ] { (void)x; };
fbl::SizedFunction<Closure, sizeof(big)> fbig(std::move(big));
static_assert(sizeof(fbig) >= sizeof(big), "size bounds");
fbig = [ x = 1, y = 2 ] {
(void)x;
(void)y;
};
fbig = [] {};
// These statements do compile though the lambda will be copied to the heap
// when they exceed the inline size.
fempty = [ x = 1, y = 2 ] {
(void)x;
(void)y;
};
fsmall = [ big = Big(), x = 1 ] { (void)x; };
fbig = [ big = Big(), x = 1, y = 2 ] {
(void)x;
(void)y;
};
END_TEST;
}
bool inline_function_size_bounds() {
BEGIN_TEST;
auto empty = [] {};
fbl::InlineFunction<Closure, sizeof(empty)> fempty(std::move(empty));
static_assert(sizeof(fempty) >= sizeof(empty), "size bounds");
auto small = [ x = 1, y = 2 ] {
(void)x; // suppress unused lambda capture warning
(void)y;
};
fbl::InlineFunction<Closure, sizeof(small)> fsmall(std::move(small));
static_assert(sizeof(fsmall) >= sizeof(small), "size bounds");
fsmall = [] {};
auto big = [ big = Big(), x = 1 ] { (void)x; };
fbl::InlineFunction<Closure, sizeof(big)> fbig(std::move(big));
static_assert(sizeof(fbig) >= sizeof(big), "size bounds");
fbig = [ x = 1, y = 2 ] {
(void)x;
(void)y;
};
fbig = [] {};
// These statements do not compile because the lambdas are too big to fit.
#if 0
fempty = [ x = 1, y = 2 ] {
(void)x;
(void)y;
};
fsmall = [ big = Big(), x = 1 ] { (void)x; };
fbig = [ big = Big(), x = 1, y = 2 ] {
(void)x;
(void)y;
};
#endif
END_TEST;
}
bool move_only_argument_and_result() {
BEGIN_TEST;
fbl::unique_ptr<int> arg(new int());
fbl::Function<MoveOp> f([](fbl::unique_ptr<int> value) {
*value += 1;
return value;
});
arg = f(std::move(arg));
EXPECT_EQ(1, *arg);
arg = f(std::move(arg));
EXPECT_EQ(2, *arg);
END_TEST;
}
void implicit_construction_helper(fbl::Closure closure) {}
bool implicit_construction() {
BEGIN_TEST;
// ensure we can implicitly construct from nullptr
implicit_construction_helper(nullptr);
// ensure we can implicitly construct from a lambda
implicit_construction_helper([] {});
END_TEST;
}
struct Obj {
void Call() {
calls++;
}
int AddOne(int x) {
calls++;
return x + 1;
}
int Sum(int a, int b, int c) {
calls++;
return a + b + c;
}
fbl::unique_ptr<int> AddAndReturn(fbl::unique_ptr<int> value) {
(*value)++;
return value;
}
uint32_t calls = 0;
};
bool bind_member() {
BEGIN_TEST;
Obj obj;
auto move_only_value = fbl::make_unique<int>(4);
BindMember(&obj, &Obj::Call)();
EXPECT_EQ(23, BindMember(&obj, &Obj::AddOne)(22));
EXPECT_EQ(6, BindMember(&obj, &Obj::Sum)(1, 2, 3));
move_only_value = BindMember(&obj, &Obj::AddAndReturn)(std::move(move_only_value));
EXPECT_EQ(5, *move_only_value);
EXPECT_EQ(3, obj.calls);
END_TEST;
}
// Test the internal IsNull mechanism.
struct Nullable {
bool is_null;
bool operator==(decltype(nullptr)) const { return is_null; }
};
struct NotNullable {};
struct NonBoolNull {
void operator==(decltype(nullptr)) const {}
};
bool null_check() {
BEGIN_TEST;
EXPECT_TRUE(fbl::internal::IsNull(nullptr));
Nullable nf = {false};
EXPECT_FALSE(fbl::internal::IsNull(nf));
Nullable nt = {true};
EXPECT_TRUE(fbl::internal::IsNull(nt));
NotNullable nn;
EXPECT_FALSE(fbl::internal::IsNull(nn));
NonBoolNull nbn;
EXPECT_FALSE(fbl::internal::IsNull(nbn));
END_TEST;
}
// This is the code which is included in <function.h>.
namespace example {
using FoldFunction = fbl::Function<int(int value, int item)>;
int FoldVector(const fbl::Vector<int>& in, int value, const FoldFunction& f) {
for (auto& item : in) {
value = f(value, item);
}
return value;
}
int SumItem(int value, int item) {
return value + item;
}
int Sum(const fbl::Vector<int>& in) {
// bind to a function pointer
FoldFunction sum(&SumItem);
return FoldVector(in, 0, sum);
}
int AlternatingSum(const fbl::Vector<int>& in) {
// bind to a lambda
int sign = 1;
FoldFunction alternating_sum([&sign](int value, int item) {
value += sign * item;
sign *= -1;
return value;
});
return FoldVector(in, 0, alternating_sum);
}
class Accumulator {
public:
void Add(int value) {
sum += value;
}
int sum = 0;
};
void CountToTen(fbl::Function<void(int)> function) {
for (int i = 1; i <= 10; i++) {
function(i);
}
}
int SumToTen() {
Accumulator accum;
CountToTen(fbl::BindMember(&accum, &Accumulator::Add));
return accum.sum;
}
} // namespace example
bool example_code() {
BEGIN_TEST;
fbl::Vector<int> in;
for (int i = 0; i < 10; i++) {
fbl::AllocChecker ac;
in.push_back(i, &ac);
EXPECT_TRUE(ac.check());
}
EXPECT_EQ(45, example::Sum(in));
EXPECT_EQ(-5, example::AlternatingSum(in));
EXPECT_EQ(55, example::SumToTen());
END_TEST;
}
} // namespace
BEGIN_TEST_CASE(function_tests)
RUN_TEST((closure<fbl::Function<Closure>>))
RUN_TEST((binary_op<fbl::Function<BinaryOp>>))
RUN_TEST((closure<fbl::SizedFunction<Closure, 0u>>))
RUN_TEST((binary_op<fbl::SizedFunction<BinaryOp, 0u>>))
RUN_TEST((closure<fbl::SizedFunction<Closure, HugeCallableSize>>))
RUN_TEST((binary_op<fbl::SizedFunction<BinaryOp, HugeCallableSize>>))
RUN_TEST((closure<fbl::InlineFunction<Closure, HugeCallableSize>>))
RUN_TEST((binary_op<fbl::InlineFunction<BinaryOp, HugeCallableSize>>))
RUN_TEST(sized_function_size_bounds);
RUN_TEST(inline_function_size_bounds);
RUN_TEST(move_only_argument_and_result);
RUN_TEST(implicit_construction);
RUN_TEST(bind_member);
RUN_TEST(null_check);
RUN_TEST(example_code);
END_TEST_CASE(function_tests)
} // namespace tests
} // namespace fbl