Merge "Sync dependencies"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7e70b41..baec077 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -272,7 +272,7 @@
     cli_test(set_value_completion)
     target_link_libraries(test_set_value_completion leaf_data_type)
     cli_test(list_manipulation)
-    cli_test(ls_interpreter)
+    cli_test(interpreter)
     cli_test(path_utils)
     target_link_libraries(test_path_utils path)
     cli_test(keyvalue_completion)
diff --git a/src/ast_path.cpp b/src/ast_path.cpp
index 0079962..0d8a606 100644
--- a/src/ast_path.cpp
+++ b/src/ast_path.cpp
@@ -115,6 +115,62 @@
 {
 }
 
+namespace {
+template <typename T, typename U>
+auto findFirstOf(const std::vector<U>& nodes)
+{
+    return std::find_if(nodes.begin(), nodes.end(), [](const auto& e) {
+        return std::holds_alternative<T>(e.m_suffix);
+    });
+}
+
+template <typename T>
+void validatePathNodes(const std::vector<T>& nodes)
+{
+    static_assert(std::is_same<T, dataNode_>() || std::is_same<T, schemaNode_>());
+
+    if (nodes.empty()) {
+        // there are default ctors, so it makes sense to specify the same thing via explicit args and not fail
+        return;
+    }
+
+    if (auto firstLeaf = findFirstOf<leaf_>(nodes);
+            firstLeaf != nodes.end() && firstLeaf != nodes.end() - 1) {
+        throw std::logic_error{"Cannot put any extra nodes after a leaf"};
+    }
+
+    if (auto firstLeafList = findFirstOf<leafList_>(nodes);
+            firstLeafList != nodes.end() && firstLeafList != nodes.end() - 1) {
+        throw std::logic_error{"Cannot put any extra nodes after a leaf-list"};
+    }
+
+    if constexpr (std::is_same<T, dataNode_>()) {
+        if (auto firstLeafListElements = findFirstOf<leafListElement_>(nodes);
+                firstLeafListElements != nodes.end() && firstLeafListElements != nodes.end() - 1) {
+            throw std::logic_error{"Cannot put any extra nodes after a leaf-list with element specification"};
+        }
+        if (auto firstList = findFirstOf<list_>(nodes);
+                firstList != nodes.end() && firstList != nodes.end() - 1) {
+            throw std::logic_error{
+                "A list with no key specification can be present only as a last item in a dataPath. Did you mean to use a schemaPath?"
+            };
+        }
+    }
+}
+}
+
+schemaPath_::schemaPath_()
+{
+}
+
+schemaPath_::schemaPath_(const Scope scope, const std::vector<schemaNode_>& nodes, const TrailingSlash trailingSlash)
+    : m_scope(scope)
+    , m_nodes(nodes)
+    , m_trailingSlash(trailingSlash)
+{
+    validatePathNodes(m_nodes);
+}
+
 bool schemaPath_::operator==(const schemaPath_& b) const
 {
     if (this->m_nodes.size() != b.m_nodes.size())
@@ -122,6 +178,18 @@
     return this->m_nodes == b.m_nodes;
 }
 
+dataPath_::dataPath_()
+{
+}
+
+dataPath_::dataPath_(const Scope scope, const std::vector<dataNode_>& nodes, const TrailingSlash trailingSlash)
+    : m_scope(scope)
+    , m_nodes(nodes)
+    , m_trailingSlash(trailingSlash)
+{
+    validatePathNodes(m_nodes);
+}
+
 bool dataPath_::operator==(const dataPath_& b) const
 {
     if (this->m_nodes.size() != b.m_nodes.size())
@@ -258,3 +326,29 @@
 
     return res;
 }
