-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
334 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,333 @@ | ||
// Copyright (c) 2021-present, Trail of Bits, Inc. | ||
|
||
#ifdef GAP_ENABLE_COROUTINES | ||
|
||
#include <doctest/doctest.h> | ||
#include <gap/coro/fmap.hpp> | ||
#include <gap/coro/single_consumer_event.hpp> | ||
#include <gap/coro/sync_wait.hpp> | ||
#include <gap/coro/task.hpp> | ||
#include <gap/coro/when_all_ready.hpp> | ||
|
||
using namespace gap::coro; | ||
|
||
namespace gap::test | ||
{ | ||
struct counted { | ||
static int default_construction_count; | ||
static int copy_construction_count; | ||
static int move_construction_count; | ||
static int destruction_count; | ||
|
||
int id; | ||
|
||
static void reset_counts() { | ||
default_construction_count = 0; | ||
copy_construction_count = 0; | ||
move_construction_count = 0; | ||
destruction_count = 0; | ||
} | ||
|
||
static int construction_count() { | ||
return default_construction_count + copy_construction_count + move_construction_count; | ||
} | ||
|
||
static int active_count() { return construction_count() - destruction_count; } | ||
|
||
counted() | ||
: id(default_construction_count++) {} | ||
counted(const counted& other) | ||
: id(other.id) { | ||
++copy_construction_count; | ||
} | ||
counted(counted&& other) | ||
: id(other.id) { | ||
++move_construction_count; | ||
other.id = -1; | ||
} | ||
~counted() { ++destruction_count; } | ||
}; | ||
|
||
int counted::default_construction_count; | ||
int counted::copy_construction_count; | ||
int counted::move_construction_count; | ||
int counted::destruction_count; | ||
|
||
TEST_SUITE_BEGIN("task"); | ||
|
||
TEST_CASE("task doesn't start until awaited") { | ||
bool started = false; | ||
auto func = [&]() -> gap::coro::task<> { | ||
started = true; | ||
co_return; | ||
}; | ||
|
||
gap::coro::sync_wait([&]() -> gap::coro::task<> { | ||
auto t = func(); | ||
CHECK(!started); | ||
|
||
co_await t; | ||
|
||
CHECK(started); | ||
}()); | ||
} | ||
|
||
TEST_CASE("awaiting default-constructed task throws broken_promise") { | ||
gap::coro::sync_wait([&]() -> gap::coro::task<> { | ||
gap::coro::task<> t; | ||
CHECK_THROWS_AS(co_await t, const gap::coro::broken_promise&); | ||
}()); | ||
} | ||
|
||
TEST_CASE("awaiting task that completes asynchronously") { | ||
bool reached_before_event = false; | ||
bool reached_after_event = false; | ||
gap::coro::single_consumer_event event; | ||
auto f = [&]() -> gap::coro::task<> { | ||
reached_before_event = true; | ||
co_await event; | ||
reached_after_event = true; | ||
}; | ||
|
||
gap::coro::sync_wait([&]() -> gap::coro::task<> { | ||
auto t = f(); | ||
|
||
CHECK(!reached_before_event); | ||
|
||
(void) co_await gap::coro::when_all_ready( | ||
[&]() -> gap::coro::task<> { | ||
co_await t; | ||
CHECK(reached_before_event); | ||
CHECK(reached_after_event); | ||
}(), | ||
[&]() -> gap::coro::task<> { | ||
CHECK(reached_before_event); | ||
CHECK(!reached_after_event); | ||
event.set(); | ||
CHECK(reached_after_event); | ||
co_return; | ||
}()); | ||
}()); | ||
} | ||
|
||
TEST_CASE("destroying task that was never awaited destroys captured args") { | ||
counted::reset_counts(); | ||
|
||
auto f = [](counted c) -> gap::coro::task< counted > { co_return c; }; | ||
|
||
CHECK(counted::active_count() == 0); | ||
|
||
{ | ||
auto t = f(counted{}); | ||
CHECK(counted::active_count() == 1); | ||
} | ||
|
||
CHECK(counted::active_count() == 0); | ||
} | ||
|
||
TEST_CASE("task destructor destroys result") { | ||
counted::reset_counts(); | ||
|
||
auto f = []() -> gap::coro::task< counted > { co_return counted{}; }; | ||
|
||
{ | ||
auto t = f(); | ||
CHECK(counted::active_count() == 0); | ||
|
||
auto& result = gap::coro::sync_wait(t); | ||
|
||
CHECK(counted::active_count() == 1); | ||
CHECK(result.id == 0); | ||
} | ||
|
||
CHECK(counted::active_count() == 0); | ||
} | ||
|
||
TEST_CASE("task of reference type") { | ||
int value = 3; | ||
auto f = [&]() -> gap::coro::task< int& > { co_return value; }; | ||
|
||
gap::coro::sync_wait([&]() -> gap::coro::task<> { | ||
SUBCASE("awaiting rvalue task") { | ||
decltype(auto) result = co_await f(); | ||
static_assert( | ||
std::is_same< decltype(result), int& >::value, | ||
"co_await r-value reference of task<int&> should result in an int&"); | ||
CHECK(&result == &value); | ||
} | ||
|
||
SUBCASE("awaiting lvalue task") { | ||
auto t = f(); | ||
decltype(auto) result = co_await t; | ||
static_assert( | ||
std::is_same< decltype(result), int& >::value, | ||
"co_await l-value reference of task<int&> should result in an int&"); | ||
CHECK(&result == &value); | ||
} | ||
}()); | ||
} | ||
|
||
TEST_CASE("passing parameter by value to task coroutine calls move-constructor exactly once") { | ||
counted::reset_counts(); | ||
|
||
auto f = [](counted arg) -> gap::coro::task<> { co_return; }; | ||
|
||
counted c; | ||
|
||
CHECK(counted::active_count() == 1); | ||
CHECK(counted::default_construction_count == 1); | ||
CHECK(counted::copy_construction_count == 0); | ||
CHECK(counted::move_construction_count == 0); | ||
CHECK(counted::destruction_count == 0); | ||
|
||
{ | ||
auto t = f(c); | ||
|
||
// Should have called copy-constructor to pass a copy of 'c' into f by value. | ||
CHECK(counted::copy_construction_count == 1); | ||
|
||
// Inside f it should have move-constructed parameter into coroutine frame variable | ||
// WARN_MESSAGE(counted::move_construction_count == 1, | ||
// "Known bug in MSVC 2017.1, not critical if it performs multiple moves"); | ||
|
||
// Active counts should be the instance 'c' and the instance captured in coroutine frame | ||
// of 't'. | ||
CHECK(counted::active_count() == 2); | ||
} | ||
|
||
CHECK(counted::active_count() == 1); | ||
} | ||
|
||
TEST_CASE("task<void> fmap pipe operator") { | ||
using gap::coro::fmap; | ||
|
||
gap::coro::single_consumer_event event; | ||
|
||
auto f = [&]() -> gap::coro::task<> { | ||
co_await event; | ||
co_return; | ||
}; | ||
|
||
auto t = f() | fmap([] { return 123; }); | ||
|
||
gap::coro::sync_wait(gap::coro::when_all_ready( | ||
[&]() -> gap::coro::task<> { CHECK(co_await t == 123); }(), | ||
[&]() -> gap::coro::task<> { | ||
event.set(); | ||
co_return; | ||
}())); | ||
} | ||
|
||
TEST_CASE("task<int> fmap pipe operator") { | ||
using gap::coro::fmap; | ||
using gap::coro::make_task; | ||
using gap::coro::sync_wait; | ||
using gap::coro::task; | ||
|
||
auto one = [&]() -> task< int > { co_return 1; }; | ||
|
||
SUBCASE("r-value fmap / r-value lambda") { | ||
auto t = one() | fmap([delta = 1](auto i) { return i + delta; }); | ||
CHECK(sync_wait(t) == 2); | ||
} | ||
|
||
SUBCASE("r-value fmap / l-value lambda") { | ||
using namespace std::string_literals; | ||
|
||
auto t = [&] { | ||
auto f = [prefix = "pfx"s](int x) { return prefix + std::to_string(x); }; | ||
|
||
// Want to make sure that the resulting awaitable has taken | ||
// a copy of the lambda passed to fmap(). | ||
return one() | fmap(f); | ||
}(); | ||
|
||
CHECK(sync_wait(t) == "pfx1"); | ||
} | ||
|
||
SUBCASE("l-value fmap / r-value lambda") { | ||
using namespace std::string_literals; | ||
|
||
auto t = [&] { | ||
auto addprefix = fmap( | ||
[prefix | ||
= "a really really long prefix that prevents small string optimisation"s]( | ||
int x) { return prefix + std::to_string(x); }); | ||
|
||
// Want to make sure that the resulting awaitable has taken | ||
// a copy of the lambda passed to fmap(). | ||
return one() | addprefix; | ||
}(); | ||
|
||
CHECK( | ||
sync_wait(t) | ||
== "a really really long prefix that prevents small string optimisation1"); | ||
} | ||
|
||
SUBCASE("l-value fmap / l-value lambda") { | ||
using namespace std::string_literals; | ||
|
||
task< std::string > t; | ||
|
||
{ | ||
auto lambda | ||
= [prefix | ||
= "a really really long prefix that prevents small string optimisation"s]( | ||
int x) { return prefix + std::to_string(x); }; | ||
|
||
auto addprefix = fmap(lambda); | ||
|
||
// Want to make sure that the resulting task has taken | ||
// a copy of the lambda passed to fmap(). | ||
t = make_task(one() | addprefix); | ||
} | ||
|
||
CHECK(!t.is_ready()); | ||
|
||
CHECK( | ||
sync_wait(t) | ||
== "a really really long prefix that prevents small string optimisation1"); | ||
} | ||
} | ||
|
||
TEST_CASE("chained fmap pipe operations") { | ||
using namespace std::string_literals; | ||
using gap::coro::sync_wait; | ||
using gap::coro::task; | ||
|
||
auto prepend = [](std::string s) { | ||
using gap::coro::fmap; | ||
return fmap([s = std::move(s)](const std::string& value) { return s + value; }); | ||
}; | ||
|
||
auto append = [](std::string s) { | ||
using gap::coro::fmap; | ||
return fmap([s = std::move(s)](const std::string& value) { return value + s; }); | ||
}; | ||
|
||
auto asyncString = [](std::string s) -> task< std::string > { co_return std::move(s); }; | ||
|
||
auto t = asyncString("base"s) | prepend("pre_"s) | append("_post"s); | ||
|
||
CHECK(sync_wait(t) == "pre_base_post"); | ||
} | ||
|
||
TEST_CASE("lots of synchronous completions doesn't result in stack-overflow") { | ||
auto completes_synchronously = []() -> gap::coro::task< int > { co_return 1; }; | ||
|
||
auto run = [&]() -> gap::coro::task<> { | ||
int sum = 0; | ||
for (int i = 0; i < 1'000'000; ++i) { | ||
sum += co_await completes_synchronously(); | ||
} | ||
CHECK(sum == 1'000'000); | ||
}; | ||
|
||
gap::coro::sync_wait(run()); | ||
} | ||
|
||
TEST_SUITE_END(); | ||
|
||
} // namespace gap::test | ||
|
||
#endif // GAP_ENABLE_COROUTINES |