blob: ba35f89fda15a1e3cd7ff684da16f295772f00ea [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
Václav Kubernát5452ede2020-06-10 12:05:11 +02009#include "trompeloeil_doctest.hpp"
Václav Kubernátb4e5b182020-11-16 19:55:09 +010010#include <experimental/iterator>
Václav Kubernát5452ede2020-06-10 12:05:11 +020011#include "ast_commands.hpp"
Václav Kubernát5452ede2020-06-10 12:05:11 +020012#include "datastoreaccess_mock.hpp"
Václav Kubernátb4e5b182020-11-16 19:55:09 +010013#include "interpreter.hpp"
Václav Kubernát5452ede2020-06-10 12:05:11 +020014#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);
Václav Kubernátd8408e02020-12-02 05:13:27 +010035 IMPLEMENT_CONST_MOCK1(hasInputNodes);
Václav Kubernát5452ede2020-06-10 12:05:11 +020036};
37
38TEST_CASE("interpreter tests")
39{
40 auto schema = std::make_shared<MockSchema>();
41 Parser parser(schema);
Václav Kubernát48e9dfa2020-07-08 10:55:12 +020042 auto datastore = std::make_shared<MockDatastoreAccess>();
Václav Kubernáte7248b22020-06-26 15:38:59 +020043 auto input_datastore = std::make_shared<MockDatastoreAccess>();
44 auto createTemporaryDatastore = [input_datastore]([[maybe_unused]] const std::shared_ptr<DatastoreAccess>& datastore) {
45 return input_datastore;
46 };
47 ProxyDatastore proxyDatastore(datastore, createTemporaryDatastore);
Václav Kubernát5452ede2020-06-10 12:05:11 +020048 std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
49
50 std::vector<command_> toInterpret;
51
52 SECTION("ls")
53 {
54 boost::variant<dataPath_, schemaPath_, module_> expectedPath;
55 boost::optional<boost::variant<dataPath_, schemaPath_, module_>> lsArg;
56 SECTION("cwd: /")
57 {
58 SECTION("arg: <none>")
59 {
60 expectedPath = dataPath_{};
61 }
62
Václav Kubernát59be0de2020-06-15 13:58:45 +020063 SECTION("arg: ..")
64 {
65 lsArg = dataPath_{Scope::Relative, {dataNode_{nodeup_{}}}};
66 expectedPath = dataPath_{};
67 }
68
69 SECTION("arg: /..")
70 {
71 lsArg = dataPath_{Scope::Absolute, {dataNode_{nodeup_{}}}};
72 expectedPath = dataPath_{Scope::Absolute, {}};
73 }
74
75 SECTION("arg: /example:a/../example:a")
76 {
77 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}},
78 {nodeup_{}},
79 {module_{"example"}, container_{"a"}}}};
80 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
81 }
82
Václav Kubernát5452ede2020-06-10 12:05:11 +020083 SECTION("arg: example:a")
84 {
85 lsArg = dataPath_{Scope::Relative, {{module_{"example"}, container_{"a"}}}};
86 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
87 }
88
89 SECTION("arg: example:list")
90 {
91 lsArg = dataPath_{Scope::Relative, {{module_{"example"}, list_{"list"}}}};
92 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
93 }
94
95 SECTION("arg: /example:a")
96 {
97 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
98 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
99 }
100
101 SECTION("arg: /example:list")
102 {
103 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
104 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
105 }
106
107 SECTION("arg example:*")
108 {
109 lsArg = module_{"example"};
110 expectedPath = module_{"example"};
111 }
112 }
113
114 SECTION("cwd: /example:a")
115 {
116 parser.changeNode({Scope::Relative, {{module_{"example"}, container_{"a"}}}});
117
118 SECTION("arg: <none>")
119 {
120 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
121 }
122
123 SECTION("arg: example:a2")
124 {
125 lsArg = dataPath_{Scope::Relative, {{container_{"a2"}}}};
126 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}, {container_{"a2"}}}};
127 }
128
129 SECTION("arg: example:listInCont")
130 {
131 lsArg = dataPath_{Scope::Relative, {{list_{"listInCont"}}}};
132 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}, {list_{"listInCont"}}}};
133 }
134
135 SECTION("arg: /example:a")
136 {
137 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
138 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
139 }
140
141 SECTION("arg: /example:list")
142 {
143 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
144 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
145 }
146 }
147 SECTION("cwd: /example:list")
148 {
149 parser.changeNode({Scope::Relative, {{module_{"example"}, list_{"list"}}}});
150
151 SECTION("arg: <none>")
152 {
153 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
154 }
155
156 SECTION("arg: example:contInList")
157 {
158 lsArg = schemaPath_{Scope::Relative, {{container_{"contInList"}}}};
159 expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}, {container_{"contInList"}}}};
160 }
161
162 SECTION("arg: /example:a")
163 {
164 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
165 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
166 }
167
168 SECTION("arg: /example:list")
169 {
170 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
171 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
172 }
173
174 SECTION("arg example:*")
175 {
176 lsArg = module_{"example"};
177 expectedPath = module_{"example"};
178 }
179 }
180 ls_ ls;
181 ls.m_path = lsArg;
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200182 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, schema()).RETURN(schema));
Václav Kubernátfaacd022020-07-08 16:44:38 +0200183 expectations.emplace_back(NAMED_REQUIRE_CALL(*schema, availableNodes(expectedPath, Recursion::NonRecursive)).RETURN(std::set<ModuleNodePair>{}));
184 toInterpret.emplace_back(ls);
Václav Kubernát5452ede2020-06-10 12:05:11 +0200185 }
186
187 SECTION("get")
188 {
189 using namespace std::string_literals;
190 DatastoreAccess::Tree treeReturned;
191 decltype(get_::m_path) inputPath;
192 std::string expectedPathArg;
193
194 SECTION("paths")
195 {
196 SECTION("/")
197 {
198 expectedPathArg = "/";
199 }
200
201 SECTION("module")
202 {
203 inputPath = module_{"mod"};
204 expectedPathArg = "/mod:*";
205 }
206
207 SECTION("path to a leaf")
208 {
209 expectedPathArg = "/mod:myLeaf";
210 Scope scope;
211 SECTION("cwd: /")
212 {
213 SECTION("absolute")
214 {
215 scope = Scope::Absolute;
216 }
217
218 SECTION("relative")
219 {
220 scope = Scope::Relative;
221 }
222
223 inputPath = dataPath_{scope, {dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
224 }
225
226 SECTION("cwd: /mod:whatever")
227 {
228 parser.changeNode(dataPath_{Scope::Relative, {dataNode_{{"mod"}, container_{"whatever"}}}});
229 SECTION("absolute")
230 {
231 scope = Scope::Absolute;
232 inputPath = dataPath_{scope, {dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
233 }
234
235 SECTION("relative")
236 {
237 scope = Scope::Relative;
238 inputPath = dataPath_{scope, {dataNode_{nodeup_{}}, dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
239 }
Václav Kubernát5452ede2020-06-10 12:05:11 +0200240 }
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
Václav Kubernát218ee7b2022-03-30 22:35:26 +0200455 SECTION("can't leave the context with cd")
456 {
457 REQUIRE_THROWS(boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{cd_{{}, dataPath_{Scope::Absolute, {dataNode_{module_{{"example"}}, container_{"somewhereElse"}}}}}}));
458 REQUIRE_THROWS(boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{cd_{{}, dataPath_{Scope::Relative, {dataNode_{nodeup_{}}}}}}));
459 boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{cancel_{}});
460 }
461
Václav Kubernáte7248b22020-06-26 15:38:59 +0200462 SECTION("exec")
463 {
464 REQUIRE_CALL(*input_datastore, getItems("/")).RETURN(DatastoreAccess::Tree{});
Václav Kubernátb3960f82020-12-01 03:21:48 +0100465 REQUIRE_CALL(*datastore, execute("/example:launch-nukes", DatastoreAccess::Tree{})).RETURN(DatastoreAccess::Tree{});
Václav Kubernáte7248b22020-06-26 15:38:59 +0200466 boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{exec_{}});
467 }
468
469 SECTION("cancel")
470 {
471 boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{cancel_{}});
472 }
473
474 REQUIRE(parser.currentPath() == dataPath_{});
475 }
476}