+
+namespace {
+template <typename NodeType>
+void impl_pushFragment(std::vector<NodeType>& where, const NodeType& what)
+{
+    if (std::holds_alternative<nodeup_>(what.m_suffix)) {
+        if (!where.empty()) { // Allow going up, when already at root
+            where.pop_back();
+        }
+    } else {
+        where.push_back(what);
+    }
+}
+}
+
+void schemaPath_::pushFragment(const schemaNode_& fragment)
+{
+    impl_pushFragment(m_nodes, fragment);
+    validatePathNodes(m_nodes);
+}
+
+void dataPath_::pushFragment(const dataNode_& fragment)
+{
+    impl_pushFragment(m_nodes, fragment);
+    validatePathNodes(m_nodes);
+}
diff --git a/src/ast_path.hpp b/src/ast_path.hpp
index da99a26..83727ff 100644
--- a/src/ast_path.hpp
+++ b/src/ast_path.hpp
@@ -113,17 +113,26 @@
 };
 
 struct schemaPath_ {
+    schemaPath_();
+    schemaPath_(const Scope scope, const std::vector<schemaNode_>& nodes, const TrailingSlash trailingSlash = TrailingSlash::NonPresent);
     bool operator==(const schemaPath_& b) const;
     Scope m_scope = Scope::Relative;
     std::vector<schemaNode_> m_nodes;
     TrailingSlash m_trailingSlash = TrailingSlash::NonPresent;
+    // @brief Pushes a new fragment. Pops a fragment if it's nodeup_
+    void pushFragment(const schemaNode_& fragment);
 };
 
 struct dataPath_ {
+    dataPath_();
+    dataPath_(const Scope scope, const std::vector<dataNode_>& nodes, const TrailingSlash trailingSlash = TrailingSlash::NonPresent);
     bool operator==(const dataPath_& b) const;
     Scope m_scope = Scope::Relative;
     std::vector<dataNode_> m_nodes;
     TrailingSlash m_trailingSlash = TrailingSlash::NonPresent;
+
+    // @brief Pushes a new fragment. Pops a fragment if it's nodeup_
+    void pushFragment(const dataNode_& fragment);
 };
 
 std::string nodeToSchemaString(decltype(dataPath_::m_nodes)::value_type node);
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index cfd0a5d..3543e93 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -14,6 +14,28 @@
 #include "interpreter.hpp"
 #include "utils.hpp"
 
+struct pathToStringVisitor : boost::static_visitor<std::string> {
+    std::string operator()(const module_& path) const
+    {
+        using namespace std::string_literals;
+        return "/"s + boost::get<module_>(path).m_name + ":*";
+    }
+    std::string operator()(const schemaPath_& path) const
+    {
+        return pathToSchemaString(path, Prefixes::WhenNeeded);
+    }
+    std::string operator()(const dataPath_& path) const
+    {
+        return pathToDataString(path, Prefixes::WhenNeeded);
+    }
+};
+
+template <typename PathType>
+std::string pathToString(const PathType& path)
+{
+    return boost::apply_visitor(pathToStringVisitor(), path);
+}
+
 void Interpreter::operator()(const commit_&) const
 {
     m_datastore.commitChanges();
@@ -36,12 +58,12 @@
             data = identityRef;
         }
     }
