blob: 24c5bc8bddec0f35955ae0d2cdb6a9a3ff140900 [file] [log] [blame]
// Copyright 2022 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 <fidl/fidl.cpp.wire.bindinggroup.test/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl/cpp/wire/server.h>
#include <zircon/fidl.h>
#include <zircon/syscalls.h>
#include <thread>
#include <vector>
#include <zxtest/zxtest.h>
namespace {
using ::fidl_cpp_wire_bindinggroup_test::Testable;
constexpr zx_status_t kTestEpitaph = 1234;
constexpr char kSimpleEcho[] = "test";
// These are the default values for the number of impls and number of bindings per impl when
// testing. Some test running functions may use template parameters to override these as needed.
const size_t kTestNumImpls = 2;
const size_t kTestNumBindingsPerImpl = 2;
struct TestImpl : fidl::Server<Testable> {
public:
explicit TestImpl(async::Loop* loop) : loop(loop) {}
void Echo(EchoRequest& request, EchoCompleter::Sync& completer) override {
echo_count_++;
completer.Reply(request.str());
}
// Always abruptly close the connection. This is not a good implementation to copy - its just
// useful to check that close handling works properly.
void Terminate(TerminateCompleter::Sync& completer) override {
terminate_count_++;
completer.Close(kTestEpitaph);
}
// Fired whenever the binding is closed via a |Close*| call on its parent |ServerBindingGroup|.
void close_handler_fired() { close_count_++; }
size_t get_echo_count() const { return echo_count_; }
size_t get_close_count() const { return close_count_; }
size_t get_terminate_count() const { return terminate_count_; }
async::Loop* loop = nullptr;
private:
size_t close_count_ = 0;
size_t echo_count_ = 0;
size_t terminate_count_ = 0;
};
constexpr auto kCloseHandler = [](TestImpl* impl, fidl::UnbindInfo info) {
impl->close_handler_fired();
EXPECT_TRUE(info.did_send_epitaph());
EXPECT_EQ(fidl::Reason::kClose, info.reason());
EXPECT_OK(info.status());
};
TEST(BindingGroup, Trivial) { fidl::ServerBindingGroup<Testable> group; }
// Tests simple patterns for adding various numbers of bindings for various numbers of
// implementations. Additionally, this test template tests that the |size| and |ForEachBinding|
// methods work as expected.
template <size_t NumImpls = kTestNumImpls, size_t NumBindingsPerImpl = kTestNumBindingsPerImpl>
void AddBindingTest() {
constexpr size_t TotalServerBindings = NumImpls * NumBindingsPerImpl;
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
// Because we're going to be using raw pointers into these vectors for the test, its important to
// pre-allocate, so that the underlying storage doesn't move.
std::vector<TestImpl> impls;
std::vector<fidl::WireClient<Testable>> clients;
impls.reserve(NumImpls);
clients.reserve(TotalServerBindings);
// Data we are tracking for the duration of the test which we will assert against.
std::map<const TestImpl*, size_t> unvisited_bindings_per_impl;
fidl::ServerBindingGroup<Testable> group;
// Create the right number of bindings for each impl as requested. Hold on to the client so that
// we may poke at it later.
for (size_t i = 0; i < NumImpls; i++) {
impls.emplace_back(&loop);
for (size_t j = i * NumBindingsPerImpl; j < (i + 1) * NumBindingsPerImpl; j++) {
auto endpoints = fidl::Endpoints<Testable>::Create();
group.AddBinding(loop.dispatcher(), std::move(endpoints.server), &impls.back(),
kCloseHandler);
clients.emplace_back(std::move(endpoints.client), loop.dispatcher());
}
unvisited_bindings_per_impl.insert({&impls.back(), NumBindingsPerImpl});
}
EXPECT_EQ(group.size(), TotalServerBindings);
EXPECT_EQ(clients.size(), TotalServerBindings);
EXPECT_EQ(unvisited_bindings_per_impl.size(), NumImpls);
// Make an |Echo| call on each |client| to ensure that its binding is actually responsive.
for (auto& client : clients) {
client->Echo(kSimpleEcho).ThenExactlyOnce([&](fidl::WireUnownedResult<Testable::Echo>& result) {
ASSERT_TRUE(result.ok());
EXPECT_OK(result.status());
EXPECT_EQ(result.value().str.get(), kSimpleEcho);
});
// Run the loop until the callback is resolved.
loop.RunUntilIdle();
}
// Ensure that each |impl| was called the number of times that we expect.
for (const auto& impl : impls) {
EXPECT_EQ(impl.get_echo_count(), NumBindingsPerImpl);
}
// Visit each binding, matching its implementation to one of the ones we're storing in the |impls|
// array. Decrement the count in that array on each visit.
size_t bindings_visited = 0;
group.ForEachBinding([&](const fidl::ServerBinding<Testable>& binding) {
bool matched = false;
bindings_visited++;
binding.AsImpl<TestImpl>([&](const TestImpl* binding_impl) {
auto matched_impl = unvisited_bindings_per_impl.find(binding_impl);
EXPECT_NE(matched_impl, unvisited_bindings_per_impl.end());
matched = true;
matched_impl->second--;
});
EXPECT_TRUE(matched);
});
// Because the previous loop decremented the count for each impl visited, we can iterate over the
// |unvisited_bindings_per_impl| counters to ensure that they are all zero, confirming that every
// |impl| has been visited the appropriate number of times.
EXPECT_EQ(bindings_visited, TotalServerBindings);
for (const auto& impl : unvisited_bindings_per_impl) {
EXPECT_EQ(impl.second, 0);
}
}
TEST(BindingGroup, AddBindingOneImplWithOneBinding) { AddBindingTest<1, 1>(); }
TEST(BindingGroup, AddBindingOneImplWithManyBindings) { AddBindingTest<1, 2>(); }
TEST(BindingGroup, AddBindingManyImplsWithOneBindingEach) { AddBindingTest<2, 1>(); }
TEST(BindingGroup, AddBindingManyImplsWithManyBindingsEach) { AddBindingTest<2, 2>(); }
// Tests adding bindings using the generator produced by the |CreateHandler| method.
template <size_t NumImpls = kTestNumImpls, size_t NumBindingsPerImpl = kTestNumBindingsPerImpl>
void CreateHandlerTest() {
constexpr size_t TotalServerBindings = NumImpls * NumBindingsPerImpl;
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
// Because we're going to be using raw pointers into these vectors for the test, its important to
// pre-allocate, so that the underlying storage doesn't move.
std::vector<TestImpl> impls;
std::vector<fidl::WireClient<Testable>> clients;
impls.reserve(NumImpls);
clients.reserve(TotalServerBindings);
fidl::ServerBindingGroup<Testable> group;
// Create the right number of bindings for each impl as requested. Hold on to the client so that
// we may poke at it later.
for (size_t i = 0; i < NumImpls; i++) {
impls.emplace_back(&loop);
auto handler = group.CreateHandler(&impls.back(), loop.dispatcher(), kCloseHandler);
for (size_t j = i * NumBindingsPerImpl; j < (i + 1) * NumBindingsPerImpl; j++) {
auto endpoints = fidl::Endpoints<Testable>::Create();
handler(std::move(endpoints.server));
clients.emplace_back(std::move(endpoints.client), loop.dispatcher());
}
}
EXPECT_EQ(group.size(), TotalServerBindings);
EXPECT_EQ(clients.size(), TotalServerBindings);
// Make an |Echo| call on each |client| to ensure that its binding is actually responsive.
for (auto& client : clients) {
client->Echo(kSimpleEcho).ThenExactlyOnce([&](fidl::WireUnownedResult<Testable::Echo>& result) {
ASSERT_TRUE(result.ok());
EXPECT_OK(result.status());
EXPECT_EQ(result.value().str.get(), kSimpleEcho);
});
// Run the loop until the callback is resolved.
loop.RunUntilIdle();
}
// Ensure that each |impl| was called the number of times that we expect.
for (const auto& impl : impls) {
EXPECT_EQ(impl.get_echo_count(), NumBindingsPerImpl);
}
}
TEST(BindingGroup, CreateHandlerOneImplWithOneBinding) { CreateHandlerTest<1, 1>(); }
TEST(BindingGroup, CreateHandlerOneImplWithManyBindings) { CreateHandlerTest<1, 2>(); }
TEST(BindingGroup, CreateHandlerManyImplsWithOneBindingEach) { CreateHandlerTest<2, 1>(); }
TEST(BindingGroup, CreateHandlerManyImplsWithManyBindingsEach) { CreateHandlerTest<2, 2>(); }
// Tests that |CloseHandler| functions are correctly passed to, and fired by, bindings in the group.
template <size_t NumImpls = kTestNumImpls, size_t NumBindingsPerImpl = kTestNumBindingsPerImpl>
void CloseHandlerTest() {
constexpr size_t TotalServerBindings = NumImpls * NumBindingsPerImpl;
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
// Because we're going to be using raw pointers into these vectors for the test, its important to
// pre-allocate, so that the underlying storage doesn't move.
std::vector<TestImpl> impls;
std::vector<fidl::WireClient<Testable>> clients;
impls.reserve(NumImpls);
clients.reserve(TotalServerBindings);
// Data we are tracking for the duration of the test which we will assert against.
size_t empty_set_handler_call_count = 0;
fidl::ServerBindingGroup<Testable> group;
// Add an |empty_handler| lambda to the |group|, and ensure that it only gets called once.
group.set_empty_set_handler([&]() { empty_set_handler_call_count++; });
// Create the right number of bindings for each impl as requested. Hold on to the client so that
// we may poke at it later.
for (size_t i = 0; i < NumImpls; i++) {
impls.emplace_back(&loop);
for (size_t j = i * NumBindingsPerImpl; j < (i + 1) * NumBindingsPerImpl; j++) {
auto endpoints = fidl::Endpoints<Testable>::Create();
group.AddBinding(loop.dispatcher(), std::move(endpoints.server), &impls.back(),
kCloseHandler);
clients.emplace_back(std::move(endpoints.client), loop.dispatcher());
}
}
EXPECT_EQ(group.size(), TotalServerBindings);
EXPECT_EQ(clients.size(), TotalServerBindings);
// Make a |Terminate| call on each |client| to ensure that it is abruptly torn down.
for (auto& client : clients) {
EXPECT_EQ(empty_set_handler_call_count, 0);
auto result = client->Terminate();
EXPECT_TRUE(result.ok());
// Run the loop until the close handlers are resolved.
loop.RunUntilIdle();
}
// Ensure that each |impl| was closed the number of times that we expect. In this case, that means
// that every closure came from a |Terminate| method call on the client, and that every binding
// was closed in this manner.
for (const auto& impl : impls) {
EXPECT_EQ(impl.get_terminate_count(), impl.get_close_count());
EXPECT_EQ(impl.get_terminate_count(), NumBindingsPerImpl);
}
// Ensure that empty handler was only called once, after the last binding resolved its |Terminate|
// handler.
EXPECT_EQ(empty_set_handler_call_count, 1);
}
TEST(BindingGroup, CloseHandlerOneImplWithOneBinding) { CloseHandlerTest<1, 1>(); }
TEST(BindingGroup, CloseHandlerOneImplWithManyBindings) { CloseHandlerTest<1, 2>(); }
TEST(BindingGroup, CloseHandlerManyImplsWithOneBindingEach) { CloseHandlerTest<2, 1>(); }
TEST(BindingGroup, CloseHandlerManyImplsWithManyBindingsEach) { CloseHandlerTest<2, 2>(); }
using KillSomeBindings = fit::function<void(fidl::ServerBindingGroup<Testable>& group,
async::Loop* loop, std::vector<TestImpl>& impls,
std::map<size_t, const TestImpl*>& open_bindings)>;
// Tests that calling methods in the |Close*()| and |Remove*()| families works as expected. The
// |KillSomeBindings| lambda is used to |Remove*| or |Close*| some number of bindings as the
// specific test requires.
void ExternalKillBindingTest(KillSomeBindings kill_some_bindings) {
constexpr size_t TotalServerBindings = kTestNumImpls * kTestNumBindingsPerImpl;
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
class EventHandler : public fidl::WireAsyncEventHandler<Testable> {
public:
void on_fidl_error(fidl::UnbindInfo info) override {
ASSERT_TRUE(info.is_peer_closed());
closed_ = true;
}
bool closed() const { return closed_; }
private:
bool closed_ = false;
};
// Because we're going to be using raw pointers into these vectors for the test, its important to
// pre-allocate, so that the underlying storage doesn't move.
std::vector<TestImpl> impls;
std::vector<fidl::WireClient<Testable>> clients;
std::vector<EventHandler> event_handlers;
impls.reserve(kTestNumImpls);
clients.reserve(TotalServerBindings);
event_handlers.reserve(TotalServerBindings);
// Data we are tracking for the duration of the test which we will assert against. The
// |kill_some_bindings| handler should replace all entries in |open_bindings| with |nullptr| when
// killing them.
std::map<size_t, const TestImpl*> open_bindings;
size_t empty_set_handler_call_count = 0;
// Create the |group| under test, and define a counter-decrementing |close_handler| to attach to
// each binding it holds.
fidl::ServerBindingGroup<Testable> group;
// Add an |empty_handler| lambda to the |group|, incrementing a simple counter each time it gets
// called.
group.set_empty_set_handler([&]() { empty_set_handler_call_count++; });
// Create the right number of bindings for each impl as requested. Hold on to the client so that
// we may poke at it later.
for (size_t i = 0; i < kTestNumImpls; i++) {
impls.emplace_back(&loop);
for (size_t j = i * kTestNumBindingsPerImpl; j < (i + 1) * kTestNumBindingsPerImpl; j++) {
auto endpoints = fidl::Endpoints<Testable>::Create();
group.AddBinding(loop.dispatcher(), std::move(endpoints.server), &impls.back(),
kCloseHandler);
event_handlers.emplace_back();
clients.emplace_back(std::move(endpoints.client), loop.dispatcher(), &event_handlers.back());
open_bindings.insert({j, &impls.back()});
}
}
EXPECT_EQ(group.size(), TotalServerBindings);
EXPECT_EQ(clients.size(), TotalServerBindings);
// Call the |kill_some_bindings| lambda to kill the servers that the test requires.
kill_some_bindings(group, &loop, impls, open_bindings);
loop.RunUntilIdle();
// Make a |Terminate| call on each remaining |client| to ensure that it is abruptly torn down.
for (const auto& open_binding : open_bindings) {
// If a |open_binding| is set to |nullptr|, that means the test body
// explicitly removed/closed the binding, and we verify this has happened.
// Otherwise, call |Terminate| to tell the server to close it.
if (open_binding.second != nullptr) {
EXPECT_FALSE(event_handlers[open_binding.first].closed());
auto result = clients[open_binding.first]->Terminate();
EXPECT_OK(result.status());
} else {
EXPECT_TRUE(event_handlers[open_binding.first].closed());
}
// Run the loop until the close handlers are resolved.
loop.RunUntilIdle();
}
// All connections should now be closed.
for (const auto& open_binding : open_bindings) {
EXPECT_TRUE(event_handlers[open_binding.first].closed());
}
// Ensure that empty handler was only called once, after the last binding resolved its |Terminate|
// handler.
EXPECT_EQ(empty_set_handler_call_count, 1);
}
TEST(BindingGroup, RemoveBindings) {
ExternalKillBindingTest([](fidl::ServerBindingGroup<Testable>& group, async::Loop* loop,
std::vector<TestImpl>& impls,
std::map<size_t, const TestImpl*>& open_bindings) {
const TestImpl* target_impl = &impls[0];
EXPECT_EQ(group.RemoveBindings(target_impl), true);
EXPECT_EQ(group.RemoveBindings(target_impl), false);
EXPECT_EQ(group.size(), 2);
loop->RunUntilIdle();
// Ensure that no close counters were incremented, since this was merely a removal.
for (auto& impl : open_bindings) {
EXPECT_EQ(impl.second->get_close_count(), 0);
}
// Mark the removed bindings as killed, so that the rest of the test knows which bindings
// should and should not be closed via the client making a |Terminate| call.
for (const auto& open_binding : open_bindings) {
if (open_binding.second == target_impl) {
open_bindings[open_binding.first] = nullptr;
}
}
});
}
TEST(BindingGroup, RemoveAll) {
ExternalKillBindingTest([](fidl::ServerBindingGroup<Testable>& group, async::Loop* loop,
std::vector<TestImpl>& impls,
std::map<size_t, const TestImpl*>& open_bindings) {
EXPECT_EQ(group.RemoveAll(), true);
EXPECT_EQ(group.RemoveAll(), false);
EXPECT_EQ(group.size(), 0);
loop->RunUntilIdle();
// Ensure that no close counters were incremented, since this was merely a removal.
for (auto& impl : open_bindings) {
EXPECT_EQ(impl.second->get_close_count(), 0);
}
// Mark the removed bindings as killed, so that the rest of the test knows which bindings
// should and should not be closed via the client making a |Terminate| call.
for (const auto& open_binding : open_bindings) {
open_bindings[open_binding.first] = nullptr;
}
});
}
TEST(BindingGroup, CloseBindings) {
ExternalKillBindingTest([](fidl::ServerBindingGroup<Testable>& group, async::Loop* loop,
std::vector<TestImpl>& impls,
std::map<size_t, const TestImpl*>& open_bindings) {
const TestImpl* target_impl = &impls[0];
EXPECT_EQ(group.CloseBindings(target_impl, kTestEpitaph), true);
EXPECT_EQ(group.CloseBindings(target_impl, kTestEpitaph), false);
EXPECT_EQ(group.size(), 2);
// Run the loop until the close handlers are resolved.
loop->RunUntilIdle();
// Ensure that the close handler was fired for the closed binding's |impl| the correct
// number of times.
EXPECT_EQ(target_impl->get_close_count(), 2);
for (const auto& impl : impls) {
if (&impl != target_impl) {
EXPECT_EQ(impl.get_close_count(), 0);
}
}
// Mark the closed binding as killed, so that the rest of the test knows which bindings
// should and should not be closed via the client making a |Terminate| call.
for (const auto& open_binding : open_bindings) {
if (open_binding.second == target_impl) {
open_bindings[open_binding.first] = nullptr;
}
}
});
}
TEST(BindingGroup, CloseAll) {
ExternalKillBindingTest([](fidl::ServerBindingGroup<Testable>& group, async::Loop* loop,
std::vector<TestImpl>& impls,
std::map<size_t, const TestImpl*>& open_bindings) {
EXPECT_EQ(group.CloseAll(kTestEpitaph), true);
EXPECT_EQ(group.CloseAll(kTestEpitaph), false);
EXPECT_EQ(group.size(), 0);
// Run the loop until the close handlers are resolved.
loop->RunUntilIdle();
// Ensure that the close handler was fired for the closed bindings' |impl|s the correct
// number of times.
for (const auto& impl : impls) {
EXPECT_EQ(impl.get_close_count(), 2);
}
// Mark the closed bindings as killed, so that the rest of the test knows which bindings
// should and should not be closed via the client making a |Terminate| call.
for (const auto& open_binding : open_bindings) {
open_bindings[open_binding.first] = nullptr;
}
});
}
TEST(BindingGroup, CannotRemoveAfterClose) {
ExternalKillBindingTest([](fidl::ServerBindingGroup<Testable>& group, async::Loop* loop,
std::vector<TestImpl>& impls,
std::map<size_t, const TestImpl*>& open_bindings) {
const TestImpl* target_impl = &impls[1];
EXPECT_EQ(group.CloseBindings(target_impl, kTestEpitaph), true);
EXPECT_EQ(group.RemoveBindings(target_impl), false);
EXPECT_EQ(group.size(), 2);
EXPECT_EQ(group.CloseAll(kTestEpitaph), true);
EXPECT_EQ(group.RemoveAll(), false);
EXPECT_EQ(group.size(), 0);
// Run the loop until the close handlers are resolved.
loop->RunUntilIdle();
// Ensure that the close handler was fired for the closed bindings' |impl|s the correct
// number of times.
for (const auto& impl : impls) {
EXPECT_EQ(impl.get_close_count(), 2);
}
// Mark the closed bindings as killed, so that the rest of the test knows which bindings
// should and should not be closed via the client making a |Terminate| call.
for (const auto& open_binding : open_bindings) {
open_bindings[open_binding.first] = nullptr;
}
});
}
TEST(BindingGroup, CannotCloseAfterRemove) {
ExternalKillBindingTest([](fidl::ServerBindingGroup<Testable>& group, async::Loop* loop,
std::vector<TestImpl>& impls,
std::map<size_t, const TestImpl*>& open_bindings) {
const TestImpl* target_impl = &impls[1];
EXPECT_EQ(group.RemoveBindings(target_impl), true);
EXPECT_EQ(group.CloseBindings(target_impl, kTestEpitaph), false);
EXPECT_EQ(group.size(), 2);
EXPECT_EQ(group.RemoveAll(), true);
EXPECT_EQ(group.CloseAll(kTestEpitaph), false);
EXPECT_EQ(group.size(), 0);
loop->RunUntilIdle();
// Ensure that no close counters were incremented, since this was merely a removal.
for (auto& impl : open_bindings) {
EXPECT_EQ(impl.second->get_close_count(), 0);
}
// Mark the removed bindings as killed, so that the rest of the test knows which bindings
// should and should not be closed via the client making a |Terminate| call.
for (const auto& open_binding : open_bindings) {
open_bindings[open_binding.first] = nullptr;
}
});
}
// These classes are used to create a server implementation with multiple
// inheritance.
class PlaceholderBase1 {
public:
virtual void Foo() = 0;
int a;
};
class PlaceholderBase2 {
public:
virtual void Bar() = 0;
int b;
};
class MultiInheritanceServer : public PlaceholderBase1,
public fidl::Server<fidl_cpp_wire_bindinggroup_test::Foo>,
public fidl::Server<Testable>,
public fidl::WireServer<fidl_cpp_wire_bindinggroup_test::Bar>,
public PlaceholderBase2 {
public:
MultiInheritanceServer() = default;
MultiInheritanceServer(MultiInheritanceServer&& other) = delete;
MultiInheritanceServer(const MultiInheritanceServer& other) = delete;
MultiInheritanceServer& operator=(MultiInheritanceServer&& other) = delete;
MultiInheritanceServer& operator=(const MultiInheritanceServer& other) = delete;
void Foo() override {}
void Bar() override {}
void Echo(EchoRequest& request, EchoCompleter::Sync& completer) override {
completer.Reply(request.str());
}
void Terminate(TerminateCompleter::Sync& completer) override { completer.Close(kTestEpitaph); }
};
TEST(BindingGroup, DisambiguateMultiProtocolImplementations) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
fidl::ServerBindingGroup<Testable> binding_group;
MultiInheritanceServer impl;
auto endpoints = fidl::Endpoints<Testable>::Create();
binding_group.AddBinding(loop.dispatcher(), std::move(endpoints.server), &impl,
fidl::kIgnoreBindingClosure);
fidl::Client client(std::move(endpoints.client), loop.dispatcher());
client->Echo({"test"}).ThenExactlyOnce([&](fidl::Result<Testable::Echo>& result) {
loop.Quit();
ASSERT_TRUE(result.is_ok());
EXPECT_EQ("test", result->str());
});
loop.Run();
}
} // namespace