From patchwork Tue Apr 7 15:34:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 26468 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id B02DBC3318 for ; Tue, 7 Apr 2026 15:35:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C204262E14; Tue, 7 Apr 2026 17:35:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="u7Y+BOH4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1A3A062DF3 for ; Tue, 7 Apr 2026 17:35:07 +0200 (CEST) Received: from killaraus.ideasonboard.com (2001-14ba-703d-e500--2a1.rev.dnainternet.fi [IPv6:2001:14ba:703d:e500::2a1]) by perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 6709178E for ; Tue, 7 Apr 2026 17:33:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775576019; bh=DAIODyJlod0XmZfvzA3o03Ugcr9m2dtZ71EOuQ5PcgQ=; h=From:To:Subject:Date:In-Reply-To:References:From; b=u7Y+BOH45NfBT7KGLZ12A9M7dU6eCqq2Z92HD+0XScKwo4hruTBlBVOFVIGC9S0/L IWLENa/EVYbipdM8gcDCwLmyn8p758iVPAOYcB7kLowcH/Rh1lzM5H5L7DlQ38VfWd iHsyYEf/TZc2+oYpEHKnpcplYctrwVfbB9H0v0lk= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Subject: [PATCH v2 28/42] test: yaml-parser: Simplify test Date: Tue, 7 Apr 2026 18:34:13 +0300 Message-ID: <20260407153427.1825999-29-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260407153427.1825999-1-laurent.pinchart@ideasonboard.com> References: <20260407153427.1825999-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Signed-off-by: Laurent Pinchart --- test/yaml-parser.cpp | 469 +++++-------------------------------------- 1 file changed, 50 insertions(+), 419 deletions(-) diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp index 8c5826f4885b..0bbda3af8ea9 100644 --- a/test/yaml-parser.cpp +++ b/test/yaml-parser.cpp @@ -12,7 +12,6 @@ #include #include -#include #include @@ -24,24 +23,12 @@ using namespace libcamera; using namespace std; static const string testYaml = - "string: libcamera\n" - "double: 3.14159\n" - "int8_t: -100\n" - "uint8_t: 100\n" - "int16_t: -1000\n" - "uint16_t: 1000\n" - "int32_t: -100000\n" - "uint32_t: 100000\n" - "size: [1920, 1080]\n" + "empty:\n" + "value: 42\n" "list:\n" - " - James\n" - " - Mary\n" + " - libcamera\n" + " - linux\n" " - \n" - "dictionary:\n" - " a: 1\n" - " c: 3\n" - " b: 2\n" - " empty:\n" "level1:\n" " level2:\n" " - [1, 2]\n" @@ -80,212 +67,9 @@ protected: return TestPass; } - enum class Type { - String, - Int8, - UInt8, - Int16, - UInt16, - Int32, - UInt32, - Double, - Size, - List, - Dictionary, - }; - - int testObjectType(const ValueNode &obj, const char *name, Type type) - { - bool isList = type == Type::List || type == Type::Size; - bool isScalar = !isList && type != Type::Dictionary; - bool isInteger8 = type == Type::Int8 || type == Type::UInt8; - bool isInteger16 = type == Type::Int16 || type == Type::UInt16; - bool isInteger32 = type == Type::Int32 || type == Type::UInt32; - bool isIntegerUpTo16 = isInteger8 || isInteger16; - bool isIntegerUpTo32 = isIntegerUpTo16 || isInteger32; - bool isSigned = type == Type::Int8 || type == Type::Int16 || - type == Type::Int32; - - if ((isScalar && !obj.isValue()) || (!isScalar && obj.isValue())) { - std::cerr - << "Object " << name << " type mismatch when compared to " - << "value" << std::endl; - return TestFail; - } - - if ((isList && !obj.isList()) || (!isList && obj.isList())) { - std::cerr - << "Object " << name << " type mismatch when compared to " - << "list" << std::endl; - return TestFail; - } - - if ((type == Type::Dictionary && !obj.isDictionary()) || - (type != Type::Dictionary && obj.isDictionary())) { - std::cerr - << "Object " << name << " type mismatch when compared to " - << "dictionary" << std::endl; - return TestFail; - } - - if (!isScalar && obj.get()) { - std::cerr - << "Object " << name << " didn't fail to parse as " - << "string" << std::endl; - return TestFail; - } - - if (!isInteger8 && obj.get()) { - std::cerr - << "Object " << name << " didn't fail to parse as " - << "int8_t" << std::endl; - return TestFail; - } - - if ((!isInteger8 || isSigned) && obj.get()) { - std::cerr - << "Object " << name << " didn't fail to parse as " - << "uint8_t" << std::endl; - return TestFail; - } - - if (!isIntegerUpTo16 && obj.get()) { - std::cerr - << "Object " << name << " didn't fail to parse as " - << "int16_t" << std::endl; - return TestFail; - } - - if ((!isIntegerUpTo16 || isSigned) && obj.get()) { - std::cerr - << "Object " << name << " didn't fail to parse as " - << "uint16_t" << std::endl; - return TestFail; - } - - if (!isIntegerUpTo32 && obj.get()) { - std::cerr - << "Object " << name << " didn't fail to parse as " - << "int32_t" << std::endl; - return TestFail; - } - - if ((!isIntegerUpTo32 || isSigned) && obj.get()) { - std::cerr - << "Object " << name << " didn't fail to parse as " - << "uint32_t" << std::endl; - return TestFail; - } - - if (!isIntegerUpTo32 && type != Type::Double && obj.get()) { - std::cerr - << "Object " << name << " didn't fail to parse as " - << "double" << std::endl; - return TestFail; - } - - if (type != Type::Size && obj.get()) { - std::cerr - << "Object " << name << " didn't fail to parse as " - << "Size" << std::endl; - return TestFail; - } - - return TestPass; - } - - int testIntegerObject(const ValueNode &obj, const char *name, Type type, - int64_t value) - { - uint64_t unsignedValue = static_cast(value); - std::string strValue = std::to_string(value); - bool isInteger8 = type == Type::Int8 || type == Type::UInt8; - bool isInteger16 = type == Type::Int16 || type == Type::UInt16; - bool isSigned = type == Type::Int8 || type == Type::Int16 || - type == Type::Int32; - - /* All integers can be parsed as strings or double. */ - - if (obj.get().value_or("") != strValue || - obj.get("") != strValue) { - std::cerr - << "Object " << name << " failed to parse as " - << "string" << std::endl; - return TestFail; - } - - if (obj.get().value_or(0.0) != value || - obj.get(0.0) != value) { - std::cerr - << "Object " << name << " failed to parse as " - << "double" << std::endl; - return TestFail; - } - - if (isInteger8) { - if (obj.get().value_or(0) != value || - obj.get(0) != value) { - std::cerr - << "Object " << name << " failed to parse as " - << "int8_t" << std::endl; - return TestFail; - } - } - - if (isInteger8 && !isSigned) { - if (obj.get().value_or(0) != unsignedValue || - obj.get(0) != unsignedValue) { - std::cerr - << "Object " << name << " failed to parse as " - << "uint8_t" << std::endl; - return TestFail; - } - } - - if (isInteger8 || isInteger16) { - if (obj.get().value_or(0) != value || - obj.get(0) != value) { - std::cerr - << "Object " << name << " failed to parse as " - << "int16_t" << std::endl; - return TestFail; - } - } - - if ((isInteger8 || isInteger16) && !isSigned) { - if (obj.get().value_or(0) != unsignedValue || - obj.get(0) != unsignedValue) { - std::cerr - << "Object " << name << " failed to parse as " - << "uint16_t" << std::endl; - return TestFail; - } - } - - if (obj.get().value_or(0) != value || - obj.get(0) != value) { - std::cerr - << "Object " << name << " failed to parse as " - << "int32_t" << std::endl; - return TestFail; - } - - if (!isSigned) { - if (obj.get().value_or(0) != unsignedValue || - obj.get(0) != unsignedValue) { - std::cerr - << "Object " << name << " failed to parse as " - << "uint32_t" << std::endl; - return TestFail; - } - } - - return TestPass; - } - int run() { - /* Test invalid YAML file */ + /* Test parsing invalid YAML file. */ File file{ invalidYamlFile_ }; if (!file.open(File::OpenModeFlag::ReadOnly)) { cerr << "Fail to open invalid YAML file" << std::endl; @@ -298,7 +82,7 @@ protected: return TestFail; } - /* Test YAML file */ + /* Test parsing valid YAML file. */ file.close(); file.setFileName(testYamlFile_); if (!file.open(File::OpenModeFlag::ReadOnly)) { @@ -313,130 +97,66 @@ protected: return TestFail; } + /* Test that the root dictionary node has been parsed correctly. */ if (!root->isDictionary()) { - cerr << "YAML root is not dictionary" << std::endl; + cerr << "Dictionary node has wrong type" << std::endl; return TestFail; } - std::vector rootElemNames = { - "string", "double", "int8_t", "uint8_t", "int16_t", - "uint16_t", "int32_t", "uint32_t", "size", "list", - "dictionary", "level1", - }; + using NodeFunc = bool (ValueNode::*)() const; - for (const char *name : rootElemNames) { - if (!root->contains(name)) { - cerr << "Missing " << name << " object in YAML root" - << std::endl; + std::map topLevelNodes = { { + { "empty", &ValueNode::isValue }, + { "value", &ValueNode::isValue }, + { "list", &ValueNode::isList }, + { "level1", &ValueNode::isDictionary }, + } }; + + if (root->size() != topLevelNodes.size()) { + std::cerr << "Dictionary node has wrong size" << std::endl; + return TestFail; + } + + for (const auto &[key, value] : root->asDict()) { + const auto iter = topLevelNodes.find(key); + if (iter == topLevelNodes.end()) { + std::cerr << "Dictionary key '" << key << "' unknown" + << std::endl; return TestFail; } + + const auto &func = iter->second; + if (!(value.*func)()) { + std::cerr << "Node '" << key << "' has wrong type" + << std::endl; + return TestFail; + } + + topLevelNodes.erase(iter); } - /* Test string object */ - auto &strObj = (*root)["string"]; + /* Test empty node. */ + auto &emptyNode = (*root)["empty"]; - if (testObjectType(strObj, "string", Type::String) != TestPass) - return TestFail; - - if (strObj.get().value_or("") != "libcamera" || - strObj.get("") != "libcamera") { - cerr << "String object parse as wrong content" << std::endl; + if (emptyNode.get("-") != "") { + std::cerr << "Empty node has incorrect content" << std::endl; return TestFail; } - /* Test int8_t object */ - auto &int8Obj = (*root)["int8_t"]; + /* Test value node. */ + auto &valueNode = (*root)["value"]; - if (testObjectType(int8Obj, "int8_t", Type::Int8) != TestPass) - return TestFail; - - if (testIntegerObject(int8Obj, "int8_t", Type::Int8, -100) != TestPass) - return TestFail; - - /* Test uint8_t object */ - auto &uint8Obj = (*root)["uint8_t"]; - - if (testObjectType(uint8Obj, "uint8_t", Type::UInt8) != TestPass) - return TestFail; - - if (testIntegerObject(uint8Obj, "uint8_t", Type::UInt8, 100) != TestPass) - return TestFail; - - /* Test int16_t object */ - auto &int16Obj = (*root)["int16_t"]; - - if (testObjectType(int16Obj, "int16_t", Type::Int16) != TestPass) - return TestFail; - - if (testIntegerObject(int16Obj, "int16_t", Type::Int16, -1000) != TestPass) - return TestFail; - - /* Test uint16_t object */ - auto &uint16Obj = (*root)["uint16_t"]; - - if (testObjectType(uint16Obj, "uint16_t", Type::UInt16) != TestPass) - return TestFail; - - if (testIntegerObject(uint16Obj, "uint16_t", Type::UInt16, 1000) != TestPass) - return TestFail; - - /* Test int32_t object */ - auto &int32Obj = (*root)["int32_t"]; - - if (testObjectType(int32Obj, "int32_t", Type::Int32) != TestPass) - return TestFail; - - if (testIntegerObject(int32Obj, "int32_t", Type::Int32, -100000) != TestPass) - return TestFail; - - /* Test uint32_t object */ - auto &uint32Obj = (*root)["uint32_t"]; - - if (testObjectType(uint32Obj, "uint32_t", Type::UInt32) != TestPass) - return TestFail; - - if (testIntegerObject(uint32Obj, "uint32_t", Type::UInt32, 100000) != TestPass) - return TestFail; - - /* Test double value */ - auto &doubleObj = (*root)["double"]; - - if (testObjectType(doubleObj, "double", Type::Double) != TestPass) - return TestFail; - - if (doubleObj.get().value_or("") != "3.14159" || - doubleObj.get("") != "3.14159") { - cerr << "Double object fail to parse as string" << std::endl; + if (valueNode.get("") != "42") { + std::cerr << "Value node has incorrect content" << std::endl; return TestFail; } - if (doubleObj.get().value_or(0.0) != 3.14159 || - doubleObj.get(0.0) != 3.14159) { - cerr << "Double object parse as wrong value" << std::endl; - return TestFail; - } - - /* Test Size value */ - auto &sizeObj = (*root)["size"]; - - if (testObjectType(sizeObj, "size", Type::Size) != TestPass) - return TestFail; - - if (sizeObj.get().value_or(Size(0, 0)) != Size(1920, 1080) || - sizeObj.get(Size(0, 0)) != Size(1920, 1080)) { - cerr << "Size object parse as wrong value" << std::endl; - return TestFail; - } - - /* Test list object */ + /* Test list node. */ auto &listObj = (*root)["list"]; - if (testObjectType(listObj, "list", Type::List) != TestPass) - return TestFail; - static constexpr std::array listValues{ - "James", - "Mary", + "libcamera", + "linux", "", }; @@ -470,102 +190,13 @@ protected: i++; } - /* Ensure that empty objects get parsed as empty strings. */ + /* Ensure that empty list elements get parsed as empty strings. */ if (!listObj[2].isValue()) { - cerr << "Empty object is not a value" << std::endl; + cerr << "Empty list element is not a value" << std::endl; return TestFail; } - /* Test dictionary object */ - auto &dictObj = (*root)["dictionary"]; - - if (testObjectType(dictObj, "dictionary", Type::Dictionary) != TestPass) - return TestFail; - - static constexpr std::array, 4> dictValues{ { - { "a", 1 }, - { "c", 3 }, - { "b", 2 }, - { "empty", -100 }, - } }; - - size_t dictSize = dictValues.size(); - - if (dictObj.size() != dictSize) { - cerr << "Dictionary object has wrong size" << std::endl; - return TestFail; - } - - i = 0; - for (const auto &[key, elem] : dictObj.asDict()) { - if (i >= dictSize) { - std::cerr << "Too many elements in dictionary during iteration" - << std::endl; - return TestFail; - } - - const auto &item = dictValues[i]; - if (item.first != key) { - std::cerr << "Dictionary key " << i << " has wrong value" - << std::endl; - return TestFail; - } - - if (&elem != &dictObj[key]) { - std::cerr << "Dictionary element " << i << " has wrong address" - << std::endl; - return TestFail; - } - - if (elem.get(-100) != item.second) { - std::cerr << "Dictionary element " << i << " has wrong value" - << std::endl; - return TestFail; - } - - i++; - } - - /* Ensure that empty objects get parsed as empty strings. */ - if (!dictObj["empty"].isValue()) { - cerr << "Empty object is not of type value" << std::endl; - return TestFail; - } - - /* Ensure that keys without values are added to a dict. */ - if (!dictObj.contains("empty")) { - cerr << "Empty element is missing in dict" << std::endl; - return TestFail; - } - - /* Test access to nonexistent member. */ - if (dictObj["nonexistent"].get("default") != "default") { - cerr << "Accessing nonexistent dict entry fails to return default" << std::endl; - return TestFail; - } - - /* Test nonexistent object has value type empty. */ - if (!dictObj["nonexistent"].isEmpty()) { - cerr << "Accessing nonexistent object returns non-empty object" << std::endl; - return TestFail; - } - - /* Test explicit cast to bool on an empty object returns true. */ - if (!!dictObj["empty"] != true) { - cerr << "Casting empty entry to bool returns false" << std::endl; - return TestFail; - } - - /* Test explicit cast to bool on nonexistent object returns false. */ - if (!!dictObj["nonexistent"] != false) { - cerr << "Casting nonexistent dict entry to bool returns true" << std::endl; - return TestFail; - } - - /* Make sure utils::map_keys() works on the adapter. */ - (void)utils::map_keys(dictObj.asDict()); - - /* Test leveled objects */ + /* Test nested nodes. */ auto &level1Obj = (*root)["level1"]; if (!level1Obj.isDictionary()) { @@ -576,7 +207,7 @@ protected: auto &level2Obj = level1Obj["level2"]; if (!level2Obj.isList() || level2Obj.size() != 2) { - cerr << "level2 object should be 2 element list" << std::endl; + cerr << "level2 object should be a 2 elements list" << std::endl; return TestFail; }