Blame engine/scheduler.cpp

Packit 209faa
// Copyright 2014 The Kyua Authors.
Packit 209faa
// All rights reserved.
Packit 209faa
//
Packit 209faa
// Redistribution and use in source and binary forms, with or without
Packit 209faa
// modification, are permitted provided that the following conditions are
Packit 209faa
// met:
Packit 209faa
//
Packit 209faa
// * Redistributions of source code must retain the above copyright
Packit 209faa
//   notice, this list of conditions and the following disclaimer.
Packit 209faa
// * Redistributions in binary form must reproduce the above copyright
Packit 209faa
//   notice, this list of conditions and the following disclaimer in the
Packit 209faa
//   documentation and/or other materials provided with the distribution.
Packit 209faa
// * Neither the name of Google Inc. nor the names of its contributors
Packit 209faa
//   may be used to endorse or promote products derived from this software
Packit 209faa
//   without specific prior written permission.
Packit 209faa
//
Packit 209faa
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
Packit 209faa
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
Packit 209faa
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
Packit 209faa
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
Packit 209faa
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
Packit 209faa
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
Packit 209faa
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
Packit 209faa
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
Packit 209faa
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
Packit 209faa
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
Packit 209faa
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Packit 209faa
Packit 209faa
#include "engine/scheduler.hpp"
Packit 209faa
Packit 209faa
extern "C" {
Packit 209faa
#include <unistd.h>
Packit 209faa
}
Packit 209faa
Packit 209faa
#include <cstdio>
Packit 209faa
#include <cstdlib>
Packit 209faa
#include <fstream>
Packit 209faa
#include <stdexcept>
Packit 209faa
Packit 209faa
#include "engine/config.hpp"
Packit 209faa
#include "engine/exceptions.hpp"
Packit 209faa
#include "engine/requirements.hpp"
Packit 209faa
#include "model/context.hpp"
Packit 209faa
#include "model/metadata.hpp"
Packit 209faa
#include "model/test_case.hpp"
Packit 209faa
#include "model/test_program.hpp"
Packit 209faa
#include "model/test_result.hpp"
Packit 209faa
#include "utils/config/tree.ipp"
Packit 209faa
#include "utils/datetime.hpp"
Packit 209faa
#include "utils/defs.hpp"
Packit 209faa
#include "utils/env.hpp"
Packit 209faa
#include "utils/format/macros.hpp"
Packit 209faa
#include "utils/fs/directory.hpp"
Packit 209faa
#include "utils/fs/exceptions.hpp"
Packit 209faa
#include "utils/fs/operations.hpp"
Packit 209faa
#include "utils/fs/path.hpp"
Packit 209faa
#include "utils/logging/macros.hpp"
Packit 209faa
#include "utils/noncopyable.hpp"
Packit 209faa
#include "utils/optional.ipp"
Packit 209faa
#include "utils/passwd.hpp"
Packit 209faa
#include "utils/process/executor.ipp"
Packit 209faa
#include "utils/process/status.hpp"
Packit 209faa
#include "utils/sanity.hpp"
Packit 209faa
#include "utils/shared_ptr.hpp"
Packit 209faa
#include "utils/stacktrace.hpp"
Packit 209faa
#include "utils/stream.hpp"
Packit 209faa
#include "utils/text/operations.ipp"
Packit 209faa
Packit 209faa
namespace config = utils::config;
Packit 209faa
namespace datetime = utils::datetime;
Packit 209faa
namespace executor = utils::process::executor;
Packit 209faa
namespace fs = utils::fs;
Packit 209faa
namespace logging = utils::logging;
Packit 209faa
namespace passwd = utils::passwd;
Packit 209faa
namespace process = utils::process;
Packit 209faa
namespace scheduler = engine::scheduler;
Packit 209faa
namespace text = utils::text;
Packit 209faa
Packit 209faa
using utils::none;
Packit 209faa
using utils::optional;
Packit 209faa
Packit 209faa
Packit 209faa
/// Timeout for the test case cleanup operation.
Packit 209faa
///
Packit 209faa
/// TODO(jmmv): This is here only for testing purposes.  Maybe we should expose
Packit 209faa
/// this setting as part of the user_config.
Packit 209faa
datetime::delta scheduler::cleanup_timeout(60, 0);
Packit 209faa
Packit 209faa
Packit 209faa
/// Timeout for the test case listing operation.
Packit 209faa
///
Packit 209faa
/// TODO(jmmv): This is here only for testing purposes.  Maybe we should expose
Packit 209faa
/// this setting as part of the user_config.
Packit 209faa
datetime::delta scheduler::list_timeout(300, 0);
Packit 209faa
Packit 209faa
Packit 209faa
namespace {
Packit 209faa
Packit 209faa
Packit 209faa
/// Magic exit status to indicate that the test case was probably skipped.
Packit 209faa
///
Packit 209faa
/// The test case was only skipped if and only if we return this exit code and
Packit 209faa
/// we find the skipped_cookie file on disk.
Packit 209faa
static const int exit_skipped = 84;
Packit 209faa
Packit 209faa
Packit 209faa
/// Text file containing the skip reason for the test case.
Packit 209faa
///
Packit 209faa
/// This will only be present within unique_work_directory if the test case
Packit 209faa
/// exited with the exit_skipped code.  However, there is no guarantee that the
Packit 209faa
/// file is there (say if the test really decided to exit with code exit_skipped
Packit 209faa
/// on its own).
Packit 209faa
static const char* skipped_cookie = "skipped.txt";
Packit 209faa
Packit 209faa
Packit 209faa
/// Mapping of interface names to interface definitions.
Packit 209faa
typedef std::map< std::string, std::shared_ptr< scheduler::interface > >
Packit 209faa
    interfaces_map;
Packit 209faa
Packit 209faa
Packit 209faa
/// Mapping of interface names to interface definitions.
Packit 209faa
///
Packit 209faa
/// Use register_interface() to add an entry to this global table.
Packit 209faa
static interfaces_map interfaces;
Packit 209faa
Packit 209faa
Packit 209faa
/// Scans the contents of a directory and appends the file listing to a file.
Packit 209faa
///
Packit 209faa
/// \param dir_path The directory to scan.
Packit 209faa
/// \param output_file The file to which to append the listing.
Packit 209faa
///
Packit 209faa
/// \throw engine::error If there are problems listing the files.
Packit 209faa
static void
Packit 209faa
append_files_listing(const fs::path& dir_path, const fs::path& output_file)
Packit 209faa
{
Packit 209faa
    std::ofstream output(output_file.c_str(), std::ios::app);
Packit 209faa
    if (!output)
Packit 209faa
        throw engine::error(F("Failed to open output file %s for append")
Packit 209faa
                            % output_file);
Packit 209faa
    try {
Packit 209faa
        std::set < std::string > names;
Packit 209faa
Packit 209faa
        const fs::directory dir(dir_path);
Packit 209faa
        for (fs::directory::const_iterator iter = dir.begin();
Packit 209faa
             iter != dir.end(); ++iter) {
Packit 209faa
            if (iter->name != "." && iter->name != "..")
Packit 209faa
                names.insert(iter->name);
Packit 209faa
        }
Packit 209faa
Packit 209faa
        if (!names.empty()) {
Packit 209faa
            output << "Files left in work directory after failure: "
Packit 209faa
                   << text::join(names, ", ") << '\n';
Packit 209faa
        }
Packit 209faa
    } catch (const fs::error& e) {
Packit 209faa
        throw engine::error(F("Cannot append files listing to %s: %s")
Packit 209faa
                            % output_file % e.what());
Packit 209faa
    }
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Maintenance data held while a test is being executed.
Packit 209faa
///
Packit 209faa
/// This data structure exists from the moment when a test is executed via
Packit 209faa
/// scheduler::spawn_test() or scheduler::impl::spawn_cleanup() to when it is
Packit 209faa
/// cleaned up with result_handle::cleanup().
Packit 209faa
///
Packit 209faa
/// This is a base data type intended to be extended for the test and cleanup
Packit 209faa
/// cases so that each contains only the relevant data.
Packit 209faa
struct exec_data : utils::noncopyable {
Packit 209faa
    /// Test program data for this test case.
Packit 209faa
    const model::test_program_ptr test_program;
Packit 209faa
Packit 209faa
    /// Name of the test case.
Packit 209faa
    const std::string test_case_name;
Packit 209faa
Packit 209faa
    /// Constructor.
Packit 209faa
    ///
Packit 209faa
    /// \param test_program_ Test program data for this test case.
Packit 209faa
    /// \param test_case_name_ Name of the test case.
Packit 209faa
    exec_data(const model::test_program_ptr test_program_,
Packit 209faa
              const std::string& test_case_name_) :
Packit 209faa
        test_program(test_program_), test_case_name(test_case_name_)
Packit 209faa
    {
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Destructor.
Packit 209faa
    virtual ~exec_data(void)
Packit 209faa
    {
Packit 209faa
    }
Packit 209faa
};
Packit 209faa
Packit 209faa
Packit 209faa
/// Maintenance data held while a test is being executed.
Packit 209faa
struct test_exec_data : public exec_data {
Packit 209faa
    /// Test program-specific execution interface.
Packit 209faa
    const std::shared_ptr< scheduler::interface > interface;
Packit 209faa
Packit 209faa
    /// User configuration passed to the execution of the test.  We need this
Packit 209faa
    /// here to recover it later when chaining the execution of a cleanup
Packit 209faa
    /// routine (if any).
Packit 209faa
    const config::tree user_config;
Packit 209faa
Packit 209faa
    /// Whether this test case still needs to have its cleanup routine executed.
Packit 209faa
    ///
Packit 209faa
    /// This is set externally when the cleanup routine is actually invoked to
Packit 209faa
    /// denote that no further attempts shall be made at cleaning this up.
Packit 209faa
    bool needs_cleanup;
Packit 209faa
Packit 209faa
    /// The exit_handle for this test once it has completed.
Packit 209faa
    ///
Packit 209faa
    /// This is set externally when the test case has finished, as we need this
Packit 209faa
    /// information to invoke the followup cleanup routine in the right context,
Packit 209faa
    /// as indicated by needs_cleanup.
Packit 209faa
    optional< executor::exit_handle > exit_handle;
Packit 209faa
Packit 209faa
    /// Constructor.
Packit 209faa
    ///
Packit 209faa
    /// \param test_program_ Test program data for this test case.
Packit 209faa
    /// \param test_case_name_ Name of the test case.
Packit 209faa
    /// \param interface_ Test program-specific execution interface.
Packit 209faa
    /// \param user_config_ User configuration passed to the test.
Packit 209faa
    test_exec_data(const model::test_program_ptr test_program_,
Packit 209faa
                   const std::string& test_case_name_,
Packit 209faa
                   const std::shared_ptr< scheduler::interface > interface_,
Packit 209faa
                   const config::tree& user_config_) :
Packit 209faa
        exec_data(test_program_, test_case_name_),
Packit 209faa
        interface(interface_), user_config(user_config_)
Packit 209faa
    {
Packit 209faa
        const model::test_case& test_case = test_program->find(test_case_name);
Packit 209faa
        needs_cleanup = test_case.get_metadata().has_cleanup();
Packit 209faa
    }
Packit 209faa
};
Packit 209faa
Packit 209faa
Packit 209faa
/// Maintenance data held while a test cleanup routine is being executed.
Packit 209faa
///
Packit 209faa
/// Instances of this object are related to a previous test_exec_data, as
Packit 209faa
/// cleanup routines can only exist once the test has been run.
Packit 209faa
struct cleanup_exec_data : public exec_data {
Packit 209faa
    /// The exit handle of the test.  This is necessary so that we can return
Packit 209faa
    /// the correct exit_handle to the user of the scheduler.
Packit 209faa
    executor::exit_handle body_exit_handle;
Packit 209faa
Packit 209faa
    /// The final result of the test's body.  This is necessary to compute the
Packit 209faa
    /// right return value for a test with a cleanup routine: the body result is
Packit 209faa
    /// respected if it is a "bad" result; else the result of the cleanup
Packit 209faa
    /// routine is used if it has failed.
Packit 209faa
    model::test_result body_result;
Packit 209faa
Packit 209faa
    /// Constructor.
Packit 209faa
    ///
Packit 209faa
    /// \param test_program_ Test program data for this test case.
Packit 209faa
    /// \param test_case_name_ Name of the test case.
Packit 209faa
    /// \param body_exit_handle_ If not none, exit handle of the body
Packit 209faa
    ///     corresponding to the cleanup routine represented by this exec_data.
Packit 209faa
    /// \param body_result_ If not none, result of the body corresponding to the
Packit 209faa
    ///     cleanup routine represented by this exec_data.
Packit 209faa
    cleanup_exec_data(const model::test_program_ptr test_program_,
Packit 209faa
                      const std::string& test_case_name_,
Packit 209faa
                      const executor::exit_handle& body_exit_handle_,
Packit 209faa
                      const model::test_result& body_result_) :
Packit 209faa
        exec_data(test_program_, test_case_name_),
Packit 209faa
        body_exit_handle(body_exit_handle_), body_result(body_result_)
Packit 209faa
    {
Packit 209faa
    }
Packit 209faa
};
Packit 209faa
Packit 209faa
Packit 209faa
/// Shared pointer to exec_data.
Packit 209faa
///
Packit 209faa
/// We require this because we want exec_data to not be copyable, and thus we
Packit 209faa
/// cannot just store it in the map without move constructors.
Packit 209faa
typedef std::shared_ptr< exec_data > exec_data_ptr;
Packit 209faa
Packit 209faa
Packit 209faa
/// Mapping of active PIDs to their maintenance data.
Packit 209faa
typedef std::map< int, exec_data_ptr > exec_data_map;
Packit 209faa
Packit 209faa
Packit 209faa
/// Enforces a test program to hold an absolute path.
Packit 209faa
///
Packit 209faa
/// TODO(jmmv): This function (which is a pretty ugly hack) exists because we
Packit 209faa
/// want the interface hooks to receive a test_program as their argument.
Packit 209faa
/// However, those hooks run after the test program has been isolated, which
Packit 209faa
/// means that the current directory has changed since when the test_program
Packit 209faa
/// objects were created.  This causes the absolute_path() method of
Packit 209faa
/// test_program to return bogus values if the internal representation of their
Packit 209faa
/// path is relative.  We should fix somehow: maybe making the fs module grab
Packit 209faa
/// its "current_path" view at program startup time; or maybe by grabbing the
Packit 209faa
/// current path at test_program creation time; or maybe something else.
Packit 209faa
///
Packit 209faa
/// \param program The test program to modify.
Packit 209faa
///
Packit 209faa
/// \return A new test program whose internal paths are absolute.
Packit 209faa
static model::test_program
Packit 209faa
force_absolute_paths(const model::test_program program)
Packit 209faa
{
Packit 209faa
    const std::string& relative = program.relative_path().str();
Packit 209faa
    const std::string absolute = program.absolute_path().str();
Packit 209faa
Packit 209faa
    const std::string root = absolute.substr(
Packit 209faa
        0, absolute.length() - relative.length());
Packit 209faa
Packit 209faa
    return model::test_program(
Packit 209faa
        program.interface_name(),
Packit 209faa
        program.relative_path(), fs::path(root),
Packit 209faa
        program.test_suite_name(),
Packit 209faa
        program.get_metadata(), program.test_cases());
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Functor to list the test cases of a test program.
Packit 209faa
class list_test_cases {
Packit 209faa
    /// Interface of the test program to execute.
Packit 209faa
    std::shared_ptr< scheduler::interface > _interface;
Packit 209faa
Packit 209faa
    /// Test program to execute.
Packit 209faa
    const model::test_program _test_program;
Packit 209faa
Packit 209faa
    /// User-provided configuration variables.
Packit 209faa
    const config::tree& _user_config;
Packit 209faa
Packit 209faa
public:
Packit 209faa
    /// Constructor.
Packit 209faa
    ///
Packit 209faa
    /// \param interface Interface of the test program to execute.
Packit 209faa
    /// \param test_program Test program to execute.
Packit 209faa
    /// \param user_config User-provided configuration variables.
Packit 209faa
    list_test_cases(
Packit 209faa
        const std::shared_ptr< scheduler::interface > interface,
Packit 209faa
        const model::test_program* test_program,
Packit 209faa
        const config::tree& user_config) :
Packit 209faa
        _interface(interface),
Packit 209faa
        _test_program(force_absolute_paths(*test_program)),
Packit 209faa
        _user_config(user_config)
Packit 209faa
    {
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Body of the subprocess.
Packit 209faa
    void
Packit 209faa
    operator()(const fs::path& UTILS_UNUSED_PARAM(control_directory))
Packit 209faa
    {
Packit 209faa
        const config::properties_map vars = scheduler::generate_config(
Packit 209faa
            _user_config, _test_program.test_suite_name());
Packit 209faa
        _interface->exec_list(_test_program, vars);
Packit 209faa
    }
Packit 209faa
};
Packit 209faa
Packit 209faa
Packit 209faa
/// Functor to execute a test program in a child process.
Packit 209faa
class run_test_program {
Packit 209faa
    /// Interface of the test program to execute.
Packit 209faa
    std::shared_ptr< scheduler::interface > _interface;
Packit 209faa
Packit 209faa
    /// Test program to execute.
Packit 209faa
    const model::test_program _test_program;
Packit 209faa
Packit 209faa
    /// Name of the test case to execute.
Packit 209faa
    const std::string& _test_case_name;
Packit 209faa
Packit 209faa
    /// User-provided configuration variables.
Packit 209faa
    const config::tree& _user_config;
Packit 209faa
Packit 209faa
    /// Verifies if the test case needs to be skipped or not.
Packit 209faa
    ///
Packit 209faa
    /// We could very well run this on the scheduler parent process before
Packit 209faa
    /// issuing the fork.  However, doing this here in the child process is
Packit 209faa
    /// better for two reasons: first, it allows us to continue using the simple
Packit 209faa
    /// spawn/wait abstraction of the scheduler; and, second, we parallelize the
Packit 209faa
    /// requirements checks among tests.
Packit 209faa
    ///
Packit 209faa
    /// \post If the test's preconditions are not met, the caller process is
Packit 209faa
    /// terminated with a special exit code and a "skipped cookie" is written to
Packit 209faa
    /// the disk with the reason for the failure.
Packit 209faa
    ///
Packit 209faa
    /// \param skipped_cookie_path File to create with the skip reason details
Packit 209faa
    ///     if this test is skipped.
Packit 209faa
    void
Packit 209faa
    do_requirements_check(const fs::path& skipped_cookie_path)
Packit 209faa
    {
Packit 209faa
        const model::test_case& test_case = _test_program.find(
Packit 209faa
            _test_case_name);
Packit 209faa
Packit 209faa
        const std::string skip_reason = engine::check_reqs(
Packit 209faa
            test_case.get_metadata(), _user_config,
Packit 209faa
            _test_program.test_suite_name(),
Packit 209faa
            fs::current_path());
Packit 209faa
        if (skip_reason.empty())
Packit 209faa
            return;
Packit 209faa
Packit 209faa
        std::ofstream output(skipped_cookie_path.c_str());
Packit 209faa
        if (!output) {
Packit 209faa
            std::perror((F("Failed to open %s for write") %
Packit 209faa
                         skipped_cookie_path).str().c_str());
Packit 209faa
            std::abort();
Packit 209faa
        }
Packit 209faa
        output << skip_reason;
Packit 209faa
        output.close();
Packit 209faa
Packit 209faa
        // Abruptly terminate the process.  We don't want to run any destructors
Packit 209faa
        // inherited from the parent process by mistake, which could, for
Packit 209faa
        // example, delete our own control files!
Packit 209faa
        ::_exit(exit_skipped);
Packit 209faa
    }
Packit 209faa
Packit 209faa
public:
Packit 209faa
    /// Constructor.
Packit 209faa
    ///
Packit 209faa
    /// \param interface Interface of the test program to execute.
Packit 209faa
    /// \param test_program Test program to execute.
Packit 209faa
    /// \param test_case_name Name of the test case to execute.
Packit 209faa
    /// \param user_config User-provided configuration variables.
Packit 209faa
    run_test_program(
Packit 209faa
        const std::shared_ptr< scheduler::interface > interface,
Packit 209faa
        const model::test_program_ptr test_program,
Packit 209faa
        const std::string& test_case_name,
Packit 209faa
        const config::tree& user_config) :
Packit 209faa
        _interface(interface),
Packit 209faa
        _test_program(force_absolute_paths(*test_program)),
Packit 209faa
        _test_case_name(test_case_name),
Packit 209faa
        _user_config(user_config)
Packit 209faa
    {
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Body of the subprocess.
Packit 209faa
    void
Packit 209faa
    operator()(const fs::path& control_directory)
Packit 209faa
    {
Packit 209faa
        const model::test_case& test_case = _test_program.find(
Packit 209faa
            _test_case_name);
Packit 209faa
        if (test_case.fake_result())
Packit 209faa
            ::_exit(EXIT_SUCCESS);
Packit 209faa
Packit 209faa
        do_requirements_check(control_directory / skipped_cookie);
Packit 209faa
Packit 209faa
        const config::properties_map vars = scheduler::generate_config(
Packit 209faa
            _user_config, _test_program.test_suite_name());
Packit 209faa
        _interface->exec_test(_test_program, _test_case_name, vars,
Packit 209faa
                              control_directory);
Packit 209faa
    }
Packit 209faa
};
Packit 209faa
Packit 209faa
Packit 209faa
/// Functor to execute a test program in a child process.
Packit 209faa
class run_test_cleanup {
Packit 209faa
    /// Interface of the test program to execute.
Packit 209faa
    std::shared_ptr< scheduler::interface > _interface;
Packit 209faa
Packit 209faa
    /// Test program to execute.
Packit 209faa
    const model::test_program _test_program;
Packit 209faa
Packit 209faa
    /// Name of the test case to execute.
Packit 209faa
    const std::string& _test_case_name;
Packit 209faa
Packit 209faa
    /// User-provided configuration variables.
Packit 209faa
    const config::tree& _user_config;
Packit 209faa
Packit 209faa
public:
Packit 209faa
    /// Constructor.
Packit 209faa
    ///
Packit 209faa
    /// \param interface Interface of the test program to execute.
Packit 209faa
    /// \param test_program Test program to execute.
Packit 209faa
    /// \param test_case_name Name of the test case to execute.
Packit 209faa
    /// \param user_config User-provided configuration variables.
Packit 209faa
    run_test_cleanup(
Packit 209faa
        const std::shared_ptr< scheduler::interface > interface,
Packit 209faa
        const model::test_program_ptr test_program,
Packit 209faa
        const std::string& test_case_name,
Packit 209faa
        const config::tree& user_config) :
Packit 209faa
        _interface(interface),
Packit 209faa
        _test_program(force_absolute_paths(*test_program)),
Packit 209faa
        _test_case_name(test_case_name),
Packit 209faa
        _user_config(user_config)
Packit 209faa
    {
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Body of the subprocess.
Packit 209faa
    void
Packit 209faa
    operator()(const fs::path& control_directory)
Packit 209faa
    {
Packit 209faa
        const config::properties_map vars = scheduler::generate_config(
Packit 209faa
            _user_config, _test_program.test_suite_name());
Packit 209faa
        _interface->exec_cleanup(_test_program, _test_case_name, vars,
Packit 209faa
                                 control_directory);
Packit 209faa
    }
Packit 209faa
};
Packit 209faa
Packit 209faa
Packit 209faa
/// Obtains the right scheduler interface for a given test program.
Packit 209faa
///
Packit 209faa
/// \param name The name of the interface of the test program.
Packit 209faa
///
Packit 209faa
/// \return An scheduler interface.
Packit 209faa
std::shared_ptr< scheduler::interface >
Packit 209faa
find_interface(const std::string& name)
Packit 209faa
{
Packit 209faa
    const interfaces_map::const_iterator iter = interfaces.find(name);
Packit 209faa
    PRE(interfaces.find(name) != interfaces.end());
Packit 209faa
    return (*iter).second;
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
}  // anonymous namespace
Packit 209faa
Packit 209faa
Packit 209faa
void
Packit 209faa
scheduler::interface::exec_cleanup(
Packit 209faa
    const model::test_program& UTILS_UNUSED_PARAM(test_program),
Packit 209faa
    const std::string& UTILS_UNUSED_PARAM(test_case_name),
Packit 209faa
    const utils::config::properties_map& UTILS_UNUSED_PARAM(vars),
Packit 209faa
    const utils::fs::path& UTILS_UNUSED_PARAM(control_directory)) const
Packit 209faa
{
Packit 209faa
    // Most test interfaces do not support standalone cleanup routines so
Packit 209faa
    // provide a default implementation that does nothing.
Packit 209faa
    UNREACHABLE_MSG("exec_cleanup not implemented for an interface that "
Packit 209faa
                    "supports standalone cleanup routines");
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Internal implementation of a lazy_test_program.
Packit 209faa
struct engine::scheduler::lazy_test_program::impl : utils::noncopyable {
Packit 209faa
    /// Whether the test cases list has been yet loaded or not.
Packit 209faa
    bool _loaded;
Packit 209faa
Packit 209faa
    /// User configuration to pass to the test program list operation.
Packit 209faa
    config::tree _user_config;
Packit 209faa
Packit 209faa
    /// Scheduler context to use to load test cases.
Packit 209faa
    scheduler::scheduler_handle& _scheduler_handle;
Packit 209faa
Packit 209faa
    /// Constructor.
Packit 209faa
    impl(const config::tree& user_config_,
Packit 209faa
         scheduler::scheduler_handle& scheduler_handle_) :
Packit 209faa
        _loaded(false), _user_config(user_config_),
Packit 209faa
        _scheduler_handle(scheduler_handle_)
Packit 209faa
    {
Packit 209faa
    }
Packit 209faa
};
Packit 209faa
Packit 209faa
Packit 209faa
/// Constructs a new test program.
Packit 209faa
///
Packit 209faa
/// \param interface_name_ Name of the test program interface.
Packit 209faa
/// \param binary_ The name of the test program binary relative to root_.
Packit 209faa
/// \param root_ The root of the test suite containing the test program.
Packit 209faa
/// \param test_suite_name_ The name of the test suite this program belongs to.
Packit 209faa
/// \param md_ Metadata of the test program.
Packit 209faa
/// \param user_config_ User configuration to pass to the scheduler.
Packit 209faa
/// \param scheduler_handle_ Scheduler context to use to load test cases.
Packit 209faa
scheduler::lazy_test_program::lazy_test_program(
Packit 209faa
    const std::string& interface_name_,
Packit 209faa
    const fs::path& binary_,
Packit 209faa
    const fs::path& root_,
Packit 209faa
    const std::string& test_suite_name_,
Packit 209faa
    const model::metadata& md_,
Packit 209faa
    const config::tree& user_config_,
Packit 209faa
    scheduler::scheduler_handle& scheduler_handle_) :
Packit 209faa
    test_program(interface_name_, binary_, root_, test_suite_name_, md_,
Packit 209faa
                 model::test_cases_map()),
Packit 209faa
    _pimpl(new impl(user_config_, scheduler_handle_))
Packit 209faa
{
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Gets or loads the list of test cases from the test program.
Packit 209faa
///
Packit 209faa
/// \return The list of test cases provided by the test program.
Packit 209faa
const model::test_cases_map&
Packit 209faa
scheduler::lazy_test_program::test_cases(void) const
Packit 209faa
{
Packit 209faa
    _pimpl->_scheduler_handle.check_interrupt();
Packit 209faa
Packit 209faa
    if (!_pimpl->_loaded) {
Packit 209faa
        const model::test_cases_map tcs = _pimpl->_scheduler_handle.list_tests(
Packit 209faa
            this, _pimpl->_user_config);
Packit 209faa
Packit 209faa
        // Due to the restrictions on when set_test_cases() may be called (as a
Packit 209faa
        // way to lazily initialize the test cases list before it is ever
Packit 209faa
        // returned), this cast is valid.
Packit 209faa
        const_cast< scheduler::lazy_test_program* >(this)->set_test_cases(tcs);
Packit 209faa
Packit 209faa
        _pimpl->_loaded = true;
Packit 209faa
Packit 209faa
        _pimpl->_scheduler_handle.check_interrupt();
Packit 209faa
    }
Packit 209faa
Packit 209faa
    INV(_pimpl->_loaded);
Packit 209faa
    return test_program::test_cases();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Internal implementation for the result_handle class.
Packit 209faa
struct engine::scheduler::result_handle::bimpl : utils::noncopyable {
Packit 209faa
    /// Generic executor exit handle for this result handle.
Packit 209faa
    executor::exit_handle generic;
Packit 209faa
Packit 209faa
    /// Mutable pointer to the corresponding scheduler state.
Packit 209faa
    ///
Packit 209faa
    /// This object references a member of the scheduler_handle that yielded
Packit 209faa
    /// this result_handle instance.  We need this direct access to clean up
Packit 209faa
    /// after ourselves when the result is destroyed.
Packit 209faa
    exec_data_map& all_exec_data;
Packit 209faa
Packit 209faa
    /// Constructor.
Packit 209faa
    ///
Packit 209faa
    /// \param generic_ Generic executor exit handle for this result handle.
Packit 209faa
    /// \param [in,out] all_exec_data_ Global object keeping track of all active
Packit 209faa
    ///     executions for an scheduler.  This is a pointer to a member of the
Packit 209faa
    ///     scheduler_handle object.
Packit 209faa
    bimpl(const executor::exit_handle generic_, exec_data_map& all_exec_data_) :
Packit 209faa
        generic(generic_), all_exec_data(all_exec_data_)
Packit 209faa
    {
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Destructor.
Packit 209faa
    ~bimpl(void)
Packit 209faa
    {
Packit 209faa
        LD(F("Removing %s from all_exec_data") % generic.original_pid());
Packit 209faa
        all_exec_data.erase(generic.original_pid());
Packit 209faa
    }
Packit 209faa
};
Packit 209faa
Packit 209faa
Packit 209faa
/// Constructor.
Packit 209faa
///
Packit 209faa
/// \param pbimpl Constructed internal implementation.
Packit 209faa
scheduler::result_handle::result_handle(std::shared_ptr< bimpl > pbimpl) :
Packit 209faa
    _pbimpl(pbimpl)
Packit 209faa
{
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Destructor.
Packit 209faa
scheduler::result_handle::~result_handle(void)
Packit 209faa
{
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Cleans up the test case results.
Packit 209faa
///
Packit 209faa
/// This function should be called explicitly as it provides the means to
Packit 209faa
/// control any exceptions raised during cleanup.  Do not rely on the destructor
Packit 209faa
/// to clean things up.
Packit 209faa
///
Packit 209faa
/// \throw engine::error If the cleanup fails, especially due to the inability
Packit 209faa
///     to remove the work directory.
Packit 209faa
void
Packit 209faa
scheduler::result_handle::cleanup(void)
Packit 209faa
{
Packit 209faa
    _pbimpl->generic.cleanup();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Returns the original PID corresponding to this result.
Packit 209faa
///
Packit 209faa
/// \return An exec_handle.
Packit 209faa
int
Packit 209faa
scheduler::result_handle::original_pid(void) const
Packit 209faa
{
Packit 209faa
    return _pbimpl->generic.original_pid();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Returns the timestamp of when spawn_test was called.
Packit 209faa
///
Packit 209faa
/// \return A timestamp.
Packit 209faa
const datetime::timestamp&
Packit 209faa
scheduler::result_handle::start_time(void) const
Packit 209faa
{
Packit 209faa
    return _pbimpl->generic.start_time();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Returns the timestamp of when wait_any_test returned this object.
Packit 209faa
///
Packit 209faa
/// \return A timestamp.
Packit 209faa
const datetime::timestamp&
Packit 209faa
scheduler::result_handle::end_time(void) const
Packit 209faa
{
Packit 209faa
    return _pbimpl->generic.end_time();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Returns the path to the test-specific work directory.
Packit 209faa
///
Packit 209faa
/// This is guaranteed to be clear of files created by the scheduler.
Packit 209faa
///
Packit 209faa
/// \return The path to a directory that exists until cleanup() is called.
Packit 209faa
fs::path
Packit 209faa
scheduler::result_handle::work_directory(void) const
Packit 209faa
{
Packit 209faa
    return _pbimpl->generic.work_directory();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Returns the path to the test's stdout file.
Packit 209faa
///
Packit 209faa
/// \return The path to a file that exists until cleanup() is called.
Packit 209faa
const fs::path&
Packit 209faa
scheduler::result_handle::stdout_file(void) const
Packit 209faa
{
Packit 209faa
    return _pbimpl->generic.stdout_file();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Returns the path to the test's stderr file.
Packit 209faa
///
Packit 209faa
/// \return The path to a file that exists until cleanup() is called.
Packit 209faa
const fs::path&
Packit 209faa
scheduler::result_handle::stderr_file(void) const
Packit 209faa
{
Packit 209faa
    return _pbimpl->generic.stderr_file();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Internal implementation for the test_result_handle class.
Packit 209faa
struct engine::scheduler::test_result_handle::impl : utils::noncopyable {
Packit 209faa
    /// Test program data for this test case.
Packit 209faa
    model::test_program_ptr test_program;
Packit 209faa
Packit 209faa
    /// Name of the test case.
Packit 209faa
    std::string test_case_name;
Packit 209faa
Packit 209faa
    /// The actual result of the test execution.
Packit 209faa
    const model::test_result test_result;
Packit 209faa
Packit 209faa
    /// Constructor.
Packit 209faa
    ///
Packit 209faa
    /// \param test_program_ Test program data for this test case.
Packit 209faa
    /// \param test_case_name_ Name of the test case.
Packit 209faa
    /// \param test_result_ The actual result of the test execution.
Packit 209faa
    impl(const model::test_program_ptr test_program_,
Packit 209faa
         const std::string& test_case_name_,
Packit 209faa
         const model::test_result& test_result_) :
Packit 209faa
        test_program(test_program_),
Packit 209faa
        test_case_name(test_case_name_),
Packit 209faa
        test_result(test_result_)
Packit 209faa
    {
Packit 209faa
    }
Packit 209faa
};
Packit 209faa
Packit 209faa
Packit 209faa
/// Constructor.
Packit 209faa
///
Packit 209faa
/// \param pbimpl Constructed internal implementation for the base object.
Packit 209faa
/// \param pimpl Constructed internal implementation.
Packit 209faa
scheduler::test_result_handle::test_result_handle(
Packit 209faa
    std::shared_ptr< bimpl > pbimpl, std::shared_ptr< impl > pimpl) :
Packit 209faa
    result_handle(pbimpl), _pimpl(pimpl)
Packit 209faa
{
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Destructor.
Packit 209faa
scheduler::test_result_handle::~test_result_handle(void)
Packit 209faa
{
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Returns the test program that yielded this result.
Packit 209faa
///
Packit 209faa
/// \return A test program.
Packit 209faa
const model::test_program_ptr
Packit 209faa
scheduler::test_result_handle::test_program(void) const
Packit 209faa
{
Packit 209faa
    return _pimpl->test_program;
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Returns the name of the test case that yielded this result.
Packit 209faa
///
Packit 209faa
/// \return A test case name
Packit 209faa
const std::string&
Packit 209faa
scheduler::test_result_handle::test_case_name(void) const
Packit 209faa
{
Packit 209faa
    return _pimpl->test_case_name;
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Returns the actual result of the test execution.
Packit 209faa
///
Packit 209faa
/// \return A test result.
Packit 209faa
const model::test_result&
Packit 209faa
scheduler::test_result_handle::test_result(void) const
Packit 209faa
{
Packit 209faa
    return _pimpl->test_result;
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Internal implementation for the scheduler_handle.
Packit 209faa
struct engine::scheduler::scheduler_handle::impl : utils::noncopyable {
Packit 209faa
    /// Generic executor instance encapsulated by this one.
Packit 209faa
    executor::executor_handle generic;
Packit 209faa
Packit 209faa
    /// Mapping of exec handles to the data required at run time.
Packit 209faa
    exec_data_map all_exec_data;
Packit 209faa
Packit 209faa
    /// Collection of test_exec_data objects.
Packit 209faa
    typedef std::vector< const test_exec_data* > test_exec_data_vector;
Packit 209faa
Packit 209faa
    /// Constructor.
Packit 209faa
    impl(void) : generic(executor::setup())
Packit 209faa
    {
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Destructor.
Packit 209faa
    ///
Packit 209faa
    /// This runs any pending cleanup routines, which should only happen if the
Packit 209faa
    /// scheduler is abruptly terminated (aka if a signal is received).
Packit 209faa
    ~impl(void)
Packit 209faa
    {
Packit 209faa
        const test_exec_data_vector tests_data = tests_needing_cleanup();
Packit 209faa
Packit 209faa
        for (test_exec_data_vector::const_iterator iter = tests_data.begin();
Packit 209faa
             iter != tests_data.end(); ++iter) {
Packit 209faa
            const test_exec_data* test_data = *iter;
Packit 209faa
Packit 209faa
            try {
Packit 209faa
                sync_cleanup(test_data);
Packit 209faa
            } catch (const std::runtime_error& e) {
Packit 209faa
                LW(F("Failed to run cleanup routine for %s:%s on abrupt "
Packit 209faa
                     "termination")
Packit 209faa
                   % test_data->test_program->relative_path()
Packit 209faa
                   % test_data->test_case_name);
Packit 209faa
            }
Packit 209faa
        }
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Finds any pending exec_datas that correspond to tests needing cleanup.
Packit 209faa
    ///
Packit 209faa
    /// \return The collection of test_exec_data objects that have their
Packit 209faa
    /// needs_cleanup property set to true.
Packit 209faa
    test_exec_data_vector
Packit 209faa
    tests_needing_cleanup(void)
Packit 209faa
    {
Packit 209faa
        test_exec_data_vector tests_data;
Packit 209faa
Packit 209faa
        for (exec_data_map::const_iterator iter = all_exec_data.begin();
Packit 209faa
             iter != all_exec_data.end(); ++iter) {
Packit 209faa
            const exec_data_ptr data = (*iter).second;
Packit 209faa
Packit 209faa
            try {
Packit 209faa
                test_exec_data* test_data = &dynamic_cast< test_exec_data& >(
Packit 209faa
                    *data.get());
Packit 209faa
                if (test_data->needs_cleanup) {
Packit 209faa
                    tests_data.push_back(test_data);
Packit 209faa
                    test_data->needs_cleanup = false;
Packit 209faa
                }
Packit 209faa
            } catch (const std::bad_cast& e) {
Packit 209faa
                // Do nothing for cleanup_exec_data objects.
Packit 209faa
            }
Packit 209faa
        }
Packit 209faa
Packit 209faa
        return tests_data;
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Cleans up a single test case synchronously.
Packit 209faa
    ///
Packit 209faa
    /// \param test_data The data of the previously executed test case to be
Packit 209faa
    ///     cleaned up.
Packit 209faa
    void
Packit 209faa
    sync_cleanup(const test_exec_data* test_data)
Packit 209faa
    {
Packit 209faa
        // The message in this result should never be seen by the user, but use
Packit 209faa
        // something reasonable just in case it leaks and we need to pinpoint
Packit 209faa
        // the call site.
Packit 209faa
        model::test_result result(model::test_result_broken,
Packit 209faa
                                  "Test case died abruptly");
Packit 209faa
Packit 209faa
        const executor::exec_handle cleanup_handle = spawn_cleanup(
Packit 209faa
            test_data->test_program, test_data->test_case_name,
Packit 209faa
            test_data->user_config, test_data->exit_handle.get(),
Packit 209faa
            result);
Packit 209faa
        generic.wait(cleanup_handle);
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Forks and executes a test case cleanup routine asynchronously.
Packit 209faa
    ///
Packit 209faa
    /// \param test_program The container test program.
Packit 209faa
    /// \param test_case_name The name of the test case to run.
Packit 209faa
    /// \param user_config User-provided configuration variables.
Packit 209faa
    /// \param body_handle The exit handle of the test case's corresponding
Packit 209faa
    ///     body.  The cleanup will be executed in the same context.
Packit 209faa
    /// \param body_result The result of the test case's corresponding body.
Packit 209faa
    ///
Packit 209faa
    /// \return A handle for the background operation.  Used to match the result
Packit 209faa
    /// of the execution returned by wait_any() with this invocation.
Packit 209faa
    executor::exec_handle
Packit 209faa
    spawn_cleanup(const model::test_program_ptr test_program,
Packit 209faa
                  const std::string& test_case_name,
Packit 209faa
                  const config::tree& user_config,
Packit 209faa
                  const executor::exit_handle& body_handle,
Packit 209faa
                  const model::test_result& body_result)
Packit 209faa
    {
Packit 209faa
        generic.check_interrupt();
Packit 209faa
Packit 209faa
        const std::shared_ptr< scheduler::interface > interface =
Packit 209faa
            find_interface(test_program->interface_name());
Packit 209faa
Packit 209faa
        LI(F("Spawning %s:%s (cleanup)") % test_program->absolute_path() %
Packit 209faa
           test_case_name);
Packit 209faa
Packit 209faa
        const executor::exec_handle handle = generic.spawn_followup(
Packit 209faa
            run_test_cleanup(interface, test_program, test_case_name,
Packit 209faa
                             user_config),
Packit 209faa
            body_handle, cleanup_timeout);
Packit 209faa
Packit 209faa
        const exec_data_ptr data(new cleanup_exec_data(
Packit 209faa
            test_program, test_case_name, body_handle, body_result));
Packit 209faa
        LD(F("Inserting %s into all_exec_data (cleanup)") % handle.pid());
Packit 209faa
        INV_MSG(all_exec_data.find(handle.pid()) == all_exec_data.end(),
Packit 209faa
                F("PID %s already in all_exec_data; not properly cleaned "
Packit 209faa
                  "up or reused too fast") % handle.pid());;
Packit 209faa
        all_exec_data.insert(exec_data_map::value_type(handle.pid(), data));
Packit 209faa
Packit 209faa
        return handle;
Packit 209faa
    }
Packit 209faa
};
Packit 209faa
Packit 209faa
Packit 209faa
/// Constructor.
Packit 209faa
scheduler::scheduler_handle::scheduler_handle(void) : _pimpl(new impl())
Packit 209faa
{
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Destructor.
Packit 209faa
scheduler::scheduler_handle::~scheduler_handle(void)
Packit 209faa
{
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Queries the path to the root of the work directory for all tests.
Packit 209faa
///
Packit 209faa
/// \return A path.
Packit 209faa
const fs::path&
Packit 209faa
scheduler::scheduler_handle::root_work_directory(void) const
Packit 209faa
{
Packit 209faa
    return _pimpl->generic.root_work_directory();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Cleans up the scheduler state.
Packit 209faa
///
Packit 209faa
/// This function should be called explicitly as it provides the means to
Packit 209faa
/// control any exceptions raised during cleanup.  Do not rely on the destructor
Packit 209faa
/// to clean things up.
Packit 209faa
///
Packit 209faa
/// \throw engine::error If there are problems cleaning up the scheduler.
Packit 209faa
void
Packit 209faa
scheduler::scheduler_handle::cleanup(void)
Packit 209faa
{
Packit 209faa
    _pimpl->generic.cleanup();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Checks if the given interface name is valid.
Packit 209faa
///
Packit 209faa
/// \param interface The name of the interface to validate.
Packit 209faa
///
Packit 209faa
/// \throw engine::error If the given interface is not supported.
Packit 209faa
void
Packit 209faa
scheduler::ensure_valid_interface(const std::string& name)
Packit 209faa
{
Packit 209faa
    if (interfaces.find(name) == interfaces.end())
Packit 209faa
        throw engine::error(F("Unsupported test interface '%s'") % name);
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Registers a new interface.
Packit 209faa
///
Packit 209faa
/// \param name The name of the interface.  Must not have yet been registered.
Packit 209faa
/// \param spec Interface specification.
Packit 209faa
void
Packit 209faa
scheduler::register_interface(const std::string& name,
Packit 209faa
                              const std::shared_ptr< interface > spec)
Packit 209faa
{
Packit 209faa
    PRE(interfaces.find(name) == interfaces.end());
Packit 209faa
    interfaces.insert(interfaces_map::value_type(name, spec));
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Returns the names of all registered interfaces.
Packit 209faa
///
Packit 209faa
/// \return A collection of interface names.
Packit 209faa
std::set< std::string >
Packit 209faa
scheduler::registered_interface_names(void)
Packit 209faa
{
Packit 209faa
    std::set< std::string > names;
Packit 209faa
    for (interfaces_map::const_iterator iter = interfaces.begin();
Packit 209faa
         iter != interfaces.end(); ++iter) {
Packit 209faa
        names.insert((*iter).first);
Packit 209faa
    }
Packit 209faa
    return names;
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Initializes the scheduler.
Packit 209faa
///
Packit 209faa
/// \pre This function can only be called if there is no other scheduler_handle
Packit 209faa
/// object alive.
Packit 209faa
///
Packit 209faa
/// \return A handle to the operations of the scheduler.
Packit 209faa
scheduler::scheduler_handle
Packit 209faa
scheduler::setup(void)
Packit 209faa
{
Packit 209faa
    return scheduler_handle();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Retrieves the list of test cases from a test program.
Packit 209faa
///
Packit 209faa
/// This operation is currently synchronous.
Packit 209faa
///
Packit 209faa
/// This operation should never throw.  Any errors during the processing of the
Packit 209faa
/// test case list are subsumed into a single test case in the return value that
Packit 209faa
/// represents the failed retrieval.
Packit 209faa
///
Packit 209faa
/// \param test_program The test program from which to obtain the list of test
Packit 209faa
/// cases.
Packit 209faa
/// \param user_config User-provided configuration variables.
Packit 209faa
///
Packit 209faa
/// \return The list of test cases.
Packit 209faa
model::test_cases_map
Packit 209faa
scheduler::scheduler_handle::list_tests(
Packit 209faa
    const model::test_program* test_program,
Packit 209faa
    const config::tree& user_config)
Packit 209faa
{
Packit 209faa
    _pimpl->generic.check_interrupt();
Packit 209faa
Packit 209faa
    const std::shared_ptr< scheduler::interface > interface = find_interface(
Packit 209faa
        test_program->interface_name());
Packit 209faa
Packit 209faa
    try {
Packit 209faa
        const executor::exec_handle exec_handle = _pimpl->generic.spawn(
Packit 209faa
            list_test_cases(interface, test_program, user_config),
Packit 209faa
            list_timeout, none);
Packit 209faa
        executor::exit_handle exit_handle = _pimpl->generic.wait(exec_handle);
Packit 209faa
Packit 209faa
        const model::test_cases_map test_cases = interface->parse_list(
Packit 209faa
            exit_handle.status(),
Packit 209faa
            exit_handle.stdout_file(),
Packit 209faa
            exit_handle.stderr_file());
Packit 209faa
Packit 209faa
        exit_handle.cleanup();
Packit 209faa
Packit 209faa
        if (test_cases.empty())
Packit 209faa
            throw std::runtime_error("Empty test cases list");
Packit 209faa
Packit 209faa
        return test_cases;
Packit 209faa
    } catch (const std::runtime_error& e) {
Packit 209faa
        // TODO(jmmv): This is a very ugly workaround for the fact that we
Packit 209faa
        // cannot report failures at the test-program level.
Packit 209faa
        LW(F("Failed to load test cases list: %s") % e.what());
Packit 209faa
        model::test_cases_map fake_test_cases;
Packit 209faa
        fake_test_cases.insert(model::test_cases_map::value_type(
Packit 209faa
            "__test_cases_list__",
Packit 209faa
            model::test_case(
Packit 209faa
                "__test_cases_list__",
Packit 209faa
                "Represents the correct processing of the test cases list",
Packit 209faa
                model::test_result(model::test_result_broken, e.what()))));
Packit 209faa
        return fake_test_cases;
Packit 209faa
    }
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Forks and executes a test case asynchronously.
Packit 209faa
///
Packit 209faa
/// Note that the caller needn't know if the test has a cleanup routine or not.
Packit 209faa
/// If there indeed is a cleanup routine, we trigger it at wait_any() time.
Packit 209faa
///
Packit 209faa
/// \param test_program The container test program.
Packit 209faa
/// \param test_case_name The name of the test case to run.
Packit 209faa
/// \param user_config User-provided configuration variables.
Packit 209faa
///
Packit 209faa
/// \return A handle for the background operation.  Used to match the result of
Packit 209faa
/// the execution returned by wait_any() with this invocation.
Packit 209faa
scheduler::exec_handle
Packit 209faa
scheduler::scheduler_handle::spawn_test(
Packit 209faa
    const model::test_program_ptr test_program,
Packit 209faa
    const std::string& test_case_name,
Packit 209faa
    const config::tree& user_config)
Packit 209faa
{
Packit 209faa
    _pimpl->generic.check_interrupt();
Packit 209faa
Packit 209faa
    const std::shared_ptr< scheduler::interface > interface = find_interface(
Packit 209faa
        test_program->interface_name());
Packit 209faa
Packit 209faa
    LI(F("Spawning %s:%s") % test_program->absolute_path() % test_case_name);
Packit 209faa
Packit 209faa
    const model::test_case& test_case = test_program->find(test_case_name);
Packit 209faa
Packit 209faa
    optional< passwd::user > unprivileged_user;
Packit 209faa
    if (user_config.is_set("unprivileged_user") &&
Packit 209faa
        test_case.get_metadata().required_user() == "unprivileged") {
Packit 209faa
        unprivileged_user = user_config.lookup< engine::user_node >(
Packit 209faa
            "unprivileged_user");
Packit 209faa
    }
Packit 209faa
Packit 209faa
    const executor::exec_handle handle = _pimpl->generic.spawn(
Packit 209faa
        run_test_program(interface, test_program, test_case_name,
Packit 209faa
                         user_config),
Packit 209faa
        test_case.get_metadata().timeout(),
Packit 209faa
        unprivileged_user);
Packit 209faa
Packit 209faa
    const exec_data_ptr data(new test_exec_data(
Packit 209faa
        test_program, test_case_name, interface, user_config));
Packit 209faa
    LD(F("Inserting %s into all_exec_data") % handle.pid());
Packit 209faa
    INV_MSG(
Packit 209faa
        _pimpl->all_exec_data.find(handle.pid()) == _pimpl->all_exec_data.end(),
Packit 209faa
        F("PID %s already in all_exec_data; not cleaned up or reused too fast")
Packit 209faa
        % handle.pid());;
Packit 209faa
    _pimpl->all_exec_data.insert(exec_data_map::value_type(handle.pid(), data));
Packit 209faa
Packit 209faa
    return handle.pid();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Waits for completion of any forked test case.
Packit 209faa
///
Packit 209faa
/// Note that if the terminated test case has a cleanup routine, this function
Packit 209faa
/// is the one in charge of spawning the cleanup routine asynchronously.
Packit 209faa
///
Packit 209faa
/// \return The result of the execution of a subprocess.  This is a dynamically
Packit 209faa
/// allocated object because the scheduler can spawn subprocesses of various
Packit 209faa
/// types and, at wait time, we don't know upfront what we are going to get.
Packit 209faa
scheduler::result_handle_ptr
Packit 209faa
scheduler::scheduler_handle::wait_any(void)
Packit 209faa
{
Packit 209faa
    _pimpl->generic.check_interrupt();
Packit 209faa
Packit 209faa
    executor::exit_handle handle = _pimpl->generic.wait_any();
Packit 209faa
Packit 209faa
    const exec_data_map::iterator iter = _pimpl->all_exec_data.find(
Packit 209faa
        handle.original_pid());
Packit 209faa
    exec_data_ptr data = (*iter).second;
Packit 209faa
Packit 209faa
    utils::dump_stacktrace_if_available(data->test_program->absolute_path(),
Packit 209faa
                                        _pimpl->generic, handle);
Packit 209faa
Packit 209faa
    optional< model::test_result > result;
Packit 209faa
    try {
Packit 209faa
        test_exec_data* test_data = &dynamic_cast< test_exec_data& >(
Packit 209faa
            *data.get());
Packit 209faa
        LD(F("Got %s from all_exec_data") % handle.original_pid());
Packit 209faa
Packit 209faa
        test_data->exit_handle = handle;
Packit 209faa
Packit 209faa
        const model::test_case& test_case = test_data->test_program->find(
Packit 209faa
            test_data->test_case_name);
Packit 209faa
Packit 209faa
        result = test_case.fake_result();
Packit 209faa
Packit 209faa
        if (!result && handle.status() && handle.status().get().exited() &&
Packit 209faa
            handle.status().get().exitstatus() == exit_skipped) {
Packit 209faa
            // If the test's process terminated with our magic "exit_skipped"
Packit 209faa
            // status, there are two cases to handle.  The first is the case
Packit 209faa
            // where the "skipped cookie" exists, in which case we never got to
Packit 209faa
            // actually invoke the test program; if that's the case, handle it
Packit 209faa
            // here.  The second case is where the test case actually decided to
Packit 209faa
            // exit with the "exit_skipped" status; in that case, just fall back
Packit 209faa
            // to the regular status handling.
Packit 209faa
            const fs::path skipped_cookie_path = handle.control_directory() /
Packit 209faa
                skipped_cookie;
Packit 209faa
            std::ifstream input(skipped_cookie_path.c_str());
Packit 209faa
            if (input) {
Packit 209faa
                result = model::test_result(model::test_result_skipped,
Packit 209faa
                                            utils::read_stream(input));
Packit 209faa
                input.close();
Packit 209faa
Packit 209faa
                // If we determined that the test needs to be skipped, we do not
Packit 209faa
                // want to run the cleanup routine because doing so could result
Packit 209faa
                // in errors.  However, we still want to run the cleanup routine
Packit 209faa
                // if the test's body reports a skip (because actions could have
Packit 209faa
                // already been taken).
Packit 209faa
                test_data->needs_cleanup = false;
Packit 209faa
            }
Packit 209faa
        }
Packit 209faa
        if (!result) {
Packit 209faa
            result = test_data->interface->compute_result(
Packit 209faa
                handle.status(),
Packit 209faa
                handle.control_directory(),
Packit 209faa
                handle.stdout_file(),
Packit 209faa
                handle.stderr_file());
Packit 209faa
        }
Packit 209faa
        INV(result);
Packit 209faa
Packit 209faa
        if (!result.get().good()) {
Packit 209faa
            append_files_listing(handle.work_directory(),
Packit 209faa
                                 handle.stderr_file());
Packit 209faa
        }
Packit 209faa
Packit 209faa
        if (test_data->needs_cleanup) {
Packit 209faa
            INV(test_case.get_metadata().has_cleanup());
Packit 209faa
            // The test body has completed and we have processed it.  If there
Packit 209faa
            // is a cleanup routine, trigger it now and wait for any other test
Packit 209faa
            // completion.  The caller never knows about cleanup routines.
Packit 209faa
            _pimpl->spawn_cleanup(test_data->test_program,
Packit 209faa
                                  test_data->test_case_name,
Packit 209faa
                                  test_data->user_config, handle, result.get());
Packit 209faa
            test_data->needs_cleanup = false;
Packit 209faa
Packit 209faa
            // TODO(jmmv): Chaining this call is ugly.  We'd be better off by
Packit 209faa
            // looping over terminated processes until we got a result suitable
Packit 209faa
            // for user consumption.  For the time being this is good enough and
Packit 209faa
            // not a problem because the call chain won't get big: the majority
Packit 209faa
            // of test cases do not have cleanup routines.
Packit 209faa
            return wait_any();
Packit 209faa
        }
Packit 209faa
    } catch (const std::bad_cast& e) {
Packit 209faa
        const cleanup_exec_data* cleanup_data =
Packit 209faa
            &dynamic_cast< const cleanup_exec_data& >(*data.get());
Packit 209faa
        LD(F("Got %s from all_exec_data (cleanup)") % handle.original_pid());
Packit 209faa
Packit 209faa
        // Handle the completion of cleanup subprocesses internally: the caller
Packit 209faa
        // is not aware that these exist so, when we return, we must return the
Packit 209faa
        // data for the original test that triggered this routine.  For example,
Packit 209faa
        // because the caller wants to see the exact same exec_handle that was
Packit 209faa
        // returned by spawn_test.
Packit 209faa
Packit 209faa
        const model::test_result& body_result = cleanup_data->body_result;
Packit 209faa
        if (body_result.good()) {
Packit 209faa
            if (!handle.status()) {
Packit 209faa
                result = model::test_result(model::test_result_broken,
Packit 209faa
                                            "Test case cleanup timed out");
Packit 209faa
            } else {
Packit 209faa
                if (!handle.status().get().exited() ||
Packit 209faa
                    handle.status().get().exitstatus() != EXIT_SUCCESS) {
Packit 209faa
                    result = model::test_result(
Packit 209faa
                        model::test_result_broken,
Packit 209faa
                        "Test case cleanup did not terminate successfully");
Packit 209faa
                } else {
Packit 209faa
                    result = body_result;
Packit 209faa
                }
Packit 209faa
            }
Packit 209faa
        } else {
Packit 209faa
            result = body_result;
Packit 209faa
        }
Packit 209faa
Packit 209faa
        // Untrack the cleanup process.  This must be done explicitly because we
Packit 209faa
        // do not create a result_handle object for the cleanup, and that is the
Packit 209faa
        // one in charge of doing so in the regular (non-cleanup) case.
Packit 209faa
        LD(F("Removing %s from all_exec_data (cleanup) in favor of %s")
Packit 209faa
           % handle.original_pid()
Packit 209faa
           % cleanup_data->body_exit_handle.original_pid());
Packit 209faa
        _pimpl->all_exec_data.erase(handle.original_pid());
Packit 209faa
Packit 209faa
        handle = cleanup_data->body_exit_handle;
Packit 209faa
    }
Packit 209faa
    INV(result);
Packit 209faa
Packit 209faa
    std::shared_ptr< result_handle::bimpl > result_handle_bimpl(
Packit 209faa
        new result_handle::bimpl(handle, _pimpl->all_exec_data));
Packit 209faa
    std::shared_ptr< test_result_handle::impl > test_result_handle_impl(
Packit 209faa
        new test_result_handle::impl(
Packit 209faa
            data->test_program, data->test_case_name, result.get()));
Packit 209faa
    return result_handle_ptr(new test_result_handle(result_handle_bimpl,
Packit 209faa
                                                    test_result_handle_impl));
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Forks and executes a test case synchronously for debugging.
Packit 209faa
///
Packit 209faa
/// \pre No other processes should be in execution by the scheduler.
Packit 209faa
///
Packit 209faa
/// \param test_program The container test program.
Packit 209faa
/// \param test_case_name The name of the test case to run.
Packit 209faa
/// \param user_config User-provided configuration variables.
Packit 209faa
/// \param stdout_target File to which to write the stdout of the test case.
Packit 209faa
/// \param stderr_target File to which to write the stderr of the test case.
Packit 209faa
///
Packit 209faa
/// \return The result of the execution of the test.
Packit 209faa
scheduler::result_handle_ptr
Packit 209faa
scheduler::scheduler_handle::debug_test(
Packit 209faa
    const model::test_program_ptr test_program,
Packit 209faa
    const std::string& test_case_name,
Packit 209faa
    const config::tree& user_config,
Packit 209faa
    const fs::path& stdout_target,
Packit 209faa
    const fs::path& stderr_target)
Packit 209faa
{
Packit 209faa
    const exec_handle exec_handle = spawn_test(
Packit 209faa
        test_program, test_case_name, user_config);
Packit 209faa
    result_handle_ptr result_handle = wait_any();
Packit 209faa
Packit 209faa
    // TODO(jmmv): We need to do this while the subprocess is alive.  This is
Packit 209faa
    // important for debugging purposes, as we should see the contents of stdout
Packit 209faa
    // or stderr as they come in.
Packit 209faa
    //
Packit 209faa
    // Unfortunately, we cannot do so.  We cannot just read and block from a
Packit 209faa
    // file, waiting for further output to appear... as this only works on pipes
Packit 209faa
    // or sockets.  We need a better interface for this whole thing.
Packit 209faa
    {
Packit 209faa
        std::auto_ptr< std::ostream > output = utils::open_ostream(
Packit 209faa
            stdout_target);
Packit 209faa
        *output << utils::read_file(result_handle->stdout_file());
Packit 209faa
    }
Packit 209faa
    {
Packit 209faa
        std::auto_ptr< std::ostream > output = utils::open_ostream(
Packit 209faa
            stderr_target);
Packit 209faa
        *output << utils::read_file(result_handle->stderr_file());
Packit 209faa
    }
Packit 209faa
Packit 209faa
    INV(result_handle->original_pid() == exec_handle);
Packit 209faa
    return result_handle;
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Checks if an interrupt has fired.
Packit 209faa
///
Packit 209faa
/// Calls to this function should be sprinkled in strategic places through the
Packit 209faa
/// code protected by an interrupts_handler object.
Packit 209faa
///
Packit 209faa
/// This is just a wrapper over signals::check_interrupt() to avoid leaking this
Packit 209faa
/// dependency to the caller.
Packit 209faa
///
Packit 209faa
/// \throw signals::interrupted_error If there has been an interrupt.
Packit 209faa
void
Packit 209faa
scheduler::scheduler_handle::check_interrupt(void) const
Packit 209faa
{
Packit 209faa
    _pimpl->generic.check_interrupt();
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Queries the current execution context.
Packit 209faa
///
Packit 209faa
/// \return The queried context.
Packit 209faa
model::context
Packit 209faa
scheduler::current_context(void)
Packit 209faa
{
Packit 209faa
    return model::context(fs::current_path(), utils::getallenv());
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Generates the set of configuration variables for a test program.
Packit 209faa
///
Packit 209faa
/// \param user_config The configuration variables provided by the user.
Packit 209faa
/// \param test_suite The name of the test suite.
Packit 209faa
///
Packit 209faa
/// \return The mapping of configuration variables for the test program.
Packit 209faa
config::properties_map
Packit 209faa
scheduler::generate_config(const config::tree& user_config,
Packit 209faa
                           const std::string& test_suite)
Packit 209faa
{
Packit 209faa
    config::properties_map props;
Packit 209faa
Packit 209faa
    try {
Packit 209faa
        props = user_config.all_properties(F("test_suites.%s") % test_suite,
Packit 209faa
                                           true);
Packit 209faa
    } catch (const config::unknown_key_error& unused_error) {
Packit 209faa
        // Ignore: not all test suites have entries in the configuration.
Packit 209faa
    }
Packit 209faa
Packit 209faa
    // TODO(jmmv): This is a hack that exists for the ATF interface only, so it
Packit 209faa
    // should be moved there.
Packit 209faa
    if (user_config.is_set("unprivileged_user")) {
Packit 209faa
        const passwd::user& user =
Packit 209faa
            user_config.lookup< engine::user_node >("unprivileged_user");
Packit 209faa
        props["unprivileged-user"] = user.name;
Packit 209faa
    }
Packit 209faa
Packit 209faa
    return props;
Packit 209faa
}