1 #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
2 #include <iostream>
3 #include <doctest/doctest.h>
4 #include <unistd.h>
5 #include <fcntl.h>
6 #include <sys/file.h>
7 #include <iostream>
8 #include <fstream>
9
10 #include <wayfire/config/file.hpp>
11 #include <wayfire/util/log.hpp>
12 #include <wayfire/config/types.hpp>
13 #include "wayfire/config/compound-option.hpp"
14 #include "../src/option-impl.hpp"
15
16 const std::string contents =
17 R"(
18 illegal_option = value
19
20 [section1]
21 option1 = value1
22 option2=3
23 #Comment
24 option3 = value value value # Comment
25
26 hey_a = 15
27 bey_a = 1.2
28
29 [section2]
30 option1 = value 4 \
31 value # Ignore
32 option2 = value \\
33 # Ignore
34 option3 = \#No way
35
36 [wrongsection
37 option1
38 )";
39
40 #include "expect_line.hpp"
41
42
43 TEST_CASE("wf::config::load_configuration_options_from_string")
44 {
45 std::stringstream log;
46 wf::log::initialize_logging(log, wf::log::LOG_LEVEL_DEBUG,
47 wf::log::LOG_COLOR_MODE_OFF);
48
49 using wf::config::compound_option_t;
50 using wf::config::compound_option_entry_t;
51
52 compound_option_t::entries_t entries;
53 entries.push_back(std::make_unique<compound_option_entry_t<int>>("hey_"));
54 entries.push_back(std::make_unique<compound_option_entry_t<double>>("bey_"));
55 auto opt = new compound_option_t{"option_list", std::move(entries)};
56
57 using namespace wf;
58 using namespace wf::config;
59 config_manager_t config;
60
61 /* Create the first section and add an option there */
62 auto section = std::make_shared<section_t>("section1");
63 section->register_new_option(
64 std::make_shared<option_t<int>>("option1", 10));
65 section->register_new_option(
66 std::make_shared<option_t<int>>("option2", 5));
67 section->register_new_option(
68 std::make_shared<option_t<std::string>>("option4", std::string("option4")));
69 section->register_new_option(std::shared_ptr<option_base_t>(opt));
70
71 config.merge_section(section);
72 load_configuration_options_from_string(config, contents, "test");
73
74 REQUIRE(config.get_section("section1"));
75 REQUIRE(config.get_section("section2"));
76 CHECK(config.get_section("wrongsection") == nullptr);
77
78 auto s1 = config.get_section("section1");
79 auto s2 = config.get_section("section2");
80
81 CHECK(s1->get_option("option1")->get_value_str() == "10");
82 CHECK(s1->get_option("option2")->get_value_str() == "3");
83 CHECK(s1->get_option("option3")->get_value_str() == "value value value");
84 CHECK(s1->get_option("option4")->get_value_str() == "option4");
85
86 CHECK(s2->get_option("option1")->get_value_str() == "value 4 value");
87 CHECK(s2->get_option("option2")->get_value_str() == "value \\");
88 CHECK(s2->get_option("option3")->get_value_str() == "#No way");
89 CHECK(!s2->get_option_or("Ignored"));
90
91 CHECK(opt->get_value<int, double>().size() == 1);
92
93 EXPECT_LINE(log, "Error in file test:2");
94 EXPECT_LINE(log, "Error in file test:5");
95 EXPECT_LINE(log, "Error in file test:20");
96 EXPECT_LINE(log, "Error in file test:21");
97 }
98
99 TEST_CASE("wf::config::load_configuration_options_from_string - "
100 "ignore compound entries not in string")
101 {
102 using namespace wf;
103 using namespace wf::config;
104
105 // Compound option with prefix `test_`
106 compound_option_t::entries_t entries;
107 entries.push_back(std::make_unique<compound_option_entry_t<std::string>>("test_"));
108 auto opt = new compound_option_t{"option_list", std::move(entries)};
109
110 config_manager_t config;
111
112 // Register compound option
113 auto section = std::make_shared<section_t>("section");
114 section->register_new_option(std::shared_ptr<option_base_t>(opt));
115
116 // Register an option which does not come from the config file
117 section->register_new_option(
118 std::make_shared<option_t<std::string>>("test_nofile", ""));
119 config.merge_section(section);
120
121 // Update with an emtpy string. The compoud option should remain empty, because
122 // there are no values from the config file.
123 load_configuration_options_from_string(config, "");
124 CHECK(opt->get_value_untyped().empty());
125 }
126
127 const std::string minimal_config_with_opt = R"(
128 [section]
129 option = value
130 )";
131
132 TEST_CASE("wf::config::load_configuration_options_from_string - lock & reload")
133 {
134 using namespace wf;
135 using namespace wf::config;
136
137 config_manager_t cfg;
138 load_configuration_options_from_string(cfg, minimal_config_with_opt);
139
140 SUBCASE("locked")
141 {
142 cfg.get_option("section/option")->set_locked();
143 load_configuration_options_from_string(cfg, "");
144 CHECK(cfg.get_option("section/option")->get_value_str() == "value");
145 }
146
147 SUBCASE("unlocked")
148 {
149 load_configuration_options_from_string(cfg, "");
150 CHECK(cfg.get_option("section/option")->get_value_str() == "");
151 }
152 }
153
build_simple_config()154 wf::config::config_manager_t build_simple_config()
155 {
156 using namespace wf;
157 using namespace wf::config;
158 auto section1 = std::make_shared<section_t>("section1");
159 auto section2 = std::make_shared<section_t>("section2");
160
161 section1->register_new_option(std::make_shared<option_t<int>>("option1", 4));
162 section1->register_new_option(std::make_shared<option_t<std::string>>("option2",
163 std::string("45 # 46 \\")));
164 section2->register_new_option(std::make_shared<option_t<double>>("option1",
165 4.25));
166
167 compound_option_t::entries_t entries;
168 entries.push_back(std::make_unique<compound_option_entry_t<int>>("hey_"));
169 entries.push_back(std::make_unique<compound_option_entry_t<double>>("bey_"));
170 auto opt = new compound_option_t{"option_list", std::move(entries)};
171 opt->set_value(compound_list_t<int, double>{{"k1", 1, 1.2}});
172 section2->register_new_option(std::shared_ptr<compound_option_t>(opt));
173
174 config_manager_t config;
175 config.merge_section(section1);
176 config.merge_section(section2);
177
178 return config;
179 }
180
181 std::string simple_config_source =
182 R"([section1]
183 option1 = 4
184 option2 = 45 \# 46 \\
185
186 [section2]
187 bey_k1 = 1.200000
188 hey_k1 = 1
189 option1 = 4.250000
190
191 )";
192
193
194 TEST_CASE("wf::config::save_configuration_options_to_string")
195 {
196 auto config = build_simple_config();
197 auto stringified = save_configuration_options_to_string(config);
198 CHECK(stringified == simple_config_source);
199 }
200
201 TEST_CASE("wf::config::save_configuration_options_to_string - compound options erase")
202 {
203 using namespace wf;
204 using namespace wf::config;
205
206 compound_option_t::entries_t entries;
207 entries.push_back(std::make_unique<compound_option_entry_t<int>>("hey_"));
208 entries.push_back(std::make_unique<compound_option_entry_t<double>>("bey_"));
209 auto opt = new compound_option_t{"option_list", std::move(entries)};
210 opt->set_value(compound_list_t<int, double>{{"k1", 1, 1.2}});
211
212 auto section = std::make_shared<section_t>("Section");
213 section->register_new_option(std::shared_ptr<compound_option_t>(opt));
214
215 // Add the same entries as in the compound option
216 section->register_new_option(std::make_shared<option_t<std::string>>("hey_k1",
217 "1"));
218 section->register_new_option(std::make_shared<option_t<std::string>>("bey_k1",
219 "1.2"));
220
221 // However, make sure that XML-created options are saved even if they match
222 // the prefix of a compound option.
223 auto special_opt = std::make_shared<option_t<int>>("hey_you", 1);
224 special_opt->priv->xml = (xmlNode*)0x123;
225 section->register_new_option(special_opt);
226
227 config_manager_t cfg;
228 cfg.merge_section(section);
229
230 // Now, clear the value from the compound option
231 opt->set_value(compound_list_t<int, double>{});
232
233 auto str = save_configuration_options_to_string(cfg);
234 // We expect that after deleting the values from the compound option,
235 // the values for k1 are not saved to the string.
236 const std::string expected =
237 R"([Section]
238 hey_you = 1
239
240 )";
241
242 CHECK(str == expected);
243 }
244
245 TEST_CASE("wf::config::load_configuration_options_from_file - no such file")
246 {
247 std::string test_config = std::string("FileDoesNotExist");
248 wf::config::config_manager_t manager;
249 CHECK(!load_configuration_options_from_file(manager, test_config));
250 }
251
252 TEST_CASE("wf::config::load_configuration_options_from_file - locking fails")
253 {
254 std::string test_config = std::string("../test/config_lock.ini");
255
256 const int delay = 100e3; /** 100ms */
257
258 int pid = fork();
259 if (pid == 0)
260 {
261 /* Lock config file before parent tries to lock it */
262 int fd = open(test_config.c_str(), O_RDWR);
263 flock(fd, LOCK_EX);
264
265 /* Obtained a lock. Now wait until parent tries to lock */
266 usleep(2 * delay);
267
268 /* By now, parent should have failed. */
269 flock(fd, LOCK_UN);
270 close(fd);
271 }
272
273 /* Wait for other process to lock the file */
274 usleep(delay);
275
276 wf::config::config_manager_t manager;
277 CHECK(!load_configuration_options_from_file(manager, test_config));
278 }
279
check_int_test_config(const wf::config::config_manager_t & manager,std::string value_opt1="12")280 void check_int_test_config(const wf::config::config_manager_t& manager,
281 std::string value_opt1 = "12")
282 {
283 auto s1 = manager.get_section("section1");
284 auto s2 = manager.get_section("section2");
285 REQUIRE(s1 != nullptr);
286 REQUIRE(s2 != nullptr);
287
288 auto o1 = manager.get_option("section1/option1");
289 auto o2 = manager.get_option("section2/option2");
290 auto o3 = manager.get_option("section2/option3");
291
292 REQUIRE(o1);
293 REQUIRE(o2);
294 REQUIRE(o3);
295 CHECK(o1->get_value_str() == value_opt1);
296 CHECK(o2->get_value_str() == "opt2");
297 CHECK(o3->get_value_str() == "DoesNotExistInXML # \\");
298 }
299
300 TEST_CASE("wf::config::load_configuration_options_from_file - success")
301 {
302 std::string test_config = std::string(TEST_SOURCE "/int_test/config.ini");
303
304 /* Init with one section */
305 wf::config::config_manager_t manager;
306 auto s = std::make_shared<wf::config::section_t>("section1");
307 s->register_new_option(
308 std::make_shared<wf::config::option_t<int>>("option1", 1));
309 manager.merge_section(s);
310
311 CHECK(load_configuration_options_from_file(manager, test_config));
312 REQUIRE(manager.get_section("section1") == s);
313 check_int_test_config(manager);
314 }
315
316 TEST_CASE("wf::config::save_configuration_to_file - success")
317 {
318 std::string test_config = std::string(TEST_SOURCE "/dummy.ini");
319
320 {
321 std::ofstream clr(test_config, std::ios::trunc | std::ios::ate);
322 clr << "Dummy";
323 }
324
325 wf::config::save_configuration_to_file(build_simple_config(), test_config);
326
327 /* Read file contents */
328 std::ifstream infile(test_config);
329 std::string file_contents((std::istreambuf_iterator<char>(infile)),
330 std::istreambuf_iterator<char>());
331
332 CHECK(file_contents == simple_config_source);
333
334 /* Check lock is released */
335 int fd = open(test_config.c_str(), O_RDWR);
336 CHECK(flock(fd, LOCK_EX | LOCK_NB) == 0);
337 flock(fd, LOCK_UN);
338 close(fd);
339 }
340
341 TEST_CASE("wf::config::build_configuration")
342 {
343 wf::log::initialize_logging(std::cout, wf::log::LOG_LEVEL_DEBUG,
344 wf::log::LOG_COLOR_MODE_ON);
345 std::string xmldir = std::string(TEST_SOURCE "/int_test/xml");
346 std::string sysconf = std::string(TEST_SOURCE "/int_test/sys.ini");
347 std::string userconf = std::string(TEST_SOURCE "/int_test/config.ini");
348
349 std::vector xmldirs(1, xmldir);
350 auto config = wf::config::build_configuration(xmldirs, sysconf, userconf);
351 check_int_test_config(config, "10");
352
353 auto o1 = config.get_option("section1/option1");
354 auto o2 = config.get_option("section2/option2");
355 auto o3 = config.get_option("section2/option3");
356 auto o4 = config.get_option("section2/option4");
357 auto o5 = config.get_option("section2/option5");
358 auto o6 = config.get_option("sectionobj:objtest/option6");
359
360 REQUIRE(o4);
361 REQUIRE(o5);
362
363 using namespace wf;
364 using namespace wf::config;
365 CHECK(std::dynamic_pointer_cast<option_t<int>>(o1) != nullptr);
366 CHECK(std::dynamic_pointer_cast<option_t<std::string>>(o2) != nullptr);
367 CHECK(std::dynamic_pointer_cast<option_t<std::string>>(o3) != nullptr);
368 CHECK(std::dynamic_pointer_cast<option_t<std::string>>(o4) != nullptr);
369 CHECK(std::dynamic_pointer_cast<option_t<std::string>>(o5) != nullptr);
370 CHECK(std::dynamic_pointer_cast<option_t<int>>(o6) != nullptr);
371
372 CHECK(o4->get_value_str() == "DoesNotExistInConfig");
373 CHECK(o5->get_value_str() == "Option5Sys");
374 CHECK(o6->get_value_str() == "10"); // bounds from xml applied
375
376 o1->reset_to_default();
377 o2->reset_to_default();
378 o3->reset_to_default();
379 o4->reset_to_default();
380 o5->reset_to_default();
381 o6->reset_to_default();
382
383 CHECK(o1->get_value_str() == "4");
384 CHECK(o2->get_value_str() == "XMLDefault");
385 CHECK(o3->get_value_str() == "");
386 CHECK(o4->get_value_str() == "DoesNotExistInConfig");
387 CHECK(o5->get_value_str() == "Option5Sys");
388 CHECK(o6->get_value_str() == "1");
389 }
390