• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 = &parameter](
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