1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <folly/Portability.h>
18
19 #include <folly/ScopeGuard.h>
20 #include <folly/Traits.h>
21 #include <folly/experimental/coro/AsyncGenerator.h>
22 #include <folly/experimental/coro/Baton.h>
23 #include <folly/experimental/coro/BlockingWait.h>
24 #include <folly/experimental/coro/Collect.h>
25 #include <folly/experimental/coro/Invoke.h>
26 #include <folly/experimental/coro/Sleep.h>
27 #include <folly/experimental/coro/Task.h>
28 #include <folly/experimental/coro/WithCancellation.h>
29 #include <folly/futures/Future.h>
30
31 #include <folly/portability/GTest.h>
32
33 #include <chrono>
34 #include <map>
35 #include <string>
36 #include <tuple>
37
38 #if FOLLY_HAS_COROUTINES
39
40 class AsyncGeneratorTest : public testing::Test {};
41
TEST_F(AsyncGeneratorTest,DefaultConstructedGeneratorIsEmpty)42 TEST_F(AsyncGeneratorTest, DefaultConstructedGeneratorIsEmpty) {
43 folly::coro::blockingWait([]() -> folly::coro::Task<void> {
44 folly::coro::AsyncGenerator<int> g;
45 auto result = co_await g.next();
46 CHECK(!result);
47 }());
48 }
49
TEST_F(AsyncGeneratorTest,GeneratorDestroyedBeforeCallingBegin)50 TEST_F(AsyncGeneratorTest, GeneratorDestroyedBeforeCallingBegin) {
51 bool started = false;
52 auto makeGenerator = [&]() -> folly::coro::AsyncGenerator<int> {
53 started = true;
54 co_return;
55 };
56
57 {
58 auto gen = makeGenerator();
59 (void)gen;
60 }
61
62 CHECK(!started);
63 }
64
TEST_F(AsyncGeneratorTest,PartiallyConsumingSequenceDestroysObjectsInScope)65 TEST_F(AsyncGeneratorTest, PartiallyConsumingSequenceDestroysObjectsInScope) {
66 bool started = false;
67 bool destroyed = false;
68 auto makeGenerator = [&]() -> folly::coro::AsyncGenerator<int> {
69 SCOPE_EXIT { destroyed = true; };
70 started = true;
71 co_yield 1;
72 co_yield 2;
73 co_return;
74 };
75
76 folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
77 {
78 auto gen = makeGenerator();
79 CHECK(!started);
80 CHECK(!destroyed);
81 auto result = co_await gen.next();
82 CHECK(started);
83 CHECK(!destroyed);
84 CHECK(result);
85 CHECK_EQ(1, *result);
86 }
87 CHECK(destroyed);
88 }());
89 }
90
TEST_F(AsyncGeneratorTest,FullyConsumeSequence)91 TEST_F(AsyncGeneratorTest, FullyConsumeSequence) {
92 auto makeGenerator = []() -> folly::coro::AsyncGenerator<int> {
93 for (int i = 0; i < 4; ++i) {
94 co_yield i;
95 }
96 };
97
98 folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
99 auto gen = makeGenerator();
100 auto result = co_await gen.next();
101 CHECK(result);
102 CHECK_EQ(0, *result);
103 result = co_await gen.next();
104 CHECK(result);
105 CHECK_EQ(1, *result);
106 result = co_await gen.next();
107 CHECK(result);
108 CHECK_EQ(2, *result);
109 result = co_await gen.next();
110 CHECK(result);
111 CHECK_EQ(3, *result);
112 result = co_await gen.next();
113 CHECK(!result);
114 }());
115 }
116
117 namespace {
118 struct SomeError : std::exception {};
119 } // namespace
120
TEST_F(AsyncGeneratorTest,ThrowExceptionBeforeFirstYield)121 TEST_F(AsyncGeneratorTest, ThrowExceptionBeforeFirstYield) {
122 auto makeGenerator = []() -> folly::coro::AsyncGenerator<int> {
123 if (true) {
124 throw SomeError{};
125 }
126 co_return;
127 };
128
129 folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
130 auto gen = makeGenerator();
131 bool caughtException = false;
132 try {
133 (void)co_await gen.next();
134 CHECK(false);
135 } catch (const SomeError&) {
136 caughtException = true;
137 }
138 CHECK(caughtException);
139 }());
140 }
141
TEST_F(AsyncGeneratorTest,ThrowExceptionAfterFirstYield)142 TEST_F(AsyncGeneratorTest, ThrowExceptionAfterFirstYield) {
143 auto makeGenerator = []() -> folly::coro::AsyncGenerator<int> {
144 co_yield 42;
145 throw SomeError{};
146 };
147
148 folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
149 auto gen = makeGenerator();
150 auto result = co_await gen.next();
151 CHECK(result);
152 CHECK_EQ(42, *result);
153 bool caughtException = false;
154 try {
155 (void)co_await gen.next();
156 CHECK(false);
157 } catch (const SomeError&) {
158 caughtException = true;
159 }
160 CHECK(caughtException);
161 }());
162 }
163
TEST_F(AsyncGeneratorTest,ConsumingManySynchronousElementsDoesNotOverflowStack)164 TEST_F(
165 AsyncGeneratorTest, ConsumingManySynchronousElementsDoesNotOverflowStack) {
166 auto makeGenerator = []() -> folly::coro::AsyncGenerator<std::uint64_t> {
167 for (std::uint64_t i = 0; i < 1'000'000; ++i) {
168 co_yield i;
169 }
170 };
171
172 folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
173 auto gen = makeGenerator();
174 std::uint64_t sum = 0;
175 while (auto result = co_await gen.next()) {
176 sum += *result;
177 }
178 CHECK_EQ(499999500000u, sum);
179 }());
180 }
181
TEST_F(AsyncGeneratorTest,ProduceResultsAsynchronously)182 TEST_F(AsyncGeneratorTest, ProduceResultsAsynchronously) {
183 folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
184 folly::Executor* executor = co_await folly::coro::co_current_executor;
185 auto makeGenerator = [&]() -> folly::coro::AsyncGenerator<int> {
186 using namespace std::literals::chrono_literals;
187 CHECK_EQ(executor, co_await folly::coro::co_current_executor);
188 co_await folly::coro::sleep(1ms);
189 CHECK_EQ(executor, co_await folly::coro::co_current_executor);
190 co_yield 1;
191 CHECK_EQ(executor, co_await folly::coro::co_current_executor);
192 co_await folly::coro::sleep(1ms);
193 CHECK_EQ(executor, co_await folly::coro::co_current_executor);
194 co_yield 2;
195 CHECK_EQ(executor, co_await folly::coro::co_current_executor);
196 co_await folly::coro::sleep(1ms);
197 CHECK_EQ(executor, co_await folly::coro::co_current_executor);
198 };
199
200 auto gen = makeGenerator();
201 auto result = co_await gen.next();
202 CHECK_EQ(1, *result);
203 result = co_await gen.next();
204 CHECK_EQ(2, *result);
205 result = co_await gen.next();
206 CHECK(!result);
207 }());
208 }
209
210 struct ConvertibleToIntReference {
211 int value;
operator int&ConvertibleToIntReference212 operator int&() { return value; }
213 };
214
TEST_F(AsyncGeneratorTest,GeneratorOfLValueReference)215 TEST_F(AsyncGeneratorTest, GeneratorOfLValueReference) {
216 auto makeGenerator = []() -> folly::coro::AsyncGenerator<int&> {
217 int value = 10;
218 co_yield value;
219 // Consumer gets a mutable reference to the value and can modify it.
220 CHECK_EQ(20, value);
221
222 // NOTE: Not allowed to yield an rvalue from an AsyncGenerator<T&>?
223 // co_yield 30; // Compile-error
224
225 co_yield ConvertibleToIntReference{30};
226 };
227
228 folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
229 auto gen = makeGenerator();
230 auto result = co_await gen.next();
231 CHECK_EQ(10, result.value());
232 *result = 20;
233 result = co_await gen.next();
234 CHECK_EQ(30, result.value());
235 result = co_await gen.next();
236 CHECK(!result.has_value());
237 }());
238 }
239
240 struct ConvertibleToInt {
operator intConvertibleToInt241 operator int() const { return 99; }
242 };
243
TEST_F(AsyncGeneratorTest,GeneratorOfConstLValueReference)244 TEST_F(AsyncGeneratorTest, GeneratorOfConstLValueReference) {
245 auto makeGenerator = []() -> folly::coro::AsyncGenerator<const int&> {
246 int value = 10;
247 co_yield value;
248 // Consumer gets a const reference to the value.
249
250 // Allowed to yield an rvalue from an AsyncGenerator<const T&>.
251 co_yield 30;
252
253 co_yield ConvertibleToInt{};
254 };
255
256 folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
257 auto gen = makeGenerator();
258 auto result = co_await gen.next();
259 CHECK_EQ(10, *result);
260 result = co_await gen.next();
261 CHECK_EQ(30, *result);
262 result = co_await gen.next();
263 CHECK_EQ(99, *result);
264 result = co_await gen.next();
265 CHECK(!result);
266 }());
267 }
268
TEST_F(AsyncGeneratorTest,GeneratorOfRValueReference)269 TEST_F(AsyncGeneratorTest, GeneratorOfRValueReference) {
270 auto makeGenerator =
271 []() -> folly::coro::AsyncGenerator<std::unique_ptr<int>&&> {
272 co_yield std::make_unique<int>(10);
273
274 auto ptr = std::make_unique<int>(20);
275 co_yield std::move(ptr);
276 CHECK(ptr == nullptr);
277 };
278
279 folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
280 auto gen = makeGenerator();
281
282 auto result = co_await gen.next();
283 CHECK_EQ(10, **result);
284 // Don't move it to a local var.
285
286 result = co_await gen.next();
287 CHECK_EQ(20, **result);
288 auto ptr = *result; // Move it to a local var.
289
290 result = co_await gen.next();
291 CHECK(!result);
292 }());
293 }
294
295 struct MoveOnly {
MoveOnlyMoveOnly296 explicit MoveOnly(int value) : value_(value) {}
MoveOnlyMoveOnly297 MoveOnly(MoveOnly&& other) noexcept
298 : value_(std::exchange(other.value_, -1)) {}
~MoveOnlyMoveOnly299 ~MoveOnly() {}
300 MoveOnly& operator=(MoveOnly&&) = delete;
valueMoveOnly301 int value() const { return value_; }
302
303 private:
304 int value_;
305 };
306
TEST_F(AsyncGeneratorTest,GeneratorOfMoveOnlyType)307 TEST_F(AsyncGeneratorTest, GeneratorOfMoveOnlyType) {
308 auto makeGenerator = []() -> folly::coro::AsyncGenerator<MoveOnly> {
309 MoveOnly rvalue(1);
310 co_yield std::move(rvalue);
311 CHECK_EQ(-1, rvalue.value());
312
313 co_yield MoveOnly(2);
314 };
315
316 folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
317 auto gen = makeGenerator();
318 auto result = co_await gen.next();
319
320 // NOTE: It's an error to dereference using '*it' as this returns a copy
321 // of the iterator's reference type, which in this case is 'MoveOnly'.
322 CHECK_EQ(1, result->value());
323
324 result = co_await gen.next();
325 CHECK_EQ(2, result->value());
326
327 result = co_await gen.next();
328 CHECK(!result);
329 }());
330 }
331
TEST_F(AsyncGeneratorTest,GeneratorOfConstValue)332 TEST_F(AsyncGeneratorTest, GeneratorOfConstValue) {
333 auto makeGenerator = []() -> folly::coro::AsyncGenerator<const int> {
334 // OK to yield prvalue
335 co_yield 42;
336
337 // OK to yield lvalue
338 int x = 123;
339 co_yield x;
340
341 co_yield ConvertibleToInt{};
342 };
343
344 folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
345 auto gen = makeGenerator();
346 auto result = co_await gen.next();
347 CHECK_EQ(42, *result);
348 static_assert(std::is_same_v<decltype(*result), const int&>);
349 result = co_await gen.next();
350 CHECK_EQ(123, *result);
351 result = co_await gen.next();
352 CHECK_EQ(99, *result);
353 result = co_await gen.next();
354 CHECK(!result);
355 }());
356 }
357
TEST_F(AsyncGeneratorTest,ExplicitValueType)358 TEST_F(AsyncGeneratorTest, ExplicitValueType) {
359 std::map<std::string, std::string> items;
360 items["foo"] = "hello";
361 items["bar"] = "goodbye";
362
363 auto makeGenerator = [&]() -> folly::coro::AsyncGenerator<
364 std::tuple<const std::string&, std::string&>,
365 std::tuple<std::string, std::string>> {
366 for (auto& [k, v] : items) {
367 co_yield {k, v};
368 }
369 };
370
371 folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
372 auto gen = makeGenerator();
373 auto result = co_await gen.next();
374 {
375 auto [kRef, vRef] = *result;
376 CHECK_EQ("bar", kRef);
377 CHECK_EQ("goodbye", vRef);
378 decltype(gen)::value_type copy = *result;
379 vRef = "au revoir";
380 CHECK_EQ("goodbye", std::get<1>(copy));
381 CHECK_EQ("au revoir", std::get<1>(*result));
382 }
383 }());
384
385 CHECK_EQ("au revoir", items["bar"]);
386 }
387
TEST_F(AsyncGeneratorTest,InvokeLambda)388 TEST_F(AsyncGeneratorTest, InvokeLambda) {
389 folly::coro::blockingWait([]() -> folly::coro::Task<void> {
390 auto ptr = std::make_unique<int>(123);
391 auto gen = folly::coro::co_invoke(
392 [p = std::move(ptr)]() mutable
393 -> folly::coro::AsyncGenerator<std::unique_ptr<int>&&> {
394 co_yield std::move(p);
395 });
396
397 auto result = co_await gen.next();
398 CHECK(result);
399 ptr = *result;
400 CHECK(ptr);
401 CHECK(*ptr == 123);
402 }());
403 }
404
405 template <typename Ref, typename Value = folly::remove_cvref_t<Ref>>
neverStream()406 folly::coro::AsyncGenerator<Ref, Value> neverStream() {
407 folly::coro::Baton baton;
408 folly::CancellationCallback cb{
409 co_await folly::coro::co_current_cancellation_token,
410 [&] { baton.post(); }};
411 co_await baton;
412 }
413
TEST_F(AsyncGeneratorTest,CancellationTokenPropagatesFromConsumer)414 TEST_F(AsyncGeneratorTest, CancellationTokenPropagatesFromConsumer) {
415 folly::coro::blockingWait([]() -> folly::coro::Task<void> {
416 folly::CancellationSource cancelSource;
417 bool suspended = false;
418 bool done = false;
419 co_await folly::coro::collectAll(
420 folly::coro::co_withCancellation(
421 cancelSource.getToken(),
422 [&]() -> folly::coro::Task<void> {
423 auto stream = neverStream<int>();
424 suspended = true;
425 auto result = co_await stream.next();
426 CHECK(!result.has_value());
427 done = true;
428 }()),
429 [&]() -> folly::coro::Task<void> {
430 co_await folly::coro::co_reschedule_on_current_executor;
431 co_await folly::coro::co_reschedule_on_current_executor;
432 co_await folly::coro::co_reschedule_on_current_executor;
433 CHECK(suspended);
434 CHECK(!done);
435 cancelSource.requestCancellation();
436 }());
437 CHECK(done);
438 }());
439 }
440
TEST_F(AsyncGeneratorTest,BlockingWaitOnFinalNextDoesNotDeadlock)441 TEST_F(AsyncGeneratorTest, BlockingWaitOnFinalNextDoesNotDeadlock) {
442 auto gen = []() -> folly::coro::AsyncGenerator<int> { co_yield 42; };
443
444 auto g = gen();
445 auto val1 = folly::coro::blockingWait(g.next());
446 CHECK_EQ(42, val1.value());
447 auto val2 = folly::coro::blockingWait(g.next());
448 CHECK(!val2.has_value());
449 }
450
TEST_F(AsyncGeneratorTest,BlockingWaitOnThrowingFinalNextDoesNotDeadlock)451 TEST_F(AsyncGeneratorTest, BlockingWaitOnThrowingFinalNextDoesNotDeadlock) {
452 auto gen = []() -> folly::coro::AsyncGenerator<int> {
453 co_yield 42;
454 throw SomeError{};
455 };
456
457 auto g = gen();
458 auto val1 = folly::coro::blockingWait(g.next());
459 CHECK_EQ(42, val1.value());
460 try {
461 folly::coro::blockingWait(g.next());
462 CHECK(false);
463 } catch (const SomeError&) {
464 }
465 }
466
range(int from,int to)467 folly::coro::AsyncGenerator<int> range(int from, int to) {
468 for (int i = from; i < to; ++i) {
469 co_yield i;
470 }
471 }
472
TEST_F(AsyncGeneratorTest,SymmetricTransfer)473 TEST_F(AsyncGeneratorTest, SymmetricTransfer) {
474 folly::coro::blockingWait([]() -> folly::coro::Task<void> {
475 int max = 100000;
476 auto g = range(1, max + 1);
477 long long sum = 0;
478 while (auto result = co_await g.next()) {
479 sum += *result;
480 }
481 EXPECT_EQ(((long long)max + 1) * max / 2, sum);
482 }());
483 }
484
TEST(AsyncGenerator,YieldCoError)485 TEST(AsyncGenerator, YieldCoError) {
486 folly::coro::blockingWait([]() -> folly::coro::Task<void> {
487 auto gen = []() -> folly::coro::AsyncGenerator<std::string> {
488 co_yield "foo";
489 co_yield "bar";
490 co_yield folly::coro::co_error(SomeError{});
491 CHECK(false);
492 }();
493
494 auto item1 = co_await gen.next();
495 CHECK(item1.value() == "foo");
496 auto item2 = co_await gen.next();
497 CHECK(item2.value() == "bar");
498
499 try {
500 (void)co_await gen.next();
501 CHECK(false);
502 } catch (const SomeError&) {
503 }
504 }());
505 }
506
TEST(AsyncGenerator,YieldCoResult)507 TEST(AsyncGenerator, YieldCoResult) {
508 folly::coro::blockingWait([]() -> folly::coro::Task<void> {
509 auto gen = []() -> folly::coro::AsyncGenerator<std::string> {
510 co_yield folly::coro::co_result(folly::Try<std::string>("foo"));
511 co_yield folly::coro::co_result(folly::Try<std::string>("bar"));
512 co_yield folly::coro::co_result(folly::Try<std::string>(SomeError{}));
513 ADD_FAILURE();
514 }();
515
516 auto item1 = co_await gen.next();
517 CHECK(item1.value() == "foo");
518 auto item2 = co_await gen.next();
519 CHECK(item2.value() == "bar");
520
521 EXPECT_THROW(co_await gen.next(), SomeError);
522
523 gen = []() -> folly::coro::AsyncGenerator<std::string> {
524 co_yield folly::coro::co_result(folly::Try<std::string>("foo"));
525 co_yield folly::coro::co_result(folly::Try<std::string>("bar"));
526 co_yield folly::coro::co_result(folly::Try<std::string>());
527 ADD_FAILURE();
528 }();
529
530 item1 = co_await gen.next();
531 CHECK(item1.value() == "foo");
532 item2 = co_await gen.next();
533 CHECK(item2.value() == "bar");
534 EXPECT_FALSE(co_await gen.next());
535 }());
536 }
537
TEST(AsyncGenerator,CoResultProxyExample)538 TEST(AsyncGenerator, CoResultProxyExample) {
539 folly::coro::blockingWait([]() -> folly::coro::Task<void> {
540 auto makeProxy = [](folly::coro::AsyncGenerator<int> gen)
541 -> folly::coro::AsyncGenerator<int> {
542 while (true) {
543 auto t = co_await folly::coro::co_awaitTry(gen.next());
544 co_yield folly::coro::co_result(std::move(t));
545 }
546 };
547
548 auto gen = []() -> folly::coro::AsyncGenerator<int> {
549 for (int i = 0; i < 5; ++i) {
550 co_yield i;
551 }
552 }();
553
554 auto proxy = makeProxy(std::move(gen));
555
556 for (int i = 0; i < 5; ++i) {
557 auto val = co_await proxy.next();
558 EXPECT_EQ(*val, i);
559 }
560 EXPECT_FALSE(co_await proxy.next());
561
562 gen = []() -> folly::coro::AsyncGenerator<int> {
563 for (int i = 0; i < 5; ++i) {
564 co_yield i;
565 }
566 throw SomeError();
567 }();
568
569 proxy = makeProxy(std::move(gen));
570
571 for (int i = 0; i < 5; ++i) {
572 auto val = co_await proxy.next();
573 EXPECT_EQ(*val, i);
574 }
575 EXPECT_THROW(co_await proxy.next(), SomeError);
576 }());
577 }
578
TEST(AsyncGeneraor,CoAwaitTry)579 TEST(AsyncGeneraor, CoAwaitTry) {
580 folly::coro::blockingWait([]() -> folly::coro::Task<void> {
581 auto gen = []() -> folly::coro::AsyncGenerator<std::string> {
582 co_yield "foo";
583 co_yield "bar";
584 co_yield folly::coro::co_error(SomeError{});
585 CHECK(false);
586 }();
587
588 auto item1 = co_await folly::coro::co_awaitTry(gen.next());
589 CHECK(item1.hasValue());
590 CHECK(*item1.value() == "foo");
591 auto item2 = co_await folly::coro::co_awaitTry(gen.next());
592 CHECK(item2.hasValue());
593 CHECK(*item2.value() == "bar");
594 auto item3 = co_await folly::coro::co_awaitTry(gen.next());
595 CHECK(item3.hasException());
596 CHECK(item3.exception().is_compatible_with<SomeError>());
597 }());
598 }
599
TEST(AsyncGenerator,SafePoint)600 TEST(AsyncGenerator, SafePoint) {
601 folly::coro::blockingWait([]() -> folly::coro::Task<void> {
602 enum class step_type {
603 init,
604 before_continue_sp,
605 after_continue_sp,
606 before_cancel_sp,
607 after_cancel_sp,
608 };
609 step_type step = step_type::init;
610
611 folly::CancellationSource cancelSrc;
612 auto gen =
613 folly::coro::co_invoke([&]() -> folly::coro::AsyncGenerator<int> {
614 step = step_type::before_continue_sp;
615 co_await folly::coro::co_safe_point;
616 step = step_type::after_continue_sp;
617
618 cancelSrc.requestCancellation();
619
620 step = step_type::before_cancel_sp;
621 co_await folly::coro::co_safe_point;
622 step = step_type::after_cancel_sp;
623 });
624
625 auto result = co_await folly::coro::co_awaitTry(
626 folly::coro::co_withCancellation(cancelSrc.getToken(), gen.next()));
627 EXPECT_THROW(result.value(), folly::OperationCancelled);
628 EXPECT_EQ(step_type::before_cancel_sp, step);
629 }());
630 }
631
TEST(AsyncGenerator,CoAwaitNothrow)632 TEST(AsyncGenerator, CoAwaitNothrow) {
633 auto res =
634 folly::coro::blockingWait(co_awaitTry([]() -> folly::coro::Task<void> {
635 auto gen = []() -> folly::coro::AsyncGenerator<int> {
636 auto inner = []() -> folly::coro::AsyncGenerator<int> {
637 co_yield 42;
638 throw std::runtime_error("");
639 }();
640 co_yield *co_await co_nothrow(inner.next());
641 try {
642 co_yield *co_await co_nothrow(inner.next());
643 } catch (...) {
644 ADD_FAILURE();
645 }
646 ADD_FAILURE();
647 }();
648
649 co_await co_nothrow(gen.next());
650 try {
651 co_await co_nothrow(gen.next());
652 } catch (...) {
653 ADD_FAILURE();
654 }
655 ADD_FAILURE();
656 }()));
657 EXPECT_TRUE(res.hasException<std::runtime_error>());
658 }
659
TEST(AsyncGenerator,CoAwaitNothrowMultiTask)660 TEST(AsyncGenerator, CoAwaitNothrowMultiTask) {
661 auto res =
662 folly::coro::blockingWait(co_awaitTry([]() -> folly::coro::Task<void> {
663 auto gen = co_await
664 []() -> folly::coro::Task<folly::coro::AsyncGenerator<int>> {
665 auto gen = []() -> folly::coro::AsyncGenerator<int> {
666 co_yield 42;
667 throw std::runtime_error("");
668 }();
669
670 co_await co_nothrow(gen.next());
671 co_return gen;
672 }();
673
674 try {
675 co_await co_nothrow(gen.next());
676 } catch (...) {
677 ADD_FAILURE();
678 }
679 ADD_FAILURE();
680 }()));
681 EXPECT_TRUE(res.hasException<std::runtime_error>());
682 }
683
684 #endif
685