/**
 * @file test_context.c
 * @author: Radek Krejci <rkrejci@cesnet.cz>
 * @brief unit tests for functions from context.c
 *
 * Copyright (c) 2018 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 "context.h"
#include "in.h"
#include "ly_common.h"
#include "schema_compile.h"
#include "tests_config.h"
#include "tree_schema_internal.h"

#ifdef _WIN32

static void
slashes_to_backslashes(char *path)
{
    while ((path = strchr(path, '/'))) {
        *path++ = '\\';
    }
}

static void
test_searchdirs(void **state)
{
    const char * const *list;
    char *path1 = strdup(TESTS_BIN "/utests");
    char *path2 = strdup(TESTS_SRC);

    slashes_to_backslashes(path1);
    slashes_to_backslashes(path2);

    assert_int_equal(LY_EINVAL, ly_ctx_set_searchdir(NULL, NULL));
    CHECK_LOG_LASTMSG("Invalid argument ctx (ly_ctx_set_searchdir()).");
    assert_null(ly_ctx_get_searchdirs(NULL));
    CHECK_LOG_LASTMSG("Invalid argument ctx (ly_ctx_get_searchdirs()).");
    assert_int_equal(LY_EINVAL, ly_ctx_unset_searchdir(NULL, NULL));
    CHECK_LOG_LASTMSG("Invalid argument ctx (ly_ctx_unset_searchdir()).");

    /* correct path */
    assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, path1));
    assert_int_equal(1, UTEST_LYCTX->search_paths.count);
    assert_string_equal(path1, UTEST_LYCTX->search_paths.objs[0]);

    /* duplicated paths */
    assert_int_equal(LY_EEXIST, ly_ctx_set_searchdir(UTEST_LYCTX, path1));
    assert_int_equal(1, UTEST_LYCTX->search_paths.count);
    assert_string_equal(path1, UTEST_LYCTX->search_paths.objs[0]);

    /* another path */
    assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, path2));
    assert_int_equal(2, UTEST_LYCTX->search_paths.count);
    assert_string_equal(path2, UTEST_LYCTX->search_paths.objs[1]);

    /* get searchpaths */
    list = ly_ctx_get_searchdirs(UTEST_LYCTX);
    assert_non_null(list);
    assert_string_equal(path1, list[0]);
    assert_string_equal(path2, list[1]);
    assert_null(list[2]);

    /* removing searchpaths */
    /* nonexisting */
    assert_int_equal(LY_EINVAL, ly_ctx_unset_searchdir(UTEST_LYCTX, "/nonexistingfile"));
    CHECK_LOG_CTX("Invalid argument value (ly_ctx_unset_searchdir()).", NULL, 0);

    /* first */
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_searchdir(UTEST_LYCTX, path1));
    assert_int_equal(1, UTEST_LYCTX->search_paths.count);
    assert_string_not_equal(path1, list[0]);

    /* second */
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_searchdir(UTEST_LYCTX, path2));
    assert_int_equal(0, UTEST_LYCTX->search_paths.count);

    free(path1);
    free(path2);
}

#else

static void
test_searchdirs(void **state)
{
    const char * const *list;

    /* invalid arguments */
    assert_int_equal(LY_EINVAL, ly_ctx_set_searchdir(NULL, NULL));
    CHECK_LOG_LASTMSG("Invalid argument ctx (ly_ctx_set_searchdir()).");
    assert_null(ly_ctx_get_searchdirs(NULL));
    CHECK_LOG_LASTMSG("Invalid argument ctx (ly_ctx_get_searchdirs()).");
    assert_int_equal(LY_EINVAL, ly_ctx_unset_searchdir(NULL, NULL));
    CHECK_LOG_LASTMSG("Invalid argument ctx (ly_ctx_unset_searchdir()).");

    /* readable and executable, but not a directory */
    assert_int_equal(LY_EINVAL, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_BIN "/utest_context"));
    CHECK_LOG_CTX("Given search directory \""TESTS_BIN "/utest_context\" is not a directory.", NULL, 0);
    /* not existing */
    assert_int_equal(LY_EINVAL, ly_ctx_set_searchdir(UTEST_LYCTX, "/nonexistingfile"));
    CHECK_LOG_CTX("Unable to use search directory \"/nonexistingfile\" (No such file or directory).", NULL, 0);

    /* ly_set_add() fails */
    /* no change */
    assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, NULL));

    /* correct path */
    assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_BIN "/utests"));
    assert_int_equal(1, UTEST_LYCTX->search_paths.count);
    assert_string_equal(TESTS_BIN "/utests", UTEST_LYCTX->search_paths.objs[0]);

    /* duplicated paths */
    assert_int_equal(LY_EEXIST, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_BIN "/utests"));
    assert_int_equal(1, UTEST_LYCTX->search_paths.count);
    assert_string_equal(TESTS_BIN "/utests", UTEST_LYCTX->search_paths.objs[0]);

    /* another paths - add 8 to fill the initial buffer of the searchpaths list */
    assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_BIN "/CMakeFiles"));
    assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_SRC "/../src"));
    assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_SRC "/../CMakeModules"));
    assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_SRC "/../doc"));
    assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_SRC));
    assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_BIN));
    assert_int_equal(7, UTEST_LYCTX->search_paths.count);

    /* get searchpaths */
    list = ly_ctx_get_searchdirs(UTEST_LYCTX);
    assert_non_null(list);
    assert_string_equal(TESTS_BIN "/utests", list[0]);
    assert_string_equal(TESTS_BIN "/CMakeFiles", list[1]);
    assert_string_equal(TESTS_SRC, list[5]);
    assert_string_equal(TESTS_BIN, list[6]);
    assert_null(list[7]);

    /* removing searchpaths */
    /* nonexisting */
    assert_int_equal(LY_EINVAL, ly_ctx_unset_searchdir(UTEST_LYCTX, "/nonexistingfile"));
    CHECK_LOG_CTX("Invalid argument value (ly_ctx_unset_searchdir()).", NULL, 0);
    /* first */
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_searchdir(UTEST_LYCTX, TESTS_BIN "/utests"));
    assert_string_not_equal(TESTS_BIN "/utests", list[0]);
    assert_int_equal(6, UTEST_LYCTX->search_paths.count);
    /* middle */
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_searchdir(UTEST_LYCTX, TESTS_SRC));
    assert_int_equal(5, UTEST_LYCTX->search_paths.count);
    /* last */
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_searchdir(UTEST_LYCTX, TESTS_BIN));
    assert_int_equal(4, UTEST_LYCTX->search_paths.count);
    /* all */
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_searchdir(UTEST_LYCTX, NULL));
    assert_int_equal(0, UTEST_LYCTX->search_paths.count);

    /* again - no change */
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_searchdir(UTEST_LYCTX, NULL));

    /* cleanup */
    ly_ctx_destroy(UTEST_LYCTX);

    /* test searchdir list in ly_ctx_new() */
    assert_int_equal(LY_EINVAL, ly_ctx_new("/nonexistingfile", 0, &UTEST_LYCTX));
    CHECK_LOG_LASTMSG("Unable to use search directory \"/nonexistingfile\" (No such file or directory).");
    assert_int_equal(LY_SUCCESS,
            ly_ctx_new(TESTS_SRC PATH_SEPARATOR TESTS_BIN PATH_SEPARATOR TESTS_BIN PATH_SEPARATOR TESTS_SRC,
            LY_CTX_DISABLE_SEARCHDIRS, &UTEST_LYCTX));
    assert_int_equal(2, UTEST_LYCTX->search_paths.count);
    assert_string_equal(TESTS_SRC, UTEST_LYCTX->search_paths.objs[0]);
    assert_string_equal(TESTS_BIN, UTEST_LYCTX->search_paths.objs[1]);
}

