From 809861be2b2954cbe12c300c98124c57ba78fb37 Mon Sep 17 00:00:00 2001 From: Hayodea Hekol Date: Sun, 14 Jun 2026 11:02:18 -0400 Subject: [PATCH] StimulusProducer: add duplicate-quale guard and attach-identity buffer lookup. Provide ensureNoDuplicateQualeIface and getAttachedStimulusBufferByAttachIdentity so session-scoped stimBuff plugins can reject duplicate quales and detach by stable DAP line identity rather than full spec equality. Co-authored-by: Cursor --- .../attachmentSupport/stimulusProducer.cpp | 38 +++ .../attachmentSupport/tests/CMakeLists.txt | 26 ++ .../stimulusProducerQualeIface_tests.cpp | 281 ++++++++++++++++++ include/user/stimulusProducer.h | 8 + 4 files changed, 353 insertions(+) create mode 100644 commonLibs/attachmentSupport/tests/stimulusProducerQualeIface_tests.cpp diff --git a/commonLibs/attachmentSupport/stimulusProducer.cpp b/commonLibs/attachmentSupport/stimulusProducer.cpp index e6a825e..7f53d24 100644 --- a/commonLibs/attachmentSupport/stimulusProducer.cpp +++ b/commonLibs/attachmentSupport/stimulusProducer.cpp @@ -82,6 +82,32 @@ std::shared_ptr StimulusProducer::getAttachedStimulusBuffer( return nullptr; } +std::shared_ptr StimulusProducer::getAttachedStimulusBufferByAttachIdentity( + const std::string& deviceIdentifier, + const std::string& qualeIfaceApi) const +{ + for (const auto& buffer : attachedStimulusBuffers) + { + if (!buffer || !buffer->deviceAttachmentSpec) + { + throw std::runtime_error( + "StimulusProducer::getAttachedStimulusBufferByAttachIdentity: " + "encountered null buffer or null deviceAttachmentSpec in " + "attachedStimulusBuffers (should never happen)"); + } + + if (buffer->deviceAttachmentSpec->deviceIdentifier != deviceIdentifier) + { continue; } + + if (buffer->deviceAttachmentSpec->qualeIfaceApi != qualeIfaceApi) + { continue; } + + return buffer; + } + + return nullptr; +} + bool StimulusProducer::hasBufferWithQualeIfaceApi( const std::string& qualeIfaceApi) const { @@ -104,6 +130,18 @@ bool StimulusProducer::hasBufferWithQualeIfaceApi( return false; } +void StimulusProducer::ensureNoDuplicateQualeIface( + const std::string& qualeIfaceApi) const +{ + if (!hasBufferWithQualeIfaceApi(qualeIfaceApi)) { + return; + } + + throw std::runtime_error( + "duplicate qualeIface '" + qualeIfaceApi + + "' for this producer session"); +} + bool StimulusProducer::addAttachedStimulusBufferIfNotExists( const std::shared_ptr& buffer) { diff --git a/commonLibs/attachmentSupport/tests/CMakeLists.txt b/commonLibs/attachmentSupport/tests/CMakeLists.txt index 721aa67..8d6f9c4 100644 --- a/commonLibs/attachmentSupport/tests/CMakeLists.txt +++ b/commonLibs/attachmentSupport/tests/CMakeLists.txt @@ -41,3 +41,29 @@ add_dependencies(intrinThresholdParams_tests gtest_main) add_test( NAME intrinThresholdParams_tests COMMAND intrinThresholdParams_tests) + +add_executable(stimulusProducerQualeIface_tests + stimulusProducerQualeIface_tests.cpp +) + +target_include_directories(stimulusProducerQualeIface_tests PRIVATE + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include + ${CMAKE_SOURCE_DIR}/libspinscale/tests + ${CMAKE_SOURCE_DIR}/smocore/include +) + +target_link_libraries(stimulusProducerQualeIface_tests + gtest_main + attachmentSupport + spinscale + spinscale_test_support + ${Boost_LIBRARIES} + ${OPENCL_LIBRARIES} +) + +add_dependencies(stimulusProducerQualeIface_tests gtest_main) + +add_test( + NAME stimulusProducerQualeIface_tests + COMMAND stimulusProducerQualeIface_tests) diff --git a/commonLibs/attachmentSupport/tests/stimulusProducerQualeIface_tests.cpp b/commonLibs/attachmentSupport/tests/stimulusProducerQualeIface_tests.cpp new file mode 100644 index 0000000..055023c --- /dev/null +++ b/commonLibs/attachmentSupport/tests/stimulusProducerQualeIface_tests.cpp @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define CL_TARGET_OPENCL_VERSION 120 +#include +#include +#include +#include +#include + +namespace smo { +namespace stim_buff { +namespace { + +class TestStimulusProducer +: public StimulusProducer +{ +public: + using StimulusProducer::StimulusProducer; + + std::shared_ptr getOrCreateAttachedStimulusBuffer( + const std::shared_ptr& + deviceAttachmentSpec) override + { + (void)deviceAttachmentSpec; + throw std::logic_error("not used in these unit tests"); + } + + bool exportsQualeIfaceApi(const std::string& qualeIfaceApi) const override + { + (void)qualeIfaceApi; + return true; + } + +protected: + sscl::co::ViralNonPostingInvoker stimFrameProductionTimesliceCInd( + sscl::SyncCancelerForAsyncWork& canceler) override + { + (void)canceler; + co_return; + } +}; + +std::optional searchForLibInSmoSearchPathsStub( + const std::string& libraryPath) +{ + (void)libraryPath; + return std::nullopt; +} + +std::shared_ptr componentThreadGetSelfStub() +{ + return sscl::ComponentThread::getSelf(); +} + +OptionParser& optionParserGetOptionsStub() +{ + return OptionParser::getOptions(); +} + +void releaseUseHostPtrBufferStub( + std::shared_ptr buffer) +{ + (void)buffer; +} + +std::shared_ptr computeManagerGetDeviceStub() +{ + return nullptr; +} + +void computeManagerReleaseDeviceStub( + std::shared_ptr device) +{ + (void)device; +} + +std::shared_ptr +comparatorManagerGetComparatorTypeStub(smo::cologex::ComparatorTypeId typeId) +{ + (void)typeId; + return nullptr; +} + +std::unique_ptr comparatorGetNewInstanceStub( + const std::shared_ptr& comparatorType) +{ + (void)comparatorType; + return nullptr; +} + +std::shared_ptr createUseHostPtrBufferStub( + void* hostPtr, + size_t size, + cl_mem_flags flags) +{ + static const std::vector> + emptyDevices; + return std::make_shared( + hostPtr, size, flags, emptyDevices); +} + +const SmoCallbacks kTestSmoCallbacks = { + .searchForLibInSmoSearchPaths = searchForLibInSmoSearchPathsStub, + .ComponentThread_getSelf = componentThreadGetSelfStub, + .OptionParser_getOptions = optionParserGetOptionsStub, + .ComputeManager_createUseHostPtrBuffer = createUseHostPtrBufferStub, + .ComputeManager_releaseUseHostPtrBuffer = releaseUseHostPtrBufferStub, + .ComputeManager_getDevice = computeManagerGetDeviceStub, + .ComputeManager_releaseDevice = computeManagerReleaseDeviceStub, + .ComparatorManager_getComparatorType = + comparatorManagerGetComparatorTypeStub, + .Comparator_getNewInstance = comparatorGetNewInstanceStub, +}; + +StagingBuffer::IOEngineConstraints testBufferConstraints() +{ + const size_t pageSize = static_cast(sysconf(_SC_PAGE_SIZE)); + const size_t channelByteSize = pageSize; + + return StagingBuffer::IOEngineConstraints( + 1, + channelByteSize, + pageSize, + pageSize); +} + +std::shared_ptr makeAttachSpec( + const std::string& deviceSelector, + const std::string& qualeIfaceApi, + const std::string& deviceIdentifier = "cam0") +{ + auto spec = std::make_shared(); + spec->deviceIdentifier = deviceIdentifier; + spec->sensorType = 'e'; + spec->qualeIfaceApi = qualeIfaceApi; + spec->stimBuffApi = "testBuff"; + spec->provider = "testProvider"; + spec->deviceSelector = deviceSelector; + return spec; +} + +std::shared_ptr makeAttachedTestBuffer( + TestStimulusProducer& producer, + const std::shared_ptr& attachSpec) +{ + const StagingBuffer::IOEngineConstraints constraints = + testBufferConstraints(); + + return std::make_shared( + producer, + attachSpec, + CONFIG_STIMBUFF_FRAME_PERIOD_MS, + constraints, + constraints, + kTestSmoCallbacks, + CL_MEM_READ_WRITE); +} + +class StimulusProducerQualeIfaceTest +: public ::testing::Test +{ +protected: + boost::asio::io_context ioContext; + std::shared_ptr producerSpec = + makeAttachSpec("model-substr:USB;location:external", "colour-yuv-y"); + TestStimulusProducer producer{producerSpec, ioContext}; +}; + +TEST_F(StimulusProducerQualeIfaceTest, HasBufferWithQualeIfaceApiDetectsChannel) +{ + EXPECT_FALSE(producer.hasBufferWithQualeIfaceApi("colour-yuv-y")); + + const auto attachSpec = makeAttachSpec( + "model-substr:USB;location:external", "colour-yuv-y", "cam-y"); + const auto buffer = makeAttachedTestBuffer(producer, attachSpec); + ASSERT_TRUE(producer.addAttachedStimulusBufferIfNotExists(buffer)); + + EXPECT_TRUE(producer.hasBufferWithQualeIfaceApi("colour-yuv-y")); + EXPECT_FALSE(producer.hasBufferWithQualeIfaceApi("colour-yuv-u")); +} + +TEST_F(StimulusProducerQualeIfaceTest, + AddAttachedStimulusBufferIfNotExistsAllowsSameQualeDifferentSelector) +{ + const auto firstSpec = makeAttachSpec( + "model-substr:USB;location:external", "colour-yuv-y", "cam-y-0"); + const auto firstBuffer = makeAttachedTestBuffer(producer, firstSpec); + ASSERT_TRUE(producer.addAttachedStimulusBufferIfNotExists(firstBuffer)); + + const auto secondSpec = makeAttachSpec( + "model-substr:USB Video Device;location:external", + "colour-yuv-y", + "cam-y-1"); + const auto secondBuffer = makeAttachedTestBuffer(producer, secondSpec); + + EXPECT_TRUE(producer.addAttachedStimulusBufferIfNotExists(secondBuffer)); + EXPECT_EQ(producer.attachedStimulusBuffers.size(), 2u); + EXPECT_TRUE(producer.hasBufferWithQualeIfaceApi("colour-yuv-y")); + EXPECT_NE(firstSpec->deviceSelector, secondSpec->deviceSelector); +} + +TEST_F(StimulusProducerQualeIfaceTest, + EnsureNoDuplicateQualeIfaceRejectsAlternateSelector) +{ + const auto firstSpec = makeAttachSpec( + "model-substr:USB;location:external", "colour-yuv-y", "cam-y-0"); + const auto firstBuffer = makeAttachedTestBuffer(producer, firstSpec); + ASSERT_TRUE(producer.addAttachedStimulusBufferIfNotExists(firstBuffer)); + + const auto duplicateQualeSpec = makeAttachSpec( + "model-substr:USB Video Device;location:external", + "colour-yuv-y", + "cam-y-1"); + + try { + producer.ensureNoDuplicateQualeIface( + duplicateQualeSpec->qualeIfaceApi); + FAIL() << "Expected std::runtime_error"; + } + catch (const std::runtime_error& exception) + { + sscl::tests::expectExceptionMessageContains( + exception, + "duplicate qualeIface 'colour-yuv-y'"); + } +} + +TEST_F(StimulusProducerQualeIfaceTest, + EnsureNoDuplicateQualeIfaceAllowsDistinctChannels) +{ + const auto ySpec = makeAttachSpec( + "model-substr:USB;location:external", "colour-yuv-y", "cam-y"); + ASSERT_TRUE(producer.addAttachedStimulusBufferIfNotExists( + makeAttachedTestBuffer(producer, ySpec))); + + EXPECT_NO_THROW( + producer.ensureNoDuplicateQualeIface("colour-yuv-u")); + EXPECT_NO_THROW( + producer.ensureNoDuplicateQualeIface("colour-yuv-v")); +} + +TEST_F(StimulusProducerQualeIfaceTest, + GetAttachedStimulusBufferByAttachIdentityIgnoresSelectorString) +{ + const auto attachedSpec = makeAttachSpec( + "model-substr:USB;location:external", "colour-yuv-u", "cam-u"); + ASSERT_TRUE(producer.addAttachedStimulusBufferIfNotExists( + makeAttachedTestBuffer(producer, attachedSpec))); + + const auto foundBuffer = + producer.getAttachedStimulusBufferByAttachIdentity( + "cam-u", "colour-yuv-u"); + ASSERT_NE(foundBuffer, nullptr); + EXPECT_EQ(foundBuffer, producer.attachedStimulusBuffers.front()); + EXPECT_NE( + foundBuffer->deviceAttachmentSpec->deviceSelector, + "model-substr:USB Video Device;location:external"); + + EXPECT_EQ( + producer.getAttachedStimulusBufferByAttachIdentity( + "cam-u", "colour-yuv-y"), + nullptr); + EXPECT_EQ( + producer.getAttachedStimulusBufferByAttachIdentity( + "cam-y", "colour-yuv-u"), + nullptr); +} + +} // namespace +} // namespace stim_buff +} // namespace smo diff --git a/include/user/stimulusProducer.h b/include/user/stimulusProducer.h index 14ebe65..0723d34 100644 --- a/include/user/stimulusProducer.h +++ b/include/user/stimulusProducer.h @@ -56,6 +56,10 @@ public: virtual std::shared_ptr getAttachedStimulusBuffer( const std::shared_ptr& spec) const; + std::shared_ptr getAttachedStimulusBufferByAttachIdentity( + const std::string& deviceIdentifier, + const std::string& qualeIfaceApi) const; + virtual std::shared_ptr getOrCreateAttachedStimulusBuffer( const std::shared_ptr &deviceAttachmentSpec) = 0; @@ -72,6 +76,10 @@ public: // Check if any attached buffer has the specified qualeIfaceApi bool hasBufferWithQualeIfaceApi(const std::string& qualeIfaceApi) const; + /** Reject a second buffer for the same qualeIface on this producer session. */ + void ensureNoDuplicateQualeIface( + const std::string& qualeIfaceApi) const; + protected: // Virtual functions for derived classes to override virtual int getStopDelayMs() const