Tests: add tests for lcameraDev, fix qutex tests

This commit is contained in:
2026-06-13 16:08:21 -04:00
parent 46f767f232
commit 2458c83c6b
9 changed files with 398 additions and 73 deletions
+4
View File
@@ -90,4 +90,8 @@ if(ENABLE_LIB_lcameraDev)
Boost::log
)
endif()
if(ENABLE_TESTS)
add_subdirectory(tests)
endif()
endif()
-6
View File
@@ -6,12 +6,6 @@ namespace lcamera_dev {
namespace {
bool startsWith(const std::string& text, const std::string& prefix)
{
return text.size() >= prefix.size()
&& text.compare(0, prefix.size(), prefix) == 0;
}
SelectorCriterionKind parseCriterionKind(const std::string& prefixToken)
{
if (prefixToken == "lcamera-id") {
+12 -7
View File
@@ -129,6 +129,18 @@ CameraIdentityRecord resolveSelectorAgainstRecords(
const CameraIdentityRecord& indexedRecord =
records.at(static_cast<size_t>(*indexCriterion));
bool hasNonIndexCriteria = false;
for (const SelectorCriterion& criterion : criteria)
{
if (criterion.kind != SelectorCriterionKind::Index)
{
hasNonIndexCriteria = true;
break;
}
}
if (!hasNonIndexCriteria) { return indexedRecord; }
auto it = std::find_if(
matches.begin(), matches.end(),
[&indexedRecord](const CameraIdentityRecord* candidate) {
@@ -141,13 +153,6 @@ CameraIdentityRecord resolveSelectorAgainstRecords(
"index: criterion conflicts with other selector clauses");
}
if (matches.size() > 1)
{
throw std::runtime_error(
"Ambiguous deviceSelector: multiple cameras match\n"
+ formatCameraListForDiagnostics(records));
}
return indexedRecord;
}
@@ -0,0 +1,22 @@
add_executable(lcameraDev_unit_tests
selectorParse_tests.cpp
selectorResolve_tests.cpp
cameraIdentity_tests.cpp
)
target_include_directories(lcameraDev_unit_tests PRIVATE
${CMAKE_SOURCE_DIR}/commonLibs/lcameraDev
${CMAKE_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
)
target_link_libraries(lcameraDev_unit_tests
gtest_main
lcameraDev
spinscale
${Boost_LIBRARIES}
)
add_dependencies(lcameraDev_unit_tests gtest_main)
add_test(NAME lcameraDev_unit_tests COMMAND lcameraDev_unit_tests)
@@ -0,0 +1,24 @@
#include <gtest/gtest.h>
#include <cameraIdentity.h>
#include <libcamera/property_ids.h>
namespace lcamera_dev {
namespace {
TEST(LocationPropertyToLabelTest, MapsKnownLocations)
{
using namespace libcamera::properties;
EXPECT_EQ(locationPropertyToLabel(CameraLocationFront), "front");
EXPECT_EQ(locationPropertyToLabel(CameraLocationBack), "back");
EXPECT_EQ(locationPropertyToLabel(CameraLocationExternal), "external");
}
TEST(LocationPropertyToLabelTest, UnknownLocationReturnsEmptyString)
{
EXPECT_EQ(locationPropertyToLabel(-1), "");
EXPECT_EQ(locationPropertyToLabel(99), "");
}
} // namespace
} // namespace lcamera_dev
@@ -0,0 +1,93 @@
#include <gtest/gtest.h>
#include <selectorParse.h>
#include <stdexcept>
namespace lcamera_dev {
namespace {
TEST(TrimWhitespaceTest, StripsLeadingAndTrailingWhitespace)
{
EXPECT_EQ(trimWhitespace(" foo bar "), "foo bar");
EXPECT_EQ(trimWhitespace("\t\nvalue\r\n"), "value");
EXPECT_EQ(trimWhitespace("no-trim"), "no-trim");
EXPECT_EQ(trimWhitespace(""), "");
}
TEST(ParseDeviceSelectorTest, BareOpaqueIdIsLibcameraId)
{
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector("/base/soc/i2c@1/imx219@10");
ASSERT_EQ(criteria.size(), 1u);
EXPECT_EQ(criteria[0].kind, SelectorCriterionKind::LibcameraId);
EXPECT_EQ(criteria[0].value, "/base/soc/i2c@1/imx219@10");
}
TEST(ParseDeviceSelectorTest, ExplicitLcameraIdPrefix)
{
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector("lcamera-id:foo bar baz");
ASSERT_EQ(criteria.size(), 1u);
EXPECT_EQ(criteria[0].kind, SelectorCriterionKind::LibcameraId);
EXPECT_EQ(criteria[0].value, "foo bar baz");
}
TEST(ParseDeviceSelectorTest, ParsesTypedPrefixes)
{
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector(
"index:0;model:imx219;model-substr:Logi;location:external");
ASSERT_EQ(criteria.size(), 4u);
EXPECT_EQ(criteria[0].kind, SelectorCriterionKind::Index);
EXPECT_EQ(criteria[0].value, "0");
EXPECT_EQ(criteria[1].kind, SelectorCriterionKind::Model);
EXPECT_EQ(criteria[1].value, "imx219");
EXPECT_EQ(criteria[2].kind, SelectorCriterionKind::ModelSubstr);
EXPECT_EQ(criteria[2].value, "Logi");
EXPECT_EQ(criteria[3].kind, SelectorCriterionKind::Location);
EXPECT_EQ(criteria[3].value, "external");
}
TEST(ParseDeviceSelectorTest, EscapedSemicolonInValue)
{
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector("lcamera-id:foo\\;bar;model:aaaa");
ASSERT_EQ(criteria.size(), 2u);
EXPECT_EQ(criteria[0].kind, SelectorCriterionKind::LibcameraId);
EXPECT_EQ(criteria[0].value, "foo;bar");
EXPECT_EQ(criteria[1].kind, SelectorCriterionKind::Model);
EXPECT_EQ(criteria[1].value, "aaaa");
}
TEST(ParseDeviceSelectorTest, EmptySelectorThrows)
{
EXPECT_THROW(parseDeviceSelector(""), std::runtime_error);
EXPECT_THROW(parseDeviceSelector(" "), std::runtime_error);
}
TEST(ParseDeviceSelectorTest, UnknownPrefixThrows)
{
EXPECT_THROW(
parseDeviceSelector("serial:abc"),
std::runtime_error);
}
TEST(ParseDeviceSelectorTest, EmptyClauseThrows)
{
EXPECT_THROW(
parseDeviceSelector("model:imx219;;location:front"),
std::runtime_error);
}
TEST(ParseDeviceSelectorTest, EmptyValueThrows)
{
EXPECT_THROW(
parseDeviceSelector("model:"),
std::runtime_error);
}
} // namespace
} // namespace lcamera_dev
@@ -0,0 +1,157 @@
#include <gtest/gtest.h>
#include <selectorParse.h>
#include <selectorResolve.h>
#include <stdexcept>
#include <string>
#include <vector>
namespace lcamera_dev {
namespace {
static CameraIdentityRecord makeRecord(
const std::string& id,
const std::string& model = "",
const std::string& locationLabel = "")
{
CameraIdentityRecord record;
record.id = id;
record.model = model;
record.locationLabel = locationLabel;
return record;
}
static std::vector<CameraIdentityRecord> sampleRecords()
{
return {
makeRecord("/base/cam0", "imx219", "back"),
makeRecord("/base/cam1", "Logitech C920", "external"),
makeRecord("/base/cam2", "imx219", "front"),
};
}
TEST(FormatCameraListForDiagnosticsTest, ListsIndexedCameras)
{
const std::vector<CameraIdentityRecord> records = sampleRecords();
const std::string text = formatCameraListForDiagnostics(records);
EXPECT_NE(text.find("Known cameras:"), std::string::npos);
EXPECT_NE(text.find("[0] id=/base/cam0"), std::string::npos);
EXPECT_NE(text.find("model=imx219"), std::string::npos);
EXPECT_NE(text.find("location=external"), std::string::npos);
}
TEST(ResolveSelectorAgainstRecordsTest, MatchesLibcameraId)
{
const std::vector<CameraIdentityRecord> records = sampleRecords();
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector("lcamera-id:/base/cam1");
const CameraIdentityRecord resolved =
resolveSelectorAgainstRecords(criteria, records);
EXPECT_EQ(resolved.id, "/base/cam1");
}
TEST(ResolveSelectorAgainstRecordsTest, MatchesModelSubstrAndLocation)
{
const std::vector<CameraIdentityRecord> records = sampleRecords();
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector("model-substr:Logitech;location:EXTERNAL");
const CameraIdentityRecord resolved =
resolveSelectorAgainstRecords(criteria, records);
EXPECT_EQ(resolved.id, "/base/cam1");
}
TEST(ResolveSelectorAgainstRecordsTest, IndexSelectsNthCamera)
{
const std::vector<CameraIdentityRecord> records = sampleRecords();
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector("index:2");
const CameraIdentityRecord resolved =
resolveSelectorAgainstRecords(criteria, records);
EXPECT_EQ(resolved.id, "/base/cam2");
}
TEST(ResolveSelectorAgainstRecordsTest, IndexCombinedWithModel)
{
const std::vector<CameraIdentityRecord> records = sampleRecords();
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector("index:0;model:imx219");
const CameraIdentityRecord resolved =
resolveSelectorAgainstRecords(criteria, records);
EXPECT_EQ(resolved.id, "/base/cam0");
}
TEST(ResolveSelectorAgainstRecordsTest, ZeroMatchesThrowsWithDiagnostics)
{
const std::vector<CameraIdentityRecord> records = sampleRecords();
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector("model:does-not-exist");
try {
resolveSelectorAgainstRecords(criteria, records);
FAIL() << "Expected std::runtime_error";
}
catch (const std::runtime_error& exc)
{
EXPECT_NE(
std::string(exc.what()).find("No camera matches deviceSelector"),
std::string::npos);
EXPECT_NE(
std::string(exc.what()).find("Known cameras:"),
std::string::npos);
}
}
TEST(ResolveSelectorAgainstRecordsTest, AmbiguousSelectorThrows)
{
const std::vector<CameraIdentityRecord> records = sampleRecords();
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector("model:imx219");
EXPECT_THROW(
resolveSelectorAgainstRecords(criteria, records),
std::runtime_error);
}
TEST(ResolveSelectorAgainstRecordsTest, IndexOutOfRangeThrows)
{
const std::vector<CameraIdentityRecord> records = sampleRecords();
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector("index:9");
EXPECT_THROW(
resolveSelectorAgainstRecords(criteria, records),
std::runtime_error);
}
TEST(ResolveSelectorAgainstRecordsTest, IndexConflictsWithOtherClauses)
{
const std::vector<CameraIdentityRecord> records = sampleRecords();
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector("index:0;location:front");
EXPECT_THROW(
resolveSelectorAgainstRecords(criteria, records),
std::runtime_error);
}
TEST(ResolveSelectorAgainstRecordsTest, InvalidIndexValueThrows)
{
const std::vector<CameraIdentityRecord> records = sampleRecords();
const std::vector<SelectorCriterion> criteria =
parseDeviceSelector("index:not-a-number");
EXPECT_THROW(
resolveSelectorAgainstRecords(criteria, records),
std::runtime_error);
}
} // namespace
} // namespace lcamera_dev