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