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
52QT_BEGIN_NAMESPACE
53
54namespace QTest {
55
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
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
105QXmlTestLogger::QXmlTestLogger(XmlMode mode, const char *filename)
106 : QAbstractTestLogger(filename), xmlmode(mode)
107{
108}
109
110QXmlTestLogger::~QXmlTestLogger() = default;
111
112void QXmlTestLogger::startLogging()
113{
114 QAbstractTestLogger::startLogging();
115 QTestCharBuffer buf;
116
117 if (xmlmode == QXmlTestLogger::Complete) {
118 QTestCharBuffer quotedTc;
119 xmlQuote(dest: &quotedTc, src: QTestResult::currentTestObjectName());
120 QTest::qt_asprintf(buf: &buf,
121 format: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
122 "<TestCase name=\"%s\">\n", quotedTc.constData());
123 outputString(msg: buf.constData());
124 }
125
126 QTestCharBuffer quotedBuild;
127 xmlQuote(dest: &quotedBuild, src: QLibraryInfo::build());
128
129 QTest::qt_asprintf(buf: &buf,
130 format: "<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(msg: buf.constData());
136}
137
138void QXmlTestLogger::stopLogging()
139{
140 QTestCharBuffer buf;
141
142 QTest::qt_asprintf(buf: &buf, format: "<Duration msecs=\"%s\"/>\n",
143 QString::number(QTestLog::msecsTotalTime()).toUtf8().constData());
144 outputString(msg: buf.constData());
145 if (xmlmode == QXmlTestLogger::Complete) {
146 outputString(msg: "</TestCase>\n");
147 }
148
149 QAbstractTestLogger::stopLogging();
150}
151
152void QXmlTestLogger::enterTestFunction(const char *function)
153{
154 QTestCharBuffer buf;
155 QTestCharBuffer quotedFunction;
156 xmlQuote(dest: &quotedFunction, src: function);
157 QTest::qt_asprintf(buf: &buf, format: "<TestFunction name=\"%s\">\n", quotedFunction.constData());
158 outputString(msg: buf.constData());
159}
160
161void QXmlTestLogger::leaveTestFunction()
162{
163 QTestCharBuffer buf;
164 QTest::qt_asprintf(buf: &buf,
165 format: " <Duration msecs=\"%s\"/>\n"
166 "</TestFunction>\n",
167 QString::number(QTestLog::msecsFunctionTime()).toUtf8().constData());
168
169 outputString(msg: buf.constData());
170}
171
172namespace QTest
173{
174
175inline static bool isEmpty(const char *str)
176{
177 return !str || !str[0];
178}
179
180static 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
199static const char *benchmarkResultFormatString()
200{
201 return "<BenchmarkResult metric=\"%s\" tag=\"%s\" value=\"%s\" iterations=\"%d\" />\n";
202}
203
204static 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
228void 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(str: tag) && QTest::isEmpty(str: gtag);
236
237 QTestCharBuffer quotedFile;
238 QTestCharBuffer cdataGtag;
239 QTestCharBuffer cdataTag;
240 QTestCharBuffer cdataDescription;
241
242 xmlQuote(dest: &quotedFile, src: file);
243 xmlCdata(dest: &cdataGtag, src: gtag);
244 xmlCdata(dest: &cdataTag, src: tag);
245 xmlCdata(dest: &cdataDescription, src: description);
246
247 QTest::qt_asprintf(buf: &buf,
248 format: QTest::incidentFormatString(noDescription: QTest::isEmpty(str: description), noTag: notag),
249 QTest::xmlIncidentType2String(type),
250 quotedFile.constData(), line,
251 cdataGtag.constData(),
252 filler,
253 cdataTag.constData(),
254 cdataDescription.constData());
255
256 outputString(msg: buf.constData());
257}
258
259void QXmlTestLogger::addBenchmarkResult(const QBenchmarkResult &result)
260{
261 QTestCharBuffer buf;
262 QTestCharBuffer quotedMetric;
263 QTestCharBuffer quotedTag;
264
265 xmlQuote(dest: &quotedMetric,
266 src: benchmarkMetricName(metric: result.metric));
267 xmlQuote(dest: &quotedTag, src: result.context.tag.toUtf8().constData());
268
269 const qreal valuePerIteration = qreal(result.value) / qreal(result.iterations);
270 QTest::qt_asprintf(
271 buf: &buf,
272 format: QTest::benchmarkResultFormatString(),
273 quotedMetric.constData(),
274 quotedTag.constData(),
275 QByteArray::number(valuePerIteration).constData(), //no 64-bit qsnprintf support
276 result.iterations);
277 outputString(msg: buf.constData());
278}
279
280void 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(str: tag) && QTest::isEmpty(str: gtag);
288
289 QTestCharBuffer quotedFile;
290 QTestCharBuffer cdataGtag;
291 QTestCharBuffer cdataTag;
292 QTestCharBuffer cdataDescription;
293
294 xmlQuote(dest: &quotedFile, src: file);
295 xmlCdata(dest: &cdataGtag, src: gtag);
296 xmlCdata(dest: &cdataTag, src: tag);
297 xmlCdata(dest: &cdataDescription, src: message.toUtf8().constData());
298
299 QTest::qt_asprintf(buf: &buf,
300 format: QTest::messageFormatString(noDescription: message.isEmpty(), noTag: notag),
301 QTest::xmlMessageType2String(type),
302 quotedFile.constData(), line,
303 cdataGtag.constData(),
304 filler,
305 cdataTag.constData(),
306 cdataDescription.constData());
307
308 outputString(msg: 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*/
316int 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('>', "&gt;");
344 MAP_ENTITY('<', "&lt;");
345 MAP_ENTITY('\'', "&apos;");
346 MAP_ENTITY('"', "&quot;");
347 MAP_ENTITY('&', "&amp;");
348
349 // not strictly necessary, but allows handling of comments without
350 // having to explicitly look for `--'
351 MAP_ENTITY('-', "&#x002D;");
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*/
376int 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(s1: src, s2: CDATA_END, n: sizeof(CDATA_END)-1)) {
399 if (dest + sizeof(CDATA_END_ESCAPED) < end) {
400 strcpy(dest: dest, src: 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
421typedef 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*/
427int 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(newSize: size))
448 break; // ran out of memory - bye
449 }
450
451 return res;
452}
453
454int QXmlTestLogger::xmlQuote(QTestCharBuffer* str, char const* src)
455{
456 return allocateStringFn(str, src, func: QXmlTestLogger::xmlQuote);
457}
458
459int QXmlTestLogger::xmlCdata(QTestCharBuffer* str, char const* src)
460{
461 return allocateStringFn(str, src, func: QXmlTestLogger::xmlCdata);
462}
463
464QT_END_NAMESPACE
465

source code of qtbase/src/testlib/qxmltestlogger.cpp