#endif

static void
test_options(void **state)
{
    /* use own context with extra flags */
    ly_ctx_destroy(UTEST_LYCTX);

    assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, 0xffff, &UTEST_LYCTX));

    /* invalid arguments */
    assert_int_equal(0, ly_ctx_get_options(NULL));
    CHECK_LOG_LASTMSG("Invalid argument ctx (ly_ctx_get_options()).");

    assert_int_equal(LY_EINVAL, ly_ctx_set_options(NULL, 0));
    CHECK_LOG_LASTMSG("Invalid argument ctx (ly_ctx_set_options()).");
    assert_int_equal(LY_EINVAL, ly_ctx_unset_options(NULL, 0));
    CHECK_LOG_LASTMSG("Invalid argument ctx (ly_ctx_unset_options()).");

    /* unset */
    /* LY_CTX_ALL_IMPLEMENTED */
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_ALL_IMPLEMENTED);
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_ALL_IMPLEMENTED));
    assert_int_equal(0, UTEST_LYCTX->flags & LY_CTX_ALL_IMPLEMENTED);

    /* LY_CTX_REF_IMPLEMENTED */
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_REF_IMPLEMENTED);
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_REF_IMPLEMENTED));
    assert_int_equal(0, UTEST_LYCTX->flags & LY_CTX_REF_IMPLEMENTED);

    /* LY_CTX_DISABLE_SEARCHDIRS */
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_DISABLE_SEARCHDIRS);
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_DISABLE_SEARCHDIRS));
    assert_int_equal(0, UTEST_LYCTX->flags & LY_CTX_DISABLE_SEARCHDIRS);

    /* LY_CTX_DISABLE_SEARCHDIR_CWD */
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_DISABLE_SEARCHDIR_CWD);
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_DISABLE_SEARCHDIR_CWD));
    assert_int_equal(0, UTEST_LYCTX->flags & LY_CTX_DISABLE_SEARCHDIR_CWD);

    /* LY_CTX_PREFER_SEARCHDIRS */
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_PREFER_SEARCHDIRS);
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_PREFER_SEARCHDIRS));
    assert_int_equal(0, UTEST_LYCTX->flags & LY_CTX_PREFER_SEARCHDIRS);

    /* LY_CTX_LEAFREF_EXTENDED */
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_LEAFREF_EXTENDED);
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_LEAFREF_EXTENDED));
    assert_int_equal(0, UTEST_LYCTX->flags & LY_CTX_LEAFREF_EXTENDED);

    /* LY_CTX_LEAFREF_LINKING */
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_LEAFREF_LINKING);
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_LEAFREF_LINKING));
    assert_int_equal(0, UTEST_LYCTX->flags & LY_CTX_LEAFREF_LINKING);

    /* LY_CTX_BUILTIN_PLUGINS_ONLY */
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_BUILTIN_PLUGINS_ONLY);
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_BUILTIN_PLUGINS_ONLY));
    assert_int_equal(0, UTEST_LYCTX->flags & LY_CTX_BUILTIN_PLUGINS_ONLY);

    assert_int_equal(UTEST_LYCTX->flags, ly_ctx_get_options(UTEST_LYCTX));

    /* set back */
    /* LY_CTX_ALL_IMPLEMENTED */
    assert_int_equal(LY_SUCCESS, ly_ctx_set_options(UTEST_LYCTX, LY_CTX_ALL_IMPLEMENTED));
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_ALL_IMPLEMENTED);

    /* LY_CTX_REF_IMPLEMENTED */
    assert_int_equal(LY_SUCCESS, ly_ctx_set_options(UTEST_LYCTX, LY_CTX_REF_IMPLEMENTED));
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_REF_IMPLEMENTED);

    /* LY_CTX_DISABLE_SEARCHDIRS */
    assert_int_equal(LY_SUCCESS, ly_ctx_set_options(UTEST_LYCTX, LY_CTX_DISABLE_SEARCHDIRS));
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_DISABLE_SEARCHDIRS);

    /* LY_CTX_DISABLE_SEARCHDIR_CWD */
    assert_int_equal(LY_SUCCESS, ly_ctx_set_options(UTEST_LYCTX, LY_CTX_DISABLE_SEARCHDIR_CWD));
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_DISABLE_SEARCHDIR_CWD);

    /* LY_CTX_PREFER_SEARCHDIRS */
    assert_int_equal(LY_SUCCESS, ly_ctx_set_options(UTEST_LYCTX, LY_CTX_PREFER_SEARCHDIRS));
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_PREFER_SEARCHDIRS);

    /* LY_CTX_LEAFREF_EXTENDED */
    assert_int_equal(LY_SUCCESS, ly_ctx_set_options(UTEST_LYCTX, LY_CTX_LEAFREF_EXTENDED));
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_LEAFREF_EXTENDED);

    /* LY_CTX_LEAFREF_LINKING */
    assert_int_equal(LY_SUCCESS, ly_ctx_set_options(UTEST_LYCTX, LY_CTX_LEAFREF_LINKING));
    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_LEAFREF_LINKING);

    /* LY_CTX_BUILTIN_PLUGINS_ONLY */
    assert_int_equal(LY_EINVAL, ly_ctx_set_options(UTEST_LYCTX, LY_CTX_BUILTIN_PLUGINS_ONLY));
    CHECK_LOG_CTX("Invalid argument option (LY_CTX_BUILTIN_PLUGINS_ONLY can be set only when creating a new context) (ly_ctx_set_options()).", NULL, 0);

    assert_int_equal(UTEST_LYCTX->flags, ly_ctx_get_options(UTEST_LYCTX));
}

