Merge branch 'ckm' into tizen
[platform/core/test/security-tests.git] / src / framework / src / test_results_collector_xml.cpp
1 /*
2  * Copyright (c) 2014-2015 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_xml.cpp
18  * @author      Lukasz Wrzosek (l.wrzosek@samsung.com)
19  * @author      Marcin Niesluchowski (m.niesluchow@samsung.com)
20  * @version     1.0
21  * @brief       Source file containing XmlCollector class definition
22  */
23
24 #include <cerrno>
25 #include <cstdio>
26 #include <cstring>
27 #include <sstream>
28
29 #include <dpl/assert.h>
30 #include <dpl/exception.h>
31 #include <dpl/test/test_results_collector_commons.h>
32
33 #include "dpl/test/test_results_collector_xml.h"
34
35 namespace DPL {
36 namespace Test {
37
38 namespace {
39
40 const char *DEFAULT_XML_FILE_NAME = "results.xml";
41
42 }
43
44 XmlCollector::XmlCollector()
45     : m_filename(DEFAULT_XML_FILE_NAME), m_verbosity(true)
46 {
47 }
48
49 TestResultsCollectorBase* XmlCollector::Constructor()
50 {
51      return new XmlCollector();
52 }
53
54 void XmlCollector::CollectCurrentTestGroupName(const std::string& name)
55 {
56     std::size_t pos = GetCurrentGroupPosition();
57     if (std::string::npos != pos) {
58         GroupFinish(pos);
59         FlushOutput();
60         m_stats = Statistic();
61      }
62
63     pos = m_outputBuffer.find("</testsuites>");
64     if (std::string::npos == pos) {
65         ThrowMsg(DPL::Exception, "Could not find test suites closing tag");
66     }
67     GroupStart(pos, name);
68 }
69
70 void XmlCollector::GroupStart(const std::size_t pos, const std::string& name)
71 {
72     std::stringstream groupHeader;
73     groupHeader << "\n\t<testsuite";
74     groupHeader << " name=\"" << EscapeSpecialCharacters(name) << "\"";
75     groupHeader << R"( tests="1")"; // include SegFault
76     groupHeader << R"( failures="1")"; // include SegFault
77     groupHeader << R"( skipped="0")";
78     groupHeader << ">";
79
80     groupHeader << "\n\t\t<testcase name=\"unknown\" status=\"FAILED\">";
81     groupHeader <<
82     "\n\t\t\t<failure type=\"FAILED\" message=\"segmentation fault\"/>";
83     groupHeader << "\n\t\t</testcase>";
84
85     groupHeader << "\n\t</testsuite>";
86
87     m_outputBuffer.insert(pos - 1, groupHeader.str());
88 }
89
90 bool XmlCollector::Configure()
91 {
92     m_fp.Reset(fopen(m_filename.c_str(), "w"));
93     if (!m_fp) {
94         LogPedantic("Could not open file " << m_filename << " for writing");
95         return false;
96     }
97     return true;
98 }
99
100 std::string XmlCollector::CollectorSpecificHelp() const
101 {
102     return CollectorFileHelp(DEFAULT_XML_FILE_NAME) + COLLECTOR_NO_VERBOSE_HELP;
103 }
104
105 void XmlCollector::Start()
106 {
107     Assert(!!m_fp && "File handle must not be null");
108     m_outputBuffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
109     m_outputBuffer.append("<testsuites>\n</testsuites>");
110     FlushOutput();
111 }
112
113 void XmlCollector::Finish()
114 {
115     std::size_t pos = GetCurrentGroupPosition();
116     if (std::string::npos != pos) {
117         GroupFinish(pos);
118         FlushOutput();
119     }
120 }
121
122 bool XmlCollector::ParseCollectorSpecificArg(const std::string& arg)
123 {
124     return ParseCollectorFileArg(arg, m_filename) || ParseCollectorNoVerboseArg(arg, m_verbosity);
125 }
126
127 void XmlCollector::CollectResult(const std::string& id, const TestResult &result)
128 {
129     m_resultBuffer.erase();
130     m_resultBuffer.append("\t\t<testcase name=\"");
131     m_resultBuffer.append(EscapeSpecialCharacters(id));
132     m_resultBuffer.append("\"");
133
134     std::ostringstream ostr;
135     ConstPerformanceResultPtr performanceResult;
136     switch (result.GetFailStatus()) {
137     case TestResult::FailStatus::NONE:
138         performanceResult = result.GetPerformanceResult();
139         if (!performanceResult) {
140             m_resultBuffer.append(" status=\"OK\"/>\n");
141             break;
142         }
143         if (!performanceResult->IsMaxDuration()) {
144             m_resultBuffer.append(" status=\"OK\" time=\"");
145             ostr << performanceResult->GetDuration().count();
146             m_resultBuffer.append(ostr.str());
147             m_resultBuffer.append("\"/>\n");
148             break;
149         }
150         m_resultBuffer.append(" status=\"OK\" time=\"");
151         ostr << performanceResult->GetDuration().count();
152         m_resultBuffer.append(ostr.str());
153         m_resultBuffer.append("\" time_expected=\"");
154         ostr.str("");
155         ostr << performanceResult->GetMaxDuration().count();
156         m_resultBuffer.append(ostr.str());
157         m_resultBuffer.append("\"/>\n");
158         break;
159     case TestResult::FailStatus::FAILED:
160         m_resultBuffer.append(" status=\"FAILED\">\n");
161         PrintfErrorMessage("FAILED", EscapeSpecialCharacters(result.GetReason()));
162         m_resultBuffer.append("\t\t</testcase>\n");
163         break;
164     case TestResult::FailStatus::IGNORED:
165         m_resultBuffer.append(" status=\"Ignored\">\n");
166         PrintfIgnoredMessage("Ignored", EscapeSpecialCharacters(result.GetReason()));
167         m_resultBuffer.append("\t\t</testcase>\n");
168         break;
169     default:
170         Assert(false && "Bad status");
171     }
172     std::size_t group_pos = GetCurrentGroupPosition();
173     if (std::string::npos == group_pos) {
174         ThrowMsg(DPL::Exception, "No current group set");
175     }
176
177     std::size_t last_case_pos = m_outputBuffer.find(
178             "<testcase name=\"unknown\"",
179             group_pos);
180     if (std::string::npos == last_case_pos) {
181         ThrowMsg(DPL::Exception, "Could not find SegFault test case");
182     }
183     m_outputBuffer.insert(last_case_pos - 2, m_resultBuffer);
184
185     m_stats.AddTest(result.GetFailStatus());
186
187     UpdateGroupHeader(group_pos,
188                       m_stats.GetTotal() + 1, // include SegFault
189                       m_stats.GetFailed() + 1, // include SegFault
190                       m_stats.GetIgnored());
191     FlushOutput();
192 }
193
194 std::size_t XmlCollector::GetCurrentGroupPosition() const
195 {
196     return m_outputBuffer.rfind("<testsuite ");
197 }
198
199 void XmlCollector::UpdateGroupHeader(const std::size_t groupPosition,
200                                      const unsigned int tests,
201                                      const unsigned int failures,
202                                      const unsigned int skipped)
203 {
204     UpdateElementAttribute(groupPosition, "tests", UIntToString(tests));
205     UpdateElementAttribute(groupPosition, "failures", UIntToString(failures));
206     UpdateElementAttribute(groupPosition, "skipped", UIntToString(skipped));
207 }
208
209 void XmlCollector::UpdateElementAttribute(const std::size_t elementPosition,
210                                           const std::string& name,
211                                           const std::string& value)
212 {
213     std::string pattern = name + "=\"";
214
215     std::size_t start = m_outputBuffer.find(pattern, elementPosition);
216     if (std::string::npos == start) {
217         ThrowMsg(DPL::Exception,
218                  "Could not find attribute " << name << " beginning");
219     }
220
221     std::size_t end = m_outputBuffer.find("\"", start + pattern.length());
222     if (std::string::npos == end) {
223         ThrowMsg(DPL::Exception,
224                  "Could not find attribute " << name << " end");
225     }
226
227     m_outputBuffer.replace(start + pattern.length(),
228                            end - start - pattern.length(),
229                            value);
230 }
231
232 std::string XmlCollector::UIntToString(const unsigned int value)
233 {
234     std::stringstream result;
235     result << value;
236     return result.str();
237 }
238
239 void XmlCollector::GroupFinish(const std::size_t groupPosition)
240 {
241     std::size_t segFaultStart =
242         m_outputBuffer.find("<testcase name=\"unknown\"", groupPosition);
243     if (std::string::npos == segFaultStart) {
244         ThrowMsg(DPL::Exception,
245                  "Could not find SegFault test case start position");
246     }
247     segFaultStart -= 2; // to erase tabs
248
249     std::string closeTag = "</testcase>";
250     std::size_t segFaultEnd = m_outputBuffer.find(closeTag, segFaultStart);
251     if (std::string::npos == segFaultEnd) {
252         ThrowMsg(DPL::Exception,
253                  "Could not find SegFault test case end position");
254     }
255     segFaultEnd += closeTag.length() + 1; // to erase new line
256
257     m_outputBuffer.erase(segFaultStart, segFaultEnd - segFaultStart);
258
259     UpdateGroupHeader(groupPosition,
260                       m_stats.GetTotal(),
261                       m_stats.GetFailed(),
262                       m_stats.GetIgnored());
263 }
264
265 void XmlCollector::FlushOutput()
266 {
267     int fd = fileno(m_fp.Get());
268     if (-1 == fd) {
269         const char* errStr = strerror(errno);
270         ThrowMsg(DPL::Exception, errStr);
271     }
272
273     if (-1 == TEMP_FAILURE_RETRY(ftruncate(fd, 0L))) {
274         const char* errStr = strerror(errno);
275         ThrowMsg(DPL::Exception, errStr);
276     }
277
278     if (-1 == TEMP_FAILURE_RETRY(fseek(m_fp.Get(), 0L, SEEK_SET))) {
279         const char* errStr = strerror(errno);
280         ThrowMsg(DPL::Exception, errStr);
281     }
282
283     if (m_outputBuffer.size() !=
284         fwrite(m_outputBuffer.c_str(), 1, m_outputBuffer.size(),
285                m_fp.Get()))
286     {
287         const char* errStr = strerror(errno);
288         ThrowMsg(DPL::Exception, errStr);
289     }
290
291     if (-1 == TEMP_FAILURE_RETRY(fflush(m_fp.Get()))) {
292         const char* errStr = strerror(errno);
293         ThrowMsg(DPL::Exception, errStr);
294     }
295 }
296
297 void XmlCollector::PrintfErrorMessage(const char* type, const std::string& message)
298 {
299     m_resultBuffer.append("\t\t\t<failure type=\"");
300     m_resultBuffer.append(EscapeSpecialCharacters(type));
301     if (m_verbosity) {
302         m_resultBuffer.append("\" message=\"");
303         m_resultBuffer.append(EscapeSpecialCharacters(message));
304     }
305     m_resultBuffer.append("\"/>\n");
306 }
307
308 void XmlCollector::PrintfIgnoredMessage(const char* type, const std::string& message)
309 {
310     m_resultBuffer.append("\t\t\t<skipped type=\"");
311     m_resultBuffer.append(EscapeSpecialCharacters(type));
312     if (m_verbosity) {
313         m_resultBuffer.append("\" message=\"");
314         m_resultBuffer.append(EscapeSpecialCharacters(message));
315     }
316     m_resultBuffer.append("\"/>\n");
317 }
318
319 std::string XmlCollector::EscapeSpecialCharacters(std::string s)
320 {
321     for (unsigned int i = 0; i < s.size();) {
322         switch (s[i]) {
323         case '"':
324             s.erase(i, 1);
325             s.insert(i, "&quot;");
326             i += 6;
327             break;
328
329         case '&':
330             s.erase(i, 1);
331             s.insert(i, "&amp;");
332             i += 5;
333             break;
334
335         case '<':
336             s.erase(i, 1);
337             s.insert(i, "&lt;");
338             i += 4;
339             break;
340
341         case '>':
342             s.erase(i, 1);
343             s.insert(i, "&gt;");
344             i += 4;
345             break;
346
347         case '\'':
348             s.erase(i, 1);
349             s.insert(i, "&#39;");
350             i += 5;
351             break;
352         default:
353             ++i;
354             break;
355         }
356     }
357     return s;
358 }
359
360 } // namespace Test
361 } // namespace DPL