First
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// 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 <cppcoro/sync_wait.hpp>
|
||||
#include <cppcoro/when_all.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include "doctest/cppcoro_doctest.h"
|
||||
|
||||
TEST_SUITE_BEGIN("async_generator");
|
||||
|
||||
TEST_CASE("default-constructed async_generator is an empty sequence")
|
||||
{
|
||||
cppcoro::sync_wait([]() -> cppcoro::task<>
|
||||
{
|
||||
// Iterating over default-constructed async_generator just
|
||||
// gives an empty sequence.
|
||||
cppcoro::async_generator<int> g;
|
||||
auto it = co_await g.begin();
|
||||
CHECK(it == g.end());
|
||||
}());
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
cppcoro::sync_wait([]() -> cppcoro::task<>
|
||||
{
|
||||
bool startedExecution = false;
|
||||
auto makeGenerator = [&]() -> cppcoro::async_generator<std::uint32_t>
|
||||
{
|
||||
startedExecution = true;
|
||||
co_yield 1;
|
||||
};
|
||||
|
||||
auto gen = makeGenerator();
|
||||
|
||||
CHECK(!startedExecution);
|
||||
|
||||
auto it = co_await gen.begin();
|
||||
|
||||
CHECK(startedExecution);
|
||||
CHECK(it != gen.end());
|
||||
CHECK(*it == 1u);
|
||||
CHECK(co_await ++it == gen.end());
|
||||
}());
|
||||
}
|
||||
|
||||
TEST_CASE("enumerate sequence of multiple values")
|
||||
{
|
||||
cppcoro::sync_wait([]() -> cppcoro::task<>
|
||||
{
|
||||
bool startedExecution = false;
|
||||
auto makeGenerator = [&]() -> cppcoro::async_generator<std::uint32_t>
|
||||
{
|
||||
startedExecution = true;
|
||||
co_yield 1;
|
||||
co_yield 2;
|
||||
co_yield 3;
|
||||
};
|
||||
|
||||
auto gen = makeGenerator();
|
||||
|
||||
CHECK(!startedExecution);
|
||||
|
||||
auto it = co_await gen.begin();
|
||||
|
||||
CHECK(startedExecution);
|
||||
|
||||
CHECK(it != gen.end());
|
||||
CHECK(*it == 1u);
|
||||
|
||||
CHECK(co_await ++it != gen.end());
|
||||
CHECK(*it == 2u);
|
||||
|
||||
CHECK(co_await ++it != gen.end());
|
||||
CHECK(*it == 3u);
|
||||
|
||||
CHECK(co_await ++it == gen.end());
|
||||
}());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
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")
|
||||
{
|
||||
cppcoro::sync_wait([]() -> cppcoro::task<>
|
||||
{
|
||||
bool aDestructed = false;
|
||||
bool bDestructed = false;
|
||||
|
||||
auto makeGenerator = [&](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;
|
||||
};
|
||||
|
||||
{
|
||||
auto gen = makeGenerator(&aDestructed);
|
||||
|
||||
CHECK(!aDestructed);
|
||||
CHECK(!bDestructed);
|
||||
|
||||
auto it = co_await gen.begin();
|
||||
CHECK(!aDestructed);
|
||||
CHECK(!bDestructed);
|
||||
CHECK(*it == 1u);
|
||||
}
|
||||
|
||||
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 produce = [&]() -> cppcoro::async_generator<std::uint32_t>
|
||||
{
|
||||
co_await p1;
|
||||
co_yield 1;
|
||||
co_await p2;
|
||||
co_yield 2;
|
||||
co_await p3;
|
||||
};
|
||||
|
||||
bool consumerFinished = false;
|
||||
|
||||
auto consume = [&]() -> cppcoro::task<>
|
||||
{
|
||||
auto generator = produce();
|
||||
auto it = co_await generator.begin();
|
||||
CHECK(*it == 1u);
|
||||
(void)co_await ++it;
|
||||
CHECK(*it == 2u);
|
||||
co_await c1;
|
||||
(void)co_await ++it;
|
||||
CHECK(it == generator.end());
|
||||
consumerFinished = true;
|
||||
};
|
||||
|
||||
auto unblock = [&]() -> cppcoro::task<>
|
||||
{
|
||||
p1.set();
|
||||
p2.set();
|
||||
c1.set();
|
||||
CHECK(!consumerFinished);
|
||||
p3.set();
|
||||
CHECK(consumerFinished);
|
||||
co_return;
|
||||
};
|
||||
|
||||
cppcoro::sync_wait(cppcoro::when_all_ready(consume(), unblock()));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
cppcoro::sync_wait([&]() -> cppcoro::task<>
|
||||
{
|
||||
CHECK_THROWS_AS(co_await gen.begin(), 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);
|
||||
|
||||
cppcoro::sync_wait([&]() -> cppcoro::task<>
|
||||
{
|
||||
auto it = co_await gen.begin();
|
||||
CHECK(*it == 1u);
|
||||
CHECK_THROWS_AS(co_await ++it, const TestException&);
|
||||
CHECK(it == gen.end());
|
||||
}());
|
||||
}
|
||||
|
||||
TEST_CASE("large number of synchronous completions doesn't result in stack-overflow")
|
||||
{
|
||||
|
||||
auto makeSequence = [](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;
|
||||
}
|
||||
};
|
||||
|
||||
auto consumer = [](cppcoro::async_generator<std::uint32_t> sequence) -> cppcoro::task<>
|
||||
{
|
||||
std::uint32_t expected = 0;
|
||||
for (auto iter = co_await sequence.begin(); iter != sequence.end(); co_await ++iter)
|
||||
{
|
||||
std::uint32_t i = *iter;
|
||||
CHECK(i == expected++);
|
||||
}
|
||||
|
||||
CHECK(expected == 1'000'000u);
|
||||
};
|
||||
|
||||
auto unblocker = [](cppcoro::single_consumer_event& event) -> cppcoro::task<>
|
||||
{
|
||||
// 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();
|
||||
|
||||
co_return;
|
||||
};
|
||||
|
||||
cppcoro::single_consumer_event event;
|
||||
|
||||
cppcoro::sync_wait(
|
||||
cppcoro::when_all_ready(
|
||||
consumer(makeSequence(event)),
|
||||
unblocker(event)));
|
||||
}
|
||||
|
||||
TEST_CASE("fmap")
|
||||
{
|
||||
using cppcoro::async_generator;
|
||||
using cppcoro::fmap;
|
||||
|
||||
auto iota = [](int count) -> async_generator<int>
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
co_yield i;
|
||||
}
|
||||
};
|
||||
|
||||
auto squares = iota(5) | fmap([](auto x) { return x * x; });
|
||||
|
||||
cppcoro::sync_wait([&]() -> cppcoro::task<>
|
||||
{
|
||||
auto it = co_await squares.begin();
|
||||
CHECK(*it == 0);
|
||||
CHECK(*co_await ++it == 1);
|
||||
CHECK(*co_await ++it == 4);
|
||||
CHECK(*co_await ++it == 9);
|
||||
CHECK(*co_await ++it == 16);
|
||||
CHECK(co_await ++it == squares.end());
|
||||
}());
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
Reference in New Issue
Block a user