1 // 2 // Automated Testing Framework (atf) 3 // 4 // Copyright (c) 2007, 2008 The NetBSD Foundation, Inc. 5 // All rights reserved. 6 // 7 // Redistribution and use in source and binary forms, with or without 8 // modification, are permitted provided that the following conditions 9 // are met: 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. 15 // 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. 28 // 29 30 #include <cstdlib> 31 #include <fstream> 32 #include <iostream> 33 #include <memory> 34 #include <utility> 35 #include <vector> 36 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" 43 44 typedef std::auto_ptr< std::ostream > ostream_ptr; 45 46 ostream_ptr 47 open_outfile(const atf::fs::path& path) 48 { 49 ostream_ptr osp; 50 if (path.str() == "-") 51 osp = ostream_ptr(new std::ofstream("/dev/stdout")); 52 else 53 osp = ostream_ptr(new std::ofstream(path.c_str())); 54 if (!(*osp)) 55 throw std::runtime_error("Could not create file " + path.str()); 56 return osp; 57 } 58 59 // ------------------------------------------------------------------------ 60 // The "writer" interface. 61 // ------------------------------------------------------------------------ 62 63 //! 64 //! \brief A base class that defines an output format. 65 //! 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. 69 //! 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 72 //! multiple ones. 73 //! 74 class writer { 75 public: 76 writer(void) {} 77 virtual ~writer(void) {} 78 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) {} 88 }; 89 90 // ------------------------------------------------------------------------ 91 // The "csv_writer" class. 92 // ------------------------------------------------------------------------ 93 94 //! 95 //! \brief A very simple plain-text output format. 96 //! 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. 101 //! 102 class csv_writer : public writer { 103 ostream_ptr m_os; 104 bool m_failed; 105 106 std::string m_tpname; 107 std::string m_tcname; 108 109 public: 110 csv_writer(const atf::fs::path& p) : 111 m_os(open_outfile(p)) 112 { 113 } 114 115 virtual 116 void 117 write_tp_start(const std::string& name, size_t ntcs) 118 { 119 m_tpname = name; 120 m_failed = false; 121 } 122 123 virtual 124 void 125 write_tp_end(const std::string& reason) 126 { 127 if (!reason.empty()) 128 (*m_os) << "tp, " << m_tpname << ", bogus, " << reason 129 << std::endl; 130 else if (m_failed) 131 (*m_os) << "tp, " << m_tpname << ", failed" << std::endl; 132 else 133 (*m_os) << "tp, " << m_tpname << ", passed" << std::endl; 134 } 135 136 virtual 137 void 138 write_tc_start(const std::string& name) 139 { 140 m_tcname = name; 141 } 142 143 virtual 144 void 145 write_tc_end(const atf::tests::tcr& tcr) 146 { 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, " + 152 tcr.get_reason(); 153 m_failed = true; 154 } else if (tcr.get_state() == atf::tests::tcr::skipped_state) { 155 str += m_tpname + ", " + m_tcname + ", skipped, " + 156 tcr.get_reason(); 157 } else 158 UNREACHABLE; 159 (*m_os) << str << std::endl; 160 } 161 }; 162 163 // ------------------------------------------------------------------------ 164 // The "ticker_writer" class. 165 // ------------------------------------------------------------------------ 166 167 //! 168 //! \brief A console-friendly output format. 169 //! 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. 174 //! 175 class ticker_writer : public writer { 176 ostream_ptr m_os; 177 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; 183 184 void 185 write_info(const std::string& what, const std::string& val) 186 { 187 if (what == "tests.root") { 188 (*m_os) << "Tests root: " << val << std::endl 189 << std::endl; 190 } 191 } 192 193 void 194 write_ntps(size_t ntps) 195 { 196 m_curtp = 1; 197 m_tcs_passed = 0; 198 m_tcs_failed = 0; 199 m_tcs_skipped = 0; 200 m_ntps = ntps; 201 } 202 203 void 204 write_tp_start(const std::string& tp, size_t ntcs) 205 { 206 using atf::text::to_string; 207 using atf::ui::format_text; 208 209 m_tpname = tp; 210 211 (*m_os) << format_text(tp + " (" + to_string(m_curtp) + 212 "/" + to_string(m_ntps) + "): " + 213 to_string(ntcs) + " test cases") 214 << std::endl; 215 (*m_os).flush(); 216 } 217 218 void 219 write_tp_end(const std::string& reason) 220 { 221 using atf::ui::format_text_with_tag; 222 223 m_curtp++; 224 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) 230 << std::endl; 231 m_failed_tps.push_back(m_tpname); 232 } 233 (*m_os) << std::endl; 234 (*m_os).flush(); 235 236 m_tpname.clear(); 237 } 238 239 void 240 write_tc_start(const std::string& tcname) 241 { 242 m_tcname = tcname; 243 244 (*m_os) << " " + tcname + ": "; 245 (*m_os).flush(); 246 } 247 248 void 249 write_tc_end(const atf::tests::tcr& tcr) 250 { 251 std::string str; 252 253 atf::tests::tcr::state s = tcr.get_state(); 254 if (s == atf::tests::tcr::passed_state) { 255 str = "Passed."; 256 m_tcs_passed++; 257 } else if (s == atf::tests::tcr::failed_state) { 258 str = "Failed: " + tcr.get_reason(); 259 m_tcs_failed++; 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(); 263 m_tcs_skipped++; 264 } else 265 UNREACHABLE; 266 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; 271 272 m_tcname = ""; 273 } 274 275 void 276 write_eof(void) 277 { 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; 282 283 if (!m_failed_tps.empty()) { 284 (*m_os) << format_text("Failed (bogus) test programs:") 285 << std::endl; 286 (*m_os) << format_text_with_tag(join(m_failed_tps, ", "), 287 " ", false) << std::endl 288 << std::endl; 289 } 290 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 295 << std::endl; 296 } 297 298 (*m_os) << format_text("Summary for " + to_string(m_ntps) + 299 " test programs:") 300 << std::endl; 301 (*m_os) << format_text_with_tag(to_string(m_tcs_passed) + 302 " passed test cases.", 303 " ", false) 304 << std::endl; 305 (*m_os) << format_text_with_tag(to_string(m_tcs_failed) + 306 " failed test cases.", 307 " ", false) 308 << std::endl; 309 (*m_os) << format_text_with_tag(to_string(m_tcs_skipped) + 310 " skipped test cases.", 311 " ", false) 312 << std::endl; 313 } 314 315 public: 316 ticker_writer(const atf::fs::path& p) : 317 m_os(open_outfile(p)) 318 { 319 } 320 }; 321 322 // ------------------------------------------------------------------------ 323 // The "xml" class. 324 // ------------------------------------------------------------------------ 325 326 //! 327 //! \brief A single-file XML output format. 328 //! 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 331 //! utilities. 332 //! 333 class xml_writer : public writer { 334 ostream_ptr m_os; 335 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; 341 342 static 343 std::string 344 attrval(const std::string& str) 345 { 346 return str; 347 } 348 349 static 350 std::string 351 elemval(const std::string& str) 352 { 353 std::string ostr; 354 for (std::string::const_iterator iter = str.begin(); 355 iter != str.end(); iter++) { 356 switch (*iter) { 357 case '&': ostr += "&"; break; 358 case '<': ostr += "<"; break; 359 case '>': ostr += ">"; break; 360 default: ostr += *iter; 361 } 362 } 363 return ostr; 364 } 365 366 void 367 write_info(const std::string& what, const std::string& val) 368 { 369 (*m_os) << "<info class=\"" << what << "\">" << val << "</info>" 370 << std::endl; 371 } 372 373 void 374 write_tp_start(const std::string& tp, size_t ntcs) 375 { 376 (*m_os) << "<tp id=\"" << attrval(tp) << "\">" << std::endl; 377 } 378 379 void 380 write_tp_end(const std::string& reason) 381 { 382 if (!reason.empty()) 383 (*m_os) << "<failed>" << elemval(reason) << "</failed>" 384 << std::endl; 385 (*m_os) << "</tp>" << std::endl; 386 } 387 388 void 389 write_tc_start(const std::string& tcname) 390 { 391 (*m_os) << "<tc id=\"" << attrval(tcname) << "\">" << std::endl; 392 } 393 394 void 395 write_tc_stdout_line(const std::string& line) 396 { 397 (*m_os) << "<so>" << elemval(line) << "</so>" << std::endl; 398 } 399 400 void 401 write_tc_stderr_line(const std::string& line) 402 { 403 (*m_os) << "<se>" << elemval(line) << "</se>" << std::endl; 404 } 405 406 void 407 write_tc_end(const atf::tests::tcr& tcr) 408 { 409 std::string str; 410 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; 420 } else 421 UNREACHABLE; 422 (*m_os) << "</tc>" << std::endl; 423 } 424 425 void 426 write_eof(void) 427 { 428 (*m_os) << "</tests-results>" << std::endl; 429 } 430 431 public: 432 xml_writer(const atf::fs::path& p) : 433 m_os(open_outfile(p)) 434 { 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\">" 439 << std::endl 440 << std::endl 441 << "<tests-results>" << std::endl; 442 } 443 }; 444 445 // ------------------------------------------------------------------------ 446 // The "converter" class. 447 // ------------------------------------------------------------------------ 448 449 //! 450 //! \brief A reader that redirects events to multiple writers. 451 //! 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. 455 //! 456 class converter : public atf::formats::atf_tps_reader { 457 typedef std::vector< writer* > outs_vector; 458 outs_vector m_outs; 459 460 void 461 got_info(const std::string& what, const std::string& val) 462 { 463 for (outs_vector::iterator iter = m_outs.begin(); 464 iter != m_outs.end(); iter++) 465 (*iter)->write_info(what, val); 466 } 467 468 void 469 got_ntps(size_t ntps) 470 { 471 for (outs_vector::iterator iter = m_outs.begin(); 472 iter != m_outs.end(); iter++) 473 (*iter)->write_ntps(ntps); 474 } 475 476 void 477 got_tp_start(const std::string& tp, size_t ntcs) 478 { 479 for (outs_vector::iterator iter = m_outs.begin(); 480 iter != m_outs.end(); iter++) 481 (*iter)->write_tp_start(tp, ntcs); 482 } 483 484 void 485 got_tp_end(const std::string& reason) 486 { 487 for (outs_vector::iterator iter = m_outs.begin(); 488 iter != m_outs.end(); iter++) 489 (*iter)->write_tp_end(reason); 490 } 491 492 void 493 got_tc_start(const std::string& tcname) 494 { 495 for (outs_vector::iterator iter = m_outs.begin(); 496 iter != m_outs.end(); iter++) 497 (*iter)->write_tc_start(tcname); 498 } 499 500 void 501 got_tc_stdout_line(const std::string& line) 502 { 503 for (outs_vector::iterator iter = m_outs.begin(); 504 iter != m_outs.end(); iter++) 505 (*iter)->write_tc_stdout_line(line); 506 } 507 508 void 509 got_tc_stderr_line(const std::string& line) 510 { 511 for (outs_vector::iterator iter = m_outs.begin(); 512 iter != m_outs.end(); iter++) 513 (*iter)->write_tc_stderr_line(line); 514 } 515 516 void 517 got_tc_end(const atf::tests::tcr& tcr) 518 { 519 for (outs_vector::iterator iter = m_outs.begin(); 520 iter != m_outs.end(); iter++) 521 (*iter)->write_tc_end(tcr); 522 } 523 524 void 525 got_eof(void) 526 { 527 for (outs_vector::iterator iter = m_outs.begin(); 528 iter != m_outs.end(); iter++) 529 (*iter)->write_eof(); 530 } 531 532 public: 533 converter(std::istream& is) : 534 atf::formats::atf_tps_reader(is) 535 { 536 } 537 538 ~converter(void) 539 { 540 for (outs_vector::iterator iter = m_outs.begin(); 541 iter != m_outs.end(); iter++) 542 delete *iter; 543 } 544 545 void 546 add_output(const std::string& fmt, const atf::fs::path& p) 547 { 548 if (fmt == "csv") { 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)); 554 } else 555 throw std::runtime_error("Unknown format `" + fmt + "'"); 556 } 557 }; 558 559 // ------------------------------------------------------------------------ 560 // The "atf_report" class. 561 // ------------------------------------------------------------------------ 562 563 class atf_report : public atf::application::app { 564 static const char* m_description; 565 566 typedef std::pair< std::string, atf::fs::path > fmt_path_pair; 567 std::vector< fmt_path_pair > m_oflags; 568 569 void process_option(int, const char*); 570 options_set specific_options(void) const; 571 572 public: 573 atf_report(void); 574 575 int main(void); 576 }; 577 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."; 581 582 atf_report::atf_report(void) : 583 app(m_description, "atf-report(1)", "atf(7)") 584 { 585 } 586 587 void 588 atf_report::process_option(int ch, const char* arg) 589 { 590 switch (ch) { 591 case 'o': 592 { 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"); 597 else { 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)); 601 } 602 } 603 break; 604 605 default: 606 UNREACHABLE; 607 } 608 } 609 610 atf_report::options_set 611 atf_report::specific_options(void) 612 const 613 { 614 using atf::application::option; 615 options_set opts; 616 opts.insert(option('o', "fmt:path", "Adds a new output file; multiple " 617 "ones can be specified, and a - " 618 "path means stdout")); 619 return opts; 620 } 621 622 int 623 atf_report::main(void) 624 { 625 if (m_oflags.empty()) 626 m_oflags.push_back(fmt_path_pair("ticker", atf::fs::path("-"))); 627 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); 639 } 640 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); 646 cnv.read(); 647 648 return EXIT_SUCCESS; 649 } 650 651 int 652 main(int argc, char* const* argv) 653 { 654 return atf_report().run(argc, argv); 655 } 656