| // Copyright 2023 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/fuzz/engine.h" |
| |
| #include <chrono> |
| |
| #include "gtest/gtest.h" |
| #include "pw_containers/vector.h" |
| #include "pw_log/log.h" |
| #include "pw_rpc/benchmark.h" |
| #include "pw_rpc/internal/client_server_testing.h" |
| #include "pw_rpc/internal/client_server_testing_threaded.h" |
| #include "pw_rpc/internal/fake_channel_output.h" |
| #include "pw_thread/non_portable_test_thread_options.h" |
| |
| namespace pw::rpc::fuzz { |
| namespace { |
| |
| using namespace std::literals::chrono_literals; |
| |
| // Maximum time, in milliseconds, that can elapse without a call completing or |
| // being dropped in some way.. |
| const chrono::SystemClock::duration kTimeout = 5s; |
| |
| // These are fairly tight constraints in order to fit within the default |
| // `PW_UNIT_TEST_CONFIG_MEMORY_POOL_SIZE`. |
| constexpr size_t kMaxPackets = 128; |
| constexpr size_t kMaxPayloadSize = 64; |
| |
| using BufferedChannelOutputBase = |
| internal::test::FakeChannelOutputBuffer<kMaxPackets, kMaxPayloadSize>; |
| |
| /// Channel output backed by a fixed buffer. |
| class BufferedChannelOutput : public BufferedChannelOutputBase { |
| public: |
| BufferedChannelOutput() : BufferedChannelOutputBase() {} |
| }; |
| |
| using FuzzerChannelOutputBase = |
| internal::WatchableChannelOutput<BufferedChannelOutput, |
| kMaxPayloadSize, |
| kMaxPackets, |
| kMaxPayloadSize>; |
| |
| /// Channel output that can be waited on by the server. |
| class FuzzerChannelOutput : public FuzzerChannelOutputBase { |
| public: |
| explicit FuzzerChannelOutput( |
| TestPacketProcessor&& server_packet_processor = nullptr, |
| TestPacketProcessor&& client_packet_processor = nullptr) |
| : FuzzerChannelOutputBase(std::move(server_packet_processor), |
| std::move(client_packet_processor)) {} |
| }; |
| |
| using FuzzerContextBase = |
| internal::ClientServerTestContextThreaded<FuzzerChannelOutput, |
| kMaxPayloadSize, |
| kMaxPackets, |
| kMaxPayloadSize>; |
| class FuzzerContext : public FuzzerContextBase { |
| public: |
| static constexpr uint32_t kChannelId = 1; |
| |
| explicit FuzzerContext( |
| TestPacketProcessor&& server_packet_processor = nullptr, |
| TestPacketProcessor&& client_packet_processor = nullptr) |
| // TODO: b/290860904 - Replace TestOptionsThread0 with TestThreadContext. |
| : FuzzerContextBase(thread::test::TestOptionsThread0(), |
| std::move(server_packet_processor), |
| std::move(client_packet_processor)) {} |
| }; |
| |
| class RpcFuzzTestingTest : public testing::Test { |
| protected: |
| void SetUp() override { context_.server().RegisterService(service_); } |
| |
| void Add(Action::Op op, size_t target, uint16_t value) { |
| actions_.push_back(Action(op, target, value).Encode()); |
| } |
| |
| void Add(Action::Op op, size_t target, char val, size_t len) { |
| actions_.push_back(Action(op, target, val, len).Encode()); |
| } |
| |
| void NextThread() { actions_.push_back(0); } |
| |
| void Run() { |
| Fuzzer fuzzer(context_.client(), FuzzerContext::kChannelId); |
| fuzzer.set_verbose(true); |
| fuzzer.set_timeout(kTimeout); |
| fuzzer.Run(actions_); |
| } |
| |
| void TearDown() override { context_.server().UnregisterService(service_); } |
| |
| private: |
| FuzzerContext context_; |
| BenchmarkService service_; |
| Vector<uint32_t, Fuzzer::kMaxActions> actions_; |
| }; |
| |
| // TODO: b/274437709 - Re-enable. |
| TEST_F(RpcFuzzTestingTest, DISABLED_SequentialRequests) { |
| // Callback thread |
| Add(Action::kWriteStream, 1, 'B', 1); |
| Add(Action::kSkip, 0, 0); |
| Add(Action::kWriteStream, 2, 'B', 2); |
| Add(Action::kSkip, 0, 0); |
| Add(Action::kWriteStream, 3, 'B', 3); |
| Add(Action::kSkip, 0, 0); |
| NextThread(); |
| |
| // Thread 1 |
| Add(Action::kWriteStream, 0, 'A', 2); |
| Add(Action::kWait, 1, 0); |
| Add(Action::kWriteStream, 1, 'A', 4); |
| NextThread(); |
| |
| // Thread 2 |
| NextThread(); |
| Add(Action::kWait, 2, 0); |
| Add(Action::kWriteStream, 2, 'A', 6); |
| |
| // Thread 3 |
| NextThread(); |
| Add(Action::kWait, 3, 0); |
| |
| Run(); |
| } |
| |
| // TODO: b/274437709 - Re-enable. |
| TEST_F(RpcFuzzTestingTest, DISABLED_SimultaneousRequests) { |
| // Callback thread |
| NextThread(); |
| |
| // Thread 1 |
| Add(Action::kWriteUnary, 1, 'A', 1); |
| Add(Action::kWait, 2, 0); |
| NextThread(); |
| |
| // Thread 2 |
| Add(Action::kWriteUnary, 2, 'B', 2); |
| Add(Action::kWait, 3, 0); |
| NextThread(); |
| |
| // Thread 3 |
| Add(Action::kWriteUnary, 3, 'C', 3); |
| Add(Action::kWait, 1, 0); |
| NextThread(); |
| |
| Run(); |
| } |
| |
| // TODO: b/274437709 - This test currently does not pass as it exhausts the fake |
| // channel. It will be re-enabled when the underlying stream is swapped for |
| // a pw_ring_buffer-based approach. |
| TEST_F(RpcFuzzTestingTest, DISABLED_CanceledRequests) { |
| // Callback thread |
| NextThread(); |
| |
| // Thread 1 |
| for (size_t i = 0; i < 10; ++i) { |
| Add(Action::kWriteUnary, i % 3, 'A', i); |
| } |
| Add(Action::kWait, 0, 0); |
| Add(Action::kWait, 1, 0); |
| Add(Action::kWait, 2, 0); |
| NextThread(); |
| |
| // Thread 2 |
| for (size_t i = 0; i < 10; ++i) { |
| Add(Action::kCancel, i % 3, 0); |
| } |
| NextThread(); |
| |
| // Thread 3 |
| NextThread(); |
| |
| Run(); |
| } |
| |
| // TODO: b/274437709 - This test currently does not pass as it exhausts the fake |
| // channel. It will be re-enabled when the underlying stream is swapped for |
| // a pw_ring_buffer-based approach. |
| TEST_F(RpcFuzzTestingTest, DISABLED_AbandonedRequests) { |
| // Callback thread |
| NextThread(); |
| |
| // Thread 1 |
| for (size_t i = 0; i < 10; ++i) { |
| Add(Action::kWriteUnary, i % 3, 'A', i); |
| } |
| Add(Action::kWait, 0, 0); |
| Add(Action::kWait, 1, 0); |
| Add(Action::kWait, 2, 0); |
| NextThread(); |
| |
| // Thread 2 |
| for (size_t i = 0; i < 10; ++i) { |
| Add(Action::kAbandon, i % 3, 0); |
| } |
| NextThread(); |
| |
| // Thread 3 |
| NextThread(); |
| |
| Run(); |
| } |
| |
| // TODO: b/274437709 - This test currently does not pass as it exhausts the fake |
| // channel. It will be re-enabled when the underlying stream is swapped for |
| // a pw_ring_buffer-based approach. |
| TEST_F(RpcFuzzTestingTest, DISABLED_SwappedRequests) { |
| Vector<uint32_t, Fuzzer::kMaxActions> actions; |
| // Callback thread |
| NextThread(); |
| // Thread 1 |
| for (size_t i = 0; i < 10; ++i) { |
| Add(Action::kWriteUnary, i % 3, 'A', i); |
| } |
| Add(Action::kWait, 0, 0); |
| Add(Action::kWait, 1, 0); |
| Add(Action::kWait, 2, 0); |
| NextThread(); |
| // Thread 2 |
| for (size_t i = 0; i < 100; ++i) { |
| auto j = i % 3; |
| Add(Action::kSwap, j, j + 1); |
| } |
| NextThread(); |
| // Thread 3 |
| NextThread(); |
| |
| Run(); |
| } |
| |
| // TODO: b/274437709 - This test currently does not pass as it exhausts the fake |
| // channel. It will be re-enabled when the underlying stream is swapped for |
| // a pw_ring_buffer-based approach. |
| TEST_F(RpcFuzzTestingTest, DISABLED_DestroyedRequests) { |
| // Callback thread |
| NextThread(); |
| |
| // Thread 1 |
| for (size_t i = 0; i < 100; ++i) { |
| Add(Action::kWriteUnary, i % 3, 'A', i); |
| } |
| Add(Action::kWait, 0, 0); |
| Add(Action::kWait, 1, 0); |
| Add(Action::kWait, 2, 0); |
| NextThread(); |
| |
| // Thread 2 |
| for (size_t i = 0; i < 100; ++i) { |
| Add(Action::kDestroy, i % 3, 0); |
| } |
| NextThread(); |
| |
| // Thread 3 |
| NextThread(); |
| |
| Run(); |
| } |
| |
| } // namespace |
| } // namespace pw::rpc::fuzz |