/* * Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/ * * Written by Václav Kubernát * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include "example_schema.hpp" #include "test_vars.hpp" #include "utils/filesystem_path.hpp" namespace { using ctx_deleter_t = decltype([](auto ctx) constexpr { ly_ctx_destroy(ctx); }); using node_deleter_t = decltype([](auto node) constexpr { lyd_free_all(node); }); } TEST_CASE("Unsafe methods") { ly_ctx* ctx; REQUIRE(ly_ctx_new(ly_yang_module_dir(), 0, &ctx) == LY_SUCCESS); // When wrapping raw lyd_nodes, the context struct however is not managed and needs to be released manually (for // example with a unique_ptr), like below. auto ctx_deleter = std::unique_ptr(ctx); REQUIRE(lys_parse_mem(ctx, example_schema.c_str(), LYS_IN_YANG, nullptr) == LY_SUCCESS); auto data = R"({ "example-schema:leafInt32": 32 })"; DOCTEST_SUBCASE("createUnmanagedContext") { DOCTEST_SUBCASE("Custom deleter") { ctx_deleter.release(); auto wrapped = libyang::createUnmanagedContext(ctx, ly_ctx_destroy); } DOCTEST_SUBCASE("No custom deleter") { auto wrapped = libyang::createUnmanagedContext(ctx, nullptr); } } DOCTEST_SUBCASE("wrapRawNode") { lyd_node* node; REQUIRE(lyd_parse_data_mem(ctx, data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, &node) == LY_SUCCESS); // The wrapped node does take ownership of the lyd_node* and deletes it on destruction of the class. auto wrapped = libyang::wrapRawNode(node); REQUIRE(wrapped.path() == "/example-schema:leafInt32"); DOCTEST_SUBCASE("Automatic memory") { // nothing } DOCTEST_SUBCASE("Releasing the raw node") { auto releasedNode = libyang::releaseRawNode(wrapped); lyd_free_all(releasedNode); } DOCTEST_SUBCASE("Retrieving the raw node") { // Calling this should not make memory problems. libyang::getRawNode(wrapped); } REQUIRE_THROWS(libyang::wrapRawNode(nullptr)); } DOCTEST_SUBCASE("wrapUnmanagedRawNode") { lyd_node* node; REQUIRE(lyd_parse_data_mem(ctx, data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, &node) == LY_SUCCESS); // With wrapConstRawNode, the node needs to be released manually. auto node_deleter = std::unique_ptr(node); // The wrapped node does NOT take ownership of the lyd_node*. auto wrapped = libyang::wrapUnmanagedRawNode(const_cast(node)); REQUIRE(wrapped.path() == "/example-schema:leafInt32"); // The schema should still be accessible. REQUIRE(wrapped.schema().name() == "leafInt32"); for (const auto& node : wrapped.childrenDfs()) { node.path(); } // It is possible to create unmanaged sets. for (const auto& node : wrapped.findXPath("/example-schema:leafInt32")) { node.path(); } // You can do low level tree manipulation with the unmanaged tree. // You have two trees `wrapped` and `anotherNodeWrapped` and you want to do some manipulation in C++. DOCTEST_SUBCASE("Inserting an UNMANAGED node into an unmanaged node") { lyd_node* anotherNode; REQUIRE(lyd_new_path(nullptr, ctx, "/example-schema:leafInt8", "0", LYD_VALIDATE_PRESENT, &anotherNode) == LY_SUCCESS); auto anotherNodeWrapped = libyang::wrapUnmanagedRawNode(const_cast(anotherNode)); wrapped.insertSibling(anotherNodeWrapped); // Both are still unmanaged, both are accessible. REQUIRE(wrapped.path() == "/example-schema:leafInt32"); REQUIRE(anotherNodeWrapped.path() == "/example-schema:leafInt8"); DOCTEST_SUBCASE("no explicit unlink") { } DOCTEST_SUBCASE("unlink an unmanaged node from an unmanaged node") { REQUIRE(wrapped.findPath("/example-schema:leafInt8")); REQUIRE(anotherNodeWrapped.findPath("/example-schema:leafInt32")); // After unlink they are not reachable from each other anotherNodeWrapped.unlink(); REQUIRE(!wrapped.findPath("/example-schema:leafInt8")); REQUIRE(!anotherNodeWrapped.findPath("/example-schema:leafInt32")); lyd_free_all(anotherNode); } } // You have a C++ managed node and you want to insert that into an unmanaged node. DOCTEST_SUBCASE("Inserting a MANAGED node into an unmanaged node") { lyd_node* anotherNode; REQUIRE(lyd_new_path(nullptr, ctx, "/example-schema:leafInt8", "0", LYD_VALIDATE_PRESENT, &anotherNode) == LY_SUCCESS); auto anotherNodeWrapped = libyang::wrapRawNode(anotherNode); wrapped.insertSibling(anotherNodeWrapped); // BOTH are now unmanaged, both are accessible. REQUIRE(wrapped.path() == "/example-schema:leafInt32"); REQUIRE(anotherNodeWrapped.path() == "/example-schema:leafInt8"); DOCTEST_SUBCASE("no explicit unlink") { } DOCTEST_SUBCASE("unlink a managed node from an unmanaged node") { REQUIRE(wrapped.findPath("/example-schema:leafInt8")); REQUIRE(anotherNodeWrapped.findPath("/example-schema:leafInt32")); // After unlink they are not reachable from each other anotherNodeWrapped.unlink(); REQUIRE(!wrapped.findPath("/example-schema:leafInt8")); REQUIRE(!anotherNodeWrapped.findPath("/example-schema:leafInt32")); // this is still unmanaged and we need to delete it lyd_free_all(anotherNode); } } // You have a C++ managed node and you want to insert an unmanaged node into it. DOCTEST_SUBCASE("Inserting a UNMANAGED node into a managed node") { lyd_node* anotherNode; REQUIRE(lyd_new_path(nullptr, ctx, "/example-schema:leafInt8", "0", LYD_VALIDATE_PRESENT, &anotherNode) == LY_SUCCESS); auto anotherNodeWrapped = libyang::wrapRawNode(anotherNode); anotherNodeWrapped.insertSibling(wrapped); // BOTH are now managed by C++, both are accessible. REQUIRE(wrapped.path() == "/example-schema:leafInt32"); REQUIRE(anotherNodeWrapped.path() == "/example-schema:leafInt8"); // Since the original `wrapped` pointer is now managed by C++, we have to release our C management. (void)node_deleter.release(); } DOCTEST_SUBCASE("Query identity from unmanaged node") { lyd_node* node; REQUIRE(lyd_new_path(nullptr, ctx, "/example-schema:leafFoodTypedef", "example-schema:pizza", LYD_VALIDATE_PRESENT, &node) == LY_SUCCESS); auto wrappedNode = libyang::wrapUnmanagedRawNode(const_cast(node)); REQUIRE(wrappedNode.path() == "/example-schema:leafFoodTypedef"); REQUIRE(wrappedNode.asTerm().valueStr() == "example-schema:pizza"); auto typeIdIdentity = std::get(wrappedNode.asTerm().value()).schema; REQUIRE(typeIdIdentity.module().name() == "example-schema"); REQUIRE(typeIdIdentity.name() == "pizza"); auto node_deleter = std::unique_ptr(node); } REQUIRE_THROWS(libyang::wrapUnmanagedRawNode(nullptr)); } DOCTEST_SUBCASE("wrapUnmanagedRawNode metadata") { std::string in = R"( { "example-schema:leafInt8": -127, "@example-schema:leafInt8": { "ietf-netconf:operation": "merge" }, "example-schema:leafString": "", "@example-schema:leafString": { "ietf-netconf:operation": "remove" } } )"; REQUIRE(ly_ctx_set_searchdir(ctx, PATH_TO_LY_STRING(TESTS_DIR / "yang")) == LY_SUCCESS); REQUIRE(lys_parse_path(ctx, PATH_TO_LY_STRING(TESTS_DIR / "yang" / "ietf-netconf@2011-06-01.yang"), LYS_IN_YANG, nullptr) == LY_SUCCESS); lyd_node* node; REQUIRE(lyd_parse_data_mem(ctx, in.c_str(), LYD_JSON, LYD_PARSE_OPAQ, LYD_VALIDATE_PRESENT, &node) == LY_SUCCESS); REQUIRE(!!node); auto node_deleter = std::unique_ptr(node); auto wrapped = libyang::wrapUnmanagedRawNode(const_cast(node)); REQUIRE(wrapped.path() == "/example-schema:leafInt8"); REQUIRE(wrapped.nextSibling().has_value()); REQUIRE(wrapped.nextSibling()->path() == "/example-schema:leafString"); REQUIRE(!wrapped.meta().empty()); REQUIRE(wrapped.meta().begin()->name() == "operation"); REQUIRE(wrapped.meta().begin()->valueStr() == "merge"); REQUIRE(wrapped.meta().begin()->module().name() == "ietf-netconf"); REQUIRE(!wrapped.nextSibling()->meta().empty()); REQUIRE(wrapped.nextSibling()->meta().begin()->name() == "operation"); REQUIRE(wrapped.nextSibling()->meta().begin()->valueStr() == "remove"); REQUIRE(wrapped.nextSibling()->meta().begin()->module().name() == "ietf-netconf"); } }