From b4f7df2e2e843c55818985c20afed3044a899cbb Mon Sep 17 00:00:00 2001 From: RJ Ascani Date: Thu, 28 Apr 2022 17:29:39 -0700 Subject: [PATCH] pw_rpc: Enable SynchronousCall wrappers for pwpb The SynchronousCall wrappers that were written for nanopb are also compatible with pwpb. This CL migrates the headers to a common directory and adds tests specific to pwpb. Change-Id: I838dc8bf54f8315fdf09911b93db8132972e79da Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/92761 Reviewed-by: Scott James Remnant Reviewed-by: Wyatt Hepler Pigweed-Auto-Submit: RJ Ascani Commit-Queue: Auto-Submit --- pw_rpc/BUILD.bazel | 14 ++ pw_rpc/BUILD.gn | 14 ++ pw_rpc/CMakeLists.txt | 11 + pw_rpc/docs.rst | 75 ++++++ pw_rpc/nanopb/BUILD.bazel | 17 +- pw_rpc/nanopb/BUILD.gn | 14 +- pw_rpc/nanopb/CMakeLists.txt | 1 + pw_rpc/nanopb/docs.rst | 75 ------ pw_rpc/nanopb/synchronous_call_test.cc | 2 +- .../pw_rpc}/synchronous_call.h | 2 +- .../pw_rpc}/synchronous_call_result.h | 0 pw_rpc/pwpb/BUILD.bazel | 13 + pw_rpc/pwpb/BUILD.gn | 16 +- pw_rpc/pwpb/CMakeLists.txt | 1 + pw_rpc/pwpb/synchronous_call_test.cc | 234 ++++++++++++++++++ 15 files changed, 383 insertions(+), 106 deletions(-) rename pw_rpc/{nanopb/public/pw_rpc/nanopb => public/pw_rpc}/synchronous_call.h (99%) rename pw_rpc/{nanopb/public/pw_rpc/nanopb => public/pw_rpc}/synchronous_call_result.h (100%) create mode 100644 pw_rpc/pwpb/synchronous_call_test.cc diff --git a/pw_rpc/BUILD.bazel b/pw_rpc/BUILD.bazel index 402da1fb21..3d911c8818 100644 --- a/pw_rpc/BUILD.bazel +++ b/pw_rpc/BUILD.bazel @@ -118,6 +118,20 @@ pw_cc_library( ], ) +pw_cc_library( + name = "synchronous_client_api", + hdrs = [ + "public/pw_rpc/synchronous_call.h", + "public/pw_rpc/synchronous_call_result.h", + ], + includes = ["public"], + deps = [ + ":pw_rpc", + "//pw_chrono:system_clock", + "//pw_sync:timed_thread_notification", + ], +) + pw_cc_library( name = "thread_testing", hdrs = ["public/pw_rpc/thread_testing.h"], diff --git a/pw_rpc/BUILD.gn b/pw_rpc/BUILD.gn index 3dc0d1c57c..4b8a7b35b2 100644 --- a/pw_rpc/BUILD.gn +++ b/pw_rpc/BUILD.gn @@ -115,6 +115,20 @@ pw_source_set("client_server") { sources = [ "client_server.cc" ] } +pw_source_set("synchronous_client_api") { + public_configs = [ ":public_include_path" ] + public_deps = [ + ":client", + ":common", + "$dir_pw_chrono:system_clock", + "$dir_pw_sync:timed_thread_notification", + ] + public = [ + "public/pw_rpc/synchronous_call.h", + "public/pw_rpc/synchronous_call_result.h", + ] +} + # Classes shared by the server and client. pw_source_set("common") { public_configs = [ ":public_include_path" ] diff --git a/pw_rpc/CMakeLists.txt b/pw_rpc/CMakeLists.txt index 022f2827b9..9c372ec169 100644 --- a/pw_rpc/CMakeLists.txt +++ b/pw_rpc/CMakeLists.txt @@ -64,6 +64,17 @@ if(Zephyr_FOUND AND CONFIG_PIGWEED_RPC_CLIENT_SERVER) zephyr_link_libraries(pw_rpc.client_server) endif() +pw_add_module_library(pw_rpc.synchronous_client_api + HEADERS + public/pw_rpc/synchronous_call.h + public/pw_rpc/synchronous_call_result.h + PUBLIC_DEPS + pw_chono.system_clock + pw_rpc.client + pw_rpc.common + pw_sync.timed_thread_notification +) + pw_add_module_library(pw_rpc.common SOURCES channel.cc diff --git a/pw_rpc/docs.rst b/pw_rpc/docs.rst index 71bc1c8598..d5732d1b4c 100644 --- a/pw_rpc/docs.rst +++ b/pw_rpc/docs.rst @@ -876,6 +876,81 @@ Example } } +Client Synchronous Call wrappers +-------------------------------- +If synchronous behavior is desired when making client calls, users can use one +of the ``SynchronousCall`` wrapper functions to make their RPC call. +These wrappers effectively wrap the asynchronous Client RPC call with a timed +thread notification and return once a result is known or a timeout has occurred. +These return a ``SynchronousCallResult`` object, which can be queried +to determine whether any error scenarios occurred and, if not, access the +response. + +``SynchronousCall`` will block indefinitely, whereas +``SynchronousCallFor`` and ``SynchronousCallUntil`` will +block for a given timeout or until a deadline, respectively. All wrappers work +with both the standalone static RPC functions and the generated Client member +methods. + +.. note:: Use of the SynchronousCall wrappers requires a TimedThreadNotification + backend. +.. note:: Only nanopb and pw_protobuf Unary RPC methods are supported. + +Example +^^^^^^^ +.. code-block:: c++ + + #include "pw_rpc/synchronous_call.h" + + void InvokeUnaryRpc() { + pw::rpc::Client client; + pw::rpc::Channel channel; + + RoomInfoRequest request; + SynchronousCallResult result = + SynchronousCall(client, channel.id(), request); + + if (result.is_rpc_error()) { + ShutdownClient(client); + } else if (result.is_server_error()) { + HandleServerError(result.status()); + } else if (result.is_timeout()) { + // SynchronousCall will block indefinitely, so we should never get here. + PW_UNREACHABLE(); + } + HandleRoomInformation(std::move(result).response()); + } + + void AnotherExample() { + pw_rpc::nanopb::Chat::Client chat_client(client, channel); + constexpr auto kTimeout = pw::chrono::SystemClock::for_at_least(500ms); + + RoomInfoRequest request; + auto result = SynchronousCallFor( + chat_client, request, kTimeout); + + if (result.is_timeout()) { + RetryRoomRequest(); + } else { + ... + } + } + +The ``SynchronousCallResult`` is also compatible with the PW_TRY +family of macros, but users should be aware that their use will lose information +about the type of error. This should only be used if the caller will handle all +error scenarios the same. + +.. code-block:: c++ + + pw::Status SyncRpc() { + const RoomInfoRequest request; + PW_TRY_ASSIGN(const RoomInfoResponse& response, + SynchronousCall(client, request)); + HandleRoomInformation(response); + return pw::OkStatus(); + } + Client implementation details ----------------------------- diff --git a/pw_rpc/nanopb/BUILD.bazel b/pw_rpc/nanopb/BUILD.bazel index 4b06f4643a..76079ecea6 100644 --- a/pw_rpc/nanopb/BUILD.bazel +++ b/pw_rpc/nanopb/BUILD.bazel @@ -51,19 +51,6 @@ pw_cc_library( ], ) -pw_cc_library( - name = "synchronous_client_api", - hdrs = [ - "public/pw_rpc/nanopb/synchronous_call.h", - "public/pw_rpc/nanopb/synchronous_call_result.h", - ], - includes = ["public"], - deps = [ - ":client_api", - "//pw_sync:timed_thread_notification", - ], -) - pw_cc_library( name = "common", srcs = ["common.cc"], @@ -275,12 +262,12 @@ pw_cc_test( ) pw_cc_test( - name = "synchronos_call_test", + name = "synchronous_call_test", srcs = ["synchronous_call_test.cc"], deps = [ - ":synchronous_client_api", ":test_method_context", "//pw_rpc:pw_rpc_test_cc.nanopb_rpc", + "//pw_rpc:synchronous_client_api", "//pw_work_queue", "//pw_work_queue:stl_test_thread", "//pw_work_queue:test_thread_header", diff --git a/pw_rpc/nanopb/BUILD.gn b/pw_rpc/nanopb/BUILD.gn index 842cc507ca..25252099e7 100644 --- a/pw_rpc/nanopb/BUILD.gn +++ b/pw_rpc/nanopb/BUILD.gn @@ -60,18 +60,6 @@ pw_source_set("client_api") { public = [ "public/pw_rpc/nanopb/client_reader_writer.h" ] } -pw_source_set("synchronous_client_api") { - public_configs = [ ":public" ] - public_deps = [ - ":client_api", - "$dir_pw_sync:timed_thread_notification", - ] - public = [ - "public/pw_rpc/nanopb/synchronous_call.h", - "public/pw_rpc/nanopb/synchronous_call_result.h", - ] -} - pw_source_set("common") { public_deps = [ "..:common", @@ -299,11 +287,11 @@ pw_test("stub_generation_test") { pw_test("synchronous_call_test") { deps = [ - ":synchronous_client_api", ":test_method_context", "$dir_pw_work_queue:pw_work_queue", "$dir_pw_work_queue:stl_test_thread", "$dir_pw_work_queue:test_thread", + "..:synchronous_client_api", "..:test_protos.nanopb_rpc", ] sources = [ "synchronous_call_test.cc" ] diff --git a/pw_rpc/nanopb/CMakeLists.txt b/pw_rpc/nanopb/CMakeLists.txt index 2b5d36cf77..8b16b63cdc 100644 --- a/pw_rpc/nanopb/CMakeLists.txt +++ b/pw_rpc/nanopb/CMakeLists.txt @@ -76,6 +76,7 @@ pw_auto_add_module_tests(pw_rpc.nanopb pw_rpc.client pw_rpc.raw pw_rpc.server + pw_rpc.synchronous_client_api pw_rpc.nanopb.common pw_rpc.protos.nanopb_rpc pw_rpc.test_protos.nanopb_rpc diff --git a/pw_rpc/nanopb/docs.rst b/pw_rpc/nanopb/docs.rst index 3be0eba610..5ed7010f13 100644 --- a/pw_rpc/nanopb/docs.rst +++ b/pw_rpc/nanopb/docs.rst @@ -263,81 +263,6 @@ service client and receive the response. // Do other stuff now that we have the room information. } -Client Synchronous Call wrappers -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If synchronous behavior is desired when making client calls, users can use one -of the ``SynchronousCall`` wrapper functions to make their RPC call. -These wrappers effectively wrap the asynchronous Client RPC call with a timed -thread notification and return once a result is known or a timeout has occurred. -These return a ``SynchronousCallResult`` object, which can be queried -to determine whether any error scenarios occurred and, if not, access the -response. - -``SynchronousCall`` will block indefinitely, whereas -``SynchronousCallFor`` and ``SynchronousCallUntil`` will -block for a given timeout or until a deadline, respectively. All wrappers work -with both the standalone static RPC functions and the generated Client member -methods. - -.. note:: Use of the SynchronousCall wrappers requires a TimedThreadNotification - backend. -.. note:: Only Unary RPC methods are supported. - -Example Usage -~~~~~~~~~~~~~ -.. code-block:: c++ - - #include "pw_rpc/nanopb/synchronous_call.h" - - void InvokeUnaryRpc() { - pw::rpc::Client client; - pw::rpc::Channel channel; - - RoomInfoRequest request; - SynchronousCallResult result = - SynchronousCall(client, channel.id(), request); - - if (result.is_rpc_error()) { - ShutdownClient(client); - } else if (result.is_server_error()) { - HandleServerError(result.status()); - } else if (result.is_timeout()) { - // SynchronousCall will block indefinitely, so we should never get here. - PW_UNREACHABLE(); - } - HandleRoomInformation(std::move(result).response()); - } - - void AnotherExample() { - pw_rpc::nanopb::Chat::Client chat_client(client, channel); - constexpr auto kTimeout = pw::chrono::SystemClock::for_at_least(500ms); - - RoomInfoRequest request; - auto result = SynchronousCallFor( - chat_client, request, kTimeout); - - if (result.is_timeout()) { - RetryRoomRequest(); - } else { - ... - } - } - -The ``SynchronousCallResult`` is also compatible with the PW_TRY -family of macros, but users should be aware that their use will lose information -about the type of error. This should only be used if the caller will handle all -error scenarios the same. - -.. code-block:: c++ - - pw::Status SyncRpc() { - const RoomInfoRequest request; - PW_TRY_ASSIGN(const RoomInfoResponse& response, - SynchronousCall(client, request)); - HandleRoomInformation(response); - return pw::OkStatus(); - } - Zephyr ====== To enable ``pw_rpc.nanopb.*`` for Zephyr add ``CONFIG_PIGWEED_RPC_NANOPB=y`` to diff --git a/pw_rpc/nanopb/synchronous_call_test.cc b/pw_rpc/nanopb/synchronous_call_test.cc index ff7b884f25..67868b2bf5 100644 --- a/pw_rpc/nanopb/synchronous_call_test.cc +++ b/pw_rpc/nanopb/synchronous_call_test.cc @@ -12,7 +12,7 @@ // License for the specific language governing permissions and limitations under // the License. -#include "pw_rpc/nanopb/synchronous_call.h" +#include "pw_rpc/synchronous_call.h" #include diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/synchronous_call.h b/pw_rpc/public/pw_rpc/synchronous_call.h similarity index 99% rename from pw_rpc/nanopb/public/pw_rpc/nanopb/synchronous_call.h rename to pw_rpc/public/pw_rpc/synchronous_call.h index 2623cf2f7c..5559705fc5 100644 --- a/pw_rpc/nanopb/public/pw_rpc/nanopb/synchronous_call.h +++ b/pw_rpc/public/pw_rpc/synchronous_call.h @@ -16,7 +16,7 @@ #include "pw_chrono/system_clock.h" #include "pw_rpc/client.h" #include "pw_rpc/internal/method_info.h" -#include "pw_rpc/nanopb/synchronous_call_result.h" +#include "pw_rpc/synchronous_call_result.h" #include "pw_sync/timed_thread_notification.h" // Synchronous Call wrappers diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/synchronous_call_result.h b/pw_rpc/public/pw_rpc/synchronous_call_result.h similarity index 100% rename from pw_rpc/nanopb/public/pw_rpc/nanopb/synchronous_call_result.h rename to pw_rpc/public/pw_rpc/synchronous_call_result.h diff --git a/pw_rpc/pwpb/BUILD.bazel b/pw_rpc/pwpb/BUILD.bazel index 383bcde499..f3c107f445 100644 --- a/pw_rpc/pwpb/BUILD.bazel +++ b/pw_rpc/pwpb/BUILD.bazel @@ -256,3 +256,16 @@ pw_cc_test( "//pw_rpc:pw_rpc_test_cc.pwpb_rpc", ], ) + +pw_cc_test( + name = "synchronous_call_test", + srcs = ["synchronous_call_test.cc"], + deps = [ + ":test_method_context", + "//pw_rpc:pw_rpc_test_cc.pwpb_rpc", + "//pw_rpc:synchronous_client_api", + "//pw_work_queue", + "//pw_work_queue:stl_test_thread", + "//pw_work_queue:test_thread_header", + ], +) diff --git a/pw_rpc/pwpb/BUILD.gn b/pw_rpc/pwpb/BUILD.gn index c6e4ae36ef..b7ad11bbbe 100644 --- a/pw_rpc/pwpb/BUILD.gn +++ b/pw_rpc/pwpb/BUILD.gn @@ -16,6 +16,7 @@ import("//build_overrides/pigweed.gni") import("$dir_pw_build/target_types.gni") import("$dir_pw_docgen/docs.gni") +import("$dir_pw_sync/backend.gni") import("$dir_pw_unit_test/test.gni") config("public") { @@ -137,9 +138,9 @@ pw_test_group("tests") { ":method_union_test", ":server_callback_test", ":server_reader_writer_test", - ":serde_test", ":stub_generation_test", + ":synchronous_call_test", ] } @@ -263,3 +264,16 @@ pw_test("stub_generation_test") { deps = [ "..:test_protos.pwpb_rpc" ] sources = [ "stub_generation_test.cc" ] } + +pw_test("synchronous_call_test") { + deps = [ + ":test_method_context", + "$dir_pw_work_queue:pw_work_queue", + "$dir_pw_work_queue:stl_test_thread", + "$dir_pw_work_queue:test_thread", + "..:synchronous_client_api", + "..:test_protos.pwpb_rpc", + ] + sources = [ "synchronous_call_test.cc" ] + enable_if = pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND != "" +} diff --git a/pw_rpc/pwpb/CMakeLists.txt b/pw_rpc/pwpb/CMakeLists.txt index d175f94ba2..c40900e646 100644 --- a/pw_rpc/pwpb/CMakeLists.txt +++ b/pw_rpc/pwpb/CMakeLists.txt @@ -58,6 +58,7 @@ pw_auto_add_module_tests(pw_rpc.pwpb pw_rpc.client pw_rpc.raw pw_rpc.server + pw_rpc.synchronous_client_api pw_rpc.pwpb.common pw_rpc.protos.pwpb_rpc pw_rpc.test_protos.pwpb_rpc diff --git a/pw_rpc/pwpb/synchronous_call_test.cc b/pw_rpc/pwpb/synchronous_call_test.cc new file mode 100644 index 0000000000..197355c746 --- /dev/null +++ b/pw_rpc/pwpb/synchronous_call_test.cc @@ -0,0 +1,234 @@ +// Copyright 2022 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#include "pw_rpc/synchronous_call.h" + +#include + +#include "gtest/gtest.h" +#include "pw_chrono/system_clock.h" +#include "pw_rpc/channel.h" +#include "pw_rpc/internal/packet.h" +#include "pw_rpc/pwpb/fake_channel_output.h" +#include "pw_rpc_test_protos/test.rpc.pwpb.h" +#include "pw_status/status.h" +#include "pw_status/status_with_size.h" +#include "pw_thread/thread.h" +#include "pw_work_queue/test_thread.h" +#include "pw_work_queue/work_queue.h" + +namespace pw::rpc::test { +namespace { + +using pw::rpc::test::pw_rpc::pwpb::TestService; +using MethodInfo = internal::MethodInfo; + +class SynchronousCallTest : public ::testing::Test { + public: + SynchronousCallTest() + : channels_({{Channel::Create<42>(&fake_output_)}}), client_(channels_) {} + + void SetUp() override { + work_thread_ = + thread::Thread(work_queue::test::WorkQueueThreadOptions(), work_queue_); + } + + void TearDown() override { + work_queue_.RequestStop(); + work_thread_.join(); + } + + protected: + using FakeChannelOutput = PwpbFakeChannelOutput<2>; + + void OnSend(std::span buffer, Status status) { + if (!status.ok()) { + return; + } + auto result = internal::Packet::FromBuffer(buffer); + EXPECT_TRUE(result.ok()); + request_packet_ = *result; + + EXPECT_TRUE(work_queue_.PushWork([this]() { SendResponse(); }).ok()); + } + + void SendResponse() { + std::array buffer; + std::array payload_buffer; + + StatusWithSize size_status = + MethodInfo::serde().EncodeResponse(response_, payload_buffer); + EXPECT_TRUE(size_status.ok()); + + auto response = + internal::Packet::Response(request_packet_, response_status_); + response.set_payload({payload_buffer.data(), size_status.size()}); + EXPECT_TRUE(client_.ProcessPacket(response.Encode(buffer).value()).ok()); + } + + void set_response(const TestResponse::Message& response, + Status response_status = OkStatus()) { + response_ = response; + response_status_ = response_status; + output().set_on_send([this](std::span buffer, + Status status) { OnSend(buffer, status); }); + } + + MethodInfo::GeneratedClient generated_client() { + return MethodInfo::GeneratedClient(client(), channel().id()); + } + + FakeChannelOutput& output() { return fake_output_; } + const Channel& channel() const { return channels_.front(); } + Client& client() { return client_; } + + private: + FakeChannelOutput fake_output_; + std::array channels_; + Client client_; + thread::Thread work_thread_; + work_queue::WorkQueueWithBuffer<1> work_queue_; + TestResponse::Message response_{}; + Status response_status_ = OkStatus(); + internal::Packet request_packet_; +}; + +TEST_F(SynchronousCallTest, SynchronousCallSuccess) { + TestRequest::Message request{.integer = 5, .status_code = 0}; + TestResponse::Message response{.value = 42, .repeated_field{}}; + + set_response(response, OkStatus()); + + auto result = SynchronousCall( + client(), channel().id(), request); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.response().value, 42); +} + +TEST_F(SynchronousCallTest, SynchronousCallServerError) { + TestRequest::Message request{.integer = 5, .status_code = 0}; + TestResponse::Message response{.value = 42, .repeated_field{}}; + + set_response(response, Status::Internal()); + + auto result = SynchronousCall( + client(), channel().id(), request); + EXPECT_TRUE(result.is_error()); + EXPECT_EQ(result.status(), Status::Internal()); + + // We should still receive the response + EXPECT_TRUE(result.is_server_response()); + EXPECT_EQ(result.response().value, 42); +} + +TEST_F(SynchronousCallTest, SynchronousCallRpcError) { + TestRequest::Message request{.integer = 5, .status_code = 0}; + + // Internally, if Channel receives a non-ok status from the + // ChannelOutput::Send, it will always return Unknown. + output().set_send_status(Status::Unknown()); + + auto result = SynchronousCall( + client(), channel().id(), request); + EXPECT_TRUE(result.is_rpc_error()); + EXPECT_EQ(result.status(), Status::Unknown()); +} + +TEST_F(SynchronousCallTest, SynchronousCallForTimeoutError) { + TestRequest::Message request{.integer = 5, .status_code = 0}; + + auto result = SynchronousCallFor( + client(), + channel().id(), + request, + chrono::SystemClock::for_at_least(std::chrono::milliseconds(1))); + + EXPECT_TRUE(result.is_timeout()); + EXPECT_EQ(result.status(), Status::DeadlineExceeded()); +} + +TEST_F(SynchronousCallTest, SynchronousCallUntilTimeoutError) { + TestRequest::Message request{.integer = 5, .status_code = 0}; + + auto result = SynchronousCallUntil( + client(), channel().id(), request, chrono::SystemClock::now()); + + EXPECT_TRUE(result.is_timeout()); + EXPECT_EQ(result.status(), Status::DeadlineExceeded()); +} + +TEST_F(SynchronousCallTest, GeneratedClientSynchronousCallSuccess) { + TestRequest::Message request{.integer = 5, .status_code = 0}; + TestResponse::Message response{.value = 42, .repeated_field{}}; + + set_response(response, OkStatus()); + + auto result = + SynchronousCall(generated_client(), request); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.response().value, 42); +} + +TEST_F(SynchronousCallTest, GeneratedClientSynchronousCallServerError) { + TestRequest::Message request{.integer = 5, .status_code = 0}; + TestResponse::Message response{.value = 42, .repeated_field{}}; + + set_response(response, Status::Internal()); + + auto result = + SynchronousCall(generated_client(), request); + EXPECT_TRUE(result.is_error()); + EXPECT_EQ(result.status(), Status::Internal()); + + // We should still receive the response + EXPECT_TRUE(result.is_server_response()); + EXPECT_EQ(result.response().value, 42); +} + +TEST_F(SynchronousCallTest, GeneratedClientSynchronousCallRpcError) { + TestRequest::Message request{.integer = 5, .status_code = 0}; + + // Internally, if Channel receives a non-ok status from the + // ChannelOutput::Send, it will always return Unknown. + output().set_send_status(Status::Unknown()); + + auto result = + SynchronousCall(generated_client(), request); + EXPECT_TRUE(result.is_rpc_error()); + EXPECT_EQ(result.status(), Status::Unknown()); +} + +TEST_F(SynchronousCallTest, GeneratedClientSynchronousCallForTimeoutError) { + TestRequest::Message request{.integer = 5, .status_code = 0}; + + auto result = SynchronousCallFor( + generated_client(), + request, + chrono::SystemClock::for_at_least(std::chrono::milliseconds(1))); + + EXPECT_TRUE(result.is_timeout()); + EXPECT_EQ(result.status(), Status::DeadlineExceeded()); +} + +TEST_F(SynchronousCallTest, GeneratedClientSynchronousCallUntilTimeoutError) { + TestRequest::Message request{.integer = 5, .status_code = 0}; + + auto result = SynchronousCallUntil( + generated_client(), request, chrono::SystemClock::now()); + + EXPECT_TRUE(result.is_timeout()); + EXPECT_EQ(result.status(), Status::DeadlineExceeded()); +} +} // namespace +} // namespace pw::rpc::test