From nobody Sun Jan 11 13:44:39 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4dpxZb5qbdz6P0df for ; Sun, 11 Jan 2026 13:44:39 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R13" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4dpxZb5XXMz461w for ; Sun, 11 Jan 2026 13:44:39 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1768139079; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=+jSY9YClGpQ2LPeXiB0vI8rUwPIbLyPiqr0D32Wlmpg=; b=Jf/+jtbD4fNWOpkXXL5Wdbtby1v/v0+Q2lMR9e/bcp/Bwfy9koIbDZ8XQMcsWKsAXvkUSH 0KgcHVvn3HXnTHj17Qy4NbKFwSbQeBo0NNghhpG8HOvstzJznC0mk/TbmyU04FkbTNCySi 6Lad0Cq/r8UV0mUAxxalfiQBB+S44UnSC7AG9khlcrMIHqvm46KeW9xe94fMzhuRh1ArHV s+zqlG8w7daGfAgrTLY2MKc2rSS54j8Nu95+5cGkXM+2BRhttQ46HHEYqpwknoM7eMHFHI P7MCw6AmAWXuAHTuHf0fS+McNArvALp+5NEBDOyWfqM0Zu3rls9Hf1LvWSFdSw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1768139079; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=+jSY9YClGpQ2LPeXiB0vI8rUwPIbLyPiqr0D32Wlmpg=; b=gRhQ0MV592MOOvnQwQXT9vFe2oJcGrfMFM5mNwTZcOo4ykLb4nLDbyUjKR+s49D1o0YAyX hBxzln4ySavn4N4X0WncAJGMAFzZr1D/dW07rTOs6Pa9wnO76Jl6dFN2R7NZcMKaFAt4sS 6LwXXiPjlk8CqWeyR1vfH/1oM+0IYB7UtPjIxbB+L9mVt8DY9oxal432ZgEZym2rb361Z1 OB19sbYLmJVd4iW06tnZfUIagPU197m8O+ZoFLYQ9XnU5YKSFpJI4lo4/KtbKHbsWMHp2D KFlyAtm59lRlOGop9BUYR79j2w29ozPi3Duk0o+gG8MbiI/WrgtqiAE6oxHUyw== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1768139079; a=rsa-sha256; cv=none; b=L2y4Wqv9lvOpMT1cue7qeXusFQAfJ7CcXbkZw7wNppsZH781c4UVgiNu/yVfOykBDeFRgd 0+2PO7RnUEFKBxck0hYNOBYQFbVZ/3huVPO1hna70bjwb1YAtrNgqikSv04MLJ7nfJI4mU xUNXx6upZbPD7iDYEsauvX36SfV0MKh873w8tr9ZIweP6l16t+DFkIK5TdB7Xy/HII4smW 144nQXEXDEdMht5YU7jjkc1cKh7lX8PD0HkGUg+YzN7n7vRNtjT01L51E02EdTgUrpbUXL oYTTtFCQpDY7Gb1UdSKtaFbgQVMLWymdQnsQ8+EUe6Y7KQKyGBfuRtXsMWQfAQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4dpxZb4tswzk7v for ; Sun, 11 Jan 2026 13:44:39 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 42ee7 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Sun, 11 Jan 2026 13:44:39 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Igor Ostapenko Subject: git: 8a21c17ccfec - main - kyua: Add "debug -x|--execute cmd" option List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: igoro X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 8a21c17ccfecf0ee54becd46d3e08ccab76ca3d6 Auto-Submitted: auto-generated Date: Sun, 11 Jan 2026 13:44:39 +0000 Message-Id: <6963a947.42ee7.1f4091b9@gitrepo.freebsd.org> The branch main has been updated by igoro: URL: https://cgit.FreeBSD.org/src/commit/?id=8a21c17ccfecf0ee54becd46d3e08ccab76ca3d6 commit 8a21c17ccfecf0ee54becd46d3e08ccab76ca3d6 Author: Igor Ostapenko AuthorDate: 2026-01-11 13:43:33 +0000 Commit: Igor Ostapenko CommitDate: 2026-01-11 13:43:33 +0000 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 --- contrib/kyua/cli/cmd_debug.cpp | 82 +++++++++++++++++++++++++++++++++- contrib/kyua/engine/debugger.hpp | 7 +++ contrib/kyua/engine/scheduler.cpp | 3 ++ contrib/kyua/utils/cmdline/options.cpp | 20 +++++++-- contrib/kyua/utils/cmdline/options.hpp | 8 +++- contrib/kyua/utils/cmdline/parser.cpp | 20 ++++++--- contrib/kyua/utils/process/child.cpp | 24 ++++++++++ contrib/kyua/utils/process/child.hpp | 5 +++ contrib/kyua/utils/process/child.ipp | 20 +++++++++ 9 files changed, 178 insertions(+), 11 deletions(-) diff --git a/contrib/kyua/cli/cmd_debug.cpp b/contrib/kyua/cli/cmd_debug.cpp index 978ccae0fdf1..060113d137fa 100644 --- 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 @@ 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( + 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)); } diff --git a/contrib/kyua/engine/debugger.hpp b/contrib/kyua/engine/debugger.hpp index 3c4d087f8ad0..ce87d41ed94d 100644 --- a/contrib/kyua/engine/debugger.hpp +++ b/contrib/kyua/engine/debugger.hpp @@ -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; }; diff --git a/contrib/kyua/engine/scheduler.cpp b/contrib/kyua/engine/scheduler.cpp index a0719ec461ab..d04fc1dfbd3d 100644 --- a/contrib/kyua/engine/scheduler.cpp +++ b/contrib/kyua/engine/scheduler.cpp @@ -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) { diff --git a/contrib/kyua/utils/cmdline/options.cpp b/contrib/kyua/utils/cmdline/options.cpp index 61736e31c11e..9d448503e3f9 100644 --- a/contrib/kyua/utils/cmdline/options.cpp +++ b/contrib/kyua/utils/cmdline/options.cpp @@ -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_) { } diff --git a/contrib/kyua/utils/cmdline/options.hpp b/contrib/kyua/utils/cmdline/options.hpp index f3a83889e491..d11de14af514 100644 --- a/contrib/kyua/utils/cmdline/options.hpp +++ b/contrib/kyua/utils/cmdline/options.hpp @@ -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) {} diff --git a/contrib/kyua/utils/cmdline/parser.cpp b/contrib/kyua/utils/cmdline/parser.cpp index 5c83f6d69cc4..29dd4612f6ad 100644 --- a/contrib/kyua/utils/cmdline/parser.cpp +++ b/contrib/kyua/utils/cmdline/parser.cpp @@ -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(""); } diff --git a/contrib/kyua/utils/process/child.cpp b/contrib/kyua/utils/process/child.cpp index 36b6b6b3e51f..c51c39e6d1ff 100644 --- a/contrib/kyua/utils/process/child.cpp +++ b/contrib/kyua/utils/process/child.cpp @@ -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 diff --git a/contrib/kyua/utils/process/child.hpp b/contrib/kyua/utils/process/child.hpp index 3e00cea8752c..fbe32311a05b 100644 --- a/contrib/kyua/utils/process/child.hpp +++ b/contrib/kyua/utils/process/child.hpp @@ -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&); diff --git a/contrib/kyua/utils/process/child.ipp b/contrib/kyua/utils/process/child.ipp index beb2ea3b0b0a..86bc01fc0d6e 100644 --- a/contrib/kyua/utils/process/child.ipp +++ b/contrib/kyua/utils/process/child.ipp @@ -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