Add yang-cli

The original idea for this was that I would libyang C++ bindings for
this. Unfortunately, there have been problems with them as explained
here: https://github.com/CESNET/libyang/issues/1106. The easiest
solution to this was to just use the C api of libyang. After creating
some safe wrappers around pointers, it wasn't too difficult.

Change-Id: I0421cb64df66c640956501e56ffc4122eef0b9b7
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index 48b8f78..57ecf24 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -14,14 +14,28 @@
 using OnInvalidSchemaPathCreate = DatastoreException;
 using OnInvalidSchemaPathDelete = void;
 using OnInvalidSchemaPathMove = sysrepo::sysrepo_exception;
+using OnInvalidRpcPath = sysrepo::sysrepo_exception;
 using OnKeyNotFound = void;
+using OnRPC = void;
 #elif defined(netconf_BACKEND)
 using OnInvalidSchemaPathCreate = std::runtime_error;
 using OnInvalidSchemaPathDelete = std::runtime_error;
 using OnInvalidSchemaPathMove = std::runtime_error;
+using OnInvalidRpcPath = std::runtime_error;
 using OnKeyNotFound = std::runtime_error;
+using OnRPC = void;
 #include "netconf_access.hpp"
 #include "netopeer_vars.hpp"
+#elif defined(yang_BACKEND)
+#include <fstream>
+#include "yang_access.hpp"
+#include "yang_access_test_vars.hpp"
+using OnInvalidSchemaPathCreate = DatastoreException;
+using OnInvalidSchemaPathDelete = DatastoreException;
+using OnInvalidSchemaPathMove = DatastoreException;
+using OnInvalidRpcPath = DatastoreException;
+using OnKeyNotFound = DatastoreException;
+using OnRPC = std::logic_error;
 #else
 #error "Unknown backend"
 #endif
@@ -51,6 +65,8 @@
         // cannot use REQUIRE_THROWS_AS(..., Exception) directly because that one
         // needs an extra `typename` deep in the bowels of doctest
         REQUIRE_THROWS_AS(what(), std::runtime_error);
+    } else if constexpr (std::is_same<Exception, std::logic_error>()) {
+        REQUIRE_THROWS_AS(what(), std::logic_error);
     } else if constexpr (std::is_same<Exception, DatastoreException>()) {
         REQUIRE_THROWS_AS(what(), DatastoreException);
     } else if constexpr (std::is_same<Exception, sysrepo::sysrepo_exception>()) {
@@ -61,6 +77,34 @@
 }
 }
 
+#if defined(yang_BACKEND)
+class TestYangAccess : public YangAccess {
+public:
+    void commitChanges() override
+    {
+        YangAccess::commitChanges();
+        dumpToSysrepo();
+    }
+
+    void copyConfig(const Datastore source, const Datastore destination) override
+    {
+        YangAccess::copyConfig(source, destination);
+        dumpToSysrepo();
+    }
+
+private:
+    void dumpToSysrepo()
+    {
+        {
+            std::ofstream of(testConfigFile);
+            of << dumpXML();
+        }
+        auto command = std::string(sysrepocfgExecutable) + " --import=" + testConfigFile + " --format=xml --datastore=running example-schema";
+        REQUIRE(std::system(command.c_str()) == 0);
+    }
+};
+#endif
+
 TEST_CASE("setting/getting values")
 {
     trompeloeil::sequence seq1;
@@ -71,6 +115,10 @@
     SysrepoAccess datastore("netconf-cli-test", Datastore::Running);
 #elif defined(netconf_BACKEND)
     NetconfAccess datastore(NETOPEER_SOCKET_PATH);
+#elif defined(yang_BACKEND)
+    TestYangAccess datastore;
+    datastore.addSchemaDir(schemaDir);
+    datastore.addSchemaFile(exampleSchemaFile);
 #else
 #error "Unknown backend"
 #endif
@@ -457,6 +505,7 @@
         REQUIRE(datastore.getItems("/example-schema:dummy") == expected);
     }
 
+#if not defined(yang_BACKEND)
     SECTION("operational data")
     {
         MockDataSupplier mockOpsData;
@@ -472,6 +521,7 @@
         REQUIRE_CALL(mockOpsData, get_data(xpath)).RETURN(expected);
         REQUIRE(datastore.getItems(xpath) == expected);
     }
+#endif
 
     SECTION("leaf list")
     {
@@ -822,6 +872,10 @@
     SysrepoAccess datastore("netconf-cli-test", Datastore::Running);
 #elif defined(netconf_BACKEND)
     NetconfAccess datastore(NETOPEER_SOCKET_PATH);
+#elif defined(yang_BACKEND)
+    YangAccess datastore;
+    datastore.addSchemaDir(schemaDir);
+    datastore.addSchemaFile(exampleSchemaFile);
 #else
 #error "Unknown backend"
 #endif
@@ -873,12 +927,12 @@
             };
         }
 
-        REQUIRE(datastore.executeRpc(rpc, input) == output);
+        catching<OnRPC>([&] {REQUIRE(datastore.executeRpc(rpc, input) == output);});
     }
 
     SECTION("non-existing RPC")
     {
-        REQUIRE_THROWS_AS(datastore.executeRpc("/example-schema:non-existing", DatastoreAccess::Tree{}), std::runtime_error);
+        catching<OnInvalidRpcPath>([&] {datastore.executeRpc("/example-schema:non-existing", DatastoreAccess::Tree{});});
     }
 
     waitForCompletionAndBitMore(seq1);