1 // Copyright 2023 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://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,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #ifndef GRPC_TEST_CORE_TRANSPORT_TEST_SUITE_TEST_H
16 #define GRPC_TEST_CORE_TRANSPORT_TEST_SUITE_TEST_H
17
18 #include <initializer_list>
19 #include <memory>
20 #include <queue>
21
22 #include "absl/functional/any_invocable.h"
23 #include "absl/random/bit_gen_ref.h"
24 #include "absl/strings/string_view.h"
25 #include "gmock/gmock.h"
26 #include "gtest/gtest.h"
27
28 #include "src/core/lib/gprpp/time.h"
29 #include "src/core/lib/iomgr/timer_manager.h"
30 #include "src/core/lib/promise/cancel_callback.h"
31 #include "src/core/lib/promise/promise.h"
32 #include "src/core/lib/resource_quota/resource_quota.h"
33 #include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h"
34 #include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.h"
35 #include "test/core/transport/test_suite/fixture.h"
36
37 namespace grpc_core {
38
39 namespace transport_test_detail {
40
41 struct NameAndLocation {
42 // NOLINTNEXTLINE
43 NameAndLocation(const char* name, SourceLocation location = {})
location_NameAndLocation44 : location_(location), name_(name) {}
NextNameAndLocation45 NameAndLocation Next() const {
46 return NameAndLocation(name_, location_, step_ + 1);
47 }
48
locationNameAndLocation49 SourceLocation location() const { return location_; }
nameNameAndLocation50 absl::string_view name() const { return name_; }
stepNameAndLocation51 int step() const { return step_; }
52
53 private:
NameAndLocationNameAndLocation54 NameAndLocation(absl::string_view name, SourceLocation location, int step)
55 : location_(location), name_(name), step_(step) {}
56 SourceLocation location_;
57 absl::string_view name_;
58 int step_ = 1;
59 };
60
61 class ActionState {
62 public:
63 enum State : uint8_t {
64 kNotCreated,
65 kNotStarted,
66 kStarted,
67 kDone,
68 kCancelled,
69 };
70
StateString(State state)71 static absl::string_view StateString(State state) {
72 switch (state) {
73 case kNotCreated:
74 return "";
75 case kNotStarted:
76 return "⏰";
77 case kStarted:
78 return "";
79 case kDone:
80 return "";
81 case kCancelled:
82 return "";
83 }
84 }
85
86 explicit ActionState(NameAndLocation name_and_location);
87
Get()88 State Get() const { return state_; }
Set(State state)89 void Set(State state) {
90 gpr_log(GPR_INFO, "%s",
91 absl::StrCat(StateString(state), " ", name(), " [", step(), "] ",
92 file(), ":", line())
93 .c_str());
94 state_ = state;
95 }
name_and_location()96 const NameAndLocation& name_and_location() const {
97 return name_and_location_;
98 }
location()99 SourceLocation location() const { return name_and_location().location(); }
file()100 const char* file() const { return location().file(); }
line()101 int line() const { return location().line(); }
name()102 absl::string_view name() const { return name_and_location().name(); }
step()103 int step() const { return name_and_location().step(); }
104 bool IsDone();
105
106 private:
107 const NameAndLocation name_and_location_;
108 std::atomic<State> state_;
109 };
110
111 using PromiseSpawner = std::function<void(absl::string_view, Promise<Empty>)>;
112 using ActionStateFactory =
113 absl::FunctionRef<std::shared_ptr<ActionState>(NameAndLocation)>;
114
115 template <typename Context>
SpawnerForContext(Context context,grpc_event_engine::experimental::EventEngine * event_engine)116 PromiseSpawner SpawnerForContext(
117 Context context,
118 grpc_event_engine::experimental::EventEngine* event_engine) {
119 return [context = std::move(context), event_engine](
120 absl::string_view name, Promise<Empty> promise) mutable {
121 // Pass new promises via event engine to allow fuzzers to explore
122 // reorderings of possibly interleaved spawns.
123 event_engine->Run([name, context = std::move(context),
124 promise = std::move(promise)]() mutable {
125 context.SpawnInfallible(name, std::move(promise));
126 });
127 };
128 }
129
130 template <typename Arg>
131 using NextSpawner = absl::AnyInvocable<void(Arg)>;
132
133 template <typename R>
WrapPromiseAndNext(std::shared_ptr<ActionState> action_state,Promise<R> promise,NextSpawner<R> next)134 Promise<Empty> WrapPromiseAndNext(std::shared_ptr<ActionState> action_state,
135 Promise<R> promise, NextSpawner<R> next) {
136 return Promise<Empty>(OnCancel(
137 [action_state, promise = std::move(promise),
138 next = std::move(next)]() mutable -> Poll<Empty> {
139 action_state->Set(ActionState::kStarted);
140 auto r = promise();
141 if (auto* p = r.value_if_ready()) {
142 action_state->Set(ActionState::kDone);
143 next(std::move(*p));
144 return Empty{};
145 } else {
146 return Pending{};
147 }
148 },
149 [action_state]() { action_state->Set(ActionState::kCancelled); }));
150 }
151
152 template <typename Arg>
WrapFollowUps(NameAndLocation,ActionStateFactory,PromiseSpawner)153 NextSpawner<Arg> WrapFollowUps(NameAndLocation, ActionStateFactory,
154 PromiseSpawner) {
155 return [](Empty) {};
156 }
157
158 template <typename Arg, typename FirstFollowUp, typename... FollowUps>
WrapFollowUps(NameAndLocation loc,ActionStateFactory action_state_factory,PromiseSpawner spawner,FirstFollowUp first,FollowUps...follow_ups)159 NextSpawner<Arg> WrapFollowUps(NameAndLocation loc,
160 ActionStateFactory action_state_factory,
161 PromiseSpawner spawner, FirstFollowUp first,
162 FollowUps... follow_ups) {
163 using Factory = promise_detail::OncePromiseFactory<Arg, FirstFollowUp>;
164 using FactoryPromise = typename Factory::Promise;
165 using Result = typename FactoryPromise::Result;
166 auto action_state = action_state_factory(loc);
167 return [spawner, factory = Factory(std::move(first)),
168 next = WrapFollowUps<Result>(loc.Next(), action_state_factory,
169 spawner, std::move(follow_ups)...),
170 action_state = std::move(action_state),
171 name = loc.name()](Arg arg) mutable {
172 action_state->Set(ActionState::kNotStarted);
173 spawner(name,
174 WrapPromiseAndNext(std::move(action_state),
175 Promise<Result>(factory.Make(std::move(arg))),
176 std::move(next)));
177 };
178 }
179
180 template <typename First, typename... FollowUps>
StartSeq(NameAndLocation loc,ActionStateFactory action_state_factory,PromiseSpawner spawner,First first,FollowUps...followups)181 void StartSeq(NameAndLocation loc, ActionStateFactory action_state_factory,
182 PromiseSpawner spawner, First first, FollowUps... followups) {
183 using Factory = promise_detail::OncePromiseFactory<void, First>;
184 using FactoryPromise = typename Factory::Promise;
185 using Result = typename FactoryPromise::Result;
186 auto action_state = action_state_factory(loc);
187 auto next = WrapFollowUps<Result>(loc.Next(), action_state_factory, spawner,
188 std::move(followups)...);
189 spawner(
190 loc.name(),
191 [spawner, first = Factory(std::move(first)), next = std::move(next),
192 action_state = std::move(action_state), name = loc.name()]() mutable {
193 action_state->Set(ActionState::kNotStarted);
194 spawner(name, WrapPromiseAndNext(std::move(action_state),
195 Promise<Result>(first.Make()),
196 std::move(next)));
197 return Empty{};
198 });
199 }
200
201 }; // namespace transport_test_detail
202
203 class TransportTest : public ::testing::Test {
204 public:
205 void RunTest();
206
207 protected:
TransportTest(std::unique_ptr<TransportFixture> fixture,const fuzzing_event_engine::Actions & actions,absl::BitGenRef rng)208 TransportTest(std::unique_ptr<TransportFixture> fixture,
209 const fuzzing_event_engine::Actions& actions,
210 absl::BitGenRef rng)
211 : event_engine_(std::make_shared<
212 grpc_event_engine::experimental::FuzzingEventEngine>(
213 []() {
214 grpc_timer_manager_set_threading(false);
215 grpc_event_engine::experimental::FuzzingEventEngine::Options
216 options;
217 return options;
218 }(),
219 actions)),
220 fixture_(std::move(fixture)),
221 rng_(rng) {}
222
223 void SetServerAcceptor();
224 CallInitiator CreateCall();
225
226 std::string RandomString(int min_length, int max_length,
227 absl::string_view character_set);
228 std::string RandomStringFrom(
229 std::initializer_list<absl::string_view> choices);
230 std::string RandomMetadataKey();
231 std::string RandomMetadataValue(absl::string_view key);
232 std::string RandomMetadataBinaryKey();
233 std::string RandomMetadataBinaryValue();
234 std::vector<std::pair<std::string, std::string>> RandomMetadata();
235 std::string RandomMessage();
rng()236 absl::BitGenRef rng() { return rng_; }
237
238 CallHandler TickUntilServerCall();
239 void WaitForAllPendingWork();
240
241 // Alternative for Seq for test driver code.
242 // Registers each step so that WaitForAllPendingWork() can report progress,
243 // and wait for completion... AND generate good failure messages when a
244 // sequence doesn't complete in a timely manner.
245 template <typename Context, typename... Actions>
SpawnTestSeq(Context context,transport_test_detail::NameAndLocation name_and_location,Actions...actions)246 void SpawnTestSeq(Context context,
247 transport_test_detail::NameAndLocation name_and_location,
248 Actions... actions) {
249 transport_test_detail::StartSeq(
250 name_and_location,
251 [this](transport_test_detail::NameAndLocation name_and_location) {
252 auto action = std::make_shared<transport_test_detail::ActionState>(
253 name_and_location);
254 pending_actions_.push(action);
255 return action;
256 },
257 transport_test_detail::SpawnerForContext(std::move(context),
258 event_engine_.get()),
259 std::move(actions)...);
260 }
261
262 private:
263 virtual void TestImpl() = 0;
264
265 void Timeout();
266
267 class Acceptor final : public ServerTransport::Acceptor {
268 public:
Acceptor(grpc_event_engine::experimental::EventEngine * event_engine,MemoryAllocator * allocator)269 Acceptor(grpc_event_engine::experimental::EventEngine* event_engine,
270 MemoryAllocator* allocator)
271 : event_engine_(event_engine), allocator_(allocator) {}
272
273 Arena* CreateArena() override;
274 absl::StatusOr<CallInitiator> CreateCall(
275 ClientMetadata& client_initial_metadata, Arena* arena) override;
276 absl::optional<CallHandler> PopHandler();
277
278 private:
279 std::queue<CallHandler> handlers_;
280 grpc_event_engine::experimental::EventEngine* const event_engine_;
281 MemoryAllocator* const allocator_;
282 };
283
284 class WatchDog {
285 public:
WatchDog(TransportTest * test)286 explicit WatchDog(TransportTest* test) : test_(test) {}
~WatchDog()287 ~WatchDog() { test_->event_engine_->Cancel(timer_); }
288
289 private:
290 TransportTest* const test_;
291 grpc_event_engine::experimental::EventEngine::TaskHandle const timer_{
292 test_->event_engine_->RunAfter(Duration::Minutes(5),
293 [this]() { test_->Timeout(); })};
294 };
295
296 std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine>
297 event_engine_{
298 std::make_shared<grpc_event_engine::experimental::FuzzingEventEngine>(
299 []() {
300 grpc_timer_manager_set_threading(false);
301 grpc_event_engine::experimental::FuzzingEventEngine::Options
302 options;
303 return options;
304 }(),
305 fuzzing_event_engine::Actions())};
306 std::unique_ptr<TransportFixture> fixture_;
307 MemoryAllocator allocator_ = MakeResourceQuota("test-quota")
308 ->memory_quota()
309 ->CreateMemoryAllocator("test-allocator");
310 Acceptor acceptor_{event_engine_.get(), &allocator_};
311 TransportFixture::ClientAndServerTransportPair transport_pair_ =
312 fixture_->CreateTransportPair(event_engine_);
313 std::queue<std::shared_ptr<transport_test_detail::ActionState>>
314 pending_actions_;
315 absl::BitGenRef rng_;
316 };
317
318 class TransportTestRegistry {
319 public:
320 static TransportTestRegistry& Get();
321 void RegisterTest(
322 absl::string_view name,
323 absl::AnyInvocable<TransportTest*(std::unique_ptr<TransportFixture>,
324 const fuzzing_event_engine::Actions&,
325 absl::BitGenRef) const>
326 create);
327
328 struct Test {
329 absl::string_view name;
330 absl::AnyInvocable<TransportTest*(std::unique_ptr<TransportFixture>,
331 const fuzzing_event_engine::Actions&,
332 absl::BitGenRef) const>
333 create;
334 };
335
tests()336 const std::vector<Test>& tests() const { return tests_; }
337
338 private:
339 std::vector<Test> tests_;
340 };
341
342 } // namespace grpc_core
343
344 #define TRANSPORT_TEST(name) \
345 class TransportTest_##name : public grpc_core::TransportTest { \
346 public: \
347 using TransportTest::TransportTest; \
348 void TestBody() override { RunTest(); } \
349 \
350 private: \
351 void TestImpl() override; \
352 static grpc_core::TransportTest* Create( \
353 std::unique_ptr<grpc_core::TransportFixture> fixture, \
354 const fuzzing_event_engine::Actions& actions, absl::BitGenRef rng) { \
355 return new TransportTest_##name(std::move(fixture), actions, rng); \
356 } \
357 static int registered_; \
358 }; \
359 int TransportTest_##name::registered_ = \
360 (grpc_core::TransportTestRegistry::Get().RegisterTest(#name, &Create), \
361 0); \
362 void TransportTest_##name::TestImpl()
363
364 #endif // GRPC_TEST_CORE_TRANSPORT_TEST_SUITE_TEST_H
365