tests FEATURE yang-data extension tests
diff --git a/tests/modules/yang/ietf-restconf@2017-01-26.yang b/tests/modules/yang/ietf-restconf@2017-01-26.yang
new file mode 100644
index 0000000..b47455b
--- /dev/null
+++ b/tests/modules/yang/ietf-restconf@2017-01-26.yang
@@ -0,0 +1,278 @@
+module ietf-restconf {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:ietf-restconf";
+ prefix "rc";
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+
+ contact
+ "WG Web: <https://datatracker.ietf.org/wg/netconf/>
+ WG List: <mailto:netconf@ietf.org>
+
+ Author: Andy Bierman
+ <mailto:andy@yumaworks.com>
+
+ Author: Martin Bjorklund
+ <mailto:mbj@tail-f.com>
+
+ Author: Kent Watsen
+ <mailto:kwatsen@juniper.net>";
+
+ description
+ "This module contains conceptual YANG specifications
+ for basic RESTCONF media type definitions used in
+ RESTCONF protocol messages.
+
+ Note that the YANG definitions within this module do not
+ represent configuration data of any kind.
+ The 'restconf-media-type' YANG extension statement
+ provides a normative syntax for XML and JSON
+ message-encoding purposes.
+
+ Copyright (c) 2017 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 8040; see
+ the RFC itself for full legal notices.";
+
+ revision 2017-01-26 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 8040: RESTCONF Protocol.";
+ }
+
+ extension yang-data {
+ argument name {
+ yin-element true;
+ }
+ description
+ "This extension is used to specify a YANG data template that
+ represents conceptual data defined in YANG. It is
+ intended to describe hierarchical data independent of
+ protocol context or specific message-encoding format.
+ Data definition statements within a yang-data extension
+ specify the generic syntax for the specific YANG data
+ template, whose name is the argument of the 'yang-data'
+ extension statement.
+
+ Note that this extension does not define a media type.
+ A specification using this extension MUST specify the
+ message-encoding rules, including the content media type.
+
+ The mandatory 'name' parameter value identifies the YANG
+ data template that is being defined. It contains the
+ template name.
+
+ This extension is ignored unless it appears as a top-level
+ statement. It MUST contain data definition statements
+ that result in exactly one container data node definition.
+ An instance of a YANG data template can thus be translated
+ into an XML instance document, whose top-level element
+ corresponds to the top-level container.
+ The module name and namespace values for the YANG module using
+ the extension statement are assigned to instance document data
+ conforming to the data definition statements within
+ this extension.
+
+ The substatements of this extension MUST follow the
+ 'data-def-stmt' rule in the YANG ABNF.
+
+ The XPath document root is the extension statement itself,
+ such that the child nodes of the document root are
+ represented by the data-def-stmt substatements within
+ this extension. This conceptual document is the context
+ for the following YANG statements:
+
+ - must-stmt
+ - when-stmt
+ - path-stmt
+ - min-elements-stmt
+ - max-elements-stmt
+ - mandatory-stmt
+ - unique-stmt
+ - ordered-by
+ - instance-identifier data type
+
+ The following data-def-stmt substatements are constrained
+ when used within a 'yang-data' extension statement.
+
+ - The list-stmt is not required to have a key-stmt defined.
+ - The if-feature-stmt is ignored if present.
+ - The config-stmt is ignored if present.
+ - The available identity values for any 'identityref'
+ leaf or leaf-list nodes are limited to the module
+ containing this extension statement and the modules
+ imported into that module.
+ ";
+ }
+
+ rc:yang-data yang-errors {
+ uses errors;
+ }
+
+ rc:yang-data yang-api {
+ uses restconf;
+ }
+
+ grouping errors {
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of a
+ YANG Patch error report within a response message.";
+
+ container errors {
+ description
+ "Represents an error report returned by the server if
+ a request results in an error.";
+
+ list error {
+ description
+ "An entry containing information about one
+ specific error that occurred while processing
+ a RESTCONF request.";
+ reference
+ "RFC 6241, Section 4.3.";
+
+ leaf error-type {
+ type enumeration {
+ enum transport {
+ description
+ "The transport layer.";
+ }
+ enum rpc {
+ description
+ "The rpc or notification layer.";
+ }
+ enum protocol {
+ description
+ "The protocol operation layer.";
+ }
+ enum application {
+ description
+ "The server application layer.";
+ }
+ }
+ mandatory true;
+ description
+ "The protocol layer where the error occurred.";
+ }
+
+ leaf error-tag {
+ type string;
+ mandatory true;
+ description
+ "The enumerated error-tag.";
+ }
+
+ leaf error-app-tag {
+ type string;
+ description
+ "The application-specific error-tag.";
+ }
+
+ leaf error-path {
+ type instance-identifier;
+ description
+ "The YANG instance identifier associated
+ with the error node.";
+ }
+
+ leaf error-message {
+ type string;
+ description
+ "A message describing the error.";
+ }
+
+ anydata error-info {
+ description
+ "This anydata value MUST represent a container with
+ zero or more data nodes representing additional
+ error information.";
+ }
+ }
+ }
+ }
+
+ grouping restconf {
+ description
+ "Conceptual grouping representing the RESTCONF
+ root resource.";
+
+ container restconf {
+ description
+ "Conceptual container representing the RESTCONF
+ root resource.";
+
+ container data {
+ description
+ "Container representing the datastore resource.
+ Represents the conceptual root of all state data
+ and configuration data supported by the server.
+ The child nodes of this container can be any data
+ resources that are defined as top-level data nodes
+ from the YANG modules advertised by the server in
+ the 'ietf-yang-library' module.";
+ }
+
+ container operations {
+ description
+ "Container for all operation resources.
+
+ Each resource is represented as an empty leaf with the
+ name of the RPC operation from the YANG 'rpc' statement.
+
+ For example, the 'system-restart' RPC operation defined
+ in the 'ietf-system' module would be represented as
+ an empty leaf in the 'ietf-system' namespace. This is
+ a conceptual leaf and will not actually be found in
+ the module:
+
+ module ietf-system {
+ leaf system-reset {
+ type empty;
+ }
+ }
+
+ To invoke the 'system-restart' RPC operation:
+
+ POST /restconf/operations/ietf-system:system-restart
+
+ To discover the RPC operations supported by the server:
+
+ GET /restconf/operations
+
+ In XML, the YANG module namespace identifies the module:
+
+ <system-restart
+ xmlns='urn:ietf:params:xml:ns:yang:ietf-system'/>
+
+ In JSON, the YANG module name identifies the module:
+
+ { 'ietf-system:system-restart' : [null] }
+ ";
+ }
+ leaf yang-library-version {
+ type string {
+ pattern '\d{4}-\d{2}-\d{2}';
+ }
+ config false;
+ mandatory true;
+ description
+ "Identifies the revision date of the 'ietf-yang-library'
+ module that is implemented by this RESTCONF server.
+ Indicates the year, month, and day in YYYY-MM-DD
+ numeric format.";
+ }
+ }
+ }
+
+}
diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt
index 3c1e777..4e3543f 100644
--- a/tests/utests/CMakeLists.txt
+++ b/tests/utests/CMakeLists.txt
@@ -45,3 +45,4 @@
ly_add_utest(NAME metadata SOURCES extensions/test_metadata.c)
ly_add_utest(NAME nacm SOURCES extensions/test_nacm.c)
+ly_add_utest(NAME yangdata SOURCES extensions/test_yangdata.c)
diff --git a/tests/utests/extensions/test_yangdata.c b/tests/utests/extensions/test_yangdata.c
new file mode 100644
index 0000000..357dcab
--- /dev/null
+++ b/tests/utests/extensions/test_yangdata.c
@@ -0,0 +1,246 @@
+/*
+ * @file test_yangdata.c
+ * @author: Radek Krejci <rkrejci@cesnet.cz>
+ * @brief unit tests for yang-data extensions support
+ *
+ * Copyright (c) 2019-2021 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 _UTEST_MAIN_
+#include "utests.h"
+
+#include "libyang.h"
+
+static int
+setup(void **state)
+{
+ UTEST_SETUP;
+
+ assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_DIR_MODULES_YANG));
+ assert_non_null(ly_ctx_load_module(UTEST_LYCTX, "ietf-restconf", "2017-01-26", NULL));
+
+ return 0;
+}
+
+static void
+test_schema(void **state)
+{
+ const struct lys_module *mod;
+ struct lysc_ext_instance *e;
+ char *printed = NULL;
+ const char *data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;"
+ "import ietf-restconf {revision-date 2017-01-26; prefix rc;}"
+ "feature x;"
+ "rc:yang-data template { container x { list l { leaf x { type string;}} leaf y {if-feature x; type string; config false;}}}}";
+ const char *info = "module a {\n"
+ " namespace \"urn:tests:extensions:yangdata:a\";\n"
+ " prefix self;\n\n"
+ " ietf-restconf:yang-data \"template\" {\n"
+ " container x {\n"
+ " status current;\n"
+ " list l {\n" /* no key */
+ " min-elements 0;\n"
+ " max-elements 4294967295;\n"
+ " ordered-by system;\n"
+ " status current;\n"
+ " leaf x {\n"
+ " type string;\n"
+ " status current;\n"
+ " }\n"
+ " }\n"
+ " leaf y {\n" /* config and if-feature are ignored */
+ " type string;\n"
+ " status current;\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}\n";
+
+ /* valid data */
+ assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, &mod));
+ assert_non_null(e = mod->compiled->exts);
+ assert_int_equal(LY_ARRAY_COUNT(mod->compiled->exts), 1);
+ assert_int_equal(LY_SUCCESS, lys_print_mem(&printed, mod, LYS_OUT_YANG_COMPILED, 0));
+ assert_string_equal(printed, info);
+ free(printed);
+
+ data = "module c {yang-version 1.1; namespace urn:tests:extensions:yangdata:c; prefix self;"
+ "import ietf-restconf {revision-date 2017-01-26; prefix rc;}"
+ "grouping g { choice ch { container a {presence a; config false;} container b {presence b; config true;}}}"
+ "rc:yang-data template { uses g;}}";
+ info = "module c {\n"
+ " namespace \"urn:tests:extensions:yangdata:c\";\n"
+ " prefix self;\n\n"
+ " ietf-restconf:yang-data \"template\" {\n"
+ " choice ch {\n"
+ " status current;\n"
+ " case a {\n"
+ " status current;\n"
+ " container a {\n"
+ " presence \"true\";\n"
+ " status current;\n"
+ " }\n"
+ " }\n"
+ " case b {\n"
+ " status current;\n"
+ " container b {\n"
+ " presence \"true\";\n"
+ " status current;\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}\n";
+ assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, &mod));
+ assert_non_null(e = mod->compiled->exts);
+ assert_int_equal(LY_ARRAY_COUNT(mod->compiled->exts), 1);
+ assert_int_equal(LY_SUCCESS, lys_print_mem(&printed, mod, LYS_OUT_YANG_COMPILED, 0));
+ assert_string_equal(printed, info);
+ free(printed);
+
+ /* ignored - valid with warning */
+ data = "module b {yang-version 1.1; namespace urn:tests:extensions:yangdata:b; prefix self;"
+ "import ietf-restconf {revision-date 2017-01-26; prefix rc;}"
+ "container b { rc:yang-data template { container x { leaf x {type string;}}}}}";
+ info = "module b {\n"
+ " namespace \"urn:tests:extensions:yangdata:b\";\n"
+ " prefix self;\n"
+ " container b {\n"
+ " config true;\n"
+ " status current;\n"
+ " }\n"
+ "}\n";
+ assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, &mod));
+ assert_null(mod->compiled->exts);
+ CHECK_LOG_CTX("Extension plugin \"libyang 2 - yang-data, version 1\": "
+ "Extension rc:yang-data is ignored since it appears as a non top-level statement in \"data node\" statement.",
+ "/b:b/{extension='rc:yang-data'}/template");
+ assert_int_equal(LY_SUCCESS, lys_print_mem(&printed, mod, LYS_OUT_YANG_COMPILED, 0));
+ assert_string_equal(printed, info);
+ free(printed);
+
+ /* sama data nodes name, but not conflicting */
+ data = "module d {yang-version 1.1; namespace urn:tests:extensions:yangdata:d; prefix self;"
+ "import ietf-restconf {revision-date 2017-01-26; prefix rc;}"
+ "leaf d { type string;}"
+ "rc:yang-data template1 { container d {presence d;}}"
+ "rc:yang-data template2 { container d {presence d;}}}";
+ info = "module d {\n"
+ " namespace \"urn:tests:extensions:yangdata:d\";\n"
+ " prefix self;\n\n"
+ " ietf-restconf:yang-data \"template1\" {\n"
+ " container d {\n"
+ " presence \"true\";\n"
+ " status current;\n"
+ " }\n"
+ " }\n"
+ " ietf-restconf:yang-data \"template2\" {\n"
+ " container d {\n"
+ " presence \"true\";\n"
+ " status current;\n"
+ " }\n"
+ " }\n"
+ " leaf d {\n"
+ " type string;\n"
+ " config true;\n"
+ " status current;\n"
+ " }\n"
+ "}\n";
+ assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, &mod));
+ assert_non_null(e = mod->compiled->exts);
+ assert_int_equal(LY_ARRAY_COUNT(mod->compiled->exts), 2);
+ assert_int_equal(LY_SUCCESS, lys_print_mem(&printed, mod, LYS_OUT_YANG_COMPILED, 0));
+ assert_string_equal(printed, info);
+ free(printed);
+}
+
+static void
+test_schema_invalid(void **state)
+{
+ const char *data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;"
+ "import ietf-restconf {revision-date 2017-01-26; prefix rc;}"
+ "rc:yang-data template { leaf x {type string;}}}";
+
+ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
+ CHECK_LOG_CTX("Invalid keyword \"leaf\" as a child of \"rc:yang-data template\" extension instance.",
+ "/a:{extension='rc:yang-data'}/template");
+
+ data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;"
+ "import ietf-restconf {revision-date 2017-01-26; prefix rc;}"
+ "rc:yang-data template { choice x { leaf x {type string;}}}}";
+ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
+ CHECK_LOG_CTX("Extension plugin \"libyang 2 - yang-data, version 1\": "
+ "Extension rc:yang-data is instantiated with leaf top level data node (inside a choice), "
+ "but only a single container data node is allowed.",
+ "/a:{extension='rc:yang-data'}/template");
+
+ data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;"
+ "import ietf-restconf {revision-date 2017-01-26; prefix rc;}"
+ "rc:yang-data template { choice x { case x { container z {presence ppp;} leaf x {type string;}}}}}";
+ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
+ CHECK_LOG_CTX("Extension plugin \"libyang 2 - yang-data, version 1\": "
+ "Extension rc:yang-data is instantiated with multiple top level data nodes (inside a single choice's case), "
+ "but only a single container data node is allowed.",
+ "/a:{extension='rc:yang-data'}/template");
+
+ data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;"
+ "import ietf-restconf {revision-date 2017-01-26; prefix rc;}"
+ "rc:yang-data template { container x { leaf x {type string;}} container y { leaf y {type string;}}}}";
+ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
+ CHECK_LOG_CTX("Extension plugin \"libyang 2 - yang-data, version 1\": "
+ "Extension rc:yang-data is instantiated with multiple top level data nodes, "
+ "but only a single container data node is allowed.",
+ "/a:{extension='rc:yang-data'}/template");
+
+ data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;"
+ "import ietf-restconf {revision-date 2017-01-26; prefix rc;}"
+ "rc:yang-data template;}";
+ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
+ CHECK_LOG_CTX("Extension plugin \"libyang 2 - yang-data, version 1\": "
+ "Extension rc:yang-data is instantiated without any top level data node, "
+ "but exactly one container data node is expected.",
+ "/a:{extension='rc:yang-data'}/template");
+
+ data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;"
+ "import ietf-restconf {revision-date 2017-01-26; prefix rc;}"
+ "rc:yang-data { container x { leaf x {type string;}}}}";
+ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
+ CHECK_LOG_CTX("Extension plugin \"libyang 2 - yang-data, version 1\": "
+ "Extension rc:yang-data is instantiated without mandatory argument representing YANG data template name.",
+ "/a:{extension='rc:yang-data'}");
+
+ data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;"
+ "import ietf-restconf {revision-date 2017-01-26; prefix rc;}"
+ "rc:yang-data template { container x { leaf x {type string;}}}"
+ "rc:yang-data template { container y { leaf y {type string;}}}}";
+ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
+ CHECK_LOG_CTX("Extension plugin \"libyang 2 - yang-data, version 1\": "
+ "Extension rc:yang-data is instantiated multiple times.",
+ "/a:{extension='rc:yang-data'}/template");
+
+ data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;"
+ "import ietf-restconf {revision-date 2017-01-26; prefix rc;}"
+ "grouping t { leaf-list x {type string;}}"
+ "rc:yang-data template { uses t;}}";
+ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
+ CHECK_LOG_CTX("Extension plugin \"libyang 2 - yang-data, version 1\": "
+ "Extension rc:yang-data is instantiated with leaf-list top level data node, "
+ "but only a single container data node is allowed.",
+ "/a:{extension='rc:yang-data'}/template");
+}
+
+int
+main(void)
+{
+ const struct CMUnitTest tests[] = {
+ UTEST(test_schema, setup),
+ UTEST(test_schema_invalid, setup),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}