/**
 * @file test_tree_data_merge.c
 * @author Radek Krejci <rkrejci@cesnet.cz>
 * @brief Cmocka tests for complex data merges.
 *
 * Copyright (c) 2017 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
 */

#define _GNU_SOURCE

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <cmocka.h>

#include "tests/config.h"
#include "libyang.h"

struct state {
    struct ly_ctx *ctx1;
    struct ly_ctx *ctx2;
    struct ly_ctx *ctx3;
    struct lyd_node *source;
    struct lyd_node *target;
    struct lyd_node *result;
    char *path, *output;
};

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 */
    st->ctx1 = ly_ctx_new(TESTS_DIR "/api/files", 0);
    if (!st->ctx1) {
        fprintf(stderr, "Failed to create context.\n");
        goto error;
    }

    return 0;

error:
    ly_ctx_destroy(st->ctx1, NULL);
    free(st);
    (*state) = NULL;

    return -1;
}

static int
teardown_dflt(void **state)
{
    struct state *st = (*state);

    free(st->path);
    free(st->output);
    lyd_free_withsiblings(st->target);
    lyd_free_withsiblings(st->source);
    lyd_free_withsiblings(st->result);
    ly_ctx_destroy(st->ctx1, NULL);
    free(st);
    (*state) = NULL;

    return 0;
}

static int
setup_mctx(void **state)
{
    struct state *st;

    (*state) = st = calloc(1, sizeof *st);
    if (!st) {
        fprintf(stderr, "Memory allocation error");
        return -1;
    }

    /* libyang context */
    st->ctx1 = ly_ctx_new(NULL, 0);
    st->ctx2 = ly_ctx_new(NULL, 0);
    st->ctx3 = ly_ctx_new(NULL, 0);
    if (!st->ctx1 || !st->ctx2 || !st->ctx3) {
        fprintf(stderr, "Failed to create context.\n");
        return -1;
    }

    return 0;
}

static int
teardown_mctx(void **state)
{
    struct state *st = (*state);

    lyd_free_withsiblings(st->source);
    lyd_free_withsiblings(st->target);
    lyd_free_withsiblings(st->result);
    ly_ctx_destroy(st->ctx1, NULL);
    ly_ctx_destroy(st->ctx2, NULL);
    ly_ctx_destroy(st->ctx3, NULL);

    free(st);
    (*state) = NULL;

    return 0;
}

static void
test_merge(void **state)
{
    struct state *st = (*state);
    uint32_t i;
    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_path(st->ctx1, TESTS_DIR "/api/files/merge_start.xml", LYD_XML, LYD_OPT_GET);
    assert_ptr_not_equal(st->target, NULL);

    asprintf(&st->path, TESTS_DIR "/api/files/mergeXX.xml");
    for (i = 1; i < 12; ++i) {
        sprintf(st->path + (strlen(st->path) - 6), "%02u.xml", i);
        st->source = lyd_parse_path(st->ctx1, st->path, LYD_XML, LYD_OPT_GET);
        assert_ptr_not_equal(st->source, NULL);

        assert_int_equal(lyd_merge(st->target, st->source, LYD_OPT_DESTRUCT), 0);
        st->source = NULL;
    }

    lyd_print_mem(&st->output, st->target, LYD_XML, 0);
    assert_string_equal(st->output, output_template);
}

