| // 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/wire.h> |
| #include <lib/async_patterns/testing/cpp/dispatcher_bound.h> |
| #include <lib/fdf/cpp/dispatcher.h> |
| #include <lib/fdf/dispatcher.h> |
| #include <lib/sync/cpp/completion.h> |
| |
| #include <memory> |
| |
| #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" |
| |
| namespace { |
| |
| TEST(ServerBinding, Control) { |
| fidl_driver_testing::ScopedFakeDriver driver; |
| auto [dispatcher, dispatcher_shutdown] = CreateSyncDispatcher(); |
| |
| struct Server : public fdf::WireServer<test_transport::TwoWayTest> { |
| void TwoWay(TwoWayRequestView request, fdf::Arena& arena, |
| TwoWayCompleter::Sync& completer) override { |
| call_count_++; |
| completer.buffer(arena).Reply(request->payload); |
| } |
| |
| void Bind(fdf::ServerEnd<test_transport::TwoWayTest> server_end) { |
| binding.emplace(fdf::Dispatcher::GetCurrent()->get(), std::move(server_end), this, |
| std::mem_fn(&Server::OnClosed)); |
| } |
| |
| void DestroyBinding() { binding.reset(); } |
| |
| size_t call_count() const { return call_count_; } |
| |
| bool close_handler_called() const { return close_handler_called_; } |
| |
| private: |
| void OnClosed(fidl::UnbindInfo) { close_handler_called_ = true; } |
| |
| size_t call_count_ = 0; |
| bool close_handler_called_ = false; |
| std::optional<fdf::ServerBinding<test_transport::TwoWayTest>> binding; |
| }; |
| |
| auto endpoints = fdf::CreateEndpoints<test_transport::TwoWayTest>(); |
| ASSERT_TRUE(endpoints.is_ok()) << endpoints.status_string(); |
| auto [client_end, server_end] = std::move(*endpoints); |
| |
| async_patterns::TestDispatcherBound<Server> server{dispatcher.async_dispatcher(), std::in_place}; |
| server.AsyncCall(&Server::Bind, std::move(server_end)); |
| |
| fdf::Arena arena('TEST'); |
| constexpr uint32_t kPayload = 42; |
| EXPECT_EQ(0u, server.SyncCall(&Server::call_count)); |
| { |
| auto result = fdf::WireCall(client_end).buffer(arena)->TwoWay(kPayload); |
| ASSERT_TRUE(result.ok()) << result.error(); |
| EXPECT_EQ(kPayload, result->payload); |
| } |
| EXPECT_EQ(1u, server.SyncCall(&Server::call_count)); |
| { |
| auto result = fdf::WireCall(client_end).buffer(arena)->TwoWay(kPayload); |
| ASSERT_TRUE(result.ok()) << result.error(); |
| EXPECT_EQ(kPayload, result->payload); |
| } |
| EXPECT_EQ(2u, server.SyncCall(&Server::call_count)); |
| |
| // Unbind does not call CloseHandler. |
| server.SyncCall(&Server::DestroyBinding); |
| EXPECT_FALSE(server.SyncCall(&Server::close_handler_called)); |
| |
| dispatcher.ShutdownAsync(); |
| dispatcher_shutdown->Wait(); |
| } |
| |
| TEST(ServerBinding, CloseHandler) { |
| fidl_driver_testing::ScopedFakeDriver driver; |
| auto [dispatcher, dispatcher_shutdown] = CreateSyncDispatcher(); |
| |
| struct Server : public fdf::WireServer<test_transport::EmptyProtocol> {}; |
| Server server; |
| |
| auto endpoints = fdf::CreateEndpoints<test_transport::EmptyProtocol>(); |
| ASSERT_TRUE(endpoints.is_ok()) << endpoints.status_string(); |
| auto [client_end, server_end] = std::move(*endpoints); |
| |
| libsync::Completion got_error; |
| std::optional<fidl::UnbindInfo> error; |
| int close_handler_count = 0; |
| |
| std::optional<fdf::ServerBinding<test_transport::EmptyProtocol>> binding; |
| async::PostTask( |
| dispatcher.async_dispatcher(), |
| [&, &dispatcher = dispatcher, &server_end = server_end, &client_end = client_end] { |
| binding.emplace(dispatcher.get(), std::move(server_end), &server, |
| [&error, &got_error, &close_handler_count](fidl::UnbindInfo info) { |
| error.emplace(info); |
| close_handler_count++; |
| got_error.Signal(); |
| }); |
| |
| client_end.reset(); |
| }); |
| |
| got_error.Wait(); |
| EXPECT_TRUE(error.has_value()); |
| EXPECT_TRUE(error->is_peer_closed()); |
| EXPECT_EQ(1, close_handler_count); |
| |
| async::PostTask(dispatcher.async_dispatcher(), [&] { binding.reset(); }); |
| dispatcher.ShutdownAsync(); |
| dispatcher_shutdown->Wait(); |
| EXPECT_EQ(1, close_handler_count); |
| } |
| |
| TEST(ServerBinding, DestructDuringCloseHandler) { |
| fidl_driver_testing::ScopedFakeDriver driver; |
| auto [dispatcher, dispatcher_shutdown] = CreateSyncDispatcher(); |
| |
| struct Server : public fdf::WireServer<test_transport::EmptyProtocol> {}; |
| Server server; |
| |
| auto endpoints = fdf::CreateEndpoints<test_transport::EmptyProtocol>(); |
| ASSERT_TRUE(endpoints.is_ok()) << endpoints.status_string(); |
| auto [client_end, server_end] = std::move(*endpoints); |
| |
| libsync::Completion got_error; |
| std::optional<fidl::UnbindInfo> error; |
| int error_count = 0; |
| |
| std::optional<fdf::ServerBinding<test_transport::EmptyProtocol>> binding; |
| async::PostTask( |
| dispatcher.async_dispatcher(), |
| [&, &dispatcher = dispatcher, &server_end = server_end, &client_end = client_end] { |
| binding.emplace(dispatcher.get(), std::move(server_end), &server, |
| [&error, &got_error, &error_count, &binding](fidl::UnbindInfo info) { |
| // Destroy binding while inside error handler. |
| binding.reset(); |
| |
| error.emplace(info); |
| error_count++; |
| got_error.Signal(); |
| }); |
| |
| client_end.reset(); |
| }); |
| |
| got_error.Wait(); |
| EXPECT_TRUE(error.has_value()); |
| EXPECT_TRUE(error->is_peer_closed()); |
| EXPECT_EQ(1, error_count); |
| |
| async::PostTask(dispatcher.async_dispatcher(), [&] { binding.reset(); }); |
| dispatcher.ShutdownAsync(); |
| dispatcher_shutdown->Wait(); |
| EXPECT_EQ(1, error_count); |
| } |
| |
| TEST(ServerBinding, 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::EmptyProtocol>(); |
| ASSERT_OK(endpoints.status_value()); |
| |
| struct Server : public fdf::WireServer<test_transport::EmptyProtocol> {}; |
| Server server; |
| |
| std::optional<fdf::ServerBinding<test_transport::EmptyProtocol>> binding; |
| libsync::Completion created; |
| async::PostTask(dispatcher->async_dispatcher(), [&] { |
| ASSERT_DEATH(binding.emplace(dispatcher->get(), std::move(endpoints->server), &server, |
| [](fidl::UnbindInfo) {}), |
| "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\\."); |
| binding.reset(); |
| created.Signal(); |
| }); |
| created.Wait(); |
| |
| dispatcher->ShutdownAsync(); |
| dispatcher_shutdown.Wait(); |
| } |
| |
| } // namespace |