diff options
Diffstat (limited to 'atf-c++/tests.cpp')
| -rw-r--r-- | atf-c++/tests.cpp | 710 | 
1 files changed, 710 insertions, 0 deletions
| diff --git a/atf-c++/tests.cpp b/atf-c++/tests.cpp new file mode 100644 index 000000000000..cdc0dfbad4f3 --- /dev/null +++ b/atf-c++/tests.cpp @@ -0,0 +1,710 @@ +// +// Automated Testing Framework (atf) +// +// Copyright (c) 2007 The NetBSD Foundation, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +//    notice, this list of conditions and the following disclaimer. +// 2. 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. +// +// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. +// + +extern "C" { +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> +} + +#include <algorithm> +#include <cctype> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <iostream> +#include <map> +#include <memory> +#include <sstream> +#include <stdexcept> +#include <vector> + +extern "C" { +#include "atf-c/error.h" +#include "atf-c/tc.h" +#include "atf-c/utils.h" +} + +#include "tests.hpp" + +#include "detail/application.hpp" +#include "detail/env.hpp" +#include "detail/exceptions.hpp" +#include "detail/fs.hpp" +#include "detail/parser.hpp" +#include "detail/sanity.hpp" +#include "detail/text.hpp" + +namespace impl = atf::tests; +namespace detail = atf::tests::detail; +#define IMPL_NAME "atf::tests" + +// ------------------------------------------------------------------------ +// The "atf_tp_writer" class. +// ------------------------------------------------------------------------ + +detail::atf_tp_writer::atf_tp_writer(std::ostream& os) : +    m_os(os), +    m_is_first(true) +{ +    atf::parser::headers_map hm; +    atf::parser::attrs_map ct_attrs; +    ct_attrs["version"] = "1"; +    hm["Content-Type"] = atf::parser::header_entry("Content-Type", +        "application/X-atf-tp", ct_attrs); +    atf::parser::write_headers(hm, m_os); +} + +void +detail::atf_tp_writer::start_tc(const std::string& ident) +{ +    if (!m_is_first) +        m_os << "\n"; +    m_os << "ident: " << ident << "\n"; +    m_os.flush(); +} + +void +detail::atf_tp_writer::end_tc(void) +{ +    if (m_is_first) +        m_is_first = false; +} + +void +detail::atf_tp_writer::tc_meta_data(const std::string& name, +                                    const std::string& value) +{ +    PRE(name != "ident"); +    m_os << name << ": " << value << "\n"; +    m_os.flush(); +} + +// ------------------------------------------------------------------------ +// Free helper functions. +// ------------------------------------------------------------------------ + +bool +detail::match(const std::string& regexp, const std::string& str) +{ +    return atf::text::match(str, regexp); +} + +// ------------------------------------------------------------------------ +// The "tc" class. +// ------------------------------------------------------------------------ + +static std::map< atf_tc_t*, impl::tc* > wraps; +static std::map< const atf_tc_t*, const impl::tc* > cwraps; + +struct impl::tc_impl : atf::utils::noncopyable { +    std::string m_ident; +    atf_tc_t m_tc; +    bool m_has_cleanup; + +    tc_impl(const std::string& ident, const bool has_cleanup) : +        m_ident(ident), +        m_has_cleanup(has_cleanup) +    { +    } + +    static void +    wrap_head(atf_tc_t *tc) +    { +        std::map< atf_tc_t*, impl::tc* >::iterator iter = wraps.find(tc); +        INV(iter != wraps.end()); +        (*iter).second->head(); +    } + +    static void +    wrap_body(const atf_tc_t *tc) +    { +        std::map< const atf_tc_t*, const impl::tc* >::const_iterator iter = +            cwraps.find(tc); +        INV(iter != cwraps.end()); +        try { +            (*iter).second->body(); +        } catch (const std::exception& e) { +            (*iter).second->fail("Caught unhandled exception: " + std::string( +                                     e.what())); +        } catch (...) { +            (*iter).second->fail("Caught unknown exception"); +        } +    } + +    static void +    wrap_cleanup(const atf_tc_t *tc) +    { +        std::map< const atf_tc_t*, const impl::tc* >::const_iterator iter = +            cwraps.find(tc); +        INV(iter != cwraps.end()); +        (*iter).second->cleanup(); +    } +}; + +impl::tc::tc(const std::string& ident, const bool has_cleanup) : +    pimpl(new tc_impl(ident, has_cleanup)) +{ +} + +impl::tc::~tc(void) +{ +    cwraps.erase(&pimpl->m_tc); +    wraps.erase(&pimpl->m_tc); + +    atf_tc_fini(&pimpl->m_tc); +} + +void +impl::tc::init(const vars_map& config) +{ +    atf_error_t err; + +    utils::auto_array< const char * > array( +        new const char*[(config.size() * 2) + 1]); +    const char **ptr = array.get(); +    for (vars_map::const_iterator iter = config.begin(); +         iter != config.end(); iter++) { +         *ptr = (*iter).first.c_str(); +         *(ptr + 1) = (*iter).second.c_str(); +         ptr += 2; +    } +    *ptr = NULL; + +    wraps[&pimpl->m_tc] = this; +    cwraps[&pimpl->m_tc] = this; + +    err = atf_tc_init(&pimpl->m_tc, pimpl->m_ident.c_str(), pimpl->wrap_head, +        pimpl->wrap_body, pimpl->m_has_cleanup ? pimpl->wrap_cleanup : NULL, +        array.get()); +    if (atf_is_error(err)) +        throw_atf_error(err); +} + +bool +impl::tc::has_config_var(const std::string& var) +    const +{ +    return atf_tc_has_config_var(&pimpl->m_tc, var.c_str()); +} + +bool +impl::tc::has_md_var(const std::string& var) +    const +{ +    return atf_tc_has_md_var(&pimpl->m_tc, var.c_str()); +} + +const std::string +impl::tc::get_config_var(const std::string& var) +    const +{ +    return atf_tc_get_config_var(&pimpl->m_tc, var.c_str()); +} + +const std::string +impl::tc::get_config_var(const std::string& var, const std::string& defval) +    const +{ +    return atf_tc_get_config_var_wd(&pimpl->m_tc, var.c_str(), defval.c_str()); +} + +const std::string +impl::tc::get_md_var(const std::string& var) +    const +{ +    return atf_tc_get_md_var(&pimpl->m_tc, var.c_str()); +} + +const impl::vars_map +impl::tc::get_md_vars(void) +    const +{ +    vars_map vars; + +    char **array = atf_tc_get_md_vars(&pimpl->m_tc); +    try { +        char **ptr; +        for (ptr = array; *ptr != NULL; ptr += 2) +            vars[*ptr] = *(ptr + 1); +    } catch (...) { +        atf_utils_free_charpp(array); +        throw; +    } + +    return vars; +} + +void +impl::tc::set_md_var(const std::string& var, const std::string& val) +{ +    atf_error_t err = atf_tc_set_md_var(&pimpl->m_tc, var.c_str(), val.c_str()); +    if (atf_is_error(err)) +        throw_atf_error(err); +} + +void +impl::tc::run(const std::string& resfile) +    const +{ +    atf_error_t err = atf_tc_run(&pimpl->m_tc, resfile.c_str()); +    if (atf_is_error(err)) +        throw_atf_error(err); +} + +void +impl::tc::run_cleanup(void) +    const +{ +    atf_error_t err = atf_tc_cleanup(&pimpl->m_tc); +    if (atf_is_error(err)) +        throw_atf_error(err); +} + +void +impl::tc::head(void) +{ +} + +void +impl::tc::cleanup(void) +    const +{ +} + +void +impl::tc::require_prog(const std::string& prog) +    const +{ +    atf_tc_require_prog(prog.c_str()); +} + +void +impl::tc::pass(void) +{ +    atf_tc_pass(); +} + +void +impl::tc::fail(const std::string& reason) +{ +    atf_tc_fail("%s", reason.c_str()); +} + +void +impl::tc::fail_nonfatal(const std::string& reason) +{ +    atf_tc_fail_nonfatal("%s", reason.c_str()); +} + +void +impl::tc::skip(const std::string& reason) +{ +    atf_tc_skip("%s", reason.c_str()); +} + +void +impl::tc::check_errno(const char* file, const int line, const int exp_errno, +                      const char* expr_str, const bool result) +{ +    atf_tc_check_errno(file, line, exp_errno, expr_str, result); +} + +void +impl::tc::require_errno(const char* file, const int line, const int exp_errno, +                        const char* expr_str, const bool result) +{ +    atf_tc_require_errno(file, line, exp_errno, expr_str, result); +} + +void +impl::tc::expect_pass(void) +{ +    atf_tc_expect_pass(); +} + +void +impl::tc::expect_fail(const std::string& reason) +{ +    atf_tc_expect_fail("%s", reason.c_str()); +} + +void +impl::tc::expect_exit(const int exitcode, const std::string& reason) +{ +    atf_tc_expect_exit(exitcode, "%s", reason.c_str()); +} + +void +impl::tc::expect_signal(const int signo, const std::string& reason) +{ +    atf_tc_expect_signal(signo, "%s", reason.c_str()); +} + +void +impl::tc::expect_death(const std::string& reason) +{ +    atf_tc_expect_death("%s", reason.c_str()); +} + +void +impl::tc::expect_timeout(const std::string& reason) +{ +    atf_tc_expect_timeout("%s", reason.c_str()); +} + +// ------------------------------------------------------------------------ +// The "tp" class. +// ------------------------------------------------------------------------ + +class tp : public atf::application::app { +public: +    typedef std::vector< impl::tc * > tc_vector; + +private: +    static const char* m_description; + +    bool m_lflag; +    atf::fs::path m_resfile; +    std::string m_srcdir_arg; +    atf::fs::path m_srcdir; + +    atf::tests::vars_map m_vars; + +    std::string specific_args(void) const; +    options_set specific_options(void) const; +    void process_option(int, const char*); + +    void (*m_add_tcs)(tc_vector&); +    tc_vector m_tcs; + +    void parse_vflag(const std::string&); +    void handle_srcdir(void); + +    tc_vector init_tcs(void); + +    enum tc_part { +        BODY, +        CLEANUP, +    }; + +    void list_tcs(void); +    impl::tc* find_tc(tc_vector, const std::string&); +    static std::pair< std::string, tc_part > process_tcarg(const std::string&); +    int run_tc(const std::string&); + +public: +    tp(void (*)(tc_vector&)); +    ~tp(void); + +    int main(void); +}; + +const char* tp::m_description = +    "This is an independent atf test program."; + +tp::tp(void (*add_tcs)(tc_vector&)) : +    app(m_description, "atf-test-program(1)", "atf(7)", false), +    m_lflag(false), +    m_resfile("/dev/stdout"), +    m_srcdir("."), +    m_add_tcs(add_tcs) +{ +} + +tp::~tp(void) +{ +    for (tc_vector::iterator iter = m_tcs.begin(); +         iter != m_tcs.end(); iter++) { +        impl::tc* tc = *iter; + +        delete tc; +    } +} + +std::string +tp::specific_args(void) +    const +{ +    return "test_case"; +} + +tp::options_set +tp::specific_options(void) +    const +{ +    using atf::application::option; +    options_set opts; +    opts.insert(option('l', "", "List test cases and their purpose")); +    opts.insert(option('r', "resfile", "The file to which the test program " +                                       "will write the results of the " +                                       "executed test case")); +    opts.insert(option('s', "srcdir", "Directory where the test's data " +                                      "files are located")); +    opts.insert(option('v', "var=value", "Sets the configuration variable " +                                         "`var' to `value'")); +    return opts; +} + +void +tp::process_option(int ch, const char* arg) +{ +    switch (ch) { +    case 'l': +        m_lflag = true; +        break; + +    case 'r': +        m_resfile = atf::fs::path(arg); +        break; + +    case 's': +        m_srcdir_arg = arg; +        break; + +    case 'v': +        parse_vflag(arg); +        break; + +    default: +        UNREACHABLE; +    } +} + +void +tp::parse_vflag(const std::string& str) +{ +    if (str.empty()) +        throw std::runtime_error("-v requires a non-empty argument"); + +    std::vector< std::string > ws = atf::text::split(str, "="); +    if (ws.size() == 1 && str[str.length() - 1] == '=') { +        m_vars[ws[0]] = ""; +    } else { +        if (ws.size() != 2) +            throw std::runtime_error("-v requires an argument of the form " +                                     "var=value"); + +        m_vars[ws[0]] = ws[1]; +    } +} + +void +tp::handle_srcdir(void) +{ +    if (m_srcdir_arg.empty()) { +        m_srcdir = atf::fs::path(m_argv0).branch_path(); +        if (m_srcdir.leaf_name() == ".libs") +            m_srcdir = m_srcdir.branch_path(); +    } else +        m_srcdir = atf::fs::path(m_srcdir_arg); + +    if (!atf::fs::exists(m_srcdir / m_prog_name)) +        throw std::runtime_error("Cannot find the test program in the " +                                 "source directory `" + m_srcdir.str() + "'"); + +    if (!m_srcdir.is_absolute()) +        m_srcdir = m_srcdir.to_absolute(); + +    m_vars["srcdir"] = m_srcdir.str(); +} + +tp::tc_vector +tp::init_tcs(void) +{ +    m_add_tcs(m_tcs); +    for (tc_vector::iterator iter = m_tcs.begin(); +         iter != m_tcs.end(); iter++) { +        impl::tc* tc = *iter; + +        tc->init(m_vars); +    } +    return m_tcs; +} + +// +// An auxiliary unary predicate that compares the given test case's +// identifier to the identifier stored in it. +// +class tc_equal_to_ident { +    const std::string& m_ident; + +public: +    tc_equal_to_ident(const std::string& i) : +        m_ident(i) +    { +    } + +    bool operator()(const impl::tc* tc) +    { +        return tc->get_md_var("ident") == m_ident; +    } +}; + +void +tp::list_tcs(void) +{ +    tc_vector tcs = init_tcs(); +    detail::atf_tp_writer writer(std::cout); + +    for (tc_vector::const_iterator iter = tcs.begin(); +         iter != tcs.end(); iter++) { +        const impl::vars_map vars = (*iter)->get_md_vars(); + +        { +            impl::vars_map::const_iterator iter2 = vars.find("ident"); +            INV(iter2 != vars.end()); +            writer.start_tc((*iter2).second); +        } + +        for (impl::vars_map::const_iterator iter2 = vars.begin(); +             iter2 != vars.end(); iter2++) { +            const std::string& key = (*iter2).first; +            if (key != "ident") +                writer.tc_meta_data(key, (*iter2).second); +        } + +        writer.end_tc(); +    } +} + +impl::tc* +tp::find_tc(tc_vector tcs, const std::string& name) +{ +    std::vector< std::string > ids; +    for (tc_vector::iterator iter = tcs.begin(); +         iter != tcs.end(); iter++) { +        impl::tc* tc = *iter; + +        if (tc->get_md_var("ident") == name) +            return tc; +    } +    throw atf::application::usage_error("Unknown test case `%s'", +                                        name.c_str()); +} + +std::pair< std::string, tp::tc_part > +tp::process_tcarg(const std::string& tcarg) +{ +    const std::string::size_type pos = tcarg.find(':'); +    if (pos == std::string::npos) { +        return std::make_pair(tcarg, BODY); +    } else { +        const std::string tcname = tcarg.substr(0, pos); + +        const std::string partname = tcarg.substr(pos + 1); +        if (partname == "body") +            return std::make_pair(tcname, BODY); +        else if (partname == "cleanup") +            return std::make_pair(tcname, CLEANUP); +        else { +            using atf::application::usage_error; +            throw usage_error("Invalid test case part `%s'", partname.c_str()); +        } +    } +} + +int +tp::run_tc(const std::string& tcarg) +{ +    const std::pair< std::string, tc_part > fields = process_tcarg(tcarg); + +    impl::tc* tc = find_tc(init_tcs(), fields.first); + +    if (!atf::env::has("__RUNNING_INSIDE_ATF_RUN") || atf::env::get( +        "__RUNNING_INSIDE_ATF_RUN") != "internal-yes-value") +    { +        std::cerr << m_prog_name << ": WARNING: Running test cases without " +            "atf-run(1) is unsupported\n"; +        std::cerr << m_prog_name << ": WARNING: No isolation nor timeout " +            "control is being applied; you may get unexpected failures; see " +            "atf-test-case(4)\n"; +    } + +    try { +        switch (fields.second) { +        case BODY: +            tc->run(m_resfile.str()); +            break; +        case CLEANUP: +            tc->run_cleanup(); +            break; +        default: +            UNREACHABLE; +        } +        return EXIT_SUCCESS; +    } catch (const std::runtime_error& e) { +        std::cerr << "ERROR: " << e.what() << "\n"; +        return EXIT_FAILURE; +    } +} + +int +tp::main(void) +{ +    using atf::application::usage_error; + +    int errcode; + +    handle_srcdir(); + +    if (m_lflag) { +        if (m_argc > 0) +            throw usage_error("Cannot provide test case names with -l"); + +        list_tcs(); +        errcode = EXIT_SUCCESS; +    } else { +        if (m_argc == 0) +            throw usage_error("Must provide a test case name"); +        else if (m_argc > 1) +            throw usage_error("Cannot provide more than one test case name"); +        INV(m_argc == 1); + +        errcode = run_tc(m_argv[0]); +    } + +    return errcode; +} + +namespace atf { +    namespace tests { +        int run_tp(int, char* const*, void (*)(tp::tc_vector&)); +    } +} + +int +impl::run_tp(int argc, char* const* argv, void (*add_tcs)(tp::tc_vector&)) +{ +    return tp(add_tcs).run(argc, argv); +} | 
