blob: 80287570ba0f594ad11e887f9f9af1c345c21dca [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.transport/cpp/driver/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/driver/runtime/testing/cpp/sync_helpers.h>
#include <lib/sync/cpp/completion.h>
#include <thread>
#include <gtest/gtest.h>
#include "sdk/lib/fidl_driver/tests/transport/api_test_helper.h"
#include "sdk/lib/fidl_driver/tests/transport/scoped_fake_driver.h"
#include "src/lib/testing/predicates/status.h"
// Test creating a typed channel endpoint pair.
TEST(Endpoints, CreateFromProtocol) {
// `std::move` pattern
{
auto endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
ASSERT_EQ(ZX_OK, endpoints.status_value());
fdf::ClientEnd<test_transport::TwoWayTest> client_end = std::move(endpoints->client);
fdf::ServerEnd<test_transport::TwoWayTest> server_end = std::move(endpoints->server);
ASSERT_TRUE(client_end.is_valid());
ASSERT_TRUE(server_end.is_valid());
}
// Destructuring pattern
{
auto endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
ASSERT_EQ(ZX_OK, endpoints.status_value());
auto [client_end, server_end] = std::move(endpoints.value());
ASSERT_TRUE(client_end.is_valid());
ASSERT_TRUE(server_end.is_valid());
}
}
// Test creating a typed channel endpoint pair using the out-parameter
// overloads.
TEST(Endpoints, CreateFromProtocolOutParameterStyleClientRetained) {
fdf::ClientEnd<test_transport::TwoWayTest> client_end;
auto server_end = fdf::CreateEndpoints(&client_end);
ASSERT_OK(server_end.status_value());
ASSERT_EQ(ZX_OK, server_end.status_value());
ASSERT_TRUE(client_end.is_valid());
ASSERT_TRUE(server_end->is_valid());
}
TEST(Endpoints, CreateFromProtocolOutParameterStyleServerRetained) {
fdf::ServerEnd<test_transport::TwoWayTest> server_end;
auto client_end = fdf::CreateEndpoints(&server_end);
ASSERT_OK(client_end.status_value());
ASSERT_EQ(ZX_OK, client_end.status_value());
ASSERT_TRUE(server_end.is_valid());
ASSERT_TRUE(client_end->is_valid());
}
TEST(WireClient, CannotDestroyInDifferentDispatcherThanBound) {
DEBUG_ONLY_TEST_MAY_SKIP();
fidl_driver_testing::ScopedFakeDriver driver;
auto [dispatcher1, dispatcher1_shutdown] = CreateSyncDispatcher();
auto [dispatcher2, dispatcher2_shutdown] = CreateSyncDispatcher();
zx::result endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
std::unique_ptr<fdf::WireClient<test_transport::TwoWayTest>> client;
// Create on one.
libsync::Completion created;
async::PostTask(dispatcher1.async_dispatcher(), [&, dispatcher = dispatcher1.get()] {
client = std::make_unique<fdf::WireClient<test_transport::TwoWayTest>>();
client->Bind(std::move(endpoints->client), dispatcher);
created.Signal();
});
created.Wait();
// Destroy on another.
libsync::Completion destroyed;
async::PostTask(dispatcher2.async_dispatcher(), [&] {
ASSERT_DEATH(client.reset(),
"The selected FIDL bindings is thread unsafe\\. Access from multiple driver "
"dispatchers detected\\. This is not allowed\\. Ensure the object is used from "
"the same \\|fdf_dispatcher_t\\|.");
destroyed.Signal();
});
destroyed.Wait();
dispatcher1.ShutdownAsync();
dispatcher2.ShutdownAsync();
dispatcher1_shutdown->Wait();
dispatcher2_shutdown->Wait();
}
TEST(WireClient, CannotDestroyOnUnmanagedThread) {
DEBUG_ONLY_TEST_MAY_SKIP();
fidl_driver_testing::ScopedFakeDriver driver;
auto [dispatcher1, dispatcher1_shutdown] = CreateSyncDispatcher();
zx::result endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
std::unique_ptr<fdf::WireClient<test_transport::TwoWayTest>> client;
// Create on one.
libsync::Completion created;
async::PostTask(dispatcher1.async_dispatcher(), [&, dispatcher = dispatcher1.get()] {
client = std::make_unique<fdf::WireClient<test_transport::TwoWayTest>>();
client->Bind(std::move(endpoints->client), dispatcher);
created.Signal();
});
created.Wait();
// Destroy on another.
libsync::Completion destroyed;
std::thread thread([&] {
ASSERT_DEATH(
client.reset(),
"The selected FIDL bindings is thread unsafe\\. The current thread is not managed by a "
"driver dispatcher\\. Ensure the object is always used from "
"a dispatcher managed thread\\.");
destroyed.Signal();
});
destroyed.Wait();
thread.join();
dispatcher1.ShutdownAsync();
dispatcher1_shutdown->Wait();
}
using DispatcherOptions = uint32_t;
// Test the shared client using both synchronized and unsynchronized dispatcher.
class WireSharedClient : public testing::TestWithParam<DispatcherOptions> {};
TEST_P(WireSharedClient, CanSendAcrossDispatcher) {
fidl_driver_testing::ScopedFakeDriver driver;
auto [dispatcher1, dispatcher1_shutdown] = CreateSyncDispatcher();
auto [dispatcher2, dispatcher2_shutdown] = CreateSyncDispatcher();
zx::result endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
std::unique_ptr<fdf::WireSharedClient<test_transport::TwoWayTest>> client;
// Create on one.
libsync::Completion created;
async::PostTask(dispatcher1.async_dispatcher(), [&, dispatcher = dispatcher1.get()] {
client = std::make_unique<fdf::WireSharedClient<test_transport::TwoWayTest>>();
client->Bind(std::move(endpoints->client), dispatcher);
created.Signal();
});
created.Wait();
// Destroy on another.
libsync::Completion destroyed;
async::PostTask(dispatcher2.async_dispatcher(), [&] {
client.reset();
destroyed.Signal();
});
destroyed.Wait();
dispatcher1.ShutdownAsync();
dispatcher2.ShutdownAsync();
dispatcher1_shutdown->Wait();
dispatcher2_shutdown->Wait();
}
TEST_P(WireSharedClient, CanDestroyOnUnmanagedThread) {
fidl_driver_testing::ScopedFakeDriver driver;
auto [dispatcher1, dispatcher1_shutdown] = CreateSyncDispatcher();
zx::result endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
std::unique_ptr<fdf::WireSharedClient<test_transport::TwoWayTest>> client;
// Create on one.
libsync::Completion created;
libsync::Completion destroyed;
async::PostTask(dispatcher1.async_dispatcher(), [&, dispatcher = dispatcher1.get()] {
client = std::make_unique<fdf::WireSharedClient<test_transport::TwoWayTest>>();
client->Bind(std::move(endpoints->client), dispatcher,
fidl::ObserveTeardown([&] { destroyed.Signal(); }));
created.Signal();
});
created.Wait();
// Destroy on another.
std::thread thread([&] { client.reset(); });
destroyed.Wait();
thread.join();
dispatcher1.ShutdownAsync();
dispatcher1_shutdown->Wait();
}
TEST(WireClient, CannotBindUnsynchronizedDispatcher) {
DEBUG_ONLY_TEST_MAY_SKIP();
fidl_driver_testing::ScopedFakeDriver driver;
libsync::Completion dispatcher_shutdown;
zx::result dispatcher = fdf::UnsynchronizedDispatcher::Create(
{}, "", [&](fdf_dispatcher_t* dispatcher) { dispatcher_shutdown.Signal(); });
ASSERT_OK(dispatcher.status_value());
zx::result endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
fdf::WireClient<test_transport::TwoWayTest> client;
libsync::Completion created;
async::PostTask(dispatcher->async_dispatcher(), [&] {
ASSERT_DEATH(client.Bind(std::move(endpoints->client), dispatcher->get()),
"The selected FIDL bindings is thread unsafe\\. A synchronized fdf_dispatcher_t "
"is required\\. Ensure the fdf_dispatcher_t does not have the "
"\\|FDF_DISPATCHER_OPTION_UNSYNCHRONIZED\\| option\\.");
client = {};
created.Signal();
});
created.Wait();
dispatcher->ShutdownAsync();
dispatcher_shutdown.Wait();
}
TEST_P(WireSharedClient, CanBindAnyDispatcher) {
fidl_driver_testing::ScopedFakeDriver driver;
libsync::Completion dispatcher_shutdown;
zx::result<fdf::Dispatcher> dispatcher;
uint32_t options = GetParam();
if ((options & FDF_DISPATCHER_OPTION_SYNCHRONIZATION_MASK) ==
FDF_DISPATCHER_OPTION_SYNCHRONIZED) {
dispatcher = fdf::SynchronizedDispatcher::Create(
fdf::SynchronizedDispatcher::Options{options}, "",
[&](fdf_dispatcher_t* dispatcher) { dispatcher_shutdown.Signal(); });
} else {
dispatcher = fdf::UnsynchronizedDispatcher::Create(
{}, "", [&](fdf_dispatcher_t* dispatcher) { dispatcher_shutdown.Signal(); });
}
ASSERT_OK(dispatcher.status_value());
zx::result endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
fdf::WireSharedClient<test_transport::TwoWayTest> client;
libsync::Completion created;
async::PostTask(dispatcher->async_dispatcher(), [&] {
client.Bind(std::move(endpoints->client), dispatcher->get());
client = {};
created.Signal();
});
created.Wait();
dispatcher->ShutdownAsync();
dispatcher_shutdown.Wait();
}
INSTANTIATE_TEST_SUITE_P(WireSharedClientTests, WireSharedClient,
testing::Values<DispatcherOptions>(FDF_DISPATCHER_OPTION_SYNCHRONIZED,
FDF_DISPATCHER_OPTION_UNSYNCHRONIZED),
[](const auto info) {
switch (info.index) {
case 0:
return "Synchronized";
case 1:
return "Unsynchronized";
default:
ZX_PANIC("Invalid index");
}
});
template <typename T>
struct ClientUnbindTest : public ::testing::Test {};
TYPED_TEST_SUITE_P(ClientUnbindTest);
TYPED_TEST_P(ClientUnbindTest, UnbindMaybeGetEndpoint) {
using Client = TypeParam;
fidl_driver_testing::ScopedFakeDriver driver;
auto [dispatcher, dispatcher_shutdown] = CreateSyncDispatcher();
auto endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
fdf_handle_t handle_number = endpoints->client.channel().get();
zx::result result =
fdf::RunOnDispatcherSync(dispatcher.async_dispatcher(), [&, dispatcher = dispatcher.get()] {
Client client(std::move(endpoints->client), dispatcher);
fit::result<fidl::Error, fdf::ClientEnd<test_transport::TwoWayTest>> result =
client.UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_ok()) << result.error_value();
ASSERT_EQ(handle_number, result.value().channel().get());
});
ASSERT_OK(result.status_value());
dispatcher.ShutdownAsync();
dispatcher_shutdown->Wait();
}
TYPED_TEST_P(ClientUnbindTest, UnbindAfterFatalError) {
using Client = TypeParam;
fidl_driver_testing::ScopedFakeDriver driver;
auto [dispatcher, dispatcher_shutdown] = CreateSyncDispatcher();
auto endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
struct EventHandler : public fdf::WireAsyncEventHandler<test_transport::TwoWayTest>,
public fdf::AsyncEventHandler<test_transport::TwoWayTest> {
void on_fidl_error(fidl::UnbindInfo info) final { errored.Signal(); }
libsync::Completion errored;
} event_handler;
std::optional<Client> client;
{
zx::result result =
fdf::RunOnDispatcherSync(dispatcher.async_dispatcher(), [&, dispatcher = dispatcher.get()] {
client.emplace(std::move(endpoints->client), dispatcher, &event_handler);
});
ASSERT_OK(result.status_value());
}
{
zx::result result =
fdf::RunOnDispatcherSync(dispatcher.async_dispatcher(), [&] { endpoints->server.reset(); });
ASSERT_OK(result.status_value());
}
{
zx::result result = fdf::WaitFor(event_handler.errored);
ASSERT_OK(result.status_value());
}
{
zx::result result = fdf::RunOnDispatcherSync(dispatcher.async_dispatcher(), [&] {
fit::result result = client.value().UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_error());
EXPECT_TRUE(result.error_value().is_peer_closed());
client.reset();
});
ASSERT_OK(result.status_value());
}
dispatcher.ShutdownAsync();
dispatcher_shutdown->Wait();
}
TYPED_TEST_P(ClientUnbindTest, UnbindTwice) {
using Client = TypeParam;
fidl_driver_testing::ScopedFakeDriver driver;
auto [dispatcher, dispatcher_shutdown] = CreateSyncDispatcher();
auto endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
zx::result result =
fdf::RunOnDispatcherSync(dispatcher.async_dispatcher(), [&, dispatcher = dispatcher.get()] {
Client client(std::move(endpoints->client), dispatcher);
{
fit::result result = client.UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_ok()) << result.error_value();
}
{
fit::result result = client.UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_error());
EXPECT_EQ(result.error_value().reason(), fidl::Reason::kUnbind);
}
});
ASSERT_OK(result.status_value());
dispatcher.ShutdownAsync();
dispatcher_shutdown->Wait();
}
TYPED_TEST_P(ClientUnbindTest, UnbindWithPendingCallsShouldFail) {
using Client = TypeParam;
fidl_driver_testing::ScopedFakeDriver driver;
auto [dispatcher, dispatcher_shutdown] = CreateSyncDispatcher();
auto endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
zx::result result =
fdf::RunOnDispatcherSync(dispatcher.async_dispatcher(), [&, dispatcher = dispatcher.get()] {
Client client(std::move(endpoints->client), dispatcher);
if constexpr (std::is_same_v<Client, fdf::WireClient<test_transport::TwoWayTest>>) {
fdf::Arena arena('TEST');
client.buffer(arena)->TwoWay({}).ThenExactlyOnce([](auto&) {});
} else {
client->TwoWay({}).ThenExactlyOnce([](auto&) {});
}
{
fit::result result = client.UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_error());
EXPECT_EQ(result.error_value().reason(), fidl::Reason::kPendingTwoWayCallPreventsUnbind);
}
{
fit::result result = client.UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_error());
EXPECT_EQ(result.error_value().reason(), fidl::Reason::kUnbind);
}
});
ASSERT_OK(result.status_value());
dispatcher.ShutdownAsync();
dispatcher_shutdown->Wait();
}
TYPED_TEST_P(ClientUnbindTest, UnbindAfterACompletedTwoWayCall) {
using Client = TypeParam;
fidl_driver_testing::ScopedFakeDriver driver;
auto [dispatcher, dispatcher_shutdown] = CreateSyncDispatcher();
auto endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>();
ASSERT_OK(endpoints.status_value());
fdf_handle_t handle_number = endpoints->client.channel().get();
libsync::Completion call_done;
std::optional<Client> client;
{
zx::result result =
fdf::RunOnDispatcherSync(dispatcher.async_dispatcher(), [&, dispatcher = dispatcher.get()] {
client.emplace(std::move(endpoints->client), dispatcher);
if constexpr (std::is_same_v<Client, fdf::WireClient<test_transport::TwoWayTest>>) {
fdf::Arena arena('TEST');
client.value().buffer(arena)->TwoWay({}).ThenExactlyOnce(
[&](auto&) { call_done.Signal(); });
} else {
client.value()->TwoWay({}).ThenExactlyOnce([&](auto&) { call_done.Signal(); });
}
});
ASSERT_OK(result.status_value());
}
class Server : public fdf::Server<test_transport::TwoWayTest> {
public:
void TwoWay(TwoWayRequest& request, TwoWayCompleter::Sync& completer) final {
completer.Reply({request.payload()});
}
} server;
std::optional<fdf::ServerBinding<test_transport::TwoWayTest>> binding;
{
zx::result result =
fdf::RunOnDispatcherSync(dispatcher.async_dispatcher(), [&, dispatcher = dispatcher.get()] {
binding.emplace(dispatcher, std::move(endpoints->server), &server,
fidl::kIgnoreBindingClosure);
});
ASSERT_OK(result.status_value());
}
ASSERT_OK(fdf::WaitFor(call_done).status_value());
{
zx::result result = fdf::RunOnDispatcherSync(dispatcher.async_dispatcher(), [&] {
fit::result result = (*client).UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_ok()) << result.error_value();
ASSERT_EQ(handle_number, result.value().channel().get());
client.reset();
binding.reset();
});
ASSERT_OK(result.status_value());
}
dispatcher.ShutdownAsync();
dispatcher_shutdown->Wait();
}
REGISTER_TYPED_TEST_SUITE_P(ClientUnbindTest, UnbindMaybeGetEndpoint, UnbindAfterFatalError,
UnbindTwice, UnbindWithPendingCallsShouldFail,
UnbindAfterACompletedTwoWayCall);
using ClientTypes = testing::Types<fdf::Client<test_transport::TwoWayTest>,
fdf::WireClient<test_transport::TwoWayTest>>;
INSTANTIATE_TYPED_TEST_SUITE_P(TestPrefix, ClientUnbindTest, ClientTypes);