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