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