Rework datastore tests

Sysrepo now supports parallelized tests. Use the new environmental
variables to implement this in netconf-cli. All of the tests now get
their own clean environment:

- Fresh repository and separate shm prefix. These get cleaned before and
after tests. The only thing that gets left are some empty directories.

- Its own model to test on.

- Separate Netopeer2 daemon: only for netconf tests - that means no
`sleep 5` for sysrepo-only tests. So no useless waiting. Wow! The daemon
also runs with its argv[0] changed to something recognizable for
`pkill`. That means that if Netopeer2 crashes for some reason, pkill
will notify me.

Side note: These changes somehow changed some of the linking, so hopefully I got
those right.

Change-Id: Ib0e582ef03fc559b24203af8afb2a295a6318ca9
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d6b26ff..3ecbe0b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -102,7 +102,7 @@
     src/sysrepo_access.cpp
     )
 
-target_link_libraries(sysrepoaccess PUBLIC datastoreaccess ast_values PRIVATE PkgConfig::SYSREPO)
+target_link_libraries(sysrepoaccess PUBLIC datastoreaccess ast_values PRIVATE PkgConfig::SYSREPO PkgConfig::LIBYANG)
 
 add_library(netconfaccess STATIC
     src/netconf-client.cpp
@@ -207,34 +207,12 @@
         message(FATAL_ERROR "Unable to find netopeer2-server, set NETOPEER2_EXECUTABLE manually.")
     endif()
 
-    set(NETOPEER_SOCKET_PATH "${CMAKE_CURRENT_BINARY_DIR}/netopeer2-server.sock")
-    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/start_daemons.sh.in ${CMAKE_CURRENT_BINARY_DIR}/start_daemons.sh @ONLY)
-    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/netopeer_vars.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/netopeer_vars.hpp @ONLY)
-    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/yang_access_test_vars.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/yang_access_test_vars.hpp @ONLY)
-    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/manage_nacm.sh.in ${CMAKE_CURRENT_BINARY_DIR}/manage_nacm.sh @ONLY)
+    pkg_get_variable(SYSREPO_SR_REPO_PATH sysrepo SR_REPO_PATH)
 
-    function(setup_datastore_tests)
-        add_test(NAME example-schema_init
-            COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/sysrepoctl-manage-module.sh ${SYSREPOCTL_EXECUTABLE} ${SYSREPOCFG_EXECUTABLE} install ${CMAKE_CURRENT_SOURCE_DIR}/tests/example-schema.yang)
-        add_test(NAME example-schema_cleanup
-            COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/sysrepoctl-manage-module.sh ${SYSREPOCTL_EXECUTABLE} ${SYSREPOCFG_EXECUTABLE} uninstall ${CMAKE_CURRENT_SOURCE_DIR}/tests/example-schema.yang)
-        add_test(NAME disable_nacm COMMAND ${CMAKE_CURRENT_BINARY_DIR}/manage_nacm.sh disable)
-        add_test(NAME enable_nacm COMMAND ${CMAKE_CURRENT_BINARY_DIR}/manage_nacm.sh enable)
-        add_test(NAME setup_netopeer COMMAND ${SYSREPOCFG_EXECUTABLE} --import=${CMAKE_CURRENT_SOURCE_DIR}/tests/netopeer-test-config.xml --datastore=startup --format=xml --module=ietf-netconf-server)
-        add_test(NAME start_daemons COMMAND ${CMAKE_CURRENT_BINARY_DIR}/start_daemons.sh)
-        add_test(NAME kill_daemons COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/kill_daemons.sh)
-        set_tests_properties(disable_nacm PROPERTIES FIXTURES_SETUP nacm_disabled)
-        set_tests_properties(enable_nacm PROPERTIES FIXTURES_CLEANUP nacm_disabled)
-        set_tests_properties(example-schema_init PROPERTIES FIXTURES_SETUP example-schema_setup)
-        set_tests_properties(setup_netopeer PROPERTIES FIXTURES_SETUP netopeer_configured)
-        set_tests_properties(example-schema_cleanup PROPERTIES FIXTURES_CLEANUP example-schema_setup)
-        set_tests_properties(setup_netopeer start_daemons kill_daemons example-schema_init example-schema_cleanup disable_nacm enable_nacm PROPERTIES RESOURCE_LOCK sysrepo)
-        set_property(TEST setup_netopeer APPEND PROPERTY FIXTURES_REQUIRED nacm_disabled)
-        set_property(TEST setup_netopeer APPEND PROPERTY FIXTURES_REQUIRED example-schema_setup)
-        set_tests_properties(start_daemons PROPERTIES FIXTURES_REQUIRED netopeer_configured FIXTURES_SETUP netopeer_running)
-        set_property(TEST example-schema_cleanup APPEND PROPERTY DEPENDS kill_daemons)
-        set_property(TEST kill_daemons APPEND PROPERTY FIXTURES_CLEANUP netopeer_running)
-    endfunction()
+    file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test_repositories)
+    file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test_sockets)
+
+    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/yang_access_test_vars.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/yang_access_test_vars.hpp @ONLY)
 
     function(cli_test name)
         if (${ARGC} GREATER 1) # this is how CMake does optional arguments
@@ -255,9 +233,8 @@
 
     function(datastore_test_impl name model backend)
         set(TESTNAME test_${name}_${backend})
+
         cli_test(${name}_${backend} ${name}.cpp)
-        set_tests_properties(${TESTNAME} PROPERTIES FIXTURES_REQUIRED ${model}_setup RESOURCE_LOCK sysrepo)
-        set_property(TEST ${TESTNAME} APPEND PROPERTY FIXTURES_REQUIRED netopeer_running)
         target_include_directories(${TESTNAME} PRIVATE ${PROJECT_SOURCE_DIR}/tests/mock)
         if (${backend} STREQUAL "sysrepo")
             target_link_libraries(${TESTNAME} sysrepoaccess)
@@ -271,6 +248,19 @@
         target_link_libraries(${TESTNAME} yangschema sysreposubscription proxydatastore PkgConfig::SYSREPO)
 
         target_compile_definitions(${TESTNAME} PRIVATE ${backend}_BACKEND)
+        set_tests_properties(${TESTNAME} PROPERTIES FIXTURES_REQUIRED ${TESTNAME}_setup)
+
+        add_test(NAME ${TESTNAME}_init COMMAND ${CMAKE_CURRENT_BINARY_DIR}/init_datastore.bash "${model}" "${backend}")
+        set_tests_properties(${TESTNAME}_init PROPERTIES FIXTURES_SETUP ${TESTNAME}_setup)
+        add_test(NAME ${TESTNAME}_cleanup COMMAND ${CMAKE_CURRENT_BINARY_DIR}/cleanup_datastore.bash "${backend}")
+        set_tests_properties(${TESTNAME}_cleanup PROPERTIES FIXTURES_CLEANUP ${TESTNAME}_setup)
+
+        set_property(TEST ${TESTNAME} ${TESTNAME}_init ${TESTNAME}_cleanup APPEND PROPERTY ENVIRONMENT
+            "SYSREPO_REPOSITORY_PATH=${CMAKE_CURRENT_BINARY_DIR}/test_repositories/${TESTNAME}"
+            "SYSREPO_SHM_PREFIX=netconf-cli_${TESTNAME}"
+            "NETOPEER_SOCKET=${CMAKE_CURRENT_BINARY_DIR}/test_sockets/${TESTNAME}.sock"
+            )
+
     endfunction()
 
     function(datastore_test name model)
@@ -298,9 +288,14 @@
     target_link_libraries(test_path_utils path)
     cli_test(keyvalue_completion)
 
