#include #include #include #include #include namespace smo { namespace device { namespace { using ParamList = std::vector>; ParamList makeParams(std::initializer_list> entries) { ParamList params; for (const auto& entry : entries) { params.emplace_back(entry.first, entry.second); } return params; } TEST(DeviceAttachmentSpecNamesContainTest, MatchesVectorAndArraySynonyms) { const std::vector widthSynonyms = { "frame-width", "dim-w", }; EXPECT_TRUE(DeviceAttachmentSpec::namesContain(widthSynonyms, "dim-w")); EXPECT_FALSE(DeviceAttachmentSpec::namesContain(widthSynonyms, "dim-h")); const std::array heightSynonyms = { "frame-height", "dim-h", }; EXPECT_TRUE(DeviceAttachmentSpec::namesContain(heightSynonyms, "frame-height")); EXPECT_FALSE(DeviceAttachmentSpec::namesContain(heightSynonyms, "frame-width")); } TEST(DeviceAttachmentSpecParamsContainTest, DetectsExactParamName) { const ParamList params = makeParams({ {"display", "0"}, {"screen", "1"}, }); EXPECT_TRUE(DeviceAttachmentSpec::paramsContain(params, "display")); EXPECT_TRUE(DeviceAttachmentSpec::paramsContain(params, "screen")); EXPECT_FALSE(DeviceAttachmentSpec::paramsContain(params, "width")); } TEST(DeviceAttachmentSpecParamNameMatchesAnySynonymGroupTest, MatchesAcrossGroups) { const std::vector widthSynonyms = {"dim-w", "frame-w"}; const std::vector heightSynonyms = {"dim-h", "frame-h"}; EXPECT_TRUE(DeviceAttachmentSpec::paramNameMatchesAnySynonymGroup( "dim-w", widthSynonyms, heightSynonyms)); EXPECT_TRUE(DeviceAttachmentSpec::paramNameMatchesAnySynonymGroup( "frame-h", widthSynonyms, heightSynonyms)); EXPECT_FALSE(DeviceAttachmentSpec::paramNameMatchesAnySynonymGroup( "colour-space", widthSynonyms, heightSynonyms)); } TEST(DeviceAttachmentSpecNormalizeParamTokenTest, TrimsAndLowercases) { EXPECT_EQ(DeviceAttachmentSpec::normalizeParamToken(" TRUE "), "true"); EXPECT_EQ(DeviceAttachmentSpec::normalizeParamToken("YUV"), "yuv"); EXPECT_EQ(DeviceAttachmentSpec::normalizeParamToken("480p"), "480p"); EXPECT_EQ(DeviceAttachmentSpec::normalizeParamToken(""), ""); } TEST(DeviceAttachmentSpecFindOptionalParamTest, LattermostExactNameWins) { const ParamList params = makeParams({ {"display", "0"}, {"other", "ignored"}, {"display", "2"}, }); const std::optional> matched = DeviceAttachmentSpec::findOptionalParam(params, "display"); ASSERT_TRUE(matched.has_value()); EXPECT_EQ(matched->first, "display"); EXPECT_EQ(matched->second, "2"); } TEST(DeviceAttachmentSpecFindOptionalParamWithSynonymsTest, LattermostSynonymWins) { const std::vector synonyms = { "cmd-timeout-ms", "command-timeout-ms", }; const ParamList params = makeParams({ {"cmd-timeout-ms", "5"}, {"command-timeout-ms", "25"}, }); const std::optional> matched = DeviceAttachmentSpec::findOptionalParamWithSynonyms(params, synonyms); ASSERT_TRUE(matched.has_value()); EXPECT_EQ(matched->first, "command-timeout-ms"); EXPECT_EQ(matched->second, "25"); } TEST(DeviceAttachmentSpecFindOptionalParamWithSynonymsTest, AbsentReturnsNullopt) { const std::vector synonyms = {"data-port"}; const ParamList params = makeParams({{"cmd-port", "56001"}}); EXPECT_FALSE(DeviceAttachmentSpec::findOptionalParamWithSynonyms( params, synonyms).has_value()); } TEST(DeviceAttachmentSpecParseParamValueAsIntTest, ParsesValidInteger) { EXPECT_EQ( DeviceAttachmentSpec::parseParamValueAsInt("width", "1280"), 1280); EXPECT_EQ( DeviceAttachmentSpec::parseParamValueAsInt("width", "-42"), -42); } TEST(DeviceAttachmentSpecParseParamValueAsIntTest, InvalidIntegerThrows) { try { DeviceAttachmentSpec::parseParamValueAsInt("width", "not-a-number"); FAIL() << "Expected std::runtime_error"; } catch (const std::runtime_error& exception) { sscl::tests::expectExceptionMessageContains( exception, "Failed to parse 'width'"); } } TEST(DeviceAttachmentSpecParseParamValueAsIntTest, OutOfRangeThrows) { try { DeviceAttachmentSpec::parseParamValueAsInt( "width", "999999999999999999999"); FAIL() << "Expected std::runtime_error"; } catch (const std::runtime_error& exception) { sscl::tests::expectExceptionMessageContains( exception, "out of range"); } } TEST(DeviceAttachmentSpecParseParamValueAsPositiveIntTest, RejectsNonPositive) { EXPECT_EQ( DeviceAttachmentSpec::parseParamValueAsPositiveInt("width", "640"), 640); try { DeviceAttachmentSpec::parseParamValueAsPositiveInt("width", "0"); FAIL() << "Expected std::runtime_error"; } catch (const std::runtime_error& exception) { sscl::tests::expectExceptionMessageContains( exception, "must be positive"); } try { DeviceAttachmentSpec::parseParamValueAsPositiveInt("width", "-10"); FAIL() << "Expected std::runtime_error"; } catch (const std::runtime_error& exception) { sscl::tests::expectExceptionMessageContains( exception, "must be positive"); } } TEST(DeviceAttachmentSpecParseParamValueAsBoolTest, ParsesRecognizedValues) { EXPECT_TRUE(DeviceAttachmentSpec::parseParamValueAsBool("")); EXPECT_TRUE(DeviceAttachmentSpec::parseParamValueAsBool("true")); EXPECT_TRUE(DeviceAttachmentSpec::parseParamValueAsBool(" YES ")); EXPECT_TRUE(DeviceAttachmentSpec::parseParamValueAsBool("1")); EXPECT_FALSE(DeviceAttachmentSpec::parseParamValueAsBool("false")); EXPECT_FALSE(DeviceAttachmentSpec::parseParamValueAsBool("0")); EXPECT_FALSE(DeviceAttachmentSpec::parseParamValueAsBool("no")); EXPECT_FALSE(DeviceAttachmentSpec::parseParamValueAsBool( "", /*emptyMeansTrue=*/false)); } TEST(DeviceAttachmentSpecParseParamValueAsBoolTest, UnknownValueThrows) { EXPECT_THROW( DeviceAttachmentSpec::parseParamValueAsBool("maybe"), std::runtime_error); } TEST(DeviceAttachmentSpecParseRequiredParamAsIntTest, MissingParamThrows) { const ParamList params = makeParams({{"screen", "1"}}); try { DeviceAttachmentSpec::parseRequiredParamAsInt(params, "display"); FAIL() << "Expected std::runtime_error"; } catch (const std::runtime_error& exception) { sscl::tests::expectExceptionMessageContains( exception, "No display specified in params"); } } TEST(DeviceAttachmentSpecParseRequiredParamAsIntTest, LattermostValueWins) { const ParamList params = makeParams({ {"display", "0"}, {"display", "3"}, }); EXPECT_EQ( DeviceAttachmentSpec::parseRequiredParamAsInt(params, "display"), 3); } TEST(DeviceAttachmentSpecParseOptionalParamAsIntTest, ReturnsDefaultWhenAbsent) { const ParamList params = makeParams({{"screen", "1"}}); EXPECT_EQ( DeviceAttachmentSpec::parseOptionalParamAsInt(params, "display", 42), 42); } TEST(DeviceAttachmentSpecParseOptionalParamAsIntWithSynonymsTest, SynonymLattermostWins) { const std::vector synonyms = { "n-dgrams-per-frame", "num-dgrams-per-frame", }; const ParamList params = makeParams({ {"n-dgrams-per-frame", "10"}, {"num-dgrams-per-frame", "99"}, }); EXPECT_EQ( DeviceAttachmentSpec::parseOptionalParamAsIntWithSynonyms( params, synonyms, 84), 99); } TEST(DeviceAttachmentSpecParseRequiredParamAsIntWithSynonymsTest, ParsesMatchedSynonym) { const std::vector synonyms = { "cmd-timeout-ms", "command-timeout-ms", }; const ParamList params = makeParams({ {"command-timeout-ms", "15"}, }); EXPECT_EQ( DeviceAttachmentSpec::parseRequiredParamAsIntWithSynonyms( params, synonyms, "missing timeout"), 15); } TEST(DeviceAttachmentSpecParseRequiredParamValueWithSynonymsTest, RequiresNonEmptyValue) { const std::vector synonyms = {"colour-space"}; const ParamList missing = makeParams({{"width", "640"}}); try { DeviceAttachmentSpec::parseRequiredParamValueWithSynonyms( missing, synonyms, "colour-space required"); FAIL() << "Expected std::runtime_error"; } catch (const std::runtime_error& exception) { sscl::tests::expectExceptionMessageContains( exception, "colour-space required"); } const ParamList emptyValue = makeParams({{"colour-space", ""}}); try { DeviceAttachmentSpec::parseRequiredParamValueWithSynonyms( emptyValue, synonyms, "colour-space required"); FAIL() << "Expected std::runtime_error"; } catch (const std::runtime_error& exception) { sscl::tests::expectExceptionMessageContains( exception, "colour-space required"); } const ParamList present = makeParams({{"colour-space", "yuv"}}); EXPECT_EQ( DeviceAttachmentSpec::parseRequiredParamValueWithSynonyms( present, synonyms, "colour-space required"), "yuv"); } TEST(DeviceAttachmentSpecParseOptionalParamValueWithSynonymsTest, OptionalStringValue) { const std::vector synonyms = {"colour-space"}; const ParamList absent = makeParams({{"width", "640"}}); EXPECT_FALSE(DeviceAttachmentSpec::parseOptionalParamValueWithSynonyms( absent, synonyms, "empty colour-space").has_value()); const ParamList emptyValue = makeParams({{"colour-space", ""}}); try { DeviceAttachmentSpec::parseOptionalParamValueWithSynonyms( emptyValue, synonyms, "empty colour-space"); FAIL() << "Expected std::runtime_error"; } catch (const std::runtime_error& exception) { sscl::tests::expectExceptionMessageContains( exception, "empty colour-space"); } const ParamList present = makeParams({{"colour-space", "yuv"}}); const std::optional parsed = DeviceAttachmentSpec::parseOptionalParamValueWithSynonyms( present, synonyms, "empty colour-space"); ASSERT_TRUE(parsed.has_value()); EXPECT_EQ(*parsed, "yuv"); } TEST(DeviceAttachmentSpecParseOptionalParamAsBoolWithSynonymsTest, ParsesPresenceAndValues) { const std::vector synonyms = { "full-planar-is-optional", "opt-planar", }; const ParamList absent = makeParams({{"width", "640"}}); EXPECT_FALSE(DeviceAttachmentSpec::parseOptionalParamAsBoolWithSynonyms( absent, synonyms, false)); const ParamList presentEmpty = makeParams({{"opt-planar", ""}}); EXPECT_TRUE(DeviceAttachmentSpec::parseOptionalParamAsBoolWithSynonyms( presentEmpty, synonyms, false)); const ParamList presentFalse = makeParams({{"opt-planar", "false"}}); EXPECT_FALSE(DeviceAttachmentSpec::parseOptionalParamAsBoolWithSynonyms( presentFalse, synonyms, true)); const ParamList lattermostWins = makeParams({ {"opt-planar", "true"}, {"full-planar-is-optional", "false"}, }); EXPECT_FALSE(DeviceAttachmentSpec::parseOptionalParamAsBoolWithSynonyms( lattermostWins, synonyms, true)); } TEST(DeviceAttachmentSpecRejectUnknownParamsTest, RejectsUnknownNames) { const std::vector knownA = {"width", "dim-w"}; const std::vector knownB = {"height", "dim-h"}; const ParamList params = makeParams({ {"dim-w", "640"}, {"unknown-param", "1"}, }); try { DeviceAttachmentSpec::rejectUnknownParams( params, "unknown param '", knownA, knownB); FAIL() << "Expected std::runtime_error"; } catch (const std::runtime_error& exception) { sscl::tests::expectExceptionMessageContains( exception, "unknown-param"); } } TEST(DeviceAttachmentSpecRejectUnknownParamsTest, AcceptsAllKnownNames) { const std::vector knownA = {"width", "dim-w"}; const std::vector knownB = {"height", "dim-h"}; const ParamList params = makeParams({ {"dim-w", "640"}, {"dim-h", "480"}, }); EXPECT_NO_THROW(DeviceAttachmentSpec::rejectUnknownParams( params, "unknown param '", knownA, knownB)); } } // namespace } // namespace device } // namespace smo