• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_rpc/test_helpers.h"
16 
17 #include <mutex>
18 
19 #include "gtest/gtest.h"
20 #include "pw_chrono/system_clock.h"
21 #include "pw_containers/vector.h"
22 #include "pw_result/result.h"
23 #include "pw_rpc/echo.pwpb.h"
24 #include "pw_rpc/echo.rpc.pwpb.h"
25 #include "pw_rpc/pwpb/client_testing.h"
26 #include "pw_rpc/pwpb/server_reader_writer.h"
27 #include "pw_status/status.h"
28 #include "pw_sync/interrupt_spin_lock.h"
29 #include "pw_sync/lock_annotations.h"
30 #include "pw_sync/timed_thread_notification.h"
31 
32 namespace pw::rpc::test {
33 namespace {
34 using namespace std::chrono_literals;
35 
36 constexpr auto kWaitForEchoTimeout =
37     pw::chrono::SystemClock::for_at_least(100ms);
38 
39 // Class that we want to test.
40 //
41 // It's main purpose is to ask EchoService for Echo and provide its result
42 // through WaitForEcho/LastEcho pair to the user.
43 class EntityUnderTest {
44  public:
EntityUnderTest(pw_rpc::pwpb::EchoService::Client & echo_client)45   explicit EntityUnderTest(pw_rpc::pwpb::EchoService::Client& echo_client)
46       : echo_client_(echo_client) {}
47 
AskForEcho()48   void AskForEcho() {
49     call_ = echo_client_.Echo(
50         pwpb::EchoMessage::Message{},
51         [this](const pwpb::EchoMessage::Message& response, pw::Status status) {
52           lock_.lock();
53           if (status.ok()) {
54             last_echo_ = response.msg;
55           } else {
56             last_echo_ = status;
57           }
58           lock_.unlock();
59           notifier_.release();
60         },
61         [this](pw::Status status) {
62           lock_.lock();
63           last_echo_ = status;
64           lock_.unlock();
65           notifier_.release();
66         });
67   }
68 
WaitForEcho(pw::chrono::SystemClock::duration duration)69   bool WaitForEcho(pw::chrono::SystemClock::duration duration) {
70     return notifier_.try_acquire_for(duration);
71   }
72 
LastEcho() const73   pw::Result<pw::InlineString<64>> LastEcho() const {
74     std::lock_guard<pw::sync::InterruptSpinLock> lock(lock_);
75     return last_echo_;
76   }
77 
78  private:
79   pw_rpc::pwpb::EchoService::Client& echo_client_;
80   PwpbUnaryReceiver<pwpb::EchoMessage::Message> call_;
81   pw::sync::TimedThreadNotification notifier_;
82   pw::Result<pw::InlineString<64>> last_echo_ PW_GUARDED_BY(lock_);
83   mutable pw::sync::InterruptSpinLock lock_;
84 };
85 
TEST(RpcTestHelpersTest,SendResponseIfCalledOk)86 TEST(RpcTestHelpersTest, SendResponseIfCalledOk) {
87   PwpbClientTestContext client_context;
88   pw_rpc::pwpb::EchoService::Client client(client_context.client(),
89                                            client_context.channel().id());
90   EntityUnderTest entity(client);
91 
92   // We need to call the function that will initiate the request before we can
93   // send the response back.
94   entity.AskForEcho();
95 
96   // SendResponseIfCalled blocks until request is received by the service (it is
97   // sent by AskForEcho to EchoService in this case) and responds to it with the
98   // response.
99   //
100   // SendResponseIfCalled will timeout if no request were sent in the `timeout`
101   // interval (see SendResponseIfCalledWithoutRequest test for the example).
102   ASSERT_EQ(SendResponseIfCalled<pw_rpc::pwpb::EchoService::Echo>(
103                 client_context, {.msg = "Hello"}),
104             OkStatus());
105 
106   // After SendResponseIfCalled returned OkStatus client should have received
107   // the response back in the RPC thread, so we can check it here. Because it is
108   // a separate thread we still need to wait with the timeout.
109   ASSERT_TRUE(entity.WaitForEcho(kWaitForEchoTimeout));
110 
111   pw::Result<pw::InlineString<64>> result = entity.LastEcho();
112   ASSERT_TRUE(result.ok());
113   EXPECT_EQ(result.value(), "Hello");
114 }
115 
TEST(RpcTestHelpersTest,SendResponseIfCalledNotOk)116 TEST(RpcTestHelpersTest, SendResponseIfCalledNotOk) {
117   PwpbClientTestContext client_context;
118   pw_rpc::pwpb::EchoService::Client client(client_context.client(),
119                                            client_context.channel().id());
120   EntityUnderTest entity(client);
121 
122   // We need to call the function that will initiate the request before we can
123   // send the response back.
124   entity.AskForEcho();
125 
126   // SendResponseIfCalled also can be used to respond with failures. In this
127   // case we are sending back pw::Status::InvalidArgument and expect to see it
128   // on the client side.
129   //
130   // SendResponseIfCalled result status is not the same status as it sends to
131   // the client, so we still are expecting the OkStatus here.
132   ASSERT_EQ(SendResponseIfCalled<pw_rpc::pwpb::EchoService::Echo>(
133                 client_context, {}, pw::Status::InvalidArgument()),
134             OkStatus());
135 
136   // After SendResponseIfCalled returned OkStatus client should have received
137   // the response back in the RPC thread, so we can check it here. Because it is
138   // a separate thread we still need to wait with the timeout.
139   ASSERT_TRUE(entity.WaitForEcho(kWaitForEchoTimeout));
140 
141   EXPECT_EQ(entity.LastEcho().status(), Status::InvalidArgument());
142 }
143 
TEST(RpcTestHelpersTest,SendResponseIfCalledNotOkShortcut)144 TEST(RpcTestHelpersTest, SendResponseIfCalledNotOkShortcut) {
145   PwpbClientTestContext client_context;
146   pw_rpc::pwpb::EchoService::Client client(client_context.client(),
147                                            client_context.channel().id());
148   EntityUnderTest entity(client);
149 
150   // We need to call the function that will initiate the request before we can
151   // send the response back.
152   entity.AskForEcho();
153 
154   // SendResponseIfCalled shortcut version exists to respond with failures. It
155   // works exactly the same, but doesn't have the response argument. In this
156   // case we are sending back pw::Status::InvalidArgument and expect to see it
157   // on the client side.
158   //
159   // SendResponseIfCalled result status is not the same status as it sends to
160   // the client, so we still are expecting the OkStatus here.
161   ASSERT_EQ(SendResponseIfCalled<pw_rpc::pwpb::EchoService::Echo>(
162                 client_context, pw::Status::InvalidArgument()),
163             OkStatus());
164 
165   // After SendResponseIfCalled returned OkStatus client should have received
166   // the response back in the RPC thread, so we can check it here. Because it is
167   // a separate thread we still need to wait with the timeout.
168   ASSERT_TRUE(entity.WaitForEcho(kWaitForEchoTimeout));
169 
170   EXPECT_EQ(entity.LastEcho().status(), Status::InvalidArgument());
171 }
172 
TEST(RpcTestHelpersTest,SendResponseIfCalledWithoutRequest)173 TEST(RpcTestHelpersTest, SendResponseIfCalledWithoutRequest) {
174   PwpbClientTestContext client_context;
175   pw_rpc::pwpb::EchoService::Client client(client_context.client(),
176                                            client_context.channel().id());
177 
178   // We don't send any request in this test and SendResponseIfCalled is expected
179   // to fail on waiting for the request with pw::Status::FailedPrecondition.
180 
181   const auto start_time = pw::chrono::SystemClock::now();
182   auto status = SendResponseIfCalled<pw_rpc::pwpb::EchoService::Echo>(
183       client_context,
184       {.msg = "Hello"},
185       pw::OkStatus(),
186       /*timeout=*/pw::chrono::SystemClock::for_at_least(10ms));
187 
188   // We set our timeout for SendResponseIfCalled to 10ms, so it should be at
189   // least 10ms since we called the SendResponseIfCalled.
190   EXPECT_GE(pw::chrono::SystemClock::now() - start_time,
191             pw::chrono::SystemClock::for_at_least(10ms));
192 
193   // We expect SendResponseIfCalled to fail, because there were no request sent
194   // for the given method.
195   EXPECT_EQ(status, Status::DeadlineExceeded());
196 }
197 
198 }  // namespace
199 }  // namespace pw::rpc::test
200