Adversarial review on test porting plan

This commit is contained in:
2026-06-13 17:59:06 -04:00
parent a29c779f6e
commit 2f31e9a034
7 changed files with 593 additions and 173 deletions
+38
View File
@@ -0,0 +1,38 @@
#ifndef SPINSCALE_TEST_SUPPORT_COROUTINE_DRIVER_H
#define SPINSCALE_TEST_SUPPORT_COROUTINE_DRIVER_H
#include <exception>
#include <boost/asio/io_context.hpp>
#include <support/threadHarness.h>
namespace sscl::tests {
class CoroutineDriver
{
public:
template <typename Invoker>
static auto completedReturnValue(Invoker &invoker)
{
if (invoker.completedReturnValues().myExceptionPtr) {
std::rethrow_exception(
invoker.completedReturnValues().myExceptionPtr);
}
return invoker.completedReturnValues().myReturnValue;
}
template <typename Invoker>
static auto pumpUntilIdleAndReturnValue(
boost::asio::io_context &ioContext,
Invoker &invoker)
{
IoContextPump::pumpUntilIdle(ioContext);
return completedReturnValue(invoker);
}
};
} // namespace sscl::tests
#endif // SPINSCALE_TEST_SUPPORT_COROUTINE_DRIVER_H
+108 -35
View File
@@ -2,6 +2,7 @@
#define SPINSCALE_TEST_SUPPORT_GROUP_ASSERTIONS_H
#include <exception>
#include <stdexcept>
#include <string>
#include <gtest/gtest.h>
@@ -21,12 +22,31 @@ int completedIntValue(Invoker &invoker)
return invoker.completedReturnValues().myReturnValue;
}
inline void expectCompletedSettlement(
inline void requireCompletedSettlement(
const sscl::co::Group::SettlementDescriptor &descriptor)
{
EXPECT_EQ(
descriptor.type,
sscl::co::Group::SettlementDescriptor::TypeE::COMPLETED);
if (descriptor.type !=
sscl::co::Group::SettlementDescriptor::TypeE::COMPLETED)
{
throw std::runtime_error("Expected completed settlement");
}
}
template <typename Invoker>
void requireCompletedIntSettlement(
const sscl::co::Group::SettlementDescriptor &descriptor,
int expectedValue)
{
requireCompletedSettlement(descriptor);
const int actualValue = completedIntValue(descriptor.invokerAs<Invoker>());
if (actualValue != expectedValue) {
throw std::runtime_error(
"Expected completed settlement value "
+ std::to_string(expectedValue)
+ ", got "
+ std::to_string(actualValue));
}
}
template <typename Invoker>
@@ -34,39 +54,85 @@ void expectCompletedIntSettlement(
const sscl::co::Group::SettlementDescriptor &descriptor,
int expectedValue)
{
ASSERT_EQ(
descriptor.type,
sscl::co::Group::SettlementDescriptor::TypeE::COMPLETED);
EXPECT_EQ(completedIntValue(descriptor.invokerAs<Invoker>()), expectedValue);
EXPECT_NO_THROW(
requireCompletedIntSettlement<Invoker>(
descriptor,
expectedValue));
}
inline void expectCompletedSettlement(
const sscl::co::Group::SettlementDescriptor &descriptor)
{
EXPECT_NO_THROW(requireCompletedSettlement(descriptor));
}
inline void requireExceptionSettlement(
const sscl::co::Group::SettlementDescriptor &descriptor)
{
if (descriptor.type !=
sscl::co::Group::SettlementDescriptor::TypeE::EXCEPTION_THROWN)
{
throw std::runtime_error("Expected exception settlement");
}
if (!descriptor.calleeException) {
throw std::runtime_error("Expected exception pointer in settlement");
}
}
inline void expectExceptionSettlement(
const sscl::co::Group::SettlementDescriptor &descriptor)
{
EXPECT_EQ(
descriptor.type,
sscl::co::Group::SettlementDescriptor::TypeE::EXCEPTION_THROWN);
EXPECT_TRUE(descriptor.calleeException != nullptr);
EXPECT_NO_THROW(requireExceptionSettlement(descriptor));
}
inline void expectRuntimeErrorSettlement(
inline void requireRuntimeErrorSettlement(
const sscl::co::Group::SettlementDescriptor &descriptor,
const std::string &expectedMessage)
{
ASSERT_EQ(
descriptor.type,
sscl::co::Group::SettlementDescriptor::TypeE::EXCEPTION_THROWN);
ASSERT_TRUE(descriptor.calleeException != nullptr);
requireExceptionSettlement(descriptor);
try {
std::rethrow_exception(descriptor.calleeException);
}
catch (const std::runtime_error &runtimeError) {
EXPECT_EQ(std::string(runtimeError.what()), expectedMessage);
const std::string actualMessage = runtimeError.what();
if (actualMessage != expectedMessage) {
throw std::runtime_error(
"Expected runtime_error settlement message \""
+ expectedMessage
+ "\", got \""
+ actualMessage
+ "\"");
}
return;
}
catch (...) {
FAIL() << "Expected std::runtime_error settlement.";
throw std::runtime_error("Expected std::runtime_error settlement");
}
}
inline void requireIntExceptionSettlement(
const sscl::co::Group::SettlementDescriptor &descriptor,
int expectedValue)
{
requireExceptionSettlement(descriptor);
try {
std::rethrow_exception(descriptor.calleeException);
}
catch (int caughtValue) {
if (caughtValue != expectedValue) {
throw std::runtime_error(
"Expected int exception settlement value "
+ std::to_string(expectedValue)
+ ", got "
+ std::to_string(caughtValue));
}
return;
}
catch (...) {
throw std::runtime_error("Expected int exception settlement");
}
}
@@ -74,29 +140,36 @@ inline void expectIntExceptionSettlement(
const sscl::co::Group::SettlementDescriptor &descriptor,
int expectedValue)
{
ASSERT_EQ(
descriptor.type,
sscl::co::Group::SettlementDescriptor::TypeE::EXCEPTION_THROWN);
ASSERT_TRUE(descriptor.calleeException != nullptr);
EXPECT_NO_THROW(
requireIntExceptionSettlement(
descriptor,
expectedValue));
}
try {
std::rethrow_exception(descriptor.calleeException);
}
catch (int caughtValue) {
EXPECT_EQ(caughtValue, expectedValue);
return;
}
catch (...) {
FAIL() << "Expected int exception settlement.";
inline void expectRuntimeErrorSettlement(
const sscl::co::Group::SettlementDescriptor &descriptor,
const std::string &expectedMessage)
{
EXPECT_NO_THROW(
requireRuntimeErrorSettlement(
descriptor,
expectedMessage));
}
inline void requireEmptyGroupError(
const std::runtime_error &runtimeError)
{
constexpr const char *expectedMessage =
"co_await: Group has no member invokers; call add() before awaiting";
if (std::string(runtimeError.what()) != expectedMessage) {
throw std::runtime_error("Unexpected empty group error message");
}
}
inline void expectEmptyGroupError(
const std::runtime_error &runtimeError)
{
constexpr const char *expectedMessage =
"co_await: Group has no member invokers; call add() before awaiting";
EXPECT_EQ(std::string(runtimeError.what()), expectedMessage);
EXPECT_NO_THROW(requireEmptyGroupError(runtimeError));
}
} // namespace sscl::tests
+54 -12
View File
@@ -217,13 +217,32 @@ void ThreadRegistry::registerThread(
DedicatedIoThread &thread)
{
std::lock_guard<std::mutex> guard(registryMutex());
threadsByRole()[role] = &thread;
auto [iterator, inserted] = threadsByRole().emplace(role, &thread);
if (!inserted) {
throw std::runtime_error(
"Test thread role already registered for " + threadRoleName(role));
}
}
void ThreadRegistry::unregisterThread(PostingThreadRole role)
void ThreadRegistry::unregisterThread(
PostingThreadRole role,
DedicatedIoThread &expectedThread)
{
std::lock_guard<std::mutex> guard(registryMutex());
threadsByRole().erase(role);
auto iterator = threadsByRole().find(role);
if (iterator == threadsByRole().end()) {
return;
}
if (iterator->second != &expectedThread) {
throw std::runtime_error(
"Test thread role registered to a different thread for "
+ threadRoleName(role));
}
threadsByRole().erase(iterator);
}
boost::asio::io_context &ThreadRegistry::ioContext(PostingThreadRole role)
@@ -272,6 +291,20 @@ PostingThreadSet::PostingThreadSet()
bodyThread(PostingThreadRole::BODY),
worldThread(PostingThreadRole::WORLD),
legThread(PostingThreadRole::LEG)
{
previousPuppeteerThread = sscl::ComponentThread::getPptr();
previousPuppeteerThreadId = sscl::pptr::puppeteerThreadId;
registerAllThreads();
installCallerAsPuppeteer();
}
PostingThreadSet::~PostingThreadSet()
{
restorePreviousPuppeteer();
unregisterAllThreads();
}
void PostingThreadSet::registerAllThreads()
{
ThreadRegistry::registerThread(PostingThreadRole::CALLER, callerThread);
ThreadRegistry::registerThread(PostingThreadRole::CALLEE, calleeThread);
@@ -279,22 +312,31 @@ PostingThreadSet::PostingThreadSet()
ThreadRegistry::registerThread(PostingThreadRole::BODY, bodyThread);
ThreadRegistry::registerThread(PostingThreadRole::WORLD, worldThread);
ThreadRegistry::registerThread(PostingThreadRole::LEG, legThread);
}
void PostingThreadSet::unregisterAllThreads()
{
ThreadRegistry::unregisterThread(PostingThreadRole::CALLER, callerThread);
ThreadRegistry::unregisterThread(PostingThreadRole::CALLEE, calleeThread);
ThreadRegistry::unregisterThread(
PostingThreadRole::ALTERNATE,
alternateThread);
ThreadRegistry::unregisterThread(PostingThreadRole::BODY, bodyThread);
ThreadRegistry::unregisterThread(PostingThreadRole::WORLD, worldThread);
ThreadRegistry::unregisterThread(PostingThreadRole::LEG, legThread);
}
void PostingThreadSet::installCallerAsPuppeteer()
{
sscl::ComponentThread::setPuppeteerThreadId(
static_cast<sscl::ThreadId>(PostingThreadRole::CALLER));
sscl::ComponentThread::setPuppeteerThread(callerThread.componentThread());
}
PostingThreadSet::~PostingThreadSet()
void PostingThreadSet::restorePreviousPuppeteer()
{
ThreadRegistry::unregisterThread(PostingThreadRole::CALLER);
ThreadRegistry::unregisterThread(PostingThreadRole::CALLEE);
ThreadRegistry::unregisterThread(PostingThreadRole::ALTERNATE);
ThreadRegistry::unregisterThread(PostingThreadRole::BODY);
ThreadRegistry::unregisterThread(PostingThreadRole::WORLD);
ThreadRegistry::unregisterThread(PostingThreadRole::LEG);
sscl::ComponentThread::setPuppeteerThread(nullptr);
sscl::ComponentThread::setPuppeteerThreadId(previousPuppeteerThreadId);
sscl::ComponentThread::setPuppeteerThread(previousPuppeteerThread);
}
DedicatedIoThread &PostingThreadSet::thread(PostingThreadRole role)
+17 -1
View File
@@ -180,7 +180,9 @@ public:
static void registerThread(
PostingThreadRole role,
DedicatedIoThread &thread);
static void unregisterThread(PostingThreadRole role);
static void unregisterThread(
PostingThreadRole role,
DedicatedIoThread &expectedThread);
static boost::asio::io_context &ioContext(PostingThreadRole role);
static std::thread::id osThreadId(PostingThreadRole role);
@@ -240,14 +242,28 @@ public:
DedicatedIoThread &leg();
private:
void registerAllThreads();
void unregisterAllThreads();
void installCallerAsPuppeteer();
void restorePreviousPuppeteer();
DedicatedIoThread callerThread;
DedicatedIoThread calleeThread;
DedicatedIoThread alternateThread;
DedicatedIoThread bodyThread;
DedicatedIoThread worldThread;
DedicatedIoThread legThread;
std::shared_ptr<sscl::PuppeteerThread> previousPuppeteerThread;
sscl::ThreadId previousPuppeteerThreadId = 0;
};
template <typename Function>
auto RunOnThread(DedicatedIoThread &thread, Function &&function)
-> std::invoke_result_t<Function &>
{
return thread.runSync(std::forward<Function>(function));
}
class CrossThreadTrace
{
public: