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