blob: ddf8199d80e124ad9067c71544b206269f841f5d [file] [log] [blame]
/**
* @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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <cmocka.h>
#include "../config.h"
#include "../../src/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");
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);
st->ctx2 = ly_ctx_new(NULL);
st->ctx3 = ly_ctx_new(NULL);
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_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);
}
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_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),};
return cmocka_run_group_tests(tests, NULL, NULL);
}