2 // Automated Testing Framework (atf)
4 // Copyright (c) 2007 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.
44 #include "application.hpp"
50 typedef std::auto_ptr
< std::ostream
> ostream_ptr
;
53 open_outfile(const tools::fs::path
& path
)
56 if (path
.str() == "-")
57 osp
= ostream_ptr(new std::ofstream("/dev/stdout"));
59 osp
= ostream_ptr(new std::ofstream(path
.c_str()));
61 throw std::runtime_error("Could not create file " + path
.str());
66 format_tv(struct timeval
* tv
)
68 std::ostringstream output
;
69 output
<< static_cast< long >(tv
->tv_sec
) << '.'
70 << std::setfill('0') << std::setw(6)
71 << static_cast< long >(tv
->tv_usec
);
75 // ------------------------------------------------------------------------
76 // The "writer" interface.
77 // ------------------------------------------------------------------------
80 //! \brief A base class that defines an output format.
82 //! The writer base class defines a generic interface to output formats.
83 //! This is meant to be subclassed, and each subclass can redefine any
84 //! method to format the information as it wishes.
86 //! This class is not tied to a output stream nor a file because, depending
87 //! on the output format, we will want to write to a single file or to
93 virtual ~writer(void) {}
95 virtual void write_info(const std::string
&, const std::string
&) {}
96 virtual void write_ntps(size_t) {}
97 virtual void write_tp_start(const std::string
&, size_t) {}
98 virtual void write_tp_end(struct timeval
*, const std::string
&) {}
99 virtual void write_tc_start(const std::string
&) {}
100 virtual void write_tc_stdout_line(const std::string
&) {}
101 virtual void write_tc_stderr_line(const std::string
&) {}
102 virtual void write_tc_end(const std::string
&, struct timeval
*,
103 const std::string
&) {}
104 virtual void write_eof(void) {}
107 // ------------------------------------------------------------------------
108 // The "csv_writer" class.
109 // ------------------------------------------------------------------------
112 //! \brief A very simple plain-text output format.
114 //! The csv_writer class implements a very simple plain-text output
115 //! format that summarizes the results of each executed test case. The
116 //! results are meant to be easily parseable by third-party tools, hence
117 //! they are formatted as a CSV file.
119 class csv_writer
: public writer
{
123 std::string m_tpname
;
124 std::string m_tcname
;
127 csv_writer(const tools::fs::path
& p
) :
128 m_os(open_outfile(p
))
134 write_tp_start(const std::string
& name
,
135 size_t ntcs
__attribute__((__unused__
)))
143 write_tp_end(struct timeval
* tv
, const std::string
& reason
)
145 const std::string timestamp
= format_tv(tv
);
148 (*m_os
) << "tp, " << timestamp
<< ", " << m_tpname
<< ", bogus, "
151 (*m_os
) << "tp, " << timestamp
<< ", "<< m_tpname
<< ", failed\n";
153 (*m_os
) << "tp, " << timestamp
<< ", "<< m_tpname
<< ", passed\n";
158 write_tc_start(const std::string
& name
)
165 write_tc_end(const std::string
& state
, struct timeval
* tv
,
166 const std::string
& reason
)
168 std::string str
= m_tpname
+ ", " + m_tcname
+ ", " + state
;
170 str
+= ", " + reason
;
171 (*m_os
) << "tc, " << format_tv(tv
) << ", " << str
<< "\n";
173 if (state
== "failed")
178 // ------------------------------------------------------------------------
179 // The "ticker_writer" class.
180 // ------------------------------------------------------------------------
183 //! \brief A console-friendly output format.
185 //! The ticker_writer class implements a formatter that is user-friendly
186 //! in the sense that it shows the execution of test cases in an easy to
187 //! read format. It is not meant to be parseable and its format can
188 //! freely change across releases.
190 class ticker_writer
: public writer
{
193 size_t m_curtp
, m_ntps
;
194 size_t m_tcs_passed
, m_tcs_failed
, m_tcs_skipped
, m_tcs_expected_failures
;
195 std::string m_tcname
, m_tpname
;
196 std::vector
< std::string
> m_failed_tcs
;
197 std::map
< std::string
, std::string
> m_expected_failures_tcs
;
198 std::vector
< std::string
> m_failed_tps
;
201 write_info(const std::string
& what
, const std::string
& val
)
203 if (what
== "tests.root") {
204 (*m_os
) << "Tests root: " << val
<< "\n\n";
209 write_ntps(size_t ntps
)
215 m_tcs_expected_failures
= 0;
220 write_tp_start(const std::string
& tp
, size_t ntcs
)
222 using tools::text::to_string
;
223 using tools::ui::format_text
;
227 (*m_os
) << format_text(tp
+ " (" + to_string(m_curtp
) +
228 "/" + to_string(m_ntps
) + "): " +
229 to_string(ntcs
) + " test cases")
235 write_tp_end(struct timeval
* tv
, const std::string
& reason
)
237 using tools::ui::format_text_with_tag
;
241 if (!reason
.empty()) {
242 (*m_os
) << format_text_with_tag("BOGUS TEST PROGRAM: Cannot "
243 "trust its results because "
244 "of `" + reason
+ "'",
245 m_tpname
+ ": ", false)
247 m_failed_tps
.push_back(m_tpname
);
249 (*m_os
) << "[" << format_tv(tv
) << "s]\n\n";
256 write_tc_start(const std::string
& tcname
)
260 (*m_os
) << " " + tcname
+ ": ";
265 write_tc_end(const std::string
& state
, struct timeval
* tv
,
266 const std::string
& reason
)
270 (*m_os
) << "[" << format_tv(tv
) << "s] ";
272 if (state
== "expected_death" || state
== "expected_exit" ||
273 state
== "expected_failure" || state
== "expected_signal" ||
274 state
== "expected_timeout") {
275 str
= "Expected failure: " + reason
;
276 m_tcs_expected_failures
++;
277 m_expected_failures_tcs
[m_tpname
+ ":" + m_tcname
] = reason
;
278 } else if (state
== "failed") {
279 str
= "Failed: " + reason
;
281 m_failed_tcs
.push_back(m_tpname
+ ":" + m_tcname
);
282 } else if (state
== "passed") {
285 } else if (state
== "skipped") {
286 str
= "Skipped: " + reason
;
291 // XXX Wrap text. format_text_with_tag does not currently allow
292 // to specify the current column, which is needed because we have
293 // already printed the tc's name.
294 (*m_os
) << str
<< '\n';
300 write_expected_failures(const std::map
< std::string
, std::string
>& xfails
,
303 using tools::ui::format_text
;
304 using tools::ui::format_text_with_tag
;
306 os
<< format_text("Test cases for known bugs:") << "\n";
308 for (std::map
< std::string
, std::string
>::const_iterator iter
=
309 xfails
.begin(); iter
!= xfails
.end(); iter
++) {
310 const std::string
& name
= (*iter
).first
;
311 const std::string
& reason
= (*iter
).second
;
313 os
<< format_text_with_tag(reason
, " " + name
+ ": ", false)
321 using tools::text::join
;
322 using tools::text::to_string
;
323 using tools::ui::format_text
;
324 using tools::ui::format_text_with_tag
;
326 if (!m_failed_tps
.empty()) {
327 (*m_os
) << format_text("Failed (bogus) test programs:")
329 (*m_os
) << format_text_with_tag(join(m_failed_tps
, ", "),
330 " ", false) << "\n\n";
333 if (!m_expected_failures_tcs
.empty()) {
334 write_expected_failures(m_expected_failures_tcs
, *m_os
);
338 if (!m_failed_tcs
.empty()) {
339 (*m_os
) << format_text("Failed test cases:") << "\n";
340 (*m_os
) << format_text_with_tag(join(m_failed_tcs
, ", "),
341 " ", false) << "\n\n";
344 (*m_os
) << format_text("Summary for " + to_string(m_ntps
) +
345 " test programs:") << "\n";
346 (*m_os
) << format_text_with_tag(to_string(m_tcs_passed
) +
347 " passed test cases.",
349 (*m_os
) << format_text_with_tag(to_string(m_tcs_failed
) +
350 " failed test cases.",
352 (*m_os
) << format_text_with_tag(to_string(m_tcs_expected_failures
) +
353 " expected failed test cases.",
355 (*m_os
) << format_text_with_tag(to_string(m_tcs_skipped
) +
356 " skipped test cases.",
361 ticker_writer(const tools::fs::path
& p
) :
362 m_os(open_outfile(p
))
367 // ------------------------------------------------------------------------
369 // ------------------------------------------------------------------------
372 //! \brief A single-file XML output format.
374 //! The xml_writer class implements a formatter that prints the results
375 //! of test cases in an XML format easily parseable later on by other
378 class xml_writer
: public writer
{
381 std::string m_tcname
, m_tpname
;
385 attrval(const std::string
& str
)
392 elemval(const std::string
& str
)
394 std::ostringstream buf
;
395 for (std::string::const_iterator iter
= str
.begin();
396 iter
!= str
.end(); iter
++) {
397 const int character
= static_cast< unsigned char >(*iter
);
398 if (character
== '&') {
400 } else if (character
== '<') {
402 } else if (character
== '>') {
404 } else if (std::isalnum(character
) || std::ispunct(character
) ||
405 std::isspace(character
)) {
406 buf
<< static_cast< char >(character
);
408 buf
<< "&#" << character
<< ";";
415 write_info(const std::string
& what
, const std::string
& val
)
417 (*m_os
) << "<info class=\"" << what
<< "\">" << val
<< "</info>\n";
421 write_tp_start(const std::string
& tp
,
422 size_t ntcs
__attribute__((__unused__
)))
424 (*m_os
) << "<tp id=\"" << attrval(tp
) << "\">\n";
428 write_tp_end(struct timeval
* tv
, const std::string
& reason
)
431 (*m_os
) << "<failed>" << elemval(reason
) << "</failed>\n";
432 (*m_os
) << "<tp-time>" << format_tv(tv
) << "</tp-time>";
433 (*m_os
) << "</tp>\n";
437 write_tc_start(const std::string
& tcname
)
439 (*m_os
) << "<tc id=\"" << attrval(tcname
) << "\">\n";
443 write_tc_stdout_line(const std::string
& line
)
445 (*m_os
) << "<so>" << elemval(line
) << "</so>\n";
449 write_tc_stderr_line(const std::string
& line
)
451 (*m_os
) << "<se>" << elemval(line
) << "</se>\n";
455 write_tc_end(const std::string
& state
, struct timeval
* tv
,
456 const std::string
& reason
)
460 if (state
== "expected_death" || state
== "expected_exit" ||
461 state
== "expected_failure" || state
== "expected_signal" ||
462 state
== "expected_timeout") {
463 (*m_os
) << "<" << state
<< ">" << elemval(reason
)
464 << "</" << state
<< ">\n";
465 } else if (state
== "passed") {
466 (*m_os
) << "<passed />\n";
467 } else if (state
== "failed") {
468 (*m_os
) << "<failed>" << elemval(reason
) << "</failed>\n";
469 } else if (state
== "skipped") {
470 (*m_os
) << "<skipped>" << elemval(reason
) << "</skipped>\n";
473 (*m_os
) << "<tc-time>" << format_tv(tv
) << "</tc-time>";
474 (*m_os
) << "</tc>\n";
480 (*m_os
) << "</tests-results>\n";
484 xml_writer(const tools::fs::path
& p
) :
485 m_os(open_outfile(p
))
487 (*m_os
) << "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
488 << "<!DOCTYPE tests-results PUBLIC "
489 "\"-//NetBSD//DTD ATF Tests Results 0.1//EN\" "
490 "\"http://www.NetBSD.org/XML/atf/tests-results.dtd\">\n\n"
495 // ------------------------------------------------------------------------
496 // The "converter" class.
497 // ------------------------------------------------------------------------
500 //! \brief A reader that redirects events to multiple writers.
502 //! The converter class implements an atf_tps_reader that, for each event
503 //! raised by the parser, redirects it to multiple writers so that they
504 //! can reformat it according to their output rules.
506 class converter
: public tools::atf_report::atf_tps_reader
{
507 typedef std::vector
< writer
* > outs_vector
;
511 got_info(const std::string
& what
, const std::string
& val
)
513 for (outs_vector::iterator iter
= m_outs
.begin();
514 iter
!= m_outs
.end(); iter
++)
515 (*iter
)->write_info(what
, val
);
519 got_ntps(size_t ntps
)
521 for (outs_vector::iterator iter
= m_outs
.begin();
522 iter
!= m_outs
.end(); iter
++)
523 (*iter
)->write_ntps(ntps
);
527 got_tp_start(const std::string
& tp
, size_t ntcs
)
529 for (outs_vector::iterator iter
= m_outs
.begin();
530 iter
!= m_outs
.end(); iter
++)
531 (*iter
)->write_tp_start(tp
, ntcs
);
535 got_tp_end(struct timeval
* tv
, const std::string
& reason
)
537 for (outs_vector::iterator iter
= m_outs
.begin();
538 iter
!= m_outs
.end(); iter
++)
539 (*iter
)->write_tp_end(tv
, reason
);
543 got_tc_start(const std::string
& tcname
)
545 for (outs_vector::iterator iter
= m_outs
.begin();
546 iter
!= m_outs
.end(); iter
++)
547 (*iter
)->write_tc_start(tcname
);
551 got_tc_stdout_line(const std::string
& line
)
553 for (outs_vector::iterator iter
= m_outs
.begin();
554 iter
!= m_outs
.end(); iter
++)
555 (*iter
)->write_tc_stdout_line(line
);
559 got_tc_stderr_line(const std::string
& line
)
561 for (outs_vector::iterator iter
= m_outs
.begin();
562 iter
!= m_outs
.end(); iter
++)
563 (*iter
)->write_tc_stderr_line(line
);
567 got_tc_end(const std::string
& state
, struct timeval
* tv
,
568 const std::string
& reason
)
570 for (outs_vector::iterator iter
= m_outs
.begin();
571 iter
!= m_outs
.end(); iter
++)
572 (*iter
)->write_tc_end(state
, tv
, reason
);
578 for (outs_vector::iterator iter
= m_outs
.begin();
579 iter
!= m_outs
.end(); iter
++)
580 (*iter
)->write_eof();
584 converter(std::istream
& is
) :
585 tools::atf_report::atf_tps_reader(is
)
591 for (outs_vector::iterator iter
= m_outs
.begin();
592 iter
!= m_outs
.end(); iter
++)
597 add_output(const std::string
& fmt
, const tools::fs::path
& p
)
600 m_outs
.push_back(new csv_writer(p
));
601 } else if (fmt
== "ticker") {
602 m_outs
.push_back(new ticker_writer(p
));
603 } else if (fmt
== "xml") {
604 m_outs
.push_back(new xml_writer(p
));
606 throw std::runtime_error("Unknown format `" + fmt
+ "'");
610 // ------------------------------------------------------------------------
611 // The "atf_report" class.
612 // ------------------------------------------------------------------------
614 class atf_report
: public tools::application::app
{
615 static const char* m_description
;
617 typedef std::pair
< std::string
, tools::fs::path
> fmt_path_pair
;
618 std::vector
< fmt_path_pair
> m_oflags
;
620 void process_option(int, const char*);
621 options_set
specific_options(void) const;
629 const char* atf_report::m_description
=
630 "atf-report is a tool that parses the output of atf-run and "
631 "generates user-friendly reports in multiple different formats.";
633 atf_report::atf_report(void) :
634 app(m_description
, "atf-report(1)", "atf(7)")
639 atf_report::process_option(int ch
, const char* arg
)
644 std::string
str(arg
);
645 std::string::size_type pos
= str
.find(':');
646 if (pos
== std::string::npos
)
647 throw std::runtime_error("Syntax error in -o option");
649 std::string fmt
= str
.substr(0, pos
);
650 tools::fs::path path
= tools::fs::path(str
.substr(pos
+ 1));
651 m_oflags
.push_back(fmt_path_pair(fmt
, path
));
661 atf_report::options_set
662 atf_report::specific_options(void)
665 using tools::application::option
;
667 opts
.insert(option('o', "fmt:path", "Adds a new output file; multiple "
668 "ones can be specified, and a - "
669 "path means stdout"));
674 atf_report::main(void)
677 throw std::runtime_error("No arguments allowed");
679 if (m_oflags
.empty())
680 m_oflags
.push_back(fmt_path_pair("ticker", tools::fs::path("-")));
682 // Look for path duplicates.
683 std::set
< tools::fs::path
> paths
;
684 for (std::vector
< fmt_path_pair
>::const_iterator iter
= m_oflags
.begin();
685 iter
!= m_oflags
.end(); iter
++) {
686 tools::fs::path p
= (*iter
).second
;
687 if (p
== tools::fs::path("/dev/stdout"))
688 p
= tools::fs::path("-");
689 if (paths
.find(p
) != paths
.end())
690 throw std::runtime_error("The file `" + p
.str() + "' was "
691 "specified more than once");
692 paths
.insert((*iter
).second
);
695 // Generate the output files.
696 converter
cnv(std::cin
);
697 for (std::vector
< fmt_path_pair
>::const_iterator iter
= m_oflags
.begin();
698 iter
!= m_oflags
.end(); iter
++)
699 cnv
.add_output((*iter
).first
, (*iter
).second
);
706 main(int argc
, char* const* argv
)
708 return atf_report().run(argc
, argv
);