Tizen 2.0 Release
[framework/web/wrt-commons.git] / modules / test / src / test_results_collector.cpp
1 /*
2  * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved
3  *
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
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 /*
17  * @file        test_results_collector.h
18  * @author      Lukasz Wrzosek (l.wrzosek@samsung.com)
19  * @version     1.0
20  * @brief       Implementation file some concrete TestResulstsCollector
21  */
22 #include <cstddef>
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>
30
31 #include <string>
32 #include <string.h>
33 #include <cstdio>
34 #include <fstream>
35 #include <sstream>
36 #include <cstdlib>
37
38 #define GREEN_RESULT_OK "[%s%s%s]\n", BOLD_GREEN_BEGIN, "   OK   ", \
39     BOLD_GREEN_END
40
41 namespace DPL {
42 namespace Test {
43 namespace {
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";
47
48 bool ParseCollectorFileArg(const std::string &arg, std::string &filename)
49 {
50     const std::string argname = "--file=";
51     if (0 == arg.find(argname)) {
52         filename = arg.substr(argname.size());
53         return true;
54     }
55     return false;
56 }
57
58 class Statistic
59 {
60   public:
61     Statistic() :
62         m_failed(0),
63         m_ignored(0),
64         m_passed(0),
65         m_count(0)
66     {}
67
68     void AddTest(TestResultsCollectorBase::FailStatus::Type type)
69     {
70         ++m_count;
71         switch (type) {
72         case TestResultsCollectorBase::FailStatus::INTERNAL:
73         case TestResultsCollectorBase::FailStatus::FAILED:   ++m_failed;
74             break;
75         case TestResultsCollectorBase::FailStatus::IGNORED:  ++m_ignored;
76             break;
77         case TestResultsCollectorBase::FailStatus::NONE:     ++m_passed;
78             break;
79         default:
80             Assert(false && "Bad FailStatus");
81         }
82     }
83
84     std::size_t GetTotal() const
85     {
86         return m_count;
87     }
88     std::size_t GetPassed() const
89     {
90         return m_passed;
91     }
92     std::size_t GetSuccesed() const
93     {
94         return m_passed;
95     }
96     std::size_t GetFailed() const
97     {
98         return m_failed;
99     }
100     std::size_t GetIgnored() const
101     {
102         return m_ignored;
103     }
104     float GetPassedOrIgnoredPercend() const
105     {
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;
111     }
112
113   private:
114     std::size_t m_failed;
115     std::size_t m_ignored;
116     std::size_t m_passed;
117     std::size_t m_count;
118 };
119
120 class ConsoleCollector :
121     public TestResultsCollectorBase
122 {
123   public:
124     static TestResultsCollectorBase* Constructor();
125
126   private:
127     ConsoleCollector() {}
128
129     virtual void CollectCurrentTestGroupName(const std::string& name)
130     {
131         printf("Starting group %s\n", name.c_str());
132         m_currentGroup = name;
133     }
134
135     virtual void Finish()
136     {
137         using namespace DPL::Colors::Text;
138
139         // Show result
140         FOREACH(group, m_groupsStats) {
141             PrintStats(group->first, group->second);
142         }
143         PrintStats("All tests together", m_stats);
144     }
145
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 = "")
150     {
151         using namespace DPL::Colors::Text;
152         std::string tmp = "'" + id + "' ...";
153
154         printf("Running test case %-60s", tmp.c_str());
155         switch (status) {
156         case TestResultsCollectorBase::FailStatus::NONE:
157             printf(GREEN_RESULT_OK);
158             break;
159         case TestResultsCollectorBase::FailStatus::FAILED:
160             PrintfErrorMessage(" FAILED ", reason, true);
161             break;
162         case TestResultsCollectorBase::FailStatus::IGNORED:
163             PrintfIgnoredMessage("Ignored ", reason, true);
164             break;
165         case TestResultsCollectorBase::FailStatus::INTERNAL:
166             PrintfErrorMessage("INTERNAL", reason, true);
167             break;
168         default:
169             Assert(false && "Bad status");
170         }
171         m_stats.AddTest(status);
172         m_groupsStats[m_currentGroup].AddTest(status);
173     }
174
175     void PrintfErrorMessage(const char* type,
176                             const std::string& message,
177                             bool verbosity)
178     {
179         using namespace DPL::Colors::Text;
180         if (verbosity) {
181             printf("[%s%s%s] %s%s%s\n",
182                    BOLD_RED_BEGIN,
183                    type,
184                    BOLD_RED_END,
185                    BOLD_YELLOW_BEGIN,
186                    message.c_str(),
187                    BOLD_YELLOW_END);
188         } else {
189             printf("[%s%s%s]\n",
190                    BOLD_RED_BEGIN,
191                    type,
192                    BOLD_RED_END);
193         }
194     }
195
196     void PrintfIgnoredMessage(const char* type,
197                               const std::string& message,
198                               bool verbosity)
199     {
200         using namespace DPL::Colors::Text;
201         if (verbosity) {
202             printf("[%s%s%s] %s%s%s\n",
203                    CYAN_BEGIN,
204                    type,
205                    CYAN_END,
206                    BOLD_GOLD_BEGIN,
207                    message.c_str(),
208                    BOLD_GOLD_END);
209         } else {
210             printf("[%s%s%s]\n",
211                    CYAN_BEGIN,
212                    type,
213                    CYAN_END);
214         }
215     }
216
217     void PrintStats(const std::string& title, const Statistic& stats)
218     {
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",
223                CYAN_BEGIN,
224                "Total tests:            ",
225                stats.GetTotal(),
226                CYAN_END);
227         printf("  %s%s%3d%s\n",
228                CYAN_BEGIN,
229                "Succeeded:            ",
230                stats.GetPassed(),
231                CYAN_END);
232         printf("  %s%s%3d%s\n",
233                CYAN_BEGIN,
234                "Failed:               ",
235                stats.GetFailed(),
236                CYAN_END);
237         printf("  %s%s%3d%s\n",
238                CYAN_BEGIN,
239                "Ignored:              ",
240                stats.GetIgnored(),
241                CYAN_END);
242     }
243
244     Statistic m_stats;
245     std::map<std::string, Statistic> m_groupsStats;
246     std::string m_currentGroup;
247 };
248
249 TestResultsCollectorBase* ConsoleCollector::Constructor()
250 {
251     return new ConsoleCollector();
252 }
253
254 class HtmlCollector :
255     public TestResultsCollectorBase
256 {
257   public:
258     static TestResultsCollectorBase* Constructor();
259
260   private:
261     HtmlCollector() : m_filename(DEFAULT_HTML_FILE_NAME) {}
262
263     virtual void CollectCurrentTestGroupName(const std::string& name)
264     {
265         fprintf(m_fp.Get(), "<b>Starting group %s", name.c_str());
266         m_currentGroup = name;
267     }
268
269     virtual bool Configure()
270     {
271         m_fp.Reset(fopen(m_filename.c_str(), "w"));
272         if (!m_fp) {
273             LogPedantic("Could not open file " << m_filename << " for writing");
274             return false;
275         }
276         return true;
277     }
278     virtual std::string CollectorSpecificHelp() const
279     {
280         return "--file=<filename> - name of file for output\n"
281                "                    default - index.html\n";
282     }
283
284     virtual void Start()
285     {
286         Assert(!!m_fp && "File handle must not be null");
287         fprintf(m_fp.Get(),
288                 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0"
289                 "Transitional//EN\" "
290                 "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
291                 ">\n");
292         fprintf(m_fp.Get(),
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");
298     }
299
300     virtual void Finish()
301     {
302         using namespace DPL::Colors::Html;
303         // Show result
304         FOREACH(group, m_groupsStats) {
305             PrintStats(group->first, group->second);
306         }
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");
312     }
313
314     virtual bool ParseCollectorSpecificArg(const std::string& arg)
315     {
316         return ParseCollectorFileArg(arg, m_filename);
317     }
318
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 = "")
323     {
324         using namespace DPL::Colors::Html;
325         std::string tmp = "'" + id + "' ...";
326
327         fprintf(m_fp.Get(), "Running test case %-100s", tmp.c_str());
328         switch (status) {
329         case TestResultsCollectorBase::FailStatus::NONE:
330             fprintf(m_fp.Get(), GREEN_RESULT_OK);
331             break;
332         case TestResultsCollectorBase::FailStatus::FAILED:
333             PrintfErrorMessage(" FAILED ", reason, true);
334             break;
335         case TestResultsCollectorBase::FailStatus::IGNORED:
336             PrintfIgnoredMessage("Ignored ", reason, true);
337             break;
338         case TestResultsCollectorBase::FailStatus::INTERNAL:
339             PrintfErrorMessage("INTERNAL", reason, true);
340             break;
341         default:
342             Assert(false && "Bad status");
343         }
344         m_groupsStats[m_currentGroup].AddTest(status);
345         m_stats.AddTest(status);
346     }
347
348     void PrintfErrorMessage(const char* type,
349                             const std::string& message,
350                             bool verbosity)
351     {
352         using namespace DPL::Colors::Html;
353         if (verbosity) {
354             fprintf(m_fp.Get(),
355                     "[%s%s%s] %s%s%s\n",
356                     BOLD_RED_BEGIN,
357                     type,
358                     BOLD_RED_END,
359                     BOLD_YELLOW_BEGIN,
360                     message.c_str(),
361                     BOLD_YELLOW_END);
362         } else {
363             fprintf(m_fp.Get(),
364                     "[%s%s%s]\n",
365                     BOLD_RED_BEGIN,
366                     type,
367                     BOLD_RED_END);
368         }
369     }
370
371     void PrintfIgnoredMessage(const char* type,
372                               const std::string& message,
373                               bool verbosity)
374     {
375         using namespace DPL::Colors::Html;
376
377         if (verbosity) {
378             fprintf(m_fp.Get(),
379                     "[%s%s%s] %s%s%s\n",
380                     CYAN_BEGIN,
381                     type,
382                     CYAN_END,
383                     BOLD_GOLD_BEGIN,
384                     message.c_str(),
385                     BOLD_GOLD_END);
386         } else {
387             fprintf(m_fp.Get(),
388                     "[%s%s%s]\n",
389                     CYAN_BEGIN,
390                     type,
391                     CYAN_END);
392         }
393     }
394
395     void PrintStats(const std::string& name, const Statistic& stats)
396     {
397         using namespace DPL::Colors::Html;
398         fprintf(
399             m_fp.Get(), "\n%sResults [%s]:%s\n", BOLD_GREEN_BEGIN,
400             name.c_str(), BOLD_GREEN_END);
401         fprintf(
402             m_fp.Get(), "%s%s%3d%s\n", CYAN_BEGIN,
403             "Total tests:            ", stats.GetTotal(), CYAN_END);
404         fprintf(
405             m_fp.Get(), "  %s%s%3d%s\n", CYAN_BEGIN,
406             "Succeeded:            ", stats.GetPassed(), CYAN_END);
407         fprintf(
408             m_fp.Get(), "  %s%s%3d%s\n", CYAN_BEGIN,
409             "Failed:               ", stats.GetFailed(), CYAN_END);
410         fprintf(
411             m_fp.Get(), "  %s%s%3d%s\n", CYAN_BEGIN,
412             "Ignored:              ", stats.GetIgnored(), CYAN_END);
413     }
414
415     std::string m_filename;
416     ScopedFClose m_fp;
417     Statistic m_stats;
418     std::string m_currentGroup;
419     std::map<std::string, Statistic> m_groupsStats;
420 };
421
422 TestResultsCollectorBase* HtmlCollector::Constructor()
423 {
424     return new HtmlCollector();
425 }
426
427 class XmlCollector :
428     public TestResultsCollectorBase
429 {
430   public:
431     static TestResultsCollectorBase* Constructor();
432
433   private:
434     XmlCollector() : m_filename(DEFAULT_XML_FILE_NAME) {}
435
436     virtual void CollectCurrentTestGroupName(const std::string& name)
437     {
438         std::size_t pos = GetCurrentGroupPosition();
439         if (std::string::npos != pos) {
440             GroupFinish(pos);
441             FlushOutput();
442             m_stats = Statistic();
443         }
444
445         pos = m_outputBuffer.find("</testsuites>");
446         if (std::string::npos == pos) {
447             ThrowMsg(DPL::Exception, "Could not find test suites closing tag");
448         }
449         GroupStart(pos, name);
450     }
451
452     void GroupStart(const std::size_t pos, const std::string& name)
453     {
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")";
460         groupHeader << ">";
461
462         groupHeader << "\n\t\t<testcase name=\"unknown\" status=\"FAILED\">";
463         groupHeader <<
464         "\n\t\t\t<failure type=\"FAILED\" message=\"segmentation fault\"/>";
465         groupHeader << "\n\t\t</testcase>";
466
467         groupHeader << "\n\t</testsuite>";
468
469         m_outputBuffer.insert(pos - 1, groupHeader.str());
470     }
471
472     virtual bool Configure()
473     {
474         m_fp.Reset(fopen(m_filename.c_str(), "w"));
475         if (!m_fp) {
476             LogPedantic("Could not open file " << m_filename << " for writing");
477             return false;
478         }
479         return true;
480     }
481
482     virtual std::string CollectorSpecificHelp() const
483     {
484         return "--file=<filename> - name of file for output\n"
485                "                    default - results.xml\n";
486     }
487
488     virtual void Start()
489     {
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>");
493         FlushOutput();
494     }
495
496     virtual void Finish()
497     {
498         std::size_t pos = GetCurrentGroupPosition();
499         if (std::string::npos != pos) {
500             GroupFinish(pos);
501             FlushOutput();
502         }
503     }
504
505     virtual bool ParseCollectorSpecificArg(const std::string& arg)
506     {
507         return ParseCollectorFileArg(arg, m_filename);
508     }
509
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 = "")
514     {
515         m_resultBuffer.erase();
516         m_resultBuffer.append("\t\t<testcase name=\"");
517         m_resultBuffer.append(EscapeSpecialCharacters(id));
518         m_resultBuffer.append("\"");
519         switch (status) {
520         case TestResultsCollectorBase::FailStatus::NONE:
521             m_resultBuffer.append(" status=\"OK\"/>\n");
522             break;
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");
527             break;
528         case TestResultsCollectorBase::FailStatus::IGNORED:
529             m_resultBuffer.append(" status=\"Ignored\">\n");
530             PrintfIgnoredMessage("Ignored", EscapeSpecialCharacters(
531                                      reason), true);
532             m_resultBuffer.append("\t\t</testcase>\n");
533             break;
534         case TestResultsCollectorBase::FailStatus::INTERNAL:
535             m_resultBuffer.append(" status=\"FAILED\">\n");
536             PrintfErrorMessage("INTERNAL", EscapeSpecialCharacters(
537                                    reason), true);
538             m_resultBuffer.append("\t\t</testcase>");
539             break;
540         default:
541             Assert(false && "Bad status");
542         }
543         std::size_t group_pos = GetCurrentGroupPosition();
544         if (std::string::npos == group_pos) {
545             ThrowMsg(DPL::Exception, "No current group set");
546         }
547
548         std::size_t last_case_pos = m_outputBuffer.find(
549                 "<testcase name=\"unknown\"",
550                 group_pos);
551         if (std::string::npos == last_case_pos) {
552             ThrowMsg(DPL::Exception, "Could not find SegFault test case");
553         }
554         m_outputBuffer.insert(last_case_pos - 2, m_resultBuffer);
555
556         m_stats.AddTest(status);
557
558         UpdateGroupHeader(group_pos,
559                           m_stats.GetTotal() + 1, // include SegFault
560                           m_stats.GetFailed() + 1, // include SegFault
561                           m_stats.GetIgnored());
562         FlushOutput();
563     }
564
565     std::size_t GetCurrentGroupPosition() const
566     {
567         return m_outputBuffer.rfind("<testsuite ");
568     }
569
570     void UpdateGroupHeader(const std::size_t groupPosition,
571                            const unsigned int tests,
572                            const unsigned int failures,
573                            const unsigned int skipped)
574     {
575         UpdateElementAttribute(groupPosition, "tests", UIntToString(tests));
576         UpdateElementAttribute(groupPosition, "failures", UIntToString(failures));
577         UpdateElementAttribute(groupPosition, "skipped", UIntToString(skipped));
578     }
579
580     void UpdateElementAttribute(const std::size_t elementPosition,
581                                 const std::string& name,
582                                 const std::string& value)
583     {
584         std::string pattern = name + "=\"";
585
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");
590         }
591
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");
596         }
597
598         m_outputBuffer.replace(start + pattern.length(),
599                                end - start - pattern.length(),
600                                value);
601     }
602
603     std::string UIntToString(const unsigned int value)
604     {
605         std::stringstream result;
606         result << value;
607         return result.str();
608     }
609
610     void GroupFinish(const std::size_t groupPosition)
611     {
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");
617         }
618         segFaultStart -= 2; // to erase tabs
619
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");
625         }
626         segFaultEnd += closeTag.length() + 1; // to erase new line
627
628         m_outputBuffer.erase(segFaultStart, segFaultEnd - segFaultStart);
629
630         UpdateGroupHeader(groupPosition,
631                           m_stats.GetTotal(),
632                           m_stats.GetFailed(),
633                           m_stats.GetIgnored());
634     }
635
636     void FlushOutput()
637     {
638         int fd = fileno(m_fp.Get());
639         if (-1 == fd) {
640             int error = errno;
641             ThrowMsg(DPL::Exception, DPL::GetErrnoString(error));
642         }
643
644         if (-1 == TEMP_FAILURE_RETRY(ftruncate(fd, 0L))) {
645             int error = errno;
646             ThrowMsg(DPL::Exception, DPL::GetErrnoString(error));
647         }
648
649         if (-1 == TEMP_FAILURE_RETRY(fseek(m_fp.Get(), 0L, SEEK_SET))) {
650             int error = errno;
651             ThrowMsg(DPL::Exception, DPL::GetErrnoString(error));
652         }
653
654         if (m_outputBuffer.size() !=
655             fwrite(m_outputBuffer.c_str(), 1, m_outputBuffer.size(),
656                    m_fp.Get()))
657         {
658             int error = errno;
659             ThrowMsg(DPL::Exception, DPL::GetErrnoString(error));
660         }
661
662         if (-1 == TEMP_FAILURE_RETRY(fflush(m_fp.Get()))) {
663             int error = errno;
664             ThrowMsg(DPL::Exception, DPL::GetErrnoString(error));
665         }
666     }
667
668     void PrintfErrorMessage(const char* type,
669                             const std::string& message,
670                             bool verbosity)
671     {
672         if (verbosity) {
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");
678         } else {
679             m_resultBuffer.append("\t\t\t<failure type=\"");
680             m_resultBuffer.append(EscapeSpecialCharacters(type));
681             m_resultBuffer.append("\"/>\n");
682         }
683     }
684
685     void PrintfIgnoredMessage(const char* type,
686                               const std::string& message,
687                               bool verbosity)
688     {
689         if (verbosity) {
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");
695         } else {
696             m_resultBuffer.append("\t\t\t<skipped type=\"");
697             m_resultBuffer.append(EscapeSpecialCharacters(type));
698             m_resultBuffer.append("\"/>\n");
699         }
700     }
701
702     std::string EscapeSpecialCharacters(std::string s)
703     {
704         for (unsigned int i = 0; i < s.size();) {
705             switch (s[i]) {
706             case '"':
707                 s.erase(i, 1);
708                 s.insert(i, "&quot;");
709                 i += 6;
710                 break;
711
712             case '&':
713                 s.erase(i, 1);
714                 s.insert(i, "&amp;");
715                 i += 5;
716                 break;
717
718             case '<':
719                 s.erase(i, 1);
720                 s.insert(i, "&lt;");
721                 i += 4;
722                 break;
723
724             case '>':
725                 s.erase(i, 1);
726                 s.insert(i, "&gt;");
727                 i += 4;
728                 break;
729
730             case '\'':
731                 s.erase(i, 1);
732                 s.insert(i, "&#39;");
733                 i += 5;
734                 break;
735             default:
736                 ++i;
737                 break;
738             }
739         }
740         return s;
741     }
742
743     std::string m_filename;
744     ScopedFClose m_fp;
745     Statistic m_stats;
746     std::string m_outputBuffer;
747     std::string m_resultBuffer;
748 };
749
750 TestResultsCollectorBase* XmlCollector::Constructor()
751 {
752     return new XmlCollector();
753 }
754
755 class CSVCollector :
756     public TestResultsCollectorBase
757 {
758   public:
759     static TestResultsCollectorBase* Constructor();
760
761   private:
762     CSVCollector() {}
763
764     virtual void Start()
765     {
766         printf("GROUP;ID;RESULT;REASON\n");
767     }
768
769     virtual void CollectCurrentTestGroupName(const std::string& name)
770     {
771         m_currentGroup = name;
772     }
773
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 = "")
778     {
779         std::string statusMsg = "";
780         switch (status) {
781         case TestResultsCollectorBase::FailStatus::NONE: statusMsg = "OK";
782             break;
783         case TestResultsCollectorBase::FailStatus::FAILED: statusMsg = "FAILED";
784             break;
785         case TestResultsCollectorBase::FailStatus::IGNORED: statusMsg =
786             "IGNORED";
787             break;
788         case TestResultsCollectorBase::FailStatus::INTERNAL: statusMsg =
789             "FAILED";
790             break;
791         default:
792             Assert(false && "Bad status");
793         }
794         printf("%s;%s;%s;%s\n",
795                m_currentGroup.c_str(),
796                id.c_str(),
797                statusMsg.c_str(),
798                reason.c_str());
799     }
800
801     std::string m_currentGroup;
802 };
803
804 TestResultsCollectorBase* CSVCollector::Constructor()
805 {
806     return new CSVCollector();
807 }
808 }
809
810 class TAPCollector :
811     public TestResultsCollectorBase
812 {
813   public:
814     static TestResultsCollectorBase* Constructor();
815
816   private:
817     TAPCollector() : m_filename(DEFAULT_TAP_FILE_NAME)  {}
818
819     virtual bool Configure()
820     {
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);
824             return false;
825         }
826         return true;
827     }
828     virtual std::string CollectorSpecificHelp() const
829     {
830         std::string retVal = "--file=<filename> - name of file for output\n"
831                              "                    default - ";
832         retVal += DEFAULT_TAP_FILE_NAME;
833         retVal += "\n";
834         return retVal;
835     }
836
837     virtual void Start()
838     {
839         Assert(m_output.good() && "Output file must be opened.");
840         m_output << "TAP version 13" << std::endl;
841         m_testIndex = 0;
842     }
843
844     virtual void Finish()
845     {
846         m_output << "1.." << m_testIndex << std::endl;
847         m_output << m_collectedData.rdbuf();
848         m_output.close();
849     }
850
851     virtual bool ParseCollectorSpecificArg(const std::string& arg)
852     {
853         return ParseCollectorFileArg(arg, m_filename);
854     }
855
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 = "")
860     {
861         m_testIndex++;
862         switch (status) {
863         case TestResultsCollectorBase::FailStatus::NONE:
864             LogBasicTAP(true, id, description);
865             endTAPLine();
866             break;
867         case TestResultsCollectorBase::FailStatus::FAILED:
868             LogBasicTAP(false, id, description);
869             endTAPLine();
870             break;
871         case TestResultsCollectorBase::FailStatus::IGNORED:
872             LogBasicTAP(true, id, description);
873             m_collectedData << " # skip " << reason;
874             endTAPLine();
875             break;
876         case TestResultsCollectorBase::FailStatus::INTERNAL:
877             LogBasicTAP(true, id, description);
878             endTAPLine();
879             m_collectedData << "    ---" << std::endl;
880             m_collectedData << "    message: " << reason << std::endl;
881             m_collectedData << "    severity: Internal" << std::endl;
882             m_collectedData << "    ..." << std::endl;
883             break;
884         default:
885             Assert(false && "Bad status");
886         }
887     }
888
889     void LogBasicTAP(bool isOK, const std::string& id,
890                      const std::string& description)
891     {
892         if (!isOK) {
893             m_collectedData << "not ";
894         }
895         m_collectedData << "ok " << m_testIndex << " [" <<
896         id << "] " << description;
897     }
898
899     void endTAPLine()
900     {
901         m_collectedData << std::endl;
902     }
903
904     std::string m_filename;
905     std::stringstream m_collectedData;
906     std::ofstream m_output;
907     int m_testIndex;
908 };
909
910 TestResultsCollectorBase* TAPCollector::Constructor()
911 {
912     return new TAPCollector();
913 }
914
915 void TestResultsCollectorBase::RegisterCollectorConstructor(
916     const std::string& name,
917     TestResultsCollectorBase::CollectorConstructorFunc func)
918 {
919     Assert(m_constructorsMap.find(name) == m_constructorsMap.end());
920     m_constructorsMap[name] = func;
921 }
922
923 TestResultsCollectorBase* TestResultsCollectorBase::Create(
924     const std::string& name)
925 {
926     ConstructorsMap::iterator found = m_constructorsMap.find(name);
927     if (found != m_constructorsMap.end()) {
928         return found->second();
929     } else {
930         return NULL;
931     }
932 }
933
934 std::vector<std::string> TestResultsCollectorBase::GetCollectorsNames()
935 {
936     std::vector<std::string> list;
937     FOREACH(it, m_constructorsMap)
938     {
939         list.push_back(it->first);
940     }
941     return list;
942 }
943
944 TestResultsCollectorBase::ConstructorsMap TestResultsCollectorBase::
945     m_constructorsMap;
946
947 namespace {
948 static int RegisterCollectorConstructors();
949 static const int RegisterHelperVariable = RegisterCollectorConstructors();
950 int RegisterCollectorConstructors()
951 {
952     (void)RegisterHelperVariable;
953
954     TestResultsCollectorBase::RegisterCollectorConstructor(
955         "text",
956         &ConsoleCollector::Constructor);
957     TestResultsCollectorBase::RegisterCollectorConstructor(
958         "html",
959         &HtmlCollector::Constructor);
960     TestResultsCollectorBase::RegisterCollectorConstructor(
961         "csv",
962         &CSVCollector::Constructor);
963     TestResultsCollectorBase::RegisterCollectorConstructor(
964         "tap",
965         &TAPCollector::Constructor);
966     TestResultsCollectorBase::RegisterCollectorConstructor(
967         "xml",
968         &XmlCollector::Constructor);
969
970     return 0;
971 }
972 }
973 }
974 }
975 #undef GREEN_RESULT_OK