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 31extern "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 56namespace cmdline = utils::cmdline; 57namespace config = utils::config; 58namespace datetime = utils::datetime; 59namespace fs = utils::fs; 60namespace logging = utils::logging; 61namespace process = utils::process; 62 63 64namespace { 65 66 67/// Fake command implementation that crashes during its execution. 68class cmd_mock_crash : public cli::cli_command { 69public: 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. 92class 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 101public: 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. 133class cmd_mock_write : public cli::cli_command { 134public: 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 163ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__home); 164ATF_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 177ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__tmpdir); 178ATF_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 190ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__hardcoded); 191ATF_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 203ATF_TEST_CASE_WITHOUT_HEAD(main__no_args); 204ATF_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 222ATF_TEST_CASE_WITHOUT_HEAD(main__unknown_command); 223ATF_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 241ATF_TEST_CASE_WITHOUT_HEAD(main__logfile__default); 242ATF_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 260ATF_TEST_CASE_WITHOUT_HEAD(main__logfile__override); 261ATF_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 279ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__default); 280ATF_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 302ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__higher); 303ATF_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 326ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__lower); 327ATF_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 350ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__error); 351ATF_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 368ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__ok); 369ATF_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 388ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__invalid_args); 389ATF_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 410ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__runtime_error); 411ATF_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 428ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__unhandled_exception); 429ATF_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 443static void 444do_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 458ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__crash); 459ATF_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 470ATF_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