1 // Copyright (c) 2008 The NetBSD Foundation, Inc.
2 // All rights reserved.
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions
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.
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.
27 #include <sys/types.h>
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"
56 // ------------------------------------------------------------------------
57 // Auxiliary functions.
58 // ------------------------------------------------------------------------
73 status_check(const status_check_t& p_type, const bool p_negated,
96 output_check(const output_check_t& p_type, const bool p_negated,
97 const std::string& p_value) :
105 class temp_file : public std::ostream {
106 std::auto_ptr< atf::fs::path > m_path;
110 temp_file(const char* pattern) :
114 const atf::fs::path file = atf::fs::path(
115 atf::env::get("TMPDIR", "/tmp")) / pattern;
117 atf::auto_array< char > buf(new char[file.str().length() + 1]);
118 std::strcpy(buf.get(), file.c_str());
120 m_fd = ::mkstemp(buf.get());
122 throw atf::system_error("atf_check::temp_file::temp_file(" +
123 file.str() + ")", "mkstemp(3) failed",
126 m_path.reset(new atf::fs::path(buf.get()));
134 } catch (const atf::system_error&) {
135 // Ignore deletion errors.
146 write(const std::string& text)
148 if (::write(m_fd, text.c_str(), text.size()) == -1)
149 throw atf::system_error("atf_check", "write(2) failed", errno);
163 } // anonymous namespace
166 parse_exit_code(const std::string& str)
169 const int value = atf::text::to_type< int >(str);
170 if (value < 0 || value > 255)
171 throw std::runtime_error("Unused reason");
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");
179 static struct name_number {
182 } signal_names_to_numbers[] = {
199 signal_name_to_number(const std::string& str)
201 struct name_number* iter = signal_names_to_numbers;
203 while (signo == INT_MIN && iter->name != NULL) {
204 if (str == iter->name || str == std::string("sig") + iter->name)
213 parse_signal(const std::string& str)
215 const int signo = signal_name_to_number(str);
216 if (signo == INT_MIN) {
218 return atf::text::to_type< int >(str);
219 } catch (std::runtime_error) {
220 throw atf::application::usage_error("Invalid signal name or number "
224 INV(signo != INT_MIN);
229 parse_status_check_arg(const std::string& arg)
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));
240 if (action == "eq") {
241 // Deprecated; use exit instead. TODO: Remove after 0.10.
244 throw atf::application::usage_error("Cannot negate eq checker");
246 value = parse_exit_code(value_str);
247 } else if (action == "exit") {
249 if (value_str.empty())
252 value = parse_exit_code(value_str);
253 } else if (action == "ignore") {
255 throw atf::application::usage_error("Cannot negate ignore checker");
258 } else if (action == "ne") {
259 // Deprecated; use not-exit instead. TODO: Remove after 0.10.
262 throw atf::application::usage_error("Cannot negate ne checker");
264 value = parse_exit_code(value_str);
265 } else if (action == "signal") {
267 if (value_str.empty())
270 value = parse_signal(value_str);
272 throw atf::application::usage_error("Invalid status checker");
274 return status_check(type, negated, value);
279 parse_output_check_arg(const std::string& arg)
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;
287 if (action == "empty")
289 else if (action == "file")
291 else if (action == "ignore") {
293 throw atf::application::usage_error("Cannot negate ignore checker");
295 } else if (action == "inline")
297 else if (action == "match")
299 else if (action == "save") {
301 throw atf::application::usage_error("Cannot negate save checker");
304 throw atf::application::usage_error("Invalid output checker");
306 return output_check(type, negated, arg.substr(delimiter + 1));
311 flatten_argv(char* const* argv)
315 char* const* arg = &argv[0];
316 while (*arg != NULL) {
329 std::auto_ptr< atf::check::check_result >
330 execute(const char* const* argv)
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] << " ";
340 atf::process::argv_array argva(argv);
341 return atf::check::exec(argva);
345 std::auto_ptr< atf::check::check_result >
346 execute_with_shell(char* const* argv)
348 const std::string cmd = flatten_argv(argv);
350 const char* sh_argv[4];
351 sh_argv[0] = atf::env::get("ATF_SHELL", ATF_SHELL).c_str();
353 sh_argv[2] = cmd.c_str();
355 return execute(sh_argv);
360 cat_file(const atf::fs::path& path)
362 std::ifstream stream(path.c_str());
364 throw std::runtime_error("Failed to open " + path.str());
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);
376 grep_file(const atf::fs::path& path, const std::string& regexp)
378 std::ifstream stream(path.c_str());
380 throw std::runtime_error("Failed to open " + path.str());
385 while (!found && !std::getline(stream, line).fail()) {
386 if (atf::text::match(line, regexp))
397 file_empty(const atf::fs::path& p)
399 atf::fs::file_info f(p);
401 return (f.get_size() == 0);
405 compare_files(const atf::fs::path& p1, const atf::fs::path& p2)
409 std::ifstream f1(p1.c_str());
411 throw std::runtime_error("Failed to open " + p1.str());
413 std::ifstream f2(p2.c_str());
415 throw std::runtime_error("Failed to open " + p1.str());
418 char buf1[512], buf2[512];
420 f1.read(buf1, sizeof(buf1));
422 throw std::runtime_error("Failed to read from " + p1.str());
424 f2.read(buf2, sizeof(buf2));
426 throw std::runtime_error("Failed to read from " + p1.str());
428 if ((f1.gcount() == 0) && (f2.gcount() == 0)) {
433 if ((f1.gcount() != f2.gcount()) ||
434 (std::memcmp(buf1, buf2, f1.gcount()) != 0)) {
444 print_diff(const atf::fs::path& p1, const atf::fs::path& p2)
446 const atf::process::status s =
447 atf::process::exec(atf::fs::path("diff"),
448 atf::process::argv_array("diff", "-u", p1.c_str(),
450 atf::process::stream_connect(STDOUT_FILENO,
452 atf::process::stream_inherit());
455 std::cerr << "Failed to run diff(3)\n";
457 if (s.exitstatus() != 1)
458 std::cerr << "Error while running diff(3)\n";
463 decode(const std::string& s)
468 res.reserve(s.length());
471 while (i < s.length()) {
476 case 'a': c = '\a'; break;
477 case 'b': c = '\b'; 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;
490 while (--count >= 0 && (unsigned)(s[i] - '0') < 8)
491 c = (c << 3) + (s[i++] - '0');
508 run_status_check(const status_check& sc, const atf::check::check_result& cr)
512 if (sc.type == sc_exit) {
513 if (cr.exited() && sc.value != INT_MIN) {
514 const int status = cr.exitcode();
516 if (!sc.negated && sc.value != status) {
517 std::cerr << "Fail: incorrect exit status: "
518 << status << ", expected: "
521 } else if (sc.negated && sc.value == status) {
522 std::cerr << "Fail: incorrect exit status: "
523 << status << ", expected: "
524 << "anything else\n";
528 } else if (cr.exited() && sc.value == INT_MIN) {
531 std::cerr << "Fail: program did not exit cleanly\n";
534 } else if (sc.type == sc_ignore) {
536 } else if (sc.type == sc_signal) {
537 if (cr.signaled() && sc.value != INT_MIN) {
538 const int status = cr.termsig();
540 if (!sc.negated && sc.value != status) {
541 std::cerr << "Fail: incorrect signal received: "
542 << status << ", expected: " << sc.value << "\n";
544 } else if (sc.negated && sc.value == status) {
545 std::cerr << "Fail: incorrect signal received: "
546 << status << ", expected: "
547 << "anything else\n";
551 } else if (cr.signaled() && sc.value == INT_MIN) {
554 std::cerr << "Fail: program did not receive a signal\n";
562 if (result == false) {
563 std::cerr << "stdout:\n";
564 cat_file(atf::fs::path(cr.stdout_path()));
567 std::cerr << "stderr:\n";
568 cat_file(atf::fs::path(cr.stderr_path()));
577 run_status_checks(const std::vector< status_check >& checks,
578 const atf::check::check_result& result)
582 for (std::vector< status_check >::const_iterator iter = checks.begin();
583 !ok && iter != checks.end(); iter++) {
584 ok |= run_status_check(*iter, result);
592 run_output_check(const output_check oc, const atf::fs::path& path,
593 const std::string& stdxxx)
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);
603 } else if (oc.negated && is_empty) {
604 std::cerr << "Fail: " << stdxxx << " is empty\n";
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 "
613 print_diff(atf::fs::path(oc.value), path);
615 } else if (oc.negated && equals) {
616 std::cerr << "Fail: " << stdxxx << " matches golden output\n";
617 cat_file(atf::fs::path(oc.value));
621 } else if (oc.type == oc_ignore) {
623 } else if (oc.type == oc_inline) {
624 temp_file temp("atf-check.XXXXXX");
625 temp.write(decode(oc.value));
628 const bool equals = compare_files(path, temp.get_path());
629 if (!oc.negated && !equals) {
630 std::cerr << "Fail: " << stdxxx << " does not match expected "
632 print_diff(temp.get_path(), path);
634 } else if (oc.negated && equals) {
635 std::cerr << "Fail: " << stdxxx << " matches expected value\n";
636 cat_file(temp.get_path());
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
647 } else if (oc.negated && matches) {
648 std::cerr << "Fail: regexp " + oc.value + " is in " << stdxxx
654 } else if (oc.type == oc_save) {
656 std::ifstream ifs(path.c_str(), std::fstream::binary);
657 ifs >> std::noskipws;
658 std::istream_iterator< char > begin(ifs), end;
660 std::ofstream ofs(oc.value.c_str(), std::fstream::binary
661 | std::fstream::trunc);
662 std::ostream_iterator <char> obegin(ofs);
664 std::copy(begin, end, obegin);
676 run_output_checks(const std::vector< output_check >& checks,
677 const atf::fs::path& path, const std::string& stdxxx)
681 for (std::vector< output_check >::const_iterator iter = checks.begin();
682 iter != checks.end(); iter++) {
683 ok &= run_output_check(*iter, path, stdxxx);
689 // ------------------------------------------------------------------------
690 // The "atf_check" application.
691 // ------------------------------------------------------------------------
695 class atf_check : public atf::application::app {
698 std::vector< status_check > m_status_checks;
699 std::vector< output_check > m_stdout_checks;
700 std::vector< output_check > m_stderr_checks;
702 static const char* m_description;
704 bool run_output_checks(const atf::check::check_result&,
705 const std::string&) const;
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&);
717 } // anonymous namespace
719 const char* atf_check::m_description =
720 "atf-check executes given command and analyzes its results.";
722 atf_check::atf_check(void) :
723 app(m_description, "atf-check(1)"),
729 atf_check::run_output_checks(const atf::check::check_result& r,
730 const std::string& stdxxx)
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");
746 atf_check::specific_args(void)
752 atf_check::options_set
753 atf_check::specific_options(void)
756 using atf::application::option;
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 "
764 opts.insert(option('e', "action:arg", "Handle stderr. Action must be "
765 "one of: empty ignore file:<path> inline:<val> match:regexp "
767 opts.insert(option('x', "", "Execute command as a shell command"));
773 atf_check::process_option(int ch, const char* arg)
777 m_status_checks.push_back(parse_status_check_arg(arg));
781 m_stdout_checks.push_back(parse_output_check_arg(arg));
785 m_stderr_checks.push_back(parse_output_check_arg(arg));
798 atf_check::main(void)
801 throw atf::application::usage_error("No command specified");
803 int status = EXIT_FAILURE;
805 std::auto_ptr< atf::check::check_result > r =
806 m_xflag ? execute_with_shell(m_argv) : execute(m_argv);
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");
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, ""));
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;
825 status = EXIT_SUCCESS;
831 main(int argc, char* const* argv)
833 return atf_check().run(argc, argv);