static void
test_merge2(void **state)
{
    struct state *st = (*state);
    const char *sch = "module x {"
                    "  namespace urn:x;"
                    "  prefix x;"
                    "  leaf x { type string; }"
                    "  container c1 {"
                    "    container c2 {"
                    "      leaf y { type string; }"
                    "}}}";
    const char *trg = "<x xmlns=\"urn:x\">x</x>";
    const char *result = "<x xmlns=\"urn:x\">x</x><c1 xmlns=\"urn:x\"><c2><y>y</y></c2></c1>";
    char *printed = NULL;

    /* merging leaf x and leaf y - without the parents, lyd_merge is supposed also to merge subtrees */
    assert_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);
    st->result = lyd_new_path(NULL, st->ctx1, "/x:c1/c2/y", "y", 0, 0);
    assert_ptr_not_equal(st->result, NULL);
    assert_int_equal(lyd_validate(&st->result, LYD_OPT_CONFIG, NULL), 0);

    /* lyd_new_path returns the first created node, so the top level c1, but we need the
     * subtree with the y node */
    st->source = st->result->child->child;
    lyd_unlink(st->source);
    lyd_free(st->result);
    st->result = NULL;

    /* the target tree contains only the x node */
    st->target = lyd_parse_mem(st->ctx1, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    /* merge them */
    assert_int_equal(lyd_merge(st->target, st->source, 0), 0);

    /* check the result */
    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge3(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_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx1, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    /* merge them */
    assert_int_equal(lyd_merge(st->target, st->source, 0), 0);
    assert_int_equal(lyd_validate(&st->target, LYD_OPT_CONFIG, NULL), 0);

    /* check the result */
    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge4(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_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx1, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    /* merge them */
    assert_int_equal(lyd_merge(st->target, st->source, 0), 0);
    assert_int_equal(lyd_validate(&st->target, LYD_OPT_CONFIG, NULL), 0);

    /* check the result */
    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge5(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_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx1, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    /* merge them */
    assert_int_equal(lyd_merge(st->target, st->source, LYD_OPT_EXPLICIT), 0);
    assert_int_equal(lyd_validate(&st->target, LYD_OPT_CONFIG, NULL), 0);

    /* check the result */
    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge6(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_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx1, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    /* merge them */
    assert_int_equal(lyd_merge(st->target, st->source, LYD_OPT_EXPLICIT), 0);
    assert_int_equal(lyd_validate(&st->target, LYD_OPT_CONFIG, NULL), 0);

    /* check the result */
    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge_dflt1(void **state)
{
    struct state *st = (*state);
    struct lyd_node *tmp;

    assert_ptr_not_equal(ly_ctx_load_module(st->ctx1, "merge-dflt", NULL), NULL);

    st->target = lyd_new_path(NULL, st->ctx1, "/merge-dflt:top/c", "c_dflt", 0, 0);
    assert_ptr_not_equal(st->target, NULL);
    assert_int_equal(lyd_validate(&(st->target), LYD_OPT_CONFIG, NULL), 0);

    st->source = lyd_new_path(NULL, st->ctx1, "/merge-dflt:top/a", "a_val", 0, 0);
    assert_ptr_not_equal(st->source, NULL);
    tmp = lyd_new_path(st->source, st->ctx1, "/merge-dflt:top/b", "b_val", 0, 0);
    assert_ptr_not_equal(tmp, NULL);
    assert_int_equal(lyd_validate(&(st->source), LYD_OPT_CONFIG, NULL), 0);

    assert_int_equal(lyd_merge(st->target, st->source, LYD_OPT_DESTRUCT), 0);
    st->source = NULL;

    /* c should be replaced and now be default */
    assert_int_equal(st->target->child->dflt, 1);
}

static void
test_merge_dflt2(void **state)
{
    struct state *st = (*state);
    struct lyd_node *tmp;

    assert_ptr_not_equal(ly_ctx_load_module(st->ctx1, "merge-dflt", NULL), NULL);

    st->target = lyd_new_path(NULL, st->ctx1, "/merge-dflt:top/c", "c_dflt", 0, 0);
    assert_ptr_not_equal(st->target, NULL);
    assert_int_equal(lyd_validate(&(st->target), LYD_OPT_CONFIG, NULL), 0);

    st->source = lyd_new_path(NULL, st->ctx1, "/merge-dflt:top/a", "a_val", 0, 0);
    assert_ptr_not_equal(st->source, NULL);
    tmp = lyd_new_path(st->source, st->ctx1, "/merge-dflt:top/b", "b_val", 0, 0);
    assert_ptr_not_equal(tmp, NULL);
    assert_int_equal(lyd_validate(&(st->source), LYD_OPT_CONFIG, NULL), 0);

    assert_int_equal(lyd_merge(st->target, st->source, LYD_OPT_EXPLICIT), 0);

    /* c should not be replaced, so c remains not default */
    assert_int_equal(st->target->child->dflt, 0);
}

static void
test_merge_to_trgctx1(void **state)
{
    struct state *st = (*state);
    const char *sch = "module x {"
                    "  namespace urn:x;"
                    "  prefix x;"
                    "  leaf x { type string; }"
                    "  leaf y { type string; }}";
    const char *src = "<x xmlns=\"urn:x\">x</x>";
    const char *trg = "<y xmlns=\"urn:x\">y</y>";
    const char *result = "<y xmlns=\"urn:x\">y</y><x xmlns=\"urn:x\">x</x>";
    char *printed = NULL;

    /* case 1: src is in ctx1, trg is in ctx2, result is expected in ctx2, src is being destroyed */
    assert_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);
    assert_ptr_not_equal(lys_parse_mem(st->ctx2, sch, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx2, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    assert_int_equal(lyd_merge(st->target, st->source, LYD_OPT_DESTRUCT), 0);
    /* forget pointer to source, it should be destroyed, if not it will be seen in valgrind as memory leak */
    st->source = NULL;

    assert_ptr_equal(st->target->schema->module->ctx, st->ctx2);
    /* check the merged data - leaf x */
    assert_ptr_not_equal(st->target->next, NULL);
    assert_ptr_equal(st->target->next->schema->module->ctx, st->ctx2);

    /* print the result after freeing the ctx1 */
    ly_ctx_destroy(st->ctx1, NULL);
    st->ctx1 = NULL;

    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge_to_trgctx2(void **state)
{
    struct state *st = (*state);
    const char *sch = "module x {"
                    "  namespace urn:x;"
                    "  prefix x;"
                    "  leaf x { type string; }"
                    "  leaf y { type string; }}";
    const char *src = "<x xmlns=\"urn:x\">x</x>";
    const char *trg = "<y xmlns=\"urn:x\">y</y>";
    const char *result = "<y xmlns=\"urn:x\">y</y><x xmlns=\"urn:x\">x</x>";
    char *printed = NULL;

    /* case 2: src is in ctx1, trg is in ctx2, result is expected in ctx2, src is not destroyed */
    assert_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);
    assert_ptr_not_equal(lys_parse_mem(st->ctx2, sch, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx2, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    assert_int_equal(lyd_merge(st->target, st->source, 0), 0);

    assert_ptr_equal(st->target->schema->module->ctx, st->ctx2);
    /* check the merged data - leaf x */
    assert_ptr_not_equal(st->target->next, NULL);
    assert_ptr_equal(st->target->next->schema->module->ctx, st->ctx2);

    assert_ptr_not_equal(st->source, st->target->next);

    /* print the result after freeing the ctx1 */
    lyd_free(st->source);
    ly_ctx_destroy(st->ctx1, NULL);
    st->source = NULL;
    st->ctx1 = NULL;

    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge_to_ctx(void **state)
{
    struct state *st = (*state);
    const char *sch = "module x {"
                    "  namespace urn:x;"
                    "  prefix x;"
                    "  leaf x { type string; }"
                    "  leaf y { type string; }}";
    const char *src = "<x xmlns=\"urn:x\">x</x>";
    const char *trg = "<y xmlns=\"urn:x\">y</y>";
    const char *result = "<y xmlns=\"urn:x\">y</y><x xmlns=\"urn:x\">x</x>";
    char *printed = NULL;

    /* case 3: src is in ctx1, trg is in ctx2, result is requested in ctx3, src is destroyed */
    assert_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);
    assert_ptr_not_equal(lys_parse_mem(st->ctx2, sch, LYS_IN_YANG), NULL);
    assert_ptr_not_equal(lys_parse_mem(st->ctx3, sch, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx2, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    assert_int_equal(lyd_merge_to_ctx(&st->target, st->source, LYD_OPT_DESTRUCT, st->ctx3), 0);
    st->source = NULL; /* source is expected to be destroyed */

    /* check the merged data - leaf y */
    assert_ptr_equal(st->target->schema->module->ctx, st->ctx3);
    /* check the merged data - leaf x */
    assert_ptr_not_equal(st->target->next, NULL);
    assert_ptr_equal(st->target->next->schema->module->ctx, st->ctx3);

    /* print the result after freeing the ctx1 and ctx2 */
    ly_ctx_destroy(st->ctx1, NULL);
    ly_ctx_destroy(st->ctx2, NULL);
    st->ctx1 = NULL;
    st->ctx2 = NULL;

    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}


const struct lys_module *
test_load_module_clb(struct ly_ctx *ctx, const char *UNUSED(name), const char *UNUSED(ns), int UNUSED(options), void *user_data)
{
    return lys_parse_mem(ctx, (char *) user_data, LYS_IN_YANG);
}

static void
test_merge_to_ctx_with_missing_schema(void **state)
{
    struct state *st = (*state);
    const char *sch_x = "module x {"
                        "  namespace urn:x;"
                        "  prefix x;"
                        "  leaf x { type string; }}";
    const char *src = "<x xmlns=\"urn:x\">x</x>";
    const char *sch_y = "module y {"
                        "  namespace urn:y;"
                        "  prefix y;"
                        "  leaf y { type string; }}";
    const char *trg = "<y xmlns=\"urn:y\">y</y>";
    const char *result = "<y xmlns=\"urn:y\">y</y><x xmlns=\"urn:x\">x</x>";
    char *printed = NULL;

    /* case 4: src contains module X schema and data, trg contains Y schema and data.
       Verify that X is loaded into Y when merging X into Y. */
    assert_ptr_not_equal(lys_parse_mem(st->ctx1, sch_x, LYS_IN_YANG), NULL);
    assert_ptr_not_equal(lys_parse_mem(st->ctx2, sch_y, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx2, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    ly_ctx_set_module_data_clb(st->ctx2, test_load_module_clb, (void *)sch_x);

    assert_int_equal(lyd_merge(st->target, st->source, 0), 0);

    assert_ptr_equal(st->target->schema->module->ctx, st->ctx2);
    /* check the merged data - leaf x */
    assert_ptr_not_equal(st->target->next, NULL);
    assert_ptr_equal(st->target->next->schema->module->ctx, st->ctx2);

    assert_ptr_not_equal(st->source, st->target->next);

    /* print the result after freeing the ctx1 */
    lyd_free(st->source);
    ly_ctx_destroy(st->ctx1, NULL);
    st->source = NULL;
    st->ctx1 = NULL;

    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge_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_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);

    st->target = lyd_parse_mem(st->ctx1, trg, LYD_XML, LYD_OPT_GET);
    assert_ptr_not_equal(st->target, NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_GET);
    assert_ptr_not_equal(st->source, NULL);

    assert_int_equal(lyd_merge(st->target, st->source, LYD_OPT_DESTRUCT), 0);
    st->source = NULL;

    lyd_print_mem(&prt, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(prt, res);
    free(prt);
}

int
main(void)
{
    const struct CMUnitTest tests[] = {
                    cmocka_unit_test_setup_teardown(test_merge, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge2, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge3, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge4, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge5, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge6, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge_dflt1, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge_dflt2, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge_to_trgctx1, setup_mctx, teardown_mctx),
                    cmocka_unit_test_setup_teardown(test_merge_to_trgctx2, setup_mctx, teardown_mctx),
                    cmocka_unit_test_setup_teardown(test_merge_to_ctx, setup_mctx, teardown_mctx),
                    cmocka_unit_test_setup_teardown(test_merge_to_ctx_with_missing_schema, setup_mctx, teardown_mctx),
                    cmocka_unit_test_setup_teardown(test_merge_leafrefs, setup_dflt, teardown_dflt),
    };

    return cmocka_run_group_tests(tests, NULL, NULL);
}
