Commit 9c47cb9c authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook GitHub Bot

Prefer to nest helper functions in coro tests

Summary: [Folly] Prefer to nest helper functions in coro tests within the specific tests which use them.

Reviewed By: ispeters, Orvid

Differential Revision: D20324066

fbshipit-source-id: 4134145c406f2d1fdeaddf9a7d244c1bd25ec60b
parent 4f5128bb
...@@ -39,26 +39,11 @@ ...@@ -39,26 +39,11 @@
using namespace folly; using namespace folly;
struct S {
int x = 42;
};
coro::Task<S> taskS() {
co_return {};
}
coro::Task<int> task42() {
co_return 42;
}
SemiFuture<int> semifuture_task42() {
return task42().semi();
}
class CoroTest : public testing::Test {}; class CoroTest : public testing::Test {};
TEST_F(CoroTest, Basic) { TEST_F(CoroTest, Basic) {
ManualExecutor executor; ManualExecutor executor;
auto task42 = []() -> coro::Task<int> { co_return 42; };
auto future = task42().scheduleOn(&executor).start(); auto future = task42().scheduleOn(&executor).start();
EXPECT_FALSE(future.isReady()); EXPECT_FALSE(future.isReady());
...@@ -71,7 +56,8 @@ TEST_F(CoroTest, Basic) { ...@@ -71,7 +56,8 @@ TEST_F(CoroTest, Basic) {
TEST_F(CoroTest, BasicSemiFuture) { TEST_F(CoroTest, BasicSemiFuture) {
ManualExecutor executor; ManualExecutor executor;
auto future = semifuture_task42().via(&executor); auto task42 = []() -> coro::Task<int> { co_return 42; };
auto future = task42().semi().via(&executor);
EXPECT_FALSE(future.isReady()); EXPECT_FALSE(future.isReady());
...@@ -84,6 +70,7 @@ TEST_F(CoroTest, BasicSemiFuture) { ...@@ -84,6 +70,7 @@ TEST_F(CoroTest, BasicSemiFuture) {
TEST_F(CoroTest, BasicFuture) { TEST_F(CoroTest, BasicFuture) {
ManualExecutor executor; ManualExecutor executor;
auto task42 = []() -> coro::Task<int> { co_return 42; };
auto future = task42().scheduleOn(&executor).start(); auto future = task42().scheduleOn(&executor).start();
EXPECT_FALSE(future.isReady()); EXPECT_FALSE(future.isReady());
...@@ -92,6 +79,7 @@ TEST_F(CoroTest, BasicFuture) { ...@@ -92,6 +79,7 @@ TEST_F(CoroTest, BasicFuture) {
} }
coro::Task<void> taskVoid() { coro::Task<void> taskVoid() {
auto task42 = []() -> coro::Task<int> { co_return 42; };
(void)co_await task42(); (void)co_await task42();
co_return; co_return;
} }
...@@ -147,44 +135,45 @@ TEST_F(CoroTest, ExecutorKeepAlive) { ...@@ -147,44 +135,45 @@ TEST_F(CoroTest, ExecutorKeepAlive) {
EXPECT_TRUE(future.isReady()); EXPECT_TRUE(future.isReady());
} }
struct CountingExecutor : public ManualExecutor { TEST_F(CoroTest, ExecutorKeepAliveDummy) {
bool keepAliveAcquire() noexcept override { struct CountingExecutor : public ManualExecutor {
++keepAliveCounter; bool keepAliveAcquire() noexcept override {
return true; ++keepAliveCounter;
} return true;
}
void keepAliveRelease() noexcept override { --keepAliveCounter; } void keepAliveRelease() noexcept override { --keepAliveCounter; }
size_t keepAliveCounter{0}; size_t keepAliveCounter{0};
}; };
coro::Task<void> executorRec(int depth) { struct ExecutorRec {
if (depth == 0) { static coro::Task<void> go(int depth) {
co_return; if (depth == 0) {
} co_return;
}
auto executor = auto executor =
dynamic_cast<CountingExecutor*>(co_await coro::co_current_executor); dynamic_cast<CountingExecutor*>(co_await coro::co_current_executor);
DCHECK(executor); DCHECK(executor);
// Note, extra keep-alives are being kept by the Futures. // Note, extra keep-alives are being kept by the Futures.
EXPECT_EQ(3, executor->keepAliveCounter); EXPECT_EQ(3, executor->keepAliveCounter);
co_await executorRec(depth - 1); co_await go(depth - 1);
} }
};
TEST_F(CoroTest, ExecutorKeepAliveDummy) {
CountingExecutor executor; CountingExecutor executor;
executorRec(42).scheduleOn(&executor).start().via(&executor).getVia( ExecutorRec::go(42).scheduleOn(&executor).start().via(&executor).getVia(
&executor); &executor);
} }
coro::Task<int> taskException() {
throw std::runtime_error("Test exception");
co_return 42;
}
TEST_F(CoroTest, FutureThrow) { TEST_F(CoroTest, FutureThrow) {
auto taskException = []() -> coro::Task<int> {
throw std::runtime_error("Test exception");
co_return 42;
};
ManualExecutor executor; ManualExecutor executor;
auto future = taskException().scheduleOn(&executor).start(); auto future = taskException().scheduleOn(&executor).start();
...@@ -213,133 +202,133 @@ TEST_F(CoroTest, LargeStack) { ...@@ -213,133 +202,133 @@ TEST_F(CoroTest, LargeStack) {
EXPECT_EQ(50000, coro::blockingWait(std::move(task))); EXPECT_EQ(50000, coro::blockingWait(std::move(task)));
} }
coro::Task<void> taskThreadNested(std::thread::id threadId) { TEST_F(CoroTest, NestedThreads) {
EXPECT_EQ(threadId, std::this_thread::get_id()); auto taskThreadNested = [](std::thread::id threadId) -> coro::Task<void> {
(void)co_await coro::sleep(std::chrono::seconds{1}); EXPECT_EQ(threadId, std::this_thread::get_id());
EXPECT_EQ(threadId, std::this_thread::get_id()); (void)co_await coro::sleep(std::chrono::seconds{1});
co_return; EXPECT_EQ(threadId, std::this_thread::get_id());
} co_return;
};
coro::Task<int> taskThread() { auto taskThread = [&]() -> coro::Task<int> {
auto threadId = std::this_thread::get_id(); auto threadId = std::this_thread::get_id();
// BUG: Under @mode/clang-opt builds this object is placed on the coroutine // BUG: Under @mode/clang-opt builds this object is placed on the coroutine
// frame and the code for the constructor assumes that it is allocated on // frame and the code for the constructor assumes that it is allocated on
// a 16-byte boundary. However, when placed in the coroutine frame it can // a 16-byte boundary. However, when placed in the coroutine frame it can
// end up at a location that is not 16-byte aligned. This causes a SIGSEGV // end up at a location that is not 16-byte aligned. This causes a SIGSEGV
// when performing a store to members that uses SSE instructions. // when performing a store to members that uses SSE instructions.
folly::ScopedEventBaseThread evbThread; folly::ScopedEventBaseThread evbThread;
co_await taskThreadNested(evbThread.getThreadId()) co_await taskThreadNested(evbThread.getThreadId())
.scheduleOn(evbThread.getEventBase()); .scheduleOn(evbThread.getEventBase());
EXPECT_EQ(threadId, std::this_thread::get_id()); EXPECT_EQ(threadId, std::this_thread::get_id());
co_return 42; co_return 42;
} };
TEST_F(CoroTest, NestedThreads) {
ScopedEventBaseThread evbThread; ScopedEventBaseThread evbThread;
auto task = taskThread().scheduleOn(evbThread.getEventBase()); auto task = taskThread().scheduleOn(evbThread.getEventBase());
EXPECT_EQ(42, coro::blockingWait(std::move(task))); EXPECT_EQ(42, coro::blockingWait(std::move(task)));
} }
coro::Task<int> taskGetCurrentExecutor(Executor* executor) {
auto current = co_await coro::co_current_executor;
EXPECT_EQ(executor, current);
co_return co_await task42().scheduleOn(current);
}
TEST_F(CoroTest, CurrentExecutor) { TEST_F(CoroTest, CurrentExecutor) {
auto taskGetCurrentExecutor = [](Executor* executor) -> coro::Task<int> {
auto current = co_await coro::co_current_executor;
EXPECT_EQ(executor, current);
auto task42 = []() -> coro::Task<int> { co_return 42; };
co_return co_await task42().scheduleOn(current);
};
ScopedEventBaseThread evbThread; ScopedEventBaseThread evbThread;
auto task = taskGetCurrentExecutor(evbThread.getEventBase()) auto task = taskGetCurrentExecutor(evbThread.getEventBase())
.scheduleOn(evbThread.getEventBase()); .scheduleOn(evbThread.getEventBase());
EXPECT_EQ(42, coro::blockingWait(std::move(task))); EXPECT_EQ(42, coro::blockingWait(std::move(task)));
} }
coro::Task<void> taskTimedWaitFuture() { TEST_F(CoroTest, TimedWaitFuture) {
auto ex = co_await coro::co_current_executor; auto taskTimedWaitFuture = []() -> coro::Task<void> {
auto fastFuture = auto ex = co_await coro::co_current_executor;
futures::sleep(std::chrono::milliseconds{50}).via(ex).thenValue([](Unit) { auto fastFuture = futures::sleep(std::chrono::milliseconds{50})
return 42; .via(ex)
}); .thenValue([](Unit) { return 42; });
auto fastResult = co_await coro::timed_wait( auto fastResult = co_await coro::timed_wait(
std::move(fastFuture), std::chrono::milliseconds{100}); std::move(fastFuture), std::chrono::milliseconds{100});
EXPECT_TRUE(fastResult); EXPECT_TRUE(fastResult);
EXPECT_EQ(42, *fastResult); EXPECT_EQ(42, *fastResult);
struct ExpectedException : public std::runtime_error {
ExpectedException() : std::runtime_error("ExpectedException") {}
};
auto throwingFuture =
futures::sleep(std::chrono::milliseconds{50})
.via(ex)
.thenValue([](Unit) { throw ExpectedException(); });
EXPECT_THROW(
(void)co_await coro::timed_wait(
std::move(throwingFuture), std::chrono::milliseconds{100}),
ExpectedException);
auto promiseFuturePair = folly::makePromiseContract<folly::Unit>(ex);
auto lifetimeFuture = std::move(promiseFuturePair.second);
auto slowFuture =
futures::sleep(std::chrono::milliseconds{200})
.via(ex)
.thenValue([lifetimePromise =
std::move(promiseFuturePair.first)](Unit) mutable {
lifetimePromise.setValue();
return 42;
});
auto slowResult = co_await coro::timed_wait(
std::move(slowFuture), std::chrono::milliseconds{100});
EXPECT_FALSE(slowResult);
// Ensure that task completes for safe executor lifetimes
(void)co_await std::move(lifetimeFuture);
struct ExpectedException : public std::runtime_error { co_return;
ExpectedException() : std::runtime_error("ExpectedException") {}
}; };
auto throwingFuture =
futures::sleep(std::chrono::milliseconds{50}).via(ex).thenValue([](Unit) {
throw ExpectedException();
});
EXPECT_THROW(
(void)co_await coro::timed_wait(
std::move(throwingFuture), std::chrono::milliseconds{100}),
ExpectedException);
auto promiseFuturePair = folly::makePromiseContract<folly::Unit>(ex);
auto lifetimeFuture = std::move(promiseFuturePair.second);
auto slowFuture =
futures::sleep(std::chrono::milliseconds{200})
.via(ex)
.thenValue([lifetimePromise =
std::move(promiseFuturePair.first)](Unit) mutable {
lifetimePromise.setValue();
return 42;
});
auto slowResult = co_await coro::timed_wait(
std::move(slowFuture), std::chrono::milliseconds{100});
EXPECT_FALSE(slowResult);
// Ensure that task completes for safe executor lifetimes
(void)co_await std::move(lifetimeFuture);
co_return;
}
TEST_F(CoroTest, TimedWaitFuture) {
coro::blockingWait(taskTimedWaitFuture()); coro::blockingWait(taskTimedWaitFuture());
} }
coro::Task<void> taskTimedWaitTask() { TEST_F(CoroTest, TimedWaitTask) {
auto fastTask = []() -> coro::Task<int> { auto taskTimedWaitTask = []() -> coro::Task<void> {
co_await coro::sleep(std::chrono::milliseconds{50}); auto fastTask = []() -> coro::Task<int> {
co_return 42; co_await coro::sleep(std::chrono::milliseconds{50});
}(); co_return 42;
auto fastResult = co_await coro::timed_wait( }();
std::move(fastTask), std::chrono::milliseconds{100}); auto fastResult = co_await coro::timed_wait(
EXPECT_TRUE(fastResult); std::move(fastTask), std::chrono::milliseconds{100});
EXPECT_EQ(42, *fastResult); EXPECT_TRUE(fastResult);
EXPECT_EQ(42, *fastResult);
struct ExpectedException : public std::runtime_error {
ExpectedException() : std::runtime_error("ExpectedException") {}
};
auto throwingTask = []() -> coro::Task<void> {
co_await coro::sleep(std::chrono::milliseconds{50});
throw ExpectedException();
}();
EXPECT_THROW(
(void)co_await coro::timed_wait(
std::move(throwingTask), std::chrono::milliseconds{100}),
ExpectedException);
auto slowTask = []() -> coro::Task<int> {
co_await futures::sleep(std::chrono::milliseconds{200});
co_return 42;
}();
auto slowResult = co_await coro::timed_wait(
std::move(slowTask), std::chrono::milliseconds{100});
EXPECT_FALSE(slowResult);
struct ExpectedException : public std::runtime_error { co_return;
ExpectedException() : std::runtime_error("ExpectedException") {}
}; };
auto throwingTask = []() -> coro::Task<void> {
co_await coro::sleep(std::chrono::milliseconds{50});
throw ExpectedException();
}();
EXPECT_THROW(
(void)co_await coro::timed_wait(
std::move(throwingTask), std::chrono::milliseconds{100}),
ExpectedException);
auto slowTask = []() -> coro::Task<int> {
co_await futures::sleep(std::chrono::milliseconds{200});
co_return 42;
}();
auto slowResult = co_await coro::timed_wait(
std::move(slowTask), std::chrono::milliseconds{100});
EXPECT_FALSE(slowResult);
co_return;
}
TEST_F(CoroTest, TimedWaitTask) {
coro::blockingWait(taskTimedWaitTask()); coro::blockingWait(taskTimedWaitTask());
} }
...@@ -367,6 +356,8 @@ TEST_F(CoroTest, TimedWaitNonCopyable) { ...@@ -367,6 +356,8 @@ TEST_F(CoroTest, TimedWaitNonCopyable) {
}())); }()));
} }
namespace {
template <int value> template <int value>
struct AwaitableInt { struct AwaitableInt {
bool await_ready() const { return true; } bool await_ready() const { return true; }
...@@ -384,36 +375,39 @@ AwaitableInt<42> operator co_await(const AwaitableWithOperator&) { ...@@ -384,36 +375,39 @@ AwaitableInt<42> operator co_await(const AwaitableWithOperator&) {
return {}; return {};
} }
coro::Task<int> taskAwaitableWithOperator() {
co_return co_await AwaitableWithOperator();
}
TEST_F(CoroTest, AwaitableWithOperator) {
EXPECT_EQ(42, coro::blockingWait(taskAwaitableWithOperator()));
}
struct AwaitableWithMemberOperator { struct AwaitableWithMemberOperator {
AwaitableInt<42> operator co_await() { return {}; } AwaitableInt<42> operator co_await() { return {}; }
}; };
AwaitableInt<24> operator co_await(const AwaitableWithMemberOperator&) { FOLLY_MAYBE_UNUSED AwaitableInt<24> operator co_await(
const AwaitableWithMemberOperator&) {
return {}; return {};
} }
coro::Task<int> taskAwaitableWithMemberOperator() { } // namespace
co_return co_await AwaitableWithMemberOperator();
TEST_F(CoroTest, AwaitableWithOperator) {
auto taskAwaitableWithOperator = []() -> coro::Task<int> {
co_return co_await AwaitableWithOperator();
};
EXPECT_EQ(42, coro::blockingWait(taskAwaitableWithOperator()));
} }
TEST_F(CoroTest, AwaitableWithMemberOperator) { TEST_F(CoroTest, AwaitableWithMemberOperator) {
EXPECT_EQ(42, coro::blockingWait(taskAwaitableWithMemberOperator())); auto taskAwaitableWithMemberOperator = []() -> coro::Task<int> {
} co_return co_await AwaitableWithMemberOperator();
};
coro::Task<int> taskBaton(fibers::Baton& baton) { EXPECT_EQ(42, coro::blockingWait(taskAwaitableWithMemberOperator()));
co_await baton;
co_return 42;
} }
TEST_F(CoroTest, Baton) { TEST_F(CoroTest, Baton) {
auto taskBaton = [](fibers::Baton& baton) -> coro::Task<int> {
co_await baton;
co_return 42;
};
ManualExecutor executor; ManualExecutor executor;
fibers::Baton baton; fibers::Baton baton;
auto future = taskBaton(baton).scheduleOn(&executor).start(); auto future = taskBaton(baton).scheduleOn(&executor).start();
...@@ -431,16 +425,19 @@ TEST_F(CoroTest, Baton) { ...@@ -431,16 +425,19 @@ TEST_F(CoroTest, Baton) {
EXPECT_EQ(42, std::move(future).get()); EXPECT_EQ(42, std::move(future).get());
} }
template <class Type>
coro::Task<Type> taskFuture(Type value) {
co_return co_await folly::makeFuture<Type>(std::move(value));
}
TEST_F(CoroTest, FulfilledFuture) { TEST_F(CoroTest, FulfilledFuture) {
auto taskFuture = [](auto value) -> coro::Task<decltype(value)> {
co_return co_await folly::makeFuture(std::move(value));
};
EXPECT_EQ(42, coro::blockingWait(taskFuture(42))); EXPECT_EQ(42, coro::blockingWait(taskFuture(42)));
} }
TEST_F(CoroTest, MoveOnlyReturn) { TEST_F(CoroTest, MoveOnlyReturn) {
auto taskFuture = [](auto value) -> coro::Task<decltype(value)> {
co_return co_await folly::makeFuture(std::move(value));
};
EXPECT_EQ(42, *coro::blockingWait(taskFuture(std::make_unique<int>(42)))); EXPECT_EQ(42, *coro::blockingWait(taskFuture(std::make_unique<int>(42))));
} }
...@@ -588,6 +585,12 @@ TEST_F(CoroTest, CancelOneSemaphoreWaitDoesNotAffectOthers) { ...@@ -588,6 +585,12 @@ TEST_F(CoroTest, CancelOneSemaphoreWaitDoesNotAffectOthers) {
TEST_F(CoroTest, FutureTry) { TEST_F(CoroTest, FutureTry) {
folly::coro::blockingWait([]() -> folly::coro::Task<void> { folly::coro::blockingWait([]() -> folly::coro::Task<void> {
auto task42 = []() -> coro::Task<int> { co_return 42; };
auto taskException = []() -> coro::Task<int> {
throw std::runtime_error("Test exception");
co_return 42;
};
{ {
auto result = co_await folly::coro::co_awaitTry(task42().semi()); auto result = co_await folly::coro::co_awaitTry(task42().semi());
EXPECT_TRUE(result.hasValue()); EXPECT_TRUE(result.hasValue());
...@@ -643,6 +646,12 @@ TEST_F(CoroTest, CancellableSleep) { ...@@ -643,6 +646,12 @@ TEST_F(CoroTest, CancellableSleep) {
TEST_F(CoroTest, DefaultConstructible) { TEST_F(CoroTest, DefaultConstructible) {
coro::blockingWait([]() -> coro::Task<void> { coro::blockingWait([]() -> coro::Task<void> {
struct S {
int x = 42;
};
auto taskS = []() -> coro::Task<S> { co_return {}; };
auto s = co_await taskS(); auto s = co_await taskS();
EXPECT_EQ(42, s.x); EXPECT_EQ(42, s.x);
}()); }());
......
...@@ -107,28 +107,13 @@ TEST_F(InlineTaskTest, SimpleRefTask) { ...@@ -107,28 +107,13 @@ TEST_F(InlineTaskTest, SimpleRefTask) {
EXPECT_EQ(&hasRun, &result); EXPECT_EQ(&hasRun, &result);
} }
struct MoveOnlyType {
int value_;
explicit MoveOnlyType(int value) noexcept : value_(value) {}
MoveOnlyType(MoveOnlyType&& other) noexcept
: value_(std::exchange(other.value_, -1)) {}
MoveOnlyType& operator=(MoveOnlyType&& other) noexcept {
value_ = std::exchange(other.value_, -1);
return *this;
}
~MoveOnlyType() { value_ = -2; }
};
struct TypeWithImplicitSingleValueConstructor {
float value_;
/* implicit */ TypeWithImplicitSingleValueConstructor(float x) : value_(x) {}
};
TEST_F(InlineTaskTest, ReturnValueWithInitializerListSyntax) { TEST_F(InlineTaskTest, ReturnValueWithInitializerListSyntax) {
struct TypeWithImplicitSingleValueConstructor {
float value_;
/* implicit */ TypeWithImplicitSingleValueConstructor(float x)
: value_(x) {}
};
auto f = []() -> InlineTask<TypeWithImplicitSingleValueConstructor> { auto f = []() -> InlineTask<TypeWithImplicitSingleValueConstructor> {
co_return {1.23f}; co_return {1.23f};
}; };
...@@ -137,15 +122,15 @@ TEST_F(InlineTaskTest, ReturnValueWithInitializerListSyntax) { ...@@ -137,15 +122,15 @@ TEST_F(InlineTaskTest, ReturnValueWithInitializerListSyntax) {
EXPECT_EQ(1.23f, result.value_); EXPECT_EQ(1.23f, result.value_);
} }
struct TypeWithImplicitMultiValueConstructor {
std::string s_;
float x_;
/* implicit */ TypeWithImplicitMultiValueConstructor(
std::string s, float x) noexcept
: s_(s), x_(x) {}
};
TEST_F(InlineTaskTest, ReturnValueWithInitializerListSyntax2) { TEST_F(InlineTaskTest, ReturnValueWithInitializerListSyntax2) {
struct TypeWithImplicitMultiValueConstructor {
std::string s_;
float x_;
/* implicit */ TypeWithImplicitMultiValueConstructor(
std::string s, float x) noexcept
: s_(s), x_(x) {}
};
auto f = []() -> InlineTask<TypeWithImplicitMultiValueConstructor> { auto f = []() -> InlineTask<TypeWithImplicitMultiValueConstructor> {
co_return {"hello", 3.1415f}; co_return {"hello", 3.1415f};
}; };
...@@ -155,6 +140,26 @@ TEST_F(InlineTaskTest, ReturnValueWithInitializerListSyntax2) { ...@@ -155,6 +140,26 @@ TEST_F(InlineTaskTest, ReturnValueWithInitializerListSyntax2) {
EXPECT_EQ(3.1415f, result.x_); EXPECT_EQ(3.1415f, result.x_);
} }
namespace {
struct MoveOnlyType {
int value_;
explicit MoveOnlyType(int value) noexcept : value_(value) {}
MoveOnlyType(MoveOnlyType&& other) noexcept
: value_(std::exchange(other.value_, -1)) {}
FOLLY_MAYBE_UNUSED MoveOnlyType& operator=(MoveOnlyType&& other) noexcept {
value_ = std::exchange(other.value_, -1);
return *this;
}
~MoveOnlyType() { value_ = -2; }
};
} // namespace
TEST_F(InlineTaskTest, TaskOfMoveOnlyType) { TEST_F(InlineTaskTest, TaskOfMoveOnlyType) {
auto f = []() -> InlineTask<MoveOnlyType> { co_return MoveOnlyType{42}; }; auto f = []() -> InlineTask<MoveOnlyType> { co_return MoveOnlyType{42}; };
...@@ -191,9 +196,9 @@ TEST_F(InlineTaskTest, ReturnLvalueReference) { ...@@ -191,9 +196,9 @@ TEST_F(InlineTaskTest, ReturnLvalueReference) {
EXPECT_EQ(&value, &x); EXPECT_EQ(&value, &x);
} }
struct MyException : std::exception {};
TEST_F(InlineTaskTest, ExceptionsPropagateFromVoidTask) { TEST_F(InlineTaskTest, ExceptionsPropagateFromVoidTask) {
struct MyException : std::exception {};
auto f = []() -> InlineTask<void> { auto f = []() -> InlineTask<void> {
co_await std::experimental::suspend_never{}; co_await std::experimental::suspend_never{};
throw MyException{}; throw MyException{};
...@@ -202,6 +207,8 @@ TEST_F(InlineTaskTest, ExceptionsPropagateFromVoidTask) { ...@@ -202,6 +207,8 @@ TEST_F(InlineTaskTest, ExceptionsPropagateFromVoidTask) {
} }
TEST_F(InlineTaskTest, ExceptionsPropagateFromValueTask) { TEST_F(InlineTaskTest, ExceptionsPropagateFromValueTask) {
struct MyException : std::exception {};
auto f = []() -> InlineTask<int> { auto f = []() -> InlineTask<int> {
co_await std::experimental::suspend_never{}; co_await std::experimental::suspend_never{};
throw MyException{}; throw MyException{};
...@@ -210,6 +217,8 @@ TEST_F(InlineTaskTest, ExceptionsPropagateFromValueTask) { ...@@ -210,6 +217,8 @@ TEST_F(InlineTaskTest, ExceptionsPropagateFromValueTask) {
} }
TEST_F(InlineTaskTest, ExceptionsPropagateFromRefTask) { TEST_F(InlineTaskTest, ExceptionsPropagateFromRefTask) {
struct MyException : std::exception {};
auto f = []() -> InlineTask<int&> { auto f = []() -> InlineTask<int&> {
co_await std::experimental::suspend_never{}; co_await std::experimental::suspend_never{};
throw MyException{}; throw MyException{};
...@@ -217,54 +226,64 @@ TEST_F(InlineTaskTest, ExceptionsPropagateFromRefTask) { ...@@ -217,54 +226,64 @@ TEST_F(InlineTaskTest, ExceptionsPropagateFromRefTask) {
EXPECT_THROW(folly::coro::blockingWait(f()), MyException); EXPECT_THROW(folly::coro::blockingWait(f()), MyException);
} }
struct ThrowingCopyConstructor { TEST_F(InlineTaskTest, ExceptionsPropagateFromReturnValueConstructor) {
ThrowingCopyConstructor() noexcept = default; struct MyException : std::exception {};
[[noreturn]] ThrowingCopyConstructor(const ThrowingCopyConstructor&) noexcept( struct ThrowingCopyConstructor {
false) { FOLLY_MAYBE_UNUSED ThrowingCopyConstructor() noexcept = default;
throw MyException{};
}
ThrowingCopyConstructor& operator=(const ThrowingCopyConstructor&) = delete; [[noreturn]] ThrowingCopyConstructor(
}; const ThrowingCopyConstructor&) noexcept(false) {
throw MyException{};
}
ThrowingCopyConstructor& operator=(const ThrowingCopyConstructor&) = delete;
};
TEST_F(InlineTaskTest, ExceptionsPropagateFromReturnValueConstructor) {
auto f = []() -> InlineTask<ThrowingCopyConstructor> { co_return {}; }; auto f = []() -> InlineTask<ThrowingCopyConstructor> { co_return {}; };
EXPECT_THROW(folly::coro::blockingWait(f()), MyException); EXPECT_THROW(folly::coro::blockingWait(f()), MyException);
} }
InlineTask<void> recursiveTask(int depth) {
if (depth > 0) {
co_await recursiveTask(depth - 1);
}
}
TEST_F(InlineTaskTest, DeepRecursionDoesntStackOverflow) { TEST_F(InlineTaskTest, DeepRecursionDoesntStackOverflow) {
folly::coro::blockingWait(recursiveTask(500000)); struct RecursiveTask {
} InlineTask<void> operator()(int depth) {
if (depth > 0) {
co_await operator()(depth - 1);
}
}
};
InlineTask<int> recursiveValueTask(int depth) { folly::coro::blockingWait(RecursiveTask{}(500000));
if (depth > 0) {
co_return co_await recursiveValueTask(depth - 1) + 1;
}
co_return 0;
} }
TEST_F(InlineTaskTest, DeepRecursionOfValueTaskDoesntStackOverflow) { TEST_F(InlineTaskTest, DeepRecursionOfValueTaskDoesntStackOverflow) {
EXPECT_EQ(500000, folly::coro::blockingWait(recursiveValueTask(500000))); struct RecursiveValueTask {
} InlineTask<int> operator()(int depth) {
if (depth > 0) {
InlineTask<void> recursiveThrowingTask(int depth) { co_return co_await operator()(depth - 1) + 1;
if (depth > 0) { }
co_await recursiveThrowingTask(depth - 1); co_return 0;
} }
};
throw MyException{}; EXPECT_EQ(500000, folly::coro::blockingWait(RecursiveValueTask{}(500000)));
} }
TEST_F(InlineTaskTest, DeepRecursionOfExceptions) { TEST_F(InlineTaskTest, DeepRecursionOfExceptions) {
struct MyException : std::exception {};
struct RecursiveThrowingTask {
static InlineTask<void> go(int depth) {
if (depth > 0) {
co_await go(depth - 1);
}
throw MyException{};
}
};
EXPECT_THROW( EXPECT_THROW(
folly::coro::blockingWait(recursiveThrowingTask(50000)), MyException); folly::coro::blockingWait(RecursiveThrowingTask::go(50000)), MyException);
} }
#endif #endif
...@@ -362,9 +362,10 @@ TEST_F(TaskTest, FutureRoundtrip) { ...@@ -362,9 +362,10 @@ TEST_F(TaskTest, FutureRoundtrip) {
std::runtime_error); std::runtime_error);
} }
// NOTE: This function is unused. namespace {
// We just want to make sure this compiles without errors or warnings. // We just want to make sure this compiles without errors or warnings.
folly::coro::Task<void> FOLLY_MAYBE_UNUSED folly::coro::Task<void>
checkAwaitingFutureOfUnitDoesntWarnAboutDiscardedResult() { checkAwaitingFutureOfUnitDoesntWarnAboutDiscardedResult() {
co_await folly::makeSemiFuture(); co_await folly::makeSemiFuture();
...@@ -372,11 +373,13 @@ checkAwaitingFutureOfUnitDoesntWarnAboutDiscardedResult() { ...@@ -372,11 +373,13 @@ checkAwaitingFutureOfUnitDoesntWarnAboutDiscardedResult() {
co_await folly::futures::sleep(1ms); co_await folly::futures::sleep(1ms);
} }
folly::coro::Task<int&> returnIntRef(int& value) { } // namespace
co_return value;
}
TEST_F(TaskTest, TaskOfLvalueReference) { TEST_F(TaskTest, TaskOfLvalueReference) {
auto returnIntRef = [](int& value) -> folly::coro::Task<int&> {
co_return value;
};
int value = 123; int value = 123;
auto&& result = folly::coro::blockingWait(returnIntRef(value)); auto&& result = folly::coro::blockingWait(returnIntRef(value));
static_assert(std::is_same_v<decltype(result), int&>); static_assert(std::is_same_v<decltype(result), int&>);
...@@ -385,6 +388,10 @@ TEST_F(TaskTest, TaskOfLvalueReference) { ...@@ -385,6 +388,10 @@ TEST_F(TaskTest, TaskOfLvalueReference) {
TEST_F(TaskTest, TaskOfLvalueReferenceAsTry) { TEST_F(TaskTest, TaskOfLvalueReferenceAsTry) {
folly::coro::blockingWait([]() -> folly::coro::Task<void> { folly::coro::blockingWait([]() -> folly::coro::Task<void> {
auto returnIntRef = [](int& value) -> folly::coro::Task<int&> {
co_return value;
};
int value = 123; int value = 123;
auto&& result = co_await co_awaitTry(returnIntRef(value)); auto&& result = co_await co_awaitTry(returnIntRef(value));
CHECK(result.hasValue()); CHECK(result.hasValue());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment