Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qxmltestlogger.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <stdio.h>
5#include <string.h>
6#include <QtCore/qglobal.h>
7#include <QtCore/qlibraryinfo.h>
8
9#include <QtTest/private/qtestlog_p.h>
10#include <QtTest/private/qxmltestlogger_p.h>
11#include <QtTest/private/qtestresult_p.h>
12#include <QtTest/private/qbenchmark_p.h>
13#include <QtTest/private/qbenchmarkmetric_p.h>
14#include <QtTest/qtestcase.h>
15
17
18namespace QTest {
19
21 {
22 switch (type) {
24 return "qdebug";
26 return "qinfo";
28 return "qwarn";
30 return "qcritical";
32 return "qfatal";
34 return "info";
36 return "warn";
37 }
38 return "??????";
39 }
40
42 {
43 switch (type) {
45 return "skip";
47 return "pass";
49 return "xfail";
51 return "fail";
53 return "xpass";
55 return "bpass";
57 return "bfail";
59 return "bxpass";
61 return "bxfail";
62 }
63 return "??????";
64 }
65
66}
67
90 : QAbstractTestLogger(filename), xmlmode(mode)
91{
92}
93
95
97{
100
101 if (xmlmode == QXmlTestLogger::Complete) {
102 QTestCharBuffer quotedTc;
103 QTest::qt_asprintf(&buf, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
104 outputString(buf.constData());
106 QTest::qt_asprintf(&buf, "<TestCase name=\"%s\">\n", quotedTc.constData());
107 outputString(buf.constData());
108 } else {
109 // Unconditional end-tag => omitting the start tag is bad.
110 Q_ASSERT_X(false, "QXmlTestLogger::startLogging",
111 "Insanely long test-case name or OOM issue");
112 }
113 }
114
115 QTestCharBuffer quotedBuild;
116 if (!QLibraryInfo::build() || xmlQuote(&quotedBuild, QLibraryInfo::build())) {
118 " <Environment>\n"
119 " <QtVersion>%s</QtVersion>\n"
120 " <QtBuild>%s</QtBuild>\n"
121 " <QTestVersion>" QTEST_VERSION_STR "</QTestVersion>\n"
122 " </Environment>\n", qVersion(), quotedBuild.constData());
123 outputString(buf.constData());
124 }
125}
126
128{
130
131 QTest::qt_asprintf(&buf, " <Duration msecs=\"%s\"/>\n",
132 QString::number(QTestLog::msecsTotalTime()).toUtf8().constData());
133 outputString(buf.constData());
134 if (xmlmode == QXmlTestLogger::Complete)
135 outputString("</TestCase>\n");
136
138}
139
140void QXmlTestLogger::enterTestFunction(const char *function)
141{
142 QTestCharBuffer quotedFunction;
143 if (xmlQuote(&quotedFunction, function)) {
145 QTest::qt_asprintf(&buf, " <TestFunction name=\"%s\">\n", quotedFunction.constData());
146 outputString(buf.constData());
147 } else {
148 // Unconditional end-tag => omitting the start tag is bad.
149 Q_ASSERT_X(false, "QXmlTestLogger::enterTestFunction",
150 "Insanely long test-function name or OOM issue");
151 }
152}
153
155{
158 " <Duration msecs=\"%s\"/>\n"
159 " </TestFunction>\n",
160 QString::number(QTestLog::msecsFunctionTime()).toUtf8().constData());
161
162 outputString(buf.constData());
163}
164
165namespace QTest
166{
167
168inline static bool isEmpty(const char *str)
169{
170 return !str || !str[0];
171}
172
173static const char *incidentFormatString(bool noDescription, bool noTag)
174{
175 if (noDescription) {
176 return noTag
177 ? " <Incident type=\"%s\" file=\"%s\" line=\"%d\" />\n"
178 : " <Incident type=\"%s\" file=\"%s\" line=\"%d\">\n"
179 " <DataTag><![CDATA[%s%s%s%s]]></DataTag>\n"
180 " </Incident>\n";
181 }
182 return noTag
183 ? " <Incident type=\"%s\" file=\"%s\" line=\"%d\">\n"
184 " <Description><![CDATA[%s%s%s%s]]></Description>\n"
185 " </Incident>\n"
186 : " <Incident type=\"%s\" file=\"%s\" line=\"%d\">\n"
187 " <DataTag><![CDATA[%s%s%s]]></DataTag>\n"
188 " <Description><![CDATA[%s]]></Description>\n"
189 " </Incident>\n";
190}
191
192static const char *benchmarkResultFormatString()
193{
194 return " <BenchmarkResult metric=\"%s\" tag=\"%s\" value=\"%.6g\" iterations=\"%d\" />\n";
195}
196
197static const char *messageFormatString(bool noDescription, bool noTag)
198{
199 if (noDescription) {
200 if (noTag)
201 return " <Message type=\"%s\" file=\"%s\" line=\"%d\" />\n";
202 else
203 return " <Message type=\"%s\" file=\"%s\" line=\"%d\">\n"
204 " <DataTag><![CDATA[%s%s%s%s]]></DataTag>\n"
205 " </Message>\n";
206 } else {
207 if (noTag)
208 return " <Message type=\"%s\" file=\"%s\" line=\"%d\">\n"
209 " <Description><![CDATA[%s%s%s%s]]></Description>\n"
210 " </Message>\n";
211 else
212 return " <Message type=\"%s\" file=\"%s\" line=\"%d\">\n"
213 " <DataTag><![CDATA[%s%s%s]]></DataTag>\n"
214 " <Description><![CDATA[%s]]></Description>\n"
215 " </Message>\n";
216 }
217}
218
219} // namespace
220
221void QXmlTestLogger::addIncident(IncidentTypes type, const char *description,
222 const char *file, int line)
223{
225 const char *tag = QTestResult::currentDataTag();
226 const char *gtag = QTestResult::currentGlobalDataTag();
227 const char *filler = (tag && gtag) ? ":" : "";
228 const bool notag = QTest::isEmpty(tag) && QTest::isEmpty(gtag);
229
230 QTestCharBuffer quotedFile;
231 QTestCharBuffer cdataGtag;
232 QTestCharBuffer cdataTag;
233 QTestCharBuffer cdataDescription;
234
235 if (xmlQuote(&quotedFile, file)
236 && xmlCdata(&cdataGtag, gtag)
237 && xmlCdata(&cdataTag, tag)
238 && xmlCdata(&cdataDescription, description)) {
239
241 QTest::incidentFormatString(QTest::isEmpty(description), notag),
243 quotedFile.constData(), line,
244 cdataGtag.constData(),
245 filler,
246 cdataTag.constData(),
247 cdataDescription.constData());
248
249 outputString(buf.constData());
250 }
251}
252
254{
255 QTestCharBuffer quotedMetric;
256 QTestCharBuffer quotedTag;
257
258 if (xmlQuote(&quotedMetric, benchmarkMetricName(result.measurement.metric))
259 && xmlQuote(&quotedTag, result.context.tag.toUtf8().constData())) {
263 quotedMetric.constData(),
264 quotedTag.constData(),
265 result.measurement.value / double(result.iterations),
266 result.iterations);
267 outputString(buf.constData());
268 }
269}
270
272 const char *file, int line)
273{
275 const char *tag = QTestResult::currentDataTag();
276 const char *gtag = QTestResult::currentGlobalDataTag();
277 const char *filler = (tag && gtag) ? ":" : "";
278 const bool notag = QTest::isEmpty(tag) && QTest::isEmpty(gtag);
279
280 QTestCharBuffer quotedFile;
281 QTestCharBuffer cdataGtag;
282 QTestCharBuffer cdataTag;
283 QTestCharBuffer cdataDescription;
284
285 if (xmlQuote(&quotedFile, file)
286 && xmlCdata(&cdataGtag, gtag)
287 && xmlCdata(&cdataTag, tag)
288 && xmlCdata(&cdataDescription, message.toUtf8().constData())) {
290 QTest::messageFormatString(message.isEmpty(), notag),
292 quotedFile.constData(), line,
293 cdataGtag.constData(),
294 filler,
295 cdataTag.constData(),
296 cdataDescription.constData());
297
298 outputString(buf.constData());
299 }
300}
301
302int QXmlTestLogger::xmlQuote(QTestCharBuffer *destBuf, char const *src, qsizetype n)
303{
304 // QTestCharBuffer initially has size 512, with '\0' at the start of its
305 // data; and we only grow it.
306 Q_ASSERT(n >= 512 && destBuf->size() == n);
307 char *dest = destBuf->data();
308
309 if (!src || !*src) {
310 Q_ASSERT(!dest[0]);
311 return 0;
312 }
313
314 char *begin = dest;
315 char *end = dest + n;
316
317 while (dest < end) {
318 switch (*src) {
319
320#define MAP_ENTITY(chr, ent) \
321 case chr: \
322 if (dest + sizeof(ent) < end) { \
323 strcpy(dest, ent); \
324 dest += sizeof(ent) - 1; \
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:
357 Q_ASSERT(dest == end && end > begin);
358 dest[-1] = '\0'; // hygiene, but it'll be ignored
359 return n;
360}
361
362int QXmlTestLogger::xmlCdata(QTestCharBuffer *destBuf, char const *src, qsizetype n)
363{
364 Q_ASSERT(n >= 512 && destBuf->size() == n);
365 char *dest = destBuf->data();
366
367 if (!src || !*src) {
368 Q_ASSERT(!dest[0]);
369 return 0;
370 }
371
372 static char const CDATA_END[] = "]]>";
373 static char const CDATA_END_ESCAPED[] = "]]]><![CDATA[]>";
374 const size_t CDATA_END_LEN = sizeof(CDATA_END) - 1;
375
376 char *begin = dest;
377 char *end = dest + n;
378 while (dest < end) {
379 if (!*src) {
380 *dest = '\0';
381 return dest - begin;
382 }
383
384 if (!strncmp(src, CDATA_END, CDATA_END_LEN)) {
385 if (dest + sizeof(CDATA_END_ESCAPED) < end) {
386 strcpy(dest, CDATA_END_ESCAPED);
387 src += CDATA_END_LEN;
388 dest += sizeof(CDATA_END_ESCAPED) - 1;
389 } else {
390 *dest = '\0';
391 return dest + sizeof(CDATA_END_ESCAPED) - begin;
392 }
393 continue;
394 }
395
396 *dest = *src;
397 ++src;
398 ++dest;
399 }
400
401 // If we get here, dest was completely filled; caller shall grow and retry:
402 Q_ASSERT(dest == end && end > begin);
403 dest[-1] = '\0'; // hygiene, but it'll be ignored
404 return n;
405}
406
407typedef int (*StringFormatFunction)(QTestCharBuffer *, char const *, qsizetype);
408
409/*
410 A wrapper for string functions written to work with a fixed size buffer so they can be called
411 with a dynamically allocated buffer.
412*/
414{
415 constexpr int MAXSIZE = 1024 * 1024 * 2;
416 int size = str->size();
417 Q_ASSERT(size >= 512 && !str->data()[0]);
418
419 do {
420 const int res = func(str, src, size);
421 if (res < size) { // Success
422 Q_ASSERT(res > 0 || (!res && (!src || !src[0])));
423 return true;
424 }
425
426 // Buffer wasn't big enough, try again, if not too big:
427 size *= 2;
428 } while (size <= MAXSIZE && str->reset(size));
429
430 return false;
431}
432
433/*
434 Copy from \a src into \a destBuf, escaping any special XML characters as
435 necessary so that destBuf is suitable for use in an XML quoted attribute
436 string. Expands \a destBuf as needed to make room, up to a size of 2
437 MiB. Input requiring more than that much space for output is considered
438 invalid.
439
440 Returns 0 on invalid or empty input, the actual length written on success.
441*/
446
447/*
448 Copy from \a src into \a destBuf, escaping any special strings such that
449 destBuf is suitable for use in an XML CDATA section. Expands \a destBuf as
450 needed to make room, up to a size of 2 MiB. Input requiring more than that
451 much space for output is considered invalid.
452
453 Returns 0 on invalid or empty input, the actual length written on success.
454*/
459
Base class for test loggers.
void outputString(const char *msg)
Convenience method to write msg to the output stream.
IncidentTypes
\value Pass The test ran to completion successfully.
virtual void startLogging()
Called before the start of a test run.
MessageTypes
The members whose names begin with Q describe messages that originate in calls, by the test or code u...
virtual void stopLogging()
Called after the end of a test run.
static const char * build() noexcept
Returns a string describing how this version of Qt was built.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
const QChar * constData() const
Returns a pointer to the data stored in the QString.
Definition qstring.h:1246
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
QChar * data()
Returns a pointer to the data stored in the QString.
Definition qstring.h:1240
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8084
static qreal msecsFunctionTime()
Definition qtestlog_p.h:119
static qreal msecsTotalTime()
Definition qtestlog_p.h:117
static const char * currentTestObjectName()
static const char * currentGlobalDataTag()
static const char * currentDataTag()
void startLogging() override
Called before the start of a test run.
void addIncident(IncidentTypes type, const char *description, const char *file=nullptr, int line=0) override
This virtual method is called when an event occurs that relates to the resolution of the test.
static bool xmlCdata(QTestCharBuffer *dest, char const *src)
void addBenchmarkResult(const QBenchmarkResult &result) override
This virtual method is called after a benchmark has been run enough times to produce usable data.
void addMessage(MessageTypes type, const QString &message, const char *file=nullptr, int line=0) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
void enterTestFunction(const char *function) override
This virtual method is called before each test function is invoked.
void stopLogging() override
Called after the end of a test run.
static bool xmlQuote(QTestCharBuffer *dest, char const *src)
QXmlTestLogger(XmlMode mode, const char *filename)
void leaveTestFunction() override
This virtual method is called after a test function has completed, to match \l enterTestFunction().
QString str
[2]
Combined button and popup list for selecting options.
int qt_asprintf(QTestCharBuffer *str, const char *format,...)
static const char * xmlIncidentType2String(QAbstractTestLogger::IncidentTypes type)
static const char * incidentFormatString(bool noDescription, bool noTag)
static const char * benchmarkResultFormatString()
static bool isEmpty(const char *str)
static const char * xmlMessageType2String(QAbstractTestLogger::MessageTypes type)
static const char * messageFormatString(bool noDescription, bool noTag)
AudioChannelLayoutTag tag
GLenum mode
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLuint end
GLenum src
GLenum type
GLenum GLuint GLenum GLsizei const GLchar * buf
GLuint GLsizei const GLchar * message
GLfloat n
GLboolean reset
GLenum func
Definition qopenglext.h:663
GLuint res
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
QtPrivate::QRegularExpressionMatchIteratorRangeBasedForIterator begin(const QRegularExpressionMatchIterator &iterator)
#define QTEST_VERSION_STR
QT_BEGIN_NAMESPACE Q_CORE_EXPORT Q_DECL_CONST_FUNCTION const char * qVersion(void) Q_DECL_NOEXCEPT
ptrdiff_t qsizetype
Definition qtypes.h:165
int(* StringFormatFunction)(QTestCharBuffer *, char const *, qsizetype)
#define MAP_ENTITY(chr, ent)
static bool allocateStringFn(QTestCharBuffer *str, char const *src, StringFormatFunction func)
QFile file
[0]