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