diff options
Diffstat (limited to 'cli/main_test.cpp')
-rw-r--r-- | cli/main_test.cpp | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/cli/main_test.cpp b/cli/main_test.cpp new file mode 100644 index 000000000000..70d167ff6963 --- /dev/null +++ b/cli/main_test.cpp @@ -0,0 +1,489 @@ +// Copyright 2010 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "cli/main.hpp" + +extern "C" { +#include <signal.h> +} + +#include <cstdlib> + +#include <atf-c++.hpp> + +#include "utils/cmdline/base_command.ipp" +#include "utils/cmdline/exceptions.hpp" +#include "utils/cmdline/globals.hpp" +#include "utils/cmdline/options.hpp" +#include "utils/cmdline/parser.hpp" +#include "utils/cmdline/ui_mock.hpp" +#include "utils/datetime.hpp" +#include "utils/defs.hpp" +#include "utils/env.hpp" +#include "utils/fs/operations.hpp" +#include "utils/fs/path.hpp" +#include "utils/logging/macros.hpp" +#include "utils/logging/operations.hpp" +#include "utils/process/child.ipp" +#include "utils/process/status.hpp" +#include "utils/test_utils.ipp" + +namespace cmdline = utils::cmdline; +namespace config = utils::config; +namespace datetime = utils::datetime; +namespace fs = utils::fs; +namespace logging = utils::logging; +namespace process = utils::process; + + +namespace { + + +/// Fake command implementation that crashes during its execution. +class cmd_mock_crash : public cli::cli_command { +public: + /// Constructs a new mock command. + /// + /// All command parameters are set to irrelevant values. + cmd_mock_crash(void) : + cli::cli_command("mock_error", "", 0, 0, "Mock command that crashes") + { + } + + /// Runs the mock command. + /// + /// \return Nothing because this function always aborts. + int + run(cmdline::ui* /* ui */, + const cmdline::parsed_cmdline& /* cmdline */, + const config::tree& /* user_config */) + { + utils::abort_without_coredump(); + } +}; + + +/// Fake command implementation that throws an exception during its execution. +class cmd_mock_error : public cli::cli_command { + /// Whether the command raises an exception captured by the parent or not. + /// + /// If this is true, the command will raise a std::runtime_error exception + /// or a subclass of it. The main program is in charge of capturing these + /// and reporting them appropriately. If false, this raises another + /// exception that does not inherit from std::runtime_error. + bool _unhandled; + +public: + /// Constructs a new mock command. + /// + /// \param unhandled If true, make run raise an exception not catched by the + /// main program. + cmd_mock_error(const bool unhandled) : + cli::cli_command("mock_error", "", 0, 0, + "Mock command that raises an error"), + _unhandled(unhandled) + { + } + + /// Runs the mock command. + /// + /// \return Nothing because this function always aborts. + /// + /// \throw std::logic_error If _unhandled is true. + /// \throw std::runtime_error If _unhandled is false. + int + run(cmdline::ui* /* ui */, + const cmdline::parsed_cmdline& /* cmdline */, + const config::tree& /* user_config */) + { + if (_unhandled) + throw std::logic_error("This is unhandled"); + else + throw std::runtime_error("Runtime error"); + } +}; + + +/// Fake command implementation that prints messages during its execution. +class cmd_mock_write : public cli::cli_command { +public: + /// Constructs a new mock command. + /// + /// All command parameters are set to irrelevant values. + cmd_mock_write(void) : cli::cli_command( + "mock_write", "", 0, 0, "Mock command that prints output") + { + } + + /// Runs the mock command. + /// + /// \param ui Object to interact with the I/O of the program. + /// + /// \return Nothing because this function always aborts. + int + run(cmdline::ui* ui, + const cmdline::parsed_cmdline& /* cmdline */, + const config::tree& /* user_config */) + { + ui->out("stdout message from subcommand"); + ui->err("stderr message from subcommand"); + return EXIT_FAILURE; + } +}; + + +} // anonymous namespace + + +ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__home); +ATF_TEST_CASE_BODY(detail__default_log_name__home) +{ + datetime::set_mock_now(2011, 2, 21, 21, 10, 30, 0); + cmdline::init("progname1"); + + utils::setenv("HOME", "/home//fake"); + utils::setenv("TMPDIR", "/do/not/use/this"); + ATF_REQUIRE_EQ( + fs::path("/home/fake/.kyua/logs/progname1.20110221-211030.log"), + cli::detail::default_log_name()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__tmpdir); +ATF_TEST_CASE_BODY(detail__default_log_name__tmpdir) +{ + datetime::set_mock_now(2011, 2, 21, 21, 10, 50, 987); + cmdline::init("progname2"); + + utils::unsetenv("HOME"); + utils::setenv("TMPDIR", "/a/b//c"); + ATF_REQUIRE_EQ(fs::path("/a/b/c/progname2.20110221-211050.log"), + cli::detail::default_log_name()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__hardcoded); +ATF_TEST_CASE_BODY(detail__default_log_name__hardcoded) +{ + datetime::set_mock_now(2011, 2, 21, 21, 15, 00, 123456); + cmdline::init("progname3"); + + utils::unsetenv("HOME"); + utils::unsetenv("TMPDIR"); + ATF_REQUIRE_EQ(fs::path("/tmp/progname3.20110221-211500.log"), + cli::detail::default_log_name()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__no_args); +ATF_TEST_CASE_BODY(main__no_args) +{ + logging::set_inmemory(); + cmdline::init("progname"); + + const int argc = 1; + const char* const argv[] = {"progname", NULL}; + + cmdline::ui_mock ui; + ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); + ATF_REQUIRE(ui.out_log().empty()); + ATF_REQUIRE(atf::utils::grep_collection("Usage error: No command provided", + ui.err_log())); + ATF_REQUIRE(atf::utils::grep_collection("Type.*progname help", + ui.err_log())); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__unknown_command); +ATF_TEST_CASE_BODY(main__unknown_command) +{ + logging::set_inmemory(); + cmdline::init("progname"); + + const int argc = 2; + const char* const argv[] = {"progname", "foo", NULL}; + + cmdline::ui_mock ui; + ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); + ATF_REQUIRE(ui.out_log().empty()); + ATF_REQUIRE(atf::utils::grep_collection("Usage error: Unknown command.*foo", + ui.err_log())); + ATF_REQUIRE(atf::utils::grep_collection("Type.*progname help", + ui.err_log())); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__logfile__default); +ATF_TEST_CASE_BODY(main__logfile__default) +{ + logging::set_inmemory(); + datetime::set_mock_now(2011, 2, 21, 21, 30, 00, 0); + cmdline::init("progname"); + + const int argc = 1; + const char* const argv[] = {"progname", NULL}; + + cmdline::ui_mock ui; + ATF_REQUIRE(!fs::exists(fs::path( + ".kyua/logs/progname.20110221-213000.log"))); + ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); + ATF_REQUIRE(fs::exists(fs::path( + ".kyua/logs/progname.20110221-213000.log"))); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__logfile__override); +ATF_TEST_CASE_BODY(main__logfile__override) +{ + logging::set_inmemory(); + datetime::set_mock_now(2011, 2, 21, 21, 30, 00, 321); + cmdline::init("progname"); + + const int argc = 2; + const char* const argv[] = {"progname", "--logfile=test.log", NULL}; + + cmdline::ui_mock ui; + ATF_REQUIRE(!fs::exists(fs::path("test.log"))); + ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); + ATF_REQUIRE(!fs::exists(fs::path( + ".kyua/logs/progname.20110221-213000.log"))); + ATF_REQUIRE(fs::exists(fs::path("test.log"))); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__default); +ATF_TEST_CASE_BODY(main__loglevel__default) +{ + logging::set_inmemory(); + cmdline::init("progname"); + + const int argc = 2; + const char* const argv[] = {"progname", "--logfile=test.log", NULL}; + + LD("Mock debug message"); + LE("Mock error message"); + LI("Mock info message"); + LW("Mock warning message"); + + cmdline::ui_mock ui; + ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); + ATF_REQUIRE(!atf::utils::grep_file("Mock debug message", "test.log")); + ATF_REQUIRE(atf::utils::grep_file("Mock error message", "test.log")); + ATF_REQUIRE(atf::utils::grep_file("Mock info message", "test.log")); + ATF_REQUIRE(atf::utils::grep_file("Mock warning message", "test.log")); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__higher); +ATF_TEST_CASE_BODY(main__loglevel__higher) +{ + logging::set_inmemory(); + cmdline::init("progname"); + + const int argc = 3; + const char* const argv[] = {"progname", "--logfile=test.log", + "--loglevel=debug", NULL}; + + LD("Mock debug message"); + LE("Mock error message"); + LI("Mock info message"); + LW("Mock warning message"); + + cmdline::ui_mock ui; + ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); + ATF_REQUIRE(atf::utils::grep_file("Mock debug message", "test.log")); + ATF_REQUIRE(atf::utils::grep_file("Mock error message", "test.log")); + ATF_REQUIRE(atf::utils::grep_file("Mock info message", "test.log")); + ATF_REQUIRE(atf::utils::grep_file("Mock warning message", "test.log")); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__lower); +ATF_TEST_CASE_BODY(main__loglevel__lower) +{ + logging::set_inmemory(); + cmdline::init("progname"); + + const int argc = 3; + const char* const argv[] = {"progname", "--logfile=test.log", + "--loglevel=warning", NULL}; + + LD("Mock debug message"); + LE("Mock error message"); + LI("Mock info message"); + LW("Mock warning message"); + + cmdline::ui_mock ui; + ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); + ATF_REQUIRE(!atf::utils::grep_file("Mock debug message", "test.log")); + ATF_REQUIRE(atf::utils::grep_file("Mock error message", "test.log")); + ATF_REQUIRE(!atf::utils::grep_file("Mock info message", "test.log")); + ATF_REQUIRE(atf::utils::grep_file("Mock warning message", "test.log")); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__error); +ATF_TEST_CASE_BODY(main__loglevel__error) +{ + logging::set_inmemory(); + cmdline::init("progname"); + + const int argc = 3; + const char* const argv[] = {"progname", "--logfile=test.log", + "--loglevel=i-am-invalid", NULL}; + + cmdline::ui_mock ui; + ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv)); + ATF_REQUIRE(atf::utils::grep_collection("Usage error.*i-am-invalid", + ui.err_log())); + ATF_REQUIRE(!fs::exists(fs::path("test.log"))); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__ok); +ATF_TEST_CASE_BODY(main__subcommand__ok) +{ + logging::set_inmemory(); + cmdline::init("progname"); + + const int argc = 2; + const char* const argv[] = {"progname", "mock_write", NULL}; + + cmdline::ui_mock ui; + ATF_REQUIRE_EQ(EXIT_FAILURE, + cli::main(&ui, argc, argv, + cli::cli_command_ptr(new cmd_mock_write()))); + ATF_REQUIRE_EQ(1, ui.out_log().size()); + ATF_REQUIRE_EQ("stdout message from subcommand", ui.out_log()[0]); + ATF_REQUIRE_EQ(1, ui.err_log().size()); + ATF_REQUIRE_EQ("stderr message from subcommand", ui.err_log()[0]); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__invalid_args); +ATF_TEST_CASE_BODY(main__subcommand__invalid_args) +{ + logging::set_inmemory(); + cmdline::init("progname"); + + const int argc = 3; + const char* const argv[] = {"progname", "mock_write", "bar", NULL}; + + cmdline::ui_mock ui; + ATF_REQUIRE_EQ(3, + cli::main(&ui, argc, argv, + cli::cli_command_ptr(new cmd_mock_write()))); + ATF_REQUIRE(ui.out_log().empty()); + ATF_REQUIRE(atf::utils::grep_collection( + "Usage error for command mock_write: Too many arguments.", + ui.err_log())); + ATF_REQUIRE(atf::utils::grep_collection("Type.*progname help", + ui.err_log())); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__runtime_error); +ATF_TEST_CASE_BODY(main__subcommand__runtime_error) +{ + logging::set_inmemory(); + cmdline::init("progname"); + + const int argc = 2; + const char* const argv[] = {"progname", "mock_error", NULL}; + + cmdline::ui_mock ui; + ATF_REQUIRE_EQ(2, cli::main(&ui, argc, argv, + cli::cli_command_ptr(new cmd_mock_error(false)))); + ATF_REQUIRE(ui.out_log().empty()); + ATF_REQUIRE(atf::utils::grep_collection("progname: E: Runtime error.", + ui.err_log())); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__unhandled_exception); +ATF_TEST_CASE_BODY(main__subcommand__unhandled_exception) +{ + logging::set_inmemory(); + cmdline::init("progname"); + + const int argc = 2; + const char* const argv[] = {"progname", "mock_error", NULL}; + + cmdline::ui_mock ui; + ATF_REQUIRE_THROW(std::logic_error, cli::main(&ui, argc, argv, + cli::cli_command_ptr(new cmd_mock_error(true)))); +} + + +static void +do_subcommand_crash(void) +{ + logging::set_inmemory(); + cmdline::init("progname"); + + const int argc = 2; + const char* const argv[] = {"progname", "mock_error", NULL}; + + cmdline::ui_mock ui; + cli::main(&ui, argc, argv, + cli::cli_command_ptr(new cmd_mock_crash())); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__crash); +ATF_TEST_CASE_BODY(main__subcommand__crash) +{ + const process::status status = process::child::fork_files( + do_subcommand_crash, fs::path("stdout.txt"), + fs::path("stderr.txt"))->wait(); + ATF_REQUIRE(status.signaled()); + ATF_REQUIRE_EQ(SIGABRT, status.termsig()); + ATF_REQUIRE(atf::utils::grep_file("Fatal signal", "stderr.txt")); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, detail__default_log_name__home); + ATF_ADD_TEST_CASE(tcs, detail__default_log_name__tmpdir); + ATF_ADD_TEST_CASE(tcs, detail__default_log_name__hardcoded); + + ATF_ADD_TEST_CASE(tcs, main__no_args); + ATF_ADD_TEST_CASE(tcs, main__unknown_command); + ATF_ADD_TEST_CASE(tcs, main__logfile__default); + ATF_ADD_TEST_CASE(tcs, main__logfile__override); + ATF_ADD_TEST_CASE(tcs, main__loglevel__default); + ATF_ADD_TEST_CASE(tcs, main__loglevel__higher); + ATF_ADD_TEST_CASE(tcs, main__loglevel__lower); + ATF_ADD_TEST_CASE(tcs, main__loglevel__error); + ATF_ADD_TEST_CASE(tcs, main__subcommand__ok); + ATF_ADD_TEST_CASE(tcs, main__subcommand__invalid_args); + ATF_ADD_TEST_CASE(tcs, main__subcommand__runtime_error); + ATF_ADD_TEST_CASE(tcs, main__subcommand__unhandled_exception); + ATF_ADD_TEST_CASE(tcs, main__subcommand__crash); +} |