static LY_ERR
test_imp_clb(const char *UNUSED(mod_name), const char *UNUSED(mod_rev), const char *UNUSED(submod_name),
        const char *UNUSED(sub_rev), void *user_data, LYS_INFORMAT *format,
        const char **module_data, void (**free_module_data)(void *model_data, void *user_data))
{
    *module_data = user_data;
    *format = LYS_IN_YANG;
    *free_module_data = NULL;
    return LY_SUCCESS;
}

static void
test_models(void **state)
{
    struct ly_in *in;
    const char *str;
    struct lys_module *mod1, *mod2;
    struct lys_glob_unres unres = {0};

    /* use own context with extra flags */
    ly_ctx_destroy(UTEST_LYCTX);

    /* invalid arguments */
    assert_int_equal(0, ly_ctx_get_change_count(NULL));
    CHECK_LOG_LASTMSG("Invalid argument ctx (ly_ctx_get_change_count()).");

    assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIRS, &UTEST_LYCTX));
    assert_int_equal(UTEST_LYCTX->change_count, ly_ctx_get_change_count(UTEST_LYCTX));

    assert_int_equal(LY_SUCCESS, ly_in_new_memory("module x {namespace urn:x;prefix x;}", &in));
    assert_int_equal(LY_EINVAL, lys_parse_in(UTEST_LYCTX, in, 4, NULL, NULL, &unres.creating, &mod1));
    lys_unres_glob_erase(&unres);
    ly_in_free(in, 0);
    CHECK_LOG_CTX("Invalid schema input format.", NULL, 0);

    /* import callback */
    ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, (void *)(str = "test"));
    assert_ptr_equal(test_imp_clb, UTEST_LYCTX->imp_clb);
    assert_ptr_equal(str, UTEST_LYCTX->imp_clb_data);
    assert_ptr_equal(test_imp_clb, ly_ctx_get_module_imp_clb(UTEST_LYCTX, (void **)&str));
    assert_string_equal("test", str);

    ly_ctx_set_module_imp_clb(UTEST_LYCTX, NULL, NULL);
    assert_null(UTEST_LYCTX->imp_clb);
    assert_null(UTEST_LYCTX->imp_clb_data);

    /* name collision of module and submodule */
    ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule y {belongs-to a {prefix a;} revision 2018-10-30;}");
    assert_int_equal(LY_SUCCESS, ly_in_new_memory("module y {namespace urn:y;prefix y;include y;}", &in));
    assert_int_equal(LY_EVALID, lys_parse_in(UTEST_LYCTX, in, LYS_IN_YANG, NULL, NULL, &unres.creating, &mod1));
    lys_unres_glob_erase(&unres);
    ly_in_free(in, 0);
    CHECK_LOG_CTX("Parsing module \"y\" failed.", NULL, 0);
    CHECK_LOG_CTX("Name collision between module and submodule of name \"y\".", NULL, 1);

    assert_int_equal(LY_SUCCESS, ly_in_new_memory("module a {namespace urn:a;prefix a;include y;revision 2018-10-30; }", &in));
    assert_int_equal(LY_SUCCESS, lys_parse_in(UTEST_LYCTX, in, LYS_IN_YANG, NULL, NULL, &unres.creating, &mod1));
    ly_in_free(in, 0);
    assert_int_equal(LY_SUCCESS, ly_in_new_memory("module y {namespace urn:y;prefix y;}", &in));
    assert_int_equal(LY_EVALID, lys_parse_in(UTEST_LYCTX, in, LYS_IN_YANG, NULL, NULL, &unres.creating, &mod1));
    lys_unres_glob_erase(&unres);
    ly_in_free(in, 0);
    CHECK_LOG_CTX("Parsing module \"y\" failed.", NULL, 0);
    CHECK_LOG_CTX("Name collision between module and submodule of name \"y\".", NULL, 1);

    ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule y {belongs-to b {prefix b;}}");
    assert_int_equal(LY_SUCCESS, ly_in_new_memory("module b {namespace urn:b;prefix b;include y;}", &in));
    assert_int_equal(LY_EVALID, lys_parse_in(UTEST_LYCTX, in, LYS_IN_YANG, NULL, NULL, &unres.creating, &mod1));
    lys_unres_glob_revert(UTEST_LYCTX, &unres);
    lys_unres_glob_erase(&unres);
    ly_in_free(in, 0);
    CHECK_LOG_CTX("Parsing module \"b\" failed.", NULL, 0);
    CHECK_LOG_CTX("Including \"y\" submodule into \"b\" failed.", NULL, 0);
    CHECK_LOG_CTX("Parsing submodule failed.", NULL, 0);
    CHECK_LOG_CTX("Name collision between submodules of name \"y\".", NULL, 1);

    /* selecting correct revision of the submodules */
    ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule y {belongs-to a {prefix a;} revision 2018-10-31;}");
    assert_int_equal(LY_SUCCESS, ly_in_new_memory("module a {namespace urn:a;prefix a;include y; revision 2018-10-31;}", &in));
    assert_int_equal(LY_SUCCESS, lys_parse_in(UTEST_LYCTX, in, LYS_IN_YANG, NULL, NULL, &unres.creating, &mod2));
    lys_unres_glob_erase(&unres);
    ly_in_free(in, 0);
    assert_string_equal("2018-10-31", mod2->parsed->includes[0].submodule->revs[0].date);

    /* reloading module in case only the compiled module resists in the context */
    assert_int_equal(LY_SUCCESS, ly_in_new_memory("module w {namespace urn:w;prefix w;revision 2018-10-24;}", &in));
    assert_int_equal(LY_SUCCESS, lys_parse(UTEST_LYCTX, in, LYS_IN_YANG, NULL, &mod1));
    ly_in_free(in, 0);
    assert_non_null(mod1->compiled);
    assert_non_null(mod1->parsed);

    assert_int_equal(LY_SUCCESS, ly_in_new_memory("module z {namespace urn:z;prefix z;import w {prefix w;revision-date 2018-10-24;}}", &in));
    ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "module w {namespace urn:w;prefix w;revision 2018-10-24;}");
    assert_int_equal(LY_SUCCESS, lys_parse(UTEST_LYCTX, in, LYS_IN_YANG, NULL, &mod2));
    ly_in_free(in, 0);
    assert_non_null(mod2);
    assert_non_null(mod1->parsed);
    assert_string_equal("w", mod1->name);
}

