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