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