mirror of
https://github.com/latentPrion/libspinscale.git
synced 2026-06-24 20:08:34 +00:00
Tests: Add all tests from the coro creation repo
We went back and brought along all the tests we implemented while we were building the new coro framework.
This commit is contained in:
@@ -0,0 +1,835 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <spinscale/co/group.h>
|
||||
#include <spinscale/componentThread.h>
|
||||
|
||||
#include <support/groupAssertions.h>
|
||||
#include <support/threadHarness.h>
|
||||
#include <support/timerAwaiters.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int delayShortMs = 50;
|
||||
constexpr int delayMediumMs = 200;
|
||||
constexpr int delayLongMs = 500;
|
||||
constexpr int delayAddWhileSuspendedProbeMs = 80;
|
||||
constexpr int expectedNonStdThrowValue = 42;
|
||||
constexpr int wave2ImmediateSettlementLabel = 1000;
|
||||
constexpr const char *expectedThrowMessage =
|
||||
"group_edge_test intentional failure";
|
||||
|
||||
using CallerDriver =
|
||||
sscl::tests::RoleNonViralPostingInvoker<
|
||||
sscl::tests::PostingThreadRole::CALLER>;
|
||||
|
||||
using CalleeIntInvoker =
|
||||
sscl::tests::RoleViralPostingInvoker<
|
||||
sscl::tests::PostingThreadRole::CALLEE,
|
||||
int>;
|
||||
|
||||
using CalleeVoidInvoker =
|
||||
sscl::tests::RoleViralPostingInvoker<
|
||||
sscl::tests::PostingThreadRole::CALLEE,
|
||||
void>;
|
||||
|
||||
CalleeIntInvoker waitAndReturnLabel(int timerLabelMilliseconds)
|
||||
{
|
||||
const boost::system::error_code waitError =
|
||||
co_await sscl::tests::DeadlineTimerAwaiter{
|
||||
sscl::ComponentThread::getSelf()->getIoContext(),
|
||||
timerLabelMilliseconds};
|
||||
sscl::tests::throwIfTimerWaitFailed(waitError);
|
||||
co_return timerLabelMilliseconds;
|
||||
}
|
||||
|
||||
CalleeIntInvoker waitThenThrowAfterDelay(int delayMilliseconds)
|
||||
{
|
||||
const boost::system::error_code waitError =
|
||||
co_await sscl::tests::DeadlineTimerAwaiter{
|
||||
sscl::ComponentThread::getSelf()->getIoContext(),
|
||||
delayMilliseconds};
|
||||
sscl::tests::throwIfTimerWaitFailed(waitError);
|
||||
throw std::runtime_error(expectedThrowMessage);
|
||||
}
|
||||
|
||||
CalleeIntInvoker waitThenThrowIntAfterDelay(int delayMilliseconds)
|
||||
{
|
||||
const boost::system::error_code waitError =
|
||||
co_await sscl::tests::DeadlineTimerAwaiter{
|
||||
sscl::ComponentThread::getSelf()->getIoContext(),
|
||||
delayMilliseconds};
|
||||
sscl::tests::throwIfTimerWaitFailed(waitError);
|
||||
throw expectedNonStdThrowValue;
|
||||
}
|
||||
|
||||
CalleeIntInvoker returnLabelImmediately(int label)
|
||||
{
|
||||
co_return label;
|
||||
}
|
||||
|
||||
CalleeVoidInvoker voidMemberAfterDelay(int delayMilliseconds)
|
||||
{
|
||||
const boost::system::error_code waitError =
|
||||
co_await sscl::tests::DeadlineTimerAwaiter{
|
||||
sscl::ComponentThread::getSelf()->getIoContext(),
|
||||
delayMilliseconds};
|
||||
sscl::tests::throwIfTimerWaitFailed(waitError);
|
||||
co_return;
|
||||
}
|
||||
|
||||
int readCompletedLabel(CalleeIntInvoker &invoker)
|
||||
{
|
||||
return invoker.completedReturnValues().myReturnValue;
|
||||
}
|
||||
|
||||
void assertCompleted(
|
||||
const sscl::co::Group::SettlementDescriptor &descriptor,
|
||||
int expectedLabel)
|
||||
{
|
||||
if (descriptor.type
|
||||
!= sscl::co::Group::SettlementDescriptor::TypeE::COMPLETED) {
|
||||
throw std::runtime_error("expected completed settlement");
|
||||
}
|
||||
|
||||
if (readCompletedLabel(descriptor.invokerAs<CalleeIntInvoker>())
|
||||
!= expectedLabel) {
|
||||
throw std::runtime_error("settlement label mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
void assertRuntimeErrorSettlement(
|
||||
const sscl::co::Group::SettlementDescriptor &descriptor)
|
||||
{
|
||||
if (descriptor.type
|
||||
!= sscl::co::Group::SettlementDescriptor::TypeE::EXCEPTION_THROWN) {
|
||||
throw std::runtime_error("expected exception settlement");
|
||||
}
|
||||
|
||||
try {
|
||||
std::rethrow_exception(descriptor.calleeException);
|
||||
}
|
||||
catch (const std::runtime_error &runtimeError) {
|
||||
if (std::string(runtimeError.what()) != expectedThrowMessage) {
|
||||
throw std::runtime_error("unexpected exception message");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
throw std::runtime_error("expected runtime_error settlement");
|
||||
}
|
||||
|
||||
void assertIntExceptionSettlement(
|
||||
const sscl::co::Group::SettlementDescriptor &descriptor)
|
||||
{
|
||||
if (descriptor.type
|
||||
!= sscl::co::Group::SettlementDescriptor::TypeE::EXCEPTION_THROWN) {
|
||||
throw std::runtime_error("expected int exception settlement");
|
||||
}
|
||||
|
||||
try {
|
||||
std::rethrow_exception(descriptor.calleeException);
|
||||
}
|
||||
catch (int caughtValue) {
|
||||
if (caughtValue != expectedNonStdThrowValue) {
|
||||
throw std::runtime_error("unexpected int exception value");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
throw std::runtime_error("expected int exception settlement");
|
||||
}
|
||||
|
||||
void assertEmptyGroupCoAwaitError(const std::runtime_error &runtimeError)
|
||||
{
|
||||
constexpr const char *expectedEmptyGroupCoAwaitMessage =
|
||||
"co_await: Group has no member invokers; call add() before awaiting";
|
||||
if (std::string(runtimeError.what()) != expectedEmptyGroupCoAwaitMessage) {
|
||||
throw std::runtime_error("unexpected empty-group error message");
|
||||
}
|
||||
}
|
||||
|
||||
sscl::co::ViralNonPostingInvoker<void> waitOnCallerThread(int delayMilliseconds)
|
||||
{
|
||||
const boost::system::error_code waitError =
|
||||
co_await sscl::tests::DeadlineTimerAwaiter{
|
||||
sscl::ComponentThread::getSelf()->getIoContext(),
|
||||
delayMilliseconds};
|
||||
sscl::tests::throwIfTimerWaitFailed(waitError);
|
||||
co_return;
|
||||
}
|
||||
|
||||
CallerDriver mixedSuccessAndFailureAwaitFirstThenAll(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker successInvoker = waitAndReturnLabel(1);
|
||||
CalleeIntInvoker failureInvoker = waitThenThrowAfterDelay(delayShortMs);
|
||||
|
||||
group.add(successInvoker);
|
||||
group.add(failureInvoker);
|
||||
|
||||
auto awaitFirst = group.getAwaitFirstSettlementInvoker();
|
||||
auto [firstDescriptor, allAfterFirst] = co_await awaitFirst;
|
||||
|
||||
if (firstDescriptor.type
|
||||
== sscl::co::Group::SettlementDescriptor::TypeE::COMPLETED) {
|
||||
assertCompleted(firstDescriptor, 1);
|
||||
}
|
||||
else if (firstDescriptor.type
|
||||
== sscl::co::Group::SettlementDescriptor::TypeE::EXCEPTION_THROWN) {
|
||||
assertRuntimeErrorSettlement(firstDescriptor);
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error("first settlement has unexpected type");
|
||||
}
|
||||
|
||||
auto awaitAll = group.getAwaitAllSettlementsInvoker();
|
||||
auto &allDescriptors = co_await awaitAll;
|
||||
|
||||
if (allDescriptors.size() != 2 || allAfterFirst.size() != 2) {
|
||||
throw std::runtime_error("mixed settlement count mismatch");
|
||||
}
|
||||
|
||||
std::size_t completedCount = 0;
|
||||
std::size_t exceptionCount = 0;
|
||||
|
||||
for (auto &descriptor : allDescriptors) {
|
||||
if (descriptor.type
|
||||
== sscl::co::Group::SettlementDescriptor::TypeE::COMPLETED) {
|
||||
++completedCount;
|
||||
assertCompleted(descriptor, 1);
|
||||
}
|
||||
else if (descriptor.type
|
||||
== sscl::co::Group::SettlementDescriptor::TypeE::EXCEPTION_THROWN) {
|
||||
++exceptionCount;
|
||||
assertRuntimeErrorSettlement(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
if (completedCount != 1 || exceptionCount != 1) {
|
||||
throw std::runtime_error("mixed settlement type counts mismatch");
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
CallerDriver singleMemberAwaitFirstThenAll(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker onlyInvoker = waitAndReturnLabel(delayShortMs);
|
||||
group.add(onlyInvoker);
|
||||
|
||||
auto awaitFirst = group.getAwaitFirstSettlementInvoker();
|
||||
auto [firstDescriptor, allAfterFirst] = co_await awaitFirst;
|
||||
assertCompleted(firstDescriptor, delayShortMs);
|
||||
|
||||
if (!group.allInvokersSettled() || allAfterFirst.size() != 1) {
|
||||
throw std::runtime_error("single member state mismatch");
|
||||
}
|
||||
|
||||
auto awaitAll = group.getAwaitAllSettlementsInvoker();
|
||||
auto &allDescriptors = co_await awaitAll;
|
||||
|
||||
if (allDescriptors.size() != 1) {
|
||||
throw std::runtime_error("single member await-all count mismatch");
|
||||
}
|
||||
|
||||
assertCompleted(allDescriptors[0], delayShortMs);
|
||||
co_return;
|
||||
}
|
||||
|
||||
CallerDriver allCompleteBeforeCoAwait(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker invokerTen = returnLabelImmediately(10);
|
||||
CalleeIntInvoker invokerTwenty = returnLabelImmediately(20);
|
||||
CalleeIntInvoker invokerThirty = returnLabelImmediately(30);
|
||||
|
||||
group.add(invokerTen);
|
||||
group.add(invokerTwenty);
|
||||
group.add(invokerThirty);
|
||||
|
||||
co_await waitOnCallerThread(delayShortMs);
|
||||
|
||||
if (!group.allInvokersSettled() || !group.firstInvokerSettled()) {
|
||||
throw std::runtime_error("immediate group did not settle before await");
|
||||
}
|
||||
|
||||
auto awaitFirst = group.getAwaitFirstSettlementInvoker();
|
||||
auto [firstDescriptor, allAfterFirst] = co_await awaitFirst;
|
||||
assertCompleted(firstDescriptor, 10);
|
||||
|
||||
auto awaitAll = group.getAwaitAllSettlementsInvoker();
|
||||
auto &allDescriptors = co_await awaitAll;
|
||||
|
||||
if (allDescriptors.size() != 3 || allAfterFirst.size() != 3) {
|
||||
throw std::runtime_error("immediate settlement count mismatch");
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
std::thread startAddWhileGroupAwaiterSuspendedProbe(
|
||||
sscl::co::Group &group,
|
||||
CalleeIntInvoker &lateInvoker,
|
||||
std::atomic<bool> &groupIsAwaitingAll,
|
||||
std::atomic<bool> &addWasRejected)
|
||||
{
|
||||
return std::thread(
|
||||
[&]()
|
||||
{
|
||||
while (!groupIsAwaitingAll.load(std::memory_order_acquire)) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(delayAddWhileSuspendedProbeMs));
|
||||
|
||||
boost::asio::post(
|
||||
sscl::tests::ThreadRegistry::ioContext(
|
||||
sscl::tests::PostingThreadRole::CALLER),
|
||||
[&]()
|
||||
{
|
||||
try {
|
||||
group.add(lateInvoker);
|
||||
}
|
||||
catch (const std::runtime_error &) {
|
||||
addWasRejected.store(true, std::memory_order_release);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
CallerDriver addWhileAwaitAllSuspended(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
std::atomic<bool> groupIsAwaitingAll{false};
|
||||
std::atomic<bool> addWasRejected{false};
|
||||
|
||||
CalleeIntInvoker slowInvokerA = waitAndReturnLabel(delayLongMs);
|
||||
CalleeIntInvoker slowInvokerB = waitAndReturnLabel(delayLongMs);
|
||||
CalleeIntInvoker lateInvoker = waitAndReturnLabel(99);
|
||||
|
||||
group.add(slowInvokerA);
|
||||
group.add(slowInvokerB);
|
||||
|
||||
std::thread addProbeThread = startAddWhileGroupAwaiterSuspendedProbe(
|
||||
group,
|
||||
lateInvoker,
|
||||
groupIsAwaitingAll,
|
||||
addWasRejected);
|
||||
|
||||
auto awaitAll = group.getAwaitAllSettlementsInvoker();
|
||||
groupIsAwaitingAll.store(true, std::memory_order_release);
|
||||
co_await awaitAll;
|
||||
|
||||
addProbeThread.join();
|
||||
|
||||
if (!addWasRejected.load(std::memory_order_acquire)) {
|
||||
throw std::runtime_error("expected add while suspended to throw");
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
CallerDriver awaitAllOnlyMixedOutcomes(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker successInvoker = returnLabelImmediately(7);
|
||||
CalleeIntInvoker failureInvoker = waitThenThrowAfterDelay(delayShortMs);
|
||||
|
||||
group.add(successInvoker);
|
||||
group.add(failureInvoker);
|
||||
|
||||
auto awaitAll = group.getAwaitAllSettlementsInvoker();
|
||||
auto &allDescriptors = co_await awaitAll;
|
||||
|
||||
if (allDescriptors.size() != 2) {
|
||||
throw std::runtime_error("await-all-only count mismatch");
|
||||
}
|
||||
|
||||
std::size_t completedCount = 0;
|
||||
std::size_t exceptionCount = 0;
|
||||
|
||||
for (auto &descriptor : allDescriptors) {
|
||||
if (descriptor.type
|
||||
== sscl::co::Group::SettlementDescriptor::TypeE::COMPLETED) {
|
||||
++completedCount;
|
||||
assertCompleted(descriptor, 7);
|
||||
}
|
||||
else if (descriptor.type
|
||||
== sscl::co::Group::SettlementDescriptor::TypeE::EXCEPTION_THROWN) {
|
||||
++exceptionCount;
|
||||
assertRuntimeErrorSettlement(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
if (completedCount != 1 || exceptionCount != 1) {
|
||||
throw std::runtime_error("await-all-only mixed counts mismatch");
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
CallerDriver checkForAndReThrowGroupExceptions(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker failureInvoker = waitThenThrowAfterDelay(delayShortMs);
|
||||
group.add(failureInvoker);
|
||||
|
||||
(void)co_await group.getAwaitAllSettlementsInvoker();
|
||||
|
||||
try {
|
||||
group.checkForAndReThrowGroupExceptions();
|
||||
}
|
||||
catch (const std::runtime_error &aggregateError) {
|
||||
if (std::string(aggregateError.what()).find(expectedThrowMessage)
|
||||
== std::string::npos) {
|
||||
throw std::runtime_error("aggregate message missing callee text");
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
throw std::runtime_error("expected aggregate group exception");
|
||||
}
|
||||
|
||||
CallerDriver emptyGroupAwaitAllThrows(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
|
||||
try {
|
||||
(void)co_await group.getAwaitAllSettlementsInvoker();
|
||||
}
|
||||
catch (const std::runtime_error &runtimeError) {
|
||||
assertEmptyGroupCoAwaitError(runtimeError);
|
||||
co_return;
|
||||
}
|
||||
|
||||
throw std::runtime_error("expected empty group await-all to throw");
|
||||
}
|
||||
|
||||
CallerDriver emptyGroupAwaitFirstThrows(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
|
||||
try {
|
||||
(void)co_await group.getAwaitFirstSettlementInvoker();
|
||||
}
|
||||
catch (const std::runtime_error &runtimeError) {
|
||||
assertEmptyGroupCoAwaitError(runtimeError);
|
||||
co_return;
|
||||
}
|
||||
|
||||
throw std::runtime_error("expected empty group await-first to throw");
|
||||
}
|
||||
|
||||
CallerDriver wrongAwaitInvokerOrder(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker shortInvoker = waitAndReturnLabel(delayShortMs);
|
||||
CalleeIntInvoker mediumInvoker = waitAndReturnLabel(delayMediumMs);
|
||||
|
||||
group.add(shortInvoker);
|
||||
group.add(mediumInvoker);
|
||||
|
||||
auto awaitFirstHandle = group.getAwaitFirstSettlementInvoker();
|
||||
auto awaitAllHandle = group.getAwaitAllSettlementsInvoker();
|
||||
|
||||
auto &allDescriptors = co_await awaitAllHandle;
|
||||
if (allDescriptors.size() != 2) {
|
||||
throw std::runtime_error("wrong-order await-all count mismatch");
|
||||
}
|
||||
|
||||
auto [firstDescriptor, allAfterFirst] = co_await awaitFirstHandle;
|
||||
assertCompleted(
|
||||
firstDescriptor,
|
||||
readCompletedLabel(firstDescriptor.invokerAs<CalleeIntInvoker>()));
|
||||
|
||||
if (!group.firstInvokerSettled() || allAfterFirst.size() != 2) {
|
||||
throw std::runtime_error("wrong-order await-first state mismatch");
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
CallerDriver doubleCoAwaitSameAwaitFirst(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker memberInvoker = returnLabelImmediately(delayShortMs);
|
||||
group.add(memberInvoker);
|
||||
|
||||
co_await waitOnCallerThread(delayShortMs);
|
||||
|
||||
auto awaitFirst = group.getAwaitFirstSettlementInvoker();
|
||||
auto [firstDescriptorA, allAfterFirstA] = co_await awaitFirst;
|
||||
auto [firstDescriptorB, allAfterFirstB] = co_await awaitFirst;
|
||||
|
||||
assertCompleted(firstDescriptorA, delayShortMs);
|
||||
assertCompleted(firstDescriptorB, delayShortMs);
|
||||
|
||||
if (&firstDescriptorA.invokerAs<CalleeIntInvoker>()
|
||||
!= &firstDescriptorB.invokerAs<CalleeIntInvoker>()) {
|
||||
throw std::runtime_error("double await-first descriptor mismatch");
|
||||
}
|
||||
|
||||
if (allAfterFirstA.size() != allAfterFirstB.size()) {
|
||||
throw std::runtime_error("double await-first snapshot mismatch");
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
CallerDriver doubleCoAwaitSameAwaitAll(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker memberInvoker = waitAndReturnLabel(delayShortMs);
|
||||
group.add(memberInvoker);
|
||||
|
||||
auto awaitAll = group.getAwaitAllSettlementsInvoker();
|
||||
auto &allDescriptorsA = co_await awaitAll;
|
||||
auto &allDescriptorsB = co_await awaitAll;
|
||||
|
||||
if (allDescriptorsA.size() != 1 || allDescriptorsB.size() != 1) {
|
||||
throw std::runtime_error("double await-all count mismatch");
|
||||
}
|
||||
|
||||
assertCompleted(allDescriptorsA[0], delayShortMs);
|
||||
assertCompleted(allDescriptorsB[0], delayShortMs);
|
||||
co_return;
|
||||
}
|
||||
|
||||
CallerDriver twoAwaitFirstHandlesSequentially(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker shortInvoker = waitAndReturnLabel(delayShortMs);
|
||||
CalleeIntInvoker mediumInvoker = waitAndReturnLabel(delayMediumMs);
|
||||
|
||||
group.add(shortInvoker);
|
||||
group.add(mediumInvoker);
|
||||
|
||||
auto awaitFirstA = group.getAwaitFirstSettlementInvoker();
|
||||
auto [firstDescriptorA, allAfterFirstA] = co_await awaitFirstA;
|
||||
assertCompleted(firstDescriptorA, delayShortMs);
|
||||
|
||||
auto awaitFirstB = group.getAwaitFirstSettlementInvoker();
|
||||
auto [firstDescriptorB, allAfterFirstB] = co_await awaitFirstB;
|
||||
assertCompleted(firstDescriptorB, delayShortMs);
|
||||
|
||||
if (&firstDescriptorA.invokerAs<CalleeIntInvoker>()
|
||||
!= &firstDescriptorB.invokerAs<CalleeIntInvoker>()) {
|
||||
throw std::runtime_error("sticky first settlement mismatch");
|
||||
}
|
||||
|
||||
(void)co_await group.getAwaitAllSettlementsInvoker();
|
||||
(void)allAfterFirstA;
|
||||
(void)allAfterFirstB;
|
||||
co_return;
|
||||
}
|
||||
|
||||
CallerDriver addSecondWaveAfterAwaitAll(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker wave1MemberA = waitAndReturnLabel(delayLongMs);
|
||||
CalleeIntInvoker wave1MemberB = waitAndReturnLabel(delayLongMs);
|
||||
|
||||
group.add(wave1MemberA);
|
||||
group.add(wave1MemberB);
|
||||
(void)co_await group.getAwaitAllSettlementsInvoker();
|
||||
|
||||
CalleeIntInvoker wave2Immediate =
|
||||
returnLabelImmediately(wave2ImmediateSettlementLabel);
|
||||
CalleeIntInvoker wave2Slow = waitAndReturnLabel(delayMediumMs);
|
||||
|
||||
group.add(wave2Immediate);
|
||||
group.add(wave2Slow);
|
||||
|
||||
co_await waitOnCallerThread(delayShortMs);
|
||||
|
||||
if (readCompletedLabel(wave2Immediate)
|
||||
!= wave2ImmediateSettlementLabel) {
|
||||
throw std::runtime_error("wave-2 immediate member did not complete");
|
||||
}
|
||||
|
||||
if (group.allInvokersSettled()) {
|
||||
throw std::runtime_error("wave-2 slow member should still be in flight");
|
||||
}
|
||||
|
||||
auto &allDescriptors =
|
||||
co_await group.getAwaitAllSettlementsInvoker();
|
||||
|
||||
if (allDescriptors.size() != 4) {
|
||||
throw std::runtime_error("expected four settlements after second wave");
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
CallerDriver shortTimerAddedAfterLongStillWinsRace(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker longInvoker = waitAndReturnLabel(delayLongMs);
|
||||
CalleeIntInvoker shortInvoker = waitAndReturnLabel(delayShortMs);
|
||||
|
||||
group.add(longInvoker);
|
||||
group.add(shortInvoker);
|
||||
|
||||
auto awaitFirst = group.getAwaitFirstSettlementInvoker();
|
||||
auto [firstDescriptor, allAfterFirst] = co_await awaitFirst;
|
||||
|
||||
assertCompleted(firstDescriptor, delayShortMs);
|
||||
|
||||
if (&firstDescriptor.invokerAs<CalleeIntInvoker>() != &shortInvoker) {
|
||||
throw std::runtime_error("short timer should win first settlement");
|
||||
}
|
||||
|
||||
(void)co_await group.getAwaitAllSettlementsInvoker();
|
||||
(void)allAfterFirst;
|
||||
co_return;
|
||||
}
|
||||
|
||||
CallerDriver nonStdExceptionSettlement(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker failureInvoker = waitThenThrowIntAfterDelay(delayShortMs);
|
||||
group.add(failureInvoker);
|
||||
|
||||
auto &allDescriptors = co_await group.getAwaitAllSettlementsInvoker();
|
||||
|
||||
if (allDescriptors.size() != 1) {
|
||||
throw std::runtime_error("non-std exception count mismatch");
|
||||
}
|
||||
|
||||
assertIntExceptionSettlement(allDescriptors[0]);
|
||||
|
||||
try {
|
||||
group.checkForAndReThrowGroupExceptions();
|
||||
}
|
||||
catch (const std::runtime_error &) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
throw std::runtime_error("expected aggregate for non-std exception");
|
||||
}
|
||||
|
||||
CallerDriver voidViralMemberInGroup(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeVoidInvoker voidInvoker = voidMemberAfterDelay(delayShortMs);
|
||||
group.add(voidInvoker);
|
||||
|
||||
auto &allDescriptors = co_await group.getAwaitAllSettlementsInvoker();
|
||||
|
||||
if (allDescriptors.size() != 1) {
|
||||
throw std::runtime_error("void group count mismatch");
|
||||
}
|
||||
|
||||
if (allDescriptors[0].type
|
||||
!= sscl::co::Group::SettlementDescriptor::TypeE::COMPLETED) {
|
||||
throw std::runtime_error("void member did not complete");
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
CallerDriver returnValuesRemainReadableAfterAwaitFirst(
|
||||
std::exception_ptr &exceptionPtr,
|
||||
std::function<void()> completion)
|
||||
{
|
||||
(void)exceptionPtr;
|
||||
(void)completion;
|
||||
|
||||
sscl::co::Group group;
|
||||
CalleeIntInvoker slowInvoker = waitAndReturnLabel(delayLongMs);
|
||||
CalleeIntInvoker fastInvoker = waitAndReturnLabel(delayShortMs);
|
||||
|
||||
group.add(slowInvoker);
|
||||
group.add(fastInvoker);
|
||||
|
||||
auto awaitFirst = group.getAwaitFirstSettlementInvoker();
|
||||
auto [firstDescriptor, allAfterFirst] = co_await awaitFirst;
|
||||
|
||||
assertCompleted(firstDescriptor, delayShortMs);
|
||||
|
||||
const int fastLabelFromDescriptor = readCompletedLabel(
|
||||
firstDescriptor.invokerAs<CalleeIntInvoker>());
|
||||
const int fastLabelFromLocal = readCompletedLabel(fastInvoker);
|
||||
|
||||
if (fastLabelFromDescriptor != fastLabelFromLocal) {
|
||||
throw std::runtime_error("descriptor/local return value mismatch");
|
||||
}
|
||||
|
||||
if (allAfterFirst.size() != 2) {
|
||||
throw std::runtime_error("expected two settlement slots");
|
||||
}
|
||||
|
||||
(void)co_await group.getAwaitAllSettlementsInvoker();
|
||||
co_return;
|
||||
}
|
||||
|
||||
class GroupEdgeTest
|
||||
: public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
template <typename Factory>
|
||||
void runScenario(Factory &&factory)
|
||||
{
|
||||
ASSERT_NO_THROW(
|
||||
sscl::tests::runNonViralPostingTask(
|
||||
threads.caller(),
|
||||
std::forward<Factory>(factory)));
|
||||
}
|
||||
|
||||
sscl::tests::PostingThreadSet threads;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#define RUN_GROUP_EDGE_SCENARIO(testName, functionName) \
|
||||
TEST_F(GroupEdgeTest, testName) \
|
||||
{ \
|
||||
runScenario( \
|
||||
[]( \
|
||||
std::exception_ptr &exceptionPtr, \
|
||||
std::function<void()> completion) \
|
||||
{ \
|
||||
return functionName(exceptionPtr, std::move(completion)); \
|
||||
}); \
|
||||
}
|
||||
|
||||
RUN_GROUP_EDGE_SCENARIO(
|
||||
MixedSuccessAndFailureAwaitFirstThenAll,
|
||||
mixedSuccessAndFailureAwaitFirstThenAll)
|
||||
RUN_GROUP_EDGE_SCENARIO(
|
||||
SingleMemberAwaitFirstThenAll,
|
||||
singleMemberAwaitFirstThenAll)
|
||||
RUN_GROUP_EDGE_SCENARIO(AllCompleteBeforeCoAwait, allCompleteBeforeCoAwait)
|
||||
RUN_GROUP_EDGE_SCENARIO(AddWhileAwaitAllSuspended, addWhileAwaitAllSuspended)
|
||||
RUN_GROUP_EDGE_SCENARIO(AwaitAllOnlyMixedOutcomes, awaitAllOnlyMixedOutcomes)
|
||||
RUN_GROUP_EDGE_SCENARIO(
|
||||
CheckForAndReThrowGroupExceptions,
|
||||
checkForAndReThrowGroupExceptions)
|
||||
RUN_GROUP_EDGE_SCENARIO(EmptyGroupAwaitAllThrows, emptyGroupAwaitAllThrows)
|
||||
RUN_GROUP_EDGE_SCENARIO(EmptyGroupAwaitFirstThrows, emptyGroupAwaitFirstThrows)
|
||||
RUN_GROUP_EDGE_SCENARIO(WrongAwaitInvokerOrder, wrongAwaitInvokerOrder)
|
||||
RUN_GROUP_EDGE_SCENARIO(DoubleCoAwaitSameAwaitFirst, doubleCoAwaitSameAwaitFirst)
|
||||
RUN_GROUP_EDGE_SCENARIO(DoubleCoAwaitSameAwaitAll, doubleCoAwaitSameAwaitAll)
|
||||
RUN_GROUP_EDGE_SCENARIO(
|
||||
TwoAwaitFirstHandlesSequentially,
|
||||
twoAwaitFirstHandlesSequentially)
|
||||
RUN_GROUP_EDGE_SCENARIO(AddSecondWaveAfterAwaitAll, addSecondWaveAfterAwaitAll)
|
||||
RUN_GROUP_EDGE_SCENARIO(
|
||||
ShortTimerAddedAfterLongStillWinsRace,
|
||||
shortTimerAddedAfterLongStillWinsRace)
|
||||
RUN_GROUP_EDGE_SCENARIO(NonStdExceptionSettlement, nonStdExceptionSettlement)
|
||||
RUN_GROUP_EDGE_SCENARIO(VoidViralMemberInGroup, voidViralMemberInGroup)
|
||||
RUN_GROUP_EDGE_SCENARIO(
|
||||
ReturnValuesRemainReadableAfterAwaitFirst,
|
||||
returnValuesRemainReadableAfterAwaitFirst)
|
||||
|
||||
TEST_F(GroupEdgeTest, NonViralVoidGroupTemplateInstantiates)
|
||||
{
|
||||
GTEST_SKIP()
|
||||
<< "NonViralPostingInvoker does not satisfy Group's awaitable concept.";
|
||||
}
|
||||
|
||||
TEST_F(GroupEdgeTest, EarlyInvokerDestructionIsUnsupported)
|
||||
{
|
||||
GTEST_SKIP()
|
||||
<< "Destroying a member invoker before group settlement completes is undefined.";
|
||||
}
|
||||
|
||||
TEST_F(GroupEdgeTest, OverlappingGroupWaitsAssertInDebug)
|
||||
{
|
||||
GTEST_SKIP()
|
||||
<< "Overlapping group co_await is debug-assert behavior.";
|
||||
}
|
||||
Reference in New Issue
Block a user