2017-05-21 23:04:19 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Copyright (c) Lewis Baker
|
|
|
|
// Licenced under MIT license. See LICENSE.txt for details.
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
#include <cppcoro/task.hpp>
|
2017-05-21 23:04:19 +02:00
|
|
|
#include <cppcoro/single_consumer_event.hpp>
|
2017-08-14 01:50:52 +02:00
|
|
|
#include <cppcoro/sync_wait.hpp>
|
|
|
|
#include <cppcoro/when_all_ready.hpp>
|
2017-05-21 23:04:19 +02:00
|
|
|
|
|
|
|
#include "counted.hpp"
|
|
|
|
|
2017-07-18 14:52:17 +02:00
|
|
|
#include <ostream>
|
2017-07-10 00:59:32 +02:00
|
|
|
#include <string>
|
2017-05-21 23:04:19 +02:00
|
|
|
#include <type_traits>
|
|
|
|
|
|
|
|
#include "doctest/doctest.h"
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
TEST_SUITE_BEGIN("task");
|
2017-05-21 23:04:19 +02:00
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
TEST_CASE("task doesn't start until awaited")
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
bool started = false;
|
2017-08-17 13:44:34 +02:00
|
|
|
auto func = [&]() -> cppcoro::task<>
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
started = true;
|
|
|
|
co_return;
|
|
|
|
};
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
cppcoro::sync_wait([&]() -> cppcoro::task<>
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
2017-08-14 01:50:52 +02:00
|
|
|
auto t = func();
|
|
|
|
CHECK(!started);
|
2017-05-21 23:04:19 +02:00
|
|
|
|
2017-08-14 01:50:52 +02:00
|
|
|
co_await t;
|
2017-05-21 23:04:19 +02:00
|
|
|
|
2017-08-14 01:50:52 +02:00
|
|
|
CHECK(started);
|
|
|
|
}());
|
2017-05-21 23:04:19 +02:00
|
|
|
}
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
TEST_CASE("awaiting default-constructed task throws broken_promise")
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
2017-08-17 13:44:34 +02:00
|
|
|
cppcoro::sync_wait([&]() -> cppcoro::task<>
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
2017-08-17 13:44:34 +02:00
|
|
|
cppcoro::task<> t;
|
2017-05-21 23:04:19 +02:00
|
|
|
CHECK_THROWS_AS(co_await t, const cppcoro::broken_promise&);
|
2017-08-14 01:50:52 +02:00
|
|
|
}());
|
2017-05-21 23:04:19 +02:00
|
|
|
}
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
TEST_CASE("awaiting task that completes asynchronously")
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
bool reachedBeforeEvent = false;
|
|
|
|
bool reachedAfterEvent = false;
|
|
|
|
cppcoro::single_consumer_event event;
|
2017-08-17 13:44:34 +02:00
|
|
|
auto f = [&]() -> cppcoro::task<>
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
reachedBeforeEvent = true;
|
|
|
|
co_await event;
|
|
|
|
reachedAfterEvent = true;
|
|
|
|
};
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
cppcoro::sync_wait([&]() -> cppcoro::task<>
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
2017-08-14 01:50:52 +02:00
|
|
|
auto t = f();
|
2017-05-21 23:04:19 +02:00
|
|
|
|
2017-08-14 01:50:52 +02:00
|
|
|
CHECK(!reachedBeforeEvent);
|
2017-05-21 23:04:19 +02:00
|
|
|
|
2017-08-14 01:50:52 +02:00
|
|
|
co_await cppcoro::when_all_ready(
|
2017-08-17 13:44:34 +02:00
|
|
|
[&]() -> cppcoro::task<>
|
2017-08-14 01:50:52 +02:00
|
|
|
{
|
|
|
|
co_await t;
|
|
|
|
CHECK(reachedBeforeEvent);
|
|
|
|
CHECK(reachedAfterEvent);
|
|
|
|
}(),
|
2017-08-17 13:44:34 +02:00
|
|
|
[&]() -> cppcoro::task<>
|
2017-08-14 01:50:52 +02:00
|
|
|
{
|
|
|
|
CHECK(reachedBeforeEvent);
|
|
|
|
CHECK(!reachedAfterEvent);
|
|
|
|
event.set();
|
|
|
|
CHECK(reachedAfterEvent);
|
|
|
|
co_return;
|
|
|
|
}());
|
|
|
|
}());
|
2017-05-21 23:04:19 +02:00
|
|
|
}
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
TEST_CASE("destroying task that was never awaited destroys captured args")
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
counted::reset_counts();
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
auto f = [](counted c) -> cppcoro::task<counted>
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
co_return c;
|
|
|
|
};
|
|
|
|
|
|
|
|
CHECK(counted::active_count() == 0);
|
|
|
|
|
|
|
|
{
|
|
|
|
auto t = f(counted{});
|
|
|
|
CHECK(counted::active_count() == 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
CHECK(counted::active_count() == 0);
|
|
|
|
}
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
TEST_CASE("task destructor destroys result")
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
counted::reset_counts();
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
auto f = []() -> cppcoro::task<counted>
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
co_return counted{};
|
|
|
|
};
|
|
|
|
|
|
|
|
{
|
|
|
|
auto t = f();
|
|
|
|
CHECK(counted::active_count() == 0);
|
|
|
|
|
2017-08-14 01:50:52 +02:00
|
|
|
auto& result = cppcoro::sync_wait(t);
|
2017-05-21 23:04:19 +02:00
|
|
|
|
|
|
|
CHECK(counted::active_count() == 1);
|
2017-08-14 01:50:52 +02:00
|
|
|
CHECK(result.id == 0);
|
2017-05-21 23:04:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
CHECK(counted::active_count() == 0);
|
|
|
|
}
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
TEST_CASE("task of reference type")
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
int value = 3;
|
2017-08-17 13:44:34 +02:00
|
|
|
auto f = [&]() -> cppcoro::task<int&>
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
co_return value;
|
|
|
|
};
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
cppcoro::sync_wait([&]() -> cppcoro::task<>
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
SUBCASE("awaiting rvalue task")
|
|
|
|
{
|
|
|
|
decltype(auto) result = co_await f();
|
|
|
|
static_assert(
|
|
|
|
std::is_same<decltype(result), int&>::value,
|
2017-08-17 13:44:34 +02:00
|
|
|
"co_await r-value reference of task<int&> should result in an int&");
|
2017-05-21 23:04:19 +02:00
|
|
|
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,
|
2017-08-17 13:44:34 +02:00
|
|
|
"co_await l-value reference of task<int&> should result in an int&");
|
2017-05-21 23:04:19 +02:00
|
|
|
CHECK(&result == &value);
|
|
|
|
}
|
2017-08-14 01:50:52 +02:00
|
|
|
}());
|
2017-05-21 23:04:19 +02:00
|
|
|
}
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
TEST_CASE("passing parameter by value to task coroutine calls move-constructor exactly once")
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
counted::reset_counts();
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
auto f = [](counted arg) -> cppcoro::task<>
|
2017-05-21 23:04:19 +02:00
|
|
|
{
|
|
|
|
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
|
2017-08-14 01:50:52 +02:00
|
|
|
//WARN_MESSAGE(counted::move_construction_count == 1,
|
|
|
|
// "Known bug in MSVC 2017.1, not critical if it performs multiple moves");
|
2017-05-21 23:04:19 +02:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
TEST_CASE("task<void> fmap pipe operator")
|
2017-07-10 00:59:32 +02:00
|
|
|
{
|
|
|
|
using cppcoro::fmap;
|
|
|
|
|
|
|
|
cppcoro::single_consumer_event event;
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
auto f = [&]() -> cppcoro::task<>
|
2017-07-10 00:59:32 +02:00
|
|
|
{
|
|
|
|
co_await event;
|
|
|
|
co_return;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto t = f() | fmap([] { return 123; });
|
|
|
|
|
|
|
|
CHECK(!t.is_ready());
|
|
|
|
|
2017-08-14 01:50:52 +02:00
|
|
|
cppcoro::sync_wait(cppcoro::when_all_ready(
|
2017-08-17 13:44:34 +02:00
|
|
|
[&]() -> cppcoro::task<>
|
2017-08-14 01:50:52 +02:00
|
|
|
{
|
|
|
|
CHECK(co_await t == 123);
|
|
|
|
}(),
|
2017-08-17 13:44:34 +02:00
|
|
|
[&]() -> cppcoro::task<>
|
2017-08-14 01:50:52 +02:00
|
|
|
{
|
|
|
|
event.set();
|
|
|
|
co_return;
|
|
|
|
}()));
|
2017-07-10 00:59:32 +02:00
|
|
|
|
2017-08-14 01:50:52 +02:00
|
|
|
CHECK(t.is_ready());
|
2017-07-10 00:59:32 +02:00
|
|
|
}
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
TEST_CASE("task<int> fmap pipe operator")
|
2017-07-10 00:59:32 +02:00
|
|
|
{
|
2017-08-17 13:44:34 +02:00
|
|
|
using cppcoro::task;
|
2017-07-10 00:59:32 +02:00
|
|
|
using cppcoro::fmap;
|
2017-08-14 01:50:52 +02:00
|
|
|
using cppcoro::sync_wait;
|
2017-07-10 00:59:32 +02:00
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
auto one = [&]() -> task<int>
|
2017-07-10 00:59:32 +02:00
|
|
|
{
|
|
|
|
co_return 1;
|
|
|
|
};
|
|
|
|
|
|
|
|
SUBCASE("r-value fmap / r-value lambda")
|
|
|
|
{
|
2017-08-17 13:44:34 +02:00
|
|
|
task<int> t = one() | fmap([delta = 1](auto i) { return i + delta; });
|
2017-07-10 00:59:32 +02:00
|
|
|
CHECK(!t.is_ready());
|
2017-08-14 01:50:52 +02:00
|
|
|
CHECK(sync_wait(t) == 2);
|
2017-07-10 00:59:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
SUBCASE("r-value fmap / l-value lambda")
|
|
|
|
{
|
|
|
|
using namespace std::string_literals;
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
task<std::string> t;
|
2017-07-10 00:59:32 +02:00
|
|
|
|
|
|
|
{
|
|
|
|
auto f = [prefix = "pfx"s](int x)
|
|
|
|
{
|
|
|
|
return prefix + std::to_string(x);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Want to make sure that the resulting task has taken
|
|
|
|
// a copy of the lambda passed to fmap().
|
|
|
|
t = one() | fmap(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
CHECK(!t.is_ready());
|
|
|
|
|
2017-08-14 01:50:52 +02:00
|
|
|
CHECK(sync_wait(t) == "pfx1");
|
2017-07-10 00:59:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
SUBCASE("l-value fmap / r-value lambda")
|
|
|
|
{
|
|
|
|
using namespace std::string_literals;
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
task<std::string> t;
|
2017-07-10 00:59:32 +02:00
|
|
|
|
|
|
|
{
|
|
|
|
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 task has taken
|
|
|
|
// a copy of the lambda passed to fmap().
|
|
|
|
t = one() | addprefix;
|
|
|
|
}
|
|
|
|
|
|
|
|
CHECK(!t.is_ready());
|
|
|
|
|
2017-08-14 01:50:52 +02:00
|
|
|
CHECK(sync_wait(t) == "a really really long prefix that prevents small string optimisation1");
|
2017-07-10 00:59:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
SUBCASE("l-value fmap / l-value lambda")
|
|
|
|
{
|
|
|
|
using namespace std::string_literals;
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
task<std::string> t;
|
2017-07-10 00:59:32 +02:00
|
|
|
|
|
|
|
{
|
|
|
|
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 = one() | addprefix;
|
|
|
|
}
|
|
|
|
|
|
|
|
CHECK(!t.is_ready());
|
|
|
|
|
2017-08-14 01:50:52 +02:00
|
|
|
CHECK(sync_wait(t) == "a really really long prefix that prevents small string optimisation1");
|
2017-07-10 00:59:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE("chained fmap pipe operations")
|
|
|
|
{
|
|
|
|
using namespace std::string_literals;
|
2017-08-17 13:44:34 +02:00
|
|
|
using cppcoro::task;
|
2017-08-14 01:50:52 +02:00
|
|
|
using cppcoro::sync_wait;
|
2017-07-10 00:59:32 +02:00
|
|
|
|
|
|
|
auto prepend = [](std::string s)
|
|
|
|
{
|
|
|
|
using cppcoro::fmap;
|
|
|
|
return fmap([s = std::move(s)](const std::string& value) { return s + value; });
|
|
|
|
};
|
|
|
|
|
|
|
|
auto append = [](std::string s)
|
|
|
|
{
|
|
|
|
using cppcoro::fmap;
|
|
|
|
return fmap([s = std::move(s)](const std::string& value){ return value + s; });
|
|
|
|
};
|
|
|
|
|
2017-08-17 13:44:34 +02:00
|
|
|
auto asyncString = [](std::string s) -> task<std::string>
|
2017-07-10 00:59:32 +02:00
|
|
|
{
|
|
|
|
co_return std::move(s);
|
|
|
|
};
|
|
|
|
|
|
|
|
auto t = asyncString("base"s) | prepend("pre_"s) | append("_post"s);
|
|
|
|
|
|
|
|
CHECK(!t.is_ready());
|
|
|
|
|
2017-08-14 01:50:52 +02:00
|
|
|
CHECK(sync_wait(t) == "pre_base_post");
|
2017-07-10 00:59:32 +02:00
|
|
|
}
|
|
|
|
|
2017-05-21 23:04:19 +02:00
|
|
|
TEST_SUITE_END();
|