| // 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 <gtest/gtest.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 |