static void
test_imports(void **state)
{
    struct lys_module *mod1, *mod2, *mod3, *import;
    char *str;
    uint16_t ctx_options;

    /* use own context with extra flags */
    ly_ctx_destroy(UTEST_LYCTX);
    ctx_options = LY_CTX_DISABLE_SEARCHDIRS | LY_CTX_NO_YANGLIBRARY;

    /* Import callback provides newer revision of module 'a',
     * however the older revision is implemented soon and therefore it is preferred. */
    assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, ctx_options, &UTEST_LYCTX));
    ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "module a {namespace urn:a; prefix a; revision 2019-09-17;}");
    assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module a {namespace urn:a;prefix a;revision 2019-09-16;}",
            LYS_IN_YANG, &mod1));
    assert_true(LYS_MOD_LATEST_REV & mod1->latest_revision);
    assert_int_equal(1, mod1->implemented);
    assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module b {namespace urn:b;prefix b;import a {prefix a;}}",
            LYS_IN_YANG, &mod2));
    assert_ptr_equal(mod1, mod2->parsed->imports[0].module);
    assert_true((LYS_MOD_LATEST_REV | LYS_MOD_IMPORTED_REV) & mod1->latest_revision);
    assert_string_equal("2019-09-16", mod1->revision);
    assert_int_equal(1, mod1->implemented);
    assert_non_null(ly_ctx_get_module(UTEST_LYCTX, "a", "2019-09-16"));
    ly_ctx_destroy(UTEST_LYCTX);

    /* Import callback provides older revision of module 'a' and it is
     * imported by another module, so it is preferred even if newer
     * revision is implemented later. */
    assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, ctx_options, &UTEST_LYCTX));
    ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "module a {namespace urn:a; prefix a; revision 2019-09-16;}");
    assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module b {namespace urn:b;prefix b;import a {prefix a;}}",
            LYS_IN_YANG, &mod2));
    assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module a {namespace urn:a;prefix a;revision 2019-09-17;}",
            LYS_IN_YANG, &mod1));
    ly_log_level(LY_LLVRB);
    assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module c {namespace urn:c;prefix c;import a {prefix a;}}",
            LYS_IN_YANG, &mod3));
    CHECK_LOG_LASTMSG("Implemented module \"a@2019-09-17\" is not used for import, revision \"2019-09-16\" is imported instead.");
    ly_log_level(LY_LLWRN);
    assert_true(LYS_MOD_LATEST_SEARCHDIRS & mod1->latest_revision);
    assert_int_equal(1, mod1->implemented);
    import = mod2->parsed->imports[0].module;
    assert_true(LYS_MOD_IMPORTED_REV & import->latest_revision);
    assert_string_equal("2019-09-16", import->revision);
    assert_int_equal(0, import->implemented);
    import = mod3->parsed->imports[0].module;
    assert_string_equal("2019-09-16", import->revision);
    assert_non_null(ly_ctx_get_module(UTEST_LYCTX, "a", "2019-09-16"));
    assert_non_null(ly_ctx_get_module(UTEST_LYCTX, "a", "2019-09-17"));
    assert_string_equal("2019-09-17", ly_ctx_get_module_implemented(UTEST_LYCTX, "a")->revision);
    ly_ctx_destroy(UTEST_LYCTX);

    /* check of circular dependency */
    assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, ctx_options, &UTEST_LYCTX));
    str = "module a {namespace urn:a; prefix a;"
            "import b {prefix b;}"
            "}";
    ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, str);
    str = "module b { yang-version 1.1; namespace urn:b; prefix b;"
            "import a {prefix a;}"
            "}";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL));
    ly_err_clean(UTEST_LYCTX, NULL);
}

static void
test_get_models(void **state)
{
    struct lys_module *mod, *mod2;
    const char *str0 = "module a {namespace urn:a;prefix a;}";
    const char *str1 = "module a {namespace urn:a;prefix a;revision 2018-10-23;}";
    const char *str2 = "module a {namespace urn:a;prefix a;revision 2018-10-23;revision 2018-10-24;}";
    struct ly_in *in0, *in1, *in2;
    struct lys_glob_unres unres = {0};

    unsigned int index = 0;
    const char *names[] = {
        "ietf-yang-metadata", "yang", "ietf-inet-types", "ietf-yang-types", "ietf-yang-schema-mount",
        "ietf-yang-structure-ext", "ietf-datastores", "ietf-yang-library", "a", "a", "a"
    };

    assert_int_equal(LY_SUCCESS, ly_in_new_memory(str0, &in0));
    assert_int_equal(LY_SUCCESS, ly_in_new_memory(str1, &in1));
    assert_int_equal(LY_SUCCESS, ly_in_new_memory(str2, &in2));

    /* invalid arguments */
    assert_ptr_equal(NULL, ly_ctx_get_module(NULL, NULL, NULL));
    CHECK_LOG_LASTMSG("Invalid argument ctx (ly_ctx_get_module()).");
    assert_ptr_equal(NULL, ly_ctx_get_module(UTEST_LYCTX, NULL, NULL));
    CHECK_LOG_CTX("Invalid argument name (ly_ctx_get_module()).", NULL, 0);
    assert_ptr_equal(NULL, ly_ctx_get_module_ns(NULL, NULL, NULL));
    CHECK_LOG_LASTMSG("Invalid argument ctx (ly_ctx_get_module_ns()).");
    assert_ptr_equal(NULL, ly_ctx_get_module_ns(UTEST_LYCTX, NULL, NULL));
    CHECK_LOG_CTX("Invalid argument ns (ly_ctx_get_module_ns()).", NULL, 0);
    assert_null(ly_ctx_get_module(UTEST_LYCTX, "nonsence", NULL));

    /* internal modules */
    assert_null(ly_ctx_get_module_implemented(UTEST_LYCTX, "ietf-yang-types"));
    mod = ly_ctx_get_module_implemented(UTEST_LYCTX, "yang");
    assert_non_null(mod);
    assert_non_null(mod->parsed);
    assert_string_equal("yang", mod->name);
    mod2 = ly_ctx_get_module_implemented_ns(UTEST_LYCTX, mod->ns);
    assert_ptr_equal(mod, mod2);
    assert_non_null(ly_ctx_get_module(UTEST_LYCTX, "ietf-yang-metadata", "2016-08-05"));
    assert_non_null(ly_ctx_get_module(UTEST_LYCTX, "ietf-yang-types", "2013-07-15"));
    assert_non_null(ly_ctx_get_module(UTEST_LYCTX, "ietf-inet-types", "2013-07-15"));
    assert_non_null(ly_ctx_get_module_ns(UTEST_LYCTX, "urn:ietf:params:xml:ns:yang:ietf-datastores", "2018-02-14"));

    /* select module by revision */
    assert_int_equal(LY_SUCCESS, lys_parse(UTEST_LYCTX, in1, LYS_IN_YANG, NULL, &mod));
    /* invalid attempts - implementing module of the same name and inserting the same module */
    assert_int_equal(LY_SUCCESS, lys_parse_in(UTEST_LYCTX, in2, LYS_IN_YANG, NULL, NULL, &unres.creating, &mod2));
    assert_int_equal(LY_EDENIED, lys_implement(mod2, NULL, &unres));
    CHECK_LOG_CTX("Module \"a@2018-10-24\" is already implemented in revision \"2018-10-23\".", NULL, 0);
    lys_unres_glob_erase(&unres);
    ly_in_reset(in1);
    /* it is already there, fine */
    assert_int_equal(LY_SUCCESS, lys_parse_in(UTEST_LYCTX, in1, LYS_IN_YANG, NULL, NULL, &unres.creating, NULL));
    /* insert the second module only as imported, not implemented */
    lys_unres_glob_erase(&unres);
    ly_in_reset(in2);
    assert_int_equal(LY_SUCCESS, lys_parse_in(UTEST_LYCTX, in2, LYS_IN_YANG, NULL, NULL, &unres.creating, &mod2));
    lys_unres_glob_erase(&unres);
    assert_non_null(mod2);
    assert_ptr_not_equal(mod, mod2);
    mod = ly_ctx_get_module_latest(UTEST_LYCTX, "a");
    assert_ptr_equal(mod, mod2);
    mod2 = ly_ctx_get_module_latest_ns(UTEST_LYCTX, mod->ns);
    assert_ptr_equal(mod, mod2);
    /* work with module with no revision */
    assert_int_equal(LY_SUCCESS, lys_parse_in(UTEST_LYCTX, in0, LYS_IN_YANG, NULL, NULL, &unres.creating, &mod));
    lys_unres_glob_erase(&unres);
    assert_ptr_equal(mod, ly_ctx_get_module(UTEST_LYCTX, "a", NULL));
    assert_ptr_not_equal(mod, ly_ctx_get_module_latest(UTEST_LYCTX, "a"));

    str1 = "submodule b {belongs-to a {prefix a;}}";
    ly_in_free(in1, 0);
    assert_int_equal(LY_SUCCESS, ly_in_new_memory(str1, &in1));
    assert_int_equal(LY_EINVAL, lys_parse_in(UTEST_LYCTX, in1, LYS_IN_YANG, NULL, NULL, &unres.creating, &mod));
    CHECK_LOG_CTX("Input data contains submodule which cannot be parsed directly without its main module.", NULL, 0);
    lys_unres_glob_erase(&unres);

    while ((mod = (struct lys_module *)ly_ctx_get_module_iter(UTEST_LYCTX, &index))) {
        assert_string_equal(names[index - 1], mod->name);
    }
    assert_int_equal(11, index);

    /* cleanup */
    ly_in_free(in0, 0);
    ly_in_free(in1, 0);
    ly_in_free(in2, 0);
}

