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