1 | // Copyright (C) 2021 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/qtestassert.h> |
5 | |
6 | #include <QtTest/private/qtestlog_p.h> |
7 | #include <QtTest/private/qtestresult_p.h> |
8 | #include <QtTest/private/qabstracttestlogger_p.h> |
9 | #include <QtTest/private/qplaintestlogger_p.h> |
10 | #include <QtTest/private/qcsvbenchmarklogger_p.h> |
11 | #include <QtTest/private/qjunittestlogger_p.h> |
12 | #include <QtTest/private/qxmltestlogger_p.h> |
13 | #include <QtTest/private/qteamcitylogger_p.h> |
14 | #include <QtTest/private/qtaptestlogger_p.h> |
15 | #if defined(HAVE_XCTEST) |
16 | #include <QtTest/private/qxctestlogger_p.h> |
17 | #endif |
18 | |
19 | #if defined(Q_OS_DARWIN) |
20 | #include <QtTest/private/qappletestlogger_p.h> |
21 | #endif |
22 | |
23 | #include <QtCore/qatomic.h> |
24 | #include <QtCore/qbytearray.h> |
25 | #include <QtCore/qelapsedtimer.h> |
26 | #include <QtCore/qlist.h> |
27 | #include <QtCore/qmutex.h> |
28 | #include <QtCore/qvariant.h> |
29 | #if QT_CONFIG(regularexpression) |
30 | #include <QtCore/QRegularExpression> |
31 | #endif |
32 | |
33 | #include <stdlib.h> |
34 | #include <string.h> |
35 | #include <limits.h> |
36 | |
37 | #include <QtCore/q20algorithm.h> |
38 | #include <atomic> |
39 | #include <cstdio> |
40 | #include <memory> |
41 | #include <vector> |
42 | |
43 | QT_BEGIN_NAMESPACE |
44 | |
45 | using namespace Qt::StringLiterals; |
46 | |
47 | static void saveCoverageTool(const char * appname, bool testfailed, bool installedTestCoverage) |
48 | { |
49 | #ifdef __COVERAGESCANNER__ |
50 | # if QT_CONFIG(testlib_selfcover) |
51 | __coveragescanner_teststate(QTestLog::failCount() > 0 ? "FAILED": |
52 | QTestLog::passCount() > 0 ? "PASSED": "SKIPPED"); |
53 | # else |
54 | if (!installedTestCoverage) |
55 | return; |
56 | // install again to make sure the filename is correct. |
57 | // without this, a plugin or similar may have changed the filename. |
58 | __coveragescanner_install(appname); |
59 | __coveragescanner_teststate(testfailed ? "FAILED": "PASSED"); |
60 | __coveragescanner_save(); |
61 | __coveragescanner_testname(""); |
62 | __coveragescanner_clear(); |
63 | unsetenv("QT_TESTCOCOON_ACTIVE"); |
64 | # endif // testlib_selfcover |
65 | #else |
66 | Q_UNUSED(appname); |
67 | Q_UNUSED(testfailed); |
68 | Q_UNUSED(installedTestCoverage); |
69 | #endif |
70 | } |
71 | |
72 | Q_CONSTINIT static QElapsedTimer elapsedFunctionTime; |
73 | Q_CONSTINIT static QElapsedTimer elapsedTotalTime; |
74 | |
75 | namespace { |
76 | class LoggerRegistry |
77 | { |
78 | using LoggersContainer = std::vector<std::shared_ptr<QAbstractTestLogger>>; |
79 | using SharedLoggersContainer = std::shared_ptr<LoggersContainer>; |
80 | |
81 | public: |
82 | void addLogger(std::unique_ptr<QAbstractTestLogger> logger) |
83 | { |
84 | // read/update/clone |
85 | const SharedLoggersContainer currentLoggers = load(); |
86 | SharedLoggersContainer newLoggers = currentLoggers |
87 | ? std::make_shared<LoggersContainer>(args&: *currentLoggers) |
88 | : std::make_shared<LoggersContainer>(); |
89 | newLoggers->emplace_back(args: std::move(logger)); |
90 | store(newLoggers: std::move(newLoggers)); |
91 | } |
92 | |
93 | void clear() { store(newLoggers: SharedLoggersContainer{}); } |
94 | |
95 | auto allLoggers() const |
96 | { |
97 | struct LoggersRange |
98 | { |
99 | const SharedLoggersContainer loggers; |
100 | |
101 | auto begin() const |
102 | { |
103 | return loggers ? loggers->cbegin() : LoggersContainer::const_iterator{}; |
104 | } |
105 | auto end() const |
106 | { |
107 | return loggers ? loggers->cend() : LoggersContainer::const_iterator{}; |
108 | } |
109 | bool isEmpty() const { return loggers ? loggers->empty() : true; } |
110 | }; |
111 | |
112 | return LoggersRange{ .loggers: load() }; |
113 | } |
114 | |
115 | private: |
116 | #ifdef __cpp_lib_atomic_shared_ptr |
117 | SharedLoggersContainer load() const { return loggers.load(std::memory_order_relaxed); } |
118 | void store(SharedLoggersContainer newLoggers) |
119 | { |
120 | loggers.store(std::move(newLoggers), std::memory_order_relaxed); |
121 | } |
122 | std::atomic<SharedLoggersContainer> loggers; |
123 | #else |
124 | SharedLoggersContainer load() const |
125 | { |
126 | return std::atomic_load_explicit(p: &loggers, std::memory_order_relaxed); |
127 | } |
128 | void store(SharedLoggersContainer newLoggers) |
129 | { |
130 | std::atomic_store_explicit(p: &loggers, r: std::move(newLoggers), std::memory_order_relaxed); |
131 | } |
132 | SharedLoggersContainer loggers; |
133 | #endif |
134 | }; |
135 | |
136 | } // namespace |
137 | |
138 | namespace QTest { |
139 | |
140 | int fails = 0; |
141 | int passes = 0; |
142 | int skips = 0; |
143 | int blacklists = 0; |
144 | enum { Unresolved, Passed, Skipped, Suppressed, Failed } currentTestState; |
145 | |
146 | struct IgnoreResultList |
147 | { |
148 | inline IgnoreResultList(QtMsgType tp, const QVariant &patternIn) |
149 | : type(tp), pattern(patternIn) {} |
150 | |
151 | static inline void clearList(IgnoreResultList *&list) |
152 | { |
153 | while (list) { |
154 | IgnoreResultList *current = list; |
155 | list = list->next; |
156 | delete current; |
157 | } |
158 | } |
159 | |
160 | static void append(IgnoreResultList *&list, QtMsgType type, const QVariant &patternIn) |
161 | { |
162 | QTest::IgnoreResultList *item = new QTest::IgnoreResultList(type, patternIn); |
163 | |
164 | if (!list) { |
165 | list = item; |
166 | return; |
167 | } |
168 | IgnoreResultList *last = list; |
169 | for ( ; last->next; last = last->next) ; |
170 | last->next = item; |
171 | } |
172 | |
173 | static bool stringsMatch(const QString &expected, const QString &actual) |
174 | { |
175 | if (expected == actual) |
176 | return true; |
177 | |
178 | // ignore an optional whitespace at the end of str |
179 | // (the space was added automatically by ~QDebug() until Qt 5.3, |
180 | // so autotests still might expect it) |
181 | if (expected.endsWith(c: u' ')) |
182 | return actual == QStringView{expected}.left(n: expected.size() - 1); |
183 | |
184 | return false; |
185 | } |
186 | |
187 | inline bool matches(QtMsgType tp, const QString &message) const |
188 | { |
189 | return tp == type |
190 | && (pattern.userType() == QMetaType::QString ? |
191 | stringsMatch(expected: pattern.toString(), actual: message) : |
192 | #if QT_CONFIG(regularexpression) |
193 | pattern.toRegularExpression().match(subject: message).hasMatch()); |
194 | #else |
195 | false); |
196 | #endif |
197 | } |
198 | |
199 | QtMsgType type; |
200 | QVariant pattern; |
201 | IgnoreResultList *next = nullptr; |
202 | }; |
203 | |
204 | static IgnoreResultList *ignoreResultList = nullptr; |
205 | Q_CONSTINIT static QBasicMutex mutex; |
206 | |
207 | static std::vector<QVariant> failOnWarningList; |
208 | |
209 | Q_GLOBAL_STATIC(LoggerRegistry, loggers) |
210 | |
211 | static int verbosity = 0; |
212 | static int maxWarnings = 2002; |
213 | static bool installedTestCoverage = true; |
214 | |
215 | static QtMessageHandler oldMessageHandler; |
216 | |
217 | static bool handleIgnoredMessage(QtMsgType type, const QString &message) |
218 | { |
219 | const QMutexLocker mutexLocker(&QTest::mutex); |
220 | |
221 | if (!ignoreResultList) |
222 | return false; |
223 | IgnoreResultList *last = nullptr; |
224 | IgnoreResultList *list = ignoreResultList; |
225 | while (list) { |
226 | if (list->matches(tp: type, message)) { |
227 | // remove the item from the list |
228 | if (last) |
229 | last->next = list->next; |
230 | else |
231 | ignoreResultList = list->next; |
232 | |
233 | delete list; |
234 | return true; |
235 | } |
236 | |
237 | last = list; |
238 | list = list->next; |
239 | } |
240 | return false; |
241 | } |
242 | |
243 | static bool handleFailOnWarning(const QMessageLogContext &context, const QString &message) |
244 | { |
245 | // failOnWarning can be called multiple times per test function, so let |
246 | // each call cause a failure if required. |
247 | for (const auto &pattern : failOnWarningList) { |
248 | if (pattern.metaType() == QMetaType::fromType<QString>()) { |
249 | if (message != pattern.toString()) |
250 | continue; |
251 | } |
252 | #if QT_CONFIG(regularexpression) |
253 | else if (pattern.metaType() == QMetaType::fromType<QRegularExpression>()) { |
254 | if (!message.contains(re: pattern.toRegularExpression())) |
255 | continue; |
256 | } |
257 | #endif |
258 | |
259 | const size_t maxMsgLen = 1024; |
260 | char msg[maxMsgLen] = {'\0'}; |
261 | std::snprintf(s: msg, maxlen: maxMsgLen, format: "Received a warning that resulted in a failure:\n%s", |
262 | qPrintable(message)); |
263 | QTestResult::addFailure(message: msg, file: context.file, line: context.line); |
264 | return true; |
265 | } |
266 | return false; |
267 | } |
268 | |
269 | static void messageHandler(QtMsgType type, const QMessageLogContext & context, const QString &message) |
270 | { |
271 | static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(QTest::maxWarnings); |
272 | |
273 | auto loggerCapture = loggers->allLoggers(); |
274 | |
275 | if (loggerCapture.isEmpty()) { |
276 | // the message handler may be called from a worker thread, after the main thread stopped |
277 | // logging. Forwarding to original message handler to avoid swallowing the message |
278 | Q_ASSERT(oldMessageHandler); |
279 | oldMessageHandler(type, context, message); |
280 | return; |
281 | } |
282 | |
283 | if (handleIgnoredMessage(type, message)) { |
284 | // the message is expected, so just swallow it. |
285 | return; |
286 | } |
287 | |
288 | if (type == QtWarningMsg && handleFailOnWarning(context, message)) |
289 | return; |
290 | |
291 | if (type != QtFatalMsg) { |
292 | if (counter.loadRelaxed() <= 0) |
293 | return; |
294 | |
295 | if (!counter.deref()) { |
296 | for (auto &logger : loggerCapture) |
297 | logger->addMessage(type: QAbstractTestLogger::Warn, |
298 | QStringLiteral("Maximum amount of warnings exceeded. Use " |
299 | "-maxwarnings to override.")); |
300 | |
301 | return; |
302 | } |
303 | } |
304 | |
305 | for (auto &logger : loggerCapture) |
306 | logger->addMessage(type, context, message); |
307 | |
308 | if (type == QtFatalMsg) { |
309 | /* Right now, we're inside the custom message handler and we're |
310 | * being qt_message_output in qglobal.cpp. After we return from |
311 | * this function, it will proceed with calling exit() and abort() |
312 | * and hence crash. Therefore, we call these logging functions such |
313 | * that we wrap up nicely, and in particular produce well-formed XML. */ |
314 | QTestResult::addFailure(message: "Received a fatal error.", file: context.file, line: context.line); |
315 | QTestLog::leaveTestFunction(); |
316 | QTestLog::stopLogging(); |
317 | } |
318 | } |
319 | } |
320 | |
321 | void QTestLog::enterTestFunction(const char* function) |
322 | { |
323 | elapsedFunctionTime.restart(); |
324 | if (printAvailableTags) |
325 | return; |
326 | |
327 | QTEST_ASSERT(function); |
328 | |
329 | for (auto &logger : QTest::loggers->allLoggers()) |
330 | logger->enterTestFunction(function); |
331 | } |
332 | |
333 | void QTestLog::enterTestData(QTestData *data) |
334 | { |
335 | QTEST_ASSERT(data); |
336 | |
337 | for (auto &logger : QTest::loggers->allLoggers()) |
338 | logger->enterTestData(data); |
339 | } |
340 | |
341 | int QTestLog::unhandledIgnoreMessages() |
342 | { |
343 | const QMutexLocker mutexLocker(&QTest::mutex); |
344 | int i = 0; |
345 | QTest::IgnoreResultList *list = QTest::ignoreResultList; |
346 | while (list) { |
347 | ++i; |
348 | list = list->next; |
349 | } |
350 | return i; |
351 | } |
352 | |
353 | void QTestLog::leaveTestFunction() |
354 | { |
355 | if (printAvailableTags) |
356 | return; |
357 | |
358 | for (auto &logger : QTest::loggers->allLoggers()) |
359 | logger->leaveTestFunction(); |
360 | } |
361 | |
362 | void QTestLog::printUnhandledIgnoreMessages() |
363 | { |
364 | const QMutexLocker mutexLocker(&QTest::mutex); |
365 | QString message; |
366 | QTest::IgnoreResultList *list = QTest::ignoreResultList; |
367 | while (list) { |
368 | if (list->pattern.userType() == QMetaType::QString) { |
369 | message = "Did not receive message: \"%1\""_L1.arg(args: list->pattern.toString()); |
370 | } else { |
371 | #if QT_CONFIG(regularexpression) |
372 | message = "Did not receive any message matching: \"%1\""_L1.arg( |
373 | args: list->pattern.toRegularExpression().pattern()); |
374 | #endif |
375 | } |
376 | for (auto &logger : QTest::loggers->allLoggers()) |
377 | logger->addMessage(type: QAbstractTestLogger::Info, message); |
378 | |
379 | list = list->next; |
380 | } |
381 | } |
382 | |
383 | void QTestLog::clearIgnoreMessages() |
384 | { |
385 | const QMutexLocker mutexLocker(&QTest::mutex); |
386 | QTest::IgnoreResultList::clearList(list&: QTest::ignoreResultList); |
387 | } |
388 | |
389 | void QTestLog::clearFailOnWarnings() |
390 | { |
391 | QTest::failOnWarningList.clear(); |
392 | } |
393 | |
394 | void QTestLog::clearCurrentTestState() |
395 | { |
396 | clearIgnoreMessages(); |
397 | clearFailOnWarnings(); |
398 | QTest::currentTestState = QTest::Unresolved; |
399 | } |
400 | |
401 | void QTestLog::addPass(const char *msg) |
402 | { |
403 | if (printAvailableTags) |
404 | return; |
405 | |
406 | QTEST_ASSERT(msg); |
407 | Q_ASSERT(QTest::currentTestState == QTest::Unresolved); |
408 | |
409 | ++QTest::passes; |
410 | QTest::currentTestState = QTest::Passed; |
411 | |
412 | for (auto &logger : QTest::loggers->allLoggers()) |
413 | logger->addIncident(type: QAbstractTestLogger::Pass, description: msg); |
414 | } |
415 | |
416 | void QTestLog::addFail(const char *msg, const char *file, int line) |
417 | { |
418 | QTEST_ASSERT(msg); |
419 | |
420 | if (QTest::currentTestState == QTest::Unresolved) { |
421 | ++QTest::fails; |
422 | } else { |
423 | // After an XPASS/Continue, or fail or skip in a function the test |
424 | // calls, we can subsequently fail. |
425 | Q_ASSERT(QTest::currentTestState == QTest::Failed |
426 | || QTest::currentTestState == QTest::Skipped); |
427 | } |
428 | // It is up to particular loggers to decide whether to report such |
429 | // subsequent failures; they may carry useful information. |
430 | |
431 | QTest::currentTestState = QTest::Failed; |
432 | for (auto &logger : QTest::loggers->allLoggers()) |
433 | logger->addIncident(type: QAbstractTestLogger::Fail, description: msg, file, line); |
434 | } |
435 | |
436 | void QTestLog::addXFail(const char *msg, const char *file, int line) |
437 | { |
438 | QTEST_ASSERT(msg); |
439 | |
440 | // Will be counted in addPass() if we get there. |
441 | |
442 | for (auto &logger : QTest::loggers->allLoggers()) |
443 | logger->addIncident(type: QAbstractTestLogger::XFail, description: msg, file, line); |
444 | } |
445 | |
446 | void QTestLog::addXPass(const char *msg, const char *file, int line) |
447 | { |
448 | QTEST_ASSERT(msg); |
449 | |
450 | if (QTest::currentTestState == QTest::Unresolved) { |
451 | ++QTest::fails; |
452 | } else { |
453 | // After an XPASS/Continue, we can subsequently XPASS again. |
454 | // Likewise after a fail or skip in a function called by the test. |
455 | Q_ASSERT(QTest::currentTestState == QTest::Failed |
456 | || QTest::currentTestState == QTest::Skipped); |
457 | } |
458 | |
459 | QTest::currentTestState = QTest::Failed; |
460 | for (auto &logger : QTest::loggers->allLoggers()) |
461 | logger->addIncident(type: QAbstractTestLogger::XPass, description: msg, file, line); |
462 | } |
463 | |
464 | void QTestLog::addBPass(const char *msg) |
465 | { |
466 | QTEST_ASSERT(msg); |
467 | Q_ASSERT(QTest::currentTestState == QTest::Unresolved); |
468 | |
469 | ++QTest::blacklists; // Not passes ? |
470 | QTest::currentTestState = QTest::Suppressed; |
471 | |
472 | for (auto &logger : QTest::loggers->allLoggers()) |
473 | logger->addIncident(type: QAbstractTestLogger::BlacklistedPass, description: msg); |
474 | } |
475 | |
476 | void QTestLog::addBFail(const char *msg, const char *file, int line) |
477 | { |
478 | QTEST_ASSERT(msg); |
479 | |
480 | if (QTest::currentTestState == QTest::Unresolved) { |
481 | ++QTest::blacklists; |
482 | } else { |
483 | // After a BXPASS/Continue, we can subsequently fail. |
484 | // Likewise after a fail or skip in a function called by a test. |
485 | Q_ASSERT(QTest::currentTestState == QTest::Suppressed |
486 | || QTest::currentTestState == QTest::Skipped); |
487 | } |
488 | |
489 | QTest::currentTestState = QTest::Suppressed; |
490 | for (auto &logger : QTest::loggers->allLoggers()) |
491 | logger->addIncident(type: QAbstractTestLogger::BlacklistedFail, description: msg, file, line); |
492 | } |
493 | |
494 | void QTestLog::addBXPass(const char *msg, const char *file, int line) |
495 | { |
496 | QTEST_ASSERT(msg); |
497 | |
498 | if (QTest::currentTestState == QTest::Unresolved) { |
499 | ++QTest::blacklists; |
500 | } else { |
501 | // After a BXPASS/Continue, we may BXPASS again. |
502 | // Likewise after a fail or skip in a function called by a test. |
503 | Q_ASSERT(QTest::currentTestState == QTest::Suppressed |
504 | || QTest::currentTestState == QTest::Skipped); |
505 | } |
506 | |
507 | QTest::currentTestState = QTest::Suppressed; |
508 | for (auto &logger : QTest::loggers->allLoggers()) |
509 | logger->addIncident(type: QAbstractTestLogger::BlacklistedXPass, description: msg, file, line); |
510 | } |
511 | |
512 | void QTestLog::addBXFail(const char *msg, const char *file, int line) |
513 | { |
514 | QTEST_ASSERT(msg); |
515 | |
516 | // Will be counted in addBPass() if we get there. |
517 | |
518 | for (auto &logger : QTest::loggers->allLoggers()) |
519 | logger->addIncident(type: QAbstractTestLogger::BlacklistedXFail, description: msg, file, line); |
520 | } |
521 | |
522 | void QTestLog::addSkip(const char *msg, const char *file, int line) |
523 | { |
524 | QTEST_ASSERT(msg); |
525 | |
526 | if (QTest::currentTestState == QTest::Unresolved) { |
527 | ++QTest::skips; |
528 | QTest::currentTestState = QTest::Skipped; |
529 | } else { |
530 | // After an B?XPASS/Continue, we might subsequently skip. |
531 | // Likewise after a skip in a function called by a test. |
532 | Q_ASSERT(QTest::currentTestState == QTest::Suppressed |
533 | || QTest::currentTestState == QTest::Failed |
534 | || QTest::currentTestState == QTest::Skipped); |
535 | } |
536 | // It is up to particular loggers to decide whether to report such |
537 | // subsequent skips; they may carry useful information. |
538 | |
539 | for (auto &logger : QTest::loggers->allLoggers()) |
540 | logger->addIncident(type: QAbstractTestLogger::Skip, description: msg, file, line); |
541 | } |
542 | |
543 | void QTestLog::addBenchmarkResults(const QList<QBenchmarkResult> &results) |
544 | { |
545 | for (auto &logger : QTest::loggers->allLoggers()) |
546 | logger->addBenchmarkResults(result: results); |
547 | } |
548 | |
549 | void QTestLog::startLogging() |
550 | { |
551 | elapsedTotalTime.start(); |
552 | elapsedFunctionTime.start(); |
553 | for (auto &logger : QTest::loggers->allLoggers()) |
554 | logger->startLogging(); |
555 | QTest::oldMessageHandler = qInstallMessageHandler(QTest::messageHandler); |
556 | } |
557 | |
558 | void QTestLog::stopLogging() |
559 | { |
560 | qInstallMessageHandler(QTest::oldMessageHandler); |
561 | for (auto &logger : QTest::loggers->allLoggers()) |
562 | logger->stopLogging(); |
563 | |
564 | QTest::loggers->clear(); |
565 | saveCoverageTool(appname: QTestResult::currentAppName(), testfailed: failCount() != 0, installedTestCoverage: QTestLog::installedTestCoverage()); |
566 | } |
567 | |
568 | void QTestLog::addLogger(LogMode mode, const char *filename) |
569 | { |
570 | if (filename && strcmp(s1: filename, s2: "-") == 0) |
571 | filename = nullptr; |
572 | |
573 | QAbstractTestLogger *logger = nullptr; |
574 | switch (mode) { |
575 | case QTestLog::Plain: |
576 | logger = new QPlainTestLogger(filename); |
577 | break; |
578 | case QTestLog::CSV: |
579 | logger = new QCsvBenchmarkLogger(filename); |
580 | break; |
581 | case QTestLog::XML: |
582 | logger = new QXmlTestLogger(QXmlTestLogger::Complete, filename); |
583 | break; |
584 | case QTestLog::LightXML: |
585 | logger = new QXmlTestLogger(QXmlTestLogger::Light, filename); |
586 | break; |
587 | case QTestLog::JUnitXML: |
588 | logger = new QJUnitTestLogger(filename); |
589 | break; |
590 | case QTestLog::TeamCity: |
591 | logger = new QTeamCityLogger(filename); |
592 | break; |
593 | case QTestLog::TAP: |
594 | logger = new QTapTestLogger(filename); |
595 | break; |
596 | #if defined(QT_USE_APPLE_UNIFIED_LOGGING) |
597 | case QTestLog::Apple: |
598 | logger = new QAppleTestLogger; |
599 | break; |
600 | #endif |
601 | #if defined(HAVE_XCTEST) |
602 | case QTestLog::XCTest: |
603 | logger = new QXcodeTestLogger; |
604 | break; |
605 | #endif |
606 | } |
607 | |
608 | QTEST_ASSERT(logger); |
609 | addLogger(logger: std::unique_ptr<QAbstractTestLogger>{ logger }); |
610 | } |
611 | |
612 | /*! |
613 | \internal |
614 | |
615 | Adds a new logger to the set of loggers that will be used |
616 | to report incidents and messages during testing. |
617 | */ |
618 | void QTestLog::addLogger(std::unique_ptr<QAbstractTestLogger> logger) |
619 | { |
620 | QTEST_ASSERT(logger); |
621 | QTest::loggers()->addLogger(logger: std::move(logger)); |
622 | } |
623 | |
624 | bool QTestLog::hasLoggers() |
625 | { |
626 | return !QTest::loggers()->allLoggers().isEmpty(); |
627 | } |
628 | |
629 | /*! |
630 | \internal |
631 | |
632 | Returns true if all loggers support repeated test runs |
633 | */ |
634 | bool QTestLog::isRepeatSupported() |
635 | { |
636 | for (auto &logger : QTest::loggers->allLoggers()) |
637 | if (!logger->isRepeatSupported()) |
638 | return false; |
639 | |
640 | return true; |
641 | } |
642 | |
643 | bool QTestLog::loggerUsingStdout() |
644 | { |
645 | auto loggersCapture = QTest::loggers->allLoggers(); |
646 | return q20::ranges::any_of(loggersCapture.begin(), loggersCapture.end(), [](auto &logger) { |
647 | return logger->isLoggingToStdout(); |
648 | }); |
649 | } |
650 | |
651 | void QTestLog::warn(const char *msg, const char *file, int line) |
652 | { |
653 | QTEST_ASSERT(msg); |
654 | |
655 | for (auto &logger : QTest::loggers->allLoggers()) |
656 | logger->addMessage(type: QAbstractTestLogger::Warn, message: QString::fromUtf8(utf8: msg), file, line); |
657 | } |
658 | |
659 | void QTestLog::info(const char *msg, const char *file, int line) |
660 | { |
661 | QTEST_ASSERT(msg); |
662 | |
663 | for (auto &logger : QTest::loggers->allLoggers()) |
664 | logger->addMessage(type: QAbstractTestLogger::Info, message: QString::fromUtf8(utf8: msg), file, line); |
665 | } |
666 | |
667 | void QTestLog::setVerboseLevel(int level) |
668 | { |
669 | QTest::verbosity = level; |
670 | } |
671 | |
672 | int QTestLog::verboseLevel() |
673 | { |
674 | return QTest::verbosity; |
675 | } |
676 | |
677 | void QTestLog::ignoreMessage(QtMsgType type, const char *msg) |
678 | { |
679 | QTEST_ASSERT(msg); |
680 | |
681 | const QMutexLocker mutexLocker(&QTest::mutex); |
682 | QTest::IgnoreResultList::append(list&: QTest::ignoreResultList, type, patternIn: QString::fromUtf8(utf8: msg)); |
683 | } |
684 | |
685 | #if QT_CONFIG(regularexpression) |
686 | void QTestLog::ignoreMessage(QtMsgType type, const QRegularExpression &expression) |
687 | { |
688 | QTEST_ASSERT(expression.isValid()); |
689 | |
690 | const QMutexLocker mutexLocker(&QTest::mutex); |
691 | QTest::IgnoreResultList::append(list&: QTest::ignoreResultList, type, patternIn: QVariant(expression)); |
692 | } |
693 | #endif // QT_CONFIG(regularexpression) |
694 | |
695 | void QTestLog::failOnWarning() |
696 | { |
697 | QTest::failOnWarningList.push_back(x: {}); |
698 | } |
699 | |
700 | void QTestLog::failOnWarning(const char *msg) |
701 | { |
702 | QTest::failOnWarningList.push_back(x: QString::fromUtf8(utf8: msg)); |
703 | } |
704 | |
705 | #if QT_CONFIG(regularexpression) |
706 | void QTestLog::failOnWarning(const QRegularExpression &expression) |
707 | { |
708 | QTEST_ASSERT(expression.isValid()); |
709 | |
710 | QTest::failOnWarningList.push_back(x: QVariant::fromValue(value: expression)); |
711 | } |
712 | #endif // QT_CONFIG(regularexpression) |
713 | |
714 | void QTestLog::setMaxWarnings(int m) |
715 | { |
716 | QTest::maxWarnings = m <= 0 ? INT_MAX : m + 2; |
717 | } |
718 | |
719 | bool QTestLog::printAvailableTags = false; |
720 | |
721 | void QTestLog::setPrintAvailableTagsMode() |
722 | { |
723 | printAvailableTags = true; |
724 | } |
725 | |
726 | int QTestLog::passCount() |
727 | { |
728 | return QTest::passes; |
729 | } |
730 | |
731 | int QTestLog::failCount() |
732 | { |
733 | return QTest::fails; |
734 | } |
735 | |
736 | int QTestLog::skipCount() |
737 | { |
738 | return QTest::skips; |
739 | } |
740 | |
741 | int QTestLog::blacklistCount() |
742 | { |
743 | return QTest::blacklists; |
744 | } |
745 | |
746 | int QTestLog::totalCount() |
747 | { |
748 | return passCount() + failCount() + skipCount() + blacklistCount(); |
749 | } |
750 | |
751 | void QTestLog::resetCounters() |
752 | { |
753 | QTest::passes = 0; |
754 | QTest::fails = 0; |
755 | QTest::skips = 0; |
756 | } |
757 | |
758 | void QTestLog::setInstalledTestCoverage(bool installed) |
759 | { |
760 | QTest::installedTestCoverage = installed; |
761 | } |
762 | |
763 | bool QTestLog::installedTestCoverage() |
764 | { |
765 | return QTest::installedTestCoverage; |
766 | } |
767 | |
768 | qint64 QTestLog::nsecsTotalTime() |
769 | { |
770 | return elapsedTotalTime.nsecsElapsed(); |
771 | } |
772 | |
773 | qint64 QTestLog::nsecsFunctionTime() |
774 | { |
775 | return elapsedFunctionTime.nsecsElapsed(); |
776 | } |
777 | |
778 | QT_END_NAMESPACE |
779 | |
780 | #include "moc_qtestlog_p.cpp" |
781 |
Definitions
- saveCoverageTool
- elapsedFunctionTime
- elapsedTotalTime
- LoggerRegistry
- addLogger
- clear
- allLoggers
- load
- store
- fails
- passes
- skips
- blacklists
- currentTestState
- IgnoreResultList
- IgnoreResultList
- clearList
- append
- stringsMatch
- matches
- ignoreResultList
- mutex
- failOnWarningList
- loggers
- verbosity
- maxWarnings
- installedTestCoverage
- oldMessageHandler
- handleIgnoredMessage
- handleFailOnWarning
- messageHandler
- enterTestFunction
- enterTestData
- unhandledIgnoreMessages
- leaveTestFunction
- printUnhandledIgnoreMessages
- clearIgnoreMessages
- clearFailOnWarnings
- clearCurrentTestState
- addPass
- addFail
- addXFail
- addXPass
- addBPass
- addBFail
- addBXPass
- addBXFail
- addSkip
- addBenchmarkResults
- startLogging
- stopLogging
- addLogger
- addLogger
- hasLoggers
- isRepeatSupported
- loggerUsingStdout
- warn
- info
- setVerboseLevel
- verboseLevel
- ignoreMessage
- ignoreMessage
- failOnWarning
- failOnWarning
- failOnWarning
- setMaxWarnings
- printAvailableTags
- setPrintAvailableTagsMode
- passCount
- failCount
- skipCount
- blacklistCount
- totalCount
- resetCounters
- setInstalledTestCoverage
- installedTestCoverage
- nsecsTotalTime
Learn Advanced QML with KDAB
Find out more