xref: /freebsd/contrib/atf/atf-sh/atf-check.cpp (revision 952231086100bed7130212b5425e44ffd732913b)
1 // Copyright (c) 2008 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 extern "C" {
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 
30 #include <limits.h>
31 #include <signal.h>
32 #include <stdint.h>
33 #include <time.h>
34 #include <unistd.h>
35 }
36 
37 #include <cerrno>
38 #include <cstdlib>
39 #include <cstring>
40 #include <fstream>
41 #include <ios>
42 #include <iostream>
43 #include <iterator>
44 #include <list>
45 #include <memory>
46 #include <utility>
47 
48 #include "atf-c++/check.hpp"
49 #include "atf-c++/detail/application.hpp"
50 #include "atf-c++/detail/env.hpp"
51 #include "atf-c++/detail/exceptions.hpp"
52 #include "atf-c++/detail/fs.hpp"
53 #include "atf-c++/detail/process.hpp"
54 #include "atf-c++/detail/sanity.hpp"
55 #include "atf-c++/detail/text.hpp"
56 
57 static const useconds_t seconds_in_useconds = (1000 * 1000);
58 static const useconds_t mseconds_in_useconds = 1000;
59 static const useconds_t useconds_in_nseconds = 1000;
60 
61 // ------------------------------------------------------------------------
62 // Auxiliary functions.
63 // ------------------------------------------------------------------------
64 
65 namespace {
66 
67 enum status_check_t {
68     sc_exit,
69     sc_ignore,
70     sc_signal,
71 };
72 
73 struct status_check {
74     status_check_t type;
75     bool negated;
76     int value;
77     bool empty;
78 
status_check__anon7e9de9140111::status_check79     status_check(const status_check_t& p_type, const bool p_negated,
80                  const int p_value, const bool p_empty) :
81         type(p_type),
82         negated(p_negated),
83         value(p_value),
84         empty(p_empty)
85     {
86     }
87 };
88 
89 enum output_check_t {
90     oc_ignore,
91     oc_inline,
92     oc_file,
93     oc_empty,
94     oc_match,
95     oc_save
96 };
97 
98 struct output_check {
99     output_check_t type;
100     bool negated;
101     std::string value;
102 
output_check__anon7e9de9140111::output_check103     output_check(const output_check_t& p_type, const bool p_negated,
104                  const std::string& p_value) :
105         type(p_type),
106         negated(p_negated),
107         value(p_value)
108     {
109     }
110 };
111 
112 class temp_file : public std::ostream {
113     std::unique_ptr< atf::fs::path > m_path;
114     int m_fd;
115 
116 public:
temp_file(const char * pattern)117     temp_file(const char* pattern) :
118         std::ostream(NULL),
119         m_fd(-1)
120     {
121         const atf::fs::path file = atf::fs::path(
122             atf::env::get("TMPDIR", "/tmp")) / pattern;
123 
124         std::string file_s = file.str();
125         // C++14 returns const char* with `std::string::data()`.
126         //
127         // TODO(ngie): remove the else block and simplify once we are on C++17
128         // or later.
129 #if __cplusplus >= 201703L
130         char *file_ch_arr = file_s.data();
131 #else
132         char *file_ch_arr = &file_s[0];
133 #endif
134         m_fd = ::mkstemp(file_ch_arr);
135         if (m_fd == -1)
136             throw atf::system_error("atf_check::temp_file::temp_file(" +
137                                     file_s + ")", "mkstemp(3) failed",
138                                     errno);
139 
140         m_path.reset(new atf::fs::path(file_ch_arr));
141     }
142 
~temp_file(void)143     ~temp_file(void)
144     {
145         close();
146         try {
147             atf::fs::remove(*m_path);
148         } catch (const atf::system_error&) {
149             // Ignore deletion errors.
150         }
151     }
152 
153     const atf::fs::path&
get_path(void) const154     get_path(void) const
155     {
156         return *m_path;
157     }
158 
159     void
write(const std::string & text)160     write(const std::string& text)
161     {
162         if (::write(m_fd, text.c_str(), text.size()) == -1)
163             throw atf::system_error("atf_check", "write(2) failed", errno);
164     }
165 
166     void
close(void)167     close(void)
168     {
169         if (m_fd != -1) {
170             flush();
171             ::close(m_fd);
172             m_fd = -1;
173         }
174     }
175 };
176 
177 } // anonymous namespace
178 
179 static useconds_t
get_monotonic_useconds(void)180 get_monotonic_useconds(void)
181 {
182     struct timespec ts;
183     useconds_t res;
184     int rc;
185 
186     rc = clock_gettime(CLOCK_MONOTONIC, &ts);
187     if (rc != 0)
188         throw std::runtime_error("clock_gettime: " +
189             std::string(strerror(errno)));
190 
191     res = ts.tv_sec * seconds_in_useconds;
192     res += ts.tv_nsec / useconds_in_nseconds;
193     return res;
194 }
195 
196 static bool
timo_expired(useconds_t timeout)197 timo_expired(useconds_t timeout)
198 {
199 
200     if (get_monotonic_useconds() >= timeout)
201         return true;
202     return false;
203 }
204 
205 
206 static int
parse_exit_code(const std::string & str)207 parse_exit_code(const std::string& str)
208 {
209     try {
210         const int value = atf::text::to_type< int >(str);
211         if (value < 0 || value > 255)
212             throw std::range_error("Unused reason");
213         return value;
214     } catch (const std::range_error&) {
215         throw atf::application::usage_error("Invalid exit code for -s option; "
216             "must be an integer in the range [0, 255]");
217     }
218 }
219 
220 static struct name_number {
221     const char *name;
222     int signo;
223 } signal_names_to_numbers[] = {
224     { "hup", SIGHUP },
225     { "int", SIGINT },
226     { "quit", SIGQUIT },
227     { "trap", SIGTRAP },
228     { "abrt", SIGABRT },
229     { "kill", SIGKILL },
230     { "segv", SIGSEGV },
231     { "pipe", SIGPIPE },
232     { "alrm", SIGALRM },
233     { "term", SIGTERM },
234     { "usr1", SIGUSR1 },
235     { "usr2", SIGUSR2 },
236     { NULL, INT_MIN },
237 };
238 
239 static int
signal_name_to_number(const std::string & str)240 signal_name_to_number(const std::string& str)
241 {
242     struct name_number* iter = signal_names_to_numbers;
243     int signo = INT_MIN;
244     while (signo == INT_MIN && iter->name != NULL) {
245         if (str == iter->name || str == std::string("sig") + iter->name)
246             signo = iter->signo;
247         else
248             iter++;
249     }
250     return signo;
251 }
252 
253 static int
parse_signal(const std::string & str)254 parse_signal(const std::string& str)
255 {
256     const int signo = signal_name_to_number(str);
257     if (signo == INT_MIN) {
258         try {
259             return atf::text::to_type< int >(str);
260         } catch (const std::runtime_error&) {
261             throw atf::application::usage_error("Invalid signal name or number "
262                 "in -s option");
263         }
264     }
265     INV(signo != INT_MIN);
266     return signo;
267 }
268 
269 static status_check
parse_status_check_arg(const std::string & arg)270 parse_status_check_arg(const std::string& arg)
271 {
272     const std::string::size_type delimiter = arg.find(':');
273     bool negated = (arg.compare(0, 4, "not-") == 0);
274     const std::string action_str = arg.substr(0, delimiter);
275     const std::string action = negated ? action_str.substr(4) : action_str;
276     const std::string value_str = (
277         delimiter == std::string::npos ? "" : arg.substr(delimiter + 1));
278     int value;
279     bool empty = false;
280 
281     status_check_t type;
282     if (action == "eq") {
283         // Deprecated; use exit instead.  TODO: Remove after 0.10.
284         type = sc_exit;
285         if (negated)
286             throw atf::application::usage_error("Cannot negate eq checker");
287         negated = false;
288         value = parse_exit_code(value_str);
289     } else if (action == "exit") {
290         type = sc_exit;
291         if (value_str.empty()) {
292             value = INT_MIN;
293             empty = true;
294         } else
295             value = parse_exit_code(value_str);
296     } else if (action == "ignore") {
297         if (negated)
298             throw atf::application::usage_error("Cannot negate ignore checker");
299         type = sc_ignore;
300         value = INT_MIN;
301     } else if (action == "ne") {
302         // Deprecated; use not-exit instead.  TODO: Remove after 0.10.
303         type = sc_exit;
304         if (negated)
305             throw atf::application::usage_error("Cannot negate ne checker");
306         negated = true;
307         value = parse_exit_code(value_str);
308     } else if (action == "signal") {
309         type = sc_signal;
310         if (value_str.empty()) {
311             value = INT_MIN;
312             empty = true;
313         } else
314             value = parse_signal(value_str);
315     } else
316         throw atf::application::usage_error("Invalid status checker");
317 
318     return status_check(type, negated, value, empty);
319 }
320 
321 static
322 output_check
parse_output_check_arg(const std::string & arg)323 parse_output_check_arg(const std::string& arg)
324 {
325     const std::string::size_type delimiter = arg.find(':');
326     const bool negated = (arg.compare(0, 4, "not-") == 0);
327     const std::string action_str = arg.substr(0, delimiter);
328     const std::string action = negated ? action_str.substr(4) : action_str;
329 
330     output_check_t type;
331     if (action == "empty")
332         type = oc_empty;
333     else if (action == "file")
334         type = oc_file;
335     else if (action == "ignore") {
336         if (negated)
337             throw atf::application::usage_error("Cannot negate ignore checker");
338         type = oc_ignore;
339     } else if (action == "inline")
340         type = oc_inline;
341     else if (action == "match")
342         type = oc_match;
343     else if (action == "save") {
344         if (negated)
345             throw atf::application::usage_error("Cannot negate save checker");
346         type = oc_save;
347     } else
348         throw atf::application::usage_error("Invalid output checker");
349 
350     return output_check(type, negated, arg.substr(delimiter + 1));
351 }
352 
353 static void
parse_repeat_check_arg(const std::string & arg,useconds_t * m_timo,useconds_t * m_interval)354 parse_repeat_check_arg(const std::string& arg, useconds_t *m_timo,
355     useconds_t *m_interval)
356 {
357     const std::string::size_type delimiter = arg.find(':');
358     const bool has_interval = (delimiter != std::string::npos);
359     const std::string timo_str = arg.substr(0, delimiter);
360 
361     long l;
362     char *end;
363 
364     // There is no reason this couldn't be a non-integer number of seconds,
365     // this was just easy to do for now.
366     errno = 0;
367     l = strtol(timo_str.c_str(), &end, 10);
368     if (errno == ERANGE)
369         throw atf::application::usage_error("Bogus timeout in seconds");
370     else if (errno != 0)
371         throw atf::application::usage_error("Timeout must be a number");
372 
373     if (*end != 0)
374         throw atf::application::usage_error("Timeout must be a number");
375 
376     *m_timo = get_monotonic_useconds() + (l * seconds_in_useconds);
377     // 50 milliseconds is chosen arbitrarily.  There is a tradeoff between
378     // longer and shorter poll times.  A shorter poll time makes for faster
379     // tests.  A longer poll time makes for lower CPU overhead for the polled
380     // operation.  50ms is chosen with these tradeoffs in mind: on
381     // microcontrollers, the hope is that we can still avoid meaningful CPU use
382     // with a small test every 50ms.  And on typical fast x86 hardware, our
383     // tests can be much more precise with time wasted than they typically are
384     // without this feature.
385     *m_interval = 50 * mseconds_in_useconds;
386 
387     if (!has_interval)
388         return;
389 
390     const std::string intv_str = arg.substr(delimiter + 1, std::string::npos);
391 
392     // Same -- this could be non-integer milliseconds.
393     errno = 0;
394     l = strtol(intv_str.c_str(), &end, 10);
395     if (errno == ERANGE)
396         throw atf::application::usage_error(
397             "Bogus repeat interval in milliseconds");
398     else if (errno != 0)
399         throw atf::application::usage_error(
400             "Repeat interval must be a number");
401 
402     if (*end != 0)
403         throw atf::application::usage_error(
404             "Repeat interval must be a number");
405 
406     *m_interval = l * mseconds_in_useconds;
407 }
408 
409 static
410 std::string
flatten_argv(char * const * argv)411 flatten_argv(char* const* argv)
412 {
413     std::string cmdline;
414 
415     char* const* arg = &argv[0];
416     while (*arg != NULL) {
417         if (arg != &argv[0])
418             cmdline += ' ';
419 
420         cmdline += *arg;
421 
422         arg++;
423     }
424 
425     return cmdline;
426 }
427 
428 static
429 std::unique_ptr< atf::check::check_result >
execute(const char * const * argv)430 execute(const char* const* argv)
431 {
432     // TODO: This should go to stderr... but fixing it now may be hard as test
433     // cases out there might be relying on stderr being silent.
434     std::cout << "Executing command [ ";
435     for (int i = 0; argv[i] != NULL; ++i)
436         std::cout << argv[i] << " ";
437     std::cout << "]\n";
438     std::cout.flush();
439 
440     atf::process::argv_array argva(argv);
441     return atf::check::exec(argva);
442 }
443 
444 static
445 std::unique_ptr< atf::check::check_result >
execute_with_shell(char * const * argv)446 execute_with_shell(char* const* argv)
447 {
448     const std::string cmd = flatten_argv(argv);
449     const std::string shell = atf::env::get("ATF_SHELL", ATF_SHELL);
450     const char* sh_argv[4];
451 
452     sh_argv[0] = shell.c_str();
453     sh_argv[1] = "-c";
454     sh_argv[2] = cmd.c_str();
455     sh_argv[3] = NULL;
456     return execute(sh_argv);
457 }
458 
459 static
460 void
cat_file(const atf::fs::path & path)461 cat_file(const atf::fs::path& path)
462 {
463     std::ifstream stream(path.c_str());
464     if (!stream)
465         throw std::runtime_error("Failed to open " + path.str());
466 
467     stream >> std::noskipws;
468     std::istream_iterator< char > begin(stream), end;
469     std::ostream_iterator< char > out(std::cerr);
470     std::copy(begin, end, out);
471 
472     stream.close();
473 }
474 
475 static
476 bool
grep_file(const atf::fs::path & path,const std::string & regexp)477 grep_file(const atf::fs::path& path, const std::string& regexp)
478 {
479     std::ifstream stream(path.c_str());
480     if (!stream)
481         throw std::runtime_error("Failed to open " + path.str());
482 
483     bool found = false;
484 
485     std::string line;
486     while (!found && !std::getline(stream, line).fail()) {
487         if (atf::text::match(line, regexp))
488             found = true;
489     }
490 
491     stream.close();
492 
493     return found;
494 }
495 
496 static
497 bool
file_empty(const atf::fs::path & p)498 file_empty(const atf::fs::path& p)
499 {
500     atf::fs::file_info f(p);
501 
502     return (f.get_size() == 0);
503 }
504 
505 static bool
compare_files(const atf::fs::path & p1,const atf::fs::path & p2)506 compare_files(const atf::fs::path& p1, const atf::fs::path& p2)
507 {
508     bool equal = false;
509 
510     std::ifstream f1(p1.c_str());
511     if (!f1)
512         throw std::runtime_error("Failed to open " + p1.str());
513 
514     std::ifstream f2(p2.c_str());
515     if (!f2)
516         throw std::runtime_error("Failed to open " + p2.str());
517 
518     for (;;) {
519         char buf1[512], buf2[512];
520 
521         f1.read(buf1, sizeof(buf1));
522         if (f1.bad())
523             throw std::runtime_error("Failed to read from " + p1.str());
524 
525         f2.read(buf2, sizeof(buf2));
526         if (f2.bad())
527             throw std::runtime_error("Failed to read from " + p2.str());
528 
529         if ((f1.gcount() == 0) && (f2.gcount() == 0)) {
530             equal = true;
531             break;
532         }
533 
534         if ((f1.gcount() != f2.gcount()) ||
535             (std::memcmp(buf1, buf2, f1.gcount()) != 0)) {
536             break;
537         }
538     }
539 
540     return equal;
541 }
542 
543 static
544 void
print_diff(const atf::fs::path & p1,const atf::fs::path & p2)545 print_diff(const atf::fs::path& p1, const atf::fs::path& p2)
546 {
547     const atf::process::status s =
548         atf::process::exec(atf::fs::path("diff"),
549                            atf::process::argv_array("diff", "-u", p1.c_str(),
550                                                     p2.c_str(), NULL),
551                            atf::process::stream_connect(STDOUT_FILENO,
552                                                         STDERR_FILENO),
553                            atf::process::stream_inherit());
554 
555     if (!s.exited())
556         std::cerr << "Failed to run diff(3)\n";
557 
558     if (s.exitstatus() != 1)
559         std::cerr << "Error while running diff(3)\n";
560 }
561 
562 static
563 std::string
decode(const std::string & s)564 decode(const std::string& s)
565 {
566     size_t i;
567     std::string res;
568 
569     res.reserve(s.length());
570 
571     i = 0;
572     while (i < s.length()) {
573         char c = s[i++];
574 
575         if (c == '\\') {
576             switch (s[i++]) {
577             case 'a': c = '\a'; break;
578             case 'b': c = '\b'; break;
579             case 'c': break;
580             case 'e': c = 033; break;
581             case 'f': c = '\f'; break;
582             case 'n': c = '\n'; break;
583             case 'r': c = '\r'; break;
584             case 't': c = '\t'; break;
585             case 'v': c = '\v'; break;
586             case '\\': break;
587             case '0':
588                 {
589                     int count = 3;
590                     c = 0;
591                     while (--count >= 0 && (unsigned)(s[i] - '0') < 8)
592                         c = (c << 3) + (s[i++] - '0');
593                     break;
594                 }
595             default:
596                 --i;
597                 break;
598             }
599         }
600 
601         res.push_back(c);
602     }
603 
604     return res;
605 }
606 
607 static
608 bool
run_status_check(const status_check & sc,const atf::check::check_result & cr)609 run_status_check(const status_check& sc, const atf::check::check_result& cr)
610 {
611     bool result;
612 
613     if (sc.type == sc_exit) {
614         if (cr.exited() && !sc.empty) {
615             const int status = cr.exitcode();
616 
617             if (!sc.negated && sc.value != status) {
618                 std::cerr << "Fail: incorrect exit status: "
619                           << status << ", expected: "
620                           << sc.value << "\n";
621                 result = false;
622             } else if (sc.negated && sc.value == status) {
623                 std::cerr << "Fail: incorrect exit status: "
624                           << status << ", expected: "
625                           << "anything else\n";
626                 result = false;
627             } else
628                 result = true;
629         } else if (cr.exited() && sc.empty) {
630             result = true;
631         } else {
632             std::cerr << "Fail: program did not exit cleanly\n";
633             result = false;
634         }
635     } else if (sc.type == sc_ignore) {
636         result = true;
637     } else if (sc.type == sc_signal) {
638         if (cr.signaled() && !sc.empty) {
639             const int status = cr.termsig();
640 
641             if (!sc.negated && sc.value != status) {
642                 std::cerr << "Fail: incorrect signal received: "
643                           << status << ", expected: " << sc.value << "\n";
644                 result = false;
645             } else if (sc.negated && sc.value == status) {
646                 std::cerr << "Fail: incorrect signal received: "
647                           << status << ", expected: "
648                           << "anything else\n";
649                 result = false;
650             } else
651                 result = true;
652         } else if (cr.signaled() && sc.empty) {
653             result = true;
654         } else {
655             std::cerr << "Fail: program did not receive a signal\n";
656             result = false;
657         }
658     } else {
659         UNREACHABLE;
660         result = false;
661     }
662 
663     if (result == false) {
664         std::cerr << "stdout:\n";
665         cat_file(atf::fs::path(cr.stdout_path()));
666         std::cerr << "\n";
667 
668         std::cerr << "stderr:\n";
669         cat_file(atf::fs::path(cr.stderr_path()));
670         std::cerr << "\n";
671     }
672 
673     return result;
674 }
675 
676 static
677 bool
run_status_checks(const std::vector<status_check> & checks,const atf::check::check_result & result)678 run_status_checks(const std::vector< status_check >& checks,
679                   const atf::check::check_result& result)
680 {
681     bool ok = false;
682 
683     for (std::vector< status_check >::const_iterator iter = checks.begin();
684          !ok && iter != checks.end(); iter++) {
685          ok |= run_status_check(*iter, result);
686     }
687 
688     return ok;
689 }
690 
691 static
692 bool
run_output_check(const output_check & oc,const atf::fs::path & path,const std::string & stdxxx)693 run_output_check(const output_check& oc, const atf::fs::path& path,
694                  const std::string& stdxxx)
695 {
696     bool result;
697 
698     if (oc.type == oc_empty) {
699         const bool is_empty = file_empty(path);
700         if (!oc.negated && !is_empty) {
701             std::cerr << "Fail: " << stdxxx << " not empty\n";
702             print_diff(atf::fs::path("/dev/null"), path);
703             result = false;
704         } else if (oc.negated && is_empty) {
705             std::cerr << "Fail: " << stdxxx << " is empty\n";
706             result = false;
707         } else
708             result = true;
709     } else if (oc.type == oc_file) {
710         const bool equals = compare_files(path, atf::fs::path(oc.value));
711         if (!oc.negated && !equals) {
712             std::cerr << "Fail: " << stdxxx << " does not match golden "
713                 "output\n";
714             print_diff(atf::fs::path(oc.value), path);
715             result = false;
716         } else if (oc.negated && equals) {
717             std::cerr << "Fail: " << stdxxx << " matches golden output\n";
718             cat_file(atf::fs::path(oc.value));
719             result = false;
720         } else
721             result = true;
722     } else if (oc.type == oc_ignore) {
723         result = true;
724     } else if (oc.type == oc_inline) {
725         temp_file temp("atf-check.XXXXXX");
726         temp.write(decode(oc.value));
727         temp.close();
728 
729         const bool equals = compare_files(path, temp.get_path());
730         if (!oc.negated && !equals) {
731             std::cerr << "Fail: " << stdxxx << " does not match expected "
732                 "value\n";
733             print_diff(temp.get_path(), path);
734             result = false;
735         } else if (oc.negated && equals) {
736             std::cerr << "Fail: " << stdxxx << " matches expected value\n";
737             cat_file(temp.get_path());
738             result = false;
739         } else
740             result = true;
741     } else if (oc.type == oc_match) {
742         const bool matches = grep_file(path, oc.value);
743         if (!oc.negated && !matches) {
744             std::cerr << "Fail: regexp " + oc.value + " not in " << stdxxx
745                       << "\n";
746             cat_file(path);
747             result = false;
748         } else if (oc.negated && matches) {
749             std::cerr << "Fail: regexp " + oc.value + " is in " << stdxxx
750                       << "\n";
751             cat_file(path);
752             result = false;
753         } else
754             result = true;
755     } else if (oc.type == oc_save) {
756         INV(!oc.negated);
757         std::ifstream ifs(path.c_str(), std::fstream::binary);
758         ifs >> std::noskipws;
759         std::istream_iterator< char > begin(ifs), end;
760 
761         std::ofstream ofs(oc.value.c_str(), std::fstream::binary
762                                      | std::fstream::trunc);
763         std::ostream_iterator <char> obegin(ofs);
764 
765         std::copy(begin, end, obegin);
766         result = true;
767     } else {
768         UNREACHABLE;
769         result = false;
770     }
771 
772     return result;
773 }
774 
775 static
776 bool
run_output_checks(const std::vector<output_check> & checks,const atf::fs::path & path,const std::string & stdxxx)777 run_output_checks(const std::vector< output_check >& checks,
778                   const atf::fs::path& path, const std::string& stdxxx)
779 {
780     bool ok = true;
781 
782     for (std::vector< output_check >::const_iterator iter = checks.begin();
783          iter != checks.end(); iter++) {
784          ok &= run_output_check(*iter, path, stdxxx);
785     }
786 
787     return ok;
788 }
789 
790 // ------------------------------------------------------------------------
791 // The "atf_check" application.
792 // ------------------------------------------------------------------------
793 
794 namespace {
795 
796 class atf_check : public atf::application::app {
797     bool m_rflag;
798     bool m_xflag;
799 
800     useconds_t m_timo;
801     useconds_t m_interval;
802 
803     std::vector< status_check > m_status_checks;
804     std::vector< output_check > m_stdout_checks;
805     std::vector< output_check > m_stderr_checks;
806 
807     static const char* m_description;
808 
809     bool run_output_checks(const atf::check::check_result&,
810                            const std::string&) const;
811 
812     std::string specific_args(void) const;
813     options_set specific_options(void) const;
814     void process_option(int, const char*);
815     void process_option_s(const std::string&);
816 
817 public:
818     atf_check(void);
819     int main(void);
820 };
821 
822 } // anonymous namespace
823 
824 const char* atf_check::m_description =
825     "atf-check executes given command and analyzes its results.";
826 
atf_check(void)827 atf_check::atf_check(void) :
828     app(m_description, "atf-check(1)"),
829     m_rflag(false),
830     m_xflag(false)
831 {
832 }
833 
834 bool
run_output_checks(const atf::check::check_result & r,const std::string & stdxxx) const835 atf_check::run_output_checks(const atf::check::check_result& r,
836                              const std::string& stdxxx)
837     const
838 {
839     if (stdxxx == "stdout") {
840         return ::run_output_checks(m_stdout_checks,
841             atf::fs::path(r.stdout_path()), "stdout");
842     } else if (stdxxx == "stderr") {
843         return ::run_output_checks(m_stderr_checks,
844             atf::fs::path(r.stderr_path()), "stderr");
845     } else {
846         UNREACHABLE;
847         return false;
848     }
849 }
850 
851 std::string
specific_args(void) const852 atf_check::specific_args(void)
853     const
854 {
855     return "<command>";
856 }
857 
858 atf_check::options_set
specific_options(void) const859 atf_check::specific_options(void)
860     const
861 {
862     using atf::application::option;
863     options_set opts;
864 
865     opts.insert(option('s', "qual:value", "Handle status. Qualifier "
866                 "must be one of: ignore exit:<num> signal:<name|num>"));
867     opts.insert(option('o', "action:arg", "Handle stdout. Action must be "
868                 "one of: empty ignore file:<path> inline:<val> match:regexp "
869                 "save:<path>"));
870     opts.insert(option('e', "action:arg", "Handle stderr. Action must be "
871                 "one of: empty ignore file:<path> inline:<val> match:regexp "
872                 "save:<path>"));
873     opts.insert(option('r', "timeout[:interval]", "Repeat failed check until "
874                 "the timeout expires."));
875     opts.insert(option('x', "", "Execute command as a shell command"));
876 
877     return opts;
878 }
879 
880 void
process_option(int ch,const char * arg)881 atf_check::process_option(int ch, const char* arg)
882 {
883     switch (ch) {
884     case 's':
885         m_status_checks.push_back(parse_status_check_arg(arg));
886         break;
887 
888     case 'o':
889         m_stdout_checks.push_back(parse_output_check_arg(arg));
890         break;
891 
892     case 'e':
893         m_stderr_checks.push_back(parse_output_check_arg(arg));
894         break;
895 
896     case 'r':
897         m_rflag = true;
898         parse_repeat_check_arg(arg, &m_timo, &m_interval);
899         break;
900 
901     case 'x':
902         m_xflag = true;
903         break;
904 
905     default:
906         UNREACHABLE;
907     }
908 }
909 
910 int
main(void)911 atf_check::main(void)
912 {
913     if (m_argc < 1)
914         throw atf::application::usage_error("No command specified");
915 
916     int status = EXIT_FAILURE;
917 
918     if (m_status_checks.empty()) {
919         m_status_checks.push_back(status_check(sc_exit, false, EXIT_SUCCESS,
920             false));
921     } else if (m_status_checks.size() > 1) {
922         // TODO: Remove this restriction.
923         throw atf::application::usage_error("Cannot specify -s more than once");
924     }
925 
926     if (m_stdout_checks.empty())
927         m_stdout_checks.push_back(output_check(oc_empty, false, ""));
928     if (m_stderr_checks.empty())
929         m_stderr_checks.push_back(output_check(oc_empty, false, ""));
930 
931     do {
932         std::unique_ptr< atf::check::check_result > r =
933             m_xflag ? execute_with_shell(m_argv) : execute(m_argv);
934 
935         if ((run_status_checks(m_status_checks, *r) == false) ||
936             (run_output_checks(*r, "stderr") == false) ||
937             (run_output_checks(*r, "stdout") == false))
938             status = EXIT_FAILURE;
939         else
940             status = EXIT_SUCCESS;
941 
942         if (m_rflag && status == EXIT_FAILURE) {
943             if (timo_expired(m_timo))
944                 break;
945             usleep(m_interval);
946         }
947     } while (m_rflag && status == EXIT_FAILURE);
948 
949     return status;
950 }
951 
952 int
main(int argc,char * const * argv)953 main(int argc, char* const* argv)
954 {
955     return atf_check().run(argc, argv);
956 }
957