Beispiel #1
0
/// Parses a user-provided test filter.
///
/// \param str The user-provided string representing a filter for tests.  Must
///     be of the form <test_program%gt;[:<test_case%gt;].
///
/// \return The parsed filter.
///
/// \throw std::runtime_error If the provided filter is invalid.
engine::test_filter
engine::test_filter::parse(const std::string& str)
{
    if (str.empty())
        throw std::runtime_error("Test filter cannot be empty");

    const std::string::size_type pos = str.find(':');
    if (pos == 0)
        throw std::runtime_error(F("Program name component in '%s' is empty")
                                 % str);
    if (pos == str.length() - 1)
        throw std::runtime_error(F("Test case component in '%s' is empty")
                                 % str);

    try {
        const fs::path test_program_(str.substr(0, pos));
        if (test_program_.is_absolute())
            throw std::runtime_error(F("Program name '%s' must be relative "
                                       "to the test suite, not absolute") %
                                       test_program_.str());
        if (pos == std::string::npos) {
            LD(F("Parsed user filter '%s': test program '%s', no test case") %
               str % test_program_.str());
            return test_filter(test_program_, "");
        } else {
            const std::string test_case_(str.substr(pos + 1));
            LD(F("Parsed user filter '%s': test program '%s', test case '%s'") %
               str % test_program_.str() % test_case_);
            return test_filter(test_program_, test_case_);
        }
    } catch (const fs::error& e) {
        throw std::runtime_error(F("Invalid path in filter '%s': %s") % str %
                                 e.what());
    }
}
Beispiel #2
0
/// Executes an external binary and replaces the current process.
///
/// This differs from process::exec() in that this function reports errors
/// caused by the exec(2) system call to let the caller decide how to handle
/// them.
///
/// This function must not use any of the logging features so that the output
/// of the subprocess is not "polluted" by our own messages.
///
/// This function must also not affect the global state of the current process
/// as otherwise we would not be able to use vfork().  Only state stored in the
/// stack can be touched.
///
/// \param program The binary to execute.
/// \param args The arguments to pass to the binary, without the program name.
///
/// \throw system_error If the exec(2) call fails.
void
process::exec_unsafe(const fs::path& program, const args_vector& args)
{
    PRE(args.size() < MAX_ARGS);
    int original_errno = 0;
    try {
        const char* argv[MAX_ARGS + 1];

        argv[0] = program.c_str();
        for (args_vector::size_type i = 0; i < args.size(); i++)
            argv[1 + i] = args[i].c_str();
        argv[1 + args.size()] = NULL;

        const int ret = ::execv(program.c_str(),
                                (char* const*)(unsigned long)(const void*)argv);
        original_errno = errno;
        INV(ret == -1);
        std::cerr << "Failed to execute " << program << ": "
                  << std::strerror(original_errno) << "\n";
    } catch (const std::runtime_error& error) {
        std::cerr << "Failed to execute " << program << ": "
                  << error.what() << "\n";
        std::abort();
    } catch (...) {
        std::cerr << "Failed to execute " << program << "; got unexpected "
            "exception during exec\n";
        std::abort();
    }

    // We must do this here to prevent our exception from being caught by the
    // generic handlers above.
    INV(original_errno != 0);
    throw system_error("Failed to execute " + program.str(), original_errno);
}
Beispiel #3
0
/// Initializes an empty database.
///
/// \param db The database to initialize.
///
/// \return The metadata record written into the new database.
///
/// \throw store::error If there is a problem initializing the database.
store::metadata
store::detail::initialize(sqlite::database& db)
{
    PRE(empty_database(db));

    const fs::path schema = schema_file();

    std::ifstream input(schema.c_str());
    if (!input)
        throw error(F("Cannot open database schema '%s'") % schema);

    LI(F("Populating new database with schema from %s") % schema);
    const std::string schema_string = utils::read_stream(input);
    try {
        db.exec(schema_string);

        const metadata metadata = metadata::fetch_latest(db);
        LI(F("New metadata entry %s") % metadata.timestamp());
        if (metadata.schema_version() != detail::current_schema_version) {
            UNREACHABLE_MSG(F("current_schema_version is out of sync with "
                              "%s") % schema);
        }
        return metadata;
    } catch (const store::integrity_error& e) {
        // Could be raised by metadata::fetch_latest.
        UNREACHABLE_MSG("Inconsistent code while creating a database");
    } catch (const sqlite::error& e) {
        throw error(F("Failed to initialize database: %s") % e.what());
    }
}
Beispiel #4
0
/// Computes the path to a new database for the given test suite.
///
/// \param root Path to the root of the test suite being run; needed to properly
///     autogenerate the identifiers.
/// \param when Timestamp for the test suite being run; needed to properly
///     autogenerate the identifiers.
///
/// \return Identifier of the created results file, if applicable, and the path
/// to such file.
fs::path
layout::new_db_for_migration(const fs::path& root,
                             const datetime::timestamp& when)
{
    const std::string generated_id = new_id(test_suite_for_path(root), when);
    const fs::path path = query_store_dir() / (
        F("results.%s.db") % generated_id);
    fs::mkdir_p(path.branch_path(), 0755);
    return path;
}
Beispiel #5
0
/// Parses a test suite configuration file.
///
/// \param file The file to parse.
/// \param user_build_root If not none, specifies a path to a directory
///     containing the test programs themselves.  The layout of the build root
///     must match the layout of the source root (which is just the directory
///     from which the Kyuafile is being read).
///
/// \return High-level representation of the configuration file.
///
/// \throw load_error If there is any problem loading the file.  This includes
///     file access errors and syntax errors.
engine::kyuafile
engine::kyuafile::load(const fs::path& file,
                       const optional< fs::path > user_build_root)
{
    const fs::path source_root_ = file.branch_path();
    const fs::path build_root_ = user_build_root ?
        user_build_root.get() : source_root_;

    return kyuafile(source_root_, build_root_,
                    parser(source_root_, build_root_,
                           fs::path(file.leaf_name())).parse());
}
Beispiel #6
0
/// Returns the test suite name for the current directory.
///
/// \return The identifier of the current test suite.
std::string
layout::test_suite_for_path(const fs::path& path)
{
    std::string test_suite;
    if (path.is_absolute())
        test_suite = path.str();
    else
        test_suite = path.to_absolute().str();
    PRE(!test_suite.empty() && test_suite[0] == '/');

    std::replace(test_suite.begin(), test_suite.end(), '/', '_');
    test_suite.erase(0, 1);

    return test_suite;
}
Beispiel #7
0
/// Creates a temporary file.
///
/// The temporary file is created using mkstemp(3) using the provided template.
/// This should be most likely used in conjunction with fs::auto_file.
///
/// \param path_template The template for the temporary path, which is a
///     basename that is created within the TMPDIR.  Must contain the XXXXXX
///     pattern, which is atomically replaced by a random unique string.
///
/// \return The generated path for the temporary directory.
///
/// \throw fs::system_error If the call to mkstemp(3) fails.
fs::path
fs::mkstemp(const std::string& path_template)
{
    PRE(path_template.find("XXXXXX") != std::string::npos);

    const fs::path tmpdir(utils::getenv_with_default("TMPDIR", "/tmp"));
    const fs::path full_template = tmpdir / path_template;

    utils::auto_array< char > buf(new char[full_template.str().length() + 1]);
    std::strcpy(buf.get(), full_template.c_str());
    if (::mkstemp(buf.get()) == -1) {
        const int original_errno = errno;
        throw fs::system_error(F("Cannot create temporary file using template "
                                 "%s") % full_template, original_errno);
    }
    return fs::path(buf.get());
}
Beispiel #8
0
/// Concatenates this path with another path.
///
/// \param rest The path to concatenate to this one.  Cannot be absolute.
///
/// \return A new path containing the concatenation of this path and the other
///     path.
///
/// \throw utils::fs::join_error If the join operation is invalid because the
///     two paths are incompatible.
fs::path
fs::path::operator/(const fs::path& rest) const
{
    if (rest.is_absolute())
        throw fs::join_error(_repr, rest._repr,
                             "Cannot concatenate a path to an absolute path");
    return fs::path(_repr + '/' + rest._repr);
}
Beispiel #9
0
/// Creates a directory.
///
/// \param dir The path to the directory to create.
/// \param mode The permissions for the new directory.
///
/// \throw system_error If the call to mkdir(2) fails.
void
fs::mkdir(const fs::path& dir, const int mode)
{
    if (::mkdir(dir.c_str(), static_cast< mode_t >(mode)) == -1) {
        const int original_errno = errno;
        throw fs::system_error(F("Failed to create directory %s") % dir,
                               original_errno);
    }
}
Beispiel #10
0
/// Locates a file in the PATH.
///
/// \param name The file to locate.
///
/// \return The path to the located file or none if it was not found.  The
/// returned path is always absolute.
optional< fs::path >
fs::find_in_path(const char* name)
{
    const optional< std::string > current_path = utils::getenv("PATH");
    if (!current_path || current_path.get().empty())
        return none;

    std::istringstream path_input(current_path.get() + ":");
    std::string path_component;
    while (std::getline(path_input, path_component, ':').good()) {
        const fs::path candidate = path_component.empty() ?
                                   fs::path(name) : (fs::path(path_component) / name);
        if (exists(candidate)) {
            if (candidate.is_absolute())
                return utils::make_optional(candidate);
            else
                return utils::make_optional(candidate.to_absolute());
        }
    }
    return none;
}
Beispiel #11
0
/// Creates a directory and any missing parents.
///
/// This is separate from the fs::mkdir function to clearly differentiate the
/// libc wrapper from the more complex algorithm implemented here.
///
/// \param dir The path to the directory to create.
/// \param mode The permissions for the new directories.
///
/// \throw system_error If any call to mkdir(2) fails.
void
fs::mkdir_p(const fs::path& dir, const int mode)
{
    try {
        fs::mkdir(dir, mode);
    } catch (const fs::system_error& e) {
        if (e.original_errno() == ENOENT) {
            fs::mkdir_p(dir.branch_path(), mode);
            fs::mkdir(dir, mode);
        } else if (e.original_errno() != EEXIST)
            throw e;
    }
}
Beispiel #12
0
 /// Constructor.
 ///
 /// \param interface_name_ Name of the test program interface.
 /// \param binary_ The name of the test program binary relative to root_.
 /// \param root_ The root of the test suite containing the test program.
 /// \param test_suite_name_ The name of the test suite this program
 ///     belongs to.
 /// \param md_ Metadata of the test program.
 impl(const std::string& interface_name_, const fs::path& binary_,
      const fs::path& root_, const std::string& test_suite_name_,
      const metadata& md_) :
     interface_name(interface_name_),
     binary(binary_),
     root(root_),
     test_suite_name(test_suite_name_),
     md(md_)
 {
     PRE_MSG(!binary.is_absolute(),
             F("The program '%s' must be relative to the root of the test "
               "suite '%s'") % binary % root);
 }
