1 // Copyright 2024 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_CALL_YODEL_YODEL_TEST_H
16 #define GRPC_TEST_CORE_CALL_YODEL_YODEL_TEST_H
17
18 #include <grpc/event_engine/event_engine.h>
19
20 #include "absl/functional/any_invocable.h"
21 #include "absl/log/log.h"
22 #include "absl/random/bit_gen_ref.h"
23 #include "absl/strings/string_view.h"
24 #include "gtest/gtest.h"
25 #include "src/core/lib/event_engine/event_engine_context.h"
26 #include "src/core/lib/promise/cancel_callback.h"
27 #include "src/core/lib/promise/detail/promise_factory.h"
28 #include "src/core/lib/promise/promise.h"
29 #include "src/core/lib/transport/call_arena_allocator.h"
30 #include "src/core/lib/transport/call_spine.h"
31 #include "src/core/lib/transport/metadata.h"
32 #include "src/core/util/debug_location.h"
33 #include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h"
34 #include "test/core/test_util/test_config.h"
35
36 namespace grpc_core {
37
38 class YodelTest;
39
40 extern bool g_yodel_fuzzing;
41
42 namespace yodel_detail {
43
44 // Capture the name and location of a test step.
45 class NameAndLocation {
46 public:
47 // Allow implicit construction from a string, to capture the start location
48 // from the variadic StartTestSeq name argument.
49 // NOLINTNEXTLINE
50 NameAndLocation(const char* name, SourceLocation location = {})
location_(location)51 : location_(location), name_(name) {}
52
location()53 SourceLocation location() const { return location_; }
name()54 absl::string_view name() const { return name_; }
55
56 private:
57 SourceLocation location_;
58 absl::string_view name_;
59 };
60
61 // Capture the state of a test step.
62 class ActionState {
63 public:
64 enum State : uint8_t {
65 // Initial state: construction of this step in the sequence has not been
66 // performed.
67 kNotCreated,
68 // The step has been created, but not yet started (the initial poll of the
69 // created promise has not occurred).
70 kNotStarted,
71 // The step has been polled, but it's not yet been completed.
72 kStarted,
73 // The step has been completed.
74 kDone,
75 // The step has been cancelled.
76 kCancelled,
77 };
78
79 // Generate a nice little prefix for log messages.
80 static absl::string_view StateString(State state);
81
82 ActionState(NameAndLocation name_and_location, int step);
83
Get()84 State Get() const { return state_; }
85 void Set(State state, SourceLocation whence = {});
name_and_location()86 const NameAndLocation& name_and_location() const {
87 return name_and_location_;
88 }
location()89 SourceLocation location() const { return name_and_location().location(); }
file()90 const char* file() const { return location().file(); }
line()91 int line() const { return location().line(); }
name()92 absl::string_view name() const { return name_and_location().name(); }
step()93 int step() const { return step_; }
94 bool IsDone();
95
96 private:
97 const NameAndLocation name_and_location_;
98 const int step_;
99 std::atomic<State> state_;
100 };
101
102 class SequenceSpawner {
103 public:
SequenceSpawner(NameAndLocation name_and_location,absl::AnyInvocable<void (absl::string_view,Promise<Empty>)> promise_spawner,absl::FunctionRef<std::shared_ptr<ActionState> (NameAndLocation,int)> action_state_factory)104 SequenceSpawner(
105 NameAndLocation name_and_location,
106 absl::AnyInvocable<void(absl::string_view, Promise<Empty>)>
107 promise_spawner,
108 absl::FunctionRef<std::shared_ptr<ActionState>(NameAndLocation, int)>
109 action_state_factory)
110 : name_and_location_(name_and_location),
111 promise_spawner_(
112 std::make_shared<
113 absl::AnyInvocable<void(absl::string_view, Promise<Empty>)>>(
114 std::move(promise_spawner))),
115 action_state_factory_(action_state_factory) {}
116
117 template <typename First, typename... FollowUps>
Start(First first,FollowUps...followups)118 void Start(First first, FollowUps... followups) {
119 using Factory = promise_detail::OncePromiseFactory<void, First>;
120 using FactoryPromise = typename Factory::Promise;
121 using Result = typename FactoryPromise::Result;
122 auto action_state = action_state_factory_(name_and_location_, step_);
123 ++step_;
124 auto next = MakeNext<Result>(std::move(followups)...);
125 (*promise_spawner_)(
126 name_and_location_.name(),
127 [spawner = promise_spawner_, first = Factory(std::move(first)),
128 next = std::move(next), action_state = std::move(action_state),
129 name_and_location = name_and_location_]() mutable {
130 action_state->Set(ActionState::kNotStarted);
131 auto promise = first.Make();
132 (*spawner)(name_and_location.name(),
133 WrapPromiseAndNext(std::move(action_state),
134 std::move(promise), std::move(next)));
135 return Empty{};
136 });
137 }
138
139 private:
140 template <typename Arg, typename FirstFollowUp, typename... FollowUps>
MakeNext(FirstFollowUp first,FollowUps...followups)141 absl::AnyInvocable<void(Arg)> MakeNext(FirstFollowUp first,
142 FollowUps... followups) {
143 using Factory = promise_detail::OncePromiseFactory<Arg, FirstFollowUp>;
144 using FactoryPromise = typename Factory::Promise;
145 using Result = typename FactoryPromise::Result;
146 auto action_state = action_state_factory_(name_and_location_, step_);
147 ++step_;
148 auto next = MakeNext<Result>(std::move(followups)...);
149 return [spawner = promise_spawner_, factory = Factory(std::move(first)),
150 next = std::move(next), action_state = std::move(action_state),
151 name_and_location = name_and_location_](Arg arg) mutable {
152 action_state->Set(ActionState::kNotStarted);
153 (*spawner)(
154 name_and_location.name(),
155 WrapPromiseAndNext(std::move(action_state),
156 factory.Make(std::move(arg)), std::move(next)));
157 };
158 }
159
160 template <typename R, typename P>
WrapPromiseAndNext(std::shared_ptr<ActionState> action_state,P promise,absl::AnyInvocable<void (R)> next)161 static Promise<Empty> WrapPromiseAndNext(
162 std::shared_ptr<ActionState> action_state, P promise,
163 absl::AnyInvocable<void(R)> next) {
164 return Promise<Empty>(OnCancel(
165 [action_state, promise = std::move(promise),
166 next = std::move(next)]() mutable -> Poll<Empty> {
167 action_state->Set(ActionState::kStarted);
168 auto r = promise();
169 if (auto* p = r.value_if_ready()) {
170 action_state->Set(ActionState::kDone);
171 next(std::move(*p));
172 return Empty{};
173 } else {
174 return Pending{};
175 }
176 },
177 [action_state]() { action_state->Set(ActionState::kCancelled); }));
178 }
179
180 template <typename Arg>
MakeNext()181 absl::AnyInvocable<void(Arg)> MakeNext() {
182 // Enforce last-arg is Empty so we don't drop things
183 return [](Empty) {};
184 }
185
186 NameAndLocation name_and_location_;
187 std::shared_ptr<absl::AnyInvocable<void(absl::string_view, Promise<Empty>)>>
188 promise_spawner_;
189 absl::FunctionRef<std::shared_ptr<ActionState>(NameAndLocation, int)>
190 action_state_factory_;
191 int step_ = 1;
192 };
193
194 template <typename Context>
SpawnerForContext(Context context,grpc_event_engine::experimental::EventEngine * event_engine)195 auto SpawnerForContext(
196 Context context,
197 grpc_event_engine::experimental::EventEngine* event_engine) {
198 return [context = std::move(context), event_engine](
199 absl::string_view name, Promise<Empty> promise) mutable {
200 // Pass new promises via event engine to allow fuzzers to explore
201 // reorderings of possibly interleaved spawns.
202 event_engine->Run([name, context, promise = std::move(promise)]() mutable {
203 context.SpawnInfallible(name, std::move(promise));
204 });
205 };
206 }
207
208 class TestRegistry {
209 public:
TestRegistry()210 TestRegistry() : next_(root_) { root_ = this; }
211
212 struct Test {
213 absl::string_view file;
214 int line;
215 std::string test_type;
216 std::string name;
217 absl::AnyInvocable<YodelTest*(const fuzzing_event_engine::Actions&,
218 absl::BitGenRef) const>
219 make;
220 };
221
222 static std::vector<Test> AllTests();
223
224 protected:
~TestRegistry()225 ~TestRegistry() {
226 Crash("unreachable: TestRegistry should never be destroyed");
227 }
228
229 private:
230 virtual void ContributeTests(std::vector<Test>& tests) = 0;
231
232 TestRegistry* next_;
233 static TestRegistry* root_;
234 };
235
236 class SimpleTestRegistry final : public TestRegistry {
237 public:
SimpleTestRegistry()238 SimpleTestRegistry() {}
239 ~SimpleTestRegistry() = delete;
240
241 void RegisterTest(
242 absl::string_view file, int line, absl::string_view test_type,
243 absl::string_view name,
244 absl::AnyInvocable<YodelTest*(const fuzzing_event_engine::Actions&,
245 absl::BitGenRef) const>
246 create);
247
Get()248 static SimpleTestRegistry& Get() {
249 static SimpleTestRegistry* const p = new SimpleTestRegistry;
250 return *p;
251 }
252
253 private:
254 void ContributeTests(std::vector<Test>& tests) override;
255
256 std::vector<Test> tests_;
257 };
258
259 template <typename /*test_type*/, typename T>
260 class ParameterizedTestRegistry final : public TestRegistry {
261 public:
ParameterizedTestRegistry()262 ParameterizedTestRegistry() {}
263 ~ParameterizedTestRegistry() = delete;
264
RegisterTest(absl::string_view file,int line,absl::string_view test_type,absl::string_view name,absl::AnyInvocable<YodelTest * (const T &,const fuzzing_event_engine::Actions &,absl::BitGenRef)const> make)265 void RegisterTest(absl::string_view file, int line,
266 absl::string_view test_type, absl::string_view name,
267 absl::AnyInvocable<YodelTest*(
268 const T&, const fuzzing_event_engine::Actions&,
269 absl::BitGenRef) const>
270 make) {
271 tests_.push_back({file, line, test_type, name, std::move(make)});
272 }
273
RegisterParameter(absl::string_view name,T value)274 void RegisterParameter(absl::string_view name, T value) {
275 parameters_.push_back({name, std::move(value)});
276 }
277
Get()278 static ParameterizedTestRegistry& Get() {
279 static ParameterizedTestRegistry* const p = new ParameterizedTestRegistry;
280 return *p;
281 }
282
283 private:
284 struct ParameterizedTest {
285 absl::string_view file;
286 int line;
287 absl::string_view test_type;
288 absl::string_view name;
289 absl::AnyInvocable<YodelTest*(
290 const T&, const fuzzing_event_engine::Actions&, absl::BitGenRef) const>
291 make;
292 };
293 struct Parameter {
294 absl::string_view name;
295 T value;
296 };
297
ContributeTests(std::vector<Test> & tests)298 void ContributeTests(std::vector<Test>& tests) override {
299 for (const auto& test : tests_) {
300 for (const auto& parameter : parameters_) {
301 tests.push_back({test.file, test.line, std::string(test.test_type),
302 absl::StrCat(test.name, "/", parameter.name),
303 [test = &test, parameter = ¶meter](
304 const fuzzing_event_engine::Actions& actions,
305 absl::BitGenRef rng) {
306 return test->make(parameter->value, actions, rng);
307 }});
308 }
309 }
310 }
311
312 std::vector<ParameterizedTest> tests_;
313 std::vector<Parameter> parameters_;
314 };
315
316 } // namespace yodel_detail
317
318 class YodelTest : public ::testing::Test {
319 public:
320 void RunTest();
321
322 protected:
323 YodelTest(const fuzzing_event_engine::Actions& actions, absl::BitGenRef rng);
324
325 // Helpers to generate various random values.
326 // When we're fuzzing, delegates to the fuzzer input to generate this data.
327 std::string RandomString(int min_length, int max_length,
328 absl::string_view character_set);
329 std::string RandomStringFrom(
330 std::initializer_list<absl::string_view> choices);
331 std::string RandomMetadataKey();
332 std::string RandomMetadataValue(absl::string_view key);
333 std::string RandomMetadataBinaryKey();
334 std::string RandomMetadataBinaryValue();
335 std::vector<std::pair<std::string, std::string>> RandomMetadata();
336 std::string RandomMessage();
rng()337 absl::BitGenRef rng() { return rng_; }
338
339 // Alternative for Seq for test driver code.
340 // Registers each step so that WaitForAllPendingWork() can report progress,
341 // and wait for completion... AND generate good failure messages when a
342 // sequence doesn't complete in a timely manner.
343 // Uses the `SpawnInfallible` method on `context` to provide an execution
344 // environment for each step.
345 // Initiates each step in a different event engine closure to maximize
346 // opportunities for fuzzers to reorder the steps, or thready-tsan to expose
347 // potential threading issues.
348 template <typename Context, typename... Actions>
SpawnTestSeq(Context context,yodel_detail::NameAndLocation name_and_location,Actions...actions)349 void SpawnTestSeq(Context context,
350 yodel_detail::NameAndLocation name_and_location,
351 Actions... actions) {
352 yodel_detail::SequenceSpawner(
353 name_and_location,
354 yodel_detail::SpawnerForContext(std::move(context),
355 state_->event_engine.get()),
356 [this](yodel_detail::NameAndLocation name_and_location, int step) {
357 auto action = std::make_shared<yodel_detail::ActionState>(
358 name_and_location, step);
359 pending_actions_.push(action);
360 return action;
361 })
362 .Start(std::move(actions)...);
363 }
364
365 class NoContext {
366 public:
NoContext(grpc_event_engine::experimental::EventEngine * ee)367 explicit NoContext(grpc_event_engine::experimental::EventEngine* ee) {
368 auto arena = SimpleArenaAllocator()->MakeArena();
369 arena->SetContext(ee);
370 party_ = Party::Make(std::move(arena));
371 }
372
373 template <typename PromiseFactory>
SpawnInfallible(absl::string_view name,PromiseFactory promise_factory)374 void SpawnInfallible(absl::string_view name,
375 PromiseFactory promise_factory) {
376 party_->Spawn(
377 name,
378 [party = party_,
379 promise_factory = std::move(promise_factory)]() mutable {
380 promise_detail::OncePromiseFactory<void, PromiseFactory> factory(
381 std::move(promise_factory));
382 return [party, underlying = factory.Make()]() mutable {
383 return underlying();
384 };
385 },
386 [](Empty) {});
387 }
388
389 private:
390 RefCountedPtr<Party> party_;
391 };
392
393 template <typename... Actions>
SpawnTestSeqWithoutContext(yodel_detail::NameAndLocation name_and_location,Actions...actions)394 void SpawnTestSeqWithoutContext(
395 yodel_detail::NameAndLocation name_and_location, Actions... actions) {
396 SpawnTestSeq(NoContext{event_engine().get()}, name_and_location,
397 std::move(actions)...);
398 }
399
MakeCall(ClientMetadataHandle client_initial_metadata)400 auto MakeCall(ClientMetadataHandle client_initial_metadata) {
401 auto arena = state_->call_arena_allocator->MakeArena();
402 arena->SetContext<grpc_event_engine::experimental::EventEngine>(
403 state_->event_engine.get());
404 return MakeCallPair(std::move(client_initial_metadata), std::move(arena));
405 }
406
407 void WaitForAllPendingWork();
408
409 template <typename T>
TickUntil(absl::FunctionRef<Poll<T> ()> poll)410 T TickUntil(absl::FunctionRef<Poll<T>()> poll) {
411 absl::optional<T> result;
412 TickUntilTrue([poll, &result]() {
413 auto r = poll();
414 if (auto* p = r.value_if_ready()) {
415 result = std::move(*p);
416 return true;
417 }
418 return false;
419 });
420 return std::move(*result);
421 }
422
423 const std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine>&
event_engine()424 event_engine() {
425 return state_->event_engine;
426 }
427
SetMaxRandomMessageSize(size_t max_random_message_size)428 void SetMaxRandomMessageSize(size_t max_random_message_size) {
429 max_random_message_size_ = max_random_message_size;
430 }
431
432 private:
433 class WatchDog;
434 struct State {
435 std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine>
436 event_engine;
437 RefCountedPtr<CallArenaAllocator> call_arena_allocator;
438 };
439
440 virtual void TestImpl() = 0;
441
442 void Timeout();
443 void TickUntilTrue(absl::FunctionRef<bool()> poll);
444
445 // Called before the test runs, after core configuration has been reset
446 // and before the event engine is started.
447 // This is a good time to register any custom core configuration builders.
InitCoreConfiguration()448 virtual void InitCoreConfiguration() {}
449 // Called after the event engine has been started, but before the test runs.
InitTest()450 virtual void InitTest() {}
451 // Called after the test has run, but before the event engine is shut down.
Shutdown()452 virtual void Shutdown() {}
453
454 absl::BitGenRef rng_;
455 fuzzing_event_engine::Actions actions_;
456 std::unique_ptr<State> state_;
457 std::queue<std::shared_ptr<yodel_detail::ActionState>> pending_actions_;
458 size_t max_random_message_size_ = 1024 * 1024;
459 };
460
461 } // namespace grpc_core
462
463 #define YODEL_TEST(test_type, name) \
464 class YodelTest_##name : public grpc_core::test_type { \
465 public: \
466 using test_type::test_type; \
467 void TestBody() override { RunTest(); } \
468 \
469 private: \
470 void TestImpl() override; \
471 static grpc_core::YodelTest* Create( \
472 const fuzzing_event_engine::Actions& actions, absl::BitGenRef rng) { \
473 return new YodelTest_##name(actions, rng); \
474 } \
475 static int registered_; \
476 }; \
477 int YodelTest_##name::registered_ = \
478 (grpc_core::yodel_detail::SimpleTestRegistry::Get().RegisterTest( \
479 __FILE__, __LINE__, #test_type, #name, &Create), \
480 0); \
481 void YodelTest_##name::TestImpl()
482
483 // NOLINTBEGIN(bugprone-macro-parentheses)
484 #define YODEL_TEST_P(test_type, parameter_type, name) \
485 class YodelTest_##name : public grpc_core::test_type { \
486 public: \
487 using test_type::test_type; \
488 void TestBody() override { RunTest(); } \
489 \
490 private: \
491 void TestImpl() override; \
492 static grpc_core::YodelTest* Create( \
493 const parameter_type& parameter, \
494 const fuzzing_event_engine::Actions& actions, absl::BitGenRef rng) { \
495 return new YodelTest_##name(parameter, actions, rng); \
496 } \
497 static int registered_; \
498 }; \
499 int YodelTest_##name::registered_ = \
500 (grpc_core::yodel_detail::ParameterizedTestRegistry< \
501 grpc_core::test_type, parameter_type>::Get() \
502 .RegisterTest(__FILE__, __LINE__, #test_type, #name, &Create), \
503 0); \
504 void YodelTest_##name::TestImpl()
505
506 #define YODEL_TEST_PARAM(test_type, parameter_type, name, value) \
507 int YodelTestParam_##name = \
508 (grpc_core::yodel_detail::ParameterizedTestRegistry< \
509 grpc_core::test_type, parameter_type>::Get() \
510 .RegisterParameter(#name, value), \
511 0)
512 // NOLINTEND(bugprone-macro-parentheses)
513
514 #endif // GRPC_TEST_CORE_CALL_YODEL_YODEL_TEST_H
515