1 /*
2  * Copyright (c) 2007, Michael Feathers, James Grenning and Bas Vodde
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the <organization> nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE EARLIER MENTIONED AUTHORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "CppUTest/TestHarness.h"
29 #include "CppUTest/JUnitTestOutput.h"
30 #include "CppUTest/TestResult.h"
31 #include "CppUTest/TestFailure.h"
32 #include "CppUTest/PlatformSpecificFunctions.h"
33 
34 struct JUnitTestCaseResultNode
35 {
JUnitTestCaseResultNodeJUnitTestCaseResultNode36     JUnitTestCaseResultNode() :
37         execTime_(0), failure_(NULLPTR), ignored_(false), lineNumber_ (0), checkCount_ (0), next_(NULLPTR)
38     {
39     }
40 
41     SimpleString name_;
42     size_t execTime_;
43     TestFailure* failure_;
44     bool ignored_;
45     SimpleString file_;
46     size_t lineNumber_;
47     size_t checkCount_;
48     JUnitTestCaseResultNode* next_;
49 };
50 
51 struct JUnitTestGroupResult
52 {
JUnitTestGroupResultJUnitTestGroupResult53     JUnitTestGroupResult() :
54         testCount_(0), failureCount_(0), totalCheckCount_(0), startTime_(0), groupExecTime_(0), head_(NULLPTR), tail_(NULLPTR)
55     {
56     }
57 
58     size_t testCount_;
59     size_t failureCount_;
60     size_t totalCheckCount_;
61     size_t startTime_;
62     size_t groupExecTime_;
63     SimpleString group_;
64     JUnitTestCaseResultNode* head_;
65     JUnitTestCaseResultNode* tail_;
66 };
67 
68 struct JUnitTestOutputImpl
69 {
70     JUnitTestGroupResult results_;
71     PlatformSpecificFile file_;
72     SimpleString package_;
73     SimpleString stdOutput_;
74 };
75 
JUnitTestOutput()76 JUnitTestOutput::JUnitTestOutput() :
77     impl_(new JUnitTestOutputImpl)
78 {
79 }
80 
~JUnitTestOutput()81 JUnitTestOutput::~JUnitTestOutput()
82 {
83     resetTestGroupResult();
84     delete impl_;
85 }
86 
resetTestGroupResult()87 void JUnitTestOutput::resetTestGroupResult()
88 {
89     impl_->results_.testCount_ = 0;
90     impl_->results_.failureCount_ = 0;
91     impl_->results_.group_ = "";
92     JUnitTestCaseResultNode* cur = impl_->results_.head_;
93     while (cur) {
94         JUnitTestCaseResultNode* tmp = cur->next_;
95         delete cur->failure_;
96         delete cur;
97         cur = tmp;
98     }
99     impl_->results_.head_ = NULLPTR;
100     impl_->results_.tail_ = NULLPTR;
101 }
102 
printTestsStarted()103 void JUnitTestOutput::printTestsStarted()
104 {
105 }
106 
printCurrentGroupStarted(const UtestShell &)107 void JUnitTestOutput::printCurrentGroupStarted(const UtestShell& /*test*/)
108 {
109 }
110 
printCurrentTestEnded(const TestResult & result)111 void JUnitTestOutput::printCurrentTestEnded(const TestResult& result)
112 {
113     impl_->results_.tail_->execTime_ = result.getCurrentTestTotalExecutionTime();
114     impl_->results_.tail_->checkCount_ = result.getCheckCount();
115 }
116 
printTestsEnded(const TestResult &)117 void JUnitTestOutput::printTestsEnded(const TestResult& /*result*/)
118 {
119 }
120 
printCurrentGroupEnded(const TestResult & result)121 void JUnitTestOutput::printCurrentGroupEnded(const TestResult& result)
122 {
123     impl_->results_.groupExecTime_ = result.getCurrentGroupTotalExecutionTime();
124     writeTestGroupToFile();
125     resetTestGroupResult();
126 }
127 
printCurrentTestStarted(const UtestShell & test)128 void JUnitTestOutput::printCurrentTestStarted(const UtestShell& test)
129 {
130     impl_->results_.testCount_++;
131     impl_->results_.group_ = test.getGroup();
132     impl_->results_.startTime_ = (size_t) GetPlatformSpecificTimeInMillis();
133 
134     if (impl_->results_.tail_ == NULLPTR) {
135         impl_->results_.head_ = impl_->results_.tail_
136                 = new JUnitTestCaseResultNode;
137     }
138     else {
139         impl_->results_.tail_->next_ = new JUnitTestCaseResultNode;
140         impl_->results_.tail_ = impl_->results_.tail_->next_;
141     }
142     impl_->results_.tail_->name_ = test.getName();
143     impl_->results_.tail_->file_ = test.getFile();
144     impl_->results_.tail_->lineNumber_ = test.getLineNumber();
145     if (!test.willRun()) {
146         impl_->results_.tail_->ignored_ = true;
147     }
148 }
149 
createFileName(const SimpleString & group)150 SimpleString JUnitTestOutput::createFileName(const SimpleString& group)
151 {
152     SimpleString fileName = "cpputest_";
153     if (!impl_->package_.isEmpty()) {
154         fileName += impl_->package_;
155         fileName += "_";
156     }
157     fileName += group;
158     return encodeFileName(fileName) + ".xml";
159 }
160 
encodeFileName(const SimpleString & fileName)161 SimpleString JUnitTestOutput::encodeFileName(const SimpleString& fileName)
162 {
163     // special character list based on: https://en.wikipedia.org/wiki/Filename
164     static const char* const forbiddenCharacters = "/\\?%*:|\"<>";
165 
166     SimpleString result = fileName;
167     for (const char* sym = forbiddenCharacters; *sym; ++sym) {
168         result.replace(*sym, '_');
169     }
170     return result;
171 }
172 
setPackageName(const SimpleString & package)173 void JUnitTestOutput::setPackageName(const SimpleString& package)
174 {
175     if (impl_ != NULLPTR) {
176         impl_->package_ = package;
177     }
178 }
179 
writeXmlHeader()180 void JUnitTestOutput::writeXmlHeader()
181 {
182     writeToFile("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
183 }
184 
writeTestSuiteSummary()185 void JUnitTestOutput::writeTestSuiteSummary()
186 {
187     SimpleString
188             buf =
189                     StringFromFormat(
190                             "<testsuite errors=\"0\" failures=\"%d\" hostname=\"localhost\" name=\"%s\" tests=\"%d\" time=\"%d.%03d\" timestamp=\"%s\">\n",
191                             (int)impl_->results_.failureCount_,
192                             impl_->results_.group_.asCharString(),
193                             (int) impl_->results_.testCount_,
194                             (int) (impl_->results_.groupExecTime_ / 1000), (int) (impl_->results_.groupExecTime_ % 1000),
195                             GetPlatformSpecificTimeString());
196     writeToFile(buf.asCharString());
197 }
198 
writeProperties()199 void JUnitTestOutput::writeProperties()
200 {
201     writeToFile("<properties>\n");
202     writeToFile("</properties>\n");
203 }
204 
encodeXmlText(const SimpleString & textbody)205 SimpleString JUnitTestOutput::encodeXmlText(const SimpleString& textbody)
206 {
207     SimpleString buf = textbody.asCharString();
208     buf.replace("&", "&amp;");
209     buf.replace("\"", "&quot;");
210     buf.replace("<", "&lt;");
211     buf.replace(">", "&gt;");
212     buf.replace("\n", "{newline}");
213     return buf;
214 }
215 
writeTestCases()216 void JUnitTestOutput::writeTestCases()
217 {
218     JUnitTestCaseResultNode* cur = impl_->results_.head_;
219 
220     while (cur) {
221         SimpleString buf = StringFromFormat(
222                 "<testcase classname=\"%s%s%s\" name=\"%s\" assertions=\"%d\" time=\"%d.%03d\" file=\"%s\" line=\"%d\">\n",
223                 impl_->package_.asCharString(),
224                 impl_->package_.isEmpty() ? "" : ".",
225                 impl_->results_.group_.asCharString(),
226                 cur->name_.asCharString(),
227                 (int) (cur->checkCount_ - impl_->results_.totalCheckCount_),
228                 (int) (cur->execTime_ / 1000), (int)(cur->execTime_ % 1000),
229                 cur->file_.asCharString(),
230                 (int) cur->lineNumber_);
231         writeToFile(buf.asCharString());
232 
233         impl_->results_.totalCheckCount_ = cur->checkCount_;
234 
235         if (cur->failure_) {
236             writeFailure(cur);
237         }
238         else if (cur->ignored_) {
239             writeToFile("<skipped />\n");
240         }
241         writeToFile("</testcase>\n");
242         cur = cur->next_;
243     }
244 }
245 
writeFailure(JUnitTestCaseResultNode * node)246 void JUnitTestOutput::writeFailure(JUnitTestCaseResultNode* node)
247 {
248     SimpleString buf = StringFromFormat(
249             "<failure message=\"%s:%d: %s\" type=\"AssertionFailedError\">\n",
250             node->failure_->getFileName().asCharString(),
251             (int) node->failure_->getFailureLineNumber(),
252             encodeXmlText(node->failure_->getMessage()).asCharString());
253     writeToFile(buf.asCharString());
254     writeToFile("</failure>\n");
255 }
256 
257 
writeFileEnding()258 void JUnitTestOutput::writeFileEnding()
259 {
260     writeToFile("<system-out>");
261     writeToFile(encodeXmlText(impl_->stdOutput_));
262     writeToFile("</system-out>\n");
263     writeToFile("<system-err></system-err>\n");
264     writeToFile("</testsuite>\n");
265 }
266 
writeTestGroupToFile()267 void JUnitTestOutput::writeTestGroupToFile()
268 {
269     openFileForWrite(createFileName(impl_->results_.group_));
270     writeXmlHeader();
271     writeTestSuiteSummary();
272     writeProperties();
273     writeTestCases();
274     writeFileEnding();
275     closeFile();
276 }
277 
278 // LCOV_EXCL_START
279 
printBuffer(const char *)280 void JUnitTestOutput::printBuffer(const char*)
281 {
282 }
283 
print(const char * output)284 void JUnitTestOutput::print(const char *output)
285 {
286     impl_->stdOutput_ += output;
287 }
288 
print(long)289 void JUnitTestOutput::print(long)
290 {
291 }
292 
print(size_t)293 void JUnitTestOutput::print(size_t)
294 {
295 }
296 
flush()297 void JUnitTestOutput::flush()
298 {
299 }
300 
301 // LCOV_EXCL_STOP
302 
printFailure(const TestFailure & failure)303 void JUnitTestOutput::printFailure(const TestFailure& failure)
304 {
305     if (impl_->results_.tail_->failure_ == NULLPTR) {
306         impl_->results_.failureCount_++;
307         impl_->results_.tail_->failure_ = new TestFailure(failure);
308     }
309 }
310 
openFileForWrite(const SimpleString & fileName)311 void JUnitTestOutput::openFileForWrite(const SimpleString& fileName)
312 {
313     impl_->file_ = PlatformSpecificFOpen(fileName.asCharString(), "w");
314 }
315 
writeToFile(const SimpleString & buffer)316 void JUnitTestOutput::writeToFile(const SimpleString& buffer)
317 {
318     PlatformSpecificFPuts(buffer.asCharString(), impl_->file_);
319 }
320 
closeFile()321 void JUnitTestOutput::closeFile()
322 {
323     PlatformSpecificFClose(impl_->file_);
324 }
325