1 //
2 // Automated Testing Framework (atf)
3 //
4 // Copyright (c) 2007 The NetBSD Foundation, Inc.
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
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.
15 //
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.
28 //
29
30 extern "C" {
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <sys/time.h>
34 #include <sys/wait.h>
35 #include <signal.h>
36 #include <unistd.h>
37 }
38
39 #include <algorithm>
40 #include <cctype>
41 #include <cerrno>
42 #include <cstdlib>
43 #include <cstring>
44 #include <fstream>
45 #include <iostream>
46 #include <map>
47 #include <memory>
48 #include <sstream>
49 #include <stdexcept>
50 #include <vector>
51
52 extern "C" {
53 #include "atf-c/error.h"
54 #include "atf-c/tc.h"
55 #include "atf-c/utils.h"
56 }
57
58 #include "tests.hpp"
59
60 #include "detail/application.hpp"
61 #include "detail/auto_array.hpp"
62 #include "detail/env.hpp"
63 #include "detail/exceptions.hpp"
64 #include "detail/fs.hpp"
65 #include "detail/sanity.hpp"
66 #include "detail/text.hpp"
67
68 namespace impl = atf::tests;
69 namespace detail = atf::tests::detail;
70 #define IMPL_NAME "atf::tests"
71
72 // ------------------------------------------------------------------------
73 // The "atf_tp_writer" class.
74 // ------------------------------------------------------------------------
75
atf_tp_writer(std::ostream & os)76 detail::atf_tp_writer::atf_tp_writer(std::ostream& os) :
77 m_os(os),
78 m_is_first(true)
79 {
80 m_os << "Content-Type: application/X-atf-tp; version=\"1\"\n\n";
81 }
82
83 void
start_tc(const std::string & ident)84 detail::atf_tp_writer::start_tc(const std::string& ident)
85 {
86 if (!m_is_first)
87 m_os << "\n";
88 m_os << "ident: " << ident << "\n";
89 m_os.flush();
90 }
91
92 void
end_tc(void)93 detail::atf_tp_writer::end_tc(void)
94 {
95 if (m_is_first)
96 m_is_first = false;
97 }
98
99 void
tc_meta_data(const std::string & name,const std::string & value)100 detail::atf_tp_writer::tc_meta_data(const std::string& name,
101 const std::string& value)
102 {
103 PRE(name != "ident");
104 m_os << name << ": " << value << "\n";
105 m_os.flush();
106 }
107
108 // ------------------------------------------------------------------------
109 // Free helper functions.
110 // ------------------------------------------------------------------------
111
112 bool
match(const std::string & regexp,const std::string & str)113 detail::match(const std::string& regexp, const std::string& str)
114 {
115 return atf::text::match(str, regexp);
116 }
117
118 // ------------------------------------------------------------------------
119 // The "tc" class.
120 // ------------------------------------------------------------------------
121
122 static std::map< atf_tc_t*, impl::tc* > wraps;
123 static std::map< const atf_tc_t*, const impl::tc* > cwraps;
124
125 struct impl::tc_impl {
126 private:
127 // Non-copyable.
128 tc_impl(const tc_impl&);
129 tc_impl& operator=(const tc_impl&);
130
131 public:
132 std::string m_ident;
133 atf_tc_t m_tc;
134 bool m_has_cleanup;
135
tc_implimpl::tc_impl136 tc_impl(const std::string& ident, const bool has_cleanup) :
137 m_ident(ident),
138 m_has_cleanup(has_cleanup)
139 {
140 }
141
142 static void
wrap_headimpl::tc_impl143 wrap_head(atf_tc_t *tc)
144 {
145 std::map< atf_tc_t*, impl::tc* >::iterator iter = wraps.find(tc);
146 INV(iter != wraps.end());
147 (*iter).second->head();
148 }
149
150 static void
wrap_bodyimpl::tc_impl151 wrap_body(const atf_tc_t *tc)
152 {
153 std::map< const atf_tc_t*, const impl::tc* >::const_iterator iter =
154 cwraps.find(tc);
155 INV(iter != cwraps.end());
156 try {
157 (*iter).second->body();
158 } catch (const std::exception& e) {
159 (*iter).second->fail("Caught unhandled exception: " + std::string(
160 e.what()));
161 } catch (...) {
162 (*iter).second->fail("Caught unknown exception");
163 }
164 }
165
166 static void
wrap_cleanupimpl::tc_impl167 wrap_cleanup(const atf_tc_t *tc)
168 {
169 std::map< const atf_tc_t*, const impl::tc* >::const_iterator iter =
170 cwraps.find(tc);
171 INV(iter != cwraps.end());
172 (*iter).second->cleanup();
173 }
174 };
175
tc(const std::string & ident,const bool has_cleanup)176 impl::tc::tc(const std::string& ident, const bool has_cleanup) :
177 pimpl(new tc_impl(ident, has_cleanup))
178 {
179 }
180
~tc(void)181 impl::tc::~tc(void)
182 {
183 cwraps.erase(&pimpl->m_tc);
184 wraps.erase(&pimpl->m_tc);
185
186 atf_tc_fini(&pimpl->m_tc);
187 }
188
189 void
init(const vars_map & config)190 impl::tc::init(const vars_map& config)
191 {
192 atf_error_t err;
193
194 auto_array< const char * > array(new const char*[(config.size() * 2) + 1]);
195 const char **ptr = array.get();
196 for (vars_map::const_iterator iter = config.begin();
197 iter != config.end(); iter++) {
198 *ptr = (*iter).first.c_str();
199 *(ptr + 1) = (*iter).second.c_str();
200 ptr += 2;
201 }
202 *ptr = NULL;
203
204 wraps[&pimpl->m_tc] = this;
205 cwraps[&pimpl->m_tc] = this;
206
207 err = atf_tc_init(&pimpl->m_tc, pimpl->m_ident.c_str(), pimpl->wrap_head,
208 pimpl->wrap_body, pimpl->m_has_cleanup ? pimpl->wrap_cleanup : NULL,
209 array.get());
210 if (atf_is_error(err))
211 throw_atf_error(err);
212 }
213
214 bool
has_config_var(const std::string & var) const215 impl::tc::has_config_var(const std::string& var)
216 const
217 {
218 return atf_tc_has_config_var(&pimpl->m_tc, var.c_str());
219 }
220
221 bool
has_md_var(const std::string & var) const222 impl::tc::has_md_var(const std::string& var)
223 const
224 {
225 return atf_tc_has_md_var(&pimpl->m_tc, var.c_str());
226 }
227
228 const std::string
get_config_var(const std::string & var) const229 impl::tc::get_config_var(const std::string& var)
230 const
231 {
232 return atf_tc_get_config_var(&pimpl->m_tc, var.c_str());
233 }
234
235 const std::string
get_config_var(const std::string & var,const std::string & defval) const236 impl::tc::get_config_var(const std::string& var, const std::string& defval)
237 const
238 {
239 return atf_tc_get_config_var_wd(&pimpl->m_tc, var.c_str(), defval.c_str());
240 }
241
242 const std::string
get_md_var(const std::string & var) const243 impl::tc::get_md_var(const std::string& var)
244 const
245 {
246 return atf_tc_get_md_var(&pimpl->m_tc, var.c_str());
247 }
248
249 const impl::vars_map
get_md_vars(void) const250 impl::tc::get_md_vars(void)
251 const
252 {
253 vars_map vars;
254
255 char **array = atf_tc_get_md_vars(&pimpl->m_tc);
256 try {
257 char **ptr;
258 for (ptr = array; *ptr != NULL; ptr += 2)
259 vars[*ptr] = *(ptr + 1);
260 } catch (...) {
261 atf_utils_free_charpp(array);
262 throw;
263 }
264
265 return vars;
266 }
267
268 void
set_md_var(const std::string & var,const std::string & val)269 impl::tc::set_md_var(const std::string& var, const std::string& val)
270 {
271 atf_error_t err = atf_tc_set_md_var(&pimpl->m_tc, var.c_str(), "%s", val.c_str());
272 if (atf_is_error(err))
273 throw_atf_error(err);
274 }
275
276 void
run(const std::string & resfile) const277 impl::tc::run(const std::string& resfile)
278 const
279 {
280 atf_error_t err = atf_tc_run(&pimpl->m_tc, resfile.c_str());
281 if (atf_is_error(err))
282 throw_atf_error(err);
283 }
284
285 void
run_cleanup(void) const286 impl::tc::run_cleanup(void)
287 const
288 {
289 atf_error_t err = atf_tc_cleanup(&pimpl->m_tc);
290 if (atf_is_error(err))
291 throw_atf_error(err);
292 }
293
294 void
head(void)295 impl::tc::head(void)
296 {
297 }
298
299 void
cleanup(void) const300 impl::tc::cleanup(void)
301 const
302 {
303 }
304
305 void
require_prog(const std::string & prog) const306 impl::tc::require_prog(const std::string& prog)
307 const
308 {
309 atf_tc_require_prog(prog.c_str());
310 }
311
312 void
pass(void)313 impl::tc::pass(void)
314 {
315 atf_tc_pass();
316 }
317
318 void
fail(const std::string & reason)319 impl::tc::fail(const std::string& reason)
320 {
321 atf_tc_fail("%s", reason.c_str());
322 }
323
324 void
fail_nonfatal(const std::string & reason)325 impl::tc::fail_nonfatal(const std::string& reason)
326 {
327 atf_tc_fail_nonfatal("%s", reason.c_str());
328 }
329
330 void
skip(const std::string & reason)331 impl::tc::skip(const std::string& reason)
332 {
333 atf_tc_skip("%s", reason.c_str());
334 }
335
336 void
check_errno(const char * file,const int line,const int exp_errno,const char * expr_str,const bool result)337 impl::tc::check_errno(const char* file, const int line, const int exp_errno,
338 const char* expr_str, const bool result)
339 {
340 atf_tc_check_errno(file, line, exp_errno, expr_str, result);
341 }
342
343 void
require_errno(const char * file,const int line,const int exp_errno,const char * expr_str,const bool result)344 impl::tc::require_errno(const char* file, const int line, const int exp_errno,
345 const char* expr_str, const bool result)
346 {
347 atf_tc_require_errno(file, line, exp_errno, expr_str, result);
348 }
349
350 void
expect_pass(void)351 impl::tc::expect_pass(void)
352 {
353 atf_tc_expect_pass();
354 }
355
356 void
expect_fail(const std::string & reason)357 impl::tc::expect_fail(const std::string& reason)
358 {
359 atf_tc_expect_fail("%s", reason.c_str());
360 }
361
362 void
expect_exit(const int exitcode,const std::string & reason)363 impl::tc::expect_exit(const int exitcode, const std::string& reason)
364 {
365 atf_tc_expect_exit(exitcode, "%s", reason.c_str());
366 }
367
368 void
expect_signal(const int signo,const std::string & reason)369 impl::tc::expect_signal(const int signo, const std::string& reason)
370 {
371 atf_tc_expect_signal(signo, "%s", reason.c_str());
372 }
373
374 void
expect_death(const std::string & reason)375 impl::tc::expect_death(const std::string& reason)
376 {
377 atf_tc_expect_death("%s", reason.c_str());
378 }
379
380 void
expect_timeout(const std::string & reason)381 impl::tc::expect_timeout(const std::string& reason)
382 {
383 atf_tc_expect_timeout("%s", reason.c_str());
384 }
385
386 // ------------------------------------------------------------------------
387 // The "tp" class.
388 // ------------------------------------------------------------------------
389
390 class tp : public atf::application::app {
391 public:
392 typedef std::vector< impl::tc * > tc_vector;
393
394 private:
395 static const char* m_description;
396
397 bool m_lflag;
398 atf::fs::path m_resfile;
399 std::string m_srcdir_arg;
400 atf::fs::path m_srcdir;
401
402 atf::tests::vars_map m_vars;
403
404 std::string specific_args(void) const;
405 options_set specific_options(void) const;
406 void process_option(int, const char*);
407
408 void (*m_add_tcs)(tc_vector&);
409 tc_vector m_tcs;
410
411 void parse_vflag(const std::string&);
412 void handle_srcdir(void);
413
414 tc_vector init_tcs(void);
415
416 enum tc_part {
417 BODY,
418 CLEANUP,
419 };
420
421 void list_tcs(void);
422 impl::tc* find_tc(tc_vector, const std::string&);
423 static std::pair< std::string, tc_part > process_tcarg(const std::string&);
424 int run_tc(const std::string&);
425
426 public:
427 tp(void (*)(tc_vector&));
428 ~tp(void);
429
430 int main(void);
431 };
432
433 const char* tp::m_description =
434 "This is an independent atf test program.";
435
tp(void (* add_tcs)(tc_vector &))436 tp::tp(void (*add_tcs)(tc_vector&)) :
437 app(m_description, "atf-test-program(1)"),
438 m_lflag(false),
439 m_resfile("/dev/stdout"),
440 m_srcdir("."),
441 m_add_tcs(add_tcs)
442 {
443 }
444
~tp(void)445 tp::~tp(void)
446 {
447 for (tc_vector::iterator iter = m_tcs.begin();
448 iter != m_tcs.end(); iter++) {
449 impl::tc* tc = *iter;
450
451 delete tc;
452 }
453 }
454
455 std::string
specific_args(void) const456 tp::specific_args(void)
457 const
458 {
459 return "test_case";
460 }
461
462 tp::options_set
specific_options(void) const463 tp::specific_options(void)
464 const
465 {
466 using atf::application::option;
467 options_set opts;
468 opts.insert(option('l', "", "List test cases and their purpose"));
469 opts.insert(option('r', "resfile", "The file to which the test program "
470 "will write the results of the "
471 "executed test case"));
472 opts.insert(option('s', "srcdir", "Directory where the test's data "
473 "files are located"));
474 opts.insert(option('v', "var=value", "Sets the configuration variable "
475 "`var' to `value'"));
476 return opts;
477 }
478
479 void
process_option(int ch,const char * arg)480 tp::process_option(int ch, const char* arg)
481 {
482 switch (ch) {
483 case 'l':
484 m_lflag = true;
485 break;
486
487 case 'r':
488 m_resfile = atf::fs::path(arg);
489 break;
490
491 case 's':
492 m_srcdir_arg = arg;
493 break;
494
495 case 'v':
496 parse_vflag(arg);
497 break;
498
499 default:
500 UNREACHABLE;
501 }
502 }
503
504 void
parse_vflag(const std::string & str)505 tp::parse_vflag(const std::string& str)
506 {
507 if (str.empty())
508 throw std::runtime_error("-v requires a non-empty argument");
509
510 std::vector< std::string > ws = atf::text::split(str, "=");
511 if (ws.size() == 1 && str[str.length() - 1] == '=') {
512 m_vars[ws[0]] = "";
513 } else {
514 if (ws.size() != 2)
515 throw std::runtime_error("-v requires an argument of the form "
516 "var=value");
517
518 m_vars[ws[0]] = ws[1];
519 }
520 }
521
522 void
handle_srcdir(void)523 tp::handle_srcdir(void)
524 {
525 if (m_srcdir_arg.empty()) {
526 m_srcdir = atf::fs::path(m_argv0).branch_path();
527 if (m_srcdir.leaf_name() == ".libs")
528 m_srcdir = m_srcdir.branch_path();
529 } else
530 m_srcdir = atf::fs::path(m_srcdir_arg);
531
532 if (!atf::fs::exists(m_srcdir / m_prog_name))
533 throw std::runtime_error("Cannot find the test program in the "
534 "source directory `" + m_srcdir.str() + "'");
535
536 if (!m_srcdir.is_absolute())
537 m_srcdir = m_srcdir.to_absolute();
538
539 m_vars["srcdir"] = m_srcdir.str();
540 }
541
542 tp::tc_vector
init_tcs(void)543 tp::init_tcs(void)
544 {
545 m_add_tcs(m_tcs);
546 for (tc_vector::iterator iter = m_tcs.begin();
547 iter != m_tcs.end(); iter++) {
548 impl::tc* tc = *iter;
549
550 tc->init(m_vars);
551 }
552 return m_tcs;
553 }
554
555 //
556 // An auxiliary unary predicate that compares the given test case's
557 // identifier to the identifier stored in it.
558 //
559 class tc_equal_to_ident {
560 const std::string& m_ident;
561
562 public:
tc_equal_to_ident(const std::string & i)563 tc_equal_to_ident(const std::string& i) :
564 m_ident(i)
565 {
566 }
567
operator ()(const impl::tc * tc)568 bool operator()(const impl::tc* tc)
569 {
570 return tc->get_md_var("ident") == m_ident;
571 }
572 };
573
574 void
list_tcs(void)575 tp::list_tcs(void)
576 {
577 tc_vector tcs = init_tcs();
578 detail::atf_tp_writer writer(std::cout);
579
580 for (tc_vector::const_iterator iter = tcs.begin();
581 iter != tcs.end(); iter++) {
582 const impl::vars_map vars = (*iter)->get_md_vars();
583
584 {
585 impl::vars_map::const_iterator iter2 = vars.find("ident");
586 INV(iter2 != vars.end());
587 writer.start_tc((*iter2).second);
588 }
589
590 for (impl::vars_map::const_iterator iter2 = vars.begin();
591 iter2 != vars.end(); iter2++) {
592 const std::string& key = (*iter2).first;
593 if (key != "ident")
594 writer.tc_meta_data(key, (*iter2).second);
595 }
596
597 writer.end_tc();
598 }
599 }
600
601 impl::tc*
find_tc(tc_vector tcs,const std::string & name)602 tp::find_tc(tc_vector tcs, const std::string& name)
603 {
604 std::vector< std::string > ids;
605 for (tc_vector::iterator iter = tcs.begin();
606 iter != tcs.end(); iter++) {
607 impl::tc* tc = *iter;
608
609 if (tc->get_md_var("ident") == name)
610 return tc;
611 }
612 throw atf::application::usage_error("Unknown test case `%s'",
613 name.c_str());
614 }
615
616 std::pair< std::string, tp::tc_part >
process_tcarg(const std::string & tcarg)617 tp::process_tcarg(const std::string& tcarg)
618 {
619 const std::string::size_type pos = tcarg.find(':');
620 if (pos == std::string::npos) {
621 return std::make_pair(tcarg, BODY);
622 } else {
623 const std::string tcname = tcarg.substr(0, pos);
624
625 const std::string partname = tcarg.substr(pos + 1);
626 if (partname == "body")
627 return std::make_pair(tcname, BODY);
628 else if (partname == "cleanup")
629 return std::make_pair(tcname, CLEANUP);
630 else {
631 using atf::application::usage_error;
632 throw usage_error("Invalid test case part `%s'", partname.c_str());
633 }
634 }
635 }
636
637 int
run_tc(const std::string & tcarg)638 tp::run_tc(const std::string& tcarg)
639 {
640 const std::pair< std::string, tc_part > fields = process_tcarg(tcarg);
641
642 impl::tc* tc = find_tc(init_tcs(), fields.first);
643
644 if (!atf::env::has("__RUNNING_INSIDE_ATF_RUN") || atf::env::get(
645 "__RUNNING_INSIDE_ATF_RUN") != "internal-yes-value")
646 {
647 std::cerr << m_prog_name << ": WARNING: Running test cases without "
648 "atf-run(1) is unsupported\n";
649 std::cerr << m_prog_name << ": WARNING: No isolation nor timeout "
650 "control is being applied; you may get unexpected failures; see "
651 "atf-test-case(4)\n";
652 }
653
654 try {
655 switch (fields.second) {
656 case BODY:
657 tc->run(m_resfile.str());
658 break;
659 case CLEANUP:
660 tc->run_cleanup();
661 break;
662 default:
663 UNREACHABLE;
664 }
665 return EXIT_SUCCESS;
666 } catch (const std::runtime_error& e) {
667 std::cerr << "ERROR: " << e.what() << "\n";
668 return EXIT_FAILURE;
669 }
670 }
671
672 int
main(void)673 tp::main(void)
674 {
675 using atf::application::usage_error;
676
677 int errcode;
678
679 handle_srcdir();
680
681 if (m_lflag) {
682 if (m_argc > 0)
683 throw usage_error("Cannot provide test case names with -l");
684
685 list_tcs();
686 errcode = EXIT_SUCCESS;
687 } else {
688 if (m_argc == 0)
689 throw usage_error("Must provide a test case name");
690 else if (m_argc > 1)
691 throw usage_error("Cannot provide more than one test case name");
692 INV(m_argc == 1);
693
694 errcode = run_tc(m_argv[0]);
695 }
696
697 return errcode;
698 }
699
700 namespace atf {
701 namespace tests {
702 int run_tp(int, char* const*, void (*)(tp::tc_vector&));
703 }
704 }
705
706 int
run_tp(int argc,char * const * argv,void (* add_tcs)(tp::tc_vector &))707 impl::run_tp(int argc, char* const* argv, void (*add_tcs)(tp::tc_vector&))
708 {
709 return tp(add_tcs).run(argc, argv);
710 }
711