schema compile CHANGE basic implementation of applying groupings into uses

Refine and uses-augment not yet implemented.
diff --git a/src/parser_yang.c b/src/parser_yang.c
index 82a9ba3..6c7253b 100644
--- a/src/parser_yang.c
+++ b/src/parser_yang.c
@@ -2822,6 +2822,8 @@
     size_t word_len;
     struct lysp_action_inout *inout;
     enum yang_keyword kw;
+    unsigned int u;
+    struct lysp_node *child;
 
     if (*inout_p) {
         LOGVAL_YANG(ctx, LY_VCODE_DUPSTMT, ly_stmt2str(inout_kw));
@@ -2880,6 +2882,12 @@
             return LY_EVALID;
         }
     }
+    /* finalize parent pointers to the reallocated items */
+    LY_ARRAY_FOR(inout->groupings, u) {
+        LY_LIST_FOR(inout->groupings[u].data, child) {
+            child->parent = (struct lysp_node*)&inout->groupings[u];
+        }
+    }
     return ret;
 }
 
@@ -2900,6 +2908,8 @@
     size_t word_len;
     enum yang_keyword kw;
     struct lysp_action *act;
+    struct lysp_node *child;
+    unsigned int u;
 
     LY_ARRAY_NEW_RET(ctx->ctx, *actions, act, LY_EMEM);
 
@@ -2945,6 +2955,12 @@
             return LY_EVALID;
         }
     }
+    /* finalize parent pointers to the reallocated items */
+    LY_ARRAY_FOR(act->groupings, u) {
+        LY_LIST_FOR(act->groupings[u].data, child) {
+            child->parent = (struct lysp_node*)&act->groupings[u];
+        }
+    }
     return ret;
 }
 
@@ -2965,6 +2981,8 @@
     size_t word_len;
     enum yang_keyword kw;
     struct lysp_notif *notif;
+    struct lysp_node *child;
+    unsigned int u;
 
     LY_ARRAY_NEW_RET(ctx->ctx, *notifs, notif, LY_EMEM);
 
@@ -3032,6 +3050,12 @@
             return LY_EVALID;
         }
     }
+    /* finalize parent pointers to the reallocated items */
+    LY_ARRAY_FOR(notif->groupings, u) {
+        LY_LIST_FOR(notif->groupings[u].data, child) {
+            child->parent = (struct lysp_node*)&notif->groupings[u];
+        }
+    }
     return ret;
 }
 
@@ -3052,6 +3076,8 @@
     size_t word_len;
     enum yang_keyword kw;
     struct lysp_grp *grp;
+    struct lysp_node *child;
+    unsigned int u;
 
     LY_ARRAY_NEW_RET(ctx->ctx, *groupings, grp, LY_EMEM);
 
@@ -3120,6 +3146,12 @@
             return LY_EVALID;
         }
     }
+    /* finalize parent pointers to the reallocated items */
+    LY_ARRAY_FOR(grp->groupings, u) {
+        LY_LIST_FOR(grp->groupings[u].data, child) {
+            child->parent = (struct lysp_node*)&grp->groupings[u];
+        }
+    }
     return ret;
 }
 
@@ -3503,6 +3535,7 @@
     enum yang_keyword kw;
     struct lysp_node *iter;
     struct lysp_node_container *cont;
+    unsigned int u;
 
     /* create structure */
     cont = calloc(1, sizeof *cont);
@@ -3597,6 +3630,12 @@
             return LY_EVALID;
         }
     }
+    /* finalize parent pointers to the reallocated items */
+    LY_ARRAY_FOR(cont->groupings, u) {
+        LY_LIST_FOR(cont->groupings[u].data, iter) {
+            iter->parent = (struct lysp_node*)&cont->groupings[u];
+        }
+    }
     return ret;
 }
 
@@ -3618,6 +3657,7 @@
     enum yang_keyword kw;
     struct lysp_node *iter;
     struct lysp_node_list *list;
+    unsigned int u;
 
     /* create structure */
     list = calloc(1, sizeof *list);
@@ -3725,6 +3765,12 @@
         }
     }
     LY_CHECK_RET(ret);
