xref: /freebsd/contrib/atf/atf-c++/tests.cpp (revision 83a1ee578c9d1ab7013e997289c7cd470c0e6902)
1 // Copyright (c) 2007 The NetBSD Foundation, Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions
6 // are met:
7 // 1. Redistributions of source code must retain the above copyright
8 //    notice, this list of conditions and the following disclaimer.
9 // 2. Redistributions in binary form must reproduce the above copyright
10 //    notice, this list of conditions and the following disclaimer in the
11 //    documentation and/or other materials provided with the distribution.
12 //
13 // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
14 // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
15 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
18 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
20 // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
22 // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
24 // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 
26 #include "atf-c++/tests.hpp"
27 
28 #if defined(HAVE_CONFIG_H)
29 #include "config.h"
30 #endif
31 
32 extern "C" {
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/time.h>
36 #include <sys/wait.h>
37 #include <signal.h>
38 #include <unistd.h>
39 }
40 
41 #include <algorithm>
42 #include <cctype>
43 #include <cerrno>
44 #include <cstdlib>
45 #include <cstring>
46 #include <fstream>
47 #include <iostream>
48 #include <map>
49 #include <memory>
50 #include <sstream>
51 #include <stdexcept>
52 #include <vector>
53 
54 extern "C" {
55 #include "atf-c/error.h"
56 #include "atf-c/tc.h"
57 #include "atf-c/utils.h"
58 }
59 
60 #include "atf-c++/detail/application.hpp"
61 #include "atf-c++/detail/auto_array.hpp"
62 #include "atf-c++/detail/env.hpp"
63 #include "atf-c++/detail/exceptions.hpp"
64 #include "atf-c++/detail/fs.hpp"
65 #include "atf-c++/detail/sanity.hpp"
66 #include "atf-c++/detail/text.hpp"
67 
68 #if defined(HAVE_GNU_GETOPT)
69 #   define GETOPT_POSIX "+"
70 #else
71 #   define GETOPT_POSIX ""
72 #endif
73 
74 namespace impl = atf::tests;
75 namespace detail = atf::tests::detail;
76 #define IMPL_NAME "atf::tests"
77 
78 using atf::application::usage_error;
79 
80 // ------------------------------------------------------------------------
81 // The "atf_tp_writer" class.
82 // ------------------------------------------------------------------------
83 
atf_tp_writer(std::ostream & os)84 detail::atf_tp_writer::atf_tp_writer(std::ostream& os) :
85     m_os(os),
86     m_is_first(true)
87 {
88     m_os << "Content-Type: application/X-atf-tp; version=\"1\"\n\n";
89 }
90 
91 void
start_tc(const std::string & ident)92 detail::atf_tp_writer::start_tc(const std::string& ident)
93 {
94     if (!m_is_first)
95         m_os << "\n";
96     m_os << "ident: " << ident << "\n";
97     m_os.flush();
98 }
99 
100 void
end_tc(void)101 detail::atf_tp_writer::end_tc(void)
102 {
103     if (m_is_first)
104         m_is_first = false;
105 }
106 
107 void
tc_meta_data(const std::string & name,const std::string & value)108 detail::atf_tp_writer::tc_meta_data(const std::string& name,
109                                     const std::string& value)
110 {
111     PRE(name != "ident");
112     m_os << name << ": " << value << "\n";
113     m_os.flush();
114 }
115 
116 // ------------------------------------------------------------------------
117 // Free helper functions.
118 // ------------------------------------------------------------------------
119 
120 std::string Program_Name;
121 
122 static void
set_program_name(const char * argv0)123 set_program_name(const char* argv0)
124 {
125     const std::string program_name = atf::fs::path(argv0).leaf_name();
126     // Libtool workaround: if running from within the source tree (binaries
127     // that are not installed yet), skip the "lt-" prefix added to files in
128     // the ".libs" directory to show the real (not temporary) name.
129     if (program_name.substr(0, 3) == "lt-")
130         Program_Name = program_name.substr(3);
131     else
132         Program_Name = program_name;
133 }
134 
135 bool
match(const std::string & regexp,const std::string & str)136 detail::match(const std::string& regexp, const std::string& str)
137 {
138     return atf::text::match(str, regexp);
139 }
140 
141 // ------------------------------------------------------------------------
142 // The "tc" class.
143 // ------------------------------------------------------------------------
144 
145 static std::map< atf_tc_t*, impl::tc* > wraps;
146 static std::map< const atf_tc_t*, const impl::tc* > cwraps;
147 
148 struct impl::tc_impl {
149 private:
150     // Non-copyable.
151     tc_impl(const tc_impl&);
152     tc_impl& operator=(const tc_impl&);
153 
154 public:
155     std::string m_ident;
156     atf_tc_t m_tc;
157     bool m_has_cleanup;
158 
tc_implimpl::tc_impl159     tc_impl(const std::string& ident, const bool has_cleanup) :
160         m_ident(ident),
161         m_has_cleanup(has_cleanup)
162     {
163     }
164 
165     static void
wrap_headimpl::tc_impl166     wrap_head(atf_tc_t *tc)
167     {
168         std::map< atf_tc_t*, impl::tc* >::iterator iter = wraps.find(tc);
169         INV(iter != wraps.end());
170         (*iter).second->head();
171     }
172 
173     static void
wrap_bodyimpl::tc_impl174     wrap_body(const atf_tc_t *tc)
175     {
176         std::map< const atf_tc_t*, const impl::tc* >::const_iterator iter =
177             cwraps.find(tc);
178         INV(iter != cwraps.end());
179         (*iter).second->body();
180     }
181 
182     static void
wrap_cleanupimpl::tc_impl183     wrap_cleanup(const atf_tc_t *tc)
184     {
185         std::map< const atf_tc_t*, const impl::tc* >::const_iterator iter =
186             cwraps.find(tc);
187         INV(iter != cwraps.end());
188         (*iter).second->cleanup();
189     }
190 };
191 
tc(const std::string & ident,const bool has_cleanup)192 impl::tc::tc(const std::string& ident, const bool has_cleanup) :
193     pimpl(new tc_impl(ident, has_cleanup))
194 {
195 }
196 
~tc(void)197 impl::tc::~tc(void)
198 {
199     cwraps.erase(&pimpl->m_tc);
200     wraps.erase(&pimpl->m_tc);
201 
202     atf_tc_fini(&pimpl->m_tc);
203 }
204 
205 void
init(const vars_map & config)206 impl::tc::init(const vars_map& config)
207 {
208     atf_error_t err;
209 
210     auto_array< const char * > array(new const char*[(config.size() * 2) + 1]);
211     const char **ptr = array.get();
212     for (vars_map::const_iterator iter = config.begin();
213          iter != config.end(); iter++) {
214          *ptr = (*iter).first.c_str();
215          *(ptr + 1) = (*iter).second.c_str();
216          ptr += 2;
217     }
218     *ptr = NULL;
219 
220     wraps[&pimpl->m_tc] = this;
221     cwraps[&pimpl->m_tc] = this;
222 
223     err = atf_tc_init(&pimpl->m_tc, pimpl->m_ident.c_str(), pimpl->wrap_head,
224         pimpl->wrap_body, pimpl->m_has_cleanup ? pimpl->wrap_cleanup : NULL,
225         array.get());
226     if (atf_is_error(err))
227         throw_atf_error(err);
228 }
229 
230 bool
has_config_var(const std::string & var) const231 impl::tc::has_config_var(const std::string& var)
232     const
233 {
234     return atf_tc_has_config_var(&pimpl->m_tc, var.c_str());
235 }
236 
237 bool
has_md_var(const std::string & var) const238 impl::tc::has_md_var(const std::string& var)
239     const
240 {
241     return atf_tc_has_md_var(&pimpl->m_tc, var.c_str());
242 }
243 
244 const std::string
get_config_var(const std::string & var) const245 impl::tc::get_config_var(const std::string& var)
246     const
247 {
248     return atf_tc_get_config_var(&pimpl->m_tc, var.c_str());
249 }
250 
251 const std::string
get_config_var(const std::string & var,const std::string & defval) const252 impl::tc::get_config_var(const std::string& var, const std::string& defval)
253     const
254 {
255     return atf_tc_get_config_var_wd(&pimpl->m_tc, var.c_str(), defval.c_str());
256 }
257 
258 const std::string
get_md_var(const std::string & var) const259 impl::tc::get_md_var(const std::string& var)
260     const
261 {
262     return atf_tc_get_md_var(&pimpl->m_tc, var.c_str());
263 }
264 
265 const impl::vars_map
get_md_vars(void) const266 impl::tc::get_md_vars(void)
267     const
268 {
269     vars_map vars;
270 
271     char **array = atf_tc_get_md_vars(&pimpl->m_tc);
272     try {
273         char **ptr;
274         for (ptr = array; *ptr != NULL; ptr += 2)
275             vars[*ptr] = *(ptr + 1);
276     } catch (...) {
277         atf_utils_free_charpp(array);
278         throw;
279     }
280 
281     return vars;
282 }
283 
284 void
set_md_var(const std::string & var,const std::string & val)285 impl::tc::set_md_var(const std::string& var, const std::string& val)
286 {
287     atf_error_t err = atf_tc_set_md_var(&pimpl->m_tc, var.c_str(), val.c_str());
288     if (atf_is_error(err))
289         throw_atf_error(err);
290 }
291 
292 void
run(const std::string & resfile) const293 impl::tc::run(const std::string& resfile)
294     const
295 {
296     atf_error_t err = atf_tc_run(&pimpl->m_tc, resfile.c_str());
297     if (atf_is_error(err))
298         throw_atf_error(err);
299 }
300 
301 void
run_cleanup(void) const302 impl::tc::run_cleanup(void)
303     const
304 {
305     atf_error_t err = atf_tc_cleanup(&pimpl->m_tc);
306     if (atf_is_error(err))
307         throw_atf_error(err);
308 }
309 
310 void
head(void)311 impl::tc::head(void)
312 {
313 }
314 
315 void
cleanup(void) const316 impl::tc::cleanup(void)
317     const
318 {
319 }
320 
321 void
require_kmod(const std::string & kmod) const322 impl::tc::require_kmod(const std::string& kmod)
323     const
324 {
325     atf_tc_require_kmod(kmod.c_str());
326 }
327 
328 void
require_prog(const std::string & prog) const329 impl::tc::require_prog(const std::string& prog)
330     const
331 {
332     atf_tc_require_prog(prog.c_str());
333 }
334 
335 void
pass(void)336 impl::tc::pass(void)
337 {
338     atf_tc_pass();
339 }
340 
341 void
fail(const std::string & reason)342 impl::tc::fail(const std::string& reason)
343 {
344     atf_tc_fail("%s", reason.c_str());
345 }
346 
347 void
fail_nonfatal(const std::string & reason)348 impl::tc::fail_nonfatal(const std::string& reason)
349 {
350     atf_tc_fail_nonfatal("%s", reason.c_str());
351 }
352 
353 void
skip(const std::string & reason)354 impl::tc::skip(const std::string& reason)
355 {
356     atf_tc_skip("%s", reason.c_str());
357 }
358 
359 void
check_errno(const char * file,const int line,const int exp_errno,const char * expr_str,const bool result)360 impl::tc::check_errno(const char* file, const int line, const int exp_errno,
361                       const char* expr_str, const bool result)
362 {
363     atf_tc_check_errno(file, line, exp_errno, expr_str, result);
364 }
365 
366 void
require_errno(const char * file,const int line,const int exp_errno,const char * expr_str,const bool result)367 impl::tc::require_errno(const char* file, const int line, const int exp_errno,
368                         const char* expr_str, const bool result)
369 {
370     atf_tc_require_errno(file, line, exp_errno, expr_str, result);
371 }
372 
373 void
expect_pass(void)374 impl::tc::expect_pass(void)
375 {
376     atf_tc_expect_pass();
377 }
378 
379 void
expect_fail(const std::string & reason)380 impl::tc::expect_fail(const std::string& reason)
381 {
382     atf_tc_expect_fail("%s", reason.c_str());
383 }
384 
385 void
expect_exit(const int exitcode,const std::string & reason)386 impl::tc::expect_exit(const int exitcode, const std::string& reason)
387 {
388     atf_tc_expect_exit(exitcode, "%s", reason.c_str());
389 }
390 
391 void
expect_signal(const int signo,const std::string & reason)392 impl::tc::expect_signal(const int signo, const std::string& reason)
393 {
394     atf_tc_expect_signal(signo, "%s", reason.c_str());
395 }
396 
397 void
expect_death(const std::string & reason)398 impl::tc::expect_death(const std::string& reason)
399 {
400     atf_tc_expect_death("%s", reason.c_str());
401 }
402 
403 void
expect_timeout(const std::string & reason)404 impl::tc::expect_timeout(const std::string& reason)
405 {
406     atf_tc_expect_timeout("%s", reason.c_str());
407 }
408 
409 // ------------------------------------------------------------------------
410 // Test program main code.
411 // ------------------------------------------------------------------------
412 
413 namespace {
414 
415 typedef std::vector< impl::tc * > tc_vector;
416 
417 enum tc_part { BODY, CLEANUP };
418 
419 static void
parse_vflag(const std::string & str,atf::tests::vars_map & vars)420 parse_vflag(const std::string& str, atf::tests::vars_map& vars)
421 {
422     if (str.empty())
423         throw std::runtime_error("-v requires a non-empty argument");
424 
425     std::vector< std::string > ws = atf::text::split(str, "=");
426     if (ws.size() == 1 && str[str.length() - 1] == '=') {
427         vars[ws[0]] = "";
428     } else {
429         if (ws.size() != 2)
430             throw std::runtime_error("-v requires an argument of the form "
431                                      "var=value");
432 
433         vars[ws[0]] = ws[1];
434     }
435 }
436 
437 static atf::fs::path
handle_srcdir(const char * argv0,const std::string & srcdir_arg)438 handle_srcdir(const char* argv0, const std::string& srcdir_arg)
439 {
440     atf::fs::path srcdir(".");
441 
442     if (srcdir_arg.empty()) {
443         srcdir = atf::fs::path(argv0).branch_path();
444         if (srcdir.leaf_name() == ".libs")
445             srcdir = srcdir.branch_path();
446     } else
447         srcdir = atf::fs::path(srcdir_arg);
448 
449     if (!atf::fs::exists(srcdir / Program_Name))
450         throw usage_error("Cannot find the test program in the source "
451                           "directory `%s'", srcdir.c_str());
452 
453     if (!srcdir.is_absolute())
454         srcdir = srcdir.to_absolute();
455 
456     return srcdir;
457 }
458 
459 static void
init_tcs(void (* add_tcs)(tc_vector &),tc_vector & tcs,const atf::tests::vars_map & vars)460 init_tcs(void (*add_tcs)(tc_vector&), tc_vector& tcs,
461          const atf::tests::vars_map& vars)
462 {
463     add_tcs(tcs);
464     for (tc_vector::iterator iter = tcs.begin(); iter != tcs.end(); iter++) {
465         impl::tc* tc = *iter;
466 
467         tc->init(vars);
468     }
469 }
470 
471 static int
list_tcs(const tc_vector & tcs)472 list_tcs(const tc_vector& tcs)
473 {
474     detail::atf_tp_writer writer(std::cout);
475 
476     for (tc_vector::const_iterator iter = tcs.begin();
477          iter != tcs.end(); iter++) {
478         const impl::vars_map vars = (*iter)->get_md_vars();
479 
480         {
481             impl::vars_map::const_iterator iter2 = vars.find("ident");
482             INV(iter2 != vars.end());
483             writer.start_tc((*iter2).second);
484         }
485 
486         for (impl::vars_map::const_iterator iter2 = vars.begin();
487              iter2 != vars.end(); iter2++) {
488             const std::string& key = (*iter2).first;
489             if (key != "ident")
490                 writer.tc_meta_data(key, (*iter2).second);
491         }
492 
493         writer.end_tc();
494     }
495 
496     return EXIT_SUCCESS;
497 }
498 
499 static impl::tc*
find_tc(tc_vector tcs,const std::string & name)500 find_tc(tc_vector tcs, const std::string& name)
501 {
502     std::vector< std::string > ids;
503     for (tc_vector::iterator iter = tcs.begin();
504          iter != tcs.end(); iter++) {
505         impl::tc* tc = *iter;
506 
507         if (tc->get_md_var("ident") == name)
508             return tc;
509     }
510     throw usage_error("Unknown test case `%s'", name.c_str());
511 }
512 
513 static std::pair< std::string, tc_part >
process_tcarg(const std::string & tcarg)514 process_tcarg(const std::string& tcarg)
515 {
516     const std::string::size_type pos = tcarg.find(':');
517     if (pos == std::string::npos) {
518         return std::make_pair(tcarg, BODY);
519     } else {
520         const std::string tcname = tcarg.substr(0, pos);
521 
522         const std::string partname = tcarg.substr(pos + 1);
523         if (partname == "body")
524             return std::make_pair(tcname, BODY);
525         else if (partname == "cleanup")
526             return std::make_pair(tcname, CLEANUP);
527         else {
528             throw usage_error("Invalid test case part `%s'", partname.c_str());
529         }
530     }
531 }
532 
533 static int
run_tc(tc_vector & tcs,const std::string & tcarg,const atf::fs::path & resfile)534 run_tc(tc_vector& tcs, const std::string& tcarg, const atf::fs::path& resfile)
535 {
536     const std::pair< std::string, tc_part > fields = process_tcarg(tcarg);
537 
538     impl::tc* tc = find_tc(tcs, fields.first);
539 
540     if (!atf::env::has("__RUNNING_INSIDE_ATF_RUN") || atf::env::get(
541         "__RUNNING_INSIDE_ATF_RUN") != "internal-yes-value")
542     {
543         std::cerr << Program_Name << ": WARNING: Running test cases outside "
544             "of kyua(1) is unsupported\n";
545         std::cerr << Program_Name << ": WARNING: No isolation nor timeout "
546             "control is being applied; you may get unexpected failures; see "
547             "atf-test-case(4)\n";
548     }
549 
550     switch (fields.second) {
551     case BODY:
552         tc->run(resfile.str());
553         break;
554     case CLEANUP:
555         tc->run_cleanup();
556         break;
557     default:
558         UNREACHABLE;
559     }
560     return EXIT_SUCCESS;
561 }
562 
563 static int
safe_main(int argc,char ** argv,void (* add_tcs)(tc_vector &))564 safe_main(int argc, char** argv, void (*add_tcs)(tc_vector&))
565 {
566     const char* argv0 = argv[0];
567 
568     bool lflag = false;
569     atf::fs::path resfile("/dev/stdout");
570     std::string srcdir_arg;
571     atf::tests::vars_map vars;
572 
573     int ch;
574     int old_opterr;
575 
576     old_opterr = opterr;
577     ::opterr = 0;
578     while ((ch = ::getopt(argc, argv, GETOPT_POSIX ":lr:s:v:")) != -1) {
579         switch (ch) {
580         case 'l':
581             lflag = true;
582             break;
583 
584         case 'r':
585             resfile = atf::fs::path(::optarg);
586             break;
587 
588         case 's':
589             srcdir_arg = ::optarg;
590             break;
591 
592         case 'v':
593             parse_vflag(::optarg, vars);
594             break;
595 
596         case ':':
597             throw usage_error("Option -%c requires an argument.", ::optopt);
598             break;
599 
600         case '?':
601         default:
602             throw usage_error("Unknown option -%c.", ::optopt);
603         }
604     }
605     argc -= optind;
606     argv += optind;
607 
608     // Clear getopt state just in case the test wants to use it.
609     ::opterr = old_opterr;
610     ::optind = 1;
611 #if defined(HAVE_OPTRESET)
612     ::optreset = 1;
613 #endif
614 
615     vars["srcdir"] = handle_srcdir(argv0, srcdir_arg).str();
616 
617     int errcode;
618 
619     tc_vector tcs;
620     if (lflag) {
621         if (argc > 0)
622             throw usage_error("Cannot provide test case names with -l");
623 
624         init_tcs(add_tcs, tcs, vars);
625         errcode = list_tcs(tcs);
626     } else {
627         if (argc == 0)
628             throw usage_error("Must provide a test case name");
629         else if (argc > 1)
630             throw usage_error("Cannot provide more than one test case name");
631         INV(argc == 1);
632 
633         init_tcs(add_tcs, tcs, vars);
634         errcode = run_tc(tcs, argv[0], resfile);
635     }
636     for (tc_vector::iterator iter = tcs.begin(); iter != tcs.end(); iter++) {
637         impl::tc* tc = *iter;
638 
639         delete tc;
640     }
641 
642     return errcode;
643 }
644 
645 }  // anonymous namespace
646 
647 namespace atf {
648     namespace tests {
649         int run_tp(int, char**, void (*)(tc_vector&));
650     }
651 }
652 
653 int
run_tp(int argc,char ** argv,void (* add_tcs)(tc_vector &))654 impl::run_tp(int argc, char** argv, void (*add_tcs)(tc_vector&))
655 {
656     try {
657         set_program_name(argv[0]);
658         return ::safe_main(argc, argv, add_tcs);
659     } catch (const usage_error& e) {
660         std::cerr
661             << Program_Name << ": ERROR: " << e.what() << '\n'
662             << Program_Name << ": See atf-test-program(1) for usage details.\n";
663         return EXIT_FAILURE;
664     }
665 }
666