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