Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qabstracttestlogger.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 <QtTest/private/qabstracttestlogger_p.h>
5#include <QtTest/qtestassert.h>
6#include <qbenchmark_p.h>
7#include <qtestresult_p.h>
8
9#include <QtCore/qbytearray.h>
10#include <QtCore/qstring.h>
11
12#include <cstdio>
13
14#include <stdio.h>
15#include <stdlib.h>
16#include <stdarg.h>
17
18#ifndef Q_OS_WIN
19#include <unistd.h>
20#endif
21
22#if defined(Q_OS_WINDOWS)
23#include <io.h>
24#endif
25
26#ifdef Q_OS_ANDROID
27#include <sys/stat.h>
28#endif
29
30QT_BEGIN_NAMESPACE
31/*!
32 \internal
33 \class QAbstractTestLogger
34 \inmodule QtTest
35 \brief Base class for test loggers
36
37 Implementations of logging for QtTest should implement all pure virtual
38 methods of this class and may implement the other virtual methods. This
39 class's documentation of each virtual method sets out how those
40 implementations are invoked by the QtTest code and offers guidance on how
41 the logging class should use the data. Actual implementations may have
42 different requirements - such as a file format with a defined schema, or a
43 target audience to serve - that affect how it interprets that guidance.
44*/
45
46/*!
47 \enum QAbstractTestLogger::IncidentTypes
48
49 \value Pass The test ran to completion successfully.
50 \value XFail The test failed a check that is known to fail; this failure
51 does not prevent successful completion of the test and may be
52 followed by further checks.
53 \value Fail The test fails.
54 \value XPass A check which was expected to fail actually passed. This is
55 counted as a failure, as it means whatever caused the known failure
56 no longer does, so the test needs an update.
57 \value Skip The current test ended prematurely, skipping some checks.
58 \value BlacklistedPass As Pass but the test was blacklisted.
59 \value BlacklistedXFail As XFail but the test was blacklisted.
60 \value BlacklistedFail As Fail but the test was blacklisted.
61 \value BlacklistedXPass As XPass but the test was blacklisted.
62
63 A test may also skip (see \l {QAbstractTestLogger::}{MessageTypes}). The
64 first of skip, Fail, XPass or the blacklisted equivalents of the last two to
65 arise is decisive for the outcome of the test: loggers which should only
66 report one outcome should thus record that as the outcome and either ignore
67 later incidents (or skips) in the same run of the test function or map them
68 to some form of message.
69
70 \note tests can be "blacklisted" when they are known to fail
71 unreliably. When testing is used to decide whether a change to the code
72 under test is acceptable, such failures are not automatic grounds for
73 rejecting the change, if the unreliable failure was known before the
74 change. QTest::qExec(), as a result, only returns a failing status code if
75 some non-blacklisted test failed. Logging backends may reasonably report a
76 blacklisted result just as they would report the non-blacklisted equivalent,
77 optionally with some annotation to indicate that the result should not be
78 taken as damning evidence against recent changes to the code under test.
79
80 \sa QAbstractTestLogger::addIncident()
81*/
82
83/*!
84 \enum QAbstractTestLogger::MessageTypes
85
86 The members whose names begin with \c Q describe messages that originate in
87 calls, by the test or code under test, to Qt logging functions (implemented
88 as macros) whose names are similar, with a \c q in place of the leading \c
89 Q. The other members describe messages generated internally by QtTest.
90
91 \value QInfo An informational message from qInfo().
92 \value QWarning A warning from qWarning().
93 \value QDebug A debug message from qDebug().
94 \value QCritical A critical error from qCritical().
95 \value QFatal A fatal error from qFatal(), or an unrecognised message from
96 the Qt logging functions.
97 \value Info Messages QtTest generates as requested by the \c{-v1} or \c{-v2}
98 command-line option being specified when running the test.
99 \value Warn A warning generated internally by QtTest
100
101 \note For these purposes, some utilities provided by QtTestlib as helper
102 functions to facilitate testing - such as \l QSignalSpy, \l
103 QTestAccessibility, \l QTest::qExtractTestData(), and the facilities to
104 deliver artificial mouse and keyboard events - are treated as test code,
105 rather than internal to QtTest; they call \l qWarning() and friends rather
106 than using the internal logging infrastructure, so that \l
107 QTest::ignoreMessage() can be used to anticipate the messages.
108
109 \sa QAbstractTestLogger::addMessage()
110*/
111
112/*!
113 Constructs the base-class parts of the logger.
114
115 Derived classes should pass this base-constructor the \a filename of the
116 file to which they shall log test results, or \nullptr to write to standard
117 output. The protected member \c stream is set to the open file descriptor.
118*/
119QAbstractTestLogger::QAbstractTestLogger(const char *filename)
120{
121 if (!filename) {
122 stream = stdout;
123 return;
124 }
125#if defined(_MSC_VER)
126 if (::fopen_s(&stream, filename, "wt")) {
127#else
128 stream = ::fopen(filename, "wt");
129 if (!stream) {
130#endif
131 fprintf(stderr, "Unable to open file for logging: %s\n", filename);
132 ::exit(1);
133 }
134#ifdef Q_OS_ANDROID
135 else {
136 // Make sure output is world-readable on Android
137 ::chmod(filename, 0666);
138 }
139#endif
140}
141
142/*!
143 Destroys the logger object.
144
145 If the protected \c stream is not standard output, it is closed. In any
146 case it is cleared.
147*/
148QAbstractTestLogger::~QAbstractTestLogger()
149{
150 QTEST_ASSERT(stream);
151 if (stream != stdout)
152 fclose(stream);
153 stream = nullptr;
154}
155
156/*!
157 Returns true if the logger supports repeated test runs.
158
159 Repetition of test runs is disabled by default, and can be enabled only for
160 test loggers that support it. Even if the logger may create syntactically
161 correct test reports, log-file analyzers may assume that test names are
162 unique within one report file.
163*/
164bool QAbstractTestLogger::isRepeatSupported() const
165{
166 return false;
167}
168
169/*!
170 Returns true if the \c output stream is standard output.
171*/
172bool QAbstractTestLogger::isLoggingToStdout() const
173{
174 return stream == stdout;
175}
176
177/*!
178 Helper utility to blot out unprintable characters in \a str.
179
180 Takes a \c{'\0'}-terminated mutable string and changes any characters of it
181 that are not suitable for printing to \c{'?'} characters.
182*/
183void QAbstractTestLogger::filterUnprintable(char *str) const
184{
185 unsigned char *idx = reinterpret_cast<unsigned char *>(str);
186 while (*idx) {
187 if (((*idx < 0x20 && *idx != '\n' && *idx != '\t') || *idx == 0x7f))
188 *idx = '?';
189 ++idx;
190 }
191}
192
193/*!
194 Convenience method to write \a msg to the output stream.
195
196 The output \a msg must be a \c{'\0'}-terminated string (and not \nullptr).
197
198 If the output \c stream is TTY the message is printed as is. If not, the
199 message is filtered via filterUnprintable() first. In both cases the output
200 \c stream is flushed after printing.
201*/
202void QAbstractTestLogger::outputString(const char *msg)
203{
204 QTEST_ASSERT(stream);
205 QTEST_ASSERT(msg);
206
207#if defined(Q_OS_WINDOWS)
208#define isatty _isatty
209#define fileno _fileno
210#endif
211
212 if (isatty(fileno(stream))) {
213 ::fputs(msg, stream);
214 ::fflush(stream);
215 } else {
216 char *filtered = new char[strlen(msg) + 1];
217 strcpy(filtered, msg);
218 filterUnprintable(filtered);
219 ::fputs(filtered, stream);
220 ::fflush(stream);
221 delete [] filtered;
222 }
223
224#if defined(Q_OS_WINDOWS)
225#undef isatty
226#undef fileno
227#endif
228}
229
230/*!
231 Called before the start of a test run.
232
233 This virtual method is called before the first tests are run. A logging
234 implementation might open a file, write some preamble, or prepare in other
235 ways, such as setting up initial values of variables. It can use the usual
236 Qt logging infrastucture, since it is also called before QtTest installs its
237 own custom message handler.
238
239 \sa stopLogging()
240*/
241void QAbstractTestLogger::startLogging()
242{
243}
244
245/*!
246 Called after the end of a test run.
247
248 This virtual method is called after all tests have run. A logging
249 implementation might collate information gathered from the run, write a
250 summary, or close a file. It can use the usual Qt logging infrastucture,
251 since it is also called after QtTest has restored the default message
252 handler it replaced with its own custom message handler.
253
254 \sa startLogging()
255*/
256void QAbstractTestLogger::stopLogging()
257{
258}
259
260void QAbstractTestLogger::addBenchmarkResults(const QList<QBenchmarkResult> &result)
261{
262 for (const auto &m : result)
263 addBenchmarkResult(m);
264}
265
266/*!
267 \fn void QAbstractTestLogger::enterTestFunction(const char *function)
268
269 This virtual method is called before each test function is invoked. It is
270 passed the name of the test function (without its class prefix) as \a
271 function. It is likewise called for \c{initTestCase()} at the start of
272 testing, after \l startLogging(), and for \c{cleanupTestCase()} at the end
273 of testing, in each case passing the name of the function. It is also called
274 with \nullptr as \a function after the last of these functions, or in the
275 event of an early end to testing, before \l stopLogging().
276
277 For data-driven test functions, this is called only once, before the data
278 function is called to set up the table of datasets and the test is run with
279 its first dataset.
280
281 Every logging implementation must implement this method. It shall typically
282 need to record the name of the function for later use in log messages.
283
284 \sa leaveTestFunction(), enterTestData()
285*/
286/*!
287 \fn void QAbstractTestLogger::leaveTestFunction()
288
289 This virtual method is called after a test function has completed, to match
290 \l enterTestFunction(). For data-driven test functions, this is called only
291 once, after the test is run with its last dataset.
292
293 Every logging implementation must implement this method. In some cases it
294 may be called more than once without an intervening call to \l
295 enterTestFunction(). In such cases, the implementation should ignore these
296 later calls, until the next call to enterTestFunction().
297
298 \sa enterTestFunction(), enterTestData()
299*/
300/*!
301 \fn void QAbstractTestLogger::enterTestData(QTestData *)
302
303 This virtual method is called before and after each call to a test
304 function. For a data-driven test, the call before is passed the name of the
305 test data row. This may combine a global data row name with a local data row
306 name. For non-data-driven tests and for the call after a test function,
307 \nullptr is passed
308
309 A logging implementation might chose to record the data row name for
310 reporting of results from the test for that data row. It should, in such a
311 case, clear its record of the name when called with \nullptr.
312
313 \sa enterTestFunction(), leaveTestFunction()
314*/
315/*!
316 \fn void QAbstractTestLogger::addIncident(IncidentTypes type, const char *description, const char *file, int line)
317
318 This virtual method is called when an event occurs that relates to the
319 resolution of the test. The \a type indicates whether this was a pass, a
320 fail or a skip, whether a failure was expected, and whether the test being
321 run is blacklisted. The \a description may be empty (for a pass) or a
322 message describing the nature of the incident. Where the location in code of
323 the incident is known, it is indicated by \a file and \a line; otherwise,
324 these are \a nullptr and 0, respectively.
325
326 Every logging implementation must implement this method. Note that there are
327 circumstances where more than one incident may be reported, in this way, for
328 a single run of a test on a single dataset. It is the implementation's
329 responsibility to recognize such cases and decide what to do about them. For
330 purposes of counting resolutions of tests in the "Totals" report at the end
331 of a test run, QtTest considers the first incident (excluding XFail and its
332 blacklisted variant) decisive.
333
334 \sa addMessage(), addBenchmarkResult()
335*/
336/*!
337 \fn void QAbstractTestLogger::addBenchmarkResult(const QBenchmarkResult &result)
338
339 This virtual method is called after a benchmark has been run enough times to
340 produce usable data. It is passed the median \a result from all cycles of
341 the code controlled by the test's QBENCHMARK loop.
342
343 Every logging implementation must implement this method.
344
345 \sa addIncident(), addMessage()
346*/
347/*!
348 \overload
349 \fn void QAbstractTestLogger::addMessage(MessageTypes type, const QString &message, const char *file, int line)
350
351 This virtual method is called, via its \c QtMsgType overload, from the
352 custom message handler QtTest installs. It is also used to
353 warn about various situations detected by QtTest itself, such
354 as \e failure to see a message anticipated by QTest::ignoreMessage() and,
355 particularly when verbosity options have been enabled via the command-line,
356 to log some extra information.
357
358 Every logging implementation must implement this method. The \a type
359 indicates the category of message and the \a message is the content to be
360 reported. When the message is associated with specific code, the name of the
361 \a file and \a line number within it are also supplied; otherwise, these are
362 \nullptr and 0, respectively.
363
364 \sa QTest::ignoreMessage(), addIncident()
365*/
366
367/*!
368 \overload
369
370 This virtual method is called from the custom message handler QtTest
371 installs in place of Qt's default message handler for the duration of
372 testing, unless QTest::ignoreMessage() was used to ignore it, or too many
373 messages have previously been processed. (The limiting number of messages is
374 controlled by the -maxwarnings option to a test and defaults to 2002.)
375
376 Logging implementations should not normally need to override this method.
377 The base implementation converts \a type to the matching \l MessageType,
378 formats the given \a message suitably for the specified \a context, and
379 forwards the converted type and formatted message to the overload that takes
380 MessageType and QString.
381
382 \sa QTest::ignoreMessage(), addIncident()
383*/
384void QAbstractTestLogger::addMessage(QtMsgType type, const QMessageLogContext &context,
385 const QString &message)
386{
387 QAbstractTestLogger::MessageTypes messageType = [=]() {
388 switch (type) {
389 case QtDebugMsg: return QAbstractTestLogger::QDebug;
390 case QtInfoMsg: return QAbstractTestLogger::QInfo;
391 case QtCriticalMsg: return QAbstractTestLogger::QCritical;
392 case QtWarningMsg: return QAbstractTestLogger::QWarning;
393 case QtFatalMsg: return QAbstractTestLogger::QFatal;
394 }
395 Q_UNREACHABLE_RETURN(QAbstractTestLogger::QFatal);
396 }();
397
398 QString formattedMessage = qFormatLogMessage(type, context, message);
399
400 // Note that we explicitly ignore the file and line of the context here,
401 // as that's what QTest::messageHandler used to do when calling the same
402 // overload directly.
403 addMessage(messageType, formattedMessage);
404}
405
406namespace
407{
408 constexpr int MAXSIZE = 1024 * 1024 * 2;
409}
410
411namespace QTest
412{
413
414/*!
415 \fn int QTest::qt_asprintf(QTestCharBuffer *buf, const char *format, ...);
416 \internal
417 */
418int qt_asprintf(QTestCharBuffer *str, const char *format, ...)
419{
420 Q_ASSERT(str);
421 int size = str->size();
422 Q_ASSERT(size > 0);
423
424 va_list ap;
425 int res = 0;
426
427 do {
428 va_start(ap, format);
429 res = std::vsnprintf(str->data(), size, format, ap);
430 va_end(ap);
431 // vsnprintf() reliably '\0'-terminates
432 Q_ASSERT(res < 0 || str->data()[res < size ? res : size - 1] == '\0');
433 // Note, we're assuming that a result of -1 is always due to running out of space.
434 if (res >= 0 && res < size) // Success
435 break;
436
437 // Buffer wasn't big enough, try again:
438 size *= 2;
439 // If too large or out of memory, take what we have:
440 } while (size <= MAXSIZE && str->reset(size));
441
442 return res;
443}
444
445}
446
447namespace QTestPrivate
448{
449
450void generateTestIdentifier(QTestCharBuffer *identifier, int parts)
451{
452 const char *testObject = parts & TestObject ? QTestResult::currentTestObjectName() : "";
453 const char *testFunction = parts & TestFunction ? (QTestResult::currentTestFunction() ? QTestResult::currentTestFunction() : "UnknownTestFunc") : "";
454 const char *objectFunctionFiller = parts & TestObject && parts & (TestFunction | TestDataTag) ? "::" : "";
455 const char *testFuctionStart = parts & TestFunction ? "(" : "";
456 const char *testFuctionEnd = parts & TestFunction ? ")" : "";
457
458 const char *dataTag = (parts & TestDataTag) && QTestResult::currentDataTag() ? QTestResult::currentDataTag() : "";
459 const char *globalDataTag = (parts & TestDataTag) && QTestResult::currentGlobalDataTag() ? QTestResult::currentGlobalDataTag() : "";
460 const char *tagFiller = (dataTag[0] && globalDataTag[0]) ? ":" : "";
461
462 QTest::qt_asprintf(identifier, "%s%s%s%s%s%s%s%s",
463 testObject, objectFunctionFiller, testFunction, testFuctionStart,
464 globalDataTag, tagFiller, dataTag, testFuctionEnd);
465}
466
467// strcat() for QTestCharBuffer objects:
468bool appendCharBuffer(QTestCharBuffer *accumulator, const QTestCharBuffer &more)
469{
470 const auto bufsize = [](const QTestCharBuffer &buf) -> int {
471 const int max = buf.size();
472 return max > 0 ? int(qstrnlen(buf.constData(), max)) : 0;
473 };
474 const int extra = bufsize(more);
475 if (extra <= 0)
476 return true; // Nothing to do, fatuous success
477
478 const int oldsize = bufsize(*accumulator);
479 const int newsize = oldsize + extra + 1; // 1 for final '\0'
480 if (newsize > MAXSIZE || !accumulator->resize(newsize))
481 return false; // too big or unable to grow
482
483 char *tail = accumulator->data() + oldsize;
484 memcpy(tail, more.constData(), extra);
485 tail[extra] = '\0';
486 return true;
487}
488
489}
490
491QT_END_NAMESPACE
void generateTestIdentifier(QTestCharBuffer *identifier, int parts)
bool appendCharBuffer(QTestCharBuffer *accumulator, const QTestCharBuffer &more)
int qt_asprintf(QTestCharBuffer *str, const char *format,...)