blob: 40b7db2310b69de703c88228de0ea15bbc1e8fc8 [file] [log] [blame]
// Copyright 2018 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 <memory>
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <unittest/unittest.h>
namespace {
// Counts instances.
class balance {
public:
balance(int* counter)
: counter_(counter) {
*counter_ += 1;
}
balance(balance&& other)
: counter_(other.counter_) {
*counter_ += 1;
}
~balance() {
*counter_ -= 1;
}
balance(const balance& other) = delete;
balance& operator=(const balance& other) = delete;
balance& operator=(balance&& other) = delete;
private:
int* const counter_;
};
void incr_arg(int* p) {
*p += 1;
}
bool default_construction() {
BEGIN_TEST;
fit::deferred_action<fit::closure> d;
EXPECT_FALSE(d);
END_TEST;
}
bool basic() {
BEGIN_TEST;
int var = 0;
{
auto do_incr = fit::defer([&var]() { incr_arg(&var); });
EXPECT_TRUE(do_incr);
EXPECT_EQ(var, 0);
}
EXPECT_EQ(var, 1);
END_TEST;
}
bool cancel() {
BEGIN_TEST;
int var = 0;
{
auto do_incr = fit::defer([&var]() { incr_arg(&var); });
EXPECT_TRUE(do_incr);
EXPECT_EQ(var, 0);
do_incr.cancel();
EXPECT_FALSE(do_incr);
EXPECT_EQ(var, 0);
// Once cancelled, call has no effect.
do_incr.call();
EXPECT_FALSE(do_incr);
EXPECT_EQ(var, 0);
}
EXPECT_EQ(var, 0);
END_TEST;
}
bool call() {
BEGIN_TEST;
int var = 0;
{
auto do_incr = fit::defer([&var]() { incr_arg(&var); });
EXPECT_TRUE(do_incr);
EXPECT_EQ(var, 0);
do_incr.call();
EXPECT_FALSE(do_incr);
EXPECT_EQ(var, 1);
// Call is effective only once.
do_incr.call();
EXPECT_FALSE(do_incr);
EXPECT_EQ(var, 1);
}
EXPECT_EQ(var, 1);
END_TEST;
}
bool recursive_call() {
BEGIN_TEST;
int var = 0;
{
auto do_incr = fit::defer<fit::closure>([]() { /* no-op */ });
EXPECT_TRUE(do_incr);
do_incr = fit::defer<fit::closure>([&do_incr, &var]() {
incr_arg(&var);
do_incr.call();
EXPECT_FALSE(do_incr);
});
EXPECT_TRUE(do_incr);
EXPECT_EQ(var, 0);
do_incr.call();
EXPECT_FALSE(do_incr);
EXPECT_EQ(var, 1);
}
EXPECT_EQ(var, 1);
END_TEST;
}
bool move_construct_basic() {
BEGIN_TEST;
int var = 0;
{
auto do_incr = fit::defer([&var]() { incr_arg(&var); });
EXPECT_TRUE(do_incr);
auto do_incr2(std::move(do_incr));
EXPECT_FALSE(do_incr);
EXPECT_TRUE(do_incr2);
EXPECT_EQ(var, 0);
}
EXPECT_EQ(var, 1);
END_TEST;
}
bool move_construct_from_canceled() {
BEGIN_TEST;
int var = 0;
{
auto do_incr = fit::defer([&var]() { incr_arg(&var); });
EXPECT_TRUE(do_incr);
do_incr.cancel();
EXPECT_FALSE(do_incr);
auto do_incr2(std::move(do_incr));
EXPECT_FALSE(do_incr);
EXPECT_FALSE(do_incr2);
EXPECT_EQ(var, 0);
}
EXPECT_EQ(var, 0);
END_TEST;
}
bool move_construct_from_called() {
BEGIN_TEST;
int var = 0;
{
auto do_incr = fit::defer([&var]() { incr_arg(&var); });
EXPECT_TRUE(do_incr);
EXPECT_EQ(var, 0);
do_incr.call();
EXPECT_FALSE(do_incr);
EXPECT_EQ(var, 1);
// Must not be called again, since do_incr has triggered already.
auto do_incr2(std::move(do_incr));
EXPECT_FALSE(do_incr);
}
EXPECT_EQ(var, 1);
END_TEST;
}
bool move_assign_basic() {
BEGIN_TEST;
int var1 = 0, var2 = 0;
{
auto do_incr = fit::defer<fit::closure>([&var1]() { incr_arg(&var1); });
auto do_incr2 = fit::defer<fit::closure>([&var2]() { incr_arg(&var2); });
EXPECT_TRUE(do_incr);
EXPECT_TRUE(do_incr2);
EXPECT_EQ(var1, 0);
EXPECT_EQ(var2, 0);
// do_incr2 is moved-to, so its associated function is called.
do_incr2 = std::move(do_incr);
EXPECT_FALSE(do_incr);
EXPECT_TRUE(do_incr2);
EXPECT_EQ(var1, 0);
EXPECT_EQ(var2, 1);
}
EXPECT_EQ(var1, 1);
EXPECT_EQ(var2, 1);
END_TEST;
}
bool move_assign_wider_scoped() {
BEGIN_TEST;
int var1 = 0, var2 = 0;
{
auto do_incr = fit::defer<fit::closure>([&var1]() { incr_arg(&var1); });
EXPECT_TRUE(do_incr);
EXPECT_EQ(var1, 0);
EXPECT_EQ(var2, 0);
{
auto do_incr2 = fit::defer<fit::closure>([&var2]() { incr_arg(&var2); });
EXPECT_TRUE(do_incr);
EXPECT_TRUE(do_incr2);
EXPECT_EQ(var1, 0);
EXPECT_EQ(var2, 0);
// do_incr is moved-to, so its associated function is called.
do_incr = std::move(do_incr2);
EXPECT_TRUE(do_incr);
EXPECT_FALSE(do_incr2);
EXPECT_EQ(var1, 1);
EXPECT_EQ(var2, 0);
}
// do_incr2 is out of scope but has been moved so its function is not
// called.
EXPECT_TRUE(do_incr);
EXPECT_EQ(var1, 1);
EXPECT_EQ(var2, 0);
}
EXPECT_EQ(var1, 1);
EXPECT_EQ(var2, 1);
END_TEST;
}
bool move_assign_from_canceled() {
BEGIN_TEST;
int var1 = 0, var2 = 0;
{
auto do_incr = fit::defer<fit::closure>([&var1]() { incr_arg(&var1); });
auto do_incr2 = fit::defer<fit::closure>([&var2]() { incr_arg(&var2); });
EXPECT_TRUE(do_incr);
EXPECT_TRUE(do_incr2);
EXPECT_EQ(var1, 0);
EXPECT_EQ(var2, 0);
do_incr.cancel();
EXPECT_FALSE(do_incr);
EXPECT_TRUE(do_incr2);
EXPECT_EQ(var1, 0);
EXPECT_EQ(var2, 0);
// do_incr2 is moved-to, so its associated function is called.
do_incr2 = std::move(do_incr);
EXPECT_FALSE(do_incr);
EXPECT_FALSE(do_incr2);
EXPECT_EQ(var1, 0);
EXPECT_EQ(var2, 1);
}
// do_incr was cancelled, this state is preserved by the move.
EXPECT_EQ(var1, 0);
EXPECT_EQ(var2, 1);
END_TEST;
}
bool move_assign_from_called() {
BEGIN_TEST;
int var1 = 0, var2 = 0;
{
auto do_incr = fit::defer<fit::closure>([&var1]() { incr_arg(&var1); });
auto do_incr2 = fit::defer<fit::closure>([&var2]() { incr_arg(&var2); });
EXPECT_TRUE(do_incr);
EXPECT_TRUE(do_incr2);
EXPECT_EQ(var1, 0);
EXPECT_EQ(var2, 0);
do_incr.call();
EXPECT_FALSE(do_incr);
EXPECT_TRUE(do_incr2);
EXPECT_EQ(var1, 1);
EXPECT_EQ(var2, 0);
// do_incr2 is moved-to, so its associated function is called.
do_incr2 = std::move(do_incr);
EXPECT_FALSE(do_incr);
EXPECT_FALSE(do_incr2);
EXPECT_EQ(var1, 1);
EXPECT_EQ(var2, 1);
}
// do_incr was called already, this state is preserved by the move.
EXPECT_EQ(var1, 1);
EXPECT_EQ(var2, 1);
END_TEST;
}
bool target_destroyed_when_scope_exited() {
BEGIN_TEST;
int call_count = 0;
int instance_count = 0;
{
auto action = fit::defer(
[&call_count, balance = balance(&instance_count) ] {
incr_arg(&call_count);
});
EXPECT_EQ(0, call_count);
EXPECT_EQ(1, instance_count);
}
EXPECT_EQ(1, call_count);
EXPECT_EQ(0, instance_count);
END_TEST;
}
bool target_destroyed_when_called() {
BEGIN_TEST;
int call_count = 0;
int instance_count = 0;
{
auto action = fit::defer(
[&call_count, balance = balance(&instance_count) ] {
incr_arg(&call_count);
});
EXPECT_EQ(0, call_count);
EXPECT_EQ(1, instance_count);
action.call();
EXPECT_EQ(1, call_count);
EXPECT_EQ(0, instance_count);
}
EXPECT_EQ(1, call_count);
EXPECT_EQ(0, instance_count);
END_TEST;
}
bool target_destroyed_when_canceled() {
BEGIN_TEST;
int call_count = 0;
int instance_count = 0;
{
auto action = fit::defer(
[&call_count, balance = balance(&instance_count) ] {
incr_arg(&call_count);
});
EXPECT_EQ(0, call_count);
EXPECT_EQ(1, instance_count);
action.cancel();
EXPECT_EQ(0, call_count);
EXPECT_EQ(0, instance_count);
}
EXPECT_EQ(0, call_count);
EXPECT_EQ(0, instance_count);
END_TEST;
}
bool target_destroyed_when_move_constructed() {
BEGIN_TEST;
int call_count = 0;
int instance_count = 0;
{
auto action = fit::defer(
[&call_count, balance = balance(&instance_count) ] {
incr_arg(&call_count);
});
EXPECT_EQ(0, call_count);
EXPECT_EQ(1, instance_count);
auto action2(std::move(action));
EXPECT_EQ(0, call_count);
EXPECT_EQ(1, instance_count);
}
EXPECT_EQ(1, call_count);
EXPECT_EQ(0, instance_count);
END_TEST;
}
bool target_destroyed_when_move_assigned() {
BEGIN_TEST;
int call_count = 0;
int instance_count = 0;
{
auto action = fit::defer<fit::closure>(
[&call_count, balance = balance(&instance_count) ] {
incr_arg(&call_count);
});
EXPECT_EQ(0, call_count);
EXPECT_EQ(1, instance_count);
auto action2 = fit::defer<fit::closure>([] {});
action2 = std::move(action);
EXPECT_EQ(0, call_count);
EXPECT_EQ(1, instance_count);
}
EXPECT_EQ(1, call_count);
EXPECT_EQ(0, instance_count);
END_TEST;
}
} // namespace
BEGIN_TEST_CASE(defer_tests)
RUN_TEST(default_construction)
RUN_TEST(basic)
RUN_TEST(cancel)
RUN_TEST(call)
RUN_TEST(recursive_call)
RUN_TEST(move_construct_basic)
RUN_TEST(move_construct_from_canceled)
RUN_TEST(move_construct_from_called)
RUN_TEST(move_assign_basic)
RUN_TEST(move_assign_wider_scoped)
RUN_TEST(move_assign_from_canceled)
RUN_TEST(move_assign_from_called)
RUN_TEST(target_destroyed_when_scope_exited)
RUN_TEST(target_destroyed_when_called)
RUN_TEST(target_destroyed_when_canceled)
RUN_TEST(target_destroyed_when_move_constructed)
RUN_TEST(target_destroyed_when_move_assigned)
END_TEST_CASE(defer_tests)