blob: d57ad64cce4a026fcf89767332870082c5c96587 [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);
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 }
Václav Kubernát5452ede2020-06-10 12:05:11 +0200239 }
240 }
241
242 SECTION("path to a list")
243 {
244 expectedPathArg = "/mod:myList[name='AHOJ']";
245 Scope scope;
246 SECTION("cwd: /")
247 {
248 SECTION("absolute")
249 {
250 scope = Scope::Absolute;
251 }
252
253 SECTION("relative")
254 {
255 scope = Scope::Relative;
256 }
257
258 inputPath = dataPath_{scope, {dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
259 }
260
261 SECTION("cwd: /mod:whatever")
262 {
263 parser.changeNode(dataPath_{Scope::Relative, {dataNode_{{"mod"}, container_{"whatever"}}}});
264 SECTION("absolute")
265 {
266 scope = Scope::Absolute;
267 inputPath = dataPath_{scope, {dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
268 }
269
270 SECTION("relative")
271 {
272 scope = Scope::Relative;
273 inputPath = dataPath_{scope, {dataNode_{nodeup_{}}, dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
274 }
275 }
276 }
277 }
278
279 SECTION("trees")
280 {
281 expectedPathArg = "/";
282 SECTION("no leaflists")
283 {
284 treeReturned = {
285 {"/mod:AHOJ", 30},
286 {"/mod:CAU", std::string{"AYYY"}},
287 {"/mod:CUS", bool{true}}
288 };
289 }
290
291 SECTION("leaflist at the beginning of a tree")
292 {
293 treeReturned = {
294 {"/mod:addresses", special_{SpecialValue::LeafList}},
295 {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
296 {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
297 {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
298 {"/mod:AHOJ", 30},
299 {"/mod:CAU", std::string{"AYYY"}},
300 };
301 }
302
303 SECTION("leaflist in the middle of a tree")
304 {
305 treeReturned = {
306 {"/mod:AHOJ", 30},
307 {"/mod:addresses", special_{SpecialValue::LeafList}},
308 {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
309 {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
310 {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
311 {"/mod:CAU", std::string{"AYYY"}},
312 };
313 }
314
315 SECTION("leaflist at the end of a tree")
316 {
317 treeReturned = {
318 {"/mod:AHOJ", 30},
319 {"/mod:CAU", std::string{"AYYY"}},
320 {"/mod:addresses", special_{SpecialValue::LeafList}},
321 {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
322 {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
323 {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
324 };
325 }
326 }
327
328 get_ getCmd;
329 getCmd.m_path = inputPath;
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200330 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, getItems(expectedPathArg)).RETURN(treeReturned));
Václav Kubernátfaacd022020-07-08 16:44:38 +0200331 toInterpret.emplace_back(getCmd);
Václav Kubernát5452ede2020-06-10 12:05:11 +0200332 }
333
334 SECTION("create/delete")
335 {
336 using namespace std::string_literals;
337 dataPath_ inputPath;
338
339 SECTION("list instance")
340 {
341 inputPath.m_nodes = {dataNode_{{"mod"}, listElement_{"department", {{"name", "engineering"s}}}}};
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200342 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, createItem("/mod:department[name='engineering']")));
343 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, deleteItem("/mod:department[name='engineering']")));
Václav Kubernát5452ede2020-06-10 12:05:11 +0200344 }
345
346 SECTION("leaflist instance")
347 {
348 inputPath.m_nodes = {dataNode_{{"mod"}, leafListElement_{"addresses", "127.0.0.1"s}}};
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200349 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, createItem("/mod:addresses[.='127.0.0.1']")));
350 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, deleteItem("/mod:addresses[.='127.0.0.1']")));
Václav Kubernát5452ede2020-06-10 12:05:11 +0200351 }
352
353 SECTION("presence container")
354 {
355 inputPath.m_nodes = {dataNode_{{"mod"}, container_{"pContainer"}}};
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200356 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, createItem("/mod:pContainer")));
357 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, deleteItem("/mod:pContainer")));
Václav Kubernát5452ede2020-06-10 12:05:11 +0200358 }
359
360 create_ createCmd;
361 createCmd.m_path = inputPath;
362 delete_ deleteCmd;
363 deleteCmd.m_path = inputPath;
Václav Kubernátfaacd022020-07-08 16:44:38 +0200364 toInterpret.emplace_back(createCmd);
365 toInterpret.emplace_back(deleteCmd);
Václav Kubernát5452ede2020-06-10 12:05:11 +0200366 }
367
Jan Kundrátb7206ad2020-06-18 21:08:14 +0200368 SECTION("delete a leaf")
369 {
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200370 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, deleteItem("/mod:someLeaf")));
Jan Kundrátb7206ad2020-06-18 21:08:14 +0200371 delete_ deleteCmd;
372 deleteCmd.m_path = {Scope::Absolute, {dataNode_{{"mod"}, leaf_{"someLeaf"}}, }};
Václav Kubernátfaacd022020-07-08 16:44:38 +0200373 toInterpret.emplace_back(deleteCmd);
Jan Kundrátb7206ad2020-06-18 21:08:14 +0200374 }
375
Václav Kubernát5452ede2020-06-10 12:05:11 +0200376 SECTION("commit")
377 {
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200378 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, commitChanges()));
Václav Kubernátfaacd022020-07-08 16:44:38 +0200379 toInterpret.emplace_back(commit_{});
Václav Kubernát5452ede2020-06-10 12:05:11 +0200380 }
381
382 SECTION("discard")
383 {
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200384 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, discardChanges()));
Václav Kubernátfaacd022020-07-08 16:44:38 +0200385 toInterpret.emplace_back(discard_{});
Václav Kubernát5452ede2020-06-10 12:05:11 +0200386 }
387
388
389 SECTION("set")
390 {
391 dataPath_ inputPath;
392 leaf_data_ inputData;
393
394 SECTION("setting identityRef without module") // The parser has to fill in the module
395 {
396 inputPath.m_nodes = {dataNode_{{"mod"}, leaf_{"animal"}}};
397 inputData = identityRef_{"Doge"};
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200398 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, setLeaf("/mod:animal", identityRef_{"mod", "Doge"})));
Václav Kubernát5452ede2020-06-10 12:05:11 +0200399 }
400
401
402 set_ setCmd;
403 setCmd.m_path = inputPath;
404 setCmd.m_data = inputData;
Václav Kubernátfaacd022020-07-08 16:44:38 +0200405 toInterpret.emplace_back(setCmd);
Václav Kubernát5452ede2020-06-10 12:05:11 +0200406 }
407
408
409 SECTION("copy")
410 {
411 SECTION("running -> startup")
412 {
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200413 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, copyConfig(Datastore::Running, Datastore::Startup)));
Václav Kubernátfaacd022020-07-08 16:44:38 +0200414 toInterpret.emplace_back(copy_{{}, Datastore::Running, Datastore::Startup});
Václav Kubernát5452ede2020-06-10 12:05:11 +0200415 }
416
417 SECTION("startup -> running")
418 {
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200419 expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, copyConfig(Datastore::Startup, Datastore::Running)));
Václav Kubernátfaacd022020-07-08 16:44:38 +0200420 toInterpret.emplace_back(copy_{{}, Datastore::Startup, Datastore::Running});
Václav Kubernát5452ede2020-06-10 12:05:11 +0200421 }
422 }
423
424 for (const auto& command : toInterpret) {
Václav Kubernát48e9dfa2020-07-08 10:55:12 +0200425 boost::apply_visitor(Interpreter(parser, proxyDatastore), command);
Václav Kubernát5452ede2020-06-10 12:05:11 +0200426 }
427}
Václav Kubernáte7248b22020-06-26 15:38:59 +0200428
429TEST_CASE("rpc")
430{
431 auto schema = std::make_shared<MockSchema>();
432 Parser parser(schema);
433 auto datastore = std::make_shared<MockDatastoreAccess>();
434 auto input_datastore = std::make_shared<MockDatastoreAccess>();
435 auto createTemporaryDatastore = [input_datastore]([[maybe_unused]] const std::shared_ptr<DatastoreAccess>& datastore) {
436 return input_datastore;
437 };
438 ProxyDatastore proxyDatastore(datastore, createTemporaryDatastore);
439
440 SECTION("entering/leaving rpc context")
441 {
442 dataPath_ rpcPath;
443 rpcPath.pushFragment({{"example"}, rpcNode_{"launch-nukes"}});
Václav Kubernátaa4250a2020-07-22 00:02:23 +0200444 prepare_ prepareCmd;
445 prepareCmd.m_path = rpcPath;
Václav Kubernáte7248b22020-06-26 15:38:59 +0200446
447 {
448 REQUIRE_CALL(*input_datastore, createItem("/example:launch-nukes"));
Václav Kubernátaa4250a2020-07-22 00:02:23 +0200449 boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{prepareCmd});
Václav Kubernáte7248b22020-06-26 15:38:59 +0200450 }
451
452 REQUIRE(parser.currentPath() == rpcPath);
453
454 SECTION("exec")
455 {
456 REQUIRE_CALL(*input_datastore, getItems("/")).RETURN(DatastoreAccess::Tree{});
457 REQUIRE_CALL(*datastore, executeRpc("/example:launch-nukes", DatastoreAccess::Tree{})).RETURN(DatastoreAccess::Tree{});
458 boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{exec_{}});
459 }
460
461 SECTION("cancel")
462 {
463 boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{cancel_{}});
464 }
465
466 REQUIRE(parser.currentPath() == dataPath_{});
467 }
468}