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/experimental/coro/BlockingWait.h>
20 #include <folly/experimental/coro/detail/InlineTask.h>
21 #include <folly/portability/GTest.h>
22 
23 #include <tuple>
24 
25 #if FOLLY_HAS_COROUTINES
26 
27 template <typename T>
28 using InlineTask = folly::coro::detail::InlineTask<T>;
29 
30 class InlineTaskTest : public testing::Test {};
31 
TEST_F(InlineTaskTest,CallVoidTaskWithoutAwaitingNeverRuns)32 TEST_F(InlineTaskTest, CallVoidTaskWithoutAwaitingNeverRuns) {
33   bool hasStarted = false;
34   auto f = [&]() -> InlineTask<void> {
35     hasStarted = true;
36     co_return;
37   };
38   {
39     auto task = f();
40     EXPECT_FALSE(hasStarted);
41   }
42   EXPECT_FALSE(hasStarted);
43 }
44 
TEST_F(InlineTaskTest,CallValueTaskWithoutAwaitingNeverRuns)45 TEST_F(InlineTaskTest, CallValueTaskWithoutAwaitingNeverRuns) {
46   bool hasStarted = false;
47   auto f = [&]() -> InlineTask<int> {
48     hasStarted = true;
49     co_return 123;
50   };
51   {
52     auto task = f();
53     EXPECT_FALSE(hasStarted);
54   }
55   EXPECT_FALSE(hasStarted);
56 }
57 
TEST_F(InlineTaskTest,CallRefTaskWithoutAwaitingNeverRuns)58 TEST_F(InlineTaskTest, CallRefTaskWithoutAwaitingNeverRuns) {
59   bool hasStarted = false;
60   int value;
61   auto f = [&]() -> InlineTask<int&> {
62     hasStarted = true;
63     co_return value;
64   };
65   {
66     auto task = f();
67     EXPECT_FALSE(hasStarted);
68   }
69   EXPECT_FALSE(hasStarted);
70 }
71 
TEST_F(InlineTaskTest,SimpleVoidTask)72 TEST_F(InlineTaskTest, SimpleVoidTask) {
73   bool hasRun = false;
74   auto f = [&]() -> InlineTask<void> {
75     hasRun = true;
76     co_return;
77   };
78   auto t = f();
79   EXPECT_FALSE(hasRun);
80   folly::coro::blockingWait(std::move(t));
81   EXPECT_TRUE(hasRun);
82 }
83 
TEST_F(InlineTaskTest,SimpleValueTask)84 TEST_F(InlineTaskTest, SimpleValueTask) {
85   bool hasRun = false;
86   auto f = [&]() -> InlineTask<int> {
87     hasRun = true;
88     co_return 42;
89   };
90   auto t = f();
91   EXPECT_FALSE(hasRun);
92   EXPECT_EQ(42, folly::coro::blockingWait(std::move(t)));
93   EXPECT_TRUE(hasRun);
94 }
95 
TEST_F(InlineTaskTest,SimpleRefTask)96 TEST_F(InlineTaskTest, SimpleRefTask) {
97   bool hasRun = false;
98   auto f = [&]() -> InlineTask<bool&> {
99     hasRun = true;
100     co_return hasRun;
101   };
102 
103   auto t = f();
104   EXPECT_FALSE(hasRun);
105   auto& result = folly::coro::blockingWait(std::move(t));
106   EXPECT_TRUE(hasRun);
107   EXPECT_EQ(&hasRun, &result);
108 }
109 
TEST_F(InlineTaskTest,ReturnValueWithInitializerListSyntax)110 TEST_F(InlineTaskTest, ReturnValueWithInitializerListSyntax) {
111   struct TypeWithImplicitSingleValueConstructor {
112     float value_;
113     /* implicit */ TypeWithImplicitSingleValueConstructor(float x)
114         : value_(x) {}
115   };
116 
117   auto f = []() -> InlineTask<TypeWithImplicitSingleValueConstructor> {
118     co_return {1.23f};
119   };
120 
121   auto result = folly::coro::blockingWait(f());
122   EXPECT_EQ(1.23f, result.value_);
123 }
124 
TEST_F(InlineTaskTest,ReturnValueWithInitializerListSyntax2)125 TEST_F(InlineTaskTest, ReturnValueWithInitializerListSyntax2) {
126   struct TypeWithImplicitMultiValueConstructor {
127     std::string s_;
128     float x_;
129     /* implicit */ TypeWithImplicitMultiValueConstructor(
130         std::string s, float x) noexcept
131         : s_(s), x_(x) {}
132   };
133 
134   auto f = []() -> InlineTask<TypeWithImplicitMultiValueConstructor> {
135     co_return {"hello", 3.1415f};
136   };
137 
138   auto result = folly::coro::blockingWait(f());
139   EXPECT_EQ("hello", result.s_);
140   EXPECT_EQ(3.1415f, result.x_);
141 }
142 
143 namespace {
144 
145 struct MoveOnlyType {
146   int value_;
147 
MoveOnlyType__anon6bc3ac010911::MoveOnlyType148   explicit MoveOnlyType(int value) noexcept : value_(value) {}
149 
MoveOnlyType__anon6bc3ac010911::MoveOnlyType150   MoveOnlyType(MoveOnlyType&& other) noexcept
151       : value_(std::exchange(other.value_, -1)) {}
152 
operator =__anon6bc3ac010911::MoveOnlyType153   FOLLY_MAYBE_UNUSED MoveOnlyType& operator=(MoveOnlyType&& other) noexcept {
154     value_ = std::exchange(other.value_, -1);
155     return *this;
156   }
157 
~MoveOnlyType__anon6bc3ac010911::MoveOnlyType158   ~MoveOnlyType() { value_ = -2; }
159 };
160 
161 } // namespace
162 
TEST_F(InlineTaskTest,TaskOfMoveOnlyType)163 TEST_F(InlineTaskTest, TaskOfMoveOnlyType) {
164   auto f = []() -> InlineTask<MoveOnlyType> { co_return MoveOnlyType{42}; };
165 
166   auto x = folly::coro::blockingWait(f());
167   EXPECT_EQ(42, x.value_);
168 
169   bool executed = false;
170   auto g = [&]() -> InlineTask<void> {
171     auto result = co_await f();
172     EXPECT_EQ(42, result.value_);
173     executed = true;
174   };
175 
176   folly::coro::blockingWait(g());
177 
178   EXPECT_TRUE(executed);
179 }
180 
TEST_F(InlineTaskTest,MoveOnlyTypeNRVO)181 TEST_F(InlineTaskTest, MoveOnlyTypeNRVO) {
182   auto f = []() -> InlineTask<MoveOnlyType> {
183     MoveOnlyType x{10};
184     co_return x;
185   };
186 
187   auto x = folly::coro::blockingWait(f());
188   EXPECT_EQ(10, x.value_);
189 }
190 
TEST_F(InlineTaskTest,ReturnLvalueReference)191 TEST_F(InlineTaskTest, ReturnLvalueReference) {
192   int value = 0;
193   auto f = [&]() -> InlineTask<int&> { co_return value; };
194 
195   auto& x = folly::coro::blockingWait(f());
196   EXPECT_EQ(&value, &x);
197 }
198 
TEST_F(InlineTaskTest,ExceptionsPropagateFromVoidTask)199 TEST_F(InlineTaskTest, ExceptionsPropagateFromVoidTask) {
200   struct MyException : std::exception {};
201 
202   auto f = []() -> InlineTask<void> {
203     co_await folly::coro::suspend_never{};
204     throw MyException{};
205   };
206   EXPECT_THROW(folly::coro::blockingWait(f()), MyException);
207 }
208 
TEST_F(InlineTaskTest,ExceptionsPropagateFromValueTask)209 TEST_F(InlineTaskTest, ExceptionsPropagateFromValueTask) {
210   struct MyException : std::exception {};
211 
212   auto f = []() -> InlineTask<int> {
213     co_await folly::coro::suspend_never{};
214     throw MyException{};
215   };
216   EXPECT_THROW(folly::coro::blockingWait(f()), MyException);
217 }
218 
TEST_F(InlineTaskTest,ExceptionsPropagateFromRefTask)219 TEST_F(InlineTaskTest, ExceptionsPropagateFromRefTask) {
220   struct MyException : std::exception {};
221 
222   auto f = []() -> InlineTask<int&> {
223     co_await folly::coro::suspend_never{};
224     throw MyException{};
225   };
226   EXPECT_THROW(folly::coro::blockingWait(f()), MyException);
227 }
228 
TEST_F(InlineTaskTest,ExceptionsPropagateFromReturnValueConstructor)229 TEST_F(InlineTaskTest, ExceptionsPropagateFromReturnValueConstructor) {
230   struct MyException : std::exception {};
231 
232   struct ThrowingCopyConstructor {
233     FOLLY_MAYBE_UNUSED ThrowingCopyConstructor() noexcept = default;
234 
235     [[noreturn]] ThrowingCopyConstructor(
236         const ThrowingCopyConstructor&) noexcept(false) {
237       throw MyException{};
238     }
239 
240     ThrowingCopyConstructor& operator=(const ThrowingCopyConstructor&) = delete;
241   };
242 
243   auto f = []() -> InlineTask<ThrowingCopyConstructor> { co_return {}; };
244   EXPECT_THROW(folly::coro::blockingWait(f()), MyException);
245 }
246 
TEST_F(InlineTaskTest,DeepRecursionDoesntStackOverflow)247 TEST_F(InlineTaskTest, DeepRecursionDoesntStackOverflow) {
248   struct RecursiveTask {
249     InlineTask<void> operator()(int depth) {
250       if (depth > 0) {
251         co_await operator()(depth - 1);
252       }
253     }
254   };
255 
256   folly::coro::blockingWait(RecursiveTask{}(500000));
257 }
258 
TEST_F(InlineTaskTest,DeepRecursionOfValueTaskDoesntStackOverflow)259 TEST_F(InlineTaskTest, DeepRecursionOfValueTaskDoesntStackOverflow) {
260   struct RecursiveValueTask {
261     InlineTask<int> operator()(int depth) {
262       if (depth > 0) {
263         co_return co_await operator()(depth - 1) + 1;
264       }
265       co_return 0;
266     }
267   };
268 
269   EXPECT_EQ(500000, folly::coro::blockingWait(RecursiveValueTask{}(500000)));
270 }
271 
TEST_F(InlineTaskTest,DeepRecursionOfExceptions)272 TEST_F(InlineTaskTest, DeepRecursionOfExceptions) {
273   struct MyException : std::exception {};
274 
275   struct RecursiveThrowingTask {
276     static InlineTask<void> go(int depth) {
277       if (depth > 0) {
278         co_await go(depth - 1);
279       }
280 
281       throw MyException{};
282     }
283   };
284 
285   EXPECT_THROW(
286       folly::coro::blockingWait(RecursiveThrowingTask::go(50000)), MyException);
287 }
288 
289 #endif
290