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);
+}