-    setup_datastore_tests()
-    datastore_test(datastore_access example-schema)
-    datastore_test(data_query example-schema)
+    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/init_datastore.bash.in
+        ${CMAKE_CURRENT_BINARY_DIR}/init_datastore.bash @ONLY)
+
+    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/cleanup_datastore.bash.in
+        ${CMAKE_CURRENT_BINARY_DIR}/cleanup_datastore.bash @ONLY)
+
+    datastore_test(datastore_access ${CMAKE_CURRENT_SOURCE_DIR}/tests/example-schema.yang)
+    datastore_test(data_query ${CMAKE_CURRENT_SOURCE_DIR}/tests/example-schema.yang)
 endif()
 
 option(WITH_PYTHON_BINDINGS "Create and install Python3 bindings for accessing datastores" OFF)
@@ -315,11 +310,21 @@
         target_link_libraries(sysrepo_subscription_py PUBLIC sysreposubscription utils)
 
         configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/python_netconfaccess.py
-            ${CMAKE_CURRENT_BINARY_DIR}/tests_python_netconfaccess.py @ONLY)
+            ${CMAKE_CURRENT_BINARY_DIR}/tests_python_netconfaccess.py COPYONLY)
+
         add_test(NAME test_netconf_cli_py COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/tests_python_netconfaccess.py)
-        set_tests_properties(test_netconf_cli_py PROPERTIES RESOURCE_LOCK sysrepo)
-        set_property(TEST test_netconf_cli_py APPEND PROPERTY FIXTURES_REQUIRED netopeer_running)
-        set_property(TEST test_netconf_cli_py APPEND PROPERTY FIXTURES_REQUIRED example-schema_setup)
+        set_tests_properties(test_netconf_cli_py PROPERTIES FIXTURES_REQUIRED test_netconf_cli_py_setup)
+
+        add_test(NAME test_netconf_cli_py_init COMMAND ${CMAKE_CURRENT_BINARY_DIR}/init_datastore.bash ${CMAKE_CURRENT_SOURCE_DIR}/tests/example-schema.yang "netconf")
+        set_tests_properties(test_netconf_cli_py_init PROPERTIES FIXTURES_SETUP test_netconf_cli_py_setup)
+        add_test(NAME test_netconf_cli_py_cleanup COMMAND ${CMAKE_CURRENT_BINARY_DIR}/cleanup_datastore.bash "netconf")
+        set_tests_properties(test_netconf_cli_py_cleanup PROPERTIES FIXTURES_CLEANUP test_netconf_cli_py_setup)
+
+        set_property(TEST test_netconf_cli_py test_netconf_cli_py_init test_netconf_cli_py_cleanup APPEND PROPERTY ENVIRONMENT
+            "SYSREPO_REPOSITORY_PATH=${CMAKE_CURRENT_BINARY_DIR}/test_repositories/test_netconf_cli_py"
+            "SYSREPO_SHM_PREFIX=netconf-cli_test_netconf_cli_py"
+            "NETOPEER_SOCKET=${CMAKE_CURRENT_BINARY_DIR}/test_sockets/test_netconf_cli_py.sock"
+            )
 
         set(sanitizer_active OFF)
         # FIXME: this just sucks. The detection is very unreliable (one could use something like
