blob: 3acef2b109038806a3e120f9147a20fc3cef428f [file] [log] [blame]
Václav Kubernát5452ede2020-06-10 12:05:11 +02001/*
2 * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
3 * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
4 *
5 * Written by Václav Kubernát <kubervac@fit.cvut.cz>
6 *
7*/
8
9#include <experimental/iterator>
10#include "trompeloeil_doctest.hpp"
11#include "ast_commands.hpp"
12#include "interpreter.hpp"
13#include "datastoreaccess_mock.hpp"
14#include "parser.hpp"
15#include "pretty_printers.hpp"
16#include "static_schema.hpp"
17
18class MockSchema : public trompeloeil::mock_interface<Schema> {
19public:
20 IMPLEMENT_CONST_MOCK1(defaultValue);
21 IMPLEMENT_CONST_MOCK1(description);
22 IMPLEMENT_CONST_MOCK2(availableNodes);
23 IMPLEMENT_CONST_MOCK1(isConfig);
24 MAKE_CONST_MOCK1(leafType, yang::TypeInfo(const std::string&), override);
25 MAKE_CONST_MOCK2(leafType, yang::TypeInfo(const schemaPath_&, const ModuleNodePair&), override);
26 IMPLEMENT_CONST_MOCK1(leafTypeName);
27 IMPLEMENT_CONST_MOCK1(isModule);
28 IMPLEMENT_CONST_MOCK1(leafrefPath);
29 IMPLEMENT_CONST_MOCK2(listHasKey);
30 IMPLEMENT_CONST_MOCK1(leafIsKey);
31 IMPLEMENT_CONST_MOCK1(listKeys);
32 MAKE_CONST_MOCK1(nodeType, yang::NodeTypes(const std::string&), override);
33 MAKE_CONST_MOCK2(nodeType, yang::NodeTypes(const schemaPath_&, const ModuleNodePair&), override);
34 IMPLEMENT_CONST_MOCK1(status);
35};
36
37TEST_CASE("interpreter tests")
38{
39 auto schema = std::make_shared<MockSchema>();
40 Parser parser(schema);
41 MockDatastoreAccess datastore;
42 std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
43
44 std::vector<command_> toInterpret;
45
46 SECTION("ls")
47 {
48 boost::variant<dataPath_, schemaPath_, module_> expectedPath;
49 boost::optional<boost::variant<dataPath_, schemaPath_, module_>> lsArg;
50 SECTION("cwd: /")
51 {
52 SECTION("arg: <none>")
53 {
54 expectedPath = dataPath_{};
55 }
56
Václav Kubernát59be0de2020-06-15 13:58:45 +020057 SECTION("arg: ..")
58 {
59 lsArg = dataPath_{Scope::Relative, {dataNode_{nodeup_{}}}};
60 expectedPath = dataPath_{};
61 }
62
63 SECTION("arg: /..")
64 {
65 lsArg = dataPath_{Scope::Absolute, {dataNode_{nodeup_{}}}};
66 expectedPath = dataPath_{Scope::Absolute, {}};
67 }
68
69 SECTION("arg: /example:a/../example:a")
70 {
71 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}},
72 {nodeup_{}},
73 {module_{"example"}, container_{"a"}}}};
74 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
75 }
76
Václav Kubernát5452ede2020-06-10 12:05:11 +020077 SECTION("arg: example:a")
78 {
79 lsArg = dataPath_{Scope::Relative, {{module_{"example"}, container_{"a"}}}};
80 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
81 }
82
83 SECTION("arg: example:list")
84 {
85 lsArg = dataPath_{Scope::Relative, {{module_{"example"}, list_{"list"}}}};
86 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
87 }
88
89 SECTION("arg: /example:a")
90 {
91 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
92 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
93 }
94
95 SECTION("arg: /example:list")
96 {
97 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
98 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
99 }
100
101 SECTION("arg example:*")
102 {
103 lsArg = module_{"example"};
104 expectedPath = module_{"example"};
105 }
106 }
107
108 SECTION("cwd: /example:a")
109 {
110 parser.changeNode({Scope::Relative, {{module_{"example"}, container_{"a"}}}});
111
112 SECTION("arg: <none>")
113 {
114 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
115 }
116
117 SECTION("arg: example:a2")
118 {
119 lsArg = dataPath_{Scope::Relative, {{container_{"a2"}}}};
120 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}, {container_{"a2"}}}};
121 }
122
123 SECTION("arg: example:listInCont")
124 {
125 lsArg = dataPath_{Scope::Relative, {{list_{"listInCont"}}}};
126 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}, {list_{"listInCont"}}}};
127 }
128
129 SECTION("arg: /example:a")
130 {
131 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
132 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
133 }
134
135 SECTION("arg: /example:list")
136 {
137 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
138 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
139 }
140 }
141 SECTION("cwd: /example:list")
142 {
143 parser.changeNode({Scope::Relative, {{module_{"example"}, list_{"list"}}}});
144
145 SECTION("arg: <none>")
146 {
147 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
148 }
149
150 SECTION("arg: example:contInList")
151 {
152 lsArg = schemaPath_{Scope::Relative, {{container_{"contInList"}}}};
153 expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}, {container_{"contInList"}}}};
154 }
155
156 SECTION("arg: /example:a")
157 {
158 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
159 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
160 }
161
162 SECTION("arg: /example:list")
163 {
164 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
165 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
166 }
167
168 SECTION("arg example:*")
169 {
170 lsArg = module_{"example"};
171 expectedPath = module_{"example"};
172 }
173 }
174 ls_ ls;
175 ls.m_path = lsArg;
176 expectations.push_back(NAMED_REQUIRE_CALL(datastore, schema()).RETURN(schema));
177 expectations.push_back(NAMED_REQUIRE_CALL(*schema, availableNodes(expectedPath, Recursion::NonRecursive)).RETURN(std::set<ModuleNodePair>{}));
178 toInterpret.push_back(ls);
179 }
180
181 SECTION("get")
182 {
183 using namespace std::string_literals;
184 DatastoreAccess::Tree treeReturned;
185 decltype(get_::m_path) inputPath;
186 std::string expectedPathArg;
187
188 SECTION("paths")
189 {
190 SECTION("/")
191 {
192 expectedPathArg = "/";
193 }
194
195 SECTION("module")
196 {
197 inputPath = module_{"mod"};
198 expectedPathArg = "/mod:*";
199 }
200
201 SECTION("path to a leaf")
202 {
203 expectedPathArg = "/mod:myLeaf";
204 Scope scope;
205 SECTION("cwd: /")
206 {
207 SECTION("absolute")
208 {
209 scope = Scope::Absolute;
210 }
211
212 SECTION("relative")
213 {
214 scope = Scope::Relative;
215 }
216
217 inputPath = dataPath_{scope, {dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
218 }
219
220 SECTION("cwd: /mod:whatever")
221 {
222 parser.changeNode(dataPath_{Scope::Relative, {dataNode_{{"mod"}, container_{"whatever"}}}});
223 SECTION("absolute")
224 {
225 scope = Scope::Absolute;
226 inputPath = dataPath_{scope, {dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
227 }
228
229 SECTION("relative")
230 {
231 scope = Scope::Relative;
232 inputPath = dataPath_{scope, {dataNode_{nodeup_{}}, dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
233 }
234
235 }
236 }
237
238 SECTION("path to a list")
239 {
240 expectedPathArg = "/mod:myList[name='AHOJ']";
241 Scope scope;
242 SECTION("cwd: /")
243 {
244 SECTION("absolute")
245 {
246 scope = Scope::Absolute;
247 }
248
249 SECTION("relative")
250 {
251 scope = Scope::Relative;
252 }
253
254 inputPath = dataPath_{scope, {dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
255 }
256
257 SECTION("cwd: /mod:whatever")
258 {
259 parser.changeNode(dataPath_{Scope::Relative, {dataNode_{{"mod"}, container_{"whatever"}}}});
260 SECTION("absolute")
261 {
262 scope = Scope::Absolute;
263 inputPath = dataPath_{scope, {dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
264 }
265
266 SECTION("relative")
267 {
268 scope = Scope::Relative;
269 inputPath = dataPath_{scope, {dataNode_{nodeup_{}}, dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
270 }
271 }
272 }
273 }
274
275 SECTION("trees")
276 {
277 expectedPathArg = "/";
278 SECTION("no leaflists")
279 {
280 treeReturned = {
281 {"/mod:AHOJ", 30},
282 {"/mod:CAU", std::string{"AYYY"}},
283 {"/mod:CUS", bool{true}}
284 };
285 }
286
287 SECTION("leaflist at the beginning of a tree")
288 {
289 treeReturned = {
290 {"/mod:addresses", special_{SpecialValue::LeafList}},
291 {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
292 {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
293 {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
294 {"/mod:AHOJ", 30},
295 {"/mod:CAU", std::string{"AYYY"}},
296 };
297 }
298
299 SECTION("leaflist in the middle of a tree")
300 {
301 treeReturned = {
302 {"/mod:AHOJ", 30},
303 {"/mod:addresses", special_{SpecialValue::LeafList}},
304 {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
305 {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
306 {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
307 {"/mod:CAU", std::string{"AYYY"}},
308 };
309 }
310
311 SECTION("leaflist at the end of a tree")
312 {
313 treeReturned = {
314 {"/mod:AHOJ", 30},
315 {"/mod:CAU", std::string{"AYYY"}},
316 {"/mod:addresses", special_{SpecialValue::LeafList}},
317 {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
318 {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
319 {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
320 };
321 }
322 }
323
324 get_ getCmd;
325 getCmd.m_path = inputPath;
326 expectations.push_back(NAMED_REQUIRE_CALL(datastore, getItems(expectedPathArg)).RETURN(treeReturned));
327 toInterpret.push_back(getCmd);
328 }
329
330 SECTION("create/delete")
331 {
332 using namespace std::string_literals;
333 dataPath_ inputPath;
334
335 SECTION("list instance")
336 {
337 inputPath.m_nodes = {dataNode_{{"mod"}, listElement_{"department", {{"name", "engineering"s}}}}};
338 expectations.push_back(NAMED_REQUIRE_CALL(datastore, createListInstance("/mod:department[name='engineering']")));
339 expectations.push_back(NAMED_REQUIRE_CALL(datastore, deleteListInstance("/mod:department[name='engineering']")));
340 }
341
342 SECTION("leaflist instance")
343 {
344 inputPath.m_nodes = {dataNode_{{"mod"}, leafListElement_{"addresses", "127.0.0.1"s}}};
345 expectations.push_back(NAMED_REQUIRE_CALL(datastore, createLeafListInstance("/mod:addresses[.='127.0.0.1']")));
346 expectations.push_back(NAMED_REQUIRE_CALL(datastore, deleteLeafListInstance("/mod:addresses[.='127.0.0.1']")));
347 }
348
349 SECTION("presence container")
350 {
351 inputPath.m_nodes = {dataNode_{{"mod"}, container_{"pContainer"}}};
352 expectations.push_back(NAMED_REQUIRE_CALL(datastore, createPresenceContainer("/mod:pContainer")));
353 expectations.push_back(NAMED_REQUIRE_CALL(datastore, deletePresenceContainer("/mod:pContainer")));
354 }
355
356 create_ createCmd;
357 createCmd.m_path = inputPath;
358 delete_ deleteCmd;
359 deleteCmd.m_path = inputPath;
360 toInterpret.push_back(createCmd);
361 toInterpret.push_back(deleteCmd);
362 }
363
364 SECTION("commit")
365 {
366 expectations.push_back(NAMED_REQUIRE_CALL(datastore, commitChanges()));
367 toInterpret.push_back(commit_{});
368 }
369
370 SECTION("discard")
371 {
372 expectations.push_back(NAMED_REQUIRE_CALL(datastore, discardChanges()));
373 toInterpret.push_back(discard_{});
374 }
375
376
377 SECTION("set")
378 {
379 dataPath_ inputPath;
380 leaf_data_ inputData;
381
382 SECTION("setting identityRef without module") // The parser has to fill in the module
383 {
384 inputPath.m_nodes = {dataNode_{{"mod"}, leaf_{"animal"}}};
385 inputData = identityRef_{"Doge"};
386 expectations.push_back(NAMED_REQUIRE_CALL(datastore, setLeaf("/mod:animal", identityRef_{"mod", "Doge"})));
387 }
388
389
390 set_ setCmd;
391 setCmd.m_path = inputPath;
392 setCmd.m_data = inputData;
393 toInterpret.push_back(setCmd);
394 }
395
396
397 SECTION("copy")
398 {
399 SECTION("running -> startup")
400 {
401 expectations.push_back(NAMED_REQUIRE_CALL(datastore, copyConfig(Datastore::Running, Datastore::Startup)));
402 toInterpret.push_back(copy_{{}, Datastore::Running, Datastore::Startup});
403 }
404
405 SECTION("startup -> running")
406 {
407 expectations.push_back(NAMED_REQUIRE_CALL(datastore, copyConfig(Datastore::Startup, Datastore::Running)));
408 toInterpret.push_back(copy_{{}, Datastore::Startup, Datastore::Running});
409 }
410 }
411
412 for (const auto& command : toInterpret) {
413 boost::apply_visitor(Interpreter(parser, datastore), command);
414 }
415}