static void
test_ylmem(void **state)
{
#define DATA_YANG_LIBRARY_START "<yang-library xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">\n"\
    "  <module-set>\n"\
    "    <name>complete</name>\n"\
    "    <module>\n"\
    "      <name>yang</name>\n"\
    "      <revision>2022-06-16</revision>\n"\
    "      <namespace>urn:ietf:params:xml:ns:yang:1</namespace>\n"\
    "    </module>\n"\
    "    <module>\n"\
    "      <name>ietf-yang-library</name>\n"\
    "      <revision>2019-01-04</revision>\n"\
    "      <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-library</namespace>\n"\
    "    </module>\n"

#define DATA_YANG_BASE_IMPORTS     "    <import-only-module>\n"\
    "      <name>ietf-yang-metadata</name>\n"\
    "      <revision>2016-08-05</revision>\n"\
    "      <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-metadata</namespace>\n"\
    "    </import-only-module>\n"\
    "    <import-only-module>\n"\
    "      <name>ietf-inet-types</name>\n"\
    "      <revision>2013-07-15</revision>\n"\
    "      <namespace>urn:ietf:params:xml:ns:yang:ietf-inet-types</namespace>\n"\
    "    </import-only-module>\n"\
    "    <import-only-module>\n"\
    "      <name>ietf-yang-types</name>\n"\
    "      <revision>2013-07-15</revision>\n"\
    "      <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-types</namespace>\n"\
    "    </import-only-module>\n"\
    "    <import-only-module>\n"\
    "      <name>ietf-datastores</name>\n"\
    "      <revision>2018-02-14</revision>\n"\
    "      <namespace>urn:ietf:params:xml:ns:yang:ietf-datastores</namespace>\n"\
    "    </import-only-module>\n"

#define DATA_YANG_SCHEMA_MODULE_STATE         "  </module-set>\n"\
    "  <schema>\n"\
    "    <name>complete</name>\n"\
    "    <module-set>complete</module-set>\n"\
    "  </schema>\n"\
    "  <content-id>9</content-id>\n"\
    "</yang-library>\n"\
    "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">\n"\
    "  <module-set-id>12</module-set-id>\n"\
    "  <module>\n"\
    "    <name>ietf-yang-metadata</name>\n"\
    "    <revision>2016-08-05</revision>\n"\
    "    <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-metadata</namespace>\n"\
    "    <conformance-type>import</conformance-type>\n"\
    "  </module>\n"\
    "  <module>\n"\
    "    <name>yang</name>\n"\
    "    <revision>2022-06-16</revision>\n"\
    "    <namespace>urn:ietf:params:xml:ns:yang:1</namespace>\n"\
    "    <conformance-type>implement</conformance-type>\n"\
    "  </module>\n"\
    "  <module>\n"\
    "    <name>ietf-inet-types</name>\n"\
    "    <revision>2013-07-15</revision>\n"\
    "    <namespace>urn:ietf:params:xml:ns:yang:ietf-inet-types</namespace>\n"\
    "    <conformance-type>import</conformance-type>\n"\
    "  </module>\n"\
    "  <module>\n"\
    "    <name>ietf-yang-types</name>\n"\
    "    <revision>2013-07-15</revision>\n"\
    "    <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-types</namespace>\n"\
    "    <conformance-type>import</conformance-type>\n"\
    "  </module>\n"\
    "  <module>\n"\
    "    <name>ietf-yang-library</name>\n"\
    "    <revision>2019-01-04</revision>\n"\
    "    <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-library</namespace>\n"\
    "    <conformance-type>implement</conformance-type>\n"\
    "  </module>\n"\
    "  <module>\n"\
    "    <name>ietf-datastores</name>\n"\
    "    <revision>2018-02-14</revision>\n"\
    "    <namespace>urn:ietf:params:xml:ns:yang:ietf-datastores</namespace>\n"\
    "    <conformance-type>import</conformance-type>\n"\
    "  </module>\n"

    const char *yanglibrary_only =
            DATA_YANG_LIBRARY_START
            DATA_YANG_BASE_IMPORTS
            DATA_YANG_SCHEMA_MODULE_STATE
            "</modules-state>\n";

    const char *with_netconf =
            DATA_YANG_LIBRARY_START
            "    <module>\n"
            "      <name>ietf-netconf</name>\n"
            "      <revision>2011-06-01</revision>\n"
            "      <namespace>urn:ietf:params:xml:ns:netconf:base:1.0</namespace>\n"
            "    </module>\n"
            DATA_YANG_BASE_IMPORTS
            "    <import-only-module>\n"
            "      <name>ietf-netconf-acm</name>\n"
            "      <revision>2018-02-14</revision>\n"
            "      <namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-acm</namespace>\n"
            "    </import-only-module>\n"
            DATA_YANG_SCHEMA_MODULE_STATE
            "  <module>\n"
            "    <name>ietf-netconf</name>\n"
            "    <revision>2011-06-01</revision>\n"
            "    <namespace>urn:ietf:params:xml:ns:netconf:base:1.0</namespace>\n"
            "    <conformance-type>implement</conformance-type>\n"
            "  </module>\n"
            "  <module>\n"
            "    <name>ietf-netconf-acm</name>\n"
            "    <revision>2018-02-14</revision>\n"
            "    <namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-acm</namespace>\n"
            "    <conformance-type>import</conformance-type>\n"
            "  </module>\n"
            "</modules-state>";

    char *with_netconf_features = malloc(8096);

    strcpy(with_netconf_features,
            DATA_YANG_LIBRARY_START
            "    <module>\n"
            "      <name>ietf-netconf</name>\n"
            "      <revision>2011-06-01</revision>\n"
            "      <namespace>urn:ietf:params:xml:ns:netconf:base:1.0</namespace>\n"
            "      <feature>writable-running</feature>\n"
            "      <feature>candidate</feature>\n"
            "      <feature>confirmed-commit</feature>\n"
            "      <feature>rollback-on-error</feature>\n"
            "      <feature>validate</feature>\n"
            "      <feature>startup</feature>\n"
            "      <feature>url</feature>\n"
            "      <feature>xpath</feature>\n"
            "    </module>\n"
            "    <import-only-module>\n"
            "      <name>ietf-yang-metadata</name>\n"
            "      <revision>2016-08-05</revision>\n"
            "      <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-metadata</namespace>\n"
            "    </import-only-module>\n"
            "    <import-only-module>\n"
            "      <name>ietf-inet-types</name>\n"
            "      <revision>2013-07-15</revision>\n"
            "      <namespace>urn:ietf:params:xml:ns:yang:ietf-inet-types</namespace>\n"
            "    </import-only-module>\n"
            "    <import-only-module>\n"
            "      <name>ietf-yang-types</name>\n"
            "      <revision>2013-07-15</revision>\n"
            "      <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-types</namespace>\n"
            "    </import-only-module>\n"
            "    <import-only-module>\n"
            "      <name>ietf-datastores</name>\n"
            "      <revision>2018-02-14</revision>\n"
            "      <namespace>urn:ietf:params:xml:ns:yang:ietf-datastores</namespace>\n"
            "    </import-only-module>\n"
            "    <import-only-module>\n"
            "      <name>ietf-netconf-acm</name>\n"
            "      <revision>2018-02-14</revision>\n"
            "      <namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-acm</namespace>\n"
            "    </import-only-module>\n");
    strcpy(with_netconf_features + strlen(with_netconf_features),
            DATA_YANG_SCHEMA_MODULE_STATE
            "  <module>\n"
            "    <name>ietf-netconf</name>\n"
            "    <revision>2011-06-01</revision>\n"
            "    <namespace>urn:ietf:params:xml:ns:netconf:base:1.0</namespace>\n"
            "    <feature>writable-running</feature>\n"
            "    <feature>candidate</feature>\n"
            "    <feature>confirmed-commit</feature>\n"
            "    <feature>rollback-on-error</feature>\n"
            "    <feature>validate</feature>\n"
            "    <feature>startup</feature>\n"
            "    <feature>url</feature>\n"
            "    <feature>xpath</feature>\n"
            "    <conformance-type>implement</conformance-type>\n"
            "  </module>\n"
            "  <module>\n"
            "    <name>ietf-netconf-acm</name>\n"
            "    <revision>2018-02-14</revision>\n"
            "    <namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-acm</namespace>\n"
            "    <conformance-type>import</conformance-type>\n"
            "  </module>\n"
            "</modules-state>");

    const char *garbage_revision =
            "<yang-library xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">\n"
            "  <module-set>\n"
            "    <name>complete</name>\n"
            "    <module>\n"
            "      <name>yang</name>\n"
            "      <revision>2022-06-16</revision>\n"
            "      <namespace>urn:ietf:params:xml:ns:yang:1</namespace>\n"
            "    </module>\n"
            "    <module>\n"
            "      <name>ietf-yang-library</name>\n"
            "      <revision>2019-01-01</revision>\n"
            "      <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-library</namespace>\n"
            "    </module>\n"
            DATA_YANG_BASE_IMPORTS
            DATA_YANG_SCHEMA_MODULE_STATE
            "</modules-state>\n";

    const char *no_yanglibrary =
            "<yang-library xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">\n"
            "  <module-set>\n"
            "    <name>complete</name>\n"
            "    <module>\n"
            "      <name>yang</name>\n"
            "      <revision>2022-06-16</revision>\n"
            "      <namespace>urn:ietf:params:xml:ns:yang:1</namespace>\n"
            "    </module>\n"
            DATA_YANG_BASE_IMPORTS
            DATA_YANG_SCHEMA_MODULE_STATE
            "</modules-state>\n";

    (void) state;
    /* seperate context to avoid double free during teadown */
    struct ly_ctx *ctx_test = NULL;

    /* test invalid parameters */
    assert_int_equal(LY_EINVAL, ly_ctx_new_ylpath(NULL, NULL, LYD_XML, 0, &ctx_test));
    assert_int_equal(LY_EINVAL, ly_ctx_new_ylpath(NULL, TESTS_SRC, LYD_XML, 0, NULL));
    assert_int_equal(LY_ESYS, ly_ctx_new_ylpath(NULL, TESTS_SRC "garbage", LYD_XML, 0, &ctx_test));

    /* basic test with ietf-yang-library-only */
    assert_int_equal(LY_SUCCESS, ly_ctx_new_ylmem(TESTS_SRC "/modules/yang/", yanglibrary_only, LYD_XML, 0, &ctx_test));
    assert_non_null(ly_ctx_get_module(ctx_test, "ietf-yang-library", "2019-01-04"));
    assert_null(ly_ctx_get_module(ctx_test, "ietf-netconf", "2011-06-01"));
    ly_ctx_destroy(ctx_test);
    ctx_test = NULL;

    /* test loading module, should also import other module */
    assert_int_equal(LY_SUCCESS, ly_ctx_new_ylmem(TESTS_SRC "/modules/yang/", with_netconf, LYD_XML, 0, &ctx_test));
    assert_non_null(ly_ctx_get_module(ctx_test, "ietf-netconf", "2011-06-01"));
    assert_int_equal(1, ly_ctx_get_module(ctx_test, "ietf-netconf", "2011-06-01")->implemented);
    assert_non_null(ly_ctx_get_module(ctx_test, "ietf-netconf-acm", "2018-02-14"));
    assert_int_equal(0, ly_ctx_get_module(ctx_test, "ietf-netconf-acm", "2018-02-14")->implemented);
    assert_int_equal(LY_ENOT, lys_feature_value(ly_ctx_get_module(ctx_test, "ietf-netconf", "2011-06-01"), "url"));
    ly_ctx_destroy(ctx_test);
    ctx_test = NULL;

    /* test loading module with feature if they are present */
    assert_int_equal(LY_SUCCESS, ly_ctx_new_ylmem(TESTS_SRC "/modules/yang/", with_netconf_features, LYD_XML, 0, &ctx_test));
    assert_non_null(ly_ctx_get_module(ctx_test, "ietf-netconf", "2011-06-01"));
    assert_non_null(ly_ctx_get_module(ctx_test, "ietf-netconf-acm", "2018-02-14"));
    assert_int_equal(LY_SUCCESS, lys_feature_value(ly_ctx_get_module(ctx_test, "ietf-netconf", "2011-06-01"), "url"));
    ly_ctx_destroy(ctx_test);
    ctx_test = NULL;

    /* test with not matching revision */
    assert_int_equal(LY_EINVAL, ly_ctx_new_ylmem(TESTS_SRC "/modules/yang/", garbage_revision, LYD_XML, 0, &ctx_test));

    /* test data containing ietf-yang-library which conflicts with the option */
    assert_int_equal(LY_EINVAL, ly_ctx_new_ylmem(TESTS_SRC "/modules/yang/", with_netconf_features, LYD_XML, LY_CTX_NO_YANGLIBRARY, &ctx_test));

    /* test creating without ietf-yang-library */
    assert_int_equal(LY_SUCCESS, ly_ctx_new_ylmem(TESTS_SRC "/modules/yang/", no_yanglibrary, LYD_XML, LY_CTX_NO_YANGLIBRARY, &ctx_test));
    assert_int_equal(NULL, ly_ctx_get_module(ctx_test, "ietf-yang-library", "2019-01-04"));
    ly_ctx_destroy(ctx_test);
    free(with_netconf_features);
}

