2 // Automated Testing Framework (atf)
4 // Copyright (c) 2008 The NetBSD Foundation, Inc.
5 // All rights reserved.
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
10 // 1. Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 // 2. Redistributions in binary form must reproduce the above copyright
13 // notice, this list of conditions and the following disclaimer in the
14 // documentation and/or other materials provided with the distribution.
16 // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
17 // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include <sys/types.h>
50 #include "atf-c++/check.hpp"
51 #include "atf-c++/config.hpp"
53 #include "atf-c++/detail/application.hpp"
54 #include "atf-c++/detail/auto_array.hpp"
55 #include "atf-c++/detail/exceptions.hpp"
56 #include "atf-c++/detail/fs.hpp"
57 #include "atf-c++/detail/process.hpp"
58 #include "atf-c++/detail/sanity.hpp"
59 #include "atf-c++/detail/text.hpp"
61 // ------------------------------------------------------------------------
62 // Auxiliary functions.
63 // ------------------------------------------------------------------------
78 status_check(const status_check_t
& p_type
, const bool p_negated
,
101 output_check(const output_check_t
& p_type
, const bool p_negated
,
102 const std::string
& p_value
) :
110 class temp_file
: public std::ostream
{
111 std::auto_ptr
< atf::fs::path
> m_path
;
115 temp_file(const atf::fs::path
& p
) :
119 atf::auto_array
< char > buf(new char[p
.str().length() + 1]);
120 std::strcpy(buf
.get(), p
.c_str());
122 m_fd
= ::mkstemp(buf
.get());
124 throw atf::system_error("atf_check::temp_file::temp_file(" +
125 p
.str() + ")", "mkstemp(3) failed",
128 m_path
.reset(new atf::fs::path(buf
.get()));
136 } catch (const atf::system_error
&) {
137 // Ignore deletion errors.
148 write(const std::string
& text
)
150 if (::write(m_fd
, text
.c_str(), text
.size()) == -1)
151 throw atf::system_error("atf_check", "write(2) failed", errno
);
165 } // anonymous namespace
168 parse_exit_code(const std::string
& str
)
171 const int value
= atf::text::to_type
< int >(str
);
172 if (value
< 0 || value
> 255)
173 throw std::runtime_error("Unused reason");
175 } catch (const std::runtime_error
&) {
176 throw atf::application::usage_error("Invalid exit code for -s option; "
177 "must be an integer in range 0-255");
181 static struct name_number
{
184 } signal_names_to_numbers
[] = {
201 signal_name_to_number(const std::string
& str
)
203 struct name_number
* iter
= signal_names_to_numbers
;
205 while (signo
== INT_MIN
&& iter
->name
!= NULL
) {
206 if (str
== iter
->name
|| str
== std::string("sig") + iter
->name
)
215 parse_signal(const std::string
& str
)
217 const int signo
= signal_name_to_number(str
);
218 if (signo
== INT_MIN
) {
220 return atf::text::to_type
< int >(str
);
221 } catch (std::runtime_error
) {
222 throw atf::application::usage_error("Invalid signal name or number "
226 INV(signo
!= INT_MIN
);
231 parse_status_check_arg(const std::string
& arg
)
233 const std::string::size_type delimiter
= arg
.find(':');
234 bool negated
= (arg
.compare(0, 4, "not-") == 0);
235 const std::string action_str
= arg
.substr(0, delimiter
);
236 const std::string action
= negated
? action_str
.substr(4) : action_str
;
237 const std::string value_str
= (
238 delimiter
== std::string::npos
? "" : arg
.substr(delimiter
+ 1));
242 if (action
== "eq") {
243 // Deprecated; use exit instead. TODO: Remove after 0.10.
246 throw atf::application::usage_error("Cannot negate eq checker");
248 value
= parse_exit_code(value_str
);
249 } else if (action
== "exit") {
251 if (value_str
.empty())
254 value
= parse_exit_code(value_str
);
255 } else if (action
== "ignore") {
257 throw atf::application::usage_error("Cannot negate ignore checker");
260 } else if (action
== "ne") {
261 // Deprecated; use not-exit instead. TODO: Remove after 0.10.
264 throw atf::application::usage_error("Cannot negate ne checker");
266 value
= parse_exit_code(value_str
);
267 } else if (action
== "signal") {
269 if (value_str
.empty())
272 value
= parse_signal(value_str
);
274 throw atf::application::usage_error("Invalid status checker");
276 return status_check(type
, negated
, value
);
281 parse_output_check_arg(const std::string
& arg
)
283 const std::string::size_type delimiter
= arg
.find(':');
284 const bool negated
= (arg
.compare(0, 4, "not-") == 0);
285 const std::string action_str
= arg
.substr(0, delimiter
);
286 const std::string action
= negated
? action_str
.substr(4) : action_str
;
289 if (action
== "empty")
291 else if (action
== "file")
293 else if (action
== "ignore") {
295 throw atf::application::usage_error("Cannot negate ignore checker");
297 } else if (action
== "inline")
299 else if (action
== "match")
301 else if (action
== "save") {
303 throw atf::application::usage_error("Cannot negate save checker");
306 throw atf::application::usage_error("Invalid output checker");
308 return output_check(type
, negated
, arg
.substr(delimiter
+ 1));
313 flatten_argv(char* const* argv
)
317 char* const* arg
= &argv
[0];
318 while (*arg
!= NULL
) {
331 std::auto_ptr
< atf::check::check_result
>
332 execute(const char* const* argv
)
334 std::cout
<< "Executing command [ ";
335 for (int i
= 0; argv
[i
] != NULL
; ++i
)
336 std::cout
<< argv
[i
] << " ";
339 atf::process::argv_array
argva(argv
);
340 return atf::check::exec(argva
);
344 std::auto_ptr
< atf::check::check_result
>
345 execute_with_shell(char* const* argv
)
347 const std::string cmd
= flatten_argv(argv
);
349 const char* sh_argv
[4];
350 sh_argv
[0] = atf::config::get("atf_shell").c_str();
352 sh_argv
[2] = cmd
.c_str();
354 return execute(sh_argv
);
359 cat_file(const atf::fs::path
& path
)
361 std::ifstream
stream(path
.c_str());
363 throw std::runtime_error("Failed to open " + path
.str());
365 stream
>> std::noskipws
;
366 std::istream_iterator
< char > begin(stream
), end
;
367 std::ostream_iterator
< char > out(std::cerr
);
368 std::copy(begin
, end
, out
);
375 grep_file(const atf::fs::path
& path
, const std::string
& regexp
)
377 std::ifstream
stream(path
.c_str());
379 throw std::runtime_error("Failed to open " + path
.str());
384 while (!found
&& !std::getline(stream
, line
).fail()) {
385 if (atf::text::match(line
, regexp
))
396 file_empty(const atf::fs::path
& p
)
398 atf::fs::file_info
f(p
);
400 return (f
.get_size() == 0);
404 compare_files(const atf::fs::path
& p1
, const atf::fs::path
& p2
)
408 std::ifstream
f1(p1
.c_str());
410 throw std::runtime_error("Failed to open " + p1
.str());
412 std::ifstream
f2(p2
.c_str());
414 throw std::runtime_error("Failed to open " + p1
.str());
417 char buf1
[512], buf2
[512];
419 f1
.read(buf1
, sizeof(buf1
));
421 throw std::runtime_error("Failed to read from " + p1
.str());
423 f2
.read(buf2
, sizeof(buf2
));
425 throw std::runtime_error("Failed to read from " + p1
.str());
427 if ((f1
.gcount() == 0) && (f2
.gcount() == 0)) {
432 if ((f1
.gcount() != f2
.gcount()) ||
433 (std::memcmp(buf1
, buf2
, f1
.gcount()) != 0)) {
443 print_diff(const atf::fs::path
& p1
, const atf::fs::path
& p2
)
445 const atf::process::status s
=
446 atf::process::exec(atf::fs::path("diff"),
447 atf::process::argv_array("diff", "-u", p1
.c_str(),
449 atf::process::stream_connect(STDOUT_FILENO
,
451 atf::process::stream_inherit());
454 std::cerr
<< "Failed to run diff(3)\n";
456 if (s
.exitstatus() != 1)
457 std::cerr
<< "Error while running diff(3)\n";
462 decode(const std::string
& s
)
467 res
.reserve(s
.length());
470 while (i
< s
.length()) {
475 case 'a': c
= '\a'; break;
476 case 'b': c
= '\b'; break;
478 case 'e': c
= 033; break;
479 case 'f': c
= '\f'; break;
480 case 'n': c
= '\n'; break;
481 case 'r': c
= '\r'; break;
482 case 't': c
= '\t'; break;
483 case 'v': c
= '\v'; break;
489 while (--count
>= 0 && (unsigned)(s
[i
] - '0') < 8)
490 c
= (c
<< 3) + (s
[i
++] - '0');
507 run_status_check(const status_check
& sc
, const atf::check::check_result
& cr
)
511 if (sc
.type
== sc_exit
) {
512 if (cr
.exited() && sc
.value
!= INT_MIN
) {
513 const int status
= cr
.exitcode();
515 if (!sc
.negated
&& sc
.value
!= status
) {
516 std::cerr
<< "Fail: incorrect exit status: "
517 << status
<< ", expected: "
520 } else if (sc
.negated
&& sc
.value
== status
) {
521 std::cerr
<< "Fail: incorrect exit status: "
522 << status
<< ", expected: "
523 << "anything else\n";
527 } else if (cr
.exited() && sc
.value
== INT_MIN
) {
530 std::cerr
<< "Fail: program did not exit cleanly\n";
533 } else if (sc
.type
== sc_ignore
) {
535 } else if (sc
.type
== sc_signal
) {
536 if (cr
.signaled() && sc
.value
!= INT_MIN
) {
537 const int status
= cr
.termsig();
539 if (!sc
.negated
&& sc
.value
!= status
) {
540 std::cerr
<< "Fail: incorrect signal received: "
541 << status
<< ", expected: " << sc
.value
<< "\n";
543 } else if (sc
.negated
&& sc
.value
== status
) {
544 std::cerr
<< "Fail: incorrect signal received: "
545 << status
<< ", expected: "
546 << "anything else\n";
550 } else if (cr
.signaled() && sc
.value
== INT_MIN
) {
553 std::cerr
<< "Fail: program did not receive a signal\n";
561 if (result
== false) {
562 std::cerr
<< "stdout:\n";
563 cat_file(atf::fs::path(cr
.stdout_path()));
566 std::cerr
<< "stderr:\n";
567 cat_file(atf::fs::path(cr
.stderr_path()));
576 run_status_checks(const std::vector
< status_check
>& checks
,
577 const atf::check::check_result
& result
)
581 for (std::vector
< status_check
>::const_iterator iter
= checks
.begin();
582 !ok
&& iter
!= checks
.end(); iter
++) {
583 ok
|= run_status_check(*iter
, result
);
591 run_output_check(const output_check oc
, const atf::fs::path
& path
,
592 const std::string
& stdxxx
)
596 if (oc
.type
== oc_empty
) {
597 const bool is_empty
= file_empty(path
);
598 if (!oc
.negated
&& !is_empty
) {
599 std::cerr
<< "Fail: " << stdxxx
<< " not empty\n";
600 print_diff(atf::fs::path("/dev/null"), path
);
602 } else if (oc
.negated
&& is_empty
) {
603 std::cerr
<< "Fail: " << stdxxx
<< " is empty\n";
607 } else if (oc
.type
== oc_file
) {
608 const bool equals
= compare_files(path
, atf::fs::path(oc
.value
));
609 if (!oc
.negated
&& !equals
) {
610 std::cerr
<< "Fail: " << stdxxx
<< " does not match golden "
612 print_diff(atf::fs::path(oc
.value
), path
);
614 } else if (oc
.negated
&& equals
) {
615 std::cerr
<< "Fail: " << stdxxx
<< " matches golden output\n";
616 cat_file(atf::fs::path(oc
.value
));
620 } else if (oc
.type
== oc_ignore
) {
622 } else if (oc
.type
== oc_inline
) {
623 atf::fs::path path2
= atf::fs::path(atf::config::get("atf_workdir"))
625 temp_file
temp(path2
);
626 temp
.write(decode(oc
.value
));
629 const bool equals
= compare_files(path
, temp
.get_path());
630 if (!oc
.negated
&& !equals
) {
631 std::cerr
<< "Fail: " << stdxxx
<< " does not match expected "
633 print_diff(temp
.get_path(), path
);
635 } else if (oc
.negated
&& equals
) {
636 std::cerr
<< "Fail: " << stdxxx
<< " matches expected value\n";
637 cat_file(temp
.get_path());
641 } else if (oc
.type
== oc_match
) {
642 const bool matches
= grep_file(path
, oc
.value
);
643 if (!oc
.negated
&& !matches
) {
644 std::cerr
<< "Fail: regexp " + oc
.value
+ " not in " << stdxxx
648 } else if (oc
.negated
&& matches
) {
649 std::cerr
<< "Fail: regexp " + oc
.value
+ " is in " << stdxxx
655 } else if (oc
.type
== oc_save
) {
657 std::ifstream
ifs(path
.c_str(), std::fstream::binary
);
658 ifs
>> std::noskipws
;
659 std::istream_iterator
< char > begin(ifs
), end
;
661 std::ofstream
ofs(oc
.value
.c_str(), std::fstream::binary
662 | std::fstream::trunc
);
663 std::ostream_iterator
<char> obegin(ofs
);
665 std::copy(begin
, end
, obegin
);
677 run_output_checks(const std::vector
< output_check
>& checks
,
678 const atf::fs::path
& path
, const std::string
& stdxxx
)
682 for (std::vector
< output_check
>::const_iterator iter
= checks
.begin();
683 iter
!= checks
.end(); iter
++) {
684 ok
&= run_output_check(*iter
, path
, stdxxx
);
690 // ------------------------------------------------------------------------
691 // The "atf_check" application.
692 // ------------------------------------------------------------------------
696 class atf_check
: public atf::application::app
{
699 std::vector
< status_check
> m_status_checks
;
700 std::vector
< output_check
> m_stdout_checks
;
701 std::vector
< output_check
> m_stderr_checks
;
703 static const char* m_description
;
705 bool run_output_checks(const atf::check::check_result
&,
706 const std::string
&) const;
708 std::string
specific_args(void) const;
709 options_set
specific_options(void) const;
710 void process_option(int, const char*);
711 void process_option_s(const std::string
&);
718 } // anonymous namespace
720 const char* atf_check::m_description
=
721 "atf-check executes given command and analyzes its results.";
723 atf_check::atf_check(void) :
724 app(m_description
, "atf-check(1)", "atf(7)"),
730 atf_check::run_output_checks(const atf::check::check_result
& r
,
731 const std::string
& stdxxx
)
734 if (stdxxx
== "stdout") {
735 return ::run_output_checks(m_stdout_checks
,
736 atf::fs::path(r
.stdout_path()), "stdout");
737 } else if (stdxxx
== "stderr") {
738 return ::run_output_checks(m_stderr_checks
,
739 atf::fs::path(r
.stderr_path()), "stderr");
747 atf_check::specific_args(void)
753 atf_check::options_set
754 atf_check::specific_options(void)
757 using atf::application::option
;
760 opts
.insert(option('s', "qual:value", "Handle status. Qualifier "
761 "must be one of: ignore exit:<num> signal:<name|num>"));
762 opts
.insert(option('o', "action:arg", "Handle stdout. Action must be "
763 "one of: empty ignore file:<path> inline:<val> match:regexp "
765 opts
.insert(option('e', "action:arg", "Handle stderr. Action must be "
766 "one of: empty ignore file:<path> inline:<val> match:regexp "
768 opts
.insert(option('x', "", "Execute command as a shell command"));
774 atf_check::process_option(int ch
, const char* arg
)
778 m_status_checks
.push_back(parse_status_check_arg(arg
));
782 m_stdout_checks
.push_back(parse_output_check_arg(arg
));
786 m_stderr_checks
.push_back(parse_output_check_arg(arg
));
799 atf_check::main(void)
802 throw atf::application::usage_error("No command specified");
804 int status
= EXIT_FAILURE
;
806 std::auto_ptr
< atf::check::check_result
> r
=
807 m_xflag
? execute_with_shell(m_argv
) : execute(m_argv
);
809 if (m_status_checks
.empty())
810 m_status_checks
.push_back(status_check(sc_exit
, false, EXIT_SUCCESS
));
811 else if (m_status_checks
.size() > 1) {
812 // TODO: Remove this restriction.
813 throw atf::application::usage_error("Cannot specify -s more than once");
816 if (m_stdout_checks
.empty())
817 m_stdout_checks
.push_back(output_check(oc_empty
, false, ""));
818 if (m_stderr_checks
.empty())
819 m_stderr_checks
.push_back(output_check(oc_empty
, false, ""));
821 if ((run_status_checks(m_status_checks
, *r
) == false) ||
822 (run_output_checks(*r
, "stderr") == false) ||
823 (run_output_checks(*r
, "stdout") == false))
824 status
= EXIT_FAILURE
;
826 status
= EXIT_SUCCESS
;
832 main(int argc
, char* const* argv
)
834 return atf_check().run(argc
, argv
);