| // 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/test_helpers.h" |
| |
| #include <mutex> |
| |
| #include "gtest/gtest.h" |
| #include "pw_chrono/system_clock.h" |
| #include "pw_containers/vector.h" |
| #include "pw_result/result.h" |
| #include "pw_rpc/echo.pwpb.h" |
| #include "pw_rpc/echo.rpc.pwpb.h" |
| #include "pw_rpc/pwpb/client_testing.h" |
| #include "pw_rpc/pwpb/server_reader_writer.h" |
| #include "pw_status/status.h" |
| #include "pw_sync/interrupt_spin_lock.h" |
| #include "pw_sync/lock_annotations.h" |
| #include "pw_sync/timed_thread_notification.h" |
| |
| namespace pw::rpc::test { |
| namespace { |
| using namespace std::chrono_literals; |
| |
| constexpr auto kWaitForEchoTimeout = |
| pw::chrono::SystemClock::for_at_least(100ms); |
| |
| // Class that we want to test. |
| // |
| // It's main purpose is to ask EchoService for Echo and provide its result |
| // through WaitForEcho/LastEcho pair to the user. |
| class EntityUnderTest { |
| public: |
| explicit EntityUnderTest(pw_rpc::pwpb::EchoService::Client& echo_client) |
| : echo_client_(echo_client) {} |
| |
| void AskForEcho() { |
| call_ = echo_client_.Echo( |
| EchoMessage::Message{}, |
| [this](const EchoMessage::Message& response, pw::Status status) { |
| lock_.lock(); |
| if (status.ok()) { |
| last_echo_ = response.msg; |
| } else { |
| last_echo_ = status; |
| } |
| lock_.unlock(); |
| notifier_.release(); |
| }, |
| [this](pw::Status status) { |
| lock_.lock(); |
| last_echo_ = status; |
| lock_.unlock(); |
| notifier_.release(); |
| }); |
| } |
| |
| bool WaitForEcho(pw::chrono::SystemClock::duration duration) { |
| return notifier_.try_acquire_for(duration); |
| } |
| |
| pw::Result<pw::Vector<char, 64>> LastEcho() const { |
| std::lock_guard<pw::sync::InterruptSpinLock> lock(lock_); |
| return last_echo_; |
| } |
| |
| private: |
| pw_rpc::pwpb::EchoService::Client& echo_client_; |
| PwpbUnaryReceiver<EchoMessage::Message> call_; |
| pw::sync::TimedThreadNotification notifier_; |
| pw::Result<pw::Vector<char, 64>> last_echo_ PW_GUARDED_BY(lock_); |
| mutable pw::sync::InterruptSpinLock lock_; |
| }; |
| |
| TEST(RpcTestHelpersTest, SendResponseIfCalledOk) { |
| PwpbClientTestContext client_context; |
| pw_rpc::pwpb::EchoService::Client client(client_context.client(), |
| client_context.channel().id()); |
| EntityUnderTest entity(client); |
| |
| // We need to call the function that will initiate the request before we can |
| // send the response back. |
| entity.AskForEcho(); |
| |
| // SendResponseIfCalled blocks until request is received by the service (it is |
| // sent by AskForEcho to EchoService in this case) and responds to it with the |
| // response. |
| // |
| // SendResponseIfCalled will timeout if no request were sent in the `timeout` |
| // interval (see SendResponseIfCalledWithoutRequest test for the example). |
| ASSERT_EQ(SendResponseIfCalled<pw_rpc::pwpb::EchoService::Echo>( |
| client_context, {.msg = "Hello"}), |
| OkStatus()); |
| |
| // After SendResponseIfCalled returned OkStatus client should have received |
| // the response back in the RPC thread, so we can check it here. Because it is |
| // a separate thread we still need to wait with the timeout. |
| ASSERT_TRUE(entity.WaitForEcho(kWaitForEchoTimeout)); |
| |
| pw::Result<pw::Vector<char, 64>> result = entity.LastEcho(); |
| ASSERT_TRUE(result.ok()); |
| EXPECT_EQ(result.value(), (pw::Vector<char, 64>{"Hello"})); |
| } |
| |
| TEST(RpcTestHelpersTest, SendResponseIfCalledNotOk) { |
| PwpbClientTestContext client_context; |
| pw_rpc::pwpb::EchoService::Client client(client_context.client(), |
| client_context.channel().id()); |
| EntityUnderTest entity(client); |
| |
| // We need to call the function that will initiate the request before we can |
| // send the response back. |
| entity.AskForEcho(); |
| |
| // SendResponseIfCalled also can be used to respond with failures. In this |
| // case we are sending back pw::Status::InvalidArgument and expect to see it |
| // on the client side. |
| // |
| // SendResponseIfCalled result status is not the same status as it sends to |
| // the client, so we still are expecting the OkStatus here. |
| ASSERT_EQ(SendResponseIfCalled<pw_rpc::pwpb::EchoService::Echo>( |
| client_context, {}, pw::Status::InvalidArgument()), |
| OkStatus()); |
| |
| // After SendResponseIfCalled returned OkStatus client should have received |
| // the response back in the RPC thread, so we can check it here. Because it is |
| // a separate thread we still need to wait with the timeout. |
| ASSERT_TRUE(entity.WaitForEcho(kWaitForEchoTimeout)); |
| |
| EXPECT_EQ(entity.LastEcho().status(), Status::InvalidArgument()); |
| } |
| |
| TEST(RpcTestHelpersTest, SendResponseIfCalledNotOkShortcut) { |
| PwpbClientTestContext client_context; |
| pw_rpc::pwpb::EchoService::Client client(client_context.client(), |
| client_context.channel().id()); |
| EntityUnderTest entity(client); |
| |
| // We need to call the function that will initiate the request before we can |
| // send the response back. |
| entity.AskForEcho(); |
| |
| // SendResponseIfCalled shortcut version exists to respond with failures. It |
| // works exactly the same, but doesn't have the response argument. In this |
| // case we are sending back pw::Status::InvalidArgument and expect to see it |
| // on the client side. |
| // |
| // SendResponseIfCalled result status is not the same status as it sends to |
| // the client, so we still are expecting the OkStatus here. |
| ASSERT_EQ(SendResponseIfCalled<pw_rpc::pwpb::EchoService::Echo>( |
| client_context, pw::Status::InvalidArgument()), |
| OkStatus()); |
| |
| // After SendResponseIfCalled returned OkStatus client should have received |
| // the response back in the RPC thread, so we can check it here. Because it is |
| // a separate thread we still need to wait with the timeout. |
| ASSERT_TRUE(entity.WaitForEcho(kWaitForEchoTimeout)); |
| |
| EXPECT_EQ(entity.LastEcho().status(), Status::InvalidArgument()); |
| } |
| |
| TEST(RpcTestHelpersTest, SendResponseIfCalledWithoutRequest) { |
| PwpbClientTestContext client_context; |
| pw_rpc::pwpb::EchoService::Client client(client_context.client(), |
| client_context.channel().id()); |
| |
| // We don't send any request in this test and SendResponseIfCalled is expected |
| // to fail on waiting for the request with pw::Status::FailedPrecondition. |
| |
| const auto start_time = pw::chrono::SystemClock::now(); |
| auto status = SendResponseIfCalled<pw_rpc::pwpb::EchoService::Echo>( |
| client_context, |
| {.msg = "Hello"}, |
| pw::OkStatus(), |
| /*timeout=*/pw::chrono::SystemClock::for_at_least(10ms)); |
| |
| // We set our timeout for SendResponseIfCalled to 10ms, so it should be at |
| // least 10ms since we called the SendResponseIfCalled. |
| EXPECT_GE(pw::chrono::SystemClock::now() - start_time, |
| pw::chrono::SystemClock::for_at_least(10ms)); |
| |
| // We expect SendResponseIfCalled to fail, because there were no request sent |
| // for the given method. |
| EXPECT_EQ(status, Status::DeadlineExceeded()); |
| } |
| |
| } // namespace |
| } // namespace pw::rpc::test |