{"id":26543,"url":"https://patchwork.libcamera.org/api/patches/26543/?format=json","web_url":"https://patchwork.libcamera.org/patch/26543/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260423230059.3180987-22-laurent.pinchart@ideasonboard.com>","date":"2026-04-23T23:00:43","name":"[v3,21/37] test: Add ValueNode unit test","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"093d49d251f9e66dc796eb822422b4f731bf73f8","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/?format=json","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/26543/mbox/","series":[{"id":5883,"url":"https://patchwork.libcamera.org/api/series/5883/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5883","date":"2026-04-23T23:00:22","name":"libcamera: Global configuration file improvements","version":3,"mbox":"https://patchwork.libcamera.org/series/5883/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/26543/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/26543/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id AE8ADBDCB5\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 23 Apr 2026 23:01:46 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 1D05E62F75;\n\tFri, 24 Apr 2026 01:01:46 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8C37D62F7B\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Apr 2026 01:01:30 +0200 (CEST)","from killaraus.ideasonboard.com\n\t(2001-14ba-703d-e500--2a1.rev.dnainternet.fi\n\t[IPv6:2001:14ba:703d:e500::2a1])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 09D48802\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Apr 2026 00:59:50 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"GzkPvzSB\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1776985191;\n\tbh=hvL4+04xk3K2ZACY0uey9xClIel8AEr7z8Z9nj0qExk=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=GzkPvzSBaRMygMTT/5D4zYAlGVxyR4XqgScrvwuM5SvR3ELIYO5Yen/zTLnW5/VEx\n\tr7/bhB2ZIb4ZWnNLUdhvavTyZlRw+0nAT8yN9El6aQEbIyUacDxZfyxBmB4nG0+C2x\n\tG1nkqbIwbl2odeuBJCGFpcxCPV7S4doqz4IMy3LY=","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Subject":"[PATCH v3 21/37] test: Add ValueNode unit test","Date":"Fri, 24 Apr 2026 02:00:43 +0300","Message-ID":"<20260423230059.3180987-22-laurent.pinchart@ideasonboard.com>","X-Mailer":"git-send-email 2.53.0","In-Reply-To":"<20260423230059.3180987-1-laurent.pinchart@ideasonboard.com>","References":"<20260423230059.3180987-1-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Add a unit test for the ValueNode class. The tests focus on the class\nitself, without considering that is currently only used when parsing\nYAML files. This duplicates some of the tests of the YamlParser class,\nwhich will be dropped from the corresponding unit test in a subsequent\nchange.\n\nSigned-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\nReviewed-by: Isaac Scott <isaac.scott@ideasonboard.com>\n---\nChanges since v2:\n\n- Add commit message\n- Drop 'overloaded' definition\n---\n test/meson.build    |   1 +\n test/value-node.cpp | 558 ++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 559 insertions(+)\n create mode 100644 test/value-node.cpp","diff":"diff --git a/test/meson.build b/test/meson.build\nindex 52f04364e4fc..e4450625ee4c 100644\n--- a/test/meson.build\n+++ b/test/meson.build\n@@ -74,6 +74,7 @@ internal_tests = [\n     {'name': 'timer-thread', 'sources': ['timer-thread.cpp']},\n     {'name': 'unique-fd', 'sources': ['unique-fd.cpp']},\n     {'name': 'utils', 'sources': ['utils.cpp']},\n+    {'name': 'value-node', 'sources': ['value-node.cpp']},\n     {'name': 'vector', 'sources': ['vector.cpp']},\n     {'name': 'yaml-parser', 'sources': ['yaml-parser.cpp']},\n ]\ndiff --git a/test/value-node.cpp b/test/value-node.cpp\nnew file mode 100644\nindex 000000000000..3b6466e75b13\n--- /dev/null\n+++ b/test/value-node.cpp\n@@ -0,0 +1,558 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2026, Ideas on Board\n+ *\n+ * ValueNode tests\n+ */\n+\n+#include <array>\n+#include <cmath>\n+#include <iostream>\n+#include <map>\n+#include <set>\n+#include <string>\n+#include <string_view>\n+#include <variant>\n+\n+#include <libcamera/base/utils.h>\n+#include <libcamera/geometry.h>\n+\n+#include \"libcamera/internal/value_node.h\"\n+\n+#include \"test.h\"\n+\n+using namespace libcamera;\n+using namespace std;\n+\n+class ValueNodeTest : public Test\n+{\n+protected:\n+\tenum class NodeType {\n+\t\tEmpty,\n+\t\tValue,\n+\t\tList,\n+\t\tDictionary,\n+\t};\n+\n+\tenum class ValueType {\n+\t\tInt8,\n+\t\tUInt8,\n+\t\tInt16,\n+\t\tUInt16,\n+\t\tInt32,\n+\t\tUInt32,\n+\t\tFloat,\n+\t\tDouble,\n+\t\tString,\n+\t\tSize,\n+\t};\n+\n+\tint testNodeValueType(const ValueNode &node, std::string_view name, ValueType type)\n+\t{\n+\t\tbool isInteger8 = type == ValueType::Int8 || type == ValueType::UInt8;\n+\t\tbool isInteger16 = type == ValueType::Int16 || type == ValueType::UInt16;\n+\t\tbool isInteger32 = type == ValueType::Int32 || type == ValueType::UInt32;\n+\t\tbool isIntegerUpTo16 = isInteger8 || isInteger16;\n+\t\tbool isIntegerUpTo32 = isIntegerUpTo16 || isInteger32;\n+\t\tbool isSigned = type == ValueType::Int8 || type == ValueType::Int16 ||\n+\t\t\t\ttype == ValueType::Int32;\n+\n+\t\tif (!isInteger8 && node.get<int8_t>()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" didn't fail to parse as \"\n+\t\t\t\t<< \"int8_t\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif ((!isInteger8 || isSigned) && node.get<uint8_t>()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" didn't fail to parse as \"\n+\t\t\t\t<< \"uint8_t\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (!isIntegerUpTo16 && node.get<int16_t>()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" didn't fail to parse as \"\n+\t\t\t\t<< \"int16_t\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif ((!isIntegerUpTo16 || isSigned) && node.get<uint16_t>()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" didn't fail to parse as \"\n+\t\t\t\t<< \"uint16_t\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (!isIntegerUpTo32 && node.get<int32_t>()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" didn't fail to parse as \"\n+\t\t\t\t<< \"int32_t\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif ((!isIntegerUpTo32 || isSigned) && node.get<uint32_t>()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" didn't fail to parse as \"\n+\t\t\t\t<< \"uint32_t\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (!isIntegerUpTo32 && type != ValueType::Float &&\n+\t\t    type != ValueType::Double && node.get<double>()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" didn't fail to parse as \"\n+\t\t\t\t<< \"double\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (type != ValueType::Size && node.get<Size>()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" didn't fail to parse as \"\n+\t\t\t\t<< \"Size\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\treturn TestPass;\n+\t}\n+\n+\tint testIntegerValue(const ValueNode &node, std::string_view name,\n+\t\t\t     ValueType type, int64_t value)\n+\t{\n+\t\tuint64_t unsignedValue = static_cast<uint64_t>(value);\n+\t\tstd::string strValue = std::to_string(value);\n+\t\tbool isSigned = type == ValueType::Int8 || type == ValueType::Int16 ||\n+\t\t\t\ttype == ValueType::Int32;\n+\t\tbool isInteger8 = type == ValueType::Int8 || type == ValueType::UInt8;\n+\t\tbool isInteger16 = type == ValueType::Int16 || type == ValueType::UInt16;\n+\n+\t\t/* All integers can be accessed as strings and double. */\n+\n+\t\tif (node.get<string>().value_or(\"\") != strValue ||\n+\t\t    node.get<string>(\"\") != strValue) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" failed to parse as \"\n+\t\t\t\t<< \"string\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (node.get<double>().value_or(0.0) != value ||\n+\t\t    node.get<double>(0.0) != value) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" failed to parse as \"\n+\t\t\t\t<< \"double\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (isInteger8) {\n+\t\t\tif (node.get<int8_t>().value_or(0) != value ||\n+\t\t\t    node.get<int8_t>(0) != value) {\n+\t\t\t\tstd::cerr\n+\t\t\t\t\t<< \"Node \" << name << \" failed to parse as \"\n+\t\t\t\t\t<< \"int8_t\" << std::endl;\n+\t\t\t\treturn TestFail;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (isInteger8 && !isSigned) {\n+\t\t\tif (node.get<uint8_t>().value_or(0) != unsignedValue ||\n+\t\t\t    node.get<uint8_t>(0) != unsignedValue) {\n+\t\t\t\tstd::cerr\n+\t\t\t\t\t<< \"Node \" << name << \" failed to parse as \"\n+\t\t\t\t\t<< \"uint8_t\" << std::endl;\n+\t\t\t\treturn TestFail;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (isInteger8 || isInteger16) {\n+\t\t\tif (node.get<int16_t>().value_or(0) != value ||\n+\t\t\t    node.get<int16_t>(0) != value) {\n+\t\t\t\tstd::cerr\n+\t\t\t\t\t<< \"Node \" << name << \" failed to parse as \"\n+\t\t\t\t\t<< \"int16_t\" << std::endl;\n+\t\t\t\treturn TestFail;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif ((isInteger8 || isInteger16) && !isSigned) {\n+\t\t\tif (node.get<uint16_t>().value_or(0) != unsignedValue ||\n+\t\t\t    node.get<uint16_t>(0) != unsignedValue) {\n+\t\t\t\tstd::cerr\n+\t\t\t\t\t<< \"Node \" << name << \" failed to parse as \"\n+\t\t\t\t\t<< \"uint16_t\" << std::endl;\n+\t\t\t\treturn TestFail;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (node.get<int32_t>().value_or(0) != value ||\n+\t\t    node.get<int32_t>(0) != value) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" failed to parse as \"\n+\t\t\t\t<< \"int32_t\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (!isSigned) {\n+\t\t\tif (node.get<uint32_t>().value_or(0) != unsignedValue ||\n+\t\t\t    node.get<uint32_t>(0) != unsignedValue) {\n+\t\t\t\tstd::cerr\n+\t\t\t\t\t<< \"Node \" << name << \" failed to parse as \"\n+\t\t\t\t\t<< \"uint32_t\" << std::endl;\n+\t\t\t\treturn TestFail;\n+\t\t\t}\n+\t\t}\n+\n+\t\treturn TestPass;\n+\t}\n+\n+\ttemplate<typename T>\n+\tbool equal(const ValueNode &node, T value)\n+\t{\n+\t\tconstexpr T eps = std::numeric_limits<T>::epsilon();\n+\n+\t\tif (std::abs(node.get<T>().value_or(0.0) - value) >= eps)\n+\t\t\treturn false;\n+\t\tif (std::abs(node.get<T>(0.0) - value) >= eps)\n+\t\t\treturn false;\n+\t\treturn true;\n+\t}\n+\n+\tint testFloatValue(const ValueNode &node, std::string_view name, double value)\n+\t{\n+\t\tstd::string strValue = std::to_string(value);\n+\n+\t\tif (node.get<string>().value_or(\"\") != strValue ||\n+\t\t    node.get<string>(\"\") != strValue) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" failed to parse as \"\n+\t\t\t\t<< \"string\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (!equal<float>(node, value)) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" failed to parse as \"\n+\t\t\t\t<< \"float\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (!equal<double>(node, value)) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Node \" << name << \" failed to parse as \"\n+\t\t\t\t<< \"double\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\treturn TestPass;\n+\t}\n+\n+\tbool testNodeType(const ValueNode &node, NodeType nodeType)\n+\t{\n+\t\tusing NodeFunc = bool (ValueNode::*)() const;\n+\t\tusing NodeDesc = std::tuple<NodeType, std::string_view, NodeFunc>;\n+\n+\t\tstatic constexpr std::array<NodeDesc, 4> nodeTypes = { {\n+\t\t\tNodeDesc{ NodeType::Empty, \"empty\", &ValueNode::isEmpty },\n+\t\t\tNodeDesc{ NodeType::Value, \"value\", &ValueNode::isValue },\n+\t\t\tNodeDesc{ NodeType::List, \"list\", &ValueNode::isList },\n+\t\t\tNodeDesc{ NodeType::Dictionary, \"dictionary\", &ValueNode::isDictionary },\n+\t\t} };\n+\n+\t\tfor (const auto &[type, name, func] : nodeTypes) {\n+\t\t\tbool value = type == nodeType;\n+\t\t\tif ((node.*func)() != value) {\n+\t\t\t\tstd::cerr\n+\t\t\t\t\t<< \"Empty ValueNode should \"\n+\t\t\t\t\t<< (value ? \"\" : \"not \") << \"be a \"\n+\t\t\t\t\t<< name << std::endl;\n+\t\t\t\treturn false;\n+\t\t\t}\n+\t\t}\n+\n+\t\treturn true;\n+\t}\n+\n+\tint run()\n+\t{\n+\t\t/* Tests on empty nodes. */\n+\t\tValueNode emptyNode;\n+\n+\t\tif (!testNodeType(emptyNode, NodeType::Empty)) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Empty node should have empty type\"\n+\t\t\t\t<< std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (static_cast<bool>(emptyNode)) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Empty node should cast to false\"\n+\t\t\t\t<< std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (emptyNode.size()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Empty node should have zero size\"\n+\t\t\t\t<< std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (emptyNode.get<std::string>()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Empty node should have no value\"\n+\t\t\t\t<< std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\t/* Tests on list nodes. */\n+\t\tValueNode listNode;\n+\n+\t\tstatic constexpr std::array<std::string_view, 3> listElemNames = {\n+\t\t\t\"libcamera\", \"linux\", \"isp\"\n+\t\t};\n+\n+\t\tfor (const auto &name : listElemNames)\n+\t\t\tlistNode.add(std::make_unique<ValueNode>(std::string{ name }));\n+\n+\t\tif (!testNodeType(listNode, NodeType::List))\n+\t\t\treturn TestFail;\n+\n+\t\tif (!static_cast<bool>(listNode)) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"List node should cast to true\"\n+\t\t\t\t<< std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (listNode.size() != 3) {\n+\t\t\tstd::cerr << \"Invalid list node size\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tlistNode.set(\"value\"s);\n+\t\tif (listNode.get<std::string>()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Setting a value on a list node should fail\"\n+\t\t\t\t<< std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tstd::set<std::string_view> names{\n+\t\t\tlistElemNames.begin(), listElemNames.end()\n+\t\t};\n+\n+\t\tfor (const auto &child : listNode.asList()) {\n+\t\t\tconst std::string childName = child.get<std::string>(\"\");\n+\n+\t\t\tif (!names.erase(childName)) {\n+\t\t\t\tstd::cerr\n+\t\t\t\t\t<< \"Invalid list child '\" << childName\n+\t\t\t\t\t<< \"'\" << std::endl;\n+\t\t\t\treturn TestFail;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!names.empty()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Missing elements in list: \"\n+\t\t\t\t<< utils::join(names, \", \") << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\t/* Tests on dictionary nodes. */\n+\t\tValueNode dictNode;\n+\n+\t\tstatic const std::array<std::pair<std::string, int>, 3> dictElemKeyValues = { {\n+\t\t\t{ \"a\", 1 },\n+\t\t\t{ \"b\", 2 },\n+\t\t\t{ \"c\", 3 },\n+\t\t} };\n+\n+\t\tfor (const auto &[key, value] : dictElemKeyValues)\n+\t\t\tdictNode.add(key, std::make_unique<ValueNode>(value));\n+\n+\t\tif (!testNodeType(dictNode, NodeType::Dictionary))\n+\t\t\treturn TestFail;\n+\n+\t\tif (!static_cast<bool>(dictNode)) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Dictionary node should cast to true\"\n+\t\t\t\t<< std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (dictNode.size() != 3) {\n+\t\t\tstd::cerr << \"Invalid dictionary node size\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tdictNode.set(\"value\"s);\n+\t\tif (dictNode.get<std::string>()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Setting a value on a dict node should fail\"\n+\t\t\t\t<< std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tstd::map<std::string, int> keyValues{\n+\t\t\tdictElemKeyValues.begin(), dictElemKeyValues.end()\n+\t\t};\n+\n+\t\tfor (const auto &[key, child] : dictNode.asDict()) {\n+\t\t\tauto iter = keyValues.find(key);\n+\t\t\tif (iter == keyValues.end()) {\n+\t\t\t\tstd::cerr\n+\t\t\t\t\t<< \"Invalid dictionary key '\" << key\n+\t\t\t\t\t<< \"'\" << std::endl;\n+\t\t\t\treturn TestFail;\n+\t\t\t}\n+\n+\t\t\tconst int value = child.get<int>(0);\n+\t\t\tif (value != iter->second) {\n+\t\t\t\tstd::cerr\n+\t\t\t\t\t<< \"Invalid dictionary value \" << value\n+\t\t\t\t\t<< \" for key '\" << key << \"'\" << std::endl;\n+\t\t\t\treturn TestFail;\n+\t\t\t}\n+\n+\t\t\tif (dictNode[key].get<int>(0) != value) {\n+\t\t\t\tstd::cerr\n+\t\t\t\t\t<< \"Dictionary lookup failed for key '\"\n+\t\t\t\t\t<< key << \"'\" << std::endl;\n+\t\t\t\treturn TestFail;\n+\t\t\t}\n+\n+\t\t\tkeyValues.erase(iter);\n+\t\t}\n+\n+\t\tif (!keyValues.empty()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Missing elements in dictionary: \"\n+\t\t\t\t<< utils::join(utils::map_keys(keyValues), \", \")\n+\t\t\t\t<< std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (!dictNode[\"nonexistent\"].isEmpty()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Accessing nonexistent dictionary element returns non-empty node\"\n+\t\t\t\t<< std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\t/* Make sure utils::map_keys() works on the adapter. */\n+\t\t(void)utils::map_keys(dictNode.asDict());\n+\n+\t\t/* Tests on value nodes. */\n+\t\tValueNode values;\n+\n+\t\tvalues.add(\"int8_t\", std::make_unique<ValueNode>(static_cast<int8_t>(-100)));\n+\t\tvalues.add(\"uint8_t\", std::make_unique<ValueNode>(static_cast<uint8_t>(100)));\n+\t\tvalues.add(\"int16_t\", std::make_unique<ValueNode>(static_cast<int16_t>(-1000)));\n+\t\tvalues.add(\"uint16_t\", std::make_unique<ValueNode>(static_cast<uint16_t>(1000)));\n+\t\tvalues.add(\"int32_t\", std::make_unique<ValueNode>(static_cast<int32_t>(-100000)));\n+\t\tvalues.add(\"uint32_t\", std::make_unique<ValueNode>(static_cast<uint32_t>(100000)));\n+\t\tvalues.add(\"float\", std::make_unique<ValueNode>(3.14159f));\n+\t\tvalues.add(\"double\", std::make_unique<ValueNode>(3.14159));\n+\t\tvalues.add(\"string\", std::make_unique<ValueNode>(\"libcamera\"s));\n+\n+\t\tstd::unique_ptr<ValueNode> sizeNode = std::make_unique<ValueNode>();\n+\t\tsizeNode->add(std::make_unique<ValueNode>(640));\n+\t\tsizeNode->add(std::make_unique<ValueNode>(480));\n+\n+\t\tvalues.add(\"size\", std::move(sizeNode));\n+\n+\t\tusing ValueVariant = std::variant<int64_t, double, Size, std::string>;\n+\n+\t\tstatic const\n+\t\tstd::array<std::tuple<std::string_view, ValueType, ValueVariant>, 10> nodesValues{ {\n+\t\t\t{ \"int8_t\", ValueType::Int8, static_cast<int64_t>(-100) },\n+\t\t\t{ \"uint8_t\", ValueType::UInt8, static_cast<int64_t>(100) },\n+\t\t\t{ \"int16_t\", ValueType::Int16, static_cast<int64_t>(-1000) },\n+\t\t\t{ \"uint16_t\", ValueType::UInt16, static_cast<int64_t>(1000) },\n+\t\t\t{ \"int32_t\", ValueType::Int32, static_cast<int64_t>(-100000) },\n+\t\t\t{ \"uint32_t\", ValueType::UInt32, static_cast<int64_t>(100000) },\n+\t\t\t{ \"float\", ValueType::Float, 3.14159 },\n+\t\t\t{ \"double\", ValueType::Double, 3.14159 },\n+\t\t\t{ \"string\", ValueType::String, \"libcamera\" },\n+\t\t\t{ \"size\", ValueType::Size, Size{ 640, 480 } },\n+\t\t} };\n+\n+\t\tfor (const auto &nodeValue : nodesValues) {\n+\t\t\t/*\n+\t\t\t * P0588R1 (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0588r1.html)\n+\t\t\t * explicitly forbids a lambda from capturing structured\n+\t\t\t * bindings. This was fixed in a later release of the\n+\t\t\t * C++ specification, but some compilers (including\n+\t\t\t * clang-14 used in CI) choke on it. We can't use\n+\t\t\t * structured bindings in the for loop, unpack the tuple\n+\t\t\t * manually instead.\n+\t\t\t */\n+\t\t\tconst auto &name = std::get<0>(nodeValue);\n+\t\t\tconst auto &type = std::get<1>(nodeValue);\n+\t\t\tconst auto &value = std::get<2>(nodeValue);\n+\n+\t\t\tconst ValueNode &node = values[name];\n+\n+\t\t\tif (testNodeValueType(node, name, type) != TestPass)\n+\t\t\t\treturn TestFail;\n+\n+\t\t\tint ret = std::visit(utils::overloaded{\n+\t\t\t\t[&](int64_t arg) -> int {\n+\t\t\t\t\treturn testIntegerValue(node, name, type, arg);\n+\t\t\t\t},\n+\n+\t\t\t\t[&](double arg) -> int {\n+\t\t\t\t\treturn testFloatValue(node, name, arg);\n+\t\t\t\t},\n+\n+\t\t\t\t[&](const Size &arg) -> int {\n+\t\t\t\t\tif (node.get<Size>().value_or(Size{}) != arg ||\n+\t\t\t\t\t    node.get<Size>(Size{}) != arg) {\n+\t\t\t\t\t\tstd::cerr\n+\t\t\t\t\t\t\t<< \"Invalid node size value\"\n+\t\t\t\t\t\t\t<< std::endl;\n+\t\t\t\t\t\treturn TestFail;\n+\t\t\t\t\t}\n+\n+\t\t\t\t\treturn TestPass;\n+\t\t\t\t},\n+\n+\t\t\t\t[&](const std::string &arg) -> int {\n+\t\t\t\t\tif (node.get<std::string>().value_or(std::string{}) != arg ||\n+\t\t\t\t\t    node.get<std::string>(std::string{}) != arg) {\n+\t\t\t\t\t\tstd::cerr\n+\t\t\t\t\t\t\t<< \"Invalid node string value\"\n+\t\t\t\t\t\t\t<< std::endl;\n+\t\t\t\t\t\treturn TestFail;\n+\t\t\t\t\t}\n+\n+\t\t\t\t\treturn TestPass;\n+\t\t\t\t},\n+\t\t\t}, value);\n+\n+\t\t\tif (ret != TestPass)\n+\t\t\t\treturn ret;\n+\t\t}\n+\n+\t\t/* Test erasure. */\n+\t\tvalues.erase(\"float\");\n+\t\tif (values.contains(\"float\")) {\n+\t\t\tstd::cerr << \"Failed to erase child node\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tvalues.add({ \"a\", \"b\", \"c\" }, std::make_unique<ValueNode>(0));\n+\t\tvalues.erase({ \"a\", \"b\" });\n+\t\tif (values[\"a\"].contains(\"b\")) {\n+\t\t\tstd::cerr << \"Failed to erase descendant node\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\treturn TestPass;\n+\t}\n+};\n+\n+TEST_REGISTER(ValueNodeTest)\n","prefixes":["v3","21/37"]}