2 // Automated Testing Framework (atf)
4 // Copyright (c) 2007, 2008, 2009 The NetBSD Foundation, Inc.
5 // All rights reserved.
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
10 // 1. Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 // 2. Redistributions in binary form must reproduce the above copyright
13 // notice, this list of conditions and the following disclaimer in the
14 // documentation and/or other materials provided with the distribution.
16 // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
17 // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include <sys/types.h>
53 #include "atf-c/error.h"
54 #include "atf-c/object.h"
57 #include "atf-c++/application.hpp"
58 #include "atf-c++/config.hpp"
59 #include "atf-c++/env.hpp"
60 #include "atf-c++/exceptions.hpp"
61 #include "atf-c++/expand.hpp"
62 #include "atf-c++/formats.hpp"
63 #include "atf-c++/fs.hpp"
64 #include "atf-c++/io.hpp"
65 #include "atf-c++/sanity.hpp"
66 #include "atf-c++/signals.hpp"
67 #include "atf-c++/tests.hpp"
68 #include "atf-c++/text.hpp"
69 #include "atf-c++/ui.hpp"
70 #include "atf-c++/user.hpp"
72 namespace impl
= atf::tests
;
73 #define IMPL_NAME "atf::tests"
75 // ------------------------------------------------------------------------
76 // Auxiliary stuff for the timeout implementation.
77 // ------------------------------------------------------------------------
80 static pid_t current_body
= 0;
81 static bool killed
= false;
84 sigalrm_handler(int signo
)
86 PRE(signo
== SIGALRM
);
88 if (current_body
!= 0) {
89 ::killpg(current_body
, SIGTERM
);
93 } // namespace timeout
95 // ------------------------------------------------------------------------
97 // ------------------------------------------------------------------------
99 const impl::tcr::state
impl::tcr::passed_state
= atf_tcr_passed_state
;
100 const impl::tcr::state
impl::tcr::failed_state
= atf_tcr_failed_state
;
101 const impl::tcr::state
impl::tcr::skipped_state
= atf_tcr_skipped_state
;
103 impl::tcr::tcr(state s
)
105 PRE(s
== passed_state
);
107 atf_error_t err
= atf_tcr_init(&m_tcr
, s
);
108 if (atf_is_error(err
))
109 throw_atf_error(err
);
112 impl::tcr::tcr(state s
, const std::string
& r
)
114 PRE(s
== failed_state
|| s
== skipped_state
);
117 atf_error_t err
= atf_tcr_init_reason_fmt(&m_tcr
, s
, "%s", r
.c_str());
118 if (atf_is_error(err
))
119 throw_atf_error(err
);
122 impl::tcr::tcr(const tcr
& o
)
124 if (o
.get_state() == passed_state
)
125 atf_tcr_init(&m_tcr
, o
.get_state());
127 atf_tcr_init_reason_fmt(&m_tcr
, o
.get_state(), "%s",
128 o
.get_reason().c_str());
131 impl::tcr::~tcr(void)
133 atf_tcr_fini(&m_tcr
);
137 impl::tcr::get_state(void)
140 return atf_tcr_get_state(&m_tcr
);
144 impl::tcr::get_reason(void)
147 const atf_dynstr_t
* r
= atf_tcr_get_reason(&m_tcr
);
148 return atf_dynstr_cstring(r
);
152 impl::tcr::operator=(const tcr
& o
)
155 atf_tcr_fini(&m_tcr
);
157 if (o
.get_state() == passed_state
)
158 atf_tcr_init(&m_tcr
, o
.get_state());
160 atf_tcr_init_reason_fmt(&m_tcr
, o
.get_state(), "%s",
161 o
.get_reason().c_str());
166 // ------------------------------------------------------------------------
168 // ------------------------------------------------------------------------
170 static std::map
< atf_tc_t
*, impl::tc
* > wraps
;
171 static std::map
< const atf_tc_t
*, const impl::tc
* > cwraps
;
174 impl::tc::wrap_head(atf_tc_t
*tc
)
176 std::map
< atf_tc_t
*, impl::tc
* >::iterator iter
= wraps
.find(tc
);
177 INV(iter
!= wraps
.end());
178 (*iter
).second
->head();
182 impl::tc::wrap_body(const atf_tc_t
*tc
)
184 std::map
< const atf_tc_t
*, const impl::tc
* >::const_iterator iter
=
186 INV(iter
!= cwraps
.end());
187 (*iter
).second
->body();
191 impl::tc::wrap_cleanup(const atf_tc_t
*tc
)
193 std::map
< const atf_tc_t
*, const impl::tc
* >::const_iterator iter
=
195 INV(iter
!= cwraps
.end());
196 (*iter
).second
->cleanup();
199 impl::tc::tc(const std::string
& ident
) :
210 atf_map_fini(&m_config
);
214 impl::tc::init(const vars_map
& config
)
218 err
= atf_map_init(&m_config
);
219 if (atf_is_error(err
))
220 throw_atf_error(err
);
222 for (vars_map::const_iterator iter
= config
.begin();
223 iter
!= config
.end(); iter
++) {
224 const char *var
= (*iter
).first
.c_str();
225 const char *val
= (*iter
).second
.c_str();
227 err
= atf_map_insert(&m_config
, var
, ::strdup(val
), true);
228 if (atf_is_error(err
)) {
229 atf_map_fini(&m_config
);
230 throw_atf_error(err
);
235 cwraps
[&m_tc
] = this;
237 err
= atf_tc_init(&m_tc
, m_ident
.c_str(), wrap_head
, wrap_body
,
238 wrap_cleanup
, &m_config
);
239 if (atf_is_error(err
)) {
240 atf_map_fini(&m_config
);
241 throw_atf_error(err
);
246 impl::tc::has_config_var(const std::string
& var
)
249 return atf_tc_has_config_var(&m_tc
, var
.c_str());
253 impl::tc::has_md_var(const std::string
& var
)
256 return atf_tc_has_md_var(&m_tc
, var
.c_str());
260 impl::tc::get_config_var(const std::string
& var
)
263 return atf_tc_get_config_var(&m_tc
, var
.c_str());
267 impl::tc::get_config_var(const std::string
& var
, const std::string
& defval
)
270 return atf_tc_get_config_var_wd(&m_tc
, var
.c_str(), defval
.c_str());
274 impl::tc::get_md_var(const std::string
& var
)
277 return atf_tc_get_md_var(&m_tc
, var
.c_str());
281 impl::tc::set_md_var(const std::string
& var
, const std::string
& val
)
283 atf_error_t err
= atf_tc_set_md_var(&m_tc
, var
.c_str(), val
.c_str());
284 if (atf_is_error(err
))
285 throw_atf_error(err
);
289 impl::tc::run(int fdout
, int fderr
, const fs::path
& workdirbase
)
293 tcr
tcrr(tcr::failed_state
, "UNINITIALIZED");
295 atf_error_t err
= atf_tc_run(&m_tc
, &tcrc
, fdout
, fderr
,
296 workdirbase
.c_path());
297 if (atf_is_error(err
))
298 throw_atf_error(err
);
300 if (atf_tcr_has_reason(&tcrc
)) {
301 const atf_dynstr_t
* r
= atf_tcr_get_reason(&tcrc
);
302 tcrr
= tcr(atf_tcr_get_state(&tcrc
), atf_dynstr_cstring(r
));
304 tcrr
= tcr(atf_tcr_get_state(&tcrc
));
312 impl::tc::cleanup(void)
318 impl::tc::require_prog(const std::string
& prog
)
321 atf_tc_require_prog(prog
.c_str());
331 impl::tc::fail(const std::string
& reason
)
333 atf_tc_fail("%s", reason
.c_str());
337 impl::tc::skip(const std::string
& reason
)
339 atf_tc_skip("%s", reason
.c_str());
342 // ------------------------------------------------------------------------
344 // ------------------------------------------------------------------------
346 class tp
: public atf::application::app
{
348 typedef std::vector
< impl::tc
* > tc_vector
;
351 static const char* m_description
;
355 std::auto_ptr
< std::ostream
> m_results_os
;
356 atf::fs::path m_srcdir
;
357 atf::fs::path m_workdir
;
358 std::vector
< std::string
> m_tcnames
;
360 atf::tests::vars_map m_vars
;
362 std::string
specific_args(void) const;
363 options_set
specific_options(void) const;
364 void process_option(int, const char*);
366 void (*m_add_tcs
)(tc_vector
&);
369 void parse_vflag(const std::string
&);
370 void handle_srcdir(void);
372 tc_vector
init_tcs(void);
373 static tc_vector
filter_tcs(tc_vector
,
374 const std::vector
< std::string
>&);
376 std::ostream
& results_stream(void);
382 tp(void (*)(tc_vector
&));
388 const char* tp::m_description
=
389 "This is an independent atf test program.";
391 tp::tp(void (*add_tcs
)(tc_vector
&)) :
392 app(m_description
, "atf-test-program(1)", "atf(7)"),
394 m_results_fd(STDOUT_FILENO
),
396 m_workdir(atf::config::get("atf_workdir")),
403 for (tc_vector::iterator iter
= m_tcs
.begin();
404 iter
!= m_tcs
.end(); iter
++) {
405 impl::tc
* tc
= *iter
;
412 tp::specific_args(void)
415 return "[test_case1 [.. test_caseN]]";
419 tp::specific_options(void)
422 using atf::application::option
;
424 opts
.insert(option('l', "", "List test cases and their purpose"));
425 opts
.insert(option('r', "fd", "The file descriptor to which the test "
426 "program will send the results of the "
428 opts
.insert(option('s', "srcdir", "Directory where the test's data "
429 "files are located"));
430 opts
.insert(option('v', "var=value", "Sets the configuration variable "
431 "`var' to `value'"));
432 opts
.insert(option('w', "workdir", "Directory where the test's "
433 "temporary files are located"));
438 tp::process_option(int ch
, const char* arg
)
447 std::istringstream
ss(arg
);
453 m_srcdir
= atf::fs::path(arg
);
461 m_workdir
= atf::fs::path(arg
);
470 tp::parse_vflag(const std::string
& str
)
473 throw std::runtime_error("-v requires a non-empty argument");
475 std::vector
< std::string
> ws
= atf::text::split(str
, "=");
476 if (ws
.size() == 1 && str
[str
.length() - 1] == '=') {
480 throw std::runtime_error("-v requires an argument of the form "
483 m_vars
[ws
[0]] = ws
[1];
488 tp::handle_srcdir(void)
490 if (!atf::fs::exists(m_srcdir
/ m_prog_name
))
491 throw std::runtime_error("Cannot find the test program in the "
492 "source directory `" + m_srcdir
.str() + "'");
494 if (!m_srcdir
.is_absolute())
495 m_srcdir
= m_srcdir
.to_absolute();
497 m_vars
["srcdir"] = m_srcdir
.str();
504 for (tc_vector::iterator iter
= m_tcs
.begin();
505 iter
!= m_tcs
.end(); iter
++) {
506 impl::tc
* tc
= *iter
;
514 // An auxiliary unary predicate that compares the given test case's
515 // identifier to the identifier stored in it.
517 class tc_equal_to_ident
{
518 const std::string
& m_ident
;
521 tc_equal_to_ident(const std::string
& i
) :
526 bool operator()(const impl::tc
* tc
)
528 return tc
->get_md_var("ident") == m_ident
;
533 tp::filter_tcs(tc_vector tcs
, const std::vector
< std::string
>& tcnames
)
537 if (tcnames
.empty()) {
538 // Special case: added for efficiency because this is the most
539 // typical situation.
542 // Collect all the test cases' identifiers.
543 std::vector
< std::string
> ids
;
544 for (tc_vector::iterator iter
= tcs
.begin();
545 iter
!= tcs
.end(); iter
++) {
546 impl::tc
* tc
= *iter
;
548 ids
.push_back(tc
->get_md_var("ident"));
551 // Iterate over all names provided by the user and, for each one,
552 // expand it as if it were a glob pattern. Collect all expansions.
553 std::vector
< std::string
> exps
;
554 for (std::vector
< std::string
>::const_iterator iter
= tcnames
.begin();
555 iter
!= tcnames
.end(); iter
++) {
556 const std::string
& glob
= *iter
;
558 std::vector
< std::string
> ms
=
559 atf::expand::expand_glob(glob
, ids
);
561 throw std::runtime_error("Unknown test case `" + glob
+ "'");
562 exps
.insert(exps
.end(), ms
.begin(), ms
.end());
565 // For each expansion, locate its corresponding test case and add
566 // it to the output set.
567 for (std::vector
< std::string
>::const_iterator iter
= exps
.begin();
568 iter
!= exps
.end(); iter
++) {
569 const std::string
& name
= *iter
;
571 tc_vector::iterator tciter
=
572 std::find_if(tcs
.begin(), tcs
.end(), tc_equal_to_ident(name
));
573 INV(tciter
!= tcs
.end());
574 tcso
.push_back(*tciter
);
584 tc_vector tcs
= filter_tcs(init_tcs(), m_tcnames
);
586 std::string::size_type maxlen
= 0;
587 for (tc_vector::const_iterator iter
= tcs
.begin();
588 iter
!= tcs
.end(); iter
++) {
589 const impl::tc
* tc
= *iter
;
591 if (maxlen
< tc
->get_md_var("ident").length())
592 maxlen
= tc
->get_md_var("ident").length();
595 for (tc_vector::const_iterator iter
= tcs
.begin();
596 iter
!= tcs
.end(); iter
++) {
597 const impl::tc
* tc
= *iter
;
599 std::cout
<< atf::ui::format_text_with_tag(tc
->get_md_var("descr"),
600 tc
->get_md_var("ident"),
609 tp::results_stream(void)
611 if (m_results_fd
== STDOUT_FILENO
)
613 else if (m_results_fd
== STDERR_FILENO
)
616 return *m_results_os
;
622 tc_vector tcs
= filter_tcs(init_tcs(), m_tcnames
);
624 if (!atf::fs::exists(m_workdir
))
625 throw std::runtime_error("Cannot find the work directory `" +
626 m_workdir
.str() + "'");
628 int errcode
= EXIT_SUCCESS
;
630 atf::signals::signal_holder
sighup(SIGHUP
);
631 atf::signals::signal_holder
sigint(SIGINT
);
632 atf::signals::signal_holder
sigterm(SIGTERM
);
634 atf::formats::atf_tcs_writer
w(results_stream(), std::cout
, std::cerr
,
636 for (tc_vector::iterator iter
= tcs
.begin();
637 iter
!= tcs
.end(); iter
++) {
638 impl::tc
* tc
= *iter
;
640 w
.start_tc(tc
->get_md_var("ident"));
641 impl::tcr tcr
= tc
->run(STDOUT_FILENO
, STDERR_FILENO
, m_workdir
);
648 if (tcr
.get_state() == impl::tcr::failed_state
)
649 errcode
= EXIT_FAILURE
;
662 for (int i
= 0; i
< m_argc
; i
++)
663 m_tcnames
.push_back(m_argv
[i
]);
666 errcode
= list_tcs();
668 if (m_results_fd
!= STDOUT_FILENO
&& m_results_fd
!= STDERR_FILENO
) {
669 atf::io::file_handle
fh(m_results_fd
);
671 std::auto_ptr
< std::ostream
>(new atf::io::postream(fh
));
681 int run_tp(int, char* const*, void (*)(tp::tc_vector
&));
686 impl::run_tp(int argc
, char* const* argv
, void (*add_tcs
)(tp::tc_vector
&))
688 return tp(add_tcs
).run(argc
, argv
);