-    m_datastore.setLeaf(absolutePathFromCommand(set), data);
+    m_datastore.setLeaf(pathToString(toCanonicalPath(set.m_path)), data);
 }
 
 void Interpreter::operator()(const get_& get) const
 {
-    auto items = m_datastore.getItems(absolutePathFromCommand(get));
+    auto items = m_datastore.getItems(pathToString(toCanonicalPath(get.m_path)));
     for (auto it = items.begin(); it != items.end(); it++) {
         auto [path, value] = *it;
         if (value.type() == typeid(special_) && boost::get<special_>(value).m_value == SpecialValue::LeafList) {
@@ -66,21 +88,21 @@
 void Interpreter::operator()(const create_& create) const
 {
     if (std::holds_alternative<listElement_>(create.m_path.m_nodes.back().m_suffix))
-        m_datastore.createListInstance(absolutePathFromCommand(create));
+        m_datastore.createListInstance(pathToString(toCanonicalPath(create.m_path)));
     else if (std::holds_alternative<leafListElement_>(create.m_path.m_nodes.back().m_suffix))
-        m_datastore.createLeafListInstance(absolutePathFromCommand(create));
+        m_datastore.createLeafListInstance(pathToString(toCanonicalPath(create.m_path)));
     else
-        m_datastore.createPresenceContainer(absolutePathFromCommand(create));
+        m_datastore.createPresenceContainer(pathToString(toCanonicalPath(create.m_path)));
 }
 
 void Interpreter::operator()(const delete_& delet) const
 {
     if (std::holds_alternative<container_>(delet.m_path.m_nodes.back().m_suffix))
-        m_datastore.deletePresenceContainer(absolutePathFromCommand(delet));
+        m_datastore.deletePresenceContainer(pathToString(toCanonicalPath(delet.m_path)));
     else if (std::holds_alternative<leafListElement_>(delet.m_path.m_nodes.back().m_suffix))
-        m_datastore.deleteLeafListInstance(absolutePathFromCommand(delet));
+        m_datastore.deleteLeafListInstance(pathToString(toCanonicalPath(delet.m_path)));
     else
-        m_datastore.deleteListInstance(absolutePathFromCommand(delet));
+        m_datastore.deleteListInstance(pathToString(toCanonicalPath(delet.m_path)));
 }
 
 void Interpreter::operator()(const ls_& ls) const
@@ -92,24 +114,7 @@
             recursion = Recursion::Recursive;
     }
 
-    std::set<ModuleNodePair> toPrint;
-
-    auto pathArg = dataPathToSchemaPath(m_parser.currentPath());
-    if (ls.m_path) {
-        if (ls.m_path->type() == typeid(module_)) {
-            toPrint = m_datastore.schema()->availableNodes(*ls.m_path, recursion);
-        } else {
-            auto schemaPath = anyPathToSchemaPath(*ls.m_path);
-            if (schemaPath.m_scope == Scope::Absolute) {
-                pathArg = schemaPath;
-            } else {
-                pathArg.m_nodes.insert(pathArg.m_nodes.end(), schemaPath.m_nodes.begin(), schemaPath.m_nodes.end());
-            }
-            toPrint = m_datastore.schema()->availableNodes(pathArg, recursion);
-        }
-    } else {
-        toPrint = m_datastore.schema()->availableNodes(pathArg, recursion);
-    }
+    auto toPrint = m_datastore.schema()->availableNodes(toCanonicalPath(ls.m_path), recursion);
 
     for (const auto& it : toPrint) {
         std::cout << (it.first ? *it.first + ":" : "" ) + it.second << std::endl;
@@ -183,7 +188,7 @@
 
 void Interpreter::operator()(const describe_& describe) const
 {
-    auto path = absolutePathFromCommand(describe);
+    auto path = pathToString(toCanonicalPath(describe.m_path));
     auto status = m_datastore.schema()->status(path);
     auto statusStr = status == yang::Status::Deprecated ? " (deprecated)" :
         status == yang::Status::Obsolete ? " (obsolete)" :
@@ -226,73 +231,69 @@
         });
 }
 
-template <typename T>
-std::string Interpreter::absolutePathFromCommand(const T& command) const
+template <typename PathType>
+boost::variant<dataPath_, schemaPath_, module_> Interpreter::toCanonicalPath(const boost::optional<PathType>& optPath) const
 {
-    if (command.m_path.m_scope == Scope::Absolute)
-        return pathToDataString(command.m_path, Prefixes::WhenNeeded);
-    else
-        return joinPaths(m_parser.currentNode(), pathToDataString(command.m_path, Prefixes::WhenNeeded));
+    if (!optPath) {
+        return m_parser.currentPath();
+    }
+    return toCanonicalPath(*optPath);
 }
 
-struct pathToStringVisitor : boost::static_visitor<std::string> {
-    std::string operator()(const module_& path) const
-    {
-        using namespace std::string_literals;
-        return "/"s + boost::get<module_>(path).m_name + ":*";
-    }
-    std::string operator()(const schemaPath_& path) const
-    {
-        return pathToSchemaString(path, Prefixes::WhenNeeded);
-    }
-    std::string operator()(const dataPath_& path) const
-    {
-        return pathToDataString(path, Prefixes::WhenNeeded);
-    }
-};
+struct impl_toCanonicalPath {
+    const dataPath_& m_parserPath;
 
-struct getPathScopeVisitor : boost::static_visitor<Scope> {
-    Scope operator()(const module_&) const
+    using ReturnType = boost::variant<dataPath_, schemaPath_, module_>;
+
+    impl_toCanonicalPath(const dataPath_& parserPath)
+        : m_parserPath(parserPath)
     {
-        throw std::logic_error("Interpreter: a top-level module has no scope.");
+    }
+    ReturnType operator()(const module_& path) const
+    {
+        return path;
+    }
+    ReturnType operator()(const schemaPath_& path) const
+    {
+        return impl(path);
+    }
+    ReturnType operator()(const dataPath_& path) const
+    {
+        return impl(path);
     }
 
-    template <typename T>
-    Scope operator()(const T& path) const
+private:
+    template <typename PathType>
+    ReturnType impl(const PathType& suffix) const
     {
-        return path.m_scope;
-    }
-};
+        PathType res = [this] {
+            if constexpr (std::is_same<PathType, schemaPath_>()) {
+                return dataPathToSchemaPath(m_parserPath);
+            } else {
+                return m_parserPath;
+            }
+        }();
 
-std::string Interpreter::absolutePathFromCommand(const get_& get) const
-{
-    using namespace std::string_literals;
-    if (!get.m_path) {
-        return m_parser.currentNode();
-    }
-
-    const auto path = *get.m_path;
-    if (path.type() == typeid(module_)) {
-        return boost::apply_visitor(pathToStringVisitor(), path);
-    } else {
-        std::string pathString = boost::apply_visitor(pathToStringVisitor(), path);
-        auto pathScope{boost::apply_visitor(getPathScopeVisitor(), path)};
-
-        if (pathScope == Scope::Absolute) {
-            return pathString;
-        } else {
-            return joinPaths(m_parser.currentNode(), pathString);
+        if (suffix.m_scope == Scope::Absolute) {
+            res = {Scope::Absolute, {}};
         }
-    }
-}
 
-std::string Interpreter::absolutePathFromCommand(const describe_& describe) const
+        for (const auto& fragment : suffix.m_nodes) {
+            res.pushFragment(fragment);
+        }
+
+        return res;
+    }
+};
+
+template <typename PathType>
+boost::variant<dataPath_, schemaPath_, module_> Interpreter::toCanonicalPath(const PathType& path) const
 {
-    auto pathStr = boost::apply_visitor(pathToStringVisitor(), describe.m_path);
-    if (boost::apply_visitor(getPathScopeVisitor(), describe.m_path) == Scope::Absolute)
-        return pathStr;
-    else
-        return joinPaths(m_parser.currentNode(), pathStr);
+    if constexpr (std::is_same<PathType, dataPath_>()) {
+        return impl_toCanonicalPath(m_parser.currentPath())(path);
+    } else {
+        return boost::apply_visitor(impl_toCanonicalPath(m_parser.currentPath()), path);
+    }
 }
 
 Interpreter::Interpreter(Parser& parser, DatastoreAccess& datastore)
diff --git a/src/interpreter.hpp b/src/interpreter.hpp
index bd90a60..e008375 100644
--- a/src/interpreter.hpp
+++ b/src/interpreter.hpp
@@ -12,6 +12,7 @@
 #include "datastore_access.hpp"
 #include "parser.hpp"
 
+
 struct Interpreter : boost::static_visitor<void> {
     Interpreter(Parser& parser, DatastoreAccess& datastore);
 
@@ -29,12 +30,14 @@
     void operator()(const move_& move) const;
 
 private:
-    template <typename T>
-    std::string absolutePathFromCommand(const T& command) const;
-    std::string absolutePathFromCommand(const get_& command) const;
-    std::string absolutePathFromCommand(const describe_& describe) const;
     std::string buildTypeInfo(const std::string& path) const;
 
+    template <typename PathType>
+    boost::variant<dataPath_, schemaPath_, module_> toCanonicalPath(const boost::optional<PathType>& path) const;
+
+    template <typename PathType>
+    boost::variant<dataPath_, schemaPath_, module_> toCanonicalPath(const PathType& path) const;
+
     Parser& m_parser;
     DatastoreAccess& m_datastore;
 };
diff --git a/src/parser.cpp b/src/parser.cpp
index ce583ea..bf00a7e 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -86,10 +86,7 @@
         m_curDir = name;
     } else {
         for (const auto& it : name.m_nodes) {
-            if (std::holds_alternative<nodeup_>(it.m_suffix))
-                m_curDir.m_nodes.pop_back();
-            else
-                m_curDir.m_nodes.push_back(it);
+            m_curDir.pushFragment(it);
         }
     }
 }