diff --git a/tests/cleanup_datastore.bash.in b/tests/cleanup_datastore.bash.in
new file mode 100755
index 0000000..66df88c
--- /dev/null
+++ b/tests/cleanup_datastore.bash.in
@@ -0,0 +1,25 @@
+set -eux
+
+if [[ $(dirname "$(dirname "$(realpath "$SYSREPO_REPOSITORY_PATH")")") != "@CMAKE_CURRENT_BINARY_DIR@" ]]; then
+    echo "\$SYSREPO_REPOSITORY_PATH is not inside the build dir! Aborting. ($SYSREPO_REPOSITORY_PATH)"
+    exit 1
+fi
+
+if [[ -z "$SYSREPO_SHM_PREFIX" ]]; then
+    echo '$SYSREPO_SHM_PREFIX is empty! Aborting.'
+    exit 1
+fi
+
+BACKEND="$1"
+shift
+if [[ "$BACKEND" = "netconf" ]]; then
+    # The `-f` argument is neccessary so that pkill matches the whole command
+    # line, including stuff set by `exec -a`. Otherwise it matches the name in
+    # /proc/{pid}/stat and that is usually limited to 15 characters, so
+    # netopeer2-server appears as netopeer2-serve
+    pkill -f "${SYSREPO_SHM_PREFIX}_netopeer2-server"
+    rm "$NETOPEER_SOCKET"
+fi
+
+rm -r "$SYSREPO_REPOSITORY_PATH"
+rm "/dev/shm/$SYSREPO_SHM_PREFIX"*
diff --git a/tests/data_query.cpp b/tests/data_query.cpp
index d6435a8..4aa597e 100644
--- a/tests/data_query.cpp
+++ b/tests/data_query.cpp
@@ -13,7 +13,6 @@
 #include "sysrepo_access.hpp"
 #elif defined(netconf_BACKEND)
 #include "netconf_access.hpp"
-#include "netopeer_vars.hpp"
 #elif defined(yang_BACKEND)
 #include "yang_access.hpp"
 #include "yang_access_test_vars.hpp"
@@ -40,7 +39,8 @@
 #ifdef sysrepo_BACKEND
     SysrepoAccess datastore(Datastore::Running);
 #elif defined(netconf_BACKEND)
-    NetconfAccess datastore(NETOPEER_SOCKET_PATH);
+    const auto NETOPEER_SOCKET = getenv("NETOPEER_SOCKET");
+    NetconfAccess datastore(NETOPEER_SOCKET);
 #elif defined(yang_BACKEND)
     YangAccess datastore;
     datastore.addSchemaDir(schemaDir);
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index 68eac3a..cc97aa9 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -27,7 +27,6 @@
 using OnKeyNotFound = std::runtime_error;
 using OnExec = void;
 #include "netconf_access.hpp"
-#include "netopeer_vars.hpp"
 #elif defined(yang_BACKEND)
 #include <fstream>
 #include "yang_access.hpp"
@@ -122,7 +121,8 @@
 #ifdef sysrepo_BACKEND
     SysrepoAccess datastore(Datastore::Running);
 #elif defined(netconf_BACKEND)
-    NetconfAccess datastore(NETOPEER_SOCKET_PATH);
+    const auto NETOPEER_SOCKET = getenv("NETOPEER_SOCKET");
+    NetconfAccess datastore(NETOPEER_SOCKET);
 #elif defined(yang_BACKEND)
     TestYangAccess datastore;
     datastore.addSchemaDir(schemaDir);
@@ -896,7 +896,8 @@
 #ifdef sysrepo_BACKEND
     auto datastore = std::make_shared<SysrepoAccess>(Datastore::Running);
 #elif defined(netconf_BACKEND)
-    auto datastore = std::make_shared<NetconfAccess>(NETOPEER_SOCKET_PATH);
+    const auto NETOPEER_SOCKET = getenv("NETOPEER_SOCKET");
+    auto datastore = std::make_shared<NetconfAccess>(NETOPEER_SOCKET);
 #elif defined(yang_BACKEND)
     auto datastore = std::make_shared<YangAccess>();
     datastore->addSchemaDir(schemaDir);
