Change tests to use doctest testing framework.
Break out tests to separate files per abstraction.
This commit is contained in:
parent
5e01767869
commit
9a620aa4c5
12 changed files with 7431 additions and 1724 deletions
273
test/async_generator_tests.cpp
Normal file
273
test/async_generator_tests.cpp
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <cppcoro/async_generator.hpp>
|
||||||
|
#include <cppcoro/single_consumer_event.hpp>
|
||||||
|
#include <cppcoro/task.hpp>
|
||||||
|
|
||||||
|
#include "doctest/doctest.h"
|
||||||
|
|
||||||
|
TEST_CASE("default-constructed async_generator is an empty sequence")
|
||||||
|
{
|
||||||
|
[]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
// Iterating over default-constructed async_generator just
|
||||||
|
// gives an empty sequence.
|
||||||
|
cppcoro::async_generator<int> g;
|
||||||
|
for co_await(int x : g)
|
||||||
|
{
|
||||||
|
CHECK(false);
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("async_generator doesn't start if begin() not called")
|
||||||
|
{
|
||||||
|
bool startedExecution = false;
|
||||||
|
{
|
||||||
|
auto gen = [&]() -> cppcoro::async_generator<int>
|
||||||
|
{
|
||||||
|
startedExecution = true;
|
||||||
|
co_yield 1;
|
||||||
|
}();
|
||||||
|
CHECK(!startedExecution);
|
||||||
|
}
|
||||||
|
CHECK(!startedExecution);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("enumerate sequence of 1 value")
|
||||||
|
{
|
||||||
|
bool startedExecution = false;
|
||||||
|
auto gen = [&]() -> cppcoro::async_generator<std::uint32_t>
|
||||||
|
{
|
||||||
|
startedExecution = true;
|
||||||
|
co_yield 1;
|
||||||
|
}();
|
||||||
|
|
||||||
|
CHECK(!startedExecution);
|
||||||
|
|
||||||
|
auto itAwaitable = gen.begin();
|
||||||
|
CHECK(startedExecution);
|
||||||
|
CHECK(itAwaitable.await_ready());
|
||||||
|
auto it = itAwaitable.await_resume();
|
||||||
|
CHECK(it != gen.end());
|
||||||
|
CHECK(*it == 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("enumerate sequence of multiple values")
|
||||||
|
{
|
||||||
|
bool startedExecution = false;
|
||||||
|
auto gen = [&]() -> cppcoro::async_generator<std::uint32_t>
|
||||||
|
{
|
||||||
|
startedExecution = true;
|
||||||
|
co_yield 1;
|
||||||
|
co_yield 2;
|
||||||
|
co_yield 3;
|
||||||
|
}();
|
||||||
|
|
||||||
|
CHECK(!startedExecution);
|
||||||
|
|
||||||
|
auto beginAwaitable = gen.begin();
|
||||||
|
CHECK(startedExecution);
|
||||||
|
CHECK(beginAwaitable.await_ready());
|
||||||
|
|
||||||
|
auto it = beginAwaitable.await_resume();
|
||||||
|
CHECK(it != gen.end());
|
||||||
|
CHECK(*it == 1u);
|
||||||
|
|
||||||
|
auto incrementAwaitable1 = ++it;
|
||||||
|
CHECK(incrementAwaitable1.await_ready());
|
||||||
|
incrementAwaitable1.await_resume();
|
||||||
|
CHECK(*it == 2u);
|
||||||
|
|
||||||
|
auto incrementAwaitable2 = ++it;
|
||||||
|
CHECK(incrementAwaitable2.await_ready());
|
||||||
|
incrementAwaitable2.await_resume();
|
||||||
|
CHECK(*it == 3u);
|
||||||
|
|
||||||
|
auto incrementAwaitable3 = ++it;
|
||||||
|
CHECK(incrementAwaitable3.await_ready());
|
||||||
|
incrementAwaitable3.await_resume();
|
||||||
|
CHECK(it == gen.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
class set_to_true_on_destruction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
set_to_true_on_destruction(bool* value)
|
||||||
|
: m_value(value)
|
||||||
|
{}
|
||||||
|
|
||||||
|
set_to_true_on_destruction(set_to_true_on_destruction&& other)
|
||||||
|
: m_value(other.m_value)
|
||||||
|
{
|
||||||
|
other.m_value = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
~set_to_true_on_destruction()
|
||||||
|
{
|
||||||
|
if (m_value != nullptr)
|
||||||
|
{
|
||||||
|
*m_value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_to_true_on_destruction(const set_to_true_on_destruction&) = delete;
|
||||||
|
set_to_true_on_destruction& operator=(const set_to_true_on_destruction&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool* m_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE("destructors of values in scope are called when async_generator destructed early")
|
||||||
|
{
|
||||||
|
bool aDestructed = false;
|
||||||
|
bool bDestructed = false;
|
||||||
|
{
|
||||||
|
auto gen = [&](set_to_true_on_destruction a) -> cppcoro::async_generator<std::uint32_t>
|
||||||
|
{
|
||||||
|
set_to_true_on_destruction b(&bDestructed);
|
||||||
|
co_yield 1;
|
||||||
|
co_yield 2;
|
||||||
|
}(&aDestructed);
|
||||||
|
|
||||||
|
CHECK(!aDestructed);
|
||||||
|
CHECK(!bDestructed);
|
||||||
|
|
||||||
|
auto beginOp = gen.begin();
|
||||||
|
CHECK(beginOp.await_ready());
|
||||||
|
CHECK(!aDestructed);
|
||||||
|
CHECK(!bDestructed);
|
||||||
|
|
||||||
|
auto it = beginOp.await_resume();
|
||||||
|
CHECK(*it == 1u);
|
||||||
|
CHECK(!aDestructed);
|
||||||
|
CHECK(!bDestructed);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(aDestructed);
|
||||||
|
CHECK(bDestructed);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("async producer with async consumer"
|
||||||
|
* doctest::description{
|
||||||
|
"This test tries to cover the different state-transition code-paths\n"
|
||||||
|
"- consumer resuming producer and producer completing asynchronously\n"
|
||||||
|
"- producer resuming consumer and consumer requesting next value synchronously\n"
|
||||||
|
"- producer resuming consumer and consumer requesting next value asynchronously" })
|
||||||
|
{
|
||||||
|
#if defined(_MSC_VER) && _MSC_FULL_VER <= 191025224 && defined(CPPCORO_RELEASE_OPTIMISED)
|
||||||
|
FAST_WARN_UNARY_FALSE("MSVC has a known codegen bug under optimised builds, skipping");
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
cppcoro::single_consumer_event p1;
|
||||||
|
cppcoro::single_consumer_event p2;
|
||||||
|
cppcoro::single_consumer_event p3;
|
||||||
|
cppcoro::single_consumer_event c1;
|
||||||
|
|
||||||
|
auto producer = [&]() -> cppcoro::async_generator<std::uint32_t>
|
||||||
|
{
|
||||||
|
co_await p1;
|
||||||
|
co_yield 1;
|
||||||
|
co_await p2;
|
||||||
|
co_yield 2;
|
||||||
|
co_await p3;
|
||||||
|
}();
|
||||||
|
|
||||||
|
auto consumer = [&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
auto it = co_await producer.begin();
|
||||||
|
CHECK(*it == 1u);
|
||||||
|
co_await ++it;
|
||||||
|
CHECK(*it == 2u);
|
||||||
|
co_await c1;
|
||||||
|
co_await ++it;
|
||||||
|
CHECK(it == producer.end());
|
||||||
|
}();
|
||||||
|
|
||||||
|
p1.set();
|
||||||
|
p2.set();
|
||||||
|
c1.set();
|
||||||
|
CHECK(!consumer.is_ready());
|
||||||
|
p3.set();
|
||||||
|
CHECK(consumer.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("exception thrown before first yield is rethrown from begin operation")
|
||||||
|
{
|
||||||
|
class TestException {};
|
||||||
|
auto gen = [&](bool shouldThrow) -> cppcoro::async_generator<std::uint32_t>
|
||||||
|
{
|
||||||
|
if (shouldThrow)
|
||||||
|
{
|
||||||
|
throw TestException();
|
||||||
|
}
|
||||||
|
co_yield 1;
|
||||||
|
}(true);
|
||||||
|
|
||||||
|
auto beginAwaitable = gen.begin();
|
||||||
|
CHECK(beginAwaitable.await_ready());
|
||||||
|
CHECK_THROWS_AS(beginAwaitable.await_resume(), const TestException&);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("exception thrown after first yield is rethrown from increment operator")
|
||||||
|
{
|
||||||
|
class TestException {};
|
||||||
|
auto gen = [&](bool shouldThrow) -> cppcoro::async_generator<std::uint32_t>
|
||||||
|
{
|
||||||
|
co_yield 1;
|
||||||
|
if (shouldThrow)
|
||||||
|
{
|
||||||
|
throw TestException();
|
||||||
|
}
|
||||||
|
}(true);
|
||||||
|
|
||||||
|
auto beginAwaitable = gen.begin();
|
||||||
|
CHECK(beginAwaitable.await_ready());
|
||||||
|
auto it = beginAwaitable.await_resume();
|
||||||
|
CHECK(*it == 1u);
|
||||||
|
auto incrementAwaitable = ++it;
|
||||||
|
CHECK(incrementAwaitable.await_ready());
|
||||||
|
CHECK_THROWS_AS(incrementAwaitable.await_resume(), const TestException&);
|
||||||
|
CHECK(it == gen.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("large number of synchronous completions doesn't result in stack-overflow")
|
||||||
|
{
|
||||||
|
cppcoro::single_consumer_event event;
|
||||||
|
|
||||||
|
auto sequence = [](cppcoro::single_consumer_event& event) -> cppcoro::async_generator<std::uint32_t>
|
||||||
|
{
|
||||||
|
for (std::uint32_t i = 0; i < 1'000'000u; ++i)
|
||||||
|
{
|
||||||
|
if (i == 500'000u) co_await event;
|
||||||
|
co_yield i;
|
||||||
|
}
|
||||||
|
}(event);
|
||||||
|
|
||||||
|
auto consumerTask = [](cppcoro::async_generator<std::uint32_t> sequence) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
std::uint32_t expected = 0;
|
||||||
|
for co_await(std::uint32_t i : sequence)
|
||||||
|
{
|
||||||
|
CHECK(i == expected++);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(expected == 1'000'000u);
|
||||||
|
}(std::move(sequence));
|
||||||
|
|
||||||
|
CHECK(!consumerTask.is_ready());
|
||||||
|
|
||||||
|
// Should have processed the first 500'000 elements synchronously with consumer driving
|
||||||
|
// iteraction before producer suspends and thus consumer suspends.
|
||||||
|
// Then we resume producer in call to set() below and it continues processing remaining
|
||||||
|
// 500'000 elements, this time with producer driving the interaction.
|
||||||
|
|
||||||
|
event.set();
|
||||||
|
|
||||||
|
CHECK(consumerTask.is_ready());
|
||||||
|
}
|
78
test/async_mutex_tests.cpp
Normal file
78
test/async_mutex_tests.cpp
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <cppcoro/async_mutex.hpp>
|
||||||
|
#include <cppcoro/single_consumer_event.hpp>
|
||||||
|
#include <cppcoro/task.hpp>
|
||||||
|
|
||||||
|
// HACK: Include <ostream> here to workaround bug in doctest.h
|
||||||
|
// See https://github.com/onqtam/doctest/issues/72
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
#include "doctest/doctest.h"
|
||||||
|
|
||||||
|
TEST_SUITE_BEGIN("async_mutex");
|
||||||
|
|
||||||
|
TEST_CASE("try_lock")
|
||||||
|
{
|
||||||
|
cppcoro::async_mutex mutex;
|
||||||
|
|
||||||
|
CHECK(mutex.try_lock());
|
||||||
|
|
||||||
|
CHECK_FALSE(mutex.try_lock());
|
||||||
|
|
||||||
|
mutex.unlock();
|
||||||
|
|
||||||
|
CHECK(mutex.try_lock());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("multiple lockers")
|
||||||
|
{
|
||||||
|
int value = 0;
|
||||||
|
cppcoro::async_mutex mutex;
|
||||||
|
cppcoro::single_consumer_event a;
|
||||||
|
cppcoro::single_consumer_event b;
|
||||||
|
cppcoro::single_consumer_event c;
|
||||||
|
cppcoro::single_consumer_event d;
|
||||||
|
|
||||||
|
auto f = [&](cppcoro::single_consumer_event& e) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
cppcoro::async_mutex_lock lock = co_await mutex.lock_async();
|
||||||
|
co_await e;
|
||||||
|
++value;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t1 = f(a);
|
||||||
|
CHECK(!t1.is_ready());
|
||||||
|
CHECK(value == 0);
|
||||||
|
|
||||||
|
auto t2 = f(b);
|
||||||
|
auto t3 = f(c);
|
||||||
|
|
||||||
|
a.set();
|
||||||
|
|
||||||
|
CHECK(value == 1);
|
||||||
|
|
||||||
|
auto t4 = f(d);
|
||||||
|
|
||||||
|
b.set();
|
||||||
|
|
||||||
|
CHECK(value == 2);
|
||||||
|
|
||||||
|
c.set();
|
||||||
|
|
||||||
|
CHECK(value == 3);
|
||||||
|
|
||||||
|
d.set();
|
||||||
|
|
||||||
|
CHECK(value == 4);
|
||||||
|
|
||||||
|
CHECK(t1.is_ready());
|
||||||
|
CHECK(t2.is_ready());
|
||||||
|
CHECK(t3.is_ready());
|
||||||
|
CHECK(t4.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE_END();
|
|
@ -5,22 +5,38 @@
|
||||||
|
|
||||||
import cake.path
|
import cake.path
|
||||||
|
|
||||||
from cake.tools import script, env, compiler, project
|
from cake.tools import script, env, compiler, project, variant
|
||||||
|
|
||||||
script.include([
|
script.include([
|
||||||
env.expand('${CPPCORO}/lib/use.cake'),
|
env.expand('${CPPCORO}/lib/use.cake'),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
headers = script.cwd([
|
||||||
|
"counted.hpp",
|
||||||
|
])
|
||||||
|
|
||||||
sources = script.cwd([
|
sources = script.cwd([
|
||||||
'main.cpp',
|
'main.cpp',
|
||||||
|
'counted.cpp',
|
||||||
|
'async_generator_tests.cpp',
|
||||||
|
'async_mutex_tests.cpp',
|
||||||
|
'cancellation_token_tests.cpp',
|
||||||
|
'lazy_task_tests.cpp',
|
||||||
|
'shared_lazy_task_tests.cpp',
|
||||||
|
'shared_task_tests.cpp',
|
||||||
|
'task_tests.cpp',
|
||||||
])
|
])
|
||||||
|
|
||||||
extras = script.cwd([
|
extras = script.cwd([
|
||||||
'build.cake',
|
'build.cake',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
intermediateBuildDir = cake.path.join(env.expand('${CPPCORO_BUILD}'), 'test', 'obj')
|
||||||
|
|
||||||
|
compiler.addDefine('CPPCORO_RELEASE_' + variant.release.upper())
|
||||||
|
|
||||||
objects = compiler.objects(
|
objects = compiler.objects(
|
||||||
targetDir=env.expand('${CPPCORO_BUILD}/test'),
|
targetDir=intermediateBuildDir,
|
||||||
sources=sources,
|
sources=sources,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,7 +48,7 @@ testExe = compiler.program(
|
||||||
vcproj = project.project(
|
vcproj = project.project(
|
||||||
target=env.expand('${CPPCORO_PROJECT}/cppcoro_tests'),
|
target=env.expand('${CPPCORO_PROJECT}/cppcoro_tests'),
|
||||||
items={
|
items={
|
||||||
'Source': sources,
|
'Source': sources + headers,
|
||||||
'': extras,
|
'': extras,
|
||||||
},
|
},
|
||||||
output=testExe,
|
output=testExe,
|
||||||
|
|
341
test/cancellation_token_tests.cpp
Normal file
341
test/cancellation_token_tests.cpp
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <cppcoro/cancellation_token.hpp>
|
||||||
|
#include <cppcoro/cancellation_source.hpp>
|
||||||
|
#include <cppcoro/cancellation_registration.hpp>
|
||||||
|
#include <cppcoro/operation_cancelled.hpp>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
// HACK: Including <ostream> here to work around bug in doctest.h
|
||||||
|
// See https://github.com/onqtam/doctest/issues/72
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
#include "doctest/doctest.h"
|
||||||
|
|
||||||
|
TEST_CASE("default cancellation_token is not cancellable")
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_token t;
|
||||||
|
CHECK(!t.is_cancellation_requested());
|
||||||
|
CHECK(!t.can_be_cancelled());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("calling request_cancellation on cancellation_source updates cancellation_token")
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_source s;
|
||||||
|
cppcoro::cancellation_token t = s.token();
|
||||||
|
CHECK(t.can_be_cancelled());
|
||||||
|
CHECK(!t.is_cancellation_requested());
|
||||||
|
s.request_cancellation();
|
||||||
|
CHECK(t.is_cancellation_requested());
|
||||||
|
CHECK(t.can_be_cancelled());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("cancellation_token can't be cancelled when last cancellation_source destructed")
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_token t;
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_source s;
|
||||||
|
t = s.token();
|
||||||
|
CHECK(t.can_be_cancelled());
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(!t.can_be_cancelled());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("cancelation_token can be cancelled when last cancellation_source destructed if cancellation already requested")
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_token t;
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_source s;
|
||||||
|
t = s.token();
|
||||||
|
CHECK(t.can_be_cancelled());
|
||||||
|
s.request_cancellation();
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(t.can_be_cancelled());
|
||||||
|
CHECK(t.is_cancellation_requested());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("cancellation_registration when cancellation not yet requested")
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_source s;
|
||||||
|
|
||||||
|
bool callbackExecuted = false;
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_registration callbackRegistration(
|
||||||
|
s.token(),
|
||||||
|
[&] { callbackExecuted = true; });
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(!callbackExecuted);
|
||||||
|
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_registration callbackRegistration(
|
||||||
|
s.token(),
|
||||||
|
[&] { callbackExecuted = true; });
|
||||||
|
|
||||||
|
CHECK(!callbackExecuted);
|
||||||
|
|
||||||
|
s.request_cancellation();
|
||||||
|
|
||||||
|
CHECK(callbackExecuted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("throw_if_cancellation_requested")
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_source s;
|
||||||
|
cppcoro::cancellation_token t = s.token();
|
||||||
|
|
||||||
|
CHECK_NOTHROW(t.throw_if_cancellation_requested());
|
||||||
|
|
||||||
|
s.request_cancellation();
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(t.throw_if_cancellation_requested(), const cppcoro::operation_cancelled&);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("cancellation_registration called immediately when cancellation already requested")
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_source s;
|
||||||
|
s.request_cancellation();
|
||||||
|
|
||||||
|
bool executed = false;
|
||||||
|
cppcoro::cancellation_registration r{ s.token(), [&] { executed = true; } };
|
||||||
|
CHECK(executed);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("register many callbacks"
|
||||||
|
* doctest::description{
|
||||||
|
"this checks the code-path that allocates the next chunk of entries "
|
||||||
|
"in the internal data-structres, which occurs on 17th callback" })
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_source s;
|
||||||
|
auto t = s.token();
|
||||||
|
|
||||||
|
int callbackExecutionCount = 0;
|
||||||
|
auto callback = [&] { ++callbackExecutionCount; };
|
||||||
|
|
||||||
|
// Allocate enough to require a second chunk to be allocated.
|
||||||
|
cppcoro::cancellation_registration r1{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r2{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r3{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r4{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r5{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r6{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r7{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r8{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r9{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r10{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r11{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r12{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r13{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r14{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r15{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r16{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r17{ t, callback };
|
||||||
|
cppcoro::cancellation_registration r18{ t, callback };
|
||||||
|
|
||||||
|
s.request_cancellation();
|
||||||
|
|
||||||
|
CHECK(callbackExecutionCount == 18);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("concurrent registration and cancellation")
|
||||||
|
{
|
||||||
|
// Just check this runs and terminates without crashing.
|
||||||
|
for (int i = 0; i < 100; ++i)
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_source source;
|
||||||
|
|
||||||
|
std::thread waiter1{ [token = source.token()]
|
||||||
|
{
|
||||||
|
std::atomic<bool> cancelled = false;
|
||||||
|
while (!cancelled)
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_registration registration{ token, [&]
|
||||||
|
{
|
||||||
|
cancelled = true;
|
||||||
|
} };
|
||||||
|
|
||||||
|
cppcoro::cancellation_registration reg0{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg1{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg2{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg3{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg4{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg5{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg6{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg7{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg8{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg9{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg10{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg11{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg12{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg13{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg14{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg15{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg17{ token, [] {} };
|
||||||
|
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
} };
|
||||||
|
|
||||||
|
std::thread waiter2{ [token = source.token()]
|
||||||
|
{
|
||||||
|
std::atomic<bool> cancelled = false;
|
||||||
|
while (!cancelled)
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_registration registration{ token, [&]
|
||||||
|
{
|
||||||
|
cancelled = true;
|
||||||
|
} };
|
||||||
|
|
||||||
|
cppcoro::cancellation_registration reg0{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg1{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg2{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg3{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg4{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg5{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg6{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg7{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg8{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg9{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg10{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg11{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg12{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg13{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg14{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg15{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg16{ token, [] {} };
|
||||||
|
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
} };
|
||||||
|
|
||||||
|
std::thread waiter3{ [token = source.token()]
|
||||||
|
{
|
||||||
|
std::atomic<bool> cancelled = false;
|
||||||
|
while (!cancelled)
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_registration registration{ token, [&]
|
||||||
|
{
|
||||||
|
cancelled = true;
|
||||||
|
} };
|
||||||
|
|
||||||
|
cppcoro::cancellation_registration reg0{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg1{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg2{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg3{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg4{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg5{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg6{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg7{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg8{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg9{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg10{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg11{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg12{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg13{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg14{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg15{ token, [] {} };
|
||||||
|
cppcoro::cancellation_registration reg16{ token, [] {} };
|
||||||
|
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
} };
|
||||||
|
|
||||||
|
std::thread canceller{ [&source]
|
||||||
|
{
|
||||||
|
source.request_cancellation();
|
||||||
|
} };
|
||||||
|
|
||||||
|
canceller.join();
|
||||||
|
waiter1.join();
|
||||||
|
waiter2.join();
|
||||||
|
waiter3.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("cancellation registration single-threaded performance")
|
||||||
|
{
|
||||||
|
struct batch
|
||||||
|
{
|
||||||
|
batch(cppcoro::cancellation_token t)
|
||||||
|
: r0(t, [] {})
|
||||||
|
, r1(t, [] {})
|
||||||
|
, r2(t, [] {})
|
||||||
|
, r3(t, [] {})
|
||||||
|
, r4(t, [] {})
|
||||||
|
, r5(t, [] {})
|
||||||
|
, r6(t, [] {})
|
||||||
|
, r7(t, [] {})
|
||||||
|
, r8(t, [] {})
|
||||||
|
, r9(t, [] {})
|
||||||
|
{}
|
||||||
|
|
||||||
|
cppcoro::cancellation_registration r0;
|
||||||
|
cppcoro::cancellation_registration r1;
|
||||||
|
cppcoro::cancellation_registration r2;
|
||||||
|
cppcoro::cancellation_registration r3;
|
||||||
|
cppcoro::cancellation_registration r4;
|
||||||
|
cppcoro::cancellation_registration r5;
|
||||||
|
cppcoro::cancellation_registration r6;
|
||||||
|
cppcoro::cancellation_registration r7;
|
||||||
|
cppcoro::cancellation_registration r8;
|
||||||
|
cppcoro::cancellation_registration r9;
|
||||||
|
};
|
||||||
|
|
||||||
|
cppcoro::cancellation_source s;
|
||||||
|
|
||||||
|
constexpr int iterationCount = 100'000;
|
||||||
|
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
for (int i = 0; i < iterationCount; ++i)
|
||||||
|
{
|
||||||
|
cppcoro::cancellation_registration r{ s.token(), [] {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
auto time1 = end - start;
|
||||||
|
|
||||||
|
start = end;
|
||||||
|
|
||||||
|
for (int i = 0; i < iterationCount; ++i)
|
||||||
|
{
|
||||||
|
batch b{ s.token() };
|
||||||
|
}
|
||||||
|
|
||||||
|
end = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
auto time2 = end - start;
|
||||||
|
|
||||||
|
start = end;
|
||||||
|
|
||||||
|
for (int i = 0; i < iterationCount; ++i)
|
||||||
|
{
|
||||||
|
batch b0{ s.token() };
|
||||||
|
batch b1{ s.token() };
|
||||||
|
batch b2{ s.token() };
|
||||||
|
batch b3{ s.token() };
|
||||||
|
batch b4{ s.token() };
|
||||||
|
}
|
||||||
|
|
||||||
|
end = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
auto time3 = end - start;
|
||||||
|
|
||||||
|
auto report = [](const char* label, auto time, std::uint64_t count)
|
||||||
|
{
|
||||||
|
auto us = std::chrono::duration_cast<std::chrono::microseconds>(time).count();
|
||||||
|
MESSAGE(label << " took " << us << "us (" << (1000.0 * us / count) << " ns/item)");
|
||||||
|
};
|
||||||
|
|
||||||
|
report("Individual", time1, iterationCount);
|
||||||
|
report("Batch10", time2, 10 * iterationCount);
|
||||||
|
report("Batch50", time3, 50 * iterationCount);
|
||||||
|
}
|
11
test/counted.cpp
Normal file
11
test/counted.cpp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include "counted.hpp"
|
||||||
|
|
||||||
|
int counted::default_construction_count;
|
||||||
|
int counted::copy_construction_count;
|
||||||
|
int counted::move_construction_count;
|
||||||
|
int counted::destruction_count;
|
42
test/counted.hpp
Normal file
42
test/counted.hpp
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
#ifndef CPPCORO_TESTS_COUNTED_HPP_INCLUDED
|
||||||
|
#define CPPCORO_TESTS_COUNTED_HPP_INCLUDED
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
5717
test/doctest/doctest.h
Normal file
5717
test/doctest/doctest.h
Normal file
File diff suppressed because it is too large
Load diff
194
test/lazy_task_tests.cpp
Normal file
194
test/lazy_task_tests.cpp
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <cppcoro/lazy_task.hpp>
|
||||||
|
#include <cppcoro/task.hpp>
|
||||||
|
#include <cppcoro/single_consumer_event.hpp>
|
||||||
|
|
||||||
|
#include "counted.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "doctest/doctest.h"
|
||||||
|
|
||||||
|
TEST_SUITE_BEGIN("lazy_task");
|
||||||
|
|
||||||
|
TEST_CASE("lazy_task doesn't start until awaited")
|
||||||
|
{
|
||||||
|
bool started = false;
|
||||||
|
auto func = [&]() -> cppcoro::lazy_task<>
|
||||||
|
{
|
||||||
|
started = true;
|
||||||
|
co_return;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t = func();
|
||||||
|
CHECK(!started);
|
||||||
|
|
||||||
|
auto consumer = [&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
co_await t;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto consumerTask = consumer();
|
||||||
|
|
||||||
|
CHECK(started);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("awaiting default-constructed lazy_task throws broken_promise")
|
||||||
|
{
|
||||||
|
[&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
cppcoro::lazy_task<> t;
|
||||||
|
CHECK_THROWS_AS(co_await t, const cppcoro::broken_promise&);
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("awaiting lazy_task that completes asynchronously")
|
||||||
|
{
|
||||||
|
bool reachedBeforeEvent = false;
|
||||||
|
bool reachedAfterEvent = false;
|
||||||
|
cppcoro::single_consumer_event event;
|
||||||
|
auto f = [&]() -> cppcoro::lazy_task<>
|
||||||
|
{
|
||||||
|
reachedBeforeEvent = true;
|
||||||
|
co_await event;
|
||||||
|
reachedAfterEvent = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t = f();
|
||||||
|
|
||||||
|
CHECK(!t.is_ready());
|
||||||
|
CHECK(!reachedBeforeEvent);
|
||||||
|
|
||||||
|
auto t2 = [](cppcoro::lazy_task<>& t) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
co_await t;
|
||||||
|
}(t);
|
||||||
|
|
||||||
|
CHECK(!t2.is_ready());
|
||||||
|
|
||||||
|
event.set();
|
||||||
|
|
||||||
|
CHECK(t.is_ready());
|
||||||
|
CHECK(t2.is_ready());
|
||||||
|
CHECK(reachedAfterEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("destroying lazy_task that was never awaited destroys captured args")
|
||||||
|
{
|
||||||
|
counted::reset_counts();
|
||||||
|
|
||||||
|
auto f = [](counted c) -> cppcoro::lazy_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("lazy_task destructor destroys result")
|
||||||
|
{
|
||||||
|
counted::reset_counts();
|
||||||
|
|
||||||
|
auto f = []() -> cppcoro::lazy_task<counted>
|
||||||
|
{
|
||||||
|
co_return counted{};
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto t = f();
|
||||||
|
CHECK(counted::active_count() == 0);
|
||||||
|
|
||||||
|
[](cppcoro::lazy_task<counted>& t) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
co_await t;
|
||||||
|
CHECK(t.is_ready());
|
||||||
|
CHECK(counted::active_count() == 1);
|
||||||
|
}(t);
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lazy_task of reference type")
|
||||||
|
{
|
||||||
|
int value = 3;
|
||||||
|
auto f = [&]() -> cppcoro::lazy_task<int&>
|
||||||
|
{
|
||||||
|
co_return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto g = [&]() -> cppcoro::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 lazy_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 lazy_task<int&> should result in an int&");
|
||||||
|
CHECK(&result == &value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t = g();
|
||||||
|
CHECK(t.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("passing parameter by value to lazy_task coroutine calls move-constructor exactly once")
|
||||||
|
{
|
||||||
|
counted::reset_counts();
|
||||||
|
|
||||||
|
auto f = [](counted arg) -> cppcoro::lazy_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_SUITE_END();
|
1723
test/main.cpp
1723
test/main.cpp
File diff suppressed because it is too large
Load diff
205
test/shared_lazy_task_tests.cpp
Normal file
205
test/shared_lazy_task_tests.cpp
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <cppcoro/shared_lazy_task.hpp>
|
||||||
|
#include <cppcoro/task.hpp>
|
||||||
|
#include <cppcoro/single_consumer_event.hpp>
|
||||||
|
|
||||||
|
#include "counted.hpp"
|
||||||
|
|
||||||
|
#include "doctest/doctest.h"
|
||||||
|
|
||||||
|
TEST_SUITE_BEGIN("shared_lazy_task");
|
||||||
|
|
||||||
|
TEST_CASE("awaiting default-constructed task throws broken_promise")
|
||||||
|
{
|
||||||
|
auto t = []() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(co_await cppcoro::shared_lazy_task<>{}, const cppcoro::broken_promise&);
|
||||||
|
}();
|
||||||
|
|
||||||
|
CHECK(t.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("coroutine doesn't start executing until awaited")
|
||||||
|
{
|
||||||
|
bool startedExecuting = false;
|
||||||
|
auto f = [&]() -> cppcoro::shared_lazy_task<>
|
||||||
|
{
|
||||||
|
startedExecuting = true;
|
||||||
|
co_return;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t = f();
|
||||||
|
|
||||||
|
CHECK(!t.is_ready());
|
||||||
|
CHECK(!startedExecuting);
|
||||||
|
|
||||||
|
auto waiter = [](cppcoro::shared_lazy_task<> t) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
co_await t;
|
||||||
|
}(t);
|
||||||
|
|
||||||
|
CHECK(waiter.is_ready());
|
||||||
|
CHECK(t.is_ready());
|
||||||
|
CHECK(startedExecuting);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("result is destroyed when last reference is destroyed")
|
||||||
|
{
|
||||||
|
counted::reset_counts();
|
||||||
|
|
||||||
|
{
|
||||||
|
auto t = []() -> cppcoro::shared_lazy_task<counted>
|
||||||
|
{
|
||||||
|
co_return counted{};
|
||||||
|
}();
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 0);
|
||||||
|
|
||||||
|
[](cppcoro::shared_lazy_task<counted> t) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
co_await t;
|
||||||
|
}(t);
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("multiple awaiters")
|
||||||
|
{
|
||||||
|
cppcoro::single_consumer_event event;
|
||||||
|
bool startedExecution = false;
|
||||||
|
auto produce = [&]() -> cppcoro::shared_lazy_task<int>
|
||||||
|
{
|
||||||
|
startedExecution = true;
|
||||||
|
co_await event;
|
||||||
|
co_return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto consume = [](cppcoro::shared_lazy_task<int> t) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
int result = co_await t;
|
||||||
|
CHECK(result == 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto sharedTask = produce();
|
||||||
|
|
||||||
|
CHECK(!sharedTask.is_ready());
|
||||||
|
CHECK(!startedExecution);
|
||||||
|
|
||||||
|
auto t1 = consume(sharedTask);
|
||||||
|
|
||||||
|
CHECK(!t1.is_ready());
|
||||||
|
CHECK(startedExecution);
|
||||||
|
CHECK(!sharedTask.is_ready());
|
||||||
|
|
||||||
|
auto t2 = consume(sharedTask);
|
||||||
|
|
||||||
|
CHECK(!t2.is_ready());
|
||||||
|
CHECK(!sharedTask.is_ready());
|
||||||
|
|
||||||
|
auto t3 = consume(sharedTask);
|
||||||
|
|
||||||
|
event.set();
|
||||||
|
|
||||||
|
CHECK(sharedTask.is_ready());
|
||||||
|
CHECK(t1.is_ready());
|
||||||
|
CHECK(t2.is_ready());
|
||||||
|
CHECK(t3.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("waiting on shared_lazy_task in loop doesn't cause stack-overflow")
|
||||||
|
{
|
||||||
|
// This test checks that awaiting a shared_lazy_task that completes
|
||||||
|
// synchronously doesn't recursively resume the awaiter inside the
|
||||||
|
// call to start executing the task. If it were to do this then we'd
|
||||||
|
// expect that this test would result in failure due to stack-overflow.
|
||||||
|
|
||||||
|
auto completesSynchronously = []() -> cppcoro::shared_lazy_task<int>
|
||||||
|
{
|
||||||
|
co_return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto run = [&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
for (int i = 0; i < 1'000'000; ++i)
|
||||||
|
{
|
||||||
|
result += co_await completesSynchronously();
|
||||||
|
}
|
||||||
|
CHECK(result == 1'000'000);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t = run();
|
||||||
|
CHECK(t.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("make_shared_task")
|
||||||
|
{
|
||||||
|
bool startedExecution = false;
|
||||||
|
|
||||||
|
auto f = [&]() -> cppcoro::lazy_task<std::string>
|
||||||
|
{
|
||||||
|
startedExecution = false;
|
||||||
|
co_return "test";
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t = f();
|
||||||
|
|
||||||
|
cppcoro::shared_lazy_task<std::string> sharedT =
|
||||||
|
cppcoro::make_shared_task(std::move(t));
|
||||||
|
|
||||||
|
CHECK(!sharedT.is_ready());
|
||||||
|
CHECK(!startedExecution);
|
||||||
|
|
||||||
|
auto consume = [](cppcoro::shared_lazy_task<std::string> t) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
auto x = co_await std::move(t);
|
||||||
|
CHECK(x == "test");
|
||||||
|
};
|
||||||
|
|
||||||
|
auto c1 = consume(sharedT);
|
||||||
|
auto c2 = consume(sharedT);
|
||||||
|
|
||||||
|
CHECK(c1.is_ready());
|
||||||
|
CHECK(c2.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("make_shared_task of void"
|
||||||
|
* doctest::description{ "Tests that workaround for MSVC 2017.1 bug is operational" })
|
||||||
|
{
|
||||||
|
bool startedExecution = false;
|
||||||
|
|
||||||
|
auto f = [&]() -> cppcoro::lazy_task<>
|
||||||
|
{
|
||||||
|
startedExecution = true;
|
||||||
|
co_return;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t = f();
|
||||||
|
|
||||||
|
cppcoro::shared_lazy_task<> sharedT = cppcoro::make_shared_task(std::move(t));
|
||||||
|
|
||||||
|
CHECK(!sharedT.is_ready());
|
||||||
|
CHECK(!startedExecution);
|
||||||
|
|
||||||
|
auto consume = [](cppcoro::shared_lazy_task<> t) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
co_await t;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto c1 = consume(sharedT);
|
||||||
|
CHECK(startedExecution);
|
||||||
|
|
||||||
|
auto c2 = consume(sharedT);
|
||||||
|
|
||||||
|
CHECK(c1.is_ready());
|
||||||
|
CHECK(c2.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE_END();
|
275
test/shared_task_tests.cpp
Normal file
275
test/shared_task_tests.cpp
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <cppcoro/shared_task.hpp>
|
||||||
|
#include <cppcoro/task.hpp>
|
||||||
|
#include <cppcoro/single_consumer_event.hpp>
|
||||||
|
|
||||||
|
#include "counted.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "doctest/doctest.h"
|
||||||
|
|
||||||
|
TEST_SUITE_BEGIN("shared_task");
|
||||||
|
|
||||||
|
TEST_CASE("default constructed shared_task")
|
||||||
|
{
|
||||||
|
SUBCASE("is_ready")
|
||||||
|
{
|
||||||
|
cppcoro::shared_task<> t;
|
||||||
|
CHECK(t.is_ready());
|
||||||
|
|
||||||
|
cppcoro::shared_task<> tCopy = t;
|
||||||
|
CHECK(t.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("awaiting throws broken_promise")
|
||||||
|
{
|
||||||
|
auto task = []() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(co_await cppcoro::shared_task<>{}, const cppcoro::broken_promise&);
|
||||||
|
}();
|
||||||
|
|
||||||
|
CHECK(task.is_ready());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("multiple waiters")
|
||||||
|
{
|
||||||
|
cppcoro::single_consumer_event event;
|
||||||
|
|
||||||
|
auto sharedTask = [](cppcoro::single_consumer_event& event) -> cppcoro::shared_task<>
|
||||||
|
{
|
||||||
|
co_await event;
|
||||||
|
}(event);
|
||||||
|
|
||||||
|
CHECK(!sharedTask.is_ready());
|
||||||
|
|
||||||
|
auto consumeTask = [](cppcoro::shared_task<> task) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
co_await task;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t1 = consumeTask(sharedTask);
|
||||||
|
auto t2 = consumeTask(sharedTask);
|
||||||
|
|
||||||
|
CHECK(!t1.is_ready());
|
||||||
|
CHECK(!t2.is_ready());
|
||||||
|
|
||||||
|
event.set();
|
||||||
|
|
||||||
|
CHECK(sharedTask.is_ready());
|
||||||
|
CHECK(t1.is_ready());
|
||||||
|
CHECK(t2.is_ready());
|
||||||
|
|
||||||
|
auto t3 = consumeTask(sharedTask);
|
||||||
|
|
||||||
|
CHECK(t3.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("unhandled exception is rethrown")
|
||||||
|
{
|
||||||
|
class X {};
|
||||||
|
|
||||||
|
auto throwingTask = []() -> cppcoro::shared_task<>
|
||||||
|
{
|
||||||
|
co_await std::experimental::suspend_never{};
|
||||||
|
throw X{};
|
||||||
|
};
|
||||||
|
|
||||||
|
[&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
auto t = throwingTask();
|
||||||
|
CHECK(t.is_ready());
|
||||||
|
CHECK_THROWS_AS(co_await t, const X&);
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("result is destroyed when last reference is destroyed")
|
||||||
|
{
|
||||||
|
counted::reset_counts();
|
||||||
|
|
||||||
|
{
|
||||||
|
cppcoro::shared_task<counted> tCopy;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto t = []() -> cppcoro::shared_task<counted>
|
||||||
|
{
|
||||||
|
co_return counted{};
|
||||||
|
}();
|
||||||
|
|
||||||
|
CHECK(t.is_ready());
|
||||||
|
|
||||||
|
tCopy = t;
|
||||||
|
|
||||||
|
CHECK(tCopy.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
cppcoro::shared_task<counted> tCopy2 = tCopy;
|
||||||
|
|
||||||
|
CHECK(tCopy2.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("assigning result from shared_task doesn't move result")
|
||||||
|
{
|
||||||
|
auto f = []() -> cppcoro::shared_task<std::string>
|
||||||
|
{
|
||||||
|
co_return "string that is longer than short-string optimisation";
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t = f();
|
||||||
|
|
||||||
|
auto g = [](cppcoro::shared_task<std::string> t) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
auto x = co_await t;
|
||||||
|
CHECK(x == "string that is longer than short-string optimisation");
|
||||||
|
|
||||||
|
auto y = co_await std::move(t);
|
||||||
|
CHECK(y == "string that is longer than short-string optimisation");
|
||||||
|
};
|
||||||
|
|
||||||
|
g(t);
|
||||||
|
g(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("shared_task of reference type")
|
||||||
|
{
|
||||||
|
const std::string value = "some string value";
|
||||||
|
|
||||||
|
auto f = [&]() -> cppcoro::shared_task<const std::string&>
|
||||||
|
{
|
||||||
|
co_return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
[&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
auto& result = co_await f();
|
||||||
|
CHECK(&result == &value);
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("shared_task returning rvalue reference moves into promise")
|
||||||
|
{
|
||||||
|
counted::reset_counts();
|
||||||
|
|
||||||
|
auto f = []() -> cppcoro::shared_task<counted>
|
||||||
|
{
|
||||||
|
co_return counted{};
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 0);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto t = f();
|
||||||
|
CHECK(counted::default_construction_count == 1);
|
||||||
|
CHECK(counted::copy_construction_count == 0);
|
||||||
|
CHECK(counted::move_construction_count == 1);
|
||||||
|
CHECK(counted::destruction_count == 1);
|
||||||
|
CHECK(counted::active_count() == 1);
|
||||||
|
|
||||||
|
// Moving task doesn't move/copy result.
|
||||||
|
auto t2 = std::move(t);
|
||||||
|
CHECK(counted::default_construction_count == 1);
|
||||||
|
CHECK(counted::copy_construction_count == 0);
|
||||||
|
CHECK(counted::move_construction_count == 1);
|
||||||
|
CHECK(counted::destruction_count == 1);
|
||||||
|
CHECK(counted::active_count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("equality comparison")
|
||||||
|
{
|
||||||
|
auto f = []() -> cppcoro::shared_task<>
|
||||||
|
{
|
||||||
|
co_return;
|
||||||
|
};
|
||||||
|
|
||||||
|
cppcoro::shared_task<> t0;
|
||||||
|
cppcoro::shared_task<> t1 = t0;
|
||||||
|
cppcoro::shared_task<> t2 = f();
|
||||||
|
cppcoro::shared_task<> t3 = t2;
|
||||||
|
cppcoro::shared_task<> t4 = f();
|
||||||
|
CHECK(t0 == t0);
|
||||||
|
CHECK(t0 == t1);
|
||||||
|
CHECK(t0 != t2);
|
||||||
|
CHECK(t0 != t3);
|
||||||
|
CHECK(t0 != t4);
|
||||||
|
CHECK(t2 == t2);
|
||||||
|
CHECK(t2 == t3);
|
||||||
|
CHECK(t2 != t4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("make_shared_task")
|
||||||
|
{
|
||||||
|
cppcoro::single_consumer_event event;
|
||||||
|
|
||||||
|
auto f = [&]() -> cppcoro::task<std::string>
|
||||||
|
{
|
||||||
|
co_await event;
|
||||||
|
co_return "foo";
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t = cppcoro::make_shared_task(f());
|
||||||
|
|
||||||
|
auto consumer = [](cppcoro::shared_task<std::string> task) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
CHECK(co_await task == "foo");
|
||||||
|
};
|
||||||
|
|
||||||
|
auto consumerTask0 = consumer(t);
|
||||||
|
auto consumerTask1 = consumer(t);
|
||||||
|
|
||||||
|
CHECK(!consumerTask0.is_ready());
|
||||||
|
CHECK(!consumerTask1.is_ready());
|
||||||
|
|
||||||
|
event.set();
|
||||||
|
|
||||||
|
CHECK(consumerTask0.is_ready());
|
||||||
|
CHECK(consumerTask1.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("make_shared_task of void-returning task"
|
||||||
|
* doctest::description{"checks that workaround for MSVC bug is getting picked up."
|
||||||
|
"MSVC 2017.1 was failing to evaluate <expr> in 'co_return <expr>' if expression was void."})
|
||||||
|
{
|
||||||
|
cppcoro::single_consumer_event event;
|
||||||
|
|
||||||
|
auto f = [&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
co_await event;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t = cppcoro::make_shared_task(f());
|
||||||
|
|
||||||
|
CHECK(!t.is_ready());
|
||||||
|
|
||||||
|
auto consumer = [](cppcoro::shared_task<> task) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
co_await task;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto consumerTask0 = consumer(t);
|
||||||
|
auto consumerTask1 = consumer(t);
|
||||||
|
|
||||||
|
CHECK(!consumerTask0.is_ready());
|
||||||
|
CHECK(!consumerTask1.is_ready());
|
||||||
|
|
||||||
|
event.set();
|
||||||
|
|
||||||
|
CHECK(consumerTask0.is_ready());
|
||||||
|
CHECK(consumerTask1.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE_END();
|
274
test/task_tests.cpp
Normal file
274
test/task_tests.cpp
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (c) Lewis Baker
|
||||||
|
// Licenced under MIT license. See LICENSE.txt for details.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <cppcoro/task.hpp>
|
||||||
|
#include <cppcoro/single_consumer_event.hpp>
|
||||||
|
|
||||||
|
#include "counted.hpp"
|
||||||
|
|
||||||
|
#include "doctest/doctest.h"
|
||||||
|
|
||||||
|
TEST_SUITE_BEGIN("task");
|
||||||
|
|
||||||
|
TEST_CASE("default constructed task")
|
||||||
|
{
|
||||||
|
cppcoro::task<> t;
|
||||||
|
|
||||||
|
CHECK(t.is_ready());
|
||||||
|
|
||||||
|
SUBCASE("throws broken_promise when awaited")
|
||||||
|
{
|
||||||
|
auto op = t.operator co_await();
|
||||||
|
CHECK(op.await_ready());
|
||||||
|
CHECK_THROWS_AS(op.await_resume(), const cppcoro::broken_promise&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("co_await synchronously completing task")
|
||||||
|
{
|
||||||
|
auto doNothingAsync = []() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
co_return;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto task = doNothingAsync();
|
||||||
|
|
||||||
|
CHECK(task.is_ready());
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
auto test = [&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
co_await task;
|
||||||
|
ok = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
test();
|
||||||
|
|
||||||
|
CHECK(ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("task of move-only type by value")
|
||||||
|
{
|
||||||
|
// unique_ptr is move-only type.
|
||||||
|
auto getIntPtrAsync = []() -> cppcoro::task<std::unique_ptr<int>>
|
||||||
|
{
|
||||||
|
co_return std::make_unique<int>(123);
|
||||||
|
};
|
||||||
|
|
||||||
|
SUBCASE("co_await temporary")
|
||||||
|
{
|
||||||
|
auto test = [&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
auto intPtr = co_await getIntPtrAsync();
|
||||||
|
REQUIRE(intPtr);
|
||||||
|
CHECK(*intPtr == 123);
|
||||||
|
};
|
||||||
|
|
||||||
|
test();
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("co_await lvalue reference")
|
||||||
|
{
|
||||||
|
auto test = [&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
// co_await yields l-value reference if task is l-value
|
||||||
|
auto intPtrTask = getIntPtrAsync();
|
||||||
|
auto& intPtr = co_await intPtrTask;
|
||||||
|
REQUIRE(intPtr);
|
||||||
|
CHECK(*intPtr == 123);
|
||||||
|
};
|
||||||
|
|
||||||
|
test();
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("co_await rvalue reference")
|
||||||
|
{
|
||||||
|
auto test = [&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
// Returns r-value reference if task is r-value
|
||||||
|
auto intPtrTask = getIntPtrAsync();
|
||||||
|
auto intPtr = co_await std::move(intPtrTask);
|
||||||
|
REQUIRE(intPtr);
|
||||||
|
CHECK(*intPtr == 123);
|
||||||
|
};
|
||||||
|
|
||||||
|
test();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("task of reference type")
|
||||||
|
{
|
||||||
|
int value = 0;
|
||||||
|
auto getRefAsync = [&]() -> cppcoro::task<int&>
|
||||||
|
{
|
||||||
|
co_return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto test = [&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
// Await r-value task results in l-value reference
|
||||||
|
decltype(auto) result = co_await getRefAsync();
|
||||||
|
CHECK(&result == &value);
|
||||||
|
|
||||||
|
// Await l-value task results in l-value reference
|
||||||
|
auto getRefTask = getRefAsync();
|
||||||
|
decltype(auto) result2 = co_await getRefTask;
|
||||||
|
CHECK(&result2 == &value);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto task = test();
|
||||||
|
CHECK(task.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("task of value-type moves into promise if passed rvalue reference")
|
||||||
|
{
|
||||||
|
counted::reset_counts();
|
||||||
|
|
||||||
|
auto f = []() -> cppcoro::task<counted>
|
||||||
|
{
|
||||||
|
co_return counted{};
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 0);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto t = f();
|
||||||
|
CHECK(counted::default_construction_count == 1);
|
||||||
|
CHECK(counted::copy_construction_count == 0);
|
||||||
|
CHECK(counted::move_construction_count == 1);
|
||||||
|
CHECK(counted::destruction_count == 1);
|
||||||
|
CHECK(counted::active_count() == 1);
|
||||||
|
|
||||||
|
// Moving task doesn't move/copy result.
|
||||||
|
auto t2 = std::move(t);
|
||||||
|
CHECK(counted::default_construction_count == 1);
|
||||||
|
CHECK(counted::copy_construction_count == 0);
|
||||||
|
CHECK(counted::move_construction_count == 1);
|
||||||
|
CHECK(counted::destruction_count == 1);
|
||||||
|
CHECK(counted::active_count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("task of value-type copies into promise if passed lvalue reference")
|
||||||
|
{
|
||||||
|
counted::reset_counts();
|
||||||
|
|
||||||
|
auto f = []() -> cppcoro::task<counted>
|
||||||
|
{
|
||||||
|
counted temp;
|
||||||
|
|
||||||
|
// Should be calling copy-constructor here since <promise>.return_value()
|
||||||
|
// is being passed an l-value reference.
|
||||||
|
co_return temp;
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 0);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto t = f();
|
||||||
|
CHECK(counted::default_construction_count == 1);
|
||||||
|
CHECK(counted::copy_construction_count == 1);
|
||||||
|
CHECK(counted::move_construction_count == 0);
|
||||||
|
CHECK(counted::destruction_count == 1);
|
||||||
|
CHECK(counted::active_count() == 1);
|
||||||
|
|
||||||
|
// Moving the task doesn't move/copy the result
|
||||||
|
auto t2 = std::move(t);
|
||||||
|
CHECK(counted::default_construction_count == 1);
|
||||||
|
CHECK(counted::copy_construction_count == 1);
|
||||||
|
CHECK(counted::move_construction_count == 0);
|
||||||
|
CHECK(counted::destruction_count == 1);
|
||||||
|
CHECK(counted::active_count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(counted::active_count() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("co_await chain of async completions")
|
||||||
|
{
|
||||||
|
cppcoro::single_consumer_event event;
|
||||||
|
bool reachedPointA = false;
|
||||||
|
bool reachedPointB = false;
|
||||||
|
auto async1 = [&]() -> cppcoro::task<int>
|
||||||
|
{
|
||||||
|
reachedPointA = true;
|
||||||
|
co_await event;
|
||||||
|
reachedPointB = true;
|
||||||
|
co_return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool reachedPointC = false;
|
||||||
|
bool reachedPointD = false;
|
||||||
|
auto async2 = [&]() -> cppcoro::task<int>
|
||||||
|
{
|
||||||
|
reachedPointC = true;
|
||||||
|
int result = co_await async1();
|
||||||
|
reachedPointD = true;
|
||||||
|
co_return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto task = async2();
|
||||||
|
|
||||||
|
CHECK(!task.is_ready());
|
||||||
|
CHECK(reachedPointA);
|
||||||
|
CHECK(!reachedPointB);
|
||||||
|
CHECK(reachedPointC);
|
||||||
|
CHECK(!reachedPointD);
|
||||||
|
|
||||||
|
event.set();
|
||||||
|
|
||||||
|
CHECK(task.is_ready());
|
||||||
|
CHECK(reachedPointB);
|
||||||
|
CHECK(reachedPointD);
|
||||||
|
|
||||||
|
[](cppcoro::task<int> t) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
int value = co_await t;
|
||||||
|
CHECK(value == 1);
|
||||||
|
}(std::move(task));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("awaiting default-constructed task throws broken_promise")
|
||||||
|
{
|
||||||
|
[]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
cppcoro::task<> broken;
|
||||||
|
CHECK_THROWS_AS(co_await broken, const cppcoro::broken_promise&);
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("awaiting task that completes with exception")
|
||||||
|
{
|
||||||
|
class X {};
|
||||||
|
|
||||||
|
auto run = [](bool doThrow = true) -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
if (doThrow) throw X{};
|
||||||
|
co_return;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto t = run();
|
||||||
|
CHECK(t.is_ready());
|
||||||
|
|
||||||
|
auto consumeT = [&]() -> cppcoro::task<>
|
||||||
|
{
|
||||||
|
SUBCASE("co_await task rethrows exception")
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(co_await t, const X&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("co_await task.when_ready() doesn't rethrow exception")
|
||||||
|
{
|
||||||
|
CHECK_NOTHROW(co_await t.when_ready());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto consumer = consumeT();
|
||||||
|
CHECK(consumer.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE_END();
|
Loading…
Reference in a new issue