diff --git a/src/parser_context.cpp b/src/parser_context.cpp
index 723fbae..17f6604 100644
--- a/src/parser_context.cpp
+++ b/src/parser_context.cpp
@@ -39,18 +39,10 @@
 
 void ParserContext::pushPathFragment(const dataNode_& node)
 {
-    auto pushNode = [] (auto& where, const auto& what) {
-        if (std::holds_alternative<nodeup_>(what.m_suffix)) {
-            where.m_nodes.pop_back();
-        } else {
-            where.m_nodes.push_back(what);
-        }
-    };
-
     if (m_curPath.type() == typeid(dataPath_)) {
-        pushNode(boost::get<dataPath_>(m_curPath), node);
+        boost::get<dataPath_>(m_curPath).pushFragment(node);
     } else {
-        pushNode(boost::get<schemaPath_>(m_curPath), dataNodeToSchemaNode(node));
+        boost::get<schemaPath_>(m_curPath).pushFragment(dataNodeToSchemaNode(node));
     }
 }
 
diff --git a/tests/cd.cpp b/tests/cd.cpp
index f85593b..e0d6293 100644
--- a/tests/cd.cpp
+++ b/tests/cd.cpp
@@ -136,6 +136,19 @@
 
         SECTION("moving up")
         {
+            SECTION("moving up when already in root")
+            {
+                input = "cd ..";
+                expected.m_path.m_nodes.push_back(dataNode_(nodeup_()));
+            }
+
+            SECTION("moving up TWICE when already in root")
+            {
+                input = "cd ../..";
+                expected.m_path.m_nodes.push_back(dataNode_(nodeup_()));
+                expected.m_path.m_nodes.push_back(dataNode_(nodeup_()));
+            }
+
             SECTION("example:a/..")
             {
                 input = "cd example:a/..";
diff --git a/tests/interpreter.cpp b/tests/interpreter.cpp
new file mode 100644
index 0000000..3acef2b
--- /dev/null
+++ b/tests/interpreter.cpp
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#include <experimental/iterator>
+#include "trompeloeil_doctest.hpp"
+#include "ast_commands.hpp"
+#include "interpreter.hpp"
+#include "datastoreaccess_mock.hpp"
+#include "parser.hpp"
+#include "pretty_printers.hpp"
+#include "static_schema.hpp"
+
+class MockSchema : public trompeloeil::mock_interface<Schema> {
+public:
+    IMPLEMENT_CONST_MOCK1(defaultValue);
+    IMPLEMENT_CONST_MOCK1(description);
+    IMPLEMENT_CONST_MOCK2(availableNodes);
+    IMPLEMENT_CONST_MOCK1(isConfig);
+    MAKE_CONST_MOCK1(leafType, yang::TypeInfo(const std::string&), override);
+    MAKE_CONST_MOCK2(leafType, yang::TypeInfo(const schemaPath_&, const ModuleNodePair&), override);
+    IMPLEMENT_CONST_MOCK1(leafTypeName);
+    IMPLEMENT_CONST_MOCK1(isModule);
+    IMPLEMENT_CONST_MOCK1(leafrefPath);
+    IMPLEMENT_CONST_MOCK2(listHasKey);
+    IMPLEMENT_CONST_MOCK1(leafIsKey);
+    IMPLEMENT_CONST_MOCK1(listKeys);
+    MAKE_CONST_MOCK1(nodeType, yang::NodeTypes(const std::string&), override);
+    MAKE_CONST_MOCK2(nodeType, yang::NodeTypes(const schemaPath_&, const ModuleNodePair&), override);
+    IMPLEMENT_CONST_MOCK1(status);
+};
+
+TEST_CASE("interpreter tests")
+{
+    auto schema = std::make_shared<MockSchema>();
+    Parser parser(schema);
+    MockDatastoreAccess datastore;
+    std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
+
+    std::vector<command_> toInterpret;
+
+    SECTION("ls")
+    {
+        boost::variant<dataPath_, schemaPath_, module_> expectedPath;
+        boost::optional<boost::variant<dataPath_, schemaPath_, module_>> lsArg;
+        SECTION("cwd: /")
+        {
+            SECTION("arg: <none>")
+            {
+                expectedPath = dataPath_{};
+            }
+
+            SECTION("arg: ..")
+            {
+                lsArg = dataPath_{Scope::Relative, {dataNode_{nodeup_{}}}};
+                expectedPath = dataPath_{};
+            }
+
+            SECTION("arg: /..")
+            {
+                lsArg = dataPath_{Scope::Absolute, {dataNode_{nodeup_{}}}};
+                expectedPath = dataPath_{Scope::Absolute, {}};
+            }
+
+            SECTION("arg: /example:a/../example:a")
+            {
+                lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}},
+                                                    {nodeup_{}},
+                                                    {module_{"example"}, container_{"a"}}}};
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
+            }
+
+            SECTION("arg: example:a")
+            {
+                lsArg = dataPath_{Scope::Relative, {{module_{"example"}, container_{"a"}}}};
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
+            }
+
+            SECTION("arg: example:list")
+            {
+                lsArg = dataPath_{Scope::Relative, {{module_{"example"}, list_{"list"}}}};
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
+            }
+
+            SECTION("arg: /example:a")
+            {
+                lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
+            }
+
+            SECTION("arg: /example:list")
+            {
+                lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
+            }
+
+            SECTION("arg example:*")
+            {
+                lsArg = module_{"example"};
+                expectedPath = module_{"example"};
+            }
+        }
+
+        SECTION("cwd: /example:a")
+        {
+            parser.changeNode({Scope::Relative, {{module_{"example"}, container_{"a"}}}});
+
+            SECTION("arg: <none>")
+            {
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
+            }
+
+            SECTION("arg: example:a2")
+            {
+                lsArg = dataPath_{Scope::Relative, {{container_{"a2"}}}};
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}, {container_{"a2"}}}};
+            }
+
+            SECTION("arg: example:listInCont")
+            {
+                lsArg = dataPath_{Scope::Relative, {{list_{"listInCont"}}}};
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}, {list_{"listInCont"}}}};
+            }
+
+            SECTION("arg: /example:a")
+            {
+                lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
+            }
+
+            SECTION("arg: /example:list")
+            {
+                lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
+            }
+        }
+        SECTION("cwd: /example:list")
+        {
+            parser.changeNode({Scope::Relative, {{module_{"example"}, list_{"list"}}}});
+
+            SECTION("arg: <none>")
+            {
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
+            }
+
+            SECTION("arg: example:contInList")
+            {
+                lsArg = schemaPath_{Scope::Relative, {{container_{"contInList"}}}};
+                expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}, {container_{"contInList"}}}};
+            }
+
+            SECTION("arg: /example:a")
+            {
+                lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
+            }
+
+            SECTION("arg: /example:list")
+            {
+                lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
+                expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
+            }
+
+            SECTION("arg example:*")
+            {
+                lsArg = module_{"example"};
+                expectedPath = module_{"example"};
+            }
+        }
+        ls_ ls;
+        ls.m_path = lsArg;
+        expectations.push_back(NAMED_REQUIRE_CALL(datastore, schema()).RETURN(schema));
+        expectations.push_back(NAMED_REQUIRE_CALL(*schema, availableNodes(expectedPath, Recursion::NonRecursive)).RETURN(std::set<ModuleNodePair>{}));
+        toInterpret.push_back(ls);
+    }
+
+    SECTION("get")
+    {
+        using namespace std::string_literals;
+        DatastoreAccess::Tree treeReturned;
+        decltype(get_::m_path) inputPath;
+        std::string expectedPathArg;
+
+        SECTION("paths")
+        {
+            SECTION("/")
+            {
+                expectedPathArg = "/";
+            }
+
+            SECTION("module")
+            {
+                inputPath = module_{"mod"};
+                expectedPathArg = "/mod:*";
+            }
+
+            SECTION("path to a leaf")
+            {
+                expectedPathArg = "/mod:myLeaf";
+                Scope scope;
+                SECTION("cwd: /")
+                {
+                    SECTION("absolute")
+                    {
+                        scope = Scope::Absolute;
+                    }
+
+                    SECTION("relative")
+                    {
+                        scope = Scope::Relative;
+                    }
+
+                    inputPath = dataPath_{scope, {dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
+                }
+
+                SECTION("cwd: /mod:whatever")
+                {
+                    parser.changeNode(dataPath_{Scope::Relative, {dataNode_{{"mod"}, container_{"whatever"}}}});
+                    SECTION("absolute")
+                    {
+                        scope = Scope::Absolute;
+                        inputPath = dataPath_{scope, {dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
+                    }
+
+                    SECTION("relative")
+                    {
+                        scope = Scope::Relative;
+                        inputPath = dataPath_{scope, {dataNode_{nodeup_{}}, dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
+                    }
+
+                }
+            }
+
+            SECTION("path to a list")
+            {
+                expectedPathArg = "/mod:myList[name='AHOJ']";
+                Scope scope;
+                SECTION("cwd: /")
+                {
+                    SECTION("absolute")
+                    {
+                        scope = Scope::Absolute;
+                    }
+
+                    SECTION("relative")
+                    {
+                        scope = Scope::Relative;
+                    }
+
+                    inputPath = dataPath_{scope, {dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
+                }
+
+                SECTION("cwd: /mod:whatever")
+                {
+                    parser.changeNode(dataPath_{Scope::Relative, {dataNode_{{"mod"}, container_{"whatever"}}}});
+                    SECTION("absolute")
+                    {
+                        scope = Scope::Absolute;
+                        inputPath = dataPath_{scope, {dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
+                    }
+
+                    SECTION("relative")
+                    {
+                        scope = Scope::Relative;
+                        inputPath = dataPath_{scope, {dataNode_{nodeup_{}}, dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
+                    }
+                }
+            }
+        }
+
+        SECTION("trees")
+        {
+            expectedPathArg = "/";
+            SECTION("no leaflists")
+            {
+                treeReturned = {
+                    {"/mod:AHOJ", 30},
+                    {"/mod:CAU", std::string{"AYYY"}},
+                    {"/mod:CUS", bool{true}}
+                };
+            }
+
+            SECTION("leaflist at the beginning of a tree")
+            {
+                treeReturned = {
+                    {"/mod:addresses", special_{SpecialValue::LeafList}},
+                    {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
+                    {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
+                    {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
+                    {"/mod:AHOJ", 30},
+                    {"/mod:CAU", std::string{"AYYY"}},
+                };
+            }
+
+            SECTION("leaflist in the middle of a tree")
+            {
+                treeReturned = {
+                    {"/mod:AHOJ", 30},
+                    {"/mod:addresses", special_{SpecialValue::LeafList}},
+                    {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
+                    {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
+                    {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
+                    {"/mod:CAU", std::string{"AYYY"}},
+                };
+            }
+
+            SECTION("leaflist at the end of a tree")
+            {
+                treeReturned = {
+                    {"/mod:AHOJ", 30},
+                    {"/mod:CAU", std::string{"AYYY"}},
+                    {"/mod:addresses", special_{SpecialValue::LeafList}},
+                    {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
+                    {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
+                    {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
+                };
+            }
+        }
+
+        get_ getCmd;
+        getCmd.m_path = inputPath;
+        expectations.push_back(NAMED_REQUIRE_CALL(datastore, getItems(expectedPathArg)).RETURN(treeReturned));
+        toInterpret.push_back(getCmd);
+    }
+
+    SECTION("create/delete")
+    {
+        using namespace std::string_literals;
+        dataPath_ inputPath;
+
+        SECTION("list instance")
+        {
+            inputPath.m_nodes = {dataNode_{{"mod"}, listElement_{"department", {{"name", "engineering"s}}}}};
+            expectations.push_back(NAMED_REQUIRE_CALL(datastore, createListInstance("/mod:department[name='engineering']")));
+            expectations.push_back(NAMED_REQUIRE_CALL(datastore, deleteListInstance("/mod:department[name='engineering']")));
+        }
+
+        SECTION("leaflist instance")
+        {
+            inputPath.m_nodes = {dataNode_{{"mod"}, leafListElement_{"addresses", "127.0.0.1"s}}};
+            expectations.push_back(NAMED_REQUIRE_CALL(datastore, createLeafListInstance("/mod:addresses[.='127.0.0.1']")));
+            expectations.push_back(NAMED_REQUIRE_CALL(datastore, deleteLeafListInstance("/mod:addresses[.='127.0.0.1']")));
+        }
+
+        SECTION("presence container")
+        {
+            inputPath.m_nodes = {dataNode_{{"mod"}, container_{"pContainer"}}};
+            expectations.push_back(NAMED_REQUIRE_CALL(datastore, createPresenceContainer("/mod:pContainer")));
+            expectations.push_back(NAMED_REQUIRE_CALL(datastore, deletePresenceContainer("/mod:pContainer")));
+        }
+
+        create_ createCmd;
+        createCmd.m_path = inputPath;
+        delete_ deleteCmd;
+        deleteCmd.m_path = inputPath;
+        toInterpret.push_back(createCmd);
+        toInterpret.push_back(deleteCmd);
+    }
+
+    SECTION("commit")
+    {
+        expectations.push_back(NAMED_REQUIRE_CALL(datastore, commitChanges()));
+        toInterpret.push_back(commit_{});
+    }
+
+    SECTION("discard")
+    {
+        expectations.push_back(NAMED_REQUIRE_CALL(datastore, discardChanges()));
+        toInterpret.push_back(discard_{});
+    }
+
+
+    SECTION("set")
+    {
+        dataPath_ inputPath;
+        leaf_data_ inputData;
+
+        SECTION("setting identityRef without module") // The parser has to fill in the module
+        {
+            inputPath.m_nodes = {dataNode_{{"mod"}, leaf_{"animal"}}};
+            inputData = identityRef_{"Doge"};
+            expectations.push_back(NAMED_REQUIRE_CALL(datastore, setLeaf("/mod:animal", identityRef_{"mod", "Doge"})));
+        }
+
+
+        set_ setCmd;
+        setCmd.m_path = inputPath;
+        setCmd.m_data = inputData;
+        toInterpret.push_back(setCmd);
+    }
+
+
+    SECTION("copy")
+    {
+        SECTION("running -> startup")
+        {
+            expectations.push_back(NAMED_REQUIRE_CALL(datastore, copyConfig(Datastore::Running, Datastore::Startup)));
+            toInterpret.push_back(copy_{{}, Datastore::Running, Datastore::Startup});
+        }
+
+        SECTION("startup -> running")
+        {
+            expectations.push_back(NAMED_REQUIRE_CALL(datastore, copyConfig(Datastore::Startup, Datastore::Running)));
+            toInterpret.push_back(copy_{{}, Datastore::Startup, Datastore::Running});
+        }
+    }
+
+    for (const auto& command : toInterpret) {
+        boost::apply_visitor(Interpreter(parser, datastore), command);
+    }
+}
diff --git a/tests/ls_interpreter.cpp b/tests/ls_interpreter.cpp
deleted file mode 100644
index 2ae0b7c..0000000
--- a/tests/ls_interpreter.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
- * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
- *
- * Written by Václav Kubernát <kubervac@fit.cvut.cz>
- *
-*/
-
-#include <experimental/iterator>
-#include "trompeloeil_doctest.hpp"
-#include "ast_commands.hpp"
-#include "interpreter.hpp"
-#include "datastoreaccess_mock.hpp"
-#include "parser.hpp"
-#include "pretty_printers.hpp"
-#include "static_schema.hpp"
-
-class MockSchema : public trompeloeil::mock_interface<Schema> {
-public:
-    IMPLEMENT_CONST_MOCK1(defaultValue);
-    IMPLEMENT_CONST_MOCK1(description);
-    IMPLEMENT_CONST_MOCK2(availableNodes);
-    IMPLEMENT_CONST_MOCK1(isConfig);
-    MAKE_CONST_MOCK1(leafType, yang::TypeInfo(const std::string&), override);
-    MAKE_CONST_MOCK2(leafType, yang::TypeInfo(const schemaPath_&, const ModuleNodePair&), override);
-    IMPLEMENT_CONST_MOCK1(leafTypeName);
-    IMPLEMENT_CONST_MOCK1(isModule);
-    IMPLEMENT_CONST_MOCK1(leafrefPath);
-    IMPLEMENT_CONST_MOCK2(listHasKey);
-    IMPLEMENT_CONST_MOCK1(leafIsKey);
-    IMPLEMENT_CONST_MOCK1(listKeys);
-    MAKE_CONST_MOCK1(nodeType, yang::NodeTypes(const std::string&), override);
-    MAKE_CONST_MOCK2(nodeType, yang::NodeTypes(const schemaPath_&, const ModuleNodePair&), override);
-    IMPLEMENT_CONST_MOCK1(status);
-};
-
-TEST_CASE("ls interpreter")
-{
-    auto schema = std::make_shared<MockSchema>();
-    Parser parser(schema);
-
-    boost::variant<dataPath_, schemaPath_, module_> expectedPath;
-    boost::optional<boost::variant<dataPath_, schemaPath_, module_>> lsArg{boost::none};
-    SECTION("cwd: /")
-    {
-        SECTION("arg: <none>")
-        {
-            expectedPath = schemaPath_{};
-        }
-
-        SECTION("arg: example:a")
-        {
-            lsArg = dataPath_{Scope::Relative, {{module_{"example"}, container_{"a"}}}};
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
-        }
-
-        SECTION("arg: example:list")
-        {
-            lsArg = dataPath_{Scope::Relative, {{module_{"example"}, list_{"list"}}}};
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
-        }
-
-        SECTION("arg: /example:a")
-        {
-            lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
-        }
-
-        SECTION("arg: /example:list")
-        {
-            lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
-        }
-
-        SECTION("arg example:*")
-        {
-            lsArg = module_{"example"};
-            expectedPath = module_{"example"};
-        }
-    }
-
-    SECTION("cwd: /example:a")
-    {
-        parser.changeNode({Scope::Relative, {{module_{"example"}, container_{"a"}}}});
-
-        SECTION("arg: <none>")
-        {
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
-        }
-
-        SECTION("arg: example:a2")
-        {
-            lsArg = dataPath_{Scope::Relative, {{container_{"a2"}}}};
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}, {container_{"a2"}}}};
-        }
-
-        SECTION("arg: example:listInCont")
-        {
-            lsArg = dataPath_{Scope::Relative, {{list_{"listInCont"}}}};
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}, {list_{"listInCont"}}}};
-        }
-
-        SECTION("arg: /example:a")
-        {
-            lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
-        }
-
-        SECTION("arg: /example:list")
-        {
-            lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
-        }
-    }
-    SECTION("cwd: /example:list")
-    {
-        parser.changeNode({Scope::Relative, {{module_{"example"}, list_{"list"}}}});
-
-        SECTION("arg: <none>")
-        {
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
-        }
-
-        SECTION("arg: example:contInList")
-        {
-            lsArg = dataPath_{Scope::Relative, {{container_{"contInList"}}}};
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}, {container_{"contInList"}}}};
-        }
-
-        SECTION("arg: /example:a")
-        {
-            lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
-        }
-
-        SECTION("arg: /example:list")
-        {
-            lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
-            expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
-        }
-
-        SECTION("arg example:*")
-        {
-            lsArg = module_{"example"};
-            expectedPath = module_{"example"};
-        }
-    }
-    MockDatastoreAccess datastore;
-    REQUIRE_CALL(datastore, schema()).RETURN(schema);
-    ls_ ls;
-    ls.m_path = lsArg;
-    REQUIRE_CALL(*schema, availableNodes(expectedPath, Recursion::NonRecursive)).RETURN(std::set<ModuleNodePair>{});
-    Interpreter(parser, datastore)(ls);
-}