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