xref: /freebsd/contrib/atf/atf-c++/tests.cpp (revision 2449fa9c4d8e60cca863396dc3c7d67cbc16359f)
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/env.hpp"
62 #include "atf-c++/detail/exceptions.hpp"
63 #include "atf-c++/detail/fs.hpp"
64 #include "atf-c++/detail/sanity.hpp"
65 #include "atf-c++/detail/text.hpp"
66 
67 #if defined(HAVE_GNU_GETOPT)
68 #   define GETOPT_POSIX "+"
69 #else
70 #   define GETOPT_POSIX ""
71 #endif
72 
73 namespace impl = atf::tests;
74 namespace detail = atf::tests::detail;
75 #define IMPL_NAME "atf::tests"
76 
77 using atf::application::usage_error;
78 
79 // ------------------------------------------------------------------------
80 // The "atf_tp_writer" class.
81 // ------------------------------------------------------------------------
82 
83 detail::atf_tp_writer::atf_tp_writer(std::ostream& os) :
84     m_os(os),
85     m_is_first(true)
86 {
87     m_os << "Content-Type: application/X-atf-tp; version=\"1\"\n\n";
88 }
89 
90 void
91 detail::atf_tp_writer::start_tc(const std::string& ident)
92 {
93     if (!m_is_first)
94         m_os << "\n";
95     m_os << "ident: " << ident << "\n";
96     m_os.flush();
97 }
98 
99 void
100 detail::atf_tp_writer::end_tc(void)
101 {
102     if (m_is_first)
103         m_is_first = false;
104 }
105 
106 void
107 detail::atf_tp_writer::tc_meta_data(const std::string& name,
108                                     const std::string& value)
109 {
110     PRE(name != "ident");
111     m_os << name << ": " << value << "\n";
112     m_os.flush();
113 }
114 
115 // ------------------------------------------------------------------------
116 // Free helper functions.
117 // ------------------------------------------------------------------------
118 
119 std::string Program_Name;
120 
121 static void
122 set_program_name(const char* argv0)
123 {
124     const std::string program_name = atf::fs::path(argv0).leaf_name();
125     Program_Name = program_name;
126 }
127 
128 bool
129 detail::match(const std::string& regexp, const std::string& str)
130 {
131     return atf::text::match(str, regexp);
132 }
133 
134 // ------------------------------------------------------------------------
135 // The "tc" class.
136 // ------------------------------------------------------------------------
137 
138 static std::map< atf_tc_t*, impl::tc* > wraps;
139 static std::map< const atf_tc_t*, const impl::tc* > cwraps;
140 
141 struct impl::tc_impl {
142 private:
143     // Non-copyable.
144     tc_impl(const tc_impl&);
145     tc_impl& operator=(const tc_impl&);
146 
147 public:
148     std::string m_ident;
149     atf_tc_t m_tc;
150     bool m_has_cleanup;
151 
152     tc_impl(const std::string& ident, const bool has_cleanup) :
153         m_ident(ident),
154         m_has_cleanup(has_cleanup)
155     {
156     }
157 
158     static void
159     wrap_head(atf_tc_t *tc)
160     {
161         std::map< atf_tc_t*, impl::tc* >::iterator iter = wraps.find(tc);
162         INV(iter != wraps.end());
163         (*iter).second->head();
164     }
165 
166     static void
167     wrap_body(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->body();
173     }
174 
175     static void
176     wrap_cleanup(const atf_tc_t *tc)
177     {
178         std::map< const atf_tc_t*, const impl::tc* >::const_iterator iter =
179             cwraps.find(tc);
180         INV(iter != cwraps.end());
181         (*iter).second->cleanup();
182     }
183 };
184 
185 impl::tc::tc(const std::string& ident, const bool has_cleanup) :
186     pimpl(new tc_impl(ident, has_cleanup))
187 {
188 }
189 
190 impl::tc::~tc(void)
191 {
192     cwraps.erase(&pimpl->m_tc);
193     wraps.erase(&pimpl->m_tc);
194 
195     atf_tc_fini(&pimpl->m_tc);
196 }
197 
198 void
199 impl::tc::init(const vars_map& config)
200 {
201     atf_error_t err;
202 
203     std::vector< const char * > array;
204     array.reserve((config.size() * 2) + 1);
205 
206     for (vars_map::const_iterator iter = config.begin();
207          iter != config.end(); iter++) {
208          array.push_back((*iter).first.c_str());
209          array.push_back((*iter).second.c_str());
210     }
211     array.push_back(nullptr);
212 
213     wraps[&pimpl->m_tc] = this;
214     cwraps[&pimpl->m_tc] = this;
215 
216     err = atf_tc_init(&pimpl->m_tc, pimpl->m_ident.c_str(), pimpl->wrap_head,
217         pimpl->wrap_body, pimpl->m_has_cleanup ? pimpl->wrap_cleanup : NULL,
218         array.data());
219     if (atf_is_error(err))
220         throw_atf_error(err);
221 }
222 
223 bool
224 impl::tc::has_config_var(const std::string& var)
225     const
226 {
227     return atf_tc_has_config_var(&pimpl->m_tc, var.c_str());
228 }
229 
230 bool
231 impl::tc::has_md_var(const std::string& var)
232     const
233 {
234     return atf_tc_has_md_var(&pimpl->m_tc, var.c_str());
235 }
236 
237 const std::string
238 impl::tc::get_config_var(const std::string& var)
239     const
240 {
241     return atf_tc_get_config_var(&pimpl->m_tc, var.c_str());
242 }
243 
244 const std::string
245 impl::tc::get_config_var(const std::string& var, const std::string& defval)
246     const
247 {
248     return atf_tc_get_config_var_wd(&pimpl->m_tc, var.c_str(), defval.c_str());
249 }
250 
251 const std::string
252 impl::tc::get_md_var(const std::string& var)
253     const
254 {
255     return atf_tc_get_md_var(&pimpl->m_tc, var.c_str());
256 }
257 
258 const impl::vars_map
259 impl::tc::get_md_vars(void)
260     const
261 {
262     vars_map vars;
263 
264     char **array = atf_tc_get_md_vars(&pimpl->m_tc);
265     try {
266         char **ptr;
267         for (ptr = array; *ptr != NULL; ptr += 2)
268             vars[*ptr] = *(ptr + 1);
269         atf_utils_free_charpp(array);
270     } catch (...) {
271         atf_utils_free_charpp(array);
272         throw;
273     }
274 
275     return vars;
276 }
277 
278 void
279 impl::tc::set_md_var(const std::string& var, const std::string& val)
280 {
281     atf_error_t err = atf_tc_set_md_var(&pimpl->m_tc, var.c_str(), val.c_str());
282     if (atf_is_error(err))
283         throw_atf_error(err);
284 }
285 
286 void
287 impl::tc::run(const std::string& resfile)
288     const
289 {
290     atf_error_t err = atf_tc_run(&pimpl->m_tc, resfile.c_str());
291     if (atf_is_error(err))
292         throw_atf_error(err);
293 }
294 
295 void
296 impl::tc::run_cleanup(void)
297     const
298 {
299     atf_error_t err = atf_tc_cleanup(&pimpl->m_tc);
300     if (atf_is_error(err))
301         throw_atf_error(err);
302 }
303 
304 void
305 impl::tc::head(void)
306 {
307 }
308 
309 void
310 impl::tc::cleanup(void)
311     const
312 {
313 }
314 
315 void
316 impl::tc::require_kmod(const std::string& kmod)
317     const
318 {
319     atf_tc_require_kmod(kmod.c_str());
320 }
321 
322 void
323 impl::tc::require_prog(const std::string& prog)
324     const
325 {
326     atf_tc_require_prog(prog.c_str());
327 }
328 
329 void
330 impl::tc::pass(void)
331 {
332     atf_tc_pass();
333 }
334 
335 void
336 impl::tc::fail(const std::string& reason)
337 {
338     atf_tc_fail("%s", reason.c_str());
339 }
340 
341 void
342 impl::tc::fail_nonfatal(const std::string& reason)
343 {
344     atf_tc_fail_nonfatal("%s", reason.c_str());
345 }
346 
347 void
348 impl::tc::skip(const std::string& reason)
349 {
350     atf_tc_skip("%s", reason.c_str());
351 }
352 
353 void
354 impl::tc::check_errno(const char* file, const int line, const int exp_errno,
355                       const char* expr_str, const bool result)
356 {
357     atf_tc_check_errno(file, line, exp_errno, expr_str, result);
358 }
359 
360 void
361 impl::tc::require_errno(const char* file, const int line, const int exp_errno,
362                         const char* expr_str, const bool result)
363 {
364     atf_tc_require_errno(file, line, exp_errno, expr_str, result);
365 }
366 
367 void
368 impl::tc::expect_pass(void)
369 {
370     atf_tc_expect_pass();
371 }
372 
373 void
374 impl::tc::expect_fail(const std::string& reason)
375 {
376     atf_tc_expect_fail("%s", reason.c_str());
377 }
378 
379 void
380 impl::tc::expect_exit(const int exitcode, const std::string& reason)
381 {
382     atf_tc_expect_exit(exitcode, "%s", reason.c_str());
383 }
384 
385 void
386 impl::tc::expect_signal(const int signo, const std::string& reason)
387 {
388     atf_tc_expect_signal(signo, "%s", reason.c_str());
389 }
390 
391 void
392 impl::tc::expect_death(const std::string& reason)
393 {
394     atf_tc_expect_death("%s", reason.c_str());
395 }
396 
397 void
398 impl::tc::expect_timeout(const std::string& reason)
399 {
400     atf_tc_expect_timeout("%s", reason.c_str());
401 }
402 
403 // ------------------------------------------------------------------------
404 // Test program main code.
405 // ------------------------------------------------------------------------
406 
407 namespace {
408 
409 typedef std::vector< impl::tc * > tc_vector;
410 
411 enum tc_part { BODY, CLEANUP };
412 
413 static void
414 parse_vflag(const std::string& str, atf::tests::vars_map& vars)
415 {
416     if (str.empty())
417         throw std::runtime_error("-v requires a non-empty argument");
418 
419     std::vector< std::string > ws = atf::text::split(str, "=");
420     if (ws.size() == 1 && str[str.length() - 1] == '=') {
421         vars[ws[0]] = "";
422     } else {
423         if (ws.size() != 2)
424             throw std::runtime_error("-v requires an argument of the form "
425                                      "var=value");
426 
427         vars[ws[0]] = ws[1];
428     }
429 }
430 
431 static atf::fs::path
432 handle_srcdir(const char* argv0, const std::string& srcdir_arg)
433 {
434     atf::fs::path srcdir(".");
435 
436     if (srcdir_arg.empty()) {
437         srcdir = atf::fs::path(argv0).branch_path();
438         if (srcdir.leaf_name() == ".libs")
439             srcdir = srcdir.branch_path();
440     } else
441         srcdir = atf::fs::path(srcdir_arg);
442 
443     if (!atf::fs::exists(srcdir / Program_Name))
444         throw usage_error("Cannot find the test program in the source "
445                           "directory `%s'", srcdir.c_str());
446 
447     if (!srcdir.is_absolute())
448         srcdir = srcdir.to_absolute();
449 
450     return srcdir;
451 }
452 
453 static void
454 init_tcs(void (*add_tcs)(tc_vector&), tc_vector& tcs,
455          const atf::tests::vars_map& vars)
456 {
457     add_tcs(tcs);
458     for (auto& tc : tcs) {
459         tc->init(vars);
460     }
461 }
462 
463 static int
464 list_tcs(const tc_vector& tcs)
465 {
466     detail::atf_tp_writer writer(std::cout);
467 
468     for (tc_vector::const_iterator iter = tcs.begin();
469          iter != tcs.end(); iter++) {
470         const impl::vars_map vars = (*iter)->get_md_vars();
471 
472         {
473             impl::vars_map::const_iterator iter2 = vars.find("ident");
474             INV(iter2 != vars.end());
475             writer.start_tc((*iter2).second);
476         }
477 
478         for (impl::vars_map::const_iterator iter2 = vars.begin();
479              iter2 != vars.end(); iter2++) {
480             const std::string& key = (*iter2).first;
481             if (key != "ident")
482                 writer.tc_meta_data(key, (*iter2).second);
483         }
484 
485         writer.end_tc();
486     }
487 
488     return EXIT_SUCCESS;
489 }
490 
491 static impl::tc*
492 find_tc(tc_vector tcs, const std::string& name)
493 {
494     std::vector< std::string > ids;
495     for (tc_vector::iterator iter = tcs.begin();
496          iter != tcs.end(); iter++) {
497         impl::tc* tc = *iter;
498 
499         if (tc->get_md_var("ident") == name)
500             return tc;
501     }
502     throw usage_error("Unknown test case `%s'", name.c_str());
503 }
504 
505 static std::pair< std::string, tc_part >
506 process_tcarg(const std::string& tcarg)
507 {
508     const std::string::size_type pos = tcarg.find(':');
509     if (pos == std::string::npos) {
510         return std::make_pair(tcarg, BODY);
511     } else {
512         const std::string tcname = tcarg.substr(0, pos);
513 
514         const std::string partname = tcarg.substr(pos + 1);
515         if (partname == "body")
516             return std::make_pair(tcname, BODY);
517         else if (partname == "cleanup")
518             return std::make_pair(tcname, CLEANUP);
519         else {
520             throw usage_error("Invalid test case part `%s'", partname.c_str());
521         }
522     }
523 }
524 
525 static int
526 run_tc(tc_vector& tcs, const std::string& tcarg, const atf::fs::path& resfile)
527 {
528     const std::pair< std::string, tc_part > fields = process_tcarg(tcarg);
529 
530     impl::tc* tc = find_tc(tcs, fields.first);
531 
532     if (!atf::env::has("__RUNNING_INSIDE_ATF_RUN") || atf::env::get(
533         "__RUNNING_INSIDE_ATF_RUN") != "internal-yes-value")
534     {
535         std::cerr << Program_Name << ": WARNING: Running test cases outside "
536             "of kyua(1) is unsupported\n";
537         std::cerr << Program_Name << ": WARNING: No isolation nor timeout "
538             "control is being applied; you may get unexpected failures; see "
539             "atf-test-case(4)\n";
540     }
541 
542     switch (fields.second) {
543     case BODY:
544         tc->run(resfile.str());
545         break;
546     case CLEANUP:
547         tc->run_cleanup();
548         break;
549     default:
550         UNREACHABLE;
551     }
552     return EXIT_SUCCESS;
553 }
554 
555 static int
556 safe_main(int argc, char** argv, void (*add_tcs)(tc_vector&))
557 {
558     const char* argv0 = argv[0];
559 
560     bool lflag = false;
561     atf::fs::path resfile("/dev/stdout");
562     std::string srcdir_arg;
563     atf::tests::vars_map vars;
564 
565     int ch;
566     int old_opterr;
567 
568     old_opterr = opterr;
569     ::opterr = 0;
570     while ((ch = ::getopt(argc, argv, GETOPT_POSIX ":lr:s:v:")) != -1) {
571         switch (ch) {
572         case 'l':
573             lflag = true;
574             break;
575 
576         case 'r':
577             resfile = atf::fs::path(::optarg);
578             break;
579 
580         case 's':
581             srcdir_arg = ::optarg;
582             break;
583 
584         case 'v':
585             parse_vflag(::optarg, vars);
586             break;
587 
588         case ':':
589             throw usage_error("Option -%c requires an argument.", ::optopt);
590             break;
591 
592         case '?':
593         default:
594             throw usage_error("Unknown option -%c.", ::optopt);
595         }
596     }
597     argc -= optind;
598     argv += optind;
599 
600     // Clear getopt state just in case the test wants to use it.
601     ::opterr = old_opterr;
602     ::optind = 1;
603 #if defined(HAVE_OPTRESET)
604     ::optreset = 1;
605 #endif
606 
607     vars["srcdir"] = handle_srcdir(argv0, srcdir_arg).str();
608 
609     int errcode;
610 
611     if (lflag) {
612         if (argc > 0)
613             throw usage_error("Cannot provide test case names with -l");
614     } else {
615         if (argc == 0)
616             throw usage_error("Must provide a test case name");
617         else if (argc > 1)
618             throw usage_error("Cannot provide more than one test case name");
619         INV(argc == 1);
620     }
621     tc_vector tcs;
622     try {
623         init_tcs(add_tcs, tcs, vars);
624         errcode = lflag ? list_tcs(tcs) : run_tc(tcs, argv[0], resfile);
625     } catch (...) {
626         for (auto& tc: tcs) {
627             delete tc;
628         }
629         throw;
630     }
631 
632     return errcode;
633 }
634 
635 }  // anonymous namespace
636 
637 namespace atf {
638     namespace tests {
639         int run_tp(int, char**, void (*)(tc_vector&));
640     }
641 }
642 
643 int
644 impl::run_tp(int argc, char** argv, void (*add_tcs)(tc_vector&))
645 {
646     try {
647         set_program_name(argv[0]);
648         return ::safe_main(argc, argv, add_tcs);
649     } catch (const usage_error& e) {
650         std::cerr
651             << Program_Name << ": ERROR: " << e.what() << '\n'
652             << Program_Name << ": See atf-test-program(1) for usage details.\n";
653         return EXIT_FAILURE;
654     }
655 }
656