blob: 37fe89bcb30f29e14aead1e4ecf305c90a12b6c2 [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/test.basic.protocol/cpp/wire.h>
#include <fidl/test.empty.protocol/cpp/wire.h>
#include <lib/sync/cpp/completion.h>
#include <memory>
#include <zxtest/zxtest.h>
#include "src/lib/fidl/llcpp/tests/dispatcher/async_loop_and_endpoints_fixture.h"
#include "src/lib/fidl/llcpp/tests/dispatcher/lsan_disabler.h"
namespace {
class ServerBindingTest : public zxtest::Test {
public:
ServerBindingTest() : loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {}
void SetUp() override {
zx::result endpoints = fidl::CreateEndpoints<test_basic_protocol::ValueEcho>();
ASSERT_OK(endpoints.status_value());
endpoints_ = std::move(*endpoints);
}
async::Loop& loop() { return loop_; }
fidl::Endpoints<test_basic_protocol::ValueEcho>& endpoints() { return endpoints_; }
private:
async::Loop loop_;
fidl::Endpoints<test_basic_protocol::ValueEcho> endpoints_;
};
struct Server : public fidl::WireServer<test_basic_protocol::ValueEcho> {
void Echo(EchoRequestView request, EchoCompleter::Sync& completer) override {
call_count++;
completer.Reply(request->s);
}
size_t call_count = 0;
};
TEST_F(ServerBindingTest, Control) {
Server server;
bool close_handler_called = false;
{
fidl::ServerBinding<test_basic_protocol::ValueEcho> binding{
loop().dispatcher(), std::move(endpoints().server), &server,
[&close_handler_called](fidl::UnbindInfo) { close_handler_called = true; }};
constexpr static const char kPayload[] = "test";
fidl::WireClient client(std::move(endpoints().client), loop().dispatcher());
EXPECT_EQ(0u, server.call_count);
{
client->Echo(kPayload).ThenExactlyOnce(
[](fidl::WireUnownedResult<test_basic_protocol::ValueEcho::Echo>& result) {
ASSERT_TRUE(result.ok());
EXPECT_EQ(kPayload, result->s.get());
});
loop().RunUntilIdle();
EXPECT_EQ(1u, server.call_count);
}
{
client->Echo(kPayload).ThenExactlyOnce(
[](fidl::WireUnownedResult<test_basic_protocol::ValueEcho::Echo>& result) {
ASSERT_TRUE(result.ok());
EXPECT_EQ(kPayload, result->s.get());
});
loop().RunUntilIdle();
EXPECT_EQ(2u, server.call_count);
}
// Unbind at end of scope. |binding| is destroyed here.
}
loop().RunUntilIdle();
// Unbind does not call CloseHandler.
EXPECT_FALSE(close_handler_called);
}
TEST_F(ServerBindingTest, CloseHandler) {
Server server;
std::optional<fidl::UnbindInfo> error;
int close_handler_count = 0;
fidl::ServerBinding<test_basic_protocol::ValueEcho> binding{
loop().dispatcher(), std::move(endpoints().server), &server,
[&close_handler_count, &error](fidl::UnbindInfo info) {
error.emplace(info);
close_handler_count++;
}};
endpoints().client.reset();
loop().RunUntilIdle();
EXPECT_TRUE(error.has_value());
EXPECT_TRUE(error->is_peer_closed());
EXPECT_EQ(1, close_handler_count);
}
TEST_F(ServerBindingTest, EndpointIsClosedBeforeCloseHandler) {
Server server;
std::optional<fidl::UnbindInfo> error;
int close_handler_count = 0;
fidl::ServerBinding<test_basic_protocol::ValueEcho> binding{
loop().dispatcher(), std::move(endpoints().server), &server,
[this, &close_handler_count, &error](fidl::UnbindInfo info) {
error.emplace(info);
close_handler_count++;
// Inside a CloseHandler, the server endpoint should already be closed.
zx_signals_t observed = ZX_SIGNAL_NONE;
ASSERT_OK(endpoints().client.channel().wait_one(ZX_CHANNEL_PEER_CLOSED,
zx::time::infinite_past(), &observed));
EXPECT_EQ(ZX_CHANNEL_PEER_CLOSED, observed);
}};
// Send an invalid message to close the connection.
uint8_t invalid[1] = {};
ASSERT_OK(endpoints().client.channel().write(0, &invalid, sizeof(invalid), nullptr, 0));
loop().RunUntilIdle();
EXPECT_TRUE(error.has_value());
EXPECT_EQ(fidl::Reason::kUnexpectedMessage, error->reason());
EXPECT_EQ(1, close_handler_count);
}
TEST_F(ServerBindingTest, CloseBindingCallsTheCloseHandler) {
Server server;
std::optional<fidl::UnbindInfo> error;
int close_handler_count = 0;
fidl::ServerBinding<test_basic_protocol::ValueEcho> binding{
loop().dispatcher(), std::move(endpoints().server), &server,
[&close_handler_count, &error](fidl::UnbindInfo info) {
error.emplace(info);
close_handler_count++;
}};
binding.Close(ZX_OK);
loop().RunUntilIdle();
EXPECT_TRUE(error.has_value());
EXPECT_TRUE(error->is_user_initiated());
EXPECT_EQ(error->reason(), fidl::Reason::kClose);
EXPECT_EQ(1, close_handler_count);
}
TEST_F(ServerBindingTest, IgnoreCloseHandlerOnClientClose) {
Server server;
fidl::ServerBinding<test_basic_protocol::ValueEcho> binding{
loop().dispatcher(), std::move(endpoints().server), &server, fidl::kIgnoreBindingClosure};
endpoints().client.reset();
loop().RunUntilIdle();
}
TEST_F(ServerBindingTest, IgnoreCloseHandlerOnBindingClose) {
Server server;
fidl::ServerBinding<test_basic_protocol::ValueEcho> binding{
loop().dispatcher(), std::move(endpoints().server), &server, fidl::kIgnoreBindingClosure};
binding.Close(ZX_OK);
loop().RunUntilIdle();
}
TEST_F(ServerBindingTest, BindingDestructionPassivatesTheCloseHandler) {
Server server;
int close_handler_count = 0;
auto binding = std::make_optional<fidl::ServerBinding<test_basic_protocol::ValueEcho>>(
loop().dispatcher(), std::move(endpoints().server), &server,
[&close_handler_count](fidl::UnbindInfo info) { close_handler_count++; });
endpoints().client.reset();
binding.reset();
loop().RunUntilIdle();
EXPECT_EQ(0, close_handler_count);
}
TEST_F(ServerBindingTest, DestructDuringCloseHandler) {
Server server;
int close_handler_count = 0;
std::unique_ptr<fidl::ServerBinding<test_basic_protocol::ValueEcho>> binding;
binding = std::make_unique<fidl::ServerBinding<test_basic_protocol::ValueEcho>>(
loop().dispatcher(), std::move(endpoints().server), &server,
[&close_handler_count, &binding](fidl::UnbindInfo info) {
close_handler_count++;
// Destroying the binding here should be allowed.
binding.reset();
});
endpoints().client.reset();
loop().RunUntilIdle();
EXPECT_EQ(1, close_handler_count);
}
TEST(ServerBindingTest, CannotDestroyOnAnotherThread) {
fidl_testing::RunWithLsanDisabled([&] {
Server server;
auto loop = std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread);
zx::result endpoints = fidl::CreateEndpoints<test_basic_protocol::ValueEcho>();
ASSERT_OK(endpoints.status_value());
auto binding = std::make_unique<fidl::ServerBinding<test_basic_protocol::ValueEcho>>(
loop->dispatcher(), std::move(endpoints->server), &server, [](fidl::UnbindInfo info) {});
// Panics when a foreign thread attempts to destroy the binding.
#if ZX_DEBUG_ASSERT_IMPLEMENTED
std::thread foreign_thread(
[&] { ASSERT_DEATH([&] { fidl_testing::RunWithLsanDisabled([&] { binding = {}; }); }); });
foreign_thread.join();
// The above thread will not be able to finish unbinding -- it would be
// terminated due to exception. That puts the binding in a corrupted state
// where the terminated thread held a reference count to the internal
// binding that will never be dropped. To workaround that we just leak the
// loop and skip any teardown.
loop.release();
#endif
});
}
TEST(ServerBindingTest, CannotCloseOnAnotherThread) {
fidl_testing::RunWithLsanDisabled([&] {
Server server;
auto loop = std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread);
zx::result endpoints = fidl::CreateEndpoints<test_basic_protocol::ValueEcho>();
ASSERT_OK(endpoints.status_value());
auto binding = std::make_unique<fidl::ServerBinding<test_basic_protocol::ValueEcho>>(
loop->dispatcher(), std::move(endpoints->server), &server, [](fidl::UnbindInfo info) {});
// Panics when a foreign thread attempts to close the binding.
#if ZX_DEBUG_ASSERT_IMPLEMENTED
std::thread foreign_thread([&] {
ASSERT_DEATH([&] { fidl_testing::RunWithLsanDisabled([&] { binding->Close(ZX_OK); }); });
});
foreign_thread.join();
// The above thread will not be able to finish unbinding -- it would be
// terminated due to exception. That puts the binding in a corrupted state
// where the terminated thread held a reference count to the internal
// binding that will never be dropped. To workaround that we just leak the
// loop and skip any teardown.
loop.release();
#endif
});
}
} // namespace