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 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 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 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 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 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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 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