Change tests to use doctest testing framework.
Break out tests to separate files per abstraction.
This commit is contained in:
parent
5e01767869
commit
9a620aa4c5
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
|
||||
|
||||
from cake.tools import script, env, compiler, project
|
||||
from cake.tools import script, env, compiler, project, variant
|
||||
|
||||
script.include([
|
||||
env.expand('${CPPCORO}/lib/use.cake'),
|
||||
])
|
||||
|
||||
headers = script.cwd([
|
||||
"counted.hpp",
|
||||
])
|
||||
|
||||
sources = script.cwd([
|
||||
'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([
|
||||
'build.cake',
|
||||
])
|
||||
|
||||
intermediateBuildDir = cake.path.join(env.expand('${CPPCORO_BUILD}'), 'test', 'obj')
|
||||
|
||||
compiler.addDefine('CPPCORO_RELEASE_' + variant.release.upper())
|
||||
|
||||
objects = compiler.objects(
|
||||
targetDir=env.expand('${CPPCORO_BUILD}/test'),
|
||||
targetDir=intermediateBuildDir,
|
||||
sources=sources,
|
||||
)
|
||||
|
||||
|
@ -32,7 +48,7 @@ testExe = compiler.program(
|
|||
vcproj = project.project(
|
||||
target=env.expand('${CPPCORO_PROJECT}/cppcoro_tests'),
|
||||
items={
|
||||
'Source': sources,
|
||||
'Source': sources + headers,
|
||||
'': extras,
|
||||
},
|
||||
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