diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp index 8c5826f4..0bbda3af 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; }