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