| /** |
| * @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); |
| } |