1// Copyright (C) 2022 The Qt Company Ltd.
2// Copyright (C) 2022 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include <QtTest/qtestcase.h>
6#include <QtTest/private/qtestcase_p.h>
7#include <QtTest/qtestassert.h>
8
9#include <QtCore/qbytearray.h>
10#include <QtCore/qcoreapplication.h>
11#include <QtCore/qdebug.h>
12#include <QtCore/qdir.h>
13#include <QtCore/qdiriterator.h>
14#include <QtCore/qfile.h>
15#include <QtCore/qfileinfo.h>
16#include <QtCore/qfloat16.h>
17#include <QtCore/qlibraryinfo.h>
18#include <QtCore/qlist.h>
19#include <QtCore/qmetaobject.h>
20#include <QtCore/qobject.h>
21#include <QtCore/qstringlist.h>
22#include <QtCore/qtemporarydir.h>
23#include <QtCore/qthread.h>
24#include <QtCore/qvarlengtharray.h>
25#include <QtCore/private/qlocking_p.h>
26#include <QtCore/private/qtools_p.h>
27#include <QtCore/private/qwaitcondition_p.h>
28
29#include <QtCore/qtestsupport_core.h>
30
31#include <QtTest/private/qtestlog_p.h>
32#include <QtTest/private/qtesttable_p.h>
33#include <QtTest/qtestdata.h>
34#include <QtTest/private/qtestresult_p.h>
35#include <QtTest/private/qsignaldumper_p.h>
36#include <QtTest/private/qbenchmark_p.h>
37#if QT_CONFIG(batch_test_support)
38#include <QtTest/private/qtestregistry_p.h>
39#endif // QT_CONFIG(batch_test_support)
40#include <QtTest/private/cycle_p.h>
41#include <QtTest/private/qtestblacklist_p.h>
42#if defined(HAVE_XCTEST)
43#include <QtTest/private/qxctestlogger_p.h>
44#endif
45#if defined Q_OS_MACOS
46#include <QtTest/private/qtestutil_macos_p.h>
47#endif
48
49#if defined(Q_OS_DARWIN)
50#include <QtTest/private/qappletestlogger_p.h>
51#endif
52
53#include <algorithm>
54#include <array>
55#if !defined(Q_OS_INTEGRITY) || __GHS_VERSION_NUMBER > 202014
56# include <charconv>
57#else
58// Broken implementation, causes link failures just by #include'ing!
59# undef __cpp_lib_to_chars // in case <version> was included
60#endif
61#include <chrono>
62#include <cmath>
63#include <limits>
64#include <memory>
65#include <mutex>
66#include <numeric>
67
68#include <stdarg.h>
69#include <stdio.h>
70#include <stdlib.h>
71
72#if defined(Q_OS_LINUX)
73#include <sys/types.h>
74#include <fcntl.h>
75#endif
76
77#ifdef Q_OS_WIN
78# include <iostream>
79# if !defined(Q_CC_MINGW) || (defined(Q_CC_MINGW) && defined(__MINGW64_VERSION_MAJOR))
80# include <crtdbg.h>
81# endif
82#include <qt_windows.h> // for Sleep
83#endif
84#ifdef Q_OS_UNIX
85#include <QtCore/private/qcore_unix_p.h>
86
87#include <errno.h>
88#if __has_include(<paths.h>)
89# include <paths.h>
90#endif
91#include <signal.h>
92#include <time.h>
93#include <sys/mman.h>
94#include <sys/uio.h>
95#include <sys/wait.h>
96#include <unistd.h>
97# if !defined(Q_OS_INTEGRITY)
98# include <sys/resource.h>
99# endif
100# ifndef _PATH_DEFPATH
101# define _PATH_DEFPATH "/usr/bin:/bin"
102# endif
103# ifndef SIGSTKSZ
104# define SIGSTKSZ 0 /* we have code to set the minimum */
105# endif
106# ifndef SA_RESETHAND
107# define SA_RESETHAND 0
108# endif
109#endif
110
111#if defined(Q_OS_MACOS)
112#include <IOKit/pwr_mgt/IOPMLib.h>
113#include <mach/task.h>
114#include <mach/mach_init.h>
115#include <CoreFoundation/CFPreferences.h>
116#endif
117
118#include <vector>
119
120QT_BEGIN_NAMESPACE
121
122using namespace Qt::StringLiterals;
123
124using QtMiscUtils::toHexUpper;
125using QtMiscUtils::fromHex;
126
127namespace {
128enum DebuggerProgram { None, Gdb, Lldb };
129
130#if defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
131static struct iovec IoVec(struct iovec vec)
132{
133 return vec;
134}
135static struct iovec IoVec(const char *str)
136{
137 struct iovec r = {};
138 r.iov_base = const_cast<char *>(str);
139 r.iov_len = strlen(s: str);
140 return r;
141}
142
143template <typename... Args> static ssize_t writeToStderr(Args &&... args)
144{
145 struct iovec vec[] = { IoVec(std::forward<Args>(args))... };
146 return ::writev(STDERR_FILENO, iovec: vec, count: std::size(vec));
147}
148
149// async-signal-safe conversion from int to string
150struct AsyncSafeIntBuffer
151{
152 // digits10 + 1 for all possible digits
153 // +1 for the sign
154 // +1 for the terminating null
155 static constexpr int Digits10 = std::numeric_limits<int>::digits10 + 3;
156 std::array<char, Digits10> array;
157 constexpr AsyncSafeIntBuffer() : array{} {} // initializes array
158 AsyncSafeIntBuffer(Qt::Initialization) {} // leaves array uninitialized
159};
160
161static struct iovec asyncSafeToString(int n, AsyncSafeIntBuffer &&result = Qt::Uninitialized)
162{
163 char *ptr = result.array.data();
164 if (false) {
165#ifdef __cpp_lib_to_chars
166 } else if (auto r = std::to_chars(first: ptr, last: ptr + result.array.size(), value: n, base: 10); r.ec == std::errc{}) {
167 ptr = r.ptr;
168#endif
169 } else {
170 // handle the sign
171 if (n < 0) {
172 *ptr++ = '-';
173 n = -n;
174 }
175
176 // find the highest power of the base that is less than this number
177 static constexpr int StartingDivider = ([]() {
178 int divider = 1;
179 for (int i = 0; i < std::numeric_limits<int>::digits10; ++i)
180 divider *= 10;
181 return divider;
182 }());
183 int divider = StartingDivider;
184 while (divider && n < divider)
185 divider /= 10;
186
187 // now convert to string
188 while (divider > 1) {
189 int quot = n / divider;
190 n = n % divider;
191 divider /= 10;
192 *ptr++ = quot + '0';
193 }
194 *ptr++ = n + '0';
195 }
196
197#ifndef QT_NO_DEBUG
198 // this isn't necessary, it just helps in the debugger
199 *ptr = '\0';
200#endif
201 struct iovec r;
202 r.iov_base = result.array.data();
203 r.iov_len = ptr - result.array.data();
204 return r;
205};
206#elif defined(Q_OS_WIN)
207// Windows doesn't need to be async-safe
208template <typename... Args> static void writeToStderr(Args &&... args)
209{
210 (std::cerr << ... << args);
211}
212
213static std::string asyncSafeToString(int n)
214{
215 return std::to_string(n);
216}
217#endif // defined(Q_OS_UNIX)
218} // unnamed namespace
219
220static bool alreadyDebugging()
221{
222#if defined(Q_OS_LINUX)
223 int fd = open(file: "/proc/self/status", O_RDONLY);
224 if (fd == -1)
225 return false;
226 char buffer[2048];
227 ssize_t size = read(fd: fd, buf: buffer, nbytes: sizeof(buffer) - 1);
228 if (size == -1) {
229 close(fd: fd);
230 return false;
231 }
232 buffer[size] = 0;
233 const char tracerPidToken[] = "\nTracerPid:";
234 char *tracerPid = strstr(haystack: buffer, needle: tracerPidToken);
235 if (!tracerPid) {
236 close(fd: fd);
237 return false;
238 }
239 tracerPid += sizeof(tracerPidToken);
240 long int pid = strtol(nptr: tracerPid, endptr: &tracerPid, base: 10);
241 close(fd: fd);
242 return pid != 0;
243#elif defined(Q_OS_WIN)
244 return IsDebuggerPresent();
245#elif defined(Q_OS_MACOS)
246 // Check if there is an exception handler for the process:
247 mach_msg_type_number_t portCount = 0;
248 exception_mask_t masks[EXC_TYPES_COUNT];
249 mach_port_t ports[EXC_TYPES_COUNT];
250 exception_behavior_t behaviors[EXC_TYPES_COUNT];
251 thread_state_flavor_t flavors[EXC_TYPES_COUNT];
252 exception_mask_t mask = EXC_MASK_ALL & ~(EXC_MASK_RESOURCE | EXC_MASK_GUARD);
253 kern_return_t result = task_get_exception_ports(mach_task_self(), mask, masks, &portCount,
254 ports, behaviors, flavors);
255 if (result == KERN_SUCCESS) {
256 for (mach_msg_type_number_t portIndex = 0; portIndex < portCount; ++portIndex) {
257 if (MACH_PORT_VALID(ports[portIndex])) {
258 return true;
259 }
260 }
261 }
262 return false;
263#else
264 // TODO
265 return false;
266#endif
267}
268
269static bool hasSystemCrashReporter()
270{
271#if defined(Q_OS_MACOS)
272 return QTestPrivate::macCrashReporterWillShowDialog();
273#else
274 return false;
275#endif
276}
277
278static void maybeDisableCoreDump()
279{
280#ifdef RLIMIT_CORE
281 bool ok = false;
282 const int disableCoreDump = qEnvironmentVariableIntValue(varName: "QTEST_DISABLE_CORE_DUMP", ok: &ok);
283 if (ok && disableCoreDump) {
284 struct rlimit limit;
285 limit.rlim_cur = 0;
286 limit.rlim_max = 0;
287 if (setrlimit(RLIMIT_CORE, rlimits: &limit) != 0)
288 qWarning(msg: "Failed to disable core dumps: %d", errno);
289 }
290#endif
291}
292
293static DebuggerProgram debugger = None;
294static void prepareStackTrace()
295{
296
297 bool ok = false;
298 const int disableStackDump = qEnvironmentVariableIntValue(varName: "QTEST_DISABLE_STACK_DUMP", ok: &ok);
299 if (ok && disableStackDump)
300 return;
301
302 if (hasSystemCrashReporter())
303 return;
304
305#if defined(Q_OS_MACOS)
306 #define CSR_ALLOW_UNRESTRICTED_FS (1 << 1)
307 std::optional<uint32_t> sipConfiguration = qt_mac_sipConfiguration();
308 if (!sipConfiguration || !(*sipConfiguration & CSR_ALLOW_UNRESTRICTED_FS))
309 return; // LLDB will fail to provide a valid stack trace
310#endif
311
312#ifdef Q_OS_UNIX
313 // like QStandardPaths::findExecutable(), but simpler
314 auto hasExecutable = [](const char *execname) {
315 std::string candidate;
316 std::string path;
317 if (const char *p = getenv(name: "PATH"); p && *p)
318 path = p;
319 else
320 path = _PATH_DEFPATH;
321 for (const char *p = std::strtok(s: &path[0], delim: ":'"); p; p = std::strtok(s: nullptr, delim: ":")) {
322 candidate = p;
323 candidate += '/';
324 candidate += execname;
325 if (QT_ACCESS(name: candidate.data(), X_OK) == 0)
326 return true;
327 }
328 return false;
329 };
330
331 static constexpr DebuggerProgram debuggerSearchOrder[] = {
332# if defined(Q_OS_QNX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
333 Gdb, Lldb
334# else
335 Lldb, Gdb
336# endif
337 };
338 for (DebuggerProgram candidate : debuggerSearchOrder) {
339 switch (candidate) {
340 case None:
341 Q_UNREACHABLE();
342 break;
343 case Gdb:
344 if (hasExecutable("gdb")) {
345 debugger = Gdb;
346 return;
347 }
348 break;
349 case Lldb:
350 if (hasExecutable("lldb")) {
351 debugger = Lldb;
352 return;
353 }
354 break;
355 }
356 }
357#endif // Q_OS_UNIX
358}
359
360#if !defined(Q_OS_WASM) || QT_CONFIG(thread)
361static void printTestRunTime()
362{
363 const int msecsFunctionTime = qRound(d: QTestLog::msecsFunctionTime());
364 const int msecsTotalTime = qRound(d: QTestLog::msecsTotalTime());
365 const char *const name = QTest::currentTestFunction();
366 writeToStderr(args: "\n ", args: name ? name : "[Non-test]",
367 args: " function time: ", args: asyncSafeToString(n: msecsFunctionTime),
368 args: "ms, total time: ", args: asyncSafeToString(n: msecsTotalTime), args: "ms\n");
369}
370
371static void generateStackTrace()
372{
373 if (debugger == None || alreadyDebugging())
374 return;
375
376# if defined(Q_OS_UNIX) && !defined(Q_OS_WASM) && !defined(Q_OS_INTEGRITY)
377 writeToStderr(args: "\n=== Stack trace ===\n");
378
379 // execlp() requires null-termination, so call the default constructor
380 AsyncSafeIntBuffer pidbuffer;
381 asyncSafeToString(n: getpid(), result: std::move(pidbuffer));
382
383 // Note: POSIX.1-2001 still has fork() in the list of async-safe functions,
384 // but in a future edition, it might be removed. It would be safer to wake
385 // up a babysitter thread to launch the debugger.
386 pid_t pid = fork();
387 if (pid == 0) {
388 // child process
389 (void) dup2(STDERR_FILENO, STDOUT_FILENO); // redirect stdout to stderr
390
391 switch (debugger) {
392 case None:
393 Q_UNREACHABLE();
394 break;
395 case Gdb:
396 execlp(file: "gdb", arg: "gdb", "--nx", "--batch", "-ex", "thread apply all bt",
397 "--pid", pidbuffer.array.data(), nullptr);
398 break;
399 case Lldb:
400 execlp(file: "lldb", arg: "lldb", "--no-lldbinit", "--batch", "-o", "bt all",
401 "--attach-pid", pidbuffer.array.data(), nullptr);
402 break;
403 }
404 _exit(status: 1);
405 } else if (pid < 0) {
406 writeToStderr(args: "Failed to start debugger.\n");
407 } else {
408 int ret;
409 EINTR_LOOP(ret, waitpid(pid, nullptr, 0));
410 }
411
412 writeToStderr(args: "=== End of stack trace ===\n");
413# endif // Q_OS_UNIX && !Q_OS_WASM
414}
415#endif // !defined(Q_OS_WASM) || QT_CONFIG(thread)
416
417static bool installCoverageTool(const char * appname, const char * testname)
418{
419#if defined(__COVERAGESCANNER__) && !QT_CONFIG(testlib_selfcover)
420 if (!qEnvironmentVariableIsEmpty("QT_TESTCOCOON_ACTIVE"))
421 return false;
422 // Set environment variable QT_TESTCOCOON_ACTIVE to prevent an eventual subtest from
423 // being considered as a stand-alone test regarding the coverage analysis.
424 qputenv("QT_TESTCOCOON_ACTIVE", "1");
425
426 // Install Coverage Tool
427 __coveragescanner_install(appname);
428 __coveragescanner_testname(testname);
429 __coveragescanner_clear();
430 return true;
431#else
432 Q_UNUSED(appname);
433 Q_UNUSED(testname);
434 return false;
435#endif
436}
437
438static bool isValidSlot(const QMetaMethod &sl)
439{
440 if (sl.access() != QMetaMethod::Private || sl.parameterCount() != 0
441 || sl.returnType() != QMetaType::Void || sl.methodType() != QMetaMethod::Slot)
442 return false;
443 const QByteArray name = sl.name();
444 return !(name.isEmpty() || name.endsWith(bv: "_data")
445 || name == "initTestCase" || name == "cleanupTestCase"
446 || name == "init" || name == "cleanup");
447}
448
449namespace QTestPrivate
450{
451 Q_TESTLIB_EXPORT Qt::MouseButtons qtestMouseButtons = Qt::NoButton;
452}
453
454namespace QTest
455{
456
457QString Internal::formatTryTimeoutDebugMessage(q_no_char8_t::QUtf8StringView expr, int timeout, int actual)
458{
459 return "QTestLib: This test case check (\"%1\") failed because the requested timeout (%2 ms) "
460 "was too short, %3 ms would have been sufficient this time."_L1
461 // ### Qt 7: remove the toString() (or earlier, when arg() can handle QUtf8StringView), passing the view directly
462 .arg(args: expr.toString(), args: QString::number(timeout), args: QString::number(actual));
463}
464
465extern Q_TESTLIB_EXPORT int lastMouseTimestamp;
466
467class WatchDog;
468
469static QObject *currentTestObject = nullptr;
470static QString mainSourcePath;
471static bool inTestFunction = false;
472
473#if defined(Q_OS_MACOS)
474static IOPMAssertionID macPowerSavingDisabled = 0;
475#endif
476
477class TestMethods {
478public:
479 Q_DISABLE_COPY_MOVE(TestMethods)
480
481 using MetaMethods = std::vector<QMetaMethod>;
482
483 explicit TestMethods(const QObject *o, MetaMethods m = {});
484
485 void invokeTests(QObject *testObject) const;
486
487 static QMetaMethod findMethod(const QObject *obj, const char *signature);
488
489private:
490 bool invokeTest(int index, QLatin1StringView tag, WatchDog *watchDog) const;
491 void invokeTestOnData(int index) const;
492
493 QMetaMethod m_initTestCaseMethod; // might not exist, check isValid().
494 QMetaMethod m_initTestCaseDataMethod;
495 QMetaMethod m_cleanupTestCaseMethod;
496 QMetaMethod m_initMethod;
497 QMetaMethod m_cleanupMethod;
498
499 MetaMethods m_methods;
500};
501
502TestMethods::TestMethods(const QObject *o, MetaMethods m)
503 : m_initTestCaseMethod(TestMethods::findMethod(obj: o, signature: "initTestCase()"))
504 , m_initTestCaseDataMethod(TestMethods::findMethod(obj: o, signature: "initTestCase_data()"))
505 , m_cleanupTestCaseMethod(TestMethods::findMethod(obj: o, signature: "cleanupTestCase()"))
506 , m_initMethod(TestMethods::findMethod(obj: o, signature: "init()"))
507 , m_cleanupMethod(TestMethods::findMethod(obj: o, signature: "cleanup()"))
508 , m_methods(std::move(m))
509{
510 if (m_methods.empty()) {
511 const QMetaObject *metaObject = o->metaObject();
512 const int count = metaObject->methodCount();
513 m_methods.reserve(n: count);
514 for (int i = 0; i < count; ++i) {
515 const QMetaMethod me = metaObject->method(index: i);
516 if (isValidSlot(sl: me))
517 m_methods.push_back(x: me);
518 }
519 }
520}
521
522QMetaMethod TestMethods::findMethod(const QObject *obj, const char *signature)
523{
524 const QMetaObject *metaObject = obj->metaObject();
525 const int funcIndex = metaObject->indexOfMethod(method: signature);
526 return funcIndex >= 0 ? metaObject->method(index: funcIndex) : QMetaMethod();
527}
528
529static int keyDelay = -1;
530static int mouseDelay = -1;
531static int eventDelay = -1;
532#if QT_CONFIG(thread)
533static int timeout = -1;
534#endif
535static bool noCrashHandler = false;
536
537/*! \internal
538 Invoke a method of the object without generating warning if the method does not exist
539*/
540static void invokeMethod(QObject *obj, const char *methodName)
541{
542 const QMetaObject *metaObject = obj->metaObject();
543 int funcIndex = metaObject->indexOfMethod(method: methodName);
544 if (funcIndex >= 0) {
545 QMetaMethod method = metaObject->method(index: funcIndex);
546 method.invoke(obj, c: Qt::DirectConnection);
547 }
548}
549
550int defaultEventDelay()
551{
552 if (eventDelay == -1) {
553 const QByteArray env = qgetenv(varName: "QTEST_EVENT_DELAY");
554 if (!env.isEmpty())
555 eventDelay = atoi(nptr: env.constData());
556 else
557 eventDelay = 0;
558 }
559 return eventDelay;
560}
561
562int Q_TESTLIB_EXPORT defaultMouseDelay()
563{
564 if (mouseDelay == -1) {
565 const QByteArray env = qgetenv(varName: "QTEST_MOUSEEVENT_DELAY");
566 if (!env.isEmpty())
567 mouseDelay = atoi(nptr: env.constData());
568 else
569 mouseDelay = defaultEventDelay();
570 }
571 return mouseDelay;
572}
573
574int Q_TESTLIB_EXPORT defaultKeyDelay()
575{
576 if (keyDelay == -1) {
577 const QByteArray env = qgetenv(varName: "QTEST_KEYEVENT_DELAY");
578 if (!env.isEmpty())
579 keyDelay = atoi(nptr: env.constData());
580 else
581 keyDelay = defaultEventDelay();
582 }
583 return keyDelay;
584}
585#if QT_CONFIG(thread)
586static std::chrono::milliseconds defaultTimeout()
587{
588 if (timeout == -1) {
589 bool ok = false;
590 timeout = qEnvironmentVariableIntValue(varName: "QTEST_FUNCTION_TIMEOUT", ok: &ok);
591
592 if (!ok || timeout <= 0)
593 timeout = 5*60*1000;
594 }
595 return std::chrono::milliseconds{timeout};
596}
597#endif
598
599Q_TESTLIB_EXPORT bool printAvailableFunctions = false;
600Q_TESTLIB_EXPORT QStringList testFunctions;
601Q_TESTLIB_EXPORT QStringList testTags;
602
603static bool qPrintTestSlots(FILE *stream, const char *filter = nullptr, const char *preamble = "")
604{
605 const auto matches = [filter](const QByteArray &s) {
606 return !filter || QLatin1StringView(s).contains(s: QLatin1StringView(filter),
607 cs: Qt::CaseInsensitive);
608 };
609 bool matched = false;
610 for (int i = 0; i < QTest::currentTestObject->metaObject()->methodCount(); ++i) {
611 QMetaMethod sl = QTest::currentTestObject->metaObject()->method(index: i);
612 if (isValidSlot(sl)) {
613 const QByteArray signature = sl.methodSignature();
614 if (matches(signature)) {
615 fprintf(stream: stream, format: "%s%s\n", preamble, signature.constData());
616 preamble = "";
617 matched = true;
618 }
619 }
620 }
621 return matched;
622}
623
624static void qPrintDataTags(FILE *stream)
625{
626 // Avoid invoking the actual test functions, and also avoid printing irrelevant output:
627 QTestLog::setPrintAvailableTagsMode();
628
629 // Get global data tags:
630 QTestTable::globalTestTable();
631 invokeMethod(obj: QTest::currentTestObject, methodName: "initTestCase_data()");
632 const QTestTable *gTable = QTestTable::globalTestTable();
633
634 const QMetaObject *currTestMetaObj = QTest::currentTestObject->metaObject();
635
636 // Process test functions:
637 for (int i = 0; i < currTestMetaObj->methodCount(); ++i) {
638 QMetaMethod tf = currTestMetaObj->method(index: i);
639
640 if (isValidSlot(sl: tf)) {
641
642 // Retrieve local tags:
643 QStringList localTags;
644 QTestTable table;
645 char *slot = qstrdup(tf.methodSignature().constData());
646 slot[strlen(s: slot) - 2] = '\0';
647 QByteArray member;
648 member.resize(size: qstrlen(str: slot) + qstrlen(str: "_data()") + 1);
649 qsnprintf(str: member.data(), n: member.size(), fmt: "%s_data()", slot);
650 invokeMethod(obj: QTest::currentTestObject, methodName: member.constData());
651 const int dataCount = table.dataCount();
652 localTags.reserve(asize: dataCount);
653 for (int j = 0; j < dataCount; ++j)
654 localTags << QLatin1StringView(table.testData(index: j)->dataTag());
655
656 // Print all tag combinations:
657 if (gTable->dataCount() == 0) {
658 if (localTags.size() == 0) {
659 // No tags at all, so just print the test function:
660 fprintf(stream: stream, format: "%s %s\n", currTestMetaObj->className(), slot);
661 } else {
662 // Only local tags, so print each of them:
663 for (int k = 0; k < localTags.size(); ++k)
664 fprintf(
665 stream: stream, format: "%s %s %s\n",
666 currTestMetaObj->className(), slot, localTags.at(i: k).toLatin1().data());
667 }
668 } else {
669 for (int j = 0; j < gTable->dataCount(); ++j) {
670 if (localTags.size() == 0) {
671 // Only global tags, so print the current one:
672 fprintf(
673 stream: stream, format: "%s %s __global__ %s\n",
674 currTestMetaObj->className(), slot, gTable->testData(index: j)->dataTag());
675 } else {
676 // Local and global tags, so print each of the local ones and
677 // the current global one:
678 for (int k = 0; k < localTags.size(); ++k)
679 fprintf(
680 stream: stream, format: "%s %s %s __global__ %s\n", currTestMetaObj->className(), slot,
681 localTags.at(i: k).toLatin1().data(), gTable->testData(index: j)->dataTag());
682 }
683 }
684 }
685
686 delete[] slot;
687 }
688 }
689}
690
691static int qToInt(const char *str)
692{
693 char *pEnd;
694 int l = static_cast<int>(strtol(nptr: str, endptr: &pEnd, base: 10));
695 if (*pEnd != 0) {
696 fprintf(stderr, format: "Invalid numeric parameter: '%s'\n", str);
697 exit(status: 1);
698 }
699 return l;
700}
701
702Q_TESTLIB_EXPORT void qtest_qParseArgs(int argc, const char *const argv[], bool qml)
703{
704 int logFormat = -1; // Not set
705 const char *logFilename = nullptr;
706
707 QTest::testFunctions.clear();
708 QTest::testTags.clear();
709
710#if defined(Q_OS_DARWIN) && defined(HAVE_XCTEST)
711 if (QXcodeTestLogger::canLogTestProgress())
712 logFormat = QTestLog::XCTest;
713#endif
714
715 const char *testOptions =
716 " New-style logging options:\n"
717 " -o filename,format : Output results to file in the specified format\n"
718 " Use - to output to stdout\n"
719 " Valid formats are:\n"
720 " txt : Plain text\n"
721 " csv : CSV format (suitable for benchmarks)\n"
722 " junitxml : XML JUnit document\n"
723 " xml : XML document\n"
724 " lightxml : A stream of XML tags\n"
725 " teamcity : TeamCity format\n"
726 " tap : Test Anything Protocol\n"
727 "\n"
728 " *** Multiple loggers can be specified, but at most one can log to stdout.\n"
729 "\n"
730 " Old-style logging options:\n"
731 " -o filename : Write the output into file\n"
732 " -txt : Output results in Plain Text\n"
733 " -csv : Output results in a CSV format (suitable for benchmarks)\n"
734 " -junitxml : Output results as XML JUnit document\n"
735 " -xml : Output results as XML document\n"
736 " -lightxml : Output results as stream of XML tags\n"
737 " -teamcity : Output results in TeamCity format\n"
738 " -tap : Output results in Test Anything Protocol format\n"
739 "\n"
740 " *** If no output file is specified, stdout is assumed.\n"
741 " *** If no output format is specified, -txt is assumed.\n"
742 "\n"
743 " Test log detail options:\n"
744 " -silent : Log failures and fatal errors only\n"
745 " -v1 : Log the start of each testfunction\n"
746 " -v2 : Log each QVERIFY/QCOMPARE/QTEST (implies -v1)\n"
747 " -vs : Log every signal emission and resulting slot invocations\n"
748 "\n"
749 " *** The -silent and -v1 options only affect plain text output.\n"
750 "\n"
751 " Testing options:\n"
752 " -functions : Returns a list of current testfunctions\n"
753 " -datatags : Returns a list of current data tags.\n"
754 " A global data tag is preceded by ' __global__ '.\n"
755 " -eventdelay ms : Set default delay for mouse and keyboard simulation to ms milliseconds\n"
756 " -keydelay ms : Set default delay for keyboard simulation to ms milliseconds\n"
757 " -mousedelay ms : Set default delay for mouse simulation to ms milliseconds\n"
758 " -maxwarnings n : Sets the maximum amount of messages to output.\n"
759 " 0 means unlimited, default: 2000\n"
760 " -nocrashhandler : Disables the crash handler. Useful for debugging crashes.\n"
761 "\n"
762 " Benchmarking options:\n"
763#if QT_CONFIG(valgrind)
764 " -callgrind : Use callgrind to time benchmarks\n"
765#endif
766#ifdef QTESTLIB_USE_PERF_EVENTS
767 " -perf : Use Linux perf events to time benchmarks\n"
768 " -perfcounter name : Use the counter named 'name'\n"
769 " -perfcounterlist : Lists the counters available\n"
770#endif
771#ifdef HAVE_TICK_COUNTER
772 " -tickcounter : Use CPU tick counters to time benchmarks\n"
773#endif
774 " -eventcounter : Counts events received during benchmarks\n"
775 " -minimumvalue n : Sets the minimum acceptable measurement value\n"
776 " -minimumtotal n : Sets the minimum acceptable total for repeated executions of a test function\n"
777 " -iterations n : Sets the number of accumulation iterations.\n"
778 " -median n : Sets the number of median iterations.\n"
779 " -vb : Print out verbose benchmarking information.\n";
780
781 for (int i = 1; i < argc; ++i) {
782 if (strcmp(s1: argv[i], s2: "-help") == 0 || strcmp(s1: argv[i], s2: "--help") == 0
783 || strcmp(s1: argv[i], s2: "/?") == 0) {
784 printf(format: " Usage: %s [options] [testfunction[:testdata]]...\n"
785 " By default, all testfunctions will be run.\n\n"
786 "%s", argv[0], testOptions);
787
788 if (qml) {
789 printf(format: "\n"
790 " QmlTest options:\n"
791 " -import dir : Specify an import directory.\n"
792 " -plugins dir : Specify a directory where to search for plugins.\n"
793 " -input dir/file : Specify the root directory for test cases or a single test case file.\n"
794 " -translation file : Specify the translation file.\n"
795 " -file-selector dir : Specify a file selector for the QML engine.\n"
796 );
797 }
798
799 printf(format: "\n"
800 " -help : This help\n");
801 exit(status: 0);
802 } else if (strcmp(s1: argv[i], s2: "-functions") == 0) {
803 if (qml) {
804 QTest::printAvailableFunctions = true;
805 } else {
806 qPrintTestSlots(stdout);
807 exit(status: 0);
808 }
809 } else if (strcmp(s1: argv[i], s2: "-datatags") == 0) {
810 if (!qml) {
811 qPrintDataTags(stdout);
812 exit(status: 0);
813 }
814 } else if (strcmp(s1: argv[i], s2: "-txt") == 0) {
815 logFormat = QTestLog::Plain;
816 } else if (strcmp(s1: argv[i], s2: "-csv") == 0) {
817 logFormat = QTestLog::CSV;
818 } else if (strcmp(s1: argv[i], s2: "-junitxml") == 0) {
819 logFormat = QTestLog::JUnitXML;
820 } else if (strcmp(s1: argv[i], s2: "-xunitxml") == 0) {
821 fprintf(stderr, format: "WARNING: xunitxml is deprecated. Please use junitxml.\n");
822 logFormat = QTestLog::JUnitXML;
823 } else if (strcmp(s1: argv[i], s2: "-xml") == 0) {
824 logFormat = QTestLog::XML;
825 } else if (strcmp(s1: argv[i], s2: "-lightxml") == 0) {
826 logFormat = QTestLog::LightXML;
827 } else if (strcmp(s1: argv[i], s2: "-teamcity") == 0) {
828 logFormat = QTestLog::TeamCity;
829 } else if (strcmp(s1: argv[i], s2: "-tap") == 0) {
830 logFormat = QTestLog::TAP;
831 } else if (strcmp(s1: argv[i], s2: "-silent") == 0) {
832 QTestLog::setVerboseLevel(-1);
833 } else if (strcmp(s1: argv[i], s2: "-v1") == 0) {
834 QTestLog::setVerboseLevel(1);
835 } else if (strcmp(s1: argv[i], s2: "-v2") == 0) {
836 QTestLog::setVerboseLevel(2);
837 } else if (strcmp(s1: argv[i], s2: "-vs") == 0) {
838 QSignalDumper::setEnabled(true);
839 } else if (strcmp(s1: argv[i], s2: "-o") == 0) {
840 if (i + 1 >= argc) {
841 fprintf(stderr, format: "-o needs an extra parameter specifying the filename and optional format\n");
842 exit(status: 1);
843 }
844 ++i;
845 // Do we have the old or new style -o option?
846 char *filename = new char[strlen(s: argv[i])+1];
847 char *format = new char[strlen(s: argv[i])+1];
848 if (sscanf(s: argv[i], format: "%[^,],%s", filename, format) == 1) {
849 // Old-style
850 logFilename = argv[i];
851 } else {
852 // New-style
853 if (strcmp(s1: format, s2: "txt") == 0)
854 logFormat = QTestLog::Plain;
855 else if (strcmp(s1: format, s2: "csv") == 0)
856 logFormat = QTestLog::CSV;
857 else if (strcmp(s1: format, s2: "lightxml") == 0)
858 logFormat = QTestLog::LightXML;
859 else if (strcmp(s1: format, s2: "xml") == 0)
860 logFormat = QTestLog::XML;
861 else if (strcmp(s1: format, s2: "junitxml") == 0)
862 logFormat = QTestLog::JUnitXML;
863 else if (strcmp(s1: format, s2: "xunitxml") == 0) {
864 fprintf(stderr, format: "WARNING: xunitxml is deprecated. Please use junitxml.\n");
865 logFormat = QTestLog::JUnitXML;
866 } else if (strcmp(s1: format, s2: "teamcity") == 0)
867 logFormat = QTestLog::TeamCity;
868 else if (strcmp(s1: format, s2: "tap") == 0)
869 logFormat = QTestLog::TAP;
870 else {
871 fprintf(stderr, format: "output format must be one of txt, csv, lightxml, xml, tap, teamcity or junitxml\n");
872 exit(status: 1);
873 }
874 if (strcmp(s1: filename, s2: "-") == 0 && QTestLog::loggerUsingStdout()) {
875 fprintf(stderr, format: "only one logger can log to stdout\n");
876 exit(status: 1);
877 }
878 QTestLog::addLogger(mode: QTestLog::LogMode(logFormat), filename);
879 }
880 delete [] filename;
881 delete [] format;
882 } else if (strcmp(s1: argv[i], s2: "-eventdelay") == 0) {
883 if (i + 1 >= argc) {
884 fprintf(stderr, format: "-eventdelay needs an extra parameter to indicate the delay(ms)\n");
885 exit(status: 1);
886 } else {
887 QTest::eventDelay = qToInt(str: argv[++i]);
888 }
889 } else if (strcmp(s1: argv[i], s2: "-keydelay") == 0) {
890 if (i + 1 >= argc) {
891 fprintf(stderr, format: "-keydelay needs an extra parameter to indicate the delay(ms)\n");
892 exit(status: 1);
893 } else {
894 QTest::keyDelay = qToInt(str: argv[++i]);
895 }
896 } else if (strcmp(s1: argv[i], s2: "-mousedelay") == 0) {
897 if (i + 1 >= argc) {
898 fprintf(stderr, format: "-mousedelay needs an extra parameter to indicate the delay(ms)\n");
899 exit(status: 1);
900 } else {
901 QTest::mouseDelay = qToInt(str: argv[++i]);
902 }
903 } else if (strcmp(s1: argv[i], s2: "-maxwarnings") == 0) {
904 if (i + 1 >= argc) {
905 fprintf(stderr, format: "-maxwarnings needs an extra parameter with the amount of warnings\n");
906 exit(status: 1);
907 } else {
908 QTestLog::setMaxWarnings(qToInt(str: argv[++i]));
909 }
910 } else if (strcmp(s1: argv[i], s2: "-nocrashhandler") == 0) {
911 QTest::noCrashHandler = true;
912#if QT_CONFIG(valgrind)
913 } else if (strcmp(s1: argv[i], s2: "-callgrind") == 0) {
914 if (!QBenchmarkValgrindUtils::haveValgrind()) {
915 fprintf(stderr,
916 format: "WARNING: Valgrind not found or too old. "
917 "Make sure it is installed and in your path. "
918 "Using the walltime measurer.\n");
919 } else if (QFileInfo(QDir::currentPath()).isWritable()) {
920 QBenchmarkGlobalData::current->setMode(
921 QBenchmarkGlobalData::CallgrindParentProcess);
922 } else {
923 fprintf(stderr,
924 format: "WARNING: Current directory not writable. "
925 "Using the walltime measurer.\n");
926 }
927 } else if (strcmp(s1: argv[i], s2: "-callgrindchild") == 0) { // "private" option
928 QBenchmarkGlobalData::current->setMode(QBenchmarkGlobalData::CallgrindChildProcess);
929 QBenchmarkGlobalData::current->callgrindOutFileBase =
930 QBenchmarkValgrindUtils::outFileBase();
931#endif
932#ifdef QTESTLIB_USE_PERF_EVENTS
933 } else if (strcmp(s1: argv[i], s2: "-perf") == 0) {
934 if (QBenchmarkPerfEventsMeasurer::isAvailable()) {
935 // perf available
936 QBenchmarkGlobalData::current->setMode(QBenchmarkGlobalData::PerfCounter);
937 } else {
938 fprintf(stderr, format: "WARNING: Linux perf events not available. Using the walltime measurer.\n");
939 }
940 } else if (strcmp(s1: argv[i], s2: "-perfcounter") == 0) {
941 if (i + 1 >= argc) {
942 fprintf(stderr, format: "-perfcounter needs an extra parameter with the name of the counter\n");
943 exit(status: 1);
944 } else {
945 QBenchmarkPerfEventsMeasurer::setCounter(argv[++i]);
946 }
947 } else if (strcmp(s1: argv[i], s2: "-perfcounterlist") == 0) {
948 QBenchmarkPerfEventsMeasurer::listCounters();
949 exit(status: 0);
950#endif
951#ifdef HAVE_TICK_COUNTER
952 } else if (strcmp(s1: argv[i], s2: "-tickcounter") == 0) {
953 QBenchmarkGlobalData::current->setMode(QBenchmarkGlobalData::TickCounter);
954#endif
955 } else if (strcmp(s1: argv[i], s2: "-eventcounter") == 0) {
956 QBenchmarkGlobalData::current->setMode(QBenchmarkGlobalData::EventCounter);
957 } else if (strcmp(s1: argv[i], s2: "-minimumvalue") == 0) {
958 if (i + 1 >= argc) {
959 fprintf(stderr, format: "-minimumvalue needs an extra parameter to indicate the minimum time(ms)\n");
960 exit(status: 1);
961 } else {
962 QBenchmarkGlobalData::current->walltimeMinimum = qToInt(str: argv[++i]);
963 }
964 } else if (strcmp(s1: argv[i], s2: "-minimumtotal") == 0) {
965 if (i + 1 >= argc) {
966 fprintf(stderr, format: "-minimumtotal needs an extra parameter to indicate the minimum total measurement\n");
967 exit(status: 1);
968 } else {
969 QBenchmarkGlobalData::current->minimumTotal = qToInt(str: argv[++i]);
970 }
971 } else if (strcmp(s1: argv[i], s2: "-iterations") == 0) {
972 if (i + 1 >= argc) {
973 fprintf(stderr, format: "-iterations needs an extra parameter to indicate the number of iterations\n");
974 exit(status: 1);
975 } else {
976 QBenchmarkGlobalData::current->iterationCount = qToInt(str: argv[++i]);
977 }
978 } else if (strcmp(s1: argv[i], s2: "-median") == 0) {
979 if (i + 1 >= argc) {
980 fprintf(stderr, format: "-median needs an extra parameter to indicate the number of median iterations\n");
981 exit(status: 1);
982 } else {
983 QBenchmarkGlobalData::current->medianIterationCount = qToInt(str: argv[++i]);
984 }
985
986 } else if (strcmp(s1: argv[i], s2: "-vb") == 0) {
987 QBenchmarkGlobalData::current->verboseOutput = true;
988#if defined(Q_OS_DARWIN)
989 } else if (strncmp(argv[i], "-Apple", 6) == 0) {
990 i += 1; // Skip Apple-specific user preferences
991 continue;
992# if defined(HAVE_XCTEST)
993 } else if (int skip = QXcodeTestLogger::parseCommandLineArgument(argv[i])) {
994 i += (skip - 1); // Eating argv[i] with a continue counts towards skips
995 continue;
996# endif
997#endif
998 } else if (argv[i][0] == '-') {
999 fprintf(stderr, format: "Unknown option: '%s'\n\n%s", argv[i], testOptions);
1000 if (qml) {
1001 fprintf(stderr, format: "\nqmltest related options:\n"
1002 " -import : Specify an import directory.\n"
1003 " -plugins : Specify a directory where to search for plugins.\n"
1004 " -input : Specify the root directory for test cases.\n"
1005 );
1006 }
1007
1008 fprintf(stderr, format: "\n"
1009 " -help : This help\n");
1010 exit(status: 1);
1011 } else {
1012 // We can't check the availability of test functions until
1013 // we load the QML files. So just store the data for now.
1014 int colon = -1;
1015 int offset;
1016 for (offset = 0; argv[i][offset]; ++offset) {
1017 if (argv[i][offset] == ':') {
1018 if (argv[i][offset + 1] == ':') {
1019 // "::" is used as a test name separator.
1020 // e.g. "ClickTests::test_click:row1".
1021 ++offset;
1022 } else {
1023 colon = offset;
1024 break;
1025 }
1026 }
1027 }
1028 if (colon == -1) {
1029 QTest::testFunctions += QString::fromLatin1(ba: argv[i]);
1030 QTest::testTags += QString();
1031 } else {
1032 QTest::testFunctions +=
1033 QString::fromLatin1(str: argv[i], size: colon);
1034 QTest::testTags +=
1035 QString::fromLatin1(ba: argv[i] + colon + 1);
1036 }
1037 }
1038 }
1039
1040 bool installedTestCoverage = installCoverageTool(appname: QTestResult::currentAppName(), testname: QTestResult::currentTestObjectName());
1041 QTestLog::setInstalledTestCoverage(installedTestCoverage);
1042
1043 // If no loggers were created by the long version of the -o command-line
1044 // option, but a logger was requested via the old-style option, add it.
1045 const bool explicitLoggerRequested = logFormat != -1;
1046 if (!QTestLog::hasLoggers() && explicitLoggerRequested)
1047 QTestLog::addLogger(mode: QTestLog::LogMode(logFormat), filename: logFilename);
1048
1049 bool addFallbackLogger = !explicitLoggerRequested;
1050
1051#if defined(QT_USE_APPLE_UNIFIED_LOGGING)
1052 // Any explicitly requested loggers will be added by now, so we can check if they use stdout
1053 const bool safeToAddAppleLogger = !AppleUnifiedLogger::preventsStderrLogging() || !QTestLog::loggerUsingStdout();
1054 if (safeToAddAppleLogger && QAppleTestLogger::debugLoggingEnabled()) {
1055 QTestLog::addLogger(QTestLog::Apple, nullptr);
1056 if (AppleUnifiedLogger::preventsStderrLogging() && !logFilename)
1057 addFallbackLogger = false; // Prevent plain test logger fallback below
1058 }
1059#endif
1060
1061 if (addFallbackLogger)
1062 QTestLog::addLogger(mode: QTestLog::Plain, filename: logFilename);
1063}
1064
1065// Temporary, backwards compatibility, until qtdeclarative's use of it is converted
1066Q_TESTLIB_EXPORT void qtest_qParseArgs(int argc, char *argv[], bool qml) {
1067 qtest_qParseArgs(argc, argv: const_cast<const char *const *>(argv), qml);
1068}
1069
1070static QList<QBenchmarkResult> qMedian(const QList<QList<QBenchmarkResult>> &container)
1071{
1072 const int count = container.size();
1073 if (count == 0)
1074 return {};
1075
1076 if (count == 1)
1077 return container.front();
1078
1079 QList<QList<QBenchmarkResult>> containerCopy = container;
1080 std::sort(first: containerCopy.begin(), last: containerCopy.end(),
1081 comp: [](const QList<QBenchmarkResult> &a, const QList<QBenchmarkResult> &b) {
1082 return a.first() < b.first();
1083 });
1084
1085 const int middle = count / 2;
1086
1087 // ### handle even-sized containers here by doing an arithmetic mean of the two middle items.
1088 return containerCopy.at(i: middle);
1089}
1090
1091struct QTestDataSetter
1092{
1093 QTestDataSetter(QTestData *data)
1094 {
1095 QTestResult::setCurrentTestData(data);
1096 }
1097 ~QTestDataSetter()
1098 {
1099 QTestResult::setCurrentTestData(nullptr);
1100 }
1101};
1102
1103void TestMethods::invokeTestOnData(int index) const
1104{
1105 /* Benchmarking: for each median iteration*/
1106
1107 bool isBenchmark = false;
1108 int i = (QBenchmarkGlobalData::current->measurer->needsWarmupIteration()) ? -1 : 0;
1109
1110 QList<QList<QBenchmarkResult>> resultsList;
1111 bool minimumTotalReached = false;
1112 do {
1113 QBenchmarkTestMethodData::current->beginDataRun();
1114 if (i < 0)
1115 QBenchmarkTestMethodData::current->iterationCount = 1;
1116
1117 /* Benchmarking: for each accumulation iteration*/
1118 bool invokeOk;
1119 do {
1120 QTest::inTestFunction = true;
1121 if (m_initMethod.isValid())
1122 m_initMethod.invoke(obj: QTest::currentTestObject, c: Qt::DirectConnection);
1123
1124 const bool initQuit =
1125 QTestResult::skipCurrentTest() || QTestResult::currentTestFailed();
1126 if (!initQuit) {
1127 QBenchmarkTestMethodData::current->results.clear();
1128 QBenchmarkTestMethodData::current->resultAccepted = false;
1129 QBenchmarkTestMethodData::current->valid = false;
1130
1131 QBenchmarkGlobalData::current->context.tag = QLatin1StringView(
1132 QTestResult::currentDataTag() ? QTestResult::currentDataTag() : "");
1133
1134 invokeOk = m_methods[index].invoke(obj: QTest::currentTestObject, c: Qt::DirectConnection);
1135 if (!invokeOk)
1136 QTestResult::addFailure(message: "Unable to execute slot", __FILE__, __LINE__);
1137
1138 isBenchmark = QBenchmarkTestMethodData::current->isBenchmark();
1139 } else {
1140 invokeOk = false;
1141 }
1142
1143 QTest::inTestFunction = false;
1144 QTestResult::finishedCurrentTestData();
1145
1146 if (!initQuit) {
1147 if (m_cleanupMethod.isValid())
1148 m_cleanupMethod.invoke(obj: QTest::currentTestObject, c: Qt::DirectConnection);
1149
1150 // Process any deleteLater(), used by event-loop-based apps.
1151 // Fixes memleak reports.
1152 if (QCoreApplication::instance())
1153 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
1154 }
1155 // If the test isn't a benchmark, finalize the result after
1156 // cleanup() has finished (or init has lead us to skip the test).
1157 if (!isBenchmark)
1158 QTestResult::finishedCurrentTestDataCleanup();
1159
1160 // If this test method has a benchmark, repeat until all measurements are
1161 // acceptable.
1162 // The QBENCHMARK macro increases the number of iterations for each run until
1163 // this happens.
1164 } while (invokeOk && isBenchmark
1165 && QBenchmarkTestMethodData::current->resultsAccepted() == false
1166 && !QTestResult::skipCurrentTest() && !QTestResult::currentTestFailed());
1167
1168 QBenchmarkTestMethodData::current->endDataRun();
1169 if (!QTestResult::skipCurrentTest() && !QTestResult::currentTestFailed()) {
1170 if (i > -1) // iteration -1 is the warmup iteration.
1171 resultsList.append(t: QBenchmarkTestMethodData::current->results);
1172
1173 if (isBenchmark && QBenchmarkGlobalData::current->verboseOutput &&
1174 !QBenchmarkTestMethodData::current->results.isEmpty()) {
1175 // we only print the first result
1176 const QBenchmarkResult &first = QBenchmarkTestMethodData::current->results.constFirst();
1177 QString pattern = i < 0 ? "warmup stage result : %1"_L1
1178 : "accumulation stage result: %1"_L1;
1179 QTestLog::info(qPrintable(pattern.arg(first.measurement.value)), file: nullptr, line: 0);
1180 }
1181 }
1182
1183 // Verify if the minimum total measurement (for the first measurement)
1184 // was reached, if it was specified:
1185 if (QBenchmarkGlobalData::current->minimumTotal == -1) {
1186 minimumTotalReached = true;
1187 } else {
1188 auto addResult = [](qreal current, const QList<QBenchmarkResult> &r) {
1189 if (!r.isEmpty())
1190 current += r.first().measurement.value;
1191 return current;
1192 };
1193 const qreal total = std::accumulate(first: resultsList.begin(), last: resultsList.end(), init: 0.0, binary_op: addResult);
1194 minimumTotalReached = (total >= QBenchmarkGlobalData::current->minimumTotal);
1195 }
1196 } while (isBenchmark
1197 && ((++i < QBenchmarkGlobalData::current->adjustMedianIterationCount()) || !minimumTotalReached)
1198 && !QTestResult::skipCurrentTest() && !QTestResult::currentTestFailed());
1199
1200 // If the test is a benchmark, finalize the result after all iterations have finished.
1201 if (isBenchmark) {
1202 bool testPassed = !QTestResult::skipCurrentTest() && !QTestResult::currentTestFailed();
1203 QTestResult::finishedCurrentTestDataCleanup();
1204 // Only report benchmark figures if the test passed
1205 if (testPassed && QBenchmarkTestMethodData::current->resultsAccepted())
1206 QTestLog::addBenchmarkResults(result: qMedian(container: resultsList));
1207 }
1208}
1209
1210#if QT_CONFIG(thread)
1211
1212class WatchDog : public QThread
1213{
1214 enum Expectation : std::size_t {
1215 // bits 0..1: state
1216 ThreadStart,
1217 TestFunctionStart,
1218 TestFunctionEnd,
1219 ThreadEnd,
1220
1221 // bits 2..: generation
1222 };
1223 static constexpr auto ExpectationMask = Expectation{ThreadStart | TestFunctionStart | TestFunctionEnd | ThreadEnd};
1224 static_assert(size_t(ExpectationMask) == 0x3);
1225 static constexpr size_t GenerationShift = 2;
1226
1227 static constexpr Expectation state(Expectation e) noexcept
1228 { return Expectation{e & ExpectationMask}; }
1229 static constexpr size_t generation(Expectation e) noexcept
1230 { return e >> GenerationShift; }
1231 static constexpr Expectation combine(Expectation e, size_t gen) noexcept
1232 { return Expectation{e | (gen << GenerationShift)}; }
1233
1234 bool waitFor(std::unique_lock<QtPrivate::mutex> &m, Expectation e)
1235 {
1236 auto expectationChanged = [this, e] { return expecting.load(m: std::memory_order_relaxed) != e; };
1237 switch (state(e)) {
1238 case TestFunctionEnd:
1239 return waitCondition.wait_for(lock&: m, rtime: defaultTimeout(), p: expectationChanged);
1240 case ThreadStart:
1241 case ThreadEnd:
1242 case TestFunctionStart:
1243 waitCondition.wait(lock&: m, p: expectationChanged);
1244 return true;
1245 }
1246 Q_UNREACHABLE_RETURN(false);
1247 }
1248
1249 void setExpectation(Expectation e)
1250 {
1251 Q_ASSERT(generation(e) == 0); // no embedded generation allowed
1252 const auto locker = qt_scoped_lock(mutex);
1253 auto cur = expecting.load(m: std::memory_order_relaxed);
1254 auto gen = generation(e: cur);
1255 if (e == TestFunctionStart)
1256 ++gen;
1257 e = combine(e, gen);
1258 expecting.store(i: e, m: std::memory_order_relaxed);
1259 waitCondition.notify_all();
1260 }
1261
1262public:
1263 WatchDog()
1264 {
1265 setObjectName("QtTest Watchdog"_L1);
1266 auto locker = qt_unique_lock(mutex);
1267 expecting.store(i: ThreadStart, m: std::memory_order_relaxed);
1268 start();
1269 waitFor(m&: locker, e: ThreadStart);
1270 }
1271
1272 ~WatchDog()
1273 {
1274 setExpectation(ThreadEnd);
1275 wait();
1276 }
1277
1278 void beginTest()
1279 {
1280 setExpectation(TestFunctionEnd);
1281 }
1282
1283 void testFinished()
1284 {
1285 setExpectation(TestFunctionStart);
1286 }
1287
1288 void run() override
1289 {
1290 auto locker = qt_unique_lock(mutex);
1291 expecting.store(i: TestFunctionStart, m: std::memory_order_release);
1292 waitCondition.notify_all();
1293 while (true) {
1294 Expectation e = expecting.load(m: std::memory_order_acquire);
1295 switch (state(e)) {
1296 case ThreadEnd:
1297 return;
1298 case ThreadStart:
1299 Q_UNREACHABLE();
1300 case TestFunctionStart:
1301 case TestFunctionEnd:
1302 if (Q_UNLIKELY(!waitFor(locker, e))) {
1303 fflush(stderr);
1304 printTestRunTime();
1305 generateStackTrace();
1306 qFatal(msg: "Test function timed out");
1307 }
1308 }
1309 }
1310 }
1311
1312private:
1313 QtPrivate::mutex mutex;
1314 QtPrivate::condition_variable waitCondition;
1315 std::atomic<Expectation> expecting;
1316};
1317
1318#else // !QT_CONFIG(thread)
1319
1320class WatchDog : public QObject
1321{
1322public:
1323 void beginTest() {};
1324 void testFinished() {};
1325};
1326
1327#endif // QT_CONFIG(thread)
1328
1329
1330static void printUnknownDataTagError(QLatin1StringView name, QLatin1StringView tag,
1331 const QTestTable &lTable, const QTestTable &gTable)
1332{
1333 fprintf(stderr, format: "Unknown testdata for function %s(): '%s'\n", name.constData(), tag.data());
1334 const int localDataCount = lTable.dataCount();
1335 if (localDataCount) {
1336 fputs(s: "Available test-specific data tags:\n", stderr);
1337 for (int i = 0; i < localDataCount; ++i)
1338 fprintf(stderr, format: "\t%s\n", lTable.testData(index: i)->dataTag());
1339 }
1340 const int globalDataCount = gTable.dataCount();
1341 if (globalDataCount) {
1342 fputs(s: "Available global data tags:\n", stderr);
1343 for (int i = 0; i < globalDataCount; ++i)
1344 fprintf(stderr, format: "\t%s\n", gTable.testData(index: i)->dataTag());
1345 }
1346 if (localDataCount == 0 && globalDataCount == 0)
1347 fputs(s: "Function has no data tags\n", stderr);
1348}
1349
1350/*!
1351 \internal
1352
1353 Call slot_data(), init(), slot(), cleanup(), init(), slot(), cleanup(), ...
1354 If data is set then it is the only test that is performed
1355
1356 If the function was successfully called, true is returned, otherwise
1357 false.
1358*/
1359bool TestMethods::invokeTest(int index, QLatin1StringView tag, WatchDog *watchDog) const
1360{
1361 QBenchmarkTestMethodData benchmarkData;
1362 QBenchmarkTestMethodData::current = &benchmarkData;
1363
1364 const QByteArray &name = m_methods[index].name();
1365 QBenchmarkGlobalData::current->context.slotName = QLatin1StringView(name) + "()"_L1;
1366
1367 char member[512];
1368 QTestTable table;
1369
1370 QTestResult::setCurrentTestFunction(name.constData());
1371
1372 const QTestTable *gTable = QTestTable::globalTestTable();
1373 const int globalDataCount = gTable->dataCount();
1374 int curGlobalDataIndex = 0;
1375 const auto globalDataTag = [gTable, globalDataCount](int index) {
1376 return globalDataCount ? gTable->testData(index)->dataTag() : nullptr;
1377 };
1378
1379 const auto dataTagMatches = [](QLatin1StringView tag, QLatin1StringView local,
1380 QLatin1StringView global) {
1381 if (tag.isEmpty()) // No tag specified => run all data sets for this function
1382 return true;
1383 if (tag == local || tag == global) // Equal to either => run it
1384 return true;
1385 // Also allow global:local as a match:
1386 return tag.startsWith(s: global) && tag.endsWith(s: local) &&
1387 tag.size() == global.size() + 1 + local.size() &&
1388 tag[global.size()] == ':';
1389 };
1390 bool foundFunction = false;
1391
1392 /* For each entry in the global data table, do: */
1393 do {
1394 if (!gTable->isEmpty())
1395 QTestResult::setCurrentGlobalTestData(gTable->testData(index: curGlobalDataIndex));
1396
1397 if (curGlobalDataIndex == 0) {
1398 qsnprintf(str: member, n: 512, fmt: "%s_data()", name.constData());
1399 invokeMethod(obj: QTest::currentTestObject, methodName: member);
1400 if (QTestResult::skipCurrentTest())
1401 break;
1402 }
1403
1404 int curDataIndex = 0;
1405 const int dataCount = table.dataCount();
1406 const auto dataTag = [&table, dataCount](int index) {
1407 return dataCount ? table.testData(index)->dataTag() : nullptr;
1408 };
1409
1410 /* For each entry in this test's data table, do: */
1411 do {
1412 QTestResult::setSkipCurrentTest(false);
1413 QTestResult::setBlacklistCurrentTest(false);
1414 if (dataTagMatches(tag, QLatin1StringView(dataTag(curDataIndex)),
1415 QLatin1StringView(globalDataTag(curGlobalDataIndex)))) {
1416 foundFunction = true;
1417 QTestPrivate::checkBlackLists(slot: name.constData(), data: dataTag(curDataIndex),
1418 global: globalDataTag(curGlobalDataIndex));
1419
1420 QTestDataSetter s(curDataIndex >= dataCount ? nullptr : table.testData(index: curDataIndex));
1421
1422 QTestPrivate::qtestMouseButtons = Qt::NoButton;
1423 if (watchDog)
1424 watchDog->beginTest();
1425 QTest::lastMouseTimestamp += 500; // Maintain at least 500ms mouse event timestamps between each test function call
1426 invokeTestOnData(index);
1427 if (watchDog)
1428 watchDog->testFinished();
1429
1430 if (!tag.isEmpty() && !globalDataCount)
1431 break;
1432 }
1433 ++curDataIndex;
1434 } while (curDataIndex < dataCount);
1435
1436 QTestResult::setCurrentGlobalTestData(nullptr);
1437 ++curGlobalDataIndex;
1438 } while (curGlobalDataIndex < globalDataCount);
1439
1440 if (!tag.isEmpty() && !foundFunction) {
1441 printUnknownDataTagError(name: QLatin1StringView(name), tag, lTable: table, gTable: *gTable);
1442 QTestResult::addFailure(qPrintable("Data tag not found: %1"_L1.arg(tag)));
1443 }
1444 QTestResult::finishedCurrentTestFunction();
1445 QTestResult::setSkipCurrentTest(false);
1446 QTestResult::setBlacklistCurrentTest(false);
1447
1448 return true;
1449}
1450
1451void *fetchData(QTestData *data, const char *tagName, int typeId)
1452{
1453 QTEST_ASSERT(typeId);
1454 QTEST_ASSERT_X(data, "QTest::fetchData()", "Test data requested, but no testdata available.");
1455 QTEST_ASSERT(data->parent());
1456
1457 int idx = data->parent()->indexOf(elementName: tagName);
1458
1459 if (Q_UNLIKELY(idx == -1 || idx >= data->dataCount())) {
1460 qFatal(msg: "QFETCH: Requested testdata '%s' not available, check your _data function.",
1461 tagName);
1462 }
1463
1464 if (Q_UNLIKELY(typeId != data->parent()->elementTypeId(idx))) {
1465 qFatal(msg: "Requested type '%s' does not match available type '%s'.",
1466 QMetaType(typeId).name(),
1467 QMetaType(data->parent()->elementTypeId(index: idx)).name());
1468 }
1469
1470 return data->data(index: idx);
1471}
1472
1473/*!
1474 * \internal
1475*/
1476char *formatString(const char *prefix, const char *suffix, size_t numArguments, ...)
1477{
1478 va_list ap;
1479 va_start(ap, numArguments);
1480
1481 QByteArray arguments;
1482 arguments += prefix;
1483
1484 if (numArguments > 0) {
1485 arguments += va_arg(ap, const char *);
1486
1487 for (size_t i = 1; i < numArguments; ++i) {
1488 arguments += ", ";
1489 arguments += va_arg(ap, const char *);
1490 }
1491 }
1492
1493 va_end(ap);
1494 arguments += suffix;
1495 return qstrdup(arguments.constData());
1496}
1497
1498/*!
1499 Returns a pointer to a string that is the string \a ba represented
1500 as a space-separated sequence of hex characters. If the input is
1501 considered too long, it is truncated. A trucation is indicated in
1502 the returned string as an ellipsis at the end. The caller has
1503 ownership of the returned pointer and must ensure it is later passed
1504 to operator delete[].
1505
1506 \a length is the length of the string \a ba.
1507*/
1508char *toHexRepresentation(const char *ba, qsizetype length)
1509{
1510 if (length == 0)
1511 return qstrdup("");
1512
1513 /* We output at maximum about maxLen characters in order to avoid
1514 * running out of memory and flooding things when the byte array
1515 * is large.
1516 *
1517 * maxLen can't be for example 200 because Qt Test is sprinkled with fixed
1518 * size char arrays.
1519 * */
1520 const qsizetype maxLen = 50;
1521 const qsizetype len = qMin(a: maxLen, b: length);
1522 char *result = nullptr;
1523
1524 if (length > maxLen) {
1525 const qsizetype size = len * 3 + 4;
1526 result = new char[size];
1527
1528 char *const forElipsis = result + size - 5;
1529 forElipsis[0] = ' ';
1530 forElipsis[1] = '.';
1531 forElipsis[2] = '.';
1532 forElipsis[3] = '.';
1533 result[size - 1] = '\0';
1534 }
1535 else {
1536 const qsizetype size = len * 3;
1537 result = new char[size];
1538 result[size - 1] = '\0';
1539 }
1540
1541 qsizetype i = 0;
1542 qsizetype o = 0;
1543
1544 while (true) {
1545 const char at = ba[i];
1546
1547 result[o] = toHexUpper(value: at >> 4);
1548 ++o;
1549 result[o] = toHexUpper(value: at);
1550
1551 ++i;
1552 ++o;
1553 if (i == len)
1554 break;
1555 result[o] = ' ';
1556 ++o;
1557 }
1558
1559 return result;
1560}
1561
1562/*!
1563 \internal
1564 Returns the same QByteArray but with only the ASCII characters still shown;
1565 everything else is replaced with \c {\xHH}.
1566*/
1567char *toPrettyCString(const char *p, qsizetype length)
1568{
1569 bool trimmed = false;
1570 auto buffer = std::make_unique<char[]>(num: 256);
1571 const char *end = p + length;
1572 char *dst = buffer.get();
1573
1574 bool lastWasHexEscape = false;
1575 *dst++ = '"';
1576 for ( ; p != end; ++p) {
1577 // we can add:
1578 // 1 byte: a single character
1579 // 2 bytes: a simple escape sequence (\n)
1580 // 3 bytes: "" and a character
1581 // 4 bytes: an hex escape sequence (\xHH)
1582 if (dst - buffer.get() > 246) {
1583 // plus the quote, the three dots and NUL, it's 255 in the worst case
1584 trimmed = true;
1585 break;
1586 }
1587
1588 // check if we need to insert "" to break an hex escape sequence
1589 if (Q_UNLIKELY(lastWasHexEscape)) {
1590 if (fromHex(c: *p) != -1) {
1591 // yes, insert it
1592 *dst++ = '"';
1593 *dst++ = '"';
1594 }
1595 lastWasHexEscape = false;
1596 }
1597
1598 if (*p < 0x7f && *p >= 0x20 && *p != '\\' && *p != '"') {
1599 *dst++ = *p;
1600 continue;
1601 }
1602
1603 // write as an escape sequence
1604 // this means we may advance dst to buffer.data() + 247 or 250
1605 *dst++ = '\\';
1606 switch (*p) {
1607 case 0x5c:
1608 case 0x22:
1609 *dst++ = uchar(*p);
1610 break;
1611 case 0x8:
1612 *dst++ = 'b';
1613 break;
1614 case 0xc:
1615 *dst++ = 'f';
1616 break;
1617 case 0xa:
1618 *dst++ = 'n';
1619 break;
1620 case 0xd:
1621 *dst++ = 'r';
1622 break;
1623 case 0x9:
1624 *dst++ = 't';
1625 break;
1626 default:
1627 // print as hex escape
1628 *dst++ = 'x';
1629 *dst++ = toHexUpper(value: uchar(*p) >> 4);
1630 *dst++ = toHexUpper(value: uchar(*p));
1631 lastWasHexEscape = true;
1632 break;
1633 }
1634 }
1635
1636 *dst++ = '"';
1637 if (trimmed) {
1638 *dst++ = '.';
1639 *dst++ = '.';
1640 *dst++ = '.';
1641 }
1642 *dst++ = '\0';
1643 return buffer.release();
1644}
1645
1646/*!
1647 \internal
1648 Returns the same QString but with only the ASCII characters still shown;
1649 everything else is replaced with \c {\uXXXX}.
1650
1651 Similar to QDebug::putString().
1652*/
1653char *toPrettyUnicode(QStringView string)
1654{
1655 auto p = string.utf16();
1656 auto length = string.size();
1657 // keep it simple for the vast majority of cases
1658 bool trimmed = false;
1659 auto buffer = std::make_unique<char[]>(num: 256);
1660 const auto end = p + length;
1661 char *dst = buffer.get();
1662
1663 *dst++ = '"';
1664 for ( ; p != end; ++p) {
1665 if (dst - buffer.get() > 245) {
1666 // plus the quote, the three dots and NUL, it's 250, 251 or 255
1667 trimmed = true;
1668 break;
1669 }
1670
1671 if (*p < 0x7f && *p >= 0x20 && *p != '\\' && *p != '"') {
1672 *dst++ = *p;
1673 continue;
1674 }
1675
1676 // write as an escape sequence
1677 // this means we may advance dst to buffer.data() + 246 or 250
1678 *dst++ = '\\';
1679 switch (*p) {
1680 case 0x22:
1681 case 0x5c:
1682 *dst++ = uchar(*p);
1683 break;
1684 case 0x8:
1685 *dst++ = 'b';
1686 break;
1687 case 0xc:
1688 *dst++ = 'f';
1689 break;
1690 case 0xa:
1691 *dst++ = 'n';
1692 break;
1693 case 0xd:
1694 *dst++ = 'r';
1695 break;
1696 case 0x9:
1697 *dst++ = 't';
1698 break;
1699 default:
1700 *dst++ = 'u';
1701 *dst++ = toHexUpper(value: *p >> 12);
1702 *dst++ = toHexUpper(value: *p >> 8);
1703 *dst++ = toHexUpper(value: *p >> 4);
1704 *dst++ = toHexUpper(value: *p);
1705 }
1706 }
1707
1708 *dst++ = '"';
1709 if (trimmed) {
1710 *dst++ = '.';
1711 *dst++ = '.';
1712 *dst++ = '.';
1713 }
1714 *dst++ = '\0';
1715 return buffer.release();
1716}
1717
1718void TestMethods::invokeTests(QObject *testObject) const
1719{
1720 const QMetaObject *metaObject = testObject->metaObject();
1721 QTEST_ASSERT(metaObject);
1722 QTestResult::setCurrentTestFunction("initTestCase");
1723 if (m_initTestCaseDataMethod.isValid())
1724 m_initTestCaseDataMethod.invoke(obj: testObject, c: Qt::DirectConnection);
1725
1726 QScopedPointer<WatchDog> watchDog;
1727 if (!alreadyDebugging()
1728#if QT_CONFIG(valgrind)
1729 && QBenchmarkGlobalData::current->mode() != QBenchmarkGlobalData::CallgrindChildProcess
1730#endif
1731 ) {
1732 watchDog.reset(other: new WatchDog);
1733 }
1734
1735 QSignalDumper::startDump();
1736
1737 if (!QTestResult::skipCurrentTest() && !QTestResult::currentTestFailed()) {
1738 if (m_initTestCaseMethod.isValid())
1739 m_initTestCaseMethod.invoke(obj: testObject, c: Qt::DirectConnection);
1740
1741 // finishedCurrentTestDataCleanup() resets QTestResult::currentTestFailed(), so use a local copy.
1742 const bool previousFailed = QTestResult::currentTestFailed();
1743 QTestResult::finishedCurrentTestData();
1744 QTestResult::finishedCurrentTestDataCleanup();
1745 QTestResult::finishedCurrentTestFunction();
1746
1747 if (!QTestResult::skipCurrentTest() && !previousFailed) {
1748 for (int i = 0, count = int(m_methods.size()); i < count; ++i) {
1749 const char *data = nullptr;
1750 if (i < QTest::testTags.size() && !QTest::testTags.at(i).isEmpty())
1751 data = qstrdup(QTest::testTags.at(i).toLatin1().constData());
1752 const bool ok = invokeTest(index: i, tag: QLatin1StringView(data), watchDog: watchDog.data());
1753 delete [] data;
1754 if (!ok)
1755 break;
1756 }
1757 }
1758
1759 const bool wasSkipped = QTestResult::skipCurrentTest();
1760 QTestResult::setSkipCurrentTest(false);
1761 QTestResult::setBlacklistCurrentTest(false);
1762 QTestResult::setCurrentTestFunction("cleanupTestCase");
1763 if (m_cleanupTestCaseMethod.isValid())
1764 m_cleanupTestCaseMethod.invoke(obj: testObject, c: Qt::DirectConnection);
1765 QTestResult::finishedCurrentTestData();
1766 // Restore skip state as it affects decision on whether we passed:
1767 QTestResult::setSkipCurrentTest(wasSkipped || QTestResult::skipCurrentTest());
1768 QTestResult::finishedCurrentTestDataCleanup();
1769 }
1770 QTestResult::finishedCurrentTestFunction();
1771 QTestResult::setCurrentTestFunction(nullptr);
1772
1773 QSignalDumper::endDump();
1774}
1775
1776bool reportResult(bool success, qxp::function_ref<const char *()> lhs,
1777 qxp::function_ref<const char *()> rhs,
1778 const char *lhsExpr, const char *rhsExpr,
1779 ComparisonOperation op, const char *file, int line)
1780{
1781 return QTestResult::reportResult(success, lhs, rhs, lhsExpr, rhsExpr, op, file, line);
1782}
1783
1784} // namespace QTest
1785
1786namespace {
1787#if defined(Q_OS_WIN)
1788
1789// Helper class for resolving symbol names by dynamically loading "dbghelp.dll".
1790class DebugSymbolResolver
1791{
1792 Q_DISABLE_COPY_MOVE(DebugSymbolResolver)
1793public:
1794 struct Symbol {
1795 Symbol() : name(nullptr), address(0) {}
1796
1797 const char *name; // Must be freed by caller.
1798 DWORD64 address;
1799 };
1800
1801 explicit DebugSymbolResolver(HANDLE process);
1802 ~DebugSymbolResolver() { cleanup(); }
1803
1804 bool isValid() const { return m_symFromAddr; }
1805
1806 Symbol resolveSymbol(DWORD64 address) const;
1807
1808private:
1809 // typedefs from DbgHelp.h/.dll
1810 struct DBGHELP_SYMBOL_INFO { // SYMBOL_INFO
1811 ULONG SizeOfStruct;
1812 ULONG TypeIndex; // Type Index of symbol
1813 ULONG64 Reserved[2];
1814 ULONG Index;
1815 ULONG Size;
1816 ULONG64 ModBase; // Base Address of module comtaining this symbol
1817 ULONG Flags;
1818 ULONG64 Value; // Value of symbol, ValuePresent should be 1
1819 ULONG64 Address; // Address of symbol including base address of module
1820 ULONG Register; // register holding value or pointer to value
1821 ULONG Scope; // scope of the symbol
1822 ULONG Tag; // pdb classification
1823 ULONG NameLen; // Actual length of name
1824 ULONG MaxNameLen;
1825 CHAR Name[1]; // Name of symbol
1826 };
1827
1828 typedef BOOL (__stdcall *SymInitializeType)(HANDLE, PCSTR, BOOL);
1829 typedef BOOL (__stdcall *SymFromAddrType)(HANDLE, DWORD64, PDWORD64, DBGHELP_SYMBOL_INFO *);
1830
1831 void cleanup();
1832
1833 const HANDLE m_process;
1834 HMODULE m_dbgHelpLib;
1835 SymFromAddrType m_symFromAddr;
1836};
1837
1838void DebugSymbolResolver::cleanup()
1839{
1840 if (m_dbgHelpLib)
1841 FreeLibrary(m_dbgHelpLib);
1842 m_dbgHelpLib = 0;
1843 m_symFromAddr = nullptr;
1844}
1845
1846DebugSymbolResolver::DebugSymbolResolver(HANDLE process)
1847 : m_process(process), m_dbgHelpLib(0), m_symFromAddr(nullptr)
1848{
1849 bool success = false;
1850 m_dbgHelpLib = LoadLibraryW(L"dbghelp.dll");
1851 if (m_dbgHelpLib) {
1852 SymInitializeType symInitialize = reinterpret_cast<SymInitializeType>(
1853 reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymInitialize")));
1854 m_symFromAddr = reinterpret_cast<SymFromAddrType>(
1855 reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymFromAddr")));
1856 success = symInitialize && m_symFromAddr && symInitialize(process, NULL, TRUE);
1857 }
1858 if (!success)
1859 cleanup();
1860}
1861
1862DebugSymbolResolver::Symbol DebugSymbolResolver::resolveSymbol(DWORD64 address) const
1863{
1864 // reserve additional buffer where SymFromAddr() will store the name
1865 struct NamedSymbolInfo : public DBGHELP_SYMBOL_INFO {
1866 enum { symbolNameLength = 255 };
1867
1868 char name[symbolNameLength + 1];
1869 };
1870
1871 Symbol result;
1872 if (!isValid())
1873 return result;
1874 NamedSymbolInfo symbolBuffer;
1875 memset(&symbolBuffer, 0, sizeof(NamedSymbolInfo));
1876 symbolBuffer.MaxNameLen = NamedSymbolInfo::symbolNameLength;
1877 symbolBuffer.SizeOfStruct = sizeof(DBGHELP_SYMBOL_INFO);
1878 if (!m_symFromAddr(m_process, address, 0, &symbolBuffer))
1879 return result;
1880 result.name = qstrdup(symbolBuffer.Name);
1881 result.address = symbolBuffer.Address;
1882 return result;
1883}
1884
1885class WindowsFaultHandler
1886{
1887public:
1888 WindowsFaultHandler()
1889 {
1890# if !defined(Q_CC_MINGW)
1891 _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);
1892# endif
1893 SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX);
1894 SetUnhandledExceptionFilter(windowsFaultHandler);
1895 }
1896
1897private:
1898 static LONG WINAPI windowsFaultHandler(struct _EXCEPTION_POINTERS *exInfo)
1899 {
1900 enum { maxStackFrames = 100 };
1901 char appName[MAX_PATH];
1902 if (!GetModuleFileNameA(NULL, appName, MAX_PATH))
1903 appName[0] = 0;
1904 const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
1905 const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
1906 const void *exceptionAddress = exInfo->ExceptionRecord->ExceptionAddress;
1907 fprintf(stderr, "A crash occurred in %s.\n", appName);
1908 if (const char *name = QTest::currentTestFunction())
1909 fprintf(stderr, "While testing %s\n", name);
1910 fprintf(stderr, "Function time: %dms Total time: %dms\n\n"
1911 "Exception address: 0x%p\n"
1912 "Exception code : 0x%lx\n",
1913 msecsFunctionTime, msecsTotalTime, exceptionAddress,
1914 exInfo->ExceptionRecord->ExceptionCode);
1915
1916 DebugSymbolResolver resolver(GetCurrentProcess());
1917 if (resolver.isValid()) {
1918 DebugSymbolResolver::Symbol exceptionSymbol = resolver.resolveSymbol(DWORD64(exceptionAddress));
1919 if (exceptionSymbol.name) {
1920 fprintf(stderr, "Nearby symbol : %s\n", exceptionSymbol.name);
1921 delete [] exceptionSymbol.name;
1922 }
1923 void *stack[maxStackFrames];
1924 fputs("\nStack:\n", stderr);
1925 const unsigned frameCount = CaptureStackBackTrace(0, DWORD(maxStackFrames), stack, NULL);
1926 for (unsigned f = 0; f < frameCount; ++f) {
1927 DebugSymbolResolver::Symbol symbol = resolver.resolveSymbol(DWORD64(stack[f]));
1928 if (symbol.name) {
1929 fprintf(stderr, "#%3u: %s() - 0x%p\n", f + 1, symbol.name, (const void *)symbol.address);
1930 delete [] symbol.name;
1931 } else {
1932 fprintf(stderr, "#%3u: Unable to obtain symbol\n", f + 1);
1933 }
1934 }
1935 }
1936
1937 fputc('\n', stderr);
1938
1939 return EXCEPTION_EXECUTE_HANDLER;
1940 }
1941};
1942using FatalSignalHandler = WindowsFaultHandler;
1943
1944#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
1945class FatalSignalHandler
1946{
1947public:
1948# define OUR_SIGNALS(F) \
1949 F(HUP) \
1950 F(INT) \
1951 F(QUIT) \
1952 F(ABRT) \
1953 F(ILL) \
1954 F(BUS) \
1955 F(FPE) \
1956 F(SEGV) \
1957 F(PIPE) \
1958 F(TERM) \
1959 /**/
1960# define CASE_LABEL(S) case SIG ## S: return QT_STRINGIFY(S);
1961# define ENUMERATE_SIGNALS(S) SIG ## S,
1962 static const char *signalName(int signum) noexcept
1963 {
1964 switch (signum) {
1965 OUR_SIGNALS(CASE_LABEL)
1966 }
1967
1968# if defined(__GLIBC_MINOR__) && (__GLIBC_MINOR__ >= 32 || __GLIBC__ > 2)
1969 // get the other signal names from glibc 2.32
1970 // (accessing the sys_sigabbrev variable causes linker warnings)
1971 if (const char *p = sigabbrev_np(sig: signum))
1972 return p;
1973# endif
1974 return "???";
1975 }
1976 static constexpr std::array fatalSignals = {
1977 OUR_SIGNALS(ENUMERATE_SIGNALS)
1978 };
1979# undef CASE_LABEL
1980# undef ENUMERATE_SIGNALS
1981
1982 static constexpr std::array crashingSignals = {
1983 // Crash signals are special, because if we return from the handler
1984 // without adjusting the machine state, the same instruction that
1985 // originally caused the crash will get re-executed and will thus cause
1986 // the same crash again. This is useful if our parent process logs the
1987 // exit result or if core dumps are enabled: the core file will point
1988 // to the actual instruction that crashed.
1989 SIGILL, SIGBUS, SIGFPE, SIGSEGV
1990 };
1991 using OldActionsArray = std::array<struct sigaction, fatalSignals.size()>;
1992
1993 FatalSignalHandler()
1994 {
1995 pauseOnCrash = qEnvironmentVariableIsSet(varName: "QTEST_PAUSE_ON_CRASH");
1996 struct sigaction act;
1997 memset(s: &act, c: 0, n: sizeof(act));
1998 act.sa_handler = SIG_DFL;
1999 oldActions().fill(u: act);
2000
2001 // Remove the handler after it is invoked.
2002 act.sa_flags = SA_RESETHAND | setupAlternateStack();
2003
2004# ifdef SA_SIGINFO
2005 act.sa_flags |= SA_SIGINFO;
2006 act.sa_sigaction = FatalSignalHandler::actionHandler;
2007# else
2008 act.sa_handler = FatalSignalHandler::regularHandler;
2009# endif
2010
2011 // Block all fatal signals in our signal handler so we don't try to close
2012 // the testlog twice.
2013 sigemptyset(set: &act.sa_mask);
2014 for (int signal : fatalSignals)
2015 sigaddset(set: &act.sa_mask, signo: signal);
2016
2017 for (size_t i = 0; i < fatalSignals.size(); ++i)
2018 sigaction(sig: fatalSignals[i], act: &act, oact: &oldActions()[i]);
2019 }
2020
2021 ~FatalSignalHandler()
2022 {
2023 // Restore the default signal handlers in place of ours.
2024 // If ours has been replaced, leave the replacement alone.
2025 auto isOurs = [](const struct sigaction &old) {
2026# ifdef SA_SIGINFO
2027 return (old.sa_flags & SA_SIGINFO) && old.sa_sigaction == FatalSignalHandler::actionHandler;
2028# else
2029 return old.sa_handler == FatalSignalHandler::regularHandler;
2030# endif
2031 };
2032 struct sigaction action;
2033
2034 for (size_t i = 0; i < fatalSignals.size(); ++i) {
2035 struct sigaction &act = oldActions()[i];
2036 if (act.sa_flags == 0 && act.sa_handler == SIG_DFL)
2037 continue; // Already the default
2038 if (sigaction(sig: fatalSignals[i], act: nullptr, oact: &action))
2039 continue; // Failed to query present handler
2040 if (isOurs(action))
2041 sigaction(sig: fatalSignals[i], act: &act, oact: nullptr);
2042 }
2043
2044 freeAlternateStack();
2045 }
2046
2047private:
2048 Q_DISABLE_COPY_MOVE(FatalSignalHandler)
2049
2050 static OldActionsArray &oldActions()
2051 {
2052 Q_CONSTINIT static OldActionsArray oldActions {};
2053 return oldActions;
2054 }
2055
2056 auto alternateStackSize()
2057 {
2058 struct R { size_t size, pageSize; };
2059 static constexpr size_t MinStackSize = 32 * 1024;
2060 size_t pageSize = sysconf(_SC_PAGESIZE);
2061 size_t size = SIGSTKSZ;
2062 if (size < MinStackSize) {
2063 size = MinStackSize;
2064 } else {
2065 // round up to a page
2066 size = (size + pageSize - 1) & -pageSize;
2067 }
2068
2069 return R{ .size: size + pageSize, .pageSize: pageSize };
2070 }
2071
2072 int setupAlternateStack()
2073 {
2074 // tvOS/watchOS both define SA_ONSTACK (in sys/signal.h) but mark sigaltstack() as
2075 // unavailable (__WATCHOS_PROHIBITED __TVOS_PROHIBITED in signal.h)
2076# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
2077 // Let the signal handlers use an alternate stack
2078 // This is necessary if SIGSEGV is to catch a stack overflow
2079 auto r = alternateStackSize();
2080 int flags = MAP_PRIVATE | MAP_ANONYMOUS;
2081# ifdef MAP_STACK
2082 flags |= MAP_STACK;
2083# endif
2084 alternateStackBase = mmap(addr: nullptr, len: r.size, PROT_READ | PROT_WRITE, flags: flags, fd: -1, offset: 0);
2085 if (alternateStackBase == MAP_FAILED)
2086 return 0;
2087
2088 // mark the bottom page inaccessible, to catch a handler stack overflow
2089 (void) mprotect(addr: alternateStackBase, len: r.pageSize, PROT_NONE);
2090
2091 stack_t stack;
2092 stack.ss_flags = 0;
2093 stack.ss_size = r.size - r.pageSize;
2094 stack.ss_sp = static_cast<char *>(alternateStackBase) + r.pageSize;
2095 sigaltstack(ss: &stack, oss: nullptr);
2096 return SA_ONSTACK;
2097# else
2098 return 0;
2099# endif
2100 }
2101
2102 void freeAlternateStack()
2103 {
2104# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
2105 if (alternateStackBase != MAP_FAILED) {
2106 stack_t stack = {};
2107 stack.ss_flags = SS_DISABLE;
2108 sigaltstack(ss: &stack, oss: nullptr);
2109 munmap(addr: alternateStackBase, len: alternateStackSize().size);
2110 }
2111# endif
2112 }
2113
2114 template <typename T> static
2115 std::enable_if_t<sizeof(std::declval<T>().si_pid) + sizeof(std::declval<T>().si_uid) >= 1>
2116 printSentSignalInfo(T *info)
2117 {
2118 writeToStderr(" sent by PID ", asyncSafeToString(info->si_pid),
2119 " UID ", asyncSafeToString(info->si_uid));
2120 }
2121 static void printSentSignalInfo(...) {}
2122
2123 template <typename T> static
2124 std::enable_if_t<sizeof(std::declval<T>().si_addr) >= 1> printCrashingSignalInfo(T *info)
2125 {
2126 using HexString = std::array<char, sizeof(quintptr) * 2>;
2127 auto toHexString = [](quintptr u, HexString &&r = {}) {
2128 int shift = sizeof(quintptr) * 8 - 4;
2129 for (size_t i = 0; i < sizeof(quintptr) * 2; ++i, shift -= 4)
2130 r[i] = QtMiscUtils::toHexLower(value: u >> shift);
2131 struct iovec vec;
2132 vec.iov_base = r.data();
2133 vec.iov_len = r.size();
2134 return vec;
2135 };
2136 writeToStderr(", code ", asyncSafeToString(info->si_code),
2137 ", for address 0x", toHexString(quintptr(info->si_addr)));
2138 }
2139 static void printCrashingSignalInfo(...) {}
2140
2141 static void actionHandler(int signum, siginfo_t *info, void * /* ucontext */)
2142 {
2143 writeToStderr(args: "Received signal ", args: asyncSafeToString(n: signum),
2144 args: " (SIG", args: signalName(signum), args: ")");
2145
2146 bool isCrashingSignal =
2147 std::find(first: crashingSignals.begin(), last: crashingSignals.end(), val: signum) != crashingSignals.end();
2148 if (isCrashingSignal && (!info || info->si_code <= 0))
2149 isCrashingSignal = false; // wasn't sent by the kernel, so it's not really a crash
2150 if (isCrashingSignal)
2151 printCrashingSignalInfo(info);
2152 else if (info && (info->si_code == SI_USER || info->si_code == SI_QUEUE))
2153 printSentSignalInfo(info);
2154
2155 printTestRunTime();
2156 if (signum != SIGINT) {
2157 generateStackTrace();
2158 if (pauseOnCrash) {
2159 writeToStderr(args: "Pausing process ", args: asyncSafeToString(n: getpid()),
2160 args: " for debugging\n");
2161 raise(SIGSTOP);
2162 }
2163 }
2164
2165 // chain back to the previous handler, if any
2166 for (size_t i = 0; i < fatalSignals.size(); ++i) {
2167 struct sigaction &act = oldActions()[i];
2168 if (signum != fatalSignals[i])
2169 continue;
2170
2171 // restore the handler (if SA_RESETHAND hasn't done the job for us)
2172 if (SA_RESETHAND == 0 || act.sa_handler != SIG_DFL || act.sa_flags)
2173 (void) sigaction(sig: signum, act: &act, oact: nullptr);
2174
2175 if (!isCrashingSignal)
2176 raise(sig: signum);
2177
2178 // signal is blocked, so it'll be delivered when we return
2179 return;
2180 }
2181
2182 // we shouldn't reach here!
2183 std::abort();
2184 }
2185
2186 [[maybe_unused]] static void regularHandler(int signum)
2187 {
2188 actionHandler(signum, info: nullptr, nullptr);
2189 }
2190
2191 void *alternateStackBase = MAP_FAILED;
2192 static bool pauseOnCrash;
2193};
2194bool FatalSignalHandler::pauseOnCrash = false;
2195#else // Q_OS_WASM or weird systems
2196class FatalSignalHandler {};
2197#endif // Q_OS_* choice
2198
2199} // unnamed namespace
2200
2201static void initEnvironment()
2202{
2203 qputenv(varName: "QT_QTESTLIB_RUNNING", value: "1");
2204}
2205
2206/*!
2207 Executes tests declared in \a testObject. In addition, the private slots
2208 \c{initTestCase()}, \c{cleanupTestCase()}, \c{init()} and \c{cleanup()}
2209 are executed if they exist. See \l{Creating a Test} for more details.
2210
2211 Optionally, the command line arguments \a argc and \a argv can be provided.
2212 For a list of recognized arguments, read \l {Qt Test Command Line Arguments}.
2213
2214 The following example will run all tests in \c MyTestObject:
2215
2216 \snippet code/src_qtestlib_qtestcase.cpp 18
2217
2218 This function returns 0 if no tests failed, or a value other than 0 if one
2219 or more tests failed or in case of unhandled exceptions. (Skipped tests do
2220 not influence the return value.)
2221
2222 For stand-alone test applications, the convenience macro \l QTEST_MAIN() can
2223 be used to declare a main() function that parses the command line arguments
2224 and executes the tests, avoiding the need to call this function explicitly.
2225
2226 The return value from this function is also the exit code of the test
2227 application when the \l QTEST_MAIN() macro is used.
2228
2229 For stand-alone test applications, this function should not be called more
2230 than once, as command-line options for logging test output to files and
2231 executing individual test functions will not behave correctly.
2232
2233 \note This function is not reentrant, only one test can run at a time. A
2234 test that was executed with qExec() can't run another test via qExec() and
2235 threads are not allowed to call qExec() simultaneously.
2236
2237 If you have programmatically created the arguments, as opposed to getting them
2238 from the arguments in \c main(), it is likely of interest to use
2239 QTest::qExec(QObject *, const QStringList &) since it is Unicode safe.
2240
2241 \sa QTEST_MAIN(), QTEST_GUILESS_MAIN(), QTEST_APPLESS_MAIN()
2242*/
2243
2244int QTest::qExec(QObject *testObject, int argc, char **argv)
2245{
2246 // NB: QtQuick's testing recombines qInit(), qRun() and qCleanup() to
2247 // provide a replacement for qExec() that calls qRun() once for each
2248 // built-in style. So think twice about moving parts between these three
2249 // functions, as doing so may mess up QtQuick's testing.
2250 qInit(testObject, argc, argv);
2251 int ret = qRun();
2252 qCleanup();
2253 return ret;
2254}
2255
2256/*! \internal
2257*/
2258void QTest::qInit(QObject *testObject, int argc, char **argv)
2259{
2260 initEnvironment();
2261 maybeDisableCoreDump();
2262 QBenchmarkGlobalData::current = new QBenchmarkGlobalData;
2263
2264#if defined(Q_OS_MACOS)
2265 // Don't restore saved window state for auto tests
2266 QTestPrivate::disableWindowRestore();
2267
2268 // Disable App Nap which may cause tests to stall
2269 QTestPrivate::AppNapDisabler appNapDisabler;
2270
2271 if (qApp && (qstrcmp(qApp->metaObject()->className(), "QApplication") == 0)) {
2272 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
2273 kIOPMAssertionLevelOn, CFSTR("QtTest running tests"),
2274 &macPowerSavingDisabled);
2275 }
2276#endif
2277
2278 QTestPrivate::parseBlackList();
2279 QTestResult::reset();
2280
2281 QTEST_ASSERT(testObject);
2282 QTEST_ASSERT(!currentTestObject);
2283 currentTestObject = testObject;
2284
2285 const QMetaObject *metaObject = testObject->metaObject();
2286 QTEST_ASSERT(metaObject);
2287
2288 QTestResult::setCurrentTestObject(metaObject->className());
2289 if (argc > 0)
2290 QTestResult::setCurrentAppName(argv[0]);
2291
2292 qtest_qParseArgs(argc, argv, qml: false);
2293
2294#if QT_CONFIG(valgrind)
2295 if (QBenchmarkGlobalData::current->mode() != QBenchmarkGlobalData::CallgrindParentProcess)
2296#endif
2297 {
2298 QTestTable::globalTestTable();
2299 QTestLog::startLogging();
2300 }
2301}
2302
2303/*! \internal
2304*/
2305int QTest::qRun()
2306{
2307 QTEST_ASSERT(currentTestObject);
2308
2309#if QT_CONFIG(valgrind)
2310 int callgrindChildExitCode = 0;
2311#endif
2312
2313#ifndef QT_NO_EXCEPTIONS
2314 try {
2315#endif
2316
2317#if QT_CONFIG(valgrind)
2318 if (QBenchmarkGlobalData::current->mode() == QBenchmarkGlobalData::CallgrindParentProcess) {
2319 if (Q_UNLIKELY(!qApp))
2320 qFatal(msg: "QtTest: -callgrind option is not available with QTEST_APPLESS_MAIN");
2321
2322 const QStringList origAppArgs(QCoreApplication::arguments());
2323 if (!QBenchmarkValgrindUtils::rerunThroughCallgrind(origAppArgs, exitCode&: callgrindChildExitCode))
2324 return -1;
2325
2326 QBenchmarkValgrindUtils::cleanup();
2327
2328 } else
2329#endif
2330 {
2331 std::optional<FatalSignalHandler> handler;
2332 prepareStackTrace();
2333 if (!noCrashHandler)
2334 handler.emplace();
2335
2336 bool seenBad = false;
2337 TestMethods::MetaMethods commandLineMethods;
2338 commandLineMethods.reserve(n: static_cast<size_t>(QTest::testFunctions.size()));
2339 for (const QString &tf : std::as_const(t&: QTest::testFunctions)) {
2340 const QByteArray tfB = tf.toLatin1();
2341 const QByteArray signature = tfB + QByteArrayLiteral("()");
2342 QMetaMethod m = TestMethods::findMethod(obj: currentTestObject, signature: signature.constData());
2343 if (m.isValid() && isValidSlot(sl: m)) {
2344 commandLineMethods.push_back(x: m);
2345 } else {
2346 fprintf(stderr, format: "Unknown test function: '%s'.", tfB.constData());
2347 if (!qPrintTestSlots(stderr, filter: tfB.constData(), preamble: " Possible matches:\n"))
2348 fputc(c: '\n', stderr);
2349 QTestResult::setCurrentTestFunction(tfB.constData());
2350 QTestResult::addFailure(qPrintable("Function not found: %1"_L1.arg(tf)));
2351 QTestResult::finishedCurrentTestFunction();
2352 // Ditch the tag that came with tf as test function:
2353 QTest::testTags.remove(i: commandLineMethods.size());
2354 seenBad = true;
2355 }
2356 }
2357 if (seenBad) {
2358 // Provide relevant help to do better next time:
2359 fprintf(stderr, format: "\n%s -functions\nlists all available test functions.\n\n",
2360 QTestResult::currentAppName());
2361 if (commandLineMethods.empty()) // All requested functions missing.
2362 return 1;
2363 }
2364 TestMethods test(currentTestObject, std::move(commandLineMethods));
2365 test.invokeTests(testObject: currentTestObject);
2366 }
2367
2368#ifndef QT_NO_EXCEPTIONS
2369 } catch (...) {
2370 QTestResult::addFailure(message: "Caught unhandled exception", __FILE__, __LINE__);
2371 if (QTestResult::currentTestFunction()) {
2372 QTestResult::finishedCurrentTestFunction();
2373 QTestResult::setCurrentTestFunction(nullptr);
2374 }
2375
2376 qCleanup();
2377
2378 // Re-throw exception to make debugging easier
2379 throw;
2380 return 1;
2381 }
2382#endif
2383
2384#if QT_CONFIG(valgrind)
2385 if (QBenchmarkGlobalData::current->mode() == QBenchmarkGlobalData::CallgrindParentProcess)
2386 return callgrindChildExitCode;
2387#endif
2388 // make sure our exit code is never going above 127
2389 // since that could wrap and indicate 0 test fails
2390 return qMin(a: QTestLog::failCount(), b: 127);
2391}
2392
2393/*! \internal
2394*/
2395void QTest::qCleanup()
2396{
2397 currentTestObject = nullptr;
2398
2399#if QT_CONFIG(valgrind)
2400 if (QBenchmarkGlobalData::current->mode() != QBenchmarkGlobalData::CallgrindParentProcess)
2401#endif
2402 {
2403 QTestLog::stopLogging();
2404 QTestTable::clearGlobalTestTable();
2405 }
2406
2407 delete QBenchmarkGlobalData::current;
2408 QBenchmarkGlobalData::current = nullptr;
2409
2410#if defined(Q_OS_MACOS)
2411 IOPMAssertionRelease(macPowerSavingDisabled);
2412#endif
2413}
2414
2415#if QT_CONFIG(batch_test_support) || defined(Q_QDOC)
2416/*!
2417 Registers the test \a name, with entry function \a entryFunction, in a
2418 central test case registry for the current binary.
2419
2420 The \a name will be listed when running the batch test binary with no
2421 parameters. Running the test binary with the argv[1] of \a name will result
2422 in \a entryFunction being called.
2423
2424 \since 6.5
2425*/
2426void QTest::qRegisterTestCase(const QString &name, TestEntryFunction entryFunction)
2427{
2428 QTest::TestRegistry::instance()->registerTest(name, entryFunction);
2429}
2430
2431QList<QString> QTest::qGetTestCaseNames()
2432{
2433 return QTest::TestRegistry::instance()->getAllTestNames();
2434}
2435
2436QTest::TestEntryFunction QTest::qGetTestCaseEntryFunction(const QString& name)
2437{
2438 return QTest::TestRegistry::instance()->getTestEntryFunction(name);
2439}
2440
2441#endif // QT_CONFIG(batch_test_support)
2442
2443/*!
2444 \overload
2445 \since 4.4
2446
2447 Behaves identically to qExec(QObject *, int, char**) but takes a
2448 QStringList of \a arguments instead of a \c char** list.
2449*/
2450int QTest::qExec(QObject *testObject, const QStringList &arguments)
2451{
2452 const int argc = arguments.size();
2453 QVarLengthArray<char *> argv(argc);
2454
2455 QList<QByteArray> args;
2456 args.reserve(asize: argc);
2457
2458 for (int i = 0; i < argc; ++i)
2459 {
2460 args.append(t: arguments.at(i).toLocal8Bit().constData());
2461 argv[i] = args.last().data();
2462 }
2463
2464 return qExec(testObject, argc, argv: argv.data());
2465}
2466
2467/*! \internal
2468*/
2469void QTest::qFail(const char *message, const char *file, int line)
2470{
2471 QTestResult::fail(message, file, line);
2472}
2473
2474/*! \internal
2475*/
2476bool QTest::qVerify(bool statement, const char *statementStr, const char *description,
2477 const char *file, int line)
2478{
2479 return QTestResult::verify(statement, statementStr, extraInfo: description, file, line);
2480}
2481
2482/*! \fn void QTest::qSkip(const char *message, const char *file, int line)
2483 \internal
2484*/
2485void QTest::qSkip(const char *message, const char *file, int line)
2486{
2487 QTestResult::addSkip(message, file, line);
2488 QTestResult::setSkipCurrentTest(true);
2489}
2490
2491/*! \fn bool QTest::qExpectFail(const char *dataIndex, const char *comment, TestFailMode mode, const char *file, int line)
2492 \internal
2493*/
2494bool QTest::qExpectFail(const char *dataIndex, const char *comment,
2495 QTest::TestFailMode mode, const char *file, int line)
2496{
2497 return QTestResult::expectFail(dataIndex, comment: qstrdup(comment), mode, file, line);
2498}
2499
2500/*!
2501 \internal
2502
2503 Executes qFail() following a failed QVERIFY_THROWS_EXCEPTION or
2504 QVERIFY_THROWS_NO_EXCEPTION, passing a suitable message created from \a expected,
2505 \a what, along with \a file and \a line.
2506
2507 The \a expected parameter contains the type of the exception that is expected to
2508 be thrown, or \nullptr, if no exception was expected.
2509
2510 The \a what parameter contains the result of \c{std::exception::what()}, or nullptr,
2511 if a non-\c{std::exception}-derived exception was caught.
2512
2513 The \a file and \a line parameters hold expansions of the \c{__FILE__} and \c{__LINE__}
2514 macros, respectively.
2515*/
2516void QTest::qCaught(const char *expected, const char *what, const char *file, int line)
2517{
2518 auto message = [&] {
2519 const auto exType = what ? "std::" : "unknown ";
2520 const auto ofType = expected ? " of type " : "";
2521 const auto no = expected ? "an" : "no";
2522 const auto withMsg = what ? " with message " : "";
2523 const auto protect = [](const char *s) { return s ? s : ""; };
2524
2525 return QString::asprintf(format: "Expected %s exception%s%s to be thrown, "
2526 "but caught %sexception%s%s",
2527 no, ofType, protect(expected),
2528 exType, withMsg, protect(what));
2529 };
2530 qFail(message: message().toUtf8().constData(), file, line);
2531}
2532
2533
2534#if QT_DEPRECATED_SINCE(6, 3)
2535/*!
2536 \internal
2537 \deprecated [6.3] Use qWarning() instead
2538*/
2539void QTest::qWarn(const char *message, const char *file, int line)
2540{
2541 QTestLog::warn(msg: message, file, line);
2542}
2543#endif
2544
2545/*!
2546 Ignores messages created by qDebug(), qInfo() or qWarning(). If the \a message
2547 with the corresponding \a type is outputted, it will be removed from the
2548 test log. If the test finished and the \a message was not outputted,
2549 a test failure is appended to the test log.
2550
2551 \note Invoking this function will only ignore one message. If the message
2552 you want to ignore is output twice, you have to call ignoreMessage() twice,
2553 too.
2554
2555 Example:
2556 \snippet code/src_qtestlib_qtestcase.cpp 19
2557
2558 The example above tests that QDir::mkdir() outputs the right warning when invoked
2559 with an invalid file name.
2560*/
2561void QTest::ignoreMessage(QtMsgType type, const char *message)
2562{
2563 QTestLog::ignoreMessage(type, msg: message);
2564}
2565
2566#if QT_CONFIG(regularexpression)
2567/*!
2568 \overload
2569
2570 Ignores messages created by qDebug(), qInfo() or qWarning(). If the message
2571 matching \a messagePattern
2572 with the corresponding \a type is outputted, it will be removed from the
2573 test log. If the test finished and the message was not outputted,
2574 a test failure is appended to the test log.
2575
2576 \note Invoking this function will only ignore one message. If the message
2577 you want to ignore is output twice, you have to call ignoreMessage() twice,
2578 too.
2579
2580 \since 5.3
2581*/
2582void QTest::ignoreMessage(QtMsgType type, const QRegularExpression &messagePattern)
2583{
2584 QTestLog::ignoreMessage(type, expression: messagePattern);
2585}
2586#endif // QT_CONFIG(regularexpression)
2587
2588/*!
2589 \since 6.3
2590 \overload failOnWarning()
2591
2592 Appends a test failure to the test log if the \a message is output.
2593
2594 \sa failOnWarning()
2595*/
2596void QTest::failOnWarning(const char *message)
2597{
2598 return QTestLog::failOnWarning(msg: message);
2599}
2600
2601#if QT_CONFIG(regularexpression)
2602/*!
2603 \since 6.3
2604
2605 Appends a test failure to the test log for each warning that matches
2606 \a messagePattern.
2607
2608 The test function will continue execution when a failure is added. To abort
2609 the test instead, you can check \l currentTestFailed() and return early if
2610 it's \c true.
2611
2612 For each warning, the first pattern that matches will cause a failure,
2613 and the remaining patterns will be ignored.
2614
2615 All patterns are cleared at the end of each test function.
2616
2617 \code
2618 void FileTest::loadFiles()
2619 {
2620 QTest::failOnWarning(QRegularExpression("^Failed to load"));
2621
2622 // Each of these will cause a test failure:
2623 qWarning() << "Failed to load image";
2624 qWarning() << "Failed to load video";
2625 }
2626 \endcode
2627
2628 To fail every test that triggers a given warning, pass a suitable regular
2629 expression to this function in \l {Creating a Test}{init()}:
2630
2631 \code
2632 void FileTest::init()
2633 {
2634 QTest::failOnWarning(QRegularExpression(".?"));
2635 }
2636 \endcode
2637
2638 \note \l ignoreMessage() takes precedence over this function, so any
2639 warnings that match a pattern given to both \c ignoreMessage() and
2640 \c failOnWarning() will be ignored.
2641
2642 \sa {Qt Test Environment Variables}{QTEST_FATAL_FAIL}
2643*/
2644void QTest::failOnWarning(const QRegularExpression &messagePattern)
2645{
2646 QTestLog::failOnWarning(expression: messagePattern);
2647}
2648#endif // QT_CONFIG(regularexpression)
2649
2650/*! \internal
2651*/
2652
2653#ifdef Q_OS_WIN
2654static inline bool isWindowsBuildDirectory(const QString &dirName)
2655{
2656 return dirName.compare("Debug"_L1, Qt::CaseInsensitive) == 0
2657 || dirName.compare("Release"_L1, Qt::CaseInsensitive) == 0;
2658}
2659#endif
2660
2661#if QT_CONFIG(temporaryfile)
2662/*!
2663 Extracts a directory from resources to disk. The content is extracted
2664 recursively to a temporary folder. The extracted content is removed
2665 automatically once the last reference to the return value goes out of scope.
2666
2667 \a dirName is the name of the directory to extract from resources.
2668
2669 Returns the temporary directory where the data was extracted or null in case of
2670 errors.
2671*/
2672QSharedPointer<QTemporaryDir> QTest::qExtractTestData(const QString &dirName)
2673{
2674 QSharedPointer<QTemporaryDir> result; // null until success, then == tempDir
2675
2676 QSharedPointer<QTemporaryDir> tempDir = QSharedPointer<QTemporaryDir>::create();
2677
2678 tempDir->setAutoRemove(true);
2679
2680 if (!tempDir->isValid())
2681 return result;
2682
2683 const QString dataPath = tempDir->path();
2684 const QString resourcePath = u':' + dirName;
2685 const QFileInfo fileInfo(resourcePath);
2686
2687 if (!fileInfo.isDir()) {
2688 qWarning(msg: "Resource path '%s' is not a directory.", qPrintable(resourcePath));
2689 return result;
2690 }
2691
2692 QDirIterator it(resourcePath, QDirIterator::Subdirectories);
2693 if (!it.hasNext()) {
2694 qWarning(msg: "Resource directory '%s' is empty.", qPrintable(resourcePath));
2695 return result;
2696 }
2697
2698 while (it.hasNext()) {
2699 QFileInfo fileInfo = it.nextFileInfo();
2700
2701 if (!fileInfo.isDir()) {
2702 const QString destination = dataPath + u'/' + QStringView{fileInfo.filePath()}.mid(pos: resourcePath.size());
2703 QFileInfo destinationFileInfo(destination);
2704 QDir().mkpath(dirPath: destinationFileInfo.path());
2705 if (!QFile::copy(fileName: fileInfo.filePath(), newName: destination)) {
2706 qWarning(msg: "Failed to copy '%s'.", qPrintable(fileInfo.filePath()));
2707 return result;
2708 }
2709 if (!QFile::setPermissions(filename: destination, permissionSpec: QFile::ReadUser | QFile::WriteUser | QFile::ReadGroup)) {
2710 qWarning(msg: "Failed to set permissions on '%s'.", qPrintable(destination));
2711 return result;
2712 }
2713 }
2714 }
2715
2716 result = std::move(tempDir);
2717
2718 return result;
2719}
2720#endif // QT_CONFIG(temporaryfile)
2721
2722/*! \internal
2723*/
2724
2725QString QTest::qFindTestData(const QString& base, const char *file, int line, const char *builddir,
2726 const char *sourcedir)
2727{
2728 QString found;
2729
2730 // Testdata priorities:
2731
2732 // 1. relative to test binary.
2733 if (qApp) {
2734 QDir binDirectory(QCoreApplication::applicationDirPath());
2735 if (binDirectory.exists(name: base)) {
2736 found = binDirectory.absoluteFilePath(fileName: base);
2737 }
2738#ifdef Q_OS_WIN
2739 // Windows: The executable is typically located in one of the
2740 // 'Release' or 'Debug' directories.
2741 else if (isWindowsBuildDirectory(binDirectory.dirName())
2742 && binDirectory.cdUp() && binDirectory.exists(base)) {
2743 found = binDirectory.absoluteFilePath(base);
2744 }
2745#endif // Q_OS_WIN
2746 else if (QTestLog::verboseLevel() >= 2) {
2747 const QString candidate = QDir::toNativeSeparators(pathName: QCoreApplication::applicationDirPath() + u'/' + base);
2748 QTestLog::info(qPrintable("testdata %1 not found relative to test binary [%2]; "
2749 "checking next location"_L1.arg(base, candidate)),
2750 file, line);
2751 }
2752 }
2753
2754 // 2. installed path.
2755 if (found.isEmpty()) {
2756 const char *testObjectName = QTestResult::currentTestObjectName();
2757 if (testObjectName) {
2758 const QString testsPath = QLibraryInfo::path(p: QLibraryInfo::TestsPath);
2759 const QString candidate = "%1/%2/%3"_L1
2760 .arg(args: testsPath, args: QFile::decodeName(localFileName: testObjectName).toLower(), args: base);
2761 if (QFileInfo::exists(file: candidate)) {
2762 found = candidate;
2763 } else if (QTestLog::verboseLevel() >= 2) {
2764 QTestLog::info(qPrintable("testdata %1 not found in tests install path [%2]; "
2765 "checking next location"_L1
2766 .arg(base, QDir::toNativeSeparators(candidate))),
2767 file, line);
2768 }
2769 }
2770 }
2771
2772 // 3. relative to test source.
2773 if (found.isEmpty() && qstrncmp(str1: file, str2: ":/", len: 2) != 0) {
2774 // srcdir is the directory containing the calling source file.
2775 QFileInfo srcdir(QFileInfo(QFile::decodeName(localFileName: file)).path());
2776
2777 // If the srcdir is relative, that means it is relative to the current working
2778 // directory of the compiler at compile time, which should be passed in as `builddir'.
2779 if (!srcdir.isAbsolute() && builddir)
2780 srcdir.setFile(QFile::decodeName(localFileName: builddir) + u'/' + srcdir.filePath());
2781
2782 const QString canonicalPath = srcdir.canonicalFilePath();
2783 const QString candidate = "%1/%2"_L1.arg(args: canonicalPath, args: base);
2784 if (!canonicalPath.isEmpty() && QFileInfo::exists(file: candidate)) {
2785 found = candidate;
2786 } else if (QTestLog::verboseLevel() >= 2) {
2787 QTestLog::info(qPrintable(
2788 "testdata %1 not found relative to source path [%2]"_L1
2789 .arg(base, QDir::toNativeSeparators(candidate))),
2790 file, line);
2791 }
2792 }
2793
2794 // 4. Try resources
2795 if (found.isEmpty()) {
2796 const QString candidate = ":/%1"_L1.arg(args: base);
2797 if (QFileInfo::exists(file: candidate)) {
2798 found = candidate;
2799 } else if (QTestLog::verboseLevel() >= 2) {
2800 QTestLog::info(qPrintable(
2801 "testdata %1 not found in resources [%2]"_L1
2802 .arg(base, QDir::toNativeSeparators(candidate))),
2803 file, line);
2804 }
2805 }
2806
2807 // 5. Try current directory
2808 if (found.isEmpty()) {
2809 const QString candidate = QDir::currentPath() + u'/' + base;
2810 if (QFileInfo::exists(file: candidate)) {
2811 found = candidate;
2812 } else if (QTestLog::verboseLevel() >= 2) {
2813 QTestLog::info(qPrintable(
2814 "testdata %1 not found in current directory [%2]"_L1
2815 .arg(base, QDir::toNativeSeparators(candidate))),
2816 file, line);
2817 }
2818 }
2819
2820 // 6. Try main source directory
2821 if (found.isEmpty()) {
2822 const QString candidate = QTest::mainSourcePath % u'/' % base;
2823 if (QFileInfo::exists(file: candidate)) {
2824 found = candidate;
2825 } else if (QTestLog::verboseLevel() >= 2) {
2826 QTestLog::info(qPrintable(
2827 "testdata %1 not found in main source directory [%2]"_L1
2828 .arg(base, QDir::toNativeSeparators(candidate))),
2829 file, line);
2830 }
2831 }
2832
2833 // 7. Try the supplied source directory
2834 if (found.isEmpty() && sourcedir) {
2835 const QString candidate = QFile::decodeName(localFileName: sourcedir) % u'/' % base;
2836 if (QFileInfo::exists(file: candidate)) {
2837 found = candidate;
2838 } else if (QTestLog::verboseLevel() >= 2) {
2839 QTestLog::info(qPrintable(
2840 "testdata %1 not found in supplied source directory [%2]"_L1
2841 .arg(base, QDir::toNativeSeparators(candidate))),
2842 file, line);
2843 }
2844 }
2845
2846
2847 if (found.isEmpty()) {
2848 QTestLog::warn(qPrintable(
2849 "testdata %1 could not be located!"_L1.arg(base)),
2850 file, line);
2851 } else if (QTestLog::verboseLevel() >= 1) {
2852 QTestLog::info(qPrintable(
2853 "testdata %1 was located at %2"_L1.arg(base, QDir::toNativeSeparators(found))),
2854 file, line);
2855 }
2856
2857 return found;
2858}
2859
2860/*! \internal
2861*/
2862QString QTest::qFindTestData(const char *base, const char *file, int line, const char *builddir,
2863 const char *sourcedir)
2864{
2865 return qFindTestData(base: QFile::decodeName(localFileName: base), file, line, builddir, sourcedir);
2866}
2867
2868/*! \internal
2869*/
2870void *QTest::qData(const char *tagName, int typeId)
2871{
2872 return fetchData(data: QTestResult::currentTestData(), tagName, typeId);
2873}
2874
2875/*! \internal
2876*/
2877void *QTest::qGlobalData(const char *tagName, int typeId)
2878{
2879 return fetchData(data: QTestResult::currentGlobalTestData(), tagName, typeId);
2880}
2881
2882/*! \internal
2883*/
2884void *QTest::qElementData(const char *tagName, int metaTypeId)
2885{
2886 QTEST_ASSERT(tagName);
2887 QTestData *data = QTestResult::currentTestData();
2888 QTEST_ASSERT(data);
2889 QTEST_ASSERT(data->parent());
2890
2891 int idx = data->parent()->indexOf(elementName: tagName);
2892 QTEST_ASSERT(idx != -1);
2893 QTEST_ASSERT(data->parent()->elementTypeId(idx) == metaTypeId);
2894
2895 return data->data(index: data->parent()->indexOf(elementName: tagName));
2896}
2897
2898/*! \internal
2899*/
2900void QTest::addColumnInternal(int id, const char *name)
2901{
2902 QTestTable *tbl = QTestTable::currentTestTable();
2903 QTEST_ASSERT_X(tbl, "QTest::addColumn()", "Cannot add testdata outside of a _data slot.");
2904
2905 tbl->addColumn(elementType: id, elementName: name);
2906}
2907
2908/*!
2909 Appends a new row to the current test data.
2910
2911 The test output will identify the test run with this test data using the
2912 name \a dataTag.
2913
2914 Returns a QTestData reference that can be used to stream in data, one value
2915 for each column in the table.
2916
2917 Example:
2918 \snippet code/src_qtestlib_qtestcase.cpp 20
2919
2920 \note This function can only be called as part of a test's data function
2921 that is invoked by the test framework.
2922
2923 See \l {Chapter 2: Data Driven Testing}{Data Driven Testing} for
2924 a more extensive example.
2925
2926 \sa addRow(), addColumn(), QFETCH()
2927*/
2928QTestData &QTest::newRow(const char *dataTag)
2929{
2930 QTEST_ASSERT_X(dataTag, "QTest::newRow()", "Data tag cannot be null");
2931 QTestTable *tbl = QTestTable::currentTestTable();
2932 QTEST_ASSERT_X(tbl, "QTest::newRow()", "Cannot add testdata outside of a _data slot.");
2933 QTEST_ASSERT_X(tbl->elementCount(), "QTest::newRow()",
2934 "Must add columns before attempting to add rows.");
2935
2936 return *tbl->newData(tag: dataTag);
2937}
2938
2939/*!
2940 \since 5.9
2941
2942 Appends a new row to the current test data.
2943
2944 The function's arguments are passed to qsnprintf() for formatting according
2945 to \a format. See the qvsnprintf() documentation for caveats and
2946 limitations.
2947
2948 The test output will identify the test run with this test data using the
2949 name that results from this formatting.
2950
2951 Returns a QTestData reference that can be used to stream in data, one value
2952 for each column in the table.
2953
2954 Example:
2955 \snippet code/src_qtestlib_qtestcase.cpp addRow
2956
2957 \note This function can only be called as part of a test's data function
2958 that is invoked by the test framework.
2959
2960 See \l {Chapter 2: Data Driven Testing}{Data Driven Testing} for
2961 a more extensive example.
2962
2963 \sa newRow(), addColumn(), QFETCH()
2964*/
2965QTestData &QTest::addRow(const char *format, ...)
2966{
2967 QTEST_ASSERT_X(format, "QTest::addRow()", "Format string cannot be null");
2968 QTestTable *tbl = QTestTable::currentTestTable();
2969 QTEST_ASSERT_X(tbl, "QTest::addRow()", "Cannot add testdata outside of a _data slot.");
2970 QTEST_ASSERT_X(tbl->elementCount(), "QTest::addRow()",
2971 "Must add columns before attempting to add rows.");
2972
2973 char buf[1024];
2974
2975 va_list va;
2976 va_start(va, format);
2977 // we don't care about failures, we accept truncation, as well as trailing garbage.
2978 // Names with more than 1K characters are nonsense, anyway.
2979 (void)qvsnprintf(str: buf, n: sizeof buf, fmt: format, ap: va);
2980 buf[sizeof buf - 1] = '\0';
2981 va_end(va);
2982
2983 return *tbl->newData(tag: buf);
2984}
2985
2986/*! \fn template <typename T> void QTest::addColumn(const char *name, T *dummy = 0)
2987
2988 Adds a column with type \c{T} to the current test data.
2989 \a name is the name of the column. \a dummy is a workaround
2990 for buggy compilers and can be ignored.
2991
2992 To populate the column with values, newRow() can be used. Use
2993 \l QFETCH() to fetch the data in the actual test.
2994
2995 Example:
2996 \snippet code/src_qtestlib_qtestcase.cpp 21
2997
2998 To add custom types to the testdata, the type must be registered with
2999 QMetaType via \l Q_DECLARE_METATYPE().
3000
3001 \note This function can only be used called as part of a test's data
3002 function that is invoked by the test framework.
3003
3004 See \l {Chapter 2: Data Driven Testing}{Data Driven Testing} for
3005 a more extensive example.
3006
3007 \sa QTest::newRow(), QFETCH(), QMetaType
3008*/
3009
3010/*!
3011 Returns the name of the binary that is currently executed.
3012*/
3013const char *QTest::currentAppName()
3014{
3015 return QTestResult::currentAppName();
3016}
3017
3018/*!
3019 Returns the name of the test function that is currently executed.
3020
3021 Example:
3022
3023 \snippet code/src_qtestlib_qtestcase.cpp 22
3024*/
3025const char *QTest::currentTestFunction()
3026{
3027 return QTestResult::currentTestFunction();
3028}
3029
3030/*!
3031 Returns the name of the current test data. If the test doesn't
3032 have any assigned testdata, the function returns \nullptr.
3033*/
3034const char *QTest::currentDataTag()
3035{
3036 return QTestResult::currentDataTag();
3037}
3038
3039/*!
3040 Returns \c true if the current test function has failed, otherwise false.
3041
3042 \sa QTest::currentTestResolved()
3043*/
3044bool QTest::currentTestFailed()
3045{
3046 return QTestResult::currentTestFailed();
3047}
3048
3049/*!
3050 \since 6.5
3051 Returns \c true if the current test function has failed or skipped.
3052
3053 This applies if the test has failed or exercised a skip. When it is true,
3054 the test function should return early. In particular, the \c{QTRY_*} macros
3055 and the test event loop terminate their loops early if executed during the
3056 test function (but not its cleanup()). After a test has called a helper
3057 function that uses this module's macros, it can use this function to test
3058 whether to return early.
3059
3060 \sa QTest::currentTestFailed()
3061*/
3062bool QTest::currentTestResolved()
3063{
3064 return QTestResult::currentTestFailed() || QTestResult::skipCurrentTest();
3065}
3066
3067/*!
3068 \internal
3069 \since 6.4
3070 Returns \c true during the run of the test-function and its set-up.
3071
3072 Used by the \c{QTRY_*} macros and \l QTestEventLoop to check whether to
3073 return when QTest::currentTestResolved() is true.
3074*/
3075bool QTest::runningTest()
3076{
3077 return QTest::inTestFunction;
3078}
3079
3080/*! \internal
3081*/
3082QObject *QTest::testObject()
3083{
3084 return currentTestObject;
3085}
3086
3087/*! \internal
3088*/
3089void QTest::setMainSourcePath(const char *file, const char *builddir)
3090{
3091 QString mainSourceFile = QFile::decodeName(localFileName: file);
3092 QFileInfo fi;
3093 if (builddir)
3094 fi.setFile(dir: QDir(QFile::decodeName(localFileName: builddir)), file: mainSourceFile);
3095 else
3096 fi.setFile(mainSourceFile);
3097 QTest::mainSourcePath = fi.absolutePath();
3098}
3099
3100#if QT_DEPRECATED_SINCE(6, 4)
3101/*! \internal
3102 \deprecated [6.4]
3103 This function is called by various specializations of QTest::qCompare
3104 to decide whether to report a failure and to produce verbose test output.
3105
3106 The failureMsg parameter can be null, in which case a default message
3107 will be output if the compare fails. If the compare succeeds, failureMsg
3108 will not be output.
3109
3110 Using this function is not optimal, because it requires the string
3111 representations of \a actualVal and \a expectedVal to be pre-calculated,
3112 even though they will be used only if the comparison fails. Prefer using the
3113 \l compare_helper() overload that takes qxp::function_ref() for such cases.
3114
3115 If the caller creates a custom failure message showing the compared values,
3116 or if those values cannot be stringified, use the overload of the function
3117 that takes no \a actualVal and \a expecetedVal parameters.
3118*/
3119bool QTest::compare_helper(bool success, const char *failureMsg,
3120 char *actualVal, char *expectedVal,
3121 const char *actual, const char *expected,
3122 const char *file, int line)
3123{
3124 return QTestResult::compare(success, failureMsg, val1: actualVal, val2: expectedVal,
3125 actual, expected, file, line);
3126}
3127#endif // QT_DEPRECATED_SINCE(6, 4)
3128
3129/*! \internal
3130 \since 6.4
3131 This function is called by various specializations of QTest::qCompare
3132 to decide whether to report a failure and to produce verbose test output.
3133
3134 The \a failureMsg parameter can be \c {nullptr}, in which case a default
3135 message will be output if the compare fails. If the comparison succeeds,
3136 \a failureMsg will not be output.
3137
3138 This overload of the function uses qxp::function_ref to defer conversion of
3139 \a actualVal and \a expectedVal to strings until that is really needed
3140 (when the comparison fails). This speeds up test case execution on success.
3141*/
3142bool QTest::compare_helper(bool success, const char *failureMsg,
3143 qxp::function_ref<const char *()> actualVal,
3144 qxp::function_ref<const char *()> expectedVal,
3145 const char *actual, const char *expected,
3146 const char *file, int line)
3147{
3148 return QTestResult::reportResult(success, lhs: actualVal, rhs: expectedVal, lhsExpr: actual, rhsExpr: expected,
3149 op: QTest::ComparisonOperation::CustomCompare,
3150 file, line, failureMessage: failureMsg);
3151}
3152
3153/*! \internal
3154 \since 6.4
3155 This function is called by various specializations of QTest::qCompare
3156 to decide whether to report a failure and to produce verbose test output.
3157
3158 This overload should be used when there is no string representation of
3159 actual and expected values, so only the \a failureMsg is shown when the
3160 comparison fails. Because of that, \a failureMsg can't be \c {nullptr}.
3161 If the comparison succeeds, \a failureMsg will not be output.
3162*/
3163bool QTest::compare_helper(bool success, const char *failureMsg, const char *actual,
3164 const char *expected, const char *file, int line)
3165{
3166 return QTestResult::compare(success, failureMsg, actual, expeceted: expected, file, line);
3167}
3168
3169template <typename T>
3170static bool floatingCompare(const T &actual, const T &expected)
3171{
3172 switch (qFpClassify(expected))
3173 {
3174 case FP_INFINITE:
3175 return (expected < 0) == (actual < 0) && qFpClassify(actual) == FP_INFINITE;
3176 case FP_NAN:
3177 return qFpClassify(actual) == FP_NAN;
3178 default:
3179 if (!qFuzzyIsNull(expected))
3180 return qFuzzyCompare(actual, expected);
3181 Q_FALLTHROUGH();
3182 case FP_SUBNORMAL: // subnormal is always fuzzily null
3183 case FP_ZERO:
3184 return qFuzzyIsNull(actual);
3185 }
3186}
3187
3188/*! \fn bool QTest::qCompare(const qfloat16 &t1, const qfloat16 &t2, const char *actual, const char *expected, const char *file, int line)
3189 \internal
3190*/
3191bool QTest::qCompare(qfloat16 const &t1, qfloat16 const &t2, const char *actual, const char *expected,
3192 const char *file, int line)
3193{
3194 return compare_helper(success: floatingCompare(actual: t1, expected: t2),
3195 failureMsg: "Compared qfloat16s are not the same (fuzzy compare)",
3196 actualVal: [&t1] { return toString(t1); }, expectedVal: [&t2] { return toString(t2); },
3197 actual, expected, file, line);
3198}
3199
3200/*! \fn bool QTest::qCompare(const float &t1, const float &t2, const char *actual, const char *expected, const char *file, int line)
3201 \internal
3202*/
3203bool QTest::qCompare(float const &t1, float const &t2, const char *actual, const char *expected,
3204 const char *file, int line)
3205{
3206 return QTestResult::compare(success: floatingCompare(actual: t1, expected: t2),
3207 failureMsg: "Compared floats are not the same (fuzzy compare)",
3208 val1: t1, val2: t2, actual, expected, file, line);
3209}
3210
3211/*! \fn bool QTest::qCompare(const double &t1, const double &t2, const char *actual, const char *expected, const char *file, int line)
3212 \internal
3213*/
3214bool QTest::qCompare(double const &t1, double const &t2, const char *actual, const char *expected,
3215 const char *file, int line)
3216{
3217 return QTestResult::compare(success: floatingCompare(actual: t1, expected: t2),
3218 failureMsg: "Compared doubles are not the same (fuzzy compare)",
3219 val1: t1, val2: t2, actual, expected, file, line);
3220}
3221
3222/*! \fn bool QTest::qCompare(int t1, int t2, const char *actual, const char *expected, const char *file, int line)
3223 \internal
3224 \since 5.14
3225*/
3226bool QTest::qCompare(int t1, int t2, const char *actual, const char *expected,
3227 const char *file, int line)
3228{
3229 return QTestResult::compare(success: t1 == t2,
3230 failureMsg: "Compared values are not the same",
3231 val1: t1, val2: t2, actual, expected, file, line);
3232}
3233
3234#if QT_POINTER_SIZE == 8
3235/*! \fn bool QTest::qCompare(qsizetype t1, qsizetype t2, const char *actual, const char *expected, const char *file, int line)
3236 \internal
3237 \since 6.0
3238*/
3239
3240bool QTest::qCompare(qsizetype t1, qsizetype t2, const char *actual, const char *expected,
3241 const char *file, int line)
3242{
3243 return QTestResult::compare(success: t1 == t2,
3244 failureMsg: "Compared values are not the same",
3245 val1: t1, val2: t2, actual, expected, file, line);
3246}
3247#endif // QT_POINTER_SIZE == 8
3248
3249/*! \fn bool QTest::qCompare(unsigned t1, unsigned t2, const char *actual, const char *expected, const char *file, int line)
3250 \internal
3251 \since 5.14
3252*/
3253bool QTest::qCompare(unsigned t1, unsigned t2, const char *actual, const char *expected,
3254 const char *file, int line)
3255{
3256 return QTestResult::compare(success: t1 == t2,
3257 failureMsg: "Compared values are not the same",
3258 val1: t1, val2: t2, actual, expected, file, line);
3259}
3260
3261/*! \fn bool QTest::qCompare(QStringView t1, QStringView t2, const char *actual, const char *expected, const char *file, int line)
3262 \internal
3263 \since 5.14
3264*/
3265bool QTest::qCompare(QStringView t1, QStringView t2, const char *actual, const char *expected,
3266 const char *file, int line)
3267{
3268 return QTestResult::compare(success: t1 == t2,
3269 failureMsg: "Compared values are not the same",
3270 val1: t1, val2: t2, actual, expected, file, line);
3271}
3272
3273/*!
3274 \internal
3275 \since 5.14
3276*/
3277bool QTest::qCompare(QStringView t1, const QLatin1StringView &t2, const char *actual, const char *expected,
3278 const char *file, int line)
3279{
3280 return QTestResult::compare(success: t1 == t2,
3281 failureMsg: "Compared values are not the same",
3282 val1: t1, val2: t2, actual, expected, file, line);
3283}
3284
3285/*!
3286 \internal
3287 \since 5.14
3288*/
3289bool QTest::qCompare(const QLatin1StringView &t1, QStringView t2, const char *actual, const char *expected,
3290 const char *file, int line)
3291{
3292 return QTestResult::compare(success: t1 == t2,
3293 failureMsg: "Compared values are not the same",
3294 val1: t1, val2: t2, actual, expected, file, line);
3295}
3296
3297/*! \fn bool QTest::qCompare(const QString &t1, const QString &t2, const char *actual, const char *expected, const char *file, int line)
3298 \internal
3299 \since 5.14
3300*/
3301
3302/*! \fn bool QTest::qCompare(const QString &t1, const QLatin1StringView &t2, const char *actual, const char *expected, const char *file, int line)
3303 \internal
3304 \since 5.14
3305*/
3306
3307/*! \fn bool QTest::qCompare(const QLatin1StringView &t1, const QString &t2, const char *actual, const char *expected, const char *file, int line)
3308 \internal
3309 \since 5.14
3310*/
3311
3312/*! \fn bool QTest::qCompare(const double &t1, const float &t2, const char *actual, const char *expected, const char *file, int line)
3313 \internal
3314*/
3315
3316/*! \fn bool QTest::qCompare(const float &t1, const double &t2, const char *actual, const char *expected, const char *file, int line)
3317 \internal
3318*/
3319
3320#define TO_STRING_IMPL(TYPE, FORMAT) \
3321template <> Q_TESTLIB_EXPORT char *QTest::toString<TYPE>(const TYPE &t) \
3322{ \
3323 char *msg = new char[128]; \
3324 qsnprintf(msg, 128, #FORMAT, t); \
3325 return msg; \
3326}
3327
3328TO_STRING_IMPL(short, %hd)
3329TO_STRING_IMPL(ushort, %hu)
3330TO_STRING_IMPL(int, %d)
3331TO_STRING_IMPL(uint, %u)
3332TO_STRING_IMPL(long, %ld)
3333TO_STRING_IMPL(ulong, %lu)
3334#if defined(Q_OS_WIN)
3335TO_STRING_IMPL(qint64, %I64d)
3336TO_STRING_IMPL(quint64, %I64u)
3337#else
3338TO_STRING_IMPL(qint64, %lld)
3339TO_STRING_IMPL(quint64, %llu)
3340#endif
3341TO_STRING_IMPL(bool, %d)
3342TO_STRING_IMPL(signed char, %hhd)
3343TO_STRING_IMPL(unsigned char, %hhu)
3344
3345/*!
3346 \internal
3347
3348 Be consistent about leading 0 in exponent.
3349
3350 POSIX specifies that %e (hence %g when using it) uses at least two digits in
3351 the exponent, requiring a leading 0 on single-digit exponents; (at least)
3352 MinGW includes a leading zero also on an already-two-digit exponent,
3353 e.g. 9e-040, which differs from more usual platforms. So massage that away.
3354*/
3355static void massageExponent(char *text)
3356{
3357 char *p = strchr(s: text, c: 'e');
3358 if (!p)
3359 return;
3360 const char *const end = p + strlen(s: p); // *end is '\0'
3361 p += (p[1] == '-' || p[1] == '+') ? 2 : 1;
3362 if (p[0] != '0' || end - 2 <= p)
3363 return;
3364 // We have a leading 0 on an exponent of at least two more digits
3365 const char *n = p + 1;
3366 while (end - 2 > n && n[0] == '0')
3367 ++n;
3368 memmove(dest: p, src: n, n: end + 1 - n);
3369}
3370
3371// Be consistent about display of infinities and NaNs (snprintf()'s varies,
3372// notably on MinGW, despite POSIX documenting "[-]inf" or "[-]infinity" for %f,
3373// %e and %g, uppercasing for their capital versions; similar for "nan"):
3374#define TO_STRING_FLOAT(TYPE, FORMAT) \
3375template <> Q_TESTLIB_EXPORT char *QTest::toString<TYPE>(const TYPE &t) \
3376{ \
3377 char *msg = new char[128]; \
3378 switch (qFpClassify(t)) { \
3379 case FP_INFINITE: \
3380 qstrncpy(msg, (t < 0 ? "-inf" : "inf"), 128); \
3381 break; \
3382 case FP_NAN: \
3383 qstrncpy(msg, "nan", 128); \
3384 break; \
3385 default: \
3386 qsnprintf(msg, 128, #FORMAT, double(t)); \
3387 massageExponent(msg); \
3388 break; \
3389 } \
3390 return msg; \
3391}
3392
3393TO_STRING_FLOAT(qfloat16, %.3g)
3394TO_STRING_FLOAT(float, %g)
3395TO_STRING_FLOAT(double, %.12g)
3396
3397template <> Q_TESTLIB_EXPORT char *QTest::toString<char>(const char &t)
3398{
3399 unsigned char c = static_cast<unsigned char>(t);
3400 char *msg = new char[16];
3401 switch (c) {
3402 case 0x00:
3403 qstrcpy(dst: msg, src: "'\\0'");
3404 break;
3405 case 0x07:
3406 qstrcpy(dst: msg, src: "'\\a'");
3407 break;
3408 case 0x08:
3409 qstrcpy(dst: msg, src: "'\\b'");
3410 break;
3411 case 0x09:
3412 qstrcpy(dst: msg, src: "'\\t'");
3413 break;
3414 case 0x0a:
3415 qstrcpy(dst: msg, src: "'\\n'");
3416 break;
3417 case 0x0b:
3418 qstrcpy(dst: msg, src: "'\\v'");
3419 break;
3420 case 0x0c:
3421 qstrcpy(dst: msg, src: "'\\f'");
3422 break;
3423 case 0x0d:
3424 qstrcpy(dst: msg, src: "'\\r'");
3425 break;
3426 case 0x22:
3427 qstrcpy(dst: msg, src: "'\\\"'");
3428 break;
3429 case 0x27:
3430 qstrcpy(dst: msg, src: "'\\\''");
3431 break;
3432 case 0x5c:
3433 qstrcpy(dst: msg, src: "'\\\\'");
3434 break;
3435 default:
3436 if (c < 0x20 || c >= 0x7F)
3437 qsnprintf(str: msg, n: 16, fmt: "'\\x%02x'", c);
3438 else
3439 qsnprintf(str: msg, n: 16, fmt: "'%c'" , c);
3440 }
3441 return msg;
3442}
3443
3444/*! \internal
3445*/
3446char *QTest::toString(const char *str)
3447{
3448 if (!str) {
3449 char *msg = new char[1];
3450 *msg = '\0';
3451 return msg;
3452 }
3453 char *msg = new char[strlen(s: str) + 1];
3454 return qstrcpy(dst: msg, src: str);
3455}
3456
3457/*! \internal
3458*/
3459char *QTest::toString(const volatile void *p) // Use volatile to match compare_ptr_helper()
3460{
3461 return QTest::toString(const_cast<const void *>(p));
3462}
3463
3464char *QTest::toString(const void *p)
3465{
3466 char *msg = new char[128];
3467 qsnprintf(str: msg, n: 128, fmt: "%p", p);
3468 return msg;
3469}
3470
3471/*! \internal
3472*/
3473char *QTest::toString(const volatile QObject *vo)
3474{
3475 if (vo == nullptr)
3476 return qstrdup("<null>");
3477
3478 auto *o = const_cast<const QObject*>(vo);
3479 const QString &name = o->objectName();
3480 const char *className = o->metaObject()->className();
3481 char *msg = new char[256];
3482 if (name.isEmpty())
3483 qsnprintf(str: msg, n: 256, fmt: "%s/%p", className, o);
3484 else
3485 qsnprintf(str: msg, n: 256, fmt: "%s/\"%s\"", className, qPrintable(name));
3486 return msg;
3487}
3488
3489/*! \fn char *QTest::toString(const QColor &color)
3490 \internal
3491*/
3492
3493/*! \fn char *QTest::toString(const QRegion &region)
3494 \internal
3495*/
3496
3497/*! \fn char *QTest::toString(const QHostAddress &addr)
3498 \internal
3499*/
3500
3501/*! \fn char *QTest::toString(QNetworkReply::NetworkError code)
3502 \internal
3503*/
3504
3505/*! \fn char *QTest::toString(const QNetworkCookie &cookie)
3506 \internal
3507*/
3508
3509/*! \fn char *QTest::toString(const QList<QNetworkCookie> &list)
3510 \internal
3511*/
3512
3513/*! \internal
3514*/
3515bool QTest::compare_string_helper(const char *t1, const char *t2, const char *actual,
3516 const char *expected, const char *file, int line)
3517{
3518 return compare_helper(success: qstrcmp(str1: t1, str2: t2) == 0, failureMsg: "Compared strings are not the same",
3519 actualVal: [t1] { return toString(str: t1); }, expectedVal: [t2] { return toString(str: t2); },
3520 actual, expected, file, line);
3521}
3522
3523/*!
3524 \namespace QTest::Internal
3525 \internal
3526*/
3527
3528/*! \fn bool QTest::compare_ptr_helper(const volatile void *t1, const volatile void *t2, const char *actual, const char *expected, const char *file, int line)
3529 \internal
3530*/
3531
3532/*! \fn bool QTest::compare_ptr_helper(const volatile void *t1, std::nullptr_t, const char *actual, const char *expected, const char *file, int line)
3533 \internal
3534*/
3535
3536/*! \fn bool QTest::compare_ptr_helper(std::nullptr_t, const volatile void *t2, const char *actual, const char *expected, const char *file, int line)
3537 \internal
3538*/
3539
3540/*! \fn template <typename T1, typename T2> bool QTest::qCompare(const T1 &t1, const T2 &t2, const char *actual, const char *expected, const char *file, int line)
3541 \internal
3542*/
3543
3544/*! \fn bool QTest::qCompare(const QIcon &t1, const QIcon &t2, const char *actual, const char *expected, const char *file, int line)
3545 \internal
3546*/
3547
3548/*! \fn bool QTest::qCompare(const QImage &t1, const QImage &t2, const char *actual, const char *expected, const char *file, int line)
3549 \internal
3550*/
3551
3552/*! \fn bool QTest::qCompare(const QPixmap &t1, const QPixmap &t2, const char *actual, const char *expected, const char *file, int line)
3553 \internal
3554*/
3555
3556/*! \fn template <typename T> bool QTest::qCompare(const T &t1, const T &t2, const char *actual, const char *expected, const char *file, int line)
3557 \internal
3558*/
3559
3560/*! \fn template <typename T> bool QTest::qCompare(const T *t1, const T *t2, const char *actual, const char *expected, const char *file, int line)
3561 \internal
3562*/
3563
3564/*! \fn template <typename T> bool QTest::qCompare(T *t, std::nullptr_t, const char *actual, const char *expected, const char *file, int line)
3565 \internal
3566*/
3567
3568/*! \fn template <typename T> bool QTest::qCompare(std::nullptr_t, T *t, const char *actual, const char *expected, const char *file, int line)
3569 \internal
3570*/
3571
3572/*! \fn template <typename T> bool QTest::qCompare(T *t1, T *t2, const char *actual, const char *expected, const char *file, int line)
3573 \internal
3574*/
3575
3576/*! \fn template <typename T1, typename T2> bool QTest::qCompare(const T1 *t1, const T2 *t2, const char *actual, const char *expected, const char *file, int line)
3577 \internal
3578*/
3579
3580/*! \fn template <typename T1, typename T2> bool QTest::qCompare(T1 *t1, T2 *t2, const char *actual, const char *expected, const char *file, int line)
3581 \internal
3582*/
3583
3584/*! \fn bool QTest::qCompare(const char *t1, const char *t2, const char *actual, const char *expected, const char *file, int line)
3585 \internal
3586*/
3587
3588/*! \fn bool QTest::qCompare(char *t1, char *t2, const char *actual, const char *expected, const char *file, int line)
3589 \internal
3590*/
3591
3592/*! \fn bool QTest::qCompare(char *t1, const char *t2, const char *actual, const char *expected, const char *file, int line)
3593 \internal
3594*/
3595
3596/*! \fn bool QTest::qCompare(const char *t1, char *t2, const char *actual, const char *expected, const char *file, int line)
3597 \internal
3598*/
3599
3600/*! \fn bool QTest::qCompare(const QString &t1, const QLatin1StringView &t2, const char *actual, const char *expected, const char *file, int line)
3601 \internal
3602*/
3603
3604/*! \fn bool QTest::qCompare(const QLatin1StringView &t1, const QString &t2, const char *actual, const char *expected, const char *file, int line)
3605 \internal
3606*/
3607
3608/*! \fn bool QTest::qCompare(const QStringList &t1, const QStringList &t2, const char *actual, const char *expected, const char *file, int line)
3609 \internal
3610*/
3611
3612/*! \fn template <typename T> bool QTest::qCompare(const QList<T> &t1, const QList<T> &t2, const char *actual, const char *expected, const char *file, int line)
3613 \internal
3614*/
3615
3616/*! \fn template <typename T> bool QTest::qCompare(const QFlags<T> &t1, const T &t2, const char *actual, const char *expected, const char *file, int line)
3617 \internal
3618*/
3619
3620/*! \fn template <typename T> bool QTest::qCompare(const QFlags<T> &t1, const int &t2, const char *actual, const char *expected, const char *file, int line)
3621 \internal
3622*/
3623
3624/*! \fn bool QTest::qCompare(const qint64 &t1, const qint32 &t2, const char *actual, const char *expected, const char *file, int line)
3625 \internal
3626*/
3627
3628/*! \fn bool QTest::qCompare(const qint64 &t1, const quint32 &t2, const char *actual, const char *expected, const char *file, int line)
3629 \internal
3630*/
3631
3632/*! \fn bool QTest::qCompare(const quint64 &t1, const quint32 &t2, const char *actual, const char *expected, const char *file, int line)
3633 \internal
3634*/
3635
3636/*! \fn bool QTest::qCompare(const qint32 &t1, const qint64 &t2, const char *actual, const char *expected, const char *file, int line)
3637 \internal
3638*/
3639
3640/*! \fn bool QTest::qCompare(const quint32 &t1, const qint64 &t2, const char *actual, const char *expected, const char *file, int line)
3641 \internal
3642*/
3643
3644/*! \fn bool QTest::qCompare(const quint32 &t1, const quint64 &t2, const char *actual, const char *expected, const char *file, int line)
3645 \internal
3646*/
3647
3648/*! \fn template <typename T> bool QTest::qTest(const T& actual, const char *elementName, const char *actualStr, const char *expected, const char *file, int line)
3649 \internal
3650*/
3651
3652/*! \fn void QTest::sendKeyEvent(KeyAction action, QWidget *widget, Qt::Key code, QString text, Qt::KeyboardModifiers modifier, int delay=-1)
3653 \internal
3654*/
3655
3656/*! \fn void QTest::sendKeyEvent(KeyAction action, QWindow *window, Qt::Key code, QString text, Qt::KeyboardModifiers modifier, int delay=-1)
3657 \internal
3658*/
3659
3660/*! \fn void QTest::sendKeyEvent(KeyAction action, QWidget *widget, Qt::Key code, char ascii, Qt::KeyboardModifiers modifier, int delay=-1)
3661 \internal
3662*/
3663
3664/*! \fn void QTest::sendKeyEvent(KeyAction action, QWindow *window, Qt::Key code, char ascii, Qt::KeyboardModifiers modifier, int delay=-1)
3665 \internal
3666*/
3667
3668/*! \fn void QTest::simulateEvent(QWidget *widget, bool press, int code, Qt::KeyboardModifiers modifier, QString text, bool repeat, int delay=-1)
3669 \internal
3670*/
3671
3672/*! \fn void QTest::simulateEvent(QWindow *window, bool press, int code, Qt::KeyboardModifiers modifier, QString text, bool repeat, int delay=-1)
3673 \internal
3674*/
3675
3676QT_END_NAMESPACE
3677

source code of qtbase/src/testlib/qtestcase.cpp