xref: /src/contrib/kyua/cli/cmd_debug.cpp (revision 8a21c17ccfecf0ee54becd46d3e08ccab76ca3d6)
108334c51SBrooks Davis // Copyright 2011 The Kyua Authors.
208334c51SBrooks Davis // All rights reserved.
308334c51SBrooks Davis //
408334c51SBrooks Davis // Redistribution and use in source and binary forms, with or without
508334c51SBrooks Davis // modification, are permitted provided that the following conditions are
608334c51SBrooks Davis // met:
708334c51SBrooks Davis //
808334c51SBrooks Davis // * Redistributions of source code must retain the above copyright
908334c51SBrooks Davis //   notice, this list of conditions and the following disclaimer.
1008334c51SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
1108334c51SBrooks Davis //   notice, this list of conditions and the following disclaimer in the
1208334c51SBrooks Davis //   documentation and/or other materials provided with the distribution.
1308334c51SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
1408334c51SBrooks Davis //   may be used to endorse or promote products derived from this software
1508334c51SBrooks Davis //   without specific prior written permission.
1608334c51SBrooks Davis //
1708334c51SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1808334c51SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1908334c51SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2008334c51SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2108334c51SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2208334c51SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2308334c51SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2408334c51SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2508334c51SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2608334c51SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2708334c51SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2808334c51SBrooks Davis 
2908334c51SBrooks Davis #include "cli/cmd_debug.hpp"
3008334c51SBrooks Davis 
318a21c17cSIgor Ostapenko extern "C" {
328a21c17cSIgor Ostapenko #include <unistd.h>
338a21c17cSIgor Ostapenko }
348a21c17cSIgor Ostapenko 
3508334c51SBrooks Davis #include <cstdlib>
366b822279SIgor Ostapenko #include <iostream>
3708334c51SBrooks Davis 
3808334c51SBrooks Davis #include "cli/common.ipp"
3908334c51SBrooks Davis #include "drivers/debug_test.hpp"
4008334c51SBrooks Davis #include "engine/filters.hpp"
4108334c51SBrooks Davis #include "utils/cmdline/exceptions.hpp"
4208334c51SBrooks Davis #include "utils/cmdline/options.hpp"
4308334c51SBrooks Davis #include "utils/cmdline/parser.ipp"
4408334c51SBrooks Davis #include "utils/cmdline/ui.hpp"
4508334c51SBrooks Davis #include "utils/format/macros.hpp"
468a21c17cSIgor Ostapenko #include "utils/fs/path.hpp"
478a21c17cSIgor Ostapenko #include "utils/process/child.ipp"
486b822279SIgor Ostapenko #include "utils/process/executor.hpp"
498a21c17cSIgor Ostapenko #include "utils/process/operations.hpp"
508a21c17cSIgor Ostapenko #include "utils/process/status.hpp"
5108334c51SBrooks Davis 
5208334c51SBrooks Davis namespace cmdline = utils::cmdline;
5308334c51SBrooks Davis namespace config = utils::config;
546b822279SIgor Ostapenko namespace executor = utils::process::executor;
558a21c17cSIgor Ostapenko namespace process = utils::process;
5608334c51SBrooks Davis 
5708334c51SBrooks Davis using cli::cmd_debug;
588a21c17cSIgor Ostapenko using utils::process::args_vector;
598a21c17cSIgor Ostapenko using utils::process::child;
6008334c51SBrooks Davis 
6108334c51SBrooks Davis 
626b822279SIgor Ostapenko namespace {
636b822279SIgor Ostapenko 
646b822279SIgor Ostapenko 
656b822279SIgor Ostapenko const cmdline::bool_option pause_before_cleanup_upon_fail_option(
666b822279SIgor Ostapenko     'p',
676b822279SIgor Ostapenko     "pause-before-cleanup-upon-fail",
686b822279SIgor Ostapenko     "Pauses right before the test cleanup upon fail");
696b822279SIgor Ostapenko 
706b822279SIgor Ostapenko 
716b822279SIgor Ostapenko const cmdline::bool_option pause_before_cleanup_option(
726b822279SIgor Ostapenko     "pause-before-cleanup",
736b822279SIgor Ostapenko     "Pauses right before the test cleanup");
746b822279SIgor Ostapenko 
756b822279SIgor Ostapenko 
768a21c17cSIgor Ostapenko static const char* DEFAULT_CMD = "$SHELL";
778a21c17cSIgor Ostapenko const cmdline::string_option execute_option(
788a21c17cSIgor Ostapenko     'x', "execute",
798a21c17cSIgor Ostapenko     "A command to run within the given execenv upon test failure",
808a21c17cSIgor Ostapenko     "cmd", DEFAULT_CMD, true);
818a21c17cSIgor Ostapenko 
828a21c17cSIgor Ostapenko 
838a21c17cSIgor Ostapenko /// Functor to execute a program.
848a21c17cSIgor Ostapenko class execute {
858a21c17cSIgor Ostapenko     const std::string& _cmd;
868a21c17cSIgor Ostapenko     executor::exit_handle& _eh;
878a21c17cSIgor Ostapenko 
888a21c17cSIgor Ostapenko public:
898a21c17cSIgor Ostapenko     /// Constructor.
908a21c17cSIgor Ostapenko     ///
918a21c17cSIgor Ostapenko     /// \param program Program binary absolute path.
928a21c17cSIgor Ostapenko     /// \param args Program arguments.
execute(const std::string & cmd_,executor::exit_handle & eh_)938a21c17cSIgor Ostapenko     execute(
948a21c17cSIgor Ostapenko         const std::string& cmd_,
958a21c17cSIgor Ostapenko         executor::exit_handle& eh_) :
968a21c17cSIgor Ostapenko         _cmd(cmd_),
978a21c17cSIgor Ostapenko         _eh(eh_)
988a21c17cSIgor Ostapenko     {
998a21c17cSIgor Ostapenko     }
1008a21c17cSIgor Ostapenko 
1018a21c17cSIgor Ostapenko     /// Body of the subprocess.
1028a21c17cSIgor Ostapenko     void
operator ()(void)1038a21c17cSIgor Ostapenko     operator()(void)
1048a21c17cSIgor Ostapenko     {
1058a21c17cSIgor Ostapenko         if (::chdir(_eh.work_directory().c_str()) == -1) {
1068a21c17cSIgor Ostapenko             std::cerr << "execute: chdir() errors: "
1078a21c17cSIgor Ostapenko                 << strerror(errno) << ".\n";
1088a21c17cSIgor Ostapenko             std::exit(EXIT_FAILURE);
1098a21c17cSIgor Ostapenko         }
1108a21c17cSIgor Ostapenko 
1118a21c17cSIgor Ostapenko         std::string program_path = "/bin/sh";
1128a21c17cSIgor Ostapenko         const char* shell = std::getenv("SHELL");
1138a21c17cSIgor Ostapenko         if (shell)
1148a21c17cSIgor Ostapenko             program_path = shell;
1158a21c17cSIgor Ostapenko 
1168a21c17cSIgor Ostapenko         args_vector av;
1178a21c17cSIgor Ostapenko         if (!(_cmd.empty() || _cmd == DEFAULT_CMD)) {
1188a21c17cSIgor Ostapenko             av.push_back("-c");
1198a21c17cSIgor Ostapenko             av.push_back(_cmd);
1208a21c17cSIgor Ostapenko         }
1218a21c17cSIgor Ostapenko 
1228a21c17cSIgor Ostapenko         process::exec(utils::fs::path(program_path), av);
1238a21c17cSIgor Ostapenko     }
1248a21c17cSIgor Ostapenko };
1258a21c17cSIgor Ostapenko 
1268a21c17cSIgor Ostapenko 
1276b822279SIgor Ostapenko /// The debugger interface implementation.
1286b822279SIgor Ostapenko class dbg : public engine::debugger {
1296b822279SIgor Ostapenko     /// Object to interact with the I/O of the program.
1306b822279SIgor Ostapenko     cmdline::ui* _ui;
1316b822279SIgor Ostapenko 
1326b822279SIgor Ostapenko     /// Representation of the command line to the subcommand.
1336b822279SIgor Ostapenko     const cmdline::parsed_cmdline& _cmdline;
1346b822279SIgor Ostapenko 
1356b822279SIgor Ostapenko public:
1366b822279SIgor Ostapenko     /// Constructor.
1376b822279SIgor Ostapenko     ///
1386b822279SIgor Ostapenko     /// \param ui_ Object to interact with the I/O of the program.
1396b822279SIgor Ostapenko     /// \param cmdline Representation of the command line to the subcommand.
dbg(cmdline::ui * ui,const cmdline::parsed_cmdline & cmdline)1406b822279SIgor Ostapenko     dbg(cmdline::ui* ui, const cmdline::parsed_cmdline& cmdline) :
1416b822279SIgor Ostapenko         _ui(ui), _cmdline(cmdline)
1426b822279SIgor Ostapenko     {}
1436b822279SIgor Ostapenko 
before_cleanup(const model::test_program_ptr &,const model::test_case &,optional<model::test_result> & result,executor::exit_handle & eh) const1446b822279SIgor Ostapenko     void before_cleanup(
1456b822279SIgor Ostapenko         const model::test_program_ptr&,
1466b822279SIgor Ostapenko         const model::test_case&,
1476b822279SIgor Ostapenko         optional< model::test_result >& result,
1486b822279SIgor Ostapenko         executor::exit_handle& eh) const
1496b822279SIgor Ostapenko     {
1506b822279SIgor Ostapenko         if (_cmdline.has_option(pause_before_cleanup_upon_fail_option
1516b822279SIgor Ostapenko             .long_name())) {
1526b822279SIgor Ostapenko             if (result && !result.get().good()) {
1536b822279SIgor Ostapenko                 _ui->out("The test failed and paused right before its cleanup "
1546b822279SIgor Ostapenko                     "routine.");
1556b822279SIgor Ostapenko                 _ui->out(F("Test work dir: %s") % eh.work_directory().str());
156ecb58f93SIgor Ostapenko                 _ui->out("Press <Enter> to continue...");
1576b822279SIgor Ostapenko                 (void) std::cin.get();
1586b822279SIgor Ostapenko             }
1596b822279SIgor Ostapenko         } else if (_cmdline.has_option(pause_before_cleanup_option
1606b822279SIgor Ostapenko             .long_name())) {
1616b822279SIgor Ostapenko             _ui->out("The test paused right before its cleanup routine.");
1626b822279SIgor Ostapenko             _ui->out(F("Test work dir: %s") % eh.work_directory().str());
163ecb58f93SIgor Ostapenko             _ui->out("Press <Enter> to continue...");
1646b822279SIgor Ostapenko             (void) std::cin.get();
1656b822279SIgor Ostapenko         }
1666b822279SIgor Ostapenko     };
1676b822279SIgor Ostapenko 
upon_test_failure(const model::test_program_ptr &,const model::test_case &,optional<model::test_result> &,executor::exit_handle & eh) const1688a21c17cSIgor Ostapenko     void upon_test_failure(
1698a21c17cSIgor Ostapenko         const model::test_program_ptr&,
1708a21c17cSIgor Ostapenko         const model::test_case&,
1718a21c17cSIgor Ostapenko         optional< model::test_result >&,
1728a21c17cSIgor Ostapenko         executor::exit_handle& eh) const
1738a21c17cSIgor Ostapenko     {
1748a21c17cSIgor Ostapenko         if (!_cmdline.has_option(execute_option.long_name()))
1758a21c17cSIgor Ostapenko             return;
1768a21c17cSIgor Ostapenko         const std::string& cmd = _cmdline.get_option<cmdline::string_option>(
1778a21c17cSIgor Ostapenko             execute_option.long_name());
1788a21c17cSIgor Ostapenko         std::unique_ptr< process::child > child = child::fork_interactive(
1798a21c17cSIgor Ostapenko             execute(cmd, eh));
1808a21c17cSIgor Ostapenko         (void) child->wait();
1818a21c17cSIgor Ostapenko     };
1828a21c17cSIgor Ostapenko 
1836b822279SIgor Ostapenko };
1846b822279SIgor Ostapenko 
1856b822279SIgor Ostapenko 
1866b822279SIgor Ostapenko }  // anonymous namespace
1876b822279SIgor Ostapenko 
1886b822279SIgor Ostapenko 
18908334c51SBrooks Davis /// Default constructor for cmd_debug.
cmd_debug(void)19008334c51SBrooks Davis cmd_debug::cmd_debug(void) : cli_command(
19108334c51SBrooks Davis     "debug", "test_case", 1, 1,
19208334c51SBrooks Davis     "Executes a single test case providing facilities for debugging")
19308334c51SBrooks Davis {
19408334c51SBrooks Davis     add_option(build_root_option);
19508334c51SBrooks Davis     add_option(kyuafile_option);
19608334c51SBrooks Davis 
1976b822279SIgor Ostapenko     add_option(pause_before_cleanup_upon_fail_option);
1986b822279SIgor Ostapenko     add_option(pause_before_cleanup_option);
1996b822279SIgor Ostapenko 
20008334c51SBrooks Davis     add_option(cmdline::path_option(
20108334c51SBrooks Davis         "stdout", "Where to direct the standard output of the test case",
20208334c51SBrooks Davis         "path", "/dev/stdout"));
20308334c51SBrooks Davis 
20408334c51SBrooks Davis     add_option(cmdline::path_option(
20508334c51SBrooks Davis         "stderr", "Where to direct the standard error of the test case",
20608334c51SBrooks Davis         "path", "/dev/stderr"));
2078a21c17cSIgor Ostapenko 
2088a21c17cSIgor Ostapenko     add_option(execute_option);
20908334c51SBrooks Davis }
21008334c51SBrooks Davis 
21108334c51SBrooks Davis 
21208334c51SBrooks Davis /// Entry point for the "debug" subcommand.
21308334c51SBrooks Davis ///
21408334c51SBrooks Davis /// \param ui Object to interact with the I/O of the program.
21508334c51SBrooks Davis /// \param cmdline Representation of the command line to the subcommand.
21608334c51SBrooks Davis /// \param user_config The runtime debuguration of the program.
21708334c51SBrooks Davis ///
21808334c51SBrooks Davis /// \return 0 if everything is OK, 1 if any of the necessary documents cannot be
21908334c51SBrooks Davis /// opened.
22008334c51SBrooks Davis int
run(cmdline::ui * ui,const cmdline::parsed_cmdline & cmdline,const config::tree & user_config)22108334c51SBrooks Davis cmd_debug::run(cmdline::ui* ui, const cmdline::parsed_cmdline& cmdline,
22208334c51SBrooks Davis                const config::tree& user_config)
22308334c51SBrooks Davis {
22408334c51SBrooks Davis     const std::string& test_case_name = cmdline.arguments()[0];
22508334c51SBrooks Davis     if (test_case_name.find(':') == std::string::npos)
22608334c51SBrooks Davis         throw cmdline::usage_error(F("'%s' is not a test case identifier "
22708334c51SBrooks Davis                                      "(missing ':'?)") % test_case_name);
22808334c51SBrooks Davis     const engine::test_filter filter = engine::test_filter::parse(
22908334c51SBrooks Davis         test_case_name);
23008334c51SBrooks Davis 
231350f3197SIgor Ostapenko     engine::debugger_ptr debugger = nullptr;
232350f3197SIgor Ostapenko     if (cmdline.has_option(pause_before_cleanup_upon_fail_option.long_name())
2338a21c17cSIgor Ostapenko         || cmdline.has_option(pause_before_cleanup_option.long_name())
2348a21c17cSIgor Ostapenko         || cmdline.has_option(execute_option.long_name())) {
235350f3197SIgor Ostapenko         debugger = std::shared_ptr< engine::debugger >(new dbg(ui, cmdline));
236350f3197SIgor Ostapenko     }
2376b822279SIgor Ostapenko 
23808334c51SBrooks Davis     const drivers::debug_test::result result = drivers::debug_test::drive(
2396b822279SIgor Ostapenko         debugger,
24008334c51SBrooks Davis         kyuafile_path(cmdline), build_root_path(cmdline), filter, user_config,
24108334c51SBrooks Davis         cmdline.get_option< cmdline::path_option >("stdout"),
24208334c51SBrooks Davis         cmdline.get_option< cmdline::path_option >("stderr"));
24308334c51SBrooks Davis 
24408334c51SBrooks Davis     ui->out(F("%s  ->  %s") % cli::format_test_case_id(result.test_case) %
24508334c51SBrooks Davis             cli::format_result(result.test_result));
24608334c51SBrooks Davis 
24708334c51SBrooks Davis     return result.test_result.good() ? EXIT_SUCCESS : EXIT_FAILURE;
24808334c51SBrooks Davis }
249