#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace lcamera_buff_tests { namespace { constexpr const char *hilEnvVar = "LCAMERADEV_HIL"; constexpr const char *machineEnvVar = "LCAMERADEV_MACHINE"; constexpr const char *defaultMachineTag = "dell-laptop"; bool hilTestsEnabled() { const char *value = std::getenv(hilEnvVar); return value != nullptr && std::string(value) == "1"; } std::string machineTagFromEnvironment() { const char *value = std::getenv(machineEnvVar); if (value != nullptr && std::string(value).size() > 0) { return value; } return defaultMachineTag; } std::shared_ptr makeAttachSpec( const test_fixtures::BakedCameraProfile *profile, const char *qualeIfaceApi) { auto spec = std::make_shared(); spec->deviceIdentifier = "cam0"; spec->sensorType = 'e'; spec->qualeIfaceApi = qualeIfaceApi; spec->stimBuffApi = "lcameraBuff"; spec->provider = "lcameraDev"; spec->deviceSelector = profile->exampleSelector; spec->stimBuffApiParams = { {"v-res", "480p"}, {"colour-space", "yuv"}, {"opt-planar", ""}, }; return spec; } std::shared_ptr makeDetachSpec( const test_fixtures::BakedCameraProfile *profile, const char *qualeIfaceApi, const char *deviceSelectorOverride) { auto spec = makeAttachSpec(profile, qualeIfaceApi); spec->deviceSelector = deviceSelectorOverride; return spec; } sscl::co::NonViralNonPostingInvoker initializeBuffCInd( std::exception_ptr& exceptionStorage, std::function callerLambda) { (void)exceptionStorage; (void)callerLambda; co_await smo::stim_buff::lcamera_buff::lcameraBuff_initializeCInd(); co_return; } sscl::co::NonViralNonPostingInvoker finalizeBuffCInd( std::exception_ptr& exceptionStorage, std::function callerLambda) { (void)exceptionStorage; (void)callerLambda; co_await smo::stim_buff::lcamera_buff::lcameraBuff_finalizeCInd(); co_return; } sscl::co::NonViralNonPostingInvoker attachDeviceCInd( std::exception_ptr& exceptionStorage, std::function callerLambda, const std::shared_ptr& spec, const std::shared_ptr& componentThread, smo::stim_buff::StimBuffDeviceOpResult& result) { (void)exceptionStorage; (void)callerLambda; result = co_await smo::stim_buff::lcamera_buff::lcameraBuff_attachDeviceCReq( sscl::co::ExplicitPostTarget(componentThread->getIoContext()), spec, componentThread); co_return; } sscl::co::NonViralNonPostingInvoker detachDeviceCInd( std::exception_ptr& exceptionStorage, std::function callerLambda, const std::shared_ptr& spec, smo::stim_buff::StimBuffDeviceOpResult& result) { (void)exceptionStorage; (void)callerLambda; result = co_await smo::stim_buff::lcamera_buff::lcameraBuff_detachDeviceCReq( sscl::co::ExplicitPostTarget( smo::stim_buff::lcamera_buff::lcameraBuffThreadingModelDesc .componentThread->getIoContext()), spec); co_return; } void runOnProbeThread( const std::function&)>& work) { sscl::tests::ProbeComponentThreadHarness harness("lcameraBuff-configure-hil"); harness.runSync(work); } class LcameraBuffConfigureHilTest : public ::testing::Test { protected: void SetUp() override { if (!hilTestsEnabled()) { GTEST_SKIP() << "Set " << hilEnvVar << "=1 to run hardware tests"; } machineTag = machineTagFromEnvironment(); requiredProfiles = sscl::tests::requiredProfilesForMachine(machineTag.c_str()); if (requiredProfiles.empty()) { GTEST_SKIP() << "No baked profiles for machine tag " << machineTag; } const test_fixtures::BakedCameraProfile *profile = findProfile("usb_hdmi_camera"); if (!profile) { GTEST_SKIP() << "usb_hdmi_camera profile not available"; } usbProfile = profile; } const test_fixtures::BakedCameraProfile *findProfile( const char *profileTag) const { for (const test_fixtures::BakedCameraProfile *profile : requiredProfiles) { if (std::string(profile->profileTag) == profileTag) { return profile; } } return nullptr; } std::string machineTag; std::vector requiredProfiles; const test_fixtures::BakedCameraProfile *usbProfile = nullptr; }; TEST_F(LcameraBuffConfigureHilTest, AttachThreeYuvChannelsWithOptPlanar480p) { const char *libPathEnv = std::getenv("LCAMERADEV_LIB_PATH"); if (libPathEnv == nullptr || std::string(libPathEnv).empty()) { GTEST_SKIP() << "Set LCAMERADEV_LIB_PATH to liblcameraDev.so for HIL attach"; } runOnProbeThread( [this](const std::shared_ptr& componentThread) { smo::stim_buff::lcamera_buff::lcameraBuffSmoHooksPtr = &hilSmoCallbacksStub(); smo::stim_buff::lcamera_buff::lcameraBuffThreadingModelDesc .componentThread = componentThread; sscl::tests::runNonViralNurseryOnComponentThread( componentThread, [](sscl::co::NonViralTaskNursery::Slot::Lease& lease) { return initializeBuffCInd( lease.getExceptionStorage(), lease.getCallerLambda()); }); std::shared_ptr producer; size_t expectedBufferCount = 0; for (const char *qualeIfaceApi : {"colour-yuv-y", "colour-yuv-u", "colour-yuv-v"}) { const std::shared_ptr spec = makeAttachSpec(usbProfile, qualeIfaceApi); smo::stim_buff::StimBuffDeviceOpResult attachResult; sscl::tests::runNonViralNurseryOnComponentThread( componentThread, [&spec, &componentThread, &attachResult]( sscl::co::NonViralTaskNursery::Slot::Lease& lease) { return attachDeviceCInd( lease.getExceptionStorage(), lease.getCallerLambda(), spec, componentThread, attachResult); }); ASSERT_TRUE(attachResult.success); ++expectedBufferCount; ASSERT_EQ( smo::stim_buff::lcamera_buff::managedStimulusProducers.size(), 1u); producer = smo::stim_buff::lcamera_buff ::managedStimulusProducers.front(); ASSERT_TRUE(producer != nullptr); EXPECT_EQ( producer->attachedStimulusBuffers.size(), expectedBufferCount); } ASSERT_TRUE(producer != nullptr); EXPECT_EQ( producer->configuredMode.pixelFormatName, "YUYV"); EXPECT_EQ( producer->layoutPath, smo::stim_buff::lcamera_buff::YuvCaptureLayoutPath ::PackedDeinterleave); EXPECT_EQ( producer->chromaSubsampling, smo::stim_buff::lcamera_buff::YuvChromaSubsampling::Yuv422); const size_t expectedUBytes = smo::stim_buff::lcamera_buff ::computeDeinterleavedChannelByteSize( smo::stim_buff::lcamera_buff::YuvChannelKind::U, producer->configuredMode.width, producer->configuredMode.height, smo::stim_buff::lcamera_buff ::YuvChromaSubsampling::Yuv422); EXPECT_EQ(expectedUBytes, 320u * 480u); for (const std::shared_ptr& bufferBase : producer->attachedStimulusBuffers) { auto channelBuffer = std::dynamic_pointer_cast< smo::stim_buff::lcamera_buff::YuvChannelStimulusBuffer>( bufferBase); ASSERT_TRUE(channelBuffer != nullptr); if (channelBuffer->channelKind == smo::stim_buff::lcamera_buff::YuvChannelKind::U || channelBuffer->channelKind == smo::stim_buff::lcamera_buff::YuvChannelKind::V) { EXPECT_EQ(channelBuffer->channelByteSize, expectedUBytes); } } for (const char *qualeIfaceApi : {"colour-yuv-v", "colour-yuv-u", "colour-yuv-y"}) { const std::shared_ptr spec = (std::string(qualeIfaceApi) == "colour-yuv-u") ? makeDetachSpec( usbProfile, qualeIfaceApi, "model-substr:HDMI USB Camera;location:external") : makeAttachSpec(usbProfile, qualeIfaceApi); smo::stim_buff::StimBuffDeviceOpResult detachResult; sscl::tests::runNonViralNurseryOnComponentThread( componentThread, [&spec, &detachResult]( sscl::co::NonViralTaskNursery::Slot::Lease& lease) { return detachDeviceCInd( lease.getExceptionStorage(), lease.getCallerLambda(), spec, detachResult); }); ASSERT_TRUE(detachResult.success); } sscl::tests::runNonViralNurseryOnComponentThread( componentThread, [](sscl::co::NonViralTaskNursery::Slot::Lease& lease) { return finalizeBuffCInd( lease.getExceptionStorage(), lease.getCallerLambda()); }); }); } } // namespace } // namespace lcamera_buff_tests