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