LcamDev: Add baked in camera profiles; use new test supports
This commit is contained in:
@@ -56,6 +56,12 @@ if(ENABLE_LIB_lcameraDev)
|
|||||||
|
|
||||||
option(ENABLE_LCAMERADEV_TOOLS "Build lcameraDev probe/list tools" ON)
|
option(ENABLE_LCAMERADEV_TOOLS "Build lcameraDev probe/list tools" ON)
|
||||||
if(ENABLE_LCAMERADEV_TOOLS)
|
if(ENABLE_LCAMERADEV_TOOLS)
|
||||||
|
if(NOT TARGET spinscale_test_support)
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"lcameraDev probe tools require spinscale_test_support. "
|
||||||
|
"Configure with -DENABLE_TESTS=ON.")
|
||||||
|
endif()
|
||||||
|
|
||||||
add_executable(lcameraDev_list_cameras
|
add_executable(lcameraDev_list_cameras
|
||||||
tools/lcameraDevListCameras.cpp
|
tools/lcameraDevListCameras.cpp
|
||||||
tools/probeRunner.cpp
|
tools/probeRunner.cpp
|
||||||
@@ -65,10 +71,12 @@ if(ENABLE_LIB_lcameraDev)
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/tools
|
${CMAKE_CURRENT_SOURCE_DIR}/tools
|
||||||
${CMAKE_SOURCE_DIR}/include
|
${CMAKE_SOURCE_DIR}/include
|
||||||
${CMAKE_BINARY_DIR}/include
|
${CMAKE_BINARY_DIR}/include
|
||||||
|
${CMAKE_SOURCE_DIR}/libspinscale/tests
|
||||||
)
|
)
|
||||||
target_link_libraries(lcameraDev_list_cameras PRIVATE
|
target_link_libraries(lcameraDev_list_cameras PRIVATE
|
||||||
lcameraDev
|
lcameraDev
|
||||||
spinscale
|
spinscale
|
||||||
|
spinscale_test_support
|
||||||
Boost::system
|
Boost::system
|
||||||
Boost::log
|
Boost::log
|
||||||
)
|
)
|
||||||
@@ -82,10 +90,12 @@ if(ENABLE_LIB_lcameraDev)
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/tools
|
${CMAKE_CURRENT_SOURCE_DIR}/tools
|
||||||
${CMAKE_SOURCE_DIR}/include
|
${CMAKE_SOURCE_DIR}/include
|
||||||
${CMAKE_BINARY_DIR}/include
|
${CMAKE_BINARY_DIR}/include
|
||||||
|
${CMAKE_SOURCE_DIR}/libspinscale/tests
|
||||||
)
|
)
|
||||||
target_link_libraries(lcameraDev_probe PRIVATE
|
target_link_libraries(lcameraDev_probe PRIVATE
|
||||||
lcameraDev
|
lcameraDev
|
||||||
spinscale
|
spinscale
|
||||||
|
spinscale_test_support
|
||||||
Boost::system
|
Boost::system
|
||||||
Boost::log
|
Boost::log
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ add_executable(lcameraDev_unit_tests
|
|||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(lcameraDev_unit_tests PRIVATE
|
target_include_directories(lcameraDev_unit_tests PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev
|
${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev
|
||||||
|
${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev/tests
|
||||||
|
${CMAKE_SOURCE_DIR}/tests/fixtures
|
||||||
${CMAKE_SOURCE_DIR}/include
|
${CMAKE_SOURCE_DIR}/include
|
||||||
${CMAKE_BINARY_DIR}/include
|
${CMAKE_BINARY_DIR}/include
|
||||||
)
|
)
|
||||||
@@ -20,3 +23,30 @@ target_link_libraries(lcameraDev_unit_tests
|
|||||||
add_dependencies(lcameraDev_unit_tests gtest_main)
|
add_dependencies(lcameraDev_unit_tests gtest_main)
|
||||||
|
|
||||||
add_test(NAME lcameraDev_unit_tests COMMAND lcameraDev_unit_tests)
|
add_test(NAME lcameraDev_unit_tests COMMAND lcameraDev_unit_tests)
|
||||||
|
|
||||||
|
add_executable(lcameraDev_hil_tests
|
||||||
|
lcameraDev_hil_tests.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(lcameraDev_hil_tests PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev
|
||||||
|
${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev/tests
|
||||||
|
${CMAKE_SOURCE_DIR}/tests/fixtures
|
||||||
|
${CMAKE_SOURCE_DIR}/libspinscale/tests
|
||||||
|
${CMAKE_SOURCE_DIR}/include
|
||||||
|
${CMAKE_BINARY_DIR}/include
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(lcameraDev_hil_tests
|
||||||
|
gtest_main
|
||||||
|
lcameraDev
|
||||||
|
spinscale
|
||||||
|
spinscale_test_support
|
||||||
|
${Boost_LIBRARIES}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(lcameraDev_hil_tests gtest_main spinscale_test_support)
|
||||||
|
|
||||||
|
add_test(NAME lcameraDev_hil_tests COMMAND lcameraDev_hil_tests)
|
||||||
|
set_tests_properties(lcameraDev_hil_tests PROPERTIES LABELS "HIL")
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#ifndef LCAMERA_DEV_TESTS_CATALOG_HELPERS_H
|
||||||
|
#define LCAMERA_DEV_TESTS_CATALOG_HELPERS_H
|
||||||
|
|
||||||
|
#include <bakedCameraProfiles.h>
|
||||||
|
#include <cameraIdentity.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace lcamera_dev {
|
||||||
|
namespace tests {
|
||||||
|
|
||||||
|
inline CameraIdentityRecord profileToIdentityRecord(
|
||||||
|
const test_fixtures::BakedCameraProfile& profile)
|
||||||
|
{
|
||||||
|
CameraIdentityRecord record;
|
||||||
|
record.id = profile.libcameraId;
|
||||||
|
record.model = profile.model;
|
||||||
|
record.locationLabel = profile.location;
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<CameraIdentityRecord> bakedProfilesAsRecords(
|
||||||
|
const char *machineTag)
|
||||||
|
{
|
||||||
|
std::vector<CameraIdentityRecord> records;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < test_fixtures::bakedCameraProfileCount; ++i)
|
||||||
|
{
|
||||||
|
const test_fixtures::BakedCameraProfile& profile =
|
||||||
|
test_fixtures::bakedCameraProfiles[i];
|
||||||
|
|
||||||
|
if (std::string(profile.machineTag) != machineTag) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
records.push_back(profileToIdentityRecord(profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tests
|
||||||
|
} // namespace lcamera_dev
|
||||||
|
|
||||||
|
#endif // LCAMERA_DEV_TESTS_CATALOG_HELPERS_H
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
#include <boostAsioLinkageFix.h>
|
||||||
|
|
||||||
|
#include <catalogHelpers.h>
|
||||||
|
#include <lcameraDev.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <support/bakedDeviceCatalog.h>
|
||||||
|
#include <support/probeComponentThread.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace lcamera_dev {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
sscl::co::NonViralNonPostingInvoker enumerateCamerasCInd(
|
||||||
|
std::exception_ptr& exceptionStorage,
|
||||||
|
std::function<void()> callerLambda,
|
||||||
|
std::vector<lcamera_dev::LcameraDevCameraInfo>& enumeratedCameras)
|
||||||
|
{
|
||||||
|
(void)exceptionStorage;
|
||||||
|
(void)callerLambda;
|
||||||
|
|
||||||
|
enumeratedCameras = co_await lcameraDev_enumerateCamerasCReq();
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sscl::co::NonViralNonPostingInvoker getOrCreateCInd(
|
||||||
|
std::exception_ptr& exceptionStorage,
|
||||||
|
std::function<void()> callerLambda,
|
||||||
|
const char *deviceSelector,
|
||||||
|
lcamera_dev::LcameraDevGetOrCreateResult& createResult)
|
||||||
|
{
|
||||||
|
(void)exceptionStorage;
|
||||||
|
(void)callerLambda;
|
||||||
|
|
||||||
|
createResult = co_await lcameraDev_getOrCreateDeviceCReq(deviceSelector);
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sscl::co::NonViralNonPostingInvoker releaseCInd(
|
||||||
|
std::exception_ptr& exceptionStorage,
|
||||||
|
std::function<void()> callerLambda,
|
||||||
|
const std::shared_ptr<lcamera_dev::CameraSession>& deviceSession)
|
||||||
|
{
|
||||||
|
(void)exceptionStorage;
|
||||||
|
(void)callerLambda;
|
||||||
|
|
||||||
|
co_await lcameraDev_releaseDeviceCReq(deviceSession);
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void runLcameraDevMainAndNurseryTask(
|
||||||
|
const std::function<void(
|
||||||
|
const std::shared_ptr<sscl::ComponentThread>&)>& work)
|
||||||
|
{
|
||||||
|
sscl::tests::ProbeComponentThreadHarness harness("lcameraDev-hil");
|
||||||
|
harness.runSync(
|
||||||
|
[&work](const std::shared_ptr<sscl::ComponentThread>& componentThread)
|
||||||
|
{
|
||||||
|
lcameraDev_main(componentThread);
|
||||||
|
work(componentThread);
|
||||||
|
lcameraDev_exit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class LcameraDevHilTest : 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string machineTag;
|
||||||
|
std::vector<const test_fixtures::BakedCameraProfile *> requiredProfiles;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(LcameraDevHilTest, EnumerateMatchesBakedCatalog)
|
||||||
|
{
|
||||||
|
std::vector<lcamera_dev::LcameraDevCameraInfo> enumeratedCameras;
|
||||||
|
|
||||||
|
runLcameraDevMainAndNurseryTask(
|
||||||
|
[&enumeratedCameras](
|
||||||
|
const std::shared_ptr<sscl::ComponentThread>& componentThread)
|
||||||
|
{
|
||||||
|
sscl::tests::runNonViralNurseryOnComponentThread(
|
||||||
|
componentThread,
|
||||||
|
[&enumeratedCameras](
|
||||||
|
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
|
||||||
|
{
|
||||||
|
return enumerateCamerasCInd(
|
||||||
|
lease.getExceptionStorage(),
|
||||||
|
lease.getCallerLambda(),
|
||||||
|
enumeratedCameras);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
EXPECT_GE(enumeratedCameras.size(), requiredProfiles.size());
|
||||||
|
|
||||||
|
for (const test_fixtures::BakedCameraProfile *profile : requiredProfiles)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
for (const lcamera_dev::LcameraDevCameraInfo& camera : enumeratedCameras)
|
||||||
|
{
|
||||||
|
if (camera.id == profile->libcameraId) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(found)
|
||||||
|
<< "Missing baked profile camera id=" << profile->libcameraId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LcameraDevHilTest, GetOrCreateByBakedSelector)
|
||||||
|
{
|
||||||
|
runLcameraDevMainAndNurseryTask(
|
||||||
|
[this](const std::shared_ptr<sscl::ComponentThread>& componentThread)
|
||||||
|
{
|
||||||
|
for (const test_fixtures::BakedCameraProfile *profile :
|
||||||
|
requiredProfiles)
|
||||||
|
{
|
||||||
|
lcamera_dev::LcameraDevGetOrCreateResult createResult;
|
||||||
|
|
||||||
|
sscl::tests::runNonViralNurseryOnComponentThread(
|
||||||
|
componentThread,
|
||||||
|
[profile, &createResult](
|
||||||
|
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
|
||||||
|
{
|
||||||
|
return getOrCreateCInd(
|
||||||
|
lease.getExceptionStorage(),
|
||||||
|
lease.getCallerLambda(),
|
||||||
|
profile->exampleSelector,
|
||||||
|
createResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
EXPECT_TRUE(createResult.deviceSession != nullptr)
|
||||||
|
<< profile->profileTag;
|
||||||
|
EXPECT_EQ(
|
||||||
|
createResult.resolvedIdentity.id,
|
||||||
|
profile->libcameraId)
|
||||||
|
<< profile->profileTag;
|
||||||
|
|
||||||
|
sscl::tests::runNonViralNurseryOnComponentThread(
|
||||||
|
componentThread,
|
||||||
|
[&createResult](
|
||||||
|
sscl::co::NonViralTaskNursery::Slot::Lease& lease)
|
||||||
|
{
|
||||||
|
return releaseCInd(
|
||||||
|
lease.getExceptionStorage(),
|
||||||
|
lease.getCallerLambda(),
|
||||||
|
createResult.deviceSession);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace lcamera_dev
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include <catalogHelpers.h>
|
||||||
#include <selectorParse.h>
|
#include <selectorParse.h>
|
||||||
#include <selectorResolve.h>
|
#include <selectorResolve.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@@ -8,6 +9,8 @@
|
|||||||
namespace lcamera_dev {
|
namespace lcamera_dev {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr const char *dellLaptopMachineTag = "dell-laptop";
|
||||||
|
|
||||||
static CameraIdentityRecord makeRecord(
|
static CameraIdentityRecord makeRecord(
|
||||||
const std::string& id,
|
const std::string& id,
|
||||||
const std::string& model = "",
|
const std::string& model = "",
|
||||||
@@ -20,7 +23,7 @@ static CameraIdentityRecord makeRecord(
|
|||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<CameraIdentityRecord> sampleRecords()
|
static std::vector<CameraIdentityRecord> syntheticAmbiguityRecords()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
makeRecord("/base/cam0", "imx219", "back"),
|
makeRecord("/base/cam0", "imx219", "back"),
|
||||||
@@ -31,18 +34,68 @@ static std::vector<CameraIdentityRecord> sampleRecords()
|
|||||||
|
|
||||||
TEST(FormatCameraListForDiagnosticsTest, ListsIndexedCameras)
|
TEST(FormatCameraListForDiagnosticsTest, ListsIndexedCameras)
|
||||||
{
|
{
|
||||||
const std::vector<CameraIdentityRecord> records = sampleRecords();
|
const std::vector<CameraIdentityRecord> records =
|
||||||
|
tests::bakedProfilesAsRecords(dellLaptopMachineTag);
|
||||||
const std::string text = formatCameraListForDiagnostics(records);
|
const std::string text = formatCameraListForDiagnostics(records);
|
||||||
|
|
||||||
EXPECT_NE(text.find("Known cameras:"), std::string::npos);
|
EXPECT_NE(text.find("Known cameras:"), std::string::npos);
|
||||||
EXPECT_NE(text.find("[0] id=/base/cam0"), std::string::npos);
|
EXPECT_NE(text.find("HDMI USB Camera"), std::string::npos);
|
||||||
EXPECT_NE(text.find("model=imx219"), std::string::npos);
|
|
||||||
EXPECT_NE(text.find("location=external"), std::string::npos);
|
EXPECT_NE(text.find("location=external"), std::string::npos);
|
||||||
|
EXPECT_NE(text.find("Integrated_Webcam_HD"), std::string::npos);
|
||||||
|
EXPECT_NE(text.find("location=front"), std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ResolveSelectorAgainstRecordsTest, BakedUsbHdmiCameraMatchesExampleSelector)
|
||||||
|
{
|
||||||
|
const std::vector<CameraIdentityRecord> records =
|
||||||
|
tests::bakedProfilesAsRecords(dellLaptopMachineTag);
|
||||||
|
const std::vector<SelectorCriterion> criteria =
|
||||||
|
parseDeviceSelector("model-substr:HDMI;location:EXTERNAL");
|
||||||
|
|
||||||
|
const CameraIdentityRecord resolved =
|
||||||
|
resolveSelectorAgainstRecords(criteria, records);
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
resolved.id,
|
||||||
|
"\\_SB_.PCI0.XHC_.RHUB.HS01-1:1.0-32e4:9415");
|
||||||
|
EXPECT_EQ(resolved.locationLabel, "external");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ResolveSelectorAgainstRecordsTest, BakedIntegratedWebcamMatchesExampleSelector)
|
||||||
|
{
|
||||||
|
const std::vector<CameraIdentityRecord> records =
|
||||||
|
tests::bakedProfilesAsRecords(dellLaptopMachineTag);
|
||||||
|
const std::vector<SelectorCriterion> criteria =
|
||||||
|
parseDeviceSelector("model-substr:Integrated;location:front");
|
||||||
|
|
||||||
|
const CameraIdentityRecord resolved =
|
||||||
|
resolveSelectorAgainstRecords(criteria, records);
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
resolved.id,
|
||||||
|
"\\_SB_.PCI0.XHC_.RHUB.HS04-4:1.0-1bcf:2b8a");
|
||||||
|
EXPECT_EQ(resolved.locationLabel, "front");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ResolveSelectorAgainstRecordsTest, BakedUsbCameraMatchesLibcameraId)
|
||||||
|
{
|
||||||
|
const std::vector<CameraIdentityRecord> records =
|
||||||
|
tests::bakedProfilesAsRecords(dellLaptopMachineTag);
|
||||||
|
const std::vector<SelectorCriterion> criteria =
|
||||||
|
parseDeviceSelector(
|
||||||
|
"lcamera-id:\\_SB_.PCI0.XHC_.RHUB.HS01-1:1.0-32e4:9415");
|
||||||
|
|
||||||
|
const CameraIdentityRecord resolved =
|
||||||
|
resolveSelectorAgainstRecords(criteria, records);
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
resolved.id,
|
||||||
|
"\\_SB_.PCI0.XHC_.RHUB.HS01-1:1.0-32e4:9415");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ResolveSelectorAgainstRecordsTest, MatchesLibcameraId)
|
TEST(ResolveSelectorAgainstRecordsTest, MatchesLibcameraId)
|
||||||
{
|
{
|
||||||
const std::vector<CameraIdentityRecord> records = sampleRecords();
|
const std::vector<CameraIdentityRecord> records = syntheticAmbiguityRecords();
|
||||||
const std::vector<SelectorCriterion> criteria =
|
const std::vector<SelectorCriterion> criteria =
|
||||||
parseDeviceSelector("lcamera-id:/base/cam1");
|
parseDeviceSelector("lcamera-id:/base/cam1");
|
||||||
|
|
||||||
@@ -54,7 +107,7 @@ TEST(ResolveSelectorAgainstRecordsTest, MatchesLibcameraId)
|
|||||||
|
|
||||||
TEST(ResolveSelectorAgainstRecordsTest, MatchesModelSubstrAndLocation)
|
TEST(ResolveSelectorAgainstRecordsTest, MatchesModelSubstrAndLocation)
|
||||||
{
|
{
|
||||||
const std::vector<CameraIdentityRecord> records = sampleRecords();
|
const std::vector<CameraIdentityRecord> records = syntheticAmbiguityRecords();
|
||||||
const std::vector<SelectorCriterion> criteria =
|
const std::vector<SelectorCriterion> criteria =
|
||||||
parseDeviceSelector("model-substr:Logitech;location:EXTERNAL");
|
parseDeviceSelector("model-substr:Logitech;location:EXTERNAL");
|
||||||
|
|
||||||
@@ -66,7 +119,7 @@ TEST(ResolveSelectorAgainstRecordsTest, MatchesModelSubstrAndLocation)
|
|||||||
|
|
||||||
TEST(ResolveSelectorAgainstRecordsTest, IndexSelectsNthCamera)
|
TEST(ResolveSelectorAgainstRecordsTest, IndexSelectsNthCamera)
|
||||||
{
|
{
|
||||||
const std::vector<CameraIdentityRecord> records = sampleRecords();
|
const std::vector<CameraIdentityRecord> records = syntheticAmbiguityRecords();
|
||||||
const std::vector<SelectorCriterion> criteria =
|
const std::vector<SelectorCriterion> criteria =
|
||||||
parseDeviceSelector("index:2");
|
parseDeviceSelector("index:2");
|
||||||
|
|
||||||
@@ -78,7 +131,7 @@ TEST(ResolveSelectorAgainstRecordsTest, IndexSelectsNthCamera)
|
|||||||
|
|
||||||
TEST(ResolveSelectorAgainstRecordsTest, IndexCombinedWithModel)
|
TEST(ResolveSelectorAgainstRecordsTest, IndexCombinedWithModel)
|
||||||
{
|
{
|
||||||
const std::vector<CameraIdentityRecord> records = sampleRecords();
|
const std::vector<CameraIdentityRecord> records = syntheticAmbiguityRecords();
|
||||||
const std::vector<SelectorCriterion> criteria =
|
const std::vector<SelectorCriterion> criteria =
|
||||||
parseDeviceSelector("index:0;model:imx219");
|
parseDeviceSelector("index:0;model:imx219");
|
||||||
|
|
||||||
@@ -90,7 +143,7 @@ TEST(ResolveSelectorAgainstRecordsTest, IndexCombinedWithModel)
|
|||||||
|
|
||||||
TEST(ResolveSelectorAgainstRecordsTest, ZeroMatchesThrowsWithDiagnostics)
|
TEST(ResolveSelectorAgainstRecordsTest, ZeroMatchesThrowsWithDiagnostics)
|
||||||
{
|
{
|
||||||
const std::vector<CameraIdentityRecord> records = sampleRecords();
|
const std::vector<CameraIdentityRecord> records = syntheticAmbiguityRecords();
|
||||||
const std::vector<SelectorCriterion> criteria =
|
const std::vector<SelectorCriterion> criteria =
|
||||||
parseDeviceSelector("model:does-not-exist");
|
parseDeviceSelector("model:does-not-exist");
|
||||||
|
|
||||||
@@ -111,7 +164,7 @@ TEST(ResolveSelectorAgainstRecordsTest, ZeroMatchesThrowsWithDiagnostics)
|
|||||||
|
|
||||||
TEST(ResolveSelectorAgainstRecordsTest, AmbiguousSelectorThrows)
|
TEST(ResolveSelectorAgainstRecordsTest, AmbiguousSelectorThrows)
|
||||||
{
|
{
|
||||||
const std::vector<CameraIdentityRecord> records = sampleRecords();
|
const std::vector<CameraIdentityRecord> records = syntheticAmbiguityRecords();
|
||||||
const std::vector<SelectorCriterion> criteria =
|
const std::vector<SelectorCriterion> criteria =
|
||||||
parseDeviceSelector("model:imx219");
|
parseDeviceSelector("model:imx219");
|
||||||
|
|
||||||
@@ -122,7 +175,7 @@ TEST(ResolveSelectorAgainstRecordsTest, AmbiguousSelectorThrows)
|
|||||||
|
|
||||||
TEST(ResolveSelectorAgainstRecordsTest, IndexOutOfRangeThrows)
|
TEST(ResolveSelectorAgainstRecordsTest, IndexOutOfRangeThrows)
|
||||||
{
|
{
|
||||||
const std::vector<CameraIdentityRecord> records = sampleRecords();
|
const std::vector<CameraIdentityRecord> records = syntheticAmbiguityRecords();
|
||||||
const std::vector<SelectorCriterion> criteria =
|
const std::vector<SelectorCriterion> criteria =
|
||||||
parseDeviceSelector("index:9");
|
parseDeviceSelector("index:9");
|
||||||
|
|
||||||
@@ -133,7 +186,7 @@ TEST(ResolveSelectorAgainstRecordsTest, IndexOutOfRangeThrows)
|
|||||||
|
|
||||||
TEST(ResolveSelectorAgainstRecordsTest, IndexConflictsWithOtherClauses)
|
TEST(ResolveSelectorAgainstRecordsTest, IndexConflictsWithOtherClauses)
|
||||||
{
|
{
|
||||||
const std::vector<CameraIdentityRecord> records = sampleRecords();
|
const std::vector<CameraIdentityRecord> records = syntheticAmbiguityRecords();
|
||||||
const std::vector<SelectorCriterion> criteria =
|
const std::vector<SelectorCriterion> criteria =
|
||||||
parseDeviceSelector("index:0;location:front");
|
parseDeviceSelector("index:0;location:front");
|
||||||
|
|
||||||
@@ -144,7 +197,7 @@ TEST(ResolveSelectorAgainstRecordsTest, IndexConflictsWithOtherClauses)
|
|||||||
|
|
||||||
TEST(ResolveSelectorAgainstRecordsTest, InvalidIndexValueThrows)
|
TEST(ResolveSelectorAgainstRecordsTest, InvalidIndexValueThrows)
|
||||||
{
|
{
|
||||||
const std::vector<CameraIdentityRecord> records = sampleRecords();
|
const std::vector<CameraIdentityRecord> records = syntheticAmbiguityRecords();
|
||||||
const std::vector<SelectorCriterion> criteria =
|
const std::vector<SelectorCriterion> criteria =
|
||||||
parseDeviceSelector("index:not-a-number");
|
parseDeviceSelector("index:not-a-number");
|
||||||
|
|
||||||
|
|||||||
@@ -1,88 +1,15 @@
|
|||||||
#include <boostAsioLinkageFix.h>
|
|
||||||
|
|
||||||
#include <probeRunner.h>
|
#include <probeRunner.h>
|
||||||
#include <spinscale/component.h>
|
|
||||||
#include <future>
|
#include <support/probeComponentThread.h>
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace lcamera_dev_probe {
|
namespace lcamera_dev_probe {
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
constexpr sscl::ThreadId PROBE_PUPPETEER_THREAD_ID = 1;
|
|
||||||
|
|
||||||
class DummyPuppeteerComponent
|
|
||||||
: public sscl::pptr::PuppeteerComponent
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit DummyPuppeteerComponent(
|
|
||||||
const std::shared_ptr<sscl::PuppeteerThread>& componentThread)
|
|
||||||
: sscl::pptr::PuppeteerComponent(componentThread)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void handleLoopExceptionHook() override
|
|
||||||
{
|
|
||||||
std::cerr << "lcameraDev probe: puppeteer loop exception\n";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void probePuppeteerMain(
|
|
||||||
const sscl::PuppeteerThread::EntryFnArguments& args,
|
|
||||||
const std::function<void(
|
|
||||||
const std::shared_ptr<sscl::ComponentThread>&)>& work,
|
|
||||||
std::promise<std::exception_ptr>& donePromise)
|
|
||||||
{
|
|
||||||
sscl::PuppeteerThread& thr = args.usableBeforeJolt;
|
|
||||||
thr.initializeTls();
|
|
||||||
sscl::ComponentThread::setPuppeteerThreadId(PROBE_PUPPETEER_THREAD_ID);
|
|
||||||
|
|
||||||
std::shared_ptr<sscl::PuppeteerThread> thrPtr =
|
|
||||||
std::static_pointer_cast<sscl::PuppeteerThread>(thr.shared_from_this());
|
|
||||||
sscl::ComponentThread::setPuppeteerThread(thrPtr);
|
|
||||||
|
|
||||||
try {
|
|
||||||
work(thrPtr);
|
|
||||||
donePromise.set_value(nullptr);
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
donePromise.set_value(std::current_exception());
|
|
||||||
}
|
|
||||||
|
|
||||||
thr.getIoContext().stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void runOnComponentThread(
|
void runOnComponentThread(
|
||||||
const std::function<void(
|
const std::function<void(
|
||||||
const std::shared_ptr<sscl::ComponentThread>&)>& work)
|
const std::shared_ptr<sscl::ComponentThread>&)>& work)
|
||||||
{
|
{
|
||||||
std::promise<std::exception_ptr> donePromise;
|
sscl::tests::ProbeComponentThreadHarness harness("lcameraDev-probe");
|
||||||
std::future<std::exception_ptr> doneFuture = donePromise.get_future();
|
harness.runSync(work);
|
||||||
|
|
||||||
DummyPuppeteerComponent dummyComponent{
|
|
||||||
std::shared_ptr<sscl::PuppeteerThread>()};
|
|
||||||
|
|
||||||
std::shared_ptr<sscl::PuppeteerThread> probeThread =
|
|
||||||
std::make_shared<sscl::PuppeteerThread>(
|
|
||||||
PROBE_PUPPETEER_THREAD_ID,
|
|
||||||
"lcameraDev-probe",
|
|
||||||
[&work, &donePromise](
|
|
||||||
const sscl::PuppeteerThread::EntryFnArguments& args)
|
|
||||||
{
|
|
||||||
probePuppeteerMain(args, work, donePromise);
|
|
||||||
},
|
|
||||||
dummyComponent,
|
|
||||||
nullptr);
|
|
||||||
|
|
||||||
dummyComponent.thread = probeThread;
|
|
||||||
|
|
||||||
probeThread->thread.join();
|
|
||||||
|
|
||||||
std::exception_ptr probeException = doneFuture.get();
|
|
||||||
if (probeException) {
|
|
||||||
std::rethrow_exception(probeException);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace lcamera_dev_probe
|
} // namespace lcamera_dev_probe
|
||||||
|
|||||||
Executable
+79
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Extract libcamera enumeration output as C++ BakedCameraProfile initializer rows.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/extractBakedCameraProfiles.sh <build-dir> <machine-tag>
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# scripts/extractBakedCameraProfiles.sh build-agent dell-laptop
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
echo "Usage: $0 <build-dir> <machine-tag>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BUILD_DIR="$1"
|
||||||
|
MACHINE_TAG="$2"
|
||||||
|
LIST_CAMERAS="${BUILD_DIR}/commonLibs/lcameraDev/lcameraDev_list_cameras"
|
||||||
|
|
||||||
|
if [[ ! -x "${LIST_CAMERAS}" ]]; then
|
||||||
|
echo "Missing executable: ${LIST_CAMERAS}" >&2
|
||||||
|
echo "Build with -DENABLE_LIB_lcameraDev=ON -DENABLE_TESTS=ON" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "// MACHINE: ${MACHINE_TAG}"
|
||||||
|
echo "// Paste rows into tests/fixtures/bakedCameraProfiles.h"
|
||||||
|
echo
|
||||||
|
|
||||||
|
"${LIST_CAMERAS}" 2>/dev/null | awk -v machine="${MACHINE_TAG}" '
|
||||||
|
/^lcameraDev: found / { next }
|
||||||
|
/^\s+\[[0-9]+\] id=/ {
|
||||||
|
line = $0
|
||||||
|
sub(/^\s+\[[0-9]+\] /, "", line)
|
||||||
|
|
||||||
|
id = ""
|
||||||
|
model = ""
|
||||||
|
location = ""
|
||||||
|
|
||||||
|
n = split(line, parts, " ")
|
||||||
|
for (i = 1; i <= n; i++) {
|
||||||
|
if (parts[i] ~ /^id=/) {
|
||||||
|
id = substr(parts[i], 4)
|
||||||
|
} else if (parts[i] ~ /^model=/) {
|
||||||
|
model = substr(parts[i], 7)
|
||||||
|
} else if (parts[i] ~ /^location=/) {
|
||||||
|
location = substr(parts[i], 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profile_tag = "camera_index_" index
|
||||||
|
if (location == "external") {
|
||||||
|
profile_tag = "usb_camera"
|
||||||
|
} else if (location == "front" || location == "back") {
|
||||||
|
profile_tag = "integrated_webcam"
|
||||||
|
}
|
||||||
|
|
||||||
|
selector = "index:" index
|
||||||
|
if (model != "") {
|
||||||
|
selector = "model-substr:" substr(model, 1, 12)
|
||||||
|
if (location != "") {
|
||||||
|
selector = selector ";location:" location
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "{\n"
|
||||||
|
printf "\t\"%s\",\n", machine
|
||||||
|
printf "\t\"%s\",\n", profile_tag
|
||||||
|
printf "\t\"%s\",\n", id
|
||||||
|
printf "\t\"%s\",\n", model
|
||||||
|
printf "\t\"%s\",\n", location
|
||||||
|
printf "\t\"\",\n"
|
||||||
|
printf "\t\"\",\n"
|
||||||
|
printf "\t\"%s\",\n", selector
|
||||||
|
printf "\ttrue,\n"
|
||||||
|
printf "},\n"
|
||||||
|
}
|
||||||
|
' index=0
|
||||||
Vendored
+61
@@ -0,0 +1,61 @@
|
|||||||
|
#ifndef TESTS_FIXTURES_BAKED_CAMERA_PROFILES_H
|
||||||
|
#define TESTS_FIXTURES_BAKED_CAMERA_PROFILES_H
|
||||||
|
|
||||||
|
/** Baked camera profiles captured from real hardware for offline unit tests.
|
||||||
|
*
|
||||||
|
* To add profiles for a new machine or camera:
|
||||||
|
* 1. Build lcameraDev_list_cameras with -DENABLE_LIB_lcameraDev=ON -DENABLE_TESTS=ON
|
||||||
|
* 2. Run scripts/extractBakedCameraProfiles.sh <build-dir> <machine-tag>
|
||||||
|
* 3. Merge the printed rows into the matching MACHINE section below.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace test_fixtures {
|
||||||
|
|
||||||
|
struct BakedCameraProfile
|
||||||
|
{
|
||||||
|
const char *machineTag;
|
||||||
|
const char *profileTag;
|
||||||
|
const char *libcameraId;
|
||||||
|
const char *model;
|
||||||
|
const char *location;
|
||||||
|
const char *usbVidPid;
|
||||||
|
const char *usbSerial;
|
||||||
|
const char *exampleSelector;
|
||||||
|
bool requiredOnMachine;
|
||||||
|
};
|
||||||
|
|
||||||
|
// MACHINE: dell-laptop
|
||||||
|
// Captured via lcameraDev_list_cameras on 2026-06-10.
|
||||||
|
inline constexpr BakedCameraProfile bakedCameraProfiles[] = {
|
||||||
|
{
|
||||||
|
"dell-laptop",
|
||||||
|
"usb_hdmi_camera",
|
||||||
|
"\\_SB_.PCI0.XHC_.RHUB.HS01-1:1.0-32e4:9415",
|
||||||
|
"HDMI USB Camera: HDMI USB Camer",
|
||||||
|
"external",
|
||||||
|
"32e4:9415",
|
||||||
|
"1020181e58586223",
|
||||||
|
"model-substr:HDMI;location:EXTERNAL",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dell-laptop",
|
||||||
|
"integrated_webcam",
|
||||||
|
"\\_SB_.PCI0.XHC_.RHUB.HS04-4:1.0-1bcf:2b8a",
|
||||||
|
"Integrated_Webcam_HD: Integrate",
|
||||||
|
"front",
|
||||||
|
"1bcf:2b8a",
|
||||||
|
"",
|
||||||
|
"model-substr:Integrated;location:front",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
inline constexpr std::size_t bakedCameraProfileCount =
|
||||||
|
sizeof(bakedCameraProfiles) / sizeof(bakedCameraProfiles[0]);
|
||||||
|
|
||||||
|
} // namespace test_fixtures
|
||||||
|
|
||||||
|
#endif // TESTS_FIXTURES_BAKED_CAMERA_PROFILES_H
|
||||||
Reference in New Issue
Block a user