2 // Automated Testing Framework (atf)
4 // Copyright (c) 2007, 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.
37 #include "atf-c++/application.hpp"
38 #include "atf-c++/fs.hpp"
39 #include "atf-c++/formats.hpp"
40 #include "atf-c++/sanity.hpp"
41 #include "atf-c++/text.hpp"
42 #include "atf-c++/ui.hpp"
44 typedef std::auto_ptr
< std::ostream
> ostream_ptr
;
47 open_outfile(const atf::fs::path
& path
)
50 if (path
.str() == "-")
51 osp
= ostream_ptr(new std::ofstream("/dev/stdout"));
53 osp
= ostream_ptr(new std::ofstream(path
.c_str()));
55 throw std::runtime_error("Could not create file " + path
.str());
59 // ------------------------------------------------------------------------
60 // The "writer" interface.
61 // ------------------------------------------------------------------------
64 //! \brief A base class that defines an output format.
66 //! The writer base class defines a generic interface to output formats.
67 //! This is meant to be subclassed, and each subclass can redefine any
68 //! method to format the information as it wishes.
70 //! This class is not tied to a output stream nor a file because, depending
71 //! on the output format, we will want to write to a single file or to
77 virtual ~writer(void) {}
79 virtual void write_info(const std::string
&, const std::string
&) {}
80 virtual void write_ntps(size_t) {}
81 virtual void write_tp_start(const std::string
&, size_t) {}
82 virtual void write_tp_end(const std::string
&) {}
83 virtual void write_tc_start(const std::string
&) {}
84 virtual void write_tc_stdout_line(const std::string
&) {}
85 virtual void write_tc_stderr_line(const std::string
&) {}
86 virtual void write_tc_end(const atf::tests::tcr
&) {}
87 virtual void write_eof(void) {}
90 // ------------------------------------------------------------------------
91 // The "csv_writer" class.
92 // ------------------------------------------------------------------------
95 //! \brief A very simple plain-text output format.
97 //! The csv_writer class implements a very simple plain-text output
98 //! format that summarizes the results of each executed test case. The
99 //! results are meant to be easily parseable by third-party tools, hence
100 //! they are formatted as a CSV file.
102 class csv_writer
: public writer
{
106 std::string m_tpname
;
107 std::string m_tcname
;
110 csv_writer(const atf::fs::path
& p
) :
111 m_os(open_outfile(p
))
117 write_tp_start(const std::string
& name
, size_t ntcs
)
125 write_tp_end(const std::string
& reason
)
128 (*m_os
) << "tp, " << m_tpname
<< ", bogus, " << reason
131 (*m_os
) << "tp, " << m_tpname
<< ", failed" << std::endl
;
133 (*m_os
) << "tp, " << m_tpname
<< ", passed" << std::endl
;
138 write_tc_start(const std::string
& name
)
145 write_tc_end(const atf::tests::tcr
& tcr
)
147 std::string str
= "tc, ";
148 if (tcr
.get_state() == atf::tests::tcr::passed_state
) {
149 str
+= m_tpname
+ ", " + m_tcname
+ ", passed";
150 } else if (tcr
.get_state() == atf::tests::tcr::failed_state
) {
151 str
+= m_tpname
+ ", " + m_tcname
+ ", failed, " +
154 } else if (tcr
.get_state() == atf::tests::tcr::skipped_state
) {
155 str
+= m_tpname
+ ", " + m_tcname
+ ", skipped, " +
159 (*m_os
) << str
<< std::endl
;
163 // ------------------------------------------------------------------------
164 // The "ticker_writer" class.
165 // ------------------------------------------------------------------------
168 //! \brief A console-friendly output format.
170 //! The ticker_writer class implements a formatter that is user-friendly
171 //! in the sense that it shows the execution of test cases in an easy to
172 //! read format. It is not meant to be parseable and its format can
173 //! freely change across releases.
175 class ticker_writer
: public writer
{
178 size_t m_curtp
, m_ntps
;
179 size_t m_tcs_passed
, m_tcs_failed
, m_tcs_skipped
;
180 std::string m_tcname
, m_tpname
;
181 std::vector
< std::string
> m_failed_tcs
;
182 std::vector
< std::string
> m_failed_tps
;
185 write_info(const std::string
& what
, const std::string
& val
)
187 if (what
== "tests.root") {
188 (*m_os
) << "Tests root: " << val
<< std::endl
194 write_ntps(size_t ntps
)
204 write_tp_start(const std::string
& tp
, size_t ntcs
)
206 using atf::text::to_string
;
207 using atf::ui::format_text
;
211 (*m_os
) << format_text(tp
+ " (" + to_string(m_curtp
) +
212 "/" + to_string(m_ntps
) + "): " +
213 to_string(ntcs
) + " test cases")
219 write_tp_end(const std::string
& reason
)
221 using atf::ui::format_text_with_tag
;
225 if (!reason
.empty()) {
226 (*m_os
) << format_text_with_tag("BOGUS TEST PROGRAM: Cannot "
227 "trust its results because "
228 "of `" + reason
+ "'",
229 m_tpname
+ ": ", false)
231 m_failed_tps
.push_back(m_tpname
);
233 (*m_os
) << std::endl
;
240 write_tc_start(const std::string
& tcname
)
244 (*m_os
) << " " + tcname
+ ": ";
249 write_tc_end(const atf::tests::tcr
& tcr
)
253 atf::tests::tcr::state s
= tcr
.get_state();
254 if (s
== atf::tests::tcr::passed_state
) {
257 } else if (s
== atf::tests::tcr::failed_state
) {
258 str
= "Failed: " + tcr
.get_reason();
260 m_failed_tcs
.push_back(m_tpname
+ ":" + m_tcname
);
261 } else if (s
== atf::tests::tcr::skipped_state
) {
262 str
= "Skipped: " + tcr
.get_reason();
267 // XXX Wrap text. format_text_with_tag does not currently allow
268 // to specify the current column, which is needed because we have
269 // already printed the tc's name.
270 (*m_os
) << str
<< std::endl
;
278 using atf::text::join
;
279 using atf::text::to_string
;
280 using atf::ui::format_text
;
281 using atf::ui::format_text_with_tag
;
283 if (!m_failed_tps
.empty()) {
284 (*m_os
) << format_text("Failed (bogus) test programs:")
286 (*m_os
) << format_text_with_tag(join(m_failed_tps
, ", "),
287 " ", false) << std::endl
291 if (!m_failed_tcs
.empty()) {
292 (*m_os
) << format_text("Failed test cases:") << std::endl
;
293 (*m_os
) << format_text_with_tag(join(m_failed_tcs
, ", "),
294 " ", false) << std::endl
298 (*m_os
) << format_text("Summary for " + to_string(m_ntps
) +
301 (*m_os
) << format_text_with_tag(to_string(m_tcs_passed
) +
302 " passed test cases.",
305 (*m_os
) << format_text_with_tag(to_string(m_tcs_failed
) +
306 " failed test cases.",
309 (*m_os
) << format_text_with_tag(to_string(m_tcs_skipped
) +
310 " skipped test cases.",
316 ticker_writer(const atf::fs::path
& p
) :
317 m_os(open_outfile(p
))
322 // ------------------------------------------------------------------------
324 // ------------------------------------------------------------------------
327 //! \brief A single-file XML output format.
329 //! The xml_writer class implements a formatter that prints the results
330 //! of test cases in an XML format easily parseable later on by other
333 class xml_writer
: public writer
{
336 size_t m_curtp
, m_ntps
;
337 size_t m_tcs_passed
, m_tcs_failed
, m_tcs_skipped
;
338 std::string m_tcname
, m_tpname
;
339 std::vector
< std::string
> m_failed_tcs
;
340 std::vector
< std::string
> m_failed_tps
;
344 attrval(const std::string
& str
)
351 elemval(const std::string
& str
)
354 for (std::string::const_iterator iter
= str
.begin();
355 iter
!= str
.end(); iter
++) {
357 case '&': ostr
+= "&"; break;
358 case '<': ostr
+= "<"; break;
359 case '>': ostr
+= ">"; break;
360 default: ostr
+= *iter
;
367 write_info(const std::string
& what
, const std::string
& val
)
369 (*m_os
) << "<info class=\"" << what
<< "\">" << val
<< "</info>"
374 write_tp_start(const std::string
& tp
, size_t ntcs
)
376 (*m_os
) << "<tp id=\"" << attrval(tp
) << "\">" << std::endl
;
380 write_tp_end(const std::string
& reason
)
383 (*m_os
) << "<failed>" << elemval(reason
) << "</failed>"
385 (*m_os
) << "</tp>" << std::endl
;
389 write_tc_start(const std::string
& tcname
)
391 (*m_os
) << "<tc id=\"" << attrval(tcname
) << "\">" << std::endl
;
395 write_tc_stdout_line(const std::string
& line
)
397 (*m_os
) << "<so>" << elemval(line
) << "</so>" << std::endl
;
401 write_tc_stderr_line(const std::string
& line
)
403 (*m_os
) << "<se>" << elemval(line
) << "</se>" << std::endl
;
407 write_tc_end(const atf::tests::tcr
& tcr
)
411 atf::tests::tcr::state s
= tcr
.get_state();
412 if (s
== atf::tests::tcr::passed_state
) {
413 (*m_os
) << "<passed />" << std::endl
;
414 } else if (s
== atf::tests::tcr::failed_state
) {
415 (*m_os
) << "<failed>" << elemval(tcr
.get_reason())
416 << "</failed>" << std::endl
;
417 } else if (s
== atf::tests::tcr::skipped_state
) {
418 (*m_os
) << "<skipped>" << elemval(tcr
.get_reason())
419 << "</skipped>" << std::endl
;
422 (*m_os
) << "</tc>" << std::endl
;
428 (*m_os
) << "</tests-results>" << std::endl
;
432 xml_writer(const atf::fs::path
& p
) :
433 m_os(open_outfile(p
))
435 (*m_os
) << "<?xml version=\"1.0\"?>" << std::endl
436 << "<!DOCTYPE tests-results PUBLIC "
437 "\"-//NetBSD//DTD ATF Tests Results 0.1//EN\" "
438 "\"http://www.NetBSD.org/XML/atf/tests-results.dtd\">"
441 << "<tests-results>" << std::endl
;
445 // ------------------------------------------------------------------------
446 // The "converter" class.
447 // ------------------------------------------------------------------------
450 //! \brief A reader that redirects events to multiple writers.
452 //! The converter class implements an atf_tps_reader that, for each event
453 //! raised by the parser, redirects it to multiple writers so that they
454 //! can reformat it according to their output rules.
456 class converter
: public atf::formats::atf_tps_reader
{
457 typedef std::vector
< writer
* > outs_vector
;
461 got_info(const std::string
& what
, const std::string
& val
)
463 for (outs_vector::iterator iter
= m_outs
.begin();
464 iter
!= m_outs
.end(); iter
++)
465 (*iter
)->write_info(what
, val
);
469 got_ntps(size_t ntps
)
471 for (outs_vector::iterator iter
= m_outs
.begin();
472 iter
!= m_outs
.end(); iter
++)
473 (*iter
)->write_ntps(ntps
);
477 got_tp_start(const std::string
& tp
, size_t ntcs
)
479 for (outs_vector::iterator iter
= m_outs
.begin();
480 iter
!= m_outs
.end(); iter
++)
481 (*iter
)->write_tp_start(tp
, ntcs
);
485 got_tp_end(const std::string
& reason
)
487 for (outs_vector::iterator iter
= m_outs
.begin();
488 iter
!= m_outs
.end(); iter
++)
489 (*iter
)->write_tp_end(reason
);
493 got_tc_start(const std::string
& tcname
)
495 for (outs_vector::iterator iter
= m_outs
.begin();
496 iter
!= m_outs
.end(); iter
++)
497 (*iter
)->write_tc_start(tcname
);
501 got_tc_stdout_line(const std::string
& line
)
503 for (outs_vector::iterator iter
= m_outs
.begin();
504 iter
!= m_outs
.end(); iter
++)
505 (*iter
)->write_tc_stdout_line(line
);
509 got_tc_stderr_line(const std::string
& line
)
511 for (outs_vector::iterator iter
= m_outs
.begin();
512 iter
!= m_outs
.end(); iter
++)
513 (*iter
)->write_tc_stderr_line(line
);
517 got_tc_end(const atf::tests::tcr
& tcr
)
519 for (outs_vector::iterator iter
= m_outs
.begin();
520 iter
!= m_outs
.end(); iter
++)
521 (*iter
)->write_tc_end(tcr
);
527 for (outs_vector::iterator iter
= m_outs
.begin();
528 iter
!= m_outs
.end(); iter
++)
529 (*iter
)->write_eof();
533 converter(std::istream
& is
) :
534 atf::formats::atf_tps_reader(is
)
540 for (outs_vector::iterator iter
= m_outs
.begin();
541 iter
!= m_outs
.end(); iter
++)
546 add_output(const std::string
& fmt
, const atf::fs::path
& p
)
549 m_outs
.push_back(new csv_writer(p
));
550 } else if (fmt
== "ticker") {
551 m_outs
.push_back(new ticker_writer(p
));
552 } else if (fmt
== "xml") {
553 m_outs
.push_back(new xml_writer(p
));
555 throw std::runtime_error("Unknown format `" + fmt
+ "'");
559 // ------------------------------------------------------------------------
560 // The "atf_report" class.
561 // ------------------------------------------------------------------------
563 class atf_report
: public atf::application::app
{
564 static const char* m_description
;
566 typedef std::pair
< std::string
, atf::fs::path
> fmt_path_pair
;
567 std::vector
< fmt_path_pair
> m_oflags
;
569 void process_option(int, const char*);
570 options_set
specific_options(void) const;
578 const char* atf_report::m_description
=
579 "atf-report is a tool that parses the output of atf-run and "
580 "generates user-friendly reports in multiple different formats.";
582 atf_report::atf_report(void) :
583 app(m_description
, "atf-report(1)", "atf(7)")
588 atf_report::process_option(int ch
, const char* arg
)
593 std::string
str(arg
);
594 std::string::size_type pos
= str
.find(':');
595 if (pos
== std::string::npos
)
596 throw std::runtime_error("Syntax error in -o option");
598 std::string fmt
= str
.substr(0, pos
);
599 atf::fs::path path
= atf::fs::path(str
.substr(pos
+ 1));
600 m_oflags
.push_back(fmt_path_pair(fmt
, path
));
610 atf_report::options_set
611 atf_report::specific_options(void)
614 using atf::application::option
;
616 opts
.insert(option('o', "fmt:path", "Adds a new output file; multiple "
617 "ones can be specified, and a - "
618 "path means stdout"));
623 atf_report::main(void)
625 if (m_oflags
.empty())
626 m_oflags
.push_back(fmt_path_pair("ticker", atf::fs::path("-")));
628 // Look for path duplicates.
629 std::set
< atf::fs::path
> paths
;
630 for (std::vector
< fmt_path_pair
>::const_iterator iter
= m_oflags
.begin();
631 iter
!= m_oflags
.end(); iter
++) {
632 atf::fs::path p
= (*iter
).second
;
633 if (p
== atf::fs::path("/dev/stdout"))
634 p
= atf::fs::path("-");
635 if (paths
.find(p
) != paths
.end())
636 throw std::runtime_error("The file `" + p
.str() + "' was "
637 "specified more than once");
638 paths
.insert((*iter
).second
);
641 // Generate the output files.
642 converter
cnv(std::cin
);
643 for (std::vector
< fmt_path_pair
>::const_iterator iter
= m_oflags
.begin();
644 iter
!= m_oflags
.end(); iter
++)
645 cnv
.add_output((*iter
).first
, (*iter
).second
);
652 main(int argc
, char* const* argv
)
654 return atf_report().run(argc
, argv
);