diff --git a/tests/enable-nacm.xml b/tests/enable-nacm.xml
deleted file mode 100644
index 4a2f36a..0000000
--- a/tests/enable-nacm.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
-  <enable-nacm>true</enable-nacm>
-</nacm>
diff --git a/tests/init_datastore.bash.in b/tests/init_datastore.bash.in
new file mode 100755
index 0000000..5aec0c0
--- /dev/null
+++ b/tests/init_datastore.bash.in
@@ -0,0 +1,43 @@
+set -eux
+export UBSAN_OPTIONS=halt_on_error=1 # UBSan doesn't stop on errors by default.
+
+if [[ $(dirname "$(dirname "$(realpath "$SYSREPO_REPOSITORY_PATH")")") != "@CMAKE_CURRENT_BINARY_DIR@" ]]; then
+    echo "\$SYSREPO_REPOSITORY_PATH is not inside the build dir! Aborting. ($SYSREPO_REPOSITORY_PATH)"
+    exit 1
+fi
+
+if [[ -z "$SYSREPO_SHM_PREFIX" ]]; then
+    echo '$SYSREPO_SHM_PREFIX is empty! Aborting.'
+    exit 1
+fi
+
+rm -rf "$SYSREPO_REPOSITORY_PATH"
+rm -rf "/dev/shm/$SYSREPO_SHM_PREFIX"*
+cp -r "@SYSREPO_SR_REPO_PATH@" "$SYSREPO_REPOSITORY_PATH"
+
+SYSREPOCTL="@SYSREPOCTL_EXECUTABLE@"
+SYSREPOCFG="@SYSREPOCFG_EXECUTABLE@"
+
+MODULE="$1"
+YANG_DIR=$(dirname "$1")
+shift
+
+# Install the module
+"$SYSREPOCTL" --search-dirs "$YANG_DIR" --install "$MODULE" -a
+
+BACKEND="$1"
+shift
+if [[ "$BACKEND" = "netconf" ]]; then
+    NETOPEER2="@NETOPEER2_EXECUTABLE@"
+    # Setup netopeer config
+    "$SYSREPOCFG" --import --datastore=running --format=xml --module=ietf-netconf-server <<< ""
+
+    # Disable nacm
+    for datastore in startup running; do
+        "$SYSREPOCFG" --import="@CMAKE_CURRENT_SOURCE_DIR@/tests/disable-nacm.xml" --datastore="$datastore" --format=xml --module=ietf-netconf-acm
+    done
+
+    # Run netopeer. Use exec -a, so that each process has a recognizable name for `pkill`.
+    (exec -a "${SYSREPO_SHM_PREFIX}_netopeer2-server" "$NETOPEER2" -v2 "-U$NETOPEER_SOCKET")
+    sleep 5
+fi
diff --git a/tests/manage_nacm.sh.in b/tests/manage_nacm.sh.in
deleted file mode 100755
index db8d611..0000000
--- a/tests/manage_nacm.sh.in
+++ /dev/null
@@ -1,7 +0,0 @@
-if [ $1 != "enable" -a $1 != "disable" ]; then
-    echo 'Argument must be "enable" or "disable."'
-    exit 1
-fi
-for datastore in startup running; do
-    @SYSREPOCFG_EXECUTABLE@ --import=@CMAKE_CURRENT_SOURCE_DIR@/tests/"$1-nacm.xml" --datastore="$datastore" --format=xml --module=ietf-netconf-acm
-done
diff --git a/tests/mock/sysrepo_subscription.cpp b/tests/mock/sysrepo_subscription.cpp
index 5527c41..03be075 100644
--- a/tests/mock/sysrepo_subscription.cpp
+++ b/tests/mock/sysrepo_subscription.cpp
@@ -23,7 +23,7 @@
 
     int operator()(
             sysrepo::S_Session sess,
-            const char *module_name,
+            [[maybe_unused]] const char *module_name,
             [[maybe_unused]] const char *xpath,
             [[maybe_unused]] sr_event_t event,
             [[maybe_unused]] uint32_t request_id)
