summaryrefslogtreecommitdiffstats
path: root/src/testlib
diff options
context:
space:
mode:
Diffstat (limited to 'src/testlib')
-rw-r--r--src/testlib/3rdparty/linux_perf_event_p.h2
-rw-r--r--src/testlib/3rdparty/qt_attribution.json22
-rw-r--r--src/testlib/3rdparty/valgrind_p.h568
-rw-r--r--src/testlib/CMakeLists.txt44
-rw-r--r--src/testlib/doc/qttestlib.qdocconf2
-rw-r--r--src/testlib/doc/snippets/CMakeLists.txt2
-rw-r--r--src/testlib/doc/snippets/code/CMakeLists.txt3
-rw-r--r--src/testlib/doc/snippets/code/doc_src_cmakelists.txt2
-rw-r--r--src/testlib/doc/snippets/code/doc_src_qsignalspy.cpp3
-rw-r--r--src/testlib/doc/snippets/code/doc_src_qtestlib.qdoc2
-rw-r--r--src/testlib/doc/snippets/code/doc_src_qttest.cpp6
-rw-r--r--src/testlib/doc/snippets/code/src_corelib_kernel_qtestsupport_core.cpp20
-rw-r--r--src/testlib/doc/snippets/code/src_corelib_kernel_qtestsupport_core_snippet.cpp10
-rw-r--r--src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp5
-rw-r--r--src/testlib/doc/src/qt-webpages.qdoc9
-rw-r--r--src/testlib/doc/src/qt6-changes.qdoc2
-rw-r--r--src/testlib/doc/src/qttest-best-practices.qdoc76
-rw-r--r--src/testlib/doc/src/qttestlib-manual.qdoc532
-rw-r--r--src/testlib/doc/src/qttestlib-tutorial1.qdoc76
-rw-r--r--src/testlib/doc/src/qttestlib-tutorial2.qdoc132
-rw-r--r--src/testlib/doc/src/qttestlib-tutorial3.qdoc75
-rw-r--r--src/testlib/doc/src/qttestlib-tutorial4.qdoc95
-rw-r--r--src/testlib/doc/src/qttestlib-tutorial5.qdoc57
-rw-r--r--src/testlib/doc/src/qttestlib-tutorial6.qdoc50
-rw-r--r--src/testlib/qabstracttestlogger.cpp13
-rw-r--r--src/testlib/qabstracttestlogger_p.h2
-rw-r--r--src/testlib/qappletestlogger.cpp10
-rw-r--r--src/testlib/qasciikey.cpp4
-rw-r--r--src/testlib/qbenchmark.cpp6
-rw-r--r--src/testlib/qbenchmark.h4
-rw-r--r--src/testlib/qbenchmark_p.h2
-rw-r--r--src/testlib/qbenchmarkperfevents.cpp3
-rw-r--r--src/testlib/qbenchmarkvalgrind.cpp5
-rw-r--r--src/testlib/qcomparisontesthelper.cpp22
-rw-r--r--src/testlib/qcomparisontesthelper_p.h373
-rw-r--r--src/testlib/qemulationdetector_p.h2
-rw-r--r--src/testlib/qjunittestlogger.cpp9
-rw-r--r--src/testlib/qplaintestlogger.cpp28
-rw-r--r--src/testlib/qplaintestlogger_p.h2
-rw-r--r--src/testlib/qpropertytesthelper_p.h99
-rw-r--r--src/testlib/qsignaldumper.cpp27
-rw-r--r--src/testlib/qsignalspy.cpp318
-rw-r--r--src/testlib/qsignalspy.h192
-rw-r--r--src/testlib/qsignalspy.qdoc124
-rw-r--r--src/testlib/qtaptestlogger.cpp4
-rw-r--r--src/testlib/qteamcitylogger.cpp14
-rw-r--r--src/testlib/qtest.h365
-rw-r--r--src/testlib/qtest_gui.h5
-rw-r--r--src/testlib/qtest_network.h9
-rw-r--r--src/testlib/qtest_widgets.h9
-rw-r--r--src/testlib/qtestaccessible.h6
-rw-r--r--src/testlib/qtestassert.h5
-rw-r--r--src/testlib/qtestblacklist.cpp16
-rw-r--r--src/testlib/qtestblacklist_p.h2
-rw-r--r--src/testlib/qtestcase.cpp1404
-rw-r--r--src/testlib/qtestcase.h411
-rw-r--r--src/testlib/qtestcase.qdoc187
-rw-r--r--src/testlib/qtestcase_p.h5
-rw-r--r--src/testlib/qtestcrashhandler.cpp663
-rw-r--r--src/testlib/qtestcrashhandler_p.h251
-rw-r--r--src/testlib/qtestevent.qdoc8
-rw-r--r--src/testlib/qtesteventloop.h10
-rw-r--r--src/testlib/qtestlog.cpp25
-rw-r--r--src/testlib/qtestlog_p.h2
-rw-r--r--src/testlib/qtestmouse.h2
-rw-r--r--src/testlib/qtestresult.cpp54
-rw-r--r--src/testlib/qtestresult_p.h6
-rw-r--r--src/testlib/qtesttable.cpp13
-rw-r--r--src/testlib/qtesttostring.h499
-rw-r--r--src/testlib/qtestwheel.h72
-rw-r--r--src/testlib/qttestglobal.h4
-rw-r--r--src/testlib/removed_api.cpp29
-rw-r--r--src/testlib/selfcover.cmake2
73 files changed, 4635 insertions, 2487 deletions
diff --git a/src/testlib/3rdparty/linux_perf_event_p.h b/src/testlib/3rdparty/linux_perf_event_p.h
index 03b3700627..6f034f0e96 100644
--- a/src/testlib/3rdparty/linux_perf_event_p.h
+++ b/src/testlib/3rdparty/linux_perf_event_p.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
/*
* Performance events:
*
diff --git a/src/testlib/3rdparty/qt_attribution.json b/src/testlib/3rdparty/qt_attribution.json
index 4dd844c232..77f58e7d75 100644
--- a/src/testlib/3rdparty/qt_attribution.json
+++ b/src/testlib/3rdparty/qt_attribution.json
@@ -4,16 +4,18 @@
"Name": "Valgrind",
"QDocModule": "qttestlib",
"QtUsage": "Used on Linux ond MacOS in the Qt Test module.",
- "Files": "valgrind_p.h callgrind_p.h",
+ "Comment": { "UpstreamFiles": [ "include/valgrind.h.in", "callgrind/callgrind.h" ],
+ "License": "These two files are BSD; the rest of valgrind is GPL" },
+ "Files": [ "valgrind_p.h", "callgrind_p.h" ],
"Description": "An instrumentation framework for building dynamic analysis tools.",
"Homepage": "http://valgrind.org/",
- "Version": "3.14.0",
+ "Version": "3.22.0",
"License": "BSD 4-clause \"Original\" or \"Old\" License",
"LicenseId": "BSD-4-Clause",
"LicenseFile": "VALGRIND_LICENSE.txt",
- "Copyright": "Copyright (C) 2000-2017 Julian Seward
-Copyright (C) 2003-2017 Josef Weidendorfer."
+ "Copyright": ["Copyright (C) 2000-2017 Julian Seward",
+ "Copyright (C) 2003-2017 Josef Weidendorfer."]
},
{
"Id": "cycle",
@@ -26,8 +28,8 @@ Copyright (C) 2003-2017 Josef Weidendorfer."
"License": "MIT License",
"LicenseId": "MIT",
"LicenseFile": "CYCLE_LICENSE.txt",
- "Copyright": "Copyright (c) 2003, 2006 Matteo Frigo
-Copyright (c) 2003, 2006 Massachusetts Institute of Technology"
+ "Copyright": ["Copyright (c) 2003, 2006 Matteo Frigo",
+ "Copyright (c) 2003, 2006 Massachusetts Institute of Technology"]
},
{
"Id": "linuxperf",
@@ -40,10 +42,10 @@ Copyright (c) 2003, 2006 Massachusetts Institute of Technology"
"Homepage": "https://www.kernel.org",
"Version": "3.7",
"License": "GNU General Public License v2.0 only with Linux Syscall Note",
- "LicenseId": "GPL-2.0 WITH Linux-syscall-note",
+ "LicenseId": "GPL-2.0-only WITH Linux-syscall-note",
"LicenseFile": "LINUX_LICENSE.txt",
- "Copyright": "Copyright (C) 2008-2009, Thomas Gleixner <tglx@linutronix.de>
-Copyright (C) 2008-2011, Red Hat, Inc., Ingo Molnar
-Copyright (C) 2008-2011, Red Hat, Inc., Peter Zijlstra"
+ "Copyright": ["Copyright (C) 2008-2009, Thomas Gleixner <tglx@linutronix.de>",
+ "Copyright (C) 2008-2011, Red Hat, Inc., Ingo Molnar",
+ "Copyright (C) 2008-2011, Red Hat, Inc., Peter Zijlstra"]
}
]
diff --git a/src/testlib/3rdparty/valgrind_p.h b/src/testlib/3rdparty/valgrind_p.h
index 577c8f05e5..f5e5518265 100644
--- a/src/testlib/3rdparty/valgrind_p.h
+++ b/src/testlib/3rdparty/valgrind_p.h
@@ -89,7 +89,7 @@
|| (__VALGRIND_MAJOR__ == 3 && __VALGRIND_MINOR__ >= 6))
*/
#define __VALGRIND_MAJOR__ 3
-#define __VALGRIND_MINOR__ 14
+#define __VALGRIND_MINOR__ 20
#include <stdarg.h>
@@ -110,6 +110,8 @@
*/
#undef PLAT_x86_darwin
#undef PLAT_amd64_darwin
+#undef PLAT_x86_freebsd
+#undef PLAT_amd64_freebsd
#undef PLAT_x86_win32
#undef PLAT_amd64_win64
#undef PLAT_x86_linux
@@ -122,6 +124,7 @@
#undef PLAT_s390x_linux
#undef PLAT_mips32_linux
#undef PLAT_mips64_linux
+#undef PLAT_nanomips_linux
#undef PLAT_x86_solaris
#undef PLAT_amd64_solaris
@@ -130,12 +133,17 @@
# define PLAT_x86_darwin 1
#elif defined(__APPLE__) && defined(__x86_64__)
# define PLAT_amd64_darwin 1
-#elif (defined(__MINGW32__) && !defined(__MINGW64__)) \
+#elif defined(__FreeBSD__) && defined(__i386__)
+# define PLAT_x86_freebsd 1
+#elif defined(__FreeBSD__) && defined(__amd64__)
+# define PLAT_amd64_freebsd 1
+#elif (defined(__MINGW32__) && defined(__i386__)) \
|| defined(__CYGWIN32__) \
|| (defined(_WIN32) && defined(_M_IX86))
# define PLAT_x86_win32 1
-#elif defined(__MINGW64__) \
- || (defined(_WIN64) && defined(_M_X64))
+#elif (defined(__MINGW32__) && defined(__x86_64__)) \
+ || (defined(_WIN32) && defined(_M_X64))
+/* __MINGW32__ and _WIN32 are defined in 64 bit mode as well. */
# define PLAT_amd64_win64 1
#elif defined(__linux__) && defined(__i386__)
# define PLAT_x86_linux 1
@@ -157,8 +165,10 @@
# define PLAT_s390x_linux 1
#elif defined(__linux__) && defined(__mips__) && (__mips==64)
# define PLAT_mips64_linux 1
-#elif defined(__linux__) && defined(__mips__) && (__mips!=64)
+#elif defined(__linux__) && defined(__mips__) && (__mips==32)
# define PLAT_mips32_linux 1
+#elif defined(__linux__) && defined(__nanomips__)
+# define PLAT_nanomips_linux 1
#elif defined(__sun) && defined(__i386__)
# define PLAT_x86_solaris 1
#elif defined(__sun) && defined(__x86_64__)
@@ -254,7 +264,7 @@
#if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) \
|| (defined(PLAT_x86_win32) && defined(__GNUC__)) \
- || defined(PLAT_x86_solaris)
+ || defined(PLAT_x86_solaris) || defined(PLAT_x86_freebsd)
typedef
struct {
@@ -394,6 +404,7 @@ valgrind_do_client_request_expr(uintptr_t _zzq_default, uintptr_t _zzq_request,
#if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) \
|| defined(PLAT_amd64_solaris) \
+ || defined(PLAT_amd64_freebsd) \
|| (defined(PLAT_amd64_win64) && defined(__GNUC__))
typedef
@@ -872,7 +883,8 @@ typedef
/* results = r3 */ \
"lgr %0, 3\n\t" \
: "=d" (_zzq_result) \
- : "a" (&_zzq_args[0]), "0" (_zzq_default) \
+ : "a" (&_zzq_args[0]), \
+ "0" ((unsigned long int)_zzq_default) \
: "cc", "2", "3", "memory" \
); \
_zzq_result; \
@@ -1045,6 +1057,75 @@ typedef
#endif /* PLAT_mips64_linux */
+#if defined(PLAT_nanomips_linux)
+
+typedef
+ struct {
+ unsigned int nraddr; /* where's the code? */
+ }
+ OrigFn;
+/*
+ 8000 c04d srl zero, zero, 13
+ 8000 c05d srl zero, zero, 29
+ 8000 c043 srl zero, zero, 3
+ 8000 c053 srl zero, zero, 19
+*/
+
+#define __SPECIAL_INSTRUCTION_PREAMBLE "srl[32] $zero, $zero, 13 \n\t" \
+ "srl[32] $zero, $zero, 29 \n\t" \
+ "srl[32] $zero, $zero, 3 \n\t" \
+ "srl[32] $zero, $zero, 19 \n\t"
+
+#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \
+ _zzq_default, _zzq_request, \
+ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \
+ __extension__ \
+ ({ volatile unsigned int _zzq_args[6]; \
+ volatile unsigned int _zzq_result; \
+ _zzq_args[0] = (unsigned int)(_zzq_request); \
+ _zzq_args[1] = (unsigned int)(_zzq_arg1); \
+ _zzq_args[2] = (unsigned int)(_zzq_arg2); \
+ _zzq_args[3] = (unsigned int)(_zzq_arg3); \
+ _zzq_args[4] = (unsigned int)(_zzq_arg4); \
+ _zzq_args[5] = (unsigned int)(_zzq_arg5); \
+ __asm__ volatile("move $a7, %1\n\t" /* default */ \
+ "move $t0, %2\n\t" /* ptr */ \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* $a7 = client_request( $t0 ) */ \
+ "or[32] $t0, $t0, $t0\n\t" \
+ "move %0, $a7\n\t" /* result */ \
+ : "=r" (_zzq_result) \
+ : "r" (_zzq_default), "r" (&_zzq_args[0]) \
+ : "$a7", "$t0", "memory"); \
+ _zzq_result; \
+ })
+
+#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \
+ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \
+ volatile unsigned long int __addr; \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ /* $a7 = guest_NRADDR */ \
+ "or[32] $t1, $t1, $t1\n\t" \
+ "move %0, $a7" /*result*/ \
+ : "=r" (__addr) \
+ : \
+ : "$a7"); \
+ _zzq_orig->nraddr = __addr; \
+ }
+
+#define VALGRIND_CALL_NOREDIR_T9 \
+ __SPECIAL_INSTRUCTION_PREAMBLE \
+ /* call-noredir $25 */ \
+ "or[32] $t2, $t2, $t2\n\t"
+
+#define VALGRIND_VEX_INJECT_IR() \
+ do { \
+ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \
+ "or[32] $t3, $t3, $t3\n\t" \
+ ); \
+ } while (0)
+
+#endif
/* Insert assembly code for other platforms here... */
#endif /* NVALGRIND */
@@ -1145,7 +1226,7 @@ typedef
/* ----------------- x86-{linux,darwin,solaris} ---------------- */
#if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) \
- || defined(PLAT_x86_solaris)
+ || defined(PLAT_x86_solaris) || defined(PLAT_x86_freebsd)
/* These regs are trashed by the hidden call. No need to mention eax
as gcc can already see that, plus causes gcc to bomb. */
@@ -1577,7 +1658,7 @@ typedef
/* ---------------- amd64-{linux,darwin,solaris} --------------- */
#if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) \
- || defined(PLAT_amd64_solaris)
+ || defined(PLAT_amd64_solaris) || defined(PLAT_amd64_freebsd)
/* ARGREGS: rdi rsi rdx rcx r8 r9 (the rest on stack in R-to-L order) */
@@ -4667,7 +4748,7 @@ typedef
"lgr 1,%1\n\t" /* copy the argvec pointer in r1 */ \
"lgr 7,11\n\t" \
"lgr 11,%2\n\t" \
- ".cfi_def_cfa r11, 0\n\t"
+ ".cfi_def_cfa 11, 0\n\t"
# define VALGRIND_CFI_EPILOGUE \
"lgr 11, 7\n\t" \
".cfi_restore_state\n\t"
@@ -4687,8 +4768,16 @@ typedef
r14 in s390_irgen_noredir (VEX/priv/guest_s390_irgen.c) to give the
function a proper return address. All others are ABI defined call
clobbers. */
-#define __CALLER_SAVED_REGS "0","1","2","3","4","5","14", \
- "f0","f1","f2","f3","f4","f5","f6","f7"
+#if defined(__VX__) || defined(__S390_VX__)
+#define __CALLER_SAVED_REGS "0", "1", "2", "3", "4", "5", "14", \
+ "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", \
+ "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", \
+ "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23", \
+ "v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31"
+#else
+#define __CALLER_SAVED_REGS "0", "1", "2", "3", "4", "5", "14", \
+ "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7"
+#endif
/* Nb: Although r11 is modified in the asm snippets below (inside
VALGRIND_CFI_PROLOGUE) it is not listed in the clobber section, for
@@ -4710,9 +4799,9 @@ typedef
"aghi 15,-160\n\t" \
"lg 1, 0(1)\n\t" /* target->r1 */ \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,160\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "d" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \
@@ -4734,9 +4823,9 @@ typedef
"lg 2, 8(1)\n\t" \
"lg 1, 0(1)\n\t" \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,160\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \
@@ -4759,9 +4848,9 @@ typedef
"lg 3,16(1)\n\t" \
"lg 1, 0(1)\n\t" \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,160\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \
@@ -4786,9 +4875,9 @@ typedef
"lg 4,24(1)\n\t" \
"lg 1, 0(1)\n\t" \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,160\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \
@@ -4815,9 +4904,9 @@ typedef
"lg 5,32(1)\n\t" \
"lg 1, 0(1)\n\t" \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,160\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \
@@ -4846,9 +4935,9 @@ typedef
"lg 6,40(1)\n\t" \
"lg 1, 0(1)\n\t" \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,160\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
@@ -4880,9 +4969,9 @@ typedef
"mvc 160(8,15), 48(1)\n\t" \
"lg 1, 0(1)\n\t" \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,168\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
@@ -4916,9 +5005,9 @@ typedef
"mvc 168(8,15), 56(1)\n\t" \
"lg 1, 0(1)\n\t" \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,176\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
@@ -4954,9 +5043,9 @@ typedef
"mvc 176(8,15), 64(1)\n\t" \
"lg 1, 0(1)\n\t" \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,184\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
@@ -4994,9 +5083,9 @@ typedef
"mvc 184(8,15), 72(1)\n\t" \
"lg 1, 0(1)\n\t" \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,192\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
@@ -5036,9 +5125,9 @@ typedef
"mvc 192(8,15), 80(1)\n\t" \
"lg 1, 0(1)\n\t" \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,200\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
@@ -5080,9 +5169,9 @@ typedef
"mvc 200(8,15), 88(1)\n\t" \
"lg 1, 0(1)\n\t" \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,208\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
@@ -5126,9 +5215,9 @@ typedef
"mvc 208(8,15), 96(1)\n\t" \
"lg 1, 0(1)\n\t" \
VALGRIND_CALL_NOREDIR_R1 \
- "lgr %0, 2\n\t" \
"aghi 15,216\n\t" \
VALGRIND_CFI_EPILOGUE \
+ "lgr %0, 2\n\t" \
: /*out*/ "=d" (_res) \
: /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \
: /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \
@@ -5678,6 +5767,422 @@ typedef
#endif /* PLAT_mips32_linux */
+/* ------------------------- nanomips-linux -------------------- */
+
+#if defined(PLAT_nanomips_linux)
+
+/* These regs are trashed by the hidden call. */
+#define __CALLER_SAVED_REGS "$t4", "$t5", "$a0", "$a1", "$a2", \
+"$a3", "$a4", "$a5", "$a6", "$a7", "$t0", "$t1", "$t2", "$t3", \
+"$t8","$t9", "$at"
+
+/* These CALL_FN_ macros assume that on mips-linux, sizeof(unsigned
+ long) == 4. */
+
+#define CALL_FN_W_v(lval, orig) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[1]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_W(lval, orig, arg1) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[2]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WW(lval, orig, arg1,arg2) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[3]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[4]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ "lw $a2,12(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[5]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ "lw $a2,12(%1)\n\t" \
+ "lw $a3,16(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[6]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ "lw $a2,12(%1)\n\t" \
+ "lw $a3,16(%1)\n\t" \
+ "lw $a4,20(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[7]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ "lw $a2,12(%1)\n\t" \
+ "lw $a3,16(%1)\n\t" \
+ "lw $a4,20(%1)\n\t" \
+ "lw $a5,24(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[8]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ "lw $a2,12(%1)\n\t" \
+ "lw $a3,16(%1)\n\t" \
+ "lw $a4,20(%1)\n\t" \
+ "lw $a5,24(%1)\n\t" \
+ "lw $a6,28(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[9]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ __asm__ volatile( \
+ "lw $t9, 0(%1)\n\t" \
+ "lw $a0, 4(%1)\n\t" \
+ "lw $a1, 8(%1)\n\t" \
+ "lw $a2,12(%1)\n\t" \
+ "lw $a3,16(%1)\n\t" \
+ "lw $a4,20(%1)\n\t" \
+ "lw $a5,24(%1)\n\t" \
+ "lw $a6,28(%1)\n\t" \
+ "lw $a7,32(%1)\n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0\n" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[10]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ __asm__ volatile( \
+ "addiu $sp, $sp, -16 \n\t" \
+ "lw $t9,36(%1) \n\t" \
+ "sw $t9, 0($sp) \n\t" \
+ "lw $t9, 0(%1) \n\t" \
+ "lw $a0, 4(%1) \n\t" \
+ "lw $a1, 8(%1) \n\t" \
+ "lw $a2,12(%1) \n\t" \
+ "lw $a3,16(%1) \n\t" \
+ "lw $a4,20(%1) \n\t" \
+ "lw $a5,24(%1) \n\t" \
+ "lw $a6,28(%1) \n\t" \
+ "lw $a7,32(%1) \n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0 \n\t" \
+ "addiu $sp, $sp, 16 \n\t" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \
+ arg7,arg8,arg9,arg10) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[11]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ __asm__ volatile( \
+ "addiu $sp, $sp, -16 \n\t" \
+ "lw $t9,36(%1) \n\t" \
+ "sw $t9, 0($sp) \n\t" \
+ "lw $t9,40(%1) \n\t" \
+ "sw $t9, 4($sp) \n\t" \
+ "lw $t9, 0(%1) \n\t" \
+ "lw $a0, 4(%1) \n\t" \
+ "lw $a1, 8(%1) \n\t" \
+ "lw $a2,12(%1) \n\t" \
+ "lw $a3,16(%1) \n\t" \
+ "lw $a4,20(%1) \n\t" \
+ "lw $a5,24(%1) \n\t" \
+ "lw $a6,28(%1) \n\t" \
+ "lw $a7,32(%1) \n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0 \n\t" \
+ "addiu $sp, $sp, 16 \n\t" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \
+ arg6,arg7,arg8,arg9,arg10, \
+ arg11) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[12]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ __asm__ volatile( \
+ "addiu $sp, $sp, -16 \n\t" \
+ "lw $t9,36(%1) \n\t" \
+ "sw $t9, 0($sp) \n\t" \
+ "lw $t9,40(%1) \n\t" \
+ "sw $t9, 4($sp) \n\t" \
+ "lw $t9,44(%1) \n\t" \
+ "sw $t9, 8($sp) \n\t" \
+ "lw $t9, 0(%1) \n\t" \
+ "lw $a0, 4(%1) \n\t" \
+ "lw $a1, 8(%1) \n\t" \
+ "lw $a2,12(%1) \n\t" \
+ "lw $a3,16(%1) \n\t" \
+ "lw $a4,20(%1) \n\t" \
+ "lw $a5,24(%1) \n\t" \
+ "lw $a6,28(%1) \n\t" \
+ "lw $a7,32(%1) \n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0 \n\t" \
+ "addiu $sp, $sp, 16 \n\t" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \
+ arg6,arg7,arg8,arg9,arg10, \
+ arg11,arg12) \
+ do { \
+ volatile OrigFn _orig = (orig); \
+ volatile unsigned long _argvec[13]; \
+ volatile unsigned long _res; \
+ _argvec[0] = (unsigned long)_orig.nraddr; \
+ _argvec[1] = (unsigned long)(arg1); \
+ _argvec[2] = (unsigned long)(arg2); \
+ _argvec[3] = (unsigned long)(arg3); \
+ _argvec[4] = (unsigned long)(arg4); \
+ _argvec[5] = (unsigned long)(arg5); \
+ _argvec[6] = (unsigned long)(arg6); \
+ _argvec[7] = (unsigned long)(arg7); \
+ _argvec[8] = (unsigned long)(arg8); \
+ _argvec[9] = (unsigned long)(arg9); \
+ _argvec[10] = (unsigned long)(arg10); \
+ _argvec[11] = (unsigned long)(arg11); \
+ _argvec[12] = (unsigned long)(arg12); \
+ __asm__ volatile( \
+ "addiu $sp, $sp, -16 \n\t" \
+ "lw $t9,36(%1) \n\t" \
+ "sw $t9, 0($sp) \n\t" \
+ "lw $t9,40(%1) \n\t" \
+ "sw $t9, 4($sp) \n\t" \
+ "lw $t9,44(%1) \n\t" \
+ "sw $t9, 8($sp) \n\t" \
+ "lw $t9,48(%1) \n\t" \
+ "sw $t9,12($sp) \n\t" \
+ "lw $t9, 0(%1) \n\t" \
+ "lw $a0, 4(%1) \n\t" \
+ "lw $a1, 8(%1) \n\t" \
+ "lw $a2,12(%1) \n\t" \
+ "lw $a3,16(%1) \n\t" \
+ "lw $a4,20(%1) \n\t" \
+ "lw $a5,24(%1) \n\t" \
+ "lw $a6,28(%1) \n\t" \
+ "lw $a7,32(%1) \n\t" \
+ VALGRIND_CALL_NOREDIR_T9 \
+ "move %0, $a0 \n\t" \
+ "addiu $sp, $sp, 16 \n\t" \
+ : /*out*/ "=r" (_res) \
+ : /*in*/ "r" (&_argvec[0]) \
+ : /*trash*/ "memory", __CALLER_SAVED_REGS \
+ ); \
+ lval = (__typeof__(lval)) _res; \
+ } while (0)
+
+#endif /* PLAT_nanomips_linux */
+
/* ------------------------- mips64-linux ------------------------- */
#if defined(PLAT_mips64_linux)
@@ -6146,6 +6651,10 @@ typedef
command. */
VG_USERREQ__GDB_MONITOR_COMMAND = 0x1202,
+ /* Allows the client program to change a dynamic command line
+ option. */
+ VG_USERREQ__CLO_CHANGE = 0x1203,
+
/* These are useful and can be interpreted by any tool that
tracks malloc() et al, by using vg_replace_malloc.c. */
VG_USERREQ__MALLOCLIKE_BLOCK = 0x1301,
@@ -6628,6 +7137,14 @@ VALGRIND_PRINTF_BACKTRACE(const char *format, ...)
command, 0, 0, 0, 0)
+/* Change the value of a dynamic command line option.
+ Note that unknown or not dynamically changeable options
+ will cause a warning message to be output. */
+#define VALGRIND_CLO_CHANGE(option) \
+ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CLO_CHANGE, \
+ option, 0, 0, 0, 0)
+
+
#undef PLAT_x86_darwin
#undef PLAT_amd64_darwin
#undef PLAT_x86_win32
@@ -6641,6 +7158,7 @@ VALGRIND_PRINTF_BACKTRACE(const char *format, ...)
#undef PLAT_s390x_linux
#undef PLAT_mips32_linux
#undef PLAT_mips64_linux
+#undef PLAT_nanomips_linux
#undef PLAT_x86_solaris
#undef PLAT_amd64_solaris
diff --git a/src/testlib/CMakeLists.txt b/src/testlib/CMakeLists.txt
index fa967ca43c..e956a47cf1 100644
--- a/src/testlib/CMakeLists.txt
+++ b/src/testlib/CMakeLists.txt
@@ -1,11 +1,7 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from testlib.pro.
-
-# special case begin
include(selfcover.cmake)
-# special case end
#####################################################################
## Test Module:
@@ -17,6 +13,7 @@ qt_internal_add_module(Test
EXCEPTIONS
SOURCES
3rdparty/cycle_p.h
+ removed_api.cpp # keep first
qabstracttestlogger.cpp qabstracttestlogger_p.h
qasciikey.cpp
qbenchmark.cpp qbenchmark.h qbenchmark_p.h
@@ -25,13 +22,14 @@ qt_internal_add_module(Test
qbenchmarkmetric.cpp qbenchmarkmetric.h qbenchmarkmetric_p.h
qbenchmarkperfevents.cpp qbenchmarkperfevents_p.h
qbenchmarktimemeasurers_p.h
+ qcomparisontesthelper.cpp qcomparisontesthelper_p.h
qcsvbenchmarklogger.cpp qcsvbenchmarklogger_p.h
qemulationdetector_p.h
qjunittestlogger.cpp qjunittestlogger_p.h
qplaintestlogger.cpp qplaintestlogger_p.h
qpropertytesthelper_p.h
qsignaldumper.cpp qsignaldumper_p.h
- qsignalspy.h
+ qsignalspy.cpp qsignalspy.h
qtaptestlogger.cpp qtaptestlogger_p.h
qteamcitylogger.cpp qteamcitylogger_p.h
qtest.h
@@ -43,6 +41,7 @@ qt_internal_add_module(Test
qtestblacklist.cpp qtestblacklist_p.h
qtestcase.cpp qtestcase.h qtestcase_p.h
qtestcoreelement_p.h
+ qtestcrashhandler.cpp qtestcrashhandler_p.h
qtestdata.cpp qtestdata.h
qtestelement.cpp qtestelement_p.h
qtestelementattribute.cpp qtestelementattribute_p.h
@@ -58,34 +57,38 @@ qt_internal_add_module(Test
qtestsystem.h
qtesttable.cpp qtesttable_p.h
qtesttouch.h
+ qtesttostring.h
+ qtestwheel.h
qttestglobal.h
qxmltestlogger.cpp qxmltestlogger_p.h
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
+ QT_NO_CONTEXTLESS_CONNECT
QT_NO_DATASTREAM
QT_NO_FOREACH
+ QT_USE_NODISCARD_FILE_OPEN
# Ensure uniform location info between release and debug builds
QT_NO_MESSAGELOGCONTEXT
LIBRARIES
Qt::CorePrivate
+ NO_PCH_SOURCES
+ removed_api.cpp
PUBLIC_LIBRARIES
Qt::Core
PRIVATE_MODULE_INTERFACE
Qt::CorePrivate
GENERATE_CPP_EXPORTS
- GENERATE_PRIVATE_CPP_EXPORTS
)
-#### Keys ignored in scope 1:.:.:testlib.pro:<TRUE>:
-# MODULE_CONFIG = "console" "testlib_defines"
+if(TARGET Gui)
+ set_property(TARGET Test
+ APPEND PROPERTY _qt_internal_sync_headers_deps Gui_sync_headers)
+endif()
## Scopes:
#####################################################################
-#### Keys ignored in scope 2:.:.:testlib.pro:UNIX AND NOT embedded:
-# QMAKE_PKGCONFIG_DESCRIPTION = "Qt" "Unit" "Testing" "Library"
-
qt_internal_extend_target(Test CONDITION QT_FEATURE_itemmodeltester
SOURCES
qabstractitemmodeltester.cpp qabstractitemmodeltester.h
@@ -135,30 +138,11 @@ set_property(TARGET Test APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS
QT_TESTCASE_SOURCEDIR="$<TARGET_PROPERTY:SOURCE_DIR>"
)
-# special case begin
-# Do not bother with disabled stuff:
-# qt_internal_extend_target(Test CONDITION (MACOS) AND (OFF AND NOT lessThan(QMAKE_XCODE_VERSION, "6.0")) ...
-# qt_internal_extend_target(Test CONDITION ((MACOS) AND (OFF AND NOT lessThan(QMAKE_XCODE_VERSION, "6.0"))) AND (NOT QMAKE_MAC_SDK_PLATFORM_PATH_ISEMPTY) ...
-# special case end
-
-#### Keys ignored in scope 9:.:.:testlib.pro:NOT QMAKE_MAC_SDK_PLATFORM_PATH_ISEMPTY:
-# MODULE_CONFIG = "xctest"
-
-#### Keys ignored in scope 10:.:.:testlib.pro:NOT TARGET Qt::Gui:
-# HEADERSCLEAN_EXCLUDE = "qtest_gui.h" "qtestaccessible.h" "qtestkeyboard.h" "qtestmouse.h" "qtesttouch.h"
-
-#### Keys ignored in scope 11:.:.:testlib.pro:NOT TARGET Qt::Widgets:
-# HEADERSCLEAN_EXCLUDE = "qtest_widgets.h"
-
-#### Keys ignored in scope 12:.:.:testlib.pro:NOT TARGET Qt::Network:
-# HEADERSCLEAN_EXCLUDE = "qtest_network.h"
qt_internal_add_docs(Test
doc/qttestlib.qdocconf
)
-# special case begin
qt_internal_apply_testlib_coverage_options(Test)
-# special case end
# include the snippet projects for developer-builds
if(QT_FEATURE_private_tests)
diff --git a/src/testlib/doc/qttestlib.qdocconf b/src/testlib/doc/qttestlib.qdocconf
index 4330fe197a..8b245a864f 100644
--- a/src/testlib/doc/qttestlib.qdocconf
+++ b/src/testlib/doc/qttestlib.qdocconf
@@ -56,5 +56,5 @@ manifestmeta.thumbnail.names = "QtTestLib/Chapter *"
navigation.landingpage = "Qt Test"
navigation.cppclassespage = "Qt Test C++ Classes"
-# Fail the documentation build if there are more warnings than the limit
+# Enforce zero documentation warnings
warninglimit = 0
diff --git a/src/testlib/doc/snippets/CMakeLists.txt b/src/testlib/doc/snippets/CMakeLists.txt
index 0982732843..77e4781c44 100644
--- a/src/testlib/doc/snippets/CMakeLists.txt
+++ b/src/testlib/doc/snippets/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#! [cmake_use]
find_package(Qt6 REQUIRED COMPONENTS Test)
diff --git a/src/testlib/doc/snippets/code/CMakeLists.txt b/src/testlib/doc/snippets/code/CMakeLists.txt
index 86057acde2..54c655a521 100644
--- a/src/testlib/doc/snippets/code/CMakeLists.txt
+++ b/src/testlib/doc/snippets/code/CMakeLists.txt
@@ -1,9 +1,8 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
add_library(testlib_code_snippets OBJECT
doc_src_qtqskip.cpp
- doc_src_qttest.cpp
src_corelib_kernel_qtestsupport_core.cpp
)
diff --git a/src/testlib/doc/snippets/code/doc_src_cmakelists.txt b/src/testlib/doc/snippets/code/doc_src_cmakelists.txt
index 6e19597775..946f09c09f 100644
--- a/src/testlib/doc/snippets/code/doc_src_cmakelists.txt
+++ b/src/testlib/doc/snippets/code/doc_src_cmakelists.txt
@@ -8,7 +8,7 @@ set(CMAKE_AUTOMOC ON)
enable_testing(true)
-add_executable(mytest tst_mytest.cpp)
+qt_add_executable(mytest tst_mytest.cpp)
add_test(NAME mytest COMMAND mytest)
target_link_libraries(mytest PRIVATE Qt::Test)
diff --git a/src/testlib/doc/snippets/code/doc_src_qsignalspy.cpp b/src/testlib/doc/snippets/code/doc_src_qsignalspy.cpp
index 20459f9eb4..99760ea730 100644
--- a/src/testlib/doc/snippets/code/doc_src_qsignalspy.cpp
+++ b/src/testlib/doc/snippets/code/doc_src_qsignalspy.cpp
@@ -43,9 +43,6 @@ SomeStruct result = qvariant_cast<SomeStruct>(spy.at(0).at(0));
QSignalSpy spy(myPushButton, SIGNAL(clicked(bool)));
//! [4]
-//! [5]
-QVERIFY(spy.wait(1000));
-//! [5]
//! [6]
QSignalSpy spy(myPushButton, &QPushButton::clicked);
diff --git a/src/testlib/doc/snippets/code/doc_src_qtestlib.qdoc b/src/testlib/doc/snippets/code/doc_src_qtestlib.qdoc
index 10d9cb5bee..9b592bdb6a 100644
--- a/src/testlib/doc/snippets/code/doc_src_qtestlib.qdoc
+++ b/src/testlib/doc/snippets/code/doc_src_qtestlib.qdoc
@@ -1,5 +1,5 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
//! [2]
testname [options] [testfunctions[:testdata]]...
diff --git a/src/testlib/doc/snippets/code/doc_src_qttest.cpp b/src/testlib/doc/snippets/code/doc_src_qttest.cpp
deleted file mode 100644
index 06b4588268..0000000000
--- a/src/testlib/doc/snippets/code/doc_src_qttest.cpp
+++ /dev/null
@@ -1,6 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-//! [0]
-#include <QTest>
-//! [0]
diff --git a/src/testlib/doc/snippets/code/src_corelib_kernel_qtestsupport_core.cpp b/src/testlib/doc/snippets/code/src_corelib_kernel_qtestsupport_core.cpp
index 906a88b20f..7faf40d9b7 100644
--- a/src/testlib/doc/snippets/code/src_corelib_kernel_qtestsupport_core.cpp
+++ b/src/testlib/doc/snippets/code/src_corelib_kernel_qtestsupport_core.cpp
@@ -5,8 +5,9 @@
// dummy class
class MyObject
{
- public:
- int isReady();
+public:
+ int isReady();
+ void startup() {}
};
// dummy function
@@ -18,9 +19,22 @@ int myNetworkServerNotResponding()
int MyObject::isReady()
{
//! [1]
+ using namespace std::chrono_literals;
int i = 0;
while (myNetworkServerNotResponding() && i++ < 50)
- QTest::qWait(250);
+ QTest::qWait(250ms);
//! [1]
return 1;
}
+
+[[maybe_unused]] static bool startup()
+{
+//! [2]
+ MyObject obj;
+ obj.startup();
+ using namespace std::chrono_literals;
+ const bool result = QTest::qWaitFor([&obj]() { return obj.isReady(); },
+ QDeadlineTimer(3s));
+//! [2]
+ return result;
+}
diff --git a/src/testlib/doc/snippets/code/src_corelib_kernel_qtestsupport_core_snippet.cpp b/src/testlib/doc/snippets/code/src_corelib_kernel_qtestsupport_core_snippet.cpp
deleted file mode 100644
index f2ba321a67..0000000000
--- a/src/testlib/doc/snippets/code/src_corelib_kernel_qtestsupport_core_snippet.cpp
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (C) 2020 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-//! [0]
- MyObject obj;
- obj.startup();
- QTest::qWaitFor([&]() {
- return obj.isReady();
- }, 3000);
-//! [0]
diff --git a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp
index 532b26b4f1..4716d06e55 100644
--- a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp
+++ b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp
@@ -170,10 +170,11 @@ void MyTestClass::cleanup()
}
//! [22]
-void mySleep()
+void quarterSecondSleep()
{
//! [23]
-QTest::qSleep(250);
+using namespace std::chrono_literals;
+QTest::qSleep(250ms);
//! [23]
}
diff --git a/src/testlib/doc/src/qt-webpages.qdoc b/src/testlib/doc/src/qt-webpages.qdoc
index 0a691bb63b..611f3795ba 100644
--- a/src/testlib/doc/src/qt-webpages.qdoc
+++ b/src/testlib/doc/src/qt-webpages.qdoc
@@ -1,13 +1,8 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
- \externalpage https://blog.qt.io/blog/2008/12/05/qtestlib-now-with-nice-graphs-pointing-upwards/
- \title qtestlib-tools Announcement
-*/
-
-/*!
- \externalpage https://www.froglogic.com/coco/
- \title Froglogic Coco Code Coverage
+ \externalpage https://www.qt.io/product/quality-assurance/coco
+ \title Coco
*/
/*!
diff --git a/src/testlib/doc/src/qt6-changes.qdoc b/src/testlib/doc/src/qt6-changes.qdoc
index 03a5c8db03..77e7b04afd 100644
--- a/src/testlib/doc/src/qt6-changes.qdoc
+++ b/src/testlib/doc/src/qt6-changes.qdoc
@@ -5,7 +5,7 @@
\page testlib-changes-qt6.html
\title Changes to Qt Test
\ingroup changes-qt-5-to-6
- \brief Migrate Qt Test to Qt 6.
+ \brief Touch-related functionality changes.
Qt 6 is a result of the conscious effort to make the framework more
efficient and easy to use.
diff --git a/src/testlib/doc/src/qttest-best-practices.qdoc b/src/testlib/doc/src/qttest-best-practices.qdoc
index d9bb363c09..cea5b3a8de 100644
--- a/src/testlib/doc/src/qttest-best-practices.qdoc
+++ b/src/testlib/doc/src/qttest-best-practices.qdoc
@@ -154,8 +154,8 @@
\section2 Use Data-driven Testing
- Data-driven tests make it easier to add new tests for boundary conditions
- found in later bug reports.
+ \l{Chapter 2: Data Driven Testing}{Data-driven tests} make it easier to add
+ new tests for boundary conditions found in later bug reports.
Using a data-driven test rather than testing several items in sequence in
a test saves repetition of very similar code and ensures later cases are
@@ -171,7 +171,7 @@
\section2 Use Coverage Tools
- Use a coverage tool such as \l {Froglogic Coco Code Coverage} or \l {gcov}
+ Use a coverage tool such as \l {Coco} or \l {gcov}
to help write tests that cover as many statements, branches, and conditions
as possible in the function or class being tested. The earlier this is done
in the development cycle for a new feature, the easier it will be to catch
@@ -180,14 +180,19 @@
\section2 Select Appropriate Mechanisms to Exclude Tests
It is important to select the appropriate mechanism to exclude inapplicable
- tests: \l QSKIP(), using conditional statements to exclude parts of a test
- function, or not building the test for a particular platform.
+ tests.
+
+ Use \l QSKIP() to handle cases where a whole test function is found at
+ run-time to be inapplicable in the current test environment. When just a
+ part of a test function is to be skipped, a conditional statement can be
+ used, optionally with a \c qDebug() call to report the reason for skipping
+ the inapplicable part.
- Use QSKIP() to handle cases where a whole test function is found at run-time
- to be inapplicable in the current test environment. When just a part of a
- test function is to be skipped, a conditional statement can be used,
- optionally with a \c qDebug() call to report the reason for skipping the
- inapplicable part.
+ When there are known test failures that should eventually be fixed,
+ \l QEXPECT_FAIL is recommended, as it supports running the rest of the
+ test, when possible. It also verifies that the issue still exists, and
+ lets the code's maintainer know if they unwittingly fix it, a benefit
+ which is gained even when using the \l {QTest::}{Abort} flag.
Test functions or data rows of a data-driven test can be limited to
particular platforms, or to particular features being enabled using
@@ -355,24 +360,47 @@
helpful test output:
\list
- \li \l {Explicitly Ignore Expected Warnings}
+ \li \l {Test for Warnings}
\li \l {Avoid Printing Debug Messages from Autotests}
\li \l {Write Well-structured Diagnostic Code}
\endlist
- \section2 Explicitly Ignore Expected Warnings
-
- If a test is expected to cause Qt to output a warning or debug message
- on the console, it should call \l QTest::ignoreMessage() to filter that
- message out of the test output and to fail the test if the message is
- not output.
-
- If such a message is only output when Qt is built in debug mode, use
- \l QLibraryInfo::isDebugBuild() to determine whether the Qt libraries
- were built in debug mode. Using \c{#ifdef QT_DEBUG} is not enough, as
- it will only tell you whether the test was built in debug mode, and
- that does not guarantee that the Qt libraries were also built in debug
- mode.
+ \section2 Test for Warnings
+
+ Just as when building your software, if test output is cluttered with
+ warnings you will find it harder to notice a warning that really is a clue
+ to the emergence of a bug. It is thus prudent to regularly check your test
+ logs for warnings, and other extraneous output, and investigate the
+ causes. When they are signs of a bug, you can make warnings trigger test
+ failure.
+
+ When the code under test \e should produce messages, such as warnings
+ about misguided use, it is also important to test that it \e does produce
+ them when so used. You can test for expected messages from the code under
+ test, produced by \l qWarning(), \l qDebug(), \l qInfo() and friends,
+ using \l QTest::ignoreMessage(). This will verify that the message is
+ produced and filter it out of the output of the test run. If the message
+ is not produced, the test will fail.
+
+ If an expected message is only output when Qt is built in debug mode, use
+ \l QLibraryInfo::isDebugBuild() to determine whether the Qt libraries were
+ built in debug mode. Using \c{#ifdef QT_DEBUG} is not enough, as it will
+ only tell you whether \e{the test} was built in debug mode, and that does
+ not guarantee that the \e{Qt libraries} were also built in debug mode.
+
+ Your tests can (since Qt 6.3) verify that they do not trigger calls to
+ \l qWarning() by calling \l QTest::failOnWarning(). This takes the warning
+ message to test for or a \l QRegularExpression to match against warnings; if
+ a matching warning is produced, it will be reported and cause the test to
+ fail. For example, a test that should produce no warnings at all can
+ \c{QTest::failOnWarning(QRegularExpression(u".*"_s))}, which will match any
+ warning at all.
+
+ You can also set the environment variable \c QT_FATAL_WARNINGS to cause
+ warnings to be treated as fatal errors. See \l qWarning() for details; this
+ is not specific to autotests. If warnings would otherwise be lost in vast
+ test logs, the occasional run with this environment variable set can help
+ you to find and eliminate any that do arise.
\section2 Avoid Printing Debug Messages from Autotests
diff --git a/src/testlib/doc/src/qttestlib-manual.qdoc b/src/testlib/doc/src/qttestlib-manual.qdoc
index 53ef055d2b..1b6f534045 100644
--- a/src/testlib/doc/src/qttestlib-manual.qdoc
+++ b/src/testlib/doc/src/qttestlib-manual.qdoc
@@ -109,6 +109,47 @@
For more examples, refer to the \l{Qt Test Tutorial}.
+ \section1 Increasing Test Function Timeout
+
+ QtTest limits the run-time of each test to catch infinite loops and similar
+ bugs. By default, any test function call will be interrupted after five
+ minutes. For data-driven tests, this applies to each call with a distinct
+ data-tag. This timeout can be configured by setting the \c QTEST_FUNCTION_TIMEOUT
+ environment variable to the maximum number of milliseconds that is acceptable
+ for a single call to take. If a test takes longer than the configured timeout,
+ it is interrupted, and \c qFatal() is called. As a result, the test aborts by
+ default, as if it had crashed.
+
+ To set \c QTEST_FUNCTION_TIMEOUT from the command line on Linux or macOS, enter:
+
+ \badcode
+ QTEST_FUNCTION_TIMEOUT=900000
+ export QTEST_FUNCTION_TIMEOUT
+ \endcode
+
+ On Windows:
+ \badcode
+ SET QTEST_FUNCTION_TIMEOUT=900000
+ \endcode
+
+ Then run the test inside this environment.
+
+ Alternatively, you can set the environment variable programmatically in the
+ test code itself, for example by calling, from the
+ \l{Creating a Test}{initMain()} special method of your test class:
+
+ \badcode
+ qputenv("QTEST_FUNCTION_TIMEOUT", "900000");
+ \endcode
+
+ To calculate a suitable value for the timeout, see how long the test usually
+ takes and decide how much longer it can take without that being a symptom of
+ some problem. Convert that longer time to milliseconds to get the timeout value.
+ For example, if you decide that a test that takes several minutes could
+ reasonably take up to twenty minutes, for example on a slow machine,
+ multiply \c{20 * 60 * 1000 = 1200000} and set the environment variable to
+ \c 1200000 instead of the \c 900000 above.
+
\if !defined(qtforpython)
\section1 Building a Test
@@ -307,6 +348,16 @@
Disables the crash handler on Unix platforms.
On Windows, it re-enables the Windows Error Reporting dialog, which is
turned off by default. This is useful for debugging crashes.
+ \li \c -repeat \e n \br
+ Run the testsuite n times or until the test fails. Useful for finding
+ flaky tests. If negative, the tests are repeated forever. This is intended
+ as a developer tool, and is only supported with the plain text logger.
+ \li \c -skipblacklisted \br
+ Skip the blacklisted tests. This option is intended to allow more accurate
+ measurement of test coverage by preventing blacklisted tests from inflating
+ coverage statistics. When not measuring test coverage, it is recommended to
+ execute blacklisted tests to reveal any changes in their results, such as
+ a new crash or the issue that caused blacklisting being resolved.
\li \c -platform \e name \br
This command line argument applies to all Qt applications, but might be
@@ -362,8 +413,14 @@
Setting this variable to a non-zero value will cause a failure in
an autotest to immediately abort the entire autotest. This is useful
to e.g. debug an unstable or intermittent failure in a test, by
- launching the test in a debugger. Support for this variable has been
+ launching the test in a debugger. Support for this variable was
added in Qt 6.1.
+ \li \c QTEST_THROW_ON_FAIL (since 6.8) \br
+ Setting this variable to a non-zero value will cause QCOMPARE()/QVERIFY()
+ etc to throw on failure (as opposed to just returning from the
+ immediately-surrounding function scope).
+ \li \c QTEST_THROW_ON_SKIP (since 6.8) \br
+ Same as \c QTEST_THROW_ON_FAIL, except affecting QSKIP().
\endlist
\section1 Creating a Benchmark
@@ -532,478 +589,5 @@
\li \l {Chapter 6: Skipping Tests with QSKIP}{Skipping Tests}
\endlist
- \note You can build and execute the tests from each chapter using the
- available source code, which is linked to at the end of each chapter.
-
-*/
-
-
-/*!
- \example tutorial1
-
- \previouspage {Qt Test Tutorial}{Qt Test Tutorial Overview}
- \nextpage {Chapter 2: Data Driven Testing}{Chapter 2}
-
- \title Chapter 1: Writing a Unit Test
- \brief How to write a unit test.
-
- This first chapter demonstrates how to write a simple unit test and how to
- run the test case as a stand-alone executable.
-
- \section1 Writing a Test
-
- Let's assume you want to test the behavior of our QString class.
- First, you need a class that contains your test functions. This class
- has to inherit from QObject:
-
- \snippet tutorial1/testqstring.cpp 0
-
- \note You need to include the QTest header and declare the test functions as
- private slots so the test framework finds and executes it.
-
- Then you need to implement the test function itself. The
- implementation could look like this:
-
- \snippet code/doc_src_qtestlib.cpp 8
-
- The \l QVERIFY() macro evaluates the expression passed as its
- argument. If the expression evaluates to true, the execution of
- the test function continues. Otherwise, a message describing the
- failure is appended to the test log, and the test function stops
- executing.
-
- But if you want a more verbose output to the test log, you should
- use the \l QCOMPARE() macro instead:
-
- \snippet tutorial1/testqstring.cpp 1
-
- If the strings are not equal, the contents of both strings are
- appended to the test log, making it immediately visible why the
- comparison failed.
-
- \section1 Preparing the Stand-Alone Executable
-
- Finally, to make our test case a stand-alone executable, the
- following two lines are needed:
-
- \snippet tutorial1/testqstring.cpp 2
-
- The \l QTEST_MAIN() macro expands to a simple \c main()
- method that runs all the test functions. Note that if both the
- declaration and the implementation of our test class are in a \c
- .cpp file, we also need to include the generated moc file to make
- Qt's introspection work.
-
- \section1 Building the Executable
-
- \include {building-examples.qdocinc} {building the executable} {tutorial1}
-
- \note If you're using windows, replace \c make with \c
- nmake or whatever build tool you use.
-
- \section1 Running the Executable
-
- Running the resulting executable should give you the following
- output:
-
- \snippet code/doc_src_qtestlib.qdoc 10
-
- Congratulations! You just wrote and executed your first unit test
- using the Qt Test framework.
-*/
-
-/*!
- \example tutorial2
-
- \previouspage {Chapter 1: Writing a Unit Test}{Chapter 1}
- \nextpage {Chapter 3: Simulating Gui Events}{Chapter 3}
-
- \title Chapter 2: Data Driven Testing
- \brief How to create data driven tests.
-
- This chapter demonstrates how to execute a test multiple times with
- different test data.
-
- So far, we have hard coded the data we wanted to test into our
- test function. If we add more test data, the function might look like
- this:
-
- \snippet code/doc_src_qtestlib.cpp 11
-
- To prevent the function from being cluttered with repetitive code, Qt Test
- supports adding test data to a test function. All we need is to add another
- private slot to our test class:
-
- \snippet tutorial2/testqstring.cpp 0
-
- \section1 Writing the Data Function
-
- A test function's associated data function has \c _data appended to its
- name. Our data function looks like this:
-
- \snippet tutorial2/testqstring.cpp 1
-
- First, we define the two elements of our test table using the \l
- QTest::addColumn() function: a test string and the
- expected result of applying the QString::toUpper() function to
- that string.
-
- Then, we add some data to the table using the \l
- QTest::newRow() function. Each set of data will become a
- separate row in the test table.
-
- \l QTest::newRow() takes one argument: a name that will be associated
- with the data set and used in the test log to identify the data set.
- Then, we stream the data set into the new table row. First an arbitrary
- string, and then the expected result of applying the
- QString::toUpper() function to that string.
-
- You can think of the test data as a two-dimensional table. In
- our case, it has two columns called \c string and \c result and
- three rows. In addition, a name and an index are associated
- with each row:
-
- \table
- \header
- \li index
- \li name
- \li string
- \li result
- \row
- \li 0
- \li all-lower
- \li "hello"
- \li HELLO
- \row
- \li 1
- \li mixed
- \li "Hello"
- \li HELLO
- \row
- \li 2
- \li all-upper
- \li "HELLO"
- \li HELLO
- \endtable
-
- When data is streamed into the row, each datum is asserted to match
- the type of the column whose value it supplies. If any assertion fails,
- the test is aborted.
-
- \section1 Rewriting the Test Function
-
- Our test function can now be rewritten:
-
- \snippet tutorial2/testqstring.cpp 2
-
- The TestQString::toUpper() function will be executed three times,
- once for each entry in the test table that we created in the
- associated TestQString::toUpper_data() function.
-
- First, we fetch the two elements of the data set using the \l
- QFETCH() macro. \l QFETCH() takes two arguments: The data type of
- the element and the element name. Then, we perform the test using
- the \l QCOMPARE() macro.
-
- This approach makes it very easy to add new data to the test
- without modifying the test itself.
-
- \section1 Preparing the Stand-Alone Executable
-
- And again, to make our test case a stand-alone executable,
- the following two lines are needed:
-
- \snippet tutorial2/testqstring.cpp 3
-
- As before, the QTEST_MAIN() macro expands to a simple main()
- method that runs all the test functions, and since both the
- declaration and the implementation of our test class are in a .cpp
- file, we also need to include the generated moc file to make Qt's
- introspection work.
-
- \section1 Building the Executable
-
- \include {building-examples.qdocinc} {building the executable} {tutorial2}
-
- \section1 Running the Executable
-
- Running the resulting executable should give you the following
- output:
-
- \snippet code/doc_src_qtestlib.qdoc 11
-*/
-
-/*!
- \example tutorial3
-
- \previouspage {Chapter 2: Data Driven Testing}{Chapter 2}
- \nextpage {Chapter 4: Replaying GUI Events}{Chapter 4}
-
- \title Chapter 3: Simulating GUI Events
- \brief Howe to simulate GUI events.
-
- Qt Test features some mechanisms to test graphical user
- interfaces. Instead of simulating native window system events,
- Qt Test sends internal Qt events. That means there are no
- side-effects on the machine the tests are running on.
-
- This chapter demonstrates how to write a simple GUI test.
-
- \section1 Writing a GUI Test
-
- This time, let's assume you want to test the behavior of our
- QLineEdit class. As before, you will need a class that contains
- your test function:
-
- \snippet tutorial3/testgui.cpp 0
-
- The only difference is that you need to include the Qt GUI class
- definitions in addition to the QTest namespace.
-
- \snippet tutorial3/testgui.cpp 1
-
- In the implementation of the test function, we first create a
- QLineEdit. Then, we simulate writing "hello world" in the line edit
- using the \l QTest::keyClicks() function.
-
- \note The widget must also be shown in order to correctly test keyboard
- shortcuts.
-
- QTest::keyClicks() simulates clicking a sequence of keys on a
- widget. Optionally, a keyboard modifier can be specified as well
- as a delay (in milliseconds) of the test after each key click. In
- a similar way, you can use the QTest::keyClick(),
- QTest::keyPress(), QTest::keyRelease(), QTest::mouseClick(),
- QTest::mouseDClick(), QTest::mouseMove(), QTest::mousePress()
- and QTest::mouseRelease() functions to simulate the associated
- GUI events.
-
- Finally, we use the \l QCOMPARE() macro to check if the line edit's
- text is as expected.
-
- \section1 Preparing the Stand-Alone Executable
-
- As before, to make our test case a stand-alone executable, the
- following two lines are needed:
-
- \snippet tutorial3/testgui.cpp 2
-
- The QTEST_MAIN() macro expands to a simple main() method that
- runs all the test functions, and since both the declaration and
- the implementation of our test class are in a .cpp file, we also
- need to include the generated moc file to make Qt's introspection
- work.
-
- \section1 Building the Executable
-
- \include {building-examples.qdocinc} {building the executable} {tutorial3}
-
- \section1 Running the Executable
-
- Running the resulting executable should give you the following
- output:
-
- \snippet code/doc_src_qtestlib.qdoc 12
-*/
-
-/*!
- \example tutorial4
-
- \previouspage {Chapter 3: Simulating GUI Events}{Chapter 3}
- \nextpage {Chapter 5: Writing a Benchmark}{Chapter 5}
-
- \title Chapter 4: Replaying GUI Events
- \brief How to replay GUI events.
-
- In this chapter, we will show how to simulate a GUI event,
- and how to store a series of GUI events as well as replay them on
- a widget.
-
- The approach to storing a series of events and replaying them is
- quite similar to the approach explained in \l {Chapter 2:
- Data Driven Testing}{chapter 2}. All you need to do is to add a data
- function to your test class:
-
- \snippet tutorial4/testgui.cpp 0
-
- \section1 Writing the Data Function
-
- As before, a test function's associated data function carries the
- same name, appended by \c{_data}.
-
- \snippet tutorial4/testgui.cpp 1
-
- First, we define the elements of the table using the
- QTest::addColumn() function: A list of GUI events, and the
- expected result of applying the list of events on a QWidget. Note
- that the type of the first element is \l QTestEventList.
-
- A QTestEventList can be populated with GUI events that can be
- stored as test data for later usage, or be replayed on any
- QWidget.
-
- In our current data function, we create two \l
- {QTestEventList} elements. The first list consists of a single click to
- the 'a' key. We add the event to the list using the
- QTestEventList::addKeyClick() function. Then we use the
- QTest::newRow() function to give the data set a name, and
- stream the event list and the expected result into the table.
-
- The second list consists of two key clicks: an 'a' with a
- following 'backspace'. Again we use the
- QTestEventList::addKeyClick() to add the events to the list, and
- QTest::newRow() to put the event list and the expected
- result into the table with an associated name.
-
- \section1 Rewriting the Test Function
-
- Our test can now be rewritten:
-
- \snippet tutorial4/testgui.cpp 2
-
- The TestGui::testGui() function will be executed two times,
- once for each entry in the test data that we created in the
- associated TestGui::testGui_data() function.
-
- First, we fetch the two elements of the data set using the \l
- QFETCH() macro. \l QFETCH() takes two arguments: the data type of
- the element and the element name. Then we create a QLineEdit, and
- apply the list of events on that widget using the
- QTestEventList::simulate() function.
-
- Finally, we use the QCOMPARE() macro to check if the line edit's
- text is as expected.
-
- \section1 Preparing the Stand-Alone Executable
-
- As before, to make our test case a stand-alone executable,
- the following two lines are needed:
-
- \snippet tutorial4/testgui.cpp 3
-
- The QTEST_MAIN() macro expands to a simple main() method that
- runs all the test functions, and since both the declaration and
- the implementation of our test class are in a .cpp file, we also
- need to include the generated moc file to make Qt's introspection
- work.
-
- \section1 Building the Executable
-
- \include {building-examples.qdocinc} {building the executable} {tutorial4}
-
- \section1 Running the Executable
-
- Running the resulting executable should give you the following
- output:
-
- \snippet code/doc_src_qtestlib.qdoc 13
-*/
-
-/*!
- \example tutorial5
-
- \previouspage {Chapter 4: Replaying GUI Events}{Chapter 4}
- \nextpage {Chapter 6: Skipping Tests with QSKIP}{Chapter 6}
-
- \title Chapter 5: Writing a Benchmark
- \brief How to write a benchmark.
-
- This final demonstrates how to write benchmarks using Qt Test.
-
- \section1 Writing a Benchmark
- To create a benchmark we extend a test function with a QBENCHMARK macro.
- A benchmark test function will then typically consist of setup code and
- a QBENCHMARK macro that contains the code to be measured. This test
- function benchmarks QString::localeAwareCompare().
-
- \snippet tutorial5/benchmarking.cpp 0
-
- Setup can be done at the beginning of the function. At this point, the clock
- is not running. The code inside the QBENCHMARK macro will be
- measured, and possibly repeated several times in order to get an
- accurate measurement.
-
- Several \l {testlib-benchmarking-measurement}{back-ends} are available
- and can be selected on the command line.
-
- \section1 Data Functions
-
- Data functions are useful for creating benchmarks that compare
- multiple data inputs, for example locale aware compare against standard
- compare.
-
- \snippet tutorial5/benchmarking.cpp 1
-
- The test function then uses the data to determine what to benchmark.
-
- \snippet tutorial5/benchmarking.cpp 2
-
- The \c{if (useLocaleCompare)} switch is placed outside the QBENCHMARK
- macro to avoid measuring its overhead. Each benchmark test function
- can have one active QBENCHMARK macro.
-
- \section1 External Tools
-
- Tools for handling and visualizing test data are available as part of
- the \l {qtestlib-tools} project.
- These include a tool for comparing performance data obtained from test
- runs and a utility to generate Web-based graphs of performance data.
-
- See the \l{qtestlib-tools Announcement}{qtestlib-tools announcement}
- for more information on these tools and a simple graphing example.
-
- \section1 Building the Executable
-
- \include {building-examples.qdocinc} {building the executable} {tutorial5}
-
- \section1 Running the Executable
-
- Running the resulting executable should give you the following
- output:
-
- \snippet code/doc_src_qtestlib.qdoc 14
-*/
-/*!
- \page qttestlib-tutorial6.html
-
- \previouspage {Chapter 5: Writing a Benchmark}{Chapter 5}
-
- \title Chapter 6: Skipping Tests with QSKIP
- \brief How to skip tests in certain cases.
-
- \section2 Using QSKIP(\a description) in a test function
-
- If the QSKIP() macro is called from a test function, it stops
- the execution of the test without adding a failure to the test log.
- It can be used to skip tests that are certain to fail. The text in
- the QSKIP \a description parameter is appended to the test log,
- and should explain why the test was not carried out.
-
- QSKIP can be used to skip testing when the implementation is not yet
- complete or not supported on a certain platform. When there are known
- failures, QEXPECT_FAIL is recommended, as it supports running the rest
- of the test, when possible.
-
- Example of QSKIP in a test function:
-
- \snippet code/doc_src_qtqskip_snippet.cpp 0
-
- In a data-driven test, each call to QSKIP() skips only the current
- row of test data. If the data-driven test contains an unconditional
- call to QSKIP, it produces a skip message for each row of test data.
-
- \section2 Using QSKIP in a _data function
-
- If called from a _data function, the QSKIP() macro stops
- execution of the _data function. This prevents execution of the
- associated test function.
-
- See below for an example:
-
- \snippet code/doc_src_qtqskip.cpp 1
-
- \section2 Using QSKIP from initTestCase() or initTestCase_data()
- If called from \c initTestCase() or \c initTestCase_data(), the
- QSKIP() macro will skip all test and _data functions.
*/
diff --git a/src/testlib/doc/src/qttestlib-tutorial1.qdoc b/src/testlib/doc/src/qttestlib-tutorial1.qdoc
new file mode 100644
index 0000000000..41d9264c0d
--- /dev/null
+++ b/src/testlib/doc/src/qttestlib-tutorial1.qdoc
@@ -0,0 +1,76 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qttestlib-tutorial1-example.html
+ \previouspage {Qt Test Tutorial}{Qt Test Tutorial Overview}
+ \nextpage {Chapter 2: Data Driven Testing}{Chapter 2}
+
+ \title Chapter 1: Writing a Unit Test
+ \brief How to write a unit test.
+
+ This first chapter demonstrates how to write a simple unit test and how to
+ run the test case as a stand-alone executable.
+
+ \section1 Writing a Test
+
+ Let's assume you want to test the behavior of our QString class.
+ First, you need a class that contains your test functions. This class
+ has to inherit from QObject:
+
+ \snippet tutorial1/testqstring.cpp 0
+
+ \note You need to include the QTest header and declare the test functions as
+ private slots so the test framework finds and executes it.
+
+ Then you need to implement the test function itself. The
+ implementation could look like this:
+
+ \snippet code/doc_src_qtestlib.cpp 8
+
+ The \l QVERIFY() macro evaluates the expression passed as its
+ argument. If the expression evaluates to true, the execution of
+ the test function continues. Otherwise, a message describing the
+ failure is appended to the test log, and the test function stops
+ executing.
+
+ But if you want a more verbose output to the test log, you should
+ use the \l QCOMPARE() macro instead:
+
+ \snippet tutorial1/testqstring.cpp 1
+
+ If the strings are not equal, the contents of both strings are
+ appended to the test log, making it immediately visible why the
+ comparison failed.
+
+ \section1 Preparing the Stand-Alone Executable
+
+ Finally, to make our test case a stand-alone executable, the
+ following two lines are needed:
+
+ \snippet tutorial1/testqstring.cpp 2
+
+ The \l QTEST_MAIN() macro expands to a simple \c main()
+ method that runs all the test functions. Note that if both the
+ declaration and the implementation of our test class are in a \c
+ .cpp file, we also need to include the generated moc file to make
+ Qt's introspection work.
+
+ \section1 Building the Executable
+
+ \include {building-examples.qdocinc} {building the executable} {tutorial1}
+
+ \note If you're using windows, replace \c make with \c
+ nmake or whatever build tool you use.
+
+ \section1 Running the Executable
+
+ Running the resulting executable should give you the following
+ output:
+
+ \snippet code/doc_src_qtestlib.qdoc 10
+
+ Congratulations! You just wrote and executed your first unit test
+ using the Qt Test framework.
+*/
diff --git a/src/testlib/doc/src/qttestlib-tutorial2.qdoc b/src/testlib/doc/src/qttestlib-tutorial2.qdoc
new file mode 100644
index 0000000000..bd828b3963
--- /dev/null
+++ b/src/testlib/doc/src/qttestlib-tutorial2.qdoc
@@ -0,0 +1,132 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qttestlib-tutorial2-example.html
+ \previouspage {Chapter 1: Writing a Unit Test}{Chapter 1}
+ \nextpage {Chapter 3: Simulating Gui Events}{Chapter 3}
+
+ \title Chapter 2: Data Driven Testing
+ \brief How to create data driven tests.
+
+ This chapter demonstrates how to execute a test multiple times with
+ different test data.
+
+ So far, we have hard coded the data we wanted to test into our
+ test function. If we add more test data, the function might look like
+ this:
+
+ \snippet code/doc_src_qtestlib.cpp 11
+
+ To prevent the function from being cluttered with repetitive code, Qt Test
+ supports adding test data to a test function. All we need is to add another
+ private slot to our test class:
+
+ \snippet tutorial2/testqstring.cpp 0
+
+ \section1 Writing the Data Function
+
+ A test function's associated data function has \c _data appended to its
+ name. Our data function looks like this:
+
+ \snippet tutorial2/testqstring.cpp 1
+
+ First, we define the two elements of our test table using the \l
+ QTest::addColumn() function: a test string and the
+ expected result of applying the QString::toUpper() function to
+ that string.
+
+ Then, we add some data to the table using the \l QTest::newRow()
+ function. We can also use \l QTest::addRow() if we need to format some data
+ in the row name, for example when generating many data rows iteratively.
+ Each row of data will become a separate row in the test table.
+
+ \l QTest::newRow() takes one argument: a name that will be associated with
+ the data set and used in the test log to identify the data row. \l
+ QTest::addRow() takes a (\c{printf}-style) format string followed by the
+ parameters to be represented in place of the formatting tokens in the format
+ string. Then, we stream the data set into the new table row. First an
+ arbitrary string, and then the expected result of applying the
+ QString::toUpper() function to that string.
+
+ You can think of the test data as a two-dimensional table. In
+ our case, it has two columns called \c string and \c result and
+ three rows. In addition, a name and an index are associated
+ with each row:
+
+ \table
+ \header
+ \li index
+ \li name
+ \li string
+ \li result
+ \row
+ \li 0
+ \li all-lower
+ \li "hello"
+ \li HELLO
+ \row
+ \li 1
+ \li mixed
+ \li "Hello"
+ \li HELLO
+ \row
+ \li 2
+ \li all-upper
+ \li "HELLO"
+ \li HELLO
+ \endtable
+
+ When data is streamed into the row, each datum is asserted to match
+ the type of the column whose value it supplies. If any assertion fails,
+ the test is aborted.
+
+ The names of rows and columns, in a given test function's data table, should
+ be unique: if two rows share a name, or two columns share a name, a warning
+ will (since Qt 6.5) be produced. See \l qWarning() for how you can cause
+ warnings to be treated as errors and \l {Test for Warnings} for how to get
+ your tests clear of other warnings.
+
+ \section1 Rewriting the Test Function
+
+ Our test function can now be rewritten:
+
+ \snippet tutorial2/testqstring.cpp 2
+
+ The TestQString::toUpper() function will be executed three times,
+ once for each entry in the test table that we created in the
+ associated TestQString::toUpper_data() function.
+
+ First, we fetch the two elements of the data set using the \l
+ QFETCH() macro. \l QFETCH() takes two arguments: The data type of
+ the element and the element name. Then, we perform the test using
+ the \l QCOMPARE() macro.
+
+ This approach makes it very easy to add new data to the test
+ without modifying the test itself.
+
+ \section1 Preparing the Stand-Alone Executable
+
+ And again, to make our test case a stand-alone executable,
+ the following two lines are needed:
+
+ \snippet tutorial2/testqstring.cpp 3
+
+ As before, the QTEST_MAIN() macro expands to a simple main()
+ method that runs all the test functions, and since both the
+ declaration and the implementation of our test class are in a .cpp
+ file, we also need to include the generated moc file to make Qt's
+ introspection work.
+
+ \section1 Building the Executable
+
+ \include {building-examples.qdocinc} {building the executable} {tutorial2}
+
+ \section1 Running the Executable
+
+ Running the resulting executable should give you the following
+ output:
+
+ \snippet code/doc_src_qtestlib.qdoc 11
+*/
diff --git a/src/testlib/doc/src/qttestlib-tutorial3.qdoc b/src/testlib/doc/src/qttestlib-tutorial3.qdoc
new file mode 100644
index 0000000000..2b7fe25c96
--- /dev/null
+++ b/src/testlib/doc/src/qttestlib-tutorial3.qdoc
@@ -0,0 +1,75 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qttestlib-tutorial3-example.html
+ \previouspage {Chapter 2: Data Driven Testing}{Chapter 2}
+ \nextpage {Chapter 4: Replaying GUI Events}{Chapter 4}
+
+ \title Chapter 3: Simulating GUI Events
+ \brief How to simulate GUI events.
+
+ Qt Test features some mechanisms to test graphical user
+ interfaces. Instead of simulating native window system events,
+ Qt Test sends internal Qt events. That means there are no
+ side-effects on the machine the tests are running on.
+
+ This chapter demonstrates how to write a simple GUI test.
+
+ \section1 Writing a GUI Test
+
+ This time, let's assume you want to test the behavior of our
+ QLineEdit class. As before, you will need a class that contains
+ your test function:
+
+ \snippet tutorial3/testgui.cpp 0
+
+ The only difference is that you need to include the Qt GUI class
+ definitions in addition to the QTest namespace.
+
+ \snippet tutorial3/testgui.cpp 1
+
+ In the implementation of the test function, we first create a
+ QLineEdit. Then, we simulate writing "hello world" in the line edit
+ using the \l QTest::keyClicks() function.
+
+ \note The widget must also be shown in order to correctly test keyboard
+ shortcuts.
+
+ QTest::keyClicks() simulates clicking a sequence of keys on a
+ widget. Optionally, a keyboard modifier can be specified as well
+ as a delay (in milliseconds) of the test after each key click. In
+ a similar way, you can use the QTest::keyClick(),
+ QTest::keyPress(), QTest::keyRelease(), QTest::mouseClick(),
+ QTest::mouseDClick(), QTest::mouseMove(), QTest::mousePress()
+ and QTest::mouseRelease() functions to simulate the associated
+ GUI events.
+
+ Finally, we use the \l QCOMPARE() macro to check if the line edit's
+ text is as expected.
+
+ \section1 Preparing the Stand-Alone Executable
+
+ As before, to make our test case a stand-alone executable, the
+ following two lines are needed:
+
+ \snippet tutorial3/testgui.cpp 2
+
+ The QTEST_MAIN() macro expands to a simple main() method that
+ runs all the test functions, and since both the declaration and
+ the implementation of our test class are in a .cpp file, we also
+ need to include the generated moc file to make Qt's introspection
+ work.
+
+ \section1 Building the Executable
+
+ \include {building-examples.qdocinc} {building the executable} {tutorial3}
+
+ \section1 Running the Executable
+
+ Running the resulting executable should give you the following
+ output:
+
+ \snippet code/doc_src_qtestlib.qdoc 12
+*/
diff --git a/src/testlib/doc/src/qttestlib-tutorial4.qdoc b/src/testlib/doc/src/qttestlib-tutorial4.qdoc
new file mode 100644
index 0000000000..d5a0121f67
--- /dev/null
+++ b/src/testlib/doc/src/qttestlib-tutorial4.qdoc
@@ -0,0 +1,95 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qttestlib-tutorial4-example.html
+ \previouspage {Chapter 3: Simulating GUI Events}{Chapter 3}
+ \nextpage {Chapter 5: Writing a Benchmark}{Chapter 5}
+
+ \title Chapter 4: Replaying GUI Events
+ \brief How to replay GUI events.
+
+ In this chapter, we will show how to simulate a GUI event,
+ and how to store a series of GUI events as well as replay them on
+ a widget.
+
+ The approach to storing a series of events and replaying them is
+ quite similar to the approach explained in \l {Chapter 2:
+ Data Driven Testing}{chapter 2}. All you need to do is to add a data
+ function to your test class:
+
+ \snippet tutorial4/testgui.cpp 0
+
+ \section1 Writing the Data Function
+
+ As before, a test function's associated data function carries the
+ same name, appended by \c{_data}.
+
+ \snippet tutorial4/testgui.cpp 1
+
+ First, we define the elements of the table using the
+ QTest::addColumn() function: A list of GUI events, and the
+ expected result of applying the list of events on a QWidget. Note
+ that the type of the first element is \l QTestEventList.
+
+ A QTestEventList can be populated with GUI events that can be
+ stored as test data for later usage, or be replayed on any
+ QWidget.
+
+ In our current data function, we create two \l
+ {QTestEventList} elements. The first list consists of a single click to
+ the 'a' key. We add the event to the list using the
+ QTestEventList::addKeyClick() function. Then we use the
+ QTest::newRow() function to give the data set a name, and
+ stream the event list and the expected result into the table.
+
+ The second list consists of two key clicks: an 'a' with a
+ following 'backspace'. Again we use the
+ QTestEventList::addKeyClick() to add the events to the list, and
+ QTest::newRow() to put the event list and the expected
+ result into the table with an associated name.
+
+ \section1 Rewriting the Test Function
+
+ Our test can now be rewritten:
+
+ \snippet tutorial4/testgui.cpp 2
+
+ The TestGui::testGui() function will be executed two times,
+ once for each entry in the test data that we created in the
+ associated TestGui::testGui_data() function.
+
+ First, we fetch the two elements of the data set using the \l
+ QFETCH() macro. \l QFETCH() takes two arguments: the data type of
+ the element and the element name. Then we create a QLineEdit, and
+ apply the list of events on that widget using the
+ QTestEventList::simulate() function.
+
+ Finally, we use the QCOMPARE() macro to check if the line edit's
+ text is as expected.
+
+ \section1 Preparing the Stand-Alone Executable
+
+ As before, to make our test case a stand-alone executable,
+ the following two lines are needed:
+
+ \snippet tutorial4/testgui.cpp 3
+
+ The QTEST_MAIN() macro expands to a simple main() method that
+ runs all the test functions, and since both the declaration and
+ the implementation of our test class are in a .cpp file, we also
+ need to include the generated moc file to make Qt's introspection
+ work.
+
+ \section1 Building the Executable
+
+ \include {building-examples.qdocinc} {building the executable} {tutorial4}
+
+ \section1 Running the Executable
+
+ Running the resulting executable should give you the following
+ output:
+
+ \snippet code/doc_src_qtestlib.qdoc 13
+*/
diff --git a/src/testlib/doc/src/qttestlib-tutorial5.qdoc b/src/testlib/doc/src/qttestlib-tutorial5.qdoc
new file mode 100644
index 0000000000..7569019b4e
--- /dev/null
+++ b/src/testlib/doc/src/qttestlib-tutorial5.qdoc
@@ -0,0 +1,57 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qttestlib-tutorial5-example.html
+ \previouspage {Chapter 4: Replaying GUI Events}{Chapter 4}
+ \nextpage {Chapter 6: Skipping Tests with QSKIP}{Chapter 6}
+
+ \title Chapter 5: Writing a Benchmark
+ \brief How to write a benchmark.
+
+ This chapter demonstrates how to write benchmarks using Qt Test.
+
+ \section1 Writing a Benchmark
+ To create a benchmark we extend a test function with a QBENCHMARK macro.
+ A benchmark test function will then typically consist of setup code and
+ a QBENCHMARK macro that contains the code to be measured. This test
+ function benchmarks QString::localeAwareCompare().
+
+ \snippet tutorial5/benchmarking.cpp 0
+
+ Setup can be done at the beginning of the function. At this point, the clock
+ is not running. The code inside the QBENCHMARK macro will be
+ measured, and possibly repeated several times in order to get an
+ accurate measurement.
+
+ Several \l {testlib-benchmarking-measurement}{back-ends} are available
+ and can be selected on the command line.
+
+ \section1 Data Functions
+
+ Data functions are useful for creating benchmarks that compare
+ multiple data inputs, for example locale aware compare against standard
+ compare.
+
+ \snippet tutorial5/benchmarking.cpp 1
+
+ The test function then uses the data to determine what to benchmark.
+
+ \snippet tutorial5/benchmarking.cpp 2
+
+ The \c{if (useLocaleCompare)} switch is placed outside the QBENCHMARK
+ macro to avoid measuring its overhead. Each benchmark test function
+ can have one active QBENCHMARK macro.
+
+ \section1 Building the Executable
+
+ \include {building-examples.qdocinc} {building the executable} {tutorial5}
+
+ \section1 Running the Executable
+
+ Running the resulting executable should give you the following
+ output:
+
+ \snippet code/doc_src_qtestlib.qdoc 14
+*/
diff --git a/src/testlib/doc/src/qttestlib-tutorial6.qdoc b/src/testlib/doc/src/qttestlib-tutorial6.qdoc
new file mode 100644
index 0000000000..602ca0b28e
--- /dev/null
+++ b/src/testlib/doc/src/qttestlib-tutorial6.qdoc
@@ -0,0 +1,50 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qttestlib-tutorial6.html
+
+ \previouspage {Chapter 5: Writing a Benchmark}{Chapter 5}
+
+ \title Chapter 6: Skipping Tests with QSKIP
+ \brief How to skip tests in certain cases.
+
+ \section2 Using QSKIP(\a description) in a test function
+
+ If the \l QSKIP() macro is called from a test function, it stops
+ the execution of the test without adding a failure to the test log.
+ It can be used to skip tests that are certain to fail. The text in
+ the QSKIP \a description parameter is appended to the test log,
+ and should explain why the test was not carried out.
+
+ QSKIP can be used to skip testing when the implementation is not yet
+ complete or not supported on a certain platform. When there are known
+ failures, \l QEXPECT_FAIL is recommended, as it supports running the rest
+ of the test, when possible.
+
+ Example of QSKIP in a test function:
+
+ \snippet code/doc_src_qtqskip_snippet.cpp 0
+
+ In a data-driven test, each call to QSKIP() skips only the current
+ row of test data. If the data-driven test contains an unconditional
+ call to QSKIP, it produces a skip message for each row of test data.
+
+ \section2 Using QSKIP in a _data function
+
+ If called from a _data function, the QSKIP() macro stops
+ execution of the _data function. This prevents execution of the
+ associated test function.
+
+ See below for an example:
+
+ \snippet code/doc_src_qtqskip.cpp 1
+
+ \section2 Using QSKIP from initTestCase() or initTestCase_data()
+
+ If called from \c initTestCase() or \c initTestCase_data(), the
+ QSKIP() macro will skip all test and _data functions.
+
+ \sa {Select Appropriate Mechanisms to Exclude Tests}
+*/
diff --git a/src/testlib/qabstracttestlogger.cpp b/src/testlib/qabstracttestlogger.cpp
index 596799f5c4..de6fb63560 100644
--- a/src/testlib/qabstracttestlogger.cpp
+++ b/src/testlib/qabstracttestlogger.cpp
@@ -148,6 +148,19 @@ QAbstractTestLogger::~QAbstractTestLogger()
}
/*!
+ Returns true if the logger supports repeated test runs.
+
+ Repetition of test runs is disabled by default, and can be enabled only for
+ test loggers that support it. Even if the logger may create syntactically
+ correct test reports, log-file analyzers may assume that test names are
+ unique within one report file.
+*/
+bool QAbstractTestLogger::isRepeatSupported() const
+{
+ return false;
+}
+
+/*!
Returns true if the \c output stream is standard output.
*/
bool QAbstractTestLogger::isLoggingToStdout() const
diff --git a/src/testlib/qabstracttestlogger_p.h b/src/testlib/qabstracttestlogger_p.h
index 188967981c..b4a66cd12a 100644
--- a/src/testlib/qabstracttestlogger_p.h
+++ b/src/testlib/qabstracttestlogger_p.h
@@ -76,6 +76,8 @@ public:
virtual void addMessage(MessageTypes type, const QString &message,
const char *file = nullptr, int line = 0) = 0;
+ virtual bool isRepeatSupported() const;
+
bool isLoggingToStdout() const;
void outputString(const char *msg);
diff --git a/src/testlib/qappletestlogger.cpp b/src/testlib/qappletestlogger.cpp
index db4f17e03a..7517a95344 100644
--- a/src/testlib/qappletestlogger.cpp
+++ b/src/testlib/qappletestlogger.cpp
@@ -3,8 +3,6 @@
#include "qappletestlogger_p.h"
-#include <QPair>
-
QT_BEGIN_NAMESPACE
#if defined(QT_USE_APPLE_UNIFIED_LOGGING)
@@ -106,6 +104,14 @@ void QAppleTestLogger::addIncident(IncidentTypes type, const char *description,
if (qstrlen(description))
message += u'\n' % QString::fromLatin1(description);
+ // As long as the Apple logger doesn't propagate the context's file and
+ // line number we need to manually print it.
+ if (context.line && context.file) {
+ QTestCharBuffer line;
+ QTest::qt_asprintf(&line, "\n [Loc: %s:%d]", context.file, context.line);
+ message += QLatin1String(line.data());
+ }
+
AppleUnifiedLogger::messageHandler(messageData.messageType, context, message, subsystem());
}
diff --git a/src/testlib/qasciikey.cpp b/src/testlib/qasciikey.cpp
index a4b6a14f35..114b757dc4 100644
--- a/src/testlib/qasciikey.cpp
+++ b/src/testlib/qasciikey.cpp
@@ -141,7 +141,7 @@ Qt::Key QTest::asciiToKey(char ascii)
case 0xb2: return Qt::Key_twosuperior;
case 0xb3: return Qt::Key_threesuperior;
case 0xb4: return Qt::Key_acute;
- case 0xb5: return Qt::Key_mu;
+ case 0xb5: return Qt::Key_micro;
case 0xb6: return Qt::Key_paragraph;
case 0xb7: return Qt::Key_periodcentered;
case 0xb8: return Qt::Key_cedilla;
@@ -369,7 +369,7 @@ char QTest::keyToAscii(Qt::Key key)
case Qt::Key_twosuperior: return char(0xb2);
case Qt::Key_threesuperior: return char(0xb3);
case Qt::Key_acute: return char(0xb4);
- case Qt::Key_mu: return char(0xb5);
+ case Qt::Key_micro: return char(0xb5);
case Qt::Key_paragraph: return char(0xb6);
case Qt::Key_periodcentered: return char(0xb7);
case Qt::Key_cedilla: return char(0xb8);
diff --git a/src/testlib/qbenchmark.cpp b/src/testlib/qbenchmark.cpp
index 4a8bd72ee9..af6ee5f7c6 100644
--- a/src/testlib/qbenchmark.cpp
+++ b/src/testlib/qbenchmark.cpp
@@ -167,7 +167,7 @@ QTest::QBenchmarkIterationController::~QBenchmarkIterationController()
/*! \internal
*/
-bool QTest::QBenchmarkIterationController::isDone()
+bool QTest::QBenchmarkIterationController::isDone() const noexcept
{
if (QBenchmarkTestMethodData::current->runOnce)
return i > 0;
@@ -176,14 +176,14 @@ bool QTest::QBenchmarkIterationController::isDone()
/*! \internal
*/
-void QTest::QBenchmarkIterationController::next()
+void QTest::QBenchmarkIterationController::next() noexcept
{
++i;
}
/*! \internal
*/
-int QTest::iterationCount()
+int QTest::iterationCount() noexcept
{
return QBenchmarkTestMethodData::current->iterationCount;
}
diff --git a/src/testlib/qbenchmark.h b/src/testlib/qbenchmark.h
index 20c643b2c6..d0e8c78567 100644
--- a/src/testlib/qbenchmark.h
+++ b/src/testlib/qbenchmark.h
@@ -28,8 +28,8 @@ public:
QBenchmarkIterationController();
QBenchmarkIterationController(RunMode runMode);
~QBenchmarkIterationController();
- bool isDone();
- void next();
+ bool isDone() const noexcept;
+ void next() noexcept;
int i;
};
diff --git a/src/testlib/qbenchmark_p.h b/src/testlib/qbenchmark_p.h
index 09379aac0f..902b092b16 100644
--- a/src/testlib/qbenchmark_p.h
+++ b/src/testlib/qbenchmark_p.h
@@ -149,7 +149,7 @@ public:
// low-level API:
namespace QTest
{
- int iterationCount();
+ int iterationCount() noexcept;
void setIterationCountHint(int count);
void setIterationCount(int count);
diff --git a/src/testlib/qbenchmarkperfevents.cpp b/src/testlib/qbenchmarkperfevents.cpp
index 1b9101e854..c161879a7d 100644
--- a/src/testlib/qbenchmarkperfevents.cpp
+++ b/src/testlib/qbenchmarkperfevents.cpp
@@ -108,7 +108,8 @@ static QList<PerfEvent> defaultCounters()
static int perf_event_open(perf_event_attr *attr, pid_t pid, int cpu, int group_fd, unsigned long flags)
{
#ifdef SYS_perf_event_open
- return syscall(SYS_perf_event_open, attr, pid, cpu, group_fd, flags);
+ // syscall() returns long, but perf_event_open() is used to get a file descriptor
+ return int(syscall(SYS_perf_event_open, attr, pid, cpu, group_fd, flags));
#else
Q_UNUSED(attr);
Q_UNUSED(pid);
diff --git a/src/testlib/qbenchmarkvalgrind.cpp b/src/testlib/qbenchmarkvalgrind.cpp
index 253108bd9d..bea3066e66 100644
--- a/src/testlib/qbenchmarkvalgrind.cpp
+++ b/src/testlib/qbenchmarkvalgrind.cpp
@@ -45,7 +45,10 @@ bool QBenchmarkValgrindUtils::rerunThroughCallgrind(const QStringList &origAppAr
static void dumpOutput(const QByteArray &data, FILE *fh)
{
QFile file;
- file.open(fh, QIODevice::WriteOnly);
+ if (!file.open(fh, QIODevice::WriteOnly)) {
+ qFatal("Could not open filehandle for dumping output: %s",
+ qPrintable(file.errorString()));
+ }
file.write(data);
}
diff --git a/src/testlib/qcomparisontesthelper.cpp b/src/testlib/qcomparisontesthelper.cpp
new file mode 100644
index 0000000000..b71267b625
--- /dev/null
+++ b/src/testlib/qcomparisontesthelper.cpp
@@ -0,0 +1,22 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qcomparisontesthelper_p.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QTestPrivate {
+
+QByteArray formatTypeWithCRefImpl(QMetaType type, bool isConst, bool isRef, bool isRvalueRef)
+{
+ QByteArray res(type.name());
+ if (isConst)
+ res.append(" const");
+ if (isRef)
+ res.append(isRvalueRef ? " &&" : " &");
+ return res;
+}
+
+} // namespace QTestPrivate
+
+QT_END_NAMESPACE
diff --git a/src/testlib/qcomparisontesthelper_p.h b/src/testlib/qcomparisontesthelper_p.h
new file mode 100644
index 0000000000..afeb1088c4
--- /dev/null
+++ b/src/testlib/qcomparisontesthelper_p.h
@@ -0,0 +1,373 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCOMPARISONTESTHELPER_P_H
+#define QCOMPARISONTESTHELPER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/q20type_traits.h>
+#include <QtCore/qxptype_traits.h>
+#include <QtTest/qtest.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QTestPrivate {
+
+#ifdef __cpp_lib_three_way_comparison
+template <typename LT, typename RT>
+using HasThreeWayComparisonOp = decltype(std::declval<LT>() <=> std::declval<RT>());
+
+template <typename LT, typename RT>
+constexpr bool implementsThreeWayComparisonOp_v = qxp::is_detected_v<HasThreeWayComparisonOp,
+ LT, RT>;
+#endif
+
+Q_TESTLIB_EXPORT QByteArray formatTypeWithCRefImpl(QMetaType type, bool isConst,
+ bool isRef, bool isRvalueRef);
+
+template <typename T>
+QByteArray formatTypeWithCRef()
+{
+ return formatTypeWithCRefImpl(QMetaType::fromType<q20::remove_cvref_t<T>>(),
+ std::is_const_v<std::remove_reference_t<T>>,
+ std::is_reference_v<T>,
+ std::is_rvalue_reference_v<T>);
+}
+
+#define FOR_EACH_CREF(Func, Left, Right, Op, Result) \
+ Func(Left &, Right &, Op, Result) \
+ Func(Left &, Right const &, Op, Result) \
+ Func(Left &, Right &&, Op, Result) \
+ Func(Left &, Right const &&, Op, Result) \
+ Func(Left const &, Right &, Op, Result) \
+ Func(Left const &, Right const &, Op, Result) \
+ Func(Left const &, Right &&, Op, Result) \
+ Func(Left const &, Right const &&, Op, Result) \
+ Func(Left &&, Right &, Op, Result) \
+ Func(Left &&, Right const &, Op, Result) \
+ Func(Left &&, Right &&, Op, Result) \
+ Func(Left &&, Right const &&, Op, Result) \
+ Func(Left const &&, Right &, Op, Result) \
+ Func(Left const &&, Right const &, Op, Result) \
+ Func(Left const &&, Right &&, Op, Result) \
+ Func(Left const &&, Right const &&, Op, Result) \
+ /* END */
+
+#define CHECK_SINGLE_OPERATOR(Left, Right, Op, Result) \
+ do { \
+ constexpr bool qtest_op_check_isImplNoexcept \
+ = noexcept(std::declval<Left>() Op std::declval<Right>()); \
+ if constexpr (!qtest_op_check_isImplNoexcept) { \
+ QEXPECT_FAIL("", QByteArray("(" + formatTypeWithCRef<Left>() \
+ + " " #Op " " + formatTypeWithCRef<Right>() \
+ + ") is not noexcept").constData(), \
+ Continue); \
+ /* Ideally, operators should be noexcept, so warn if they are not. */ \
+ /* Do not make it a hard error, because the fix is not always trivial. */ \
+ QVERIFY(qtest_op_check_isImplNoexcept); \
+ } \
+ static_assert(std::is_convertible_v<decltype( \
+ std::declval<Left>() Op std::declval<Right>()), Result>); \
+ if constexpr (!std::is_same_v<Left, Right>) { \
+ static_assert(std::is_convertible_v<decltype( \
+ std::declval<Right>() Op std::declval<Left>()), Result>); \
+ } \
+ } while (false); \
+ /* END */
+
+/*!
+ \internal
+
+ This function checks that the types \c LeftType and \c RightType properly
+ define {in}equality operators (== and !=). The checks are performed for
+ all combinations of cvref-qualified lvalues and rvalues.
+*/
+template <typename LeftType, typename RightType = LeftType>
+void testEqualityOperatorsCompile()
+{
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, ==, bool)
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, !=, bool)
+}
+
+/*!
+ \internal
+
+ This function checks that the types \c LeftType and \c RightType properly
+ define all comparison operators (==, !=, <, >, <=, >=). The checks are
+ performed for all combinations of cvref-qualified lvalues and rvalues.
+
+ If compiled in C++20 mode, also checks \c {operator<=>()} if that is
+ implemented.
+*/
+template <typename LeftType, typename RightType = LeftType>
+void testAllComparisonOperatorsCompile()
+{
+ testEqualityOperatorsCompile<LeftType, RightType>();
+ if (QTest::currentTestFailed())
+ return;
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, >, bool)
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, <, bool)
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, >=, bool)
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, <=, bool)
+#ifdef __cpp_lib_three_way_comparison
+ if constexpr (implementsThreeWayComparisonOp_v<LeftType, RightType>) {
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, <=>, std::partial_ordering)
+ }
+#endif
+}
+
+#undef CHECK_SINGLE_OPERATOR
+#undef FOR_EACH_CREF
+
+#define CHECK_RUNTIME_CREF(Func, Left, Right, Op, Expected) \
+ do { \
+ Func(Left, Right, Op, Expected); \
+ Func(std::as_const(Left), Right, Op, Expected); \
+ Func(Left, std::as_const(Right), Op, Expected); \
+ Func(std::as_const(Left), std::as_const(Right), Op, Expected); \
+ } while (false) \
+ /* END */
+
+#define CHECK_RUNTIME_LR(Left, Right, Op, Expected) \
+ do { \
+ QCOMPARE_EQ(Left Op Right, Expected); \
+ QCOMPARE_EQ(std::move(Left) Op Right, Expected); \
+ QCOMPARE_EQ(Left Op std::move(Right), Expected); \
+ QCOMPARE_EQ(std::move(Left) Op std::move(Right), Expected); \
+ } while (false) \
+ /* END */
+
+#ifdef __cpp_lib_three_way_comparison
+
+// Hide the macro under an ifdef, because it otherwise triggers a warning
+// in Clang C++17 build.
+#define CHECK_RUNTIME_3WAY(Left, Right, Op, Expected) \
+ do { \
+ QCOMPARE_EQ((Left <=> Right) Op 0, Expected); \
+ QCOMPARE_EQ((std::move(Left) <=> Right) Op 0, Expected); \
+ QCOMPARE_EQ((Left <=> std::move(Right)) Op 0, Expected); \
+ QCOMPARE_EQ((std::move(Left) <=> std::move(Right)) Op 0, Expected); \
+ } while (false) \
+ /* END */
+
+#endif // __cpp_lib_three_way_comparison
+
+/*!
+ \internal
+ Basic testing of equality operators.
+
+ The helper function tests {in}equality operators (== and !=) for the \a lhs
+ operand of type \c {LeftType} and the \a rhs operand of type \c {RightType},
+ plus the reverse order of the operands.
+
+ The \a expectedEqual parameter is an expected result for \c {operator==()}.
+
+ \note Any test calling this method will need to check the test state after
+ doing so, if there is any later code in the test.
+
+ \code
+ QTime early(12, 34, 56, 00);
+ QTime later(12, 34, 56, 01);
+ QTestPrivate::testEqualityOperators(early, later, false);
+ if (QTest:currentTestFailed())
+ return;
+ \endcode
+*/
+template <typename LeftType, typename RightType>
+void testEqualityOperators(LeftType lhs, RightType rhs, bool expectedEqual)
+{
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, ==, expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, !=, !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, ==, expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, !=, !expectedEqual);
+}
+
+/*!
+ \internal
+ Basic testing of equality and relation operators.
+
+ The helper function tests all six relation and equality operators
+ (==, !=, <, >, <=, >=) for the \a lhs operand of type \c {LeftType} and
+ the \a rhs operand of type \c {RightType} and all six for the reverse
+ order of the operands.
+
+ If compiled in C++20 mode, also checks \c {operator<=>()} if that is
+ implemented.
+
+ When compiled in C++17 mode, the \c OrderingType must be one of
+ Qt::partial_ordering, Qt::strong_ordering, or Qt::weak_ordering.
+ In C++20 mode, also the \c {std::*_ordering} types can be used.
+
+ The \a expectedOrdering parameter provides the expected
+ relation between \a lhs and \a rhs.
+
+ \note Any test calling this method will need to check the test state after
+ doing so, if there is any later code in the test.
+
+ \code
+ QDateTime now = QDateTime::currentDateTime();
+ QDateTime later = now.addMSec(1);
+ QTestPrivate::testComparisonOperators(now, later, Qt::weak_ordering::less);
+ if (QTest:currentTestFailed())
+ return;
+ \endcode
+*/
+template <typename LeftType, typename RightType, typename OrderingType>
+void testAllComparisonOperators(LeftType lhs, RightType rhs, OrderingType expectedOrdering)
+{
+ constexpr bool isQOrderingType = std::is_same_v<OrderingType, Qt::partial_ordering>
+ || std::is_same_v<OrderingType, Qt::weak_ordering>
+ || std::is_same_v<OrderingType, Qt::strong_ordering>;
+#ifdef __cpp_lib_three_way_comparison
+ constexpr bool isStdOrderingType = std::is_same_v<OrderingType, std::partial_ordering>
+ || std::is_same_v<OrderingType, std::weak_ordering>
+ || std::is_same_v<OrderingType, std::strong_ordering>;
+#else
+ constexpr bool isStdOrderingType = false;
+#endif
+
+ static_assert(isQOrderingType || isStdOrderingType,
+ "Please provide, as the expectedOrdering parameter, a value "
+ "of one of the Qt::{partial,weak,strong}_ordering or "
+ "std::{partial,weak,strong}_ordering types.");
+
+ // We have all sorts of operator==() between Q*Ordering and std::*_ordering
+ // types, so we can just compare to Qt::partial_ordering.
+ const bool expectedEqual = expectedOrdering == Qt::partial_ordering::equivalent;
+ const bool expectedLess = expectedOrdering == Qt::partial_ordering::less;
+ const bool expectedUnordered = expectedOrdering == Qt::partial_ordering::unordered;
+
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, ==,
+ !expectedUnordered && expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, !=,
+ expectedUnordered || !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, <,
+ !expectedUnordered && expectedLess);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, >,
+ !expectedUnordered && !expectedLess && !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, <=,
+ !expectedUnordered && (expectedEqual || expectedLess));
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, >=,
+ !expectedUnordered && !expectedLess);
+#ifdef __cpp_lib_three_way_comparison
+ if constexpr (implementsThreeWayComparisonOp_v<LeftType, RightType>) {
+ if constexpr (std::is_convertible_v<OrderingType, std::strong_ordering>)
+ static_assert(std::is_same_v<decltype(lhs <=> rhs), std::strong_ordering>);
+ else if constexpr (std::is_convertible_v<OrderingType, std::weak_ordering>)
+ static_assert(std::is_same_v<decltype(lhs <=> rhs), std::weak_ordering>);
+ else
+ static_assert(std::is_same_v<decltype(lhs <=> rhs), std::partial_ordering>);
+
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, lhs, rhs, ==,
+ !expectedUnordered && expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, lhs, rhs, !=,
+ expectedUnordered || !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, lhs, rhs, <,
+ !expectedUnordered && expectedLess);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, lhs, rhs, >,
+ !expectedUnordered && !expectedLess && !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, lhs, rhs, <=,
+ !expectedUnordered && (expectedEqual || expectedLess));
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, lhs, rhs, >=,
+ !expectedUnordered && !expectedLess);
+ }
+#endif
+
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, ==,
+ !expectedUnordered && expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, !=,
+ expectedUnordered || !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, <,
+ !expectedUnordered && !expectedLess && !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, >,
+ !expectedUnordered && expectedLess);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, <=,
+ !expectedUnordered && !expectedLess);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, >=,
+ !expectedUnordered && (expectedEqual || expectedLess));
+#ifdef __cpp_lib_three_way_comparison
+ if constexpr (implementsThreeWayComparisonOp_v<LeftType, RightType>) {
+ if constexpr (std::is_convertible_v<OrderingType, std::strong_ordering>)
+ static_assert(std::is_same_v<decltype(rhs <=> lhs), std::strong_ordering>);
+ else if constexpr (std::is_convertible_v<OrderingType, std::weak_ordering>)
+ static_assert(std::is_same_v<decltype(rhs <=> lhs), std::weak_ordering>);
+ else
+ static_assert(std::is_same_v<decltype(rhs <=> lhs), std::partial_ordering>);
+
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, rhs, lhs, ==,
+ !expectedUnordered && expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, rhs, lhs, !=,
+ expectedUnordered || !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, rhs, lhs, <,
+ !expectedUnordered && !expectedLess && !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, rhs, lhs, >,
+ !expectedUnordered && expectedLess);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, rhs, lhs, <=,
+ !expectedUnordered && !expectedLess);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, rhs, lhs, >=,
+ !expectedUnordered && (expectedEqual || expectedLess));
+ }
+#endif
+}
+
+#ifdef __cpp_lib_three_way_comparison
+#undef CHECK_RUNTIME_3WAY
+#endif
+#undef CHECK_RUNTIME_LR
+#undef CHECK_RUNTIME_CREF
+
+} // namespace QTestPrivate
+
+/*!
+ \internal
+
+ A helper macro that calls QTestPrivate::testEqualityOperators(), checks the
+ test's state after the function is executed, and generates a meaningful
+ debug message with the original file and line numbers if the test has
+ failed.
+*/
+#define QT_TEST_EQUALITY_OPS(Left, Right, Expected) \
+ do { \
+ auto report = qScopeGuard([] { \
+ qDebug("testEqualityOperators(" #Left ", " #Right ", " #Expected ") " \
+ "failed in " __FILE__ " on line %d", __LINE__); \
+ }); \
+ QTestPrivate::testEqualityOperators(Left, Right, Expected); \
+ if (QTest::currentTestFailed()) \
+ return; \
+ report.dismiss(); \
+ } while (false)
+
+/*!
+ \internal
+
+ A helper macro that calls QTestPrivate::testAllComparisonOperators(), checks
+ the test's state after the function is executed, and generates a meaningful
+ debug message with the original file and line numbers if the test has
+ failed.
+*/
+#define QT_TEST_ALL_COMPARISON_OPS(Left, Right, Expected) \
+ do { \
+ auto report = qScopeGuard([] { \
+ qDebug("testAllComparisonOperators(" #Left ", " #Right ", " #Expected ") " \
+ "failed in " __FILE__ " on line %d", __LINE__); \
+ }); \
+ QTestPrivate::testAllComparisonOperators(Left, Right, Expected); \
+ if (QTest::currentTestFailed()) \
+ return; \
+ report.dismiss(); \
+ } while (false)
+
+QT_END_NAMESPACE
+
+#endif // QCOMPARISONTESTHELPER_P_H
diff --git a/src/testlib/qemulationdetector_p.h b/src/testlib/qemulationdetector_p.h
index 85bc39d56a..a4b62818af 100644
--- a/src/testlib/qemulationdetector_p.h
+++ b/src/testlib/qemulationdetector_p.h
@@ -1,5 +1,5 @@
// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QEMULATIONDETECTOR_P_H
#define QEMULATIONDETECTOR_P_H
diff --git a/src/testlib/qjunittestlogger.cpp b/src/testlib/qjunittestlogger.cpp
index 670fecdcc5..4ee5788bee 100644
--- a/src/testlib/qjunittestlogger.cpp
+++ b/src/testlib/qjunittestlogger.cpp
@@ -9,13 +9,6 @@
#include <QtTest/private/qbenchmark_p.h>
#include <QtTest/private/qtestlog_p.h>
-#ifdef min // windows.h without NOMINMAX is included by the benchmark headers.
-# undef min
-#endif
-#ifdef max
-# undef max
-#endif
-
#include <QtCore/qlibraryinfo.h>
#include <string.h>
@@ -28,7 +21,7 @@ QT_BEGIN_NAMESPACE
QJUnitTestLogger implements logging in a JUnit-compatible XML format.
The \l{JUnit XML} format was originally developed for Java testing.
- It is supported by \l{Squish Test Center}.
+ It is supported by \l{Test Center}.
*/
// QTBUG-95424 links to further useful documentation.
diff --git a/src/testlib/qplaintestlogger.cpp b/src/testlib/qplaintestlogger.cpp
index 428673d5f8..58c7f6bf5b 100644
--- a/src/testlib/qplaintestlogger.cpp
+++ b/src/testlib/qplaintestlogger.cpp
@@ -16,13 +16,6 @@
#include <stdlib.h>
#include <string.h>
-#ifdef min // windows.h without NOMINMAX is included by the benchmark headers.
-# undef min
-#endif
-#ifdef max
-# undef max
-#endif
-
#include <QtCore/QByteArray>
#include <QtCore/qmath.h>
#include <QtCore/QLibraryInfo>
@@ -110,7 +103,7 @@ template <int N> struct FixedBufString
namespace QTest {
- static const char *incidentType2String(QAbstractTestLogger::IncidentTypes type)
+ static const char *ptIncidentType2String(QAbstractTestLogger::IncidentTypes type)
{
switch (type) {
case QAbstractTestLogger::Skip:
@@ -140,7 +133,7 @@ namespace QTest {
return "RESULT ";
}
- static const char *messageType2String(QAbstractTestLogger::MessageTypes type)
+ static const char *ptMessageType2String(QAbstractTestLogger::MessageTypes type)
{
switch (type) {
case QAbstractTestLogger::QDebug:
@@ -243,7 +236,7 @@ namespace QTest {
QPlainTestLogger implements basic logging of test results.
- The format is Qt-specific and aims to be be easy to read.
+ The format is Qt-specific and aims to be easy to read.
*/
void QPlainTestLogger::outputMessage(const char *str)
@@ -456,7 +449,7 @@ void QPlainTestLogger::stopLogging()
void QPlainTestLogger::enterTestFunction(const char * /*function*/)
{
if (QTestLog::verboseLevel() >= 1)
- printMessage(MessageSource::Other, QTest::messageType2String(Info), "entering");
+ printMessage(MessageSource::Other, QTest::ptMessageType2String(Info), "entering");
}
void QPlainTestLogger::leaveTestFunction()
@@ -471,7 +464,7 @@ void QPlainTestLogger::addIncident(IncidentTypes type, const char *description,
&& QTestLog::verboseLevel() < 0)
return;
- printMessage(MessageSource::Incident, QTest::incidentType2String(type), description, file, line);
+ printMessage(MessageSource::Incident, QTest::ptIncidentType2String(type), description, file, line);
}
void QPlainTestLogger::addBenchmarkResults(const QList<QBenchmarkResult> &results)
@@ -496,7 +489,16 @@ void QPlainTestLogger::addMessage(MessageTypes type, const QString &message,
if (type != QFatal && QTestLog::verboseLevel() < 0)
return;
- printMessage(MessageSource::Other, QTest::messageType2String(type), qPrintable(message), file, line);
+ printMessage(MessageSource::Other, QTest::ptMessageType2String(type), qPrintable(message), file, line);
+}
+
+bool QPlainTestLogger::isRepeatSupported() const
+{
+ // The plain text logger creates unstructured reports. Such reports are not
+ // parser friendly, and are unlikely to be parsed by any test reporting
+ // tools. We can therefore allow repeated test runs with minimum risk that
+ // any parsers fails to handle repeated test names.
+ return true;
}
QT_END_NAMESPACE
diff --git a/src/testlib/qplaintestlogger_p.h b/src/testlib/qplaintestlogger_p.h
index 13228a7631..819a54fd50 100644
--- a/src/testlib/qplaintestlogger_p.h
+++ b/src/testlib/qplaintestlogger_p.h
@@ -43,6 +43,8 @@ public:
void addMessage(MessageTypes type, const QString &message,
const char *file = nullptr, int line = 0) override;
+ bool isRepeatSupported() const override;
+
private:
enum class MessageSource {
Incident,
diff --git a/src/testlib/qpropertytesthelper_p.h b/src/testlib/qpropertytesthelper_p.h
index f2cb82cdd7..c691802a39 100644
--- a/src/testlib/qpropertytesthelper_p.h
+++ b/src/testlib/qpropertytesthelper_p.h
@@ -84,6 +84,14 @@ namespace QTestPrivate {
allocate its returned string using \c {new char[]}, so that it can be used
in place of \l {QTest::toString()}.
+ The \a helperConstructor method is used to create another instance of
+ \c TestedClass. This instance is used to test for binding loops. By default,
+ the method returns a default-constructed \c TestedClass. A custom
+ \a helperConstructor should be provided if \c TestedClass is not
+ default-constructible. Some very specific properties cannot be tested for
+ binding loops. Pass a lambda that returns an \c {std::nullptr} as
+ \a helperConstructor in such case.
+
\note Any test calling this method will need to call
\code
if (QTest::currentTestFailed())
@@ -100,7 +108,9 @@ void testReadWritePropertyBasics(
std::function<bool(const PropertyType &, const PropertyType &)> comparator =
[](const PropertyType &lhs, const PropertyType &rhs) { return lhs == rhs; },
std::function<char *(const PropertyType &)> represent =
- [](const PropertyType &val) { return QTest::toString(val); })
+ [](const PropertyType &val) { return QTest::toString(val); },
+ std::function<std::unique_ptr<TestedClass>(void)> helperConstructor =
+ []() { return std::make_unique<TestedClass>(); })
{
// get the property
const QMetaObject *metaObject = instance.metaObject();
@@ -189,6 +199,45 @@ void testReadWritePropertyBasics(
// value didn't change -> the signal should not be emitted
if (spy)
QCOMPARE(spy->size(), 4);
+
+ // test binding loop
+ if (std::unique_ptr<TestedClass> helperObj = helperConstructor()) {
+ // Reset to 'initial', so that the binding loop test could check the
+ // 'changed' value, because some tests already rely on the 'instance' to
+ // have the 'changed' value once this test passes
+ testedObj.setProperty(propertyName, QVariant::fromValue(initial));
+ const QPropertyBinding<PropertyType> binding([&]() {
+ QObject *obj = static_cast<QObject *>(helperObj.get());
+ obj->setProperty(propertyName, QVariant::fromValue(changed));
+ return obj->property(propertyName).template value<PropertyType>();
+ }, {});
+ bindable.setBinding(binding);
+ QPROPERTY_TEST_COMPARISON_HELPER(
+ testedObj.property(propertyName).template value<PropertyType>(), changed,
+ comparator, represent);
+ QVERIFY2(!binding.error().hasError(), qPrintable(binding.error().description()));
+ }
+}
+
+/*!
+ \internal
+ \overload
+
+ This overload supports the case where the caller only needs to override
+ the default for \a helperConstructor. It uses the defaults for all the other
+ parameters.
+*/
+template<typename TestedClass, typename PropertyType>
+void testReadWritePropertyBasics(
+ TestedClass &instance, const PropertyType &initial, const PropertyType &changed,
+ const char *propertyName,
+ std::function<std::unique_ptr<TestedClass>(void)> helperConstructor)
+{
+ testReadWritePropertyBasics<TestedClass, PropertyType>(
+ instance, initial, changed, propertyName,
+ [](const PropertyType &lhs, const PropertyType &rhs) { return lhs == rhs; },
+ [](const PropertyType &val) { return QTest::toString(val); },
+ helperConstructor);
}
/*!
@@ -224,6 +273,14 @@ void testReadWritePropertyBasics(
allocate its returned string using \c {new char[]}, so that it can be used
in place of \l {QTest::toString()}.
+ The \a helperConstructor method is used to create another instance of
+ \c TestedClass. This instance is used to test for binding loops. By default,
+ the method returns a default-constructed \c TestedClass. A custom
+ \a helperConstructor should be provided if \c TestedClass is not
+ default-constructible. Some very specific properties cannot be tested for
+ binding loops. Pass a lambda that returns an \c {std::nullptr} as
+ \a helperConstructor in such case.
+
\note Any test calling this method will need to call
\code
if (QTest::currentTestFailed())
@@ -242,7 +299,9 @@ void testWriteOncePropertyBasics(
std::function<bool(const PropertyType &, const PropertyType &)> comparator =
[](const PropertyType &lhs, const PropertyType &rhs) { return lhs == rhs; },
std::function<char *(const PropertyType &)> represent =
- [](const PropertyType &val) { return QTest::toString(val); })
+ [](const PropertyType &val) { return QTest::toString(val); },
+ std::function<std::unique_ptr<TestedClass>(void)> helperConstructor =
+ []() { return std::make_unique<TestedClass>(); })
{
// get the property
const QMetaObject *metaObject = instance.metaObject();
@@ -276,10 +335,19 @@ void testWriteOncePropertyBasics(
propObserver.setBinding(bindable.makeBinding());
QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), prior, comparator, represent);
- // Create a binding that sets the 'changed' value to the property
- QProperty<PropertyType> propSetter(changed);
+ // Create a binding that sets the 'changed' value to the property.
+ // This also tests binding loops.
QVERIFY(!bindable.hasBinding());
- bindable.setBinding(Qt::makePropertyBinding(propSetter));
+ std::unique_ptr<TestedClass> helperObj = helperConstructor();
+ QProperty<PropertyType> propSetter(changed); // if the helperConstructor() returns nullptr
+ const QPropertyBinding<PropertyType> binding = helperObj
+ ? Qt::makePropertyBinding([&]() {
+ QObject *obj = static_cast<QObject *>(helperObj.get());
+ obj->setProperty(propertyName, QVariant::fromValue(changed));
+ return obj->property(propertyName).template value<PropertyType>();
+ })
+ : Qt::makePropertyBinding(propSetter);
+ bindable.setBinding(binding);
QVERIFY(bindable.hasBinding());
QPROPERTY_TEST_COMPARISON_HELPER(
@@ -303,6 +371,27 @@ void testWriteOncePropertyBasics(
QVERIFY(!bindable.hasBinding());
}
+/*!
+ \internal
+ \overload
+
+ This overload supports the case where the caller only needs to override
+ the default for \a helperConstructor. It uses the defaults for all the other
+ parameters.
+*/
+template<typename TestedClass, typename PropertyType>
+void testWriteOncePropertyBasics(
+ TestedClass &instance, const PropertyType &prior, const PropertyType &changed,
+ const char *propertyName,
+ bool bindingPreservedOnWrite,
+ std::function<std::unique_ptr<TestedClass>(void)> helperConstructor)
+{
+ testWriteOncePropertyBasics<TestedClass, PropertyType>(
+ instance, prior, changed, propertyName, bindingPreservedOnWrite,
+ [](const PropertyType &lhs, const PropertyType &rhs) { return lhs == rhs; },
+ [](const PropertyType &val) { return QTest::toString(val); },
+ helperConstructor);
+}
/*!
\internal
diff --git a/src/testlib/qsignaldumper.cpp b/src/testlib/qsignaldumper.cpp
index 86c6c521ff..9a6412ffc2 100644
--- a/src/testlib/qsignaldumper.cpp
+++ b/src/testlib/qsignaldumper.cpp
@@ -24,10 +24,21 @@ inline static void qPrintMessage(const QByteArray &ba)
}
Q_GLOBAL_STATIC(QList<QByteArray>, ignoreClasses)
-static int iLevel = 0;
-static int ignoreLevel = 0;
+Q_CONSTINIT static QBasicMutex ignoreClassesMutex;
+Q_CONSTINIT thread_local int iLevel = 0;
+Q_CONSTINIT thread_local int ignoreLevel = 0;
enum { IndentSpacesCount = 4 };
+static bool classIsIgnored(const char *className)
+{
+ if (Q_LIKELY(!ignoreClasses.exists()))
+ return false;
+ QMutexLocker locker(&ignoreClassesMutex);
+ if (ignoreClasses()->isEmpty())
+ return false;
+ return ignoreClasses()->contains(QByteArrayView(className));
+}
+
static void qSignalDumperCallback(QObject *caller, int signal_index, void **argv)
{
Q_ASSERT(caller);
@@ -38,7 +49,7 @@ static void qSignalDumperCallback(QObject *caller, int signal_index, void **argv
QMetaMethod member = QMetaObjectPrivate::signal(mo, signal_index);
Q_ASSERT(member.isValid());
- if (QTest::ignoreClasses() && QTest::ignoreClasses()->contains(mo->className())) {
+ if (classIsIgnored(mo->className())) {
++QTest::ignoreLevel;
return;
}
@@ -98,8 +109,7 @@ static void qSignalDumperCallbackSlot(QObject *caller, int method_index, void **
if (!member.isValid())
return;
- if (QTest::ignoreLevel ||
- (QTest::ignoreClasses() && QTest::ignoreClasses()->contains(mo->className())))
+ if (QTest::ignoreLevel || classIsIgnored(mo->className()))
return;
QByteArray str;
@@ -122,8 +132,7 @@ static void qSignalDumperCallbackSlot(QObject *caller, int method_index, void **
static void qSignalDumperCallbackEndSignal(QObject *caller, int /*signal_index*/)
{
Q_ASSERT(caller); Q_ASSERT(caller->metaObject());
- if (QTest::ignoreClasses()
- && QTest::ignoreClasses()->contains(caller->metaObject()->className())) {
+ if (classIsIgnored(caller->metaObject()->className())) {
--QTest::ignoreLevel;
Q_ASSERT(QTest::ignoreLevel >= 0);
return;
@@ -156,13 +165,15 @@ void QSignalDumper::endDump()
void QSignalDumper::ignoreClass(const QByteArray &klass)
{
+ QMutexLocker locker(&QTest::ignoreClassesMutex);
if (QTest::ignoreClasses())
QTest::ignoreClasses()->append(klass);
}
void QSignalDumper::clearIgnoredClasses()
{
- if (QTest::ignoreClasses())
+ QMutexLocker locker(&QTest::ignoreClassesMutex);
+ if (QTest::ignoreClasses.exists())
QTest::ignoreClasses()->clear();
}
diff --git a/src/testlib/qsignalspy.cpp b/src/testlib/qsignalspy.cpp
new file mode 100644
index 0000000000..a45ca59378
--- /dev/null
+++ b/src/testlib/qsignalspy.cpp
@@ -0,0 +1,318 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsignalspy.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QSignalSpy
+ \inmodule QtTest
+
+ \brief The QSignalSpy class enables introspection of signal emission.
+
+ QSignalSpy can connect to any signal of any object and records its emission.
+ QSignalSpy itself is a list of QVariant lists. Each emission of the signal
+ will append one item to the list, containing the arguments of the signal.
+
+ The following example records all signal emissions for the \c clicked() signal
+ of a QCheckBox:
+
+ \snippet code/doc_src_qsignalspy.cpp 0
+
+ \c{spy.takeFirst()} returns the arguments for the first emitted signal, as a
+ list of QVariant objects. The \c clicked() signal has a single bool argument,
+ which is stored as the first entry in the list of arguments.
+
+ The example below catches a signal from a custom object:
+
+ \snippet code/doc_src_qsignalspy.cpp 1
+
+ \note Non-standard data types need to be registered, using
+ the qRegisterMetaType() function, before you can create a
+ QSignalSpy. For example:
+
+ \snippet code/doc_src_qsignalspy.cpp 2
+
+ To retrieve the instance, you can use qvariant_cast:
+
+ \snippet code/doc_src_qsignalspy.cpp 3
+
+ \section1 Verifying Signal Emissions
+
+ The QSignalSpy class provides an elegant mechanism for capturing the list
+ of signals emitted by an object. However, you should verify its validity
+ after construction. The constructor does a number of sanity checks, such as
+ verifying that the signal to be spied upon actually exists. To make the
+ diagnosis of test failures easier, the results of these checks should be
+ checked by calling \c QVERIFY(spy.isValid()) before proceeding further with
+ a test.
+
+ \sa QVERIFY()
+ */
+
+/*! \fn QSignalSpy::QSignalSpy(const QObject *object, const char *signal)
+
+ Constructs a new QSignalSpy that listens for emissions of the \a signal
+ from the QObject \a object. If QSignalSpy is not able to listen for a
+ valid signal (for example, because \a object is \nullptr or \a signal does
+ not denote a valid signal of \a object), an explanatory warning message
+ will be output using qWarning() and subsequent calls to \c isValid() will
+ return false.
+
+ Example:
+ \snippet code/doc_src_qsignalspy.cpp 4
+*/
+
+/*! \fn template <typename PointerToMemberFunction> QSignalSpy::QSignalSpy(const QObject *object, PointerToMemberFunction signal)
+ \since 5.4
+
+ Constructs a new QSignalSpy that listens for emissions of the \a signal
+ from the QObject \a object. If QSignalSpy is not able to listen for a
+ valid signal (for example, because \a object is \nullptr or \a signal does
+ not denote a valid signal of \a object), an explanatory warning message
+ will be output using qWarning() and subsequent calls to \c isValid() will
+ return false.
+
+ Example:
+ \snippet code/doc_src_qsignalspy.cpp 6
+*/
+
+/*! \fn QSignalSpy::QSignalSpy(const QObject *obj, QMetaMethod signal)
+ \since 5.14
+
+ Constructs a new QSignalSpy that listens for emissions of the \a signal
+ from the QObject \a obj. If QSignalSpy is not able to listen for a
+ valid signal (for example, because \a obj is \nullptr or \a signal does
+ not denote a valid signal of \a obj), an explanatory warning message
+ will be output using qWarning() and subsequent calls to \c isValid() will
+ return false.
+
+ This constructor is convenient to use when Qt's meta-object system is
+ heavily used in a test.
+
+ Basic usage example:
+ \snippet code/doc_src_qsignalspy.cpp 7
+
+ Imagine we need to check whether all properties of the QWindow class
+ that represent minimum and maximum dimensions are properly writable.
+ The following example demonstrates one of the approaches:
+ \snippet code/doc_src_qsignalspy.cpp 8
+*/
+
+/*! \fn QSignalSpy::isValid() const
+
+ Returns \c true if the signal spy listens to a valid signal, otherwise false.
+*/
+
+/*! \fn QSignalSpy::signal() const
+
+ Returns the normalized signal the spy is currently listening to.
+*/
+
+/*! \fn bool QSignalSpy::wait(int timeout)
+ \since 5.0
+
+ This is an overloaded function, equivalent passing \a timeout to the
+ chrono overload:
+ \code
+ wait(std::chrono::milliseconds{timeout});
+ \endcode
+
+ Returns \c true if the signal was emitted at least once in \a timeout,
+ otherwise returns \c false.
+*/
+
+/*!
+ \since 6.6
+
+ Starts an event loop that runs until the given signal is received
+ or \a timeout has passed, whichever happens first.
+
+ \a timeout is any valid std::chrono::duration (std::chrono::seconds,
+ std::chrono::milliseconds ...etc).
+
+ Returns \c true if the signal was emitted at least once in \a timeout,
+ otherwise returns \c false.
+
+ Example:
+ \code
+ using namespace std::chrono_literals;
+ QSignalSpy spy(object, signal);
+ spy.wait(2s);
+ \endcode
+*/
+bool QSignalSpy::wait(std::chrono::milliseconds timeout)
+{
+ QMutexLocker locker(&m_mutex);
+ Q_ASSERT(!m_waiting);
+ const qsizetype origCount = size();
+ m_waiting = true;
+ locker.unlock();
+
+ m_loop.enterLoop(timeout);
+
+ locker.relock();
+ m_waiting = false;
+ return size() > origCount;
+}
+
+static bool isSignalMetaMethodValid(QMetaMethod signal)
+{
+ if (!signal.isValid()) {
+ qWarning("QSignalSpy: Null signal is not valid");
+ return false;
+ }
+
+ if (signal.methodType() != QMetaMethod::Signal) {
+ qWarning("QSignalSpy: Not a signal: '%s'", signal.methodSignature().constData());
+ return false;
+ }
+
+ return true;
+}
+
+static bool isObjectValid(const QObject *object)
+{
+ const bool valid = !!object;
+
+ if (!valid)
+ qWarning("QSignalSpy: Cannot spy on a null object");
+
+ return valid;
+}
+
+QSignalSpy::ObjectSignal QSignalSpy::verify(const QObject *obj, const char *aSignal)
+{
+ if (!isObjectValid(obj))
+ return {};
+
+ if (!aSignal) {
+ qWarning("QSignalSpy: Null signal name is not valid");
+ return {};
+ }
+
+ if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) {
+ qWarning("QSignalSpy: Not a valid signal, use the SIGNAL macro");
+ return {};
+ }
+
+ const QByteArray ba = QMetaObject::normalizedSignature(aSignal + 1);
+ const QMetaObject * const mo = obj->metaObject();
+ const int sigIndex = mo->indexOfMethod(ba.constData());
+ if (sigIndex < 0) {
+ qWarning("QSignalSpy: No such signal: '%s'", ba.constData());
+ return {};
+ }
+
+ return verify(obj, mo->method(sigIndex));
+}
+
+QSignalSpy::ObjectSignal QSignalSpy::verify(const QObject *obj, QMetaMethod signal)
+{
+ if (isObjectValid(obj) && isSignalMetaMethodValid(signal))
+ return {obj, signal};
+ else
+ return {};
+}
+
+QList<int> QSignalSpy::makeArgs(const QMetaMethod &member, const QObject *obj)
+{
+ QList<int> result;
+ result.reserve(member.parameterCount());
+ for (int i = 0; i < member.parameterCount(); ++i) {
+ QMetaType tp = member.parameterMetaType(i);
+ if (!tp.isValid() && obj) {
+ void *argv[] = { &tp, &i };
+ QMetaObject::metacall(const_cast<QObject*>(obj),
+ QMetaObject::RegisterMethodArgumentMetaType,
+ member.methodIndex(), argv);
+ }
+ if (!tp.isValid()) {
+ qWarning("QSignalSpy: Unable to handle parameter '%s' of type '%s' of method '%s',"
+ " use qRegisterMetaType to register it.",
+ member.parameterNames().at(i).constData(),
+ member.parameterTypes().at(i).constData(),
+ member.name().constData());
+ }
+ result.append(tp.id());
+ }
+ return result;
+}
+
+class QSignalSpyPrivate : public QObject
+{
+ QSignalSpy * const q;
+public:
+ explicit QSignalSpyPrivate(QSignalSpy *qq) : q(qq) {}
+
+ int qt_metacall(QMetaObject::Call call, int methodId, void **a) override;
+};
+
+QSignalSpy::QSignalSpy(ObjectSignal os)
+ : args(os.obj ? makeArgs(os.sig, os.obj) : QList<int>{})
+{
+ if (!os.obj)
+ return;
+
+ auto i = std::make_unique<QSignalSpyPrivate>(this);
+
+ const auto signalIndex = os.sig.methodIndex();
+ const auto slotIndex = QObject::staticMetaObject.methodCount();
+ if (!QMetaObject::connect(os.obj, signalIndex,
+ i.get(), slotIndex, Qt::DirectConnection)) {
+ qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
+ return;
+ }
+
+ d_ptr = std::move(i);
+
+ sig = os.sig.methodSignature();
+}
+
+/*!
+ Destructor.
+*/
+QSignalSpy::~QSignalSpy()
+ = default;
+
+void QSignalSpy::appendArgs(void **a)
+{
+ QList<QVariant> list;
+ list.reserve(args.size());
+ for (qsizetype i = 0; i < args.size(); ++i) {
+ const QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
+ if (type == QMetaType::QVariant)
+ list << *reinterpret_cast<QVariant *>(a[i + 1]);
+ else
+ list << QVariant(QMetaType(type), a[i + 1]);
+ }
+ QMutexLocker locker(&m_mutex);
+ append(std::move(list));
+
+ if (m_waiting) {
+ locker.unlock();
+ m_loop.exitLoop();
+ }
+}
+
+/*!
+ \reimp
+ \internal
+*/
+int QSignalSpyPrivate::qt_metacall(QMetaObject::Call call, int methodId, void **a)
+{
+ methodId = QObject::qt_metacall(call, methodId, a);
+ if (methodId < 0)
+ return methodId;
+
+ if (call == QMetaObject::InvokeMetaMethod) {
+ if (methodId == 0) {
+ q->appendArgs(a);
+ }
+ --methodId;
+ }
+ return methodId;
+}
+
+QT_END_NAMESPACE
diff --git a/src/testlib/qsignalspy.h b/src/testlib/qsignalspy.h
index 61192cd9e8..591545b4d5 100644
--- a/src/testlib/qsignalspy.h
+++ b/src/testlib/qsignalspy.h
@@ -6,198 +6,66 @@
#include <QtCore/qbytearray.h>
#include <QtCore/qlist.h>
-#include <QtCore/qobject.h>
#include <QtCore/qmetaobject.h>
#include <QtTest/qtesteventloop.h>
#include <QtCore/qvariant.h>
+#include <QtCore/qmutex.h>
+
+#include <memory>
QT_BEGIN_NAMESPACE
class QVariant;
-
-class QSignalSpy: public QObject, public QList<QList<QVariant> >
+class QSignalSpyPrivate;
+class QSignalSpy : public QList<QList<QVariant> >
{
+ struct ObjectSignal {
+ const QObject *obj;
+ QMetaMethod sig;
+ };
+ friend class QSignalSpyPrivate;
+ std::unique_ptr<QSignalSpyPrivate> d_ptr;
public:
explicit QSignalSpy(const QObject *obj, const char *aSignal)
- : m_waiting(false)
- {
- if (!isObjectValid(obj))
- return;
-
- if (!aSignal) {
- qWarning("QSignalSpy: Null signal name is not valid");
- return;
- }
-
- if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) {
- qWarning("QSignalSpy: Not a valid signal, use the SIGNAL macro");
- return;
- }
-
- const QByteArray ba = QMetaObject::normalizedSignature(aSignal + 1);
- const QMetaObject * const mo = obj->metaObject();
- const int sigIndex = mo->indexOfMethod(ba.constData());
- if (sigIndex < 0) {
- qWarning("QSignalSpy: No such signal: '%s'", ba.constData());
- return;
- }
-
- if (!connectToSignal(obj, sigIndex))
- return;
-
- sig = ba;
- initArgs(mo->method(sigIndex), obj);
- }
-
+ : QSignalSpy(verify(obj, aSignal)) {}
#ifdef Q_QDOC
template <typename PointerToMemberFunction>
QSignalSpy(const QObject *object, PointerToMemberFunction signal);
#else
template <typename Func>
QSignalSpy(const typename QtPrivate::FunctionPointer<Func>::Object *obj, Func signal0)
- : m_waiting(false)
- {
- if (!isObjectValid(obj))
- return;
-
- if (!signal0) {
- qWarning("QSignalSpy: Null signal name is not valid");
- return;
- }
-
- const QMetaObject * const mo = obj->metaObject();
- const QMetaMethod signalMetaMethod = QMetaMethod::fromSignal(signal0);
- const int sigIndex = signalMetaMethod.methodIndex();
-
- if (!isSignalMetaMethodValid(signalMetaMethod))
- return;
-
- if (!connectToSignal(obj, sigIndex))
- return;
-
- sig = signalMetaMethod.methodSignature();
- initArgs(mo->method(sigIndex), obj);
- }
+ : QSignalSpy(verify(obj, QMetaMethod::fromSignal(signal0))) {}
#endif // Q_QDOC
-
- QSignalSpy(const QObject *obj, const QMetaMethod &signal)
- : m_waiting(false)
- {
- if (isObjectValid(obj) && isSignalMetaMethodValid(signal) &&
- connectToSignal(obj, signal.methodIndex())) {
- sig = signal.methodSignature();
- initArgs(signal, obj);
- }
- }
+ QSignalSpy(const QObject *obj, QMetaMethod signal)
+ : QSignalSpy(verify(obj, signal)) {}
+ Q_TESTLIB_EXPORT ~QSignalSpy();
inline bool isValid() const { return !sig.isEmpty(); }
inline QByteArray signal() const { return sig; }
- bool wait(int timeout = 5000)
- {
- Q_ASSERT(!m_waiting);
- const qsizetype origCount = size();
- m_waiting = true;
- m_loop.enterLoopMSecs(timeout);
- m_waiting = false;
- return size() > origCount;
- }
-
- int qt_metacall(QMetaObject::Call call, int methodId, void **a) override
- {
- methodId = QObject::qt_metacall(call, methodId, a);
- if (methodId < 0)
- return methodId;
-
- if (call == QMetaObject::InvokeMetaMethod) {
- if (methodId == 0) {
- appendArgs(a);
- }
- --methodId;
- }
- return methodId;
- }
+ bool wait(int timeout)
+ { return wait(std::chrono::milliseconds{timeout}); }
+
+ Q_TESTLIB_EXPORT bool wait(std::chrono::milliseconds timeout = std::chrono::seconds{5});
private:
- bool connectToSignal(const QObject *sender, int sigIndex)
- {
- static const int memberOffset = QObject::staticMetaObject.methodCount();
- const bool connected = QMetaObject::connect(
- sender, sigIndex, this, memberOffset, Qt::DirectConnection, nullptr);
-
- if (!connected)
- qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
-
- return connected;
- }
-
- static bool isSignalMetaMethodValid(const QMetaMethod &signal)
- {
- const bool valid = signal.isValid() && signal.methodType() == QMetaMethod::Signal;
-
- if (!valid)
- qWarning("QSignalSpy: Not a valid signal: '%s'", signal.methodSignature().constData());
-
- return valid;
- }
-
- static bool isObjectValid(const QObject *object)
- {
- const bool valid = !!object;
-
- if (!valid)
- qWarning("QSignalSpy: Cannot spy on a null object");
-
- return valid;
- }
-
- void initArgs(const QMetaMethod &member, const QObject *obj)
- {
- args.reserve(member.parameterCount());
- for (int i = 0; i < member.parameterCount(); ++i) {
- QMetaType tp = member.parameterMetaType(i);
- if (!tp.isValid() && obj) {
- void *argv[] = { &tp, &i };
- QMetaObject::metacall(const_cast<QObject*>(obj),
- QMetaObject::RegisterMethodArgumentMetaType,
- member.methodIndex(), argv);
- }
- if (!tp.isValid()) {
- qWarning("QSignalSpy: Unable to handle parameter '%s' of type '%s' of method '%s',"
- " use qRegisterMetaType to register it.",
- member.parameterNames().at(i).constData(),
- member.parameterTypes().at(i).constData(),
- member.name().constData());
- }
- args << tp.id();
- }
- }
-
- void appendArgs(void **a)
- {
- QList<QVariant> list;
- list.reserve(args.size());
- for (int i = 0; i < args.size(); ++i) {
- const QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
- if (type == QMetaType::QVariant)
- list << *reinterpret_cast<QVariant *>(a[i + 1]);
- else
- list << QVariant(QMetaType(type), a[i + 1]);
- }
- append(list);
-
- if (m_waiting)
- m_loop.exitLoop();
- }
+ Q_TESTLIB_EXPORT explicit QSignalSpy(ObjectSignal os);
+
+ Q_TESTLIB_EXPORT static ObjectSignal verify(const QObject *obj, QMetaMethod signal);
+ Q_TESTLIB_EXPORT static ObjectSignal verify(const QObject *obj, const char *aSignal);
+
+ Q_TESTLIB_EXPORT static QList<int> makeArgs(const QMetaMethod &member, const QObject *obj);
+ Q_TESTLIB_EXPORT void appendArgs(void **a);
// the full, normalized signal name
QByteArray sig;
// holds the QMetaType types for the argument list of the signal
- QList<int> args;
+ const QList<int> args;
QTestEventLoop m_loop;
- bool m_waiting;
+ bool m_waiting = false;
+ QMutex m_mutex; // protects m_waiting and the QList base class, between appendArgs() and wait()
};
QT_END_NAMESPACE
diff --git a/src/testlib/qsignalspy.qdoc b/src/testlib/qsignalspy.qdoc
deleted file mode 100644
index 85b91589f7..0000000000
--- a/src/testlib/qsignalspy.qdoc
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (C) 2019 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
-
-/*!
- \class QSignalSpy
- \inmodule QtTest
-
- \brief The QSignalSpy class enables introspection of signal emission.
-
- QSignalSpy can connect to any signal of any object and records its emission.
- QSignalSpy itself is a list of QVariant lists. Each emission of the signal
- will append one item to the list, containing the arguments of the signal.
-
- The following example records all signal emissions for the \c clicked() signal
- of a QCheckBox:
-
- \snippet code/doc_src_qsignalspy.cpp 0
-
- \c{spy.takeFirst()} returns the arguments for the first emitted signal, as a
- list of QVariant objects. The \c clicked() signal has a single bool argument,
- which is stored as the first entry in the list of arguments.
-
- The example below catches a signal from a custom object:
-
- \snippet code/doc_src_qsignalspy.cpp 1
-
- \note Non-standard data types need to be registered, using
- the qRegisterMetaType() function, before you can create a
- QSignalSpy. For example:
-
- \snippet code/doc_src_qsignalspy.cpp 2
-
- To retrieve the instance, you can use qvariant_cast:
-
- \snippet code/doc_src_qsignalspy.cpp 3
-
- \section1 Verifying Signal Emissions
-
- The QSignalSpy class provides an elegant mechanism for capturing the list
- of signals emitted by an object. However, you should verify its validity
- after construction. The constructor does a number of sanity checks, such as
- verifying that the signal to be spied upon actually exists. To make the
- diagnosis of test failures easier, the results of these checks should be
- checked by calling \c QVERIFY(spy.isValid()) before proceeding further with
- a test.
-
- \sa QVERIFY()
- */
-
-/*! \fn QSignalSpy::QSignalSpy(const QObject *object, const char *signal)
-
- Constructs a new QSignalSpy that listens for emissions of the \a signal
- from the QObject \a object. If QSignalSpy is not able to listen for a
- valid signal (for example, because \a object is \nullptr or \a signal does
- not denote a valid signal of \a object), an explanatory warning message
- will be output using qWarning() and subsequent calls to \c isValid() will
- return false.
-
- Example:
- \snippet code/doc_src_qsignalspy.cpp 4
-*/
-
-/*! \fn template <typename PointerToMemberFunction> QSignalSpy::QSignalSpy(const QObject *object, PointerToMemberFunction signal)
- \since 5.4
-
- Constructs a new QSignalSpy that listens for emissions of the \a signal
- from the QObject \a object. If QSignalSpy is not able to listen for a
- valid signal (for example, because \a object is \nullptr or \a signal does
- not denote a valid signal of \a object), an explanatory warning message
- will be output using qWarning() and subsequent calls to \c isValid() will
- return false.
-
- Example:
- \snippet code/doc_src_qsignalspy.cpp 6
-*/
-
-/*! \fn QSignalSpy::QSignalSpy(const QObject *obj, const QMetaMethod &signal)
- \since 5.14
-
- Constructs a new QSignalSpy that listens for emissions of the \a signal
- from the QObject \a obj. If QSignalSpy is not able to listen for a
- valid signal (for example, because \a obj is \nullptr or \a signal does
- not denote a valid signal of \a obj), an explanatory warning message
- will be output using qWarning() and subsequent calls to \c isValid() will
- return false.
-
- This constructor is convenient to use when Qt's meta-object system is
- heavily used in a test.
-
- Basic usage example:
- \snippet code/doc_src_qsignalspy.cpp 7
-
- Imagine we need to check whether all properties of the QWindow class
- that represent minimum and maximum dimensions are properly writable.
- The following example demonstrates one of the approaches:
- \snippet code/doc_src_qsignalspy.cpp 8
-*/
-
-/*! \fn QSignalSpy::isValid() const
-
- Returns \c true if the signal spy listens to a valid signal, otherwise false.
-*/
-
-/*! \fn QSignalSpy::signal() const
-
- Returns the normalized signal the spy is currently listening to.
-*/
-
-/*! \fn int QSignalSpy::qt_metacall(QMetaObject::Call call, int id, void **a)
- \internal
-*/
-
-/*! \fn QSignalSpy::wait(int timeout)
-
- \since 5.0
-
- Starts an event loop that runs until the given signal is received.
- Optionally the event loop can return earlier on a \a timeout (in milliseconds).
-
- Returns \c true if the signal was emitted at least once in \a timeout milliseconds, otherwise returns \c false.
-
- Example:
- \snippet code/doc_src_qsignalspy.cpp 5
-*/
diff --git a/src/testlib/qtaptestlogger.cpp b/src/testlib/qtaptestlogger.cpp
index 2c87c15d6c..76f0ba0e8b 100644
--- a/src/testlib/qtaptestlogger.cpp
+++ b/src/testlib/qtaptestlogger.cpp
@@ -302,8 +302,8 @@ void QTapTestLogger::addIncident(IncidentTypes type, const char *description,
static const QRegularExpression compareOpRegex(
u"^(?<message>.*)\n"
- "\\s*Left\\s+\\((?<actualexpression>.*)\\)\\s*: (?<actual>.*)\n"
- "\\s*Right\\s+\\((?<expectedexpresssion>.*)\\)\\s*: "
+ "\\s*Computed\\s+\\((?<actualexpression>.*)\\)\\s*: (?<actual>.*)\n"
+ "\\s*Baseline\\s+\\((?<expectedexpresssion>.*)\\)\\s*: "
"(?<expected>.*)$"_s);
const QString descriptionString = QString::fromUtf8(description);
diff --git a/src/testlib/qteamcitylogger.cpp b/src/testlib/qteamcitylogger.cpp
index a3e68ff31c..840a0334f6 100644
--- a/src/testlib/qteamcitylogger.cpp
+++ b/src/testlib/qteamcitylogger.cpp
@@ -7,8 +7,8 @@
#include <QtTest/private/qtestlog_p.h>
#include <QtTest/private/qteamcitylogger_p.h>
#include <QtCore/qbytearray.h>
+#include <private/qlocale_p.h>
-#include <cctype>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
@@ -21,7 +21,7 @@ using namespace Qt::StringLiterals;
namespace QTest {
- static const char *incidentType2String(QAbstractTestLogger::IncidentTypes type)
+ static const char *tcIncidentType2String(QAbstractTestLogger::IncidentTypes type)
{
switch (type) {
case QAbstractTestLogger::Skip:
@@ -46,7 +46,7 @@ namespace QTest {
return "??????";
}
- static const char *messageType2String(QAbstractTestLogger::MessageTypes type)
+ static const char *tcMessageType2String(QAbstractTestLogger::MessageTypes type)
{
switch (type) {
case QAbstractTestLogger::QDebug:
@@ -135,7 +135,7 @@ void QTeamCityLogger::addIncident(IncidentTypes type, const char *description,
}
if (type == QAbstractTestLogger::XFail) {
- addPendingMessage(QTest::incidentType2String(type), description, file, line);
+ addPendingMessage(QTest::tcIncidentType2String(type), description, file, line);
return;
}
@@ -197,7 +197,7 @@ void QTeamCityLogger::addMessage(MessageTypes type, const QString &message,
QTestCharBuffer escapedMessage;
tcEscapedString(&escapedMessage, qUtf8Printable(message));
- addPendingMessage(QTest::messageType2String(type), escapedMessage.constData(), file, line);
+ addPendingMessage(QTest::tcMessageType2String(type), escapedMessage.constData(), file, line);
}
void QTeamCityLogger::tcEscapedString(QTestCharBuffer *buf, const char *str) const
@@ -235,7 +235,7 @@ void QTeamCityLogger::tcEscapedString(QTestCharBuffer *buf, const char *str) con
swallowSpace = false;
break;
default:
- if (std::isspace(ch)) {
+ if (ascii_isspace(ch)) {
if (swallowSpace)
continue;
swallowSpace = true;
@@ -252,7 +252,7 @@ void QTeamCityLogger::tcEscapedString(QTestCharBuffer *buf, const char *str) con
Q_ASSERT(p[-1] == ' ');
--p;
}
- Q_ASSERT(p == buf->data() || !std::isspace(p[-1]));
+ Q_ASSERT(p == buf->data() || !ascii_isspace(p[-1]));
*p = '\0';
}
diff --git a/src/testlib/qtest.h b/src/testlib/qtest.h
index f87995e0db..ff3744a48e 100644
--- a/src/testlib/qtest.h
+++ b/src/testlib/qtest.h
@@ -12,34 +12,13 @@
#include <QtTest/qttestglobal.h>
#include <QtTest/qtestcase.h>
#include <QtTest/qtestdata.h>
+#include <QtTest/qtesttostring.h>
#include <QtTest/qbenchmark.h>
-#include <QtCore/qbitarray.h>
-#include <QtCore/qbytearray.h>
-#include <QtCore/qcborarray.h>
-#include <QtCore/qcborcommon.h>
-#include <QtCore/qcbormap.h>
-#include <QtCore/qcborvalue.h>
-#include <QtCore/qstring.h>
-#include <QtCore/qstringlist.h>
-#include <QtCore/qcborcommon.h>
-#include <QtCore/qdatetime.h>
-#if QT_CONFIG(itemmodel)
-#include <QtCore/qabstractitemmodel.h>
-#endif
-#include <QtCore/qobject.h>
-#include <QtCore/qvariant.h>
-#include <QtCore/qurl.h>
-#include <QtCore/quuid.h>
-
#if defined(TESTCASE_LOWDPI)
#include <QtCore/qcoreapplication.h>
#endif
-#include <QtCore/qpoint.h>
-#include <QtCore/qsize.h>
-#include <QtCore/qrect.h>
-
#include <initializer_list>
#include <memory>
@@ -48,346 +27,6 @@ QT_BEGIN_NAMESPACE
namespace QTest
{
-template <> inline char *toString(const QStringView &str)
-{
- return QTest::toPrettyUnicode(str);
-}
-
-template<> inline char *toString(const QString &str)
-{
- return toString(QStringView(str));
-}
-
-template<> inline char *toString(const QLatin1StringView &str)
-{
- return toString(QString(str));
-}
-
-template<> inline char *toString(const QByteArray &ba)
-{
- return QTest::toPrettyCString(ba.constData(), ba.size());
-}
-
-template<> inline char *toString(const QBitArray &ba)
-{
- qsizetype size = ba.size();
- char *str = new char[size + 1];
- for (qsizetype i = 0; i < size; ++i)
- str[i] = "01"[ba.testBit(i)];
- str[size] = '\0';
- return str;
-}
-
-#if QT_CONFIG(datestring)
-template<> inline char *toString(const QTime &time)
-{
- return time.isValid()
- ? qstrdup(qPrintable(time.toString(u"hh:mm:ss.zzz")))
- : qstrdup("Invalid QTime");
-}
-
-template<> inline char *toString(const QDate &date)
-{
- return date.isValid()
- ? qstrdup(qPrintable(date.toString(u"yyyy/MM/dd")))
- : qstrdup("Invalid QDate");
-}
-
-template<> inline char *toString(const QDateTime &dateTime)
-{
- return dateTime.isValid()
- ? qstrdup(qPrintable(dateTime.toString(u"yyyy/MM/dd hh:mm:ss.zzz[t]")))
- : qstrdup("Invalid QDateTime");
-}
-#endif // datestring
-
-template<> inline char *toString(const QCborError &c)
-{
- // use the Q_ENUM formatting
- return toString(c.c);
-}
-
-template<> inline char *toString(const QChar &c)
-{
- const ushort uc = c.unicode();
- if (uc < 128) {
- char msg[32] = {'\0'};
- qsnprintf(msg, sizeof(msg), "QChar: '%c' (0x%x)", char(uc), unsigned(uc));
- return qstrdup(msg);
- }
- return qstrdup(qPrintable(QString::fromLatin1("QChar: '%1' (0x%2)").arg(c).arg(QString::number(static_cast<int>(c.unicode()), 16))));
-}
-
-#if QT_CONFIG(itemmodel)
-template<> inline char *toString(const QModelIndex &idx)
-{
- char msg[128];
- qsnprintf(msg, sizeof(msg), "QModelIndex(%d,%d,%p,%p)", idx.row(), idx.column(), idx.internalPointer(), idx.model());
- return qstrdup(msg);
-}
-#endif
-
-template<> inline char *toString(const QPoint &p)
-{
- char msg[128] = {'\0'};
- qsnprintf(msg, sizeof(msg), "QPoint(%d,%d)", p.x(), p.y());
- return qstrdup(msg);
-}
-
-template<> inline char *toString(const QSize &s)
-{
- char msg[128] = {'\0'};
- qsnprintf(msg, sizeof(msg), "QSize(%dx%d)", s.width(), s.height());
- return qstrdup(msg);
-}
-
-template<> inline char *toString(const QRect &s)
-{
- char msg[256] = {'\0'};
- qsnprintf(msg, sizeof(msg), "QRect(%d,%d %dx%d) (bottomright %d,%d)",
- s.left(), s.top(), s.width(), s.height(), s.right(), s.bottom());
- return qstrdup(msg);
-}
-
-template<> inline char *toString(const QPointF &p)
-{
- char msg[64] = {'\0'};
- qsnprintf(msg, sizeof(msg), "QPointF(%g,%g)", p.x(), p.y());
- return qstrdup(msg);
-}
-
-template<> inline char *toString(const QSizeF &s)
-{
- char msg[64] = {'\0'};
- qsnprintf(msg, sizeof(msg), "QSizeF(%gx%g)", s.width(), s.height());
- return qstrdup(msg);
-}
-
-template<> inline char *toString(const QRectF &s)
-{
- char msg[256] = {'\0'};
- qsnprintf(msg, sizeof(msg), "QRectF(%g,%g %gx%g) (bottomright %g,%g)",
- s.left(), s.top(), s.width(), s.height(), s.right(), s.bottom());
- return qstrdup(msg);
-}
-
-template<> inline char *toString(const QUrl &uri)
-{
- if (!uri.isValid())
- return qstrdup(qPrintable(QLatin1StringView("Invalid URL: ") + uri.errorString()));
- return qstrdup(uri.toEncoded().constData());
-}
-
-template <> inline char *toString(const QUuid &uuid)
-{
- return qstrdup(uuid.toByteArray().constData());
-}
-
-template<> inline char *toString(const QVariant &v)
-{
- QByteArray vstring("QVariant(");
- if (v.isValid()) {
- QByteArray type(v.typeName());
- if (type.isEmpty()) {
- type = QByteArray::number(v.userType());
- }
- vstring.append(type);
- if (!v.isNull()) {
- vstring.append(',');
- if (v.canConvert<QString>()) {
- vstring.append(v.toString().toLocal8Bit());
- }
- else {
- vstring.append("<value not representable as string>");
- }
- }
- }
- vstring.append(')');
-
- return qstrdup(vstring.constData());
-}
-
-template<> inline char *toString(const QPartialOrdering &o)
-{
- if (o == QPartialOrdering::Less)
- return qstrdup("Less");
- if (o == QPartialOrdering::Equivalent)
- return qstrdup("Equivalent");
- if (o == QPartialOrdering::Greater)
- return qstrdup("Greater");
- if (o == QPartialOrdering::Unordered)
- return qstrdup("Unordered");
- return qstrdup("<invalid>");
-}
-
-namespace Internal {
-struct QCborValueFormatter
-{
- enum { BufferLen = 256 };
- static char *formatSimpleType(QCborSimpleType st)
- {
- char *buf = new char[BufferLen];
- qsnprintf(buf, BufferLen, "QCborValue(QCborSimpleType(%d))", int(st));
- return buf;
- }
-
- static char *formatTag(QCborTag tag, const QCborValue &taggedValue)
- {
- QScopedArrayPointer<char> hold(format(taggedValue));
- char *buf = new char[BufferLen];
- qsnprintf(buf, BufferLen, "QCborValue(QCborTag(%llu), %s)", tag, hold.get());
- return buf;
- }
-
- static char *innerFormat(QCborValue::Type t, const char *str)
- {
- static const QMetaEnum typeEnum = []() {
- int idx = QCborValue::staticMetaObject.indexOfEnumerator("Type");
- return QCborValue::staticMetaObject.enumerator(idx);
- }();
-
- char *buf = new char[BufferLen];
- const char *typeName = typeEnum.valueToKey(t);
- if (typeName)
- qsnprintf(buf, BufferLen, "QCborValue(%s, %s)", typeName, str);
- else
- qsnprintf(buf, BufferLen, "QCborValue(<unknown type 0x%02x>)", t);
- return buf;
- }
-
- template<typename T> static char *format(QCborValue::Type type, const T &t)
- {
- QScopedArrayPointer<char> hold(QTest::toString(t));
- return innerFormat(type, hold.get());
- }
-
- static char *format(const QCborValue &v)
- {
- switch (v.type()) {
- case QCborValue::Integer:
- return format(v.type(), v.toInteger());
- case QCborValue::ByteArray:
- return format(v.type(), v.toByteArray());
- case QCborValue::String:
- return format(v.type(), v.toString());
- case QCborValue::Array:
- return innerFormat(v.type(), QScopedArrayPointer<char>(format(v.toArray())).get());
- case QCborValue::Map:
- return innerFormat(v.type(), QScopedArrayPointer<char>(format(v.toMap())).get());
- case QCborValue::Tag:
- return formatTag(v.tag(), v.taggedValue());
- case QCborValue::SimpleType:
- break;
- case QCborValue::True:
- return qstrdup("QCborValue(true)");
- case QCborValue::False:
- return qstrdup("QCborValue(false)");
- case QCborValue::Null:
- return qstrdup("QCborValue(nullptr)");
- case QCborValue::Undefined:
- return qstrdup("QCborValue()");
- case QCborValue::Double:
- return format(v.type(), v.toDouble());
- case QCborValue::DateTime:
- case QCborValue::Url:
- case QCborValue::RegularExpression:
- return format(v.type(), v.taggedValue().toString());
- case QCborValue::Uuid:
- return format(v.type(), v.toUuid());
- case QCborValue::Invalid:
- return qstrdup("QCborValue(<invalid>)");
- }
-
- if (v.isSimpleType())
- return formatSimpleType(v.toSimpleType());
- return innerFormat(v.type(), "");
- }
-
- static char *format(const QCborArray &a)
- {
- QByteArray out(1, '[');
- const char *comma = "";
- for (QCborValueConstRef v : a) {
- QScopedArrayPointer<char> s(format(v));
- out += comma;
- out += s.get();
- comma = ", ";
- }
- out += ']';
- return qstrdup(out.constData());
- }
-
- static char *format(const QCborMap &m)
- {
- QByteArray out(1, '{');
- const char *comma = "";
- for (auto pair : m) {
- QScopedArrayPointer<char> key(format(pair.first));
- QScopedArrayPointer<char> value(format(pair.second));
- out += comma;
- out += key.get();
- out += ": ";
- out += value.get();
- comma = ", ";
- }
- out += '}';
- return qstrdup(out.constData());
- }
-};
-}
-
-template<> inline char *toString(const QCborValue &v)
-{
- return Internal::QCborValueFormatter::format(v);
-}
-
-template<> inline char *toString(const QCborValueRef &v)
-{
- return toString(QCborValue(v));
-}
-
-template<> inline char *toString(const QCborArray &a)
-{
- return Internal::QCborValueFormatter::format(a);
-}
-
-template<> inline char *toString(const QCborMap &m)
-{
- return Internal::QCborValueFormatter::format(m);
-}
-
-template <typename T1, typename T2>
-inline char *toString(const std::pair<T1, T2> &pair)
-{
- const QScopedArrayPointer<char> first(toString(pair.first));
- const QScopedArrayPointer<char> second(toString(pair.second));
- return formatString("std::pair(", ")", 2, first.data(), second.data());
-}
-
-template <typename Tuple, int... I>
-inline char *toString(const Tuple & tuple, QtPrivate::IndexesList<I...>) {
- using UP = std::unique_ptr<char[]>;
- // Generate a table of N + 1 elements where N is the number of
- // elements in the tuple.
- // The last element is needed to support the empty tuple use case.
- const UP data[] = {
- UP(toString(std::get<I>(tuple)))..., UP{}
- };
- return formatString("std::tuple(", ")", sizeof...(I), data[I].get()...);
-}
-
-template <class... Types>
-inline char *toString(const std::tuple<Types...> &tuple)
-{
- static const std::size_t params_count = sizeof...(Types);
- return toString(tuple, typename QtPrivate::Indexes<params_count>::Value());
-}
-
-inline char *toString(std::nullptr_t)
-{
- return toString(QStringLiteral("nullptr"));
-}
-
template<>
inline bool qCompare(QString const &t1, QLatin1StringView const &t2, const char *actual,
const char *expected, const char *file, int line)
@@ -628,7 +267,7 @@ void qRegister##TestObject() \
QTEST_SET_MAIN_SOURCE_PATH \
return QTest::qExec(&tc, argc, argv); \
}; \
- QTest::qRegisterTestCase(BATCHED_TEST_NAME, runTest); \
+ QTest::qRegisterTestCase(QStringLiteral(BATCHED_TEST_NAME), runTest); \
} \
\
Q_CONSTRUCTOR_FUNCTION(qRegister##TestObject)
diff --git a/src/testlib/qtest_gui.h b/src/testlib/qtest_gui.h
index e748b5e0e2..667debb3c4 100644
--- a/src/testlib/qtest_gui.h
+++ b/src/testlib/qtest_gui.h
@@ -17,12 +17,15 @@
#include <QtTest/qtestevent.h>
#include <QtTest/qtestmouse.h>
#include <QtTest/qtesttouch.h>
+#include <QtTest/qtestwheel.h>
#include <QtTest/qtestkeyboard.h>
#include <QtGui/qcolor.h>
#include <QtGui/qpixmap.h>
#include <QtGui/qimage.h>
+#if QT_CONFIG(shortcut)
#include <QtGui/qkeysequence.h>
+#endif
#include <QtGui/qregion.h>
#include <QtGui/qvector2d.h>
#include <QtGui/qvector3d.h>
@@ -104,10 +107,12 @@ template<> inline char *toString(const QVector4D &v)
}
#endif // !QT_NO_VECTOR4D
+#if QT_CONFIG(shortcut)
template<> inline char *toString(const QKeySequence &keySequence)
{
return toString(keySequence.toString());
}
+#endif
inline bool qCompare(QIcon const &t1, QIcon const &t2, const char *actual, const char *expected,
const char *file, int line)
diff --git a/src/testlib/qtest_network.h b/src/testlib/qtest_network.h
index 61f80a536c..403a663ae2 100644
--- a/src/testlib/qtest_network.h
+++ b/src/testlib/qtest_network.h
@@ -4,7 +4,7 @@
#ifndef QTEST_NETWORK_H
#define QTEST_NETWORK_H
-#include <QtTest/qtest.h>
+#include <QtTest/qtesttostring.h>
// enable NETWORK features
#ifndef QT_NETWORK_LIB
@@ -43,6 +43,7 @@ inline char *toString<QHostAddress>(const QHostAddress &addr)
return toString(addr.toString());
}
+} // namespace QTest
inline char *toString(QNetworkReply::NetworkError code)
{
@@ -57,7 +58,7 @@ inline char *toString(QNetworkReply::NetworkError code)
inline char *toString(const QNetworkCookie &cookie)
{
- return toString(cookie.toRawForm());
+ return QTest::toString(cookie.toRawForm());
}
inline char *toString(const QList<QNetworkCookie> &list)
@@ -69,11 +70,9 @@ inline char *toString(const QList<QNetworkCookie> &list)
result.chop(2); // remove trailing ", "
}
result.append(')');
- return toString(result);
+ return QTest::toString(result);
}
-} // namespace QTest
-
QT_END_NAMESPACE
#endif
diff --git a/src/testlib/qtest_widgets.h b/src/testlib/qtest_widgets.h
index 2b60d94fd7..90ee3aa6aa 100644
--- a/src/testlib/qtest_widgets.h
+++ b/src/testlib/qtest_widgets.h
@@ -62,15 +62,16 @@ inline QByteArray toString(QSizePolicy sp)
}
} // namespace Internal
+} // namespace QTest
inline char *toString(QSizePolicy::Policy p)
{
- return qstrdup(Internal::toString(p));
+ return qstrdup(QTest::Internal::toString(p));
}
inline char *toString(QSizePolicy::ControlTypes ct)
{
- return qstrdup(Internal::toString(ct).constData());
+ return qstrdup(QTest::Internal::toString(ct).constData());
}
inline char *toString(QSizePolicy::ControlType ct)
@@ -80,11 +81,9 @@ inline char *toString(QSizePolicy::ControlType ct)
inline char *toString(QSizePolicy sp)
{
- return qstrdup(Internal::toString(sp).constData());
+ return qstrdup(QTest::Internal::toString(sp).constData());
}
-} // namespace QTest
-
QT_END_NAMESPACE
#endif
diff --git a/src/testlib/qtestaccessible.h b/src/testlib/qtestaccessible.h
index a9b237bd5d..a4236aa5f3 100644
--- a/src/testlib/qtestaccessible.h
+++ b/src/testlib/qtestaccessible.h
@@ -108,7 +108,7 @@ public:
qWarning("Timeout waiting for accessibility event.");
return false;
}
- const bool res = *eventList().first() == *ev;
+ const bool res = *eventList().constFirst() == *ev;
if (!res)
qWarning("%s", qPrintable(msgAccessibilityEventListMismatch(eventList(), ev)));
delete eventList().takeFirst();
@@ -150,8 +150,8 @@ private:
static void updateHandler(QAccessibleEvent *event)
{
auto ev = copyEvent(event);
- if (ev->object()) {
- QObject::connect(ev->object(), &QObject::destroyed, [&, ev](){
+ if (auto obj = ev->object()) {
+ QObject::connect(obj, &QObject::destroyed, obj, [&, ev](){
auto index= eventList().indexOf(ev);
if (index == -1)
return;
diff --git a/src/testlib/qtestassert.h b/src/testlib/qtestassert.h
index f112d847ae..63ebbed71c 100644
--- a/src/testlib/qtestassert.h
+++ b/src/testlib/qtestassert.h
@@ -8,10 +8,9 @@
QT_BEGIN_NAMESPACE
+#define QTEST_ASSERT(cond) ((cond) ? static_cast<void>(0) : qt_assert(#cond, __FILE__, __LINE__))
-#define QTEST_ASSERT(cond) do { if (!(cond)) qt_assert(#cond,__FILE__,__LINE__); } while (false)
-
-#define QTEST_ASSERT_X(cond, where, what) do { if (!(cond)) qt_assert_x(where, what,__FILE__,__LINE__); } while (false)
+#define QTEST_ASSERT_X(cond, where, what) ((cond) ? static_cast<void>(0) : qt_assert_x(where, what, __FILE__, __LINE__))
QT_END_NAMESPACE
diff --git a/src/testlib/qtestblacklist.cpp b/src/testlib/qtestblacklist.cpp
index 868971fc17..4154f8f2a6 100644
--- a/src/testlib/qtestblacklist.cpp
+++ b/src/testlib/qtestblacklist.cpp
@@ -28,8 +28,7 @@ using namespace Qt::StringLiterals;
Each blacklist line is interpreted as a list of keywords in an AND-relationship.
To blacklist a test for multiple platforms (OR-relationship), use separate lines.
- The key "ci" applies only when run by COIN. The key "cmake" applies when Qt
- is built using CMake. Other keys name platforms, operating systems,
+ The key "ci" applies only when run by COIN. Other keys name platforms, operating systems,
distributions, tool-chains or architectures; a ! prefix reverses what it
checks. A version, joined to a key (at present, only for distributions and
for msvc) with a hyphen, limits the key to the specific version. A keyword
@@ -47,7 +46,8 @@ using namespace Qt::StringLiterals;
data row's name coincides with that of a local data row, some unintended
matches may result; try to keep your data-row tags distinct.)
- Subsequent lines give conditions for ignoring this test.
+ Subsequent lines give conditions for ignoring this test. You need at least
+ one or the group has no effect.
# See qtbase/src/testlib/qtestblacklist.cpp for format
# Test doesn't work on QNX at all
@@ -118,6 +118,9 @@ static QSet<QByteArray> keywords()
#ifdef Q_OS_WATCHOS
<< "watchos"
#endif
+#ifdef Q_OS_VISIONOS
+ << "visionos"
+#endif
#ifdef Q_OS_ANDROID
<< "android"
#endif
@@ -169,8 +172,6 @@ static QSet<QByteArray> keywords()
#ifdef QT_BUILD_INTERNAL
<< "developer-build"
#endif
-
- << "cmake"
;
QCoreApplication *app = QCoreApplication::instance();
@@ -278,7 +279,8 @@ void parseBlackList()
}
}
-void checkBlackLists(const char *slot, const char *data, const char *global)
+// Returns \c true if this test-case is blacklisted.
+bool checkBlackLists(const char *slot, const char *data, const char *global)
{
bool ignore = ignoreAll;
@@ -300,7 +302,7 @@ void checkBlackLists(const char *slot, const char *data, const char *global)
}
}
- QTestResult::setBlacklistCurrentTest(ignore);
+ return ignore;
}
} // QTestPrivate
diff --git a/src/testlib/qtestblacklist_p.h b/src/testlib/qtestblacklist_p.h
index 2bc9b684ab..3bba0e7672 100644
--- a/src/testlib/qtestblacklist_p.h
+++ b/src/testlib/qtestblacklist_p.h
@@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE
namespace QTestPrivate {
// Export functions so they can also be used by QQuickTest
Q_TESTLIB_EXPORT void parseBlackList();
- Q_TESTLIB_EXPORT void checkBlackLists(const char *slot, const char *data,
+ Q_TESTLIB_EXPORT bool checkBlackLists(const char *slot, const char *data,
const char *global = nullptr);
}
diff --git a/src/testlib/qtestcase.cpp b/src/testlib/qtestcase.cpp
index 62512dc9d3..5648fedd63 100644
--- a/src/testlib/qtestcase.cpp
+++ b/src/testlib/qtestcase.cpp
@@ -10,7 +10,7 @@
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdebug.h>
#include <QtCore/qdir.h>
-#include <QtCore/qdiriterator.h>
+#include <QtCore/qdirlisting.h>
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qfloat16.h>
@@ -39,6 +39,7 @@
#endif // QT_CONFIG(batch_test_support)
#include <QtTest/private/cycle_p.h>
#include <QtTest/private/qtestblacklist_p.h>
+#include <QtTest/private/qtestcrashhandler_p.h>
#if defined(HAVE_XCTEST)
#include <QtTest/private/qxctestlogger_p.h>
#endif
@@ -64,6 +65,7 @@
#include <memory>
#include <mutex>
#include <numeric>
+#include <optional>
#include <stdarg.h>
#include <stdio.h>
@@ -115,6 +117,10 @@
#include <CoreFoundation/CFPreferences.h>
#endif
+#if defined(Q_OS_WASM)
+#include <emscripten.h>
+#endif
+
#include <vector>
QT_BEGIN_NAMESPACE
@@ -124,335 +130,229 @@ using namespace Qt::StringLiterals;
using QtMiscUtils::toHexUpper;
using QtMiscUtils::fromHex;
-namespace {
-enum DebuggerProgram { None, Gdb, Lldb };
-
-#if defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
-static struct iovec IoVec(struct iovec vec)
+static bool installCoverageTool(const char * appname, const char * testname)
{
- return vec;
+#if defined(__COVERAGESCANNER__) && !QT_CONFIG(testlib_selfcover)
+ if (!qEnvironmentVariableIsEmpty("QT_TESTCOCOON_ACTIVE"))
+ return false;
+ // Set environment variable QT_TESTCOCOON_ACTIVE to prevent an eventual subtest from
+ // being considered as a stand-alone test regarding the coverage analysis.
+ qputenv("QT_TESTCOCOON_ACTIVE", "1");
+
+ // Install Coverage Tool
+ __coveragescanner_install(appname);
+ __coveragescanner_testname(testname);
+ __coveragescanner_clear();
+ return true;
+#else
+ Q_UNUSED(appname);
+ Q_UNUSED(testname);
+ return false;
+#endif
}
-static struct iovec IoVec(const char *str)
+
+static bool isValidSlot(const QMetaMethod &sl)
{
- struct iovec r = {};
- r.iov_base = const_cast<char *>(str);
- r.iov_len = strlen(str);
- return r;
+ if (sl.access() != QMetaMethod::Private || sl.parameterCount() != 0
+ || sl.returnType() != QMetaType::Void || sl.methodType() != QMetaMethod::Slot)
+ return false;
+ const QByteArray name = sl.name();
+ return !(name.isEmpty() || name.endsWith("_data")
+ || name == "initTestCase" || name == "cleanupTestCase"
+ || name == "init" || name == "cleanup");
}
-template <typename... Args> static ssize_t writeToStderr(Args &&... args)
+namespace QTestPrivate
{
- struct iovec vec[] = { IoVec(std::forward<Args>(args))... };
- return ::writev(STDERR_FILENO, vec, std::size(vec));
+ Q_TESTLIB_EXPORT Qt::MouseButtons qtestMouseButtons = Qt::NoButton;
}
-// async-signal-safe conversion from int to string
-struct AsyncSafeIntBuffer
+namespace {
+
+class TestFailedException : public std::exception // clazy:exclude=copyable-polymorphic
{
- // digits10 + 1 for all possible digits
- // +1 for the sign
- // +1 for the terminating null
- static constexpr int Digits10 = std::numeric_limits<int>::digits10 + 3;
- std::array<char, Digits10> array;
- constexpr AsyncSafeIntBuffer() : array{} {} // initializes array
- AsyncSafeIntBuffer(Qt::Initialization) {} // leaves array uninitialized
+public:
+ TestFailedException() = default;
+ ~TestFailedException() override = default;
+
+ const char *what() const noexcept override { return "QtTest: test failed"; }
};
-static struct iovec asyncSafeToString(int n, AsyncSafeIntBuffer &&result = Qt::Uninitialized)
+class TestSkippedException : public std::exception // clazy:exclude=copyable-polymorphic
{
- char *ptr = result.array.data();
- if (false) {
-#ifdef __cpp_lib_to_chars
- } else if (auto r = std::to_chars(ptr, ptr + result.array.size(), n, 10); r.ec == std::errc{}) {
- ptr = r.ptr;
-#endif
- } else {
- // handle the sign
- if (n < 0) {
- *ptr++ = '-';
- n = -n;
- }
-
- // find the highest power of the base that is less than this number
- static constexpr int StartingDivider = ([]() {
- int divider = 1;
- for (int i = 0; i < std::numeric_limits<int>::digits10; ++i)
- divider *= 10;
- return divider;
- }());
- int divider = StartingDivider;
- while (divider && n < divider)
- divider /= 10;
-
- // now convert to string
- while (divider > 1) {
- int quot = n / divider;
- n = n % divider;
- divider /= 10;
- *ptr++ = quot + '0';
- }
- *ptr++ = n + '0';
- }
+public:
+ TestSkippedException() = default;
+ ~TestSkippedException() override = default;
-#ifndef QT_NO_DEBUG
- // this isn't necessary, it just helps in the debugger
- *ptr = '\0';
-#endif
- struct iovec r;
- r.iov_base = result.array.data();
- r.iov_len = ptr - result.array.data();
- return r;
+ const char *what() const noexcept override { return "QtTest: test was skipped"; }
};
-#elif defined(Q_OS_WIN)
-// Windows doesn't need to be async-safe
-template <typename... Args> static void writeToStderr(Args &&... args)
-{
- (std::cerr << ... << args);
-}
-static std::string asyncSafeToString(int n)
-{
- return std::to_string(n);
-}
-#endif // defined(Q_OS_UNIX)
} // unnamed namespace
-static bool alreadyDebugging()
+namespace QTest
{
-#if defined(Q_OS_LINUX)
- int fd = open("/proc/self/status", O_RDONLY);
- if (fd == -1)
- return false;
- char buffer[2048];
- ssize_t size = read(fd, buffer, sizeof(buffer) - 1);
- if (size == -1) {
- close(fd);
- return false;
- }
- buffer[size] = 0;
- const char tracerPidToken[] = "\nTracerPid:";
- char *tracerPid = strstr(buffer, tracerPidToken);
- if (!tracerPid) {
- close(fd);
- return false;
- }
- tracerPid += sizeof(tracerPidToken);
- long int pid = strtol(tracerPid, &tracerPid, 10);
- close(fd);
- return pid != 0;
-#elif defined(Q_OS_WIN)
- return IsDebuggerPresent();
-#elif defined(Q_OS_MACOS)
- // Check if there is an exception handler for the process:
- mach_msg_type_number_t portCount = 0;
- exception_mask_t masks[EXC_TYPES_COUNT];
- mach_port_t ports[EXC_TYPES_COUNT];
- exception_behavior_t behaviors[EXC_TYPES_COUNT];
- thread_state_flavor_t flavors[EXC_TYPES_COUNT];
- exception_mask_t mask = EXC_MASK_ALL & ~(EXC_MASK_RESOURCE | EXC_MASK_GUARD);
- kern_return_t result = task_get_exception_ports(mach_task_self(), mask, masks, &portCount,
- ports, behaviors, flavors);
- if (result == KERN_SUCCESS) {
- for (mach_msg_type_number_t portIndex = 0; portIndex < portCount; ++portIndex) {
- if (MACH_PORT_VALID(ports[portIndex])) {
- return true;
- }
- }
- }
- return false;
-#else
- // TODO
- return false;
-#endif
-}
-static bool hasSystemCrashReporter()
+void Internal::throwOnFail() { throw TestFailedException(); }
+void Internal::throwOnSkip() { throw TestSkippedException(); }
+
+Q_CONSTINIT static QBasicAtomicInt g_throwOnFail = Q_BASIC_ATOMIC_INITIALIZER(0);
+Q_CONSTINIT static QBasicAtomicInt g_throwOnSkip = Q_BASIC_ATOMIC_INITIALIZER(0);
+
+void Internal::maybeThrowOnFail()
{
-#if defined(Q_OS_MACOS)
- return QTestPrivate::macCrashReporterWillShowDialog();
-#else
- return false;
-#endif
+ if (g_throwOnFail.loadRelaxed() > 0)
+ Internal::throwOnFail();
}
-static void maybeDisableCoreDump()
+void Internal::maybeThrowOnSkip()
{
-#ifdef RLIMIT_CORE
- bool ok = false;
- const int disableCoreDump = qEnvironmentVariableIntValue("QTEST_DISABLE_CORE_DUMP", &ok);
- if (ok && disableCoreDump) {
- struct rlimit limit;
- limit.rlim_cur = 0;
- limit.rlim_max = 0;
- if (setrlimit(RLIMIT_CORE, &limit) != 0)
- qWarning("Failed to disable core dumps: %d", errno);
- }
-#endif
+ if (g_throwOnSkip.loadRelaxed() > 0)
+ Internal::throwOnSkip();
}
-static DebuggerProgram debugger = None;
-static void prepareStackTrace()
-{
+/*!
+ \since 6.8
+ \macro QTEST_THROW_ON_FAIL
+ \relates <QTest>
- bool ok = false;
- const int disableStackDump = qEnvironmentVariableIntValue("QTEST_DISABLE_STACK_DUMP", &ok);
- if (ok && disableStackDump)
- return;
+ When defined, QCOMPARE()/QVERIFY() etc always throw on failure.
+ QTest::throwOnFail() then no longer has any effect.
+*/
- if (hasSystemCrashReporter())
- return;
+/*!
+ \since 6.8
+ \macro QTEST_THROW_ON_SKIP
+ \relates <QTest>
-#if defined(Q_OS_MACOS)
- #define CSR_ALLOW_UNRESTRICTED_FS (1 << 1)
- std::optional<uint32_t> sipConfiguration = qt_mac_sipConfiguration();
- if (!sipConfiguration || !(*sipConfiguration & CSR_ALLOW_UNRESTRICTED_FS))
- return; // LLDB will fail to provide a valid stack trace
-#endif
+ When defined, QSKIP() always throws. QTest::throwOnSkip() then no longer
+ has any effect.
+*/
-#ifdef Q_OS_UNIX
- // like QStandardPaths::findExecutable(), but simpler
- auto hasExecutable = [](const char *execname) {
- std::string candidate;
- std::string path;
- if (const char *p = getenv("PATH"); p && *p)
- path = p;
- else
- path = _PATH_DEFPATH;
- for (const char *p = std::strtok(&path[0], ":'"); p; p = std::strtok(nullptr, ":")) {
- candidate = p;
- candidate += '/';
- candidate += execname;
- if (QT_ACCESS(candidate.data(), X_OK) == 0)
- return true;
- }
- return false;
- };
+/*!
+ \since 6.8
+ \class QTest::ThrowOnFailEnabler
+ \inmodule QtTestLib
- static constexpr DebuggerProgram debuggerSearchOrder[] = {
-# if defined(Q_OS_QNX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
- Gdb, Lldb
-# else
- Lldb, Gdb
-# endif
- };
- for (DebuggerProgram candidate : debuggerSearchOrder) {
- switch (candidate) {
- case None:
- Q_UNREACHABLE();
- break;
- case Gdb:
- if (hasExecutable("gdb")) {
- debugger = Gdb;
- return;
- }
- break;
- case Lldb:
- if (hasExecutable("lldb")) {
- debugger = Lldb;
- return;
- }
- break;
- }
- }
-#endif // Q_OS_UNIX
-}
+ RAII class around setThrowOnFail().
+*/
+/*!
+ \fn QTest::ThrowOnFailEnabler::ThrowOnFailEnabler()
-#if !defined(Q_OS_WASM) || QT_CONFIG(thread)
-static void printTestRunTime()
-{
- const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
- const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
- const char *const name = QTest::currentTestFunction();
- writeToStderr("\n ", name ? name : "[Non-test]",
- " function time: ", asyncSafeToString(msecsFunctionTime),
- "ms, total time: ", asyncSafeToString(msecsTotalTime), "ms\n");
-}
+ Constructor. Calls \c{setThrowOnFail(true)}.
+*/
+/*!
+ \fn QTest::ThrowOnFailEnabler::~ThrowOnFailEnabler()
-static void generateStackTrace()
-{
- if (debugger == None || alreadyDebugging())
- return;
+ Destructor. Calls \c{setThrowOnFail(false)}.
+*/
-# if defined(Q_OS_UNIX) && !defined(Q_OS_WASM) && !defined(Q_OS_INTEGRITY)
- writeToStderr("\n=== Stack trace ===\n");
+/*!
+ \since 6.8
+ \class QTest::ThrowOnFailDisabler
+ \inmodule QtTestLib
- // execlp() requires null-termination, so call the default constructor
- AsyncSafeIntBuffer pidbuffer;
- asyncSafeToString(getpid(), std::move(pidbuffer));
+ RAII class around setThrowOnFail().
+*/
+/*!
+ \fn QTest::ThrowOnFailDisabler::ThrowOnFailDisabler()
- // Note: POSIX.1-2001 still has fork() in the list of async-safe functions,
- // but in a future edition, it might be removed. It would be safer to wake
- // up a babysitter thread to launch the debugger.
- pid_t pid = fork();
- if (pid == 0) {
- // child process
- (void) dup2(STDERR_FILENO, STDOUT_FILENO); // redirect stdout to stderr
+ Constructor. Calls \c{setThrowOnFail(false)}.
+*/
+/*!
+ \fn QTest::ThrowOnFailDisabler::~ThrowOnFailDisabler()
- switch (debugger) {
- case None:
- Q_UNREACHABLE();
- break;
- case Gdb:
- execlp("gdb", "gdb", "--nx", "--batch", "-ex", "thread apply all bt",
- "--pid", pidbuffer.array.data(), nullptr);
- break;
- case Lldb:
- execlp("lldb", "lldb", "--no-lldbinit", "--batch", "-o", "bt all",
- "--attach-pid", pidbuffer.array.data(), nullptr);
- break;
- }
- _exit(1);
- } else if (pid < 0) {
- writeToStderr("Failed to start debugger.\n");
- } else {
- int ret;
- EINTR_LOOP(ret, waitpid(pid, nullptr, 0));
- }
+ Destructor. Calls \c{setThrowOnFail(true)}.
+*/
- writeToStderr("=== End of stack trace ===\n");
-# endif // Q_OS_UNIX && !Q_OS_WASM
-}
-#endif // !defined(Q_OS_WASM) || QT_CONFIG(thread)
+/*!
+ \since 6.8
+ \class QTest::ThrowOnSkipEnabler
+ \inmodule QtTestLib
-static bool installCoverageTool(const char * appname, const char * testname)
-{
-#if defined(__COVERAGESCANNER__) && !QT_CONFIG(testlib_selfcover)
- if (!qEnvironmentVariableIsEmpty("QT_TESTCOCOON_ACTIVE"))
- return false;
- // Set environment variable QT_TESTCOCOON_ACTIVE to prevent an eventual subtest from
- // being considered as a stand-alone test regarding the coverage analysis.
- qputenv("QT_TESTCOCOON_ACTIVE", "1");
+ RAII class around setThrowOnSkip().
+*/
+/*!
+ \fn QTest::ThrowOnSkipEnabler::ThrowOnSkipEnabler()
- // Install Coverage Tool
- __coveragescanner_install(appname);
- __coveragescanner_testname(testname);
- __coveragescanner_clear();
- return true;
-#else
- Q_UNUSED(appname);
- Q_UNUSED(testname);
- return false;
-#endif
-}
+ Constructor. Calls \c{setThrowOnSkip(true)}.
+*/
+/*!
+ \fn QTest::ThrowOnSkipEnabler::~ThrowOnSkipEnabler()
-static bool isValidSlot(const QMetaMethod &sl)
-{
- if (sl.access() != QMetaMethod::Private || sl.parameterCount() != 0
- || sl.returnType() != QMetaType::Void || sl.methodType() != QMetaMethod::Slot)
- return false;
- const QByteArray name = sl.name();
- return !(name.isEmpty() || name.endsWith("_data")
- || name == "initTestCase" || name == "cleanupTestCase"
- || name == "init" || name == "cleanup");
-}
+ Destructor. Calls \c{setThrowOnSkip(false)}.
+*/
-namespace QTestPrivate
+/*!
+ \since 6.8
+ \class QTest::ThrowOnSkipDisabler
+ \inmodule QtTestLib
+
+ RAII class around setThrowOnSkip().
+*/
+/*!
+ \fn QTest::ThrowOnSkipDisabler::ThrowOnSkipDisabler()
+
+ Constructor. Calls \c{setThrowOnSkip(false)}.
+*/
+/*!
+ \fn QTest::ThrowOnSkipDisabler::~ThrowOnSkipDisabler()
+
+ Destructor. Calls \c{setThrowOnSkip(true)}.
+*/
+
+/*!
+ \since 6.8
+
+ Enables (\a enable = \c true) or disables (\ enable = \c false) throwing on
+ QCOMPARE()/QVERIFY() failures (as opposed to just returning from the
+ immediately-surrounding function context).
+
+ The feature is reference-counted: If you call this function \e{N} times
+ with \c{true}, you need to call it \e{N} times with \c{false} to get back
+ to where you started.
+
+ The default is \c{false}, unless the \l{Qt Test Environment Variables}
+ {QTEST_THROW_ON_FAIL environment variable} is set.
+
+ This call has no effect when the \l{QTEST_THROW_ON_FAIL} C++ macro is
+ defined.
+
+ \note You must compile your tests with exceptions enabled to use this
+ feature.
+
+ \sa setThrowOnSkip(), ThrowOnFailEnabler, ThrowOnFailDisabler, QTEST_THROW_ON_FAIL
+*/
+void setThrowOnFail(bool enable) noexcept
{
- Q_TESTLIB_EXPORT Qt::MouseButtons qtestMouseButtons = Qt::NoButton;
+ g_throwOnFail.fetchAndAddRelaxed(enable ? 1 : -1);
}
-namespace QTest
+/*!
+ \since 6.8
+
+ Enables (\a enable = \c true) or disables (\ enable = \c false) throwing on
+ QSKIP() (as opposed to just returning from the immediately-surrounding
+ function context).
+
+ The feature is reference-counted: If you call this function \e{N} times
+ with \c{true}, you need to call it \e{N} times with \c{false} to get back
+ to where you started.
+
+ The default is \c{false}, unless the \l{Qt Test Environment Variables}
+ {QTEST_THROW_ON_SKIP environment variable} is set.
+
+ This call has no effect when the \l{QTEST_THROW_ON_SKIP} C++ macro is
+ defined.
+
+ \note You must compile your tests with exceptions enabled to use this
+ feature.
+
+ \sa setThrowOnFail(), ThrowOnSkipEnabler, ThrowOnSkipDisabler, QTEST_THROW_ON_SKIP
+*/
+void setThrowOnSkip(bool enable) noexcept
{
+ g_throwOnSkip.fetchAndAddRelaxed(enable ? 1 : -1);
+}
QString Internal::formatTryTimeoutDebugMessage(q_no_char8_t::QUtf8StringView expr, int timeout, int actual)
{
@@ -487,7 +387,7 @@ public:
static QMetaMethod findMethod(const QObject *obj, const char *signature);
private:
- bool invokeTest(int index, QLatin1StringView tag, WatchDog *watchDog) const;
+ bool invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const;
void invokeTestOnData(int index) const;
QMetaMethod m_initTestCaseMethod; // might not exist, check isValid().
@@ -532,19 +432,32 @@ static int eventDelay = -1;
#if QT_CONFIG(thread)
static int timeout = -1;
#endif
-static bool noCrashHandler = false;
+static int repetitions = 1;
+static bool repeatForever = false;
+static bool skipBlacklisted = false;
-/*! \internal
- Invoke a method of the object without generating warning if the method does not exist
-*/
-static void invokeMethod(QObject *obj, const char *methodName)
+namespace Internal {
+bool noCrashHandler = false;
+}
+
+static bool invokeTestMethodIfValid(QMetaMethod m, QObject *obj = QTest::currentTestObject)
+{
+ if (!m.isValid())
+ return false;
+ bool ok = true;
+ try { ok = m.invoke(obj, Qt ::DirectConnection); }
+ catch (const TestFailedException &) {} // ignore (used for control flow)
+ catch (const TestSkippedException &) {} // ditto
+ // every other exception is someone else's problem
+ return ok;
+}
+
+static void invokeTestMethodIfExists(const char *methodName, QObject *obj = QTest::currentTestObject)
{
const QMetaObject *metaObject = obj->metaObject();
int funcIndex = metaObject->indexOfMethod(methodName);
- if (funcIndex >= 0) {
- QMetaMethod method = metaObject->method(funcIndex);
- method.invoke(obj, Qt::DirectConnection);
- }
+ // doesn't generate a warning if it doesn't exist:
+ invokeTestMethodIfValid(metaObject->method(funcIndex), obj);
}
int defaultEventDelay()
@@ -628,7 +541,7 @@ static void qPrintDataTags(FILE *stream)
// Get global data tags:
QTestTable::globalTestTable();
- invokeMethod(QTest::currentTestObject, "initTestCase_data()");
+ invokeTestMethodIfExists("initTestCase_data()");
const QTestTable *gTable = QTestTable::globalTestTable();
const QMetaObject *currTestMetaObj = QTest::currentTestObject->metaObject();
@@ -647,7 +560,7 @@ static void qPrintDataTags(FILE *stream)
QByteArray member;
member.resize(qstrlen(slot) + qstrlen("_data()") + 1);
qsnprintf(member.data(), member.size(), "%s_data()", slot);
- invokeMethod(QTest::currentTestObject, member.constData());
+ invokeTestMethodIfExists(member.constData());
const int dataCount = table.dataCount();
localTags.reserve(dataCount);
for (int j = 0; j < dataCount; ++j)
@@ -704,10 +617,18 @@ Q_TESTLIB_EXPORT void qtest_qParseArgs(int argc, const char *const argv[], bool
int logFormat = -1; // Not set
const char *logFilename = nullptr;
+ repetitions = 1;
+ repeatForever = false;
+
QTest::testFunctions.clear();
QTest::testTags.clear();
-#if defined(Q_OS_MAC) && defined(HAVE_XCTEST)
+ if (qEnvironmentVariableIsSet("QTEST_THROW_ON_FAIL"))
+ QTest::setThrowOnFail(true);
+ if (qEnvironmentVariableIsSet("QTEST_THROW_ON_SKIP"))
+ QTest::setThrowOnSkip(true);
+
+#if defined(Q_OS_DARWIN) && defined(HAVE_XCTEST)
if (QXcodeTestLogger::canLogTestProgress())
logFormat = QTestLog::XCTest;
#endif
@@ -758,6 +679,15 @@ Q_TESTLIB_EXPORT void qtest_qParseArgs(int argc, const char *const argv[], bool
" -maxwarnings n : Sets the maximum amount of messages to output.\n"
" 0 means unlimited, default: 2000\n"
" -nocrashhandler : Disables the crash handler. Useful for debugging crashes.\n"
+ " -repeat n : Run the testsuite n times or until the test fails.\n"
+ " Useful for finding flaky tests. If negative, the tests are\n"
+ " repeated forever. This is intended as a developer tool, and\n"
+ " is only supported with the plain text logger.\n"
+ " -skipblacklisted : Skip blacklisted tests. Useful for measuring test coverage.\n"
+ " -[no]throwonfail : Enables/disables throwing on QCOMPARE()/QVERIFY()/etc.\n"
+ " Default: off, unless QTEST_THROW_ON_FAIL is set."
+ " -[no]throwonskip : Enables/disables throwing on QSKIP().\n"
+ " Default: off, unless QTEST_THROW_ON_SKIP is set."
"\n"
" Benchmarking options:\n"
#if QT_CONFIG(valgrind)
@@ -907,8 +837,26 @@ Q_TESTLIB_EXPORT void qtest_qParseArgs(int argc, const char *const argv[], bool
} else {
QTestLog::setMaxWarnings(qToInt(argv[++i]));
}
+ } else if (strcmp(argv[i], "-repeat") == 0) {
+ if (i + 1 >= argc) {
+ fprintf(stderr, "-repeat needs an extra parameter for the number of repetitions\n");
+ exit(1);
+ } else {
+ repetitions = qToInt(argv[++i]);
+ repeatForever = repetitions < 0;
+ }
} else if (strcmp(argv[i], "-nocrashhandler") == 0) {
- QTest::noCrashHandler = true;
+ QTest::Internal::noCrashHandler = true;
+ } else if (strcmp(argv[i], "-skipblacklisted") == 0) {
+ QTest::skipBlacklisted = true;
+ } else if (strcmp(argv[i], "-throwonfail") == 0) {
+ QTest::setThrowOnFail(true);
+ } else if (strcmp(argv[i], "-nothrowonfail") == 0) {
+ QTest::setThrowOnFail(false);
+ } else if (strcmp(argv[i], "-throwonskip") == 0) {
+ QTest::setThrowOnSkip(true);
+ } else if (strcmp(argv[i], "-nothrowonskip") == 0) {
+ QTest::setThrowOnSkip(false);
#if QT_CONFIG(valgrind)
} else if (strcmp(argv[i], "-callgrind") == 0) {
if (!QBenchmarkValgrindUtils::haveValgrind()) {
@@ -1050,16 +998,21 @@ Q_TESTLIB_EXPORT void qtest_qParseArgs(int argc, const char *const argv[], bool
#if defined(QT_USE_APPLE_UNIFIED_LOGGING)
// Any explicitly requested loggers will be added by now, so we can check if they use stdout
- const bool safeToAddAppleLogger = !AppleUnifiedLogger::willMirrorToStderr() || !QTestLog::loggerUsingStdout();
+ const bool safeToAddAppleLogger = !AppleUnifiedLogger::preventsStderrLogging() || !QTestLog::loggerUsingStdout();
if (safeToAddAppleLogger && QAppleTestLogger::debugLoggingEnabled()) {
QTestLog::addLogger(QTestLog::Apple, nullptr);
- if (AppleUnifiedLogger::willMirrorToStderr() && !logFilename)
+ if (AppleUnifiedLogger::preventsStderrLogging() && !logFilename)
addFallbackLogger = false; // Prevent plain test logger fallback below
}
#endif
if (addFallbackLogger)
QTestLog::addLogger(QTestLog::Plain, logFilename);
+
+ if (repetitions != 1 && !QTestLog::isRepeatSupported()) {
+ fprintf(stderr, "-repeat is only supported with plain text logger\n");
+ exit(1);
+ }
}
// Temporary, backwards compatibility, until qtdeclarative's use of it is converted
@@ -1118,8 +1071,7 @@ void TestMethods::invokeTestOnData(int index) const
bool invokeOk;
do {
QTest::inTestFunction = true;
- if (m_initMethod.isValid())
- m_initMethod.invoke(QTest::currentTestObject, Qt::DirectConnection);
+ invokeTestMethodIfValid(m_initMethod);
const bool initQuit =
QTestResult::skipCurrentTest() || QTestResult::currentTestFailed();
@@ -1131,7 +1083,7 @@ void TestMethods::invokeTestOnData(int index) const
QBenchmarkGlobalData::current->context.tag = QLatin1StringView(
QTestResult::currentDataTag() ? QTestResult::currentDataTag() : "");
- invokeOk = m_methods[index].invoke(QTest::currentTestObject, Qt::DirectConnection);
+ invokeOk = invokeTestMethodIfValid(m_methods[index]);
if (!invokeOk)
QTestResult::addFailure("Unable to execute slot", __FILE__, __LINE__);
@@ -1144,8 +1096,7 @@ void TestMethods::invokeTestOnData(int index) const
QTestResult::finishedCurrentTestData();
if (!initQuit) {
- if (m_cleanupMethod.isValid())
- m_cleanupMethod.invoke(QTest::currentTestObject, Qt::DirectConnection);
+ invokeTestMethodIfValid(m_cleanupMethod);
// Process any deleteLater(), used by event-loop-based apps.
// Fixes memleak reports.
@@ -1211,17 +1162,30 @@ void TestMethods::invokeTestOnData(int index) const
class WatchDog : public QThread
{
- enum Expectation {
+ enum Expectation : std::size_t {
+ // bits 0..1: state
ThreadStart,
TestFunctionStart,
TestFunctionEnd,
ThreadEnd,
- };
- bool waitFor(std::unique_lock<QtPrivate::mutex> &m, Expectation e)
+ // bits 2..: generation
+ };
+ static constexpr auto ExpectationMask = Expectation{ThreadStart | TestFunctionStart | TestFunctionEnd | ThreadEnd};
+ static_assert(size_t(ExpectationMask) == 0x3);
+ static constexpr size_t GenerationShift = 2;
+
+ static constexpr Expectation state(Expectation e) noexcept
+ { return Expectation{e & ExpectationMask}; }
+ static constexpr size_t generation(Expectation e) noexcept
+ { return e >> GenerationShift; }
+ static constexpr Expectation combine(Expectation e, size_t gen) noexcept
+ { return Expectation{e | (gen << GenerationShift)}; }
+
+ bool waitFor(std::unique_lock<std::mutex> &m, Expectation e)
{
auto expectationChanged = [this, e] { return expecting.load(std::memory_order_relaxed) != e; };
- switch (e) {
+ switch (state(e)) {
case TestFunctionEnd:
return waitCondition.wait_for(m, defaultTimeout(), expectationChanged);
case ThreadStart:
@@ -1233,6 +1197,19 @@ class WatchDog : public QThread
Q_UNREACHABLE_RETURN(false);
}
+ void setExpectation(Expectation e)
+ {
+ Q_ASSERT(generation(e) == 0); // no embedded generation allowed
+ const auto locker = qt_scoped_lock(mutex);
+ auto cur = expecting.load(std::memory_order_relaxed);
+ auto gen = generation(cur);
+ if (e == TestFunctionStart)
+ ++gen;
+ e = combine(e, gen);
+ expecting.store(e, std::memory_order_relaxed);
+ waitCondition.notify_all();
+ }
+
public:
WatchDog()
{
@@ -1245,36 +1222,29 @@ public:
~WatchDog()
{
- {
- const auto locker = qt_scoped_lock(mutex);
- expecting.store(ThreadEnd, std::memory_order_relaxed);
- waitCondition.notify_all();
- }
+ setExpectation(ThreadEnd);
wait();
}
void beginTest()
{
- const auto locker = qt_scoped_lock(mutex);
- expecting.store(TestFunctionEnd, std::memory_order_relaxed);
- waitCondition.notify_all();
+ setExpectation(TestFunctionEnd);
}
void testFinished()
{
- const auto locker = qt_scoped_lock(mutex);
- expecting.store(TestFunctionStart, std::memory_order_relaxed);
- waitCondition.notify_all();
+ setExpectation(TestFunctionStart);
}
void run() override
{
+ CrashHandler::blockUnixSignals();
auto locker = qt_unique_lock(mutex);
expecting.store(TestFunctionStart, std::memory_order_release);
waitCondition.notify_all();
while (true) {
Expectation e = expecting.load(std::memory_order_acquire);
- switch (e) {
+ switch (state(e)) {
case ThreadEnd:
return;
case ThreadStart:
@@ -1283,8 +1253,8 @@ public:
case TestFunctionEnd:
if (Q_UNLIKELY(!waitFor(locker, e))) {
fflush(stderr);
- printTestRunTime();
- generateStackTrace();
+ CrashHandler::printTestRunTime();
+ CrashHandler::generateStackTrace();
qFatal("Test function timed out");
}
}
@@ -1292,8 +1262,8 @@ public:
}
private:
- QtPrivate::mutex mutex;
- QtPrivate::condition_variable waitCondition;
+ std::mutex mutex;
+ std::condition_variable waitCondition;
std::atomic<Expectation> expecting;
};
@@ -1338,7 +1308,7 @@ static void printUnknownDataTagError(QLatin1StringView name, QLatin1StringView t
If the function was successfully called, true is returned, otherwise
false.
*/
-bool TestMethods::invokeTest(int index, QLatin1StringView tag, WatchDog *watchDog) const
+bool TestMethods::invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const
{
QBenchmarkTestMethodData benchmarkData;
QBenchmarkTestMethodData::current = &benchmarkData;
@@ -1370,6 +1340,7 @@ bool TestMethods::invokeTest(int index, QLatin1StringView tag, WatchDog *watchDo
tag[global.size()] == ':';
};
bool foundFunction = false;
+ bool blacklisted = false;
/* For each entry in the global data table, do: */
do {
@@ -1378,7 +1349,7 @@ bool TestMethods::invokeTest(int index, QLatin1StringView tag, WatchDog *watchDo
if (curGlobalDataIndex == 0) {
qsnprintf(member, 512, "%s_data()", name.constData());
- invokeMethod(QTest::currentTestObject, member);
+ invokeTestMethodIfExists(member);
if (QTestResult::skipCurrentTest())
break;
}
@@ -1396,18 +1367,28 @@ bool TestMethods::invokeTest(int index, QLatin1StringView tag, WatchDog *watchDo
if (dataTagMatches(tag, QLatin1StringView(dataTag(curDataIndex)),
QLatin1StringView(globalDataTag(curGlobalDataIndex)))) {
foundFunction = true;
- QTestPrivate::checkBlackLists(name.constData(), dataTag(curDataIndex),
- globalDataTag(curGlobalDataIndex));
-
- QTestDataSetter s(curDataIndex >= dataCount ? nullptr : table.testData(curDataIndex));
-
- QTestPrivate::qtestMouseButtons = Qt::NoButton;
- if (watchDog)
- watchDog->beginTest();
- QTest::lastMouseTimestamp += 500; // Maintain at least 500ms mouse event timestamps between each test function call
- invokeTestOnData(index);
- if (watchDog)
- watchDog->testFinished();
+ blacklisted = QTestPrivate::checkBlackLists(name.constData(), dataTag(curDataIndex),
+ globalDataTag(curGlobalDataIndex));
+ if (blacklisted)
+ QTestResult::setBlacklistCurrentTest(true);
+
+ if (blacklisted && skipBlacklisted) {
+ QTest::qSkip("Skipping blacklisted test since -skipblacklisted option is set.",
+ NULL, 0);
+ QTestResult::finishedCurrentTestData();
+ QTestResult::finishedCurrentTestDataCleanup();
+ } else {
+ QTestDataSetter s(
+ curDataIndex >= dataCount ? nullptr : table.testData(curDataIndex));
+
+ QTestPrivate::qtestMouseButtons = Qt::NoButton;
+ if (watchDog)
+ watchDog->beginTest();
+ QTest::lastMouseTimestamp += 500; // Maintain at least 500ms mouse event timestamps between each test function call
+ invokeTestOnData(index);
+ if (watchDog)
+ watchDog->testFinished();
+ }
if (!tag.isEmpty() && !globalDataCount)
break;
@@ -1626,65 +1607,77 @@ char *toPrettyCString(const char *p, qsizetype length)
}
/*!
+ \fn char *toPrettyUnicode(QStringView string)
\internal
Returns the same QString but with only the ASCII characters still shown;
everything else is replaced with \c {\uXXXX}.
Similar to QDebug::putString().
*/
+
+constexpr qsizetype PrettyUnicodeMaxOutputSize = 256;
+// escape sequence, closing quote, the three dots and NUL
+constexpr qsizetype PrettyUnicodeMaxIncrement = sizeof(R"(\uXXXX"...)"); // includes NUL
+
+static char *writePrettyUnicodeChar(char16_t ch, char * const buffer)
+{
+ auto dst = buffer;
+ auto first = [&](int n) { Q_ASSERT(dst - buffer == n); return dst; };
+ if (ch < 0x7f && ch >= 0x20 && ch != '\\' && ch != '"') {
+ *dst++ = ch;
+ return first(1);
+ }
+
+ // write as an escape sequence
+ *dst++ = '\\';
+ switch (ch) {
+ case 0x22:
+ case 0x5c:
+ *dst++ = uchar(ch);
+ break;
+ case 0x8:
+ *dst++ = 'b';
+ break;
+ case 0xc:
+ *dst++ = 'f';
+ break;
+ case 0xa:
+ *dst++ = 'n';
+ break;
+ case 0xd:
+ *dst++ = 'r';
+ break;
+ case 0x9:
+ *dst++ = 't';
+ break;
+ default:
+ *dst++ = 'u';
+ *dst++ = toHexUpper(ch >> 12);
+ *dst++ = toHexUpper(ch >> 8);
+ *dst++ = toHexUpper(ch >> 4);
+ *dst++ = toHexUpper(ch);
+ return first(6);
+ }
+ return first(2);
+}
+
char *toPrettyUnicode(QStringView string)
{
auto p = string.utf16();
auto length = string.size();
// keep it simple for the vast majority of cases
bool trimmed = false;
- auto buffer = std::make_unique<char[]>(256);
+ auto buffer = std::make_unique<char[]>(PrettyUnicodeMaxOutputSize);
const auto end = p + length;
char *dst = buffer.get();
*dst++ = '"';
for ( ; p != end; ++p) {
- if (dst - buffer.get() > 245) {
- // plus the quote, the three dots and NUL, it's 250, 251 or 255
+ if (dst - buffer.get() > PrettyUnicodeMaxOutputSize - PrettyUnicodeMaxIncrement) {
trimmed = true;
break;
}
-
- if (*p < 0x7f && *p >= 0x20 && *p != '\\' && *p != '"') {
- *dst++ = *p;
- continue;
- }
-
- // write as an escape sequence
- // this means we may advance dst to buffer.data() + 246 or 250
- *dst++ = '\\';
- switch (*p) {
- case 0x22:
- case 0x5c:
- *dst++ = uchar(*p);
- break;
- case 0x8:
- *dst++ = 'b';
- break;
- case 0xc:
- *dst++ = 'f';
- break;
- case 0xa:
- *dst++ = 'n';
- break;
- case 0xd:
- *dst++ = 'r';
- break;
- case 0x9:
- *dst++ = 't';
- break;
- default:
- *dst++ = 'u';
- *dst++ = toHexUpper(*p >> 12);
- *dst++ = toHexUpper(*p >> 8);
- *dst++ = toHexUpper(*p >> 4);
- *dst++ = toHexUpper(*p);
- }
+ dst = writePrettyUnicodeChar(*p, dst);
}
*dst++ = '"';
@@ -1702,23 +1695,21 @@ void TestMethods::invokeTests(QObject *testObject) const
const QMetaObject *metaObject = testObject->metaObject();
QTEST_ASSERT(metaObject);
QTestResult::setCurrentTestFunction("initTestCase");
- if (m_initTestCaseDataMethod.isValid())
- m_initTestCaseDataMethod.invoke(testObject, Qt::DirectConnection);
+ invokeTestMethodIfValid(m_initTestCaseDataMethod, testObject);
- QScopedPointer<WatchDog> watchDog;
- if (!alreadyDebugging()
+ std::optional<WatchDog> watchDog = std::nullopt;
+ if (!CrashHandler::alreadyDebugging()
#if QT_CONFIG(valgrind)
&& QBenchmarkGlobalData::current->mode() != QBenchmarkGlobalData::CallgrindChildProcess
#endif
) {
- watchDog.reset(new WatchDog);
+ watchDog.emplace();
}
QSignalDumper::startDump();
if (!QTestResult::skipCurrentTest() && !QTestResult::currentTestFailed()) {
- if (m_initTestCaseMethod.isValid())
- m_initTestCaseMethod.invoke(testObject, Qt::DirectConnection);
+ invokeTestMethodIfValid(m_initTestCaseMethod, testObject);
// finishedCurrentTestDataCleanup() resets QTestResult::currentTestFailed(), so use a local copy.
const bool previousFailed = QTestResult::currentTestFailed();
@@ -1731,7 +1722,7 @@ void TestMethods::invokeTests(QObject *testObject) const
const char *data = nullptr;
if (i < QTest::testTags.size() && !QTest::testTags.at(i).isEmpty())
data = qstrdup(QTest::testTags.at(i).toLatin1().constData());
- const bool ok = invokeTest(i, QLatin1StringView(data), watchDog.data());
+ const bool ok = invokeTest(i, QLatin1StringView(data), watchDog);
delete [] data;
if (!ok)
break;
@@ -1742,8 +1733,7 @@ void TestMethods::invokeTests(QObject *testObject) const
QTestResult::setSkipCurrentTest(false);
QTestResult::setBlacklistCurrentTest(false);
QTestResult::setCurrentTestFunction("cleanupTestCase");
- if (m_cleanupTestCaseMethod.isValid())
- m_cleanupTestCaseMethod.invoke(testObject, Qt::DirectConnection);
+ invokeTestMethodIfValid(m_cleanupTestCaseMethod, testObject);
QTestResult::finishedCurrentTestData();
// Restore skip state as it affects decision on whether we passed:
QTestResult::setSkipCurrentTest(wasSkipped || QTestResult::skipCurrentTest());
@@ -1755,430 +1745,34 @@ void TestMethods::invokeTests(QObject *testObject) const
QSignalDumper::endDump();
}
+#if QT_DEPRECATED_SINCE(6, 8)
+static const char *functionRefFormatter(const void *f)
+{
+ auto formatter = static_cast<const qxp::function_ref<const char *()> *>(f);
+ return (*formatter)();
+};
+
bool reportResult(bool success, qxp::function_ref<const char *()> lhs,
qxp::function_ref<const char *()> rhs,
const char *lhsExpr, const char *rhsExpr,
ComparisonOperation op, const char *file, int line)
{
- return QTestResult::reportResult(success, lhs, rhs, lhsExpr, rhsExpr, op, file, line);
-}
-
-} // namespace QTest
-
-namespace {
-#if defined(Q_OS_WIN)
-
-// Helper class for resolving symbol names by dynamically loading "dbghelp.dll".
-class DebugSymbolResolver
-{
- Q_DISABLE_COPY_MOVE(DebugSymbolResolver)
-public:
- struct Symbol {
- Symbol() : name(nullptr), address(0) {}
-
- const char *name; // Must be freed by caller.
- DWORD64 address;
- };
-
- explicit DebugSymbolResolver(HANDLE process);
- ~DebugSymbolResolver() { cleanup(); }
-
- bool isValid() const { return m_symFromAddr; }
-
- Symbol resolveSymbol(DWORD64 address) const;
-
-private:
- // typedefs from DbgHelp.h/.dll
- struct DBGHELP_SYMBOL_INFO { // SYMBOL_INFO
- ULONG SizeOfStruct;
- ULONG TypeIndex; // Type Index of symbol
- ULONG64 Reserved[2];
- ULONG Index;
- ULONG Size;
- ULONG64 ModBase; // Base Address of module comtaining this symbol
- ULONG Flags;
- ULONG64 Value; // Value of symbol, ValuePresent should be 1
- ULONG64 Address; // Address of symbol including base address of module
- ULONG Register; // register holding value or pointer to value
- ULONG Scope; // scope of the symbol
- ULONG Tag; // pdb classification
- ULONG NameLen; // Actual length of name
- ULONG MaxNameLen;
- CHAR Name[1]; // Name of symbol
- };
-
- typedef BOOL (__stdcall *SymInitializeType)(HANDLE, PCSTR, BOOL);
- typedef BOOL (__stdcall *SymFromAddrType)(HANDLE, DWORD64, PDWORD64, DBGHELP_SYMBOL_INFO *);
-
- void cleanup();
-
- const HANDLE m_process;
- HMODULE m_dbgHelpLib;
- SymFromAddrType m_symFromAddr;
-};
-
-void DebugSymbolResolver::cleanup()
-{
- if (m_dbgHelpLib)
- FreeLibrary(m_dbgHelpLib);
- m_dbgHelpLib = 0;
- m_symFromAddr = nullptr;
+ return QTestResult::reportResult(success, &lhs, &rhs,
+ functionRefFormatter, functionRefFormatter,
+ lhsExpr, rhsExpr, op, file, line);
}
+#endif // QT_DEPRECATED_SINCE(6, 8)
-DebugSymbolResolver::DebugSymbolResolver(HANDLE process)
- : m_process(process), m_dbgHelpLib(0), m_symFromAddr(nullptr)
+bool reportResult(bool success, const void *lhs, const void *rhs,
+ const char *(*lhsFormatter)(const void*),
+ const char *(*rhsFormatter)(const void*),
+ const char *lhsExpr, const char *rhsExpr,
+ ComparisonOperation op, const char *file, int line)
{
- bool success = false;
- m_dbgHelpLib = LoadLibraryW(L"dbghelp.dll");
- if (m_dbgHelpLib) {
- SymInitializeType symInitialize = reinterpret_cast<SymInitializeType>(
- reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymInitialize")));
- m_symFromAddr = reinterpret_cast<SymFromAddrType>(
- reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymFromAddr")));
- success = symInitialize && m_symFromAddr && symInitialize(process, NULL, TRUE);
- }
- if (!success)
- cleanup();
+ return QTestResult::reportResult(success, lhs, rhs, lhsFormatter, rhsFormatter,
+ lhsExpr, rhsExpr, op, file, line);
}
-
-DebugSymbolResolver::Symbol DebugSymbolResolver::resolveSymbol(DWORD64 address) const
-{
- // reserve additional buffer where SymFromAddr() will store the name
- struct NamedSymbolInfo : public DBGHELP_SYMBOL_INFO {
- enum { symbolNameLength = 255 };
-
- char name[symbolNameLength + 1];
- };
-
- Symbol result;
- if (!isValid())
- return result;
- NamedSymbolInfo symbolBuffer;
- memset(&symbolBuffer, 0, sizeof(NamedSymbolInfo));
- symbolBuffer.MaxNameLen = NamedSymbolInfo::symbolNameLength;
- symbolBuffer.SizeOfStruct = sizeof(DBGHELP_SYMBOL_INFO);
- if (!m_symFromAddr(m_process, address, 0, &symbolBuffer))
- return result;
- result.name = qstrdup(symbolBuffer.Name);
- result.address = symbolBuffer.Address;
- return result;
-}
-
-class WindowsFaultHandler
-{
-public:
- WindowsFaultHandler()
- {
-# if !defined(Q_CC_MINGW)
- _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);
-# endif
- SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX);
- SetUnhandledExceptionFilter(windowsFaultHandler);
- }
-
-private:
- static LONG WINAPI windowsFaultHandler(struct _EXCEPTION_POINTERS *exInfo)
- {
- enum { maxStackFrames = 100 };
- char appName[MAX_PATH];
- if (!GetModuleFileNameA(NULL, appName, MAX_PATH))
- appName[0] = 0;
- const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
- const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
- const void *exceptionAddress = exInfo->ExceptionRecord->ExceptionAddress;
- fprintf(stderr, "A crash occurred in %s.\n", appName);
- if (const char *name = QTest::currentTestFunction())
- fprintf(stderr, "While testing %s\n", name);
- fprintf(stderr, "Function time: %dms Total time: %dms\n\n"
- "Exception address: 0x%p\n"
- "Exception code : 0x%lx\n",
- msecsFunctionTime, msecsTotalTime, exceptionAddress,
- exInfo->ExceptionRecord->ExceptionCode);
-
- DebugSymbolResolver resolver(GetCurrentProcess());
- if (resolver.isValid()) {
- DebugSymbolResolver::Symbol exceptionSymbol = resolver.resolveSymbol(DWORD64(exceptionAddress));
- if (exceptionSymbol.name) {
- fprintf(stderr, "Nearby symbol : %s\n", exceptionSymbol.name);
- delete [] exceptionSymbol.name;
- }
- void *stack[maxStackFrames];
- fputs("\nStack:\n", stderr);
- const unsigned frameCount = CaptureStackBackTrace(0, DWORD(maxStackFrames), stack, NULL);
- for (unsigned f = 0; f < frameCount; ++f) {
- DebugSymbolResolver::Symbol symbol = resolver.resolveSymbol(DWORD64(stack[f]));
- if (symbol.name) {
- fprintf(stderr, "#%3u: %s() - 0x%p\n", f + 1, symbol.name, (const void *)symbol.address);
- delete [] symbol.name;
- } else {
- fprintf(stderr, "#%3u: Unable to obtain symbol\n", f + 1);
- }
- }
- }
-
- fputc('\n', stderr);
-
- return EXCEPTION_EXECUTE_HANDLER;
- }
-};
-using FatalSignalHandler = WindowsFaultHandler;
-
-#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
-class FatalSignalHandler
-{
-public:
-# define OUR_SIGNALS(F) \
- F(HUP) \
- F(INT) \
- F(QUIT) \
- F(ABRT) \
- F(ILL) \
- F(BUS) \
- F(FPE) \
- F(SEGV) \
- F(PIPE) \
- F(TERM) \
- /**/
-# define CASE_LABEL(S) case SIG ## S: return QT_STRINGIFY(S);
-# define ENUMERATE_SIGNALS(S) SIG ## S,
- static const char *signalName(int signum) noexcept
- {
- switch (signum) {
- OUR_SIGNALS(CASE_LABEL)
- }
-
-# if defined(__GLIBC_MINOR__) && (__GLIBC_MINOR__ >= 32 || __GLIBC__ > 2)
- // get the other signal names from glibc 2.32
- // (accessing the sys_sigabbrev variable causes linker warnings)
- if (const char *p = sigabbrev_np(signum))
- return p;
-# endif
- return "???";
- }
- static constexpr std::array fatalSignals = {
- OUR_SIGNALS(ENUMERATE_SIGNALS)
- };
-# undef CASE_LABEL
-# undef ENUMERATE_SIGNALS
-
- static constexpr std::array crashingSignals = {
- // Crash signals are special, because if we return from the handler
- // without adjusting the machine state, the same instruction that
- // originally caused the crash will get re-executed and will thus cause
- // the same crash again. This is useful if our parent process logs the
- // exit result or if core dumps are enabled: the core file will point
- // to the actual instruction that crashed.
- SIGILL, SIGBUS, SIGFPE, SIGSEGV
- };
- using OldActionsArray = std::array<struct sigaction, fatalSignals.size()>;
-
- FatalSignalHandler()
- {
- pauseOnCrash = qEnvironmentVariableIsSet("QTEST_PAUSE_ON_CRASH");
- struct sigaction act;
- memset(&act, 0, sizeof(act));
- act.sa_handler = SIG_DFL;
- oldActions().fill(act);
-
- // Remove the handler after it is invoked.
- act.sa_flags = SA_RESETHAND | setupAlternateStack();
-
-# ifdef SA_SIGINFO
- act.sa_flags |= SA_SIGINFO;
- act.sa_sigaction = FatalSignalHandler::actionHandler;
-# else
- act.sa_handler = FatalSignalHandler::regularHandler;
-# endif
-
- // Block all fatal signals in our signal handler so we don't try to close
- // the testlog twice.
- sigemptyset(&act.sa_mask);
- for (int signal : fatalSignals)
- sigaddset(&act.sa_mask, signal);
-
- for (size_t i = 0; i < fatalSignals.size(); ++i)
- sigaction(fatalSignals[i], &act, &oldActions()[i]);
- }
-
- ~FatalSignalHandler()
- {
- // Restore the default signal handlers in place of ours.
- // If ours has been replaced, leave the replacement alone.
- auto isOurs = [](const struct sigaction &old) {
-# ifdef SA_SIGINFO
- return (old.sa_flags & SA_SIGINFO) && old.sa_sigaction == FatalSignalHandler::actionHandler;
-# else
- return old.sa_handler == FatalSignalHandler::regularHandler;
-# endif
- };
- struct sigaction action;
-
- for (size_t i = 0; i < fatalSignals.size(); ++i) {
- struct sigaction &act = oldActions()[i];
- if (act.sa_flags == 0 && act.sa_handler == SIG_DFL)
- continue; // Already the default
- if (sigaction(fatalSignals[i], nullptr, &action))
- continue; // Failed to query present handler
- if (isOurs(action))
- sigaction(fatalSignals[i], &act, nullptr);
- }
-
- freeAlternateStack();
- }
-
-private:
- Q_DISABLE_COPY_MOVE(FatalSignalHandler)
-
- static OldActionsArray &oldActions()
- {
- Q_CONSTINIT static OldActionsArray oldActions {};
- return oldActions;
- }
-
- auto alternateStackSize()
- {
- struct R { size_t size, pageSize; };
- static constexpr size_t MinStackSize = 32 * 1024;
- size_t pageSize = sysconf(_SC_PAGESIZE);
- size_t size = SIGSTKSZ;
- if (size < MinStackSize) {
- size = MinStackSize;
- } else {
- // round up to a page
- size = (size + pageSize - 1) & -pageSize;
- }
-
- return R{ size + pageSize, pageSize };
- }
-
- int setupAlternateStack()
- {
- // tvOS/watchOS both define SA_ONSTACK (in sys/signal.h) but mark sigaltstack() as
- // unavailable (__WATCHOS_PROHIBITED __TVOS_PROHIBITED in signal.h)
-# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
- // Let the signal handlers use an alternate stack
- // This is necessary if SIGSEGV is to catch a stack overflow
- auto r = alternateStackSize();
- int flags = MAP_PRIVATE | MAP_ANONYMOUS;
-# ifdef MAP_STACK
- flags |= MAP_STACK;
-# endif
- alternateStackBase = mmap(nullptr, r.size, PROT_READ | PROT_WRITE, flags, -1, 0);
- if (alternateStackBase == MAP_FAILED)
- return 0;
-
- // mark the bottom page inaccessible, to catch a handler stack overflow
- (void) mprotect(alternateStackBase, r.pageSize, PROT_NONE);
-
- stack_t stack;
- stack.ss_flags = 0;
- stack.ss_size = r.size - r.pageSize;
- stack.ss_sp = static_cast<char *>(alternateStackBase) + r.pageSize;
- sigaltstack(&stack, nullptr);
- return SA_ONSTACK;
-# else
- return 0;
-# endif
- }
-
- void freeAlternateStack()
- {
-# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
- if (alternateStackBase != MAP_FAILED) {
- stack_t stack = {};
- stack.ss_flags = SS_DISABLE;
- sigaltstack(&stack, nullptr);
- munmap(alternateStackBase, alternateStackSize().size);
- }
-# endif
- }
-
- template <typename T> static
- std::enable_if_t<sizeof(std::declval<T>().si_pid) + sizeof(std::declval<T>().si_uid) >= 1>
- printSentSignalInfo(T *info)
- {
- writeToStderr(" sent by PID ", asyncSafeToString(info->si_pid),
- " UID ", asyncSafeToString(info->si_uid));
- }
- static void printSentSignalInfo(...) {}
-
- template <typename T> static
- std::enable_if_t<sizeof(std::declval<T>().si_addr) >= 1> printCrashingSignalInfo(T *info)
- {
- using HexString = std::array<char, sizeof(quintptr) * 2>;
- auto toHexString = [](quintptr u, HexString &&r = {}) {
- int shift = sizeof(quintptr) * 8 - 4;
- for (size_t i = 0; i < sizeof(quintptr) * 2; ++i, shift -= 4)
- r[i] = QtMiscUtils::toHexLower(u >> shift);
- struct iovec vec;
- vec.iov_base = r.data();
- vec.iov_len = r.size();
- return vec;
- };
- writeToStderr(", code ", asyncSafeToString(info->si_code),
- ", for address 0x", toHexString(quintptr(info->si_addr)));
- }
- static void printCrashingSignalInfo(...) {}
-
- static void actionHandler(int signum, siginfo_t *info, void * /* ucontext */)
- {
- writeToStderr("Received signal ", asyncSafeToString(signum),
- " (SIG", signalName(signum), ")");
-
- bool isCrashingSignal =
- std::find(crashingSignals.begin(), crashingSignals.end(), signum) != crashingSignals.end();
- if (isCrashingSignal && (!info || info->si_code <= 0))
- isCrashingSignal = false; // wasn't sent by the kernel, so it's not really a crash
- if (isCrashingSignal)
- printCrashingSignalInfo(info);
- else if (info && (info->si_code == SI_USER || info->si_code == SI_QUEUE))
- printSentSignalInfo(info);
-
- printTestRunTime();
- if (signum != SIGINT) {
- generateStackTrace();
- if (pauseOnCrash) {
- writeToStderr("Pausing process ", asyncSafeToString(getpid()),
- " for debugging\n");
- raise(SIGSTOP);
- }
- }
-
- // chain back to the previous handler, if any
- for (size_t i = 0; i < fatalSignals.size(); ++i) {
- struct sigaction &act = oldActions()[i];
- if (signum != fatalSignals[i])
- continue;
-
- // restore the handler (if SA_RESETHAND hasn't done the job for us)
- if (SA_RESETHAND == 0 || act.sa_handler != SIG_DFL || act.sa_flags)
- (void) sigaction(signum, &act, nullptr);
-
- if (!isCrashingSignal)
- raise(signum);
-
- // signal is blocked, so it'll be delivered when we return
- return;
- }
-
- // we shouldn't reach here!
- std::abort();
- }
-
- [[maybe_unused]] static void regularHandler(int signum)
- {
- actionHandler(signum, nullptr, nullptr);
- }
-
- void *alternateStackBase = MAP_FAILED;
- static bool pauseOnCrash;
-};
-bool FatalSignalHandler::pauseOnCrash = false;
-#else // Q_OS_WASM or weird systems
-class FatalSignalHandler {};
-#endif // Q_OS_* choice
-
-} // unnamed namespace
+} // namespace QTest
static void initEnvironment()
{
@@ -2232,6 +1826,14 @@ int QTest::qExec(QObject *testObject, int argc, char **argv)
qInit(testObject, argc, argv);
int ret = qRun();
qCleanup();
+
+#if defined(Q_OS_WASM)
+ EM_ASM({
+ if (typeof Module != "undefined" && typeof Module.notifyTestFinished != "undefined")
+ Module.notifyTestFinished($0);
+ }, ret);
+#endif // Q_OS_WASM
+
return ret;
}
@@ -2240,7 +1842,7 @@ int QTest::qExec(QObject *testObject, int argc, char **argv)
void QTest::qInit(QObject *testObject, int argc, char **argv)
{
initEnvironment();
- maybeDisableCoreDump();
+ CrashHandler::maybeDisableCoreDump();
QBenchmarkGlobalData::current = new QBenchmarkGlobalData;
#if defined(Q_OS_MACOS)
@@ -2276,10 +1878,7 @@ void QTest::qInit(QObject *testObject, int argc, char **argv)
#if QT_CONFIG(valgrind)
if (QBenchmarkGlobalData::current->mode() != QBenchmarkGlobalData::CallgrindParentProcess)
#endif
- {
- QTestTable::globalTestTable();
QTestLog::startLogging();
- }
}
/*! \internal
@@ -2310,9 +1909,9 @@ int QTest::qRun()
} else
#endif
{
- std::optional<FatalSignalHandler> handler;
- prepareStackTrace();
- if (!noCrashHandler)
+ std::optional<CrashHandler::FatalSignalHandler> handler;
+ CrashHandler::prepareStackTrace();
+ if (!Internal::noCrashHandler)
handler.emplace();
bool seenBad = false;
@@ -2344,7 +1943,12 @@ int QTest::qRun()
return 1;
}
TestMethods test(currentTestObject, std::move(commandLineMethods));
- test.invokeTests(currentTestObject);
+
+ while (QTestLog::failCount() == 0 && (repeatForever || repetitions-- > 0)) {
+ QTestTable::globalTestTable();
+ test.invokeTests(currentTestObject);
+ QTestTable::clearGlobalTestTable();
+ }
}
#ifndef QT_NO_EXCEPTIONS
@@ -2381,10 +1985,7 @@ void QTest::qCleanup()
#if QT_CONFIG(valgrind)
if (QBenchmarkGlobalData::current->mode() != QBenchmarkGlobalData::CallgrindParentProcess)
#endif
- {
QTestLog::stopLogging();
- QTestTable::clearGlobalTestTable();
- }
delete QBenchmarkGlobalData::current;
QBenchmarkGlobalData::current = nullptr;
@@ -2402,6 +2003,8 @@ void QTest::qCleanup()
The \a name will be listed when running the batch test binary with no
parameters. Running the test binary with the argv[1] of \a name will result
in \a entryFunction being called.
+
+ \since 6.5
*/
void QTest::qRegisterTestCase(const QString &name, TestEntryFunction entryFunction)
{
@@ -2510,6 +2113,39 @@ void QTest::qCaught(const char *expected, const char *what, const char *file, in
qFail(message().toUtf8().constData(), file, line);
}
+/*!
+ \internal
+
+ Contains the implementation of the catch(...) block of
+ QVERIFY_THROWS_EXCEPTION.
+
+ The function inspects std::current_exception() by rethrowing it using
+ std::rethrow_exception().
+
+ The function must be called from a catch handler.
+
+ If the exception inherits std::exception, its what() message is logged and
+ this function returns normally. The caller of this function must then
+ execute a \c{QTEST_FAIL_ACTION} to exit from the test function.
+
+ Otherwise, a message saying an unknown exception was caught is logged and
+ this function rethrows the exception, skipping the \c{QTEST_FAIL_ACTION}
+ that follows this function call in the caller.
+*/
+void QTest::qCaught(const char *expected, const char *file, int line)
+{
+ try {
+ // let's see what the cat brought us:
+ std::rethrow_exception(std::current_exception());
+ } catch (const std::exception &e) {
+ qCaught(expected, e.what(), file, line);
+ } catch (...) {
+ qCaught(expected, nullptr, file, line);
+ throw;
+ }
+ // caller shall invoke `QTEST_FAIL_ACTION` if control reached here
+}
+
#if QT_DEPRECATED_SINCE(6, 3)
/*!
@@ -2566,6 +2202,19 @@ void QTest::ignoreMessage(QtMsgType type, const QRegularExpression &messagePatte
#endif // QT_CONFIG(regularexpression)
/*!
+ \since 6.8
+ \overload failOnWarning()
+
+ Appends a test failure to the test log if any warning is output.
+
+ \sa failOnWarning()
+*/
+void QTest::failOnWarning()
+{
+ return QTestLog::failOnWarning();
+}
+
+/*!
\since 6.3
\overload failOnWarning()
@@ -2611,7 +2260,17 @@ void QTest::failOnWarning(const char *message)
\code
void FileTest::init()
{
- QTest::failOnWarning(QRegularExpression(".?"));
+ QTest::failOnWarning(
+ QRegularExpression("QFile::.*: File(.*) already open"));
+ }
+ \endcode
+
+ For the common case of failing on \e any warning pass no parameter:
+
+ \code
+ void FileTest::init()
+ {
+ QTest::failOnWarning();
}
\endcode
@@ -2669,30 +2328,36 @@ QSharedPointer<QTemporaryDir> QTest::qExtractTestData(const QString &dirName)
return result;
}
- QDirIterator it(resourcePath, QDirIterator::Subdirectories);
- if (!it.hasNext()) {
- qWarning("Resource directory '%s' is empty.", qPrintable(resourcePath));
- return result;
- }
-
- while (it.hasNext()) {
- QFileInfo fileInfo = it.nextFileInfo();
-
- if (!fileInfo.isDir()) {
- const QString destination = dataPath + u'/' + QStringView{fileInfo.filePath()}.mid(resourcePath.size());
+ bool isResourceDirEmpty = true;
+ for (const auto &dirEntry : QDirListing(resourcePath, QDirListing::IteratorFlag::Recursive)) {
+ isResourceDirEmpty = false;
+ if (!dirEntry.isDir()) {
+ const QString &filePath = dirEntry.filePath();
+ const QString destination =
+ dataPath + u'/' + QStringView{filePath}.sliced(resourcePath.size());
QFileInfo destinationFileInfo(destination);
QDir().mkpath(destinationFileInfo.path());
- if (!QFile::copy(fileInfo.filePath(), destination)) {
- qWarning("Failed to copy '%s'.", qPrintable(fileInfo.filePath()));
+ QFile file(filePath);
+ if (!file.copy(destination)) {
+ qWarning("Failed to copy '%ls': %ls.", qUtf16Printable(filePath),
+ qUtf16Printable(file.errorString()));
return result;
}
- if (!QFile::setPermissions(destination, QFile::ReadUser | QFile::WriteUser | QFile::ReadGroup)) {
- qWarning("Failed to set permissions on '%s'.", qPrintable(destination));
+
+ file.setFileName(destination);
+ if (!file.setPermissions(QFile::ReadUser | QFile::WriteUser | QFile::ReadGroup)) {
+ qWarning("Failed to set permissions on '%ls': %ls.", qUtf16Printable(destination),
+ qUtf16Printable(file.errorString()));
return result;
}
}
}
+ if (isResourceDirEmpty) {
+ qWarning("Resource directory '%s' is empty.", qPrintable(resourcePath));
+ return result;
+ }
+
result = std::move(tempDir);
return result;
@@ -2886,9 +2551,13 @@ void QTest::addColumnInternal(int id, const char *name)
}
/*!
- Appends a new row to the current test data. \a dataTag is the name of
- the testdata that will appear in the test output. Returns a QTestData reference
- that can be used to stream in data.
+ Appends a new row to the current test data.
+
+ The test output will identify the test run with this test data using the
+ name \a dataTag.
+
+ Returns a QTestData reference that can be used to stream in data, one value
+ for each column in the table.
Example:
\snippet code/src_qtestlib_qtestcase.cpp 20
@@ -2899,14 +2568,15 @@ void QTest::addColumnInternal(int id, const char *name)
See \l {Chapter 2: Data Driven Testing}{Data Driven Testing} for
a more extensive example.
- \sa addColumn(), QFETCH()
+ \sa addRow(), addColumn(), QFETCH()
*/
QTestData &QTest::newRow(const char *dataTag)
{
QTEST_ASSERT_X(dataTag, "QTest::newRow()", "Data tag cannot be null");
QTestTable *tbl = QTestTable::currentTestTable();
QTEST_ASSERT_X(tbl, "QTest::newRow()", "Cannot add testdata outside of a _data slot.");
- QTEST_ASSERT_X(tbl->elementCount(), "QTest::newRow()", "Must add columns before attempting to add rows.");
+ QTEST_ASSERT_X(tbl->elementCount(), "QTest::newRow()",
+ "Must add columns before attempting to add rows.");
return *tbl->newData(dataTag);
}
@@ -2914,13 +2584,17 @@ QTestData &QTest::newRow(const char *dataTag)
/*!
\since 5.9
- Appends a new row to the current test data. The function's arguments are passed
- to qsnprintf() for formatting according to \a format. See the qvsnprintf()
- documentation for caveats and limitations.
+ Appends a new row to the current test data.
- The formatted string will appear as the name of this test data in the test output.
+ The function's arguments are passed to qsnprintf() for formatting according
+ to \a format. See the qvsnprintf() documentation for caveats and
+ limitations.
- Returns a QTestData reference that can be used to stream in data.
+ The test output will identify the test run with this test data using the
+ name that results from this formatting.
+
+ Returns a QTestData reference that can be used to stream in data, one value
+ for each column in the table.
Example:
\snippet code/src_qtestlib_qtestcase.cpp addRow
@@ -2931,14 +2605,15 @@ QTestData &QTest::newRow(const char *dataTag)
See \l {Chapter 2: Data Driven Testing}{Data Driven Testing} for
a more extensive example.
- \sa addColumn(), QFETCH()
+ \sa newRow(), addColumn(), QFETCH()
*/
QTestData &QTest::addRow(const char *format, ...)
{
QTEST_ASSERT_X(format, "QTest::addRow()", "Format string cannot be null");
QTestTable *tbl = QTestTable::currentTestTable();
QTEST_ASSERT_X(tbl, "QTest::addRow()", "Cannot add testdata outside of a _data slot.");
- QTEST_ASSERT_X(tbl->elementCount(), "QTest::addRow()", "Must add columns before attempting to add rows.");
+ QTEST_ASSERT_X(tbl->elementCount(), "QTest::addRow()",
+ "Must add columns before attempting to add rows.");
char buf[1024];
@@ -3022,7 +2697,7 @@ bool QTest::currentTestFailed()
This applies if the test has failed or exercised a skip. When it is true,
the test function should return early. In particular, the \c{QTRY_*} macros
- and \l QTestEventLoop terminate their loops early if executed during the
+ and the test event loop terminate their loops early if executed during the
test function (but not its cleanup()). After a test has called a helper
function that uses this module's macros, it can use this function to test
whether to return early.
@@ -3096,6 +2771,7 @@ bool QTest::compare_helper(bool success, const char *failureMsg,
}
#endif // QT_DEPRECATED_SINCE(6, 4)
+#if QT_DEPRECATED_SINCE(6, 8)
/*! \internal
\since 6.4
This function is called by various specializations of QTest::qCompare
@@ -3115,7 +2791,34 @@ bool QTest::compare_helper(bool success, const char *failureMsg,
const char *actual, const char *expected,
const char *file, int line)
{
- return QTestResult::reportResult(success, actualVal, expectedVal, actual, expected,
+ return QTestResult::reportResult(success, &actualVal, &expectedVal,
+ QTest::functionRefFormatter,
+ QTest::functionRefFormatter, actual, expected,
+ QTest::ComparisonOperation::CustomCompare,
+ file, line, failureMsg);
+}
+#endif // QT_DEPRECATED_SINCE(6, 8)
+
+/*! \internal
+ \since 6.8
+ This function is called by various specializations of QTest::qCompare
+ to decide whether to report a failure and to produce verbose test output.
+
+ The \a failureMsg parameter can be \c {nullptr}, in which case a default
+ message will be output if the compare fails. If the comparison succeeds,
+ \a failureMsg will not be output.
+*/
+
+bool QTest::compare_helper(bool success, const char *failureMsg,
+ const void *actualPtr, const void *expectedPtr,
+ const char *(*actualFormatter)(const void *),
+ const char *(*expectedFormatter)(const void *),
+ const char *actual, const char *expected,
+ const char *file, int line)
+{
+ return QTestResult::reportResult(success, actualPtr, expectedPtr,
+ actualFormatter, expectedFormatter,
+ actual, expected,
QTest::ComparisonOperation::CustomCompare,
file, line, failureMsg);
}
@@ -3161,9 +2864,10 @@ static bool floatingCompare(const T &actual, const T &expected)
bool QTest::qCompare(qfloat16 const &t1, qfloat16 const &t2, const char *actual, const char *expected,
const char *file, int line)
{
+ auto formatter = Internal::genericToString<qfloat16>;
return compare_helper(floatingCompare(t1, t2),
"Compared qfloat16s are not the same (fuzzy compare)",
- [&t1] { return toString(t1); }, [&t2] { return toString(t2); },
+ &t1, &t2, formatter, formatter,
actual, expected, file, line);
}
@@ -3428,11 +3132,6 @@ char *QTest::toString(const char *str)
*/
char *QTest::toString(const volatile void *p) // Use volatile to match compare_ptr_helper()
{
- return QTest::toString(const_cast<const void *>(p));
-}
-
-char *QTest::toString(const void *p)
-{
char *msg = new char[128];
qsnprintf(msg, 128, "%p", p);
return msg;
@@ -3485,8 +3184,9 @@ char *QTest::toString(const volatile QObject *vo)
bool QTest::compare_string_helper(const char *t1, const char *t2, const char *actual,
const char *expected, const char *file, int line)
{
+ auto formatter = Internal::genericToString<const char *>;
return compare_helper(qstrcmp(t1, t2) == 0, "Compared strings are not the same",
- [t1] { return toString(t1); }, [t2] { return toString(t2); },
+ &t1, &t2, formatter, formatter,
actual, expected, file, line);
}
diff --git a/src/testlib/qtestcase.h b/src/testlib/qtestcase.h
index 54978e34e7..a855ace6a9 100644
--- a/src/testlib/qtestcase.h
+++ b/src/testlib/qtestcase.h
@@ -5,6 +5,7 @@
#define QTESTCASE_H
#include <QtTest/qttestglobal.h>
+#include <QtTest/qtesttostring.h>
#include <QtCore/qstring.h>
#include <QtCore/qnamespace.h>
@@ -23,64 +24,78 @@
QT_BEGIN_NAMESPACE
+#ifndef QT_NO_EXCEPTIONS
+
+#ifdef QTEST_THROW_ON_FAILURE
+# define QTEST_FAIL_ACTION QTest::Internal::throwOnFail()
+#else
+# define QTEST_FAIL_ACTION do { QTest::Internal::maybeThrowOnFail(); return; } while (false)
+#endif
+
+#ifdef QTEST_THROW_ON_SKIP
+# define QTEST_SKIP_ACTION QTest::Internal::throwOnSkip()
+#else
+# define QTEST_SKIP_ACTION do { QTest::Internal::maybeThrowOnSkip(); return; } while (false)
+#endif
+
+#else
+# if defined(QTEST_THROW_ON_FAILURE) || defined(QTEST_THROW_ON_SKIP)
+# error QTEST_THROW_ON_FAILURE/SKIP require exception support enabled.
+# endif
+#endif // QT_NO_EXCEPTIONS
+
+#ifndef QTEST_FAIL_ACTION
+# define QTEST_FAIL_ACTION return
+#endif
+
+#ifndef QTEST_SKIP_ACTION
+# define QTEST_SKIP_ACTION return
+#endif
+
class qfloat16;
class QRegularExpression;
#define QVERIFY(statement) \
do {\
if (!QTest::qVerify(static_cast<bool>(statement), #statement, "", __FILE__, __LINE__))\
- return;\
+ QTEST_FAIL_ACTION; \
} while (false)
#define QFAIL(message) \
do {\
QTest::qFail(static_cast<const char *>(message), __FILE__, __LINE__);\
- return;\
+ QTEST_FAIL_ACTION; \
} while (false)
#define QVERIFY2(statement, description) \
do {\
if (statement) {\
if (!QTest::qVerify(true, #statement, static_cast<const char *>(description), __FILE__, __LINE__))\
- return;\
+ QTEST_FAIL_ACTION; \
} else {\
if (!QTest::qVerify(false, #statement, static_cast<const char *>(description), __FILE__, __LINE__))\
- return;\
+ QTEST_FAIL_ACTION; \
}\
} while (false)
#define QCOMPARE(actual, expected) \
do {\
if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\
- return;\
+ QTEST_FAIL_ACTION; \
} while (false)
-// A wrapper lambda is introduced to extend the lifetime of lhs and rhs in
-// case they are temporary objects.
-// We also use IILE to prevent potential name clashes and shadowing of variables
-// from user code. A drawback of the approach is that it looks ugly :(
#define QCOMPARE_OP_IMPL(lhs, rhs, op, opId) \
do { \
- if (![](auto &&qt_lhs_arg, auto &&qt_rhs_arg) { \
- /* assumes that op does not actually move from qt_{lhs, rhs}_arg */ \
- return QTest::reportResult(std::forward<decltype(qt_lhs_arg)>(qt_lhs_arg) \
- op \
- std::forward<decltype(qt_rhs_arg)>(qt_rhs_arg), \
- [&qt_lhs_arg] { return QTest::toString(qt_lhs_arg); }, \
- [&qt_rhs_arg] { return QTest::toString(qt_rhs_arg); }, \
- #lhs, #rhs, QTest::ComparisonOperation::opId, \
- __FILE__, __LINE__); \
- }(lhs, rhs)) { \
- return; \
- } \
+ if (!QTest::qCompareOp<QTest::ComparisonOperation::opId>(lhs, rhs, #lhs, #rhs, __FILE__, __LINE__)) \
+ QTEST_FAIL_ACTION; \
} while (false)
-#define QCOMPARE_EQ(lhs, rhs) QCOMPARE_OP_IMPL(lhs, rhs, ==, Equal)
-#define QCOMPARE_NE(lhs, rhs) QCOMPARE_OP_IMPL(lhs, rhs, !=, NotEqual)
-#define QCOMPARE_LT(lhs, rhs) QCOMPARE_OP_IMPL(lhs, rhs, <, LessThan)
-#define QCOMPARE_LE(lhs, rhs) QCOMPARE_OP_IMPL(lhs, rhs, <=, LessThanOrEqual)
-#define QCOMPARE_GT(lhs, rhs) QCOMPARE_OP_IMPL(lhs, rhs, >, GreaterThan)
-#define QCOMPARE_GE(lhs, rhs) QCOMPARE_OP_IMPL(lhs, rhs, >=, GreaterThanOrEqual)
+#define QCOMPARE_EQ(computed, baseline) QCOMPARE_OP_IMPL(computed, baseline, ==, Equal)
+#define QCOMPARE_NE(computed, baseline) QCOMPARE_OP_IMPL(computed, baseline, !=, NotEqual)
+#define QCOMPARE_LT(computed, baseline) QCOMPARE_OP_IMPL(computed, baseline, <, LessThan)
+#define QCOMPARE_LE(computed, baseline) QCOMPARE_OP_IMPL(computed, baseline, <=, LessThanOrEqual)
+#define QCOMPARE_GT(computed, baseline) QCOMPARE_OP_IMPL(computed, baseline, >, GreaterThan)
+#define QCOMPARE_GE(computed, baseline) QCOMPARE_OP_IMPL(computed, baseline, >=, GreaterThanOrEqual)
#ifndef QT_NO_EXCEPTIONS
@@ -89,12 +104,9 @@ do { \
QT_TRY { \
__VA_ARGS__; \
/* success */ \
- } QT_CATCH (const std::exception &e) { \
- QTest::qCaught(nullptr, e.what(), __FILE__, __LINE__); \
- return; \
} QT_CATCH (...) { \
- QTest::qCaught(nullptr, nullptr, __FILE__, __LINE__); \
- QT_RETHROW; \
+ QTest::qCaught(nullptr, __FILE__, __LINE__); \
+ QTEST_FAIL_ACTION; \
} \
} while (false) \
/* end */
@@ -111,22 +123,20 @@ inline void useVerifyThrowsException() {}
# define QVERIFY_THROWS_EXCEPTION(exceptiontype, ...) \
do {\
+ bool qverify_throws_exception_did_not_throw = false; \
QT_TRY {\
- QT_TRY {\
- __VA_ARGS__;\
- QTest::qFail("Expected exception of type " #exceptiontype " to be thrown" \
- " but no exception caught", __FILE__, __LINE__);\
- return;\
- } QT_CATCH (const exceptiontype &) {\
- /* success */\
- }\
- } QT_CATCH (const std::exception &e) {\
- QTest::qCaught(#exceptiontype, e.what(), __FILE__, __LINE__);\
- return;\
+ __VA_ARGS__; \
+ QTest::qFail("Expected exception of type " #exceptiontype " to be thrown" \
+ " but no exception caught", __FILE__, __LINE__); \
+ qverify_throws_exception_did_not_throw = true; \
+ } QT_CATCH (const exceptiontype &) { \
+ /* success */ \
} QT_CATCH (...) {\
- QTest::qCaught(#exceptiontype, nullptr, __FILE__, __LINE__);\
- QT_RETHROW;\
+ QTest::qCaught(#exceptiontype, __FILE__, __LINE__); \
+ QTEST_FAIL_ACTION; \
}\
+ if (qverify_throws_exception_did_not_throw) \
+ QTEST_FAIL_ACTION; \
} while (false)
#else // QT_NO_EXCEPTIONS
@@ -175,9 +185,14 @@ inline void useVerifyThrowsException() {}
} \
}
-#define QTRY_IMPL(expr, timeout)\
- const int qt_test_step = timeout < 350 ? timeout / 7 + 1 : 50; \
- const int qt_test_timeoutValue = timeout; \
+#define QTRY_IMPL(expr, timeoutAsGiven)\
+ const auto qt_test_timeoutAsMs = [&] { \
+ /* make 5s work w/o user action: */ \
+ using namespace std::chrono_literals; \
+ return std::chrono::milliseconds{timeoutAsGiven}; \
+ }(); \
+ const int qt_test_step = qt_test_timeoutAsMs.count() < 350 ? qt_test_timeoutAsMs.count() / 7 + 1 : 50; \
+ const int qt_test_timeoutValue = qt_test_timeoutAsMs.count(); \
{ QTRY_LOOP_IMPL(expr, qt_test_timeoutValue, qt_test_step) } \
QTRY_TIMEOUT_DEBUG_IMPL(expr, qt_test_timeoutValue, qt_test_step)
// Ends with an if-block, so doesn't want a following semicolon.
@@ -189,7 +204,7 @@ do { \
QVERIFY(expr); \
} while (false)
-#define QTRY_VERIFY(expr) QTRY_VERIFY_WITH_TIMEOUT(expr, 5000)
+#define QTRY_VERIFY(expr) QTRY_VERIFY_WITH_TIMEOUT(expr, 5s)
// Will try to wait for the expression to become true while allowing event processing
#define QTRY_VERIFY2_WITH_TIMEOUT(expr, messageExpression, timeout) \
@@ -198,7 +213,7 @@ do { \
QVERIFY2(expr, messageExpression); \
} while (false)
-#define QTRY_VERIFY2(expr, messageExpression) QTRY_VERIFY2_WITH_TIMEOUT(expr, messageExpression, 5000)
+#define QTRY_VERIFY2(expr, messageExpression) QTRY_VERIFY2_WITH_TIMEOUT(expr, messageExpression, 5s)
// Will try to wait for the comparison to become successful while allowing event processing
#define QTRY_COMPARE_WITH_TIMEOUT(expr, expected, timeout) \
@@ -207,48 +222,49 @@ do { \
QCOMPARE(expr, expected); \
} while (false)
-#define QTRY_COMPARE(expr, expected) QTRY_COMPARE_WITH_TIMEOUT(expr, expected, 5000)
+#define QTRY_COMPARE(expr, expected) QTRY_COMPARE_WITH_TIMEOUT(expr, expected, 5s)
-#define QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(left, right, op, opId, timeout) \
+#define QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, op, opId, timeout) \
do { \
- QTRY_IMPL(((left) op (right)), timeout) \
- QCOMPARE_OP_IMPL(left, right, op, opId); \
+ using Q_Cmp = QTest::Internal::Compare<QTest::ComparisonOperation::opId>; \
+ QTRY_IMPL(Q_Cmp::compare((computed), (baseline)), timeout) \
+ QCOMPARE_OP_IMPL(computed, baseline, op, opId); \
} while (false)
-#define QTRY_COMPARE_EQ_WITH_TIMEOUT(left, right, timeout) \
- QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(left, right, ==, Equal, timeout)
+#define QTRY_COMPARE_EQ_WITH_TIMEOUT(computed, baseline, timeout) \
+ QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, ==, Equal, timeout)
-#define QTRY_COMPARE_EQ(left, right) QTRY_COMPARE_EQ_WITH_TIMEOUT(left, right, 5000)
+#define QTRY_COMPARE_EQ(computed, baseline) QTRY_COMPARE_EQ_WITH_TIMEOUT(computed, baseline, 5s)
-#define QTRY_COMPARE_NE_WITH_TIMEOUT(left, right, timeout) \
- QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(left, right, !=, NotEqual, timeout)
+#define QTRY_COMPARE_NE_WITH_TIMEOUT(computed, baseline, timeout) \
+ QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, !=, NotEqual, timeout)
-#define QTRY_COMPARE_NE(left, right) QTRY_COMPARE_NE_WITH_TIMEOUT(left, right, 5000)
+#define QTRY_COMPARE_NE(computed, baseline) QTRY_COMPARE_NE_WITH_TIMEOUT(computed, baseline, 5s)
-#define QTRY_COMPARE_LT_WITH_TIMEOUT(left, right, timeout) \
- QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(left, right, <, LessThan, timeout)
+#define QTRY_COMPARE_LT_WITH_TIMEOUT(computed, baseline, timeout) \
+ QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, <, LessThan, timeout)
-#define QTRY_COMPARE_LT(left, right) QTRY_COMPARE_LT_WITH_TIMEOUT(left, right, 5000)
+#define QTRY_COMPARE_LT(computed, baseline) QTRY_COMPARE_LT_WITH_TIMEOUT(computed, baseline, 5s)
-#define QTRY_COMPARE_LE_WITH_TIMEOUT(left, right, timeout) \
- QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(left, right, <=, LessThanOrEqual, timeout)
+#define QTRY_COMPARE_LE_WITH_TIMEOUT(computed, baseline, timeout) \
+ QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, <=, LessThanOrEqual, timeout)
-#define QTRY_COMPARE_LE(left, right) QTRY_COMPARE_LE_WITH_TIMEOUT(left, right, 5000)
+#define QTRY_COMPARE_LE(computed, baseline) QTRY_COMPARE_LE_WITH_TIMEOUT(computed, baseline, 5s)
-#define QTRY_COMPARE_GT_WITH_TIMEOUT(left, right, timeout) \
- QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(left, right, >, GreaterThan, timeout)
+#define QTRY_COMPARE_GT_WITH_TIMEOUT(computed, baseline, timeout) \
+ QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, >, GreaterThan, timeout)
-#define QTRY_COMPARE_GT(left, right) QTRY_COMPARE_GT_WITH_TIMEOUT(left, right, 5000)
+#define QTRY_COMPARE_GT(computed, baseline) QTRY_COMPARE_GT_WITH_TIMEOUT(computed, baseline, 5s)
-#define QTRY_COMPARE_GE_WITH_TIMEOUT(left, right, timeout) \
- QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(left, right, >=, GreaterThanOrEqual, timeout)
+#define QTRY_COMPARE_GE_WITH_TIMEOUT(computed, baseline, timeout) \
+ QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, >=, GreaterThanOrEqual, timeout)
-#define QTRY_COMPARE_GE(left, right) QTRY_COMPARE_GE_WITH_TIMEOUT(left, right, 5000)
+#define QTRY_COMPARE_GE(computed, baseline) QTRY_COMPARE_GE_WITH_TIMEOUT(computed, baseline, 5s)
#define QSKIP_INTERNAL(statement) \
do {\
QTest::qSkip(static_cast<const char *>(statement), __FILE__, __LINE__);\
- return;\
+ QTEST_SKIP_ACTION; \
} while (false)
#define QSKIP(statement, ...) QSKIP_INTERNAL(statement)
@@ -256,7 +272,7 @@ do {\
#define QEXPECT_FAIL(dataIndex, comment, mode)\
do {\
if (!QTest::qExpectFail(dataIndex, static_cast<const char *>(comment), QTest::mode, __FILE__, __LINE__))\
- return;\
+ QTEST_FAIL_ACTION; \
} while (false)
#define QFETCH(Type, name)\
@@ -268,7 +284,7 @@ do {\
#define QTEST(actual, testElement)\
do {\
if (!QTest::qTest(actual, testElement, #actual, #testElement, __FILE__, __LINE__))\
- return;\
+ QTEST_FAIL_ACTION; \
} while (false)
#ifdef QT_TESTCASE_BUILDDIR
@@ -290,83 +306,71 @@ do {\
class QObject;
class QTestData;
-#define QTEST_COMPARE_DECL(KLASS)\
- template<> Q_TESTLIB_EXPORT char *toString<KLASS >(const KLASS &);
-
namespace QTest
{
namespace Internal {
+ [[noreturn]] Q_TESTLIB_EXPORT void throwOnFail();
+ [[noreturn]] Q_TESTLIB_EXPORT void throwOnSkip();
+ Q_TESTLIB_EXPORT void maybeThrowOnFail();
+ Q_TESTLIB_EXPORT void maybeThrowOnSkip();
+
Q_TESTLIB_EXPORT QString formatTryTimeoutDebugMessage(q_no_char8_t::QUtf8StringView expr, int timeout, int actual);
- template<typename T> // Output registered enums
- inline typename std::enable_if<QtPrivate::IsQEnumHelper<T>::Value, char*>::type toString(T e)
+ template <ComparisonOperation> struct Compare;
+ template <> struct Compare<ComparisonOperation::Equal>
{
- QMetaEnum me = QMetaEnum::fromType<T>();
- return qstrdup(me.valueToKey(int(e))); // int cast is necessary to support enum classes
- }
-
- template <typename T>
- inline typename std::enable_if<!QtPrivate::IsQEnumHelper<T>::Value && std::is_enum_v<T>, char*>::type toString(const T &e)
+ template <typename T1, typename T2> static bool compare(T1 &&lhs, T2 &&rhs)
+ { return std::forward<T1>(lhs) == std::forward<T2>(rhs); }
+ };
+ template <> struct Compare<ComparisonOperation::NotEqual>
{
- return qstrdup(QByteArray::number(static_cast<std::underlying_type_t<T>>(e)).constData());
- }
-
- template <typename T> // Fallback; for built-in types debug streaming must be possible
- inline typename std::enable_if<!QtPrivate::IsQEnumHelper<T>::Value && !std::is_enum_v<T>, char *>::type toString(const T &t)
- {
-#ifndef QT_NO_DEBUG_STREAM
- if constexpr (QTypeTraits::has_ostream_operator_v<QDebug, T>) {
- return qstrdup(QDebug::toString(t).toUtf8().constData());
- } else {
- static_assert(!QMetaTypeId2<T>::IsBuiltIn,
- "Built-in type must implement debug streaming operator "
- "or provide QTest::toString specialization");
- }
-#endif
- return nullptr;
- }
+ template <typename T1, typename T2> static bool compare(T1 &&lhs, T2 &&rhs)
+ { return std::forward<T1>(lhs) != std::forward<T2>(rhs); }
+ };
+ template <> struct Compare<ComparisonOperation::LessThan>
+ {
+ template <typename T1, typename T2> static bool compare(T1 &&lhs, T2 &&rhs)
+ { return std::forward<T1>(lhs) < std::forward<T2>(rhs); }
+ };
+ template <> struct Compare<ComparisonOperation::LessThanOrEqual>
+ {
+ template <typename T1, typename T2> static bool compare(T1 &&lhs, T2 &&rhs)
+ { return std::forward<T1>(lhs) <= std::forward<T2>(rhs); }
+ };
+ template <> struct Compare<ComparisonOperation::GreaterThan>
+ {
+ template <typename T1, typename T2> static bool compare(T1 &&lhs, T2 &&rhs)
+ { return std::forward<T1>(lhs) > std::forward<T2>(rhs); }
+ };
+ template <> struct Compare<ComparisonOperation::GreaterThanOrEqual>
+ {
+ template <typename T1, typename T2> static bool compare(T1 &&lhs, T2 &&rhs)
+ { return std::forward<T1>(lhs) >= std::forward<T2>(rhs); }
+ };
- template<typename F> // Output QFlags of registered enumerations
- inline typename std::enable_if<QtPrivate::IsQEnumHelper<F>::Value, char*>::type toString(QFlags<F> f)
+ template <typename T1> const char *genericToString(const void *arg)
{
- const QMetaEnum me = QMetaEnum::fromType<F>();
- return qstrdup(me.valueToKeys(int(f.toInt())).constData());
+ using QTest::toString;
+ return toString(*static_cast<const T1 *>(arg));
}
- template <typename F> // Fallback: Output hex value
- inline typename std::enable_if<!QtPrivate::IsQEnumHelper<F>::Value, char*>::type toString(QFlags<F> f)
+ template <> inline const char *genericToString<char *>(const void *arg)
{
- const size_t space = 3 + 2 * sizeof(unsigned); // 2 for 0x, two hex digits per byte, 1 for '\0'
- char *msg = new char[space];
- qsnprintf(msg, space, "0x%x", unsigned(f.toInt()));
- return msg;
+ using QTest::toString;
+ return toString(static_cast<const char *>(arg));
}
- } // namespace Internal
-
- template<typename T>
- inline char *toString(const T &t)
+ template <typename T> const char *pointerToString(const void *arg)
{
- return Internal::toString(t);
+ using QTest::toString;
+ return toString(static_cast<const T *>(arg));
}
- template <typename T1, typename T2>
- inline char *toString(const QPair<T1, T2> &pair);
-
- template <typename T1, typename T2>
- inline char *toString(const std::pair<T1, T2> &pair);
-
- template <class... Types>
- inline char *toString(const std::tuple<Types...> &tuple);
+ // Exported so Qt Quick Test can also use it for generating backtraces upon crashes.
+ Q_TESTLIB_EXPORT extern bool noCrashHandler;
- Q_TESTLIB_EXPORT char *toHexRepresentation(const char *ba, qsizetype length);
- Q_TESTLIB_EXPORT char *toPrettyCString(const char *unicode, qsizetype length);
- Q_TESTLIB_EXPORT char *toPrettyUnicode(QStringView string);
- Q_TESTLIB_EXPORT char *toString(const char *);
- Q_TESTLIB_EXPORT char *toString(const volatile void *);
- Q_TESTLIB_EXPORT char *toString(const void *); // ### FIXME: Qt 7: Remove
- Q_TESTLIB_EXPORT char *toString(const volatile QObject *);
+ } // namespace Internal
Q_TESTLIB_EXPORT void qInit(QObject *testObject, int argc = 0, char **argv = nullptr);
Q_TESTLIB_EXPORT int qRun();
@@ -381,6 +385,36 @@ namespace QTest
#endif // QT_CONFIG(batch_test_support)
Q_TESTLIB_EXPORT void setMainSourcePath(const char *file, const char *builddir = nullptr);
+ Q_TESTLIB_EXPORT void setThrowOnFail(bool enable) noexcept;
+ Q_TESTLIB_EXPORT void setThrowOnSkip(bool enable) noexcept;
+
+ class ThrowOnFailEnabler {
+ Q_DISABLE_COPY_MOVE(ThrowOnFailEnabler)
+ public:
+ ThrowOnFailEnabler() { setThrowOnFail(true); }
+ ~ThrowOnFailEnabler() { setThrowOnFail(false); }
+ };
+
+ class ThrowOnSkipEnabler {
+ Q_DISABLE_COPY_MOVE(ThrowOnSkipEnabler)
+ public:
+ ThrowOnSkipEnabler() { setThrowOnSkip(true); }
+ ~ThrowOnSkipEnabler() { setThrowOnSkip(false); }
+ };
+
+ class ThrowOnFailDisabler {
+ Q_DISABLE_COPY_MOVE(ThrowOnFailDisabler)
+ public:
+ ThrowOnFailDisabler() { setThrowOnFail(false); }
+ ~ThrowOnFailDisabler() { setThrowOnFail(true); }
+ };
+
+ class ThrowOnSkipDisabler {
+ Q_DISABLE_COPY_MOVE(ThrowOnSkipDisabler)
+ public:
+ ThrowOnSkipDisabler() { setThrowOnSkip(false); }
+ ~ThrowOnSkipDisabler() { setThrowOnSkip(true); }
+ };
Q_TESTLIB_EXPORT bool qVerify(bool statement, const char *statementStr, const char *description,
const char *file, int line);
@@ -391,6 +425,8 @@ namespace QTest
const char *file, int line);
Q_DECL_COLD_FUNCTION
Q_TESTLIB_EXPORT void qCaught(const char *expected, const char *what, const char *file, int line);
+ Q_DECL_COLD_FUNCTION
+ Q_TESTLIB_EXPORT void qCaught(const char *expected, const char *file, int line);
#if QT_DEPRECATED_SINCE(6, 3)
QT_DEPRECATED_VERSION_X_6_3("Use qWarning() instead")
Q_TESTLIB_EXPORT void qWarn(const char *message, const char *file = nullptr, int line = 0);
@@ -399,6 +435,7 @@ namespace QTest
#if QT_CONFIG(regularexpression)
Q_TESTLIB_EXPORT void ignoreMessage(QtMsgType type, const QRegularExpression &messagePattern);
#endif
+ Q_TESTLIB_EXPORT void failOnWarning();
Q_TESTLIB_EXPORT void failOnWarning(const char *message);
#if QT_CONFIG(regularexpression)
Q_TESTLIB_EXPORT void failOnWarning(const QRegularExpression &messagePattern);
@@ -426,10 +463,8 @@ namespace QTest
Q_TESTLIB_EXPORT Qt::Key asciiToKey(char ascii);
Q_TESTLIB_EXPORT char keyToAscii(Qt::Key key);
- // ### TODO: remove QTestResult::compare() overload that takes char * values
- // when this overload is removed.
#if QT_DEPRECATED_SINCE(6, 4)
- QT_DEPRECATED_VERSION_X_6_4("use an overload that takes function_ref as parameters, "
+ QT_DEPRECATED_VERSION_X_6_4("use an overload that takes a formatter callback, "
"or an overload that takes only failure message, if you "
"do not need to stringify the values")
Q_TESTLIB_EXPORT bool compare_helper(bool success, const char *failureMsg,
@@ -437,11 +472,22 @@ namespace QTest
const char *actual, const char *expected,
const char *file, int line);
#endif // QT_DEPRECATED_SINCE(6, 4)
+#if QT_DEPRECATED_SINCE(6, 8)
+ QT_DEPRECATED_VERSION_X_6_8("use an overload that takes a formatter callback, "
+ "or an overload that takes only failure message, if you "
+ "do not need to stringify the values")
Q_TESTLIB_EXPORT bool compare_helper(bool success, const char *failureMsg,
qxp::function_ref<const char*()> actualVal,
qxp::function_ref<const char*()> expectedVal,
const char *actual, const char *expected,
const char *file, int line);
+#endif // QT_DEPRECATED_SINCE(6, 8)
+ Q_TESTLIB_EXPORT bool compare_helper(bool success, const char *failureMsg,
+ const void *actualPtr, const void *expectedPtr,
+ const char *(*actualFormatter)(const void *),
+ const char *(*expectedFormatter)(const void *),
+ const char *actual, const char *expected,
+ const char *file, int line);
Q_TESTLIB_EXPORT bool compare_helper(bool success, const char *failureMsg,
const char *actual, const char *expected,
const char *file, int line);
@@ -509,81 +555,71 @@ namespace QTest
inline bool compare_ptr_helper(const volatile void *t1, const volatile void *t2, const char *actual,
const char *expected, const char *file, int line)
{
+ auto formatter = Internal::pointerToString<void>;
return compare_helper(t1 == t2, "Compared pointers are not the same",
- [t1] { return toString(t1); }, [t2] { return toString(t2); },
- actual, expected, file, line);
+ const_cast<const void *>(t1), const_cast<const void *>(t2),
+ formatter, formatter, actual, expected, file, line);
}
inline bool compare_ptr_helper(const volatile QObject *t1, const volatile QObject *t2, const char *actual,
const char *expected, const char *file, int line)
{
+ auto formatter = Internal::pointerToString<QObject>;
return compare_helper(t1 == t2, "Compared QObject pointers are not the same",
- [t1] { return toString(t1); }, [t2] { return toString(t2); },
- actual, expected, file, line);
+ const_cast<const QObject *>(t1), const_cast<const QObject *>(t2),
+ formatter, formatter, actual, expected, file, line);
}
inline bool compare_ptr_helper(const volatile QObject *t1, std::nullptr_t, const char *actual,
const char *expected, const char *file, int line)
{
+ auto lhsFormatter = Internal::pointerToString<QObject>;
+ auto rhsFormatter = Internal::genericToString<std::nullptr_t>;
return compare_helper(t1 == nullptr, "Compared QObject pointers are not the same",
- [t1] { return toString(t1); }, [] { return toString(nullptr); },
- actual, expected, file, line);
+ const_cast<const QObject *>(t1), nullptr,
+ lhsFormatter, rhsFormatter, actual, expected, file, line);
}
inline bool compare_ptr_helper(std::nullptr_t, const volatile QObject *t2, const char *actual,
const char *expected, const char *file, int line)
{
+ auto lhsFormatter = Internal::genericToString<std::nullptr_t>;
+ auto rhsFormatter = Internal::pointerToString<QObject>;
return compare_helper(nullptr == t2, "Compared QObject pointers are not the same",
- [] { return toString(nullptr); }, [t2] { return toString(t2); },
- actual, expected, file, line);
+ nullptr, const_cast<const QObject *>(t2),
+ lhsFormatter, rhsFormatter, actual, expected, file, line);
}
inline bool compare_ptr_helper(const volatile void *t1, std::nullptr_t, const char *actual,
const char *expected, const char *file, int line)
{
+ auto lhsFormatter = Internal::pointerToString<void>;
+ auto rhsFormatter = Internal::genericToString<std::nullptr_t>;
return compare_helper(t1 == nullptr, "Compared pointers are not the same",
- [t1] { return toString(t1); }, [] { return toString(nullptr); },
- actual, expected, file, line);
+ const_cast<const void *>(t1), nullptr,
+ lhsFormatter, rhsFormatter, actual, expected, file, line);
}
inline bool compare_ptr_helper(std::nullptr_t, const volatile void *t2, const char *actual,
const char *expected, const char *file, int line)
{
+ auto lhsFormatter = Internal::genericToString<std::nullptr_t>;
+ auto rhsFormatter = Internal::pointerToString<void>;
return compare_helper(nullptr == t2, "Compared pointers are not the same",
- [] { return toString(nullptr); }, [t2] { return toString(t2); },
- actual, expected, file, line);
+ nullptr, const_cast<const void *>(t2),
+ lhsFormatter, rhsFormatter, actual, expected, file, line);
}
- Q_TESTLIB_EXPORT bool compare_string_helper(const char *t1, const char *t2, const char *actual,
- const char *expected, const char *file, int line);
-
- Q_TESTLIB_EXPORT char *formatString(const char *prefix, const char *suffix, size_t numArguments, ...);
-
-#ifndef Q_QDOC
- QTEST_COMPARE_DECL(short)
- QTEST_COMPARE_DECL(ushort)
- QTEST_COMPARE_DECL(int)
- QTEST_COMPARE_DECL(uint)
- QTEST_COMPARE_DECL(long)
- QTEST_COMPARE_DECL(ulong)
- QTEST_COMPARE_DECL(qint64)
- QTEST_COMPARE_DECL(quint64)
-
- QTEST_COMPARE_DECL(float)
- QTEST_COMPARE_DECL(double)
- QTEST_COMPARE_DECL(qfloat16)
- QTEST_COMPARE_DECL(char)
- QTEST_COMPARE_DECL(signed char)
- QTEST_COMPARE_DECL(unsigned char)
- QTEST_COMPARE_DECL(bool)
-#endif
-
- template <typename T1, typename T2>
+ template <typename T1, typename T2 = T1>
inline bool qCompare(const T1 &t1, const T2 &t2, const char *actual, const char *expected,
const char *file, int line)
{
+ using D1 = std::decay_t<T1>;
+ using D2 = std::decay_t<T2>;
+ using Internal::genericToString;
return compare_helper(t1 == t2, "Compared values are not the same",
- [&t1] { return toString(t1); }, [&t2] { return toString(t2); },
+ std::addressof(t1), std::addressof(t2),
+ genericToString<D1>, genericToString<D2>,
actual, expected, file, line);
}
@@ -671,13 +707,38 @@ namespace QTest
qMetaTypeId<T>())), actualStr, expected, file, line);
}
+#if QT_DEPRECATED_SINCE(6, 8)
+ QT_DEPRECATED_VERSION_X_6_8("use the overload without qxp::function_ref")
Q_TESTLIB_EXPORT bool reportResult(bool success, qxp::function_ref<const char*()> lhs,
qxp::function_ref<const char*()> rhs,
const char *lhsExpr, const char *rhsExpr,
ComparisonOperation op, const char *file, int line);
+#endif // QT_DEPRECATED_SINCE(6, 8)
+
+ Q_TESTLIB_EXPORT bool reportResult(bool success, const void *lhs, const void *rhs,
+ const char *(*lhsFormatter)(const void*),
+ const char *(*rhsFormatter)(const void*),
+ const char *lhsExpr, const char *rhsExpr,
+ ComparisonOperation op, const char *file, int line);
+
+ template <ComparisonOperation op, typename T1, typename T2 = T1>
+ inline bool qCompareOp(T1 &&lhs, T2 &&rhs, const char *lhsExpr, const char *rhsExpr,
+ const char *file, int line)
+ {
+ using D1 = std::decay_t<T1>;
+ using D2 = std::decay_t<T2>;
+ using Internal::genericToString;
+ using Comparator = Internal::Compare<op>;
+
+ /* assumes that op does not actually move from lhs and rhs */
+ bool result = Comparator::compare(std::forward<T1>(lhs), std::forward<T2>(rhs));
+ return reportResult(result, std::addressof(lhs), std::addressof(rhs),
+ genericToString<D1>, genericToString<D2>,
+ lhsExpr, rhsExpr, op, file, line);
+
+ }
}
-#undef QTEST_COMPARE_DECL
#define QWARN(msg) QTest::qWarn(static_cast<const char *>(msg), __FILE__, __LINE__)
diff --git a/src/testlib/qtestcase.qdoc b/src/testlib/qtestcase.qdoc
index e2181c816b..6a067c351f 100644
--- a/src/testlib/qtestcase.qdoc
+++ b/src/testlib/qtestcase.qdoc
@@ -103,7 +103,7 @@
\snippet code/src_qtestlib_qtestcase.cpp 2
When comparing floating-point types (\c float, \c double, and \c qfloat16),
- \l {qFuzzyCompare()} is used for finite values. If \l {<QtGlobal>::}{qFuzzyIsNull()}
+ \l {qFuzzyCompare()} is used for finite values. If \l {<QtNumeric>::}{qFuzzyIsNull()}
is true for both values, they are also considered equal. Infinities
match if they have the same sign, and any NaN as actual value matches
with any NaN as expected value (even though NaN != NaN, even when
@@ -137,18 +137,18 @@
QCOMPARE_GT(), QCOMPARE_GE()
*/
-/*! \macro QCOMPARE_EQ(left, right)
+/*! \macro QCOMPARE_EQ(computed, baseline)
\since 6.4
\relates QTest
- The QCOMPARE_EQ() macro checks that \a left is equal to \a right using
+ The QCOMPARE_EQ() macro checks that \a computed is equal to \a baseline using
the equality operator. If that is true, execution continues. If not, a
failure is recorded in the test log and the test function returns without
attempting any later checks.
- It is generally similar to calling \c {QVERIFY(left == right);}
- but prints a formatted error message reporting \a left and \a right argument
+ It is generally similar to calling \c {QVERIFY(computed == baseline);}
+ but prints a formatted error message reporting \a computed and \a baseline argument
expressions and values in case of failure.
\include qtestcase.qdoc macro-usage-limitation
@@ -164,18 +164,18 @@
QCOMPARE_GE()
*/
-/*! \macro QCOMPARE_NE(left, right)
+/*! \macro QCOMPARE_NE(computed, baseline)
\since 6.4
\relates QTest
- The QCOMPARE_NE() macro checks that \a left is not equal to \a right using
+ The QCOMPARE_NE() macro checks that \a computed is not equal to \a baseline using
the inequality operator. If that is true, execution continues. If not, a
failure is recorded in the test log and the test function returns without
attempting any later checks.
- It is generally similar to calling \c {QVERIFY(left != right);}
- but prints a formatted error message reporting \a left and \a right argument
+ It is generally similar to calling \c {QVERIFY(computed != baseline);}
+ but prints a formatted error message reporting \a computed and \a baseline argument
expressions and values in case of failure.
\include qtestcase.qdoc macro-usage-limitation
@@ -185,18 +185,18 @@
\sa QCOMPARE_EQ(), QCOMPARE_LT(), QCOMPARE_LE(), QCOMPARE_GT(), QCOMPARE_GE()
*/
-/*! \macro QCOMPARE_LT(left, right)
+/*! \macro QCOMPARE_LT(computed, baseline)
\since 6.4
\relates QTest
- The QCOMPARE_LT() macro checks that \a left is less than \a right using the
+ The QCOMPARE_LT() macro checks that \a computed is less than \a baseline using the
less-than operator. If that is true, execution continues. If not, a failure
is recorded in the test log and the test function returns without attempting
any later checks.
- It is generally similar to calling \c {QVERIFY(left < right);}
- but prints a formatted error message reporting \a left and \a right argument
+ It is generally similar to calling \c {QVERIFY(computed < baseline);}
+ but prints a formatted error message reporting \a computed and \a baseline argument
expressions and values in case of failure.
\include qtestcase.qdoc macro-usage-limitation
@@ -206,18 +206,18 @@
\sa QCOMPARE_EQ(), QCOMPARE_NE(), QCOMPARE_LE(), QCOMPARE_GT(), QCOMPARE_GE()
*/
-/*! \macro QCOMPARE_LE(left, right)
+/*! \macro QCOMPARE_LE(computed, baseline)
\since 6.4
\relates QTest
- The QCOMPARE_LE() macro checks that \a left is at most \a right using the
+ The QCOMPARE_LE() macro checks that \a computed is at most \a baseline using the
less-than-or-equal-to operator. If that is true, execution continues. If
not, a failure is recorded in the test log and the test function returns
without attempting any later checks.
- It is generally similar to calling \c {QVERIFY(left <= right);}
- but prints a formatted error message reporting \a left and \a right argument
+ It is generally similar to calling \c {QVERIFY(computed <= baseline);}
+ but prints a formatted error message reporting \a computed and \a baseline argument
expressions and values in case of failure.
\include qtestcase.qdoc macro-usage-limitation
@@ -227,18 +227,18 @@
\sa QCOMPARE_EQ(), QCOMPARE_NE(), QCOMPARE_LT(), QCOMPARE_GT(), QCOMPARE_GE()
*/
-/*! \macro QCOMPARE_GT(left, right)
+/*! \macro QCOMPARE_GT(computed, baseline)
\since 6.4
\relates QTest
- The QCOMPARE_GT() macro checks that \a left is greater than \a right using
+ The QCOMPARE_GT() macro checks that \a computed is greater than \a baseline using
the greater-than operator. If that is true, execution continues. If not, a
failure is recorded in the test log and the test function returns without
attempting any later checks.
- It is generally similar to calling \c {QVERIFY(left > right);}
- but prints a formatted error message reporting \a left and \a right argument
+ It is generally similar to calling \c {QVERIFY(computed > baseline);}
+ but prints a formatted error message reporting \a computed and \a baseline argument
expressions and values in case of failure.
\include qtestcase.qdoc macro-usage-limitation
@@ -248,18 +248,18 @@
\sa QCOMPARE_EQ(), QCOMPARE_NE(), QCOMPARE_LT(), QCOMPARE_LE(), QCOMPARE_GE()
*/
-/*! \macro QCOMPARE_GE(left, right)
+/*! \macro QCOMPARE_GE(computed, baseline)
\since 6.4
\relates QTest
- The QCOMPARE_GE() macro checks that \a left is at least \a right using the
+ The QCOMPARE_GE() macro checks that \a computed is at least \a baseline using the
greater-than-or-equal-to operator. If that is true, execution continues. If
not, a failure is recorded in the test log and the test function returns
without attempting any later checks.
- It is generally similar to calling \c {QVERIFY(left >= right);}
- but prints a formatted error message reporting \a left and \a right argument
+ It is generally similar to calling \c {QVERIFY(computed >= baseline);}
+ but prints a formatted error message reporting \a computed and \a baseline argument
expressions and values in case of failure.
\include qtestcase.qdoc macro-usage-limitation
@@ -359,6 +359,10 @@
is reached, a failure is recorded in the test log and the test won't be
executed further.
+ //![chrono-timeout]
+ Since Qt 6.8, the \a timeout can also be a \c{std::chrono} literal such as \c{2s}.
+ //![chrono-timeout]
+
\note This macro can only be used in a test function that is invoked
by the test framework.
@@ -390,9 +394,11 @@
except that it outputs a verbose \a message when \a condition is still false
after the specified \a timeout (in milliseconds). The \a message is a plain C string.
+ \include qtestcase.qdoc chrono-timeout
+
Example:
\code
- QTRY_VERIFY2_WITH_TIMEOUT(list.size() > 2, QByteArray::number(list.size()).constData(), 10000);
+ QTRY_VERIFY2_WITH_TIMEOUT(list.size() > 2, QByteArray::number(list.size()).constData(), 10s);
\endcode
\note This macro can only be used in a test function that is invoked
@@ -413,7 +419,7 @@
Example:
\code
- QTRY_VERIFY2_WITH_TIMEOUT(list.size() > 2, QByteArray::number(list.size()).constData());
+ QTRY_VERIFY2(list.size() > 2, QByteArray::number(list.size()).constData());
\endcode
\note This macro can only be used in a test function that is invoked
@@ -434,6 +440,8 @@
will be processed. If the timeout is reached, a failure is recorded in the
test log and the test won't be executed further.
+ \include qtestcase.qdoc chrono-timeout
+
\note This macro can only be used in a test function that is invoked
by the test framework.
@@ -455,26 +463,28 @@
QEXPECT_FAIL()
*/
-/*! \macro QTRY_COMPARE_EQ_WITH_TIMEOUT(left, right, timeout)
+/*! \macro QTRY_COMPARE_EQ_WITH_TIMEOUT(computed, baseline, timeout)
\since 6.4
\relates QTest
This macro is similar to QCOMPARE_EQ(), but performs the comparison of the
- \a left and \a right values repeatedly, until either the comparison returns
+ \a computed and \a baseline values repeatedly, until either the comparison returns
\c true or the \a timeout (in milliseconds) is reached. Between each
comparison, events will be processed. If the timeout is reached, a failure
is recorded in the test log and the test won't be executed further.
+ \include qtestcase.qdoc chrono-timeout
+
\include qtestcase.qdoc macro-usage-limitation
\sa QCOMPARE_EQ(), QTRY_COMPARE_EQ()
*/
-/*! \macro QTRY_COMPARE_EQ(left, right)
+/*! \macro QTRY_COMPARE_EQ(computed, baseline)
\since 6.4
\relates QTest
- Performs comparison of \a left and \a right values by invoking
+ Performs comparison of \a computed and \a baseline values by invoking
QTRY_COMPARE_EQ_WITH_TIMEOUT with a timeout of five seconds.
\include qtestcase.qdoc macro-usage-limitation
@@ -482,26 +492,28 @@
\sa QCOMPARE_EQ(), QTRY_COMPARE_EQ_WITH_TIMEOUT()
*/
-/*! \macro QTRY_COMPARE_NE_WITH_TIMEOUT(left, right, timeout)
+/*! \macro QTRY_COMPARE_NE_WITH_TIMEOUT(computed, baseline, timeout)
\since 6.4
\relates QTest
This macro is similar to QCOMPARE_NE(), but performs the comparison of the
- \a left and \a right values repeatedly, until either the comparison returns
+ \a computed and \a baseline values repeatedly, until either the comparison returns
\c true or the \a timeout (in milliseconds) is reached. Between each
comparison, events will be processed. If the timeout is reached, a failure
is recorded in the test log and the test won't be executed further.
+ \include qtestcase.qdoc chrono-timeout
+
\include qtestcase.qdoc macro-usage-limitation
\sa QCOMPARE_NE(), QTRY_COMPARE_NE()
*/
-/*! \macro QTRY_COMPARE_NE(left, right)
+/*! \macro QTRY_COMPARE_NE(computed, baseline)
\since 6.4
\relates QTest
- Performs comparison of \a left and \a right values by invoking
+ Performs comparison of \a computed and \a baseline values by invoking
QTRY_COMPARE_NE_WITH_TIMEOUT with a timeout of five seconds.
\include qtestcase.qdoc macro-usage-limitation
@@ -509,26 +521,28 @@
\sa QCOMPARE_NE(), QTRY_COMPARE_NE_WITH_TIMEOUT()
*/
-/*! \macro QTRY_COMPARE_LT_WITH_TIMEOUT(left, right, timeout)
+/*! \macro QTRY_COMPARE_LT_WITH_TIMEOUT(computed, baseline, timeout)
\since 6.4
\relates QTest
This macro is similar to QCOMPARE_LT(), but performs the comparison of the
- \a left and \a right values repeatedly, until either the comparison returns
+ \a computed and \a baseline values repeatedly, until either the comparison returns
\c true or the \a timeout (in milliseconds) is reached. Between each
comparison, events will be processed. If the timeout is reached, a failure
is recorded in the test log and the test won't be executed further.
+ \include qtestcase.qdoc chrono-timeout
+
\include qtestcase.qdoc macro-usage-limitation
\sa QCOMPARE_LT(), QTRY_COMPARE_LT()
*/
-/*! \macro QTRY_COMPARE_LT(left, right)
+/*! \macro QTRY_COMPARE_LT(computed, baseline)
\since 6.4
\relates QTest
- Performs comparison of \a left and \a right values by invoking
+ Performs comparison of \a computed and \a baseline values by invoking
QTRY_COMPARE_LT_WITH_TIMEOUT with a timeout of five seconds.
\include qtestcase.qdoc macro-usage-limitation
@@ -536,26 +550,28 @@
\sa QCOMPARE_LT(), QTRY_COMPARE_LT_WITH_TIMEOUT()
*/
-/*! \macro QTRY_COMPARE_LE_WITH_TIMEOUT(left, right, timeout)
+/*! \macro QTRY_COMPARE_LE_WITH_TIMEOUT(computed, baseline, timeout)
\since 6.4
\relates QTest
This macro is similar to QCOMPARE_LE(), but performs the comparison of the
- \a left and \a right values repeatedly, until either the comparison returns
+ \a computed and \a baseline values repeatedly, until either the comparison returns
\c true or the \a timeout (in milliseconds) is reached. Between each
comparison, events will be processed. If the timeout is reached, a failure
is recorded in the test log and the test won't be executed further.
+ \include qtestcase.qdoc chrono-timeout
+
\include qtestcase.qdoc macro-usage-limitation
\sa QCOMPARE_LE(), QTRY_COMPARE_LE()
*/
-/*! \macro QTRY_COMPARE_LE(left, right)
+/*! \macro QTRY_COMPARE_LE(computed, baseline)
\since 6.4
\relates QTest
- Performs comparison of \a left and \a right values by invoking
+ Performs comparison of \a computed and \a baseline values by invoking
QTRY_COMPARE_LE_WITH_TIMEOUT with a timeout of five seconds.
\include qtestcase.qdoc macro-usage-limitation
@@ -563,26 +579,28 @@
\sa QCOMPARE_LE(), QTRY_COMPARE_LE_WITH_TIMEOUT()
*/
-/*! \macro QTRY_COMPARE_GT_WITH_TIMEOUT(left, right, timeout)
+/*! \macro QTRY_COMPARE_GT_WITH_TIMEOUT(computed, baseline, timeout)
\since 6.4
\relates QTest
This macro is similar to QCOMPARE_GT(), but performs the comparison of the
- \a left and \a right values repeatedly, until either the comparison returns
+ \a computed and \a baseline values repeatedly, until either the comparison returns
\c true or the \a timeout (in milliseconds) is reached. Between each
comparison, events will be processed. If the timeout is reached, a failure
is recorded in the test log and the test won't be executed further.
+ \include qtestcase.qdoc chrono-timeout
+
\include qtestcase.qdoc macro-usage-limitation
\sa QCOMPARE_GT(), QTRY_COMPARE_GT()
*/
-/*! \macro QTRY_COMPARE_GT(left, right)
+/*! \macro QTRY_COMPARE_GT(computed, baseline)
\since 6.4
\relates QTest
- Performs comparison of \a left and \a right values by invoking
+ Performs comparison of \a computed and \a baseline values by invoking
QTRY_COMPARE_GT_WITH_TIMEOUT with a timeout of five seconds.
\include qtestcase.qdoc macro-usage-limitation
@@ -590,26 +608,28 @@
\sa QCOMPARE_GT(), QTRY_COMPARE_GT_WITH_TIMEOUT()
*/
-/*! \macro QTRY_COMPARE_GE_WITH_TIMEOUT(left, right, timeout)
+/*! \macro QTRY_COMPARE_GE_WITH_TIMEOUT(computed, baseline, timeout)
\since 6.4
\relates QTest
This macro is similar to QCOMPARE_GE(), but performs the comparison of the
- \a left and \a right values repeatedly, until either the comparison returns
+ \a computed and \a baseline values repeatedly, until either the comparison returns
\c true or the \a timeout (in milliseconds) is reached. Between each
comparison, events will be processed. If the timeout is reached, a failure
is recorded in the test log and the test won't be executed further.
+ \include qtestcase.qdoc chrono-timeout
+
\include qtestcase.qdoc macro-usage-limitation
\sa QCOMPARE_GE(), QTRY_COMPARE_GE()
*/
-/*! \macro QTRY_COMPARE_GE(left, right)
+/*! \macro QTRY_COMPARE_GE(computed, baseline)
\since 6.4
\relates QTest
- Performs comparison of \a left and \a right values by invoking
+ Performs comparison of \a computed and \a baseline values by invoking
QTRY_COMPARE_GE_WITH_TIMEOUT with a timeout of five seconds.
\include qtestcase.qdoc macro-usage-limitation
@@ -782,7 +802,8 @@
failure will be reported.
If a \l QVERIFY() or \l QCOMPARE() is marked as an expected failure,
- but passes instead, an unexpected pass (XPASS) is written to the test log.
+ but passes instead, an unexpected pass (XPASS) is written to the test log
+ and will be counted as a test failure.
The parameter \a dataIndex describes for which entry in the test data the
failure is expected. Pass an empty string (\c{""}) if the failure
@@ -961,8 +982,8 @@
this macro.
Unlike QBENCHMARK, the contents of the contained code block is only run
- once. The elapsed time will be reported as "0" if it's to short to
- be measured by the selected backend. (Use)
+ once. The elapsed time will be reported as "0" if it's too short to
+ be measured by the selected backend.
\sa {Qt Test Overview#Creating a Benchmark}{Creating a Benchmark},
{Chapter 5: Writing a Benchmark}{Writing a Benchmark}
@@ -1230,7 +1251,7 @@
\sa QTest::keyClick()
*/
-/*! \fn void QTest::mousePress(QWidget *widget, Qt::MouseButton button, Qt::KeyboardModifiers modifier = 0, QPoint pos = QPoint(), int delay=-1)
+/*! \fn void QTest::mousePress(QWidget *widget, Qt::MouseButton button, Qt::KeyboardModifiers modifier, QPoint pos = QPoint(), int delay=-1)
Simulates pressing a mouse \a button with an optional \a modifier
on a \a widget. The position is defined by \a pos; the default
@@ -1241,7 +1262,7 @@
\sa QTest::mouseRelease(), QTest::mouseClick()
*/
-/*! \fn void QTest::mousePress(QWindow *window, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = 0, QPoint pos = QPoint(), int delay=-1)
+/*! \fn void QTest::mousePress(QWindow *window, Qt::MouseButton button, Qt::KeyboardModifiers stateKey, QPoint pos = QPoint(), int delay=-1)
\overload
\since 5.0
@@ -1254,7 +1275,7 @@
\sa QTest::mouseRelease(), QTest::mouseClick()
*/
-/*! \fn void QTest::mouseRelease(QWidget *widget, Qt::MouseButton button, Qt::KeyboardModifiers modifier = 0, QPoint pos = QPoint(), int delay=-1)
+/*! \fn void QTest::mouseRelease(QWidget *widget, Qt::MouseButton button, Qt::KeyboardModifiers modifier, QPoint pos = QPoint(), int delay=-1)
Simulates releasing a mouse \a button with an optional \a modifier
on a \a widget. The position of the release is defined by \a pos;
@@ -1274,7 +1295,7 @@
\sa QTest::mousePress(), QTest::mouseClick()
*/
-/*! \fn void QTest::mouseRelease(QWindow *window, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = 0, QPoint pos = QPoint(), int delay=-1)
+/*! \fn void QTest::mouseRelease(QWindow *window, Qt::MouseButton button, Qt::KeyboardModifiers stateKey, QPoint pos = QPoint(), int delay=-1)
\overload
\since 5.0
@@ -1296,7 +1317,7 @@
\sa QTest::mousePress(), QTest::mouseClick()
*/
-/*! \fn void QTest::mouseClick(QWidget *widget, Qt::MouseButton button, Qt::KeyboardModifiers modifier = 0, QPoint pos = QPoint(), int delay=-1)
+/*! \fn void QTest::mouseClick(QWidget *widget, Qt::MouseButton button, Qt::KeyboardModifiers modifier, QPoint pos = QPoint(), int delay=-1)
Simulates clicking a mouse \a button with an optional \a modifier
on a \a widget. The position of the click is defined by \a pos;
@@ -1307,7 +1328,7 @@
\sa QTest::mousePress(), QTest::mouseRelease()
*/
-/*! \fn void QTest::mouseClick(QWindow *window, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = 0, QPoint pos = QPoint(), int delay=-1)
+/*! \fn void QTest::mouseClick(QWindow *window, Qt::MouseButton button, Qt::KeyboardModifiers stateKey, QPoint pos = QPoint(), int delay=-1)
\overload
\since 5.0
@@ -1320,7 +1341,7 @@
\sa QTest::mousePress(), QTest::mouseRelease()
*/
-/*! \fn void QTest::mouseDClick(QWidget *widget, Qt::MouseButton button, Qt::KeyboardModifiers modifier = 0, QPoint pos = QPoint(), int delay=-1)
+/*! \fn void QTest::mouseDClick(QWidget *widget, Qt::MouseButton button, Qt::KeyboardModifiers modifier, QPoint pos = QPoint(), int delay=-1)
Simulates double clicking a mouse \a button with an optional \a
modifier on a \a widget. The position of the click is defined by
@@ -1331,7 +1352,7 @@
\sa QTest::mouseClick()
*/
-/*! \fn void QTest::mouseDClick(QWindow *window, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = 0, QPoint pos = QPoint(), int delay=-1)
+/*! \fn void QTest::mouseDClick(QWindow *window, Qt::MouseButton button, Qt::KeyboardModifiers stateKey, QPoint pos = QPoint(), int delay=-1)
\overload
\since 5.0
@@ -1362,11 +1383,15 @@
moving the mouse pointer.
*/
-/*!
- \fn template <typename T1, typename T2> char *QTest::toString(const QPair<T1, T2> &pair)
- \overload
- \since 5.11
- Returns a textual representation of the \a pair.
+/*! \fn void QTest::wheelEvent(QWindow *window, QPointF pos, QPoint angleDelta, QPoint pixelDelta = QPoint(0, 0), Qt::KeyboardModifiers stateKey = Qt::NoModifier, Qt::ScrollPhase phase = Qt::NoScrollPhase)
+ \since 6.8
+
+ Simulates a wheel event within \a window at position \a pos in local
+ window coordinates. \a angleDelta contains the wheel rotation angle.
+ A positive value means forward rotation, and a negative one means backward.
+ \a pixelDelta contains the scrolling distance in pixels on screen. This value can be null.
+ The keyboard states at the time of the event are specified by \a stateKey.
+ The scrolling phase of the event is specified by \a phase.
*/
/*!
@@ -1398,7 +1423,7 @@
*/
/*!
- \fn template<typename T> char *QTest::toString(const T &value)
+ \fn template<typename T, QTest::Internal::is_suitable_type_v<T> = true> char *QTest::toString(const T &value)
Returns a textual representation of \a value. This function is used by
\l QCOMPARE() to output verbose information in case of a test failure.
@@ -1583,7 +1608,8 @@
*/
/*!
- \fn char *QTest::toString(QSizePolicy::ControlType ct)
+ \fn char *toString(QSizePolicy::ControlType ct)
+ \relates QTest
\overload
\since 5.5
@@ -1591,7 +1617,8 @@
*/
/*!
- \fn char *QTest::toString(QSizePolicy::ControlTypes cts)
+ \fn char *toString(QSizePolicy::ControlTypes cts)
+ \relates QTest
\overload
\since 5.5
@@ -1599,7 +1626,8 @@
*/
/*!
- \fn char *QTest::toString(QSizePolicy::Policy p)
+ \fn char *toString(QSizePolicy::Policy p)
+ \relates QTest
\overload
\since 5.5
@@ -1607,7 +1635,8 @@
*/
/*!
- \fn char *QTest::toString(QSizePolicy sp)
+ \fn char *toString(QSizePolicy sp)
+ \relates QTest
\overload
\since 5.5
@@ -1622,22 +1651,16 @@
*/
/*!
- \fn template <typename Tuple, int... I> char *QTest::toString(const Tuple &tuple, QtPrivate::IndexesList<I...> )
- \internal
- \since 5.12
-*/
-
-/*!
\fn QPointingDevice * QTest::createTouchDevice(QInputDevice::DeviceType devType = QInputDevice::DeviceType::TouchScreen, QInputDevice::Capabilities caps = QInputDevice::Capability::Position)
\since 5.8
Creates a dummy touch device of type \a devType with capabilities \a caps for
simulation of touch events.
- The touch device will be registered with the QPA window system interface,
- and deleted automatically when the QCoreApplication is deleted. So you
- should typically use createTouchDevice() to initialize a QPointingDevice
- member variable in your test case class, and use the same instance for all tests.
+ The touch device will be registered with the Qt window system interface.
+ You should typically use createTouchDevice() to initialize a QPointingDevice
+ member variable in your test case class, use the same instance for all tests and
+ delete it when no longer needed.
\sa QTest::QTouchEventSequence, touchEvent()
*/
diff --git a/src/testlib/qtestcase_p.h b/src/testlib/qtestcase_p.h
index 91a5314f97..ef3e083f88 100644
--- a/src/testlib/qtestcase_p.h
+++ b/src/testlib/qtestcase_p.h
@@ -16,7 +16,6 @@
//
#include <QtTest/qtestcase.h>
-#include <QtTest/private/qttestexports_p.h>
#include <QtTest/qttestglobal.h>
#include <QtCore/qstring.h>
@@ -26,8 +25,8 @@ QT_BEGIN_NAMESPACE
namespace QTest {
#if QT_CONFIG(batch_test_support)
- Q_TESTLIB_PRIVATE_EXPORT QList<QString> qGetTestCaseNames();
- Q_TESTLIB_PRIVATE_EXPORT TestEntryFunction qGetTestCaseEntryFunction(const QString &name);
+ Q_TESTLIB_EXPORT QList<QString> qGetTestCaseNames();
+ Q_TESTLIB_EXPORT TestEntryFunction qGetTestCaseEntryFunction(const QString &name);
#endif // QT_CONFIG(batch_test_support)
} // namespace QTest
diff --git a/src/testlib/qtestcrashhandler.cpp b/src/testlib/qtestcrashhandler.cpp
new file mode 100644
index 0000000000..aabac1c466
--- /dev/null
+++ b/src/testlib/qtestcrashhandler.cpp
@@ -0,0 +1,663 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// Copyright (C) 2024 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtTest/qtestcase.h>
+#include <QtTest/private/qtestcrashhandler_p.h>
+#include <QtTest/qtestassert.h>
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qdiriterator.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qfloat16.h>
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qtemporarydir.h>
+#include <QtCore/qthread.h>
+#include <QtCore/qvarlengtharray.h>
+#include <QtCore/private/qlocking_p.h>
+#include <QtCore/private/qtools_p.h>
+#include <QtCore/private/qwaitcondition_p.h>
+
+#include <QtCore/qtestsupport_core.h>
+
+#include <QtTest/private/qtestlog_p.h>
+#include <QtTest/private/qtesttable_p.h>
+#include <QtTest/qtestdata.h>
+#include <QtTest/private/qtestresult_p.h>
+#include <QtTest/private/qsignaldumper_p.h>
+#include <QtTest/private/qbenchmark_p.h>
+#if QT_CONFIG(batch_test_support)
+#include <QtTest/private/qtestregistry_p.h>
+#endif // QT_CONFIG(batch_test_support)
+#include <QtTest/private/cycle_p.h>
+#include <QtTest/private/qtestblacklist_p.h>
+#if defined(HAVE_XCTEST)
+#include <QtTest/private/qxctestlogger_p.h>
+#endif
+#if defined Q_OS_MACOS
+#include <QtTest/private/qtestutil_macos_p.h>
+#endif
+
+#if defined(Q_OS_DARWIN)
+#include <QtTest/private/qappletestlogger_p.h>
+#endif
+
+#if !defined(Q_OS_INTEGRITY) || __GHS_VERSION_NUMBER > 202014
+# include <charconv>
+#else
+// Broken implementation, causes link failures just by #include'ing!
+# undef __cpp_lib_to_chars // in case <version> was included
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(Q_OS_LINUX)
+#include <sys/types.h>
+#include <fcntl.h>
+#endif
+
+#ifdef Q_OS_UNIX
+#include <QtCore/private/qcore_unix_p.h>
+
+#include <errno.h>
+#if __has_include(<paths.h>)
+# include <paths.h>
+#endif
+#include <signal.h>
+#include <time.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <unistd.h>
+# if !defined(Q_OS_INTEGRITY)
+# include <sys/resource.h>
+# endif
+# ifndef _PATH_DEFPATH
+# define _PATH_DEFPATH "/usr/bin:/bin"
+# endif
+# ifndef SIGSTKSZ
+# define SIGSTKSZ 0 /* we have code to set the minimum */
+# endif
+# ifndef SA_RESETHAND
+# define SA_RESETHAND 0
+# endif
+#endif
+
+#if defined(Q_OS_MACOS)
+#include <IOKit/pwr_mgt/IOPMLib.h>
+#include <mach/task.h>
+#include <mach/mach_init.h>
+#include <CoreFoundation/CFPreferences.h>
+#endif
+
+#if defined(Q_OS_WASM)
+#include <emscripten.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+namespace QTest {
+namespace CrashHandler {
+#if defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
+struct iovec IoVec(struct iovec vec)
+{
+ return vec;
+}
+struct iovec IoVec(const char *str)
+{
+ struct iovec r = {};
+ r.iov_base = const_cast<char *>(str);
+ r.iov_len = strlen(str);
+ return r;
+}
+
+struct iovec asyncSafeToString(int n, AsyncSafeIntBuffer &&result)
+{
+ char *ptr = result.array.data();
+ if (false) {
+#ifdef __cpp_lib_to_chars
+ } else if (auto r = std::to_chars(ptr, ptr + result.array.size(), n, 10); r.ec == std::errc{}) {
+ ptr = r.ptr;
+#endif
+ } else {
+ // handle the sign
+ if (n < 0) {
+ *ptr++ = '-';
+ n = -n;
+ }
+
+ // find the highest power of the base that is less than this number
+ static constexpr int StartingDivider = ([]() {
+ int divider = 1;
+ for (int i = 0; i < std::numeric_limits<int>::digits10; ++i)
+ divider *= 10;
+ return divider;
+ }());
+ int divider = StartingDivider;
+ while (divider && n < divider)
+ divider /= 10;
+
+ // now convert to string
+ while (divider > 1) {
+ int quot = n / divider;
+ n = n % divider;
+ divider /= 10;
+ *ptr++ = quot + '0';
+ }
+ *ptr++ = n + '0';
+ }
+
+#ifndef QT_NO_DEBUG
+ // this isn't necessary, it just helps in the debugger
+ *ptr = '\0';
+#endif
+ struct iovec r;
+ r.iov_base = result.array.data();
+ r.iov_len = ptr - result.array.data();
+ return r;
+};
+#endif // defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
+
+bool alreadyDebugging()
+{
+#if defined(Q_OS_LINUX)
+ int fd = open("/proc/self/status", O_RDONLY);
+ if (fd == -1)
+ return false;
+ char buffer[2048];
+ ssize_t size = read(fd, buffer, sizeof(buffer) - 1);
+ if (size == -1) {
+ close(fd);
+ return false;
+ }
+ buffer[size] = 0;
+ const char tracerPidToken[] = "\nTracerPid:";
+ char *tracerPid = strstr(buffer, tracerPidToken);
+ if (!tracerPid) {
+ close(fd);
+ return false;
+ }
+ tracerPid += sizeof(tracerPidToken);
+ long int pid = strtol(tracerPid, &tracerPid, 10);
+ close(fd);
+ return pid != 0;
+#elif defined(Q_OS_WIN)
+ return IsDebuggerPresent();
+#elif defined(Q_OS_MACOS)
+ // Check if there is an exception handler for the process:
+ mach_msg_type_number_t portCount = 0;
+ exception_mask_t masks[EXC_TYPES_COUNT];
+ mach_port_t ports[EXC_TYPES_COUNT];
+ exception_behavior_t behaviors[EXC_TYPES_COUNT];
+ thread_state_flavor_t flavors[EXC_TYPES_COUNT];
+ exception_mask_t mask = EXC_MASK_ALL & ~(EXC_MASK_RESOURCE | EXC_MASK_GUARD);
+ kern_return_t result = task_get_exception_ports(mach_task_self(), mask, masks, &portCount,
+ ports, behaviors, flavors);
+ if (result == KERN_SUCCESS) {
+ for (mach_msg_type_number_t portIndex = 0; portIndex < portCount; ++portIndex) {
+ if (MACH_PORT_VALID(ports[portIndex])) {
+ return true;
+ }
+ }
+ }
+ return false;
+#else
+ // TODO
+ return false;
+#endif
+}
+
+namespace {
+enum DebuggerProgram { None, Gdb, Lldb };
+static bool hasSystemCrashReporter()
+{
+#if defined(Q_OS_MACOS)
+ return QTestPrivate::macCrashReporterWillShowDialog();
+#else
+ return false;
+#endif
+}
+} // unnamed namespaced
+
+void maybeDisableCoreDump()
+{
+#ifdef RLIMIT_CORE
+ bool ok = false;
+ const int disableCoreDump = qEnvironmentVariableIntValue("QTEST_DISABLE_CORE_DUMP", &ok);
+ if (ok && disableCoreDump) {
+ struct rlimit limit;
+ limit.rlim_cur = 0;
+ limit.rlim_max = 0;
+ if (setrlimit(RLIMIT_CORE, &limit) != 0)
+ qWarning("Failed to disable core dumps: %d", errno);
+ }
+#endif
+}
+
+static DebuggerProgram debugger = None;
+void prepareStackTrace()
+{
+
+ bool ok = false;
+ const int disableStackDump = qEnvironmentVariableIntValue("QTEST_DISABLE_STACK_DUMP", &ok);
+ if (ok && disableStackDump)
+ return;
+
+ if (hasSystemCrashReporter())
+ return;
+
+#if defined(Q_OS_MACOS)
+ #define CSR_ALLOW_UNRESTRICTED_FS (1 << 1)
+ std::optional<uint32_t> sipConfiguration = qt_mac_sipConfiguration();
+ if (!sipConfiguration || !(*sipConfiguration & CSR_ALLOW_UNRESTRICTED_FS))
+ return; // LLDB will fail to provide a valid stack trace
+#endif
+
+#ifdef Q_OS_UNIX
+ // like QStandardPaths::findExecutable(), but simpler
+ auto hasExecutable = [](const char *execname) {
+ std::string candidate;
+ std::string path;
+ if (const char *p = getenv("PATH"); p && *p)
+ path = p;
+ else
+ path = _PATH_DEFPATH;
+ for (const char *p = std::strtok(&path[0], ":'"); p; p = std::strtok(nullptr, ":")) {
+ candidate = p;
+ candidate += '/';
+ candidate += execname;
+ if (QT_ACCESS(candidate.data(), X_OK) == 0)
+ return true;
+ }
+ return false;
+ };
+
+ static constexpr DebuggerProgram debuggerSearchOrder[] = {
+# if defined(Q_OS_QNX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
+ Gdb, Lldb
+# else
+ Lldb, Gdb
+# endif
+ };
+ for (DebuggerProgram candidate : debuggerSearchOrder) {
+ switch (candidate) {
+ case None:
+ Q_UNREACHABLE();
+ break;
+ case Gdb:
+ if (hasExecutable("gdb")) {
+ debugger = Gdb;
+ return;
+ }
+ break;
+ case Lldb:
+ if (hasExecutable("lldb")) {
+ debugger = Lldb;
+ return;
+ }
+ break;
+ }
+ }
+#endif // Q_OS_UNIX
+}
+
+#if !defined(Q_OS_WASM) || QT_CONFIG(thread)
+void printTestRunTime()
+{
+ const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
+ const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
+ const char *const name = QTest::currentTestFunction();
+ writeToStderr("\n ", name ? name : "[Non-test]",
+ " function time: ", asyncSafeToString(msecsFunctionTime),
+ "ms, total time: ", asyncSafeToString(msecsTotalTime), "ms\n");
+}
+
+void generateStackTrace()
+{
+ if (debugger == None || alreadyDebugging())
+ return;
+
+# if defined(Q_OS_UNIX) && !defined(Q_OS_WASM) && !defined(Q_OS_INTEGRITY) && !defined(Q_OS_VXWORKS)
+ writeToStderr("\n=== Stack trace ===\n");
+
+ // execlp() requires null-termination, so call the default constructor
+ AsyncSafeIntBuffer pidbuffer;
+ asyncSafeToString(getpid(), std::move(pidbuffer));
+
+ // Note: POSIX.1-2001 still has fork() in the list of async-safe functions,
+ // but in a future edition, it might be removed. It would be safer to wake
+ // up a babysitter thread to launch the debugger.
+ pid_t pid = fork();
+ if (pid == 0) {
+ // child process
+ (void) dup2(STDERR_FILENO, STDOUT_FILENO); // redirect stdout to stderr
+
+ switch (debugger) {
+ case None:
+ Q_UNREACHABLE();
+ break;
+ case Gdb:
+ execlp("gdb", "gdb", "--nx", "--batch", "-ex", "thread apply all bt",
+ "--pid", pidbuffer.array.data(), nullptr);
+ break;
+ case Lldb:
+ execlp("lldb", "lldb", "--no-lldbinit", "--batch", "-o", "bt all",
+ "--attach-pid", pidbuffer.array.data(), nullptr);
+ break;
+ }
+ _exit(1);
+ } else if (pid < 0) {
+ writeToStderr("Failed to start debugger.\n");
+ } else {
+ int ret;
+ QT_EINTR_LOOP(ret, waitpid(pid, nullptr, 0));
+ }
+
+ writeToStderr("=== End of stack trace ===\n");
+# endif // Q_OS_UNIX && !Q_OS_WASM && !Q_OS_INTEGRITY && !Q_OS_VXWORKS
+}
+#endif // !defined(Q_OS_WASM) || QT_CONFIG(thread)
+
+#if defined(Q_OS_WIN)
+void blockUnixSignals()
+{
+ // Windows does have C signals, but doesn't use them for the purposes we're
+ // talking about here
+}
+#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
+void blockUnixSignals()
+{
+ // Block most Unix signals so the WatchDog thread won't be called when
+ // external signals are delivered, thus avoiding interfering with the test
+ sigset_t set;
+ sigfillset(&set);
+
+ // we allow the crashing signals, in case we have bugs
+ for (int signo : FatalSignalHandler::fatalSignals)
+ sigdelset(&set, signo);
+
+ pthread_sigmask(SIG_BLOCK, &set, nullptr);
+}
+#endif // Q_OS_* choice
+
+#if defined(Q_OS_WIN)
+void DebugSymbolResolver::cleanup()
+{
+ if (m_dbgHelpLib)
+ FreeLibrary(m_dbgHelpLib);
+ m_dbgHelpLib = 0;
+ m_symFromAddr = nullptr;
+}
+
+DebugSymbolResolver::DebugSymbolResolver(HANDLE process)
+ : m_process(process), m_dbgHelpLib(0), m_symFromAddr(nullptr)
+{
+ bool success = false;
+ m_dbgHelpLib = LoadLibraryW(L"dbghelp.dll");
+ if (m_dbgHelpLib) {
+ SymInitializeType symInitialize = reinterpret_cast<SymInitializeType>(
+ reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymInitialize")));
+ m_symFromAddr = reinterpret_cast<SymFromAddrType>(
+ reinterpret_cast<QFunctionPointer>(GetProcAddress(m_dbgHelpLib, "SymFromAddr")));
+ success = symInitialize && m_symFromAddr && symInitialize(process, NULL, TRUE);
+ }
+ if (!success)
+ cleanup();
+}
+
+DebugSymbolResolver::Symbol DebugSymbolResolver::resolveSymbol(DWORD64 address) const
+{
+ // reserve additional buffer where SymFromAddr() will store the name
+ struct NamedSymbolInfo : public DBGHELP_SYMBOL_INFO {
+ enum { symbolNameLength = 255 };
+
+ char name[symbolNameLength + 1];
+ };
+
+ Symbol result;
+ if (!isValid())
+ return result;
+ NamedSymbolInfo symbolBuffer;
+ memset(&symbolBuffer, 0, sizeof(NamedSymbolInfo));
+ symbolBuffer.MaxNameLen = NamedSymbolInfo::symbolNameLength;
+ symbolBuffer.SizeOfStruct = sizeof(DBGHELP_SYMBOL_INFO);
+ if (!m_symFromAddr(m_process, address, 0, &symbolBuffer))
+ return result;
+ result.name = qstrdup(symbolBuffer.Name);
+ result.address = symbolBuffer.Address;
+ return result;
+}
+
+WindowsFaultHandler::WindowsFaultHandler()
+{
+# if !defined(Q_CC_MINGW)
+ _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);
+# endif
+ SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX);
+ SetUnhandledExceptionFilter(windowsFaultHandler);
+}
+
+LONG WINAPI WindowsFaultHandler::windowsFaultHandler(struct _EXCEPTION_POINTERS *exInfo)
+{
+ enum { maxStackFrames = 100 };
+ char appName[MAX_PATH];
+ if (!GetModuleFileNameA(NULL, appName, MAX_PATH))
+ appName[0] = 0;
+ const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
+ const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
+ const void *exceptionAddress = exInfo->ExceptionRecord->ExceptionAddress;
+ fprintf(stderr, "A crash occurred in %s.\n", appName);
+ if (const char *name = QTest::currentTestFunction())
+ fprintf(stderr, "While testing %s\n", name);
+ fprintf(stderr, "Function time: %dms Total time: %dms\n\n"
+ "Exception address: 0x%p\n"
+ "Exception code : 0x%lx\n",
+ msecsFunctionTime, msecsTotalTime, exceptionAddress,
+ exInfo->ExceptionRecord->ExceptionCode);
+
+ DebugSymbolResolver resolver(GetCurrentProcess());
+ if (resolver.isValid()) {
+ DebugSymbolResolver::Symbol exceptionSymbol = resolver.resolveSymbol(DWORD64(exceptionAddress));
+ if (exceptionSymbol.name) {
+ fprintf(stderr, "Nearby symbol : %s\n", exceptionSymbol.name);
+ delete [] exceptionSymbol.name;
+ }
+ void *stack[maxStackFrames];
+ fputs("\nStack:\n", stderr);
+ const unsigned frameCount = CaptureStackBackTrace(0, DWORD(maxStackFrames), stack, NULL);
+ for (unsigned f = 0; f < frameCount; ++f) {
+ DebugSymbolResolver::Symbol symbol = resolver.resolveSymbol(DWORD64(stack[f]));
+ if (symbol.name) {
+ fprintf(stderr, "#%3u: %s() - 0x%p\n", f + 1, symbol.name, (const void *)symbol.address);
+ delete [] symbol.name;
+ } else {
+ fprintf(stderr, "#%3u: Unable to obtain symbol\n", f + 1);
+ }
+ }
+ }
+
+ fputc('\n', stderr);
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
+bool FatalSignalHandler::pauseOnCrash = false;
+
+FatalSignalHandler::FatalSignalHandler()
+{
+ pauseOnCrash = qEnvironmentVariableIsSet("QTEST_PAUSE_ON_CRASH");
+ struct sigaction act;
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = SIG_DFL;
+ oldActions().fill(act);
+
+ // Remove the handler after it is invoked.
+ act.sa_flags = SA_RESETHAND | setupAlternateStack();
+
+# ifdef SA_SIGINFO
+ act.sa_flags |= SA_SIGINFO;
+ act.sa_sigaction = FatalSignalHandler::actionHandler;
+# else
+ act.sa_handler = FatalSignalHandler::regularHandler;
+# endif
+
+ // Block all fatal signals in our signal handler so we don't try to close
+ // the testlog twice.
+ sigemptyset(&act.sa_mask);
+ for (int signal : fatalSignals)
+ sigaddset(&act.sa_mask, signal);
+
+ for (size_t i = 0; i < fatalSignals.size(); ++i)
+ sigaction(fatalSignals[i], &act, &oldActions()[i]);
+}
+
+FatalSignalHandler::~FatalSignalHandler()
+{
+ // Restore the default signal handlers in place of ours.
+ // If ours has been replaced, leave the replacement alone.
+ auto isOurs = [](const struct sigaction &old) {
+# ifdef SA_SIGINFO
+ return (old.sa_flags & SA_SIGINFO) && old.sa_sigaction == FatalSignalHandler::actionHandler;
+# else
+ return old.sa_handler == FatalSignalHandler::regularHandler;
+# endif
+ };
+ struct sigaction action;
+
+ for (size_t i = 0; i < fatalSignals.size(); ++i) {
+ struct sigaction &act = oldActions()[i];
+ if (act.sa_flags == 0 && act.sa_handler == SIG_DFL)
+ continue; // Already the default
+ if (sigaction(fatalSignals[i], nullptr, &action))
+ continue; // Failed to query present handler
+ if (isOurs(action))
+ sigaction(fatalSignals[i], &act, nullptr);
+ }
+
+ freeAlternateStack();
+}
+
+FatalSignalHandler::OldActionsArray &FatalSignalHandler::oldActions()
+{
+ Q_CONSTINIT static OldActionsArray oldActions {};
+ return oldActions;
+}
+
+auto FatalSignalHandler::alternateStackSize()
+{
+ struct R { size_t size, pageSize; };
+ static constexpr size_t MinStackSize = 32 * 1024;
+ size_t pageSize = sysconf(_SC_PAGESIZE);
+ size_t size = SIGSTKSZ;
+ if (size < MinStackSize) {
+ size = MinStackSize;
+ } else {
+ // round up to a page
+ size = (size + pageSize - 1) & -pageSize;
+ }
+
+ return R{ size + pageSize, pageSize };
+}
+
+int FatalSignalHandler::setupAlternateStack()
+{
+ // tvOS/watchOS both define SA_ONSTACK (in sys/signal.h) but mark sigaltstack() as
+ // unavailable (__WATCHOS_PROHIBITED __TVOS_PROHIBITED in signal.h)
+# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
+ // Let the signal handlers use an alternate stack
+ // This is necessary if SIGSEGV is to catch a stack overflow
+ auto r = alternateStackSize();
+ int flags = MAP_PRIVATE | MAP_ANONYMOUS;
+# ifdef MAP_STACK
+ flags |= MAP_STACK;
+# endif
+ alternateStackBase = mmap(nullptr, r.size, PROT_READ | PROT_WRITE, flags, -1, 0);
+ if (alternateStackBase == MAP_FAILED)
+ return 0;
+
+ // mark the bottom page inaccessible, to catch a handler stack overflow
+ (void) mprotect(alternateStackBase, r.pageSize, PROT_NONE);
+
+ stack_t stack;
+ stack.ss_flags = 0;
+ stack.ss_size = r.size - r.pageSize;
+ stack.ss_sp = static_cast<char *>(alternateStackBase) + r.pageSize;
+ sigaltstack(&stack, nullptr);
+ return SA_ONSTACK;
+# else
+ return 0;
+# endif
+}
+
+void FatalSignalHandler::freeAlternateStack()
+{
+# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
+ if (alternateStackBase != MAP_FAILED) {
+ stack_t stack = {};
+ stack.ss_flags = SS_DISABLE;
+ sigaltstack(&stack, nullptr);
+ munmap(alternateStackBase, alternateStackSize().size);
+ }
+# endif
+}
+
+void FatalSignalHandler::actionHandler(int signum, siginfo_t *info, void *)
+{
+ writeToStderr("Received signal ", asyncSafeToString(signum),
+ " (SIG", signalName(signum), ")");
+
+ bool isCrashingSignal =
+ std::find(crashingSignals.begin(), crashingSignals.end(), signum) != crashingSignals.end();
+ if (isCrashingSignal && (!info || info->si_code <= 0))
+ isCrashingSignal = false; // wasn't sent by the kernel, so it's not really a crash
+ if (isCrashingSignal)
+ printCrashingSignalInfo(info);
+ else if (info && (info->si_code == SI_USER || info->si_code == SI_QUEUE))
+ printSentSignalInfo(info);
+
+ printTestRunTime();
+ if (signum != SIGINT) {
+ generateStackTrace();
+ if (pauseOnCrash) {
+ writeToStderr("Pausing process ", asyncSafeToString(getpid()),
+ " for debugging\n");
+ raise(SIGSTOP);
+ }
+ }
+
+ // chain back to the previous handler, if any
+ for (size_t i = 0; i < fatalSignals.size(); ++i) {
+ struct sigaction &act = oldActions()[i];
+ if (signum != fatalSignals[i])
+ continue;
+
+ // restore the handler (if SA_RESETHAND hasn't done the job for us)
+ if (SA_RESETHAND == 0 || act.sa_handler != SIG_DFL || act.sa_flags)
+ (void) sigaction(signum, &act, nullptr);
+
+ if (!isCrashingSignal)
+ raise(signum);
+
+ // signal is blocked, so it'll be delivered when we return
+ return;
+ }
+
+ // we shouldn't reach here!
+ std::abort();
+}
+#endif // defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
+
+} // namespace CrashHandler
+} // namespace QTest
+
+QT_END_NAMESPACE
diff --git a/src/testlib/qtestcrashhandler_p.h b/src/testlib/qtestcrashhandler_p.h
new file mode 100644
index 0000000000..02e19bfaa9
--- /dev/null
+++ b/src/testlib/qtestcrashhandler_p.h
@@ -0,0 +1,251 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QTESTCRASHHANDLER_H
+#define QTESTCRASHHANDLER_H
+
+#include <QtTest/qttestglobal.h>
+
+#include <QtCore/private/qtools_p.h>
+
+#ifdef Q_OS_UNIX
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#endif
+
+#ifdef Q_OS_WIN
+#include <iostream>
+# if !defined(Q_CC_MINGW) || (defined(Q_CC_MINGW) && defined(__MINGW64_VERSION_MAJOR))
+# include <crtdbg.h>
+# endif
+#include <qt_windows.h> // for Sleep
+#endif
+
+QT_BEGIN_NAMESPACE
+namespace QTest {
+namespace CrashHandler {
+#if defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
+ struct iovec IoVec(struct iovec vec);
+ struct iovec IoVec(const char *str);
+
+ template <typename... Args> static ssize_t writeToStderr(Args &&... args)
+ {
+ struct iovec vec[] = { IoVec(std::forward<Args>(args))... };
+ return ::writev(STDERR_FILENO, vec, std::size(vec));
+ }
+
+ // async-signal-safe conversion from int to string
+ struct AsyncSafeIntBuffer
+ {
+ // digits10 + 1 for all possible digits
+ // +1 for the sign
+ // +1 for the terminating null
+ static constexpr int Digits10 = std::numeric_limits<int>::digits10 + 3;
+ std::array<char, Digits10> array;
+ constexpr AsyncSafeIntBuffer() : array{} {} // initializes array
+ AsyncSafeIntBuffer(Qt::Initialization) {} // leaves array uninitialized
+ };
+
+ struct iovec asyncSafeToString(int n, AsyncSafeIntBuffer &&result = Qt::Uninitialized);
+#elif defined(Q_OS_WIN)
+ // Windows doesn't need to be async-safe
+ template <typename... Args> static void writeToStderr(Args &&... args)
+ {
+ (std::cerr << ... << args);
+ }
+
+ inline std::string asyncSafeToString(int n)
+ {
+ return std::to_string(n);
+ }
+#endif // defined(Q_OS_UNIX) && (!defined(Q_OS_WASM) || QT_CONFIG(thread))
+
+ bool alreadyDebugging();
+ void blockUnixSignals();
+
+#if !defined(Q_OS_WASM) || QT_CONFIG(thread)
+ void printTestRunTime();
+ void generateStackTrace();
+#endif
+
+ void maybeDisableCoreDump();
+ Q_TESTLIB_EXPORT void prepareStackTrace();
+
+#if defined(Q_OS_WIN)
+ // Helper class for resolving symbol names by dynamically loading "dbghelp.dll".
+ class DebugSymbolResolver
+ {
+ Q_DISABLE_COPY_MOVE(DebugSymbolResolver)
+ public:
+ struct Symbol
+ {
+ Symbol() : name(nullptr), address(0) {}
+
+ const char *name; // Must be freed by caller.
+ DWORD64 address;
+ };
+
+ explicit DebugSymbolResolver(HANDLE process);
+ ~DebugSymbolResolver() { cleanup(); }
+
+ bool isValid() const { return m_symFromAddr; }
+
+ Symbol resolveSymbol(DWORD64 address) const;
+
+ private:
+ // typedefs from DbgHelp.h/.dll
+ struct DBGHELP_SYMBOL_INFO { // SYMBOL_INFO
+ ULONG SizeOfStruct;
+ ULONG TypeIndex; // Type Index of symbol
+ ULONG64 Reserved[2];
+ ULONG Index;
+ ULONG Size;
+ ULONG64 ModBase; // Base Address of module comtaining this symbol
+ ULONG Flags;
+ ULONG64 Value; // Value of symbol, ValuePresent should be 1
+ ULONG64 Address; // Address of symbol including base address of module
+ ULONG Register; // register holding value or pointer to value
+ ULONG Scope; // scope of the symbol
+ ULONG Tag; // pdb classification
+ ULONG NameLen; // Actual length of name
+ ULONG MaxNameLen;
+ CHAR Name[1]; // Name of symbol
+ };
+
+ typedef BOOL (__stdcall *SymInitializeType)(HANDLE, PCSTR, BOOL);
+ typedef BOOL (__stdcall *SymFromAddrType)(HANDLE, DWORD64, PDWORD64, DBGHELP_SYMBOL_INFO *);
+
+ void cleanup();
+
+ const HANDLE m_process;
+ HMODULE m_dbgHelpLib;
+ SymFromAddrType m_symFromAddr;
+ };
+
+ class Q_TESTLIB_EXPORT WindowsFaultHandler
+ {
+ public:
+ WindowsFaultHandler();
+
+ private:
+ static LONG WINAPI windowsFaultHandler(struct _EXCEPTION_POINTERS *exInfo);
+ };
+ using FatalSignalHandler = WindowsFaultHandler;
+#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
+ class Q_TESTLIB_EXPORT FatalSignalHandler
+ {
+ public:
+ # define OUR_SIGNALS(F) \
+ F(HUP) \
+ F(INT) \
+ F(QUIT) \
+ F(ABRT) \
+ F(ILL) \
+ F(BUS) \
+ F(FPE) \
+ F(SEGV) \
+ F(PIPE) \
+ F(TERM) \
+ /**/
+ # define CASE_LABEL(S) case SIG ## S: return QT_STRINGIFY(S);
+ # define ENUMERATE_SIGNALS(S) SIG ## S,
+ static const char *signalName(int signum) noexcept
+ {
+ switch (signum) {
+ OUR_SIGNALS(CASE_LABEL)
+ }
+
+ # if defined(__GLIBC_MINOR__) && (__GLIBC_MINOR__ >= 32 || __GLIBC__ > 2)
+ // get the other signal names from glibc 2.32
+ // (accessing the sys_sigabbrev variable causes linker warnings)
+ if (const char *p = sigabbrev_np(signum))
+ return p;
+ # endif
+ return "???";
+ }
+ static constexpr std::array fatalSignals = {
+ OUR_SIGNALS(ENUMERATE_SIGNALS)
+ };
+ # undef CASE_LABEL
+ # undef ENUMERATE_SIGNALS
+
+ static constexpr std::array crashingSignals = {
+ // Crash signals are special, because if we return from the handler
+ // without adjusting the machine state, the same instruction that
+ // originally caused the crash will get re-executed and will thus cause
+ // the same crash again. This is useful if our parent process logs the
+ // exit result or if core dumps are enabled: the core file will point
+ // to the actual instruction that crashed.
+ SIGILL, SIGBUS, SIGFPE, SIGSEGV
+ };
+ using OldActionsArray = std::array<struct sigaction, fatalSignals.size()>;
+
+ FatalSignalHandler();
+ ~FatalSignalHandler();
+
+ private:
+ Q_DISABLE_COPY_MOVE(FatalSignalHandler)
+
+ static OldActionsArray &oldActions();
+ auto alternateStackSize();
+ int setupAlternateStack();
+ void freeAlternateStack();
+
+ template <typename T> static
+ std::enable_if_t<sizeof(std::declval<T>().si_pid) + sizeof(std::declval<T>().si_uid) >= 1>
+ printSentSignalInfo(T *info)
+ {
+ writeToStderr(" sent by PID ", asyncSafeToString(info->si_pid),
+ " UID ", asyncSafeToString(info->si_uid));
+ }
+ static void printSentSignalInfo(...) {}
+
+ template <typename T> static
+ std::enable_if_t<sizeof(std::declval<T>().si_addr) >= 1> printCrashingSignalInfo(T *info)
+ {
+ using HexString = std::array<char, sizeof(quintptr) * 2>;
+ auto toHexString = [](quintptr u, HexString &&r = {}) {
+ int shift = sizeof(quintptr) * 8 - 4;
+ for (size_t i = 0; i < sizeof(quintptr) * 2; ++i, shift -= 4)
+ r[i] = QtMiscUtils::toHexLower(u >> shift);
+ struct iovec vec;
+ vec.iov_base = r.data();
+ vec.iov_len = r.size();
+ return vec;
+ };
+ writeToStderr(", code ", asyncSafeToString(info->si_code),
+ ", for address 0x", toHexString(quintptr(info->si_addr)));
+ }
+ static void printCrashingSignalInfo(...) {}
+ static void actionHandler(int signum, siginfo_t *info, void * /* ucontext */);
+
+ [[maybe_unused]] static void regularHandler(int signum)
+ {
+ actionHandler(signum, nullptr, nullptr);
+ }
+
+ void *alternateStackBase = MAP_FAILED;
+ static bool pauseOnCrash;
+ };
+#else // Q_OS_WASM or weird systems
+class Q_TESTLIB_EXPORT FatalSignalHandler {};
+inline void blockUnixSignals() {}
+#endif // Q_OS_* choice
+} // namespace CrashHandler
+} // namespace QTest
+QT_END_NAMESPACE
+
+#endif // QTESTCRASHHANDLER_H
diff --git a/src/testlib/qtestevent.qdoc b/src/testlib/qtestevent.qdoc
index f71b99f8d7..a7af6d5592 100644
--- a/src/testlib/qtestevent.qdoc
+++ b/src/testlib/qtestevent.qdoc
@@ -118,25 +118,25 @@
For an example, please read the \l QTestEventList class documentation.
*/
-/*! \fn void QTestEventList::addMousePress(Qt::MouseButton button, Qt::KeyboardModifiers modifiers = 0, QPoint pos = QPoint(), int delay=-1)
+/*! \fn void QTestEventList::addMousePress(Qt::MouseButton button, Qt::KeyboardModifiers modifiers, QPoint pos = QPoint(), int delay=-1)
Add a mouse press to the list. The event will press the \a button with optional \a modifiers at the position \a pos with an optional \a delay. The default position is the center of the widget.
\sa QTest::mousePress()
*/
-/*! \fn void QTestEventList::addMouseRelease(Qt::MouseButton button, Qt::KeyboardModifiers modifiers = 0, QPoint pos = QPoint(), int delay=-1)
+/*! \fn void QTestEventList::addMouseRelease(Qt::MouseButton button, Qt::KeyboardModifiers modifiers, QPoint pos = QPoint(), int delay=-1)
Add a mouse release to the list. The event will release the \a button with optional \a modifiers at the position \a pos with an optional \a delay. The default position is the center of the widget.
\sa QTest::mouseRelease()
*/
-/*! \fn void QTestEventList::addMouseClick(Qt::MouseButton button, Qt::KeyboardModifiers modifiers = 0, QPoint pos = QPoint(), int delay=-1)
+/*! \fn void QTestEventList::addMouseClick(Qt::MouseButton button, Qt::KeyboardModifiers modifiers, QPoint pos = QPoint(), int delay=-1)
Add a mouse click to the list. The event will click the \a button with optional \a modifiers at the position \a pos with an optional \a delay. The default position is the center of the widget.
\sa QTest::mouseClick()
*/
-/*! \fn void QTestEventList::addMouseDClick(Qt::MouseButton button, Qt::KeyboardModifiers modifiers = 0, QPoint pos = QPoint(), int delay=-1)
+/*! \fn void QTestEventList::addMouseDClick(Qt::MouseButton button, Qt::KeyboardModifiers modifiers, QPoint pos = QPoint(), int delay=-1)
Add a double mouse click to the list. The event will double click the \a button with optional \a modifiers at the position \a pos with an optional \a delay. The default position is the center of the widget.
diff --git a/src/testlib/qtesteventloop.h b/src/testlib/qtesteventloop.h
index 2860882ca1..6e90880690 100644
--- a/src/testlib/qtesteventloop.h
+++ b/src/testlib/qtesteventloop.h
@@ -25,8 +25,9 @@ public:
: QObject(parent), _timeout(false)
{}
- inline void enterLoopMSecs(int ms);
- inline void enterLoop(int secs) { enterLoopMSecs(secs * 1000); }
+ void enterLoopMSecs(int ms) { enterLoop(std::chrono::milliseconds{ms}); };
+ void enterLoop(int secs) { enterLoop(std::chrono::seconds{secs}); }
+ inline void enterLoop(std::chrono::milliseconds msecs);
inline void changeInterval(int secs)
{ killTimer(timerId); timerId = startTimer(secs * 1000); }
@@ -55,7 +56,7 @@ private:
Q_DECL_UNUSED_MEMBER uint reserved :31;
};
-inline void QTestEventLoop::enterLoopMSecs(int ms)
+inline void QTestEventLoop::enterLoop(std::chrono::milliseconds msecs)
{
Q_ASSERT(!loop);
_timeout = false;
@@ -63,9 +64,10 @@ inline void QTestEventLoop::enterLoopMSecs(int ms)
if (QTest::runningTest() && QTest::currentTestResolved())
return;
+ using namespace std::chrono_literals;
QEventLoop l;
// if tests want to measure sub-second precision, use a precise timer
- timerId = startTimer(ms, ms < 1000 ? Qt::PreciseTimer : Qt::CoarseTimer);
+ timerId = startTimer(msecs, msecs < 1s ? Qt::PreciseTimer : Qt::CoarseTimer);
loop = &l;
l.exec();
diff --git a/src/testlib/qtestlog.cpp b/src/testlib/qtestlog.cpp
index dca4bc1c10..929ccb370b 100644
--- a/src/testlib/qtestlog.cpp
+++ b/src/testlib/qtestlog.cpp
@@ -179,7 +179,7 @@ namespace QTest {
static bool handleFailOnWarning(const QMessageLogContext &context, const QString &message)
{
- // failOnWarnings can be called multiple times per test function, so let
+ // failOnWarning can be called multiple times per test function, so let
// each call cause a failure if required.
for (const auto &pattern : failOnWarningList) {
if (pattern.metaType() == QMetaType::fromType<QString>()) {
@@ -318,7 +318,6 @@ void QTestLog::clearIgnoreMessages()
QTest::IgnoreResultList::clearList(QTest::ignoreResultList);
}
-
void QTestLog::clearFailOnWarnings()
{
QTest::failOnWarningList.clear();
@@ -326,6 +325,8 @@ void QTestLog::clearFailOnWarnings()
void QTestLog::clearCurrentTestState()
{
+ clearIgnoreMessages();
+ clearFailOnWarnings();
QTest::currentTestState = QTest::Unresolved;
}
@@ -559,6 +560,21 @@ bool QTestLog::hasLoggers()
return !QTest::loggers()->empty();
}
+/*!
+ \internal
+
+ Returns true if all loggers support repeated test runs
+*/
+bool QTestLog::isRepeatSupported()
+{
+ FOREACH_TEST_LOGGER {
+ if (!logger->isRepeatSupported())
+ return false;
+ }
+
+ return true;
+}
+
bool QTestLog::loggerUsingStdout()
{
FOREACH_TEST_LOGGER {
@@ -613,6 +629,11 @@ void QTestLog::ignoreMessage(QtMsgType type, const QRegularExpression &expressio
}
#endif // QT_CONFIG(regularexpression)
+void QTestLog::failOnWarning()
+{
+ QTest::failOnWarningList.push_back({});
+}
+
void QTestLog::failOnWarning(const char *msg)
{
QTest::failOnWarningList.push_back(QString::fromUtf8(msg));
diff --git a/src/testlib/qtestlog_p.h b/src/testlib/qtestlog_p.h
index 9717858afb..ab5fb593d4 100644
--- a/src/testlib/qtestlog_p.h
+++ b/src/testlib/qtestlog_p.h
@@ -71,6 +71,7 @@ public:
#ifndef QT_NO_REGULAREXPRESSION
static void ignoreMessage(QtMsgType type, const QRegularExpression &expression);
#endif
+ static void failOnWarning();
static void failOnWarning(const char *msg);
#ifndef QT_NO_REGULAREXPRESSION
static void failOnWarning(const QRegularExpression &expression);
@@ -91,6 +92,7 @@ public:
static void addLogger(QAbstractTestLogger *logger);
static bool hasLoggers();
+ static bool isRepeatSupported();
static bool loggerUsingStdout();
static void setVerboseLevel(int level);
diff --git a/src/testlib/qtestmouse.h b/src/testlib/qtestmouse.h
index 01a3c9c6b5..567d80c521 100644
--- a/src/testlib/qtestmouse.h
+++ b/src/testlib/qtestmouse.h
@@ -175,7 +175,7 @@ namespace QTest
stateKey &= Qt::KeyboardModifierMask;
- QEvent::Type meType;
+ QEvent::Type meType = QEvent::None;
using namespace QTestPrivate;
switch (action)
{
diff --git a/src/testlib/qtestresult.cpp b/src/testlib/qtestresult.cpp
index 00799a02da..7c5ce9ce54 100644
--- a/src/testlib/qtestresult.cpp
+++ b/src/testlib/qtestresult.cpp
@@ -149,13 +149,6 @@ void QTestResult::finishedCurrentTestData()
addFailure("QEXPECT_FAIL was called without any subsequent verification statements");
clearExpectFail();
-
- if (!QTest::hasFailed() && QTestLog::unhandledIgnoreMessages()) {
- QTestLog::printUnhandledIgnoreMessages();
- addFailure("Not all expected messages were received");
- }
- QTestLog::clearIgnoreMessages();
- QTestLog::clearFailOnWarnings();
}
/*!
@@ -175,6 +168,11 @@ void QTestResult::finishedCurrentTestData()
*/
void QTestResult::finishedCurrentTestDataCleanup()
{
+ if (!QTest::hasFailed() && QTestLog::unhandledIgnoreMessages()) {
+ QTestLog::printUnhandledIgnoreMessages();
+ addFailure("Not all expected messages were received");
+ }
+
// If the current test hasn't failed or been skipped, then it passes.
if (!QTest::hasFailed() && !QTest::skipCurrentTest) {
if (QTest::blacklistCurrentTest)
@@ -289,21 +287,27 @@ void QTestResult::fail(const char *msg, const char *file, int line)
checkStatement(false, msg, file, line);
}
+// QPalette's << operator produces 1363 characters. A comparison failure
+// involving two palettes can therefore require 2726 characters, not including
+// the other output produced by QTest. Users might also have their own types
+// with large amounts of output, so use a sufficiently high value here.
+static constexpr size_t maxMsgLen = 4096;
+
bool QTestResult::verify(bool statement, const char *statementStr,
const char *description, const char *file, int line)
{
QTEST_ASSERT(statementStr);
- char msg[1024];
+ char msg[maxMsgLen];
msg[0] = '\0';
if (QTestLog::verboseLevel() >= 2) {
- qsnprintf(msg, 1024, "QVERIFY(%s)", statementStr);
+ qsnprintf(msg, maxMsgLen, "QVERIFY(%s)", statementStr);
QTestLog::info(msg, file, line);
}
if (statement == !!QTest::expectFailMode) {
- qsnprintf(msg, 1024,
+ qsnprintf(msg, maxMsgLen,
statement ? "'%s' returned TRUE unexpectedly. (%s)" : "'%s' returned FALSE. (%s)",
statementStr, description ? description : "");
}
@@ -313,12 +317,12 @@ bool QTestResult::verify(bool statement, const char *statementStr,
static const char *leftArgNameForOp(QTest::ComparisonOperation op)
{
- return op == QTest::ComparisonOperation::CustomCompare ? "Actual " : "Left ";
+ return op == QTest::ComparisonOperation::CustomCompare ? "Actual " : "Computed ";
}
static const char *rightArgNameForOp(QTest::ComparisonOperation op)
{
- return op == QTest::ComparisonOperation::CustomCompare ? "Expected " : "Right ";
+ return op == QTest::ComparisonOperation::CustomCompare ? "Expected " : "Baseline ";
}
// Overload to format failures for "const char *" - no need to strdup().
@@ -371,7 +375,6 @@ static bool compareHelper(bool success, const char *failureMsg,
const char *file, int line,
bool hasValues = true)
{
- const size_t maxMsgLen = 1024;
char msg[maxMsgLen];
msg[0] = '\0';
@@ -534,7 +537,8 @@ bool QTestResult::compare(bool success, const char *failureMsg,
void QTestResult::addFailure(const char *message, const char *file, int line)
{
clearExpectFail();
- QTestEventLoop::instance().exitLoop();
+ if (qApp && QThread::currentThread() == qApp->thread())
+ QTestEventLoop::instance().exitLoop();
if (QTest::blacklistCurrentTest)
QTestLog::addBFail(message, file, line);
@@ -609,28 +613,28 @@ static const char *failureMessageForOp(QTest::ComparisonOperation op)
case ComparisonOperation::CustomCompare:
return "Compared values are not the same"; /* not used */
case ComparisonOperation::Equal:
- return "Left value is expected to be equal to right value, but is not";
+ return "The computed value is expected to be equal to the baseline, but is not";
case ComparisonOperation::NotEqual:
- return "Left value is expected to be different from right value, but is not";
+ return "The computed value is expected to be different from the baseline, but is not";
case ComparisonOperation::LessThan:
- return "Left value is expected to be less than right value, but is not";
+ return "The computed value is expected to be less than the baseline, but is not";
case ComparisonOperation::LessThanOrEqual:
- return "Left value is expected to be less than or equal to right value, but is not";
+ return "The computed value is expected to be less than or equal to the baseline, but is not";
case ComparisonOperation::GreaterThan:
- return "Left value is expected to be greater than right value, but is not";
+ return "The computed value is expected to be greater than the baseline, but is not";
case ComparisonOperation::GreaterThanOrEqual:
- return "Left value is expected to be greater than or equal to right value, but is not";
+ return "The computed value is expected to be greater than or equal to the baseline, but is not";
}
Q_UNREACHABLE_RETURN("");
}
-bool QTestResult::reportResult(bool success, qxp::function_ref<const char *()> lhs,
- qxp::function_ref<const char *()> rhs,
+bool QTestResult::reportResult(bool success, const void *lhs, const void *rhs,
+ const char *(*lhsFormatter)(const void*),
+ const char *(*rhsFormatter)(const void*),
const char *lhsExpr, const char *rhsExpr,
QTest::ComparisonOperation op, const char *file, int line,
const char *failureMessage)
{
- const size_t maxMsgLen = 1024;
char msg[maxMsgLen];
msg[0] = '\0';
@@ -650,8 +654,8 @@ bool QTestResult::reportResult(bool success, qxp::function_ref<const char *()> l
return checkStatement(success, msg, file, line);
}
- const std::unique_ptr<const char[]> lhsPtr{ lhs() };
- const std::unique_ptr<const char[]> rhsPtr{ rhs() };
+ const std::unique_ptr<const char[]> lhsPtr{ lhsFormatter(lhs) };
+ const std::unique_ptr<const char[]> rhsPtr{ rhsFormatter(rhs) };
if (!failureMessage)
failureMessage = failureMessageForOp(op);
diff --git a/src/testlib/qtestresult_p.h b/src/testlib/qtestresult_p.h
index 48c2c34611..e94de64c06 100644
--- a/src/testlib/qtestresult_p.h
+++ b/src/testlib/qtestresult_p.h
@@ -17,7 +17,6 @@
#include <QtTest/qttestglobal.h>
#include <QtCore/qstringfwd.h>
-#include <QtCore/qxpfunctional.h>
#include <QtCore/private/qglobal_p.h>
QT_BEGIN_NAMESPACE
@@ -102,8 +101,9 @@ public:
static void setCurrentAppName(const char *appName);
static const char *currentAppName();
- static bool reportResult(bool success, qxp::function_ref<const char *()> lhs,
- qxp::function_ref<const char *()> rhs,
+ static bool reportResult(bool success, const void *lhs, const void *rhs,
+ const char *(*lhsFormatter)(const void *),
+ const char *(*rhsFormatter)(const void *),
const char *lhsExpr, const char *rhsExpr,
QTest::ComparisonOperation op, const char *file, int line,
const char *failureMessage = nullptr);
diff --git a/src/testlib/qtesttable.cpp b/src/testlib/qtesttable.cpp
index 934d1797c7..2276365505 100644
--- a/src/testlib/qtesttable.cpp
+++ b/src/testlib/qtesttable.cpp
@@ -5,6 +5,7 @@
#include <QtTest/qtestdata.h>
#include <QtTest/qtestassert.h>
+#include <QtCore/private/qduplicatetracker_p.h>
#include <QtCore/qmetaobject.h>
#include <string.h>
@@ -35,6 +36,9 @@ public:
using DataList = std::vector<QTestData *>;
DataList dataList;
+ using TagSet = QDuplicateTracker<std::string>;
+ TagSet tagSet;
+
void addColumn(int elemType, const char *elemName) { elementList.push_back(Element(elemName, elemType)); }
void addRow(QTestData *data) { dataList.push_back(data); }
@@ -49,6 +53,8 @@ void QTestTable::addColumn(int type, const char *name)
{
QTEST_ASSERT(type);
QTEST_ASSERT(name);
+ if (indexOf(name) != -1)
+ qWarning() << "Duplicate data column" << name << "- please rename.";
d->addColumn(type, name);
}
@@ -70,6 +76,10 @@ bool QTestTable::isEmpty() const
QTestData *QTestTable::newData(const char *tag)
{
+ QTEST_ASSERT(tag);
+ if (d->tagSet.hasSeen(tag))
+ qWarning("Duplicate data tag \"%s\" - please rename.", tag);
+
QTestData *dt = new QTestData(tag, this);
d->addRow(dt);
return dt;
@@ -110,6 +120,9 @@ public:
bool operator()(const QTestTablePrivate::Element &e) const
{ return !strcmp(e.name, m_needle); }
+ bool operator()(const QTestData *e) const
+ { return !strcmp(e->dataTag(), m_needle); }
+
private:
const char *m_needle;
};
diff --git a/src/testlib/qtesttostring.h b/src/testlib/qtesttostring.h
new file mode 100644
index 0000000000..18262332ba
--- /dev/null
+++ b/src/testlib/qtesttostring.h
@@ -0,0 +1,499 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2024 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QTESTTOSTRING_H
+#define QTESTTOSTRING_H
+
+#include <QtTest/qttestglobal.h>
+
+#if QT_CONFIG(itemmodel)
+# include <QtCore/qabstractitemmodel.h>
+#endif
+#include <QtCore/qbitarray.h>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qcborarray.h>
+#include <QtCore/qcborcommon.h>
+#include <QtCore/qcbormap.h>
+#include <QtCore/qcborvalue.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qmetatype.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qpoint.h>
+#include <QtCore/qrect.h>
+#include <QtCore/qsize.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qurl.h>
+#include <QtCore/quuid.h>
+#include <QtCore/qvariant.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QTest {
+namespace Internal {
+
+template<typename T> // Output registered enums
+inline typename std::enable_if<QtPrivate::IsQEnumHelper<T>::Value, char*>::type toString(T e)
+{
+ QMetaEnum me = QMetaEnum::fromType<T>();
+ return qstrdup(me.valueToKey(int(e))); // int cast is necessary to support enum classes
+}
+
+template <typename T>
+inline typename std::enable_if<!QtPrivate::IsQEnumHelper<T>::Value && std::is_enum_v<T>, char*>::type toString(const T &e)
+{
+ return qstrdup(QByteArray::number(static_cast<std::underlying_type_t<T>>(e)).constData());
+}
+
+template <typename T> // Fallback; for built-in types debug streaming must be possible
+inline typename std::enable_if<!QtPrivate::IsQEnumHelper<T>::Value && !std::is_enum_v<T>, char *>::type toString(const T &t)
+{
+ char *result = nullptr;
+#ifndef QT_NO_DEBUG_STREAM
+ if constexpr (QTypeTraits::has_ostream_operator_v<QDebug, T>) {
+ result = qstrdup(QDebug::toString(t).toUtf8().constData());
+ } else {
+ static_assert(!QMetaTypeId2<T>::IsBuiltIn,
+ "Built-in type must implement debug streaming operator "
+ "or provide QTest::toString specialization");
+ }
+#endif
+ return result;
+}
+
+template<typename F> // Output QFlags of registered enumerations
+inline typename std::enable_if<QtPrivate::IsQEnumHelper<F>::Value, char*>::type toString(QFlags<F> f)
+{
+ const QMetaEnum me = QMetaEnum::fromType<F>();
+ return qstrdup(me.valueToKeys(int(f.toInt())).constData());
+}
+
+template <typename F> // Fallback: Output hex value
+inline typename std::enable_if<!QtPrivate::IsQEnumHelper<F>::Value, char*>::type toString(QFlags<F> f)
+{
+ const size_t space = 3 + 2 * sizeof(unsigned); // 2 for 0x, two hex digits per byte, 1 for '\0'
+ char *msg = new char[space];
+ qsnprintf(msg, space, "0x%x", unsigned(f.toInt()));
+ return msg;
+}
+
+template <typename T>
+constexpr bool is_suitable_type_helper_v = std::disjunction_v<std::is_same<T, char>,
+ std::is_same<T, void>,
+ std::is_same<T, QObject>
+ >;
+
+template <typename T>
+using is_suitable_type_v =
+ std::enable_if_t<!(std::is_pointer_v<T>
+ && is_suitable_type_helper_v<
+ std::remove_const_t<std::remove_pointer_t<T>>>),
+ bool>;
+
+} // namespace Internal
+
+Q_TESTLIB_EXPORT bool compare_string_helper(const char *t1, const char *t2, const char *actual,
+ const char *expected, const char *file, int line);
+Q_TESTLIB_EXPORT char *formatString(const char *prefix, const char *suffix, size_t numArguments, ...);
+Q_TESTLIB_EXPORT char *toHexRepresentation(const char *ba, qsizetype length);
+Q_TESTLIB_EXPORT char *toPrettyCString(const char *unicode, qsizetype length);
+Q_TESTLIB_EXPORT char *toPrettyUnicode(QStringView string);
+Q_TESTLIB_EXPORT char *toString(const char *);
+Q_TESTLIB_EXPORT char *toString(const volatile void *);
+Q_TESTLIB_EXPORT char *toString(const volatile QObject *);
+
+template<typename T, Internal::is_suitable_type_v<T> = true>
+inline char *toString(const T &t)
+{
+ return Internal::toString(t);
+}
+
+template <typename T1, typename T2>
+inline char *toString(const std::pair<T1, T2> &pair);
+
+template <class... Types>
+inline char *toString(const std::tuple<Types...> &tuple);
+
+template <typename Rep, typename Period>
+inline char *toString(std::chrono::duration<Rep, Period> duration);
+
+#define QTEST_COMPARE_DECL(KLASS)\
+ template<> Q_TESTLIB_EXPORT char *toString<KLASS >(const KLASS &);
+#ifndef Q_QDOC
+QTEST_COMPARE_DECL(short)
+QTEST_COMPARE_DECL(ushort)
+QTEST_COMPARE_DECL(int)
+QTEST_COMPARE_DECL(uint)
+QTEST_COMPARE_DECL(long)
+QTEST_COMPARE_DECL(ulong)
+QTEST_COMPARE_DECL(qint64)
+QTEST_COMPARE_DECL(quint64)
+
+QTEST_COMPARE_DECL(float)
+QTEST_COMPARE_DECL(double)
+QTEST_COMPARE_DECL(qfloat16)
+QTEST_COMPARE_DECL(char)
+QTEST_COMPARE_DECL(signed char)
+QTEST_COMPARE_DECL(unsigned char)
+QTEST_COMPARE_DECL(bool)
+#endif
+#undef QTEST_COMPARE_DECL
+
+template <> inline char *toString(const QStringView &str)
+{
+ return QTest::toPrettyUnicode(str);
+}
+
+template<> inline char *toString(const QString &str)
+{
+ return toString(QStringView(str));
+}
+
+template<> inline char *toString(const QLatin1StringView &str)
+{
+ return toString(QString(str));
+}
+
+template<> inline char *toString(const QByteArray &ba)
+{
+ return QTest::toPrettyCString(ba.constData(), ba.size());
+}
+
+template<> inline char *toString(const QBitArray &ba)
+{
+ qsizetype size = ba.size();
+ char *str = new char[size + 1];
+ for (qsizetype i = 0; i < size; ++i)
+ str[i] = "01"[ba.testBit(i)];
+ str[size] = '\0';
+ return str;
+}
+
+#if QT_CONFIG(datestring)
+template<> inline char *toString(const QTime &time)
+{
+ return time.isValid()
+ ? qstrdup(qPrintable(time.toString(u"hh:mm:ss.zzz")))
+ : qstrdup("Invalid QTime");
+}
+
+template<> inline char *toString(const QDate &date)
+{
+ return date.isValid()
+ ? qstrdup(qPrintable(date.toString(u"yyyy/MM/dd")))
+ : qstrdup("Invalid QDate");
+}
+
+template<> inline char *toString(const QDateTime &dateTime)
+{
+ return dateTime.isValid()
+ ? qstrdup(qPrintable(dateTime.toString(u"yyyy/MM/dd hh:mm:ss.zzz[t]")))
+ : qstrdup("Invalid QDateTime");
+}
+#endif // datestring
+
+template<> inline char *toString(const QCborError &c)
+{
+ // use the Q_ENUM formatting
+ return toString(c.c);
+}
+
+template<> inline char *toString(const QChar &c)
+{
+ const ushort uc = c.unicode();
+ if (uc < 128) {
+ char msg[32] = {'\0'};
+ qsnprintf(msg, sizeof(msg), "QChar: '%c' (0x%x)", char(uc), unsigned(uc));
+ return qstrdup(msg);
+ }
+ return qstrdup(qPrintable(QString::fromLatin1("QChar: '%1' (0x%2)").arg(c).arg(QString::number(static_cast<int>(c.unicode()), 16))));
+}
+
+#if QT_CONFIG(itemmodel)
+template<> inline char *toString(const QModelIndex &idx)
+{
+ char msg[128];
+ qsnprintf(msg, sizeof(msg), "QModelIndex(%d,%d,%p,%p)", idx.row(), idx.column(), idx.internalPointer(), idx.model());
+ return qstrdup(msg);
+}
+#endif
+
+template<> inline char *toString(const QPoint &p)
+{
+ char msg[128] = {'\0'};
+ qsnprintf(msg, sizeof(msg), "QPoint(%d,%d)", p.x(), p.y());
+ return qstrdup(msg);
+}
+
+template<> inline char *toString(const QSize &s)
+{
+ char msg[128] = {'\0'};
+ qsnprintf(msg, sizeof(msg), "QSize(%dx%d)", s.width(), s.height());
+ return qstrdup(msg);
+}
+
+template<> inline char *toString(const QRect &s)
+{
+ char msg[256] = {'\0'};
+ qsnprintf(msg, sizeof(msg), "QRect(%d,%d %dx%d) (bottomright %d,%d)",
+ s.left(), s.top(), s.width(), s.height(), s.right(), s.bottom());
+ return qstrdup(msg);
+}
+
+template<> inline char *toString(const QPointF &p)
+{
+ char msg[64] = {'\0'};
+ qsnprintf(msg, sizeof(msg), "QPointF(%g,%g)", p.x(), p.y());
+ return qstrdup(msg);
+}
+
+template<> inline char *toString(const QSizeF &s)
+{
+ char msg[64] = {'\0'};
+ qsnprintf(msg, sizeof(msg), "QSizeF(%gx%g)", s.width(), s.height());
+ return qstrdup(msg);
+}
+
+template<> inline char *toString(const QRectF &s)
+{
+ char msg[256] = {'\0'};
+ qsnprintf(msg, sizeof(msg), "QRectF(%g,%g %gx%g) (bottomright %g,%g)",
+ s.left(), s.top(), s.width(), s.height(), s.right(), s.bottom());
+ return qstrdup(msg);
+}
+
+template<> inline char *toString(const QUrl &uri)
+{
+ if (!uri.isValid())
+ return qstrdup(qPrintable(QLatin1StringView("Invalid URL: ") + uri.errorString()));
+ return qstrdup(uri.toEncoded().constData());
+}
+
+template <> inline char *toString(const QUuid &uuid)
+{
+ return qstrdup(uuid.toByteArray().constData());
+}
+
+template<> inline char *toString(const QVariant &v)
+{
+ QByteArray vstring("QVariant(");
+ if (v.isValid()) {
+ QByteArray type(v.typeName());
+ if (type.isEmpty()) {
+ type = QByteArray::number(v.userType());
+ }
+ vstring.append(type);
+ if (!v.isNull()) {
+ vstring.append(',');
+ if (v.canConvert<QString>()) {
+ vstring.append(v.toString().toLocal8Bit());
+ }
+ else {
+ vstring.append("<value not representable as string>");
+ }
+ }
+ }
+ vstring.append(')');
+
+ return qstrdup(vstring.constData());
+}
+
+template<> inline char *toString(const QPartialOrdering &o)
+{
+ if (o == QPartialOrdering::Less)
+ return qstrdup("Less");
+ if (o == QPartialOrdering::Equivalent)
+ return qstrdup("Equivalent");
+ if (o == QPartialOrdering::Greater)
+ return qstrdup("Greater");
+ if (o == QPartialOrdering::Unordered)
+ return qstrdup("Unordered");
+ return qstrdup("<invalid>");
+}
+
+namespace Internal {
+struct QCborValueFormatter
+{
+ enum { BufferLen = 256 };
+ static char *formatSimpleType(QCborSimpleType st)
+ {
+ char *buf = new char[BufferLen];
+ qsnprintf(buf, BufferLen, "QCborValue(QCborSimpleType(%d))", int(st));
+ return buf;
+ }
+
+ static char *formatTag(QCborTag tag, const QCborValue &taggedValue)
+ {
+ QScopedArrayPointer<char> hold(format(taggedValue));
+ char *buf = new char[BufferLen];
+ qsnprintf(buf, BufferLen, "QCborValue(QCborTag(%llu), %s)", tag, hold.get());
+ return buf;
+ }
+
+ static char *innerFormat(QCborValue::Type t, const char *str)
+ {
+ static const QMetaEnum typeEnum = []() {
+ int idx = QCborValue::staticMetaObject.indexOfEnumerator("Type");
+ return QCborValue::staticMetaObject.enumerator(idx);
+ }();
+
+ char *buf = new char[BufferLen];
+ const char *typeName = typeEnum.valueToKey(t);
+ if (typeName)
+ qsnprintf(buf, BufferLen, "QCborValue(%s, %s)", typeName, str);
+ else
+ qsnprintf(buf, BufferLen, "QCborValue(<unknown type 0x%02x>)", t);
+ return buf;
+ }
+
+ template<typename T> static char *format(QCborValue::Type type, const T &t)
+ {
+ QScopedArrayPointer<char> hold(QTest::toString(t));
+ return innerFormat(type, hold.get());
+ }
+
+ static char *format(const QCborValue &v)
+ {
+ switch (v.type()) {
+ case QCborValue::Integer:
+ return format(v.type(), v.toInteger());
+ case QCborValue::ByteArray:
+ return format(v.type(), v.toByteArray());
+ case QCborValue::String:
+ return format(v.type(), v.toString());
+ case QCborValue::Array:
+ return innerFormat(v.type(), QScopedArrayPointer<char>(format(v.toArray())).get());
+ case QCborValue::Map:
+ return innerFormat(v.type(), QScopedArrayPointer<char>(format(v.toMap())).get());
+ case QCborValue::Tag:
+ return formatTag(v.tag(), v.taggedValue());
+ case QCborValue::SimpleType:
+ break;
+ case QCborValue::True:
+ return qstrdup("QCborValue(true)");
+ case QCborValue::False:
+ return qstrdup("QCborValue(false)");
+ case QCborValue::Null:
+ return qstrdup("QCborValue(nullptr)");
+ case QCborValue::Undefined:
+ return qstrdup("QCborValue()");
+ case QCborValue::Double:
+ return format(v.type(), v.toDouble());
+ case QCborValue::DateTime:
+ case QCborValue::Url:
+ case QCborValue::RegularExpression:
+ return format(v.type(), v.taggedValue().toString());
+ case QCborValue::Uuid:
+ return format(v.type(), v.toUuid());
+ case QCborValue::Invalid:
+ return qstrdup("QCborValue(<invalid>)");
+ }
+
+ if (v.isSimpleType())
+ return formatSimpleType(v.toSimpleType());
+ return innerFormat(v.type(), "");
+ }
+
+ static char *format(const QCborArray &a)
+ {
+ QByteArray out(1, '[');
+ const char *comma = "";
+ for (QCborValueConstRef v : a) {
+ QScopedArrayPointer<char> s(format(v));
+ out += comma;
+ out += s.get();
+ comma = ", ";
+ }
+ out += ']';
+ return qstrdup(out.constData());
+ }
+
+ static char *format(const QCborMap &m)
+ {
+ QByteArray out(1, '{');
+ const char *comma = "";
+ for (auto pair : m) {
+ QScopedArrayPointer<char> key(format(pair.first));
+ QScopedArrayPointer<char> value(format(pair.second));
+ out += comma;
+ out += key.get();
+ out += ": ";
+ out += value.get();
+ comma = ", ";
+ }
+ out += '}';
+ return qstrdup(out.constData());
+ }
+};
+}
+
+template<> inline char *toString(const QCborValue &v)
+{
+ return Internal::QCborValueFormatter::format(v);
+}
+
+template<> inline char *toString(const QCborValueRef &v)
+{
+ return toString(QCborValue(v));
+}
+
+template<> inline char *toString(const QCborArray &a)
+{
+ return Internal::QCborValueFormatter::format(a);
+}
+
+template<> inline char *toString(const QCborMap &m)
+{
+ return Internal::QCborValueFormatter::format(m);
+}
+
+template <typename Rep, typename Period> char *toString(std::chrono::duration<Rep, Period> dur)
+{
+ QString r;
+ QDebug d(&r);
+ d.nospace() << qSetRealNumberPrecision(9) << dur;
+ if constexpr (Period::num != 1 || Period::den != 1) {
+ // include the equivalent value in seconds, in parentheses
+ using namespace std::chrono;
+ d << " (" << duration_cast<duration<qreal>>(dur).count() << "s)";
+ }
+ return qstrdup(std::move(r).toUtf8().constData());
+}
+
+template <typename T1, typename T2>
+inline char *toString(const std::pair<T1, T2> &pair)
+{
+ const QScopedArrayPointer<char> first(toString(pair.first));
+ const QScopedArrayPointer<char> second(toString(pair.second));
+ return formatString("std::pair(", ")", 2, first.data(), second.data());
+}
+
+template <typename Tuple, std::size_t... I>
+inline char *tupleToString(const Tuple &tuple, std::index_sequence<I...>) {
+ using UP = std::unique_ptr<char[]>;
+ // Generate a table of N + 1 elements where N is the number of
+ // elements in the tuple.
+ // The last element is needed to support the empty tuple use case.
+ const UP data[] = {
+ UP(toString(std::get<I>(tuple)))..., UP{}
+ };
+ return formatString("std::tuple(", ")", sizeof...(I), data[I].get()...);
+}
+
+template <class... Types>
+inline char *toString(const std::tuple<Types...> &tuple)
+{
+ return tupleToString(tuple, std::make_index_sequence<sizeof...(Types)>{});
+}
+
+inline char *toString(std::nullptr_t)
+{
+ return toString(QStringView(u"nullptr"));
+}
+} // namespace QTest
+
+QT_END_NAMESPACE
+
+#endif // QTESTTOSTRING_H
diff --git a/src/testlib/qtestwheel.h b/src/testlib/qtestwheel.h
new file mode 100644
index 0000000000..564a586b4b
--- /dev/null
+++ b/src/testlib/qtestwheel.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QTESTWHEEL_H
+#define QTESTWHEEL_H
+
+#if 0
+// inform syncqt
+#pragma qt_no_master_include
+#endif
+
+#include <QtTest/qttestglobal.h>
+#include <QtTest/qtestassert.h>
+#include <QtTest/qtestsystem.h>
+#include <QtTest/qtestspontaneevent.h>
+#include <QtCore/qpoint.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qpointer.h>
+#include <QtGui/qevent.h>
+#include <QtGui/qwindow.h>
+
+#include <QtCore/QDebug>
+
+QT_BEGIN_NAMESPACE
+
+Q_GUI_EXPORT void qt_handleWheelEvent(QWindow *window, const QPointF &local,
+ const QPointF &global, QPoint pixelDelta,
+ QPoint angleDelta, Qt::KeyboardModifiers mods, Qt::ScrollPhase phase);
+
+namespace QTest
+{
+ /*! \internal
+ This function creates a mouse wheel event and calls
+ QWindowSystemInterface::handleWheelEvent().
+ \a window is the window that should be receiving the event and \a pos
+ provides the location of the event in the window's local coordinates.
+ \a angleDelta contains the wheel rotation angle, while \a pixelDelta
+ contains the scrolling distance in pixels on screen.
+ The keyboard states at the time of the event are specified by \a stateKey.
+ The scrolling phase of the event is specified by \a phase.
+ */
+ [[maybe_unused]] static void wheelEvent(QWindow *window, QPointF pos,
+ QPoint angleDelta, QPoint pixelDelta = QPoint(0, 0),
+ Qt::KeyboardModifiers stateKey = Qt::NoModifier,
+ Qt::ScrollPhase phase = Qt::NoScrollPhase)
+ {
+ QTEST_ASSERT(window);
+
+ // pos is in window local coordinates
+ const QSize windowSize = window->geometry().size();
+ if (windowSize.width() <= pos.x() || windowSize.height() <= pos.y()) {
+ qWarning("Mouse event at %d, %d occurs outside target window (%dx%d).",
+ static_cast<int>(pos.x()), static_cast<int>(pos.y()), windowSize.width(), windowSize.height());
+ }
+
+ if (pos.isNull())
+ pos = QPoint(window->width() / 2, window->height() / 2);
+
+ QPointF global = window->mapToGlobal(pos);
+ QPointer<QWindow> w(window);
+
+ if (angleDelta.isNull() && pixelDelta.isNull())
+ qWarning("No angle or pixel delta specified.");
+
+ qt_handleWheelEvent(w, pos, global, pixelDelta, angleDelta, stateKey, phase);
+ qApp->processEvents();
+ }
+}
+
+QT_END_NAMESPACE
+
+#endif // QTESTWHEEL_H
diff --git a/src/testlib/qttestglobal.h b/src/testlib/qttestglobal.h
index ee2f350b80..8ede78c2a2 100644
--- a/src/testlib/qttestglobal.h
+++ b/src/testlib/qttestglobal.h
@@ -4,10 +4,6 @@
#ifndef QTTESTGLOBAL_H
#define QTTESTGLOBAL_H
-#if 0
-#pragma qt_deprecates(qtest_global.h)
-#endif
-
#include <QtCore/qglobal.h>
#include <QtTest/qttestlib-config.h>
#include <QtTest/qttestexports.h>
diff --git a/src/testlib/removed_api.cpp b/src/testlib/removed_api.cpp
new file mode 100644
index 0000000000..00a53735a0
--- /dev/null
+++ b/src/testlib/removed_api.cpp
@@ -0,0 +1,29 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2024 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#define QT_TESTLIB_BUILD_REMOVED_API
+
+#include "qtest.h"
+
+#if QT_TESTLIB_REMOVED_SINCE(6, 8)
+
+QT_BEGIN_NAMESPACE
+
+namespace QTest {
+
+Q_TESTLIB_EXPORT char *toString(const void *p)
+{
+ const volatile void *ptr = p;
+ return toString(ptr);
+}
+
+} // namespace QTest
+
+QT_END_NAMESPACE
+
+// #include "qotherheader.h"
+// implement removed functions from qotherheader.h
+// order sections alphabetically to reduce chances of merge conflicts
+
+#endif // QT_TESTLIB_REMOVED_SINCE(6, 8)
diff --git a/src/testlib/selfcover.cmake b/src/testlib/selfcover.cmake
index 92580aacb0..9addaf645b 100644
--- a/src/testlib/selfcover.cmake
+++ b/src/testlib/selfcover.cmake
@@ -31,7 +31,7 @@ function(qt_internal_apply_testlib_coverage_options target)
--cs-mcc # enable Multiple Condition Coverage
--cs-mcdc # enable Multiple Condition / Decision Coverage
# (recommended for ISO 26262 ASIL A, B and C -- highly recommended for ASIL D)
- # https://doc.froglogic.com/squish-coco/4.1/codecoverage.html#sec%3Amcdc
+ # https://doc.qt.io/coco/code-coverage-analysis.html#mc-dc
)
target_compile_options(${target} PRIVATE
${testlib_coverage_options}