From e261787cfef0909343af386dea8b59e4dff31e72 Mon Sep 17 00:00:00 2001 From: Hayodea Hekol Date: Sun, 14 Jun 2026 11:05:17 -0400 Subject: [PATCH] Add env-gated lcameraBuff configure HIL tests on baked USB profile. HIL attaches Y/U/V channels, verifies shared producer state, and detaches using an alternate selector string for the U channel quale. Co-authored-by: Cursor --- stimBuffApis/lcameraBuff/tests/CMakeLists.txt | 30 ++ .../tests/lcameraBuff_configure_hil_tests.cpp | 327 ++++++++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 stimBuffApis/lcameraBuff/tests/lcameraBuff_configure_hil_tests.cpp diff --git a/stimBuffApis/lcameraBuff/tests/CMakeLists.txt b/stimBuffApis/lcameraBuff/tests/CMakeLists.txt index 6a24902..342b418 100644 --- a/stimBuffApis/lcameraBuff/tests/CMakeLists.txt +++ b/stimBuffApis/lcameraBuff/tests/CMakeLists.txt @@ -27,3 +27,33 @@ target_link_libraries(lcameraBuff_unit_tests add_dependencies(lcameraBuff_unit_tests gtest_main spinscale_test_support) add_test(NAME lcameraBuff_unit_tests COMMAND lcameraBuff_unit_tests) + +add_executable(lcameraBuff_configure_hil_tests + lcameraBuff_configure_hil_tests.cpp + hilSmoCallbacksStub.cpp +) + +target_include_directories(lcameraBuff_configure_hil_tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/stimBuffApis/lcameraBuff + ${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev + ${CMAKE_SOURCE_DIR}/libspinscale/tests + ${CMAKE_SOURCE_DIR}/tests/fixtures + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include +) + +target_link_libraries(lcameraBuff_configure_hil_tests + gtest_main + lcameraBuff + lcameraDev + spinscale_test_support + smocore + ${Boost_LIBRARIES} + ${OPENCL_LIBRARIES} +) + +add_dependencies(lcameraBuff_configure_hil_tests gtest_main spinscale_test_support) + +add_test(NAME lcameraBuff_configure_hil_tests COMMAND lcameraBuff_configure_hil_tests) +set_tests_properties(lcameraBuff_configure_hil_tests PROPERTIES LABELS "HIL") diff --git a/stimBuffApis/lcameraBuff/tests/lcameraBuff_configure_hil_tests.cpp b/stimBuffApis/lcameraBuff/tests/lcameraBuff_configure_hil_tests.cpp new file mode 100644 index 0000000..b5b5004 --- /dev/null +++ b/stimBuffApis/lcameraBuff/tests/lcameraBuff_configure_hil_tests.cpp @@ -0,0 +1,327 @@ +#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::attachedStimulusProducers.size(), + 1u); + producer = smo::stim_buff::lcamera_buff + ::attachedStimulusProducers.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