static LY_ERR
check_node_priv_parsed_is_set(struct lysc_node *node, void *data, ly_bool *UNUSED(dfs_continue))
{
    const struct lysp_node *pnode;
    const char ***iter;

    pnode = (const struct lysp_node *)node->priv;
    CHECK_POINTER(pnode, 1);
    iter = (const char ***)data;
    CHECK_POINTER(**iter, 1);
    CHECK_STRING(pnode->name, **iter);
    (*iter)++;

    return LY_SUCCESS;
}

static LY_ERR
check_node_priv_parsed_not_set(struct lysc_node *node, void *UNUSED(data), ly_bool *UNUSED(dfs_continue))
{
    CHECK_POINTER(node->priv, 0);
    return LY_SUCCESS;
}

static void
check_ext_instance_priv_parsed_is_set(struct lysc_ext_instance *ext)
{
    LY_ARRAY_COUNT_TYPE u, v;
    struct lysc_ext_substmt *substmts;
    struct lysc_node *cnode;
    const char **iter;
    const char *check[] = {
        "tmp_cont", "lf", NULL
    };

    LY_ARRAY_FOR(ext, u) {
        substmts = ext[u].substmts;
        LY_ARRAY_FOR(substmts, v) {
            if (substmts && substmts[v].storage_p && (substmts[v].stmt & LY_STMT_DATA_NODE_MASK)) {
                cnode = *(struct lysc_node **)substmts[v].storage_p;
                iter = check;
                assert_int_equal(LY_SUCCESS, lysc_tree_dfs_full(cnode, check_node_priv_parsed_is_set, &iter));
            }
        }
    }
}

