From 806b2ff1f29deb390ed45d423155c39228e94801 Mon Sep 17 00:00:00 2001 From: Balint Uveges Date: Mon, 24 Aug 2026 15:18:32 +0200 Subject: [PATCH] printer sid BUGFIX include nodes augmented into other modules The SID generator collected data-namespace items only from the processed module's own compiled tree (lysc_module_dfs_full(module, ...)). Nodes that the module contributes to other modules by augmentation live in the target modules' trees, so they were never collected and got no SID. For models that place feature data via augments (e.g. augmenting a common root module), the generated .sid file ended up with just the module item and no data items. Collect all nodes defined by the processed module wherever they are grafted: after walking the module's own tree, also walk every context module whose augmented_by references the processed module. collect_data_cb now attributes each node to its defining module (node->module), so a foreign module's own nodes are skipped while traversing its tree, and a module's .sid no longer wrongly includes nodes augmented into it by others. Add test_augment covering both directions (the augmenting module's .sid contains the augmented-in node; the base module's .sid does not). Co-authored-by: Cursor --- src/printer_sid.c | 38 ++++++++++++++ tests/utests/schema/test_printer_sid.c | 73 ++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/src/printer_sid.c b/src/printer_sid.c index 384c3ee10..a8eff1db3 100644 --- a/src/printer_sid.c +++ b/src/printer_sid.c @@ -50,6 +50,7 @@ struct sid_collect_data { uint64_t count; /**< Number of items collected so far; exceeding @p max, surplus items are only counted. */ uint64_t max; /**< Maximum number of items (capacity of the array). */ struct ly_ctx *ctx; /**< libyang context used for error logging. */ + const struct lys_module *module; /**< Module whose nodes are collected; used to attribute augmented nodes to their defining module. */ }; /** @@ -178,6 +179,13 @@ collect_data_cb(struct lysc_node *node, void *data, ly_bool *UNUSED(dfs_continue return LY_SUCCESS; /* choice/case get no item, but their subtree is still traversed */ } + /* collect only nodes defined by the module being processed; when another + * module's tree is traversed this keeps exactly the nodes augmented in by + * the processed module and skips the target module's own nodes */ + if (node->module != collect_data->module) { + return LY_SUCCESS; + } + path = sid_node_path(node); LY_CHECK_ERR_RET(!path, LOGMEM(collect_data->ctx), LY_EMEM); @@ -343,12 +351,42 @@ sid_collect_items(struct sid_collect_data *callback_data, const struct lys_modul LY_CHECK_RET((rc = sid_item_add(callback_data, "feature", feature->name)), rc); } + /* collect_data_cb attributes nodes to their defining module, so foreign + nodes are skipped whenever another module's tree is traversed below */ + callback_data->module = module; + /* data namespace: walk the entire compiled schema tree depth-first. lysc_module_dfs_full traverses all nodes including RPCs, actions, notifications, input, output, choice and case nodes. sid_node_path() builds the RFC 9595 schema-node-path identifiers (choice/case names omitted). */ LY_CHECK_RET((rc = lysc_module_dfs_full(module, collect_data_cb, callback_data)), rc); + /* data namespace: nodes that this module augments into other modules live in + the target modules' trees (RFC 9595 still assigns them to this module's .sid). + Traverse every context module that lists this module in its augmented_by and + collect the nodes defined here (collect_data_cb filters by defining module). */ + { + const struct lys_module *aug_target; + uint32_t mod_idx = 0; + + while ((aug_target = ly_ctx_get_module_iter(module->ctx, &mod_idx))) { + ly_bool augmented = 0; + + if ((aug_target == module) || !aug_target->compiled) { + continue; + } + LY_ARRAY_FOR(aug_target->augmented_by, i) { + if (aug_target->augmented_by[i] == module) { + augmented = 1; + break; + } + } + if (augmented) { + LY_CHECK_RET((rc = lysc_module_dfs_full(aug_target, collect_data_cb, callback_data)), rc); + } + } + } + /* data namespace: also traverse the data trees of compiled top-level extension instances that define their own data tree outside the standard module trees (rc:yang-data, sx:structure); the top-level data node is obtained from the diff --git a/tests/utests/schema/test_printer_sid.c b/tests/utests/schema/test_printer_sid.c index f380644bd..729991b27 100644 --- a/tests/utests/schema/test_printer_sid.c +++ b/tests/utests/schema/test_printer_sid.c @@ -453,6 +453,78 @@ test_exts(void **state) lyd_free_all(sid_file); } +/** + * @brief Test case: augment coverage. A module's .sid file must include the data + * nodes it augments into another module (RFC 9595), and a module's own .sid must + * not include nodes augmented into it by other modules. + */ +static void +test_augment(void **state) +{ + struct lys_module *base, *aug; + struct lyd_node *sid_file = NULL; + static const char *mod_base = + "module b1 {\n" + " yang-version 1.1;\n" + " namespace \"urn:b1\";\n" + " prefix b1;\n" + " revision 2024-01-01;\n" + " container cont {\n" + " leaf l { type string; }\n" + " }\n" + "}\n"; + static const char *mod_aug = + "module a1 {\n" + " yang-version 1.1;\n" + " namespace \"urn:a1\";\n" + " prefix a1;\n" + " import b1 { prefix b1; }\n" + " revision 2024-01-01;\n" + " augment \"/b1:cont\" {\n" + " leaf x { type string; }\n" + " }\n" + "}\n"; + + UTEST_ADD_MODULE(mod_base, LYS_IN_YANG, NULL, &base); + UTEST_ADD_MODULE(mod_aug, LYS_IN_YANG, NULL, &aug); + assert_non_null(ly_ctx_load_module(_UC->ctx, "ietf-sid-file", NULL, NULL)); + + /* the augmenting module's .sid contains the node it augments in: /b1:cont/a1:x */ + assert_int_equal(LY_SUCCESS, lys_sid_gen(aug, 100, 2, LYS_SID_FILE_UNPUBLISHED, "d", &sid_file)); + assert_non_null(sid_file); + check_json_tree(sid_file, + "{\"ietf-sid-file:sid-file\":{" + "\"module-name\":\"a1\"," + "\"module-revision\":\"2024-01-01\"," + "\"sid-file-status\":\"unpublished\"," + "\"description\":\"d\"," + "\"dependency-revision\":[{\"module-name\":\"b1\",\"module-revision\":\"2024-01-01\"}]," + "\"assignment-range\":[{\"entry-point\":\"100\",\"size\":\"2\"}]," + "\"item\":[" + "{\"namespace\":\"module\",\"identifier\":\"a1\",\"status\":\"unstable\",\"sid\":\"100\"}," + "{\"namespace\":\"data\",\"identifier\":\"/b1:cont/a1:x\",\"status\":\"unstable\",\"sid\":\"101\"}" + "]}}"); + lyd_free_all(sid_file); + sid_file = NULL; + + /* the base module's own .sid must not contain the node augmented in by a1 */ + assert_int_equal(LY_SUCCESS, lys_sid_gen(base, 100, 3, LYS_SID_FILE_UNPUBLISHED, "d", &sid_file)); + assert_non_null(sid_file); + check_json_tree(sid_file, + "{\"ietf-sid-file:sid-file\":{" + "\"module-name\":\"b1\"," + "\"module-revision\":\"2024-01-01\"," + "\"sid-file-status\":\"unpublished\"," + "\"description\":\"d\"," + "\"assignment-range\":[{\"entry-point\":\"100\",\"size\":\"3\"}]," + "\"item\":[" + "{\"namespace\":\"module\",\"identifier\":\"b1\",\"status\":\"unstable\",\"sid\":\"100\"}," + "{\"namespace\":\"data\",\"identifier\":\"/b1:cont\",\"status\":\"unstable\",\"sid\":\"101\"}," + "{\"namespace\":\"data\",\"identifier\":\"/b1:cont/l\",\"status\":\"unstable\",\"sid\":\"102\"}" + "]}}"); + lyd_free_all(sid_file); +} + int main(void) { @@ -461,6 +533,7 @@ main(void) UTEST(test_gen), UTEST(test_flow), UTEST(test_exts), + UTEST(test_augment), }; return cmocka_run_group_tests(tests, NULL, NULL);