mirror of
https://git.freebsd.org/src.git
synced 2026-01-11 19:57:22 +00:00
kyua: Add "debug -x|--execute cmd" option
With execenv=jail specified, the "cmd" runs inside the test's jail. Reviewed by: ngie, markj Differential Revision: https://reviews.freebsd.org/D52642
This commit is contained in:
parent
7afa03963c
commit
8a21c17ccf
9 changed files with 178 additions and 11 deletions
|
|
@ -28,6 +28,10 @@
|
|||
|
||||
#include "cli/cmd_debug.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include <unistd.h>
|
||||
}
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
|
|
@ -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 @@ const cmdline::bool_option pause_before_cleanup_option(
|
|||
"Pauses right before the test cleanup");
|
||||
|
||||
|
||||
static const char* DEFAULT_CMD = "$SHELL";
|
||||
const cmdline::string_option execute_option(
|
||||
'x', "execute",
|
||||
"A command to run within the given execenv 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 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
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<cmdline::string_option>(
|
||||
execute_option.long_name());
|
||||
std::unique_ptr< process::child > child = child::fork_interactive(
|
||||
execute(cmd, eh));
|
||||
(void) child->wait();
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -127,6 +204,8 @@ cmd_debug::cmd_debug(void) : cli_command(
|
|||
add_option(cmdline::path_option(
|
||||
"stderr", "Where to direct the standard error of the test case",
|
||||
"path", "/dev/stderr"));
|
||||
|
||||
add_option(execute_option);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -151,7 +230,8 @@ cmd_debug::run(cmdline::ui* ui, const cmdline::parsed_cmdline& cmdline,
|
|||
|
||||
engine::debugger_ptr debugger = nullptr;
|
||||
if (cmdline.has_option(pause_before_cleanup_upon_fail_option.long_name())
|
||||
|| cmdline.has_option(pause_before_cleanup_option.long_name())) {
|
||||
|| cmdline.has_option(pause_before_cleanup_option.long_name())
|
||||
|| cmdline.has_option(execute_option.long_name())) {
|
||||
debugger = std::shared_ptr< engine::debugger >(new dbg(ui, cmdline));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,13 @@ public:
|
|||
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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1403,6 +1403,9 @@ scheduler::scheduler_handle::wait_any(void)
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -53,15 +53,18 @@ namespace text = utils::text;
|
|||
/// 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 @@ cmdline::base_option::arg_name(void) const
|
|||
}
|
||||
|
||||
|
||||
/// 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 @@ cmdline::string_option::string_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_) :
|
||||
base_option(short_name_, long_name_, description_, arg_name_,
|
||||
default_value_)
|
||||
default_value_, arg_is_optional_)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,6 +91,9 @@ class base_option {
|
|||
/// 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 @@ class base_option {
|
|||
|
||||
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 @@ public:
|
|||
|
||||
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 @@ public:
|
|||
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) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,10 @@ options_to_getopt_data(const cmdline::options_vector& options,
|
|||
|
||||
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 @@ options_to_getopt_data(const cmdline::options_vector& options,
|
|||
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 @@ cmdline::parse(const int argc, const char* const* argv,
|
|||
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 @@ cmdline::parse(const int argc, const char* const* argv,
|
|||
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("");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,6 +235,30 @@ process::child::fork_capture_aux(void)
|
|||
}
|
||||
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ class child : noncopyable {
|
|||
|
||||
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 @@ public:
|
|||
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&);
|
||||
|
|
|
|||
|
|
@ -104,6 +104,26 @@ child::fork_capture(Hook hook)
|
|||
}
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue