data tree FEATURE lyd_merge() implemented
diff --git a/src/tree_data.c b/src/tree_data.c
index 7ec7004..7dfae9e 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -740,6 +740,16 @@
return ret;
}
+/**
+ * @brief Update node value.
+ *
+ * @param[in] node Node to update.
+ * @param[in] value New value to set.
+ * @param[in] value_type Type of @p value for any node.
+ * @param[out] new_parent Set to @p node if the value was updated, otherwise set to NULL.
+ * @param[out] new_node Set to @p node if the value was updated, otherwise set to NULL.
+ * @return LY_ERR value.
+ */
static LY_ERR
lyd_new_path_update(struct lyd_node *node, const void *value, LYD_ANYDATA_VALUETYPE value_type,
struct lyd_node **new_parent, struct lyd_node **new_node)
@@ -2190,6 +2200,148 @@
return NULL;
}
+/**
+ * @brief Merge a source sibling into target siblings.
+ *
+ * @param[in,out] first_trg First target sibling, is updated if top-level.
+ * @param[in] parent_trg Target parent.
+ * @param[in,out] sibling_src Source sibling to merge, set to NULL if spent.
+ * @param[in] options Merge options.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lyd_merge_sibling_r(struct lyd_node **first_trg, struct lyd_node *parent_trg, const struct lyd_node **sibling_src_p,
+ int options)
+{
+ LY_ERR ret;
+ const struct lyd_node *child_src, *tmp, *sibling_src;
+ struct lyd_node *match_trg, *dup_src, *next, *elem;
+ struct lysc_type *type;
+ LYD_ANYDATA_VALUETYPE tmp_val_type;
+ union lyd_any_value tmp_val;
+
+ sibling_src = *sibling_src_p;
+ if (sibling_src->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) {
+ /* try to find the exact instance */
+ ret = lyd_find_sibling_first(*first_trg, sibling_src, &match_trg);
+ } else {
+ /* try to simply find the node, there cannot be more instances */
+ ret = lyd_find_sibling_val(*first_trg, sibling_src->schema, NULL, 0, &match_trg);
+ }
+
+ if (!ret) {
+ /* node found, make sure even value matches for all node types */
+ if ((match_trg->schema->nodetype == LYS_LEAF) && lyd_compare(sibling_src, match_trg, LYD_COMPARE_DEFAULTS)) {
+ /* since they are different, they cannot both be default */
+ assert(!(sibling_src->flags & LYD_DEFAULT) || !(match_trg->flags & LYD_DEFAULT));
+
+ /* update value (or only LYD_DEFAULT flag) only if no flag set or the source node is not default */
+ if (!(options & LYD_MERGE_EXPLICIT) || !(sibling_src->flags & LYD_DEFAULT)) {
+ type = ((struct lysc_node_leaf *)match_trg->schema)->type;
+ type->plugin->free(LYD_NODE_CTX(match_trg), &((struct lyd_node_term *)match_trg)->value);
+ LY_CHECK_RET(type->plugin->duplicate(LYD_NODE_CTX(match_trg), &((struct lyd_node_term *)sibling_src)->value,
+ &((struct lyd_node_term *)match_trg)->value));
+
+ /* copy flags and add LYD_NEW */
+ match_trg->flags = sibling_src->flags | LYD_NEW;
+ }
+ } else if ((match_trg->schema->nodetype & LYS_ANYDATA) && lyd_compare(sibling_src, match_trg, 0)) {
+ if (options & LYD_MERGE_DESTRUCT) {
+ dup_src = (struct lyd_node *)sibling_src;
+ lyd_unlink_tree(dup_src);
+ /* spend it */
+ *sibling_src_p = NULL;
+ } else {
+ dup_src = lyd_dup(sibling_src, NULL, 0);
+ LY_CHECK_RET(!dup_src, LY_EMEM);
+ }
+ /* just switch values */
+ tmp_val_type = ((struct lyd_node_any *)match_trg)->value_type;
+ tmp_val = ((struct lyd_node_any *)match_trg)->value;
+ ((struct lyd_node_any *)match_trg)->value_type = ((struct lyd_node_any *)sibling_src)->value_type;
+ ((struct lyd_node_any *)match_trg)->value = ((struct lyd_node_any *)sibling_src)->value;
+ ((struct lyd_node_any *)sibling_src)->value_type = tmp_val_type;
+ ((struct lyd_node_any *)sibling_src)->value = tmp_val;
+
+ /* copy flags and add LYD_NEW */
+ match_trg->flags = sibling_src->flags | LYD_NEW;
+
+ /* dup_src is not needed, actually */
+ lyd_free_tree(dup_src);
+ } else {
+ /* check descendants, recursively */
+ LY_LIST_FOR_SAFE(lyd_node_children(sibling_src), tmp, child_src) {
+ if ((child_src->schema->nodetype == LYS_LEAF) && (child_src->schema->flags & LYS_KEY)) {
+ /* skip keys */
+ continue;
+ }
+
+ LY_CHECK_RET(lyd_merge_sibling_r(lyd_node_children_p(match_trg), match_trg, &child_src, options));
+ }
+ }
+ } else {
+ /* node not found, merge it */
+ if (options & LYD_MERGE_DESTRUCT) {
+ dup_src = (struct lyd_node *)sibling_src;
+ lyd_unlink_tree(dup_src);
+ /* spend it */
+ *sibling_src_p = NULL;
+ } else {
+ dup_src = lyd_dup(sibling_src, NULL, LYD_DUP_RECURSIVE | LYD_DUP_WITH_FLAGS);
+ LY_CHECK_RET(!dup_src, LY_EMEM);
+ }
+
+ /* set LYD_NEW for all the new nodes, required for validation */
+ LYD_TREE_DFS_BEGIN(dup_src, next, elem) {
+ elem->flags |= LYD_NEW;
+ LYD_TREE_DFS_END(dup_src, next, elem);
+ }
+
+ lyd_insert_node(parent_trg, first_trg, dup_src);
+ }
+
+ return LY_SUCCESS;
+}
+
+API LY_ERR
+lyd_merge(struct lyd_node **target, const struct lyd_node *source, int options)
+{
+ const struct lyd_node *sibling_src, *tmp;
+ int first;
+
+ LY_CHECK_ARG_RET(NULL, target, LY_EINVAL);
+
+ if (!source) {
+ /* nothing to merge */
+ return LY_SUCCESS;
+ }
+
+ if (lysc_data_parent((*target)->schema) || lysc_data_parent(source->schema)) {
+ LOGERR(LYD_NODE_CTX(source), LY_EINVAL, "Invalid arguments - can merge only 2 top-level subtrees (%s()).", __func__);
+ return LY_EINVAL;
+ }
+
+ LY_LIST_FOR_SAFE(source, tmp, sibling_src) {
+ first = sibling_src == source ? 1 : 0;
+ LY_CHECK_RET(lyd_merge_sibling_r(target, NULL, &sibling_src, options));
+ if (first && !sibling_src) {
+ /* source was spent (unlinked), move to the next node */
+ source = tmp;
+ }
+
+ if (options & LYD_MERGE_NOSIBLINGS) {
+ break;
+ }
+ }
+
+ if (options & LYD_MERGE_DESTRUCT) {
+ /* free any leftover source data that were not merged */
+ lyd_free_siblings((struct lyd_node *)source);
+ }
+
+ return LY_SUCCESS;
+}
+
static LY_ERR
lyd_path_str_enlarge(char **buffer, size_t *buflen, size_t reqlen, int is_static)
{
diff --git a/src/tree_data.h b/src/tree_data.h
index 8e021d6..1c4e9c0 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -1043,11 +1043,44 @@
struct lyd_node *lyd_dup(const struct lyd_node *node, struct lyd_node_inner *parent, int options);
/**
- * @brief Resolve instance-identifier defined by lyd_value_path structure.
+ * @defgroup mergeoptions Data merge options.
+ * @ingroup datatree
*
- * @param[in] path Path structure specifying the instance-identifier target.
+ * Various options to change lyd_merge() behavior.
+ *
+ * Default behavior:
+ * - source data tree is not modified in any way,
+ * - source data tree is merged with any succeeding siblings,
+ * - any default nodes from source replace explicit nodes in the target.
+ * @{
+ */
+
+#define LYD_MERGE_DESTRUCT 0x01 /**< Spend source data tree in the function, it cannot be used afterwards! */
+#define LYD_MERGE_NOSIBLINGS 0x02 /**< Merge only the single source data tree, no siblings. */
+#define LYD_MERGE_EXPLICIT 0x04 /**< Default nodes in the source tree are ignored if there are explicit nodes
+ in the target tree. */
+
+/** @} mergeoptions */
+
+/**
+ * @brief Merge the source data tree into the target data tree. Merge may not be complete until validation
+ * is called on the resulting data tree (data from more cases may be present, default and non-default values).
+ *
+ * @param[in,out] target Target data tree to merge into, must be a top-level tree.
+ * @param[in] source Source data tree to merge, must be a top-level tree.
+ * @param[in] options Bitmask of option flags, see @ref mergeoptions.
+ * @return LY_SUCCESS on success,
+ * @return LY_ERR value on error.
+ */
+LY_ERR lyd_merge(struct lyd_node **target, const struct lyd_node *source, int options);
+
+/**
+ * @brief Find the target in data of a compiled ly_path structure (instance-identifier).
+ *
+ * @param[in] path Compiled path structure.
* @param[in] tree Data tree to be searched.
- * @return Target node of the instance-identifier present in the given data @p tree.
+ * @return Found target node,
+ * @return NULL if not found.
*/
const struct lyd_node_term *lyd_target(const struct ly_path *path, const struct lyd_node *tree);
diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt
index 19b3950..9d1836e 100644
--- a/tests/utests/CMakeLists.txt
+++ b/tests/utests/CMakeLists.txt
@@ -18,5 +18,6 @@
ly_add_utest(NAME printer_xml SOURCES data/test_printer_xml.c)
ly_add_utest(NAME validation SOURCES data/test_validation.c)
ly_add_utest(NAME types SOURCES data/test_types.c)
+ly_add_utest(NAME merge SOURCES data/test_merge.c)
ly_add_utest(NAME metadata SOURCES extensions/test_metadata.c)
ly_add_utest(NAME nacm SOURCES extensions/test_nacm.c)
diff --git a/tests/utests/data/test_merge.c b/tests/utests/data/test_merge.c
new file mode 100644
index 0000000..c109c74
--- /dev/null
+++ b/tests/utests/data/test_merge.c
@@ -0,0 +1,675 @@
+/**
+ * @file test_merge.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief tests for complex data merges.
+ *
+ * Copyright (c) 2020 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "libyang.h"
+
+struct state {
+ struct ly_ctx *ctx;
+ struct lyd_node *source;
+ struct lyd_node *target;
+ struct lyd_node *result;
+};
+
+static int
+setup_dflt(void **state)
+{
+ struct state *st;
+
+ (*state) = st = calloc(1, sizeof *st);
+ if (!st) {
+ fprintf(stderr, "Memory allocation error.\n");
+ return -1;
+ }
+
+ /* libyang context */
+ if (ly_ctx_new(NULL, 0, &st->ctx)) {
+ fprintf(stderr, "Failed to create context.\n");
+ goto error;
+ }
+
+ return 0;
+
+error:
+ ly_ctx_destroy(st->ctx, NULL);
+ free(st);
+ (*state) = NULL;
+
+ return -1;
+}
+
+static int
+teardown_dflt(void **state)
+{
+ struct state *st = (*state);
+
+ lyd_free_siblings(st->target);
+ lyd_free_siblings(st->source);
+ lyd_free_siblings(st->result);
+ ly_ctx_destroy(st->ctx, NULL);
+ free(st);
+ (*state) = NULL;
+
+ return 0;
+}
+
+static void
+test_batch(void **state)
+{
+ struct state *st = (*state);
+ LY_ERR ret;
+ uint32_t i;
+ char *str;
+
+ const char *start =
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>yang</name>"
+ "<revision>2016-02-11</revision>"
+ "<conformance-type>implement</conformance-type>"
+ "</module>"
+ "</modules-state>";
+ const char *data[] = {
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>ietf-yang-library</name>"
+ "<revision>2016-02-01</revision>"
+ "<conformance-type>implement</conformance-type>"
+ "</module>"
+ "</modules-state>"
+ ,
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>ietf-netconf-acm</name>"
+ "<revision>2012-02-22</revision>"
+ "<conformance-type>implement</conformance-type>"
+ "</module>"
+ "</modules-state>"
+ ,
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>ietf-netconf</name>"
+ "<revision>2011-06-01</revision>"
+ "<conformance-type>implement</conformance-type>"
+ "</module>"
+ "</modules-state>"
+ ,
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>ietf-netconf-monitoring</name>"
+ "<revision>2010-10-04</revision>"
+ "<conformance-type>implement</conformance-type>"
+ "</module>"
+ "</modules-state>"
+ ,
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>ietf-netconf-with-defaults</name>"
+ "<revision>2011-06-01</revision>"
+ "<conformance-type>implement</conformance-type>"
+ "</module>"
+ "</modules-state>"
+ ,
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>yang</name>"
+ "<revision>2016-02-11</revision>"
+ "<namespace>urn:ietf:params:xml:ns:yang:1</namespace>"
+ "<conformance-type>implement</conformance-type>"
+ "</module>"
+ "</modules-state>"
+ ,
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>ietf-yang-library</name>"
+ "<revision>2016-02-01</revision>"
+ "<namespace>urn:ietf:params:xml:ns:yang:ietf-yang-library</namespace>"
+ "<conformance-type>implement</conformance-type>"
+ "</module>"
+ "</modules-state>"
+ ,
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>ietf-netconf-acm</name>"
+ "<revision>2012-02-22</revision>"
+ "<namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-acm</namespace>"
+ "<conformance-type>implement</conformance-type>"
+ "</module>"
+ "</modules-state>"
+ ,
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>ietf-netconf</name>"
+ "<revision>2011-06-01</revision>"
+ "<namespace>urn:ietf:params:xml:ns:netconf:base:1.0</namespace>"
+ "<feature>writable-running</feature>"
+ "<feature>candidate</feature>"
+ "<feature>rollback-on-error</feature>"
+ "<feature>validate</feature>"
+ "<feature>startup</feature>"
+ "<feature>xpath</feature>"
+ "<conformance-type>implement</conformance-type>"
+ "</module>"
+ "</modules-state>"
+ ,
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>ietf-netconf-monitoring</name>"
+ "<revision>2010-10-04</revision>"
+ "<namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring</namespace>"
+ "<conformance-type>implement</conformance-type>"
+ "</module>"
+ "</modules-state>"
+ ,
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>ietf-netconf-with-defaults</name>"
+ "<revision>2011-06-01</revision>"
+ "<namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults</namespace>"
+ "<conformance-type>implement</conformance-type>"
+ "</module>"
+ "</modules-state>"
+ };
+ const char *output_template =
+ "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
+ "<module>"
+ "<name>yang</name>"
+ "<revision>2016-02-11</revision>"
+ "<conformance-type>implement</conformance-type>"
+ "<namespace>urn:ietf:params:xml:ns:yang:1</namespace>"
+ "</module>"
+ "<module>"
+ "<name>ietf-yang-library</name>"
+ "<revision>2016-02-01</revision>"
+ "<conformance-type>implement</conformance-type>"
+ "<namespace>urn:ietf:params:xml:ns:yang:ietf-yang-library</namespace>"
+ "</module>"
+ "<module>"
+ "<name>ietf-netconf-acm</name>"
+ "<revision>2012-02-22</revision>"
+ "<conformance-type>implement</conformance-type>"
+ "<namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-acm</namespace>"
+ "</module>"
+ "<module>"
+ "<name>ietf-netconf</name>"
+ "<revision>2011-06-01</revision>"
+ "<conformance-type>implement</conformance-type>"
+ "<namespace>urn:ietf:params:xml:ns:netconf:base:1.0</namespace>"
+ "<feature>writable-running</feature>"
+ "<feature>candidate</feature>"
+ "<feature>rollback-on-error</feature>"
+ "<feature>validate</feature>"
+ "<feature>startup</feature>"
+ "<feature>xpath</feature>"
+ "</module>"
+ "<module>"
+ "<name>ietf-netconf-monitoring</name>"
+ "<revision>2010-10-04</revision>"
+ "<conformance-type>implement</conformance-type>"
+ "<namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring</namespace>"
+ "</module>"
+ "<module>"
+ "<name>ietf-netconf-with-defaults</name>"
+ "<revision>2011-06-01</revision>"
+ "<conformance-type>implement</conformance-type>"
+ "<namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults</namespace>"
+ "</module>"
+ "</modules-state>";
+
+ st->target = lyd_parse_mem(st->ctx, start, LYD_XML, LYD_OPT_PARSE_ONLY);
+ assert_non_null(st->target);
+
+ for (i = 0; i < 11; ++i) {
+ st->source = lyd_parse_mem(st->ctx, data[i], LYD_XML, LYD_OPT_PARSE_ONLY);
+ assert_non_null(st->source);
+
+ ret = lyd_merge(&st->target, st->source, LYD_MERGE_DESTRUCT);
+ assert_int_equal(ret, LY_SUCCESS);
+ st->source = NULL;
+ }
+
+ lyd_print_mem(&str, st->target, LYD_XML, 0);
+ assert_string_equal(str, output_template);
+ free(str);
+}
+
+static void
+test_leaf(void **state)
+{
+ struct state *st = (*state);
+ const char *sch = "module x {"
+ " namespace urn:x;"
+ " prefix x;"
+ " container A {"
+ " leaf f1 {type string;}"
+ " container B {"
+ " leaf f2 {type string;}"
+ " }"
+ " }"
+ " }";
+ const char *trg = "<A xmlns=\"urn:x\"> <f1>block</f1> </A>";
+ const char *src = "<A xmlns=\"urn:x\"> <f1>aa</f1> <B> <f2>bb</f2> </B> </A>";
+ const char *result = "<A xmlns=\"urn:x\"><f1>aa</f1><B><f2>bb</f2></B></A>";
+ char *printed = NULL;
+
+ assert_non_null(lys_parse_mem(st->ctx, sch, LYS_IN_YANG));
+
+ st->source = lyd_parse_mem(st->ctx, src, LYD_XML, LYD_VALOPT_DATA_ONLY);
+ assert_non_null(st->source);
+
+ st->target = lyd_parse_mem(st->ctx, trg, LYD_XML, LYD_VALOPT_DATA_ONLY);
+ assert_non_null(st->target);
+
+ /* merge them */
+ assert_int_equal(lyd_merge(&st->target, st->source, 0), LY_SUCCESS);
+ assert_int_equal(lyd_validate(&st->target, NULL, LYD_VALOPT_DATA_ONLY), LY_SUCCESS);
+
+ /* check the result */
+ lyd_print_mem(&printed, st->target, LYD_XML, LYDP_WITHSIBLINGS);
+ assert_string_equal(printed, result);
+ free(printed);
+}
+
+static void
+test_container(void **state)
+{
+ struct state *st = (*state);
+ const char *sch =
+ "module A {"
+ "namespace \"aa:A\";"
+ "prefix A;"
+ "container A {"
+ "leaf f1 {type string;}"
+ "container B {"
+ "leaf f2 {type string;}"
+ "}"
+ "container C {"
+ "leaf f3 {type string;}"
+ "}"
+ "}"
+ "}";
+
+ const char *trg = "<A xmlns=\"aa:A\"> <B> <f2>aaa</f2> </B> </A>";
+ const char *src = "<A xmlns=\"aa:A\"> <C> <f3>bbb</f3> </C> </A>";
+ const char *result = "<A xmlns=\"aa:A\"><B><f2>aaa</f2></B><C><f3>bbb</f3></C></A>";
+ char *printed = NULL;
+
+ assert_non_null(lys_parse_mem(st->ctx, sch, LYS_IN_YANG));
+
+ st->source = lyd_parse_mem(st->ctx, src, LYD_XML, LYD_VALOPT_DATA_ONLY);
+ assert_non_null(st->source);
+
+ st->target = lyd_parse_mem(st->ctx, trg, LYD_XML, LYD_VALOPT_DATA_ONLY);
+ assert_non_null(st->target);
+
+ /* merge them */
+ assert_int_equal(lyd_merge(&st->target, st->source, 0), LY_SUCCESS);
+ assert_int_equal(lyd_validate(&st->target, NULL, LYD_VALOPT_DATA_ONLY), LY_SUCCESS);
+
+ /* check the result */
+ lyd_print_mem(&printed, st->target, LYD_XML, LYDP_WITHSIBLINGS);
+ assert_string_equal(printed, result);
+ free(printed);
+}
+
+static void
+test_list(void **state)
+{
+ struct state *st = (*state);
+ const char *sch =
+ "module merge {"
+ "namespace \"http://test/merge\";"
+ "prefix merge;"
+
+ "container inner1 {"
+ "list b-list1 {"
+ "key p1;"
+ "leaf p1 {"
+ "type uint8;"
+ "}"
+ "leaf p2 {"
+ "type string;"
+ "}"
+ "leaf p3 {"
+ "type boolean;"
+ "default false;"
+ "}"
+ "}"
+ "}"
+ "}";
+
+
+ const char *trg =
+ "<inner1 xmlns=\"http://test/merge\">"
+ "<b-list1>"
+ "<p1>1</p1>"
+ "<p2>a</p2>"
+ "<p3>true</p3>"
+ "</b-list1>"
+ "</inner1>";
+ const char *src =
+ "<inner1 xmlns=\"http://test/merge\">"
+ "<b-list1>"
+ "<p1>1</p1>"
+ "<p2>b</p2>"
+ "</b-list1>"
+ "</inner1>";
+ const char *result =
+ "<inner1 xmlns=\"http://test/merge\">"
+ "<b-list1>"
+ "<p1>1</p1>"
+ "<p2>b</p2>"
+ "<p3>true</p3>"
+ "</b-list1>"
+ "</inner1>";
+ char *printed = NULL;
+
+ assert_non_null(lys_parse_mem(st->ctx, sch, LYS_IN_YANG));
+
+ st->source = lyd_parse_mem(st->ctx, src, LYD_XML, LYD_VALOPT_DATA_ONLY);
+ assert_non_null(st->source);
+
+ st->target = lyd_parse_mem(st->ctx, trg, LYD_XML, LYD_VALOPT_DATA_ONLY);
+ assert_non_null(st->target);
+
+ /* merge them */
+ assert_int_equal(lyd_merge(&st->target, st->source, LYD_MERGE_EXPLICIT), LY_SUCCESS);
+ assert_int_equal(lyd_validate(&st->target, NULL, LYD_VALOPT_DATA_ONLY), LY_SUCCESS);
+
+ /* check the result */
+ lyd_print_mem(&printed, st->target, LYD_XML, LYDP_WITHSIBLINGS);
+ assert_string_equal(printed, result);
+ free(printed);
+}
+
+static void
+test_list2(void **state)
+{
+ struct state *st = (*state);
+ const char *sch =
+ "module merge {"
+ "namespace \"http://test/merge\";"
+ "prefix merge;"
+
+ "container inner1 {"
+ "list b-list1 {"
+ "key p1;"
+ "leaf p1 {"
+ "type uint8;"
+ "}"
+ "leaf p2 {"
+ "type string;"
+ "}"
+ "container inner2 {"
+ "leaf p3 {"
+ "type boolean;"
+ "default false;"
+ "}"
+ "leaf p4 {"
+ "type string;"
+ "}"
+ "}"
+ "}"
+ "}"
+ "}";
+
+
+ const char *trg =
+ "<inner1 xmlns=\"http://test/merge\">"
+ "<b-list1>"
+ "<p1>1</p1>"
+ "<p2>a</p2>"
+ "<inner2>"
+ "<p4>val</p4>"
+ "</inner2>"
+ "</b-list1>"
+ "</inner1>";
+ const char *src =
+ "<inner1 xmlns=\"http://test/merge\">"
+ "<b-list1>"
+ "<p1>1</p1>"
+ "<p2>b</p2>"
+ "</b-list1>"
+ "</inner1>";
+ const char *result =
+ "<inner1 xmlns=\"http://test/merge\">"
+ "<b-list1>"
+ "<p1>1</p1>"
+ "<p2>b</p2>"
+ "<inner2>"
+ "<p4>val</p4>"
+ "</inner2>"
+ "</b-list1>"
+ "</inner1>";
+ char *printed = NULL;
+
+ assert_non_null(lys_parse_mem(st->ctx, sch, LYS_IN_YANG));
+
+ st->source = lyd_parse_mem(st->ctx, src, LYD_XML, LYD_VALOPT_DATA_ONLY);
+ assert_non_null(st->source);
+
+ st->target = lyd_parse_mem(st->ctx, trg, LYD_XML, LYD_VALOPT_DATA_ONLY);
+ assert_non_null(st->target);
+
+ /* merge them */
+ assert_int_equal(lyd_merge(&st->target, st->source, LYD_MERGE_EXPLICIT), LY_SUCCESS);
+ assert_int_equal(lyd_validate(&st->target, NULL, LYD_VALOPT_DATA_ONLY), LY_SUCCESS);
+
+ /* check the result */
+ lyd_print_mem(&printed, st->target, LYD_XML, LYDP_WITHSIBLINGS);
+ assert_string_equal(printed, result);
+ free(printed);
+}
+
+static void
+test_case(void **state)
+{
+ struct state *st = (*state);
+ const char *sch =
+ "module merge {"
+ "namespace \"http://test/merge\";"
+ "prefix merge;"
+ "container cont {"
+ "choice ch {"
+ "container inner {"
+ "leaf p1 {"
+ "type string;"
+ "}"
+ "}"
+ "case c2 {"
+ "leaf p1 {"
+ "type string;"
+ "}"
+ "}"
+ "}"
+ "}"
+ "}";
+
+ const char *trg =
+ "<cont xmlns=\"http://test/merge\">"
+ "<inner>"
+ "<p1>1</p1>"
+ "</inner>"
+ "</cont>";
+ const char *src =
+ "<cont xmlns=\"http://test/merge\">"
+ "<p1>1</p1>"
+ "</cont>";
+ const char *result =
+ "<cont xmlns=\"http://test/merge\">"
+ "<p1>1</p1>"
+ "</cont>";
+ char *printed = NULL;
+
+ assert_non_null(lys_parse_mem(st->ctx, sch, LYS_IN_YANG));
+
+ st->source = lyd_parse_mem(st->ctx, src, LYD_XML, LYD_VALOPT_DATA_ONLY);
+ assert_non_null(st->source);
+
+ st->target = lyd_parse_mem(st->ctx, trg, LYD_XML, LYD_VALOPT_DATA_ONLY);
+ assert_non_null(st->target);
+
+ /* merge them */
+ assert_int_equal(lyd_merge(&st->target, st->source, 0), LY_SUCCESS);
+ assert_int_equal(lyd_validate(&st->target, NULL, LYD_VALOPT_DATA_ONLY), LY_SUCCESS);
+
+ /* check the result */
+ lyd_print_mem(&printed, st->target, LYD_XML, LYDP_WITHSIBLINGS);
+ assert_string_equal(printed, result);
+ free(printed);
+}
+
+static void
+test_dflt(void **state)
+{
+ struct state *st = (*state);
+ struct lyd_node *tmp;
+ const char *sch =
+ "module merge-dflt {"
+ "namespace \"urn:merge-dflt\";"
+ "prefix md;"
+ "container top {"
+ "leaf a {"
+ "type string;"
+ "}"
+ "leaf b {"
+ "type string;"
+ "}"
+ "leaf c {"
+ "type string;"
+ "default \"c_dflt\";"
+ "}"
+ "}"
+ "}";
+
+ assert_non_null(lys_parse_mem(st->ctx, sch, LYS_IN_YANG));
+
+ st->target = lyd_new_path(NULL, st->ctx, "/merge-dflt:top/c", "c_dflt", 0);
+ assert_non_null(st->target);
+ assert_int_equal(lyd_validate(&(st->target), NULL, LYD_VALOPT_DATA_ONLY), LY_SUCCESS);
+
+ st->source = lyd_new_path(NULL, st->ctx, "/merge-dflt:top/a", "a_val", 0);
+ assert_non_null(st->source);
+ tmp = lyd_new_path(st->source, st->ctx, "/merge-dflt:top/b", "b_val", 0);
+ assert_non_null(tmp);
+ assert_int_equal(lyd_validate(&(st->source), NULL, LYD_VALOPT_DATA_ONLY), LY_SUCCESS);
+
+ assert_int_equal(lyd_merge(&st->target, st->source, LYD_MERGE_DESTRUCT), LY_SUCCESS);
+ st->source = NULL;
+
+ /* c should be replaced and now be default */
+ assert_true(lyd_node_children(st->target)->flags & LYD_DEFAULT);
+}
+
+static void
+test_dflt2(void **state)
+{
+ struct state *st = (*state);
+ struct lyd_node *tmp;
+ const char *sch =
+ "module merge-dflt {"
+ "namespace \"urn:merge-dflt\";"
+ "prefix md;"
+ "container top {"
+ "leaf a {"
+ "type string;"
+ "}"
+ "leaf b {"
+ "type string;"
+ "}"
+ "leaf c {"
+ "type string;"
+ "default \"c_dflt\";"
+ "}"
+ "}"
+ "}";
+
+ assert_non_null(lys_parse_mem(st->ctx, sch, LYS_IN_YANG));
+
+ st->target = lyd_new_path(NULL, st->ctx, "/merge-dflt:top/c", "c_dflt", 0);
+ assert_non_null(st->target);
+ assert_int_equal(lyd_validate(&(st->target), NULL, LYD_VALOPT_DATA_ONLY), LY_SUCCESS);
+
+ st->source = lyd_new_path(NULL, st->ctx, "/merge-dflt:top/a", "a_val", 0);
+ assert_non_null(st->source);
+ tmp = lyd_new_path(st->source, st->ctx, "/merge-dflt:top/b", "b_val", 0);
+ assert_non_null(tmp);
+ assert_int_equal(lyd_validate(&(st->source), NULL, LYD_VALOPT_DATA_ONLY), LY_SUCCESS);
+
+ assert_int_equal(lyd_merge(&st->target, st->source, LYD_MERGE_EXPLICIT), LY_SUCCESS);
+
+ /* c should not be replaced, so c remains not default */
+ assert_false(lyd_node_children(st->target)->flags & LYD_DEFAULT);
+}
+
+static void
+test_leafrefs(void **state)
+{
+ struct state *st = (*state);
+ const char *sch = "module x {"
+ " namespace urn:x;"
+ " prefix x;"
+ " list l {"
+ " key n;"
+ " leaf n { type string; }"
+ " leaf t { type string; }"
+ " leaf r { type leafref { path '/l/n'; } }}}";
+ const char *trg = "<l xmlns=\"urn:x\"><n>a</n></l>"
+ "<l xmlns=\"urn:x\"><n>b</n><r>a</r></l>";
+ const char *src = "<l xmlns=\"urn:x\"><n>c</n><r>a</r></l>"
+ "<l xmlns=\"urn:x\"><n>a</n><t>*</t></l>";
+ const char *res = "<l xmlns=\"urn:x\"><n>a</n><t>*</t></l>"
+ "<l xmlns=\"urn:x\"><n>b</n><r>a</r></l>"
+ "<l xmlns=\"urn:x\"><n>c</n><r>a</r></l>";
+ char *prt = NULL;
+
+ assert_non_null(lys_parse_mem(st->ctx, sch, LYS_IN_YANG));
+
+ st->target = lyd_parse_mem(st->ctx, trg, LYD_XML, LYD_VALOPT_DATA_ONLY);
+ assert_non_null(st->target);
+
+ st->source = lyd_parse_mem(st->ctx, src, LYD_XML, LYD_VALOPT_DATA_ONLY);
+ assert_non_null(st->source);
+
+ assert_int_equal(lyd_merge(&st->target, st->source, LYD_MERGE_DESTRUCT), LY_SUCCESS);
+ st->source = NULL;
+
+ lyd_print_mem(&prt, st->target, LYD_XML, LYDP_WITHSIBLINGS);
+ assert_string_equal(prt, res);
+ free(prt);
+}
+
+int
+main(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(test_batch, setup_dflt, teardown_dflt),
+ cmocka_unit_test_setup_teardown(test_leaf, setup_dflt, teardown_dflt),
+ cmocka_unit_test_setup_teardown(test_container, setup_dflt, teardown_dflt),
+ cmocka_unit_test_setup_teardown(test_list, setup_dflt, teardown_dflt),
+ cmocka_unit_test_setup_teardown(test_list2, setup_dflt, teardown_dflt),
+ cmocka_unit_test_setup_teardown(test_case, setup_dflt, teardown_dflt),
+ cmocka_unit_test_setup_teardown(test_dflt, setup_dflt, teardown_dflt),
+ cmocka_unit_test_setup_teardown(test_dflt2, setup_dflt, teardown_dflt),
+ cmocka_unit_test_setup_teardown(test_leafrefs, setup_dflt, teardown_dflt),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}