Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions cli/cmd_debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "cli/cmd_debug.hpp"

#include <cstdlib>
#include <iostream>

#include "cli/common.ipp"
#include "drivers/debug_test.hpp"
Expand All @@ -38,13 +39,76 @@
#include "utils/cmdline/parser.ipp"
#include "utils/cmdline/ui.hpp"
#include "utils/format/macros.hpp"
#include "utils/process/executor.hpp"

namespace cmdline = utils::cmdline;
namespace config = utils::config;
namespace executor = utils::process::executor;

using cli::cmd_debug;


namespace {


const cmdline::bool_option pause_before_cleanup_upon_fail_option(
'p',
"pause-before-cleanup-upon-fail",
"Pauses right before the test cleanup upon fail");


const cmdline::bool_option pause_before_cleanup_option(
"pause-before-cleanup",
"Pauses right before the test cleanup");


/// The debugger interface implementation.
class dbg : public engine::debugger {
/// Object to interact with the I/O of the program.
cmdline::ui* _ui;

/// Representation of the command line to the subcommand.
const cmdline::parsed_cmdline& _cmdline;

public:
/// Constructor.
///
/// \param ui_ Object to interact with the I/O of the program.
/// \param cmdline Representation of the command line to the subcommand.
dbg(cmdline::ui* ui, const cmdline::parsed_cmdline& cmdline) :
_ui(ui), _cmdline(cmdline)
{}

void before_cleanup(
const model::test_program_ptr&,
const model::test_case&,
optional< model::test_result >& result,
executor::exit_handle& eh) const
{
if (_cmdline.has_option(pause_before_cleanup_upon_fail_option
.long_name())) {
if (result && !result.get().good()) {
_ui->out("The test failed and paused right before its cleanup "
"routine.");
_ui->out(F("Test work dir: %s") % eh.work_directory().str());
_ui->out("Press any key to continue...");
(void) std::cin.get();
}
} else if (_cmdline.has_option(pause_before_cleanup_option
.long_name())) {
_ui->out("The test paused right before its cleanup routine.");
_ui->out(F("Test work dir: %s") % eh.work_directory().str());
_ui->out("Press any key to continue...");
(void) std::cin.get();
}
};

};


} // anonymous namespace


