diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 753b7b4..a4869b7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(spinscale_test_support STATIC support/threadHarness.cpp + support/probeComponentThread.cpp ) target_include_directories(spinscale_test_support PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/tests/fixtures ) target_link_libraries(spinscale_test_support PUBLIC diff --git a/tests/support/bakedDeviceCatalog.h b/tests/support/bakedDeviceCatalog.h new file mode 100644 index 0000000..e4bdf94 --- /dev/null +++ b/tests/support/bakedDeviceCatalog.h @@ -0,0 +1,71 @@ +#ifndef SPINSCALE_TEST_SUPPORT_BAKED_DEVICE_CATALOG_H +#define SPINSCALE_TEST_SUPPORT_BAKED_DEVICE_CATALOG_H + +#include +#include +#include +#include + +#include + +namespace sscl::tests { + +inline std::vector +profilesForMachine(const char *machineTag) +{ + std::vector matches; + + for (std::size_t i = 0; i < test_fixtures::bakedCameraProfileCount; ++i) + { + const test_fixtures::BakedCameraProfile& profile = + test_fixtures::bakedCameraProfiles[i]; + + if (std::string(profile.machineTag) == machineTag) { + matches.push_back(&profile); + } + } + + return matches; +} + +inline std::optional +findProfileByTag(const char *machineTag, const char *profileTag) +{ + for (std::size_t i = 0; i < test_fixtures::bakedCameraProfileCount; ++i) + { + const test_fixtures::BakedCameraProfile& profile = + test_fixtures::bakedCameraProfiles[i]; + + if (std::string(profile.machineTag) == machineTag + && std::string(profile.profileTag) == profileTag) + { + return &profile; + } + } + + return std::nullopt; +} + +inline std::vector +requiredProfilesForMachine(const char *machineTag) +{ + std::vector matches; + + for (std::size_t i = 0; i < test_fixtures::bakedCameraProfileCount; ++i) + { + const test_fixtures::BakedCameraProfile& profile = + test_fixtures::bakedCameraProfiles[i]; + + if (std::string(profile.machineTag) == machineTag + && profile.requiredOnMachine) + { + matches.push_back(&profile); + } + } + + return matches; +} + +} // namespace sscl::tests + +#endif // SPINSCALE_TEST_SUPPORT_BAKED_DEVICE_CATALOG_H diff --git a/tests/support/exceptionAssertions.h b/tests/support/exceptionAssertions.h new file mode 100644 index 0000000..29e2789 --- /dev/null +++ b/tests/support/exceptionAssertions.h @@ -0,0 +1,63 @@ +#ifndef SPINSCALE_TEST_SUPPORT_EXCEPTION_ASSERTIONS_H +#define SPINSCALE_TEST_SUPPORT_EXCEPTION_ASSERTIONS_H + +#include +#include +#include + +#include + +namespace sscl::tests { + +inline void requireExceptionMessageContains( + const std::exception &exception, + const std::string &expectedSubstring) +{ + const std::string message = exception.what(); + if (message.find(expectedSubstring) == std::string::npos) { + throw std::runtime_error( + "Expected exception message to contain \"" + + expectedSubstring + + "\", got \"" + + message + + "\""); + } +} + +inline void expectExceptionMessageContains( + const std::exception &exception, + const std::string &expectedSubstring) +{ + EXPECT_NO_THROW( + requireExceptionMessageContains(exception, expectedSubstring)); +} + +inline void requireExceptionPtrMessageContains( + const std::exception_ptr &exceptionPtr, + const std::string &expectedSubstring) +{ + try { + std::rethrow_exception(exceptionPtr); + } + catch (const std::exception &exception) { + requireExceptionMessageContains(exception, expectedSubstring); + return; + } + catch (...) { + throw std::runtime_error("Expected std::exception in exception_ptr"); + } +} + +inline void expectExceptionPtrMessageContains( + const std::exception_ptr &exceptionPtr, + const std::string &expectedSubstring) +{ + EXPECT_NO_THROW( + requireExceptionPtrMessageContains( + exceptionPtr, + expectedSubstring)); +} + +} // namespace sscl::tests + +#endif // SPINSCALE_TEST_SUPPORT_EXCEPTION_ASSERTIONS_H diff --git a/tests/support/probeComponentThread.cpp b/tests/support/probeComponentThread.cpp new file mode 100644 index 0000000..a48c514 --- /dev/null +++ b/tests/support/probeComponentThread.cpp @@ -0,0 +1,118 @@ +#include + +#include + +#include + +namespace sscl::tests { + +namespace { + +constexpr sscl::ThreadId PROBE_PUPPETEER_THREAD_ID = 2; + +class ProbeDummyPuppeteerComponent +: public sscl::pptr::PuppeteerComponent +{ +public: + explicit ProbeDummyPuppeteerComponent( + const std::shared_ptr& componentThreadIn) + : sscl::pptr::PuppeteerComponent(componentThreadIn) + {} + + void handleLoopExceptionHook() override + { + std::cerr << "ProbeComponentThreadHarness: puppeteer loop exception\n"; + } +}; + +void probePuppeteerMain( + const sscl::PuppeteerThread::EntryFnArguments& args, + const std::function&)>& work, + std::promise& donePromise) +{ + sscl::PuppeteerThread& thr = args.usableBeforeJolt; + thr.initializeTls(); + sscl::ComponentThread::setPuppeteerThreadId(PROBE_PUPPETEER_THREAD_ID); + + std::shared_ptr thrPtr = + std::static_pointer_cast(thr.shared_from_this()); + sscl::ComponentThread::setPuppeteerThread(thrPtr); + + try { + work(thrPtr); + donePromise.set_value(nullptr); + } + catch (...) { + donePromise.set_value(std::current_exception()); + } + + thr.getIoContext().stop(); +} + +} // namespace + +ProbeComponentThreadHarness::ProbeComponentThreadHarness( + const char *threadName) +: threadName(threadName), + dummyComponent(std::make_shared( + std::shared_ptr())) +{} + +ProbeComponentThreadHarness::~ProbeComponentThreadHarness() = default; + +std::shared_ptr +ProbeComponentThreadHarness::componentThread() const +{ + return lastComponentThread; +} + +void ProbeComponentThreadHarness::runSync( + const std::function&)>& work) +{ + std::promise donePromise; + std::future doneFuture = donePromise.get_future(); + + std::shared_ptr runThread = + std::make_shared( + PROBE_PUPPETEER_THREAD_ID, + threadName, + [&work, &donePromise]( + const sscl::PuppeteerThread::EntryFnArguments& args) + { + probePuppeteerMain(args, work, donePromise); + }, + *dummyComponent, + nullptr); + + dummyComponent->thread = runThread; + lastComponentThread = runThread; + runThread->thread.join(); + + std::exception_ptr probeException = doneFuture.get(); + if (probeException) { + std::rethrow_exception(probeException); + } +} + +void runNonViralNurseryOnComponentThread( + const std::shared_ptr& componentThread, + std::function invokerFactory, + std::chrono::milliseconds timeout) +{ + (void)timeout; + + sscl::co::NonViralTaskNursery nursery; + nursery.openAdmission(); + nursery.launch( + [&invokerFactory](sscl::co::NonViralTaskNursery::Slot::Lease& lease) + { + return invokerFactory(lease); + }); + nursery.closeAdmission(); + nursery.syncAwaitAllSettlements(componentThread->getIoContext()); +} + +} // namespace sscl::tests diff --git a/tests/support/probeComponentThread.h b/tests/support/probeComponentThread.h new file mode 100644 index 0000000..cabc9d5 --- /dev/null +++ b/tests/support/probeComponentThread.h @@ -0,0 +1,66 @@ +#ifndef SPINSCALE_TEST_SUPPORT_PROBE_COMPONENT_THREAD_H +#define SPINSCALE_TEST_SUPPORT_PROBE_COMPONENT_THREAD_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace sscl::tests { + +constexpr std::chrono::milliseconds defaultProbeTaskTimeout{10000}; + +void runNonViralNurseryOnComponentThread( + const std::shared_ptr& componentThread, + std::function invokerFactory, + std::chrono::milliseconds timeout = defaultProbeTaskTimeout); + +class ProbeComponentThreadHarness +{ +public: + explicit ProbeComponentThreadHarness( + const char *threadName = "spinscale-probe"); + ~ProbeComponentThreadHarness(); + + ProbeComponentThreadHarness(const ProbeComponentThreadHarness &) = delete; + ProbeComponentThreadHarness &operator=( + const ProbeComponentThreadHarness &) = delete; + + std::shared_ptr componentThread() const; + + void runSync( + const std::function&)>& work); + + template + void runNonViralNurseryTask( + InvokerFactory &&invokerFactory, + std::chrono::milliseconds timeout = defaultProbeTaskTimeout) + { + runSync( + [this, &invokerFactory, timeout]( + const std::shared_ptr& componentThread) + { + sscl::tests::runNonViralNurseryOnComponentThread( + componentThread, + std::forward(invokerFactory), + timeout); + }); + } + +private: + std::string threadName; + std::shared_ptr dummyComponent; + std::shared_ptr lastComponentThread; +}; + +} // namespace sscl::tests + +#endif // SPINSCALE_TEST_SUPPORT_PROBE_COMPONENT_THREAD_H