/////////////////////////////////////////////////////////////////////////////// // Copyright (c) Lewis Baker // Licenced under MIT license. See LICENSE.txt for details. /////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #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 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 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 { 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();