extensions CHANGE metadata substatements
Support processing of all the Metadata annotation's substatements. Also
add basic test for overall annotation implementation
diff --git a/Doxyfile.in b/Doxyfile.in
index dbc9674..e47a7ae 100644
--- a/Doxyfile.in
+++ b/Doxyfile.in
@@ -784,6 +784,7 @@
INPUT = ./src/libyang.h \
./src/context.h \
./src/tree_schema.h \
+ ./src/plugins_exts.h \
./src/log.h \
./src/set.h \
./src/dict.h
diff --git a/src/parser_stmt.c b/src/parser_stmt.c
index 225dd02..f282322 100644
--- a/src/parser_stmt.c
+++ b/src/parser_stmt.c
@@ -752,8 +752,9 @@
}
LY_ERR
-lysp_stmt_parse(struct lysc_ctx *ctx, const struct lysp_stmt *stmt, enum ly_stmt kw, void **result)
+lysp_stmt_parse(struct lysc_ctx *ctx, const struct lysp_stmt *stmt, enum ly_stmt kw, void **result, struct lysp_ext_instance **exts)
{
+ LY_ERR ret = LY_SUCCESS;
struct lys_parser_ctx pctx = {0};
pctx.ctx = ctx->ctx;
@@ -762,11 +763,15 @@
pctx.path = ctx->path;
switch(kw) {
+ case LY_STMT_STATUS: {
+ ret = lysp_stmt_status(&pctx, stmt, *(uint16_t**)result, exts);
+ break;
+ }
case LY_STMT_TYPE: {
struct lysp_type *type;
type = calloc(1, sizeof *type);
- lysp_stmt_type(&pctx, stmt, type);
+ ret = lysp_stmt_type(&pctx, stmt, type);
(*result) = type;
break;
}
@@ -775,5 +780,5 @@
return LY_EINT;
}
- return LY_SUCCESS;
+ return ret;
}
diff --git a/src/plugins_exts_metadata.c b/src/plugins_exts_metadata.c
index 2d85fc8..80a116e 100644
--- a/src/plugins_exts_metadata.c
+++ b/src/plugins_exts_metadata.c
@@ -93,6 +93,10 @@
void
annotation_free(struct ly_ctx *ctx, struct lysc_ext_instance *ext)
{
+ if (!ext->data) {
+ return;
+ }
+
struct lyext_metadata *annotation = (struct lyext_metadata*)ext->data;
annotation_substmt[0].storage = &annotation->iffeatures;
annotation_substmt[1].storage = &annotation->units;
diff --git a/src/tree_schema.h b/src/tree_schema.h
index abe4908..fe84d17 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -33,7 +33,7 @@
/**
* @brief Macro to iterate via all elements in a schema tree which can be instantiated in data tree
- * (skips cases, input, output). This is the opening part to the #LYS_TREE_DFS_END - they always have to be used together.
+ * (skips cases, input, output). This is the opening part to the #LYSC_TREE_DFS_END - they always have to be used together.
*
* The function follows deep-first search algorithm:
* <pre>
@@ -166,12 +166,14 @@
*/
enum ly_stmt {
LY_STMT_NONE = 0,
- LY_STMT_STATUS,
- LY_STMT_CONFIG,
+ LY_STMT_STATUS, /**< in lysc_ext_substmt::storage stored as a pointer to `uint16_t`, only cardinality < #LY_STMT_CARD_SOME is allowed */
+ LY_STMT_CONFIG, /**< in lysc_ext_substmt::storage stored as a pointer to `uint16_t`, only cardinality < #LY_STMT_CARD_SOME is allowed */
LY_STMT_MANDATORY,
- LY_STMT_UNITS,
+ LY_STMT_UNITS, /**< in lysc_ext_substmt::storage stored as a pointer to `const char *` (cardinality < #LY_STMT_CARD_SOME)
+ or as a pointer to a [sized array](@ref sizedarrays) `const char **` */
LY_STMT_DEFAULT,
- LY_STMT_TYPE,
+ LY_STMT_TYPE, /**< in lysc_ext_substmt::storage stored as a pointer to `struct lysc_type *` (cardinality < #LY_STMT_CARD_SOME)
+ or as a pointer to a [sized array](@ref sizedarrays) `struct lysc_type **` */
LY_STMT_ACTION,
LY_STMT_ANYDATA,
@@ -196,7 +198,8 @@
LY_STMT_FRACTION_DIGITS,
LY_STMT_GROUPING,
LY_STMT_IDENTITY,
- LY_STMT_IF_FEATURE,
+ LY_STMT_IF_FEATURE, /**< in lysc_ext_substmt::storage stored as a pointer to `struct lysc_iffeature` (cardinality < #LY_STMT_CARD_SOME)
+ or as a pointer to a [sized array](@ref sizedarrays) `struct lysc_iffeature *` */
LY_STMT_IMPORT,
LY_STMT_INCLUDE,
LY_STMT_INPUT,
@@ -1114,14 +1117,14 @@
* @brief YANG extension instance
*/
struct lysc_ext_instance {
- struct lysc_ext *def; /**< pointer to the extension definition */
+ uint32_t insubstmt_index; /**< in case the instance is in a substatement that can appear multiple times,
+ this identifies the index of the substatement for this extension instance */
struct lys_module *module; /**< module where the extension instantiated is defined */
+ struct lysc_ext *def; /**< pointer to the extension definition */
void *parent; /**< pointer to the parent element holding the extension instance(s), use
::lysc_ext_instance#parent_type to access the schema element */
const char *argument; /**< optional value of the extension's argument */
LYEXT_SUBSTMT insubstmt; /**< value identifying placement of the extension instance */
- uint32_t insubstmt_index; /**< in case the instance is in a substatement that can appear multiple times,
- this identifies the index of the substatement for this extension instance */
LYEXT_PARENT parent_type; /**< type of the parent structure */
struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */
void *data; /**< private plugins's data, not used by libyang */
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index 25e9ffa..d680606 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -470,7 +470,17 @@
LY_EVALID);
if (ext->def->plugin && ext->def->plugin->compile) {
+ lysc_update_path(ctx, ext->parent_type == LYEXT_PAR_NODE ? (struct lysc_node*)ext->parent : NULL, "{extension}");
+ lysc_update_path(ctx, NULL, ext_p->name );
+ if (ext->argument) {
+ lysc_update_path(ctx, (struct lysc_node*)ext, ext->argument);
+ }
LY_CHECK_RET(ext->def->plugin->compile(ctx, ext_p, ext),LY_EVALID);
+ if (ext->argument) {
+ lysc_update_path(ctx, NULL, NULL);
+ }
+ lysc_update_path(ctx, NULL, NULL);
+ lysc_update_path(ctx, NULL, NULL);
}
return LY_SUCCESS;
@@ -6652,7 +6662,6 @@
unsigned int u;
struct lysp_stmt *stmt;
void *parsed = NULL, **compiled = NULL;
- struct ly_set mandatory_stmts = {0};
/* check for invalid substatements */
for (stmt = ext->child; stmt; stmt = stmt->next) {
@@ -6662,32 +6671,56 @@
}
}
if (!substmts[u].stmt) {
- LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG, "Invalid keyword \"%s\" as a child of \"%s\" extension instance.",
- stmt->stmt, ext->name);
+ LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG, "Invalid keyword \"%s\" as a child of \"%s%s%s\" extension instance.",
+ stmt->stmt, ext->name, ext->argument ? " " : "", ext->argument ? ext->argument : "");
goto cleanup;
}
- if (substmts[u].cardinality == LY_STMT_CARD_MAND || substmts[u].cardinality == LY_STMT_CARD_SOME) {
- ly_set_add(&mandatory_stmts, &substmts[u], LY_SET_OPT_USEASLIST);
- }
}
+ /* TODO store inherited data, e.g. status first, but mark them somehow to allow to overwrite them and not detect duplicity */
+
/* keep order of the processing the same as the order in the defined substmts,
* the order is important for some of the statements depending on others (e.g. type needs status and units) */
for (u = 0; substmts[u].stmt; ++u) {
+ int stmt_present = 0;
+
for (stmt = ext->child; stmt; stmt = stmt->next) {
if (substmts[u].stmt != stmt->kw) {
continue;
}
+ stmt_present = 1;
if (substmts[u].storage) {
switch (stmt->kw) {
+ case LY_STMT_STATUS:
+ assert(substmts[u].cardinality < LY_STMT_CARD_SOME);
+ LY_CHECK_ERR_GOTO(r = lysp_stmt_parse(ctx, stmt, stmt->kw, &substmts[u].storage, /* TODO */ NULL), ret = r, cleanup);
+ break;
+ case LY_STMT_UNITS: {
+ const char **units;
+
+ if (substmts[u].cardinality < LY_STMT_CARD_SOME) {
+ /* single item */
+ if (*((const char **)substmts[u].storage)) {
+ LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LY_VCODE_DUPSTMT, stmt->stmt);
+ goto cleanup;
+ }
+ units = (const char **)substmts[u].storage;
+ } else {
+ /* sized array */
+ const char ***units_array = (const char ***)substmts[u].storage;
+ LY_ARRAY_NEW_GOTO(ctx->ctx, *units_array, units, ret, cleanup);
+ }
+ *units = lydict_insert(ctx->ctx, stmt->arg, 0);
+ break;
+ }
case LY_STMT_TYPE: {
uint16_t *flags = lys_compile_extension_instance_storage(LY_STMT_STATUS, substmts);
const char **units = lys_compile_extension_instance_storage(LY_STMT_UNITS, substmts);
if (substmts[u].cardinality < LY_STMT_CARD_SOME) {
/* single item */
- if (*(struct lysc_type**)substmts->storage) {
+ if (*(struct lysc_type**)substmts[u].storage) {
LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LY_VCODE_DUPSTMT, stmt->stmt);
goto cleanup;
}
@@ -6699,7 +6732,7 @@
compiled = (void*)type;
}
- LY_CHECK_ERR_GOTO(r = lysp_stmt_parse(ctx, stmt, stmt->kw, &parsed), ret = r, cleanup);
+ LY_CHECK_ERR_GOTO(r = lysp_stmt_parse(ctx, stmt, stmt->kw, &parsed, NULL), ret = r, cleanup);
LY_CHECK_ERR_GOTO(r = lys_compile_type(ctx, ext->parent_type == LYEXT_PAR_NODE ? ((struct lysc_node*)ext->parent)->sp : NULL,
flags ? *flags : 0, ctx->mod_def->parsed, ext->name, parsed, (struct lysc_type**)compiled,
units && !*units ? units : NULL), lysp_type_free(ctx->ctx, parsed); free(parsed); ret = r, cleanup);
@@ -6707,33 +6740,44 @@
free(parsed);
break;
}
- /* TODO support other substatements (parse stmt to lysp and then compile lysp to lysc) */
+ case LY_STMT_IF_FEATURE: {
+ struct lysc_iffeature *iff = NULL;
+
+ if (substmts[u].cardinality < LY_STMT_CARD_SOME) {
+ /* single item */
+ if (((struct lysc_iffeature*)substmts[u].storage)->features) {
+ LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LY_VCODE_DUPSTMT, stmt->stmt);
+ goto cleanup;
+ }
+ iff = (struct lysc_iffeature*)substmts[u].storage;
+ } else {
+ /* sized array */
+ struct lysc_iffeature **iffs = (struct lysc_iffeature**)substmts[u].storage;
+ LY_ARRAY_NEW_GOTO(ctx->ctx, *iffs, iff, ret, cleanup);
+ }
+ LY_CHECK_ERR_GOTO(r = lys_compile_iffeature(ctx, &stmt->arg, iff), ret = r, cleanup);
+ break;
+ }
+ /* TODO support other substatements (parse stmt to lysp and then compile lysp to lysc),
+ * also note that in many statements their extensions are not taken into account */
default:
- LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG, "Statement \"%s\" is not supported as an extension (found in \"%s\") substatement.",
- stmt->stmt, ext->name);
+ LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG, "Statement \"%s\" is not supported as an extension (found in \"%s%s%s\") substatement.",
+ stmt->stmt, ext->name, ext->argument ? " " : "", ext->argument ? ext->argument : "");
goto cleanup;
}
}
-
- if (substmts[u].cardinality == LY_STMT_CARD_MAND || substmts[u].cardinality == LY_STMT_CARD_SOME) {
- int i = ly_set_contains(&mandatory_stmts, &substmts[u]);
- if (i != -1) {
- ly_set_rm_index(&mandatory_stmts, i, NULL);
- }
- }
}
- }
- if (mandatory_stmts.count) {
- LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LY_VCODE_MISSTMT, "type", ext->name);
- goto cleanup;
+ if ((substmts[u].cardinality == LY_STMT_CARD_MAND || substmts[u].cardinality == LY_STMT_CARD_SOME) && !stmt_present) {
+ LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG, "Missing mandatory keyword \"%s\" as a child of \"%s%s%s\".",
+ ly_stmt2str(substmts[u].stmt), ext->name, ext->argument ? " " : "", ext->argument ? ext->argument : "");
+ goto cleanup;
+ }
}
ret = LY_SUCCESS;
cleanup:
- ly_set_erase(&mandatory_stmts, NULL);
-
return ret;
}
diff --git a/src/tree_schema_free.c b/src/tree_schema_free.c
index 3535414..790b89f 100644
--- a/src/tree_schema_free.c
+++ b/src/tree_schema_free.c
@@ -918,6 +918,7 @@
}
break;
case LY_STMT_STATUS:
+ case LY_STMT_CONFIG:
/* nothing to do */
break;
case LY_STMT_IF_FEATURE: {
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index c9c6134..5ca89c5 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -710,7 +710,10 @@
*/
void lysp_type_free(struct ly_ctx *ctx, struct lysp_type *type);
-LY_ERR lysp_stmt_parse(struct lysc_ctx *ctx, const struct lysp_stmt *stmt, enum ly_stmt kw, void **result);
+/**
+ * @param[in,out] exts [sized array](@ref sizedarrays) For extension instances in case of statements that do not store extension instances in their own list.
+ */
+LY_ERR lysp_stmt_parse(struct lysc_ctx *ctx, const struct lysp_stmt *stmt, enum ly_stmt kw, void **result, struct lysp_ext_instance **exts);
/**
* @brief Free the compiled type structure.
diff --git a/tests/features/CMakeLists.txt b/tests/features/CMakeLists.txt
index 20c5d94..6e4a366 100644
--- a/tests/features/CMakeLists.txt
+++ b/tests/features/CMakeLists.txt
@@ -1,8 +1,10 @@
set(local_tests
features_types
+ features_metadata
features_nacm)
set(local_tests_wraps
" "
+ " "
" ")
set(tests ${tests} ${local_tests} PARENT_SCOPE)
set(tests_wraps ${tests_wraps} ${local_tests_wraps} PARENT_SCOPE)
diff --git a/tests/features/test_metadata.c b/tests/features/test_metadata.c
new file mode 100644
index 0000000..9c60e37
--- /dev/null
+++ b/tests/features/test_metadata.c
@@ -0,0 +1,186 @@
+/*
+ * @file test_metadata.c
+ * @author: Radek Krejci <rkrejci@cesnet.cz>
+ * @brief unit tests for Metadata extension (annotation) support
+ *
+ * Copyright (c) 2019 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
+ */
+
+#include "tests/config.h"
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "../../src/libyang.h"
+#include "../../src/plugins_exts_metadata.h"
+
+#define BUFSIZE 1024
+char logbuf[BUFSIZE] = {0};
+int store = -1; /* negative for infinite logging, positive for limited logging */
+
+struct state_s {
+ void *func;
+ struct ly_ctx *ctx;
+};
+
+/* set to 0 to printing error messages to stderr instead of checking them in code */
+#define ENABLE_LOGGER_CHECKING 1
+
+#if ENABLE_LOGGER_CHECKING
+static void
+logger(LY_LOG_LEVEL level, const char *msg, const char *path)
+{
+ (void) level; /* unused */
+ if (store) {
+ if (path && path[0]) {
+ snprintf(logbuf, BUFSIZE - 1, "%s %s", msg, path);
+ } else {
+ strncpy(logbuf, msg, BUFSIZE - 1);
+ }
+ if (store > 0) {
+ --store;
+ }
+ }
+}
+#endif
+
+static int
+setup(void **state)
+{
+ struct state_s *s;
+
+ s = calloc(1, sizeof *s);
+ assert_non_null(s);
+
+#if ENABLE_LOGGER_CHECKING
+ ly_set_log_clb(logger, 1);
+#endif
+
+ assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, 0, &s->ctx));
+ *state = s;
+
+ return 0;
+}
+
+static int
+teardown(void **state)
+{
+ struct state_s *s = (struct state_s*)(*state);
+
+#if ENABLE_LOGGER_CHECKING
+ if (s->func) {
+ fprintf(stderr, "%s\n", logbuf);
+ }
+#endif
+
+ ly_ctx_destroy(s->ctx, NULL);
+ free(s);
+
+ return 0;
+}
+
+void
+logbuf_clean(void)
+{
+ logbuf[0] = '\0';
+}
+
+#if ENABLE_LOGGER_CHECKING
+# define logbuf_assert(str) assert_string_equal(logbuf, str)
+#else
+# define logbuf_assert(str)
+#endif
+
+static void
+test_yang(void **state)
+{
+ struct state_s *s = (struct state_s*)(*state);
+ s->func = test_yang;
+
+ struct lys_module *mod;
+ struct lysc_ext_instance *e;
+ struct lyext_metadata *ant;
+
+ const char *data = "module a {yang-version 1.1; namespace urn:tests:extensions:metadata:a; prefix a;"
+ "import ietf-yang-metadata {prefix md;}"
+ "feature f;"
+ "md:annotation x {"
+ " description \"test\";"
+ " if-feature f;"
+ " reference \"test\";"
+ " status \"current\";"
+ " type uint8;"
+ " units meters;"
+ "}}";
+ assert_non_null(mod = lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+ assert_int_equal(1, LY_ARRAY_SIZE(mod->compiled->exts));
+ e = &mod->compiled->exts[0];
+ assert_non_null(ant = (struct lyext_metadata*)e->data);
+ assert_string_equal("meters", ant->units);
+
+ /* invalid */
+ /* missing mandatory type substatement */
+ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;"
+ "import ietf-yang-metadata {prefix md;}"
+ "md:annotation aa;}";
+ assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+ logbuf_assert("Missing mandatory keyword \"type\" as a child of \"md:annotation aa\". /aa:{extension='md:annotation'}/aa");
+
+ /* not allowed substatement */
+ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;"
+ "import ietf-yang-metadata {prefix md;}"
+ "md:annotation aa {default x;}}";
+ assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+ logbuf_assert("Invalid keyword \"default\" as a child of \"md:annotation aa\" extension instance. /aa:{extension='md:annotation'}/aa");
+
+ /* invalid cardinality of units substatement */
+ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;"
+ "import ietf-yang-metadata {prefix md;}"
+ "md:annotation aa {type string; units x; units y;}}";
+ assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+ logbuf_assert("Duplicate keyword \"units\". /aa:{extension='md:annotation'}/aa");
+
+ /* invalid cardinality of status substatement */
+ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;"
+ "import ietf-yang-metadata {prefix md;}"
+ "md:annotation aa {type string; status current; status obsolete;}}";
+ assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+ logbuf_assert("Duplicate keyword \"status\". /aa:{extension='md:annotation'}/aa");
+
+ /* invalid cardinality of status substatement */
+ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;"
+ "import ietf-yang-metadata {prefix md;}"
+ "md:annotation aa {type string; type uint8;}}";
+ assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+ logbuf_assert("Duplicate keyword \"type\". /aa:{extension='md:annotation'}/aa");
+
+ /* duplication of the same annotation */
+ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;"
+ "import ietf-yang-metadata {prefix md;}"
+ "md:annotation aa {type string;} md:annotation aa {type uint8;}}";
+ assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+ logbuf_assert("Extension plugin \"libyang 2 - metadata, version 1\": "
+ "Extension md:annotation is instantiated multiple times.) /aa:{extension='md:annotation'}/aa");
+
+ s->func = NULL;
+}
+
+int main(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(test_yang, setup, teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/tests/features/test_nacm.c b/tests/features/test_nacm.c
index 87882da..6ae1900 100644
--- a/tests/features/test_nacm.c
+++ b/tests/features/test_nacm.c
@@ -136,14 +136,14 @@
"nacm:default-deny-all;}";
assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
logbuf_assert("Extension plugin \"libyang 2 - NACM, version 1\": "
- "Extension nacm:default-deny-all is allowed only in a data nodes, but it is placed in \"module\" statement.) /");
+ "Extension nacm:default-deny-all is allowed only in a data nodes, but it is placed in \"module\" statement.) /aa:{extension='nacm:default-deny-all'}");
data = "module aa {yang-version 1.1; namespace urn:tests:extensions:nacm:aa; prefix en;"
"import ietf-netconf-acm {revision-date 2018-02-14; prefix nacm;}"
"leaf l { type string; nacm:default-deny-all; nacm:default-deny-write;}}";
assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
logbuf_assert("Extension plugin \"libyang 2 - NACM, version 1\": "
- "Extension nacm:default-deny-write is mixed with nacm:default-deny-all.) /aa:l");
+ "Extension nacm:default-deny-write is mixed with nacm:default-deny-all.) /aa:l/{extension='nacm:default-deny-write'}");
s->func = NULL;
}
@@ -181,14 +181,14 @@
"notification notif {nacm:default-deny-write;}}";
assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
logbuf_assert("Extension plugin \"libyang 2 - NACM, version 1\": "
- "Extension nacm:default-deny-write is not allowed in Notification statement.) /aa:notif");
+ "Extension nacm:default-deny-write is not allowed in Notification statement.) /aa:notif/{extension='nacm:default-deny-write'}");
data = "module aa {yang-version 1.1; namespace urn:tests:extensions:nacm:aa; prefix en;"
"import ietf-netconf-acm {revision-date 2018-02-14; prefix nacm;}"
"leaf l { type string; nacm:default-deny-write; nacm:default-deny-write;}}";
assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
logbuf_assert("Extension plugin \"libyang 2 - NACM, version 1\": "
- "Extension nacm:default-deny-write is instantiated multiple times.) /aa:l");
+ "Extension nacm:default-deny-write is instantiated multiple times.) /aa:l/{extension='nacm:default-deny-write'}");
s->func = NULL;
}