blob: 966855d13db860cb53e984f3489bc6dae6e75ffe [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);
Václav Kubernát48e9dfa2020-07-08 10:55:12 +020041 auto datastore = std::make_shared<MockDatastoreAccess>();
Václav Kubernáte7248b22020-06-26 15:38:59 +020042 auto input_datastore = std::make_shared<MockDatastoreAccess>();
43 auto createTemporaryDatastore = [input_datastore]([[maybe_unused]] const std::shared_ptr<DatastoreAccess>& datastore) {
44 return input_datastore;
45 };
46 ProxyDatastore proxyDatastore(datastore, createTemporaryDatastore);
Václav Kubernát5452ede2020-06-10 12:05:11 +020047 std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
48
49 std::vector<command_> toInterpret;
50
51 SECTION("ls")
52 {
53 boost::variant<dataPath_, schemaPath_, module_> expectedPath;
54 boost::optional<boost::variant<dataPath_, schemaPath_, module_>> lsArg;
55 SECTION("cwd: /")
56 {
57 SECTION("arg: <none>")
58 {
59 expectedPath = dataPath_{};
60 }
61
Václav Kubernát59be0de2020-06-15 13:58:45 +020062 SECTION("arg: ..")
63 {
64 lsArg = dataPath_{Scope::Relative, {dataNode_{nodeup_{}}}};
65 expectedPath = dataPath_{};
66 }
67
68 SECTION("arg: /..")
69 {
70 lsArg = dataPath_{Scope::Absolute, {dataNode_{nodeup_{}}}};
71 expectedPath = dataPath_{Scope::Absolute, {}};
72 }
73
74 SECTION("arg: /example:a/../example:a")
75 {
76 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}},
77 {nodeup_{}},
78 {module_{"example"}, container_{"a"}}}};
79 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
80 }
81
Václav Kubernát5452ede2020-06-10 12:05:11 +020082 SECTION("arg: example:a")
83 {
84 lsArg = dataPath_{Scope::Relative, {{module_{"example"}, container_{"a"}}}};
85 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
86 }
87
88 SECTION("arg: example:list")
89 {
90 lsArg = dataPath_{Scope::Relative, {{module_{"example"}, list_{"list"}}}};
91 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
92 }
93
94 SECTION("arg: /example:a")
95 {
96 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
97 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
98 }
99
100 SECTION("arg: /example:list")
101 {
102 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
103 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
104 }
105
106 SECTION("arg example:*")
107 {
108 lsArg = module_{"example"};
109 expectedPath = module_{"example"};
110 }
111 }
112
113 SECTION("cwd: /example:a")
114 {
115 parser.changeNode({Scope::Relative, {{module_{"example"}, container_{"a"}}}});
116
117 SECTION("arg: <none>")
118 {
119 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
120 }
121
122 SECTION("arg: example:a2")
123 {
124 lsArg = dataPath_{Scope::Relative, {{container_{"a2"}}}};
125 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}, {container_{"a2"}}}};
126 }
127
128 SECTION("arg: example:listInCont")
129 {
130 lsArg = dataPath_{Scope::Relative, {{list_{"listInCont"}}}};
131 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}, {list_{"listInCont"}}}};
132 }
133
134 SECTION("arg: /example:a")
135 {
136 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
137 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
138 }
139
140 SECTION("arg: /example:list")
141 {
142 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
143 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
144 }
145 }
146 SECTION("cwd: /example:list")
147 {
148 parser.changeNode({Scope::Relative, {{module_{"example"}, list_{"list"}}}});
149
150 SECTION("arg: <none>")
151 {
152 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
153 }
154
155 SECTION("arg: example:contInList")
156 {
157 lsArg = schemaPath_{Scope::Relative, {{container_{"contInList"}}}};
158 expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}, {container_{"contInList"}}}};
159 }
160
161 SECTION("arg: /example:a")
162 {
163 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
164 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
165 }
166
167 SECTION("arg: /example:list")
168 {
169 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
170 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
171 }
172
173 SECTION("arg example:*")
174 {
175 lsArg = module_{"example"};
176 expectedPath = module_{"example"};
177 }
178 }
179 ls_ ls;
180 ls.m_path = lsArg;
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200181 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, schema()).RETURN(schema));
Václav Kubernátfaacd022020-07-08 16:44:38 +0200182 expectations.emplace_back(NAMED_REQUIRE_CALL(*schema, availableNodes(expectedPath, Recursion::NonRecursive)).RETURN(std::set<ModuleNodePair>{}));
183 toInterpret.emplace_back(ls);
Václav Kubernát5452ede2020-06-10 12:05:11 +0200184 }
185
186 SECTION("get")
187 {
188 using namespace std::string_literals;
189 DatastoreAccess::Tree treeReturned;
190 decltype(get_::m_path) inputPath;
191 std::string expectedPathArg;
192
193 SECTION("paths")
194 {
195 SECTION("/")
196 {
197 expectedPathArg = "/";
198 }
199
200 SECTION("module")
201 {
202 inputPath = module_{"mod"};
203 expectedPathArg = "/mod:*";
204 }
205
206 SECTION("path to a leaf")
207 {
208 expectedPathArg = "/mod:myLeaf";
209 Scope scope;
210 SECTION("cwd: /")
211 {
212 SECTION("absolute")
213 {
214 scope = Scope::Absolute;
215 }
216
217 SECTION("relative")
218 {
219 scope = Scope::Relative;
220 }
221
222 inputPath = dataPath_{scope, {dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
223 }
224
225 SECTION("cwd: /mod:whatever")
226 {
227 parser.changeNode(dataPath_{Scope::Relative, {dataNode_{{"mod"}, container_{"whatever"}}}});
228 SECTION("absolute")
229 {
230 scope = Scope::Absolute;
231 inputPath = dataPath_{scope, {dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
232 }
233
234 SECTION("relative")
235 {
236 scope = Scope::Relative;
237 inputPath = dataPath_{scope, {dataNode_{nodeup_{}}, dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
238 }
239
240 }
241 }
242
243 SECTION("path to a list")
244 {
245 expectedPathArg = "/mod:myList[name='AHOJ']";
246 Scope scope;
247 SECTION("cwd: /")
248 {
249 SECTION("absolute")
250 {
251 scope = Scope::Absolute;
252 }
253
254 SECTION("relative")
255 {
256 scope = Scope::Relative;
257 }
258
259 inputPath = dataPath_{scope, {dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
260 }
261
262 SECTION("cwd: /mod:whatever")
263 {
264 parser.changeNode(dataPath_{Scope::Relative, {dataNode_{{"mod"}, container_{"whatever"}}}});
265 SECTION("absolute")
266 {
267 scope = Scope::Absolute;
268 inputPath = dataPath_{scope, {dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
269 }
270
271 SECTION("relative")
272 {
273 scope = Scope::Relative;
274 inputPath = dataPath_{scope, {dataNode_{nodeup_{}}, dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
275 }
276 }
277 }
278 }
279
280 SECTION("trees")
281 {
282 expectedPathArg = "/";
283 SECTION("no leaflists")
284 {
285 treeReturned = {
286 {"/mod:AHOJ", 30},
287 {"/mod:CAU", std::string{"AYYY"}},
288 {"/mod:CUS", bool{true}}
289 };
290 }
291
292 SECTION("leaflist at the beginning of a tree")
293 {
294 treeReturned = {
295 {"/mod:addresses", special_{SpecialValue::LeafList}},
296 {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
297 {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
298 {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
299 {"/mod:AHOJ", 30},
300 {"/mod:CAU", std::string{"AYYY"}},
301 };
302 }
303
304 SECTION("leaflist in the middle of a tree")
305 {
306 treeReturned = {
307 {"/mod:AHOJ", 30},
308 {"/mod:addresses", special_{SpecialValue::LeafList}},
309 {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
310 {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
311 {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
312 {"/mod:CAU", std::string{"AYYY"}},
313 };
314 }
315
316 SECTION("leaflist at the end of a tree")
317 {
318 treeReturned = {
319 {"/mod:AHOJ", 30},
320 {"/mod:CAU", std::string{"AYYY"}},
321 {"/mod:addresses", special_{SpecialValue::LeafList}},
322 {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
323 {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
324 {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
325 };
326 }
327 }
328
329 get_ getCmd;
330 getCmd.m_path = inputPath;
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200331 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, getItems(expectedPathArg)).RETURN(treeReturned));
Václav Kubernátfaacd022020-07-08 16:44:38 +0200332 toInterpret.emplace_back(getCmd);
Václav Kubernát5452ede2020-06-10 12:05:11 +0200333 }
334
335 SECTION("create/delete")
336 {
337 using namespace std::string_literals;
338 dataPath_ inputPath;
339
340 SECTION("list instance")
341 {
342 inputPath.m_nodes = {dataNode_{{"mod"}, listElement_{"department", {{"name", "engineering"s}}}}};
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200343 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, createItem("/mod:department[name='engineering']")));
344 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, deleteItem("/mod:department[name='engineering']")));
Václav Kubernát5452ede2020-06-10 12:05:11 +0200345 }
346
347 SECTION("leaflist instance")
348 {
349 inputPath.m_nodes = {dataNode_{{"mod"}, leafListElement_{"addresses", "127.0.0.1"s}}};
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200350 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, createItem("/mod:addresses[.='127.0.0.1']")));
351 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, deleteItem("/mod:addresses[.='127.0.0.1']")));
Václav Kubernát5452ede2020-06-10 12:05:11 +0200352 }
353
354 SECTION("presence container")
355 {
356 inputPath.m_nodes = {dataNode_{{"mod"}, container_{"pContainer"}}};
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200357 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, createItem("/mod:pContainer")));
358 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, deleteItem("/mod:pContainer")));
Václav Kubernát5452ede2020-06-10 12:05:11 +0200359 }
360
361 create_ createCmd;
362 createCmd.m_path = inputPath;
363 delete_ deleteCmd;
364 deleteCmd.m_path = inputPath;
Václav Kubernátfaacd022020-07-08 16:44:38 +0200365 toInterpret.emplace_back(createCmd);
366 toInterpret.emplace_back(deleteCmd);
Václav Kubernát5452ede2020-06-10 12:05:11 +0200367 }
368
Jan Kundrátb7206ad2020-06-18 21:08:14 +0200369 SECTION("delete a leaf")
370 {
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200371 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, deleteItem("/mod:someLeaf")));
Jan Kundrátb7206ad2020-06-18 21:08:14 +0200372 delete_ deleteCmd;
373 deleteCmd.m_path = {Scope::Absolute, {dataNode_{{"mod"}, leaf_{"someLeaf"}}, }};
Václav Kubernátfaacd022020-07-08 16:44:38 +0200374 toInterpret.emplace_back(deleteCmd);
Jan Kundrátb7206ad2020-06-18 21:08:14 +0200375 }
376
Václav Kubernát5452ede2020-06-10 12:05:11 +0200377 SECTION("commit")
378 {
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200379 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, commitChanges()));
Václav Kubernátfaacd022020-07-08 16:44:38 +0200380 toInterpret.emplace_back(commit_{});
Václav Kubernát5452ede2020-06-10 12:05:11 +0200381 }
382
383 SECTION("discard")
384 {
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200385 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, discardChanges()));
Václav Kubernátfaacd022020-07-08 16:44:38 +0200386 toInterpret.emplace_back(discard_{});
Václav Kubernát5452ede2020-06-10 12:05:11 +0200387 }
388
389
390 SECTION("set")
391 {
392 dataPath_ inputPath;
393 leaf_data_ inputData;
394
395 SECTION("setting identityRef without module") // The parser has to fill in the module
396 {
397 inputPath.m_nodes = {dataNode_{{"mod"}, leaf_{"animal"}}};
398 inputData = identityRef_{"Doge"};
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200399 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, setLeaf("/mod:animal", identityRef_{"mod", "Doge"})));
Václav Kubernát5452ede2020-06-10 12:05:11 +0200400 }
401
402
403 set_ setCmd;
404 setCmd.m_path = inputPath;
405 setCmd.m_data = inputData;
Václav Kubernátfaacd022020-07-08 16:44:38 +0200406 toInterpret.emplace_back(setCmd);
Václav Kubernát5452ede2020-06-10 12:05:11 +0200407 }
408
409
410 SECTION("copy")
411 {
412 SECTION("running -> startup")
413 {
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200414 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, copyConfig(Datastore::Running, Datastore::Startup)));
Václav Kubernátfaacd022020-07-08 16:44:38 +0200415 toInterpret.emplace_back(copy_{{}, Datastore::Running, Datastore::Startup});
Václav Kubernát5452ede2020-06-10 12:05:11 +0200416 }
417
418 SECTION("startup -> running")
419 {
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200420 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, copyConfig(Datastore::Startup, Datastore::Running)));
Václav Kubernátfaacd022020-07-08 16:44:38 +0200421 toInterpret.emplace_back(copy_{{}, Datastore::Startup, Datastore::Running});
Václav Kubernát5452ede2020-06-10 12:05:11 +0200422 }
423 }
424
425 for (const auto& command : toInterpret) {
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200426 boost::apply_visitor(Interpreter(parser, proxyDatastore), command);
Václav Kubernát5452ede2020-06-10 12:05:11 +0200427 }
428}
Václav Kubernáte7248b22020-06-26 15:38:59 +0200429
430TEST_CASE("rpc")
431{
432 auto schema = std::make_shared<MockSchema>();
433 Parser parser(schema);
434 auto datastore = std::make_shared<MockDatastoreAccess>();
435 auto input_datastore = std::make_shared<MockDatastoreAccess>();
436 auto createTemporaryDatastore = [input_datastore]([[maybe_unused]] const std::shared_ptr<DatastoreAccess>& datastore) {
437 return input_datastore;
438 };
439 ProxyDatastore proxyDatastore(datastore, createTemporaryDatastore);
440
441 SECTION("entering/leaving rpc context")
442 {
443 dataPath_ rpcPath;
444 rpcPath.pushFragment({{"example"}, rpcNode_{"launch-nukes"}});
Václav Kubernátaa4250a2020-07-22 00:02:23 +0200445 prepare_ prepareCmd;
446 prepareCmd.m_path = rpcPath;
Václav Kubernáte7248b22020-06-26 15:38:59 +0200447
448 {
449 REQUIRE_CALL(*input_datastore, createItem("/example:launch-nukes"));
Václav Kubernátaa4250a2020-07-22 00:02:23 +0200450 boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{prepareCmd});
Václav Kubernáte7248b22020-06-26 15:38:59 +0200451 }
452
453 REQUIRE(parser.currentPath() == rpcPath);
454
455 SECTION("exec")
456 {
457 REQUIRE_CALL(*input_datastore, getItems("/")).RETURN(DatastoreAccess::Tree{});
458 REQUIRE_CALL(*datastore, executeRpc("/example:launch-nukes", DatastoreAccess::Tree{})).RETURN(DatastoreAccess::Tree{});
459 boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{exec_{}});
460 }
461
462 SECTION("cancel")
463 {
464 boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{cancel_{}});
465 }
466
467 REQUIRE(parser.currentPath() == dataPath_{});
468 }
469}