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