static void
check_ext_instance_priv_parsed_not_set(struct lysc_ext_instance *ext)
{
    LY_ARRAY_COUNT_TYPE u, v;
    struct lysc_ext_substmt *substmts;
    struct lysc_node *cnode;

    LY_ARRAY_FOR(ext, u) {
        substmts = ext[u].substmts;
        LY_ARRAY_FOR(substmts, v) {
            if (substmts && substmts[v].storage_p && (substmts[v].stmt & LY_STMT_DATA_NODE_MASK)) {
                cnode = *(struct lysc_node **)substmts[v].storage_p;
                if (cnode) {
                    CHECK_POINTER((struct lysp_node *)cnode->priv, 0);
                }
            }
        }
    }
}

/**
 * @brief Testing of LY_CTX_SET_PRIV_PARSED.
 */
static void
test_set_priv_parsed(void **state)
{
    struct lys_module *mod;
    const char *schema_a;
    const char **iter;
    const char *check[] = {
        "cont", "contnotif", "contx", "grpleaf", "augleaf", "l1",
        "l1a", "l1b", "l1c", "foo1", "ll", "any", "l2",
        "l2c", "l2cx", "ch", "cas", "casx", "oper",
        "input", "inparam", "output", "outparam", "n1", NULL
    };

    /* each node must have a unique name. */
    schema_a = "module a {\n"
            "  namespace urn:tests:a;\n"
            "  prefix a;yang-version 1.1;\n"
            "\n"
            "  import ietf-restconf {\n"
            "    prefix rc;\n"
            "    revision-date 2017-01-26;\n"
            "  }\n"
            "\n"
            "  rc:yang-data \"tmp\" {\n"
            "    container tmp_cont {\n"
            "      leaf lf {\n"
            "        type string;\n"
            "      }\n"
            "    }\n"
            "  }\n"
            "  container cont {\n"
            "    notification contnotif;\n"
            "    leaf-list contx {\n"
            "      type string;\n"
            "    }\n"
            "    uses grp;\n"
            "  }\n"
            "  list l1 {\n"
            "    key \"l1a l1b\";\n"
            "    leaf l1a {\n"
            "      type string;\n"
            "    }\n"
            "    leaf l1b {\n"
            "      type string;\n"
            "    }\n"
            "    leaf l1c {\n"
            "      type string;\n"
            "    }\n"
            "  }\n"
            "  feature f1;\n"
            "  feature f2;\n"
            "  leaf foo1 {\n"
            "    type uint16;\n"
            "    if-feature f1;\n"
            "  }\n"
            "  leaf foo2 {\n"
            "    type uint16;\n"
            "  }\n"
            "  leaf foo3 {\n"
            "    type uint16;\n"
            "    if-feature f2;\n"
            "  }\n"
            "  leaf-list ll {\n"
            "    type string;\n"
            "  }\n"
            "  anydata any {\n"
            "    config false;\n"
            "  }\n"
            "  list l2 {\n"
            "    config false;\n"
            "    container l2c {\n"
            "      leaf l2cx {\n"
            "        type string;\n"
            "      }\n"
            "    }\n"
            "  }\n"
            "  choice ch {\n"
            "    case cas {\n"
            "      leaf casx {\n"
            "        type string;\n"
            "      }\n"
            "    }\n"
            "  }\n"
            "  rpc oper {\n"
            "    input {\n"
            "      leaf inparam {\n"
            "        type string;\n"
            "      }\n"
            "    }\n"
            "    output {\n"
            "      leaf outparam {\n"
            "        type int8;\n"
            "      }\n"
            "    }\n"
            "  }\n"
            "  notification n1;\n"
            "  grouping grp {\n"
            "    leaf grpleaf {\n"
            "      type uint16;\n"
            "    }\n"
            "  }\n"
            "  augment /cont {\n"
            "    leaf augleaf {\n"
            "      type uint16;\n"
            "    }\n"
            "  }\n"
            "  deviation /a:foo2 {\n"
            "    deviate not-supported;\n"
            "  }\n"
            "}\n";

    /* use own context with extra flags */
    ly_ctx_destroy(UTEST_LYCTX);
    const char *feats[] = {"f1", NULL};

    assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, LY_CTX_SET_PRIV_PARSED, &UTEST_LYCTX));
    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));
    UTEST_ADD_MODULE(schema_a, LYS_IN_YANG, feats, NULL);

    print_message("[          ] create context\n");
    mod = ly_ctx_get_module(UTEST_LYCTX, "a", NULL);
    iter = check;
    assert_int_equal(LY_SUCCESS, lysc_module_dfs_full(mod, check_node_priv_parsed_is_set, &iter));
    check_ext_instance_priv_parsed_is_set(mod->compiled->exts);

    print_message("[          ] unset option\n");
    assert_int_equal(LY_SUCCESS, ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_SET_PRIV_PARSED));
    mod = ly_ctx_get_module(UTEST_LYCTX, "a", NULL);
    iter = check;
    assert_int_equal(LY_SUCCESS, lysc_module_dfs_full(mod, check_node_priv_parsed_not_set, &iter));
    check_ext_instance_priv_parsed_not_set(mod->compiled->exts);

    print_message("[          ] set option\n");
    assert_int_equal(LY_SUCCESS, ly_ctx_set_options(UTEST_LYCTX, LY_CTX_SET_PRIV_PARSED));
    mod = ly_ctx_get_module(UTEST_LYCTX, "a", NULL);
    iter = check;
    assert_int_equal(LY_SUCCESS, lysc_module_dfs_full(mod, check_node_priv_parsed_is_set, &iter));
    check_ext_instance_priv_parsed_is_set(mod->compiled->exts);
}

