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/CancellationToken.h>
20 #include <folly/Chrono.h>
21 #include <folly/executors/ManualExecutor.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/Coroutine.h>
26 #include <folly/experimental/coro/CurrentExecutor.h>
27 #include <folly/experimental/coro/DetachOnCancel.h>
28 #include <folly/experimental/coro/Invoke.h>
29 #include <folly/experimental/coro/Sleep.h>
30 #include <folly/experimental/coro/Task.h>
31 #include <folly/experimental/coro/TimedWait.h>
32 #include <folly/experimental/coro/WithCancellation.h>
33 #include <folly/fibers/Semaphore.h>
34 #include <folly/futures/Future.h>
35 #include <folly/io/async/ScopedEventBaseThread.h>
36 #include <folly/lang/Assume.h>
37 #include <folly/portability/GTest.h>
38 
39 #if FOLLY_HAS_COROUTINES
40 
41 using namespace folly;
42 
43 class CoroTest : public testing::Test {};
44 
TEST_F(CoroTest,Basic)45 TEST_F(CoroTest, Basic) {
46   ManualExecutor executor;
47   auto task42 = []() -> coro::Task<int> { co_return 42; };
48   auto future = task42().scheduleOn(&executor).start();
49 
50   EXPECT_FALSE(future.isReady());
51 
52   executor.drain();
53 
54   EXPECT_TRUE(future.isReady());
55   EXPECT_EQ(42, std::move(future).get());
56 }
57 
TEST_F(CoroTest,BasicSemiFuture)58 TEST_F(CoroTest, BasicSemiFuture) {
59   ManualExecutor executor;
60   auto task42 = []() -> coro::Task<int> { co_return 42; };
61   auto future = task42().semi().via(&executor);
62 
63   EXPECT_FALSE(future.isReady());
64 
65   executor.drain();
66 
67   EXPECT_TRUE(future.isReady());
68   EXPECT_EQ(42, std::move(future).get());
69 }
70 
TEST_F(CoroTest,BasicFuture)71 TEST_F(CoroTest, BasicFuture) {
72   ManualExecutor executor;
73 
74   auto task42 = []() -> coro::Task<int> { co_return 42; };
75   auto future = task42().scheduleOn(&executor).start();
76 
77   EXPECT_FALSE(future.isReady());
78 
79   EXPECT_EQ(42, std::move(future).via(&executor).getVia(&executor));
80 }
81 
taskVoid()82 coro::Task<void> taskVoid() {
83   auto task42 = []() -> coro::Task<int> { co_return 42; };
84   (void)co_await task42();
85   co_return;
86 }
87 
TEST_F(CoroTest,Basic2)88 TEST_F(CoroTest, Basic2) {
89   ManualExecutor executor;
90   auto future = taskVoid().scheduleOn(&executor).start();
91 
92   EXPECT_FALSE(future.isReady());
93 
94   executor.drain();
95 
96   EXPECT_TRUE(future.isReady());
97 }
98 
TEST_F(CoroTest,TaskOfMoveOnly)99 TEST_F(CoroTest, TaskOfMoveOnly) {
100   auto f = []() -> coro::Task<std::unique_ptr<int>> {
101     co_return std::make_unique<int>(123);
102   };
103 
104   auto p = coro::blockingWait(f());
105   EXPECT_TRUE(p);
106   EXPECT_EQ(123, *p);
107 }
108 
taskSleep()109 coro::Task<void> taskSleep() {
110   (void)co_await coro::sleep(std::chrono::seconds{1});
111   co_return;
112 }
113 
TEST_F(CoroTest,Sleep)114 TEST_F(CoroTest, Sleep) {
115   ScopedEventBaseThread evbThread;
116 
117   auto startTime = std::chrono::steady_clock::now();
118   auto task = taskSleep().scheduleOn(evbThread.getEventBase());
119 
120   coro::blockingWait(std::move(task));
121 
122   // The total time should be roughly 1 second. Some builds, especially
123   // optimized ones, may result in slightly less than 1 second, so we perform
124   // rounding here.
125   auto totalTime = std::chrono::steady_clock::now() - startTime;
126   EXPECT_GE(
127       chrono::round<std::chrono::seconds>(totalTime), std::chrono::seconds{1});
128 }
129 
TEST_F(CoroTest,ExecutorKeepAlive)130 TEST_F(CoroTest, ExecutorKeepAlive) {
131   auto future = [] {
132     ScopedEventBaseThread evbThread;
133 
134     return taskSleep().scheduleOn(evbThread.getEventBase()).start();
135   }();
136   EXPECT_TRUE(future.isReady());
137 }
138 
TEST_F(CoroTest,ExecutorKeepAliveDummy)139 TEST_F(CoroTest, ExecutorKeepAliveDummy) {
140   struct CountingExecutor : public ManualExecutor {
141     bool keepAliveAcquire() noexcept override {
142       ++keepAliveCounter;
143       return true;
144     }
145     void keepAliveRelease() noexcept override { --keepAliveCounter; }
146 
147     size_t keepAliveCounter{0};
148   };
149 
150   struct ExecutorRec {
151     static coro::Task<void> go(int depth) {
152       if (depth == 0) {
153         co_return;
154       }
155 
156       auto executor =
157           dynamic_cast<CountingExecutor*>(co_await coro::co_current_executor);
158       DCHECK(executor);
159 
160       // Note, extra keep-alives are being kept by the Futures.
161       EXPECT_EQ(3, executor->keepAliveCounter);
162 
163       co_await go(depth - 1);
164     }
165   };
166 
167   CountingExecutor executor;
168   ExecutorRec::go(42).scheduleOn(&executor).start().via(&executor).getVia(
169       &executor);
170 }
171 
TEST_F(CoroTest,FutureThrow)172 TEST_F(CoroTest, FutureThrow) {
173   auto taskException = []() -> coro::Task<int> {
174     throw std::runtime_error("Test exception");
175     co_return 42;
176   };
177 
178   ManualExecutor executor;
179   auto future = taskException().scheduleOn(&executor).start();
180 
181   EXPECT_FALSE(future.isReady());
182 
183   executor.drain();
184 
185   EXPECT_TRUE(future.isReady());
186   EXPECT_THROW(std::move(future).get(), std::runtime_error);
187 }
188 
taskRecursion(int depth)189 coro::Task<int> taskRecursion(int depth) {
190   if (depth > 0) {
191     EXPECT_EQ(depth - 1, co_await taskRecursion(depth - 1));
192   } else {
193     (void)co_await coro::sleep(std::chrono::seconds{1});
194   }
195 
196   co_return depth;
197 }
198 
TEST_F(CoroTest,LargeStack)199 TEST_F(CoroTest, LargeStack) {
200   ScopedEventBaseThread evbThread;
201   auto task = taskRecursion(50000).scheduleOn(evbThread.getEventBase());
202 
203   EXPECT_EQ(50000, coro::blockingWait(std::move(task)));
204 }
205 
TEST_F(CoroTest,NestedThreads)206 TEST_F(CoroTest, NestedThreads) {
207   auto taskThreadNested = [](std::thread::id threadId) -> coro::Task<void> {
208     EXPECT_EQ(threadId, std::this_thread::get_id());
209     (void)co_await coro::sleep(std::chrono::seconds{1});
210     EXPECT_EQ(threadId, std::this_thread::get_id());
211     co_return;
212   };
213 
214   auto taskThread = [&]() -> coro::Task<int> {
215     auto threadId = std::this_thread::get_id();
216 
217     // BUG: Under @mode/clang-opt builds this object is placed on the coroutine
218     // frame and the code for the constructor assumes that it is allocated on
219     // a 16-byte boundary. However, when placed in the coroutine frame it can
220     // end up at a location that is not 16-byte aligned. This causes a SIGSEGV
221     // when performing a store to members that uses SSE instructions.
222     folly::ScopedEventBaseThread evbThread;
223 
224     co_await taskThreadNested(evbThread.getThreadId())
225         .scheduleOn(evbThread.getEventBase());
226 
227     EXPECT_EQ(threadId, std::this_thread::get_id());
228 
229     co_return 42;
230   };
231 
232   ScopedEventBaseThread evbThread;
233   auto task = taskThread().scheduleOn(evbThread.getEventBase());
234   EXPECT_EQ(42, coro::blockingWait(std::move(task)));
235 }
236 
TEST_F(CoroTest,CurrentExecutor)237 TEST_F(CoroTest, CurrentExecutor) {
238   auto taskGetCurrentExecutor = [](Executor* executor) -> coro::Task<int> {
239     auto current = co_await coro::co_current_executor;
240     EXPECT_EQ(executor, current);
241     auto task42 = []() -> coro::Task<int> { co_return 42; };
242     co_return co_await task42().scheduleOn(current);
243   };
244 
245   ScopedEventBaseThread evbThread;
246   auto task = taskGetCurrentExecutor(evbThread.getEventBase())
247                   .scheduleOn(evbThread.getEventBase());
248   EXPECT_EQ(42, coro::blockingWait(std::move(task)));
249 }
250 
TEST_F(CoroTest,TimedWaitFuture)251 TEST_F(CoroTest, TimedWaitFuture) {
252   auto taskTimedWaitFuture = []() -> coro::Task<void> {
253     auto ex = co_await coro::co_current_executor;
254     auto fastFuture = futures::sleep(std::chrono::milliseconds{50})
255                           .via(ex)
256                           .thenValue([](Unit) { return 42; });
257     auto fastResult = co_await coro::timed_wait(
258         std::move(fastFuture), std::chrono::milliseconds{100});
259     EXPECT_TRUE(fastResult);
260     EXPECT_EQ(42, *fastResult);
261 
262     struct ExpectedException : public std::runtime_error {
263       ExpectedException() : std::runtime_error("ExpectedException") {}
264     };
265 
266     auto throwingFuture =
267         futures::sleep(std::chrono::milliseconds{50})
268             .via(ex)
269             .thenValue([](Unit) { throw ExpectedException(); });
270     EXPECT_THROW(
271         (void)co_await coro::timed_wait(
272             std::move(throwingFuture), std::chrono::milliseconds{100}),
273         ExpectedException);
274 
275     auto promiseFuturePair = folly::makePromiseContract<folly::Unit>(ex);
276     auto lifetimeFuture = std::move(promiseFuturePair.second);
277     auto slowFuture =
278         futures::sleep(std::chrono::milliseconds{200})
279             .via(ex)
280             .thenValue([lifetimePromise =
281                             std::move(promiseFuturePair.first)](Unit) mutable {
282               lifetimePromise.setValue();
283               return 42;
284             });
285     auto slowResult = co_await coro::timed_wait(
286         std::move(slowFuture), std::chrono::milliseconds{100});
287     EXPECT_FALSE(slowResult);
288 
289     // Ensure that task completes for safe executor lifetimes
290     (void)co_await std::move(lifetimeFuture);
291 
292     co_return;
293   };
294 
295   coro::blockingWait(taskTimedWaitFuture());
296 }
297 
TEST_F(CoroTest,TimedWaitTask)298 TEST_F(CoroTest, TimedWaitTask) {
299   auto taskTimedWaitTask = []() -> coro::Task<void> {
300     auto fastTask = []() -> coro::Task<int> {
301       co_await coro::sleep(std::chrono::milliseconds{50});
302       co_return 42;
303     }();
304     auto fastResult = co_await coro::timed_wait(
305         std::move(fastTask), std::chrono::milliseconds{100});
306     EXPECT_TRUE(fastResult);
307     EXPECT_EQ(42, *fastResult);
308 
309     struct ExpectedException : public std::runtime_error {
310       ExpectedException() : std::runtime_error("ExpectedException") {}
311     };
312 
313     auto throwingTask = []() -> coro::Task<void> {
314       co_await coro::sleep(std::chrono::milliseconds{50});
315       throw ExpectedException();
316     }();
317     EXPECT_THROW(
318         (void)co_await coro::timed_wait(
319             std::move(throwingTask), std::chrono::milliseconds{100}),
320         ExpectedException);
321 
322     auto slowTask = []() -> coro::Task<int> {
323       co_await futures::sleep(std::chrono::milliseconds{200});
324       co_return 42;
325     }();
326     auto slowResult = co_await coro::timed_wait(
327         std::move(slowTask), std::chrono::milliseconds{100});
328     EXPECT_FALSE(slowResult);
329 
330     co_return;
331   };
332 
333   coro::blockingWait(taskTimedWaitTask());
334 }
335 
TEST_F(CoroTest,TimedWaitKeepAlive)336 TEST_F(CoroTest, TimedWaitKeepAlive) {
337   auto start = std::chrono::steady_clock::now();
338   coro::blockingWait([]() -> coro::Task<void> {
339     co_await coro::timed_wait(
340         coro::sleep(std::chrono::milliseconds{100}), std::chrono::seconds{60});
341     co_return;
342   }());
343   auto duration = std::chrono::steady_clock::now() - start;
344   EXPECT_LE(duration, std::chrono::seconds{30});
345 }
346 
TEST_F(CoroTest,TimedWaitNonCopyable)347 TEST_F(CoroTest, TimedWaitNonCopyable) {
348   auto task = []() -> coro::Task<std::unique_ptr<int>> {
349     co_return std::make_unique<int>(42);
350   }();
351   EXPECT_EQ(
352       42,
353       **coro::blockingWait(
354           [&]() -> coro::Task<folly::Optional<std::unique_ptr<int>>> {
355             co_return co_await coro::timed_wait(
356                 std::move(task), std::chrono::seconds{60});
357           }()));
358 }
359 
360 namespace {
361 
362 template <int value>
363 struct AwaitableInt {
await_ready__anon4b303c821711::AwaitableInt364   bool await_ready() const { return true; }
365 
await_suspend__anon4b303c821711::AwaitableInt366   bool await_suspend(coro::coroutine_handle<>) { assume_unreachable(); }
367 
await_resume__anon4b303c821711::AwaitableInt368   int await_resume() { return value; }
369 };
370 
371 struct AwaitableWithOperator {};
372 
operator co_await(const AwaitableWithOperator &)373 AwaitableInt<42> operator co_await(const AwaitableWithOperator&) {
374   return {};
375 }
376 
377 struct AwaitableWithMemberOperator {
operator co_await__anon4b303c821711::AwaitableWithMemberOperator378   AwaitableInt<42> operator co_await() { return {}; }
379 };
380 
operator co_await(const AwaitableWithMemberOperator &)381 FOLLY_MAYBE_UNUSED AwaitableInt<24> operator co_await(
382     const AwaitableWithMemberOperator&) {
383   return {};
384 }
385 
386 } // namespace
387 
TEST_F(CoroTest,AwaitableWithOperator)388 TEST_F(CoroTest, AwaitableWithOperator) {
389   auto taskAwaitableWithOperator = []() -> coro::Task<int> {
390     co_return co_await AwaitableWithOperator();
391   };
392 
393   EXPECT_EQ(42, coro::blockingWait(taskAwaitableWithOperator()));
394 }
395 
TEST_F(CoroTest,AwaitableWithMemberOperator)396 TEST_F(CoroTest, AwaitableWithMemberOperator) {
397   auto taskAwaitableWithMemberOperator = []() -> coro::Task<int> {
398     co_return co_await AwaitableWithMemberOperator();
399   };
400 
401   EXPECT_EQ(42, coro::blockingWait(taskAwaitableWithMemberOperator()));
402 }
403 
TEST_F(CoroTest,Baton)404 TEST_F(CoroTest, Baton) {
405   auto taskBaton = [](fibers::Baton& baton) -> coro::Task<int> {
406     co_await baton;
407     co_return 42;
408   };
409 
410   ManualExecutor executor;
411   fibers::Baton baton;
412   auto future = taskBaton(baton).scheduleOn(&executor).start();
413 
414   EXPECT_FALSE(future.isReady());
415 
416   executor.drain();
417 
418   EXPECT_FALSE(future.isReady());
419 
420   baton.post();
421   executor.drain();
422 
423   EXPECT_TRUE(future.isReady());
424   EXPECT_EQ(42, std::move(future).get());
425 }
426 
TEST_F(CoroTest,FulfilledFuture)427 TEST_F(CoroTest, FulfilledFuture) {
428   auto taskFuture = [](auto value) -> coro::Task<decltype(value)> {
429     co_return co_await folly::makeFuture(std::move(value));
430   };
431 
432   EXPECT_EQ(42, coro::blockingWait(taskFuture(42)));
433 }
434 
TEST_F(CoroTest,MoveOnlyReturn)435 TEST_F(CoroTest, MoveOnlyReturn) {
436   auto taskFuture = [](auto value) -> coro::Task<decltype(value)> {
437     co_return co_await folly::makeFuture(std::move(value));
438   };
439 
440   EXPECT_EQ(42, *coro::blockingWait(taskFuture(std::make_unique<int>(42))));
441 }
442 
TEST_F(CoroTest,co_invoke)443 TEST_F(CoroTest, co_invoke) {
444   ManualExecutor executor;
445   Promise<folly::Unit> p;
446   auto coroFuture =
447       coro::co_invoke([f = p.getSemiFuture()]() mutable -> coro::Task<void> {
448         (void)co_await std::move(f);
449         co_return;
450       })
451           .scheduleOn(&executor)
452           .start();
453   executor.drain();
454   EXPECT_FALSE(coroFuture.isReady());
455 
456   p.setValue(folly::unit);
457 
458   executor.drain();
459   EXPECT_TRUE(coroFuture.isReady());
460 }
461 
TEST_F(CoroTest,Semaphore)462 TEST_F(CoroTest, Semaphore) {
463   static constexpr size_t kTasks = 10;
464   static constexpr size_t kIterations = 10000;
465   static constexpr size_t kNumTokens = 10;
466   static constexpr size_t kNumThreads = 16;
467 
468   fibers::Semaphore sem(kNumTokens);
469 
470   struct Worker {
471     explicit Worker(fibers::Semaphore& s) : sem(s), t([&] { run(); }) {}
472 
473     void run() {
474       folly::EventBase evb;
475 
476       {
477         std::shared_ptr<folly::EventBase> completionCounter(
478             &evb, [](folly::EventBase* evb_) { evb_->terminateLoopSoon(); });
479 
480         for (size_t i = 0; i < kTasks; ++i) {
481           coro::co_invoke([&, completionCounter]() -> coro::Task<void> {
482             for (size_t j = 0; j < kIterations; ++j) {
483               co_await sem.co_wait();
484               ++counter;
485               sem.signal();
486               --counter;
487 
488               EXPECT_LT(counter, kNumTokens);
489               EXPECT_GE(counter, 0);
490             }
491           })
492               .scheduleOn(&evb)
493               .start();
494         }
495       }
496       evb.loopForever();
497     }
498 
499     fibers::Semaphore& sem;
500     int counter{0};
501     std::thread t;
502   };
503 
504   std::vector<Worker> workers;
505   workers.reserve(kNumThreads);
506   for (size_t i = 0; i < kNumThreads; ++i) {
507     workers.emplace_back(sem);
508   }
509 
510   for (auto& worker : workers) {
511     worker.t.join();
512   }
513 
514   for (auto& worker : workers) {
515     EXPECT_EQ(0, worker.counter);
516   }
517 }
518 
TEST_F(CoroTest,SemaphoreWaitWhenCancellationAlreadyRequested)519 TEST_F(CoroTest, SemaphoreWaitWhenCancellationAlreadyRequested) {
520   folly::coro::blockingWait([&]() -> folly::coro::Task<> {
521     folly::CancellationSource cancelSource;
522     cancelSource.requestCancellation();
523 
524     // Run some logic while in an already-cancelled state.
525     co_await folly::coro::co_withCancellation(
526         cancelSource.getToken(), []() -> folly::coro::Task<> {
527           folly::fibers::Semaphore sem{1};
528 
529           // If in a signalled state then should complete normally
530           co_await sem.co_wait();
531 
532           // And the semaphore should no longer be in the signalled state.
533 
534           // But if not signalled then should complete with cancellation
535           // immediately.
536           EXPECT_THROW(co_await sem.co_wait(), folly::OperationCancelled);
537         }());
538   }());
539 }
540 
TEST_F(CoroTest,CancelOutstandingSemaphoreWait)541 TEST_F(CoroTest, CancelOutstandingSemaphoreWait) {
542   struct ExpectedError : std::exception {};
543 
544   folly::coro::blockingWait([&]() -> folly::coro::Task<> {
545     folly::fibers::Semaphore sem{0};
546     try {
547       co_await folly::coro::collectAll(
548           [&]() -> folly::coro::Task<> {
549             EXPECT_THROW(co_await sem.co_wait(), OperationCancelled);
550           }(),
551           []() -> folly::coro::Task<> {
552             co_await folly::coro::co_reschedule_on_current_executor;
553             // Completing the second task with an error will cause
554             // collectAll() to request cancellation of the other task
555             // whcih should reqeust cancellation of sem.co_wait().
556             co_yield folly::coro::co_error(ExpectedError{});
557           }());
558     } catch (const ExpectedError&) {
559     }
560   }());
561 }
562 
TEST_F(CoroTest,CancelOneSemaphoreWaitDoesNotAffectOthers)563 TEST_F(CoroTest, CancelOneSemaphoreWaitDoesNotAffectOthers) {
564   folly::coro::blockingWait([]() -> folly::coro::Task<void> {
565     folly::fibers::Semaphore sem{0};
566 
567     folly::CancellationSource cancelSource;
568 
569     co_await folly::coro::collectAll(
570         [&]() -> folly::coro::Task<> {
571           EXPECT_THROW(
572               (co_await folly::coro::co_withCancellation(
573                   cancelSource.getToken(), sem.co_wait())),
574               OperationCancelled);
575         }(),
576         [&]() -> folly::coro::Task<> { co_await sem.co_wait(); }(),
577         [&]() -> folly::coro::Task<> {
578           co_await folly::coro::co_reschedule_on_current_executor;
579           cancelSource.requestCancellation();
580           sem.signal();
581         }());
582   }());
583 }
584 
TEST_F(CoroTest,FutureTry)585 TEST_F(CoroTest, FutureTry) {
586   folly::coro::blockingWait([]() -> folly::coro::Task<void> {
587     auto task42 = []() -> coro::Task<int> { co_return 42; };
588     auto taskException = []() -> coro::Task<int> {
589       throw std::runtime_error("Test exception");
590       co_return 42;
591     };
592 
593     {
594       auto result = co_await folly::coro::co_awaitTry(task42().semi());
595       EXPECT_TRUE(result.hasValue());
596       EXPECT_EQ(42, result.value());
597     }
598 
599     {
600       auto result = co_await folly::coro::co_awaitTry(taskException().semi());
601       EXPECT_TRUE(result.hasException());
602     }
603 
604     {
605       auto result = co_await folly::coro::co_awaitTry(
606           task42().semi().via(co_await folly::coro::co_current_executor));
607       EXPECT_TRUE(result.hasValue());
608       EXPECT_EQ(42, result.value());
609     }
610 
611     {
612       auto result =
613           co_await folly::coro::co_awaitTry(taskException().semi().via(
614               co_await folly::coro::co_current_executor));
615       EXPECT_TRUE(result.hasException());
616     }
617   }());
618 }
619 
TEST_F(CoroTest,CancellableSleep)620 TEST_F(CoroTest, CancellableSleep) {
621   using namespace std::chrono;
622   using namespace std::chrono_literals;
623 
624   CancellationSource cancelSrc;
625 
626   auto start = steady_clock::now();
627   EXPECT_THROW(
628       coro::blockingWait([&]() -> coro::Task<void> {
629         co_await coro::collectAll(
630             [&]() -> coro::Task<void> {
631               co_await coro::co_withCancellation(
632                   cancelSrc.getToken(), coro::sleep(10s));
633             }(),
634             [&]() -> coro::Task<void> {
635               co_await coro::co_reschedule_on_current_executor;
636               co_await coro::co_reschedule_on_current_executor;
637               co_await coro::co_reschedule_on_current_executor;
638               cancelSrc.requestCancellation();
639             }());
640       }()),
641       OperationCancelled);
642   auto end = steady_clock::now();
643   CHECK((end - start) < 1s);
644 }
645 
TEST_F(CoroTest,DefaultConstructible)646 TEST_F(CoroTest, DefaultConstructible) {
647   coro::blockingWait([]() -> coro::Task<void> {
648     struct S {
649       int x = 42;
650     };
651 
652     auto taskS = []() -> coro::Task<S> { co_return {}; };
653 
654     auto s = co_await taskS();
655     EXPECT_EQ(42, s.x);
656   }());
657 }
658 
TEST(Coro,CoReturnTry)659 TEST(Coro, CoReturnTry) {
660   EXPECT_EQ(42, folly::coro::blockingWait([]() -> folly::coro::Task<int> {
661               co_return folly::Try<int>(42);
662             }()));
663 
664   struct ExpectedException : public std::runtime_error {
665     ExpectedException() : std::runtime_error("ExpectedException") {}
666   };
667   EXPECT_THROW(
668       folly::coro::blockingWait([]() -> folly::coro::Task<int> {
669         co_return folly::Try<int>(ExpectedException());
670       }()),
671       ExpectedException);
672 
673   EXPECT_EQ(42, folly::coro::blockingWait([]() -> folly::coro::Task<int> {
674               folly::Try<int> t(42);
675               co_return t;
676             }()));
677 
678   EXPECT_EQ(42, folly::coro::blockingWait([]() -> folly::coro::Task<int> {
679               const folly::Try<int> tConst(42);
680               co_return tConst;
681             }()));
682 }
683 
TEST(Coro,CoThrow)684 TEST(Coro, CoThrow) {
685   struct ExpectedException : public std::runtime_error {
686     ExpectedException() : std::runtime_error("ExpectedException") {}
687   };
688   EXPECT_THROW(
689       folly::coro::blockingWait([]() -> folly::coro::Task<int> {
690         co_yield folly::coro::co_error(ExpectedException());
691         ADD_FAILURE() << "unreachable";
692         // Intential lack of co_return statement to check
693         // that compiler treats code after co_yield co_error()
694         // as unreachable.
695       }()),
696       ExpectedException);
697 
698   EXPECT_THROW(
699       folly::coro::blockingWait([]() -> folly::coro::Task<void> {
700         co_yield folly::coro::co_error(ExpectedException());
701         ADD_FAILURE() << "unreachable";
702       }()),
703       ExpectedException);
704 }
705 
TEST_F(CoroTest,DetachOnCancel)706 TEST_F(CoroTest, DetachOnCancel) {
707   folly::coro::blockingWait([&]() -> folly::coro::Task<> {
708     folly::CancellationSource cancelSource;
709     cancelSource.requestCancellation();
710 
711     // Run some logic while in an already-cancelled state.
712     co_await folly::coro::co_withCancellation(
713         cancelSource.getToken(), []() -> folly::coro::Task<> {
714           EXPECT_THROW(
715               co_await folly::coro::detachOnCancel(
716                   folly::futures::sleep(std::chrono::seconds{1})
717                       .deferValue([](auto) { return 42; })),
718               folly::OperationCancelled);
719         }());
720   }());
721 
722   folly::coro::blockingWait([&]() -> folly::coro::Task<> {
723     folly::CancellationSource cancelSource;
724 
725     co_await folly::coro::co_withCancellation(
726         cancelSource.getToken(), []() -> folly::coro::Task<> {
727           EXPECT_EQ(
728               42,
729               co_await folly::coro::detachOnCancel(
730                   folly::futures::sleep(std::chrono::milliseconds{10})
731                       .deferValue([](auto) { return 42; })));
732         }());
733   }());
734 
735   folly::coro::blockingWait([&]() -> folly::coro::Task<> {
736     folly::CancellationSource cancelSource;
737 
738     co_await folly::coro::co_withCancellation(
739         cancelSource.getToken(), []() -> folly::coro::Task<> {
740           co_await folly::coro::detachOnCancel([]() -> folly::coro::Task<void> {
741             co_await folly::futures::sleep(std::chrono::milliseconds{10});
742           }());
743         }());
744   }());
745 }
746 #endif
747