Patch Detail
Show a patch.
GET /api/patches/26467/?format=api
{ "id": 26467, "url": "https://patchwork.libcamera.org/api/patches/26467/?format=api", "web_url": "https://patchwork.libcamera.org/patch/26467/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20260407153427.1825999-28-laurent.pinchart@ideasonboard.com>", "date": "2026-04-07T15:34:12", "name": "[v2,27/42] test: Add ValueNode unit test", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "a1de86ba52450d29160570836c5f25ec0e2e7ac5", "submitter": { "id": 2, "url": "https://patchwork.libcamera.org/api/people/2/?format=api", "name": "Laurent Pinchart", "email": "laurent.pinchart@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/26467/mbox/", "series": [ { "id": 5873, "url": "https://patchwork.libcamera.org/api/series/5873/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5873", "date": "2026-04-07T15:33:45", "name": "libcamera: Global configuration file improvements", "version": 2, "mbox": "https://patchwork.libcamera.org/series/5873/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/26467/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/26467/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 6C02FC3316\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 7 Apr 2026 15:35:11 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id BA8DC62E09;\n\tTue, 7 Apr 2026 17:35:10 +0200 (CEST)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id A23AB62E0F\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 7 Apr 2026 17:35:05 +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 UTF8SMTPSA id 0E85778E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 7 Apr 2026 17:33:38 +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=\"PdMV9S7U\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1775576018;\n\tbh=HOt119qKSFJu/3Px9eFQewp9OFlZyFGA9ULH743CQQ0=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=PdMV9S7UzK2ElSf02CtiVk915juilZutdPdIcxEuw6gAkmRQf4Vjs8NxziPVQPR15\n\tXZQV19eLDfpJAJNeFYjJfg7/uDRiPJIXPvy34B3YaZxMZp66yJRdcNSgf8ObC8kFDc\n\tPkUv7j0WN5yloVX9lPNKeHi/ADrC9e2X7kwq1Zpo=", "From": "Laurent Pinchart <laurent.pinchart@ideasonboard.com>", "To": "libcamera-devel@lists.libcamera.org", "Subject": "[PATCH v2 27/42] test: Add ValueNode unit test", "Date": "Tue, 7 Apr 2026 18:34:12 +0300", "Message-ID": "<20260407153427.1825999-28-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", "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": "Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n---\n test/meson.build | 1 +\n test/value-node.cpp | 565 ++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 566 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..e9cb7ae11b9f\n--- /dev/null\n+++ b/test/value-node.cpp\n@@ -0,0 +1,565 @@\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+template<class... Ts>\n+struct overloaded : Ts... {\n+\tusing Ts::operator()...;\n+};\n+template<class... Ts>\n+overloaded(Ts...) -> overloaded<Ts...>;\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(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": [ "v2", "27/42" ] }