static void
test_explicit_compile(void **state)
{
    uint32_t i;
    struct lys_module *mod;
    const char *schema_a = "module a {\n"
            "  namespace urn:tests:a;\n"
            "  prefix a;yang-version 1.1;\n"
            "  feature f1;\n"
            "  feature f2;\n"
            "  leaf foo1 {\n"
            "    type uint16;\n"
            "    if-feature f1;\n"
            "  }\n"
            "  leaf foo2 {\n"
            "    type uint16;\n"
            "  }\n"
            "  container cont {\n"
            "    leaf foo3 {\n"
            "      type string;\n"
            "    }\n"
            "  }\n"
            "}\n";
    const char *schema_b = "module b {\n"
            "  namespace urn:tests:b;\n"
            "  prefix b;yang-version 1.1;\n"
            "  import a {\n"
            "    prefix a;\n"
            "  }\n"
            "  augment /a:cont {\n"
            "    leaf augleaf {\n"
            "      type uint16;\n"
            "    }\n"
            "  }\n"
            "}\n";
    const char *schema_c = "module c {\n"
            "  namespace urn:tests:c;\n"
            "  prefix c;yang-version 1.1;\n"
            "  import a {\n"
            "    prefix a;\n"
            "  }\n"
            "  deviation /a:foo2 {\n"
            "    deviate not-supported;\n"
            "  }\n"
            "}\n";

    /* use own context with extra flags */
    ly_ctx_destroy(UTEST_LYCTX);
    const char *feats[] = {"f1", NULL};

    assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, LY_CTX_EXPLICIT_COMPILE, &UTEST_LYCTX));
    UTEST_ADD_MODULE(schema_a, LYS_IN_YANG, NULL, &mod);
    UTEST_ADD_MODULE(schema_b, LYS_IN_YANG, NULL, NULL);
    UTEST_ADD_MODULE(schema_c, LYS_IN_YANG, NULL, NULL);
    assert_int_equal(LY_SUCCESS, lys_set_implemented((struct lys_module *)mod, feats));

    /* none of the modules should be compiled */
    i = 0;
    while ((mod = ly_ctx_get_module_iter(UTEST_LYCTX, &i))) {
        assert_null(mod->compiled);
    }

    assert_int_equal(LY_SUCCESS, ly_ctx_compile(UTEST_LYCTX));

    /* check internal modules */
    mod = ly_ctx_get_module_implemented(UTEST_LYCTX, "yang");
    assert_non_null(mod);
    mod = ly_ctx_get_module_implemented(UTEST_LYCTX, "ietf-datastores");
    assert_non_null(mod);
    mod = ly_ctx_get_module_implemented(UTEST_LYCTX, "ietf-yang-library");
    assert_non_null(mod);

    /* check test modules */
    mod = ly_ctx_get_module_implemented(UTEST_LYCTX, "a");
    assert_non_null(mod);
    mod = ly_ctx_get_module_implemented(UTEST_LYCTX, "b");
    assert_non_null(mod);
    mod = ly_ctx_get_module_implemented(UTEST_LYCTX, "c");
    assert_non_null(mod);
}

int
main(void)
{
    const struct CMUnitTest tests[] = {
        UTEST(test_searchdirs),
        UTEST(test_options),
        UTEST(test_models),
        UTEST(test_imports),
        UTEST(test_get_models),
        UTEST(test_ylmem),
        UTEST(test_set_priv_parsed),
        UTEST(test_explicit_compile),
    };

    return cmocka_run_group_tests(tests, NULL, NULL);
}
