• 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 #include "test/core/call/yodel/yodel_test.h"
16 
17 #include <memory>
18 
19 #include "absl/random/random.h"
20 #include "src/core/config/core_configuration.h"
21 #include "src/core/lib/iomgr/exec_ctx.h"
22 #include "src/core/lib/iomgr/timer_manager.h"
23 #include "src/core/lib/resource_quota/resource_quota.h"
24 #include "test/core/event_engine/event_engine_test_utils.h"
25 #include "test/core/test_util/build.h"
26 
27 namespace grpc_core {
28 
29 bool g_yodel_fuzzing;
30 
31 namespace yodel_detail {
32 
33 TestRegistry* TestRegistry::root_ = nullptr;
34 
35 ///////////////////////////////////////////////////////////////////////////////
36 // ActionState
37 
ActionState(NameAndLocation name_and_location,int step)38 ActionState::ActionState(NameAndLocation name_and_location, int step)
39     : name_and_location_(name_and_location), step_(step), state_(kNotCreated) {}
40 
StateString(State state)41 absl::string_view ActionState::StateString(State state) {
42   // We use emoji here to make it easier to visually scan the logs.
43   switch (state) {
44     case kNotCreated:
45       return "��";
46     case kNotStarted:
47       return "⏰";
48     case kStarted:
49       return "��";
50     case kDone:
51       return "��";
52     case kCancelled:
53       return "��";
54   }
55 }
56 
Set(State state,SourceLocation whence)57 void ActionState::Set(State state, SourceLocation whence) {
58   LOG(INFO) << StateString(state) << " " << name() << " [" << step()
59             << "] t=" << Timestamp::Now() << " " << file() << ":" << line()
60             << " @ " << whence.file() << ":" << whence.line();
61   state_ = state;
62 }
63 
IsDone()64 bool ActionState::IsDone() {
65   switch (state_) {
66     case kNotCreated:
67     case kNotStarted:
68     case kStarted:
69       return false;
70     case kDone:
71     case kCancelled:
72       return true;
73   }
74 }
75 
76 ///////////////////////////////////////////////////////////////////////////////
77 // TestRegistry
78 
AllTests()79 std::vector<TestRegistry::Test> TestRegistry::AllTests() {
80   std::vector<Test> tests;
81   for (auto* r = root_; r; r = r->next_) {
82     r->ContributeTests(tests);
83   }
84   std::vector<Test> out;
85   for (auto& test : tests) {
86     if (absl::StartsWith(test.name, "DISABLED_")) continue;
87     out.emplace_back(std::move(test));
88   }
89   std::stable_sort(out.begin(), out.end(), [](const Test& a, const Test& b) {
90     return std::make_tuple(a.file, a.line) < std::make_tuple(b.file, b.line);
91   });
92   return out;
93 }
94 
95 ///////////////////////////////////////////////////////////////////////////////
96 // SimpleTestRegistry
97 
RegisterTest(absl::string_view file,int line,absl::string_view test_type,absl::string_view name,absl::AnyInvocable<YodelTest * (const fuzzing_event_engine::Actions &,absl::BitGenRef)const> create)98 void SimpleTestRegistry::RegisterTest(
99     absl::string_view file, int line, absl::string_view test_type,
100     absl::string_view name,
101     absl::AnyInvocable<YodelTest*(const fuzzing_event_engine::Actions&,
102                                   absl::BitGenRef) const>
103         create) {
104   tests_.push_back({file, line, std::string(test_type), std::string(name),
105                     std::move(create)});
106 }
107 
ContributeTests(std::vector<Test> & tests)108 void SimpleTestRegistry::ContributeTests(std::vector<Test>& tests) {
109   for (const auto& test : tests_) {
110     tests.push_back(
111         {test.file, test.line, test.test_type, test.name,
112          [test = &test](const fuzzing_event_engine::Actions& actions,
113                         absl::BitGenRef rng) {
114            return test->make(actions, rng);
115          }});
116   }
117 }
118 
119 }  // namespace yodel_detail
120 
121 ///////////////////////////////////////////////////////////////////////////////
122 // YodelTest::WatchDog
123 
124 class YodelTest::WatchDog {
125  public:
WatchDog(YodelTest * test)126   explicit WatchDog(YodelTest* test) : test_(test) {}
~WatchDog()127   ~WatchDog() { test_->state_->event_engine->Cancel(timer_); }
128 
129  private:
130   YodelTest* const test_;
131   grpc_event_engine::experimental::EventEngine::TaskHandle const timer_{
132       // For fuzzing, we'll wait for a year since the fuzzing EE allows delays
133       // capped to one year for each RunAfter() call. This will prevent
134       // pre-mature timeouts of some legitimate fuzzed inputs.
135       test_->state_->event_engine->RunAfter(
136           g_yodel_fuzzing ? Duration::Hours(24 * 365) : Duration::Minutes(5),
__anon0a8de82d0302() 137           [this]() { test_->Timeout(); })};
138 };
139 
140 ///////////////////////////////////////////////////////////////////////////////
141 // YodelTest
142 
YodelTest(const fuzzing_event_engine::Actions & actions,absl::BitGenRef rng)143 YodelTest::YodelTest(const fuzzing_event_engine::Actions& actions,
144                      absl::BitGenRef rng)
145     : rng_(rng), actions_(actions) {}
146 
RunTest()147 void YodelTest::RunTest() {
148   CoreConfiguration::Reset();
149   InitCoreConfiguration();
150   state_ = std::make_unique<State>();
151   state_->event_engine =
152       std::make_shared<grpc_event_engine::experimental::FuzzingEventEngine>(
153           []() {
154             grpc_timer_manager_set_start_threaded(false);
155             grpc_event_engine::experimental::FuzzingEventEngine::Options
156                 options;
157             return options;
158           }(),
159           actions_);
160   grpc_init();
161   state_->call_arena_allocator = MakeRefCounted<CallArenaAllocator>(
162       ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator(
163           "test-allocator"),
164       1024);
165   {
166     ExecCtx exec_ctx;
167     InitTest();
168   }
169   {
170     ExecCtx exec_ctx;
171     TestImpl();
172   }
173   EXPECT_EQ(pending_actions_.size(), 0)
174       << "There are still pending actions: did you forget to call "
175          "WaitForAllPendingWork()?";
176   Shutdown();
177   state_->event_engine->TickUntilIdle();
178   state_->event_engine->UnsetGlobalHooks();
179   grpc_event_engine::experimental::WaitForSingleOwner(
180       std::move(state_->event_engine));
181   grpc_shutdown_blocking();
182   if (!grpc_wait_until_shutdown(10)) {
183     LOG(FATAL) << "Timeout in waiting for gRPC shutdown";
184   }
185   state_.reset();
186   AsanAssertNoLeaks();
187 }
188 
TickUntilTrue(absl::FunctionRef<bool ()> poll)189 void YodelTest::TickUntilTrue(absl::FunctionRef<bool()> poll) {
190   WatchDog watchdog(this);
191   while (!poll()) {
192     ExecCtx exec_ctx;
193     state_->event_engine->Tick();
194   }
195 }
196 
WaitForAllPendingWork()197 void YodelTest::WaitForAllPendingWork() {
198   WatchDog watchdog(this);
199   while (!pending_actions_.empty()) {
200     if (pending_actions_.front()->IsDone()) {
201       pending_actions_.pop();
202       continue;
203     }
204     state_->event_engine->Tick();
205   }
206 }
207 
Timeout()208 void YodelTest::Timeout() {
209   std::vector<std::string> lines;
210   lines.emplace_back(absl::StrCat(
211       "Timeout waiting for pending actions to complete ", Timestamp::Now()));
212   while (!pending_actions_.empty()) {
213     auto action = std::move(pending_actions_.front());
214     pending_actions_.pop();
215     if (action->IsDone()) continue;
216     absl::string_view state_name =
217         yodel_detail::ActionState::StateString(action->Get());
218     absl::string_view file_name = action->file();
219     auto pos = file_name.find_last_of('/');
220     if (pos != absl::string_view::npos) {
221       file_name = file_name.substr(pos + 1);
222     }
223     lines.emplace_back(absl::StrCat("  ", state_name, " ", action->name(), " [",
224                                     action->step(), "]: ", file_name, ":",
225                                     action->line()));
226   }
227   Crash(absl::StrJoin(lines, "\n"));
228 }
229 
RandomString(int min_length,int max_length,absl::string_view character_set)230 std::string YodelTest::RandomString(int min_length, int max_length,
231                                     absl::string_view character_set) {
232   std::string out;
233   int length = absl::LogUniform<int>(rng_, min_length, max_length + 1);
234   for (int i = 0; i < length; ++i) {
235     out.push_back(
236         character_set[absl::Uniform<uint8_t>(rng_, 0, character_set.size())]);
237   }
238   return out;
239 }
240 
RandomStringFrom(std::initializer_list<absl::string_view> choices)241 std::string YodelTest::RandomStringFrom(
242     std::initializer_list<absl::string_view> choices) {
243   size_t idx = absl::Uniform<size_t>(rng_, 0, choices.size());
244   auto it = choices.begin();
245   for (size_t i = 0; i < idx; ++i) ++it;
246   return std::string(*it);
247 }
248 
RandomMetadataKey()249 std::string YodelTest::RandomMetadataKey() {
250   if (absl::Bernoulli(rng_, 0.1)) {
251     return RandomStringFrom({
252         ":path",
253         ":method",
254         ":status",
255         ":authority",
256         ":scheme",
257     });
258   }
259   std::string out;
260   do {
261     out = RandomString(1, 128, "abcdefghijklmnopqrstuvwxyz-_");
262   } while (absl::EndsWith(out, "-bin"));
263   return out;
264 }
265 
RandomMetadataValue(absl::string_view key)266 std::string YodelTest::RandomMetadataValue(absl::string_view key) {
267   if (key == ":method") {
268     return RandomStringFrom({"GET", "POST", "PUT"});
269   }
270   if (key == ":status") {
271     return absl::StrCat(absl::Uniform<int>(rng_, 100, 600));
272   }
273   if (key == ":scheme") {
274     return RandomStringFrom({"http", "https"});
275   }
276   if (key == "te") {
277     return "trailers";
278   }
279   static const NoDestruct<std::string> kChars{[]() {
280     std::string out;
281     for (char c = 32; c < 127; c++) out.push_back(c);
282     return out;
283   }()};
284   return RandomString(0, 128, *kChars);
285 }
286 
RandomMetadataBinaryKey()287 std::string YodelTest::RandomMetadataBinaryKey() {
288   return RandomString(1, 128, "abcdefghijklmnopqrstuvwxyz-_") + "-bin";
289 }
290 
RandomMetadataBinaryValue()291 std::string YodelTest::RandomMetadataBinaryValue() {
292   static const NoDestruct<std::string> kChars{[]() {
293     std::string out;
294     for (int c = 0; c < 256; c++) {
295       out.push_back(static_cast<char>(static_cast<uint8_t>(c)));
296     }
297     return out;
298   }()};
299   return RandomString(0, 4096, *kChars);
300 }
301 
RandomMetadata()302 std::vector<std::pair<std::string, std::string>> YodelTest::RandomMetadata() {
303   size_t size = 0;
304   const size_t max_size = absl::LogUniform<size_t>(rng_, 64, 8000);
305   std::vector<std::pair<std::string, std::string>> out;
306   for (;;) {
307     std::string key;
308     std::string value;
309     if (absl::Bernoulli(rng_, 0.1)) {
310       key = RandomMetadataBinaryKey();
311       value = RandomMetadataBinaryValue();
312     } else {
313       key = RandomMetadataKey();
314       value = RandomMetadataValue(key);
315     }
316     bool include = true;
317     for (size_t i = 0; i < out.size(); ++i) {
318       if (out[i].first == key) {
319         include = false;
320         break;
321       }
322     }
323     if (!include) continue;
324     size_t this_size = 32 + key.size() + value.size();
325     if (size + this_size > max_size) {
326       if (out.empty()) continue;
327       break;
328     }
329     size += this_size;
330     out.emplace_back(std::move(key), std::move(value));
331   }
332   return out;
333 }
334 
RandomMessage()335 std::string YodelTest::RandomMessage() {
336   static const NoDestruct<std::string> kChars{[]() {
337     std::string out;
338     for (int c = 0; c < 256; c++) {
339       out.push_back(static_cast<char>(static_cast<uint8_t>(c)));
340     }
341     return out;
342   }()};
343   return RandomString(0, max_random_message_size_, *kChars);
344 }
345 
346 }  // namespace grpc_core
347