[v3,22/37] test: yaml-parser: Simplify test
diff mbox series

Message ID 20260423230059.3180987-23-laurent.pinchart@ideasonboard.com
State New
Headers show
Series
  • libcamera: Global configuration file improvements
Related show

Commit Message

Laurent Pinchart April 23, 2026, 11 p.m. UTC
Most of the tests in the YamlParser unit test cover the ValueNode class.
They are now implemented in the ValueNode unit test. Drop them, and only
keep the tests that related to YAML parsing.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 test/yaml-parser.cpp | 469 +++++--------------------------------------
 1 file changed, 50 insertions(+), 419 deletions(-)

Patch
diff mbox series

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 <unistd.h>
 
 #include <libcamera/base/file.h>
-#include <libcamera/base/utils.h>
 
 #include <libcamera/geometry.h>
 
@@ -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::string>()) {
-			std::cerr
-				<< "Object " << name << " didn't fail to parse as "
-				<< "string" << std::endl;
-			return TestFail;
-		}
-
-		if (!isInteger8 && obj.get<int8_t>()) {
-			std::cerr
-				<< "Object " << name << " didn't fail to parse as "
-				<< "int8_t" << std::endl;
-			return TestFail;
-		}
-
-		if ((!isInteger8 || isSigned) && obj.get<uint8_t>()) {
-			std::cerr
-				<< "Object " << name << " didn't fail to parse as "
-				<< "uint8_t" << std::endl;
-			return TestFail;
-		}
-
-		if (!isIntegerUpTo16 && obj.get<int16_t>()) {
-			std::cerr
-				<< "Object " << name << " didn't fail to parse as "
-				<< "int16_t" << std::endl;
-			return TestFail;
-		}
-
-		if ((!isIntegerUpTo16 || isSigned) && obj.get<uint16_t>()) {
-			std::cerr
-				<< "Object " << name << " didn't fail to parse as "
-				<< "uint16_t" << std::endl;
-			return TestFail;
-		}
-
-		if (!isIntegerUpTo32 && obj.get<int32_t>()) {
-			std::cerr
-				<< "Object " << name << " didn't fail to parse as "
-				<< "int32_t" << std::endl;
-			return TestFail;
-		}
-
-		if ((!isIntegerUpTo32 || isSigned) && obj.get<uint32_t>()) {
-			std::cerr
-				<< "Object " << name << " didn't fail to parse as "
-				<< "uint32_t" << std::endl;
-			return TestFail;
-		}
-
-		if (!isIntegerUpTo32 && type != Type::Double && obj.get<double>()) {
-			std::cerr
-				<< "Object " << name << " didn't fail to parse as "
-				<< "double" << std::endl;
-			return TestFail;
-		}
-
-		if (type != Type::Size && obj.get<Size>()) {
-			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<uint64_t>(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<string>().value_or("") != strValue ||
-		    obj.get<string>("") != strValue) {
-			std::cerr
-				<< "Object " << name << " failed to parse as "
-				<< "string" << std::endl;
-			return TestFail;
-		}
-
-		if (obj.get<double>().value_or(0.0) != value ||
-		    obj.get<double>(0.0) != value) {
-			std::cerr
-				<< "Object " << name << " failed to parse as "
-				<< "double" << std::endl;
-			return TestFail;
-		}
-
-		if (isInteger8) {
-			if (obj.get<int8_t>().value_or(0) != value ||
-			    obj.get<int8_t>(0) != value) {
-				std::cerr
-					<< "Object " << name << " failed to parse as "
-					<< "int8_t" << std::endl;
-				return TestFail;
-			}
-		}
-
-		if (isInteger8 && !isSigned) {
-			if (obj.get<uint8_t>().value_or(0) != unsignedValue ||
-			    obj.get<uint8_t>(0) != unsignedValue) {
-				std::cerr
-					<< "Object " << name << " failed to parse as "
-					<< "uint8_t" << std::endl;
-				return TestFail;
-			}
-		}
-
-		if (isInteger8 || isInteger16) {
-			if (obj.get<int16_t>().value_or(0) != value ||
-			    obj.get<int16_t>(0) != value) {
-				std::cerr
-					<< "Object " << name << " failed to parse as "
-					<< "int16_t" << std::endl;
-				return TestFail;
-			}
-		}
-
-		if ((isInteger8 || isInteger16) && !isSigned) {
-			if (obj.get<uint16_t>().value_or(0) != unsignedValue ||
-			    obj.get<uint16_t>(0) != unsignedValue) {
-				std::cerr
-					<< "Object " << name << " failed to parse as "
-					<< "uint16_t" << std::endl;
-				return TestFail;
-			}
-		}
-
-		if (obj.get<int32_t>().value_or(0) != value ||
-		    obj.get<int32_t>(0) != value) {
-			std::cerr
-				<< "Object " << name << " failed to parse as "
-				<< "int32_t" << std::endl;
-			return TestFail;
-		}
-
-		if (!isSigned) {
-			if (obj.get<uint32_t>().value_or(0) != unsignedValue ||
-			    obj.get<uint32_t>(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<const char *> 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<std::string, NodeFunc> 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<string>().value_or("") != "libcamera" ||
-		    strObj.get<string>("") != "libcamera") {
-			cerr << "String object parse as wrong content" << std::endl;
+		if (emptyNode.get<string>("-") != "") {
+			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<string>().value_or("") != "3.14159" ||
-		    doubleObj.get<string>("") != "3.14159") {
-			cerr << "Double object fail to parse as string" << std::endl;
+		if (valueNode.get<string>("") != "42") {
+			std::cerr << "Value node has incorrect content" << std::endl;
 			return TestFail;
 		}
 
-		if (doubleObj.get<double>().value_or(0.0) != 3.14159 ||
-		    doubleObj.get<double>(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<Size>().value_or(Size(0, 0)) != Size(1920, 1080) ||
-		    sizeObj.get<Size>(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<const char *, 3> 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<std::pair<const char *, int>, 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<int32_t>(-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<std::string>("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;
 		}