// 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 #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" #include "pw_unit_test/framework.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; /// Channel output backed by a fixed buffer. class BufferedChannelOutput : public BufferedChannelOutputBase { public: BufferedChannelOutput() : BufferedChannelOutputBase() {} }; using FuzzerChannelOutputBase = internal::WatchableChannelOutput; /// 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; 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 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 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