| // Copyright 2020 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 <fidl/test.transitional/cpp/wire.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/cpp/time.h> |
| #include <lib/fidl/cpp/wire/server.h> |
| #include <lib/fidl/epitaph.h> |
| #include <lib/fit/defer.h> |
| #include <lib/sync/completion.h> |
| #include <lib/sync/cpp/completion.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/eventpair.h> |
| #include <lib/zx/time.h> |
| #include <string.h> |
| #include <zircon/types.h> |
| |
| #include <type_traits> |
| |
| #include <zxtest/zxtest.h> |
| |
| namespace { |
| |
| using ::test_basic_protocol::Values; |
| |
| class Server : public fidl::WireServer<Values> { |
| public: |
| explicit Server(const char* data, size_t size) : data_(data), size_(size) {} |
| |
| void Echo(EchoRequestView request, EchoCompleter::Sync& completer) override { |
| ASSERT_EQ(size_, request->s.size()); |
| EXPECT_EQ(0, strncmp(data_, request->s.data(), size_)); |
| two_way_count_.fetch_add(1); |
| completer.Reply(request->s); |
| } |
| |
| void OneWay(OneWayRequestView request, OneWayCompleter::Sync&) override { |
| ASSERT_EQ(size_, request->in.size()); |
| EXPECT_EQ(0, strncmp(data_, request->in.data(), size_)); |
| one_way_count_.fetch_add(1); |
| } |
| |
| unsigned int two_way_count() const { return two_way_count_.load(); } |
| |
| unsigned int one_way_count() const { return one_way_count_.load(); } |
| |
| private: |
| const char* data_; |
| size_t size_; |
| std::atomic_uint two_way_count_ = 0; |
| std::atomic_uint one_way_count_ = 0; |
| }; |
| |
| TEST(GenAPITestCase, EchoAsyncManaged) { |
| auto endpoints = fidl::CreateEndpoints<Values>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| fidl::WireSharedClient<Values> client(std::move(local), loop.dispatcher()); |
| |
| static constexpr char data[] = "Echo() sync managed"; |
| auto server_binding = fidl::BindServer(loop.dispatcher(), std::move(remote), |
| std::make_unique<Server>(data, strlen(data))); |
| |
| sync_completion_t done; |
| client->Echo(fidl::StringView(data)) |
| .ThenExactlyOnce([&done](fidl::WireUnownedResult<Values::Echo>& result) { |
| ASSERT_OK(result.status()); |
| ASSERT_EQ(strlen(data), result.value().s.size()); |
| EXPECT_EQ(0, strncmp(result.value().s.data(), data, strlen(data))); |
| sync_completion_signal(&done); |
| }); |
| ASSERT_OK(sync_completion_wait(&done, ZX_TIME_INFINITE)); |
| |
| server_binding.Unbind(); |
| } |
| |
| TEST(GenAPITestCase, EchoAsyncResponseContext) { |
| class ResponseContext final : public fidl::WireResponseContext<Values::Echo> { |
| public: |
| ResponseContext(libsync::Completion* done, const char* data, size_t size) |
| : done_(done), data_(data), size_(size) {} |
| |
| void OnResult(fidl::WireUnownedResult<Values::Echo>& result) override { |
| ASSERT_OK(result.status()); |
| auto& out = result.value().s; |
| ASSERT_EQ(size_, out.size()); |
| EXPECT_EQ(0, strncmp(out.data(), data_, size_)); |
| done_->Signal(); |
| } |
| |
| private: |
| libsync::Completion* done_; |
| const char* data_; |
| size_t size_; |
| }; |
| |
| auto endpoints = fidl::CreateEndpoints<Values>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| fidl::WireSharedClient<Values> client(std::move(local), loop.dispatcher()); |
| |
| static constexpr char kData[] = "Echo() sync response context"; |
| fidl::BindServer(loop.dispatcher(), std::move(remote), |
| std::make_unique<Server>(kData, strlen(kData))); |
| |
| libsync::Completion done; |
| ResponseContext context(&done, kData, strlen(kData)); |
| client->Echo(fidl::StringView(kData)).ThenExactlyOnce(&context); |
| ASSERT_OK(done.Wait(zx::duration::infinite())); |
| } |
| |
| TEST(GenAPITestCase, EchoAsyncCallerAllocated) { |
| class ResponseContext final : public fidl::WireResponseContext<Values::Echo> { |
| public: |
| ResponseContext(sync_completion_t* done, const char* data, size_t size) |
| : done_(done), data_(data), size_(size) {} |
| |
| void OnResult(fidl::WireUnownedResult<Values::Echo>& result) override { |
| ASSERT_OK(result.status()); |
| auto& out = result.value().s; |
| ASSERT_EQ(size_, out.size()); |
| EXPECT_EQ(0, strncmp(out.data(), data_, size_)); |
| sync_completion_signal(done_); |
| } |
| |
| private: |
| sync_completion_t* done_; |
| const char* data_; |
| size_t size_; |
| }; |
| |
| auto endpoints = fidl::CreateEndpoints<Values>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| fidl::WireSharedClient<Values> client(std::move(local), loop.dispatcher()); |
| |
| static constexpr char data[] = "Echo() sync caller-allocated"; |
| auto server_binding = fidl::BindServer(loop.dispatcher(), std::move(remote), |
| std::make_unique<Server>(data, strlen(data))); |
| |
| sync_completion_t done; |
| fidl::AsyncClientBuffer<Values::Echo> buffer; |
| ResponseContext context(&done, data, strlen(data)); |
| client.buffer(buffer.view())->Echo(fidl::StringView(data)).ThenExactlyOnce(&context); |
| ASSERT_OK(sync_completion_wait(&done, ZX_TIME_INFINITE)); |
| |
| server_binding.Unbind(); |
| } |
| |
| TEST(GenAPITestCase, EchoSyncManaged) { |
| auto endpoints = fidl::CreateEndpoints<Values>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| fidl::WireSharedClient<Values> client(std::move(local), loop.dispatcher()); |
| |
| static constexpr char kData[] = "Echo() sync managed"; |
| auto server = std::make_shared<Server>(kData, strlen(kData)); |
| fidl::BindServer(loop.dispatcher(), std::move(remote), server); |
| |
| fidl::WireResult result = client.sync()->Echo(fidl::StringView(kData)); |
| ASSERT_OK(result.status()); |
| ASSERT_EQ(strlen(kData), result.value().s.size()); |
| EXPECT_EQ(0, strncmp(result.value().s.data(), kData, strlen(kData))); |
| EXPECT_EQ(1, server->two_way_count()); |
| EXPECT_EQ(0, server->one_way_count()); |
| } |
| |
| TEST(GenAPITestCase, OneWaySyncManaged) { |
| auto endpoints = fidl::CreateEndpoints<Values>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| fidl::WireClient<Values> client(std::move(local), loop.dispatcher()); |
| |
| static constexpr char kData[] = "OneWay() sync managed"; |
| auto server = std::make_shared<Server>(kData, strlen(kData)); |
| fidl::BindServer(loop.dispatcher(), std::move(remote), server); |
| |
| fidl::Status result = client.sync()->OneWay(fidl::StringView(kData)); |
| EXPECT_OK(result.status()); |
| ASSERT_OK(loop.RunUntilIdle()); |
| EXPECT_EQ(1, server->one_way_count()); |
| EXPECT_EQ(0, server->two_way_count()); |
| } |
| |
| TEST(GenAPITestCase, AsyncEventHandlerExhaustivenessNotRequired) { |
| class EventHandlerNone : public fidl::WireAsyncEventHandler<test_basic_protocol::TwoEvents> {}; |
| class EventHandlerA : public fidl::WireAsyncEventHandler<test_basic_protocol::TwoEvents> { |
| void EventA() override {} |
| }; |
| class EventHandlerB : public fidl::WireAsyncEventHandler<test_basic_protocol::TwoEvents> { |
| void EventB() override {} |
| }; |
| class EventHandlerAll : public fidl::WireAsyncEventHandler<test_basic_protocol::TwoEvents> { |
| void EventA() override {} |
| void EventB() override {} |
| }; |
| class EventHandlerAllTransitional |
| : public fidl::WireSyncEventHandler<test_transitional::TransitionalEvent> {}; |
| static_assert(!std::is_abstract_v<EventHandlerNone>); |
| static_assert(!std::is_abstract_v<EventHandlerA>); |
| static_assert(!std::is_abstract_v<EventHandlerB>); |
| static_assert(!std::is_abstract_v<EventHandlerAll>); |
| static_assert(!std::is_abstract_v<EventHandlerAllTransitional>); |
| } |
| |
| TEST(GenAPITestCase, EventManaged) { |
| auto endpoints = fidl::CreateEndpoints<Values>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| |
| static constexpr char data[] = "OnEvent() managed"; |
| class EventHandler : public fidl::WireAsyncEventHandler<Values> { |
| public: |
| EventHandler() = default; |
| |
| sync_completion_t& done() { return done_; } |
| |
| void OnValueEvent(fidl::WireEvent<Values::OnValueEvent>* event) override { |
| ASSERT_EQ(strlen(data), event->s.size()); |
| EXPECT_EQ(0, strncmp(event->s.data(), data, strlen(data))); |
| sync_completion_signal(&done_); |
| } |
| |
| private: |
| sync_completion_t done_; |
| }; |
| |
| auto event_handler = std::make_shared<EventHandler>(); |
| fidl::WireSharedClient<Values> client(std::move(local), loop.dispatcher(), event_handler.get(), |
| fidl::ShareUntilTeardown(event_handler)); |
| |
| auto server_binding = fidl::BindServer(loop.dispatcher(), std::move(remote), |
| std::make_unique<Server>(data, strlen(data))); |
| |
| // Wait for the event from the server. |
| ASSERT_OK(fidl::WireSendEvent(server_binding)->OnValueEvent(fidl::StringView(data))); |
| ASSERT_OK(sync_completion_wait(&event_handler->done(), ZX_TIME_INFINITE)); |
| |
| server_binding.Unbind(); |
| } |
| |
| TEST(GenAPITestCase, ConsumeEventsWhenEventHandlerIsAbsent) { |
| auto endpoints = fidl::CreateEndpoints<test_basic_protocol::ResourceEvent>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| |
| fidl::WireSharedClient<test_basic_protocol::ResourceEvent> client(std::move(local), |
| loop.dispatcher()); |
| |
| // Send an unhandled event. The event should be silently discarded since |
| // the user did not provide an event handler. |
| zx::eventpair ep1, ep2; |
| ASSERT_OK(zx::eventpair::create(0, &ep1, &ep2)); |
| ASSERT_OK(fidl::WireSendEvent(remote)->OnResourceEvent(std::move(ep1))); |
| zx_signals_t observed; |
| ASSERT_OK(zx_object_wait_one(ep2.get(), ZX_EVENTPAIR_PEER_CLOSED, ZX_TIME_INFINITE, &observed)); |
| ASSERT_EQ(ZX_EVENTPAIR_PEER_CLOSED, observed); |
| } |
| |
| TEST(GenAPITestCase, ConsumeEventsWhenEventHandlerMethodIsAbsent) { |
| auto endpoints = fidl::CreateEndpoints<test_basic_protocol::ResourceEvent>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| |
| class EventHandler : public fidl::WireAsyncEventHandler<test_basic_protocol::ResourceEvent> {}; |
| |
| fidl::WireSharedClient<test_basic_protocol::ResourceEvent> client( |
| std::move(local), loop.dispatcher(), std::make_unique<EventHandler>()); |
| |
| // Send an unhandled event. The event should be silently discarded since |
| // the user did not provide a handler method for |OnResourceEvent|. |
| zx::eventpair ep1, ep2; |
| ASSERT_OK(zx::eventpair::create(0, &ep1, &ep2)); |
| ASSERT_OK(fidl::WireSendEvent(remote)->OnResourceEvent(std::move(ep1))); |
| zx_signals_t observed; |
| ASSERT_OK(zx_object_wait_one(ep2.get(), ZX_EVENTPAIR_PEER_CLOSED, ZX_TIME_INFINITE, &observed)); |
| ASSERT_EQ(ZX_EVENTPAIR_PEER_CLOSED, observed); |
| } |
| |
| // This is test is almost identical to ClientBindingTestCase.Epitaph in llcpp_client_test.cc but |
| // validates the part of the flow that's handled in the generated code. |
| TEST(GenAPITestCase, Epitaph) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| |
| auto endpoints = fidl::CreateEndpoints<test_empty_protocol::Empty>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| sync_completion_t unbound; |
| |
| class EventHandler : public fidl::WireAsyncEventHandler<test_empty_protocol::Empty> { |
| public: |
| explicit EventHandler(sync_completion_t& unbound) : unbound_(unbound) {} |
| |
| void on_fidl_error(fidl::UnbindInfo info) override { |
| EXPECT_EQ(fidl::Reason::kPeerClosed, info.reason()); |
| EXPECT_EQ(ZX_ERR_BAD_STATE, info.status()); |
| sync_completion_signal(&unbound_); |
| } |
| |
| private: |
| sync_completion_t& unbound_; |
| }; |
| |
| fidl::WireSharedClient<test_empty_protocol::Empty> client( |
| std::move(local), loop.dispatcher(), std::make_unique<EventHandler>(unbound)); |
| |
| // Send an epitaph and wait for on_unbound to run. |
| ASSERT_OK(fidl_epitaph_write(remote.channel().get(), ZX_ERR_BAD_STATE)); |
| EXPECT_OK(sync_completion_wait(&unbound, ZX_TIME_INFINITE)); |
| } |
| |
| TEST(GenAPITestCase, UnbindInfoEncodeError) { |
| class ErrorServer : public fidl::WireServer<Values> { |
| public: |
| explicit ErrorServer() = default; |
| |
| void Echo(EchoRequestView request, EchoCompleter::Sync& completer) override { |
| // Fail to send the reply due to an encoding error (the buffer is too small). |
| // The buffer still needs to be properly aligned. |
| constexpr size_t kSmallSize = 8; |
| FIDL_ALIGNDECL uint8_t small_buffer[kSmallSize]; |
| static_assert(sizeof(fidl::WireResponse<Values::Echo>) > kSmallSize); |
| fidl::BufferSpan too_small(small_buffer, std::size(small_buffer)); |
| completer.buffer(too_small).Reply(request->s); |
| EXPECT_EQ(ZX_ERR_BUFFER_TOO_SMALL, completer.result_of_reply().status()); |
| completer.Close(ZX_OK); // This should not panic. |
| } |
| |
| void OneWay(OneWayRequestView, OneWayCompleter::Sync&) override {} |
| }; |
| |
| auto endpoints = fidl::CreateEndpoints<Values>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| fidl::WireSharedClient<Values> client(std::move(local), loop.dispatcher()); |
| |
| sync_completion_t done; |
| fidl::OnUnboundFn<ErrorServer> on_unbound = [&done](ErrorServer*, fidl::UnbindInfo info, |
| fidl::ServerEnd<Values>) { |
| EXPECT_EQ(fidl::Reason::kEncodeError, info.reason()); |
| EXPECT_EQ(ZX_ERR_BUFFER_TOO_SMALL, info.status()); |
| sync_completion_signal(&done); |
| }; |
| auto server = std::make_unique<ErrorServer>(); |
| auto server_binding = |
| fidl::BindServer(loop.dispatcher(), std::move(remote), server.get(), std::move(on_unbound)); |
| |
| // Make a synchronous call which should fail as a result of the server end closing. |
| fidl::WireResult result = client.sync()->Echo(fidl::StringView("")); |
| ASSERT_FALSE(result.ok()); |
| EXPECT_EQ(ZX_ERR_PEER_CLOSED, result.status()); |
| |
| // Wait for the unbound handler to run. |
| ASSERT_OK(sync_completion_wait(&done, ZX_TIME_INFINITE)); |
| } |
| |
| TEST(GenAPITestCase, UnbindInfoDecodeError) { |
| auto endpoints = fidl::CreateEndpoints<Values>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_OK(loop.StartThread()); |
| sync_completion_t done; |
| |
| class EventHandler : public fidl::WireAsyncEventHandler<Values> { |
| public: |
| explicit EventHandler(sync_completion_t& done) : done_(done) {} |
| |
| void OnValueEvent(fidl::WireEvent<Values::OnValueEvent>* event) override { |
| FAIL(); |
| sync_completion_signal(&done_); |
| } |
| |
| void on_fidl_error(fidl::UnbindInfo info) override { |
| EXPECT_EQ(fidl::Reason::kDecodeError, info.reason()); |
| sync_completion_signal(&done_); |
| } |
| |
| private: |
| sync_completion_t& done_; |
| }; |
| |
| fidl::WireSharedClient<Values> client(std::move(local), loop.dispatcher(), |
| std::make_unique<EventHandler>(done)); |
| |
| // Set up an Values.OnEvent() message but send it without the payload. This should trigger a |
| // decoding error. |
| fidl::internal::TransactionalEvent<Values::OnValueEvent> resp(fidl::StringView("")); |
| fidl::internal::OwnedEncodedMessage<fidl::internal::TransactionalEvent<Values::OnValueEvent>> |
| encoded(&resp); |
| ASSERT_TRUE(encoded.ok()); |
| auto bytes = encoded.GetOutgoingMessage().CopyBytes(); |
| ASSERT_OK(remote.channel().write(0, bytes.data(), sizeof(fidl_message_header_t), nullptr, 0)); |
| |
| ASSERT_OK(sync_completion_wait(&done, ZX_TIME_INFINITE)); |
| } |
| |
| // After a client is unbound, no more calls can be made on that client. |
| TEST(GenAPITestCase, UnbindPreventsSubsequentCalls) { |
| // Use a server to count the number of |OneWay| calls. |
| class Server : public fidl::WireServer<Values> { |
| public: |
| Server() = default; |
| |
| void Echo(EchoRequestView request, EchoCompleter::Sync& completer) override { |
| ZX_PANIC("Not used in this test"); |
| } |
| |
| void OneWay(OneWayRequestView, OneWayCompleter::Sync&) override { num_one_way_.fetch_add(1); } |
| |
| int num_one_way() const { return num_one_way_.load(); } |
| |
| private: |
| std::atomic<int> num_one_way_ = 0; |
| }; |
| |
| auto endpoints = fidl::CreateEndpoints<Values>(); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| fidl::WireSharedClient<Values> client(std::move(endpoints->client), loop.dispatcher()); |
| |
| auto server = std::make_shared<Server>(); |
| auto server_binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), server); |
| |
| ASSERT_OK(loop.RunUntilIdle()); |
| EXPECT_EQ(0, server->num_one_way()); |
| |
| ASSERT_OK(client->OneWay("foo").status()); |
| |
| ASSERT_OK(loop.RunUntilIdle()); |
| EXPECT_EQ(1, server->num_one_way()); |
| |
| client.AsyncTeardown(); |
| ASSERT_OK(loop.RunUntilIdle()); |
| EXPECT_EQ(1, server->num_one_way()); |
| |
| ASSERT_EQ(ZX_ERR_CANCELED, client->OneWay("foo").status()); |
| ASSERT_OK(loop.RunUntilIdle()); |
| EXPECT_EQ(1, server->num_one_way()); |
| } |
| |
| fidl::Endpoints<Values> CreateEndpointsWithoutClientWriteRight() { |
| zx::result endpoints = fidl::CreateEndpoints<Values>(); |
| EXPECT_OK(endpoints.status_value()); |
| if (!endpoints.is_ok()) |
| return {}; |
| |
| auto [client_end, server_end] = std::move(*endpoints); |
| { |
| zx::channel client_channel_non_writable; |
| EXPECT_OK( |
| client_end.channel().replace(ZX_RIGHT_READ | ZX_RIGHT_WAIT, &client_channel_non_writable)); |
| client_end.channel() = std::move(client_channel_non_writable); |
| } |
| |
| return fidl::Endpoints<Values>{std::move(client_end), std::move(server_end)}; |
| } |
| |
| class ExpectErrorResponseContext final : public fidl::WireResponseContext<Values::Echo> { |
| public: |
| explicit ExpectErrorResponseContext(sync_completion_t* did_error, zx_status_t expected_status, |
| fidl::Reason expected_reason) |
| : did_error_(did_error), |
| expected_status_(expected_status), |
| expected_reason_(expected_reason) {} |
| |
| void OnResult(fidl::WireUnownedResult<Values::Echo>& result) override { |
| EXPECT_TRUE(!result.ok()); |
| EXPECT_STATUS(expected_status_, result.status()); |
| EXPECT_EQ(expected_reason_, result.error().reason()); |
| sync_completion_signal(did_error_); |
| } |
| |
| private: |
| sync_completion_t* did_error_; |
| zx_status_t expected_status_; |
| fidl::Reason expected_reason_; |
| }; |
| |
| // If writing to the channel fails, the response context ownership should be |
| // released back to the user with a call to |OnError|. |
| TEST(GenAPITestCase, ResponseContextOwnershipReleasedOnError) { |
| fidl::Endpoints<Values> endpoints; |
| ASSERT_NO_FAILURES(endpoints = CreateEndpointsWithoutClientWriteRight()); |
| auto [client_end, server_end] = std::move(endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| fidl::WireSharedClient<Values> client(std::move(client_end), loop.dispatcher()); |
| loop.StartThread("client-test"); |
| |
| libsync::Completion error; |
| ExpectErrorResponseContext context(error.get(), ZX_ERR_ACCESS_DENIED, |
| fidl::Reason::kTransportError); |
| |
| fidl::AsyncClientBuffer<Values::Echo> buffer; |
| client.buffer(buffer.view())->Echo("foo").ThenExactlyOnce(&context); |
| ASSERT_OK(error.Wait()); |
| } |
| |
| TEST(GenAPITestCase, AsyncNotifySendError) { |
| auto do_test = [](auto&& client_instance_indicator) { |
| using ClientType = cpp20::remove_cvref_t<decltype(client_instance_indicator)>; |
| fidl::Endpoints<Values> endpoints; |
| ASSERT_NO_FAILURES(endpoints = CreateEndpointsWithoutClientWriteRight()); |
| auto [local, remote] = std::move(endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ClientType client(std::move(local), loop.dispatcher()); |
| |
| libsync::Completion error; |
| ExpectErrorResponseContext context(error.get(), ZX_ERR_ACCESS_DENIED, |
| fidl::Reason::kTransportError); |
| |
| fidl::AsyncClientBuffer<Values::Echo> buffer; |
| client.buffer(buffer.view())->Echo("foo").ThenExactlyOnce(&context); |
| // The context should be asynchronously notified. |
| EXPECT_FALSE(error.signaled()); |
| loop.RunUntilIdle(); |
| EXPECT_TRUE(error.signaled()); |
| }; |
| |
| do_test(fidl::WireClient<Values>{}); |
| do_test(fidl::WireSharedClient<Values>{}); |
| } |
| |
| TEST(GenAPITestCase, AsyncNotifyTeardownError) { |
| fidl::Endpoints<Values> endpoints; |
| ASSERT_NO_FAILURES(endpoints = CreateEndpointsWithoutClientWriteRight()); |
| auto [local, remote] = std::move(endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| fidl::WireSharedClient<Values> client(std::move(local), loop.dispatcher()); |
| client.AsyncTeardown(); |
| loop.RunUntilIdle(); |
| |
| libsync::Completion error; |
| ExpectErrorResponseContext context(error.get(), ZX_ERR_CANCELED, fidl::Reason::kUnbind); |
| |
| fidl::AsyncClientBuffer<Values::Echo> buffer; |
| client.buffer(buffer.view())->Echo("foo").ThenExactlyOnce(&context); |
| EXPECT_FALSE(error.signaled()); |
| loop.RunUntilIdle(); |
| EXPECT_TRUE(error.signaled()); |
| } |
| |
| TEST(GenAPITestCase, SyncNotifyErrorIfDispatcherShutdown) { |
| auto do_test = [](auto&& client_instance_indicator) { |
| using ClientType = cpp20::remove_cvref_t<decltype(client_instance_indicator)>; |
| fidl::Endpoints<Values> endpoints; |
| ASSERT_NO_FAILURES(endpoints = CreateEndpointsWithoutClientWriteRight()); |
| auto [local, remote] = std::move(endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ClientType client(std::move(local), loop.dispatcher()); |
| |
| libsync::Completion error; |
| // Note that the reason is |kUnbind| because shutting down the loop will |
| // synchronously teardown the client. Once the internal bindings object is |
| // destroyed, the client would forget what was the original reason for |
| // teardown (kDispatcherError). |
| // |
| // We may want to improve the post-teardown error fidelity by remembering |
| // the reason on the client object. |
| ExpectErrorResponseContext context(error.get(), ZX_ERR_CANCELED, fidl::Reason::kUnbind); |
| |
| loop.Shutdown(); |
| EXPECT_FALSE(error.signaled()); |
| |
| fidl::AsyncClientBuffer<Values::Echo> buffer; |
| client.buffer(buffer.view())->Echo("foo").ThenExactlyOnce(&context); |
| // If the loop was shutdown, |context| should still be notified, although |
| // it has to happen on the current stack frame. |
| EXPECT_TRUE(error.signaled()); |
| }; |
| |
| do_test(fidl::WireClient<Values>{}); |
| do_test(fidl::WireSharedClient<Values>{}); |
| } |
| |
| // An integration-style test that verifies that user-supplied async callbacks |
| // attached using |Then| with client lifetime are not invoked when the client is |
| // destroyed by the user (i.e. explicit cancellation) instead of due to errors. |
| TEST(GenAPITestCase, ThenWithClientLifetime) { |
| auto do_test = [](auto&& client_instance_indicator) { |
| using ClientType = cpp20::remove_cvref_t<decltype(client_instance_indicator)>; |
| auto endpoints = fidl::CreateEndpoints<Values>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| |
| struct Receiver { |
| ClientType client; |
| }; |
| Receiver receiver{ClientType(std::move(local), loop.dispatcher())}; |
| |
| bool destroyed = false; |
| receiver.client->Echo("foo").Then([observer = fit::defer([&] { destroyed = true; })]( |
| fidl::WireUnownedResult<Values::Echo>& result) { |
| ADD_FATAL_FAILURE("Should not be invoked"); |
| }); |
| // Immediately start cancellation. |
| receiver = {}; |
| ASSERT_FALSE(destroyed); |
| loop.RunUntilIdle(); |
| |
| // The callback should be destroyed without being called. |
| ASSERT_TRUE(destroyed); |
| }; |
| |
| do_test(fidl::WireClient<Values>{}); |
| do_test(fidl::WireSharedClient<Values>{}); |
| } |
| |
| // An integration-style test that verifies that user-supplied async callbacks |
| // that takes |fidl::WireUnownedResult| are correctly notified when the binding |
| // is torn down by the user (i.e. explicit cancellation). |
| TEST(GenAPITestCase, ThenExactlyOnce) { |
| auto do_test = [](auto&& client_instance_indicator) { |
| using ClientType = cpp20::remove_cvref_t<decltype(client_instance_indicator)>; |
| auto endpoints = fidl::CreateEndpoints<Values>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ClientType client(std::move(local), loop.dispatcher()); |
| bool called = false; |
| bool destroyed = false; |
| auto callback_destruction_observer = fit::defer([&] { destroyed = true; }); |
| |
| client->Echo("foo").ThenExactlyOnce([observer = std::move(callback_destruction_observer), |
| &called](fidl::WireUnownedResult<Values::Echo>& result) { |
| called = true; |
| EXPECT_STATUS(ZX_ERR_CANCELED, result.status()); |
| EXPECT_EQ(fidl::Reason::kUnbind, result.reason()); |
| }); |
| // Immediately start cancellation. |
| client = {}; |
| loop.RunUntilIdle(); |
| |
| ASSERT_TRUE(called); |
| // The callback should be destroyed after being called. |
| ASSERT_TRUE(destroyed); |
| }; |
| |
| do_test(fidl::WireClient<Values>{}); |
| do_test(fidl::WireSharedClient<Values>{}); |
| } |
| |
| // The client should not notify the user of teardown completion until all |
| // up-calls to user code have finished. This is essential for a two-phase |
| // shutdown pattern to prevent use-after-free. |
| TEST(WireSharedClient, TeardownCompletesAfterUserCallbackReturns) { |
| // This invariant should hold regardless of how many threads are on the |
| // dispatcher. |
| for (int num_threads = 1; num_threads < 4; num_threads++) { |
| auto endpoints = fidl::CreateEndpoints<test_basic_protocol::ResourceEvent>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| for (int i = 0; i < num_threads; i++) { |
| ASSERT_OK(loop.StartThread()); |
| } |
| |
| class EventHandler : public fidl::WireAsyncEventHandler<test_basic_protocol::ResourceEvent> { |
| void OnResourceEvent( |
| fidl::WireEvent<test_basic_protocol::ResourceEvent::OnResourceEvent>* event) override { |
| // Signal to the test that the dispatcher thread has entered into |
| // a user callback. |
| event_ = zx::eventpair(event->h.release()); |
| event_.signal_peer(ZX_SIGNAL_NONE, ZX_USER_SIGNAL_0); |
| |
| // Block the user callback until |ZX_USER_SIGNAL_1| is observed. |
| zx_signals_t observed; |
| ASSERT_OK(event_.wait_one(ZX_USER_SIGNAL_1, zx::time::infinite(), &observed)); |
| ASSERT_EQ(ZX_USER_SIGNAL_1, observed); |
| } |
| |
| private: |
| zx::eventpair event_; |
| }; |
| |
| fidl::WireSharedClient<test_basic_protocol::ResourceEvent> client( |
| std::move(local), loop.dispatcher(), std::make_unique<EventHandler>()); |
| |
| zx::eventpair ep1, ep2; |
| ASSERT_OK(zx::eventpair::create(0, &ep1, &ep2)); |
| ASSERT_OK(fidl::WireSendEvent(remote)->OnResourceEvent(std::move(ep1))); |
| |
| zx_signals_t observed; |
| ASSERT_OK(zx_object_wait_one(ep2.get(), ZX_USER_SIGNAL_0, ZX_TIME_INFINITE, &observed)); |
| ASSERT_EQ(ZX_USER_SIGNAL_0, observed); |
| |
| // Initiate teardown. The |EventHandler| must not be destroyed until the |
| // |OnResourceEvent| callback returns. |
| client.AsyncTeardown(); |
| ASSERT_EQ(ZX_ERR_TIMED_OUT, |
| ep2.wait_one(ZX_EVENTPAIR_PEER_CLOSED, async::Now(loop.dispatcher()) + zx::msec(250), |
| &observed)); |
| |
| ep2.signal_peer(ZX_SIGNAL_NONE, ZX_USER_SIGNAL_1); |
| ASSERT_OK(ep2.wait_one(ZX_EVENTPAIR_PEER_CLOSED, zx::time::infinite(), &observed)); |
| } |
| } |
| |
| // After the first call fails during sending, the client bindings should |
| // teardown thereby disallowing subsequent calls. In addition, the user should |
| // receive an error in the event handler. |
| TEST(AllClients, SendErrorLeadsToBindingTeardown) { |
| auto do_test = [](auto&& client_instance_indicator) { |
| using ClientType = cpp20::remove_cvref_t<decltype(client_instance_indicator)>; |
| fidl::Endpoints<Values> endpoints; |
| ASSERT_NO_FAILURES(endpoints = CreateEndpointsWithoutClientWriteRight()); |
| auto [local, remote] = std::move(endpoints); |
| |
| class EventHandler : public fidl::WireAsyncEventHandler<Values> { |
| public: |
| void on_fidl_error(fidl::UnbindInfo info) override { |
| errored_ = true; |
| EXPECT_STATUS(ZX_ERR_ACCESS_DENIED, info.status()); |
| EXPECT_EQ(fidl::Reason::kTransportError, info.reason()); |
| } |
| |
| bool errored() const { return errored_; } |
| |
| private: |
| bool errored_ = false; |
| } event_handler; |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ClientType client(std::move(local), loop.dispatcher(), &event_handler); |
| |
| EXPECT_FALSE(event_handler.errored()); |
| client->Echo("foo").ThenExactlyOnce([](fidl::WireUnownedResult<Values::Echo>&) {}); |
| loop.RunUntilIdle(); |
| EXPECT_TRUE(event_handler.errored()); |
| |
| bool called = false; |
| client->Echo("foo").ThenExactlyOnce([&called](fidl::WireUnownedResult<Values::Echo>& result) { |
| called = true; |
| EXPECT_EQ(fidl::Reason::kUnbind, result.reason()); |
| EXPECT_STATUS(ZX_ERR_CANCELED, result.status()); |
| }); |
| loop.RunUntilIdle(); |
| EXPECT_TRUE(called); |
| }; |
| |
| do_test(fidl::WireClient<Values>{}); |
| do_test(fidl::WireSharedClient<Values>{}); |
| } |
| |
| // If a call fails due to a peer closed error, the client bindings should still |
| // process any remaining messages received on the endpoint before tearing down. |
| TEST(AllClients, DrainAllMessageInPeerClosedSendError) { |
| auto do_test = [](auto&& client_instance_indicator) { |
| using ClientType = cpp20::remove_cvref_t<decltype(client_instance_indicator)>; |
| zx::result endpoints = fidl::CreateEndpoints<Values>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = std::move(*endpoints); |
| |
| static constexpr char data[] = "test"; |
| class EventHandler : public fidl::WireAsyncEventHandler<Values> { |
| public: |
| EventHandler() = default; |
| |
| bool received() const { return received_; } |
| |
| void OnValueEvent(fidl::WireEvent<Values::OnValueEvent>* event) override { |
| ASSERT_EQ(strlen(data), event->s.size()); |
| EXPECT_EQ(0, strncmp(event->s.data(), data, strlen(data))); |
| received_ = true; |
| } |
| |
| private: |
| bool received_ = false; |
| } event_handler; |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ClientType client(std::move(local), loop.dispatcher(), &event_handler); |
| |
| // Send an event and close the server endpoint. |
| ASSERT_OK(fidl::WireSendEvent(remote)->OnValueEvent(fidl::StringView(data))); |
| remote.reset(); |
| |
| // The event should not be received unless the |loop| was polled. |
| EXPECT_FALSE(event_handler.received()); |
| |
| // Make a client method call which should fail, but not interfere with |
| // reading the event. |
| { |
| fidl::Status result = client->OneWay("foo"); |
| EXPECT_EQ(fidl::Reason::kPeerClosed, result.reason()); |
| EXPECT_STATUS(ZX_ERR_PEER_CLOSED, result.status()); |
| } |
| ASSERT_OK(loop.RunUntilIdle()); |
| EXPECT_TRUE(event_handler.received()); |
| |
| // The client binding should still be torn down. |
| { |
| fidl::Status result = client->OneWay("foo"); |
| EXPECT_EQ(fidl::Reason::kUnbind, result.reason()); |
| EXPECT_STATUS(ZX_ERR_CANCELED, result.status()); |
| } |
| }; |
| |
| do_test(fidl::WireClient<Values>{}); |
| do_test(fidl::WireSharedClient<Values>{}); |
| } |
| |
| } // namespace |