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("&", "&");
209 buf.replace("\"", """);
210 buf.replace("<", "<");
211 buf.replace(">", ">");
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