Beispiel #13
0
/// Backs up a database for schema migration purposes.
///
/// \todo We should probably use the SQLite backup API instead of doing a raw
/// file copy.  We issue our backup call with the database already open, but
/// because it is quiescent, it's OK to do so.
///
/// \param source Location of the database to be backed up.
/// \param old_version Version of the database's CURRENT schema, used to
///     determine the name of the backup file.
///
/// \throw error If there is a problem during the backup.
void
store::detail::backup_database(const fs::path& source, const int old_version)
{
    const fs::path target(F("%s.v%s.backup") % source.str() % old_version);

    LI(F("Backing up database %s to %s") % source % target);

    std::ifstream input(source.c_str());
    if (!input)
        throw error(F("Cannot open database file %s") % source);

    std::ofstream output(target.c_str());
    if (!output)
        throw error(F("Cannot create database backup file %s") % target);

    char buffer[1024];
    while (input.good()) {
        input.read(buffer, sizeof(buffer));
        if (input.good() || input.eof())
            output.write(buffer, input.gcount());
    }
    if (!input.good() && !input.eof())
        throw error(F("Error while reading input file %s") % source);
}
Beispiel #14
0
/// Recursively removes a directory.
///
/// This operation simulates a "rm -r".  No effort is made to forcibly delete
/// files and no attention is paid to mount points.
///
/// \param directory The directory to remove.
///
/// \throw fs::error If there is a problem removing any directory or file.
void
fs::rm_r(const fs::path& directory)
{
    DIR* dirp = ::opendir(directory.c_str());
    if (dirp == NULL) {
        const int original_errno = errno;
        throw fs::system_error(F("Failed to open directory %s") %
                               directory.str(), original_errno);
    }
    try {
        ::dirent* dp;
        while ((dp = ::readdir(dirp)) != NULL) {
            const std::string name = dp->d_name;
            if (name == "." || name == "..")
                continue;

            const fs::path entry = directory / dp->d_name;

            const struct ::stat sb = safe_stat(entry);
            if (S_ISDIR(sb.st_mode)) {
                LD(F("Descending into %s") % entry);
                fs::rm_r(entry);
            } else {
                LD(F("Removing file %s") % entry);
                fs::unlink(entry);
            }
        }
    } catch (...) {
        ::closedir(dirp);
        throw;
    }
    ::closedir(dirp);

    LD(F("Removing empty directory %s") % directory);
    fs::rmdir(directory);
}
Beispiel #15
0
    /// Constructor.
    ///
    /// \param interface_name_ Name of the test program interface.
    /// \param binary_ The name of the test program binary relative to root_.
    /// \param root_ The root of the test suite containing the test program.
    /// \param test_suite_name_ The name of the test suite this program
    ///     belongs to.
    /// \param md_ Metadata of the test program.
    /// \param test_cases_ The collection of test cases in the test program.
    impl(const std::string& interface_name_, const fs::path& binary_,
         const fs::path& root_, const std::string& test_suite_name_,
         const model::metadata& md_, const model::test_cases_map& test_cases_) :
        interface_name(interface_name_),
        binary(binary_),
        root(root_),
        test_suite_name(test_suite_name_),
        md(md_),
        test_cases(test_cases_)
    {
        PRE_MSG(!binary.is_absolute(),
                F("The program '%s' must be relative to the root of the test "
                  "suite '%s'") % binary % root);

        for (model::test_cases_map::const_iterator iter = test_cases.begin();
             iter != test_cases.end(); ++iter) {
            PRE_MSG((*iter).first == (*iter).second.name(),
                    F("The test case '%s' has been registered with the "
                      "non-matching name '%s'") %
                    (*iter).first % (*iter).second.name());
        }
    }
