2 * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 * @file test_results_collector.h
18 * @author Lukasz Wrzosek (l.wrzosek@samsung.com)
20 * @brief Implementation file some concrete TestResulstsCollector
23 #include <dpl/test/test_results_collector.h>
24 #include <dpl/colors.h>
25 #include <dpl/assert.h>
26 #include <dpl/foreach.h>
27 #include <dpl/scoped_fclose.h>
28 #include <dpl/exception.h>
29 #include <dpl/errno_string.h>
38 #define GREEN_RESULT_OK "[%s%s%s]\n", BOLD_GREEN_BEGIN, " OK ", \
44 const char *DEFAULT_HTML_FILE_NAME = "index.html";
45 const char *DEFAULT_TAP_FILE_NAME = "results.tap";
46 const char *DEFAULT_XML_FILE_NAME = "results.xml";
48 bool ParseCollectorFileArg(const std::string &arg, std::string &filename)
50 const std::string argname = "--file=";
51 if (0 == arg.find(argname)) {
52 filename = arg.substr(argname.size());
68 void AddTest(TestResultsCollectorBase::FailStatus::Type type)
72 case TestResultsCollectorBase::FailStatus::INTERNAL:
73 case TestResultsCollectorBase::FailStatus::FAILED: ++m_failed;
75 case TestResultsCollectorBase::FailStatus::IGNORED: ++m_ignored;
77 case TestResultsCollectorBase::FailStatus::NONE: ++m_passed;
80 Assert(false && "Bad FailStatus");
84 std::size_t GetTotal() const
88 std::size_t GetPassed() const
92 std::size_t GetSuccesed() const
96 std::size_t GetFailed() const
100 std::size_t GetIgnored() const
104 float GetPassedOrIgnoredPercend() const
106 float passIgnoredPercent =
107 100.0f * (static_cast<float>(m_passed)
108 + static_cast<float>(m_ignored))
109 / static_cast<float>(m_count);
110 return passIgnoredPercent;
114 std::size_t m_failed;
115 std::size_t m_ignored;
116 std::size_t m_passed;
120 class ConsoleCollector :
121 public TestResultsCollectorBase
124 static TestResultsCollectorBase* Constructor();
127 ConsoleCollector() {}
129 virtual void CollectCurrentTestGroupName(const std::string& name)
131 printf("Starting group %s\n", name.c_str());
132 m_currentGroup = name;
135 virtual void Finish()
137 using namespace DPL::Colors::Text;
140 FOREACH(group, m_groupsStats) {
141 PrintStats(group->first, group->second);
143 PrintStats("All tests together", m_stats);
146 virtual void CollectResult(const std::string& id,
147 const std::string& /*description*/,
148 const FailStatus::Type status = FailStatus::NONE,
149 const std::string& reason = "")
151 using namespace DPL::Colors::Text;
152 std::string tmp = "'" + id + "' ...";
154 printf("Running test case %-60s", tmp.c_str());
156 case TestResultsCollectorBase::FailStatus::NONE:
157 printf(GREEN_RESULT_OK);
159 case TestResultsCollectorBase::FailStatus::FAILED:
160 PrintfErrorMessage(" FAILED ", reason, true);
162 case TestResultsCollectorBase::FailStatus::IGNORED:
163 PrintfIgnoredMessage("Ignored ", reason, true);
165 case TestResultsCollectorBase::FailStatus::INTERNAL:
166 PrintfErrorMessage("INTERNAL", reason, true);
169 Assert(false && "Bad status");
171 m_stats.AddTest(status);
172 m_groupsStats[m_currentGroup].AddTest(status);
175 void PrintfErrorMessage(const char* type,
176 const std::string& message,
179 using namespace DPL::Colors::Text;
181 printf("[%s%s%s] %s%s%s\n",
196 void PrintfIgnoredMessage(const char* type,
197 const std::string& message,
200 using namespace DPL::Colors::Text;
202 printf("[%s%s%s] %s%s%s\n",
217 void PrintStats(const std::string& title, const Statistic& stats)
219 using namespace DPL::Colors::Text;
220 printf("\n%sResults [%s]: %s\n", BOLD_GREEN_BEGIN,
221 title.c_str(), BOLD_GREEN_END);
222 printf("%s%s%3d%s\n",
227 printf(" %s%s%3d%s\n",
232 printf(" %s%s%3d%s\n",
237 printf(" %s%s%3d%s\n",
245 std::map<std::string, Statistic> m_groupsStats;
246 std::string m_currentGroup;
249 TestResultsCollectorBase* ConsoleCollector::Constructor()
251 return new ConsoleCollector();
254 class HtmlCollector :
255 public TestResultsCollectorBase
258 static TestResultsCollectorBase* Constructor();
261 HtmlCollector() : m_filename(DEFAULT_HTML_FILE_NAME) {}
263 virtual void CollectCurrentTestGroupName(const std::string& name)
265 fprintf(m_fp.Get(), "<b>Starting group %s", name.c_str());
266 m_currentGroup = name;
269 virtual bool Configure()
271 m_fp.Reset(fopen(m_filename.c_str(), "w"));
273 LogPedantic("Could not open file " << m_filename << " for writing");
278 virtual std::string CollectorSpecificHelp() const
280 return "--file=<filename> - name of file for output\n"
281 " default - index.html\n";
286 Assert(!!m_fp && "File handle must not be null");
288 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0"
289 "Transitional//EN\" "
290 "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
293 "<html xmlns=\"http://www.w3.org/1999/xhtml\" "
294 "lang=\"en\" dir=\"ltr\">\n");
295 fprintf(m_fp.Get(), "<body style=\"background-color: black;\">\n");
296 fprintf(m_fp.Get(), "<pre>\n");
297 fprintf(m_fp.Get(), "<font color=\"white\">\n");
300 virtual void Finish()
302 using namespace DPL::Colors::Html;
304 FOREACH(group, m_groupsStats) {
305 PrintStats(group->first, group->second);
307 PrintStats("All tests together", m_stats);
308 fprintf(m_fp.Get(), "</font>\n");
309 fprintf(m_fp.Get(), "</pre>\n");
310 fprintf(m_fp.Get(), "</body>\n");
311 fprintf(m_fp.Get(), "</html>\n");
314 virtual bool ParseCollectorSpecificArg(const std::string& arg)
316 return ParseCollectorFileArg(arg, m_filename);
319 virtual void CollectResult(const std::string& id,
320 const std::string& /*description*/,
321 const FailStatus::Type status = FailStatus::NONE,
322 const std::string& reason = "")
324 using namespace DPL::Colors::Html;
325 std::string tmp = "'" + id + "' ...";
327 fprintf(m_fp.Get(), "Running test case %-100s", tmp.c_str());
329 case TestResultsCollectorBase::FailStatus::NONE:
330 fprintf(m_fp.Get(), GREEN_RESULT_OK);
332 case TestResultsCollectorBase::FailStatus::FAILED:
333 PrintfErrorMessage(" FAILED ", reason, true);
335 case TestResultsCollectorBase::FailStatus::IGNORED:
336 PrintfIgnoredMessage("Ignored ", reason, true);
338 case TestResultsCollectorBase::FailStatus::INTERNAL:
339 PrintfErrorMessage("INTERNAL", reason, true);
342 Assert(false && "Bad status");
344 m_groupsStats[m_currentGroup].AddTest(status);
345 m_stats.AddTest(status);
348 void PrintfErrorMessage(const char* type,
349 const std::string& message,
352 using namespace DPL::Colors::Html;
371 void PrintfIgnoredMessage(const char* type,
372 const std::string& message,
375 using namespace DPL::Colors::Html;
395 void PrintStats(const std::string& name, const Statistic& stats)
397 using namespace DPL::Colors::Html;
399 m_fp.Get(), "\n%sResults [%s]:%s\n", BOLD_GREEN_BEGIN,
400 name.c_str(), BOLD_GREEN_END);
402 m_fp.Get(), "%s%s%3d%s\n", CYAN_BEGIN,
403 "Total tests: ", stats.GetTotal(), CYAN_END);
405 m_fp.Get(), " %s%s%3d%s\n", CYAN_BEGIN,
406 "Succeeded: ", stats.GetPassed(), CYAN_END);
408 m_fp.Get(), " %s%s%3d%s\n", CYAN_BEGIN,
409 "Failed: ", stats.GetFailed(), CYAN_END);
411 m_fp.Get(), " %s%s%3d%s\n", CYAN_BEGIN,
412 "Ignored: ", stats.GetIgnored(), CYAN_END);
415 std::string m_filename;
418 std::string m_currentGroup;
419 std::map<std::string, Statistic> m_groupsStats;
422 TestResultsCollectorBase* HtmlCollector::Constructor()
424 return new HtmlCollector();
428 public TestResultsCollectorBase
431 static TestResultsCollectorBase* Constructor();
434 XmlCollector() : m_filename(DEFAULT_XML_FILE_NAME) {}
436 virtual void CollectCurrentTestGroupName(const std::string& name)
438 std::size_t pos = GetCurrentGroupPosition();
439 if (std::string::npos != pos) {
442 m_stats = Statistic();
445 pos = m_outputBuffer.find("</testsuites>");
446 if (std::string::npos == pos) {
447 ThrowMsg(DPL::Exception, "Could not find test suites closing tag");
449 GroupStart(pos, name);
452 void GroupStart(const std::size_t pos, const std::string& name)
454 std::stringstream groupHeader;
455 groupHeader << "\n\t<testsuite";
456 groupHeader << " name=\"" << EscapeSpecialCharacters(name) << "\"";
457 groupHeader << R"( tests="1")"; // include SegFault
458 groupHeader << R"( failures="1")"; // include SegFault
459 groupHeader << R"( skipped="0")";
462 groupHeader << "\n\t\t<testcase name=\"unknown\" status=\"FAILED\">";
464 "\n\t\t\t<failure type=\"FAILED\" message=\"segmentation fault\"/>";
465 groupHeader << "\n\t\t</testcase>";
467 groupHeader << "\n\t</testsuite>";
469 m_outputBuffer.insert(pos - 1, groupHeader.str());
472 virtual bool Configure()
474 m_fp.Reset(fopen(m_filename.c_str(), "w"));
476 LogPedantic("Could not open file " << m_filename << " for writing");
482 virtual std::string CollectorSpecificHelp() const
484 return "--file=<filename> - name of file for output\n"
485 " default - results.xml\n";
490 Assert(!!m_fp && "File handle must not be null");
491 m_outputBuffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
492 m_outputBuffer.append("<testsuites>\n</testsuites>");
496 virtual void Finish()
498 std::size_t pos = GetCurrentGroupPosition();
499 if (std::string::npos != pos) {
505 virtual bool ParseCollectorSpecificArg(const std::string& arg)
507 return ParseCollectorFileArg(arg, m_filename);
510 virtual void CollectResult(const std::string& id,
511 const std::string& /*description*/,
512 const FailStatus::Type status = FailStatus::NONE,
513 const std::string& reason = "")
515 m_resultBuffer.erase();
516 m_resultBuffer.append("\t\t<testcase name=\"");
517 m_resultBuffer.append(EscapeSpecialCharacters(id));
518 m_resultBuffer.append("\"");
520 case TestResultsCollectorBase::FailStatus::NONE:
521 m_resultBuffer.append(" status=\"OK\"/>\n");
523 case TestResultsCollectorBase::FailStatus::FAILED:
524 m_resultBuffer.append(" status=\"FAILED\">\n");
525 PrintfErrorMessage("FAILED", EscapeSpecialCharacters(reason), true);
526 m_resultBuffer.append("\t\t</testcase>\n");
528 case TestResultsCollectorBase::FailStatus::IGNORED:
529 m_resultBuffer.append(" status=\"Ignored\">\n");
530 PrintfIgnoredMessage("Ignored", EscapeSpecialCharacters(
532 m_resultBuffer.append("\t\t</testcase>\n");
534 case TestResultsCollectorBase::FailStatus::INTERNAL:
535 m_resultBuffer.append(" status=\"FAILED\">\n");
536 PrintfErrorMessage("INTERNAL", EscapeSpecialCharacters(
538 m_resultBuffer.append("\t\t</testcase>");
541 Assert(false && "Bad status");
543 std::size_t group_pos = GetCurrentGroupPosition();
544 if (std::string::npos == group_pos) {
545 ThrowMsg(DPL::Exception, "No current group set");
548 std::size_t last_case_pos = m_outputBuffer.find(
549 "<testcase name=\"unknown\"",
551 if (std::string::npos == last_case_pos) {
552 ThrowMsg(DPL::Exception, "Could not find SegFault test case");
554 m_outputBuffer.insert(last_case_pos - 2, m_resultBuffer);
556 m_stats.AddTest(status);
558 UpdateGroupHeader(group_pos,
559 m_stats.GetTotal() + 1, // include SegFault
560 m_stats.GetFailed() + 1, // include SegFault
561 m_stats.GetIgnored());
565 std::size_t GetCurrentGroupPosition() const
567 return m_outputBuffer.rfind("<testsuite ");
570 void UpdateGroupHeader(const std::size_t groupPosition,
571 const unsigned int tests,
572 const unsigned int failures,
573 const unsigned int skipped)
575 UpdateElementAttribute(groupPosition, "tests", UIntToString(tests));
576 UpdateElementAttribute(groupPosition, "failures", UIntToString(failures));
577 UpdateElementAttribute(groupPosition, "skipped", UIntToString(skipped));
580 void UpdateElementAttribute(const std::size_t elementPosition,
581 const std::string& name,
582 const std::string& value)
584 std::string pattern = name + "=\"";
586 std::size_t start = m_outputBuffer.find(pattern, elementPosition);
587 if (std::string::npos == start) {
588 ThrowMsg(DPL::Exception,
589 "Could not find attribute " << name << " beginning");
592 std::size_t end = m_outputBuffer.find("\"", start + pattern.length());
593 if (std::string::npos == end) {
594 ThrowMsg(DPL::Exception,
595 "Could not find attribute " << name << " end");
598 m_outputBuffer.replace(start + pattern.length(),
599 end - start - pattern.length(),
603 std::string UIntToString(const unsigned int value)
605 std::stringstream result;
610 void GroupFinish(const std::size_t groupPosition)
612 std::size_t segFaultStart =
613 m_outputBuffer.find("<testcase name=\"unknown\"", groupPosition);
614 if (std::string::npos == segFaultStart) {
615 ThrowMsg(DPL::Exception,
616 "Could not find SegFault test case start position");
618 segFaultStart -= 2; // to erase tabs
620 std::string closeTag = "</testcase>";
621 std::size_t segFaultEnd = m_outputBuffer.find(closeTag, segFaultStart);
622 if (std::string::npos == segFaultEnd) {
623 ThrowMsg(DPL::Exception,
624 "Could not find SegFault test case end position");
626 segFaultEnd += closeTag.length() + 1; // to erase new line
628 m_outputBuffer.erase(segFaultStart, segFaultEnd - segFaultStart);
630 UpdateGroupHeader(groupPosition,
633 m_stats.GetIgnored());
638 int fd = fileno(m_fp.Get());
641 ThrowMsg(DPL::Exception, DPL::GetErrnoString(error));
644 if (-1 == TEMP_FAILURE_RETRY(ftruncate(fd, 0L))) {
646 ThrowMsg(DPL::Exception, DPL::GetErrnoString(error));
649 if (-1 == TEMP_FAILURE_RETRY(fseek(m_fp.Get(), 0L, SEEK_SET))) {
651 ThrowMsg(DPL::Exception, DPL::GetErrnoString(error));
654 if (m_outputBuffer.size() !=
655 fwrite(m_outputBuffer.c_str(), 1, m_outputBuffer.size(),
659 ThrowMsg(DPL::Exception, DPL::GetErrnoString(error));
662 if (-1 == TEMP_FAILURE_RETRY(fflush(m_fp.Get()))) {
664 ThrowMsg(DPL::Exception, DPL::GetErrnoString(error));
668 void PrintfErrorMessage(const char* type,
669 const std::string& message,
673 m_resultBuffer.append("\t\t\t<failure type=\"");
674 m_resultBuffer.append(EscapeSpecialCharacters(type));
675 m_resultBuffer.append("\" message=\"");
676 m_resultBuffer.append(EscapeSpecialCharacters(message));
677 m_resultBuffer.append("\"/>\n");
679 m_resultBuffer.append("\t\t\t<failure type=\"");
680 m_resultBuffer.append(EscapeSpecialCharacters(type));
681 m_resultBuffer.append("\"/>\n");
685 void PrintfIgnoredMessage(const char* type,
686 const std::string& message,
690 m_resultBuffer.append("\t\t\t<skipped type=\"");
691 m_resultBuffer.append(EscapeSpecialCharacters(type));
692 m_resultBuffer.append("\" message=\"");
693 m_resultBuffer.append(EscapeSpecialCharacters(message));
694 m_resultBuffer.append("\"/>\n");
696 m_resultBuffer.append("\t\t\t<skipped type=\"");
697 m_resultBuffer.append(EscapeSpecialCharacters(type));
698 m_resultBuffer.append("\"/>\n");
702 std::string EscapeSpecialCharacters(std::string s)
704 for (unsigned int i = 0; i < s.size();) {
708 s.insert(i, """);
714 s.insert(i, "&");
732 s.insert(i, "'");
743 std::string m_filename;
746 std::string m_outputBuffer;
747 std::string m_resultBuffer;
750 TestResultsCollectorBase* XmlCollector::Constructor()
752 return new XmlCollector();
756 public TestResultsCollectorBase
759 static TestResultsCollectorBase* Constructor();
766 printf("GROUP;ID;RESULT;REASON\n");
769 virtual void CollectCurrentTestGroupName(const std::string& name)
771 m_currentGroup = name;
774 virtual void CollectResult(const std::string& id,
775 const std::string& /*description*/,
776 const FailStatus::Type status = FailStatus::NONE,
777 const std::string& reason = "")
779 std::string statusMsg = "";
781 case TestResultsCollectorBase::FailStatus::NONE: statusMsg = "OK";
783 case TestResultsCollectorBase::FailStatus::FAILED: statusMsg = "FAILED";
785 case TestResultsCollectorBase::FailStatus::IGNORED: statusMsg =
788 case TestResultsCollectorBase::FailStatus::INTERNAL: statusMsg =
792 Assert(false && "Bad status");
794 printf("%s;%s;%s;%s\n",
795 m_currentGroup.c_str(),
801 std::string m_currentGroup;
804 TestResultsCollectorBase* CSVCollector::Constructor()
806 return new CSVCollector();
811 public TestResultsCollectorBase
814 static TestResultsCollectorBase* Constructor();
817 TAPCollector() : m_filename(DEFAULT_TAP_FILE_NAME) {}
819 virtual bool Configure()
821 m_output.open(m_filename.c_str(), std::ios_base::trunc);
822 if (m_output.fail()) {
823 LogError("Can't open output file: " << m_filename);
828 virtual std::string CollectorSpecificHelp() const
830 std::string retVal = "--file=<filename> - name of file for output\n"
832 retVal += DEFAULT_TAP_FILE_NAME;
839 Assert(m_output.good() && "Output file must be opened.");
840 m_output << "TAP version 13" << std::endl;
844 virtual void Finish()
846 m_output << "1.." << m_testIndex << std::endl;
847 m_output << m_collectedData.rdbuf();
851 virtual bool ParseCollectorSpecificArg(const std::string& arg)
853 return ParseCollectorFileArg(arg, m_filename);
856 virtual void CollectResult(const std::string& id,
857 const std::string& description,
858 const FailStatus::Type status = FailStatus::NONE,
859 const std::string& reason = "")
863 case TestResultsCollectorBase::FailStatus::NONE:
864 LogBasicTAP(true, id, description);
867 case TestResultsCollectorBase::FailStatus::FAILED:
868 LogBasicTAP(false, id, description);
871 case TestResultsCollectorBase::FailStatus::IGNORED:
872 LogBasicTAP(true, id, description);
873 m_collectedData << " # skip " << reason;
876 case TestResultsCollectorBase::FailStatus::INTERNAL:
877 LogBasicTAP(true, id, description);
879 m_collectedData << " ---" << std::endl;
880 m_collectedData << " message: " << reason << std::endl;
881 m_collectedData << " severity: Internal" << std::endl;
882 m_collectedData << " ..." << std::endl;
885 Assert(false && "Bad status");
889 void LogBasicTAP(bool isOK, const std::string& id,
890 const std::string& description)
893 m_collectedData << "not ";
895 m_collectedData << "ok " << m_testIndex << " [" <<
896 id << "] " << description;
901 m_collectedData << std::endl;
904 std::string m_filename;
905 std::stringstream m_collectedData;
906 std::ofstream m_output;
910 TestResultsCollectorBase* TAPCollector::Constructor()
912 return new TAPCollector();
915 void TestResultsCollectorBase::RegisterCollectorConstructor(
916 const std::string& name,
917 TestResultsCollectorBase::CollectorConstructorFunc func)
919 Assert(m_constructorsMap.find(name) == m_constructorsMap.end());
920 m_constructorsMap[name] = func;
923 TestResultsCollectorBase* TestResultsCollectorBase::Create(
924 const std::string& name)
926 ConstructorsMap::iterator found = m_constructorsMap.find(name);
927 if (found != m_constructorsMap.end()) {
928 return found->second();
934 std::vector<std::string> TestResultsCollectorBase::GetCollectorsNames()
936 std::vector<std::string> list;
937 FOREACH(it, m_constructorsMap)
939 list.push_back(it->first);
944 TestResultsCollectorBase::ConstructorsMap TestResultsCollectorBase::
948 static int RegisterCollectorConstructors();
949 static const int RegisterHelperVariable = RegisterCollectorConstructors();
950 int RegisterCollectorConstructors()
952 (void)RegisterHelperVariable;
954 TestResultsCollectorBase::RegisterCollectorConstructor(
956 &ConsoleCollector::Constructor);
957 TestResultsCollectorBase::RegisterCollectorConstructor(
959 &HtmlCollector::Constructor);
960 TestResultsCollectorBase::RegisterCollectorConstructor(
962 &CSVCollector::Constructor);
963 TestResultsCollectorBase::RegisterCollectorConstructor(
965 &TAPCollector::Constructor);
966 TestResultsCollectorBase::RegisterCollectorConstructor(
968 &XmlCollector::Constructor);
975 #undef GREEN_RESULT_OK