blob: b296ca1a9e2897351e0a895827c032f58f9a66c2 [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
57 SECTION("arg: example:a")
58 {
59 lsArg = dataPath_{Scope::Relative, {{module_{"example"}, container_{"a"}}}};
60 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
61 }
62
63 SECTION("arg: example:list")
64 {
65 lsArg = dataPath_{Scope::Relative, {{module_{"example"}, list_{"list"}}}};
66 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
67 }
68
69 SECTION("arg: /example:a")
70 {
71 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
72 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
73 }
74
75 SECTION("arg: /example:list")
76 {
77 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
78 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
79 }
80
81 SECTION("arg example:*")
82 {
83 lsArg = module_{"example"};
84 expectedPath = module_{"example"};
85 }
86 }
87
88 SECTION("cwd: /example:a")
89 {
90 parser.changeNode({Scope::Relative, {{module_{"example"}, container_{"a"}}}});
91
92 SECTION("arg: <none>")
93 {
94 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
95 }
96
97 SECTION("arg: example:a2")
98 {
99 lsArg = dataPath_{Scope::Relative, {{container_{"a2"}}}};
100 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}, {container_{"a2"}}}};
101 }
102
103 SECTION("arg: example:listInCont")
104 {
105 lsArg = dataPath_{Scope::Relative, {{list_{"listInCont"}}}};
106 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}, {list_{"listInCont"}}}};
107 }
108
109 SECTION("arg: /example:a")
110 {
111 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
112 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
113 }
114
115 SECTION("arg: /example:list")
116 {
117 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
118 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
119 }
120 }
121 SECTION("cwd: /example:list")
122 {
123 parser.changeNode({Scope::Relative, {{module_{"example"}, list_{"list"}}}});
124
125 SECTION("arg: <none>")
126 {
127 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
128 }
129
130 SECTION("arg: example:contInList")
131 {
132 lsArg = schemaPath_{Scope::Relative, {{container_{"contInList"}}}};
133 expectedPath = schemaPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}, {container_{"contInList"}}}};
134 }
135
136 SECTION("arg: /example:a")
137 {
138 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
139 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, container_{"a"}}}};
140 }
141
142 SECTION("arg: /example:list")
143 {
144 lsArg = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
145 expectedPath = dataPath_{Scope::Absolute, {{module_{"example"}, list_{"list"}}}};
146 }
147
148 SECTION("arg example:*")
149 {
150 lsArg = module_{"example"};
151 expectedPath = module_{"example"};
152 }
153 }
154 ls_ ls;
155 ls.m_path = lsArg;
156 expectations.push_back(NAMED_REQUIRE_CALL(datastore, schema()).RETURN(schema));
157 expectations.push_back(NAMED_REQUIRE_CALL(*schema, availableNodes(expectedPath, Recursion::NonRecursive)).RETURN(std::set<ModuleNodePair>{}));
158 toInterpret.push_back(ls);
159 }
160
161 SECTION("get")
162 {
163 using namespace std::string_literals;
164 DatastoreAccess::Tree treeReturned;
165 decltype(get_::m_path) inputPath;
166 std::string expectedPathArg;
167
168 SECTION("paths")
169 {
170 SECTION("/")
171 {
172 expectedPathArg = "/";
173 }
174
175 SECTION("module")
176 {
177 inputPath = module_{"mod"};
178 expectedPathArg = "/mod:*";
179 }
180
181 SECTION("path to a leaf")
182 {
183 expectedPathArg = "/mod:myLeaf";
184 Scope scope;
185 SECTION("cwd: /")
186 {
187 SECTION("absolute")
188 {
189 scope = Scope::Absolute;
190 }
191
192 SECTION("relative")
193 {
194 scope = Scope::Relative;
195 }
196
197 inputPath = dataPath_{scope, {dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
198 }
199
200 SECTION("cwd: /mod:whatever")
201 {
202 parser.changeNode(dataPath_{Scope::Relative, {dataNode_{{"mod"}, container_{"whatever"}}}});
203 SECTION("absolute")
204 {
205 scope = Scope::Absolute;
206 inputPath = dataPath_{scope, {dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
207 }
208
209 SECTION("relative")
210 {
211 scope = Scope::Relative;
212 inputPath = dataPath_{scope, {dataNode_{nodeup_{}}, dataNode_{{"mod"}, leaf_{"myLeaf"}}}};
213 }
214
215 }
216 }
217
218 SECTION("path to a list")
219 {
220 expectedPathArg = "/mod:myList[name='AHOJ']";
221 Scope scope;
222 SECTION("cwd: /")
223 {
224 SECTION("absolute")
225 {
226 scope = Scope::Absolute;
227 }
228
229 SECTION("relative")
230 {
231 scope = Scope::Relative;
232 }
233
234 inputPath = dataPath_{scope, {dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
235 }
236
237 SECTION("cwd: /mod:whatever")
238 {
239 parser.changeNode(dataPath_{Scope::Relative, {dataNode_{{"mod"}, container_{"whatever"}}}});
240 SECTION("absolute")
241 {
242 scope = Scope::Absolute;
243 inputPath = dataPath_{scope, {dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
244 }
245
246 SECTION("relative")
247 {
248 scope = Scope::Relative;
249 inputPath = dataPath_{scope, {dataNode_{nodeup_{}}, dataNode_{{"mod"}, listElement_{"myList", {{"name", "AHOJ"s}}}}}};
250 }
251 }
252 }
253 }
254
255 SECTION("trees")
256 {
257 expectedPathArg = "/";
258 SECTION("no leaflists")
259 {
260 treeReturned = {
261 {"/mod:AHOJ", 30},
262 {"/mod:CAU", std::string{"AYYY"}},
263 {"/mod:CUS", bool{true}}
264 };
265 }
266
267 SECTION("leaflist at the beginning of a tree")
268 {
269 treeReturned = {
270 {"/mod:addresses", special_{SpecialValue::LeafList}},
271 {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
272 {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
273 {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
274 {"/mod:AHOJ", 30},
275 {"/mod:CAU", std::string{"AYYY"}},
276 };
277 }
278
279 SECTION("leaflist in the middle of a tree")
280 {
281 treeReturned = {
282 {"/mod:AHOJ", 30},
283 {"/mod:addresses", special_{SpecialValue::LeafList}},
284 {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
285 {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
286 {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
287 {"/mod:CAU", std::string{"AYYY"}},
288 };
289 }
290
291 SECTION("leaflist at the end of a tree")
292 {
293 treeReturned = {
294 {"/mod:AHOJ", 30},
295 {"/mod:CAU", std::string{"AYYY"}},
296 {"/mod:addresses", special_{SpecialValue::LeafList}},
297 {"/mod:addresses[.='0.0.0.0']", std::string{"0.0.0.0"}},
298 {"/mod:addresses[.='127.0.0.1']", std::string{"127.0.0.1"}},
299 {"/mod:addresses[.='192.168.0.1']", std::string{"192.168.0.1"}},
300 };
301 }
302 }
303
304 get_ getCmd;
305 getCmd.m_path = inputPath;
306 expectations.push_back(NAMED_REQUIRE_CALL(datastore, getItems(expectedPathArg)).RETURN(treeReturned));
307 toInterpret.push_back(getCmd);
308 }
309
310 SECTION("create/delete")
311 {
312 using namespace std::string_literals;
313 dataPath_ inputPath;
314
315 SECTION("list instance")
316 {
317 inputPath.m_nodes = {dataNode_{{"mod"}, listElement_{"department", {{"name", "engineering"s}}}}};
318 expectations.push_back(NAMED_REQUIRE_CALL(datastore, createListInstance("/mod:department[name='engineering']")));
319 expectations.push_back(NAMED_REQUIRE_CALL(datastore, deleteListInstance("/mod:department[name='engineering']")));
320 }
321
322 SECTION("leaflist instance")
323 {
324 inputPath.m_nodes = {dataNode_{{"mod"}, leafListElement_{"addresses", "127.0.0.1"s}}};
325 expectations.push_back(NAMED_REQUIRE_CALL(datastore, createLeafListInstance("/mod:addresses[.='127.0.0.1']")));
326 expectations.push_back(NAMED_REQUIRE_CALL(datastore, deleteLeafListInstance("/mod:addresses[.='127.0.0.1']")));
327 }
328
329 SECTION("presence container")
330 {
331 inputPath.m_nodes = {dataNode_{{"mod"}, container_{"pContainer"}}};
332 expectations.push_back(NAMED_REQUIRE_CALL(datastore, createPresenceContainer("/mod:pContainer")));
333 expectations.push_back(NAMED_REQUIRE_CALL(datastore, deletePresenceContainer("/mod:pContainer")));
334 }
335
336 create_ createCmd;
337 createCmd.m_path = inputPath;
338 delete_ deleteCmd;
339 deleteCmd.m_path = inputPath;
340 toInterpret.push_back(createCmd);
341 toInterpret.push_back(deleteCmd);
342 }
343
344 SECTION("commit")
345 {
346 expectations.push_back(NAMED_REQUIRE_CALL(datastore, commitChanges()));
347 toInterpret.push_back(commit_{});
348 }
349
350 SECTION("discard")
351 {
352 expectations.push_back(NAMED_REQUIRE_CALL(datastore, discardChanges()));
353 toInterpret.push_back(discard_{});
354 }
355
356
357 SECTION("set")
358 {
359 dataPath_ inputPath;
360 leaf_data_ inputData;
361
362 SECTION("setting identityRef without module") // The parser has to fill in the module
363 {
364 inputPath.m_nodes = {dataNode_{{"mod"}, leaf_{"animal"}}};
365 inputData = identityRef_{"Doge"};
366 expectations.push_back(NAMED_REQUIRE_CALL(datastore, setLeaf("/mod:animal", identityRef_{"mod", "Doge"})));
367 }
368
369
370 set_ setCmd;
371 setCmd.m_path = inputPath;
372 setCmd.m_data = inputData;
373 toInterpret.push_back(setCmd);
374 }
375
376
377 SECTION("copy")
378 {
379 SECTION("running -> startup")
380 {
381 expectations.push_back(NAMED_REQUIRE_CALL(datastore, copyConfig(Datastore::Running, Datastore::Startup)));
382 toInterpret.push_back(copy_{{}, Datastore::Running, Datastore::Startup});
383 }
384
385 SECTION("startup -> running")
386 {
387 expectations.push_back(NAMED_REQUIRE_CALL(datastore, copyConfig(Datastore::Startup, Datastore::Running)));
388 toInterpret.push_back(copy_{{}, Datastore::Startup, Datastore::Running});
389 }
390 }
391
392 for (const auto& command : toInterpret) {
393 boost::apply_visitor(Interpreter(parser, datastore), command);
394 }
395}