- Change fmap, schedule_on, resume_on, make_shared_task work with arbitrary awaitables. - Add make_task to wrap an arbitrary awaitable in a task<T>. - Remove specialisation of fmap() for task<T>, shared_task<T> as the generalised version obsoletes it.
249 lines
5 KiB
C++
249 lines
5 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (c) Lewis Baker
|
|
// Licenced under MIT license. See LICENSE.txt for details.
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <cppcoro/shared_task.hpp>
|
|
#include <cppcoro/task.hpp>
|
|
#include <cppcoro/sync_wait.hpp>
|
|
#include <cppcoro/when_all_ready.hpp>
|
|
#include <cppcoro/single_consumer_event.hpp>
|
|
#include <cppcoro/fmap.hpp>
|
|
|
|
#include "counted.hpp"
|
|
|
|
#include <ostream>
|
|
#include <string>
|
|
|
|
#include "doctest/doctest.h"
|
|
|
|
TEST_SUITE_BEGIN("shared_task");
|
|
|
|
TEST_CASE("awaiting default-constructed task throws broken_promise")
|
|
{
|
|
cppcoro::sync_wait([]() -> cppcoro::task<>
|
|
{
|
|
CHECK_THROWS_AS(co_await cppcoro::shared_task<>{}, const cppcoro::broken_promise&);
|
|
}());
|
|
}
|
|
|
|
TEST_CASE("coroutine doesn't start executing until awaited")
|
|
{
|
|
bool startedExecuting = false;
|
|
auto f = [&]() -> cppcoro::shared_task<>
|
|
{
|
|
startedExecuting = true;
|
|
co_return;
|
|
};
|
|
|
|
auto t = f();
|
|
|
|
CHECK(!t.is_ready());
|
|
CHECK(!startedExecuting);
|
|
|
|
cppcoro::sync_wait([](cppcoro::shared_task<> t) -> cppcoro::task<>
|
|
{
|
|
co_await t;
|
|
}(t));
|
|
|
|
CHECK(t.is_ready());
|
|
CHECK(startedExecuting);
|
|
}
|
|
|
|
TEST_CASE("result is destroyed when last reference is destroyed")
|
|
{
|
|
counted::reset_counts();
|
|
|
|
{
|
|
auto t = []() -> cppcoro::shared_task<counted>
|
|
{
|
|
co_return counted{};
|
|
}();
|
|
|
|
CHECK(counted::active_count() == 0);
|
|
|
|
cppcoro::sync_wait(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_task<int>
|
|
{
|
|
startedExecution = true;
|
|
co_await event;
|
|
co_return 1;
|
|
};
|
|
|
|
auto consume = [](cppcoro::shared_task<int> t) -> cppcoro::task<>
|
|
{
|
|
int result = co_await t;
|
|
CHECK(result == 1);
|
|
};
|
|
|
|
auto sharedTask = produce();
|
|
|
|
cppcoro::sync_wait(cppcoro::when_all_ready(
|
|
consume(sharedTask),
|
|
consume(sharedTask),
|
|
consume(sharedTask),
|
|
[&]() -> cppcoro::task<>
|
|
{
|
|
event.set();
|
|
CHECK(sharedTask.is_ready());
|
|
co_return;
|
|
}()));
|
|
|
|
CHECK(sharedTask.is_ready());
|
|
}
|
|
|
|
TEST_CASE("waiting on shared_task in loop doesn't cause stack-overflow")
|
|
{
|
|
// This test checks that awaiting a shared_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_task<int>
|
|
{
|
|
co_return 1;
|
|
};
|
|
|
|
cppcoro::sync_wait([&]() -> cppcoro::task<>
|
|
{
|
|
int result = 0;
|
|
for (int i = 0; i < 1'000'000; ++i)
|
|
{
|
|
result += co_await completesSynchronously();
|
|
}
|
|
CHECK(result == 1'000'000);
|
|
}());
|
|
}
|
|
|
|
TEST_CASE("make_shared_task")
|
|
{
|
|
bool startedExecution = false;
|
|
|
|
auto f = [&]() -> cppcoro::task<std::string>
|
|
{
|
|
startedExecution = false;
|
|
co_return "test";
|
|
};
|
|
|
|
auto t = f();
|
|
|
|
cppcoro::shared_task<std::string> sharedT =
|
|
cppcoro::make_shared_task(std::move(t));
|
|
|
|
CHECK(!sharedT.is_ready());
|
|
CHECK(!startedExecution);
|
|
|
|
auto consume = [](cppcoro::shared_task<std::string> t) -> cppcoro::task<>
|
|
{
|
|
auto x = co_await std::move(t);
|
|
CHECK(x == "test");
|
|
};
|
|
|
|
cppcoro::sync_wait(cppcoro::when_all_ready(
|
|
consume(sharedT),
|
|
consume(sharedT)));
|
|
}
|
|
|
|
TEST_CASE("make_shared_task of void"
|
|
* doctest::description{ "Tests that workaround for 'co_return <void-expr>' bug is operational if required" })
|
|
{
|
|
bool startedExecution = false;
|
|
|
|
auto f = [&]() -> cppcoro::task<>
|
|
{
|
|
startedExecution = true;
|
|
co_return;
|
|
};
|
|
|
|
auto t = f();
|
|
|
|
cppcoro::shared_task<> sharedT = cppcoro::make_shared_task(std::move(t));
|
|
|
|
CHECK(!sharedT.is_ready());
|
|
CHECK(!startedExecution);
|
|
|
|
auto consume = [](cppcoro::shared_task<> t) -> cppcoro::task<>
|
|
{
|
|
co_await t;
|
|
};
|
|
|
|
auto c1 = consume(sharedT);
|
|
cppcoro::sync_wait(c1);
|
|
|
|
CHECK(startedExecution);
|
|
|
|
auto c2 = consume(sharedT);
|
|
cppcoro::sync_wait(c2);
|
|
|
|
CHECK(c1.is_ready());
|
|
CHECK(c2.is_ready());
|
|
}
|
|
|
|
TEST_CASE("shared_task<void> fmap operator")
|
|
{
|
|
cppcoro::single_consumer_event event;
|
|
int value = 0;
|
|
|
|
auto setNumber = [&]() -> cppcoro::shared_task<>
|
|
{
|
|
co_await event;
|
|
value = 123;
|
|
};
|
|
|
|
cppcoro::sync_wait(cppcoro::when_all_ready(
|
|
[&]() -> cppcoro::task<>
|
|
{
|
|
auto numericStringTask =
|
|
setNumber()
|
|
| cppcoro::fmap([&]() { return std::to_string(value); });
|
|
|
|
CHECK(co_await numericStringTask == "123");
|
|
}(),
|
|
[&]() -> cppcoro::task<>
|
|
{
|
|
CHECK(value == 0);
|
|
event.set();
|
|
CHECK(value == 123);
|
|
co_return;
|
|
}()));
|
|
}
|
|
|
|
TEST_CASE("shared_task<T> fmap operator")
|
|
{
|
|
cppcoro::single_consumer_event event;
|
|
|
|
auto getNumber = [&]() -> cppcoro::shared_task<int>
|
|
{
|
|
co_await event;
|
|
co_return 123;
|
|
};
|
|
|
|
cppcoro::sync_wait(cppcoro::when_all_ready(
|
|
[&]() -> cppcoro::task<>
|
|
{
|
|
auto numericStringTask =
|
|
getNumber()
|
|
| cppcoro::fmap([](int x) { return std::to_string(x); });
|
|
|
|
CHECK(co_await numericStringTask == "123");
|
|
}(),
|
|
[&]() -> cppcoro::task<>
|
|
{
|
|
event.set();
|
|
co_return;
|
|
}()));
|
|
}
|
|
|
|
TEST_SUITE_END();
|