1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtTest module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include <stdio.h>
41 #include <string.h>
42 #include <QtCore/qglobal.h>
43 #include <QtCore/qlibraryinfo.h>
44
45 #include <QtTest/private/qtestlog_p.h>
46 #include <QtTest/private/qxmltestlogger_p.h>
47 #include <QtTest/private/qtestresult_p.h>
48 #include <QtTest/private/qbenchmark_p.h>
49 #include <QtTest/private/qbenchmarkmetric_p.h>
50 #include <QtTest/qtestcase.h>
51
52 QT_BEGIN_NAMESPACE
53
54 namespace QTest {
55
xmlMessageType2String(QAbstractTestLogger::MessageTypes type)56 static const char* xmlMessageType2String(QAbstractTestLogger::MessageTypes type)
57 {
58 switch (type) {
59 case QAbstractTestLogger::Warn:
60 return "warn";
61 case QAbstractTestLogger::QSystem:
62 return "system";
63 case QAbstractTestLogger::QDebug:
64 return "qdebug";
65 case QAbstractTestLogger::QInfo:
66 return "qinfo";
67 case QAbstractTestLogger::QWarning:
68 return "qwarn";
69 case QAbstractTestLogger::QFatal:
70 return "qfatal";
71 case QAbstractTestLogger::Skip:
72 return "skip";
73 case QAbstractTestLogger::Info:
74 return "info";
75 }
76 return "??????";
77 }
78
xmlIncidentType2String(QAbstractTestLogger::IncidentTypes type)79 static const char* xmlIncidentType2String(QAbstractTestLogger::IncidentTypes type)
80 {
81 switch (type) {
82 case QAbstractTestLogger::Pass:
83 return "pass";
84 case QAbstractTestLogger::XFail:
85 return "xfail";
86 case QAbstractTestLogger::Fail:
87 return "fail";
88 case QAbstractTestLogger::XPass:
89 return "xpass";
90 case QAbstractTestLogger::BlacklistedPass:
91 return "bpass";
92 case QAbstractTestLogger::BlacklistedFail:
93 return "bfail";
94 case QAbstractTestLogger::BlacklistedXPass:
95 return "bxpass";
96 case QAbstractTestLogger::BlacklistedXFail:
97 return "bxfail";
98 }
99 return "??????";
100 }
101
102 }
103
104
QXmlTestLogger(XmlMode mode,const char * filename)105 QXmlTestLogger::QXmlTestLogger(XmlMode mode, const char *filename)
106 : QAbstractTestLogger(filename), xmlmode(mode)
107 {
108 }
109
110 QXmlTestLogger::~QXmlTestLogger() = default;
111
startLogging()112 void QXmlTestLogger::startLogging()
113 {
114 QAbstractTestLogger::startLogging();
115 QTestCharBuffer buf;
116
117 if (xmlmode == QXmlTestLogger::Complete) {
118 QTestCharBuffer quotedTc;
119 xmlQuote("edTc, QTestResult::currentTestObjectName());
120 QTest::qt_asprintf(&buf,
121 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
122 "<TestCase name=\"%s\">\n", quotedTc.constData());
123 outputString(buf.constData());
124 }
125
126 QTestCharBuffer quotedBuild;
127 xmlQuote("edBuild, QLibraryInfo::build());
128
129 QTest::qt_asprintf(&buf,
130 "<Environment>\n"
131 " <QtVersion>%s</QtVersion>\n"
132 " <QtBuild>%s</QtBuild>\n"
133 " <QTestVersion>" QTEST_VERSION_STR "</QTestVersion>\n"
134 "</Environment>\n", qVersion(), quotedBuild.constData());
135 outputString(buf.constData());
136 }
137
stopLogging()138 void QXmlTestLogger::stopLogging()
139 {
140 QTestCharBuffer buf;
141
142 QTest::qt_asprintf(&buf, "<Duration msecs=\"%s\"/>\n",
143 QString::number(QTestLog::msecsTotalTime()).toUtf8().constData());
144 outputString(buf.constData());
145 if (xmlmode == QXmlTestLogger::Complete) {
146 outputString("</TestCase>\n");
147 }
148
149 QAbstractTestLogger::stopLogging();
150 }
151
enterTestFunction(const char * function)152 void QXmlTestLogger::enterTestFunction(const char *function)
153 {
154 QTestCharBuffer buf;
155 QTestCharBuffer quotedFunction;
156 xmlQuote("edFunction, function);
157 QTest::qt_asprintf(&buf, "<TestFunction name=\"%s\">\n", quotedFunction.constData());
158 outputString(buf.constData());
159 }
160
leaveTestFunction()161 void QXmlTestLogger::leaveTestFunction()
162 {
163 QTestCharBuffer buf;
164 QTest::qt_asprintf(&buf,
165 " <Duration msecs=\"%s\"/>\n"
166 "</TestFunction>\n",
167 QString::number(QTestLog::msecsFunctionTime()).toUtf8().constData());
168
169 outputString(buf.constData());
170 }
171
172 namespace QTest
173 {
174
isEmpty(const char * str)175 inline static bool isEmpty(const char *str)
176 {
177 return !str || !str[0];
178 }
179
incidentFormatString(bool noDescription,bool noTag)180 static const char *incidentFormatString(bool noDescription, bool noTag)
181 {
182 if (noDescription) {
183 return noTag
184 ? "<Incident type=\"%s\" file=\"%s\" line=\"%d\" />\n"
185 : "<Incident type=\"%s\" file=\"%s\" line=\"%d\">\n"
186 " <DataTag><![CDATA[%s%s%s%s]]></DataTag>\n"
187 "</Incident>\n";
188 }
189 return noTag
190 ? "<Incident type=\"%s\" file=\"%s\" line=\"%d\">\n"
191 " <Description><![CDATA[%s%s%s%s]]></Description>\n"
192 "</Incident>\n"
193 : "<Incident type=\"%s\" file=\"%s\" line=\"%d\">\n"
194 " <DataTag><![CDATA[%s%s%s]]></DataTag>\n"
195 " <Description><![CDATA[%s]]></Description>\n"
196 "</Incident>\n";
197 }
198
benchmarkResultFormatString()199 static const char *benchmarkResultFormatString()
200 {
201 return "<BenchmarkResult metric=\"%s\" tag=\"%s\" value=\"%s\" iterations=\"%d\" />\n";
202 }
203
messageFormatString(bool noDescription,bool noTag)204 static const char *messageFormatString(bool noDescription, bool noTag)
205 {
206 if (noDescription) {
207 if (noTag)
208 return "<Message type=\"%s\" file=\"%s\" line=\"%d\" />\n";
209 else
210 return "<Message type=\"%s\" file=\"%s\" line=\"%d\">\n"
211 " <DataTag><![CDATA[%s%s%s%s]]></DataTag>\n"
212 "</Message>\n";
213 } else {
214 if (noTag)
215 return "<Message type=\"%s\" file=\"%s\" line=\"%d\">\n"
216 " <Description><![CDATA[%s%s%s%s]]></Description>\n"
217 "</Message>\n";
218 else
219 return "<Message type=\"%s\" file=\"%s\" line=\"%d\">\n"
220 " <DataTag><![CDATA[%s%s%s]]></DataTag>\n"
221 " <Description><![CDATA[%s]]></Description>\n"
222 "</Message>\n";
223 }
224 }
225
226 } // namespace
227
addIncident(IncidentTypes type,const char * description,const char * file,int line)228 void QXmlTestLogger::addIncident(IncidentTypes type, const char *description,
229 const char *file, int line)
230 {
231 QTestCharBuffer buf;
232 const char *tag = QTestResult::currentDataTag();
233 const char *gtag = QTestResult::currentGlobalDataTag();
234 const char *filler = (tag && gtag) ? ":" : "";
235 const bool notag = QTest::isEmpty(tag) && QTest::isEmpty(gtag);
236
237 QTestCharBuffer quotedFile;
238 QTestCharBuffer cdataGtag;
239 QTestCharBuffer cdataTag;
240 QTestCharBuffer cdataDescription;
241
242 xmlQuote("edFile, file);
243 xmlCdata(&cdataGtag, gtag);
244 xmlCdata(&cdataTag, tag);
245 xmlCdata(&cdataDescription, description);
246
247 QTest::qt_asprintf(&buf,
248 QTest::incidentFormatString(QTest::isEmpty(description), notag),
249 QTest::xmlIncidentType2String(type),
250 quotedFile.constData(), line,
251 cdataGtag.constData(),
252 filler,
253 cdataTag.constData(),
254 cdataDescription.constData());
255
256 outputString(buf.constData());
257 }
258
addBenchmarkResult(const QBenchmarkResult & result)259 void QXmlTestLogger::addBenchmarkResult(const QBenchmarkResult &result)
260 {
261 QTestCharBuffer buf;
262 QTestCharBuffer quotedMetric;
263 QTestCharBuffer quotedTag;
264
265 xmlQuote("edMetric,
266 benchmarkMetricName(result.metric));
267 xmlQuote("edTag, result.context.tag.toUtf8().constData());
268
269 const qreal valuePerIteration = qreal(result.value) / qreal(result.iterations);
270 QTest::qt_asprintf(
271 &buf,
272 QTest::benchmarkResultFormatString(),
273 quotedMetric.constData(),
274 quotedTag.constData(),
275 QByteArray::number(valuePerIteration).constData(), //no 64-bit qsnprintf support
276 result.iterations);
277 outputString(buf.constData());
278 }
279
addMessage(MessageTypes type,const QString & message,const char * file,int line)280 void QXmlTestLogger::addMessage(MessageTypes type, const QString &message,
281 const char *file, int line)
282 {
283 QTestCharBuffer buf;
284 const char *tag = QTestResult::currentDataTag();
285 const char *gtag = QTestResult::currentGlobalDataTag();
286 const char *filler = (tag && gtag) ? ":" : "";
287 const bool notag = QTest::isEmpty(tag) && QTest::isEmpty(gtag);
288
289 QTestCharBuffer quotedFile;
290 QTestCharBuffer cdataGtag;
291 QTestCharBuffer cdataTag;
292 QTestCharBuffer cdataDescription;
293
294 xmlQuote("edFile, file);
295 xmlCdata(&cdataGtag, gtag);
296 xmlCdata(&cdataTag, tag);
297 xmlCdata(&cdataDescription, message.toUtf8().constData());
298
299 QTest::qt_asprintf(&buf,
300 QTest::messageFormatString(message.isEmpty(), notag),
301 QTest::xmlMessageType2String(type),
302 quotedFile.constData(), line,
303 cdataGtag.constData(),
304 filler,
305 cdataTag.constData(),
306 cdataDescription.constData());
307
308 outputString(buf.constData());
309 }
310
311 /*
312 Copy up to n characters from the src string into dest, escaping any special
313 XML characters as necessary so that dest is suitable for use in an XML
314 quoted attribute string.
315 */
xmlQuote(QTestCharBuffer * destBuf,char const * src,size_t n)316 int QXmlTestLogger::xmlQuote(QTestCharBuffer* destBuf, char const* src, size_t n)
317 {
318 if (n == 0) return 0;
319
320 char *dest = destBuf->data();
321 *dest = 0;
322 if (!src) return 0;
323
324 char* begin = dest;
325 char* end = dest + n;
326
327 while (dest < end) {
328 switch (*src) {
329
330 #define MAP_ENTITY(chr, ent) \
331 case chr: \
332 if (dest + sizeof(ent) < end) { \
333 strcpy(dest, ent); \
334 dest += sizeof(ent) - 1; \
335 } \
336 else { \
337 *dest = 0; \
338 return (dest+sizeof(ent)-begin); \
339 } \
340 ++src; \
341 break;
342
343 MAP_ENTITY('>', ">");
344 MAP_ENTITY('<', "<");
345 MAP_ENTITY('\'', "'");
346 MAP_ENTITY('"', """);
347 MAP_ENTITY('&', "&");
348
349 // not strictly necessary, but allows handling of comments without
350 // having to explicitly look for `--'
351 MAP_ENTITY('-', "-");
352
353 #undef MAP_ENTITY
354
355 case 0:
356 *dest = 0;
357 return (dest-begin);
358
359 default:
360 *dest = *src;
361 ++dest;
362 ++src;
363 break;
364 }
365 }
366
367 // If we get here, dest was completely filled (dest == end)
368 *(dest-1) = 0;
369 return (dest-begin);
370 }
371
372 /*
373 Copy up to n characters from the src string into dest, escaping any
374 special strings such that dest is suitable for use in an XML CDATA section.
375 */
xmlCdata(QTestCharBuffer * destBuf,char const * src,size_t n)376 int QXmlTestLogger::xmlCdata(QTestCharBuffer *destBuf, char const* src, size_t n)
377 {
378 if (!n) return 0;
379
380 char *dest = destBuf->data();
381
382 if (!src || n == 1) {
383 *dest = 0;
384 return 0;
385 }
386
387 static char const CDATA_END[] = "]]>";
388 static char const CDATA_END_ESCAPED[] = "]]]><![CDATA[]>";
389
390 char* begin = dest;
391 char* end = dest + n;
392 while (dest < end) {
393 if (!*src) {
394 *dest = 0;
395 return (dest-begin);
396 }
397
398 if (!strncmp(src, CDATA_END, sizeof(CDATA_END)-1)) {
399 if (dest + sizeof(CDATA_END_ESCAPED) < end) {
400 strcpy(dest, CDATA_END_ESCAPED);
401 src += sizeof(CDATA_END)-1;
402 dest += sizeof(CDATA_END_ESCAPED) - 1;
403 }
404 else {
405 *dest = 0;
406 return (dest+sizeof(CDATA_END_ESCAPED)-begin);
407 }
408 continue;
409 }
410
411 *dest = *src;
412 ++src;
413 ++dest;
414 }
415
416 // If we get here, dest was completely filled (dest == end)
417 *(dest-1) = 0;
418 return (dest-begin);
419 }
420
421 typedef int (*StringFormatFunction)(QTestCharBuffer*,char const*,size_t);
422
423 /*
424 A wrapper for string functions written to work with a fixed size buffer so they can be called
425 with a dynamically allocated buffer.
426 */
allocateStringFn(QTestCharBuffer * str,char const * src,StringFormatFunction func)427 int allocateStringFn(QTestCharBuffer* str, char const* src, StringFormatFunction func)
428 {
429 static const int MAXSIZE = 1024*1024*2;
430
431 int size = str->size();
432
433 int res = 0;
434
435 for (;;) {
436 res = func(str, src, size);
437 str->data()[size - 1] = '\0';
438 if (res < size) {
439 // We succeeded or fatally failed
440 break;
441 }
442 // buffer wasn't big enough, try again
443 size *= 2;
444 if (size > MAXSIZE) {
445 break;
446 }
447 if (!str->reset(size))
448 break; // ran out of memory - bye
449 }
450
451 return res;
452 }
453
xmlQuote(QTestCharBuffer * str,char const * src)454 int QXmlTestLogger::xmlQuote(QTestCharBuffer* str, char const* src)
455 {
456 return allocateStringFn(str, src, QXmlTestLogger::xmlQuote);
457 }
458
xmlCdata(QTestCharBuffer * str,char const * src)459 int QXmlTestLogger::xmlCdata(QTestCharBuffer* str, char const* src)
460 {
461 return allocateStringFn(str, src, QXmlTestLogger::xmlCdata);
462 }
463
464 QT_END_NAMESPACE
465