From 63532a6ee2fff0ac31130a2106425a3d044a5dbd Mon Sep 17 00:00:00 2001 From: Hayodea Hekol Date: Sun, 14 Jun 2026 11:04:22 -0400 Subject: [PATCH] Resolve device selector on detach and add YuvStimProducer state tests. Detach finds the shared producer via lcameraDev_resolveDeviceSelectorCReq then removes buffers by attach identity; unit tests cover quale guards. Co-authored-by: Cursor --- stimBuffApis/lcameraBuff/lcameraBuff.cpp | 30 +-- .../lcameraBuff/lcameraBuffInternal.h | 1 + stimBuffApis/lcameraBuff/tests/CMakeLists.txt | 2 + .../lcameraBuff/tests/hilSmoCallbacksStub.cpp | 95 +++++++ .../lcameraBuff/tests/hilSmoCallbacksStub.h | 20 ++ .../tests/yuvStimProducer_state_tests.cpp | 244 ++++++++++++++++++ 6 files changed, 374 insertions(+), 18 deletions(-) create mode 100644 stimBuffApis/lcameraBuff/tests/hilSmoCallbacksStub.cpp create mode 100644 stimBuffApis/lcameraBuff/tests/hilSmoCallbacksStub.h create mode 100644 stimBuffApis/lcameraBuff/tests/yuvStimProducer_state_tests.cpp diff --git a/stimBuffApis/lcameraBuff/lcameraBuff.cpp b/stimBuffApis/lcameraBuff/lcameraBuff.cpp index a289ff5..454b7ef 100644 --- a/stimBuffApis/lcameraBuff/lcameraBuff.cpp +++ b/stimBuffApis/lcameraBuff/lcameraBuff.cpp @@ -28,6 +28,7 @@ LcameraDevDllState::LcameraDevDllState() lcameraDev_main(nullptr), lcameraDev_exit(nullptr), lcameraDev_getOrCreateDeviceCReq(nullptr), + lcameraDev_resolveDeviceSelectorCReq(nullptr), lcameraDev_releaseDeviceCReq(nullptr), lcameraDev_configureSessionModeCReq(nullptr) {} @@ -47,23 +48,6 @@ std::shared_ptr findStimProducerByCameraId( return nullptr; } -std::shared_ptr findStimProducerWithAttachedBuffer( - const std::shared_ptr& desc) -{ - for (const std::shared_ptr& producer : - attachedStimulusProducers) - { - assert(producer != nullptr); - if (producer->getAttachedStimulusBufferByAttachIdentity( - desc->deviceIdentifier, desc->qualeIfaceApi)) - { - return producer; - } - } - - return nullptr; -} - bool validateAttachRequest( const std::shared_ptr& spec) { @@ -110,6 +94,11 @@ void loadLcameraDevSymbols() dlsym( lcameraDevDll.dlopenHandle.get(), "lcameraDev_getOrCreateDeviceCReq")); + lcameraDevDll.lcameraDev_resolveDeviceSelectorCReq = reinterpret_cast< + lcameraDev_resolveDeviceSelectorCReqFn *>( + dlsym( + lcameraDevDll.dlopenHandle.get(), + "lcameraDev_resolveDeviceSelectorCReq")); lcameraDevDll.lcameraDev_releaseDeviceCReq = reinterpret_cast< lcameraDev_releaseDeviceCReqFn *>( dlsym( @@ -124,6 +113,7 @@ void loadLcameraDevSymbols() if (!lcameraDevDll.lcameraDev_main || !lcameraDevDll.lcameraDev_exit || !lcameraDevDll.lcameraDev_getOrCreateDeviceCReq + || !lcameraDevDll.lcameraDev_resolveDeviceSelectorCReq || !lcameraDevDll.lcameraDev_releaseDeviceCReq || !lcameraDevDll.lcameraDev_configureSessionModeCReq) { @@ -276,7 +266,11 @@ lcameraBuff_detachDeviceCReq( co_return StimBuffDeviceOpResult{false, desc}; } - auto producer = findStimProducerWithAttachedBuffer(desc); + const lcamera_dev::CameraIdentityRecord resolvedIdentity = + co_await (*lcameraDevDll.lcameraDev_resolveDeviceSelectorCReq)( + desc->deviceSelector); + + auto producer = findStimProducerByCameraId(resolvedIdentity.id); if (!producer) { co_return StimBuffDeviceOpResult{true, desc}; } diff --git a/stimBuffApis/lcameraBuff/lcameraBuffInternal.h b/stimBuffApis/lcameraBuff/lcameraBuffInternal.h index 84ca222..39b7c4e 100644 --- a/stimBuffApis/lcameraBuff/lcameraBuffInternal.h +++ b/stimBuffApis/lcameraBuff/lcameraBuffInternal.h @@ -25,6 +25,7 @@ struct LcameraDevDllState lcameraDev_mainFn *lcameraDev_main; lcameraDev_exitFn *lcameraDev_exit; lcameraDev_getOrCreateDeviceCReqFn *lcameraDev_getOrCreateDeviceCReq; + lcameraDev_resolveDeviceSelectorCReqFn *lcameraDev_resolveDeviceSelectorCReq; lcameraDev_releaseDeviceCReqFn *lcameraDev_releaseDeviceCReq; lcameraDev_configureSessionModeCReqFn *lcameraDev_configureSessionModeCReq; }; diff --git a/stimBuffApis/lcameraBuff/tests/CMakeLists.txt b/stimBuffApis/lcameraBuff/tests/CMakeLists.txt index 1a89dc7..6a24902 100644 --- a/stimBuffApis/lcameraBuff/tests/CMakeLists.txt +++ b/stimBuffApis/lcameraBuff/tests/CMakeLists.txt @@ -2,6 +2,8 @@ add_executable(lcameraBuff_unit_tests lcameraBuffParams_tests.cpp pixelAndColorFormatDecisions_tests.cpp yuvLayoutClassification_tests.cpp + yuvStimProducer_state_tests.cpp + hilSmoCallbacksStub.cpp ) target_include_directories(lcameraBuff_unit_tests PRIVATE diff --git a/stimBuffApis/lcameraBuff/tests/hilSmoCallbacksStub.cpp b/stimBuffApis/lcameraBuff/tests/hilSmoCallbacksStub.cpp new file mode 100644 index 0000000..8c9164d --- /dev/null +++ b/stimBuffApis/lcameraBuff/tests/hilSmoCallbacksStub.cpp @@ -0,0 +1,95 @@ +#include +#include +#include +#include + +namespace lcamera_buff_tests { + +namespace { + +std::optional searchForLibInSmoSearchPathsStub( + const std::string& libraryPath) +{ + (void)libraryPath; + + const char *overridePath = std::getenv("LCAMERADEV_LIB_PATH"); + if (overridePath != nullptr && std::string(overridePath).size() > 0) { + return overridePath; + } + + 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; +} + +const smo::stim_buff::SmoCallbacks hilCallbacks = { + .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, +}; + +} // namespace + +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 smo::stim_buff::SmoCallbacks& hilSmoCallbacksStub() +{ + return hilCallbacks; +} + +} // namespace lcamera_buff_tests diff --git a/stimBuffApis/lcameraBuff/tests/hilSmoCallbacksStub.h b/stimBuffApis/lcameraBuff/tests/hilSmoCallbacksStub.h new file mode 100644 index 0000000..edbef95 --- /dev/null +++ b/stimBuffApis/lcameraBuff/tests/hilSmoCallbacksStub.h @@ -0,0 +1,20 @@ +#ifndef LCAMERA_BUFF_TESTS_HIL_SMO_CALLBACKS_STUB_H +#define LCAMERA_BUFF_TESTS_HIL_SMO_CALLBACKS_STUB_H + +#include +#include +#include +#include + +namespace lcamera_buff_tests { + +const smo::stim_buff::SmoCallbacks& hilSmoCallbacksStub(); + +std::shared_ptr createUseHostPtrBufferStub( + void* hostPtr, + size_t size, + cl_mem_flags flags); + +} // namespace lcamera_buff_tests + +#endif // LCAMERA_BUFF_TESTS_HIL_SMO_CALLBACKS_STUB_H diff --git a/stimBuffApis/lcameraBuff/tests/yuvStimProducer_state_tests.cpp b/stimBuffApis/lcameraBuff/tests/yuvStimProducer_state_tests.cpp new file mode 100644 index 0000000..94a095e --- /dev/null +++ b/stimBuffApis/lcameraBuff/tests/yuvStimProducer_state_tests.cpp @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace smo { +namespace stim_buff { +namespace lcamera_buff { +namespace { + +constexpr const char *usbExternalSelector = + "model-substr:USB;location:external"; +constexpr const char *usbAliasSelector = + "model-substr:USB Video Device;location:external"; + +std::shared_ptr makeLcameraAttachSpec( + 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 = "lcameraBuff"; + spec->provider = "lcameraDev"; + spec->deviceSelector = deviceSelector; + spec->stimBuffApiParams = { + {"v-res", "480p"}, + {"colour-space", "yuv"}, + {"opt-planar", ""}, + }; + return spec; +} + +LcameraBuffParsedParams makeDefaultParsedParams() +{ + LcameraBuffParsedParams parsedParams; + parsedParams.width = 640; + parsedParams.height = 480; + parsedParams.colourSpace = lcamera_dev::LcameraDevColourSpace::Yuv; + parsedParams.fullPlanarIsOptional = true; + return parsedParams; +} + +lcamera_dev::LcameraDevConfiguredCameraMode makeYuyv480ConfiguredMode() +{ + lcamera_dev::LcameraDevConfiguredCameraMode configuredMode; + configuredMode.width = 640; + configuredMode.height = 480; + configuredMode.colourSpace = lcamera_dev::LcameraDevColourSpace::Yuv; + configuredMode.pixelFormatName = "YUYV"; + configuredMode.isFullyPlanar = false; + configuredMode.planeCount = 1; + return configuredMode; +} + +lcamera_dev::CameraIdentityRecord makeTestCameraIdentity() +{ + lcamera_dev::CameraIdentityRecord identity; + identity.id = "unit-test-camera-1"; + identity.model = "UnitTestCam"; + return identity; +} + +class YuvStimProducerQualeIfaceTest +: public ::testing::Test +{ +protected: + void SetUp() override + { + lcameraBuffSmoHooksPtr = &lcamera_buff_tests::hilSmoCallbacksStub(); + } + + void TearDown() override + { + lcameraBuffSmoHooksPtr = nullptr; + } + + boost::asio::io_context ioContext; + const LcameraBuffParsedParams parsedParams = makeDefaultParsedParams(); + const lcamera_dev::LcameraDevConfiguredCameraMode configuredMode = + makeYuyv480ConfiguredMode(); + const lcamera_dev::CameraIdentityRecord cameraIdentity = + makeTestCameraIdentity(); + + YuvStimProducer makeProducer( + const std::shared_ptr& producerSpec) + { + return YuvStimProducer( + producerSpec, + ioContext, + nullptr, + cameraIdentity, + parsedParams, + configuredMode); + } +}; + +TEST_F(YuvStimProducerQualeIfaceTest, GetOrCreateSameSpecReturnsExistingBuffer) +{ + auto producerSpec = makeLcameraAttachSpec( + usbExternalSelector, "colour-yuv-y"); + YuvStimProducer producer = makeProducer(producerSpec); + + const auto firstBuffer = + producer.getOrCreateAttachedStimulusBuffer(producerSpec); + ASSERT_NE(firstBuffer, nullptr); + + const auto secondBuffer = + producer.getOrCreateAttachedStimulusBuffer(producerSpec); + + EXPECT_EQ(firstBuffer, secondBuffer); + EXPECT_EQ(producer.attachedStimulusBuffers.size(), 1u); +} + +TEST_F(YuvStimProducerQualeIfaceTest, + RejectDuplicateQualeWhenAlternateSelectorResolvesToSameSession) +{ + /* One YuvStimProducer == one resolved libcamera camera id / session. + * A second DAP line may spell a different deviceSelector yet resolve to + * that same session via attachToExistingProducer; it must not attach the + * same qualeIface API twice. + */ + auto producerSpec = makeLcameraAttachSpec( + usbExternalSelector, "colour-yuv-y"); + YuvStimProducer producer = makeProducer(producerSpec); + + const auto firstSpec = makeLcameraAttachSpec( + usbExternalSelector, "colour-yuv-y", "cam-y-line0"); + ASSERT_NE(producer.getOrCreateAttachedStimulusBuffer(firstSpec), nullptr); + + const auto duplicateQualeAlternateSelectorSpec = makeLcameraAttachSpec( + usbAliasSelector, "colour-yuv-y", "cam-y-line1"); + + try { + producer.getOrCreateAttachedStimulusBuffer( + duplicateQualeAlternateSelectorSpec); + FAIL() << "Expected std::runtime_error"; + } + catch (const std::runtime_error& exception) + { + sscl::tests::expectExceptionMessageContains( + exception, + "already attached for camera session"); + sscl::tests::expectExceptionMessageContains( + exception, + "colour-yuv-y"); + } + + EXPECT_EQ(producer.attachedStimulusBuffers.size(), 1u); + EXPECT_NE( + firstSpec->deviceSelector, + duplicateQualeAlternateSelectorSpec->deviceSelector); +} + +TEST_F(YuvStimProducerQualeIfaceTest, + AllowDistinctQualeIfacesForAlternateSelectorsOnSameSession) +{ + auto producerSpec = makeLcameraAttachSpec( + usbExternalSelector, "colour-yuv-y"); + YuvStimProducer producer = makeProducer(producerSpec); + + const auto ySpec = makeLcameraAttachSpec( + usbExternalSelector, "colour-yuv-y", "cam-y"); + const auto uSpec = makeLcameraAttachSpec( + usbAliasSelector, "colour-yuv-u", "cam-u"); + const auto vSpec = makeLcameraAttachSpec( + usbExternalSelector, "colour-yuv-v", "cam-v"); + + ASSERT_NE(producer.getOrCreateAttachedStimulusBuffer(ySpec), nullptr); + ASSERT_NE(producer.getOrCreateAttachedStimulusBuffer(uSpec), nullptr); + ASSERT_NE(producer.getOrCreateAttachedStimulusBuffer(vSpec), nullptr); + + EXPECT_EQ(producer.attachedStimulusBuffers.size(), 3u); + EXPECT_TRUE(producer.hasBufferWithQualeIfaceApi("colour-yuv-y")); + EXPECT_TRUE(producer.hasBufferWithQualeIfaceApi("colour-yuv-u")); + EXPECT_TRUE(producer.hasBufferWithQualeIfaceApi("colour-yuv-v")); +} + +TEST_F(YuvStimProducerQualeIfaceTest, + RejectDuplicateQualeAfterYViaSelectorAAndUViaSelectorB) +{ + auto producerSpec = makeLcameraAttachSpec( + usbExternalSelector, "colour-yuv-y"); + YuvStimProducer producer = makeProducer(producerSpec); + + ASSERT_NE(producer.getOrCreateAttachedStimulusBuffer( + makeLcameraAttachSpec(usbExternalSelector, "colour-yuv-y", "cam-y")), + nullptr); + ASSERT_NE(producer.getOrCreateAttachedStimulusBuffer( + makeLcameraAttachSpec(usbAliasSelector, "colour-yuv-u", "cam-u")), + nullptr); + + const auto duplicateYViaAlternateSelector = makeLcameraAttachSpec( + usbAliasSelector, "colour-yuv-y", "cam-y-second-dap-line"); + + EXPECT_THROW( + producer.getOrCreateAttachedStimulusBuffer( + duplicateYViaAlternateSelector), + std::runtime_error); + EXPECT_EQ(producer.attachedStimulusBuffers.size(), 2u); +} + +TEST_F(YuvStimProducerQualeIfaceTest, + FindAttachedBufferByAttachIdentityWithAlternateSelector) +{ + auto producerSpec = makeLcameraAttachSpec( + usbExternalSelector, "colour-yuv-y"); + YuvStimProducer producer = makeProducer(producerSpec); + + const auto uSpec = makeLcameraAttachSpec( + usbExternalSelector, "colour-yuv-u", "cam-u"); + ASSERT_NE(producer.getOrCreateAttachedStimulusBuffer(uSpec), nullptr); + + const auto foundBuffer = + producer.getAttachedStimulusBufferByAttachIdentity( + "cam-u", "colour-yuv-u"); + ASSERT_NE(foundBuffer, nullptr); + EXPECT_EQ( + foundBuffer->deviceAttachmentSpec->deviceSelector, + usbExternalSelector); + + EXPECT_EQ( + producer.getAttachedStimulusBuffer( + makeLcameraAttachSpec( + usbAliasSelector, "colour-yuv-u", "cam-u")), + nullptr); + EXPECT_NE( + producer.getAttachedStimulusBufferByAttachIdentity( + "cam-u", "colour-yuv-u"), + nullptr); +} + +} // namespace +} // namespace lcamera_buff +} // namespace stim_buff +} // namespace smo