+    /* finalize parent pointers to the reallocated items */
+    LY_ARRAY_FOR(list->groupings, u) {
+        LY_LIST_FOR(list->groupings[u].data, iter) {
+            iter->parent = (struct lysp_node*)&list->groupings[u];
+        }
+    }
 checks:
     if (list->max && list->min > list->max) {
         LOGVAL_YANG(ctx, LYVE_SEMANTICS,
@@ -4271,6 +4317,8 @@
     enum yang_keyword kw, prev_kw = 0;
     enum yang_module_stmt mod_stmt = Y_MOD_MODULE_HEADER;
     struct lysp_module *dup;
+    struct lysp_node *child;
+    unsigned int u;
 
     /* (sub)module name */
     LY_CHECK_RET(get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len));
@@ -4452,6 +4500,14 @@
         }
     }
     LY_CHECK_RET(ret);
+
+    /* finalize parent pointers to the reallocated items */
+    LY_ARRAY_FOR(mod->groupings, u) {
+        LY_LIST_FOR(mod->groupings[u].data, child) {
+            child->parent = (struct lysp_node*)&mod->groupings[u];
+        }
+    }
+
 checks:
     /* mandatory substatements */
     if (mod->submodule) {
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index cf042b6..bbae473 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -216,7 +216,7 @@
 
     /* get module where the extension definition should be placed */
     for (u = 0; ext_p->name[u] != ':'; ++u);
-    mod = lys_module_find_prefix(ctx->mod, ext_p->name, u);
+    mod = lys_module_find_prefix(ctx->mod_def, ext_p->name, u);
     LY_CHECK_ERR_RET(!mod, LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_REFERENCE,
                                   "Invalid prefix \"%.*s\" used for extension instance identifier.", u, ext_p->name),
                      LY_EVALID);
