1 // Copyright 2011 Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 // may be used to endorse or promote products derived from this software
15 // without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 #include "cli/config.hpp"
30
31 #include <atf-c++.hpp>
32
33 #include "engine/config.hpp"
34 #include "engine/exceptions.hpp"
35 #include "utils/env.hpp"
36 #include "utils/format/macros.hpp"
37 #include "utils/fs/operations.hpp"
38 #include "utils/fs/path.hpp"
39
40 namespace cmdline = utils::cmdline;
41 namespace config = utils::config;
42 namespace fs = utils::fs;
43
44
45 namespace {
46
47
48 /// Creates a configuration file for testing purposes.
49 ///
50 /// To ensure that the loaded file is the one created by this function, use
51 /// validate_mock_config().
52 ///
53 /// \param name The name of the configuration file to create.
54 /// \param cookie The magic value to set in the configuration file, or NULL if a
55 /// broken configuration file is desired.
56 static void
create_mock_config(const char * name,const char * cookie)57 create_mock_config(const char* name, const char* cookie)
58 {
59 if (cookie != NULL) {
60 atf::utils::create_file(
61 name,
62 F("syntax(2)\n"
63 "test_suites.suite.magic_value = '%s'\n") % cookie);
64 } else {
65 atf::utils::create_file(name, "syntax(200)\n");
66 }
67 }
68
69
70 /// Creates an invalid system configuration.
71 ///
72 /// \param cookie The magic value to set in the configuration file, or NULL if a
73 /// broken configuration file is desired.
74 static void
mock_system_config(const char * cookie)75 mock_system_config(const char* cookie)
76 {
77 fs::mkdir(fs::path("system-dir"), 0755);
78 utils::setenv("KYUA_CONFDIR", (fs::current_path() / "system-dir").str());
79 create_mock_config("system-dir/kyua.conf", cookie);
80 }
81
82
83 /// Creates an invalid user configuration.
84 ///
85 /// \param cookie The magic value to set in the configuration file, or NULL if a
86 /// broken configuration file is desired.
87 static void
mock_user_config(const char * cookie)88 mock_user_config(const char* cookie)
89 {
90 fs::mkdir(fs::path("user-dir"), 0755);
91 fs::mkdir(fs::path("user-dir/.kyua"), 0755);
92 utils::setenv("HOME", (fs::current_path() / "user-dir").str());
93 create_mock_config("user-dir/.kyua/kyua.conf", cookie);
94 }
95
96
97 /// Ensures that a loaded configuration was created with create_mock_config().
98 ///
99 /// \param user_config The configuration to validate.
100 /// \param cookie The magic value to expect in the configuration file.
101 static void
validate_mock_config(const config::tree & user_config,const char * cookie)102 validate_mock_config(const config::tree& user_config, const char* cookie)
103 {
104 const config::properties_map& properties = user_config.all_properties(
105 "test_suites.suite", true);
106 const config::properties_map::const_iterator iter =
107 properties.find("magic_value");
108 ATF_REQUIRE(iter != properties.end());
109 ATF_REQUIRE_EQ(cookie, (*iter).second);
110 }
111
112
113 /// Ensures that two configuration trees are equal.
114 ///
115 /// \param exp_tree The expected configuration tree.
116 /// \param actual_tree The configuration tree being validated against exp_tree.
117 static void
require_eq(const config::tree & exp_tree,const config::tree & actual_tree)118 require_eq(const config::tree& exp_tree, const config::tree& actual_tree)
119 {
120 ATF_REQUIRE(exp_tree.all_properties() == actual_tree.all_properties());
121 }
122
123
124 } // anonymous namespace
125
126
127 ATF_TEST_CASE_WITHOUT_HEAD(load_config__none);
ATF_TEST_CASE_BODY(load_config__none)128 ATF_TEST_CASE_BODY(load_config__none)
129 {
130 utils::setenv("KYUA_CONFDIR", "/the/system/does/not/exist");
131 utils::setenv("HOME", "/the/user/does/not/exist");
132
133 std::map< std::string, std::vector< std::string > > options;
134 options["config"].push_back(cli::config_option.default_value());
135 const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
136
137 require_eq(engine::default_config(),
138 cli::load_config(mock_cmdline, true));
139 }
140
141
142 ATF_TEST_CASE_WITHOUT_HEAD(load_config__explicit__ok);
ATF_TEST_CASE_BODY(load_config__explicit__ok)143 ATF_TEST_CASE_BODY(load_config__explicit__ok)
144 {
145 mock_system_config(NULL);
146 mock_user_config(NULL);
147
148 create_mock_config("test-file", "hello");
149
150 std::map< std::string, std::vector< std::string > > options;
151 options["config"].push_back("test-file");
152 const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
153
154 const config::tree user_config = cli::load_config(mock_cmdline, true);
155 validate_mock_config(user_config, "hello");
156 }
157
158
159 ATF_TEST_CASE_WITHOUT_HEAD(load_config__explicit__disable);
ATF_TEST_CASE_BODY(load_config__explicit__disable)160 ATF_TEST_CASE_BODY(load_config__explicit__disable)
161 {
162 mock_system_config(NULL);
163 mock_user_config(NULL);
164
165 std::map< std::string, std::vector< std::string > > options;
166 options["config"].push_back("none");
167 const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
168
169 require_eq(engine::default_config(),
170 cli::load_config(mock_cmdline, true));
171 }
172
173
174 ATF_TEST_CASE_WITHOUT_HEAD(load_config__explicit__fail);
ATF_TEST_CASE_BODY(load_config__explicit__fail)175 ATF_TEST_CASE_BODY(load_config__explicit__fail)
176 {
177 mock_system_config("ok1");
178 mock_user_config("ok2");
179
180 create_mock_config("test-file", NULL);
181
182 std::map< std::string, std::vector< std::string > > options;
183 options["config"].push_back("test-file");
184 const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
185
186 ATF_REQUIRE_THROW_RE(engine::error, "200",
187 cli::load_config(mock_cmdline, true));
188
189 const config::tree config = cli::load_config(mock_cmdline, false);
190 require_eq(engine::default_config(), config);
191 }
192
193
194 ATF_TEST_CASE_WITHOUT_HEAD(load_config__user__ok);
ATF_TEST_CASE_BODY(load_config__user__ok)195 ATF_TEST_CASE_BODY(load_config__user__ok)
196 {
197 mock_system_config(NULL);
198 mock_user_config("I am the user config");
199
200 std::map< std::string, std::vector< std::string > > options;
201 options["config"].push_back(cli::config_option.default_value());
202 const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
203
204 const config::tree user_config = cli::load_config(mock_cmdline, true);
205 validate_mock_config(user_config, "I am the user config");
206 }
207
208
209 ATF_TEST_CASE_WITHOUT_HEAD(load_config__user__fail);
ATF_TEST_CASE_BODY(load_config__user__fail)210 ATF_TEST_CASE_BODY(load_config__user__fail)
211 {
212 mock_system_config("valid");
213 mock_user_config(NULL);
214
215 std::map< std::string, std::vector< std::string > > options;
216 options["config"].push_back(cli::config_option.default_value());
217 const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
218
219 ATF_REQUIRE_THROW_RE(engine::error, "200",
220 cli::load_config(mock_cmdline, true));
221
222 const config::tree config = cli::load_config(mock_cmdline, false);
223 require_eq(engine::default_config(), config);
224 }
225
226
227 ATF_TEST_CASE_WITHOUT_HEAD(load_config__user__bad_home);
ATF_TEST_CASE_BODY(load_config__user__bad_home)228 ATF_TEST_CASE_BODY(load_config__user__bad_home)
229 {
230 mock_system_config("Fallback system config");
231 utils::setenv("HOME", "");
232
233 std::map< std::string, std::vector< std::string > > options;
234 options["config"].push_back(cli::config_option.default_value());
235 const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
236
237 const config::tree user_config = cli::load_config(mock_cmdline, true);
238 validate_mock_config(user_config, "Fallback system config");
239 }
240
241
242 ATF_TEST_CASE_WITHOUT_HEAD(load_config__system__ok);
ATF_TEST_CASE_BODY(load_config__system__ok)243 ATF_TEST_CASE_BODY(load_config__system__ok)
244 {
245 mock_system_config("I am the system config");
246 utils::setenv("HOME", "/the/user/does/not/exist");
247
248 std::map< std::string, std::vector< std::string > > options;
249 options["config"].push_back(cli::config_option.default_value());
250 const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
251
252 const config::tree user_config = cli::load_config(mock_cmdline, true);
253 validate_mock_config(user_config, "I am the system config");
254 }
255
256
257 ATF_TEST_CASE_WITHOUT_HEAD(load_config__system__fail);
ATF_TEST_CASE_BODY(load_config__system__fail)258 ATF_TEST_CASE_BODY(load_config__system__fail)
259 {
260 mock_system_config(NULL);
261 utils::setenv("HOME", "/the/user/does/not/exist");
262
263 std::map< std::string, std::vector< std::string > > options;
264 options["config"].push_back(cli::config_option.default_value());
265 const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
266
267 ATF_REQUIRE_THROW_RE(engine::error, "200",
268 cli::load_config(mock_cmdline, true));
269
270 const config::tree config = cli::load_config(mock_cmdline, false);
271 require_eq(engine::default_config(), config);
272 }
273
274
275 ATF_TEST_CASE_WITHOUT_HEAD(load_config__overrides__no);
ATF_TEST_CASE_BODY(load_config__overrides__no)276 ATF_TEST_CASE_BODY(load_config__overrides__no)
277 {
278 utils::setenv("KYUA_CONFDIR", fs::current_path().str());
279
280 std::map< std::string, std::vector< std::string > > options;
281 options["config"].push_back(cli::config_option.default_value());
282 options["variable"].push_back("architecture=1");
283 options["variable"].push_back("platform=2");
284 const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
285
286 const config::tree user_config = cli::load_config(mock_cmdline, true);
287 ATF_REQUIRE_EQ("1",
288 user_config.lookup< config::string_node >("architecture"));
289 ATF_REQUIRE_EQ("2",
290 user_config.lookup< config::string_node >("platform"));
291 }
292
293
294 ATF_TEST_CASE_WITHOUT_HEAD(load_config__overrides__yes);
ATF_TEST_CASE_BODY(load_config__overrides__yes)295 ATF_TEST_CASE_BODY(load_config__overrides__yes)
296 {
297 atf::utils::create_file(
298 "config",
299 "syntax(2)\n"
300 "architecture = 'do not see me'\n"
301 "platform = 'see me'\n");
302
303 std::map< std::string, std::vector< std::string > > options;
304 options["config"].push_back("config");
305 options["variable"].push_back("architecture=overriden");
306 const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
307
308 const config::tree user_config = cli::load_config(mock_cmdline, true);
309 ATF_REQUIRE_EQ("overriden",
310 user_config.lookup< config::string_node >("architecture"));
311 ATF_REQUIRE_EQ("see me",
312 user_config.lookup< config::string_node >("platform"));
313 }
314
315
316 ATF_TEST_CASE_WITHOUT_HEAD(load_config__overrides__fail);
ATF_TEST_CASE_BODY(load_config__overrides__fail)317 ATF_TEST_CASE_BODY(load_config__overrides__fail)
318 {
319 utils::setenv("KYUA_CONFDIR", fs::current_path().str());
320
321 std::map< std::string, std::vector< std::string > > options;
322 options["config"].push_back(cli::config_option.default_value());
323 options["variable"].push_back(".a=d");
324 const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
325
326 ATF_REQUIRE_THROW_RE(engine::error, "Empty component in key.*'\\.a'",
327 cli::load_config(mock_cmdline, true));
328
329 const config::tree config = cli::load_config(mock_cmdline, false);
330 require_eq(engine::default_config(), config);
331 }
332
333
ATF_INIT_TEST_CASES(tcs)334 ATF_INIT_TEST_CASES(tcs)
335 {
336 ATF_ADD_TEST_CASE(tcs, load_config__none);
337 ATF_ADD_TEST_CASE(tcs, load_config__explicit__ok);
338 ATF_ADD_TEST_CASE(tcs, load_config__explicit__disable);
339 ATF_ADD_TEST_CASE(tcs, load_config__explicit__fail);
340 ATF_ADD_TEST_CASE(tcs, load_config__user__ok);
341 ATF_ADD_TEST_CASE(tcs, load_config__user__fail);
342 ATF_ADD_TEST_CASE(tcs, load_config__user__bad_home);
343 ATF_ADD_TEST_CASE(tcs, load_config__system__ok);
344 ATF_ADD_TEST_CASE(tcs, load_config__system__fail);
345 ATF_ADD_TEST_CASE(tcs, load_config__overrides__no);
346 ATF_ADD_TEST_CASE(tcs, load_config__overrides__yes);
347 ATF_ADD_TEST_CASE(tcs, load_config__overrides__fail);
348 }
349