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(&quotedTc, 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(&quotedBuild, 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(&quotedFunction, 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(&quotedFile, 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(&quotedMetric,
266         benchmarkMetricName(result.metric));
267     xmlQuote(&quotedTag, 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(&quotedFile, 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('>', "&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 */
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