First
This commit is contained in:
@@ -0,0 +1,427 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/when_all.hpp>
|
||||
|
||||
#include <cppcoro/config.hpp>
|
||||
#include <cppcoro/async_manual_reset_event.hpp>
|
||||
#include <cppcoro/async_mutex.hpp>
|
||||
#include <cppcoro/fmap.hpp>
|
||||
#include <cppcoro/shared_task.hpp>
|
||||
#include <cppcoro/sync_wait.hpp>
|
||||
#include <cppcoro/task.hpp>
|
||||
|
||||
#include "counted.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <ostream>
|
||||
#include "doctest/cppcoro_doctest.h"
|
||||
|
||||
TEST_SUITE_BEGIN("when_all");
|
||||
|
||||
namespace
|
||||
{
|
||||
template<template<typename T> class TASK, typename T>
|
||||
TASK<T> when_event_set_return(cppcoro::async_manual_reset_event& event, T value)
|
||||
{
|
||||
co_await event;
|
||||
co_return std::move(value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("when_all() with no args completes immediately")
|
||||
{
|
||||
[[maybe_unused]] std::tuple<> result = cppcoro::sync_wait(cppcoro::when_all());
|
||||
}
|
||||
|
||||
TEST_CASE("when_all() with one arg")
|
||||
{
|
||||
bool started = false;
|
||||
bool finished = false;
|
||||
auto f = [&](cppcoro::async_manual_reset_event& event) -> cppcoro::task<std::string>
|
||||
{
|
||||
started = true;
|
||||
co_await event;
|
||||
finished = true;
|
||||
co_return "foo";
|
||||
};
|
||||
|
||||
cppcoro::async_manual_reset_event event;
|
||||
|
||||
auto whenAllTask = cppcoro::when_all(f(event));
|
||||
CHECK(!started);
|
||||
|
||||
cppcoro::sync_wait(cppcoro::when_all_ready(
|
||||
[&]() -> cppcoro::task<>
|
||||
{
|
||||
auto[s] = co_await whenAllTask;
|
||||
CHECK(s == "foo");
|
||||
}(),
|
||||
[&]() -> cppcoro::task<>
|
||||
{
|
||||
CHECK(started);
|
||||
CHECK(!finished);
|
||||
event.set();
|
||||
CHECK(finished);
|
||||
co_return;
|
||||
}()));
|
||||
}
|
||||
|
||||
TEST_CASE("when_all() with awaitables")
|
||||
{
|
||||
cppcoro::sync_wait([]() -> cppcoro::task<>
|
||||
{
|
||||
auto makeTask = [](int x) -> cppcoro::task<int>
|
||||
{
|
||||
co_return x;
|
||||
};
|
||||
|
||||
cppcoro::async_manual_reset_event event;
|
||||
event.set();
|
||||
|
||||
cppcoro::async_mutex mutex;
|
||||
|
||||
auto[eventResult, mutexLock, number] = co_await cppcoro::when_all(
|
||||
std::ref(event),
|
||||
mutex.scoped_lock_async(),
|
||||
makeTask(123) | cppcoro::fmap([](int x) { return x + 1; }));
|
||||
|
||||
(void)eventResult;
|
||||
(void)mutexLock;
|
||||
CHECK(number == 124);
|
||||
CHECK(!mutex.try_lock());
|
||||
}());
|
||||
}
|
||||
|
||||
TEST_CASE("when_all() with all task types")
|
||||
{
|
||||
counted::reset_counts();
|
||||
|
||||
auto run = [](cppcoro::async_manual_reset_event& event) -> cppcoro::task<>
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
|
||||
auto[a, b] = co_await cppcoro::when_all(
|
||||
when_event_set_return<cppcoro::task>(event, "foo"s),
|
||||
when_event_set_return<cppcoro::shared_task>(event, counted{}));
|
||||
|
||||
CHECK(a == "foo");
|
||||
CHECK(b.id == 0);
|
||||
CHECK(counted::active_count() == 1);
|
||||
};
|
||||
|
||||
cppcoro::async_manual_reset_event event;
|
||||
|
||||
cppcoro::sync_wait(cppcoro::when_all_ready(
|
||||
run(event),
|
||||
[&]() -> cppcoro::task<>
|
||||
{
|
||||
event.set();
|
||||
co_return;
|
||||
}()));
|
||||
}
|
||||
|
||||
TEST_CASE("when_all() throws if any task throws")
|
||||
{
|
||||
struct X {};
|
||||
struct Y {};
|
||||
|
||||
int startedCount = 0;
|
||||
auto makeTask = [&](int value) -> cppcoro::task<int>
|
||||
{
|
||||
++startedCount;
|
||||
if (value == 0) throw X{};
|
||||
else if (value == 1) throw Y{};
|
||||
else co_return value;
|
||||
};
|
||||
|
||||
cppcoro::sync_wait([&]() -> cppcoro::task<>
|
||||
{
|
||||
try
|
||||
{
|
||||
// This could either throw X or Y exception.
|
||||
// The exact exception that is thrown is not defined if multiple tasks throw an exception.
|
||||
// TODO: Consider throwing some kind of aggregate_exception that collects all of the exceptions together.
|
||||
(void)co_await cppcoro::when_all(makeTask(0), makeTask(1), makeTask(2));
|
||||
}
|
||||
catch (const X&)
|
||||
{
|
||||
}
|
||||
catch (const Y&)
|
||||
{
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TEST_CASE("when_all() with task<void>")
|
||||
{
|
||||
int voidTaskCount = 0;
|
||||
auto makeVoidTask = [&]() -> cppcoro::task<>
|
||||
{
|
||||
++voidTaskCount;
|
||||
co_return;
|
||||
};
|
||||
|
||||
auto makeIntTask = [](int x) -> cppcoro::task<int>
|
||||
{
|
||||
co_return x;
|
||||
};
|
||||
|
||||
// Single void task in when_all()
|
||||
auto[x] = cppcoro::sync_wait(cppcoro::when_all(makeVoidTask()));
|
||||
(void)x;
|
||||
CHECK(voidTaskCount == 1);
|
||||
|
||||
// Multiple void tasks in when_all()
|
||||
auto[a, b] = cppcoro::sync_wait(cppcoro::when_all(
|
||||
makeVoidTask(),
|
||||
makeVoidTask()));
|
||||
(void)a;
|
||||
(void)b;
|
||||
CHECK(voidTaskCount == 3);
|
||||
|
||||
// Mixing void and non-void tasks in when_all()
|
||||
auto[v1, i, v2] = cppcoro::sync_wait(cppcoro::when_all(
|
||||
makeVoidTask(),
|
||||
makeIntTask(123),
|
||||
makeVoidTask()));
|
||||
(void)v1;
|
||||
(void)v2;
|
||||
CHECK(voidTaskCount == 5);
|
||||
|
||||
CHECK(i == 123);
|
||||
}
|
||||
|
||||
TEST_CASE("when_all() with vector<task<>>")
|
||||
{
|
||||
int startedCount = 0;
|
||||
auto makeTask = [&](cppcoro::async_manual_reset_event& event) -> cppcoro::task<>
|
||||
{
|
||||
++startedCount;
|
||||
co_await event;
|
||||
};
|
||||
|
||||
cppcoro::async_manual_reset_event event1;
|
||||
cppcoro::async_manual_reset_event event2;
|
||||
|
||||
bool finished = false;
|
||||
|
||||
auto run = [&]() -> cppcoro::task<>
|
||||
{
|
||||
std::vector<cppcoro::task<>> tasks;
|
||||
tasks.push_back(makeTask(event1));
|
||||
tasks.push_back(makeTask(event2));
|
||||
tasks.push_back(makeTask(event1));
|
||||
|
||||
auto allTask = cppcoro::when_all(std::move(tasks));
|
||||
|
||||
CHECK(startedCount == 0);
|
||||
|
||||
co_await allTask;
|
||||
|
||||
finished = true;
|
||||
};
|
||||
|
||||
cppcoro::sync_wait(cppcoro::when_all_ready(
|
||||
run(),
|
||||
[&]() -> cppcoro::task<>
|
||||
{
|
||||
CHECK(startedCount == 3);
|
||||
CHECK(!finished);
|
||||
|
||||
event1.set();
|
||||
|
||||
CHECK(!finished);
|
||||
|
||||
event2.set();
|
||||
|
||||
CHECK(finished);
|
||||
co_return;
|
||||
}()));
|
||||
}
|
||||
|
||||
TEST_CASE("when_all() with vector<shared_task<>>")
|
||||
{
|
||||
int startedCount = 0;
|
||||
auto makeTask = [&](cppcoro::async_manual_reset_event& event) -> cppcoro::shared_task<>
|
||||
{
|
||||
++startedCount;
|
||||
co_await event;
|
||||
};
|
||||
|
||||
cppcoro::async_manual_reset_event event1;
|
||||
cppcoro::async_manual_reset_event event2;
|
||||
|
||||
bool finished = false;
|
||||
|
||||
auto run = [&]() -> cppcoro::task<>
|
||||
{
|
||||
std::vector<cppcoro::shared_task<>> tasks;
|
||||
tasks.push_back(makeTask(event1));
|
||||
tasks.push_back(makeTask(event2));
|
||||
tasks.push_back(makeTask(event1));
|
||||
|
||||
auto allTask = cppcoro::when_all(std::move(tasks));
|
||||
|
||||
CHECK(startedCount == 0);
|
||||
|
||||
co_await allTask;
|
||||
|
||||
finished = true;
|
||||
};
|
||||
|
||||
cppcoro::sync_wait(cppcoro::when_all_ready(
|
||||
run(),
|
||||
[&]() -> cppcoro::task<>
|
||||
{
|
||||
CHECK(startedCount == 3);
|
||||
CHECK(!finished);
|
||||
|
||||
event1.set();
|
||||
|
||||
CHECK(!finished);
|
||||
|
||||
event2.set();
|
||||
|
||||
CHECK(finished);
|
||||
|
||||
co_return;
|
||||
}()));
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template<template<typename T> class TASK>
|
||||
void check_when_all_vector_of_task_value()
|
||||
{
|
||||
cppcoro::async_manual_reset_event event1;
|
||||
cppcoro::async_manual_reset_event event2;
|
||||
|
||||
bool whenAllCompleted = false;
|
||||
|
||||
cppcoro::sync_wait(cppcoro::when_all_ready(
|
||||
[&]() -> cppcoro::task<>
|
||||
{
|
||||
std::vector<TASK<int>> tasks;
|
||||
|
||||
tasks.emplace_back(when_event_set_return<TASK>(event1, 1));
|
||||
tasks.emplace_back(when_event_set_return<TASK>(event2, 2));
|
||||
|
||||
auto whenAllTask = cppcoro::when_all(std::move(tasks));
|
||||
|
||||
auto values = co_await whenAllTask;
|
||||
REQUIRE(values.size() == 2);
|
||||
CHECK(values[0] == 1);
|
||||
CHECK(values[1] == 2);
|
||||
|
||||
whenAllCompleted = true;
|
||||
}(),
|
||||
[&]() -> cppcoro::task<>
|
||||
{
|
||||
CHECK(!whenAllCompleted);
|
||||
event2.set();
|
||||
CHECK(!whenAllCompleted);
|
||||
event1.set();
|
||||
CHECK(whenAllCompleted);
|
||||
co_return;
|
||||
}()));
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CPPCORO_RELEASE_OPTIMISED)
|
||||
constexpr bool isOptimised = true;
|
||||
#else
|
||||
constexpr bool isOptimised = false;
|
||||
#endif
|
||||
|
||||
// Disable test on MSVC x86 optimised due to bad codegen bug in
|
||||
// `co_await whenAllTask` expression under MSVC 15.7 (Preview 2) and earlier.
|
||||
TEST_CASE("when_all() with vector<task<T>>"
|
||||
* doctest::skip(CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC <= 191426316 && CPPCORO_CPU_X86 && isOptimised))
|
||||
{
|
||||
check_when_all_vector_of_task_value<cppcoro::task>();
|
||||
}
|
||||
|
||||
// Disable test on MSVC x64 optimised due to bad codegen bug in
|
||||
// 'co_await whenAllTask' expression.
|
||||
// Issue reported to MS on 19/11/2017.
|
||||
TEST_CASE("when_all() with vector<shared_task<T>>"
|
||||
* doctest::skip(CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC <= 191225805 &&
|
||||
isOptimised && CPPCORO_CPU_X64))
|
||||
{
|
||||
check_when_all_vector_of_task_value<cppcoro::shared_task>();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template<template<typename T> class TASK>
|
||||
void check_when_all_vector_of_task_reference()
|
||||
{
|
||||
cppcoro::async_manual_reset_event event1;
|
||||
cppcoro::async_manual_reset_event event2;
|
||||
|
||||
int value1 = 1;
|
||||
int value2 = 2;
|
||||
|
||||
auto makeTask = [](cppcoro::async_manual_reset_event& event, int& value) -> TASK<int&>
|
||||
{
|
||||
co_await event;
|
||||
co_return value;
|
||||
};
|
||||
|
||||
bool whenAllComplete = false;
|
||||
|
||||
cppcoro::sync_wait(cppcoro::when_all_ready(
|
||||
[&]() -> cppcoro::task<>
|
||||
{
|
||||
std::vector<TASK<int&>> tasks;
|
||||
tasks.emplace_back(makeTask(event1, value1));
|
||||
tasks.emplace_back(makeTask(event2, value2));
|
||||
|
||||
auto whenAllTask = cppcoro::when_all(std::move(tasks));
|
||||
|
||||
std::vector<std::reference_wrapper<int>> values = co_await whenAllTask;
|
||||
REQUIRE(values.size() == 2);
|
||||
CHECK(&values[0].get() == &value1);
|
||||
CHECK(&values[1].get() == &value2);
|
||||
|
||||
whenAllComplete = true;
|
||||
}(),
|
||||
[&]() -> cppcoro::task<>
|
||||
{
|
||||
CHECK(!whenAllComplete);
|
||||
event2.set();
|
||||
CHECK(!whenAllComplete);
|
||||
event1.set();
|
||||
CHECK(whenAllComplete);
|
||||
co_return;
|
||||
}()));
|
||||
}
|
||||
}
|
||||
|
||||
// Disable test on MSVC x64 optimised due to bad codegen bug in
|
||||
// 'co_await whenAllTask' expression.
|
||||
// Issue reported to MS on 19/11/2017.
|
||||
TEST_CASE("when_all() with vector<task<T&>>"
|
||||
* doctest::skip(CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC <= 191225805 &&
|
||||
isOptimised && CPPCORO_CPU_X64))
|
||||
{
|
||||
check_when_all_vector_of_task_reference<cppcoro::task>();
|
||||
}
|
||||
|
||||
// Disable test on MSVC x64 optimised due to bad codegen bug in
|
||||
// 'co_await whenAllTask' expression.
|
||||
// Issue reported to MS on 19/11/2017.
|
||||
TEST_CASE("when_all() with vector<shared_task<T&>>"
|
||||
* doctest::skip(CPPCORO_COMPILER_MSVC && CPPCORO_COMPILER_MSVC <= 191225805 &&
|
||||
isOptimised && CPPCORO_CPU_X64))
|
||||
{
|
||||
check_when_all_vector_of_task_reference<cppcoro::shared_task>();
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
Reference in New Issue
Block a user