1 // Copyright 2010 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/main.hpp" 30 31 extern "C" { 32 #include <signal.h> 33 } 34 35 #include <cstdlib> 36 37 #include <atf-c++.hpp> 38 39 #include "utils/cmdline/base_command.ipp" 40 #include "utils/cmdline/exceptions.hpp" 41 #include "utils/cmdline/globals.hpp" 42 #include "utils/cmdline/options.hpp" 43 #include "utils/cmdline/parser.hpp" 44 #include "utils/cmdline/ui_mock.hpp" 45 #include "utils/datetime.hpp" 46 #include "utils/defs.hpp" 47 #include "utils/env.hpp" 48 #include "utils/fs/operations.hpp" 49 #include "utils/fs/path.hpp" 50 #include "utils/logging/macros.hpp" 51 #include "utils/logging/operations.hpp" 52 #include "utils/process/child.ipp" 53 #include "utils/process/status.hpp" 54 #include "utils/test_utils.ipp" 55 56 namespace cmdline = utils::cmdline; 57 namespace config = utils::config; 58 namespace datetime = utils::datetime; 59 namespace fs = utils::fs; 60 namespace logging = utils::logging; 61 namespace process = utils::process; 62 63 64 namespace { 65 66 67 /// Fake command implementation that crashes during its execution. 68 class cmd_mock_crash : public cli::cli_command { 69 public: 70 /// Constructs a new mock command. 71 /// 72 /// All command parameters are set to irrelevant values. 73 cmd_mock_crash(void) : 74 cli::cli_command("mock_error", "", 0, 0, "Mock command that crashes") 75 { 76 } 77 78 /// Runs the mock command. 79 /// 80 /// \return Nothing because this function always aborts. 81 int 82 run(cmdline::ui* /* ui */, 83 const cmdline::parsed_cmdline& /* cmdline */, 84 const config::tree& /* user_config */) 85 { 86 utils::abort_without_coredump(); 87 } 88 }; 89 90 91 /// Fake command implementation that throws an exception during its execution. 92 class cmd_mock_error : public cli::cli_command { 93 /// Whether the command raises an exception captured by the parent or not. 94 /// 95 /// If this is true, the command will raise a std::runtime_error exception 96 /// or a subclass of it. The main program is in charge of capturing these 97 /// and reporting them appropriately. If false, this raises another 98 /// exception that does not inherit from std::runtime_error. 99 bool _unhandled; 100 101 public: 102 /// Constructs a new mock command. 103 /// 104 /// \param unhandled If true, make run raise an exception not catched by the 105 /// main program. 106 cmd_mock_error(const bool unhandled) : 107 cli::cli_command("mock_error", "", 0, 0, 108 "Mock command that raises an error"), 109 _unhandled(unhandled) 110 { 111 } 112 113 /// Runs the mock command. 114 /// 115 /// \return Nothing because this function always aborts. 116 /// 117 /// \throw std::logic_error If _unhandled is true. 118 /// \throw std::runtime_error If _unhandled is false. 119 int 120 run(cmdline::ui* /* ui */, 121 const cmdline::parsed_cmdline& /* cmdline */, 122 const config::tree& /* user_config */) 123 { 124 if (_unhandled) 125 throw std::logic_error("This is unhandled"); 126 else 127 throw std::runtime_error("Runtime error"); 128 } 129 }; 130 131 132 /// Fake command implementation that prints messages during its execution. 133 class cmd_mock_write : public cli::cli_command { 134 public: 135 /// Constructs a new mock command. 136 /// 137 /// All command parameters are set to irrelevant values. 138 cmd_mock_write(void) : cli::cli_command( 139 "mock_write", "", 0, 0, "Mock command that prints output") 140 { 141 } 142 143 /// Runs the mock command. 144 /// 145 /// \param ui Object to interact with the I/O of the program. 146 /// 147 /// \return Nothing because this function always aborts. 148 int 149 run(cmdline::ui* ui, 150 const cmdline::parsed_cmdline& /* cmdline */, 151 const config::tree& /* user_config */) 152 { 153 ui->out("stdout message from subcommand"); 154 ui->err("stderr message from subcommand"); 155 return EXIT_FAILURE; 156 } 157 }; 158 159 160 } // anonymous namespace 161 162 163 ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__home); 164 ATF_TEST_CASE_BODY(detail__default_log_name__home) 165 { 166 datetime::set_mock_now(2011, 2, 21, 21, 10, 30, 0); 167 cmdline::init("progname1"); 168 169 utils::setenv("HOME", "/home//fake"); 170 utils::setenv("TMPDIR", "/do/not/use/this"); 171 ATF_REQUIRE_EQ( 172 fs::path("/home/fake/.kyua/logs/progname1.20110221-211030.log"), 173 cli::detail::default_log_name()); 174 } 175 176 177 ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__tmpdir); 178 ATF_TEST_CASE_BODY(detail__default_log_name__tmpdir) 179 { 180 datetime::set_mock_now(2011, 2, 21, 21, 10, 50, 987); 181 cmdline::init("progname2"); 182 183 utils::unsetenv("HOME"); 184 utils::setenv("TMPDIR", "/a/b//c"); 185 ATF_REQUIRE_EQ(fs::path("/a/b/c/progname2.20110221-211050.log"), 186 cli::detail::default_log_name()); 187 } 188 189 190 ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__hardcoded); 191 ATF_TEST_CASE_BODY(detail__default_log_name__hardcoded) 192 { 193 datetime::set_mock_now(2011, 2, 21, 21, 15, 00, 123456); 194 cmdline::init("progname3"); 195 196 utils::unsetenv("HOME"); 197 utils::unsetenv("TMPDIR"); 198 ATF_REQUIRE_EQ(fs::path("/tmp/progname3.20110221-211500.log"), 199 cli::detail::default_log_name()); 200 } 201 202 203 ATF_TEST_CASE_WITHOUT_HEAD(main__no_args); 204 ATF_TEST_CASE_BODY(main__no_args) 205 { 206 logging::set_inmemory(); 207 cmdline::init("progname"); 208 209 const int argc = 1; 210 const char* const argv[] = {"progname", NULL}; 211 212 cmdline::ui_mock ui; 213 ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); 214 ATF_REQUIRE(ui.out_log().empty()); 215 ATF_REQUIRE(atf::utils::grep_collection("Usage error: No command provided", 216 ui.err_log())); 217 ATF_REQUIRE(atf::utils::grep_collection("Type.*progname help", 218 ui.err_log())); 219 } 220 221 222 ATF_TEST_CASE_WITHOUT_HEAD(main__unknown_command); 223 ATF_TEST_CASE_BODY(main__unknown_command) 224 { 225 logging::set_inmemory(); 226 cmdline::init("progname"); 227 228 const int argc = 2; 229 const char* const argv[] = {"progname", "foo", NULL}; 230 231 cmdline::ui_mock ui; 232 ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); 233 ATF_REQUIRE(ui.out_log().empty()); 234 ATF_REQUIRE(atf::utils::grep_collection("Usage error: Unknown command.*foo", 235 ui.err_log())); 236 ATF_REQUIRE(atf::utils::grep_collection("Type.*progname help", 237 ui.err_log())); 238 } 239 240 241 ATF_TEST_CASE_WITHOUT_HEAD(main__logfile__default); 242 ATF_TEST_CASE_BODY(main__logfile__default) 243 { 244 logging::set_inmemory(); 245 datetime::set_mock_now(2011, 2, 21, 21, 30, 00, 0); 246 cmdline::init("progname"); 247 248 const int argc = 1; 249 const char* const argv[] = {"progname", NULL}; 250 251 cmdline::ui_mock ui; 252 ATF_REQUIRE(!fs::exists(fs::path( 253 ".kyua/logs/progname.20110221-213000.log"))); 254 ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); 255 ATF_REQUIRE(fs::exists(fs::path( 256 ".kyua/logs/progname.20110221-213000.log"))); 257 } 258 259 260 ATF_TEST_CASE_WITHOUT_HEAD(main__logfile__override); 261 ATF_TEST_CASE_BODY(main__logfile__override) 262 { 263 logging::set_inmemory(); 264 datetime::set_mock_now(2011, 2, 21, 21, 30, 00, 321); 265 cmdline::init("progname"); 266 267 const int argc = 2; 268 const char* const argv[] = {"progname", "--logfile=test.log", NULL}; 269 270 cmdline::ui_mock ui; 271 ATF_REQUIRE(!fs::exists(fs::path("test.log"))); 272 ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); 273 ATF_REQUIRE(!fs::exists(fs::path( 274 ".kyua/logs/progname.20110221-213000.log"))); 275 ATF_REQUIRE(fs::exists(fs::path("test.log"))); 276 } 277 278 279 ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__default); 280 ATF_TEST_CASE_BODY(main__loglevel__default) 281 { 282 logging::set_inmemory(); 283 cmdline::init("progname"); 284 285 const int argc = 2; 286 const char* const argv[] = {"progname", "--logfile=test.log", NULL}; 287 288 LD("Mock debug message"); 289 LE("Mock error message"); 290 LI("Mock info message"); 291 LW("Mock warning message"); 292 293 cmdline::ui_mock ui; 294 ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); 295 ATF_REQUIRE(!atf::utils::grep_file("Mock debug message", "test.log")); 296 ATF_REQUIRE(atf::utils::grep_file("Mock error message", "test.log")); 297 ATF_REQUIRE(atf::utils::grep_file("Mock info message", "test.log")); 298 ATF_REQUIRE(atf::utils::grep_file("Mock warning message", "test.log")); 299 } 300 301 302 ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__higher); 303 ATF_TEST_CASE_BODY(main__loglevel__higher) 304 { 305 logging::set_inmemory(); 306 cmdline::init("progname"); 307 308 const int argc = 3; 309 const char* const argv[] = {"progname", "--logfile=test.log", 310 "--loglevel=debug", NULL}; 311 312 LD("Mock debug message"); 313 LE("Mock error message"); 314 LI("Mock info message"); 315 LW("Mock warning message"); 316 317 cmdline::ui_mock ui; 318 ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); 319 ATF_REQUIRE(atf::utils::grep_file("Mock debug message", "test.log")); 320 ATF_REQUIRE(atf::utils::grep_file("Mock error message", "test.log")); 321 ATF_REQUIRE(atf::utils::grep_file("Mock info message", "test.log")); 322 ATF_REQUIRE(atf::utils::grep_file("Mock warning message", "test.log")); 323 } 324 325 326 ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__lower); 327 ATF_TEST_CASE_BODY(main__loglevel__lower) 328 { 329 logging::set_inmemory(); 330 cmdline::init("progname"); 331 332 const int argc = 3; 333 const char* const argv[] = {"progname", "--logfile=test.log", 334 "--loglevel=warning", NULL}; 335 336 LD("Mock debug message"); 337 LE("Mock error message"); 338 LI("Mock info message"); 339 LW("Mock warning message"); 340 341 cmdline::ui_mock ui; 342 ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); 343 ATF_REQUIRE(!atf::utils::grep_file("Mock debug message", "test.log")); 344 ATF_REQUIRE(atf::utils::grep_file("Mock error message", "test.log")); 345 ATF_REQUIRE(!atf::utils::grep_file("Mock info message", "test.log")); 346 ATF_REQUIRE(atf::utils::grep_file("Mock warning message", "test.log")); 347 } 348 349 350 ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__error); 351 ATF_TEST_CASE_BODY(main__loglevel__error) 352 { 353 logging::set_inmemory(); 354 cmdline::init("progname"); 355 356 const int argc = 3; 357 const char* const argv[] = {"progname", "--logfile=test.log", 358 "--loglevel=i-am-invalid", NULL}; 359 360 cmdline::ui_mock ui; 361 ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); 362 ATF_REQUIRE(atf::utils::grep_collection("Usage error.*i-am-invalid", 363 ui.err_log())); 364 ATF_REQUIRE(!fs::exists(fs::path("test.log"))); 365 } 366 367 368 ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__ok); 369 ATF_TEST_CASE_BODY(main__subcommand__ok) 370 { 371 logging::set_inmemory(); 372 cmdline::init("progname"); 373 374 const int argc = 2; 375 const char* const argv[] = {"progname", "mock_write", NULL}; 376 377 cmdline::ui_mock ui; 378 ATF_REQUIRE_EQ(EXIT_FAILURE, 379 cli::main(&ui, argc, argv, 380 cli::cli_command_ptr(new cmd_mock_write()))); 381 ATF_REQUIRE_EQ(1, ui.out_log().size()); 382 ATF_REQUIRE_EQ("stdout message from subcommand", ui.out_log()[0]); 383 ATF_REQUIRE_EQ(1, ui.err_log().size()); 384 ATF_REQUIRE_EQ("stderr message from subcommand", ui.err_log()[0]); 385 } 386 387 388 ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__invalid_args); 389 ATF_TEST_CASE_BODY(main__subcommand__invalid_args) 390 { 391 logging::set_inmemory(); 392 cmdline::init("progname"); 393 394 const int argc = 3; 395 const char* const argv[] = {"progname", "mock_write", "bar", NULL}; 396 397 cmdline::ui_mock ui; 398 ATF_REQUIRE_EQ(3, 399 cli::main(&ui, argc, argv, 400 cli::cli_command_ptr(new cmd_mock_write()))); 401 ATF_REQUIRE(ui.out_log().empty()); 402 ATF_REQUIRE(atf::utils::grep_collection( 403 "Usage error for command mock_write: Too many arguments.", 404 ui.err_log())); 405 ATF_REQUIRE(atf::utils::grep_collection("Type.*progname help", 406 ui.err_log())); 407 } 408 409 410 ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__runtime_error); 411 ATF_TEST_CASE_BODY(main__subcommand__runtime_error) 412 { 413 logging::set_inmemory(); 414 cmdline::init("progname"); 415 416 const int argc = 2; 417 const char* const argv[] = {"progname", "mock_error", NULL}; 418 419 cmdline::ui_mock ui; 420 ATF_REQUIRE_EQ(2, cli::main(&ui, argc, argv, 421 cli::cli_command_ptr(new cmd_mock_error(false)))); 422 ATF_REQUIRE(ui.out_log().empty()); 423 ATF_REQUIRE(atf::utils::grep_collection("progname: E: Runtime error.", 424 ui.err_log())); 425 } 426 427 428 ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__unhandled_exception); 429 ATF_TEST_CASE_BODY(main__subcommand__unhandled_exception) 430 { 431 logging::set_inmemory(); 432 cmdline::init("progname"); 433 434 const int argc = 2; 435 const char* const argv[] = {"progname", "mock_error", NULL}; 436 437 cmdline::ui_mock ui; 438 ATF_REQUIRE_THROW(std::logic_error, cli::main(&ui, argc, argv, 439 cli::cli_command_ptr(new cmd_mock_error(true)))); 440 } 441 442 443 static void 444 do_subcommand_crash(void) 445 { 446 logging::set_inmemory(); 447 cmdline::init("progname"); 448 449 const int argc = 2; 450 const char* const argv[] = {"progname", "mock_error", NULL}; 451 452 cmdline::ui_mock ui; 453 cli::main(&ui, argc, argv, 454 cli::cli_command_ptr(new cmd_mock_crash())); 455 } 456 457 458 ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__crash); 459 ATF_TEST_CASE_BODY(main__subcommand__crash) 460 { 461 const process::status status = process::child::fork_files( 462 do_subcommand_crash, fs::path("stdout.txt"), 463 fs::path("stderr.txt"))->wait(); 464 ATF_REQUIRE(status.signaled()); 465 ATF_REQUIRE_EQ(SIGABRT, status.termsig()); 466 ATF_REQUIRE(atf::utils::grep_file("Fatal signal", "stderr.txt")); 467 } 468 469 470 ATF_INIT_TEST_CASES(tcs) 471 { 472 ATF_ADD_TEST_CASE(tcs, detail__default_log_name__home); 473 ATF_ADD_TEST_CASE(tcs, detail__default_log_name__tmpdir); 474 ATF_ADD_TEST_CASE(tcs, detail__default_log_name__hardcoded); 475 476 ATF_ADD_TEST_CASE(tcs, main__no_args); 477 ATF_ADD_TEST_CASE(tcs, main__unknown_command); 478 ATF_ADD_TEST_CASE(tcs, main__logfile__default); 479 ATF_ADD_TEST_CASE(tcs, main__logfile__override); 480 ATF_ADD_TEST_CASE(tcs, main__loglevel__default); 481 ATF_ADD_TEST_CASE(tcs, main__loglevel__higher); 482 ATF_ADD_TEST_CASE(tcs, main__loglevel__lower); 483 ATF_ADD_TEST_CASE(tcs, main__loglevel__error); 484 ATF_ADD_TEST_CASE(tcs, main__subcommand__ok); 485 ATF_ADD_TEST_CASE(tcs, main__subcommand__invalid_args); 486 ATF_ADD_TEST_CASE(tcs, main__subcommand__runtime_error); 487 ATF_ADD_TEST_CASE(tcs, main__subcommand__unhandled_exception); 488 ATF_ADD_TEST_CASE(tcs, main__subcommand__crash); 489 } 490