diff --git a/tests/netopeer-test-config.xml b/tests/netopeer-test-config.xml
deleted file mode 100644
index e69de29..0000000
--- a/tests/netopeer-test-config.xml
+++ /dev/null
diff --git a/tests/netopeer_vars.hpp.in b/tests/netopeer_vars.hpp.in
deleted file mode 100644
index 6036e70..0000000
--- a/tests/netopeer_vars.hpp.in
+++ /dev/null
@@ -1 +0,0 @@
-#define NETOPEER_SOCKET_PATH "@NETOPEER_SOCKET_PATH@"
diff --git a/tests/python_netconfaccess.py b/tests/python_netconfaccess.py
index ae317ee..d686cd0 100644
--- a/tests/python_netconfaccess.py
+++ b/tests/python_netconfaccess.py
@@ -1,7 +1,8 @@
+import os
 import sysrepo_subscription_py as sr_sub
 import netconf_cli_py as nc
 
-c = nc.NetconfAccess(socketPath = "@NETOPEER_SOCKET_PATH@")
+c = nc.NetconfAccess(socketPath = os.environ['NETOPEER_SOCKET'])
 data = c.getItems("/ietf-netconf-monitoring:netconf-state/datastores")
 for (k, v) in data:
     print(f"{k}: {type(v)} {v}", flush=True)
diff --git a/tests/start_daemons.sh.in b/tests/start_daemons.sh.in
deleted file mode 100755
index 5343441..0000000
--- a/tests/start_daemons.sh.in
+++ /dev/null
@@ -1,9 +0,0 @@
-set -eux -o pipefail
-shopt -s failglob
-export ASAN_OPTIONS=verify_asan_link_order=false
-export UBSAN_OPTIONS=halt_on_error=1
-
-@CMAKE_CURRENT_SOURCE_DIR@/tests/kill_daemons.sh || true
-
-@NETOPEER2_EXECUTABLE@ -v2 -U@NETOPEER_SOCKET_PATH@
-sleep 5
diff --git a/tests/sysrepoctl-manage-module.sh b/tests/sysrepoctl-manage-module.sh
deleted file mode 100755
index b618fec..0000000
--- a/tests/sysrepoctl-manage-module.sh
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/usr/bin/env bash
-
-set -eux -o pipefail
-shopt -s failglob
-
-SYSREPOCTL="${1}"
-shift
-if [[ ! -x "${SYSREPOCTL}" ]]; then
-  echo "Cannot locate \$SYSREPOCTL"
-  exit 1
-fi
-
-SYSREPOCFG="${1}"
-shift
-if [[ ! -x "${SYSREPOCFG}" ]]; then
-  echo "Cannot locate \$SYSREPOCFG"
-  exit 1
-fi
-
-MODE="${1}"
-shift
-
-if [[ ! -f "${1}" ]]; then
-  echo "No YANG file specified"
-  exit 1
-fi
-
-MODULE=$(basename --suffix .yang "${1}")
-YANG_DIR=$(dirname "${1}")
-
-if [[ "${MODE}" == "install" ]]; then
-  ${SYSREPOCTL} -C
-  ${SYSREPOCTL} --uninstall "${MODULE}" -a || true
-  ${SYSREPOCTL} -C
-  ${SYSREPOCTL} --search-dirs "${YANG_DIR}" --install "${1}" -a
-  JSON_DATA="${YANG_DIR}/${MODULE}.json"
-  XML_DATA="${YANG_DIR}/${MODULE}.startup.xml"
-  if [[ -f "${JSON_DATA}" ]] ;then
-    ${SYSREPOCFG} -d startup -f json "${MODULE}" -i "${JSON_DATA}" -a
-  elif [[ -f "${XML_DATA}" ]]; then
-    ${SYSREPOCFG} -d startup -f xml "${MODULE}" -i "${XML_DATA}" -a
-  fi
-elif [[ "${MODE}" == "uninstall" ]]; then
-  ${SYSREPOCTL} --uninstall "${MODULE}" -a
-else
-  echo "Mode of operation not specified"
-  exit 1
-fi