blob: e663ec71388a94fd1dcd043de539b80e76869522 [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 "lib/fidl/cpp/binding_set.h"
#include <memory>
#include <zxtest/zxtest.h>
#include "lib/fidl/cpp/test/async_loop_for_test.h"
#include "lib/fidl/cpp/test/frobinator_impl.h"
namespace fidl {
namespace {
TEST(BindingSet, Trivial) { BindingSet<fidl::test::frobinator::Frobinator> binding_set; }
TEST(BindingSet, Control) {
constexpr size_t kCount = 10;
fidl::test::frobinator::FrobinatorPtr ptrs[kCount];
test::FrobinatorImpl impls[kCount];
BindingSet<fidl::test::frobinator::Frobinator> binding_set;
int empty_count = 0;
binding_set.set_empty_set_handler([&empty_count]() { ++empty_count; });
fidl::test::AsyncLoopForTest loop;
for (size_t i = 0; i < kCount; ++i) {
if (i % 2 == 0) {
binding_set.AddBinding(&impls[i], ptrs[i].NewRequest());
} else {
ptrs[i] = binding_set.AddBinding(&impls[i]).Bind();
}
}
EXPECT_EQ(kCount, binding_set.size());
for (const auto& impl : impls)
EXPECT_TRUE(impl.frobs.empty());
for (auto& ptr : ptrs)
ptr->Frob("one");
loop.RunUntilIdle();
for (const auto& impl : impls)
EXPECT_EQ(1u, impl.frobs.size());
for (size_t i = 0; i < kCount / 2; ++i)
ptrs[i].Unbind();
loop.RunUntilIdle();
EXPECT_EQ(kCount / 2, binding_set.size());
EXPECT_EQ(0, empty_count);
for (size_t i = kCount / 2; i < kCount; ++i)
ptrs[i]->Frob("two");
loop.RunUntilIdle();
for (size_t i = 0; i < kCount; ++i) {
size_t expected = (i < kCount / 2 ? 1 : 2);
EXPECT_EQ(expected, impls[i].frobs.size());
}
binding_set.CloseAll();
EXPECT_EQ(0u, binding_set.size());
EXPECT_EQ(0, empty_count);
for (size_t i = kCount / 2; i < kCount; ++i)
ptrs[i]->Frob("three");
loop.RunUntilIdle();
for (size_t i = 0; i < kCount; ++i) {
size_t expected = (i < kCount / 2 ? 1 : 2);
EXPECT_EQ(expected, impls[i].frobs.size());
}
}
TEST(BindingSet, Iterator) {
constexpr size_t kCount = 2;
fidl::test::frobinator::FrobinatorPtr ptrs[kCount];
test::FrobinatorImpl impls[kCount];
BindingSet<fidl::test::frobinator::Frobinator> binding_set;
fidl::test::AsyncLoopForTest loop;
for (size_t i = 0; i < kCount; i++)
ptrs[i] = binding_set.AddBinding(&impls[i]).Bind();
EXPECT_EQ(kCount, binding_set.size());
auto it = binding_set.bindings().begin();
EXPECT_EQ((*it)->impl(), &impls[0]);
++it;
EXPECT_EQ((*it)->impl(), &impls[1]);
++it;
EXPECT_EQ(it, binding_set.bindings().end());
}
TEST(BindingSet, EmptyHandler) {
constexpr size_t kCount = 4;
fidl::test::frobinator::FrobinatorPtr ptrs[kCount];
test::FrobinatorImpl impls[kCount];
BindingSet<fidl::test::frobinator::Frobinator> binding_set;
int empty_count = 0;
binding_set.set_empty_set_handler([&empty_count]() { ++empty_count; });
fidl::test::AsyncLoopForTest loop;
for (size_t i = 0; i < kCount; ++i)
binding_set.AddBinding(&impls[i], ptrs[i].NewRequest());
EXPECT_EQ(kCount, binding_set.size());
EXPECT_EQ(0, empty_count);
loop.RunUntilIdle();
EXPECT_EQ(0, empty_count);
for (size_t i = 0; i < kCount - 1; ++i)
ptrs[i].Unbind();
EXPECT_EQ(0, empty_count);
EXPECT_EQ(kCount, binding_set.size());
loop.RunUntilIdle();
EXPECT_EQ(0, empty_count);
EXPECT_EQ(1u, binding_set.size());
ptrs[kCount - 1].Unbind();
EXPECT_EQ(0, empty_count);
EXPECT_EQ(1u, binding_set.size());
loop.RunUntilIdle();
EXPECT_EQ(1, empty_count);
EXPECT_EQ(0u, binding_set.size());
}
TEST(BindingSet, EmptyHandlerOnManualClose) {
fidl::test::frobinator::FrobinatorPtr ptr;
test::FrobinatorImpl impl;
BindingSet<fidl::test::frobinator::Frobinator> binding_set;
int empty_count = 0;
binding_set.set_empty_set_handler([&empty_count]() { ++empty_count; });
fidl::test::AsyncLoopForTest loop;
// Add the binding.
binding_set.AddBinding(&impl, ptr.NewRequest());
EXPECT_EQ(1u, binding_set.size());
EXPECT_EQ(0, empty_count);
// Run till idle, nothing should change.
loop.RunUntilIdle();
EXPECT_EQ(1u, binding_set.size());
EXPECT_EQ(0, empty_count);
// Unbind and wait until the binding has been removed from the binding set.
ptr.Unbind();
loop.RunUntilIdle();
EXPECT_EQ(0u, binding_set.size());
EXPECT_EQ(1, empty_count);
// Run till idle, nothing should change.
loop.RunUntilIdle();
EXPECT_EQ(0u, binding_set.size());
EXPECT_EQ(1, empty_count);
// Unbinding should not do anything since it is already not part of the set.
ptr.Unbind();
loop.RunUntilIdle();
EXPECT_EQ(0u, binding_set.size());
EXPECT_EQ(1, empty_count);
}
TEST(BindingSet, BindingDestroyedAfterRemovalFromSet) {
fidl::test::AsyncLoopForTest loop;
BindingSet<fidl::test::frobinator::Frobinator, std::unique_ptr<test::FrobinatorImpl>> binding_set;
auto check_binding_set_empty_on_destroy = [&binding_set] {
EXPECT_TRUE(binding_set.bindings().empty());
};
fidl::test::frobinator::FrobinatorPtr ptr;
// Add the binding.
binding_set.AddBinding(std::make_unique<test::FrobinatorImpl>(check_binding_set_empty_on_destroy),
ptr.NewRequest());
EXPECT_EQ(1u, binding_set.size());
// Unbind and wait until the binding has been removed from the binding set.
ptr.Unbind();
loop.RunUntilIdle();
EXPECT_TRUE(binding_set.bindings().empty());
}
TEST(BindingSet, BindingDestroyedAfterCloseAll) {
fidl::test::AsyncLoopForTest loop;
BindingSet<fidl::test::frobinator::Frobinator, std::unique_ptr<test::FrobinatorImpl>> binding_set;
auto check_binding_set_empty_on_destroy = [&binding_set] {
EXPECT_TRUE(binding_set.bindings().empty());
};
fidl::test::frobinator::FrobinatorPtr ptr;
// Add the binding.
binding_set.AddBinding(std::make_unique<test::FrobinatorImpl>(check_binding_set_empty_on_destroy),
ptr.NewRequest());
EXPECT_EQ(1u, binding_set.size());
binding_set.CloseAll();
loop.RunUntilIdle();
EXPECT_TRUE(binding_set.bindings().empty());
}
TEST(BindingSet, EpitaphSentWithCloseAll) {
fidl::test::AsyncLoopForTest loop;
BindingSet<fidl::test::frobinator::Frobinator, std::unique_ptr<test::FrobinatorImpl>> binding_set;
// Attach an error handler.
fidl::test::frobinator::FrobinatorPtr ptr;
bool client_error_handler_invoked = false;
zx_status_t client_error_handler_status = ZX_OK;
ptr.set_error_handler([&](zx_status_t status) {
client_error_handler_status = status;
client_error_handler_invoked = true;
});
// Add the binding.
binding_set.AddBinding(std::make_unique<test::FrobinatorImpl>(), ptr.NewRequest());
EXPECT_EQ(1u, binding_set.size());
constexpr auto kEpitaphValue = ZX_ERR_ADDRESS_UNREACHABLE;
binding_set.CloseAll(kEpitaphValue);
loop.RunUntilIdle();
EXPECT_TRUE(binding_set.bindings().empty());
ASSERT_TRUE(client_error_handler_invoked);
EXPECT_EQ(client_error_handler_status, kEpitaphValue);
}
TEST(BindingSet, EpitaphSentWithClose) {
fidl::test::AsyncLoopForTest loop;
BindingSet<fidl::test::frobinator::Frobinator, test::FrobinatorImpl*> binding_set;
// Attach an error handler.
fidl::test::frobinator::FrobinatorPtr ptr;
bool client_error_handler_invoked = false;
zx_status_t client_error_handler_status = ZX_OK;
ptr.set_error_handler([&](zx_status_t status) {
client_error_handler_status = status;
client_error_handler_invoked = true;
});
test::FrobinatorImpl impl;
// Add the binding.
binding_set.AddBinding(&impl, ptr.NewRequest());
EXPECT_EQ(1u, binding_set.size());
constexpr auto kEpitaphValue = ZX_ERR_ADDRESS_UNREACHABLE;
binding_set.CloseBinding(&impl, kEpitaphValue);
loop.RunUntilIdle();
EXPECT_TRUE(binding_set.bindings().empty());
ASSERT_TRUE(client_error_handler_invoked);
EXPECT_EQ(client_error_handler_status, kEpitaphValue);
}
TEST(BindingSet, CloseBindingsHandlesEmptySet) {
fidl::test::AsyncLoopForTest loop;
BindingSet<fidl::test::frobinator::Frobinator, test::FrobinatorImpl*> binding_set;
bool empty_set_handled = false;
binding_set.set_empty_set_handler([&] { empty_set_handled = true; });
fidl::test::frobinator::FrobinatorPtr ptr;
test::FrobinatorImpl impl;
fidl::test::frobinator::FrobinatorPtr other_ptr;
test::FrobinatorImpl other_impl;
// Add the bindings.
binding_set.AddBinding(&impl, ptr.NewRequest());
binding_set.AddBinding(&other_impl, other_ptr.NewRequest());
loop.RunUntilIdle();
EXPECT_EQ(2u, binding_set.size());
EXPECT_FALSE(empty_set_handled);
// Check that the empty_set_handler is not called when the first binding is removed.
constexpr auto kEpitaphValue = ZX_ERR_ADDRESS_UNREACHABLE;
binding_set.CloseBinding(&impl, kEpitaphValue);
loop.RunUntilIdle();
EXPECT_FALSE(empty_set_handled);
// Check that the empty_set_handler is called when the last binding is removed.
binding_set.CloseBinding(&other_impl, kEpitaphValue);
loop.RunUntilIdle();
EXPECT_TRUE(empty_set_handled);
}
TEST(BindingSet, RemoveBindingDeletesTheBinding) {
fidl::test::AsyncLoopForTest loop;
BindingSet<fidl::test::frobinator::Frobinator, test::FrobinatorImpl*> binding_set;
auto check_binding_set_empty_on_destroy = [&binding_set] {
EXPECT_TRUE(binding_set.bindings().empty());
};
test::FrobinatorImpl frobinator(check_binding_set_empty_on_destroy);
fidl::test::frobinator::FrobinatorPtr ptr;
// Add the binding.
binding_set.AddBinding(&frobinator, ptr.NewRequest());
EXPECT_EQ(1u, binding_set.size());
// Remove the binding.
EXPECT_TRUE(binding_set.RemoveBinding(&frobinator));
EXPECT_TRUE(binding_set.bindings().empty());
// Try to remove the binding again.
EXPECT_FALSE(binding_set.RemoveBinding(&frobinator));
}
TEST(BindingSet, ErrorHandlerCalledAfterError) {
fidl::test::AsyncLoopForTest loop;
BindingSet<fidl::test::frobinator::Frobinator, std::unique_ptr<test::FrobinatorImpl>> binding_set;
auto check_binding_set_empty_on_destroy = [&binding_set] {
EXPECT_TRUE(binding_set.bindings().empty());
};
fidl::test::frobinator::FrobinatorPtr ptr;
bool handler_called = false;
// Add the binding.
binding_set.AddBinding(std::make_unique<test::FrobinatorImpl>(check_binding_set_empty_on_destroy),
ptr.NewRequest(), nullptr,
[&handler_called](zx_status_t) { handler_called = true; });
EXPECT_FALSE(handler_called);
// Trigger error.
ptr.Unbind();
loop.RunUntilIdle();
EXPECT_TRUE(handler_called);
}
TEST(BindingSet, ErrorHandlerDestroysBindingSetAndBindings) {
fidl::test::AsyncLoopForTest loop;
BindingSet<fidl::test::frobinator::Frobinator, test::FrobinatorImpl*> binding_set;
auto check_binding_set_empty_on_destroy = [&binding_set] {
EXPECT_TRUE(binding_set.bindings().empty());
};
test::FrobinatorImpl frobinator(check_binding_set_empty_on_destroy);
fidl::test::frobinator::FrobinatorPtr ptr;
bool handler_called = false;
// Add the binding.
binding_set.AddBinding(&frobinator, ptr.NewRequest(), nullptr,
[&handler_called, &binding_set, &frobinator](zx_status_t) {
binding_set.RemoveBinding(&frobinator);
binding_set.CloseAll();
handler_called = true;
});
EXPECT_FALSE(handler_called);
// Trigger error.
ptr.Unbind();
loop.RunUntilIdle();
EXPECT_TRUE(handler_called);
}
TEST(BindingSet, ErrorHandlerDestroysBindingSetAndBindingsWithUniquePtr) {
fidl::test::AsyncLoopForTest loop;
BindingSet<fidl::test::frobinator::Frobinator, std::unique_ptr<test::FrobinatorImpl>> binding_set;
auto check_binding_set_empty_on_destroy = [&binding_set] {
EXPECT_TRUE(binding_set.bindings().empty());
};
auto frobinator = std::make_unique<test::FrobinatorImpl>(check_binding_set_empty_on_destroy);
test::FrobinatorImpl* frobinator_raw_ptr = frobinator.get();
fidl::test::frobinator::FrobinatorPtr ptr;
bool handler_called = false;
// Add the binding.
binding_set.AddBinding(std::move(frobinator), ptr.NewRequest(), nullptr,
[&handler_called, &binding_set, frobinator_raw_ptr](zx_status_t) {
binding_set.RemoveBinding(frobinator_raw_ptr);
binding_set.CloseAll();
handler_called = true;
});
EXPECT_FALSE(handler_called);
// Trigger error.
ptr.Unbind();
loop.RunUntilIdle();
EXPECT_TRUE(handler_called);
}
TEST(BindingSet, ErrorHandlerFunctionMoveOnly) {
BindingSet<fidl::test::frobinator::Frobinator> binding_set;
fidl::test::AsyncLoopForTest loop;
// Use the reference counting of a `shared_ptr` canary to make sure that the lambda capture
// context is never copied.
auto ref_counted_canary = std::make_shared<int>(1337);
std::weak_ptr<int> ref_counted_canary_weak = ref_counted_canary;
ASSERT_EQ(ref_counted_canary_weak.use_count(), 1);
std::unique_ptr<int> force_move_only;
test::FrobinatorImpl impl;
fidl::test::frobinator::FrobinatorPtr ptr;
binding_set.AddBinding(&impl, ptr.NewRequest(), nullptr,
[ref_counted_canary = std::move(ref_counted_canary),
force_move_only = std::move(force_move_only)](zx_status_t) {});
// There still should only be one reference to the canary (the lambda capture).
ASSERT_EQ(ref_counted_canary_weak.use_count(), 1);
// Trigger error.
ptr.Unbind();
loop.RunUntilIdle();
// The canary should've been dropped and cleaned up.
ASSERT_TRUE(ref_counted_canary_weak.expired());
}
TEST(BindingSet, ErrorHandlerFunctionValidWhenInvoked) {
// Declared outside of `MoveOnlyCanary` because local classes may not have static members.
constexpr uint64_t kAliveCanaryValue = 0x1234567890ABCDEF;
constexpr uint64_t kMovedFromCanaryValue = 0xFEDCBA0987654321;
constexpr uint64_t kDeadCanaryValue = 0;
static_assert(kAliveCanaryValue != kMovedFromCanaryValue &&
kAliveCanaryValue != kDeadCanaryValue &&
kMovedFromCanaryValue != kDeadCanaryValue,
"Different canary values cannot be equivalent");
// A move-only type with a destructor that uses a canary to detect whether it has previously been
// destructed.
class MoveOnlyCanary {
public:
constexpr MoveOnlyCanary() = default;
MoveOnlyCanary(MoveOnlyCanary&& other) : canary_(other.TakeCanary()) {}
MoveOnlyCanary& operator=(MoveOnlyCanary&& other) {
if (this == &other) {
return *this;
}
canary_ = other.TakeCanary();
return *this;
}
MoveOnlyCanary(const MoveOnlyCanary&) = delete;
MoveOnlyCanary& operator=(const MoveOnlyCanary&) = delete;
~MoveOnlyCanary() { Kill(); }
bool IsAlive() const { return canary_ == kAliveCanaryValue; }
bool IsMovedFrom() const { return canary_ == kMovedFromCanaryValue; }
private:
void Kill() {
ASSERT_TRUE(IsMovedFrom() || IsAlive());
canary_ = kDeadCanaryValue;
}
uint64_t TakeCanary() {
EXPECT_TRUE(IsAlive());
uint64_t ret = canary_;
canary_ = kMovedFromCanaryValue;
return ret;
}
uint64_t canary_ = kAliveCanaryValue;
};
BindingSet<fidl::test::frobinator::Frobinator> binding_set;
fidl::test::AsyncLoopForTest loop;
test::FrobinatorImpl impl;
fidl::test::frobinator::FrobinatorPtr ptr;
MoveOnlyCanary canary;
ASSERT_TRUE(canary.IsAlive());
bool handler_called = false;
binding_set.AddBinding(
&impl, ptr.NewRequest(), nullptr,
[canary = std::move(canary), &handler_called](zx_status_t) { handler_called = true; });
// Trigger error.
ptr.Unbind();
loop.RunUntilIdle();
ASSERT_TRUE(handler_called);
}
} // namespace
} // namespace fidl