1 #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
2 #include <doctest/doctest.h>
3 #include <set>
4 
5 #include <sstream>
6 #include <wayfire/config/types.hpp>
7 #include <wayfire/config/compound-option.hpp>
8 #include <wayfire/config/xml.hpp>
9 #include <wayfire/util/log.hpp>
10 #include <linux/input-event-codes.h>
11 
12 static const std::string xml_option_int =
13     R"(
14 <option name="IntOption" type="int">
15 <default>3</default>
16 <min>0</min>
17 <max>10</max>
18 </option>
19 )";
20 
21 static const std::string xml_option_string =
22     R"(
23 <option name="StringOption" type="string">
24 <default></default>
25 </option>
26 )";
27 
28 static const std::string xml_option_key =
29     R"(
30 <option name="KeyOption" type="key">
31 <default>&lt;super&gt; KEY_E</default>
32 </option>
33 )";
34 
35 static const std::string xml_option_dyn_list =
36     R"(
37 <option name="DynList" type="dynamic-list">
38 <entry prefix="cmd_" type="string"/>
39 <entry prefix="act_" type="activator"/>
40 <unused/>
41 </option>
42 )";
43 
44 static const std::string xml_option_dyn_list_no_prefix =
45     R"(
46 <option name="DynList" type="dynamic-list">
47 <entry type="string"/>
48 </option>
49 )";
50 
51 static const std::string xml_option_dyn_list_no_type =
52     R"(
53 <option name="DynList" type="dynamic-list">
54 <entry prefix="pre_"/>
55 </option>
56 )";
57 
58 static const std::string xml_option_dyn_list_wrong_type =
59     R"(
60 <option name="DynList" type="dynamic-list">
61 <entry prefix="pre_" type="invalid"/>
62 </option>
63 )";
64 
65 static const std::string xml_option_bad_tag =
66     R"(
67 <invalid name="KeyOption" type="key">
68 <default>&lt;super&gt; KEY_E</default>
69 </invalid>
70 )";
71 
72 static const std::string xml_option_int_bad_min =
73     R"(
74 <option name="IntOption" type="int">
75 <default>3</default>
76 <min>sfd</min>
77 <max>10</max>
78 </option>
79 )";
80 
81 static const std::string xml_option_int_bad_max =
82     R"(
83 <option name="IntOption" type="int">
84 <default>3</default>
85 <min>0</min>
86 <max>sdf</max>
87 </option>
88 )";
89 
90 static const std::string xml_option_bad_type =
91     R"(
92 <option name="KeyOption" type="unknown">
93 <default>&lt;super&gt; KEY_E</default>
94 </option>
95 )";
96 
97 static const std::string xml_option_bad_default =
98     R"(
99 <option name="KeyOption" type="key">
100 <default>&lt;super&gt; e</default>
101 </option>
102 )";
103 
104 
105 static const std::string xml_option_missing_name =
106     R"(
107 <option type="int">
108 </option>
109 )";
110 
111 static const std::string xml_option_missing_type =
112     R"(
113 <option name="IntOption">
114 </option>
115 )";
116 
117 static const std::string xml_option_missing_default_value =
118     R"(
119 <option name="IntOption" type="int">
120 </option>
121 )";
122 
123 #include "expect_line.hpp"
124 
125 TEST_CASE("wf::config::xml::create_option")
126 {
127     std::stringstream log;
128     wf::log::initialize_logging(log,
129         wf::log::LOG_LEVEL_DEBUG, wf::log::LOG_COLOR_MODE_OFF);
130 
131     namespace wxml = wf::config::xml;
132     namespace wc   = wf::config;
133     xmlNodePtr option_node;
134 
135     auto initialize_option = [&] (std::string source)
__anon0794be180102(std::string source) 136     {
137         auto node = xmlParseDoc((const xmlChar*)source.c_str());
138         REQUIRE(node != nullptr);
139         option_node = xmlDocGetRootElement(node);
140         return wxml::create_option_from_xml_node(option_node);
141     };
142 
143     SUBCASE("Not an XML option")
144     {
145         auto opt = std::make_shared<wf::config::option_t<int>>("Test", 1);
146         CHECK(wxml::get_option_xml_node(opt) == nullptr);
147     }
148 
149     SUBCASE("IntOption")
150     {
151         auto option = initialize_option(xml_option_int);
152         REQUIRE(option != nullptr);
153 
154         CHECK(option->get_name() == "IntOption");
155 
156         auto as_int =
157             std::dynamic_pointer_cast<wc::option_t<int>>(option);
158         REQUIRE(as_int);
159         REQUIRE(as_int->get_minimum());
160         REQUIRE(as_int->get_maximum());
161 
162         CHECK(as_int->get_value() == 3);
163         CHECK(as_int->get_minimum().value() == 0);
164         CHECK(as_int->get_maximum().value() == 10);
165         CHECK(wxml::get_option_xml_node(as_int) == option_node);
166     }
167 
168     SUBCASE("StringOption")
169     {
170         auto option = initialize_option(xml_option_string);
171         REQUIRE(option != nullptr);
172 
173         CHECK(option->get_name() == "StringOption");
174 
175         auto as_string =
176             std::dynamic_pointer_cast<wc::option_t<std::string>>(option);
177         REQUIRE(as_string);
178         CHECK(as_string->get_value() == "");
179     }
180 
181     SUBCASE("KeyOption")
182     {
183         auto option = initialize_option(xml_option_key);
184         REQUIRE(option != nullptr);
185 
186         CHECK(option->get_name() == "KeyOption");
187 
188         auto as_key =
189             std::dynamic_pointer_cast<wc::option_t<wf::keybinding_t>>(option);
190         REQUIRE(as_key);
191 
192         CHECK(as_key->get_value() ==
193             wf::keybinding_t{wf::KEYBOARD_MODIFIER_LOGO, KEY_E});
194         CHECK(wxml::get_option_xml_node(option) == option_node);
195     }
196 
197     SUBCASE("DynamicList")
198     {
199         auto option = initialize_option(xml_option_dyn_list);
200         REQUIRE(option != nullptr);
201 
202         CHECK(option->get_name() == "DynList");
203 
204         auto as_co =
205             std::dynamic_pointer_cast<wc::compound_option_t>(option);
206         REQUIRE(as_co != nullptr);
207 
208         CHECK(as_co->get_value<std::string, wf::activatorbinding_t>() ==
209             wf::config::compound_list_t<std::string, wf::activatorbinding_t>{});
210 
211         const auto& entries = as_co->get_entries();
212         REQUIRE(entries.size() == 2);
213         CHECK(
214             dynamic_cast<wc::compound_option_entry_t<std::string>*>(entries[0].get()));
215         CHECK(dynamic_cast<wc::compound_option_entry_t<wf::activatorbinding_t>*>(
216             entries[1].get()));
217     }
218 
219     /* Generate a subcase where the given xml source can't be parsed to an
220      * option, and check that the output in the log is as expected. */
221 #define SUBCASE_BAD_OPTION(subcase_name, xml_source, expected_log) \
222     SUBCASE(subcase_name) \
223     { \
224         auto option = initialize_option(xml_source); \
225         CHECK(option == nullptr); \
226         EXPECT_LINE(log, expected_log); \
227     }
228 
229     SUBCASE_BAD_OPTION("Invalid xml tag",
230         xml_option_bad_tag, "is not an option element");
231 
232     SUBCASE_BAD_OPTION("Invalid option type",
233         xml_option_bad_type, "invalid type \"unknown\"");
234 
235     SUBCASE_BAD_OPTION("Invalid default value",
236         xml_option_bad_default, "invalid default value");
237 
238     SUBCASE_BAD_OPTION("Invalid minimum value",
239         xml_option_int_bad_min, "invalid minimum value");
240 
241     SUBCASE_BAD_OPTION("Invalid maximum value",
242         xml_option_int_bad_max, "invalid maximum value");
243 
244     SUBCASE_BAD_OPTION("Missing option name",
245         xml_option_missing_name, "missing \"name\" attribute");
246 
247     SUBCASE_BAD_OPTION("Missing option type",
248         xml_option_missing_type, "missing \"type\" attribute");
249 
250     SUBCASE_BAD_OPTION("Missing option default value",
251         xml_option_missing_default_value, "no default value specified");
252 
253     SUBCASE_BAD_OPTION("Dynamic list without prefix",
254         xml_option_dyn_list_no_prefix, "missing \"prefix\" attribute");
255 
256     SUBCASE_BAD_OPTION("Dynamic list without type",
257         xml_option_dyn_list_no_type, "missing \"type\" attribute");
258 
259     SUBCASE_BAD_OPTION("Dynamic list with invalid type",
260         xml_option_dyn_list_wrong_type, "invalid type");
261 }
262 
263 /* ------------------------- create_section test ---------------------------- */
264 static const std::string xml_section_empty =
265     R"(
266 <plugin name="TestPluginEmpty">
267 </plugin>
268 )";
269 
270 static const std::string xml_section_no_plugins =
271     R"(
272 <plugin name="TestPluginNoPlugins">
273 <description> </description>
274 <check> </check>
275 </plugin>
276 )";
277 
278 static const std::string xml_section_full =
279     R"(
280 <plugin name="TestPluginFull">
281     <option name="KeyOption" type="key">
282         <default>&lt;super&gt; KEY_E</default>
283     </option>
284     <option name="ButtonOption" type="button">
285         <default>&lt;super&gt; BTN_LEFT</default>
286     </option>
287     <option name="TouchOption" type="gesture">
288         <default>swipe up 3</default>
289     </option>
290     <option name="ActivatorOption" type="activator">
291         <default>&lt;super&gt; KEY_E | swipe up 3</default>
292     </option>
293     <option name="IntOption" type="int">
294         <default>3</default>
295     </option>
296     <option name="OutputModeOption" type="output::mode">
297         <default>1920x1080</default>
298     </option>
299     <option name="OutputPositionOption" type="output::position">
300         <default>0, 0</default>
301     </option>
302     <group>
303         <option name="BoolOption" type="bool">
304             <default>true</default>
305         </option>
306         <option name="DoubleOption" type="double">
307             <default>5.0</default>
308         </option>
309         <subgroup>
310             <option name="StringOption" type="string">
311                 <default>test</default>
312             </option>
313         </subgroup>
314         <option name="KeyOption2" type="invalid">
315             <default>&lt;super&gt; KEY_T</default>
316         </option>
317     </group>
318 </plugin>
319 )";
320 
321 static const std::string xml_section_missing_name =
322     R"(
323 <plugin>
324     <option name="KeyOption" type="key">
325         <default>&lt;super&gt; KEY_T</default>
326     </option>
327 </plugin>
328 )";
329 
330 static const std::string xml_section_bad_tag =
331     R"(
332 <invalid>
333     <option name="KeyOption" type="key">
334         <default>&lt;super&gt; KEY_T</default>
335     </option>
336 </invalid>
337 )";
338 
339 TEST_CASE("wf::config::xml::create_section")
340 {
341     std::stringstream log;
342     wf::log::initialize_logging(log,
343         wf::log::LOG_LEVEL_DEBUG, wf::log::LOG_COLOR_MODE_OFF);
344 
345     namespace wxml = wf::config::xml;
346     namespace wc   = wf::config;
347 
348     xmlNodePtr section_root;
349     auto initialize_section = [&] (std::string xml_source)
__anon0794be180202(std::string xml_source) 350     {
351         auto node = xmlParseDoc((const xmlChar*)xml_source.c_str());
352         REQUIRE(node != nullptr);
353         section_root = xmlDocGetRootElement(node);
354         return wxml::create_section_from_xml_node(section_root);
355     };
356 
357     SUBCASE("Section without XML")
358     {
359         auto section = std::make_shared<wc::section_t>("TestSection");
360         CHECK(wxml::get_section_xml_node(section) == nullptr);
361     }
362 
363     SUBCASE("Empty section")
364     {
365         auto section = initialize_section(xml_section_empty);
366         REQUIRE(section != nullptr);
367         CHECK(section->get_name() == "TestPluginEmpty");
368         CHECK(section->get_registered_options().empty());
369         CHECK(wxml::get_section_xml_node(section) == section_root);
370     }
371 
372     SUBCASE("Empty section - unnecessary data")
373     {
374         auto section = initialize_section(xml_section_no_plugins);
375         REQUIRE(section != nullptr);
376         CHECK(section->get_name() == "TestPluginNoPlugins");
377         CHECK(section->get_registered_options().empty());
378         CHECK(wxml::get_section_xml_node(section) == section_root);
379     }
380 
381     SUBCASE("Section with options")
382     {
383         auto section = initialize_section(xml_section_full);
384         REQUIRE(section != nullptr);
385         CHECK(section->get_name() == "TestPluginFull");
386 
387         auto opts = section->get_registered_options();
388         std::set<std::string> opt_names;
389         for (auto& opt : opts)
390         {
391             opt_names.insert(opt->get_name());
392         }
393 
394         std::set<std::string> expected_names = {
395             "KeyOption", "ButtonOption", "TouchOption", "ActivatorOption",
396             "IntOption", "DoubleOption", "BoolOption", "StringOption",
397             "OutputModeOption", "OutputPositionOption"};
398         CHECK(opt_names == expected_names);
399         CHECK(wxml::get_section_xml_node(section) == section_root);
400     }
401 
402     SUBCASE("Missing section name")
403     {
404         auto section = initialize_section(xml_section_missing_name);
405         CHECK(section == nullptr);
406         EXPECT_LINE(log, "missing \"name\" attribute");
407     }
408 
409     SUBCASE("Invalid section xml tag")
410     {
411         auto section = initialize_section(xml_section_bad_tag);
412         CHECK(section == nullptr);
413         EXPECT_LINE(log, "is not a plugin/object element");
414     }
415 }
416