#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