Beispiel #16
0
/// Computes the test cases list of a test program.
///
/// \param status The termination status of the subprocess used to execute
///     the exec_test() method or none if the test timed out.
/// \param stdout_path Path to the file containing the stdout of the test.
/// \param stderr_path Path to the file containing the stderr of the test.
///
/// \return A list of test cases.
///
/// \throw error If there is a problem parsing the test case list.
model::test_cases_map
engine::atf_interface::parse_list(const optional< process::status >& status,
                                  const fs::path& stdout_path,
                                  const fs::path& stderr_path) const
{
    const std::string stderr_contents = utils::read_file(stderr_path);
    if (!stderr_contents.empty())
        LW("Test case list wrote to stderr: " + stderr_contents);

    if (!status)
        throw engine::error("Test case list timed out");
    if (status.get().exited()) {
        const int exitstatus = status.get().exitstatus();
        if (exitstatus == EXIT_SUCCESS) {
            // Nothing to do; fall through.
        } else if (exitstatus == exit_eacces) {
            throw engine::error("Permission denied to run test program");
        } else if (exitstatus == exit_enoent) {
            throw engine::error("Cannot find test program");
        } else if (exitstatus == exit_enoexec) {
            throw engine::error("Invalid test program format");
        } else {
            throw engine::error("Test program did not exit cleanly");
        }
    } else {
        throw engine::error("Test program received signal");
    }

    std::ifstream input(stdout_path.c_str());
    if (!input)
        throw engine::load_error(stdout_path, "Cannot open file for read");
    const model::test_cases_map test_cases = parse_atf_list(input);

    if (!stderr_contents.empty())
        throw engine::error("Test case list wrote to stderr");

    return test_cases;
}
Beispiel #17
0
/// Gets the absolute path to the test program.
///
/// \return The absolute path to the test program binary.
const fs::path
model::test_program::absolute_path(void) const
{
    const fs::path full_path = _pimpl->root / _pimpl->binary;
    return full_path.is_absolute() ? full_path : full_path.to_absolute();
}
Beispiel #18
0
/// Checks if a file exists.
///
/// Be aware that this is racy in the same way as access(2) is.
///
/// \param path The file to check the existance of.
///
/// \return True if the file exists; false otherwise.
bool
fs::exists(const fs::path& path)
{
    return ::access(path.c_str(), F_OK) == 0;
}
Beispiel #19
0
/// Waits for completion of any forked test case.
///
/// Note that if the terminated test case has a cleanup routine, this function
/// is the one in charge of spawning the cleanup routine asynchronously.
///
/// \return The result of the execution of a subprocess.  This is a dynamically
/// allocated object because the scheduler can spawn subprocesses of various
/// types and, at wait time, we don't know upfront what we are going to get.
scheduler::result_handle_ptr
scheduler::scheduler_handle::wait_any(void)
{
    _pimpl->generic.check_interrupt();

    executor::exit_handle handle = _pimpl->generic.wait_any();

    const exec_data_map::iterator iter = _pimpl->all_exec_data.find(
        handle.original_pid());
    exec_data_ptr& data = (*iter).second;

    utils::dump_stacktrace_if_available(data->test_program->absolute_path(),
                                        _pimpl->generic, handle);

    optional< model::test_result > result;
    try {
        test_exec_data* test_data = &dynamic_cast< test_exec_data& >(
            *data.get());

        test_data->exit_handle = handle;

        const model::test_case& test_case = test_data->test_program->find(
            test_data->test_case_name);

        result = test_case.fake_result();

        if (!result && handle.status() && handle.status().get().exited() &&
            handle.status().get().exitstatus() == exit_skipped) {
            // If the test's process terminated with our magic "exit_skipped"
            // status, there are two cases to handle.  The first is the case
            // where the "skipped cookie" exists, in which case we never got to
            // actually invoke the test program; if that's the case, handle it
            // here.  The second case is where the test case actually decided to
            // exit with the "exit_skipped" status; in that case, just fall back
            // to the regular status handling.
            const fs::path skipped_cookie_path = handle.control_directory() /
                skipped_cookie;
            std::ifstream input(skipped_cookie_path.c_str());
            if (input) {
                result = model::test_result(model::test_result_skipped,
                                            utils::read_stream(input));
                input.close();

                // If we determined that the test needs to be skipped, we do not
                // want to run the cleanup routine because doing so could result
                // in errors.  However, we still want to run the cleanup routine
                // if the test's body reports a skip (because actions could have
                // already been taken).
                test_data->needs_cleanup = false;
            }
        }
        if (!result) {
            result = test_data->interface->compute_result(
                handle.status(),
                handle.control_directory(),
                handle.stdout_file(),
                handle.stderr_file());
        }
        INV(result);

        if (!result.get().good()) {
            append_files_listing(handle.work_directory(),
                                 handle.stderr_file());
        }

        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.
            _pimpl->spawn_cleanup(test_data->test_program,
                                  test_data->test_case_name,
                                  test_data->user_config, handle, result.get());
            test_data->needs_cleanup = false;

            // TODO(jmmv): Chaining this call is ugly.  We'd be better off by
            // looping over terminated processes until we got a result suitable
            // for user consumption.  For the time being this is good enough and
            // not a problem because the call chain won't get big: the majority
            // of test cases do not have cleanup routines.
            return wait_any();
        }
    } catch (const std::bad_cast& e) {
        const cleanup_exec_data* cleanup_data =
            &dynamic_cast< const cleanup_exec_data& >(*data.get());

        // Handle the completion of cleanup subprocesses internally: the caller
        // is not aware that these exist so, when we return, we must return the
        // data for the original test that triggered this routine.  For example,
        // because the caller wants to see the exact same exec_handle that was
        // returned by spawn_test.

        const model::test_result& body_result = cleanup_data->body_result;
        if (body_result.good()) {
            if (!handle.status()) {
                result = model::test_result(model::test_result_broken,
                                            "Test case cleanup timed out");
            } else {
                if (!handle.status().get().exited() ||
                    handle.status().get().exitstatus() != EXIT_SUCCESS) {
                    result = model::test_result(
                        model::test_result_broken,
                        "Test case cleanup did not terminate successfully");
                } else {
                    result = body_result;
                }
            }
        } else {
            result = body_result;
        }

        handle = cleanup_data->body_exit_handle;
    }
    INV(result);

    std::shared_ptr< result_handle::bimpl > result_handle_bimpl(
        new result_handle::bimpl(handle, _pimpl->all_exec_data));
    std::shared_ptr< test_result_handle::impl > test_result_handle_impl(
        new test_result_handle::impl(
            data->test_program, data->test_case_name, result.get()));
    return result_handle_ptr(new test_result_handle(result_handle_bimpl,
                                                    test_result_handle_impl));
}