10a6a1f1dSLionel Sambuc //
20a6a1f1dSLionel Sambuc // Automated Testing Framework (atf)
30a6a1f1dSLionel Sambuc //
40a6a1f1dSLionel Sambuc // Copyright (c) 2007 The NetBSD Foundation, Inc.
50a6a1f1dSLionel Sambuc // All rights reserved.
60a6a1f1dSLionel Sambuc //
70a6a1f1dSLionel Sambuc // Redistribution and use in source and binary forms, with or without
80a6a1f1dSLionel Sambuc // modification, are permitted provided that the following conditions
90a6a1f1dSLionel Sambuc // are met:
100a6a1f1dSLionel Sambuc // 1. Redistributions of source code must retain the above copyright
110a6a1f1dSLionel Sambuc // notice, this list of conditions and the following disclaimer.
120a6a1f1dSLionel Sambuc // 2. Redistributions in binary form must reproduce the above copyright
130a6a1f1dSLionel Sambuc // notice, this list of conditions and the following disclaimer in the
140a6a1f1dSLionel Sambuc // documentation and/or other materials provided with the distribution.
150a6a1f1dSLionel Sambuc //
160a6a1f1dSLionel Sambuc // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
170a6a1f1dSLionel Sambuc // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
180a6a1f1dSLionel Sambuc // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
190a6a1f1dSLionel Sambuc // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
200a6a1f1dSLionel Sambuc // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
210a6a1f1dSLionel Sambuc // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
220a6a1f1dSLionel Sambuc // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
230a6a1f1dSLionel Sambuc // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
240a6a1f1dSLionel Sambuc // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
250a6a1f1dSLionel Sambuc // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
260a6a1f1dSLionel Sambuc // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
270a6a1f1dSLionel Sambuc // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
280a6a1f1dSLionel Sambuc //
290a6a1f1dSLionel Sambuc
300a6a1f1dSLionel Sambuc extern "C" {
310a6a1f1dSLionel Sambuc #include <sys/types.h>
320a6a1f1dSLionel Sambuc #include <sys/stat.h>
330a6a1f1dSLionel Sambuc #include <sys/time.h>
340a6a1f1dSLionel Sambuc
350a6a1f1dSLionel Sambuc #include <fcntl.h>
360a6a1f1dSLionel Sambuc #include <signal.h>
370a6a1f1dSLionel Sambuc #include <unistd.h>
380a6a1f1dSLionel Sambuc }
390a6a1f1dSLionel Sambuc
400a6a1f1dSLionel Sambuc #include <cassert>
410a6a1f1dSLionel Sambuc #include <cerrno>
420a6a1f1dSLionel Sambuc #include <cstdlib>
430a6a1f1dSLionel Sambuc #include <cstring>
440a6a1f1dSLionel Sambuc #include <fstream>
450a6a1f1dSLionel Sambuc #include <iostream>
460a6a1f1dSLionel Sambuc
470a6a1f1dSLionel Sambuc #include "config_file.hpp"
480a6a1f1dSLionel Sambuc #include "env.hpp"
490a6a1f1dSLionel Sambuc #include "fs.hpp"
500a6a1f1dSLionel Sambuc #include "io.hpp"
510a6a1f1dSLionel Sambuc #include "parser.hpp"
520a6a1f1dSLionel Sambuc #include "process.hpp"
530a6a1f1dSLionel Sambuc #include "requirements.hpp"
540a6a1f1dSLionel Sambuc #include "signals.hpp"
550a6a1f1dSLionel Sambuc #include "test-program.hpp"
560a6a1f1dSLionel Sambuc #include "text.hpp"
570a6a1f1dSLionel Sambuc #include "timers.hpp"
580a6a1f1dSLionel Sambuc #include "user.hpp"
590a6a1f1dSLionel Sambuc
600a6a1f1dSLionel Sambuc namespace impl = tools::test_program;
610a6a1f1dSLionel Sambuc namespace detail = tools::test_program::detail;
620a6a1f1dSLionel Sambuc
630a6a1f1dSLionel Sambuc namespace {
640a6a1f1dSLionel Sambuc
650a6a1f1dSLionel Sambuc typedef std::map< std::string, std::string > vars_map;
660a6a1f1dSLionel Sambuc
670a6a1f1dSLionel Sambuc static void
check_stream(std::ostream & os)680a6a1f1dSLionel Sambuc check_stream(std::ostream& os)
690a6a1f1dSLionel Sambuc {
700a6a1f1dSLionel Sambuc // If we receive a signal while writing to the stream, the bad bit gets set.
710a6a1f1dSLionel Sambuc // Things seem to behave fine afterwards if we clear such error condition.
720a6a1f1dSLionel Sambuc // However, I'm not sure if it's safe to query errno at this point.
730a6a1f1dSLionel Sambuc if (os.bad()) {
740a6a1f1dSLionel Sambuc if (errno == EINTR)
750a6a1f1dSLionel Sambuc os.clear();
760a6a1f1dSLionel Sambuc else
770a6a1f1dSLionel Sambuc throw std::runtime_error("Failed");
780a6a1f1dSLionel Sambuc }
790a6a1f1dSLionel Sambuc }
800a6a1f1dSLionel Sambuc
810a6a1f1dSLionel Sambuc namespace atf_tp {
820a6a1f1dSLionel Sambuc
830a6a1f1dSLionel Sambuc static const tools::parser::token_type eof_type = 0;
840a6a1f1dSLionel Sambuc static const tools::parser::token_type nl_type = 1;
850a6a1f1dSLionel Sambuc static const tools::parser::token_type text_type = 2;
860a6a1f1dSLionel Sambuc static const tools::parser::token_type colon_type = 3;
870a6a1f1dSLionel Sambuc static const tools::parser::token_type dblquote_type = 4;
880a6a1f1dSLionel Sambuc
890a6a1f1dSLionel Sambuc class tokenizer : public tools::parser::tokenizer< std::istream > {
900a6a1f1dSLionel Sambuc public:
tokenizer(std::istream & is,size_t curline)910a6a1f1dSLionel Sambuc tokenizer(std::istream& is, size_t curline) :
920a6a1f1dSLionel Sambuc tools::parser::tokenizer< std::istream >
930a6a1f1dSLionel Sambuc (is, true, eof_type, nl_type, text_type, curline)
940a6a1f1dSLionel Sambuc {
950a6a1f1dSLionel Sambuc add_delim(':', colon_type);
960a6a1f1dSLionel Sambuc add_quote('"', dblquote_type);
970a6a1f1dSLionel Sambuc }
980a6a1f1dSLionel Sambuc };
990a6a1f1dSLionel Sambuc
1000a6a1f1dSLionel Sambuc } // namespace atf_tp
1010a6a1f1dSLionel Sambuc
1020a6a1f1dSLionel Sambuc class metadata_reader : public detail::atf_tp_reader {
1030a6a1f1dSLionel Sambuc impl::test_cases_map m_tcs;
1040a6a1f1dSLionel Sambuc
got_tc(const std::string & ident,const vars_map & props)1050a6a1f1dSLionel Sambuc void got_tc(const std::string& ident, const vars_map& props)
1060a6a1f1dSLionel Sambuc {
1070a6a1f1dSLionel Sambuc if (m_tcs.find(ident) != m_tcs.end())
1080a6a1f1dSLionel Sambuc throw(std::runtime_error("Duplicate test case " + ident +
1090a6a1f1dSLionel Sambuc " in test program"));
1100a6a1f1dSLionel Sambuc m_tcs[ident] = props;
1110a6a1f1dSLionel Sambuc
1120a6a1f1dSLionel Sambuc if (m_tcs[ident].find("has.cleanup") == m_tcs[ident].end())
1130a6a1f1dSLionel Sambuc m_tcs[ident].insert(std::make_pair("has.cleanup", "false"));
1140a6a1f1dSLionel Sambuc
1150a6a1f1dSLionel Sambuc if (m_tcs[ident].find("timeout") == m_tcs[ident].end())
1160a6a1f1dSLionel Sambuc m_tcs[ident].insert(std::make_pair("timeout", "300"));
1170a6a1f1dSLionel Sambuc }
1180a6a1f1dSLionel Sambuc
1190a6a1f1dSLionel Sambuc public:
metadata_reader(std::istream & is)1200a6a1f1dSLionel Sambuc metadata_reader(std::istream& is) :
1210a6a1f1dSLionel Sambuc detail::atf_tp_reader(is)
1220a6a1f1dSLionel Sambuc {
1230a6a1f1dSLionel Sambuc }
1240a6a1f1dSLionel Sambuc
1250a6a1f1dSLionel Sambuc const impl::test_cases_map&
get_tcs(void) const1260a6a1f1dSLionel Sambuc get_tcs(void)
1270a6a1f1dSLionel Sambuc const
1280a6a1f1dSLionel Sambuc {
1290a6a1f1dSLionel Sambuc return m_tcs;
1300a6a1f1dSLionel Sambuc }
1310a6a1f1dSLionel Sambuc };
1320a6a1f1dSLionel Sambuc
1330a6a1f1dSLionel Sambuc struct get_metadata_params {
1340a6a1f1dSLionel Sambuc const tools::fs::path& executable;
1350a6a1f1dSLionel Sambuc const vars_map& config;
1360a6a1f1dSLionel Sambuc
get_metadata_params__anon3f8612980111::get_metadata_params1370a6a1f1dSLionel Sambuc get_metadata_params(const tools::fs::path& p_executable,
1380a6a1f1dSLionel Sambuc const vars_map& p_config) :
1390a6a1f1dSLionel Sambuc executable(p_executable),
1400a6a1f1dSLionel Sambuc config(p_config)
1410a6a1f1dSLionel Sambuc {
1420a6a1f1dSLionel Sambuc }
1430a6a1f1dSLionel Sambuc };
1440a6a1f1dSLionel Sambuc
1450a6a1f1dSLionel Sambuc struct test_case_params {
1460a6a1f1dSLionel Sambuc const tools::fs::path& executable;
1470a6a1f1dSLionel Sambuc const std::string& test_case_name;
1480a6a1f1dSLionel Sambuc const std::string& test_case_part;
1490a6a1f1dSLionel Sambuc const vars_map& metadata;
1500a6a1f1dSLionel Sambuc const vars_map& config;
1510a6a1f1dSLionel Sambuc const tools::fs::path& resfile;
1520a6a1f1dSLionel Sambuc const tools::fs::path& workdir;
1530a6a1f1dSLionel Sambuc
test_case_params__anon3f8612980111::test_case_params1540a6a1f1dSLionel Sambuc test_case_params(const tools::fs::path& p_executable,
1550a6a1f1dSLionel Sambuc const std::string& p_test_case_name,
1560a6a1f1dSLionel Sambuc const std::string& p_test_case_part,
1570a6a1f1dSLionel Sambuc const vars_map& p_metadata,
1580a6a1f1dSLionel Sambuc const vars_map& p_config,
1590a6a1f1dSLionel Sambuc const tools::fs::path& p_resfile,
1600a6a1f1dSLionel Sambuc const tools::fs::path& p_workdir) :
1610a6a1f1dSLionel Sambuc executable(p_executable),
1620a6a1f1dSLionel Sambuc test_case_name(p_test_case_name),
1630a6a1f1dSLionel Sambuc test_case_part(p_test_case_part),
1640a6a1f1dSLionel Sambuc metadata(p_metadata),
1650a6a1f1dSLionel Sambuc config(p_config),
1660a6a1f1dSLionel Sambuc resfile(p_resfile),
1670a6a1f1dSLionel Sambuc workdir(p_workdir)
1680a6a1f1dSLionel Sambuc {
1690a6a1f1dSLionel Sambuc }
1700a6a1f1dSLionel Sambuc };
1710a6a1f1dSLionel Sambuc
1720a6a1f1dSLionel Sambuc static
1730a6a1f1dSLionel Sambuc std::string
generate_timestamp(void)1740a6a1f1dSLionel Sambuc generate_timestamp(void)
1750a6a1f1dSLionel Sambuc {
1760a6a1f1dSLionel Sambuc struct timeval tv;
1770a6a1f1dSLionel Sambuc if (gettimeofday(&tv, NULL) == -1)
1780a6a1f1dSLionel Sambuc return "0.0";
1790a6a1f1dSLionel Sambuc
1800a6a1f1dSLionel Sambuc char buf[32];
1810a6a1f1dSLionel Sambuc const int len = snprintf(buf, sizeof(buf), "%ld.%ld",
1820a6a1f1dSLionel Sambuc static_cast< long >(tv.tv_sec),
1830a6a1f1dSLionel Sambuc static_cast< long >(tv.tv_usec));
1840a6a1f1dSLionel Sambuc if (len >= static_cast< int >(sizeof(buf)) || len < 0)
1850a6a1f1dSLionel Sambuc return "0.0";
1860a6a1f1dSLionel Sambuc else
1870a6a1f1dSLionel Sambuc return buf;
1880a6a1f1dSLionel Sambuc }
1890a6a1f1dSLionel Sambuc
1900a6a1f1dSLionel Sambuc static
1910a6a1f1dSLionel Sambuc void
append_to_vector(std::vector<std::string> & v1,const std::vector<std::string> & v2)1920a6a1f1dSLionel Sambuc append_to_vector(std::vector< std::string >& v1,
1930a6a1f1dSLionel Sambuc const std::vector< std::string >& v2)
1940a6a1f1dSLionel Sambuc {
1950a6a1f1dSLionel Sambuc std::copy(v2.begin(), v2.end(),
1960a6a1f1dSLionel Sambuc std::back_insert_iterator< std::vector< std::string > >(v1));
1970a6a1f1dSLionel Sambuc }
1980a6a1f1dSLionel Sambuc
1990a6a1f1dSLionel Sambuc static
2000a6a1f1dSLionel Sambuc char**
vector_to_argv(const std::vector<std::string> & v)2010a6a1f1dSLionel Sambuc vector_to_argv(const std::vector< std::string >& v)
2020a6a1f1dSLionel Sambuc {
2030a6a1f1dSLionel Sambuc char** argv = new char*[v.size() + 1];
2040a6a1f1dSLionel Sambuc for (std::vector< std::string >::size_type i = 0; i < v.size(); i++) {
2050a6a1f1dSLionel Sambuc argv[i] = strdup(v[i].c_str());
2060a6a1f1dSLionel Sambuc }
2070a6a1f1dSLionel Sambuc argv[v.size()] = NULL;
2080a6a1f1dSLionel Sambuc return argv;
2090a6a1f1dSLionel Sambuc }
2100a6a1f1dSLionel Sambuc
2110a6a1f1dSLionel Sambuc static
2120a6a1f1dSLionel Sambuc void
exec_or_exit(const tools::fs::path & executable,const std::vector<std::string> & argv)2130a6a1f1dSLionel Sambuc exec_or_exit(const tools::fs::path& executable,
2140a6a1f1dSLionel Sambuc const std::vector< std::string >& argv)
2150a6a1f1dSLionel Sambuc {
2160a6a1f1dSLionel Sambuc // This leaks memory in case of a failure, but it is OK. Exiting will
2170a6a1f1dSLionel Sambuc // do the necessary cleanup.
2180a6a1f1dSLionel Sambuc char* const* native_argv = vector_to_argv(argv);
2190a6a1f1dSLionel Sambuc
2200a6a1f1dSLionel Sambuc ::execv(executable.c_str(), native_argv);
2210a6a1f1dSLionel Sambuc
2220a6a1f1dSLionel Sambuc const std::string message = "Failed to execute '" + executable.str() +
2230a6a1f1dSLionel Sambuc "': " + std::strerror(errno) + "\n";
2240a6a1f1dSLionel Sambuc if (::write(STDERR_FILENO, message.c_str(), message.length()) == -1)
2250a6a1f1dSLionel Sambuc std::abort();
2260a6a1f1dSLionel Sambuc std::exit(EXIT_FAILURE);
2270a6a1f1dSLionel Sambuc }
2280a6a1f1dSLionel Sambuc
2290a6a1f1dSLionel Sambuc static
2300a6a1f1dSLionel Sambuc std::vector< std::string >
config_to_args(const vars_map & config)2310a6a1f1dSLionel Sambuc config_to_args(const vars_map& config)
2320a6a1f1dSLionel Sambuc {
2330a6a1f1dSLionel Sambuc std::vector< std::string > args;
2340a6a1f1dSLionel Sambuc
2350a6a1f1dSLionel Sambuc for (vars_map::const_iterator iter = config.begin();
2360a6a1f1dSLionel Sambuc iter != config.end(); iter++)
2370a6a1f1dSLionel Sambuc args.push_back("-v" + (*iter).first + "=" + (*iter).second);
2380a6a1f1dSLionel Sambuc
2390a6a1f1dSLionel Sambuc return args;
2400a6a1f1dSLionel Sambuc }
2410a6a1f1dSLionel Sambuc
2420a6a1f1dSLionel Sambuc static
2430a6a1f1dSLionel Sambuc void
silence_stdin(void)2440a6a1f1dSLionel Sambuc silence_stdin(void)
2450a6a1f1dSLionel Sambuc {
2460a6a1f1dSLionel Sambuc ::close(STDIN_FILENO);
2470a6a1f1dSLionel Sambuc int fd = ::open("/dev/null", O_RDONLY);
2480a6a1f1dSLionel Sambuc if (fd == -1)
2490a6a1f1dSLionel Sambuc throw std::runtime_error("Could not open /dev/null");
2500a6a1f1dSLionel Sambuc assert(fd == STDIN_FILENO);
2510a6a1f1dSLionel Sambuc }
2520a6a1f1dSLionel Sambuc
2530a6a1f1dSLionel Sambuc static
2540a6a1f1dSLionel Sambuc void
prepare_child(const tools::fs::path & workdir)2550a6a1f1dSLionel Sambuc prepare_child(const tools::fs::path& workdir)
2560a6a1f1dSLionel Sambuc {
257*e1cdaee1SLionel Sambuc #if !defined(NDEBUG) && defined(__minix)
258*e1cdaee1SLionel Sambuc const int ret =
259*e1cdaee1SLionel Sambuc #endif /* !defined(NDEBUG) && defined(__minix) */
260*e1cdaee1SLionel Sambuc ::setpgid(::getpid(), 0);
2610a6a1f1dSLionel Sambuc assert(ret != -1);
2620a6a1f1dSLionel Sambuc
2630a6a1f1dSLionel Sambuc ::umask(S_IWGRP | S_IWOTH);
2640a6a1f1dSLionel Sambuc
2650a6a1f1dSLionel Sambuc for (int i = 1; i <= tools::signals::last_signo; i++)
2660a6a1f1dSLionel Sambuc tools::signals::reset(i);
2670a6a1f1dSLionel Sambuc
2680a6a1f1dSLionel Sambuc tools::env::set("HOME", workdir.str());
2690a6a1f1dSLionel Sambuc tools::env::unset("LANG");
2700a6a1f1dSLionel Sambuc tools::env::unset("LC_ALL");
2710a6a1f1dSLionel Sambuc tools::env::unset("LC_COLLATE");
2720a6a1f1dSLionel Sambuc tools::env::unset("LC_CTYPE");
2730a6a1f1dSLionel Sambuc tools::env::unset("LC_MESSAGES");
2740a6a1f1dSLionel Sambuc tools::env::unset("LC_MONETARY");
2750a6a1f1dSLionel Sambuc tools::env::unset("LC_NUMERIC");
2760a6a1f1dSLionel Sambuc tools::env::unset("LC_TIME");
2770a6a1f1dSLionel Sambuc tools::env::set("TZ", "UTC");
2780a6a1f1dSLionel Sambuc
2790a6a1f1dSLionel Sambuc tools::env::set("__RUNNING_INSIDE_ATF_RUN", "internal-yes-value");
2800a6a1f1dSLionel Sambuc
2810a6a1f1dSLionel Sambuc tools::fs::change_directory(workdir);
2820a6a1f1dSLionel Sambuc
2830a6a1f1dSLionel Sambuc silence_stdin();
2840a6a1f1dSLionel Sambuc }
2850a6a1f1dSLionel Sambuc
2860a6a1f1dSLionel Sambuc static
2870a6a1f1dSLionel Sambuc void
get_metadata_child(void * raw_params)2880a6a1f1dSLionel Sambuc get_metadata_child(void* raw_params)
2890a6a1f1dSLionel Sambuc {
2900a6a1f1dSLionel Sambuc const get_metadata_params* params =
2910a6a1f1dSLionel Sambuc static_cast< const get_metadata_params* >(raw_params);
2920a6a1f1dSLionel Sambuc
2930a6a1f1dSLionel Sambuc std::vector< std::string > argv;
2940a6a1f1dSLionel Sambuc argv.push_back(params->executable.leaf_name());
2950a6a1f1dSLionel Sambuc argv.push_back("-l");
2960a6a1f1dSLionel Sambuc argv.push_back("-s" + params->executable.branch_path().str());
2970a6a1f1dSLionel Sambuc append_to_vector(argv, config_to_args(params->config));
2980a6a1f1dSLionel Sambuc
2990a6a1f1dSLionel Sambuc exec_or_exit(params->executable, argv);
3000a6a1f1dSLionel Sambuc }
3010a6a1f1dSLionel Sambuc
3020a6a1f1dSLionel Sambuc void
run_test_case_child(void * raw_params)3030a6a1f1dSLionel Sambuc run_test_case_child(void* raw_params)
3040a6a1f1dSLionel Sambuc {
3050a6a1f1dSLionel Sambuc const test_case_params* params =
3060a6a1f1dSLionel Sambuc static_cast< const test_case_params* >(raw_params);
3070a6a1f1dSLionel Sambuc
3080a6a1f1dSLionel Sambuc const std::pair< int, int > user = tools::get_required_user(
3090a6a1f1dSLionel Sambuc params->metadata, params->config);
3100a6a1f1dSLionel Sambuc if (user.first != -1 && user.second != -1)
3110a6a1f1dSLionel Sambuc tools::user::drop_privileges(user);
3120a6a1f1dSLionel Sambuc
3130a6a1f1dSLionel Sambuc // The input 'tp' parameter may be relative and become invalid once
3140a6a1f1dSLionel Sambuc // we change the current working directory.
3150a6a1f1dSLionel Sambuc const tools::fs::path absolute_executable = params->executable.to_absolute();
3160a6a1f1dSLionel Sambuc
3170a6a1f1dSLionel Sambuc // Prepare the test program's arguments. We use dynamic memory and
3180a6a1f1dSLionel Sambuc // do not care to release it. We are going to die anyway very soon,
3190a6a1f1dSLionel Sambuc // either due to exec(2) or to exit(3).
3200a6a1f1dSLionel Sambuc std::vector< std::string > argv;
3210a6a1f1dSLionel Sambuc argv.push_back(absolute_executable.leaf_name());
3220a6a1f1dSLionel Sambuc argv.push_back("-r" + params->resfile.str());
3230a6a1f1dSLionel Sambuc argv.push_back("-s" + absolute_executable.branch_path().str());
3240a6a1f1dSLionel Sambuc append_to_vector(argv, config_to_args(params->config));
3250a6a1f1dSLionel Sambuc argv.push_back(params->test_case_name + ":" + params->test_case_part);
3260a6a1f1dSLionel Sambuc
3270a6a1f1dSLionel Sambuc prepare_child(params->workdir);
3280a6a1f1dSLionel Sambuc exec_or_exit(absolute_executable, argv);
3290a6a1f1dSLionel Sambuc }
3300a6a1f1dSLionel Sambuc
3310a6a1f1dSLionel Sambuc static void
tokenize_result(const std::string & line,std::string & out_state,std::string & out_arg,std::string & out_reason)3320a6a1f1dSLionel Sambuc tokenize_result(const std::string& line, std::string& out_state,
3330a6a1f1dSLionel Sambuc std::string& out_arg, std::string& out_reason)
3340a6a1f1dSLionel Sambuc {
3350a6a1f1dSLionel Sambuc const std::string::size_type pos = line.find_first_of(":(");
3360a6a1f1dSLionel Sambuc if (pos == std::string::npos) {
3370a6a1f1dSLionel Sambuc out_state = line;
3380a6a1f1dSLionel Sambuc out_arg = "";
3390a6a1f1dSLionel Sambuc out_reason = "";
3400a6a1f1dSLionel Sambuc } else if (line[pos] == ':') {
3410a6a1f1dSLionel Sambuc out_state = line.substr(0, pos);
3420a6a1f1dSLionel Sambuc out_arg = "";
3430a6a1f1dSLionel Sambuc out_reason = tools::text::trim(line.substr(pos + 1));
3440a6a1f1dSLionel Sambuc } else if (line[pos] == '(') {
3450a6a1f1dSLionel Sambuc const std::string::size_type pos2 = line.find("):", pos);
3460a6a1f1dSLionel Sambuc if (pos2 == std::string::npos)
3470a6a1f1dSLionel Sambuc throw std::runtime_error("Invalid test case result '" + line +
3480a6a1f1dSLionel Sambuc "': unclosed optional argument");
3490a6a1f1dSLionel Sambuc out_state = line.substr(0, pos);
3500a6a1f1dSLionel Sambuc out_arg = line.substr(pos + 1, pos2 - pos - 1);
3510a6a1f1dSLionel Sambuc out_reason = tools::text::trim(line.substr(pos2 + 2));
3520a6a1f1dSLionel Sambuc } else
3530a6a1f1dSLionel Sambuc std::abort();
3540a6a1f1dSLionel Sambuc }
3550a6a1f1dSLionel Sambuc
3560a6a1f1dSLionel Sambuc static impl::test_case_result
handle_result(const std::string & state,const std::string & arg,const std::string & reason)3570a6a1f1dSLionel Sambuc handle_result(const std::string& state, const std::string& arg,
3580a6a1f1dSLionel Sambuc const std::string& reason)
3590a6a1f1dSLionel Sambuc {
3600a6a1f1dSLionel Sambuc assert(state == "passed");
3610a6a1f1dSLionel Sambuc
3620a6a1f1dSLionel Sambuc if (!arg.empty() || !reason.empty())
3630a6a1f1dSLionel Sambuc throw std::runtime_error("The test case result '" + state + "' cannot "
3640a6a1f1dSLionel Sambuc "be accompanied by a reason nor an expected value");
3650a6a1f1dSLionel Sambuc
3660a6a1f1dSLionel Sambuc return impl::test_case_result(state, -1, reason);
3670a6a1f1dSLionel Sambuc }
3680a6a1f1dSLionel Sambuc
3690a6a1f1dSLionel Sambuc static impl::test_case_result
handle_result_with_reason(const std::string & state,const std::string & arg,const std::string & reason)3700a6a1f1dSLionel Sambuc handle_result_with_reason(const std::string& state, const std::string& arg,
3710a6a1f1dSLionel Sambuc const std::string& reason)
3720a6a1f1dSLionel Sambuc {
3730a6a1f1dSLionel Sambuc assert(state == "expected_death" || state == "expected_failure" ||
3740a6a1f1dSLionel Sambuc state == "expected_timeout" || state == "failed" || state == "skipped");
3750a6a1f1dSLionel Sambuc
3760a6a1f1dSLionel Sambuc if (!arg.empty() || reason.empty())
3770a6a1f1dSLionel Sambuc throw std::runtime_error("The test case result '" + state + "' must "
3780a6a1f1dSLionel Sambuc "be accompanied by a reason but not by an expected value");
3790a6a1f1dSLionel Sambuc
3800a6a1f1dSLionel Sambuc return impl::test_case_result(state, -1, reason);
3810a6a1f1dSLionel Sambuc }
3820a6a1f1dSLionel Sambuc
3830a6a1f1dSLionel Sambuc static impl::test_case_result
handle_result_with_reason_and_arg(const std::string & state,const std::string & arg,const std::string & reason)3840a6a1f1dSLionel Sambuc handle_result_with_reason_and_arg(const std::string& state,
3850a6a1f1dSLionel Sambuc const std::string& arg,
3860a6a1f1dSLionel Sambuc const std::string& reason)
3870a6a1f1dSLionel Sambuc {
3880a6a1f1dSLionel Sambuc assert(state == "expected_exit" || state == "expected_signal");
3890a6a1f1dSLionel Sambuc
3900a6a1f1dSLionel Sambuc if (reason.empty())
3910a6a1f1dSLionel Sambuc throw std::runtime_error("The test case result '" + state + "' must "
3920a6a1f1dSLionel Sambuc "be accompanied by a reason");
3930a6a1f1dSLionel Sambuc
3940a6a1f1dSLionel Sambuc int value;
3950a6a1f1dSLionel Sambuc if (arg.empty()) {
3960a6a1f1dSLionel Sambuc value = -1;
3970a6a1f1dSLionel Sambuc } else {
3980a6a1f1dSLionel Sambuc try {
3990a6a1f1dSLionel Sambuc value = tools::text::to_type< int >(arg);
4000a6a1f1dSLionel Sambuc } catch (const std::runtime_error&) {
4010a6a1f1dSLionel Sambuc throw std::runtime_error("The value '" + arg + "' passed to the '" +
4020a6a1f1dSLionel Sambuc state + "' state must be an integer");
4030a6a1f1dSLionel Sambuc }
4040a6a1f1dSLionel Sambuc }
4050a6a1f1dSLionel Sambuc
4060a6a1f1dSLionel Sambuc return impl::test_case_result(state, value, reason);
4070a6a1f1dSLionel Sambuc }
4080a6a1f1dSLionel Sambuc
4090a6a1f1dSLionel Sambuc } // anonymous namespace
4100a6a1f1dSLionel Sambuc
atf_tp_reader(std::istream & is)4110a6a1f1dSLionel Sambuc detail::atf_tp_reader::atf_tp_reader(std::istream& is) :
4120a6a1f1dSLionel Sambuc m_is(is)
4130a6a1f1dSLionel Sambuc {
4140a6a1f1dSLionel Sambuc }
4150a6a1f1dSLionel Sambuc
~atf_tp_reader(void)4160a6a1f1dSLionel Sambuc detail::atf_tp_reader::~atf_tp_reader(void)
4170a6a1f1dSLionel Sambuc {
4180a6a1f1dSLionel Sambuc }
4190a6a1f1dSLionel Sambuc
4200a6a1f1dSLionel Sambuc void
got_tc(const std::string & ident,const std::map<std::string,std::string> & md)4210a6a1f1dSLionel Sambuc detail::atf_tp_reader::got_tc(
4220a6a1f1dSLionel Sambuc const std::string& ident __attribute__((__unused__)),
4230a6a1f1dSLionel Sambuc const std::map< std::string, std::string >& md __attribute__((__unused__)))
4240a6a1f1dSLionel Sambuc {
4250a6a1f1dSLionel Sambuc }
4260a6a1f1dSLionel Sambuc
4270a6a1f1dSLionel Sambuc void
got_eof(void)4280a6a1f1dSLionel Sambuc detail::atf_tp_reader::got_eof(void)
4290a6a1f1dSLionel Sambuc {
4300a6a1f1dSLionel Sambuc }
4310a6a1f1dSLionel Sambuc
4320a6a1f1dSLionel Sambuc void
validate_and_insert(const std::string & name,const std::string & value,const size_t lineno,std::map<std::string,std::string> & md)4330a6a1f1dSLionel Sambuc detail::atf_tp_reader::validate_and_insert(const std::string& name,
4340a6a1f1dSLionel Sambuc const std::string& value, const size_t lineno,
4350a6a1f1dSLionel Sambuc std::map< std::string, std::string >& md)
4360a6a1f1dSLionel Sambuc {
4370a6a1f1dSLionel Sambuc using tools::parser::parse_error;
4380a6a1f1dSLionel Sambuc
4390a6a1f1dSLionel Sambuc if (value.empty())
4400a6a1f1dSLionel Sambuc throw parse_error(lineno, "The value for '" + name +"' cannot be "
4410a6a1f1dSLionel Sambuc "empty");
4420a6a1f1dSLionel Sambuc
4430a6a1f1dSLionel Sambuc const std::string ident_regex = "^[_A-Za-z0-9]+$";
4440a6a1f1dSLionel Sambuc const std::string integer_regex = "^[0-9]+$";
4450a6a1f1dSLionel Sambuc
4460a6a1f1dSLionel Sambuc if (name == "descr") {
4470a6a1f1dSLionel Sambuc // Any non-empty value is valid.
4480a6a1f1dSLionel Sambuc } else if (name == "has.cleanup") {
4490a6a1f1dSLionel Sambuc try {
4500a6a1f1dSLionel Sambuc (void)tools::text::to_bool(value);
4510a6a1f1dSLionel Sambuc } catch (const std::runtime_error&) {
4520a6a1f1dSLionel Sambuc throw parse_error(lineno, "The has.cleanup property requires a"
4530a6a1f1dSLionel Sambuc " boolean value");
4540a6a1f1dSLionel Sambuc }
4550a6a1f1dSLionel Sambuc } else if (name == "ident") {
4560a6a1f1dSLionel Sambuc if (!tools::text::match(value, ident_regex))
4570a6a1f1dSLionel Sambuc throw parse_error(lineno, "The identifier must match " +
4580a6a1f1dSLionel Sambuc ident_regex + "; was '" + value + "'");
4590a6a1f1dSLionel Sambuc } else if (name == "require.arch") {
4600a6a1f1dSLionel Sambuc } else if (name == "require.config") {
4610a6a1f1dSLionel Sambuc } else if (name == "require.files") {
4620a6a1f1dSLionel Sambuc } else if (name == "require.machine") {
4630a6a1f1dSLionel Sambuc } else if (name == "require.memory") {
4640a6a1f1dSLionel Sambuc try {
4650a6a1f1dSLionel Sambuc (void)tools::text::to_bytes(value);
4660a6a1f1dSLionel Sambuc } catch (const std::runtime_error&) {
4670a6a1f1dSLionel Sambuc throw parse_error(lineno, "The require.memory property requires an "
4680a6a1f1dSLionel Sambuc "integer value representing an amount of bytes");
4690a6a1f1dSLionel Sambuc }
4700a6a1f1dSLionel Sambuc } else if (name == "require.progs") {
4710a6a1f1dSLionel Sambuc } else if (name == "require.user") {
4720a6a1f1dSLionel Sambuc } else if (name == "timeout") {
4730a6a1f1dSLionel Sambuc if (!tools::text::match(value, integer_regex))
4740a6a1f1dSLionel Sambuc throw parse_error(lineno, "The timeout property requires an integer"
4750a6a1f1dSLionel Sambuc " value");
4760a6a1f1dSLionel Sambuc } else if (name == "use.fs") {
4770a6a1f1dSLionel Sambuc // Deprecated; ignore it.
4780a6a1f1dSLionel Sambuc } else if (name.size() > 2 && name[0] == 'X' && name[1] == '-') {
4790a6a1f1dSLionel Sambuc // Any non-empty value is valid.
4800a6a1f1dSLionel Sambuc } else {
4810a6a1f1dSLionel Sambuc throw parse_error(lineno, "Unknown property '" + name + "'");
4820a6a1f1dSLionel Sambuc }
4830a6a1f1dSLionel Sambuc
4840a6a1f1dSLionel Sambuc md.insert(std::make_pair(name, value));
4850a6a1f1dSLionel Sambuc }
4860a6a1f1dSLionel Sambuc
4870a6a1f1dSLionel Sambuc void
read(void)4880a6a1f1dSLionel Sambuc detail::atf_tp_reader::read(void)
4890a6a1f1dSLionel Sambuc {
4900a6a1f1dSLionel Sambuc using tools::parser::parse_error;
4910a6a1f1dSLionel Sambuc using namespace atf_tp;
4920a6a1f1dSLionel Sambuc
4930a6a1f1dSLionel Sambuc std::pair< size_t, tools::parser::headers_map > hml =
4940a6a1f1dSLionel Sambuc tools::parser::read_headers(m_is, 1);
4950a6a1f1dSLionel Sambuc tools::parser::validate_content_type(hml.second,
4960a6a1f1dSLionel Sambuc "application/X-atf-tp", 1);
4970a6a1f1dSLionel Sambuc
4980a6a1f1dSLionel Sambuc tokenizer tkz(m_is, hml.first);
4990a6a1f1dSLionel Sambuc tools::parser::parser< tokenizer > p(tkz);
5000a6a1f1dSLionel Sambuc
5010a6a1f1dSLionel Sambuc try {
5020a6a1f1dSLionel Sambuc tools::parser::token t = p.expect(text_type, "property name");
5030a6a1f1dSLionel Sambuc if (t.text() != "ident")
5040a6a1f1dSLionel Sambuc throw parse_error(t.lineno(), "First property of a test case "
5050a6a1f1dSLionel Sambuc "must be 'ident'");
5060a6a1f1dSLionel Sambuc
5070a6a1f1dSLionel Sambuc std::map< std::string, std::string > props;
5080a6a1f1dSLionel Sambuc do {
5090a6a1f1dSLionel Sambuc const std::string name = t.text();
5100a6a1f1dSLionel Sambuc t = p.expect(colon_type, "`:'");
5110a6a1f1dSLionel Sambuc const std::string value = tools::text::trim(p.rest_of_line());
5120a6a1f1dSLionel Sambuc t = p.expect(nl_type, "new line");
5130a6a1f1dSLionel Sambuc validate_and_insert(name, value, t.lineno(), props);
5140a6a1f1dSLionel Sambuc
5150a6a1f1dSLionel Sambuc t = p.expect(eof_type, nl_type, text_type, "property name, new "
5160a6a1f1dSLionel Sambuc "line or eof");
5170a6a1f1dSLionel Sambuc if (t.type() == nl_type || t.type() == eof_type) {
5180a6a1f1dSLionel Sambuc const std::map< std::string, std::string >::const_iterator
5190a6a1f1dSLionel Sambuc iter = props.find("ident");
5200a6a1f1dSLionel Sambuc if (iter == props.end())
5210a6a1f1dSLionel Sambuc throw parse_error(t.lineno(), "Test case definition did "
5220a6a1f1dSLionel Sambuc "not define an 'ident' property");
5230a6a1f1dSLionel Sambuc ATF_PARSER_CALLBACK(p, got_tc((*iter).second, props));
5240a6a1f1dSLionel Sambuc props.clear();
5250a6a1f1dSLionel Sambuc
5260a6a1f1dSLionel Sambuc if (t.type() == nl_type) {
5270a6a1f1dSLionel Sambuc t = p.expect(text_type, "property name");
5280a6a1f1dSLionel Sambuc if (t.text() != "ident")
5290a6a1f1dSLionel Sambuc throw parse_error(t.lineno(), "First property of a "
5300a6a1f1dSLionel Sambuc "test case must be 'ident'");
5310a6a1f1dSLionel Sambuc }
5320a6a1f1dSLionel Sambuc }
5330a6a1f1dSLionel Sambuc } while (t.type() != eof_type);
5340a6a1f1dSLionel Sambuc ATF_PARSER_CALLBACK(p, got_eof());
5350a6a1f1dSLionel Sambuc } catch (const parse_error& pe) {
5360a6a1f1dSLionel Sambuc p.add_error(pe);
5370a6a1f1dSLionel Sambuc p.reset(nl_type);
5380a6a1f1dSLionel Sambuc }
5390a6a1f1dSLionel Sambuc }
5400a6a1f1dSLionel Sambuc
5410a6a1f1dSLionel Sambuc impl::test_case_result
parse_test_case_result(const std::string & line)5420a6a1f1dSLionel Sambuc detail::parse_test_case_result(const std::string& line)
5430a6a1f1dSLionel Sambuc {
5440a6a1f1dSLionel Sambuc std::string state, arg, reason;
5450a6a1f1dSLionel Sambuc tokenize_result(line, state, arg, reason);
5460a6a1f1dSLionel Sambuc
5470a6a1f1dSLionel Sambuc if (state == "expected_death")
5480a6a1f1dSLionel Sambuc return handle_result_with_reason(state, arg, reason);
5490a6a1f1dSLionel Sambuc else if (state.compare(0, 13, "expected_exit") == 0)
5500a6a1f1dSLionel Sambuc return handle_result_with_reason_and_arg(state, arg, reason);
5510a6a1f1dSLionel Sambuc else if (state.compare(0, 16, "expected_failure") == 0)
5520a6a1f1dSLionel Sambuc return handle_result_with_reason(state, arg, reason);
5530a6a1f1dSLionel Sambuc else if (state.compare(0, 15, "expected_signal") == 0)
5540a6a1f1dSLionel Sambuc return handle_result_with_reason_and_arg(state, arg, reason);
5550a6a1f1dSLionel Sambuc else if (state.compare(0, 16, "expected_timeout") == 0)
5560a6a1f1dSLionel Sambuc return handle_result_with_reason(state, arg, reason);
5570a6a1f1dSLionel Sambuc else if (state == "failed")
5580a6a1f1dSLionel Sambuc return handle_result_with_reason(state, arg, reason);
5590a6a1f1dSLionel Sambuc else if (state == "passed")
5600a6a1f1dSLionel Sambuc return handle_result(state, arg, reason);
5610a6a1f1dSLionel Sambuc else if (state == "skipped")
5620a6a1f1dSLionel Sambuc return handle_result_with_reason(state, arg, reason);
5630a6a1f1dSLionel Sambuc else
5640a6a1f1dSLionel Sambuc throw std::runtime_error("Unknown test case result type in: " + line);
5650a6a1f1dSLionel Sambuc }
5660a6a1f1dSLionel Sambuc
atf_tps_writer(std::ostream & os)5670a6a1f1dSLionel Sambuc impl::atf_tps_writer::atf_tps_writer(std::ostream& os) :
5680a6a1f1dSLionel Sambuc m_os(os)
5690a6a1f1dSLionel Sambuc {
5700a6a1f1dSLionel Sambuc tools::parser::headers_map hm;
5710a6a1f1dSLionel Sambuc tools::parser::attrs_map ct_attrs;
5720a6a1f1dSLionel Sambuc ct_attrs["version"] = "3";
5730a6a1f1dSLionel Sambuc hm["Content-Type"] =
5740a6a1f1dSLionel Sambuc tools::parser::header_entry("Content-Type", "application/X-atf-tps",
5750a6a1f1dSLionel Sambuc ct_attrs);
5760a6a1f1dSLionel Sambuc tools::parser::write_headers(hm, m_os);
5770a6a1f1dSLionel Sambuc }
5780a6a1f1dSLionel Sambuc
5790a6a1f1dSLionel Sambuc void
info(const std::string & what,const std::string & val)5800a6a1f1dSLionel Sambuc impl::atf_tps_writer::info(const std::string& what, const std::string& val)
5810a6a1f1dSLionel Sambuc {
5820a6a1f1dSLionel Sambuc m_os << "info: " << what << ", " << val << "\n";
5830a6a1f1dSLionel Sambuc m_os.flush();
5840a6a1f1dSLionel Sambuc }
5850a6a1f1dSLionel Sambuc
5860a6a1f1dSLionel Sambuc void
ntps(size_t p_ntps)5870a6a1f1dSLionel Sambuc impl::atf_tps_writer::ntps(size_t p_ntps)
5880a6a1f1dSLionel Sambuc {
5890a6a1f1dSLionel Sambuc m_os << "tps-count: " << p_ntps << "\n";
5900a6a1f1dSLionel Sambuc m_os.flush();
5910a6a1f1dSLionel Sambuc }
5920a6a1f1dSLionel Sambuc
5930a6a1f1dSLionel Sambuc void
start_tp(const std::string & tp,size_t ntcs)5940a6a1f1dSLionel Sambuc impl::atf_tps_writer::start_tp(const std::string& tp, size_t ntcs)
5950a6a1f1dSLionel Sambuc {
5960a6a1f1dSLionel Sambuc m_tpname = tp;
5970a6a1f1dSLionel Sambuc m_os << "tp-start: " << generate_timestamp() << ", " << tp << ", "
5980a6a1f1dSLionel Sambuc << ntcs << "\n";
5990a6a1f1dSLionel Sambuc m_os.flush();
6000a6a1f1dSLionel Sambuc }
6010a6a1f1dSLionel Sambuc
6020a6a1f1dSLionel Sambuc void
end_tp(const std::string & reason)6030a6a1f1dSLionel Sambuc impl::atf_tps_writer::end_tp(const std::string& reason)
6040a6a1f1dSLionel Sambuc {
6050a6a1f1dSLionel Sambuc assert(reason.find('\n') == std::string::npos);
6060a6a1f1dSLionel Sambuc if (reason.empty())
6070a6a1f1dSLionel Sambuc m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname << "\n";
6080a6a1f1dSLionel Sambuc else
6090a6a1f1dSLionel Sambuc m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname
6100a6a1f1dSLionel Sambuc << ", " << reason << "\n";
6110a6a1f1dSLionel Sambuc m_os.flush();
6120a6a1f1dSLionel Sambuc }
6130a6a1f1dSLionel Sambuc
6140a6a1f1dSLionel Sambuc void
start_tc(const std::string & tcname)6150a6a1f1dSLionel Sambuc impl::atf_tps_writer::start_tc(const std::string& tcname)
6160a6a1f1dSLionel Sambuc {
6170a6a1f1dSLionel Sambuc m_tcname = tcname;
6180a6a1f1dSLionel Sambuc m_os << "tc-start: " << generate_timestamp() << ", " << tcname << "\n";
6190a6a1f1dSLionel Sambuc m_os.flush();
6200a6a1f1dSLionel Sambuc }
6210a6a1f1dSLionel Sambuc
6220a6a1f1dSLionel Sambuc void
stdout_tc(const std::string & line)6230a6a1f1dSLionel Sambuc impl::atf_tps_writer::stdout_tc(const std::string& line)
6240a6a1f1dSLionel Sambuc {
6250a6a1f1dSLionel Sambuc m_os << "tc-so:" << line << "\n";
6260a6a1f1dSLionel Sambuc check_stream(m_os);
6270a6a1f1dSLionel Sambuc m_os.flush();
6280a6a1f1dSLionel Sambuc check_stream(m_os);
6290a6a1f1dSLionel Sambuc }
6300a6a1f1dSLionel Sambuc
6310a6a1f1dSLionel Sambuc void
stderr_tc(const std::string & line)6320a6a1f1dSLionel Sambuc impl::atf_tps_writer::stderr_tc(const std::string& line)
6330a6a1f1dSLionel Sambuc {
6340a6a1f1dSLionel Sambuc m_os << "tc-se:" << line << "\n";
6350a6a1f1dSLionel Sambuc check_stream(m_os);
6360a6a1f1dSLionel Sambuc m_os.flush();
6370a6a1f1dSLionel Sambuc check_stream(m_os);
6380a6a1f1dSLionel Sambuc }
6390a6a1f1dSLionel Sambuc
6400a6a1f1dSLionel Sambuc void
end_tc(const std::string & state,const std::string & reason)6410a6a1f1dSLionel Sambuc impl::atf_tps_writer::end_tc(const std::string& state,
6420a6a1f1dSLionel Sambuc const std::string& reason)
6430a6a1f1dSLionel Sambuc {
6440a6a1f1dSLionel Sambuc std::string str = ", " + m_tcname + ", " + state;
6450a6a1f1dSLionel Sambuc if (!reason.empty())
6460a6a1f1dSLionel Sambuc str += ", " + reason;
6470a6a1f1dSLionel Sambuc m_os << "tc-end: " << generate_timestamp() << str << "\n";
6480a6a1f1dSLionel Sambuc m_os.flush();
6490a6a1f1dSLionel Sambuc }
6500a6a1f1dSLionel Sambuc
6510a6a1f1dSLionel Sambuc impl::metadata
get_metadata(const tools::fs::path & executable,const vars_map & config)6520a6a1f1dSLionel Sambuc impl::get_metadata(const tools::fs::path& executable,
6530a6a1f1dSLionel Sambuc const vars_map& config)
6540a6a1f1dSLionel Sambuc {
6550a6a1f1dSLionel Sambuc get_metadata_params params(executable, config);
6560a6a1f1dSLionel Sambuc tools::process::child child =
6570a6a1f1dSLionel Sambuc tools::process::fork(get_metadata_child,
6580a6a1f1dSLionel Sambuc tools::process::stream_capture(),
6590a6a1f1dSLionel Sambuc tools::process::stream_inherit(),
6600a6a1f1dSLionel Sambuc static_cast< void * >(¶ms));
6610a6a1f1dSLionel Sambuc
6620a6a1f1dSLionel Sambuc tools::io::pistream outin(child.stdout_fd());
6630a6a1f1dSLionel Sambuc
6640a6a1f1dSLionel Sambuc metadata_reader parser(outin);
6650a6a1f1dSLionel Sambuc parser.read();
6660a6a1f1dSLionel Sambuc
6670a6a1f1dSLionel Sambuc const tools::process::status status = child.wait();
6680a6a1f1dSLionel Sambuc if (!status.exited() || status.exitstatus() != EXIT_SUCCESS)
6690a6a1f1dSLionel Sambuc throw tools::parser::format_error("Test program returned failure "
6700a6a1f1dSLionel Sambuc "exit status for test case list");
6710a6a1f1dSLionel Sambuc
6720a6a1f1dSLionel Sambuc return metadata(parser.get_tcs());
6730a6a1f1dSLionel Sambuc }
6740a6a1f1dSLionel Sambuc
6750a6a1f1dSLionel Sambuc impl::test_case_result
read_test_case_result(const tools::fs::path & results_path)6760a6a1f1dSLionel Sambuc impl::read_test_case_result(const tools::fs::path& results_path)
6770a6a1f1dSLionel Sambuc {
6780a6a1f1dSLionel Sambuc std::ifstream results_file(results_path.c_str());
6790a6a1f1dSLionel Sambuc if (!results_file)
6800a6a1f1dSLionel Sambuc throw std::runtime_error("Failed to open " + results_path.str());
6810a6a1f1dSLionel Sambuc
6820a6a1f1dSLionel Sambuc std::string line, extra_line;
6830a6a1f1dSLionel Sambuc std::getline(results_file, line);
6840a6a1f1dSLionel Sambuc if (!results_file.good())
6850a6a1f1dSLionel Sambuc throw std::runtime_error("Results file is empty");
6860a6a1f1dSLionel Sambuc
6870a6a1f1dSLionel Sambuc while (std::getline(results_file, extra_line).good())
6880a6a1f1dSLionel Sambuc line += "<<NEWLINE UNEXPECTED>>" + extra_line;
6890a6a1f1dSLionel Sambuc
6900a6a1f1dSLionel Sambuc results_file.close();
6910a6a1f1dSLionel Sambuc
6920a6a1f1dSLionel Sambuc return detail::parse_test_case_result(line);
6930a6a1f1dSLionel Sambuc }
6940a6a1f1dSLionel Sambuc
6950a6a1f1dSLionel Sambuc namespace {
6960a6a1f1dSLionel Sambuc
6970a6a1f1dSLionel Sambuc static volatile bool terminate_poll;
6980a6a1f1dSLionel Sambuc
6990a6a1f1dSLionel Sambuc static void
sigchld_handler(const int signo)7000a6a1f1dSLionel Sambuc sigchld_handler(const int signo __attribute__((__unused__)))
7010a6a1f1dSLionel Sambuc {
7020a6a1f1dSLionel Sambuc terminate_poll = true;
7030a6a1f1dSLionel Sambuc }
7040a6a1f1dSLionel Sambuc
7050a6a1f1dSLionel Sambuc class child_muxer : public tools::io::muxer {
7060a6a1f1dSLionel Sambuc impl::atf_tps_writer& m_writer;
7070a6a1f1dSLionel Sambuc
7080a6a1f1dSLionel Sambuc void
line_callback(const size_t index,const std::string & line)7090a6a1f1dSLionel Sambuc line_callback(const size_t index, const std::string& line)
7100a6a1f1dSLionel Sambuc {
7110a6a1f1dSLionel Sambuc switch (index) {
7120a6a1f1dSLionel Sambuc case 0: m_writer.stdout_tc(line); break;
7130a6a1f1dSLionel Sambuc case 1: m_writer.stderr_tc(line); break;
7140a6a1f1dSLionel Sambuc default: std::abort();
7150a6a1f1dSLionel Sambuc }
7160a6a1f1dSLionel Sambuc }
7170a6a1f1dSLionel Sambuc
7180a6a1f1dSLionel Sambuc public:
child_muxer(const int * fds,const size_t nfds,impl::atf_tps_writer & writer)7190a6a1f1dSLionel Sambuc child_muxer(const int* fds, const size_t nfds,
7200a6a1f1dSLionel Sambuc impl::atf_tps_writer& writer) :
7210a6a1f1dSLionel Sambuc muxer(fds, nfds),
7220a6a1f1dSLionel Sambuc m_writer(writer)
7230a6a1f1dSLionel Sambuc {
7240a6a1f1dSLionel Sambuc }
7250a6a1f1dSLionel Sambuc };
7260a6a1f1dSLionel Sambuc
7270a6a1f1dSLionel Sambuc } // anonymous namespace
7280a6a1f1dSLionel Sambuc
7290a6a1f1dSLionel Sambuc std::pair< std::string, tools::process::status >
run_test_case(const tools::fs::path & executable,const std::string & test_case_name,const std::string & test_case_part,const vars_map & metadata,const vars_map & config,const tools::fs::path & resfile,const tools::fs::path & workdir,atf_tps_writer & writer)7300a6a1f1dSLionel Sambuc impl::run_test_case(const tools::fs::path& executable,
7310a6a1f1dSLionel Sambuc const std::string& test_case_name,
7320a6a1f1dSLionel Sambuc const std::string& test_case_part,
7330a6a1f1dSLionel Sambuc const vars_map& metadata,
7340a6a1f1dSLionel Sambuc const vars_map& config,
7350a6a1f1dSLionel Sambuc const tools::fs::path& resfile,
7360a6a1f1dSLionel Sambuc const tools::fs::path& workdir,
7370a6a1f1dSLionel Sambuc atf_tps_writer& writer)
7380a6a1f1dSLionel Sambuc {
7390a6a1f1dSLionel Sambuc // TODO: Capture termination signals and deliver them to the subprocess
7400a6a1f1dSLionel Sambuc // instead. Or maybe do something else; think about it.
7410a6a1f1dSLionel Sambuc
7420a6a1f1dSLionel Sambuc test_case_params params(executable, test_case_name, test_case_part,
7430a6a1f1dSLionel Sambuc metadata, config, resfile, workdir);
7440a6a1f1dSLionel Sambuc tools::process::child child =
7450a6a1f1dSLionel Sambuc tools::process::fork(run_test_case_child,
7460a6a1f1dSLionel Sambuc tools::process::stream_capture(),
7470a6a1f1dSLionel Sambuc tools::process::stream_capture(),
7480a6a1f1dSLionel Sambuc static_cast< void * >(¶ms));
7490a6a1f1dSLionel Sambuc
7500a6a1f1dSLionel Sambuc terminate_poll = false;
7510a6a1f1dSLionel Sambuc
7520a6a1f1dSLionel Sambuc const vars_map::const_iterator iter = metadata.find("timeout");
7530a6a1f1dSLionel Sambuc assert(iter != metadata.end());
7540a6a1f1dSLionel Sambuc const unsigned int timeout =
7550a6a1f1dSLionel Sambuc tools::text::to_type< unsigned int >((*iter).second);
7560a6a1f1dSLionel Sambuc const pid_t child_pid = child.pid();
7570a6a1f1dSLionel Sambuc
7580a6a1f1dSLionel Sambuc // Get the input stream of stdout and stderr.
7590a6a1f1dSLionel Sambuc tools::io::file_handle outfh = child.stdout_fd();
7600a6a1f1dSLionel Sambuc tools::io::file_handle errfh = child.stderr_fd();
7610a6a1f1dSLionel Sambuc
7620a6a1f1dSLionel Sambuc bool timed_out = false;
7630a6a1f1dSLionel Sambuc
7640a6a1f1dSLionel Sambuc // Process the test case's output and multiplex it into our output
7650a6a1f1dSLionel Sambuc // stream as we read it.
7660a6a1f1dSLionel Sambuc int fds[2] = {outfh.get(), errfh.get()};
7670a6a1f1dSLionel Sambuc child_muxer mux(fds, 2, writer);
7680a6a1f1dSLionel Sambuc try {
7690a6a1f1dSLionel Sambuc timers::child_timer timeout_timer(timeout, child_pid, terminate_poll);
7700a6a1f1dSLionel Sambuc signals::signal_programmer sigchld(SIGCHLD, sigchld_handler);
7710a6a1f1dSLionel Sambuc mux.mux(terminate_poll);
7720a6a1f1dSLionel Sambuc timed_out = timeout_timer.fired();
7730a6a1f1dSLionel Sambuc } catch (...) {
7740a6a1f1dSLionel Sambuc std::abort();
7750a6a1f1dSLionel Sambuc }
7760a6a1f1dSLionel Sambuc
7770a6a1f1dSLionel Sambuc ::killpg(child_pid, SIGKILL);
7780a6a1f1dSLionel Sambuc mux.flush();
7790a6a1f1dSLionel Sambuc tools::process::status status = child.wait();
7800a6a1f1dSLionel Sambuc
7810a6a1f1dSLionel Sambuc std::string reason;
7820a6a1f1dSLionel Sambuc
7830a6a1f1dSLionel Sambuc if (timed_out) {
7840a6a1f1dSLionel Sambuc // Don't assume the child process has been signaled due to the timeout
7850a6a1f1dSLionel Sambuc // expiration as older versions did. The child process may have exited
7860a6a1f1dSLionel Sambuc // but we may have timed out due to a subchild process getting stuck.
7870a6a1f1dSLionel Sambuc reason = "Test case timed out after " + tools::text::to_string(timeout) +
7880a6a1f1dSLionel Sambuc " " + (timeout == 1 ? "second" : "seconds");
7890a6a1f1dSLionel Sambuc }
7900a6a1f1dSLionel Sambuc
7910a6a1f1dSLionel Sambuc return std::make_pair(reason, status);
7920a6a1f1dSLionel Sambuc }
793