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