diff --git a/contrib/kyua/cli/cmd_debug.cpp b/contrib/kyua/cli/cmd_debug.cpp --- a/contrib/kyua/cli/cmd_debug.cpp +++ b/contrib/kyua/cli/cmd_debug.cpp @@ -28,6 +28,10 @@ #include "cli/cmd_debug.hpp" +extern "C" { +#include +} + #include #include @@ -39,13 +43,20 @@ #include "utils/cmdline/parser.ipp" #include "utils/cmdline/ui.hpp" #include "utils/format/macros.hpp" +#include "utils/fs/path.hpp" +#include "utils/process/child.ipp" #include "utils/process/executor.hpp" +#include "utils/process/operations.hpp" +#include "utils/process/status.hpp" namespace cmdline = utils::cmdline; namespace config = utils::config; namespace executor = utils::process::executor; +namespace process = utils::process; using cli::cmd_debug; +using utils::process::args_vector; +using utils::process::child; namespace { @@ -62,6 +73,57 @@ "Pauses right before the test cleanup"); +static const char* DEFAULT_CMD = "$SHELL"; +const cmdline::string_option execute_option( + 'x', "execute", + "A command to run upon test failure", + "cmd", DEFAULT_CMD, true); + + +/// Functor to execute a program. +class execute { + const std::string& _cmd; + executor::exit_handle& _eh; + +public: + /// Constructor. + /// + /// \param program Program binary absolute path. + /// \param args Program arguments. + execute( + const std::string& cmd_, + executor::exit_handle& eh_) : + _cmd(cmd_), + _eh(eh_) + { + } + + /// Body of the subprocess. + void + operator()(void) + { + if (::chdir(_eh.work_directory().c_str()) == -1) { + std::cerr << "execute: chdir() errors: " + << strerror(errno) << ".\n"; + std::exit(EXIT_FAILURE); + } + + std::string program_path = "/bin/sh"; + const char* shell = std::getenv("SHELL"); + if (shell) + program_path = shell; + + args_vector av; + if (!(_cmd.empty() || _cmd == DEFAULT_CMD)) { + av.push_back("-c"); + av.push_back(_cmd); + } + + process::exec(utils::fs::path(program_path), av); + } +}; + + /// The debugger interface implementation. class dbg : public engine::debugger { /// Object to interact with the I/O of the program. @@ -103,6 +165,21 @@ } }; + void upon_test_failure( + const model::test_program_ptr&, + const model::test_case&, + optional< model::test_result >&, + executor::exit_handle& eh) const + { + if (!_cmdline.has_option(execute_option.long_name())) + return; + const std::string& cmd = _cmdline.get_option( + execute_option.long_name()); + std::unique_ptr< process::child > child = child::fork_interactive( + execute(cmd, eh)); + (void) child->wait(); + }; + }; @@ -127,6 +204,8 @@ add_option(cmdline::path_option( "stderr", "Where to direct the standard error of the test case", "path", "/dev/stderr")); + + add_option(execute_option); } diff --git a/contrib/kyua/engine/debugger.hpp b/contrib/kyua/engine/debugger.hpp --- a/contrib/kyua/engine/debugger.hpp +++ b/contrib/kyua/engine/debugger.hpp @@ -58,6 +58,13 @@ const model::test_case&, optional< model::test_result >&, executor::exit_handle&) const = 0; + + /// Called upon test failure. + virtual void upon_test_failure( + const model::test_program_ptr&, + const model::test_case&, + optional< model::test_result >&, + executor::exit_handle&) const = 0; }; diff --git a/contrib/kyua/engine/scheduler.cpp b/contrib/kyua/engine/scheduler.cpp --- a/contrib/kyua/engine/scheduler.cpp +++ b/contrib/kyua/engine/scheduler.cpp @@ -1401,6 +1401,9 @@ if (debugger) { debugger->before_cleanup(test_data->test_program, test_case, result, handle); + if (!result.get().good()) + debugger->upon_test_failure(test_data->test_program, test_case, + result, handle); } if (test_data->needs_cleanup) { diff --git a/contrib/kyua/utils/cmdline/options.hpp b/contrib/kyua/utils/cmdline/options.hpp --- a/contrib/kyua/utils/cmdline/options.hpp +++ b/contrib/kyua/utils/cmdline/options.hpp @@ -91,6 +91,9 @@ /// Descriptive name of the required argument; empty if not allowed. std::string _arg_name; + /// If the option can be used without an explicit argument provided. + bool _arg_is_optional = false; + /// Whether the option has a default value or not. /// /// \todo We should probably be using the optional class here. @@ -101,7 +104,7 @@ public: base_option(const char, const char*, const char*, const char* = NULL, - const char* = NULL); + const char* = NULL, bool = false); base_option(const char*, const char*, const char* = NULL, const char* = NULL); virtual ~base_option(void); @@ -113,6 +116,7 @@ bool needs_arg(void) const; const std::string& arg_name(void) const; + bool arg_is_optional(void) const; bool has_default_value(void) const; const std::string& default_value(void) const; @@ -219,7 +223,7 @@ class string_option : public base_option { public: string_option(const char, const char*, const char*, const char*, - const char* = NULL); + const char* = NULL, bool = false); string_option(const char*, const char*, const char*, const char* = NULL); virtual ~string_option(void) {} diff --git a/contrib/kyua/utils/cmdline/options.cpp b/contrib/kyua/utils/cmdline/options.cpp --- a/contrib/kyua/utils/cmdline/options.cpp +++ b/contrib/kyua/utils/cmdline/options.cpp @@ -53,15 +53,18 @@ /// purposes. /// \param default_value_ If not NULL, specifies that the option has a default /// value for the mandatory argument. +/// \param arg_is_optional_ Specifies if a value must be provided or not. cmdline::base_option::base_option(const char short_name_, const char* long_name_, const char* description_, const char* arg_name_, - const char* default_value_) : + const char* default_value_, + bool arg_is_optional_) : _short_name(short_name_), _long_name(long_name_), _description(description_), _arg_name(arg_name_ == NULL ? "" : arg_name_), + _arg_is_optional(arg_is_optional_), _has_default_value(default_value_ != NULL), _default_value(default_value_ == NULL ? "" : default_value_) { @@ -164,6 +167,16 @@ } +/// Returns optionality of the argument. +/// +/// \return The optionality. +bool +cmdline::base_option::arg_is_optional(void) const +{ + return _arg_is_optional; +} + + /// Checks whether the option has a default value for its argument. /// /// \pre needs_arg() must be true. @@ -558,9 +571,10 @@ const char* long_name_, const char* description_, const char* arg_name_, - const char* default_value_) : + const char* default_value_, + bool arg_is_optional_) : base_option(short_name_, long_name_, description_, arg_name_, - default_value_) + default_value_, arg_is_optional_) { } diff --git a/contrib/kyua/utils/cmdline/parser.cpp b/contrib/kyua/utils/cmdline/parser.cpp --- a/contrib/kyua/utils/cmdline/parser.cpp +++ b/contrib/kyua/utils/cmdline/parser.cpp @@ -88,7 +88,10 @@ long_option.name = option->long_name().c_str(); if (option->needs_arg()) - long_option.has_arg = required_argument; + if (option->arg_is_optional()) + long_option.has_arg = optional_argument; + else + long_option.has_arg = required_argument; else long_option.has_arg = no_argument; @@ -96,7 +99,7 @@ if (option->has_short_name()) { data.short_options += option->short_name(); if (option->needs_arg()) - data.short_options += ':'; + data.short_options += option->arg_is_optional() ? "::" : ":"; id = option->short_name(); } else { id = cur_id++; @@ -320,9 +323,11 @@ for (cmdline::options_vector::const_iterator iter = options.begin(); iter != options.end(); iter++) { const cmdline::base_option* option = *iter; - if (option->needs_arg() && option->has_default_value()) + if (option->needs_arg() && option->has_default_value() && + !option->arg_is_optional()) { option_values[option->long_name()].push_back( option->default_value()); + } } args_vector args; @@ -357,8 +362,13 @@ if (::optarg != NULL) { option->validate(::optarg); option_values[option->long_name()].push_back(::optarg); - } else - INV(option->has_default_value()); + } else { + if (option->arg_is_optional()) + option_values[option->long_name()].push_back( + option->default_value()); + else + INV(option->has_default_value()); + } } else { option_values[option->long_name()].push_back(""); } diff --git a/contrib/kyua/utils/process/child.hpp b/contrib/kyua/utils/process/child.hpp --- a/contrib/kyua/utils/process/child.hpp +++ b/contrib/kyua/utils/process/child.hpp @@ -80,6 +80,8 @@ static std::unique_ptr< child > fork_capture_aux(void); + static std::unique_ptr< child > fork_interactive(void); + static std::unique_ptr< child > fork_files_aux(const fs::path&, const fs::path&); @@ -92,6 +94,9 @@ static std::unique_ptr< child > fork_capture(Hook); std::istream& output(void); + template< typename Hook > + static std::unique_ptr< child > fork_interactive(Hook); + template< typename Hook > static std::unique_ptr< child > fork_files(Hook, const fs::path&, const fs::path&); diff --git a/contrib/kyua/utils/process/child.cpp b/contrib/kyua/utils/process/child.cpp --- a/contrib/kyua/utils/process/child.cpp +++ b/contrib/kyua/utils/process/child.cpp @@ -235,6 +235,30 @@ } +std::unique_ptr< process::child > +process::child::fork_interactive(void) +{ + std::cout.flush(); + std::cerr.flush(); + + std::unique_ptr< signals::interrupts_inhibiter > inhibiter( + new signals::interrupts_inhibiter); + pid_t pid = detail::syscall_fork(); + if (pid == -1) { + inhibiter.reset(); // Unblock signals. + throw process::system_error("fork(2) failed", errno); + } else if (pid == 0) { + inhibiter.reset(); // Unblock signals. + return {}; + } else { + signals::add_pid_to_kill(pid); + inhibiter.reset(NULL); // Unblock signals. + return std::unique_ptr< process::child >( + new process::child(new impl(pid, NULL))); + } +} + + /// Helper function for fork(). /// /// Please note: if you update this function to change the return type or to diff --git a/contrib/kyua/utils/process/child.ipp b/contrib/kyua/utils/process/child.ipp --- a/contrib/kyua/utils/process/child.ipp +++ b/contrib/kyua/utils/process/child.ipp @@ -104,6 +104,26 @@ } +template< typename Hook > +std::unique_ptr< child > +child::fork_interactive(Hook hook) +{ + std::unique_ptr< child > child = fork_interactive(); + if (child.get() == NULL) { + try { + hook(); + std::abort(); + } catch (const std::runtime_error& e) { + detail::report_error_and_abort(e); + } catch (...) { + detail::report_error_and_abort(); + } + } + + return child; +} + + } // namespace process } // namespace utils