xref: /freebsd/contrib/kyua/cli/config_test.cpp (revision 55141f2c8991b2a6adbf30bb0fe3e6cbc303f06d)
1 // Copyright 2011 The Kyua Authors.
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/cmdline/options.hpp"
36 #include "utils/cmdline/parser.ipp"
37 #include "utils/config/tree.ipp"
38 #include "utils/env.hpp"
39 #include "utils/format/macros.hpp"
40 #include "utils/fs/operations.hpp"
41 #include "utils/fs/path.hpp"
42 
43 namespace cmdline = utils::cmdline;
44 namespace config = utils::config;
45 namespace fs = utils::fs;
46 
47 
48 namespace {
49 
50 
51 /// Creates a configuration file for testing purposes.
52 ///
53 /// To ensure that the loaded file is the one created by this function, use
54 /// validate_mock_config().
55 ///
56 /// \param name The name of the configuration file to create.
57 /// \param cookie The magic value to set in the configuration file, or NULL if a
58 ///     broken configuration file is desired.
59 static void
60 create_mock_config(const char* name, const char* cookie)
61 {
62     if (cookie != NULL) {
63         atf::utils::create_file(
64             name,
65             F("syntax(2)\n"
66               "test_suites.suite.magic_value = '%s'\n") % cookie);
67     } else {
68         atf::utils::create_file(name, "syntax(200)\n");
69     }
70 }
71 
72 
73 /// Creates an invalid system configuration.
74 ///
75 /// \param cookie The magic value to set in the configuration file, or NULL if a
76 ///     broken configuration file is desired.
77 static void
78 mock_system_config(const char* cookie)
79 {
80     fs::mkdir(fs::path("system-dir"), 0755);
81     utils::setenv("KYUA_CONFDIR", (fs::current_path() / "system-dir").str());
82     create_mock_config("system-dir/kyua.conf", cookie);
83 }
84 
85 
86 /// Creates an invalid user configuration.
87 ///
88 /// \param cookie The magic value to set in the configuration file, or NULL if a
89 ///     broken configuration file is desired.
90 static void
91 mock_user_config(const char* cookie)
92 {
93     fs::mkdir(fs::path("user-dir"), 0755);
94     fs::mkdir(fs::path("user-dir/.kyua"), 0755);
95     utils::setenv("HOME", (fs::current_path() / "user-dir").str());
96     create_mock_config("user-dir/.kyua/kyua.conf", cookie);
97 }
98 
99 
100 /// Ensures that a loaded configuration was created with create_mock_config().
101 ///
102 /// \param user_config The configuration to validate.
103 /// \param cookie The magic value to expect in the configuration file.
104 static void
105 validate_mock_config(const config::tree& user_config, const char* cookie)
106 {
107     const config::properties_map& properties = user_config.all_properties(
108         "test_suites.suite", true);
109     const config::properties_map::const_iterator iter =
110         properties.find("magic_value");
111     ATF_REQUIRE(iter != properties.end());
112     ATF_REQUIRE_EQ(cookie, (*iter).second);
113 }
114 
115 
116 /// Ensures that two configuration trees are equal.
117 ///
118 /// \param exp_tree The expected configuration tree.
119 /// \param actual_tree The configuration tree being validated against exp_tree.
120 static void
121 require_eq(const config::tree& exp_tree, const config::tree& actual_tree)
122 {
123     ATF_REQUIRE(exp_tree.all_properties() == actual_tree.all_properties());
124 }
125 
126 
127 }  // anonymous namespace
128 
129 
130 ATF_TEST_CASE_WITHOUT_HEAD(load_config__none);
131 ATF_TEST_CASE_BODY(load_config__none)
132 {
133     utils::setenv("KYUA_CONFDIR", "/the/system/does/not/exist");
134     utils::setenv("HOME", "/the/user/does/not/exist");
135 
136     std::map< std::string, std::vector< std::string > > options;
137     options["config"].push_back(cli::config_option.default_value());
138     const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
139 
140     require_eq(engine::default_config(),
141                cli::load_config(mock_cmdline, true));
142 }
143 
144 
145 ATF_TEST_CASE_WITHOUT_HEAD(load_config__explicit__ok);
146 ATF_TEST_CASE_BODY(load_config__explicit__ok)
147 {
148     mock_system_config(NULL);
149     mock_user_config(NULL);
150 
151     create_mock_config("test-file", "hello");
152 
153     std::map< std::string, std::vector< std::string > > options;
154     options["config"].push_back("test-file");
155     const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
156 
157     const config::tree user_config = cli::load_config(mock_cmdline, true);
158     validate_mock_config(user_config, "hello");
159 }
160 
161 
162 ATF_TEST_CASE_WITHOUT_HEAD(load_config__explicit__disable);
163 ATF_TEST_CASE_BODY(load_config__explicit__disable)
164 {
165     mock_system_config(NULL);
166     mock_user_config(NULL);
167 
168     std::map< std::string, std::vector< std::string > > options;
169     options["config"].push_back("none");
170     const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
171 
172     require_eq(engine::default_config(),
173                cli::load_config(mock_cmdline, true));
174 }
175 
176 
177 ATF_TEST_CASE_WITHOUT_HEAD(load_config__explicit__fail);
178 ATF_TEST_CASE_BODY(load_config__explicit__fail)
179 {
180     mock_system_config("ok1");
181     mock_user_config("ok2");
182 
183     create_mock_config("test-file", NULL);
184 
185     std::map< std::string, std::vector< std::string > > options;
186     options["config"].push_back("test-file");
187     const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
188 
189     ATF_REQUIRE_THROW_RE(engine::error, "200",
190                          cli::load_config(mock_cmdline, true));
191 
192     const config::tree config = cli::load_config(mock_cmdline, false);
193     require_eq(engine::default_config(), config);
194 }
195 
196 
197 ATF_TEST_CASE_WITHOUT_HEAD(load_config__user__ok);
198 ATF_TEST_CASE_BODY(load_config__user__ok)
199 {
200     mock_system_config(NULL);
201     mock_user_config("I am the user config");
202 
203     std::map< std::string, std::vector< std::string > > options;
204     options["config"].push_back(cli::config_option.default_value());
205     const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
206 
207     const config::tree user_config = cli::load_config(mock_cmdline, true);
208     validate_mock_config(user_config, "I am the user config");
209 }
210 
211 
212 ATF_TEST_CASE_WITHOUT_HEAD(load_config__user__fail);
213 ATF_TEST_CASE_BODY(load_config__user__fail)
214 {
215     mock_system_config("valid");
216     mock_user_config(NULL);
217 
218     std::map< std::string, std::vector< std::string > > options;
219     options["config"].push_back(cli::config_option.default_value());
220     const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
221 
222     ATF_REQUIRE_THROW_RE(engine::error, "200",
223                          cli::load_config(mock_cmdline, true));
224 
225     const config::tree config = cli::load_config(mock_cmdline, false);
226     require_eq(engine::default_config(), config);
227 }
228 
229 
230 ATF_TEST_CASE_WITHOUT_HEAD(load_config__user__bad_home);
231 ATF_TEST_CASE_BODY(load_config__user__bad_home)
232 {
233     mock_system_config("Fallback system config");
234     utils::setenv("HOME", "");
235 
236     std::map< std::string, std::vector< std::string > > options;
237     options["config"].push_back(cli::config_option.default_value());
238     const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
239 
240     const config::tree user_config = cli::load_config(mock_cmdline, true);
241     validate_mock_config(user_config, "Fallback system config");
242 }
243 
244 
245 ATF_TEST_CASE_WITHOUT_HEAD(load_config__system__ok);
246 ATF_TEST_CASE_BODY(load_config__system__ok)
247 {
248     mock_system_config("I am the system config");
249     utils::setenv("HOME", "/the/user/does/not/exist");
250 
251     std::map< std::string, std::vector< std::string > > options;
252     options["config"].push_back(cli::config_option.default_value());
253     const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
254 
255     const config::tree user_config = cli::load_config(mock_cmdline, true);
256     validate_mock_config(user_config, "I am the system config");
257 }
258 
259 
260 ATF_TEST_CASE_WITHOUT_HEAD(load_config__system__fail);
261 ATF_TEST_CASE_BODY(load_config__system__fail)
262 {
263     mock_system_config(NULL);
264     utils::setenv("HOME", "/the/user/does/not/exist");
265 
266     std::map< std::string, std::vector< std::string > > options;
267     options["config"].push_back(cli::config_option.default_value());
268     const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
269 
270     ATF_REQUIRE_THROW_RE(engine::error, "200",
271                          cli::load_config(mock_cmdline, true));
272 
273     const config::tree config = cli::load_config(mock_cmdline, false);
274     require_eq(engine::default_config(), config);
275 }
276 
277 
278 ATF_TEST_CASE_WITHOUT_HEAD(load_config__overrides__no);
279 ATF_TEST_CASE_BODY(load_config__overrides__no)
280 {
281     utils::setenv("KYUA_CONFDIR", fs::current_path().str());
282 
283     std::map< std::string, std::vector< std::string > > options;
284     options["config"].push_back(cli::config_option.default_value());
285     options["variable"].push_back("architecture=1");
286     options["variable"].push_back("platform=2");
287     const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
288 
289     const config::tree user_config = cli::load_config(mock_cmdline, true);
290     ATF_REQUIRE_EQ("1",
291                    user_config.lookup< config::string_node >("architecture"));
292     ATF_REQUIRE_EQ("2",
293                    user_config.lookup< config::string_node >("platform"));
294 }
295 
296 
297 ATF_TEST_CASE_WITHOUT_HEAD(load_config__overrides__yes);
298 ATF_TEST_CASE_BODY(load_config__overrides__yes)
299 {
300     atf::utils::create_file(
301         "config",
302         "syntax(2)\n"
303         "architecture = 'do not see me'\n"
304         "platform = 'see me'\n");
305 
306     std::map< std::string, std::vector< std::string > > options;
307     options["config"].push_back("config");
308     options["variable"].push_back("architecture=overriden");
309     const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
310 
311     const config::tree user_config = cli::load_config(mock_cmdline, true);
312     ATF_REQUIRE_EQ("overriden",
313                    user_config.lookup< config::string_node >("architecture"));
314     ATF_REQUIRE_EQ("see me",
315                    user_config.lookup< config::string_node >("platform"));
316 }
317 
318 
319 ATF_TEST_CASE_WITHOUT_HEAD(load_config__overrides__fail);
320 ATF_TEST_CASE_BODY(load_config__overrides__fail)
321 {
322     utils::setenv("KYUA_CONFDIR", fs::current_path().str());
323 
324     std::map< std::string, std::vector< std::string > > options;
325     options["config"].push_back(cli::config_option.default_value());
326     options["variable"].push_back(".a=d");
327     const cmdline::parsed_cmdline mock_cmdline(options, cmdline::args_vector());
328 
329     ATF_REQUIRE_THROW_RE(engine::error, "Empty component in key.*'\\.a'",
330                          cli::load_config(mock_cmdline, true));
331 
332     const config::tree config = cli::load_config(mock_cmdline, false);
333     require_eq(engine::default_config(), config);
334 }
335 
336 
337 ATF_INIT_TEST_CASES(tcs)
338 {
339     ATF_ADD_TEST_CASE(tcs, load_config__none);
340     ATF_ADD_TEST_CASE(tcs, load_config__explicit__ok);
341     ATF_ADD_TEST_CASE(tcs, load_config__explicit__disable);
342     ATF_ADD_TEST_CASE(tcs, load_config__explicit__fail);
343     ATF_ADD_TEST_CASE(tcs, load_config__user__ok);
344     ATF_ADD_TEST_CASE(tcs, load_config__user__fail);
345     ATF_ADD_TEST_CASE(tcs, load_config__user__bad_home);
346     ATF_ADD_TEST_CASE(tcs, load_config__system__ok);
347     ATF_ADD_TEST_CASE(tcs, load_config__system__fail);
348     ATF_ADD_TEST_CASE(tcs, load_config__overrides__no);
349     ATF_ADD_TEST_CASE(tcs, load_config__overrides__yes);
350     ATF_ADD_TEST_CASE(tcs, load_config__overrides__fail);
351 }
352