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