context BUGFIX searching for the latest revision
when the latest revision is already in the context and searching via
import callback and searchapths provides an older revisions, the newest
revision from the context must be used. On the other hand, when some
revision is present in the context (and callback/searchpaths were not
used to get it), the callback/searchpaths should be tried to get a newer
revision.
diff --git a/src/tree_schema.c b/src/tree_schema.c
index c034581..240fa6d 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -641,10 +641,6 @@
/* make sure that the newest revision is at position 0 */
lysp_sort_revisions(submod->revs);
- if (custom_check) {
- LY_CHECK_GOTO(custom_check((*context).ctx, NULL, submod, check_data), error);
- }
-
/* decide the latest revision */
latest_sp = ly_ctx_get_submodule((*context).ctx, submod->belongsto, submod->name, NULL);
if (latest_sp) {
@@ -652,18 +648,28 @@
if (!latest_sp->revs) {
/* latest has no revision, so mod is anyway newer */
submod->latest_revision = latest_sp->latest_revision;
- latest_sp->latest_revision = 0;
+ /* the latest_sp is zeroed later when the new module is being inserted into the context */
+ } else if (strcmp(submod->revs[0].date, latest_sp->revs[0].date) > 0) {
+ submod->latest_revision = latest_sp->latest_revision;
+ /* the latest_sp is zeroed later when the new module is being inserted into the context */
} else {
- if (strcmp(submod->revs[0].date, latest_sp->revs[0].date) > 0) {
- submod->latest_revision = latest_sp->latest_revision;
- latest_sp->latest_revision = 0;
- }
+ latest_sp = NULL;
}
+ } else {
+ latest_sp = NULL;
}
} else {
submod->latest_revision = 1;
}
+ if (custom_check) {
+ LY_CHECK_GOTO(custom_check((*context).ctx, NULL, submod, check_data), error);
+ }
+
+ if (latest_sp) {
+ latest_sp->latest_revision = 0;
+ }
+
/* remap possibly changed and reallocated typedefs and groupings list back to the main context */
memcpy(&main_ctx->tpdfs_nodes, &(*context).tpdfs_nodes, sizeof main_ctx->tpdfs_nodes);
memcpy(&main_ctx->grps_nodes, &(*context).grps_nodes, sizeof main_ctx->grps_nodes);
@@ -724,6 +730,27 @@
mod->revision = lydict_insert(ctx, mod->parsed->revs[0].date, 0);
}
+ /* decide the latest revision */
+ latest = (struct lys_module*)ly_ctx_get_module_latest(ctx, mod->name);
+ if (latest) {
+ if (mod->revision) {
+ if (!latest->revision) {
+ /* latest has no revision, so mod is anyway newer */
+ mod->latest_revision = latest->latest_revision;
+ /* the latest is zeroed later when the new module is being inserted into the context */
+ } else if (strcmp(mod->revision, latest->revision) > 0) {
+ mod->latest_revision = latest->latest_revision;
+ /* the latest is zeroed later when the new module is being inserted into the context */
+ } else {
+ latest = NULL;
+ }
+ } else {
+ latest = NULL;
+ }
+ } else {
+ mod->latest_revision = 1;
+ }
+
if (custom_check) {
LY_CHECK_GOTO(custom_check(ctx, mod->parsed, NULL, check_data), error);
}
@@ -781,21 +808,8 @@
LY_CHECK_GOTO(lys_extension_precompile(NULL, ctx, mod, mod->parsed->extensions, &mod->off_extensions), error);
}
- /* decide the latest revision */
- latest = (struct lys_module*)ly_ctx_get_module_latest(ctx, mod->name);
if (latest) {
- if (mod->revision) {
- if (!latest->revision) {
- /* latest has no revision, so mod is anyway newer */
- mod->latest_revision = latest->latest_revision;
- latest->latest_revision = 0;
- } else if (strcmp(mod->revision, latest->revision) > 0) {
- mod->latest_revision = latest->latest_revision;
- latest->latest_revision = 0;
- }
- }
- } else {
- mod->latest_revision = 1;
+ latest->latest_revision = 0;
}
/* add into context */
diff --git a/src/tree_schema_helpers.c b/src/tree_schema_helpers.c
index c6c4511..9259b6f 100644
--- a/src/tree_schema_helpers.c
+++ b/src/tree_schema_helpers.c
@@ -678,11 +678,13 @@
{
struct lysp_load_module_check_data *info = data;
const char *filename, *dot, *rev, *name;
+ uint8_t latest_revision;
size_t len;
struct lysp_revision *revs;
name = mod ? mod->mod->name : submod->name;
revs = mod ? mod->revs : submod->revs;
+ latest_revision = mod ? mod->mod->latest_revision : submod->latest_revision;
if (info->name) {
/* check name of the parsed model */
@@ -698,6 +700,9 @@
revs ? revs[0].date : "none", info->revision);
return LY_EINVAL;
}
+ } else if (!latest_revision) {
+ /* do not log, we just need to drop the schema and use the latest revision from the context */
+ return LY_EEXIST;
}
if (submod) {
assert(info->submoduleof);
@@ -804,7 +809,7 @@
LYS_INFORMAT format = LYS_IN_UNKNOWN;
void (*module_data_free)(void *module_data, void *user_data) = NULL;
struct lysp_load_module_check_data check_data = {0};
- struct lys_module *m;
+ struct lys_module *m = NULL;
assert(mod);
@@ -824,6 +829,11 @@
/* get the requested module of the latest revision in the context */
latest_in_the_context:
*mod = (struct lys_module*)ly_ctx_get_module_latest(ctx, name);
+ if (*mod && (*mod)->latest_revision == 1) {
+ /* let us now search with callback and searchpaths to check if there is newer revision outside the context */
+ m = *mod;
+ *mod = NULL;
+ }
}
}
@@ -871,9 +881,12 @@
}
}
- if ((*mod) && !revision && ((*mod)->latest_revision == 1)) {
- /* update the latest_revision flag - here we have selected the latest available schema,
- * consider that even the callback provides correct latest revision */
+ /* update the latest_revision flag - here we have selected the latest available schema,
+ * consider that even the callback provides correct latest revision */
+ if (!(*mod) && m) {
+ m->latest_revision = 2;
+ *mod = m;
+ } else if ((*mod) && !revision && ((*mod)->latest_revision == 1)) {
(*mod)->latest_revision = 2;
}
} else {
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index 75e55fd..de02622 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -397,7 +397,7 @@
* @param[in] mod Schema module where the prefix was used.
* @param[in] prefix Prefix used to reference a module.
* @param[in] len Length of the prefix since it is not necessary NULL-terminated.
- * @return Pointer to the module or NULL if the module is not found.
+ * @return Pointer to the module or NULL if the module is not found or it is not compiled.
*/
struct lysc_module *lysc_module_find_prefix(const struct lysc_module *mod, const char *prefix, size_t len);
diff --git a/tests/src/test_context.c b/tests/src/test_context.c
index d6d2334..a70a01a 100644
--- a/tests/src/test_context.c
+++ b/tests/src/test_context.c
@@ -166,7 +166,7 @@
/* test searchdir list in ly_ctx_new() */
assert_int_equal(LY_EINVAL, ly_ctx_new("/nonexistingfile", 0, &ctx));
logbuf_assert("Unable to use search directory \"/nonexistingfile\" (No such file or directory).");
- assert_int_equal(LY_SUCCESS, ly_ctx_new(TESTS_SRC":/home:/home:"TESTS_SRC, 0, &ctx));
+ assert_int_equal(LY_SUCCESS, ly_ctx_new(TESTS_SRC":/home:/home:"TESTS_SRC, LY_CTX_DISABLE_SEARCHDIRS, &ctx));
assert_int_equal(2, ctx->search_paths.count);
assert_string_equal(TESTS_SRC, ctx->search_paths.objs[0]);
assert_string_equal("/home", ctx->search_paths.objs[1]);
@@ -345,6 +345,51 @@
}
static void
+test_imports(void **state)
+{
+ *state = test_imports;
+
+ struct ly_ctx *ctx;
+ struct lys_module *mod1, *mod2, *import;
+
+ /* import callback provides newer revision of module 'a' than present in context, so when importing 'a', the newer revision
+ * from the callback should be loaded into the context and used as an import */
+ assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIRS, &ctx));
+ ly_ctx_set_module_imp_clb(ctx, test_imp_clb, "module a {namespace urn:a; prefix a; revision 2019-09-17;}");
+ assert_non_null(mod1 = lys_parse_mem(ctx, "module a {namespace urn:a;prefix a;revision 2019-09-16;}", LYS_IN_YANG));
+ assert_int_equal(1, mod1->latest_revision);
+ assert_int_equal(1, mod1->implemented);
+ assert_non_null(mod2 = lys_parse_mem(ctx, "module b {namespace urn:b;prefix b;import a {prefix a;}}", LYS_IN_YANG));
+ import = mod2->compiled->imports[0].module;
+ assert_int_equal(2, import->latest_revision);
+ assert_int_equal(0, mod1->latest_revision);
+ assert_ptr_not_equal(mod1, import);
+ assert_string_equal("2019-09-17", import->revision);
+ assert_int_equal(0, import->implemented);
+ assert_non_null(ly_ctx_get_module(ctx, "a", "2019-09-16"));
+ ly_ctx_destroy(ctx, NULL);
+
+ /* import callback provides older revision of module 'a' than present in context, so when importing a, the newer revision
+ * already present in the context should be selected and the callback's revision should not be loaded into the context */
+ assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIRS, &ctx));
+ ly_ctx_set_module_imp_clb(ctx, test_imp_clb, "module a {namespace urn:a; prefix a; revision 2019-09-17;}");
+ assert_non_null(mod1 = lys_parse_mem(ctx, "module a {namespace urn:a;prefix a;revision 2019-09-18;}", LYS_IN_YANG));
+ assert_int_equal(1, mod1->latest_revision);
+ assert_int_equal(1, mod1->implemented);
+ assert_non_null(mod2 = lys_parse_mem(ctx, "module b {namespace urn:b;prefix b;import a {prefix a;}}", LYS_IN_YANG));
+ import = mod2->compiled->imports[0].module;
+ assert_ptr_equal(mod1, import);
+ assert_int_equal(2, import->latest_revision);
+ assert_int_equal(1, import->implemented);
+ assert_string_equal("2019-09-18", import->revision);
+ assert_null(ly_ctx_get_module(ctx, "a", "2019-09-17"));
+ ly_ctx_destroy(ctx, NULL);
+
+ /* cleanup */
+ *state = NULL;
+}
+
+static void
test_get_models(void **state)
{
*state = test_get_models;
@@ -426,6 +471,7 @@
cmocka_unit_test_setup_teardown(test_searchdirs, logger_setup, logger_teardown),
cmocka_unit_test_setup_teardown(test_options, logger_setup, logger_teardown),
cmocka_unit_test_setup_teardown(test_models, logger_setup, logger_teardown),
+ cmocka_unit_test_setup_teardown(test_imports, logger_setup, logger_teardown),
cmocka_unit_test_setup_teardown(test_get_models, logger_setup, logger_teardown),
};