@@ -314,7 +314,7 @@
 
     if (checkversion || expr_size > 1) {
         /* check that we have 1.1 module */
-        if (ctx->mod->compiled->version != LYS_VERSION_1_1) {
+        if (ctx->mod_def->parsed->version != LYS_VERSION_1_1) {
             LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
                    "Invalid value \"%s\" of if-feature - YANG 1.1 expression in YANG 1.0 module.", *value);
             return LY_EVALID;
@@ -520,12 +520,12 @@
 {
     unsigned int u, v;
     const char *s, *name;
-    struct lysc_module *mod;
+    struct lys_module *mod;
     struct lysc_ident **idref;
 
     assert(ident || bases);
 
-    if (LY_ARRAY_SIZE(bases_p) > 1 && ctx->mod->compiled->version < 2) {
+    if (LY_ARRAY_SIZE(bases_p) > 1 && ctx->mod_def->parsed->version < 2) {
         LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
                "Multiple bases in %s are allowed only in YANG 1.1 modules.", ident ? "identity" : "identityref type");
         return LY_EVALID;
@@ -536,10 +536,10 @@
         if (s) {
             /* prefixed identity */
             name = &s[1];
-            mod = lysc_module_find_prefix(ctx->mod->compiled, bases_p[u], s - bases_p[u]);
+            mod = lys_module_find_prefix(ctx->mod_def, bases_p[u], s - bases_p[u]);
         } else {
             name = bases_p[u];
-            mod = ctx->mod->compiled;
+            mod = ctx->mod_def;
         }
         if (!mod) {
             if (ident) {
@@ -552,17 +552,17 @@
             return LY_EVALID;
         }
         idref = NULL;
-        if (mod->identities) {
-            for (v = 0; v < LY_ARRAY_SIZE(mod->identities); ++v) {
-                if (!strcmp(name, mod->identities[v].name)) {
+        if (mod->compiled && mod->compiled->identities) {
+            for (v = 0; v < LY_ARRAY_SIZE(mod->compiled->identities); ++v) {
+                if (!strcmp(name, mod->compiled->identities[v].name)) {
                     if (ident) {
                         /* we have match! store the backlink */
-                        LY_ARRAY_NEW_RET(ctx->ctx, mod->identities[v].derived, idref, LY_EMEM);
+                        LY_ARRAY_NEW_RET(ctx->ctx, mod->compiled->identities[v].derived, idref, LY_EMEM);
                         *idref = ident;
                     } else {
                         /* we have match! store the found identity */
                         LY_ARRAY_NEW_RET(ctx->ctx, *bases, idref, LY_EMEM);
-                        *idref = &mod->identities[v];
+                        *idref = &mod->compiled->identities[v];
                     }
                     break;
                 }
@@ -1457,7 +1457,7 @@
     uint32_t position = 0;
     struct lysc_type_enum_item *e, storage;
 
-    if (base_enums && ctx->mod->compiled->version < 2) {
+    if (base_enums && ctx->mod_def->parsed->version < 2) {
         LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG, "%s type can be subtyped only in YANG 1.1 modules.",
                basetype == LY_TYPE_ENUM ? "Enumeration" : "Bits");
         return LY_EVALID;
@@ -2487,7 +2487,7 @@
 
     tctx = calloc(1, sizeof *tctx);
     LY_CHECK_ERR_RET(!tctx, LOGMEM(ctx->ctx), LY_EMEM);
-    for (ret = lysp_type_find(type_p->name, context_node_p, ctx->mod->parsed,
+    for (ret = lysp_type_find(type_p->name, context_node_p, ctx->mod_def->parsed,
                              &basetype, &tctx->tpdf, &tctx->node, &tctx->mod);
             ret == LY_SUCCESS;
             ret = lysp_type_find(tctx_prev->tpdf->type.name, tctx_prev->node, tctx_prev->mod,
@@ -2639,7 +2639,7 @@
         /* get restrictions from the node itself */
         (*type)->basetype = basetype;
         ++(*type)->refcount;
-        ret = lys_compile_type_(ctx, context_node_p, context_flags, context_mod, context_name, type_p, ctx->mod, basetype, options, NULL, base, type);
+        ret = lys_compile_type_(ctx, context_node_p, context_flags, context_mod, context_name, type_p, ctx->mod_def, basetype, options, NULL, base, type);
         LY_CHECK_GOTO(ret, cleanup);
     } else {
         /* no specific restriction in leaf's type definition, copy from the base */
@@ -2708,7 +2708,7 @@
     DUP_STRING(ctx->ctx, leaf_p->units, leaf->units);
     DUP_STRING(ctx->ctx, leaf_p->dflt, leaf->dflt);
 
-    ret = lys_compile_type(ctx, node_p, node_p->flags, ctx->mod->parsed, node_p->name, &leaf_p->type, options, &leaf->type,
+    ret = lys_compile_type(ctx, node_p, node_p->flags, ctx->mod_def->parsed, node_p->name, &leaf_p->type, options, &leaf->type,
                            leaf->units ? NULL : &leaf->units, leaf->dflt || (leaf->flags & LYS_MAND_TRUE) ? NULL : &leaf->dflt);
     LY_CHECK_GOTO(ret, done);
     if (leaf->type->basetype == LY_TYPE_LEAFREF) {
@@ -2765,7 +2765,7 @@
     llist->min = llist_p->min;
     llist->max = llist_p->max ? llist_p->max : (uint32_t)-1;
 
-    ret = lys_compile_type(ctx, node_p, node_p->flags, ctx->mod->parsed, node_p->name, &llist_p->type, options, &llist->type,
+    ret = lys_compile_type(ctx, node_p, node_p->flags, ctx->mod_def->parsed, node_p->name, &llist_p->type, options, &llist->type,
                            llist->units ? NULL : &llist->units, (llist->dflts || llist->min) ? NULL : &dflt);
     LY_CHECK_GOTO(ret, done);
     if (dflt) {
@@ -2785,7 +2785,7 @@
             }
         }
     } else if (llist->type->basetype == LY_TYPE_EMPTY) {
-        if (ctx->mod->compiled->version < LYS_VERSION_1_1) {
+        if (ctx->mod_def->parsed->version < LYS_VERSION_1_1) {
             LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SEMANTICS,
                    "Leaf-list of type \"empty\" is allowed only in YANG 1.1 modules.");
             return LY_EVALID;
@@ -2886,7 +2886,7 @@
             LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SEMANTICS, "Key of the configuration list must not be status leaf.");
             return LY_EVALID;
         }
-        if (ctx->mod->compiled->version < LYS_VERSION_1_1) {
+        if (ctx->mod_def->parsed->version < LYS_VERSION_1_1) {
             /* YANG 1.0 denies key to be of empty type */
             if ((*key)->type->basetype == LY_TYPE_EMPTY) {
                 LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SEMANTICS,
@@ -3230,6 +3230,118 @@
 }
 
 /**
+ * @brief Compile parsed uses statement - resolve target grouping and connect its content into parent.
+ * If present, also apply uses's modificators.
+ *
+ * @param[in] ctx Compile context
+ * @param[in] uses_p Parsed uses schema node.
+ * @param[in] options Various options to modify compiler behavior, see [compile flags](@ref scflags).
+ * @param[in] parent Compiled parent node where the content of the referenced grouping is supposed to be connected. It is
+ * NULL for top-level nodes, in such a case the module where the node will be connected is taken from
+ * the compile context.
+ * @return LY_ERR value - LY_SUCCESS or LY_EVALID.
+ */
+static LY_ERR
+lys_compile_uses(struct lysc_ctx *ctx, struct lysp_node_uses *uses_p, int options, struct lysc_node *parent)
+{
+    struct lysp_node *node_p;
+    struct lysc_node *last;
+    const struct lysp_grp *grp = NULL;
+    unsigned int u, grp_stack_count;
+    int found;
+    const char *id, *name, *prefix;
+    size_t prefix_len, name_len;
+    struct lys_module *mod, *mod_old;
+    LY_ERR ret = LY_EVALID;
+
+    /* search for the grouping definition */
+    found = 0;
+    id = uses_p->name;
+    lys_parse_nodeid(&id, &prefix, &prefix_len, &name, &name_len);
+    if (prefix) {
+        mod = lys_module_find_prefix(ctx->mod_def, prefix, prefix_len);
+        if (!mod) {
+            LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_REFERENCE,
+                   "Invalid prefix used for grouping reference (%s).", uses_p->name);
+            return LY_EVALID;
+        }
+    } else {
+        mod = ctx->mod_def;
+    }
+    if (mod == ctx->mod_def) {
+        for (node_p = uses_p->parent; !found && node_p; node_p = node_p->parent) {
+            grp = lysp_node_groupings(node_p);
+            LY_ARRAY_FOR(grp, u) {
+                if (!strcmp(grp[u].name, name)) {
+                    grp = &grp[u];
+                    found = 1;
+                    break;
+                }
+            }
+        }
+    }
+    if (!found) {
+        /* search in top-level groupings */
+        grp = mod->parsed->groupings;
+        LY_ARRAY_FOR(grp, u) {
+            if (!strcmp(grp[u].name, name)) {
+                grp = &grp[u];
+                found = 1;
+                break;
+            }
+        }
+    }
+    if (!found) {
+        LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SEMANTICS,
+               "Grouping \"%s\" referenced by a uses statement not found.", uses_p->name);
+        return LY_EVALID;
+    }
+
+    /* grouping must not reference themselves - stack in ctx maintains list of groupings currently being applied */
+    grp_stack_count = ctx->groupings.count;
+    ly_set_add(&ctx->groupings, (void*)grp, 0);
+    if (grp_stack_count == ctx->groupings.count) {
+        /* the target grouping is already in the stack, so we are already inside it -> circular dependency */
+        LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_REFERENCE,
+               "Grouping \"%s\" references itself through a uses statement.", grp->name);
+        return LY_EVALID;
+    }
+
+    /* switch context's mod_def */
+    mod_old = ctx->mod_def;
+    ctx->mod_def = mod;
+
+    /* check status */
+    LY_CHECK_GOTO(lysc_check_status(ctx, uses_p->flags, mod_old, uses_p->name, grp->flags, mod, grp->name), error);
+
+    /* remember the last parent's child present before connecting the grouping content, it will be used later
+     * to know where start when applying uses's modificators */
+    if (parent) {
+        last = (struct lysc_node*)lysc_node_children(parent);
+    } else {
+        last = ctx->mod->compiled->data;
+    }
+    if (last) {
+        last = last->prev; /* get the last one */
+    }
+
+    /* connect the grouping's content */
+    LY_LIST_FOR(grp->data, node_p) {
+        LY_CHECK_GOTO(lys_compile_node(ctx, node_p, options, parent), error);
+    }
+
+    ret = LY_SUCCESS;
+error:
+    /* reload previous context's mod_def */
+    ctx->mod_def = mod_old;
+    /* remove the grouping from the stack for circular groupings dependency check */
+    ly_set_rm_index(&ctx->groupings, ctx->groupings.count - 1, NULL);
+    assert(ctx->groupings.count == grp_stack_count);
+
+    return ret;
+}
+
+/**
  * @brief Compile parsed schema node information.
  * @param[in] ctx Compile context
  * @param[in] node_p Parsed schema node.
@@ -3274,6 +3386,8 @@
         node = (struct lysc_node*)calloc(1, sizeof(struct lysc_node_anydata));
         node_compile_spec = lys_compile_node_any;
         break;
+    case LYS_USES:
+        return lys_compile_uses(ctx, (struct lysp_node_uses*)node_p, options, parent);
     default:
         LOGINT(ctx->ctx);
         return LY_EINT;
@@ -3414,6 +3528,7 @@
 
     ctx.ctx = sp->ctx;
     ctx.mod = mod;
+    ctx.mod_def = mod;
 
     mod->compiled = mod_c = calloc(1, sizeof *mod_c);
     LY_CHECK_ERR_RET(!mod_c, LOGMEM(sp->ctx), LY_EMEM);
@@ -3494,6 +3609,7 @@
         }
     }
     ly_set_erase(&ctx.unres, NULL);
+    ly_set_erase(&ctx.groupings, NULL);
 
     if (options & LYSC_OPT_FREE_SP) {
         lysp_module_free(mod->parsed);
@@ -3505,6 +3621,7 @@
 
 error:
     ly_set_erase(&ctx.unres, NULL);
+    ly_set_erase(&ctx.groupings, NULL);
     lysc_module_free(mod_c, NULL);
     ((struct lys_module*)mod)->compiled = NULL;
     return ret;
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index 60b616b..219a2c7 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -61,6 +61,11 @@
 struct lysc_ctx {
     struct ly_ctx *ctx;
     struct lys_module *mod;
+    struct lys_module *mod_def; /* context module for the definitions of the nodes being currently
+                                   processed - groupings are supposed to be evaluated in place where
+                                   defined, but its content instances are supposed to be placed into
+                                   the target module (mod) */
+    struct ly_set groupings;    /* stack for groupings circular check */
     struct ly_set unres;        /* to validate leafref's target and xpath of when/must */
     uint16_t path_len;
 #define LYSC_CTX_BUFSIZE 4078
diff --git a/tests/src/test_parser_yang.c b/tests/src/test_parser_yang.c
index e725f4a..b07725a 100644
--- a/tests/src/test_parser_yang.c
+++ b/tests/src/test_parser_yang.c
@@ -1755,6 +1755,114 @@
     return test_any(state, YANG_ANYXML);
 }
 
+static void
+test_grouping(void **state)
+{
+    *state = test_grouping;
+
+    struct lysp_module mod = {0};
+    struct ly_parser_ctx ctx = {0};
+    struct lysp_grp *grp = NULL;
+    const char *str;
+
+    assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, 0, &ctx.ctx));
+    assert_non_null(ctx.ctx);
+    ctx.line = 1;
+    ctx.mod = &mod;
+    ctx.mod->version = 2; /* simulate YANG 1.1 */
+
+    /* invalid cardinality */
+#define TEST_DUP(MEMBER, VALUE1, VALUE2) \
+    str = "l {" MEMBER" "VALUE1";"MEMBER" "VALUE2";} ..."; \
+    assert_int_equal(LY_EVALID, parse_grouping(&ctx, &str, NULL, &grp)); \
+    logbuf_assert("Duplicate keyword \""MEMBER"\". Line number 1."); \
+    FREE_ARRAY(ctx.ctx, grp, lysp_grp_free); grp = NULL;
+
+    TEST_DUP("description", "text1", "text2");
+    TEST_DUP("reference", "1", "2");
+    TEST_DUP("status", "current", "obsolete");
+#undef TEST_DUP
+
+    /* full content */
+    str = "grp {action x;anydata any;anyxml anyxml; choice ch;container c;description test;grouping g;leaf l {type string;}"
+          "leaf-list ll {type string;} list li;notification not;reference test;status current;typedef t {type int8;}uses g;m:ext;} ...";
+    assert_int_equal(LY_SUCCESS, parse_grouping(&ctx, &str, NULL, &grp));
+    assert_non_null(grp);
+    assert_int_equal(LYS_GROUPING, grp->nodetype);
+    assert_string_equal("grp", grp->name);
+    assert_string_equal("test", grp->dsc);
+    assert_non_null(grp->exts);
+    assert_string_equal("test", grp->ref);
+    assert_null(grp->parent);
+    assert_int_equal( LYS_STATUS_CURR, grp->flags);
+    ly_set_erase(&ctx.tpdfs_nodes, NULL);
+    FREE_ARRAY(ctx.ctx, grp, lysp_grp_free); grp = NULL;
+
+    /* invalid content */
+    str = "grp {config true} ...";
+    assert_int_equal(LY_EVALID, parse_grouping(&ctx, &str, NULL, &grp));
+    logbuf_assert("Invalid keyword \"config\" as a child of \"grouping\". Line number 1.");
+    FREE_ARRAY(ctx.ctx, grp, lysp_grp_free); grp = NULL;
+
+    str = "grp {must 'expr'} ...";
+    assert_int_equal(LY_EVALID, parse_grouping(&ctx, &str, NULL, &grp));
+    logbuf_assert("Invalid keyword \"must\" as a child of \"grouping\". Line number 1.");
+    FREE_ARRAY(ctx.ctx, grp, lysp_grp_free); grp = NULL;
+
+    *state = NULL;
+    ly_ctx_destroy(ctx.ctx, NULL);
+}
+
+static void
+test_uses(void **state)
+{
+    *state = test_uses;
+
+    struct lysp_module mod = {0};
+    struct ly_parser_ctx ctx = {0};
+    struct lysp_node_uses *u = NULL;
+    const char *str;
+
+    assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, 0, &ctx.ctx));
+    assert_non_null(ctx.ctx);
+    ctx.line = 1;
+    ctx.mod = &mod;
+    ctx.mod->version = 2; /* simulate YANG 1.1 */
+
+    /* invalid cardinality */
+#define TEST_DUP(MEMBER, VALUE1, VALUE2) \
+    str = "l {" MEMBER" "VALUE1";"MEMBER" "VALUE2";} ..."; \
+    assert_int_equal(LY_EVALID, parse_uses(&ctx, &str, NULL, (struct lysp_node**)&u)); \
+    logbuf_assert("Duplicate keyword \""MEMBER"\". Line number 1."); \
+    lysp_node_free(ctx.ctx, (struct lysp_node*)u); u = NULL;
+
+    TEST_DUP("description", "text1", "text2");
+    TEST_DUP("reference", "1", "2");
+    TEST_DUP("status", "current", "obsolete");
+    TEST_DUP("when", "true", "false");
+#undef TEST_DUP
+
+    /* full content */
+    str = "grpref {augment some/node;description test;if-feature f;reference test;refine some/other/node;status current;when true;m:ext;} ...";
+    assert_int_equal(LY_SUCCESS, parse_uses(&ctx, &str, NULL, (struct lysp_node**)&u));
+    assert_non_null(u);
+    assert_int_equal(LYS_USES, u->nodetype);
+    assert_string_equal("grpref", u->name);
+    assert_string_equal("test", u->dsc);
+    assert_non_null(u->exts);
+    assert_non_null(u->iffeatures);
+    assert_string_equal("test", u->ref);
+    assert_non_null(u->augments);
+    assert_non_null(u->refines);
+    assert_non_null(u->when);
+    assert_null(u->parent);
+    assert_null(u->next);
+    assert_int_equal(LYS_STATUS_CURR, u->flags);
+    lysp_node_free(ctx.ctx, (struct lysp_node*)u); u = NULL;
+
+    *state = NULL;
+    ly_ctx_destroy(ctx.ctx, NULL);
+}
 int main(void)
 {
     const struct CMUnitTest tests[] = {
@@ -1776,6 +1884,8 @@
         cmocka_unit_test_setup_teardown(test_case, logger_setup, logger_teardown),
         cmocka_unit_test_setup_teardown(test_anydata, logger_setup, logger_teardown),
         cmocka_unit_test_setup_teardown(test_anyxml, logger_setup, logger_teardown),
+        cmocka_unit_test_setup_teardown(test_grouping, logger_setup, logger_teardown),
+        cmocka_unit_test_setup_teardown(test_uses, logger_setup, logger_teardown),
     };
 
     return cmocka_run_group_tests(tests, NULL, NULL);
diff --git a/tests/src/test_tree_schema_compile.c b/tests/src/test_tree_schema_compile.c
index ec02274..4acbe4d 100644
--- a/tests/src/test_tree_schema_compile.c
+++ b/tests/src/test_tree_schema_compile.c
@@ -2212,6 +2212,52 @@
     ly_ctx_destroy(ctx, NULL);
 }
 
+static void
+test_uses(void **state)
+{
+    *state = test_uses;
+
+    struct ly_ctx *ctx;
+    struct lys_module *mod;
+    struct lysc_node *parent, *child;
+
+    assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIRS, &ctx));
+
+    assert_non_null(mod = lys_parse_mem(ctx, "module grp {namespace urn:grp;prefix g; typedef mytype {type string;}"
+                                        "grouping grp {leaf x {type mytype;}}}", LYS_IN_YANG));
+    assert_int_equal(LY_SUCCESS, lys_compile(mod, 0));
+
+
+    assert_non_null(mod = lys_parse_mem(ctx, "module a {namespace urn:a;prefix a;import grp {prefix g;}"
+                                        "grouping grp_a_top {leaf a1 {type int8;}}"
+                                        "container a {uses grp_a; uses grp_a_top; uses g:grp; grouping grp_a {leaf a2 {type uint8;}}}}", LYS_IN_YANG));
+    assert_int_equal(LY_SUCCESS, lys_compile(mod, 0));
+    assert_non_null((parent = mod->compiled->data));
+    assert_int_equal(LYS_CONTAINER, parent->nodetype);
+    assert_non_null((child = ((struct lysc_node_container*)parent)->child));
+    assert_string_equal("a2", child->name);
+    assert_non_null((child = child->next));
+    assert_string_equal("a1", child->name);
+    assert_non_null((child = child->next));
+    assert_string_equal("x", child->name);
+
+    /* invalid */
+    assert_non_null(mod = lys_parse_mem(ctx, "module aa {namespace urn:aa;prefix aa;uses missinggrp;}", LYS_IN_YANG));
+    assert_int_equal(LY_EVALID, lys_compile(mod, 0));
+    logbuf_assert("Grouping \"missinggrp\" referenced by a uses statement not found.");
+
+    assert_non_null(mod = lys_parse_mem(ctx, "module bb {namespace urn:bb;prefix bb;uses grp;"
+                                        "grouping grp {leaf a{type string;}uses grp1;}"
+                                        "grouping grp1 {leaf b {type string;}uses grp2;}"
+                                        "grouping grp2 {leaf c {type string;}uses grp;}}", LYS_IN_YANG));
+    assert_int_equal(LY_EVALID, lys_compile(mod, 0));
+    logbuf_assert("Grouping \"grp\" references itself through a uses statement.");
+
+    *state = NULL;
+    ly_ctx_destroy(ctx, NULL);
+}
+
+
 int main(void)
 {
     const struct CMUnitTest tests[] = {
@@ -2236,6 +2282,7 @@
         cmocka_unit_test_setup_teardown(test_node_list, logger_setup, logger_teardown),
         cmocka_unit_test_setup_teardown(test_node_choice, logger_setup, logger_teardown),
         cmocka_unit_test_setup_teardown(test_node_anydata, logger_setup, logger_teardown),
+        cmocka_unit_test_setup_teardown(test_uses, logger_setup, logger_teardown),
     };
 
     return cmocka_run_group_tests(tests, NULL, NULL);