/// Default constructor for cmd_debug.
cmd_debug::cmd_debug(void) : cli_command(
"debug", "test_case", 1, 1,
Expand All @@ -53,6 +117,9 @@ cmd_debug::cmd_debug(void) : cli_command(
add_option(build_root_option);
add_option(kyuafile_option);

add_option(pause_before_cleanup_upon_fail_option);
add_option(pause_before_cleanup_option);

add_option(cmdline::path_option(
"stdout", "Where to direct the standard output of the test case",
"path", "/dev/stdout"));
Expand Down Expand Up @@ -82,7 +149,10 @@ cmd_debug::run(cmdline::ui* ui, const cmdline::parsed_cmdline& cmdline,
const engine::test_filter filter = engine::test_filter::parse(
test_case_name);

auto debugger = std::shared_ptr< engine::debugger >(new dbg(ui, cmdline));

const drivers::debug_test::result result = drivers::debug_test::drive(
debugger,
kyuafile_path(cmdline), build_root_path(cmdline), filter, user_config,
cmdline.get_option< cmdline::path_option >("stdout"),
cmdline.get_option< cmdline::path_option >("stderr"));
Expand Down
21 changes: 20 additions & 1 deletion doc/kyua-debug.1.in
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.Dd October 13, 2014
.Dd March 25, 2025
.Dt KYUA-DEBUG 1
.Os
.Sh NAME
Expand All @@ -35,6 +35,8 @@
.Nm
.Op Fl -build-root Ar path
.Op Fl -kyuafile Ar file
.Op Fl -pause-before-cleanup-upon-fail
.Op Fl -pause-before-cleanup
.Op Fl -stdout Ar path
.Op Fl -stderr Ar path
.Ar test_case
Expand Down Expand Up @@ -84,6 +86,23 @@ Specifies the Kyuafile to process.
Defaults to
.Pa Kyuafile
file in the current directory.
.It Fl -pause-before-cleanup-upon-fail , Fl p
Causes Kyua to pause right before the test cleanup routine is invoked if the
test fails.
When paused, Kyua waits for input on stdin, and any key press resumes
execution.
.sp
This can be useful for debugging system or end-to-end tests
which alter the system by creating artifacts such as files, network
interfaces, routing entries, firewall configuration, containers, etc.
This flag makes it possible to preserve such artifacts immediately after a test
failure, simplifying debugging.
Otherwise, these artifacts would be removed by the test cleanup routine or
by Kyua built-in cleanup mechanism.
.It Fl -pause-before-cleanup
The unconditional variant of the previous option.
This can be helpful for reproducing the infrastructure or fixture of a passing
test for further development or additional analysis.
.It Fl -stderr Ar path
Specifies the file to which to send the standard error of the test
program's body.
Expand Down
7 changes: 6 additions & 1 deletion drivers/debug_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ using utils::optional;
///
/// \returns A structure with all results computed by this driver.
drivers::debug_test::result
drivers::debug_test::drive(const fs::path& kyuafile_path,
drivers::debug_test::drive(engine::debugger_ptr debugger,
const fs::path& kyuafile_path,
const optional< fs::path > build_root,
const engine::test_filter& filter,
const config::tree& user_config,
Expand Down Expand Up @@ -92,6 +93,10 @@ drivers::debug_test::drive(const fs::path& kyuafile_path,
const model::test_program_ptr test_program = match.get().first;
const std::string& test_case_name = match.get().second;

const model::test_case test_case = test_program->find(test_case_name);
if (debugger)
test_case.attach_debugger(debugger);

scheduler::result_handle_ptr result_handle = handle.debug_test(
test_program, test_case_name, user_config,
stdout_path, stderr_path);
Expand Down
6 changes: 5 additions & 1 deletion drivers/debug_test.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@
#if !defined(DRIVERS_DEBUG_TEST_HPP)
#define DRIVERS_DEBUG_TEST_HPP

#include "engine/debugger.hpp"
#include "engine/filters.hpp"
#include "model/test_result.hpp"
#include "utils/config/tree_fwd.hpp"
#include "utils/fs/path_fwd.hpp"
#include "utils/optional_fwd.hpp"

using engine::debugger;

namespace drivers {
namespace debug_test {

Expand All @@ -68,7 +71,8 @@ class result {
};


result drive(const utils::fs::path&, const utils::optional< utils::fs::path >,
result drive(std::shared_ptr< debugger >,
const utils::fs::path&, const utils::optional< utils::fs::path >,
const engine::test_filter&, const utils::config::tree&,
const utils::fs::path&, const utils::fs::path&);

Expand Down
71 changes: 71 additions & 0 deletions engine/debugger.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2025 The Kyua Authors.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of Google Inc. nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

/// \file engine/debugger.hpp
/// The interface between the engine and the users outside.

#if !defined(ENGINE_DEBUGGER_HPP)
#define ENGINE_DEBUGGER_HPP

#include "model/test_case_fwd.hpp"
#include "model/test_program_fwd.hpp"
#include "model/test_result_fwd.hpp"
#include "utils/optional_fwd.hpp"
#include "utils/process/executor_fwd.hpp"

namespace executor = utils::process::executor;

using utils::optional;


namespace engine {


/// Abstract debugger interface.
class debugger {
public:
debugger() {}
virtual ~debugger() {}

/// Called right before test cleanup.
virtual void before_cleanup(
const model::test_program_ptr&,
const model::test_case&,
optional< model::test_result >&,
executor::exit_handle&) const = 0;
};


/// Pointer to a debugger implementation.
typedef std::shared_ptr< debugger > debugger_ptr;


} // namespace engine


#endif // !defined(ENGINE_DEBUGGER_HPP)
8 changes: 8 additions & 0 deletions engine/scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ extern "C" {
#include <stdexcept>

#include "engine/config.hpp"
#include "engine/debugger.hpp"
#include "engine/exceptions.hpp"
#include "engine/execenv/execenv.hpp"
#include "engine/requirements.hpp"
Expand Down Expand Up @@ -1396,8 +1397,15 @@ scheduler::scheduler_handle::wait_any(void)
handle.stderr_file());
}

std::shared_ptr< debugger > debugger = test_case.get_debugger();
if (debugger) {
debugger->before_cleanup(test_data->test_program, test_case,
result, handle);
}

if (test_data->needs_cleanup) {
INV(test_case.get_metadata().has_cleanup());

// The test body has completed and we have processed it. If there
// is a cleanup routine, trigger it now and wait for any other test
// completion. The caller never knows about cleanup routines.
Expand Down
21 changes: 21 additions & 0 deletions model/test_case.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ struct model::test_case::impl : utils::noncopyable {
/// Fake result to return instead of running the test case.
optional< model::test_result > fake_result;

/// Optional pointer to a debugger attached.
engine::debugger_ptr debugger;

/// Constructor.
///
/// \param name_ The name of the test case within the test program.
Expand Down Expand Up @@ -233,6 +236,24 @@ model::test_case::get_raw_metadata(void) const
}


/// Attach a debugger to the test case.
void
model::test_case::attach_debugger(engine::debugger_ptr debugger) const
{
_pimpl->debugger = debugger;
}


/// Gets the optional pointer to a debugger.
///
/// \return An optional pointer to a debugger.
engine::debugger_ptr
model::test_case::get_debugger() const
{
return _pimpl->debugger;
}


/// Gets the fake result pre-stored for this test case.
///
/// \return A fake result, or none if not defined.
Expand Down
5 changes: 5 additions & 0 deletions model/test_case.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@
#include <ostream>
#include <string>

#include "engine/debugger.hpp"
#include "model/metadata_fwd.hpp"
#include "model/test_result_fwd.hpp"
#include "utils/noncopyable.hpp"
#include "utils/optional_fwd.hpp"


namespace model {


Expand Down Expand Up @@ -71,6 +73,9 @@ class test_case {
const metadata& get_raw_metadata(void) const;
utils::optional< test_result > fake_result(void) const;

void attach_debugger(engine::debugger_ptr) const;
engine::debugger_ptr get_debugger() const;

bool operator==(const test_case&) const;
bool operator!=(const test_case&) const;
};
Expand Down