1 // Copyright 2023 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/fuzz/engine.h"
16
17 #include <chrono>
18
19 #include "gtest/gtest.h"
20 #include "pw_containers/vector.h"
21 #include "pw_log/log.h"
22 #include "pw_rpc/benchmark.h"
23 #include "pw_rpc/internal/client_server_testing_threaded.h"
24 #include "pw_rpc/internal/fake_channel_output.h"
25 #include "pw_thread/test_threads.h"
26
27 namespace pw::rpc::fuzz {
28 namespace {
29
30 using namespace std::literals::chrono_literals;
31
32 // Maximum time, in milliseconds, that can elapse without a call completing or
33 // being dropped in some way..
34 const chrono::SystemClock::duration kTimeout = 5s;
35
36 // These are fairly tight constraints in order to fit within the default
37 // `PW_UNIT_TEST_CONFIG_MEMORY_POOL_SIZE`.
38 constexpr size_t kMaxPackets = 128;
39 constexpr size_t kMaxPayloadSize = 64;
40
41 using BufferedChannelOutputBase =
42 internal::test::FakeChannelOutputBuffer<kMaxPackets, kMaxPayloadSize>;
43
44 /// Channel output backed by a fixed buffer.
45 class BufferedChannelOutput : public BufferedChannelOutputBase {
46 public:
BufferedChannelOutput()47 BufferedChannelOutput() : BufferedChannelOutputBase() {}
48 };
49
50 using FuzzerChannelOutputBase =
51 internal::WatchableChannelOutput<BufferedChannelOutput,
52 kMaxPayloadSize,
53 kMaxPackets,
54 kMaxPayloadSize>;
55
56 /// Channel output that can be waited on by the server.
57 class FuzzerChannelOutput : public FuzzerChannelOutputBase {
58 public:
FuzzerChannelOutput()59 FuzzerChannelOutput() : FuzzerChannelOutputBase() {}
60 };
61
62 using FuzzerContextBase =
63 internal::ClientServerTestContextThreaded<FuzzerChannelOutput,
64 kMaxPayloadSize,
65 kMaxPackets,
66 kMaxPayloadSize>;
67 class FuzzerContext : public FuzzerContextBase {
68 public:
69 static constexpr uint32_t kChannelId = 1;
70
FuzzerContext()71 FuzzerContext() : FuzzerContextBase(thread::test::TestOptionsThread0()) {}
72 };
73
74 class RpcFuzzTestingTest : public testing::Test {
75 protected:
SetUp()76 void SetUp() override { context_.server().RegisterService(service_); }
77
Add(Action::Op op,size_t target,uint16_t value)78 void Add(Action::Op op, size_t target, uint16_t value) {
79 actions_.push_back(Action(op, target, value).Encode());
80 }
81
Add(Action::Op op,size_t target,char val,size_t len)82 void Add(Action::Op op, size_t target, char val, size_t len) {
83 actions_.push_back(Action(op, target, val, len).Encode());
84 }
85
NextThread()86 void NextThread() { actions_.push_back(0); }
87
Run()88 void Run() {
89 Fuzzer fuzzer(context_.client(), FuzzerContext::kChannelId);
90 fuzzer.set_verbose(true);
91 fuzzer.set_timeout(kTimeout);
92 fuzzer.Run(actions_);
93 }
94
95 private:
96 FuzzerContext context_;
97 BenchmarkService service_;
98 Vector<uint32_t, Fuzzer::kMaxActions> actions_;
99 };
100
TEST_F(RpcFuzzTestingTest,SequentialRequests)101 TEST_F(RpcFuzzTestingTest, SequentialRequests) {
102 // Callback thread
103 Add(Action::kWriteStream, 1, 'B', 1);
104 Add(Action::kSkip, 0, 0);
105 Add(Action::kWriteStream, 2, 'B', 2);
106 Add(Action::kSkip, 0, 0);
107 Add(Action::kWriteStream, 3, 'B', 3);
108 Add(Action::kSkip, 0, 0);
109 NextThread();
110
111 // Thread 1
112 Add(Action::kWriteStream, 0, 'A', 2);
113 Add(Action::kWait, 1, 0);
114 Add(Action::kWriteStream, 1, 'A', 4);
115 NextThread();
116
117 // Thread 2
118 NextThread();
119 Add(Action::kWait, 2, 0);
120 Add(Action::kWriteStream, 2, 'A', 6);
121
122 // Thread 3
123 NextThread();
124 Add(Action::kWait, 3, 0);
125
126 Run();
127 }
128
129 // TODO(b/274437709): Re-enable.
TEST_F(RpcFuzzTestingTest,DISABLED_SimultaneousRequests)130 TEST_F(RpcFuzzTestingTest, DISABLED_SimultaneousRequests) {
131 // Callback thread
132 NextThread();
133
134 // Thread 1
135 Add(Action::kWriteUnary, 1, 'A', 1);
136 Add(Action::kWait, 2, 0);
137 NextThread();
138
139 // Thread 2
140 Add(Action::kWriteUnary, 2, 'B', 2);
141 Add(Action::kWait, 3, 0);
142 NextThread();
143
144 // Thread 3
145 Add(Action::kWriteUnary, 3, 'C', 3);
146 Add(Action::kWait, 1, 0);
147 NextThread();
148
149 Run();
150 }
151
152 // TODO(b/274437709) This test currently does not pass as it exhausts the fake
153 // channel. It will be re-enabled when the underlying stream is swapped for
154 // a pw_ring_buffer-based approach.
TEST_F(RpcFuzzTestingTest,DISABLED_CanceledRequests)155 TEST_F(RpcFuzzTestingTest, DISABLED_CanceledRequests) {
156 // Callback thread
157 NextThread();
158
159 // Thread 1
160 for (size_t i = 0; i < 10; ++i) {
161 Add(Action::kWriteUnary, i % 3, 'A', i);
162 }
163 Add(Action::kWait, 0, 0);
164 Add(Action::kWait, 1, 0);
165 Add(Action::kWait, 2, 0);
166 NextThread();
167
168 // Thread 2
169 for (size_t i = 0; i < 10; ++i) {
170 Add(Action::kCancel, i % 3, 0);
171 }
172 NextThread();
173
174 // Thread 3
175 NextThread();
176
177 Run();
178 }
179
180 // TODO(b/274437709) This test currently does not pass as it exhausts the fake
181 // channel. It will be re-enabled when the underlying stream is swapped for
182 // a pw_ring_buffer-based approach.
TEST_F(RpcFuzzTestingTest,DISABLED_AbandonedRequests)183 TEST_F(RpcFuzzTestingTest, DISABLED_AbandonedRequests) {
184 // Callback thread
185 NextThread();
186
187 // Thread 1
188 for (size_t i = 0; i < 10; ++i) {
189 Add(Action::kWriteUnary, i % 3, 'A', i);
190 }
191 Add(Action::kWait, 0, 0);
192 Add(Action::kWait, 1, 0);
193 Add(Action::kWait, 2, 0);
194 NextThread();
195
196 // Thread 2
197 for (size_t i = 0; i < 10; ++i) {
198 Add(Action::kAbandon, i % 3, 0);
199 }
200 NextThread();
201
202 // Thread 3
203 NextThread();
204
205 Run();
206 }
207
208 // TODO(b/274437709) This test currently does not pass as it exhausts the fake
209 // channel. It will be re-enabled when the underlying stream is swapped for
210 // a pw_ring_buffer-based approach.
TEST_F(RpcFuzzTestingTest,DISABLED_SwappedRequests)211 TEST_F(RpcFuzzTestingTest, DISABLED_SwappedRequests) {
212 Vector<uint32_t, Fuzzer::kMaxActions> actions;
213 // Callback thread
214 NextThread();
215 // Thread 1
216 for (size_t i = 0; i < 10; ++i) {
217 Add(Action::kWriteUnary, i % 3, 'A', i);
218 }
219 Add(Action::kWait, 0, 0);
220 Add(Action::kWait, 1, 0);
221 Add(Action::kWait, 2, 0);
222 NextThread();
223 // Thread 2
224 for (size_t i = 0; i < 100; ++i) {
225 auto j = i % 3;
226 Add(Action::kSwap, j, j + 1);
227 }
228 NextThread();
229 // Thread 3
230 NextThread();
231
232 Run();
233 }
234
235 // TODO(b/274437709) This test currently does not pass as it exhausts the fake
236 // channel. It will be re-enabled when the underlying stream is swapped for
237 // a pw_ring_buffer-based approach.
TEST_F(RpcFuzzTestingTest,DISABLED_DestroyedRequests)238 TEST_F(RpcFuzzTestingTest, DISABLED_DestroyedRequests) {
239 // Callback thread
240 NextThread();
241
242 // Thread 1
243 for (size_t i = 0; i < 100; ++i) {
244 Add(Action::kWriteUnary, i % 3, 'A', i);
245 }
246 Add(Action::kWait, 0, 0);
247 Add(Action::kWait, 1, 0);
248 Add(Action::kWait, 2, 0);
249 NextThread();
250
251 // Thread 2
252 for (size_t i = 0; i < 100; ++i) {
253 Add(Action::kDestroy, i % 3, 0);
254 }
255 NextThread();
256
257 // Thread 3
258 NextThread();
259
260 Run();
261 }
262
263 } // namespace
264 } // namespace pw::rpc::fuzz
265