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.h889
-rw-r--r--src/testlib/3rdparty/qt_attribution.json22
-rw-r--r--src/testlib/3rdparty/valgrind_p.h568
-rw-r--r--src/testlib/CMakeLists.txt77
-rw-r--r--src/testlib/configure.cmake10
-rw-r--r--src/testlib/doc/includes/building-examples.qdocinc40
-rw-r--r--src/testlib/doc/qttestlib.qdocconf7
-rw-r--r--src/testlib/doc/snippets/CMakeLists.txt3
-rw-r--r--src/testlib/doc/snippets/code/CMakeLists.txt4
-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.qdoc53
-rw-r--r--src/testlib/doc/snippets/code/doc_src_qtqskip.cpp4
-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.cpp13
-rw-r--r--src/testlib/doc/snippets/code/src_qtestlib_qtestcase_snippet.cpp13
-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.qdoc104
-rw-r--r--src/testlib/doc/src/qttest-index.qdoc45
-rw-r--r--src/testlib/doc/src/qttestlib-manual.qdoc490
-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/qabstractitemmodeltester.cpp3
-rw-r--r--src/testlib/qabstracttestlogger.cpp23
-rw-r--r--src/testlib/qabstracttestlogger_p.h3
-rw-r--r--src/testlib/qappletestlogger.cpp10
-rw-r--r--src/testlib/qasciikey.cpp4
-rw-r--r--src/testlib/qbenchmark.cpp32
-rw-r--r--src/testlib/qbenchmark.h4
-rw-r--r--src/testlib/qbenchmark_p.h37
-rw-r--r--src/testlib/qbenchmarkevent.cpp16
-rw-r--r--src/testlib/qbenchmarkevent_p.h7
-rw-r--r--src/testlib/qbenchmarkmeasurement.cpp35
-rw-r--r--src/testlib/qbenchmarkmeasurement_p.h13
-rw-r--r--src/testlib/qbenchmarkmetric.cpp2
-rw-r--r--src/testlib/qbenchmarkperfevents.cpp216
-rw-r--r--src/testlib/qbenchmarkperfevents_p.h12
-rw-r--r--src/testlib/qbenchmarktimemeasurers_p.h12
-rw-r--r--src/testlib/qbenchmarkvalgrind.cpp21
-rw-r--r--src/testlib/qbenchmarkvalgrind_p.h6
-rw-r--r--src/testlib/qcomparisontesthelper.cpp22
-rw-r--r--src/testlib/qcomparisontesthelper_p.h373
-rw-r--r--src/testlib/qcsvbenchmarklogger.cpp5
-rw-r--r--src/testlib/qemulationdetector_p.h2
-rw-r--r--src/testlib/qjunittestlogger.cpp13
-rw-r--r--src/testlib/qplaintestlogger.cpp296
-rw-r--r--src/testlib/qplaintestlogger_p.h9
-rw-r--r--src/testlib/qpropertytesthelper_p.h117
-rw-r--r--src/testlib/qsignaldumper.cpp29
-rw-r--r--src/testlib/qsignalspy.cpp318
-rw-r--r--src/testlib/qsignalspy.h196
-rw-r--r--src/testlib/qsignalspy.qdoc124
-rw-r--r--src/testlib/qt_cmdline.cmake1
-rw-r--r--src/testlib/qtaptestlogger.cpp77
-rw-r--r--src/testlib/qteamcitylogger.cpp176
-rw-r--r--src/testlib/qteamcitylogger_p.h12
-rw-r--r--src/testlib/qtest.h381
-rw-r--r--src/testlib/qtest_gui.h37
-rw-r--r--src/testlib/qtest_network.h9
-rw-r--r--src/testlib/qtest_widgets.h9
-rw-r--r--src/testlib/qtestaccessible.h8
-rw-r--r--src/testlib/qtestassert.h5
-rw-r--r--src/testlib/qtestblacklist.cpp27
-rw-r--r--src/testlib/qtestblacklist_p.h2
-rw-r--r--src/testlib/qtestcase.cpp1554
-rw-r--r--src/testlib/qtestcase.h441
-rw-r--r--src/testlib/qtestcase.qdoc451
-rw-r--r--src/testlib/qtestcase_p.h35
-rw-r--r--src/testlib/qtestcrashhandler.cpp663
-rw-r--r--src/testlib/qtestcrashhandler_p.h251
-rw-r--r--src/testlib/qtestevent.h4
-rw-r--r--src/testlib/qtestevent.qdoc8
-rw-r--r--src/testlib/qtesteventloop.h15
-rw-r--r--src/testlib/qtestkeyboard.h2
-rw-r--r--src/testlib/qtestlog.cpp45
-rw-r--r--src/testlib/qtestlog_p.h5
-rw-r--r--src/testlib/qtestmouse.h16
-rw-r--r--src/testlib/qtestregistry.cpp36
-rw-r--r--src/testlib/qtestregistry_p.h47
-rw-r--r--src/testlib/qtestresult.cpp220
-rw-r--r--src/testlib/qtestresult_p.h13
-rw-r--r--src/testlib/qtesttable.cpp13
-rw-r--r--src/testlib/qtesttostring.h499
-rw-r--r--src/testlib/qtesttouch.h2
-rw-r--r--src/testlib/qtestutil_macos.mm2
-rw-r--r--src/testlib/qtestwheel.h72
-rw-r--r--src/testlib/qttestglobal.h9
-rw-r--r--src/testlib/qxmltestlogger.cpp4
-rw-r--r--src/testlib/removed_api.cpp29
-rw-r--r--src/testlib/selfcover.cmake5
97 files changed, 7213 insertions, 2811 deletions
diff --git a/src/testlib/3rdparty/linux_perf_event_p.h b/src/testlib/3rdparty/linux_perf_event_p.h
index 4f63c05d27..6f034f0e96 100644
--- a/src/testlib/3rdparty/linux_perf_event_p.h
+++ b/src/testlib/3rdparty/linux_perf_event_p.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
/*
* Performance events:
*
@@ -37,6 +38,21 @@ enum perf_type_id {
};
/*
+ * attr.config layout for type PERF_TYPE_HARDWARE and PERF_TYPE_HW_CACHE
+ * PERF_TYPE_HARDWARE: 0xEEEEEEEE000000AA
+ * AA: hardware event ID
+ * EEEEEEEE: PMU type ID
+ * PERF_TYPE_HW_CACHE: 0xEEEEEEEE00DDCCBB
+ * BB: hardware cache ID
+ * CC: hardware cache op ID
+ * DD: hardware cache op result ID
+ * EEEEEEEE: PMU type ID
+ * If the PMU type ID is 0, the PERF_TYPE_RAW will be applied.
+ */
+#define PERF_PMU_TYPE_SHIFT 32
+#define PERF_HW_EVENT_MASK 0xffffffff
+
+/*
* Generalized performance event event_id types, used by the
* attr.event_id parameter of the sys_perf_event_open()
* syscall:
@@ -109,6 +125,9 @@ enum perf_sw_ids {
PERF_COUNT_SW_PAGE_FAULTS_MAJ = 6,
PERF_COUNT_SW_ALIGNMENT_FAULTS = 7,
PERF_COUNT_SW_EMULATION_FAULTS = 8,
+ PERF_COUNT_SW_DUMMY = 9,
+ PERF_COUNT_SW_BPF_OUTPUT = 10,
+ PERF_COUNT_SW_CGROUP_SWITCHES = 11,
PERF_COUNT_SW_MAX, /* non-ABI */
};
@@ -132,10 +151,24 @@ enum perf_event_sample_format {
PERF_SAMPLE_BRANCH_STACK = 1U << 11,
PERF_SAMPLE_REGS_USER = 1U << 12,
PERF_SAMPLE_STACK_USER = 1U << 13,
-
- PERF_SAMPLE_MAX = 1U << 14, /* non-ABI */
+ PERF_SAMPLE_WEIGHT = 1U << 14,
+ PERF_SAMPLE_DATA_SRC = 1U << 15,
+ PERF_SAMPLE_IDENTIFIER = 1U << 16,
+ PERF_SAMPLE_TRANSACTION = 1U << 17,
+ PERF_SAMPLE_REGS_INTR = 1U << 18,
+ PERF_SAMPLE_PHYS_ADDR = 1U << 19,
+ PERF_SAMPLE_AUX = 1U << 20,
+ PERF_SAMPLE_CGROUP = 1U << 21,
+ PERF_SAMPLE_DATA_PAGE_SIZE = 1U << 22,
+ PERF_SAMPLE_CODE_PAGE_SIZE = 1U << 23,
+ PERF_SAMPLE_WEIGHT_STRUCT = 1U << 24,
+
+ PERF_SAMPLE_MAX = 1U << 25, /* non-ABI */
+
+ __PERF_SAMPLE_CALLCHAIN_EARLY = 1ULL << 63, /* non-ABI; internal use */
};
+#define PERF_SAMPLE_WEIGHT_TYPE (PERF_SAMPLE_WEIGHT | PERF_SAMPLE_WEIGHT_STRUCT)
/*
* values to program into branch_sample_type when PERF_SAMPLE_BRANCH is set
*
@@ -146,17 +179,81 @@ enum perf_event_sample_format {
* The branch types can be combined, however BRANCH_ANY covers all types
* of branches and therefore it supersedes all the other types.
*/
+enum perf_branch_sample_type_shift {
+ PERF_SAMPLE_BRANCH_USER_SHIFT = 0, /* user branches */
+ PERF_SAMPLE_BRANCH_KERNEL_SHIFT = 1, /* kernel branches */
+ PERF_SAMPLE_BRANCH_HV_SHIFT = 2, /* hypervisor branches */
+
+ PERF_SAMPLE_BRANCH_ANY_SHIFT = 3, /* any branch types */
+ PERF_SAMPLE_BRANCH_ANY_CALL_SHIFT = 4, /* any call branch */
+ PERF_SAMPLE_BRANCH_ANY_RETURN_SHIFT = 5, /* any return branch */
+ PERF_SAMPLE_BRANCH_IND_CALL_SHIFT = 6, /* indirect calls */
+ PERF_SAMPLE_BRANCH_ABORT_TX_SHIFT = 7, /* transaction aborts */
+ PERF_SAMPLE_BRANCH_IN_TX_SHIFT = 8, /* in transaction */
+ PERF_SAMPLE_BRANCH_NO_TX_SHIFT = 9, /* not in transaction */
+ PERF_SAMPLE_BRANCH_COND_SHIFT = 10, /* conditional branches */
+
+ PERF_SAMPLE_BRANCH_CALL_STACK_SHIFT = 11, /* call/ret stack */
+ PERF_SAMPLE_BRANCH_IND_JUMP_SHIFT = 12, /* indirect jumps */
+ PERF_SAMPLE_BRANCH_CALL_SHIFT = 13, /* direct call */
+
+ PERF_SAMPLE_BRANCH_NO_FLAGS_SHIFT = 14, /* no flags */
+ PERF_SAMPLE_BRANCH_NO_CYCLES_SHIFT = 15, /* no cycles */
+
+ PERF_SAMPLE_BRANCH_TYPE_SAVE_SHIFT = 16, /* save branch type */
+
+ PERF_SAMPLE_BRANCH_HW_INDEX_SHIFT = 17, /* save low level index of raw branch records */
+
+ PERF_SAMPLE_BRANCH_MAX_SHIFT /* non-ABI */
+};
+
enum perf_branch_sample_type {
- PERF_SAMPLE_BRANCH_USER = 1U << 0, /* user branches */
- PERF_SAMPLE_BRANCH_KERNEL = 1U << 1, /* kernel branches */
- PERF_SAMPLE_BRANCH_HV = 1U << 2, /* hypervisor branches */
+ PERF_SAMPLE_BRANCH_USER = 1U << PERF_SAMPLE_BRANCH_USER_SHIFT,
+ PERF_SAMPLE_BRANCH_KERNEL = 1U << PERF_SAMPLE_BRANCH_KERNEL_SHIFT,
+ PERF_SAMPLE_BRANCH_HV = 1U << PERF_SAMPLE_BRANCH_HV_SHIFT,
+
+ PERF_SAMPLE_BRANCH_ANY = 1U << PERF_SAMPLE_BRANCH_ANY_SHIFT,
+ PERF_SAMPLE_BRANCH_ANY_CALL = 1U << PERF_SAMPLE_BRANCH_ANY_CALL_SHIFT,
+ PERF_SAMPLE_BRANCH_ANY_RETURN = 1U << PERF_SAMPLE_BRANCH_ANY_RETURN_SHIFT,
+ PERF_SAMPLE_BRANCH_IND_CALL = 1U << PERF_SAMPLE_BRANCH_IND_CALL_SHIFT,
+ PERF_SAMPLE_BRANCH_ABORT_TX = 1U << PERF_SAMPLE_BRANCH_ABORT_TX_SHIFT,
+ PERF_SAMPLE_BRANCH_IN_TX = 1U << PERF_SAMPLE_BRANCH_IN_TX_SHIFT,
+ PERF_SAMPLE_BRANCH_NO_TX = 1U << PERF_SAMPLE_BRANCH_NO_TX_SHIFT,
+ PERF_SAMPLE_BRANCH_COND = 1U << PERF_SAMPLE_BRANCH_COND_SHIFT,
+
+ PERF_SAMPLE_BRANCH_CALL_STACK = 1U << PERF_SAMPLE_BRANCH_CALL_STACK_SHIFT,
+ PERF_SAMPLE_BRANCH_IND_JUMP = 1U << PERF_SAMPLE_BRANCH_IND_JUMP_SHIFT,
+ PERF_SAMPLE_BRANCH_CALL = 1U << PERF_SAMPLE_BRANCH_CALL_SHIFT,
- PERF_SAMPLE_BRANCH_ANY = 1U << 3, /* any branch types */
- PERF_SAMPLE_BRANCH_ANY_CALL = 1U << 4, /* any call branch */
- PERF_SAMPLE_BRANCH_ANY_RETURN = 1U << 5, /* any return branch */
- PERF_SAMPLE_BRANCH_IND_CALL = 1U << 6, /* indirect calls */
+ PERF_SAMPLE_BRANCH_NO_FLAGS = 1U << PERF_SAMPLE_BRANCH_NO_FLAGS_SHIFT,
+ PERF_SAMPLE_BRANCH_NO_CYCLES = 1U << PERF_SAMPLE_BRANCH_NO_CYCLES_SHIFT,
- PERF_SAMPLE_BRANCH_MAX = 1U << 7, /* non-ABI */
+ PERF_SAMPLE_BRANCH_TYPE_SAVE =
+ 1U << PERF_SAMPLE_BRANCH_TYPE_SAVE_SHIFT,
+
+ PERF_SAMPLE_BRANCH_HW_INDEX = 1U << PERF_SAMPLE_BRANCH_HW_INDEX_SHIFT,
+
+ PERF_SAMPLE_BRANCH_MAX = 1U << PERF_SAMPLE_BRANCH_MAX_SHIFT,
+};
+
+/*
+ * Common flow change classification
+ */
+enum {
+ PERF_BR_UNKNOWN = 0, /* unknown */
+ PERF_BR_COND = 1, /* conditional */
+ PERF_BR_UNCOND = 2, /* unconditional */
+ PERF_BR_IND = 3, /* indirect */
+ PERF_BR_CALL = 4, /* function call */
+ PERF_BR_IND_CALL = 5, /* indirect function call */
+ PERF_BR_RET = 6, /* function return */
+ PERF_BR_SYSCALL = 7, /* syscall */
+ PERF_BR_SYSRET = 8, /* syscall return */
+ PERF_BR_COND_CALL = 9, /* conditional function call */
+ PERF_BR_COND_RET = 10, /* conditional function return */
+ PERF_BR_ERET = 11, /* exception return */
+ PERF_BR_IRQ = 12, /* irq */
+ PERF_BR_MAX,
};
#define PERF_SAMPLE_BRANCH_PLM_ALL \
@@ -174,6 +271,28 @@ enum perf_sample_regs_abi {
};
/*
+ * Values for the memory transaction event qualifier, mostly for
+ * abort events. Multiple bits can be set.
+ */
+enum {
+ PERF_TXN_ELISION = (1 << 0), /* From elision */
+ PERF_TXN_TRANSACTION = (1 << 1), /* From transaction */
+ PERF_TXN_SYNC = (1 << 2), /* Instruction is related */
+ PERF_TXN_ASYNC = (1 << 3), /* Instruction not related */
+ PERF_TXN_RETRY = (1 << 4), /* Retry possible */
+ PERF_TXN_CONFLICT = (1 << 5), /* Conflict abort */
+ PERF_TXN_CAPACITY_WRITE = (1 << 6), /* Capacity write abort */
+ PERF_TXN_CAPACITY_READ = (1 << 7), /* Capacity read abort */
+
+ PERF_TXN_MAX = (1 << 8), /* non-ABI */
+
+ /* bits 32..63 are reserved for the abort code */
+
+ PERF_TXN_ABORT_MASK = (0xffffffffULL << 32),
+ PERF_TXN_ABORT_SHIFT = 32,
+};
+
+/*
* The format of the data returned by read() on a perf event fd,
* as specified by attr.read_format:
*
@@ -182,6 +301,7 @@ enum perf_sample_regs_abi {
* { u64 time_enabled; } && PERF_FORMAT_TOTAL_TIME_ENABLED
* { u64 time_running; } && PERF_FORMAT_TOTAL_TIME_RUNNING
* { u64 id; } && PERF_FORMAT_ID
+ * { u64 lost; } && PERF_FORMAT_LOST
* } && !PERF_FORMAT_GROUP
*
* { u64 nr;
@@ -189,6 +309,7 @@ enum perf_sample_regs_abi {
* { u64 time_running; } && PERF_FORMAT_TOTAL_TIME_RUNNING
* { u64 value;
* { u64 id; } && PERF_FORMAT_ID
+ * { u64 lost; } && PERF_FORMAT_LOST
* } cntr[nr];
* } && PERF_FORMAT_GROUP
* };
@@ -198,8 +319,9 @@ enum perf_event_read_format {
PERF_FORMAT_TOTAL_TIME_RUNNING = 1U << 1,
PERF_FORMAT_ID = 1U << 2,
PERF_FORMAT_GROUP = 1U << 3,
+ PERF_FORMAT_LOST = 1U << 4,
- PERF_FORMAT_MAX = 1U << 4, /* non-ABI */
+ PERF_FORMAT_MAX = 1U << 5, /* non-ABI */
};
#define PERF_ATTR_SIZE_VER0 64 /* sizeof first published struct */
@@ -207,9 +329,16 @@ enum perf_event_read_format {
#define PERF_ATTR_SIZE_VER2 80 /* add: branch_sample_type */
#define PERF_ATTR_SIZE_VER3 96 /* add: sample_regs_user */
/* add: sample_stack_user */
+#define PERF_ATTR_SIZE_VER4 104 /* add: sample_regs_intr */
+#define PERF_ATTR_SIZE_VER5 112 /* add: aux_watermark */
+#define PERF_ATTR_SIZE_VER6 120 /* add: aux_sample_size */
+#define PERF_ATTR_SIZE_VER7 128 /* add: sig_data */
/*
* Hardware event_id to monitor via a performance monitoring event:
+ *
+ * @sample_max_stack: Max number of frame pointers in a callchain,
+ * should be < /proc/sys/kernel/perf_event_max_stack
*/
struct perf_event_attr {
@@ -270,8 +399,22 @@ struct perf_event_attr {
exclude_callchain_kernel : 1, /* exclude kernel callchains */
exclude_callchain_user : 1, /* exclude user callchains */
-
- __reserved_1 : 41;
+ mmap2 : 1, /* include mmap with inode data */
+ comm_exec : 1, /* flag comm events that are due to an exec */
+ use_clockid : 1, /* use @clockid for time fields */
+ context_switch : 1, /* context switch data */
+ write_backward : 1, /* Write ring buffer from end to beginning */
+ namespaces : 1, /* include namespaces data */
+ ksymbol : 1, /* include ksymbol events */
+ bpf_event : 1, /* include bpf events */
+ aux_output : 1, /* generate AUX records instead of events */
+ cgroup : 1, /* include cgroup events */
+ text_poke : 1, /* include text poke events */
+ build_id : 1, /* use build id in mmap2 events */
+ inherit_thread : 1, /* children only inherit if cloned with CLONE_THREAD */
+ remove_on_exec : 1, /* event is removed from task on exec */
+ sigtrap : 1, /* send synchronous SIGTRAP on event */
+ __reserved_1 : 26;
union {
__u32 wakeup_events; /* wakeup every n events */
@@ -281,10 +424,14 @@ struct perf_event_attr {
__u32 bp_type;
union {
__u64 bp_addr;
+ __u64 kprobe_func; /* for perf_kprobe */
+ __u64 uprobe_path; /* for perf_uprobe */
__u64 config1; /* extension of config */
};
union {
__u64 bp_len;
+ __u64 kprobe_addr; /* when kprobe_func == NULL */
+ __u64 probe_offset; /* for perf_[k,u]probe */
__u64 config2; /* extension of config1 */
};
__u64 branch_sample_type; /* enum perf_branch_sample_type */
@@ -300,22 +447,71 @@ struct perf_event_attr {
*/
__u32 sample_stack_user;
- /* Align to u64. */
- __u32 __reserved_2;
+ __s32 clockid;
+ /*
+ * Defines set of regs to dump for each sample
+ * state captured on:
+ * - precise = 0: PMU interrupt
+ * - precise > 0: sampled instruction
+ *
+ * See asm/perf_regs.h for details.
+ */
+ __u64 sample_regs_intr;
+
+ /*
+ * Wakeup watermark for AUX area
+ */
+ __u32 aux_watermark;
+ __u16 sample_max_stack;
+ __u16 __reserved_2;
+ __u32 aux_sample_size;
+ __u32 __reserved_3;
+
+ /*
+ * User provided data if sigtrap=1, passed back to user via
+ * siginfo_t::si_perf_data, e.g. to permit user to identify the event.
+ * Note, siginfo_t::si_perf_data is long-sized, and sig_data will be
+ * truncated accordingly on 32 bit architectures.
+ */
+ __u64 sig_data;
};
-#define perf_flags(attr) (*(&(attr)->read_format + 1))
+/*
+ * Structure used by below PERF_EVENT_IOC_QUERY_BPF command
+ * to query bpf programs attached to the same perf tracepoint
+ * as the given perf event.
+ */
+struct perf_event_query_bpf {
+ /*
+ * The below ids array length
+ */
+ __u32 ids_len;
+ /*
+ * Set by the kernel to indicate the number of
+ * available programs
+ */
+ __u32 prog_cnt;
+ /*
+ * User provided buffer to store program ids
+ */
+ __u32 ids[];
+};
/*
* Ioctls that can be done on a perf event fd:
*/
-#define PERF_EVENT_IOC_ENABLE _IO ('$', 0)
-#define PERF_EVENT_IOC_DISABLE _IO ('$', 1)
-#define PERF_EVENT_IOC_REFRESH _IO ('$', 2)
-#define PERF_EVENT_IOC_RESET _IO ('$', 3)
-#define PERF_EVENT_IOC_PERIOD _IOW('$', 4, __u64)
-#define PERF_EVENT_IOC_SET_OUTPUT _IO ('$', 5)
-#define PERF_EVENT_IOC_SET_FILTER _IOW('$', 6, char *)
+#define PERF_EVENT_IOC_ENABLE _IO ('$', 0)
+#define PERF_EVENT_IOC_DISABLE _IO ('$', 1)
+#define PERF_EVENT_IOC_REFRESH _IO ('$', 2)
+#define PERF_EVENT_IOC_RESET _IO ('$', 3)
+#define PERF_EVENT_IOC_PERIOD _IOW('$', 4, __u64)
+#define PERF_EVENT_IOC_SET_OUTPUT _IO ('$', 5)
+#define PERF_EVENT_IOC_SET_FILTER _IOW('$', 6, char *)
+#define PERF_EVENT_IOC_ID _IOR('$', 7, __u64 *)
+#define PERF_EVENT_IOC_SET_BPF _IOW('$', 8, __u32)
+#define PERF_EVENT_IOC_PAUSE_OUTPUT _IOW('$', 9, __u32)
+#define PERF_EVENT_IOC_QUERY_BPF _IOWR('$', 10, struct perf_event_query_bpf *)
+#define PERF_EVENT_IOC_MODIFY_ATTRIBUTES _IOW('$', 11, struct perf_event_attr *)
enum perf_event_ioc_flags {
PERF_IOC_FLAG_GROUP = 1U << 0,
@@ -331,7 +527,7 @@ struct perf_event_mmap_page {
/*
* Bits needed to read the hw events in user-space.
*
- * u32 seq, time_mult, time_shift, idx, width;
+ * u32 seq, time_mult, time_shift, index, width;
* u64 count, enabled, running;
* u64 cyc, time_offset;
* s64 pmc = 0;
@@ -350,11 +546,11 @@ struct perf_event_mmap_page {
* time_shift = pc->time_shift;
* }
*
- * idx = pc->index;
+ * index = pc->index;
* count = pc->offset;
- * if (pc->cap_usr_rdpmc && idx) {
+ * if (pc->cap_user_rdpmc && index) {
* width = pc->pmc_width;
- * pmc = rdpmc(idx - 1);
+ * pmc = rdpmc(index - 1);
* }
*
* barrier();
@@ -370,13 +566,20 @@ struct perf_event_mmap_page {
__u64 time_running; /* time event on cpu */
union {
__u64 capabilities;
- __u64 cap_usr_time : 1,
- cap_usr_rdpmc : 1,
- cap_____res : 62;
+ struct {
+ __u64 cap_bit0 : 1, /* Always 0, deprecated, see commit 860f085b74e9 */
+ cap_bit0_is_deprecated : 1, /* Always 1, signals that bit 0 is zero */
+
+ cap_user_rdpmc : 1, /* The RDPMC instruction can be used to read counts */
+ cap_user_time : 1, /* The time_{shift,mult,offset} fields are used */
+ cap_user_time_zero : 1, /* The time_zero field is used */
+ cap_user_time_short : 1, /* the time_{cycle,mask} fields are used */
+ cap_____res : 58;
+ };
};
/*
- * If cap_usr_rdpmc this field provides the bit-width of the value
+ * If cap_user_rdpmc this field provides the bit-width of the value
* read using the rdpmc() or equivalent instruction. This can be used
* to sign extend the result like:
*
@@ -394,16 +597,16 @@ struct perf_event_mmap_page {
* u64 delta;
*
* quot = (cyc >> time_shift);
- * rem = cyc & ((1 << time_shift) - 1);
+ * rem = cyc & (((u64)1 << time_shift) - 1);
* delta = time_offset + quot * time_mult +
* ((rem * time_mult) >> time_shift);
*
* Where time_offset,time_mult,time_shift and cyc are read in the
* seqcount loop described above. This delta can then be added to
- * enabled and possible running (if idx), improving the scaling:
+ * enabled and possible running (if index), improving the scaling:
*
* enabled += delta;
- * if (idx)
+ * if (index)
* running += delta;
*
* quot = count / running;
@@ -413,28 +616,101 @@ struct perf_event_mmap_page {
__u16 time_shift;
__u32 time_mult;
__u64 time_offset;
+ /*
+ * If cap_usr_time_zero, the hardware clock (e.g. TSC) can be calculated
+ * from sample timestamps.
+ *
+ * time = timestamp - time_zero;
+ * quot = time / time_mult;
+ * rem = time % time_mult;
+ * cyc = (quot << time_shift) + (rem << time_shift) / time_mult;
+ *
+ * And vice versa:
+ *
+ * quot = cyc >> time_shift;
+ * rem = cyc & (((u64)1 << time_shift) - 1);
+ * timestamp = time_zero + quot * time_mult +
+ * ((rem * time_mult) >> time_shift);
+ */
+ __u64 time_zero;
+
+ __u32 size; /* Header size up to __reserved[] fields. */
+ __u32 __reserved_1;
+
+ /*
+ * If cap_usr_time_short, the hardware clock is less than 64bit wide
+ * and we must compute the 'cyc' value, as used by cap_usr_time, as:
+ *
+ * cyc = time_cycles + ((cyc - time_cycles) & time_mask)
+ *
+ * NOTE: this form is explicitly chosen such that cap_usr_time_short
+ * is a correction on top of cap_usr_time, and code that doesn't
+ * know about cap_usr_time_short still works under the assumption
+ * the counter doesn't wrap.
+ */
+ __u64 time_cycles;
+ __u64 time_mask;
/*
* Hole for extension of the self monitor capabilities
*/
- __u64 __reserved[120]; /* align to 1k */
+ __u8 __reserved[116*8]; /* align to 1k. */
/*
* Control data for the mmap() data buffer.
*
- * User-space reading the @data_head value should issue an rmb(), on
- * SMP capable platforms, after reading this value -- see
- * perf_event_wakeup().
+ * User-space reading the @data_head value should issue an smp_rmb(),
+ * after reading this value.
*
* When the mapping is PROT_WRITE the @data_tail value should be
- * written by userspace to reflect the last read data. In this case
- * the kernel will not over-write unread data.
+ * written by userspace to reflect the last read data, after issueing
+ * an smp_mb() to separate the data read from the ->data_tail store.
+ * In this case the kernel will not over-write unread data.
+ *
+ * See perf_output_put_handle() for the data ordering.
+ *
+ * data_{offset,size} indicate the location and size of the perf record
+ * buffer within the mmapped area.
*/
__u64 data_head; /* head in the data section */
__u64 data_tail; /* user-space written tail */
+ __u64 data_offset; /* where the buffer starts */
+ __u64 data_size; /* data buffer size */
+
+ /*
+ * AUX area is defined by aux_{offset,size} fields that should be set
+ * by the userspace, so that
+ *
+ * aux_offset >= data_offset + data_size
+ *
+ * prior to mmap()ing it. Size of the mmap()ed area should be aux_size.
+ *
+ * Ring buffer pointers aux_{head,tail} have the same semantics as
+ * data_{head,tail} and same ordering rules apply.
+ */
+ __u64 aux_head;
+ __u64 aux_tail;
+ __u64 aux_offset;
+ __u64 aux_size;
};
+/*
+ * The current state of perf_event_header::misc bits usage:
+ * ('|' used bit, '-' unused bit)
+ *
+ * 012 CDEF
+ * |||---------||||
+ *
+ * Where:
+ * 0-2 CPUMODE_MASK
+ *
+ * C PROC_MAP_PARSE_TIMEOUT
+ * D MMAP_DATA / COMM_EXEC / FORK_EXEC / SWITCH_OUT
+ * E MMAP_BUILD_ID / EXACT_IP / SCHED_OUT_PREEMPT
+ * F (reserved)
+ */
+
#define PERF_RECORD_MISC_CPUMODE_MASK (7 << 0)
#define PERF_RECORD_MISC_CPUMODE_UNKNOWN (0 << 0)
#define PERF_RECORD_MISC_KERNEL (1 << 0)
@@ -444,11 +720,45 @@ struct perf_event_mmap_page {
#define PERF_RECORD_MISC_GUEST_USER (5 << 0)
/*
- * Indicates that the content of PERF_SAMPLE_IP points to
- * the actual instruction that triggered the event. See also
- * perf_event_attr::precise_ip.
+ * Indicates that /proc/PID/maps parsing are truncated by time out.
+ */
+#define PERF_RECORD_MISC_PROC_MAP_PARSE_TIMEOUT (1 << 12)
+/*
+ * Following PERF_RECORD_MISC_* are used on different
+ * events, so can reuse the same bit position:
+ *
+ * PERF_RECORD_MISC_MMAP_DATA - PERF_RECORD_MMAP* events
+ * PERF_RECORD_MISC_COMM_EXEC - PERF_RECORD_COMM event
+ * PERF_RECORD_MISC_FORK_EXEC - PERF_RECORD_FORK event (perf internal)
+ * PERF_RECORD_MISC_SWITCH_OUT - PERF_RECORD_SWITCH* events
+ */
+#define PERF_RECORD_MISC_MMAP_DATA (1 << 13)
+#define PERF_RECORD_MISC_COMM_EXEC (1 << 13)
+#define PERF_RECORD_MISC_FORK_EXEC (1 << 13)
+#define PERF_RECORD_MISC_SWITCH_OUT (1 << 13)
+/*
+ * These PERF_RECORD_MISC_* flags below are safely reused
+ * for the following events:
+ *
+ * PERF_RECORD_MISC_EXACT_IP - PERF_RECORD_SAMPLE of precise events
+ * PERF_RECORD_MISC_SWITCH_OUT_PREEMPT - PERF_RECORD_SWITCH* events
+ * PERF_RECORD_MISC_MMAP_BUILD_ID - PERF_RECORD_MMAP2 event
+ *
+ *
+ * PERF_RECORD_MISC_EXACT_IP:
+ * Indicates that the content of PERF_SAMPLE_IP points to
+ * the actual instruction that triggered the event. See also
+ * perf_event_attr::precise_ip.
+ *
+ * PERF_RECORD_MISC_SWITCH_OUT_PREEMPT:
+ * Indicates that thread was preempted in TASK_RUNNING state.
+ *
+ * PERF_RECORD_MISC_MMAP_BUILD_ID:
+ * Indicates that mmap2 event carries build id data.
*/
#define PERF_RECORD_MISC_EXACT_IP (1 << 14)
+#define PERF_RECORD_MISC_SWITCH_OUT_PREEMPT (1 << 14)
+#define PERF_RECORD_MISC_MMAP_BUILD_ID (1 << 14)
/*
* Reserve the last bit to indicate some extended misc field
*/
@@ -460,18 +770,50 @@ struct perf_event_header {
__u16 size;
};
+struct perf_ns_link_info {
+ __u64 dev;
+ __u64 ino;
+};
+
+enum {
+ NET_NS_INDEX = 0,
+ UTS_NS_INDEX = 1,
+ IPC_NS_INDEX = 2,
+ PID_NS_INDEX = 3,
+ USER_NS_INDEX = 4,
+ MNT_NS_INDEX = 5,
+ CGROUP_NS_INDEX = 6,
+
+ NR_NAMESPACES, /* number of available namespaces */
+};
+
enum perf_event_type {
/*
* If perf_event_attr.sample_id_all is set then all event types will
* have the sample_type selected fields related to where/when
- * (identity) an event took place (TID, TIME, ID, CPU, STREAM_ID)
- * described in PERF_RECORD_SAMPLE below, it will be stashed just after
- * the perf_event_header and the fields already present for the existing
- * fields, i.e. at the end of the payload. That way a newer perf.data
- * file will be supported by older perf tools, with these new optional
- * fields being ignored.
+ * (identity) an event took place (TID, TIME, ID, STREAM_ID, CPU,
+ * IDENTIFIER) described in PERF_RECORD_SAMPLE below, it will be stashed
+ * just after the perf_event_header and the fields already present for
+ * the existing fields, i.e. at the end of the payload. That way a newer
+ * perf.data file will be supported by older perf tools, with these new
+ * optional fields being ignored.
*
+ * struct sample_id {
+ * { u32 pid, tid; } && PERF_SAMPLE_TID
+ * { u64 time; } && PERF_SAMPLE_TIME
+ * { u64 id; } && PERF_SAMPLE_ID
+ * { u64 stream_id;} && PERF_SAMPLE_STREAM_ID
+ * { u32 cpu, res; } && PERF_SAMPLE_CPU
+ * { u64 id; } && PERF_SAMPLE_IDENTIFIER
+ * } && perf_event_attr::sample_id_all
+ *
+ * Note that PERF_SAMPLE_IDENTIFIER duplicates PERF_SAMPLE_ID. The
+ * advantage of PERF_SAMPLE_IDENTIFIER is that its position is fixed
+ * relative to header.size.
+ */
+
+ /*
* The MMAP events record the PROT_EXEC mappings so that we can
* correlate userspace IPs to code. They have the following structure:
*
@@ -483,6 +825,7 @@ enum perf_event_type {
* u64 len;
* u64 pgoff;
* char filename[];
+ * struct sample_id sample_id;
* };
*/
PERF_RECORD_MMAP = 1,
@@ -492,6 +835,7 @@ enum perf_event_type {
* struct perf_event_header header;
* u64 id;
* u64 lost;
+ * struct sample_id sample_id;
* };
*/
PERF_RECORD_LOST = 2,
@@ -502,6 +846,7 @@ enum perf_event_type {
*
* u32 pid, tid;
* char comm[];
+ * struct sample_id sample_id;
* };
*/
PERF_RECORD_COMM = 3,
@@ -512,6 +857,7 @@ enum perf_event_type {
* u32 pid, ppid;
* u32 tid, ptid;
* u64 time;
+ * struct sample_id sample_id;
* };
*/
PERF_RECORD_EXIT = 4,
@@ -522,6 +868,7 @@ enum perf_event_type {
* u64 time;
* u64 id;
* u64 stream_id;
+ * struct sample_id sample_id;
* };
*/
PERF_RECORD_THROTTLE = 5,
@@ -533,6 +880,7 @@ enum perf_event_type {
* u32 pid, ppid;
* u32 tid, ptid;
* u64 time;
+ * struct sample_id sample_id;
* };
*/
PERF_RECORD_FORK = 7,
@@ -543,6 +891,7 @@ enum perf_event_type {
* u32 pid, tid;
*
* struct read_format values;
+ * struct sample_id sample_id;
* };
*/
PERF_RECORD_READ = 8,
@@ -551,6 +900,13 @@ enum perf_event_type {
* struct {
* struct perf_event_header header;
*
+ * #
+ * # Note that PERF_SAMPLE_IDENTIFIER duplicates PERF_SAMPLE_ID.
+ * # The advantage of PERF_SAMPLE_IDENTIFIER is that its position
+ * # is fixed relative to header.
+ * #
+ *
+ * { u64 id; } && PERF_SAMPLE_IDENTIFIER
* { u64 ip; } && PERF_SAMPLE_IP
* { u32 pid, tid; } && PERF_SAMPLE_TID
* { u64 time; } && PERF_SAMPLE_TIME
@@ -579,7 +935,10 @@ enum perf_event_type {
* { u32 size;
* char data[size];}&& PERF_SAMPLE_RAW
*
- * { u64 from, to, flags } lbr[nr];} && PERF_SAMPLE_BRANCH_STACK
+ * { u64 nr;
+ * { u64 hw_idx; } && PERF_SAMPLE_BRANCH_HW_INDEX
+ * { u64 from, to, flags } lbr[nr];
+ * } && PERF_SAMPLE_BRANCH_STACK
*
* { u64 abi; # enum perf_sample_regs_abi
* u64 regs[weight(mask)]; } && PERF_SAMPLE_REGS_USER
@@ -587,14 +946,248 @@ enum perf_event_type {
* { u64 size;
* char data[size];
* u64 dyn_size; } && PERF_SAMPLE_STACK_USER
+ *
+ * { union perf_sample_weight
+ * {
+ * u64 full; && PERF_SAMPLE_WEIGHT
+ * #if defined(__LITTLE_ENDIAN_BITFIELD)
+ * struct {
+ * u32 var1_dw;
+ * u16 var2_w;
+ * u16 var3_w;
+ * } && PERF_SAMPLE_WEIGHT_STRUCT
+ * #elif defined(__BIG_ENDIAN_BITFIELD)
+ * struct {
+ * u16 var3_w;
+ * u16 var2_w;
+ * u32 var1_dw;
+ * } && PERF_SAMPLE_WEIGHT_STRUCT
+ * #endif
+ * }
+ * }
+ * { u64 data_src; } && PERF_SAMPLE_DATA_SRC
+ * { u64 transaction; } && PERF_SAMPLE_TRANSACTION
+ * { u64 abi; # enum perf_sample_regs_abi
+ * u64 regs[weight(mask)]; } && PERF_SAMPLE_REGS_INTR
+ * { u64 phys_addr;} && PERF_SAMPLE_PHYS_ADDR
+ * { u64 size;
+ * char data[size]; } && PERF_SAMPLE_AUX
+ * { u64 data_page_size;} && PERF_SAMPLE_DATA_PAGE_SIZE
+ * { u64 code_page_size;} && PERF_SAMPLE_CODE_PAGE_SIZE
* };
*/
PERF_RECORD_SAMPLE = 9,
+ /*
+ * The MMAP2 records are an augmented version of MMAP, they add
+ * maj, min, ino numbers to be used to uniquely identify each mapping
+ *
+ * struct {
+ * struct perf_event_header header;
+ *
+ * u32 pid, tid;
+ * u64 addr;
+ * u64 len;
+ * u64 pgoff;
+ * union {
+ * struct {
+ * u32 maj;
+ * u32 min;
+ * u64 ino;
+ * u64 ino_generation;
+ * };
+ * struct {
+ * u8 build_id_size;
+ * u8 __reserved_1;
+ * u16 __reserved_2;
+ * u8 build_id[20];
+ * };
+ * };
+ * u32 prot, flags;
+ * char filename[];
+ * struct sample_id sample_id;
+ * };
+ */
+ PERF_RECORD_MMAP2 = 10,
+
+ /*
+ * Records that new data landed in the AUX buffer part.
+ *
+ * struct {
+ * struct perf_event_header header;
+ *
+ * u64 aux_offset;
+ * u64 aux_size;
+ * u64 flags;
+ * struct sample_id sample_id;
+ * };
+ */
+ PERF_RECORD_AUX = 11,
+
+ /*
+ * Indicates that instruction trace has started
+ *
+ * struct {
+ * struct perf_event_header header;
+ * u32 pid;
+ * u32 tid;
+ * struct sample_id sample_id;
+ * };
+ */
+ PERF_RECORD_ITRACE_START = 12,
+
+ /*
+ * Records the dropped/lost sample number.
+ *
+ * struct {
+ * struct perf_event_header header;
+ *
+ * u64 lost;
+ * struct sample_id sample_id;
+ * };
+ */
+ PERF_RECORD_LOST_SAMPLES = 13,
+
+ /*
+ * Records a context switch in or out (flagged by
+ * PERF_RECORD_MISC_SWITCH_OUT). See also
+ * PERF_RECORD_SWITCH_CPU_WIDE.
+ *
+ * struct {
+ * struct perf_event_header header;
+ * struct sample_id sample_id;
+ * };
+ */
+ PERF_RECORD_SWITCH = 14,
+
+ /*
+ * CPU-wide version of PERF_RECORD_SWITCH with next_prev_pid and
+ * next_prev_tid that are the next (switching out) or previous
+ * (switching in) pid/tid.
+ *
+ * struct {
+ * struct perf_event_header header;
+ * u32 next_prev_pid;
+ * u32 next_prev_tid;
+ * struct sample_id sample_id;
+ * };
+ */
+ PERF_RECORD_SWITCH_CPU_WIDE = 15,
+
+ /*
+ * struct {
+ * struct perf_event_header header;
+ * u32 pid;
+ * u32 tid;
+ * u64 nr_namespaces;
+ * { u64 dev, inode; } [nr_namespaces];
+ * struct sample_id sample_id;
+ * };
+ */
+ PERF_RECORD_NAMESPACES = 16,
+
+ /*
+ * Record ksymbol register/unregister events:
+ *
+ * struct {
+ * struct perf_event_header header;
+ * u64 addr;
+ * u32 len;
+ * u16 ksym_type;
+ * u16 flags;
+ * char name[];
+ * struct sample_id sample_id;
+ * };
+ */
+ PERF_RECORD_KSYMBOL = 17,
+
+ /*
+ * Record bpf events:
+ * enum perf_bpf_event_type {
+ * PERF_BPF_EVENT_UNKNOWN = 0,
+ * PERF_BPF_EVENT_PROG_LOAD = 1,
+ * PERF_BPF_EVENT_PROG_UNLOAD = 2,
+ * };
+ *
+ * struct {
+ * struct perf_event_header header;
+ * u16 type;
+ * u16 flags;
+ * u32 id;
+ * u8 tag[BPF_TAG_SIZE];
+ * struct sample_id sample_id;
+ * };
+ */
+ PERF_RECORD_BPF_EVENT = 18,
+
+ /*
+ * struct {
+ * struct perf_event_header header;
+ * u64 id;
+ * char path[];
+ * struct sample_id sample_id;
+ * };
+ */
+ PERF_RECORD_CGROUP = 19,
+
+ /*
+ * Records changes to kernel text i.e. self-modified code. 'old_len' is
+ * the number of old bytes, 'new_len' is the number of new bytes. Either
+ * 'old_len' or 'new_len' may be zero to indicate, for example, the
+ * addition or removal of a trampoline. 'bytes' contains the old bytes
+ * followed immediately by the new bytes.
+ *
+ * struct {
+ * struct perf_event_header header;
+ * u64 addr;
+ * u16 old_len;
+ * u16 new_len;
+ * u8 bytes[];
+ * struct sample_id sample_id;
+ * };
+ */
+ PERF_RECORD_TEXT_POKE = 20,
+
+ /*
+ * Data written to the AUX area by hardware due to aux_output, may need
+ * to be matched to the event by an architecture-specific hardware ID.
+ * This records the hardware ID, but requires sample_id to provide the
+ * event ID. e.g. Intel PT uses this record to disambiguate PEBS-via-PT
+ * records from multiple events.
+ *
+ * struct {
+ * struct perf_event_header header;
+ * u64 hw_id;
+ * struct sample_id sample_id;
+ * };
+ */
+ PERF_RECORD_AUX_OUTPUT_HW_ID = 21,
+
PERF_RECORD_MAX, /* non-ABI */
};
+enum perf_record_ksymbol_type {
+ PERF_RECORD_KSYMBOL_TYPE_UNKNOWN = 0,
+ PERF_RECORD_KSYMBOL_TYPE_BPF = 1,
+ /*
+ * Out of line code such as kprobe-replaced instructions or optimized
+ * kprobes or ftrace trampolines.
+ */
+ PERF_RECORD_KSYMBOL_TYPE_OOL = 2,
+ PERF_RECORD_KSYMBOL_TYPE_MAX /* non-ABI */
+};
+
+#define PERF_RECORD_KSYMBOL_FLAGS_UNREGISTER (1 << 0)
+
+enum perf_bpf_event_type {
+ PERF_BPF_EVENT_UNKNOWN = 0,
+ PERF_BPF_EVENT_PROG_LOAD = 1,
+ PERF_BPF_EVENT_PROG_UNLOAD = 2,
+ PERF_BPF_EVENT_MAX, /* non-ABI */
+};
+
#define PERF_MAX_STACK_DEPTH 127
+#define PERF_MAX_CONTEXTS_PER_STACK 8
enum perf_callchain_context {
PERF_CONTEXT_HV = (__u64)-32,
@@ -608,8 +1201,198 @@ enum perf_callchain_context {
PERF_CONTEXT_MAX = (__u64)-4095,
};
-#define PERF_FLAG_FD_NO_GROUP (1U << 0)
-#define PERF_FLAG_FD_OUTPUT (1U << 1)
-#define PERF_FLAG_PID_CGROUP (1U << 2) /* pid=cgroup id, per-cpu mode only */
+/**
+ * PERF_RECORD_AUX::flags bits
+ */
+#define PERF_AUX_FLAG_TRUNCATED 0x01 /* record was truncated to fit */
+#define PERF_AUX_FLAG_OVERWRITE 0x02 /* snapshot from overwrite mode */
+#define PERF_AUX_FLAG_PARTIAL 0x04 /* record contains gaps */
+#define PERF_AUX_FLAG_COLLISION 0x08 /* sample collided with another */
+#define PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK 0xff00 /* PMU specific trace format type */
+
+/* CoreSight PMU AUX buffer formats */
+#define PERF_AUX_FLAG_CORESIGHT_FORMAT_CORESIGHT 0x0000 /* Default for backward compatibility */
+#define PERF_AUX_FLAG_CORESIGHT_FORMAT_RAW 0x0100 /* Raw format of the source */
+
+#define PERF_FLAG_FD_NO_GROUP (1UL << 0)
+#define PERF_FLAG_FD_OUTPUT (1UL << 1)
+#define PERF_FLAG_PID_CGROUP (1UL << 2) /* pid=cgroup id, per-cpu mode only */
+#define PERF_FLAG_FD_CLOEXEC (1UL << 3) /* O_CLOEXEC */
+
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+union perf_mem_data_src {
+ __u64 val;
+ struct {
+ __u64 mem_op:5, /* type of opcode */
+ mem_lvl:14, /* memory hierarchy level */
+ mem_snoop:5, /* snoop mode */
+ mem_lock:2, /* lock instr */
+ mem_dtlb:7, /* tlb access */
+ mem_lvl_num:4, /* memory hierarchy level number */
+ mem_remote:1, /* remote */
+ mem_snoopx:2, /* snoop mode, ext */
+ mem_blk:3, /* access blocked */
+ mem_hops:3, /* hop level */
+ mem_rsvd:18;
+ };
+};
+#elif defined(__BIG_ENDIAN_BITFIELD)
+union perf_mem_data_src {
+ __u64 val;
+ struct {
+ __u64 mem_rsvd:18,
+ mem_hops:3, /* hop level */
+ mem_blk:3, /* access blocked */
+ mem_snoopx:2, /* snoop mode, ext */
+ mem_remote:1, /* remote */
+ mem_lvl_num:4, /* memory hierarchy level number */
+ mem_dtlb:7, /* tlb access */
+ mem_lock:2, /* lock instr */
+ mem_snoop:5, /* snoop mode */
+ mem_lvl:14, /* memory hierarchy level */
+ mem_op:5; /* type of opcode */
+ };
+};
+#else
+#error "Unknown endianness"
+#endif
+
+/* type of opcode (load/store/prefetch,code) */
+#define PERF_MEM_OP_NA 0x01 /* not available */
+#define PERF_MEM_OP_LOAD 0x02 /* load instruction */
+#define PERF_MEM_OP_STORE 0x04 /* store instruction */
+#define PERF_MEM_OP_PFETCH 0x08 /* prefetch */
+#define PERF_MEM_OP_EXEC 0x10 /* code (execution) */
+#define PERF_MEM_OP_SHIFT 0
+
+/*
+ * PERF_MEM_LVL_* namespace being depricated to some extent in the
+ * favour of newer composite PERF_MEM_{LVLNUM_,REMOTE_,SNOOPX_} fields.
+ * Supporting this namespace inorder to not break defined ABIs.
+ *
+ * memory hierarchy (memory level, hit or miss)
+ */
+#define PERF_MEM_LVL_NA 0x01 /* not available */
+#define PERF_MEM_LVL_HIT 0x02 /* hit level */
+#define PERF_MEM_LVL_MISS 0x04 /* miss level */
+#define PERF_MEM_LVL_L1 0x08 /* L1 */
+#define PERF_MEM_LVL_LFB 0x10 /* Line Fill Buffer */
+#define PERF_MEM_LVL_L2 0x20 /* L2 */
+#define PERF_MEM_LVL_L3 0x40 /* L3 */
+#define PERF_MEM_LVL_LOC_RAM 0x80 /* Local DRAM */
+#define PERF_MEM_LVL_REM_RAM1 0x100 /* Remote DRAM (1 hop) */
+#define PERF_MEM_LVL_REM_RAM2 0x200 /* Remote DRAM (2 hops) */
+#define PERF_MEM_LVL_REM_CCE1 0x400 /* Remote Cache (1 hop) */
+#define PERF_MEM_LVL_REM_CCE2 0x800 /* Remote Cache (2 hops) */
+#define PERF_MEM_LVL_IO 0x1000 /* I/O memory */
+#define PERF_MEM_LVL_UNC 0x2000 /* Uncached memory */
+#define PERF_MEM_LVL_SHIFT 5
+
+#define PERF_MEM_REMOTE_REMOTE 0x01 /* Remote */
+#define PERF_MEM_REMOTE_SHIFT 37
+
+#define PERF_MEM_LVLNUM_L1 0x01 /* L1 */
+#define PERF_MEM_LVLNUM_L2 0x02 /* L2 */
+#define PERF_MEM_LVLNUM_L3 0x03 /* L3 */
+#define PERF_MEM_LVLNUM_L4 0x04 /* L4 */
+/* 5-0xa available */
+#define PERF_MEM_LVLNUM_ANY_CACHE 0x0b /* Any cache */
+#define PERF_MEM_LVLNUM_LFB 0x0c /* LFB */
+#define PERF_MEM_LVLNUM_RAM 0x0d /* RAM */
+#define PERF_MEM_LVLNUM_PMEM 0x0e /* PMEM */
+#define PERF_MEM_LVLNUM_NA 0x0f /* N/A */
+
+#define PERF_MEM_LVLNUM_SHIFT 33
+
+/* snoop mode */
+#define PERF_MEM_SNOOP_NA 0x01 /* not available */
+#define PERF_MEM_SNOOP_NONE 0x02 /* no snoop */
+#define PERF_MEM_SNOOP_HIT 0x04 /* snoop hit */
+#define PERF_MEM_SNOOP_MISS 0x08 /* snoop miss */
+#define PERF_MEM_SNOOP_HITM 0x10 /* snoop hit modified */
+#define PERF_MEM_SNOOP_SHIFT 19
+
+#define PERF_MEM_SNOOPX_FWD 0x01 /* forward */
+/* 1 free */
+#define PERF_MEM_SNOOPX_SHIFT 38
+
+/* locked instruction */
+#define PERF_MEM_LOCK_NA 0x01 /* not available */
+#define PERF_MEM_LOCK_LOCKED 0x02 /* locked transaction */
+#define PERF_MEM_LOCK_SHIFT 24
+
+/* TLB access */
+#define PERF_MEM_TLB_NA 0x01 /* not available */
+#define PERF_MEM_TLB_HIT 0x02 /* hit level */
+#define PERF_MEM_TLB_MISS 0x04 /* miss level */
+#define PERF_MEM_TLB_L1 0x08 /* L1 */
+#define PERF_MEM_TLB_L2 0x10 /* L2 */
+#define PERF_MEM_TLB_WK 0x20 /* Hardware Walker*/
+#define PERF_MEM_TLB_OS 0x40 /* OS fault handler */
+#define PERF_MEM_TLB_SHIFT 26
+
+/* Access blocked */
+#define PERF_MEM_BLK_NA 0x01 /* not available */
+#define PERF_MEM_BLK_DATA 0x02 /* data could not be forwarded */
+#define PERF_MEM_BLK_ADDR 0x04 /* address conflict */
+#define PERF_MEM_BLK_SHIFT 40
+
+/* hop level */
+#define PERF_MEM_HOPS_0 0x01 /* remote core, same node */
+#define PERF_MEM_HOPS_1 0x02 /* remote node, same socket */
+#define PERF_MEM_HOPS_2 0x03 /* remote socket, same board */
+#define PERF_MEM_HOPS_3 0x04 /* remote board */
+/* 5-7 available */
+#define PERF_MEM_HOPS_SHIFT 43
+
+#define PERF_MEM_S(a, s) \
+ (((__u64)PERF_MEM_##a##_##s) << PERF_MEM_##a##_SHIFT)
+
+/*
+ * single taken branch record layout:
+ *
+ * from: source instruction (may not always be a branch insn)
+ * to: branch target
+ * mispred: branch target was mispredicted
+ * predicted: branch target was predicted
+ *
+ * support for mispred, predicted is optional. In case it
+ * is not supported mispred = predicted = 0.
+ *
+ * in_tx: running in a hardware transaction
+ * abort: aborting a hardware transaction
+ * cycles: cycles from last branch (or 0 if not supported)
+ * type: branch type
+ */
+struct perf_branch_entry {
+ __u64 from;
+ __u64 to;
+ __u64 mispred:1, /* target mispredicted */
+ predicted:1,/* target predicted */
+ in_tx:1, /* in transaction */
+ abort:1, /* transaction abort */
+ cycles:16, /* cycle count to last branch */
+ type:4, /* branch type */
+ reserved:40;
+};
+
+union perf_sample_weight {
+ __u64 full;
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ struct {
+ __u32 var1_dw;
+ __u16 var2_w;
+ __u16 var3_w;
+ };
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ struct {
+ __u16 var3_w;
+ __u16 var2_w;
+ __u32 var1_dw;
+ };
+#else
+#error "Unknown endianness"
+#endif
+};
#endif /* _UAPI_LINUX_PERF_EVENT_H */
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 f8e78bf6ab..e956a47cf1 100644
--- a/src/testlib/CMakeLists.txt
+++ b/src/testlib/CMakeLists.txt
@@ -1,8 +1,7 @@
-# Generated from testlib.pro.
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
-# special case begin
include(selfcover.cmake)
-# special case end
#####################################################################
## Test Module:
@@ -14,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
@@ -22,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
@@ -38,8 +39,9 @@ qt_internal_add_module(Test
qtestaccessible.h
qtestassert.h
qtestblacklist.cpp qtestblacklist_p.h
- qtestcase.cpp qtestcase.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
@@ -55,17 +57,23 @@ 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
@@ -73,20 +81,24 @@ qt_internal_add_module(Test
GENERATE_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
)
+qt_internal_extend_target(Test CONDITION QT_FEATURE_batch_test_support
+ SOURCES
+ qtestregistry.cpp qtestregistry_p.h
+)
+
qt_internal_extend_target(Test CONDITION QT_FEATURE_valgrind
SOURCES
3rdparty/callgrind_p.h
@@ -126,32 +138,39 @@ 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)
add_subdirectory(doc/snippets/code)
endif()
+
+if(WASM)
+ # Keep in sync with is_test files in Qt6WasmMacros.cmake
+ set(testrunner_files
+ "${QtBase_SOURCE_DIR}/util/wasm/batchedtestrunner/batchedtestrunner.html"
+ "${QtBase_SOURCE_DIR}/util/wasm/batchedtestrunner/batchedtestrunner.js"
+ "${QtBase_SOURCE_DIR}/util/wasm/batchedtestrunner/emrunadapter.js"
+ "${QtBase_SOURCE_DIR}/util/wasm/batchedtestrunner/qwasmjsruntime.js"
+ "${QtBase_SOURCE_DIR}/util/wasm/batchedtestrunner/qwasmtestmain.js"
+ "${QtBase_SOURCE_DIR}/util/wasm/batchedtestrunner/util.js"
+ "${QtBase_SOURCE_DIR}/util/wasm/batchedtestrunner/qtestoutputreporter.js"
+ "${QtBase_SOURCE_DIR}/util/wasm/batchedtestrunner/qtestoutputreporter.css")
+
+ qt_path_join(install_dir_libexec "${QT_INSTALL_DIR}" "${INSTALL_LIBEXECDIR}")
+ qt_copy_or_install(FILES ${testrunner_files}
+ DESTINATION "${install_dir_libexec}")
+
+ # In a prefix build, the above copy_or_install won't put the files
+ # in the build dir, but they are required there for tests at configure time
+ if(QT_WILL_INSTALL)
+ qt_path_join(build_dir_libexec "${QT_BUILD_DIR}" "${INSTALL_LIBEXECDIR}")
+ foreach(testrunner_file ${testrunner_files})
+ file(COPY "${testrunner_file}" DESTINATION "${build_dir_libexec}")
+ endforeach()
+ endif()
+endif()
diff --git a/src/testlib/configure.cmake b/src/testlib/configure.cmake
index d83b4f09b0..3490c64874 100644
--- a/src/testlib/configure.cmake
+++ b/src/testlib/configure.cmake
@@ -1,3 +1,6 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
#### Inputs
@@ -29,6 +32,13 @@ qt_feature("valgrind" PUBLIC
PURPOSE "Profiling support with callgrind."
CONDITION ( LINUX OR APPLE ) AND QT_FEATURE_process AND QT_FEATURE_regularexpression
)
+qt_feature("batch_test_support" PUBLIC
+ LABEL "Batch tests"
+ PURPOSE "Allows merging of all tests into a single executable on demand"
+ AUTODETECT QT_BUILD_TESTS_BATCHED
+ ENABLE INPUT_batch_tests STREQUAL 'yes'
+)
qt_configure_add_summary_section(NAME "Qt Testlib")
qt_configure_add_summary_entry(ARGS "itemmodeltester")
+qt_configure_add_summary_entry(ARGS "batch_test_support")
qt_configure_end_summary_section() # end of "Qt Testlib" section
diff --git a/src/testlib/doc/includes/building-examples.qdocinc b/src/testlib/doc/includes/building-examples.qdocinc
new file mode 100644
index 0000000000..8f10812a11
--- /dev/null
+++ b/src/testlib/doc/includes/building-examples.qdocinc
@@ -0,0 +1,40 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+//! [building the executable]
+ You can build the test case executable using CMake or qmake.
+
+ \section2 Building with CMake
+
+ Configure your build settings in your CMakeLists.txt file:
+
+ \quotefile \1/CMakeLists.txt
+
+ Next, from the command line, run either \c cmake or use the \c qt-cmake
+ convenience script located in
+ \c Qt-prefix/<version>/<platform>/bin/qt-cmake:
+
+ \badcode
+ <Qt-prefix>/<version>/<platform>/bin/qt-cmake <source-dir> <build-dir> -G Ninja
+ \endcode
+
+ Then, run your preferred generator tool to build the executable. Here, we're
+ using Ninja:
+
+ \badcode
+ ninja
+ \endcode
+
+ \section2 Building with qmake
+
+ Configure your build settings in your \c .pro file:
+
+ \quotefile \1/\1.pro
+
+ Next, run \c qmake, and, finally, run \c make to build your executable:
+
+ \badcode
+ qmake
+ make
+ \endcode
+//! [building the executable]
diff --git a/src/testlib/doc/qttestlib.qdocconf b/src/testlib/doc/qttestlib.qdocconf
index d359eff143..8b245a864f 100644
--- a/src/testlib/doc/qttestlib.qdocconf
+++ b/src/testlib/doc/qttestlib.qdocconf
@@ -41,17 +41,20 @@ sources += ../../corelib/kernel/qtestsupport_core.cpp \
exampledirs += ../../../examples/qtestlib \
.. \
. \
- snippets
+ snippets \
+ includes
excludedirs += ../../../examples/widgets/doc
imagedirs += images
+defines += QT_FEATURE_batch_test_support
+
# Add a thumbnail for examples that do not have images
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 09d54fd56e..77e4781c44 100644
--- a/src/testlib/doc/snippets/CMakeLists.txt
+++ b/src/testlib/doc/snippets/CMakeLists.txt
@@ -1,3 +1,6 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
#! [cmake_use]
find_package(Qt6 REQUIRED COMPONENTS Test)
target_link_libraries(mytarget PRIVATE Qt6::Test)
diff --git a/src/testlib/doc/snippets/code/CMakeLists.txt b/src/testlib/doc/snippets/code/CMakeLists.txt
index 8a244e4dd6..54c655a521 100644
--- a/src/testlib/doc/snippets/code/CMakeLists.txt
+++ b/src/testlib/doc/snippets/code/CMakeLists.txt
@@ -1,6 +1,8 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# 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 bd58d0493b..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]]...
@@ -49,3 +49,54 @@ PASS : TestQString::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of TestQString *********
//! [10]
+
+//! [11]
+********* Start testing of TestQString *********
+Config: Using QtTest library %VERSION%, Qt %VERSION%
+PASS : TestQString::initTestCase()
+PASS : TestQString::toUpper(all-lower)
+PASS : TestQString::toUpper(mixed)
+PASS : TestQString::toUpper(all-upper)
+PASS : TestQString::cleanupTestCase()
+Totals: 5 passed, 0 failed, 0 skipped, 0 blacklisted, 0ms
+********* Finished testing of TestQString *********
+//! [11]
+
+//! [12]
+********* Start testing of TestGui *********
+Config: Using QtTest library %VERSION%, Qt %VERSION%
+PASS : TestGui::initTestCase()
+PASS : TestGui::testGui()
+PASS : TestGui::cleanupTestCase()
+Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted, 20ms
+********* Finished testing of TestGui **
+//! [12]
+
+//! [13]
+********* Start testing of TestGui *********
+Config: Using QtTest library %VERSION%, Qt %VERSION%
+PASS : TestGui::initTestCase()
+PASS : TestGui::testGui(char)
+PASS : TestGui::testGui(there+back-again)
+PASS : TestGui::cleanupTestCase()
+Totals: 4 passed, 0 failed, 0 skipped, 0 blacklisted, 18ms
+********* Finished testing of TestGui *********
+//! [13]
+
+//! [14]
+********* Start testing of TestBenchmark *********
+Config: Using QtTest library %VERSION%, Qt %VERSION%
+PASS : TestBenchmark::initTestCase()
+PASS : TestBenchmark::simple()
+RESULT : TestBenchmark::simple():
+ 0.00030 msecs per iteration (total: 79, iterations: 262144)
+PASS : TestBenchmark::multiple(locale-aware-compare)
+RESULT : TestBenchmark::multiple():"locale-aware-compare":
+ 0.00029 msecs per iteration (total: 78, iterations: 262144)
+.....
+PASS : TestBenchmark::series(locale-aware-compare:8001)
+RESULT : TestBenchmark::series():"locale-aware-compare:8001":
+ 0.039 msecs per iteration (total: 81, iterations: 2048)
+Totals: 15 passed, 0 failed, 0 skipped, 0 blacklisted, 3971ms
+********* Finished testing of TestBenchmark *********
+//! [14]
diff --git a/src/testlib/doc/snippets/code/doc_src_qtqskip.cpp b/src/testlib/doc/snippets/code/doc_src_qtqskip.cpp
index 14f5085dd4..85aed2870d 100644
--- a/src/testlib/doc/snippets/code/doc_src_qtqskip.cpp
+++ b/src/testlib/doc/snippets/code/doc_src_qtqskip.cpp
@@ -13,8 +13,8 @@ void tst_Skip::test_data()
{
//! [1]
QTest::addColumn<bool>("bool");
- QTest::newRow("local 1") << false;
- QTest::newRow("local 2") << true;
+ QTest::newRow("local.1") << false;
+ QTest::newRow("local.2") << true;
QSKIP("skipping all");
//! [1]
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 63d0b992fc..4716d06e55 100644
--- a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp
+++ b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp
@@ -56,8 +56,8 @@ void TestQString::toInt_data()
QTest::addColumn<QString>("aString");
QTest::addColumn<int>("expected");
- QTest::newRow("positive value") << "42" << 42;
- QTest::newRow("negative value") << "-42" << -42;
+ QTest::newRow("positive+value") << "42" << 42;
+ QTest::newRow("negative-value") << "-42" << -42;
QTest::newRow("zero") << "0" << 0;
}
//! [3]
@@ -135,8 +135,8 @@ dir.mkdir("");
void MyTestClass::addSingleStringRows()
{
QTest::addColumn<QString>("aString");
- QTest::newRow("just hello") << QString("hello");
- QTest::newRow("a null string") << QString();
+ QTest::newRow("just.hello") << QString("hello");
+ QTest::newRow("a.null.string") << QString();
}
//! [20]
@@ -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/snippets/code/src_qtestlib_qtestcase_snippet.cpp b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase_snippet.cpp
index e64e9daecf..aa24746522 100644
--- a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase_snippet.cpp
+++ b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase_snippet.cpp
@@ -119,3 +119,16 @@ char *toString(const MyType &t)
return repr;
}
//! [34]
+
+//! [35]
+QSignalSpy doubleClickSpy(target, &TargetClass::doubleClicked);
+const QPoint p(1, 2);
+QTest::mousePress(&myWindow, Qt::LeftButton, Qt::NoModifier, p);
+QVERIFY(target.isPressed());
+QTest::mouseRelease(&myWindow, Qt::LeftButton, Qt::NoModifier, p, 10);
+QCOMPARE(target.isPressed(), false);
+QTest::mousePress(&myWindow, Qt::LeftButton, Qt::NoModifier, p, 10);
+QCOMPARE(target.pressCount(), 2);
+QTest::mouseRelease(&myWindow, Qt::LeftButton, Qt::NoModifier, p, 10);
+QCOMPARE(doubleClickSpy.count(), 1);
+//! [35]
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 57f07d4a16..cea5b3a8de 100644
--- a/src/testlib/doc/src/qttest-best-practices.qdoc
+++ b/src/testlib/doc/src/qttest-best-practices.qdoc
@@ -108,7 +108,11 @@
Do not simply number the test-case, or use bug-tracking identifiers. Someone
reading the test output will have no idea what the numbers or identifiers
mean. You can add a comment on the test-row that mentions the bug-tracking
- identifier, when relevant.
+ identifier, when relevant. It's best to avoid spacing characters and
+ characters that may be significant to command-line shells on which you may
+ want to run tests. This makes it easier to specify the test and tag on \l{Qt
+ Test Command Line Arguments}{the command-line} to your test program - for
+ example, to limit a test run to just one test-case.
\section2 Write Self-contained Test Functions
@@ -150,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
@@ -167,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
@@ -176,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
@@ -198,16 +207,28 @@
attempt to run a slot that is not implemented. In the second case, the
test will not attempt to run a test slot even though it should.
- If an entire test program is inapplicable for a specific platform or
- unless a particular feature is enabled, the best approach is to use the
- parent directory's \c .pro file to avoid building the test. For example,
- if the \c tests/auto/gui/someclass test is not valid for \macOS, add the
- following line to \c tests/auto/gui.pro:
+ If an entire test program is inapplicable for a specific platform or unless
+ a particular feature is enabled, the best approach is to use the parent
+ directory's build configuration to avoid building the test. For example, if
+ the \c tests/auto/gui/someclass test is not valid for \macOS, wrap its
+ inclusion as a subdirectory in \c{tests/auto/gui/CMakeLists.txt} in a
+ platform check:
+
+ \badcode
+ if(NOT APPLE)
+ add_subdirectory(someclass)
+ endif
+ \endcode
+
+ or, if using \c qmake, add the following line to \c tests/auto/gui.pro:
\badcode
mac*: SUBDIRS -= someclass
\endcode
+ See also \l {Chapter 6: Skipping Tests with QSKIP}
+ {Skipping Tests with QSKIP}.
+
\section2 Avoid Q_ASSERT
The \l Q_ASSERT macro causes a program to abort whenever the asserted
@@ -339,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/qttest-index.qdoc b/src/testlib/doc/src/qttest-index.qdoc
index 48549a9388..59ce61eb0d 100644
--- a/src/testlib/doc/src/qttest-index.qdoc
+++ b/src/testlib/doc/src/qttest-index.qdoc
@@ -1,4 +1,4 @@
-// Copyright (C) 2020 The Qt Company Ltd.
+// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\page qttest-index.html
@@ -16,13 +16,17 @@
to work with the Qt version it was developed against. However, source
compatibility is guaranteed.
- \include module-use.qdocinc using qt module
- \snippet snippets/CMakeLists.txt cmake_use
+ \section1 Using the Module
- See also the \l {Build with CMake} overview.
+ \include {module-use.qdocinc} {using the c++ api}
- \include module-use.qdocinc building with qmake
- \snippet snippets/code/code.pro qmake_use
+ \section2 Building with CMake
+
+ \include {module-use.qdocinc} {building with cmake} {Test}
+
+ \section2 Building with qmake
+
+ \include {module-use.qdocinc} {building_with_qmake} {testlib}
\section1 Articles and Guides
@@ -32,6 +36,19 @@
\li \l{Qt Test Tutorial}
\endlist
+ \section1 Reference
+
+ \list
+ \li \l{Qt Test C++ Classes}
+ \endlist
+
+ The \l {Qt Quick Test} module enables unit testing of Qt Quick applications.
+
+ \list
+ \li \l{Qt Quick Test QML Types}
+ \li \l{Qt Quick Test C++ API}
+ \endlist
+
\section1 Module Evolution
\l{Changes to Qt Test} lists important changes in the module API
and functionality that were done for the Qt 6 series of Qt.
@@ -45,21 +62,7 @@
See \l{Qt Licensing} for further details.
Furthermore, Qt Test in Qt \QtVersion may contain third party
- modules under following permissive licenses:
+ modules under the following permissive licenses:
\generatelist{groupsbymodule attributions-qttestlib}
-
- \section1 Reference
-
- \list
- \li \l{Qt Test C++ Classes}
- \endlist
-
- The \l {Qt Quick Test} module enables unit testing Qt Quick applications.
-
- \list
- \li \l{Qt Quick Test QML Types}
- \li \l{Qt Quick Test C++ API}
- \endlist
-
*/
diff --git a/src/testlib/doc/src/qttestlib-manual.qdoc b/src/testlib/doc/src/qttestlib-manual.qdoc
index 4c67c34d23..1b6f534045 100644
--- a/src/testlib/doc/src/qttestlib-manual.qdoc
+++ b/src/testlib/doc/src/qttestlib-manual.qdoc
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Qt Company Ltd.
+// 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
@@ -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
@@ -224,7 +265,7 @@
\li \c -o \e{filename,format} \br
Writes output to the specified file, in the specified format (one
of \c txt, \c csv, \c junitxml, \c xml, \c lightxml, \c teamcity
- or \c tap). The special filename \c - may be used to log to
+ or \c tap). Use the special filename \c{-} (hyphen) to log to
standard output.
\li \c -o \e filename \br
Writes output to the specified file.
@@ -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
@@ -520,9 +577,8 @@
\title Qt Test Tutorial
- This tutorial gives a short introduction to how to use some of the
- features of the Qt Test framework. It is divided into five
- chapters:
+ This tutorial introduces some of the features of the Qt Test framework. It
+ is divided into six chapters:
\list 1
\li \l {Chapter 1: Writing a Unit Test}{Writing a Unit Test}
@@ -533,427 +589,5 @@
\li \l {Chapter 6: Skipping Tests with QSKIP}{Skipping Tests}
\endlist
-*/
-
-
-/*!
- \example tutorial1
-
- \nextpage {Chapter 2: Data Driven Testing}{Chapter 2}
-
- \title Chapter 1: Writing a Unit Test
- \brief How to write a unit test.
-
- In this first chapter we will see how to write a simple unit test
- for a class, and how to execute it.
-
- \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.
-
- 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 Executing a Test
-
- Now that we finished writing our test, we want to execute
- it. Assuming that our test was saved as \c testqstring.cpp in an
- empty directory, we build the test using \c qmake to create a
- project and generate a makefile.
-
- \snippet code/doc_src_qtestlib.qdoc 9
-
- \note If you're using windows, replace \c make with \c
- nmake or whatever build tool you use.
-
- 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.
-
- In this chapter we will demonstrate 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 that the function ends up being cluttered by 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 carries the same name,
- appended by \c{_data}. 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 as well as an index is 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.
-
- 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.
-*/
-
-/*!
- \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.
-
- In this chapter we will see 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.
-
- 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.
-*/
-
-/*!
- \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.
-
- 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.
-*/
-
-/*!
- \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.
-
- In this final chapter we will demonstrate 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, the clock is not
- running at this point. 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 "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.
-
-*/
-/*!
- \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 explains 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/qabstractitemmodeltester.cpp b/src/testlib/qabstractitemmodeltester.cpp
index e7aa8979d5..eb52d28d59 100644
--- a/src/testlib/qabstractitemmodeltester.cpp
+++ b/src/testlib/qabstractitemmodeltester.cpp
@@ -322,6 +322,7 @@ QAbstractItemModelTester::FailureReportingMode QAbstractItemModelTester::failure
If \a value is false, it disables it.
\since 6.4
+ \sa QAbstractItemModel::fetchMore()
*/
void QAbstractItemModelTester::setUseFetchMore(bool value)
{
@@ -866,7 +867,7 @@ void QAbstractItemModelTesterPrivate::layoutChanged()
MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::LayoutChanged);
changeInFlight = ChangeInFlight::None;
- for (int i = 0; i < changing.count(); ++i) {
+ for (int i = 0; i < changing.size(); ++i) {
QPersistentModelIndex p = changing[i];
MODELTESTER_COMPARE(model->index(p.row(), p.column(), p.parent()), QModelIndex(p));
}
diff --git a/src/testlib/qabstracttestlogger.cpp b/src/testlib/qabstracttestlogger.cpp
index d329386745..de6fb63560 100644
--- a/src/testlib/qabstracttestlogger.cpp
+++ b/src/testlib/qabstracttestlogger.cpp
@@ -3,6 +3,7 @@
#include <QtTest/private/qabstracttestlogger_p.h>
#include <QtTest/qtestassert.h>
+#include <qbenchmark_p.h>
#include <qtestresult_p.h>
#include <QtCore/qbytearray.h>
@@ -147,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
@@ -222,6 +236,12 @@ void QAbstractTestLogger::stopLogging()
{
}
+void QAbstractTestLogger::addBenchmarkResults(const QList<QBenchmarkResult> &result)
+{
+ for (const auto &m : result)
+ addBenchmarkResult(m);
+}
+
/*!
\fn void QAbstractTestLogger::enterTestFunction(const char *function)
@@ -351,8 +371,7 @@ void QAbstractTestLogger::addMessage(QtMsgType type, const QMessageLogContext &c
case QtWarningMsg: return QAbstractTestLogger::QWarning;
case QtFatalMsg: return QAbstractTestLogger::QFatal;
}
- Q_UNREACHABLE();
- return QAbstractTestLogger::QFatal;
+ Q_UNREACHABLE_RETURN(QAbstractTestLogger::QFatal);
}();
QString formattedMessage = qFormatLogMessage(type, context, message);
diff --git a/src/testlib/qabstracttestlogger_p.h b/src/testlib/qabstracttestlogger_p.h
index d913bb649f..b4a66cd12a 100644
--- a/src/testlib/qabstracttestlogger_p.h
+++ b/src/testlib/qabstracttestlogger_p.h
@@ -68,6 +68,7 @@ public:
virtual void addIncident(IncidentTypes type, const char *description,
const char *file = nullptr, int line = 0) = 0;
virtual void addBenchmarkResult(const QBenchmarkResult &result) = 0;
+ virtual void addBenchmarkResults(const QList<QBenchmarkResult> &result);
virtual void addMessage(QtMsgType, const QMessageLogContext &,
const QString &);
@@ -75,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 00dded0394..af6ee5f7c6 100644
--- a/src/testlib/qbenchmark.cpp
+++ b/src/testlib/qbenchmark.cpp
@@ -96,10 +96,13 @@ int QBenchmarkTestMethodData::adjustIterationCount(int suggestion)
return iterationCount;
}
-void QBenchmarkTestMethodData::setResult(
- qreal value, QTest::QBenchmarkMetric metric, bool setByMacro)
+void QBenchmarkTestMethodData::setResults(const QList<QBenchmarkMeasurerBase::Measurement> &list,
+ bool setByMacro)
{
bool accepted = false;
+ QBenchmarkMeasurerBase::Measurement firstMeasurement = {};
+ if (!list.isEmpty())
+ firstMeasurement = list.constFirst();
// Always accept the result if the iteration count has been
// specified on the command line with -iterations.
@@ -114,9 +117,9 @@ void QBenchmarkTestMethodData::setResult(
// Test the result directly without calling the measurer if the minimum time
// has been specified on the command line with -minimumvalue.
else if (QBenchmarkGlobalData::current->walltimeMinimum != -1)
- accepted = (value > QBenchmarkGlobalData::current->walltimeMinimum);
+ accepted = (firstMeasurement.value > QBenchmarkGlobalData::current->walltimeMinimum);
else
- accepted = QBenchmarkGlobalData::current->measurer->isMeasurementAccepted(value);
+ accepted = QBenchmarkGlobalData::current->measurer->isMeasurementAccepted(firstMeasurement);
// Accept the result or double the number of iterations.
if (accepted)
@@ -124,8 +127,10 @@ void QBenchmarkTestMethodData::setResult(
else
iterationCount *= 2;
- this->result = QBenchmarkResult(
- QBenchmarkGlobalData::current->context, value, iterationCount, metric, setByMacro);
+ valid = true;
+ results.reserve(list.size());
+ for (auto m : list)
+ results.emplaceBack(QBenchmarkGlobalData::current->context, m, iterationCount, setByMacro);
}
/*!
@@ -133,7 +138,7 @@ void QBenchmarkTestMethodData::setResult(
\internal
The QBenchmarkIterationController class is used by the QBENCHMARK macro to
- drive the benchmarking loop. It is repsonsible for starting and stopping
+ drive the benchmarking loop. It is responsible for starting and stopping
the timing measurements as well as calling the result reporting functions.
*/
@@ -157,13 +162,12 @@ QTest::QBenchmarkIterationController::QBenchmarkIterationController()
*/
QTest::QBenchmarkIterationController::~QBenchmarkIterationController()
{
- const qreal result = QTest::endBenchmarkMeasurement();
- QBenchmarkTestMethodData::current->setResult(result, QBenchmarkGlobalData::current->measurer->metricType());
+ QBenchmarkTestMethodData::current->setResults(QTest::endBenchmarkMeasurement());
}
/*! \internal
*/
-bool QTest::QBenchmarkIterationController::isDone()
+bool QTest::QBenchmarkIterationController::isDone() const noexcept
{
if (QBenchmarkTestMethodData::current->runOnce)
return i > 0;
@@ -172,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;
}
@@ -209,7 +213,7 @@ void QTest::beginBenchmarkMeasurement()
/*! \internal
*/
-quint64 QTest::endBenchmarkMeasurement()
+QList<QBenchmarkMeasurerBase::Measurement> QTest::endBenchmarkMeasurement()
{
// the clock is ticking before the line below, don't add code here.
return QBenchmarkGlobalData::current->measurer->stop();
@@ -234,7 +238,7 @@ quint64 QTest::endBenchmarkMeasurement()
*/
void QTest::setBenchmarkResult(qreal result, QTest::QBenchmarkMetric metric)
{
- QBenchmarkTestMethodData::current->setResult(result, metric, false);
+ QBenchmarkTestMethodData::current->setResult({ result, metric }, false);
}
template <typename T>
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 ddb5226fc4..902b092b16 100644
--- a/src/testlib/qbenchmark_p.h
+++ b/src/testlib/qbenchmark_p.h
@@ -61,28 +61,24 @@ class QBenchmarkResult
{
public:
QBenchmarkContext context;
- qreal value = -1;
+ QBenchmarkMeasurerBase::Measurement measurement = { -1, QTest::FramesPerSecond };
int iterations = -1;
- QTest::QBenchmarkMetric metric = QTest::FramesPerSecond;
bool setByMacro = true;
- bool valid = false;
QBenchmarkResult() = default;
QBenchmarkResult(
- const QBenchmarkContext &context, const qreal value, const int iterations,
- QTest::QBenchmarkMetric metric, bool setByMacro)
+ const QBenchmarkContext &context, QBenchmarkMeasurerBase::Measurement m,
+ const int iterations, bool setByMacro)
: context(context)
- , value(value)
+ , measurement(m)
, iterations(iterations)
- , metric(metric)
, setByMacro(setByMacro)
- , valid(true)
{ }
bool operator<(const QBenchmarkResult &other) const
{
- return (value / iterations) < (other.value / other.iterations);
+ return (measurement.value / iterations) < (other.measurement.value / other.iterations);
}
};
Q_DECLARE_TYPEINFO(QBenchmarkResult, Q_RELOCATABLE_TYPE);
@@ -119,10 +115,10 @@ private:
};
/*
- The QBenchmarkTestMethodData class stores all benchmark-related data
- for the current test case. QBenchmarkTestMethodData:current is
- created at the beginning of qInvokeTestMethod() and cleared at
- the end.
+ The QBenchmarkTestMethodData class stores all benchmark-related data for the
+ current test case. QBenchmarkTestMethodData:current is set to a local
+ instance at the beginning of TestMethods::invokeTest() and cleared by its
+ destructor when that instance drops out of scope.
*/
class Q_TESTLIB_EXPORT QBenchmarkTestMethodData
{
@@ -136,12 +132,15 @@ public:
void beginDataRun();
void endDataRun();
- bool isBenchmark() const { return result.valid; }
+ bool isBenchmark() const { return valid; }
bool resultsAccepted() const { return resultAccepted; }
int adjustIterationCount(int suggestion);
- void setResult(qreal value, QTest::QBenchmarkMetric metric, bool setByMacro = true);
+ void setResults(const QList<QBenchmarkMeasurerBase::Measurement> &m, bool setByMacro = true);
+ void setResult(QBenchmarkMeasurerBase::Measurement m, bool setByMacro = true)
+ { setResults({ m }, setByMacro); }
- QBenchmarkResult result;
+ QList<QBenchmarkResult> results;
+ bool valid = false;
bool resultAccepted = false;
bool runOnce = false;
int iterationCount = -1;
@@ -150,12 +149,12 @@ public:
// low-level API:
namespace QTest
{
- int iterationCount();
+ int iterationCount() noexcept;
void setIterationCountHint(int count);
void setIterationCount(int count);
- Q_TESTLIB_EXPORT void beginBenchmarkMeasurement();
- Q_TESTLIB_EXPORT quint64 endBenchmarkMeasurement();
+ void beginBenchmarkMeasurement();
+ QList<QBenchmarkMeasurerBase::Measurement> endBenchmarkMeasurement();
}
QT_END_NAMESPACE
diff --git a/src/testlib/qbenchmarkevent.cpp b/src/testlib/qbenchmarkevent.cpp
index 59a0db0b8d..5a895106a2 100644
--- a/src/testlib/qbenchmarkevent.cpp
+++ b/src/testlib/qbenchmarkevent.cpp
@@ -18,21 +18,16 @@ void QBenchmarkEvent::start()
QAbstractEventDispatcher::instance()->installNativeEventFilter(this);
}
-qint64 QBenchmarkEvent::checkpoint()
-{
- return eventCounter;
-}
-
-qint64 QBenchmarkEvent::stop()
+QList<QBenchmarkMeasurerBase::Measurement> QBenchmarkEvent::stop()
{
QAbstractEventDispatcher::instance()->removeNativeEventFilter(this);
- return eventCounter;
+ return { { qreal(eventCounter), QTest::Events } };
}
// It's very tempting to simply reject a measurement if 0 events
// where counted, however that is a possible situation and returning
// false here will create a infinite loop. Do not change this.
-bool QBenchmarkEvent::isMeasurementAccepted(qint64 measurement)
+bool QBenchmarkEvent::isMeasurementAccepted(QBenchmarkMeasurerBase::Measurement measurement)
{
Q_UNUSED(measurement);
return true;
@@ -49,11 +44,6 @@ int QBenchmarkEvent::adjustMedianCount(int suggestion)
return 1;
}
-QTest::QBenchmarkMetric QBenchmarkEvent::metricType()
-{
- return QTest::Events;
-}
-
// This could be done in a much better way, this is just the beginning.
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
bool QBenchmarkEvent::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
diff --git a/src/testlib/qbenchmarkevent_p.h b/src/testlib/qbenchmarkevent_p.h
index 0a1e9b1dbf..7e997e7779 100644
--- a/src/testlib/qbenchmarkevent_p.h
+++ b/src/testlib/qbenchmarkevent_p.h
@@ -28,13 +28,10 @@ public:
QBenchmarkEvent();
~QBenchmarkEvent();
void start() override;
- qint64 checkpoint() override;
- qint64 stop() override;
- bool isMeasurementAccepted(qint64 measurement) override;
+ QList<Measurement> stop() override;
+ bool isMeasurementAccepted(Measurement measurement) override;
int adjustIterationCount(int suggestion) override;
int adjustMedianCount(int suggestion) override;
- bool repeatCount() override { return 1; }
- QTest::QBenchmarkMetric metricType() override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override;
#else
diff --git a/src/testlib/qbenchmarkmeasurement.cpp b/src/testlib/qbenchmarkmeasurement.cpp
index 799eb06add..99d5b1dd4f 100644
--- a/src/testlib/qbenchmarkmeasurement.cpp
+++ b/src/testlib/qbenchmarkmeasurement.cpp
@@ -16,19 +16,14 @@ void QBenchmarkTimeMeasurer::start()
time.start();
}
-qint64 QBenchmarkTimeMeasurer::checkpoint()
+QList<QBenchmarkMeasurerBase::Measurement> QBenchmarkTimeMeasurer::stop()
{
- return time.elapsed();
+ return { { qreal(time.elapsed()), QTest::WalltimeMilliseconds } };
}
-qint64 QBenchmarkTimeMeasurer::stop()
+bool QBenchmarkTimeMeasurer::isMeasurementAccepted(Measurement measurement)
{
- return time.elapsed();
-}
-
-bool QBenchmarkTimeMeasurer::isMeasurementAccepted(qint64 measurement)
-{
- return (measurement > 50);
+ return (measurement.value > 50);
}
int QBenchmarkTimeMeasurer::adjustIterationCount(int suggestion)
@@ -46,11 +41,6 @@ int QBenchmarkTimeMeasurer::adjustMedianCount(int)
return 1;
}
-QTest::QBenchmarkMetric QBenchmarkTimeMeasurer::metricType()
-{
- return QTest::WalltimeMilliseconds;
-}
-
#ifdef HAVE_TICK_COUNTER // defined in 3rdparty/cycle_p.h
void QBenchmarkTickMeasurer::start()
@@ -58,19 +48,13 @@ void QBenchmarkTickMeasurer::start()
startTicks = getticks();
}
-qint64 QBenchmarkTickMeasurer::checkpoint()
+QList<QBenchmarkMeasurerBase::Measurement> QBenchmarkTickMeasurer::stop()
{
CycleCounterTicks now = getticks();
- return qRound64(elapsed(now, startTicks));
+ return { { elapsed(now, startTicks), QTest::CPUTicks } };
}
-qint64 QBenchmarkTickMeasurer::stop()
-{
- CycleCounterTicks now = getticks();
- return qRound64(elapsed(now, startTicks));
-}
-
-bool QBenchmarkTickMeasurer::isMeasurementAccepted(qint64)
+bool QBenchmarkTickMeasurer::isMeasurementAccepted(QBenchmarkMeasurerBase::Measurement)
{
return true;
}
@@ -90,11 +74,6 @@ bool QBenchmarkTickMeasurer::needsWarmupIteration()
return true;
}
-QTest::QBenchmarkMetric QBenchmarkTickMeasurer::metricType()
-{
- return QTest::CPUTicks;
-}
-
#endif
diff --git a/src/testlib/qbenchmarkmeasurement_p.h b/src/testlib/qbenchmarkmeasurement_p.h
index b0220bf5b7..56b978228b 100644
--- a/src/testlib/qbenchmarkmeasurement_p.h
+++ b/src/testlib/qbenchmarkmeasurement_p.h
@@ -16,6 +16,7 @@
//
#include <QtTest/qbenchmark.h>
+#include <QtCore/qlist.h>
#include <QtCore/private/qglobal_p.h>
QT_BEGIN_NAMESPACE
@@ -23,17 +24,19 @@ QT_BEGIN_NAMESPACE
class QBenchmarkMeasurerBase
{
public:
+ struct Measurement
+ {
+ qreal value;
+ QTest::QBenchmarkMetric metric;
+ };
virtual ~QBenchmarkMeasurerBase() = default;
virtual void init() {}
virtual void start() = 0;
- virtual qint64 checkpoint() = 0;
- virtual qint64 stop() = 0;
- virtual bool isMeasurementAccepted(qint64 measurement) = 0;
+ virtual QList<Measurement> stop() = 0;
+ virtual bool isMeasurementAccepted(Measurement m) = 0;
virtual int adjustIterationCount(int suggestion) = 0;
virtual int adjustMedianCount(int suggestion) = 0;
- virtual bool repeatCount() { return true; }
virtual bool needsWarmupIteration() { return false; }
- virtual QTest::QBenchmarkMetric metricType() = 0;
};
QT_END_NAMESPACE
diff --git a/src/testlib/qbenchmarkmetric.cpp b/src/testlib/qbenchmarkmetric.cpp
index 10042093c7..7f5206982a 100644
--- a/src/testlib/qbenchmarkmetric.cpp
+++ b/src/testlib/qbenchmarkmetric.cpp
@@ -111,7 +111,7 @@ const char * QTest::benchmarkMetricName(QBenchmarkMetric metric)
/*!
\since 4.7
- Retuns the units of measure for the specified \a metric.
+ Returns the units of measure for the specified \a metric.
*/
const char * QTest::benchmarkMetricUnit(QBenchmarkMetric metric)
{
diff --git a/src/testlib/qbenchmarkperfevents.cpp b/src/testlib/qbenchmarkperfevents.cpp
index 9a06fdbff5..c161879a7d 100644
--- a/src/testlib/qbenchmarkperfevents.cpp
+++ b/src/testlib/qbenchmarkperfevents.cpp
@@ -17,8 +17,9 @@
#include <string.h>
#include <stdio.h>
-#include <sys/syscall.h>
#include <sys/ioctl.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
#include "3rdparty/linux_perf_event_p.h"
@@ -47,7 +48,13 @@
QT_BEGIN_NAMESPACE
+struct PerfEvent
+{
+ quint32 type;
+ quint64 config;
+};
static perf_event_attr attr;
+Q_GLOBAL_STATIC(QList<PerfEvent>, eventTypes);
static void initPerf()
{
@@ -62,14 +69,20 @@ static void initPerf()
attr.inherit_stat = true; // aggregate all the info from child processes
attr.task = true; // trace fork/exits
- // set a default performance counter: CPU cycles
- attr.type = PERF_TYPE_HARDWARE;
- attr.config = PERF_COUNT_HW_CPU_CYCLES; // default
-
done = true;
}
}
+static QList<PerfEvent> defaultCounters()
+{
+ return {
+ { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_TASK_CLOCK },
+ { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CPU_CYCLES },
+ { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_INSTRUCTIONS },
+ { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS },
+ };
+}
+
// This class does not exist in the API so it's qdoc comment marker was removed.
/*
@@ -95,7 +108,8 @@ static void initPerf()
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);
@@ -128,8 +142,8 @@ bool QBenchmarkPerfEventsMeasurer::isAvailable()
HARDWARE BUS_CYCLES BusCycles bus-cycles
HARDWARE STALLED_CYCLES_FRONTEND StalledCycles stalled-cycles-frontend idle-cycles-frontend
HARDWARE STALLED_CYCLES_BACKEND StalledCycles stalled-cycles-backend idle-cycles-backend
- SOFTWARE CPU_CLOCK WalltimeMilliseconds cpu-clock
- SOFTWARE TASK_CLOCK WalltimeMilliseconds task-clock
+ SOFTWARE CPU_CLOCK WalltimeNanoseconds cpu-clock
+ SOFTWARE TASK_CLOCK WalltimeNanoseconds task-clock
SOFTWARE PAGE_FAULTS PageFaults page-faults faults
SOFTWARE PAGE_FAULTS_MAJ MajorPageFaults major-faults
SOFTWARE PAGE_FAULTS_MIN MinorPageFaults minor-faults
@@ -195,7 +209,7 @@ for $entry (sort @strings) {
$map{$entry}[2],
$map{$entry}[3];
}
-print " { 0, PERF_TYPE_MAX, 0, QTest::Events }\n};\n";
+print "};\n";
=== cut perl ===
*/
@@ -309,7 +323,7 @@ static const Events eventlist[] = {
{ 170, PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES, QTest::CacheMisses },
{ 183, PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES, QTest::CacheReferences },
{ 200, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CONTEXT_SWITCHES, QTest::ContextSwitches },
- { 217, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_CLOCK, QTest::WalltimeMilliseconds },
+ { 217, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_CLOCK, QTest::WalltimeNanoseconds },
{ 227, PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES, QTest::CPUCycles },
{ 238, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS, QTest::CPUMigrations },
{ 253, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CONTEXT_SWITCHES, QTest::ContextSwitches },
@@ -378,17 +392,15 @@ static const Events eventlist[] = {
{ 1292, PERF_TYPE_HARDWARE, PERF_COUNT_HW_REF_CPU_CYCLES, QTest::RefCPUCycles },
{ 1303, PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_BACKEND, QTest::StalledCycles },
{ 1326, PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_FRONTEND, QTest::StalledCycles },
- { 1350, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_TASK_CLOCK, QTest::WalltimeMilliseconds },
- { 0, PERF_TYPE_MAX, 0, QTest::Events }
+ { 1350, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_TASK_CLOCK, QTest::WalltimeNanoseconds },
};
/* -- END GENERATED CODE -- */
-QTest::QBenchmarkMetric QBenchmarkPerfEventsMeasurer::metricForEvent(quint32 type, quint64 event_id)
+static QTest::QBenchmarkMetric metricForEvent(PerfEvent counter)
{
- const Events *ptr = eventlist;
- for ( ; ptr->type != PERF_TYPE_MAX; ++ptr) {
- if (ptr->type == type && ptr->event_id == event_id)
- return ptr->metric;
+ for (const Events &ev : eventlist) {
+ if (ev.type == counter.type && ev.event_id == counter.config)
+ return ev.metric;
}
return QTest::Events;
}
@@ -396,47 +408,39 @@ QTest::QBenchmarkMetric QBenchmarkPerfEventsMeasurer::metricForEvent(quint32 typ
void QBenchmarkPerfEventsMeasurer::setCounter(const char *name)
{
initPerf();
- const char *colon = strchr(name, ':');
- int n = colon ? colon - name : strlen(name);
- const Events *ptr = eventlist;
- for ( ; ptr->type != PERF_TYPE_MAX; ++ptr) {
- int c = strncmp(name, eventlist_strings + ptr->offset, n);
- if (c == 0)
+ eventTypes->clear();
+ std::string_view input = name;
+ if (qsizetype idx = input.find(':'); idx >= 0)
+ input = input.substr(0, idx);
+
+ while (!input.empty()) {
+ std::string_view countername = input;
+ if (qsizetype idx = countername.find(','); idx >= 0)
+ countername = countername.substr(0, idx);
+
+ for (const Events &ev : eventlist) {
+ int c = countername.compare(eventlist_strings + ev.offset);
+ if (c > 0)
+ continue;
+ if (c < 0) {
+ fprintf(stderr, "ERROR: Performance counter type '%.*s' is unknown\n",
+ int(countername.size()), countername.data());
+ exit(1);
+ }
+ eventTypes->append({ ev.type, ev.event_id });
break;
- if (c < 0) {
- fprintf(stderr, "ERROR: Performance counter type '%s' is unknown\n", name);
- exit(1);
}
- }
-
- attr.type = ptr->type;
- attr.config = ptr->event_id;
- // now parse the attributes
- if (!colon)
- return;
- while (*++colon) {
- switch (*colon) {
- case 'u':
- attr.exclude_user = true;
- break;
- case 'k':
- attr.exclude_kernel = true;
- break;
- case 'h':
- attr.exclude_hv = true;
- break;
- case 'G':
- attr.exclude_guest = true;
- break;
- case 'H':
- attr.exclude_host = true;
- break;
- default:
- fprintf(stderr, "ERROR: Unknown attribute '%c'\n", *colon);
- exit(1);
- }
+ if (countername.size() == input.size())
+ input = {};
+ else
+ input.remove_prefix(countername.size() + 1);
}
+
+ // We used to support attributes, but our code was the opposite of what
+ // perf(1) does, plus QBenchlib isn't exactly expected to be used to
+ // profile Linux kernel code or launch guest VMs as part of the workload.
+ // So we keep accepting the colon as a delimiter but ignore it.
}
void QBenchmarkPerfEventsMeasurer::listCounters()
@@ -447,28 +451,20 @@ void QBenchmarkPerfEventsMeasurer::listCounters()
}
printf("The following performance counters are available:\n");
- const Events *ptr = eventlist;
- for ( ; ptr->type != PERF_TYPE_MAX; ++ptr) {
- printf(" %-30s [%s]\n", eventlist_strings + ptr->offset,
- ptr->type == PERF_TYPE_HARDWARE ? "hardware" :
- ptr->type == PERF_TYPE_SOFTWARE ? "software" :
- ptr->type == PERF_TYPE_HW_CACHE ? "cache" : "other");
+ for (const Events &ev : eventlist) {
+ printf(" %-30s [%s]\n", eventlist_strings + ev.offset,
+ ev.type == PERF_TYPE_HARDWARE ? "hardware" :
+ ev.type == PERF_TYPE_SOFTWARE ? "software" :
+ ev.type == PERF_TYPE_HW_CACHE ? "cache" : "other");
}
-
- printf("\nAttributes can be specified by adding a colon and the following:\n"
- " u - exclude measuring in the userspace\n"
- " k - exclude measuring in kernel mode\n"
- " h - exclude measuring in the hypervisor\n"
- " G - exclude measuring when running virtualized (guest VM)\n"
- " H - exclude measuring when running non-virtualized (host system)\n"
- "Attributes can be combined, for example: -perfcounter branch-mispredicts:kh\n");
}
QBenchmarkPerfEventsMeasurer::QBenchmarkPerfEventsMeasurer() = default;
QBenchmarkPerfEventsMeasurer::~QBenchmarkPerfEventsMeasurer()
{
- qt_safe_close(fd);
+ for (int fd : std::as_const(fds))
+ qt_safe_close(fd);
}
void QBenchmarkPerfEventsMeasurer::init()
@@ -477,43 +473,56 @@ void QBenchmarkPerfEventsMeasurer::init()
void QBenchmarkPerfEventsMeasurer::start()
{
-
initPerf();
- if (fd == -1) {
- // pid == 0 -> attach to the current process
- // cpu == -1 -> monitor on all CPUs
- // group_fd == -1 -> this is the group leader
- // flags == 0 -> reserved, must be zero
- fd = perf_event_open(&attr, 0, -1, -1, 0);
- if (fd == -1) {
- perror("QBenchmarkPerfEventsMeasurer::start: perf_event_open");
- exit(1);
- } else {
- ::fcntl(fd, F_SETFD, FD_CLOEXEC);
+ QList<PerfEvent> &counters = *eventTypes;
+ if (counters.isEmpty())
+ counters = defaultCounters();
+ if (fds.isEmpty()) {
+ pid_t pid = 0; // attach to the current process only
+ int cpu = -1; // on any CPU
+ int group_fd = -1;
+ int flags = PERF_FLAG_FD_CLOEXEC;
+
+ fds.reserve(counters.size());
+ for (PerfEvent counter : std::as_const(counters)) {
+ attr.type = counter.type;
+ attr.config = counter.config;
+ int fd = perf_event_open(&attr, pid, cpu, group_fd, flags);
+ if (fd == -1) {
+ // probably a paranoid kernel (/proc/sys/kernel/perf_event_paranoid)
+ attr.exclude_kernel = true;
+ attr.exclude_hv = true;
+ fd = perf_event_open(&attr, pid, cpu, group_fd, flags);
+ }
+ if (fd == -1) {
+ perror("QBenchmarkPerfEventsMeasurer::start: perf_event_open");
+ exit(1);
+ }
+
+ fds.append(fd);
}
}
- // enable the counter
- ::ioctl(fd, PERF_EVENT_IOC_RESET);
- ::ioctl(fd, PERF_EVENT_IOC_ENABLE);
+ // enable the counters
+ for (int fd : std::as_const(fds))
+ ::ioctl(fd, PERF_EVENT_IOC_RESET);
+ prctl(PR_TASK_PERF_EVENTS_ENABLE);
}
-qint64 QBenchmarkPerfEventsMeasurer::checkpoint()
+QList<QBenchmarkMeasurerBase::Measurement> QBenchmarkPerfEventsMeasurer::stop()
{
- ::ioctl(fd, PERF_EVENT_IOC_DISABLE);
- qint64 value = readValue();
- ::ioctl(fd, PERF_EVENT_IOC_ENABLE);
- return value;
-}
+ // disable the counters
+ prctl(PR_TASK_PERF_EVENTS_DISABLE);
-qint64 QBenchmarkPerfEventsMeasurer::stop()
-{
- // disable the counter
- ::ioctl(fd, PERF_EVENT_IOC_DISABLE);
- return readValue();
+ const QList<PerfEvent> &counters = *eventTypes;
+ QList<Measurement> result(counters.size(), {});
+ for (qsizetype i = 0; i < counters.size(); ++i) {
+ result[i] = readValue(i);
+ }
+ return result;
}
-bool QBenchmarkPerfEventsMeasurer::isMeasurementAccepted(qint64)
+bool QBenchmarkPerfEventsMeasurer::isMeasurementAccepted(Measurement)
{
return true;
}
@@ -528,11 +537,6 @@ int QBenchmarkPerfEventsMeasurer::adjustMedianCount(int)
return 1;
}
-QTest::QBenchmarkMetric QBenchmarkPerfEventsMeasurer::metricType()
-{
- return metricForEvent(attr.type, attr.config);
-}
-
static quint64 rawReadValue(int fd)
{
/* from the kernel docs:
@@ -568,14 +572,10 @@ static quint64 rawReadValue(int fd)
return results.value * (double(results.time_running) / double(results.time_enabled));
}
-qint64 QBenchmarkPerfEventsMeasurer::readValue()
+QBenchmarkMeasurerBase::Measurement QBenchmarkPerfEventsMeasurer::readValue(qsizetype idx)
{
- quint64 raw = rawReadValue(fd);
- if (metricType() == QTest::WalltimeMilliseconds) {
- // perf returns nanoseconds
- return raw / 1000000;
- }
- return raw;
+ quint64 raw = rawReadValue(fds.at(idx));
+ return { qreal(qint64(raw)), metricForEvent(eventTypes->at(idx)) };
}
QT_END_NAMESPACE
diff --git a/src/testlib/qbenchmarkperfevents_p.h b/src/testlib/qbenchmarkperfevents_p.h
index c42545b99e..5f94ea7794 100644
--- a/src/testlib/qbenchmarkperfevents_p.h
+++ b/src/testlib/qbenchmarkperfevents_p.h
@@ -26,23 +26,19 @@ public:
~QBenchmarkPerfEventsMeasurer();
void init() override;
void start() override;
- qint64 checkpoint() override;
- qint64 stop() override;
- bool isMeasurementAccepted(qint64 measurement) override;
+ QList<Measurement> stop() override;
+ bool isMeasurementAccepted(Measurement measurement) override;
int adjustIterationCount(int suggestion) override;
int adjustMedianCount(int suggestion) override;
- bool repeatCount() override { return true; }
bool needsWarmupIteration() override { return true; }
- QTest::QBenchmarkMetric metricType() override;
static bool isAvailable();
- static QTest::QBenchmarkMetric metricForEvent(quint32 type, quint64 event_id);
static void setCounter(const char *name);
static void listCounters();
private:
- int fd = -1;
+ QList<int> fds;
- qint64 readValue();
+ Measurement readValue(qsizetype idx = 0);
};
QT_END_NAMESPACE
diff --git a/src/testlib/qbenchmarktimemeasurers_p.h b/src/testlib/qbenchmarktimemeasurers_p.h
index d07af873cc..2f1364db02 100644
--- a/src/testlib/qbenchmarktimemeasurers_p.h
+++ b/src/testlib/qbenchmarktimemeasurers_p.h
@@ -25,13 +25,11 @@ class QBenchmarkTimeMeasurer : public QBenchmarkMeasurerBase
{
public:
void start() override;
- qint64 checkpoint() override;
- qint64 stop() override;
- bool isMeasurementAccepted(qint64 measurement) override;
+ QList<Measurement> stop() override;
+ bool isMeasurementAccepted(Measurement measurement) override;
int adjustIterationCount(int sugestion) override;
int adjustMedianCount(int suggestion) override;
bool needsWarmupIteration() override;
- QTest::QBenchmarkMetric metricType() override;
private:
QElapsedTimer time;
};
@@ -42,13 +40,11 @@ class QBenchmarkTickMeasurer : public QBenchmarkMeasurerBase
{
public:
void start() override;
- qint64 checkpoint() override;
- qint64 stop() override;
- bool isMeasurementAccepted(qint64 measurement) override;
+ QList<Measurement> stop() override;
+ bool isMeasurementAccepted(Measurement measurement) override;
int adjustIterationCount(int) override;
int adjustMedianCount(int suggestion) override;
bool needsWarmupIteration() override;
- QTest::QBenchmarkMetric metricType() override;
private:
CycleCounterTicks startTicks;
};
diff --git a/src/testlib/qbenchmarkvalgrind.cpp b/src/testlib/qbenchmarkvalgrind.cpp
index 43e444b211..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);
}
@@ -170,19 +173,14 @@ void QBenchmarkCallgrindMeasurer::start()
CALLGRIND_ZERO_STATS;
}
-qint64 QBenchmarkCallgrindMeasurer::checkpoint()
+QList<QBenchmarkMeasurerBase::Measurement> QBenchmarkCallgrindMeasurer::stop()
{
CALLGRIND_DUMP_STATS;
const qint64 result = QBenchmarkValgrindUtils::extractLastResult();
- return result;
-}
-
-qint64 QBenchmarkCallgrindMeasurer::stop()
-{
- return checkpoint();
+ return { { qreal(result), QTest::InstructionReads } };
}
-bool QBenchmarkCallgrindMeasurer::isMeasurementAccepted(qint64 measurement)
+bool QBenchmarkCallgrindMeasurer::isMeasurementAccepted(Measurement measurement)
{
Q_UNUSED(measurement);
return true;
@@ -203,9 +201,4 @@ bool QBenchmarkCallgrindMeasurer::needsWarmupIteration()
return true;
}
-QTest::QBenchmarkMetric QBenchmarkCallgrindMeasurer::metricType()
-{
- return QTest::InstructionReads;
-}
-
QT_END_NAMESPACE
diff --git a/src/testlib/qbenchmarkvalgrind_p.h b/src/testlib/qbenchmarkvalgrind_p.h
index 7c0cfe570e..f9925c9211 100644
--- a/src/testlib/qbenchmarkvalgrind_p.h
+++ b/src/testlib/qbenchmarkvalgrind_p.h
@@ -41,13 +41,11 @@ class QBenchmarkCallgrindMeasurer : public QBenchmarkMeasurerBase
{
public:
void start() override;
- qint64 checkpoint() override;
- qint64 stop() override;
- bool isMeasurementAccepted(qint64 measurement) override;
+ QList<Measurement> stop() override;
+ bool isMeasurementAccepted(Measurement measurement) override;
int adjustIterationCount(int) override;
int adjustMedianCount(int) override;
bool needsWarmupIteration() override;
- QTest::QBenchmarkMetric metricType() override;
};
QT_END_NAMESPACE
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/qcsvbenchmarklogger.cpp b/src/testlib/qcsvbenchmarklogger.cpp
index 1514936a1d..d4de8f08b7 100644
--- a/src/testlib/qcsvbenchmarklogger.cpp
+++ b/src/testlib/qcsvbenchmarklogger.cpp
@@ -57,13 +57,14 @@ void QCsvBenchmarkLogger::addBenchmarkResult(const QBenchmarkResult &result)
: "";
const char *filler = (tag[0] && gtag[0]) ? ":" : "";
- const char *metric = QTest::benchmarkMetricName(result.metric);
+ const char *metric = QTest::benchmarkMetricName(result.measurement.metric);
char buf[1024];
// "function","[globaltag:]tag","metric",value_per_iteration,total,iterations
qsnprintf(buf, sizeof(buf), "\"%s\",\"%s%s%s\",\"%s\",%.13g,%.13g,%u\n",
fn, gtag, filler, tag, metric,
- result.value / result.iterations, result.value, result.iterations);
+ result.measurement.value / result.iterations,
+ result.measurement.value, result.iterations);
outputString(buf);
}
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 69b31edb6e..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.
@@ -45,7 +38,7 @@ QJUnitTestLogger::~QJUnitTestLogger()
// We track test timing per test case, so we
// need to maintain our own elapsed timer.
-static QElapsedTimer elapsedTestcaseTime;
+Q_CONSTINIT static QElapsedTimer elapsedTestcaseTime;
static qreal elapsedTestCaseSeconds()
{
return elapsedTestcaseTime.nsecsElapsed() / 1e9;
@@ -179,7 +172,7 @@ void QJUnitTestLogger::leaveTestFunction()
void QJUnitTestLogger::leaveTestCase()
{
currentTestCase->addAttribute(QTest::AI_Time,
- toSecondsFormat(elapsedTestCaseSeconds()).constData());
+ toSecondsFormat(elapsedTestCaseSeconds() * 1000).constData());
if (!systemOutputElement->childElements().empty())
currentTestCase->addChild(systemOutputElement);
diff --git a/src/testlib/qplaintestlogger.cpp b/src/testlib/qplaintestlogger.cpp
index e716c38a73..58c7f6bf5b 100644
--- a/src/testlib/qplaintestlogger.cpp
+++ b/src/testlib/qplaintestlogger.cpp
@@ -10,18 +10,12 @@
#include <QtCore/private/qlogging_p.h>
-#include <stdarg.h>
+#include <array>
+
#include <stdio.h>
#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>
@@ -38,9 +32,78 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+namespace {
+static const char multiplePrefixes[] = "\0kMGTPE"; // kilo, mega, giga, tera, peta, exa
+static const char submultiplePrefixes[] = "afpnum"; // atto, femto, pico, nano, micro, milli
+
+template <int N> struct FixedBufString
+{
+ static constexpr size_t MaxSize = N;
+ size_t used = 0;
+ std::array<char, N + 2> buf; // for the newline and terminating null
+ FixedBufString()
+ {
+ clear();
+ }
+ void clear()
+ {
+ used = 0;
+ buf[0] = '\0';
+ }
+
+ operator const char *() const
+ {
+ return buf.data();
+ }
+
+ void append(const char *text)
+ {
+ size_t len = qMin(strlen(text), MaxSize - used);
+ memcpy(buf.data() + used, text, len);
+ used += len;
+ buf[used] = '\0';
+ }
+
+ template <typename... Args> void appendf(const char *format, Args &&... args)
+ {
+ // vsnprintf includes the terminating null
+ used += qsnprintf(buf.data() + used, MaxSize - used + 1, format,
+ std::forward<Args>(args)...);
+ }
+
+ template <int Power = 1000> void appendScaled(qreal value, const char *unit)
+ {
+ char prefix[2] = {};
+ qreal v = qAbs(value);
+ qint64 ratio;
+ if (v < 1 && Power == 1000) {
+ const char *prefixes = submultiplePrefixes;
+ ratio = qreal(std::atto::num) / std::atto::den;
+ while (value * ratio > 1000 && *prefixes) {
+ ++prefixes;
+ ratio *= 1000;
+ }
+ prefix[0] = *prefixes;
+ } else {
+ const char *prefixes = multiplePrefixes;
+ ratio = 1;
+ while (value > 1000 * ratio) { // yes, even for binary
+ ++prefixes;
+ ratio *= Power;
+ }
+ prefix[0] = *prefixes;
+ }
+
+ // adjust the value by the ratio
+ value /= ratio;
+ appendf(", %.3g %s%s", value, prefix, unit);
+ }
+};
+} // unnamed namespace
+
namespace QTest {
- static const char *incidentType2String(QAbstractTestLogger::IncidentTypes type)
+ static const char *ptIncidentType2String(QAbstractTestLogger::IncidentTypes type)
{
switch (type) {
case QAbstractTestLogger::Skip:
@@ -62,8 +125,7 @@ namespace QTest {
case QAbstractTestLogger::BlacklistedXFail:
return "BXFAIL ";
}
- Q_UNREACHABLE();
- return nullptr;
+ Q_UNREACHABLE_RETURN(nullptr);
}
static const char *benchmarkResult2String()
@@ -71,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:
@@ -89,8 +151,7 @@ namespace QTest {
case QAbstractTestLogger::Warn:
return "WARNING";
}
- Q_UNREACHABLE();
- return nullptr;
+ Q_UNREACHABLE_RETURN(nullptr);
}
template <typename T>
@@ -111,19 +172,19 @@ namespace QTest {
}
// Pretty-prints a benchmark result using the given number of digits.
- template <typename T> QString formatResult(T number, int significantDigits)
+ template <typename T> QByteArray formatResult(T number, int significantDigits)
{
if (number < T(0))
- return u"NAN"_s;
+ return "NAN";
if (number == T(0))
- return u"0"_s;
+ return "0";
- QString beforeDecimalPoint = QString::number(qint64(number), 'f', 0);
- QString afterDecimalPoint = QString::number(number, 'f', 20);
- afterDecimalPoint.remove(0, beforeDecimalPoint.length() + 1);
+ QByteArray beforeDecimalPoint = QByteArray::number(qint64(number), 'f', 0);
+ QByteArray afterDecimalPoint = QByteArray::number(number, 'f', 20);
+ afterDecimalPoint.remove(0, beforeDecimalPoint.size() + 1);
- int beforeUse = qMin(beforeDecimalPoint.length(), significantDigits);
- int beforeRemove = beforeDecimalPoint.length() - beforeUse;
+ int beforeUse = qMin(beforeDecimalPoint.size(), significantDigits);
+ int beforeRemove = beforeDecimalPoint.size() - beforeUse;
// Replace insignificant digits before the decimal point with zeros.
beforeDecimalPoint.chop(beforeRemove);
@@ -134,30 +195,30 @@ namespace QTest {
int afterUse = significantDigits - beforeUse;
// leading zeroes after the decimal point does not count towards the digit use.
- if (beforeDecimalPoint == u'0' && !afterDecimalPoint.isEmpty()) {
+ if (beforeDecimalPoint == "0" && !afterDecimalPoint.isEmpty()) {
++afterUse;
int i = 0;
- while (i < afterDecimalPoint.length() && afterDecimalPoint.at(i) == u'0')
+ while (i < afterDecimalPoint.size() && afterDecimalPoint.at(i) == '0')
++i;
afterUse += i;
}
- int afterRemove = afterDecimalPoint.length() - afterUse;
+ int afterRemove = afterDecimalPoint.size() - afterUse;
afterDecimalPoint.chop(afterRemove);
- QChar separator = u',';
- QChar decimalPoint = u'.';
+ char separator = ',';
+ char decimalPoint = '.';
// insert thousands separators
- int length = beforeDecimalPoint.length();
- for (int i = beforeDecimalPoint.length() -1; i >= 1; --i) {
+ int length = beforeDecimalPoint.size();
+ for (int i = beforeDecimalPoint.size() -1; i >= 1; --i) {
if ((length - i) % 3 == 0)
beforeDecimalPoint.insert(i, separator);
}
- QString print;
+ QByteArray print;
print = beforeDecimalPoint;
if (afterUse > 0)
print.append(decimalPoint);
@@ -167,15 +228,6 @@ namespace QTest {
return print;
}
-
- template <typename T>
- int formatResult(char * buffer, int bufferSize, T number, int significantDigits)
- {
- QString result = formatResult(number, significantDigits);
- int size = result.length();
- qstrncpy(buffer, std::move(result).toLatin1().constData(), bufferSize);
- return size;
- }
}
/*! \internal
@@ -184,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)
@@ -242,60 +294,110 @@ void QPlainTestLogger::printMessage(MessageSource source, const char *type, cons
outputMessage(messagePrefix.data());
}
-void QPlainTestLogger::printBenchmarkResult(const QBenchmarkResult &result)
+void QPlainTestLogger::printBenchmarkResultsHeader(const QBenchmarkResult &result)
{
- const char *bmtag = QTest::benchmarkResult2String();
-
- char buf1[1024];
- qsnprintf(
- buf1, sizeof(buf1), "%s: %s::%s",
- bmtag,
- QTestResult::currentTestObjectName(),
- result.context.slotName.toLatin1().data());
-
- char bufTag[1024];
- bufTag[0] = 0;
- QByteArray tag = result.context.tag.toLocal8Bit();
- if (tag.isEmpty() == false) {
- qsnprintf(bufTag, sizeof(bufTag), ":\"%s\"", tag.data());
- }
-
-
- char fillFormat[8];
- int fillLength = 5;
- qsnprintf(fillFormat, sizeof(fillFormat), ":\n%%%ds", fillLength);
- char fill[1024];
- qsnprintf(fill, sizeof(fill), fillFormat, "");
-
- const char * unitText = QTest::benchmarkMetricUnit(result.metric);
+ FixedBufString<1022> buf;
+ buf.appendf("%s: %s::%s", QTest::benchmarkResult2String(),
+ QTestResult::currentTestObjectName(), result.context.slotName.toLatin1().data());
+
+ if (QByteArray tag = result.context.tag.toLocal8Bit(); !tag.isEmpty())
+ buf.appendf(":\"%s\":\n", tag.data());
+ else
+ buf.append(":\n");
+ outputMessage(buf);
+}
- qreal valuePerIteration = qreal(result.value) / qreal(result.iterations);
- char resultBuffer[100] = "";
- QTest::formatResult(resultBuffer, 100, valuePerIteration, QTest::countSignificantDigits(result.value));
+void QPlainTestLogger::printBenchmarkResults(const QList<QBenchmarkResult> &results)
+{
+ using namespace std::chrono;
+ FixedBufString<1022> buf;
+ auto findResultFor = [&results](QTest::QBenchmarkMetric metric) -> std::optional<qreal> {
+ for (const QBenchmarkResult &result : results) {
+ if (result.measurement.metric == metric)
+ return result.measurement.value;
+ }
+ return std::nullopt;
+ };
+
+ // we need the execution time quite often, so find it first
+ qreal executionTime = 0;
+ if (auto ns = findResultFor(QTest::WalltimeNanoseconds))
+ executionTime = *ns / (1000 * 1000 * 1000);
+ else if (auto ms = findResultFor(QTest::WalltimeMilliseconds))
+ executionTime = *ms / 1000;
+
+ for (const QBenchmarkResult &result : results) {
+ buf.clear();
+
+ const char * unitText = QTest::benchmarkMetricUnit(result.measurement.metric);
+ int significantDigits = QTest::countSignificantDigits(result.measurement.value);
+ qreal valuePerIteration = qreal(result.measurement.value) / qreal(result.iterations);
+ buf.appendf(" %s %s%s", QTest::formatResult(valuePerIteration, significantDigits).constData(),
+ unitText, result.setByMacro ? " per iteration" : "");
+
+ switch (result.measurement.metric) {
+ case QTest::BitsPerSecond:
+ // for bits/s, we'll use powers of 10 (1 Mbit/s = 1000 kbit/s = 1000000 bit/s)
+ buf.appendScaled<1000>(result.measurement.value, "bit/s");
+ break;
+ case QTest::BytesPerSecond:
+ // for B/s, we'll use powers of 2 (1 MB/s = 1024 kB/s = 1048576 B/s)
+ buf.appendScaled<1024>(result.measurement.value, "B/s");
+ break;
- char buf2[1024];
- qsnprintf(buf2, sizeof(buf2), "%s %s", resultBuffer, unitText);
+ case QTest::CPUCycles:
+ case QTest::RefCPUCycles:
+ if (!qIsNull(executionTime))
+ buf.appendScaled(result.measurement.value / executionTime, "Hz");
+ break;
- char buf2_[1024];
- QByteArray iterationText = " per iteration";
- Q_ASSERT(result.iterations > 0);
- qsnprintf(buf2_, sizeof(buf2_), "%s", iterationText.data());
+ case QTest::Instructions:
+ if (auto cycles = findResultFor(QTest::CPUCycles)) {
+ buf.appendf(", %.3f instr/cycle", result.measurement.value / *cycles);
+ break;
+ }
+ Q_FALLTHROUGH();
+
+ case QTest::InstructionReads:
+ case QTest::Events:
+ case QTest::BytesAllocated:
+ case QTest::CPUMigrations:
+ case QTest::BusCycles:
+ case QTest::StalledCycles:
+ case QTest::BranchInstructions:
+ case QTest::BranchMisses:
+ case QTest::CacheReferences:
+ case QTest::CacheReads:
+ case QTest::CacheWrites:
+ case QTest::CachePrefetches:
+ case QTest::CacheMisses:
+ case QTest::CacheReadMisses:
+ case QTest::CacheWriteMisses:
+ case QTest::CachePrefetchMisses:
+ case QTest::ContextSwitches:
+ case QTest::PageFaults:
+ case QTest::MinorPageFaults:
+ case QTest::MajorPageFaults:
+ case QTest::AlignmentFaults:
+ case QTest::EmulationFaults:
+ if (!qIsNull(executionTime))
+ buf.appendScaled(result.measurement.value / executionTime, "/sec");
+ break;
- char buf3[1024];
- Q_ASSERT(result.iterations > 0);
- QTest::formatResult(resultBuffer, 100, result.value, QTest::countSignificantDigits(result.value));
- qsnprintf(buf3, sizeof(buf3), " (total: %s, iterations: %d)", resultBuffer, result.iterations);
+ case QTest::FramesPerSecond:
+ case QTest::CPUTicks:
+ case QTest::WalltimeMilliseconds:
+ case QTest::WalltimeNanoseconds:
+ break; // no additional information
+ }
- char buf[1024];
+ Q_ASSERT(result.iterations > 0);
+ buf.appendf(" (total: %s, iterations: %d)\n",
+ QTest::formatResult(result.measurement.value, significantDigits).constData(),
+ result.iterations);
- if (result.setByMacro) {
- qsnprintf(buf, sizeof(buf), "%s%s%s%s%s%s\n", buf1, bufTag, fill, buf2, buf2_, buf3);
- } else {
- qsnprintf(buf, sizeof(buf), "%s%s%s%s\n", buf1, bufTag, fill, buf2);
+ outputMessage(buf);
}
-
- memcpy(buf, bmtag, strlen(bmtag));
- outputMessage(buf);
}
QPlainTestLogger::QPlainTestLogger(const char *filename)
@@ -347,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()
@@ -362,16 +464,17 @@ 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::addBenchmarkResult(const QBenchmarkResult &result)
+void QPlainTestLogger::addBenchmarkResults(const QList<QBenchmarkResult> &results)
{
// suppress benchmark results in silent mode
- if (QTestLog::verboseLevel() < 0)
+ if (QTestLog::verboseLevel() < 0 || results.isEmpty())
return;
- printBenchmarkResult(result);
+ printBenchmarkResultsHeader(results.first());
+ printBenchmarkResults(results);
}
void QPlainTestLogger::addMessage(QtMsgType type, const QMessageLogContext &context, const QString &message)
@@ -386,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 ddb406f261..819a54fd50 100644
--- a/src/testlib/qplaintestlogger_p.h
+++ b/src/testlib/qplaintestlogger_p.h
@@ -33,7 +33,9 @@ public:
void addIncident(IncidentTypes type, const char *description,
const char *file = nullptr, int line = 0) override;
- void addBenchmarkResult(const QBenchmarkResult &result) override;
+ void addBenchmarkResult(const QBenchmarkResult &) final override
+ { Q_UNREACHABLE(); }
+ void addBenchmarkResults(const QList<QBenchmarkResult> &results) override;
void addMessage(QtMsgType, const QMessageLogContext &,
const QString &) override;
@@ -41,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,
@@ -49,7 +53,8 @@ private:
void printMessage(MessageSource source, const char *type, const char *msg,
const char *file = nullptr, int line = 0);
void outputMessage(const char *str);
- void printBenchmarkResult(const QBenchmarkResult &result);
+ void printBenchmarkResultsHeader(const QBenchmarkResult &result);
+ void printBenchmarkResults(const QList<QBenchmarkResult> &result);
};
QT_END_NAMESPACE
diff --git a/src/testlib/qpropertytesthelper_p.h b/src/testlib/qpropertytesthelper_p.h
index b5bd8d2583..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();
@@ -129,7 +139,7 @@ void testReadWritePropertyBasics(
testedObj.property(propertyName).template value<PropertyType>(), initial, comparator,
represent);
if (spy)
- QCOMPARE(spy->count(), 1);
+ QCOMPARE(spy->size(), 1);
QUntypedBindable bindable = metaProperty.bindable(&instance);
@@ -148,7 +158,7 @@ void testReadWritePropertyBasics(
QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), changed, comparator, represent);
QPROPERTY_TEST_COMPARISON_HELPER(propObserverLambda.value(), changed, comparator, represent);
if (spy)
- QCOMPARE(spy->count(), 2);
+ QCOMPARE(spy->size(), 2);
// Bind object's property to other property
QProperty<PropertyType> propSetter(initial);
@@ -162,7 +172,7 @@ void testReadWritePropertyBasics(
QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), initial, comparator, represent);
QPROPERTY_TEST_COMPARISON_HELPER(propObserverLambda.value(), initial, comparator, represent);
if (spy)
- QCOMPARE(spy->count(), 3);
+ QCOMPARE(spy->size(), 3);
// Count notifications triggered; should only happen on actual change.
int updateCount = 0;
@@ -177,7 +187,7 @@ void testReadWritePropertyBasics(
QPROPERTY_TEST_COMPARISON_HELPER(propObserverLambda.value(), changed, comparator, represent);
QCOMPARE(updateCount, 1);
if (spy)
- QCOMPARE(spy->count(), 4);
+ QCOMPARE(spy->size(), 4);
// Test that manually setting the value (even the same one) breaks the
// binding.
@@ -188,7 +198,46 @@ void testReadWritePropertyBasics(
// value didn't change -> the signal should not be emitted
if (spy)
- QCOMPARE(spy->count(), 4);
+ 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(
@@ -287,7 +355,7 @@ void testWriteOncePropertyBasics(
represent);
QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), changed, comparator, represent);
if (spy)
- QCOMPARE(spy->count(), 1);
+ QCOMPARE(spy->size(), 1);
// Attempt to set back the 'prior' value and verify that it has no effect
testedObj.setProperty(propertyName, QVariant::fromValue(prior));
@@ -296,13 +364,34 @@ void testWriteOncePropertyBasics(
represent);
QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), changed, comparator, represent);
if (spy)
- QCOMPARE(spy->count(), 1);
+ QCOMPARE(spy->size(), 1);
if (bindingPreservedOnWrite)
QVERIFY(bindable.hasBinding());
else
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
@@ -386,7 +475,7 @@ void testReadOnlyPropertyBasics(
testedObj.property(propertyName).template value<PropertyType>(), initial, comparator,
represent);
if (spy)
- QCOMPARE(spy->count(), 0);
+ QCOMPARE(spy->size(), 0);
QProperty<PropertyType> propObserver;
propObserver.setBinding(bindable.makeBinding());
@@ -402,7 +491,7 @@ void testReadOnlyPropertyBasics(
QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), changed, comparator, represent);
if (spy)
- QCOMPARE(spy->count(), 1);
+ QCOMPARE(spy->size(), 1);
}
} // namespace QTestPrivate
diff --git a/src/testlib/qsignaldumper.cpp b/src/testlib/qsignaldumper.cpp
index 7bef9a8f11..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;
}
@@ -60,7 +71,7 @@ static void qSignalDumperCallback(QObject *caller, int signal_index, void **argv
str += " (";
QList<QByteArray> args = member.parameterTypes();
- for (int i = 0; i < args.count(); ++i) {
+ for (int i = 0; i < args.size(); ++i) {
const QByteArray &arg = args.at(i);
int typeId = QMetaType::fromName(args.at(i).constData()).id();
if (arg.endsWith('*') || arg.endsWith('&')) {
@@ -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 efe07f0360..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);
- }
-
-#ifdef Q_CLANG_QDOC
+ : 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);
- }
-#endif // Q_CLANG_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(verify(obj, QMetaMethod::fromSignal(signal0))) {}
+#endif // Q_QDOC
+ 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 int origCount = count();
- m_waiting = true;
- m_loop.enterLoopMSecs(timeout);
- m_waiting = false;
- return count() > 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.count());
- for (int i = 0; i < args.count(); ++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/qt_cmdline.cmake b/src/testlib/qt_cmdline.cmake
index e69de29bb2..d3601c1e39 100644
--- a/src/testlib/qt_cmdline.cmake
+++ b/src/testlib/qt_cmdline.cmake
@@ -0,0 +1 @@
+qt_commandline_option(batch-tests TYPE boolean NAME batch_tests)
diff --git a/src/testlib/qtaptestlogger.cpp b/src/testlib/qtaptestlogger.cpp
index b6f8c87144..76f0ba0e8b 100644
--- a/src/testlib/qtaptestlogger.cpp
+++ b/src/testlib/qtaptestlogger.cpp
@@ -281,25 +281,53 @@ void QTapTestLogger::addIncident(IncidentTypes type, const char *description,
const char *indent = isExpectedFail ? YAML_INDENT YAML_INDENT YAML_INDENT : YAML_INDENT;
if (!ok) {
#if QT_CONFIG(regularexpression)
+ enum class OperationType {
+ Unknown,
+ Compare, /* Plain old QCOMPARE */
+ Verify, /* QVERIFY */
+ CompareOp, /* QCOMPARE_OP */
+ };
+
// This is fragile, but unfortunately testlib doesn't plumb
// the expected and actual values to the loggers (yet).
- static QRegularExpression verifyRegex(u"^'(?<actualexpression>.*)' returned "
- "(?<actual>\\w+).+\\((?<message>.*)\\)$"_s);
+ static const QRegularExpression verifyRegex(
+ u"^'(?<actualexpression>.*)' returned "
+ "(?<actual>\\w+)\\. \\((?<message>.*)\\)$"_s);
- static QRegularExpression compareRegex(
+ static const QRegularExpression compareRegex(
u"^(?<message>.*)\n"
"\\s*Actual\\s+\\((?<actualexpression>.*)\\)\\s*: (?<actual>.*)\n"
"\\s*Expected\\s+\\((?<expectedexpresssion>.*)\\)\\s*: "
"(?<expected>.*)$"_s);
- QString descriptionString = QString::fromUtf8(description);
+ static const QRegularExpression compareOpRegex(
+ u"^(?<message>.*)\n"
+ "\\s*Computed\\s+\\((?<actualexpression>.*)\\)\\s*: (?<actual>.*)\n"
+ "\\s*Baseline\\s+\\((?<expectedexpresssion>.*)\\)\\s*: "
+ "(?<expected>.*)$"_s);
+
+ const QString descriptionString = QString::fromUtf8(description);
QRegularExpressionMatch match = verifyRegex.match(descriptionString);
- const bool isVerify = match.hasMatch();
- if (!isVerify)
+
+ OperationType opType = OperationType::Unknown;
+ if (match.hasMatch())
+ opType = OperationType::Verify;
+
+ if (opType == OperationType::Unknown) {
match = compareRegex.match(descriptionString);
+ if (match.hasMatch())
+ opType = OperationType::Compare;
+ }
+
+ if (opType == OperationType::Unknown) {
+ match = compareOpRegex.match(descriptionString);
+ if (match.hasMatch())
+ opType = OperationType::CompareOp;
+ }
- if (match.hasMatch()) {
+ if (opType != OperationType::Unknown) {
QString message = match.captured(u"message");
+ QLatin1StringView comparisonType;
QString expected;
QString actual;
const auto parenthesize = [&match](QLatin1StringView key) -> QString {
@@ -307,16 +335,45 @@ void QTapTestLogger::addIncident(IncidentTypes type, const char *description,
};
const QString actualExpression = parenthesize("actualexpression"_L1);
- if (isVerify) {
+ if (opType == OperationType::Verify) {
+ comparisonType = "QVERIFY"_L1;
actual = match.captured(u"actual").toLower() % actualExpression;
expected = (actual.startsWith("true "_L1) ? "false"_L1 : "true"_L1)
% actualExpression;
if (message.isEmpty())
message = u"Verification failed"_s;
- } else {
+ } else if (opType == OperationType::Compare) {
+ comparisonType = "QCOMPARE"_L1;
expected = match.captured(u"expected")
% parenthesize("expectedexpresssion"_L1);
actual = match.captured(u"actual") % actualExpression;
+ } else {
+ struct ComparisonInfo {
+ const char *comparisonType;
+ const char *comparisonStringOp;
+ };
+ // get a proper comparison type based on the error message
+ const auto info = [](const QString &err) -> ComparisonInfo {
+ if (err.contains("different"_L1))
+ return { "QCOMPARE_NE", "!= " };
+ else if (err.contains("less than or equal to"_L1))
+ return { "QCOMPARE_LE", "<= " };
+ else if (err.contains("greater than or equal to"_L1))
+ return { "QCOMPARE_GE", ">= " };
+ else if (err.contains("less than"_L1))
+ return { "QCOMPARE_LT", "< " };
+ else if (err.contains("greater than"_L1))
+ return { "QCOMPARE_GT", "> " };
+ else if (err.contains("to be equal to"_L1))
+ return { "QCOMPARE_EQ", "== " };
+ else
+ return { "Unknown", "" };
+ }(message);
+ comparisonType = QLatin1StringView(info.comparisonType);
+ expected = QLatin1StringView(info.comparisonStringOp)
+ % match.captured(u"expected")
+ % parenthesize("expectedexpresssion"_L1);
+ actual = match.captured(u"actual") % actualExpression;
}
QTestCharBuffer diagnosticsYamlish;
@@ -329,7 +386,7 @@ void QTapTestLogger::addIncident(IncidentTypes type, const char *description,
"%sfound: %s\n"
"%sexpected: %s\n"
"%sactual: %s\n",
- indent, isVerify ? "QVERIFY" : "QCOMPARE",
+ indent, comparisonType.latin1(),
indent, qPrintable(message),
indent, qPrintable(expected), indent, qPrintable(actual),
indent, qPrintable(expected), indent, qPrintable(actual)
diff --git a/src/testlib/qteamcitylogger.cpp b/src/testlib/qteamcitylogger.cpp
index 20d15e8d3f..840a0334f6 100644
--- a/src/testlib/qteamcitylogger.cpp
+++ b/src/testlib/qteamcitylogger.cpp
@@ -7,6 +7,9 @@
#include <QtTest/private/qtestlog_p.h>
#include <QtTest/private/qteamcitylogger_p.h>
#include <QtCore/qbytearray.h>
+#include <private/qlocale_p.h>
+
+#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@@ -18,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:
@@ -43,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:
@@ -83,16 +86,20 @@ void QTeamCityLogger::startLogging()
{
QAbstractTestLogger::startLogging();
- flowID = tcEscapedString(QString::fromUtf8(QTestResult::currentTestObjectName()));
+ tcEscapedString(&flowID, QTestResult::currentTestObjectName());
- QString str = "##teamcity[testSuiteStarted name='%1' flowId='%1']\n"_L1.arg(flowID);
- outputString(qPrintable(str));
+ QTestCharBuffer buf;
+ QTest::qt_asprintf(&buf, "##teamcity[testSuiteStarted name='%s' flowId='%s']\n",
+ flowID.constData(), flowID.constData());
+ outputString(buf.constData());
}
void QTeamCityLogger::stopLogging()
{
- QString str = "##teamcity[testSuiteFinished name='%1' flowId='%1']\n"_L1.arg(flowID);
- outputString(qPrintable(str));
+ QTestCharBuffer buf;
+ QTest::qt_asprintf(&buf, "##teamcity[testSuiteFinished name='%s' flowId='%s']\n",
+ flowID.constData(), flowID.constData());
+ outputString(buf.constData());
QAbstractTestLogger::stopLogging();
}
@@ -114,56 +121,66 @@ void QTeamCityLogger::addIncident(IncidentTypes type, const char *description,
if ((type == Pass || type == XFail || type == BlacklistedPass || type == BlacklistedXFail) && QTestLog::verboseLevel() < 0)
return;
- QString buf;
+ QTestCharBuffer buf;
+ QTestCharBuffer tmpFuncName;
+ escapedTestFuncName(&tmpFuncName);
- QString tmpFuncName = escapedTestFuncName();
+ if (qstrcmp(tmpFuncName.constData(), currTestFuncName.constData()) != 0) {
+ QTest::qt_asprintf(&buf, "##teamcity[testStarted name='%s' flowId='%s']\n",
+ tmpFuncName.constData(), flowID.constData());
+ outputString(buf.constData());
- if (tmpFuncName != currTestFuncName) {
- buf = "##teamcity[testStarted name='%1' flowId='%2']\n"_L1.arg(tmpFuncName, flowID);
- outputString(qPrintable(buf));
+ currTestFuncName.clear();
+ QTestPrivate::appendCharBuffer(&currTestFuncName, tmpFuncName);
}
- currTestFuncName = tmpFuncName;
-
if (type == QAbstractTestLogger::XFail) {
- addPendingMessage(QTest::incidentType2String(type), QString::fromUtf8(description), file, line);
+ addPendingMessage(QTest::tcIncidentType2String(type), description, file, line);
return;
}
- QString detailedText = tcEscapedString(QString::fromUtf8(description));
+ QTestCharBuffer detailedText;
+ tcEscapedString(&detailedText, description);
// Test failed
if (type == Fail || type == XPass) {
- QString messageText(u"Failure!"_s);
-
+ QTestCharBuffer messageText;
if (file)
- messageText += " |[Loc: %1(%2)|]"_L1.arg(QString::fromUtf8(file)).arg(line);
+ QTest::qt_asprintf(&messageText, "Failure! |[Loc: %s(%d)|]", file, line);
+ else
+ QTest::qt_asprintf(&messageText, "Failure!");
- buf = "##teamcity[testFailed name='%1' message='%2' details='%3' flowId='%4']\n"_L1
- .arg(tmpFuncName, messageText, detailedText, flowID);
+ QTest::qt_asprintf(&buf, "##teamcity[testFailed name='%s' message='%s' details='%s'"
+ " flowId='%s']\n", tmpFuncName.constData(), messageText.constData(),
+ detailedText.constData(), flowID.constData());
- outputString(qPrintable(buf));
+ outputString(buf.constData());
} else if (type == Skip) {
- if (file)
- detailedText.append(" |[Loc: %1(%2)|]"_L1.arg(QString::fromUtf8(file)).arg(line));
+ if (file) {
+ QTestCharBuffer detail;
+ QTest::qt_asprintf(&detail, " |[Loc: %s(%d)|]", file, line);
+ QTestPrivate::appendCharBuffer(&detailedText, detail);
+ }
- buf = "##teamcity[testIgnored name='%1' message='%2' flowId='%3']\n"_L1
- .arg(escapedTestFuncName(), detailedText, flowID);
+ QTest::qt_asprintf(&buf, "##teamcity[testIgnored name='%s' message='%s' flowId='%s']\n",
+ currTestFuncName.constData(), detailedText.constData(),
+ flowID.constData());
- outputString(qPrintable(buf));
+ outputString(buf.constData());
}
if (!pendingMessages.isEmpty()) {
- buf = "##teamcity[testStdOut name='%1' out='%2' flowId='%3']\n"_L1
- .arg(tmpFuncName, pendingMessages, flowID);
-
- outputString(qPrintable(buf));
+ QTest::qt_asprintf(&buf, "##teamcity[testStdOut name='%s' out='%s' flowId='%s']\n",
+ tmpFuncName.constData(), pendingMessages.constData(),
+ flowID.constData());
+ outputString(buf.constData());
pendingMessages.clear();
}
- buf = "##teamcity[testFinished name='%1' flowId='%2']\n"_L1.arg(tmpFuncName, flowID);
- outputString(qPrintable(buf));
+ QTest::qt_asprintf(&buf, "##teamcity[testFinished name='%s' flowId='%s']\n",
+ tmpFuncName.constData(), flowID.constData());
+ outputString(buf.constData());
}
void QTeamCityLogger::addBenchmarkResult(const QBenchmarkResult &)
@@ -178,68 +195,85 @@ void QTeamCityLogger::addMessage(MessageTypes type, const QString &message,
if (type != QFatal && QTestLog::verboseLevel() < 0)
return;
- QString escapedMessage = tcEscapedString(message);
- addPendingMessage(QTest::messageType2String(type), escapedMessage, file, line);
+ QTestCharBuffer escapedMessage;
+ tcEscapedString(&escapedMessage, qUtf8Printable(message));
+ addPendingMessage(QTest::tcMessageType2String(type), escapedMessage.constData(), file, line);
}
-QString QTeamCityLogger::tcEscapedString(const QString &str) const
+void QTeamCityLogger::tcEscapedString(QTestCharBuffer *buf, const char *str) const
{
- QString formattedString;
+ {
+ size_t size = qstrlen(str) + 1;
+ for (const char *p = str; *p; ++p) {
+ if (strchr("\n\r|[]'", *p))
+ ++size;
+ }
+ Q_ASSERT(size <= size_t(INT_MAX));
+ buf->resize(int(size));
+ }
- for (QChar ch : str) {
- switch (ch.toLatin1()) {
+ bool swallowSpace = true;
+ char *p = buf->data();
+ for (; *str; ++str) {
+ char ch = *str;
+ switch (ch) {
case '\n':
- formattedString.append("|n"_L1);
+ p++[0] = '|';
+ ch = 'n';
+ swallowSpace = false;
break;
case '\r':
- formattedString.append("|r"_L1);
+ p++[0] = '|';
+ ch = 'r';
+ swallowSpace = false;
break;
case '|':
- formattedString.append("||"_L1);
- break;
case '[':
- formattedString.append("|["_L1);
- break;
case ']':
- formattedString.append("|]"_L1);
- break;
case '\'':
- formattedString.append("|'"_L1);
+ p++[0] = '|';
+ swallowSpace = false;
break;
default:
- formattedString.append(ch);
+ if (ascii_isspace(ch)) {
+ if (swallowSpace)
+ continue;
+ swallowSpace = true;
+ ch = ' ';
+ } else {
+ swallowSpace = false;
+ }
+ break;
}
+ p++[0] = ch;
}
-
- return std::move(formattedString).simplified();
+ Q_ASSERT(p < buf->data() + buf->size());
+ if (swallowSpace && p > buf->data()) {
+ Q_ASSERT(p[-1] == ' ');
+ --p;
+ }
+ Q_ASSERT(p == buf->data() || !ascii_isspace(p[-1]));
+ *p = '\0';
}
-QString QTeamCityLogger::escapedTestFuncName() const
+void QTeamCityLogger::escapedTestFuncName(QTestCharBuffer *buf) const
{
- const char *fn = QTestResult::currentTestFunction() ? QTestResult::currentTestFunction()
- : "UnknownTestFunc";
- const char *tag = QTestResult::currentDataTag() ? QTestResult::currentDataTag() : "";
-
- return tcEscapedString(QString::asprintf("%s(%s)", fn, tag));
+ constexpr int TestTag = QTestPrivate::TestFunction | QTestPrivate::TestDataTag;
+ QTestPrivate::generateTestIdentifier(buf, TestTag);
}
-void QTeamCityLogger::addPendingMessage(const char *type, const QString &msg, const char *file, int line)
+void QTeamCityLogger::addPendingMessage(const char *type, const char *msg,
+ const char *file, int line)
{
- QString pendMessage;
-
- if (!pendingMessages.isEmpty())
- pendMessage += "|n"_L1;
+ const char *pad = pendingMessages.isEmpty() ? "" : "|n";
- if (file) {
- pendMessage += "%1 |[Loc: %2(%3)|]: %4"_L1
- .arg(QString::fromUtf8(type), QString::fromUtf8(file),
- QString::number(line), msg);
-
- } else {
- pendMessage += "%1: %2"_L1.arg(QString::fromUtf8(type), msg);
- }
+ QTestCharBuffer newMessage;
+ if (file)
+ QTest::qt_asprintf(&newMessage, "%s%s |[Loc: %s(%d)|]: %s", pad, type, file, line, msg);
+ else
+ QTest::qt_asprintf(&newMessage, "%s%s: %s", pad, type, msg);
- pendingMessages.append(pendMessage);
+ QTestPrivate::appendCharBuffer(&pendingMessages, newMessage);
}
QT_END_NAMESPACE
diff --git a/src/testlib/qteamcitylogger_p.h b/src/testlib/qteamcitylogger_p.h
index 41df8bb7d1..406aa690e5 100644
--- a/src/testlib/qteamcitylogger_p.h
+++ b/src/testlib/qteamcitylogger_p.h
@@ -41,13 +41,13 @@ public:
const char *file = nullptr, int line = 0) override;
private:
- QString currTestFuncName;
- QString pendingMessages;
- QString flowID;
+ QTestCharBuffer currTestFuncName;
+ QTestCharBuffer pendingMessages;
+ QTestCharBuffer flowID;
- QString tcEscapedString(const QString &str) const;
- QString escapedTestFuncName() const;
- void addPendingMessage(const char *type, const QString &msg, const char *file, int line);
+ void tcEscapedString(QTestCharBuffer *buf, const char *str) const;
+ void escapedTestFuncName(QTestCharBuffer *buf) const;
+ void addPendingMessage(const char *type, const char *msg, const char *file, int line);
};
QT_END_NAMESPACE
diff --git a/src/testlib/qtest.h b/src/testlib/qtest.h
index cb73a9538a..ff3744a48e 100644
--- a/src/testlib/qtest.h
+++ b/src/testlib/qtest.h
@@ -5,37 +5,20 @@
#ifndef QTEST_H
#define QTEST_H
+#if 0
+#pragma qt_class(QTest)
+#endif
+
#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>
@@ -44,333 +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.length());
-}
-
-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());
-}
-
-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)
@@ -421,7 +77,7 @@ bool _q_compareSequence(ActualIterator actualIt, ActualIterator actualEnd,
delete [] val2;
}
}
- return compare_helper(isOk, msg, nullptr, nullptr, actual, expected, file, line);
+ return compare_helper(isOk, msg, actual, expected, file, line);
}
namespace Internal {
@@ -578,6 +234,7 @@ struct QtCoverageScanner
#define TESTLIB_SELFCOVERAGE_START(name)
#endif
+#if !defined(QTEST_BATCH_TESTS)
// Internal (but used by some testlib selftests to hack argc and argv).
// Tests should normally implement initMain() if they have set-up to do before
// instantiating the test class.
@@ -591,6 +248,30 @@ int main(int argc, char *argv[]) \
QTEST_SET_MAIN_SOURCE_PATH \
return QTest::qExec(&tc, argc, argv); \
}
+#else
+// BATCHED_TEST_NAME is defined for each test in a batch in cmake. Some odd
+// targets, like snippets, don't define it though. Play safe by providing a
+// default value.
+#if !defined(BATCHED_TEST_NAME)
+#define BATCHED_TEST_NAME "other"
+#endif
+#define QTEST_MAIN_WRAPPER(TestObject, ...) \
+\
+void qRegister##TestObject() \
+{ \
+ auto runTest = [](int argc, char** argv) -> int { \
+ TESTLIB_SELFCOVERAGE_START(TestObject) \
+ QT_PREPEND_NAMESPACE(QTest::Internal::callInitMain)<TestObject>(); \
+ __VA_ARGS__ \
+ TestObject tc; \
+ QTEST_SET_MAIN_SOURCE_PATH \
+ return QTest::qExec(&tc, argc, argv); \
+ }; \
+ QTest::qRegisterTestCase(QStringLiteral(BATCHED_TEST_NAME), runTest); \
+} \
+\
+Q_CONSTRUCTOR_FUNCTION(qRegister##TestObject)
+#endif
// For when you don't even want a QApplication:
#define QTEST_APPLESS_MAIN(TestObject) QTEST_MAIN_WRAPPER(TestObject)
diff --git a/src/testlib/qtest_gui.h b/src/testlib/qtest_gui.h
index 68c13ee57b..667debb3c4 100644
--- a/src/testlib/qtest_gui.h
+++ b/src/testlib/qtest_gui.h
@@ -17,11 +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>
@@ -77,7 +81,7 @@ template<> inline char *toString(const QRegion &region)
return qstrdup(result.constData());
}
-#if !defined(QT_NO_VECTOR2D) || defined(Q_CLANG_QDOC)
+#if !defined(QT_NO_VECTOR2D) || defined(Q_QDOC)
template<> inline char *toString(const QVector2D &v)
{
QByteArray result = "QVector2D(" + QByteArray::number(double(v.x())) + ", "
@@ -85,7 +89,7 @@ template<> inline char *toString(const QVector2D &v)
return qstrdup(result.constData());
}
#endif // !QT_NO_VECTOR2D
-#if !defined(QT_NO_VECTOR3D) || defined(Q_CLANG_QDOC)
+#if !defined(QT_NO_VECTOR3D) || defined(Q_QDOC)
template<> inline char *toString(const QVector3D &v)
{
QByteArray result = "QVector3D(" + QByteArray::number(double(v.x())) + ", "
@@ -93,7 +97,7 @@ template<> inline char *toString(const QVector3D &v)
return qstrdup(result.constData());
}
#endif // !QT_NO_VECTOR3D
-#if !defined(QT_NO_VECTOR4D) || defined(Q_CLANG_QDOC)
+#if !defined(QT_NO_VECTOR4D) || defined(Q_QDOC)
template<> inline char *toString(const QVector4D &v)
{
QByteArray result = "QVector4D(" + QByteArray::number(double(v.x())) + ", "
@@ -103,6 +107,13 @@ 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)
{
@@ -122,17 +133,17 @@ inline bool qCompare(QImage const &t1, QImage const &t2,
qsnprintf(msg, 1024, "Compared QImages differ.\n"
" Actual (%s).isNull(): %d\n"
" Expected (%s).isNull(): %d", actual, t1Null, expected, t2Null);
- return compare_helper(false, msg, nullptr, nullptr, actual, expected, file, line);
+ return compare_helper(false, msg, actual, expected, file, line);
}
if (t1Null && t2Null)
- return compare_helper(true, nullptr, nullptr, nullptr, actual, expected, file, line);
+ return compare_helper(true, nullptr, actual, expected, file, line);
if (!qFuzzyCompare(t1.devicePixelRatio(), t2.devicePixelRatio())) {
qsnprintf(msg, 1024, "Compared QImages differ in device pixel ratio.\n"
" Actual (%s): %g\n"
" Expected (%s): %g",
actual, t1.devicePixelRatio(),
expected, t2.devicePixelRatio());
- return compare_helper(false, msg, nullptr, nullptr, actual, expected, file, line);
+ return compare_helper(false, msg, actual, expected, file, line);
}
if (t1.width() != t2.width() || t1.height() != t2.height()) {
qsnprintf(msg, 1024, "Compared QImages differ in size.\n"
@@ -140,17 +151,17 @@ inline bool qCompare(QImage const &t1, QImage const &t2,
" Expected (%s): %dx%d",
actual, t1.width(), t1.height(),
expected, t2.width(), t2.height());
- return compare_helper(false, msg, nullptr, nullptr, actual, expected, file, line);
+ return compare_helper(false, msg, actual, expected, file, line);
}
if (t1.format() != t2.format()) {
qsnprintf(msg, 1024, "Compared QImages differ in format.\n"
" Actual (%s): %d\n"
" Expected (%s): %d",
actual, t1.format(), expected, t2.format());
- return compare_helper(false, msg, nullptr, nullptr, actual, expected, file, line);
+ return compare_helper(false, msg, actual, expected, file, line);
}
return compare_helper(t1 == t2, "Compared values are not the same",
- nullptr, nullptr, actual, expected, file, line);
+ actual, expected, file, line);
}
inline bool qCompare(QPixmap const &t1, QPixmap const &t2, const char *actual, const char *expected,
@@ -164,17 +175,17 @@ inline bool qCompare(QPixmap const &t1, QPixmap const &t2, const char *actual, c
qsnprintf(msg, 1024, "Compared QPixmaps differ.\n"
" Actual (%s).isNull(): %d\n"
" Expected (%s).isNull(): %d", actual, t1Null, expected, t2Null);
- return compare_helper(false, msg, nullptr, nullptr, actual, expected, file, line);
+ return compare_helper(false, msg, actual, expected, file, line);
}
if (t1Null && t2Null)
- return compare_helper(true, nullptr, nullptr, nullptr, actual, expected, file, line);
+ return compare_helper(true, nullptr, actual, expected, file, line);
if (!qFuzzyCompare(t1.devicePixelRatio(), t2.devicePixelRatio())) {
qsnprintf(msg, 1024, "Compared QPixmaps differ in device pixel ratio.\n"
" Actual (%s): %g\n"
" Expected (%s): %g",
actual, t1.devicePixelRatio(),
expected, t2.devicePixelRatio());
- return compare_helper(false, msg, nullptr, nullptr, actual, expected, file, line);
+ return compare_helper(false, msg, actual, expected, file, line);
}
if (t1.width() != t2.width() || t1.height() != t2.height()) {
qsnprintf(msg, 1024, "Compared QPixmaps differ in size.\n"
@@ -182,7 +193,7 @@ inline bool qCompare(QPixmap const &t1, QPixmap const &t2, const char *actual, c
" Expected (%s): %dx%d",
actual, t1.width(), t1.height(),
expected, t2.width(), t2.height());
- return compare_helper(false, msg, nullptr, nullptr, actual, expected, file, line);
+ return compare_helper(false, msg, actual, expected, file, line);
}
return qCompare(t1.toImage(), t2.toImage(), actual, expected, file, 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 5db348fd22..a4236aa5f3 100644
--- a/src/testlib/qtestaccessible.h
+++ b/src/testlib/qtestaccessible.h
@@ -108,14 +108,14 @@ 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();
return res;
}
static bool containsEvent(QAccessibleEvent *event) {
- for (const QAccessibleEvent *ev : qAsConst(eventList())) {
+ for (const QAccessibleEvent *ev : std::as_const(eventList())) {
if (*ev == *event)
return true;
}
@@ -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 7b64942e7b..4154f8f2a6 100644
--- a/src/testlib/qtestblacklist.cpp
+++ b/src/testlib/qtestblacklist.cpp
@@ -25,8 +25,10 @@ using namespace Qt::StringLiterals;
referring to this documentation is kind to readers. Comments can also be used
to indicate the reasons for ignoring particular cases.
- 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,
+ 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. 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
@@ -44,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
@@ -57,7 +60,7 @@ using namespace Qt::StringLiterals;
# Flaky in COIN on macOS, not reproducible by developers
[testSlowly]
- ci osx
+ macos ci
# Needs basic C++11 support
[testfunction2:testData]
@@ -115,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
@@ -149,8 +155,10 @@ static QSet<QByteArray> keywords()
<< "msvc-2015"
# elif _MSC_VER <= 1916
<< "msvc-2017"
-# else
+# elif _MSC_VER <= 1929
<< "msvc-2019"
+# else
+ << "msvc-2022"
# endif
#endif
@@ -164,8 +172,6 @@ static QSet<QByteArray> keywords()
#ifdef QT_BUILD_INTERNAL
<< "developer-build"
#endif
-
- << "cmake"
;
QCoreApplication *app = QCoreApplication::instance();
@@ -257,7 +263,7 @@ void parseBlackList()
if (line.isEmpty())
continue;
if (line.startsWith('[')) {
- function = line.mid(1, line.length() - 2);
+ function = line.mid(1, line.size() - 2);
continue;
}
bool condition = checkCondition(line);
@@ -273,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;
@@ -295,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 80f656fcd3..2ab55cac36 100644
--- a/src/testlib/qtestcase.cpp
+++ b/src/testlib/qtestcase.cpp
@@ -3,13 +3,14 @@
// 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/qtestcase_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/qdirlisting.h>
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qfloat16.h>
@@ -33,8 +34,12 @@
#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>
+#include <QtTest/private/qtestcrashhandler_p.h>
#if defined(HAVE_XCTEST)
#include <QtTest/private/qxctestlogger_p.h>
#endif
@@ -60,6 +65,7 @@
#include <memory>
#include <mutex>
#include <numeric>
+#include <optional>
#include <stdarg.h>
#include <stdio.h>
@@ -71,6 +77,7 @@
#endif
#ifdef Q_OS_WIN
+# include <iostream>
# if !defined(Q_CC_MINGW) || (defined(Q_CC_MINGW) && defined(__MINGW64_VERSION_MAJOR))
# include <crtdbg.h>
# endif
@@ -80,14 +87,24 @@
#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/uio.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
@@ -100,6 +117,10 @@
#include <CoreFoundation/CFPreferences.h>
#endif
+#if defined(Q_OS_WASM)
+#include <emscripten.h>
+#endif
+
#include <vector>
QT_BEGIN_NAMESPACE
@@ -109,237 +130,6 @@ using namespace Qt::StringLiterals;
using QtMiscUtils::toHexUpper;
using QtMiscUtils::fromHex;
-namespace {
-enum DebuggerProgram { None, Gdb, Lldb };
-
-#ifdef Q_OS_UNIX
-static struct iovec IoVec(struct iovec vec)
-{
- return vec;
-}
-static struct iovec IoVec(const char *str)
-{
- struct iovec r = {};
- r.iov_base = const_cast<char *>(str);
- r.iov_len = strlen(str);
- return r;
-}
-
-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
-};
-
-static struct iovec asyncSafeToString(int n, AsyncSafeIntBuffer &&result = Qt::Uninitialized)
-{
- 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 // Q_OS_UNIX
-} // unnamed namespace
-
-static 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
-}
-
-static bool hasSystemCrashReporter()
-{
-#if defined(Q_OS_MACOS)
- return QTestPrivate::macCrashReporterWillShowDialog();
-#else
- return false;
-#endif
-}
-
-static void disableCoreDump()
-{
-#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
-}
-Q_CONSTRUCTOR_FUNCTION(disableCoreDump);
-
-static DebuggerProgram debugger = None;
-static 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
-
- // prepare the command to be run (our PID shouldn't change!)
-# ifdef Q_OS_LINUX
- debugger = Gdb;
-# elif defined(Q_OS_MACOS)
- debugger = Lldb;
-# endif
-}
-
-[[maybe_unused]] static void generateStackTrace()
-{
- if (debugger == None || alreadyDebugging())
- return;
-
-#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
- const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
- const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
- writeToStderr("\n=== Received signal at function time: ", asyncSafeToString(msecsFunctionTime),
- "ms, total time: ", asyncSafeToString(msecsTotalTime),
- "ms, dumping stack ===\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;
- EINTR_LOOP(ret, waitpid(pid, nullptr, 0));
- }
- writeToStderr("=== End of stack trace ===\n");
-#endif
-}
-
static bool installCoverageTool(const char * appname, const char * testname)
{
#if defined(__COVERAGESCANNER__) && !QT_CONFIG(testlib_selfcover)
@@ -377,9 +167,193 @@ namespace QTestPrivate
Q_TESTLIB_EXPORT Qt::MouseButtons qtestMouseButtons = Qt::NoButton;
}
+namespace {
+
+class TestFailedException : public std::exception // clazy:exclude=copyable-polymorphic
+{
+public:
+ TestFailedException() = default;
+ ~TestFailedException() override = default;
+
+ const char *what() const noexcept override { return "QtTest: test failed"; }
+};
+
+class TestSkippedException : public std::exception // clazy:exclude=copyable-polymorphic
+{
+public:
+ TestSkippedException() = default;
+ ~TestSkippedException() override = default;
+
+ const char *what() const noexcept override { return "QtTest: test was skipped"; }
+};
+
+} // unnamed namespace
+
namespace QTest
{
+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 (g_throwOnFail.loadRelaxed() > 0)
+ Internal::throwOnFail();
+}
+
+void Internal::maybeThrowOnSkip()
+{
+ if (g_throwOnSkip.loadRelaxed() > 0)
+ Internal::throwOnSkip();
+}
+
+/*!
+ \since 6.8
+ \macro QTEST_THROW_ON_FAIL
+ \relates <QTest>
+
+ When defined, QCOMPARE()/QVERIFY() etc always throw on failure.
+ QTest::throwOnFail() then no longer has any effect.
+*/
+
+/*!
+ \since 6.8
+ \macro QTEST_THROW_ON_SKIP
+ \relates <QTest>
+
+ When defined, QSKIP() always throws. QTest::throwOnSkip() then no longer
+ has any effect.
+*/
+
+/*!
+ \since 6.8
+ \class QTest::ThrowOnFailEnabler
+ \inmodule QtTestLib
+
+ RAII class around setThrowOnFail().
+*/
+/*!
+ \fn QTest::ThrowOnFailEnabler::ThrowOnFailEnabler()
+
+ Constructor. Calls \c{setThrowOnFail(true)}.
+*/
+/*!
+ \fn QTest::ThrowOnFailEnabler::~ThrowOnFailEnabler()
+
+ Destructor. Calls \c{setThrowOnFail(false)}.
+*/
+
+/*!
+ \since 6.8
+ \class QTest::ThrowOnFailDisabler
+ \inmodule QtTestLib
+
+ RAII class around setThrowOnFail().
+*/
+/*!
+ \fn QTest::ThrowOnFailDisabler::ThrowOnFailDisabler()
+
+ Constructor. Calls \c{setThrowOnFail(false)}.
+*/
+/*!
+ \fn QTest::ThrowOnFailDisabler::~ThrowOnFailDisabler()
+
+ Destructor. Calls \c{setThrowOnFail(true)}.
+*/
+
+/*!
+ \since 6.8
+ \class QTest::ThrowOnSkipEnabler
+ \inmodule QtTestLib
+
+ RAII class around setThrowOnSkip().
+*/
+/*!
+ \fn QTest::ThrowOnSkipEnabler::ThrowOnSkipEnabler()
+
+ Constructor. Calls \c{setThrowOnSkip(true)}.
+*/
+/*!
+ \fn QTest::ThrowOnSkipEnabler::~ThrowOnSkipEnabler()
+
+ Destructor. Calls \c{setThrowOnSkip(false)}.
+*/
+
+/*!
+ \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
+{
+ g_throwOnFail.fetchAndAddRelaxed(enable ? 1 : -1);
+}
+
+/*!
+ \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)
{
return "QTestLib: This test case check (\"%1\") failed because the requested timeout (%2 ms) "
@@ -394,6 +368,7 @@ class WatchDog;
static QObject *currentTestObject = nullptr;
static QString mainSourcePath;
+static bool inTestFunction = false;
#if defined(Q_OS_MACOS)
static IOPMAssertionID macPowerSavingDisabled = 0;
@@ -412,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().
@@ -457,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()
@@ -525,16 +513,25 @@ Q_TESTLIB_EXPORT bool printAvailableFunctions = false;
Q_TESTLIB_EXPORT QStringList testFunctions;
Q_TESTLIB_EXPORT QStringList testTags;
-static void qPrintTestSlots(FILE *stream, const char *filter = nullptr)
+static bool qPrintTestSlots(FILE *stream, const char *filter = nullptr, const char *preamble = "")
{
+ const auto matches = [filter](const QByteArray &s) {
+ return !filter || QLatin1StringView(s).contains(QLatin1StringView(filter),
+ Qt::CaseInsensitive);
+ };
+ bool matched = false;
for (int i = 0; i < QTest::currentTestObject->metaObject()->methodCount(); ++i) {
QMetaMethod sl = QTest::currentTestObject->metaObject()->method(i);
if (isValidSlot(sl)) {
const QByteArray signature = sl.methodSignature();
- if (!filter || QLatin1StringView(signature).contains(QLatin1StringView(filter), Qt::CaseInsensitive))
- fprintf(stream, "%s\n", signature.constData());
+ if (matches(signature)) {
+ fprintf(stream, "%s%s\n", preamble, signature.constData());
+ preamble = "";
+ matched = true;
+ }
}
}
+ return matched;
}
static void qPrintDataTags(FILE *stream)
@@ -544,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();
@@ -563,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)
@@ -571,7 +568,7 @@ static void qPrintDataTags(FILE *stream)
// Print all tag combinations:
if (gTable->dataCount() == 0) {
- if (localTags.count() == 0) {
+ if (localTags.size() == 0) {
// No tags at all, so just print the test function:
fprintf(stream, "%s %s\n", currTestMetaObj->className(), slot);
} else {
@@ -583,7 +580,7 @@ static void qPrintDataTags(FILE *stream)
}
} else {
for (int j = 0; j < gTable->dataCount(); ++j) {
- if (localTags.count() == 0) {
+ if (localTags.size() == 0) {
// Only global tags, so print the current one:
fprintf(
stream, "%s %s __global__ %s\n",
@@ -620,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
@@ -674,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)
@@ -823,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()) {
@@ -966,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
@@ -983,21 +1020,24 @@ Q_TESTLIB_EXPORT void qtest_qParseArgs(int argc, char *argv[], bool qml) {
qtest_qParseArgs(argc, const_cast<const char *const *>(argv), qml);
}
-QBenchmarkResult qMedian(const QList<QBenchmarkResult> &container)
+static QList<QBenchmarkResult> qMedian(const QList<QList<QBenchmarkResult>> &container)
{
- const int count = container.count();
+ const int count = container.size();
if (count == 0)
- return QBenchmarkResult();
+ return {};
if (count == 1)
return container.front();
- QList<QBenchmarkResult> containerCopy = container;
- std::sort(containerCopy.begin(), containerCopy.end());
+ QList<QList<QBenchmarkResult>> containerCopy = container;
+ std::sort(containerCopy.begin(), containerCopy.end(),
+ [](const QList<QBenchmarkResult> &a, const QList<QBenchmarkResult> &b) {
+ return a.first() < b.first();
+ });
const int middle = count / 2;
- // ### handle even-sized containers here by doing an aritmetic mean of the two middle items.
+ // ### handle even-sized containers here by doing an arithmetic mean of the two middle items.
return containerCopy.at(middle);
}
@@ -1013,15 +1053,6 @@ struct QTestDataSetter
}
};
-namespace {
-
-qreal addResult(qreal current, const QBenchmarkResult& r)
-{
- return current + r.value;
-}
-
-}
-
void TestMethods::invokeTestOnData(int index) const
{
/* Benchmarking: for each median iteration*/
@@ -1029,27 +1060,30 @@ void TestMethods::invokeTestOnData(int index) const
bool isBenchmark = false;
int i = (QBenchmarkGlobalData::current->measurer->needsWarmupIteration()) ? -1 : 0;
- QList<QBenchmarkResult> results;
+ QList<QList<QBenchmarkResult>> resultsList;
bool minimumTotalReached = false;
do {
QBenchmarkTestMethodData::current->beginDataRun();
+ if (i < 0)
+ QBenchmarkTestMethodData::current->iterationCount = 1;
/* Benchmarking: for each accumulation iteration*/
bool invokeOk;
do {
- if (m_initMethod.isValid())
- m_initMethod.invoke(QTest::currentTestObject, Qt::DirectConnection);
+ QTest::inTestFunction = true;
+ invokeTestMethodIfValid(m_initMethod);
const bool initQuit =
QTestResult::skipCurrentTest() || QTestResult::currentTestFailed();
if (!initQuit) {
- QBenchmarkTestMethodData::current->result = QBenchmarkResult();
+ QBenchmarkTestMethodData::current->results.clear();
QBenchmarkTestMethodData::current->resultAccepted = false;
+ QBenchmarkTestMethodData::current->valid = false;
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__);
@@ -1058,11 +1092,11 @@ void TestMethods::invokeTestOnData(int index) const
invokeOk = false;
}
+ QTest::inTestFunction = false;
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.
@@ -1085,26 +1119,29 @@ void TestMethods::invokeTestOnData(int index) const
QBenchmarkTestMethodData::current->endDataRun();
if (!QTestResult::skipCurrentTest() && !QTestResult::currentTestFailed()) {
if (i > -1) // iteration -1 is the warmup iteration.
- results.append(QBenchmarkTestMethodData::current->result);
-
- if (isBenchmark && QBenchmarkGlobalData::current->verboseOutput) {
- if (i == -1) {
- QTestLog::info(qPrintable(
- QString::fromLatin1("warmup stage result : %1")
- .arg(QBenchmarkTestMethodData::current->result.value)), nullptr, 0);
- } else {
- QTestLog::info(qPrintable(
- QString::fromLatin1("accumulation stage result: %1")
- .arg(QBenchmarkTestMethodData::current->result.value)), nullptr, 0);
- }
+ resultsList.append(QBenchmarkTestMethodData::current->results);
+
+ if (isBenchmark && QBenchmarkGlobalData::current->verboseOutput &&
+ !QBenchmarkTestMethodData::current->results.isEmpty()) {
+ // we only print the first result
+ const QBenchmarkResult &first = QBenchmarkTestMethodData::current->results.constFirst();
+ QString pattern = i < 0 ? "warmup stage result : %1"_L1
+ : "accumulation stage result: %1"_L1;
+ QTestLog::info(qPrintable(pattern.arg(first.measurement.value)), nullptr, 0);
}
}
- // Verify if the minimum total measurement is reached, if it was specified:
+ // Verify if the minimum total measurement (for the first measurement)
+ // was reached, if it was specified:
if (QBenchmarkGlobalData::current->minimumTotal == -1) {
minimumTotalReached = true;
} else {
- const qreal total = std::accumulate(results.begin(), results.end(), 0.0, addResult);
+ auto addResult = [](qreal current, const QList<QBenchmarkResult> &r) {
+ if (!r.isEmpty())
+ current += r.first().measurement.value;
+ return current;
+ };
+ const qreal total = std::accumulate(resultsList.begin(), resultsList.end(), 0.0, addResult);
minimumTotalReached = (total >= QBenchmarkGlobalData::current->minimumTotal);
}
} while (isBenchmark
@@ -1117,7 +1154,7 @@ void TestMethods::invokeTestOnData(int index) const
QTestResult::finishedCurrentTestDataCleanup();
// Only report benchmark figures if the test passed
if (testPassed && QBenchmarkTestMethodData::current->resultsAccepted())
- QTestLog::addBenchmarkResult(qMedian(results));
+ QTestLog::addBenchmarkResults(qMedian(resultsList));
}
}
@@ -1125,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:
@@ -1144,8 +1194,20 @@ class WatchDog : public QThread
waitCondition.wait(m, expectationChanged);
return true;
}
- Q_UNREACHABLE();
- return false;
+ 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:
@@ -1160,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:
@@ -1197,7 +1252,9 @@ public:
case TestFunctionStart:
case TestFunctionEnd:
if (Q_UNLIKELY(!waitFor(locker, e))) {
- generateStackTrace();
+ fflush(stderr);
+ CrashHandler::printTestRunTime();
+ CrashHandler::generateStackTrace();
qFatal("Test function timed out");
}
}
@@ -1205,8 +1262,8 @@ public:
}
private:
- QtPrivate::mutex mutex;
- QtPrivate::condition_variable waitCondition;
+ std::mutex mutex;
+ std::condition_variable waitCondition;
std::atomic<Expectation> expecting;
};
@@ -1219,9 +1276,29 @@ public:
void testFinished() {};
};
-#endif
+#endif // QT_CONFIG(thread)
+static void printUnknownDataTagError(QLatin1StringView name, QLatin1StringView tag,
+ const QTestTable &lTable, const QTestTable &gTable)
+{
+ fprintf(stderr, "Unknown testdata for function %s(): '%s'\n", name.constData(), tag.data());
+ const int localDataCount = lTable.dataCount();
+ if (localDataCount) {
+ fputs("Available test-specific data tags:\n", stderr);
+ for (int i = 0; i < localDataCount; ++i)
+ fprintf(stderr, "\t%s\n", lTable.testData(i)->dataTag());
+ }
+ const int globalDataCount = gTable.dataCount();
+ if (globalDataCount) {
+ fputs("Available global data tags:\n", stderr);
+ for (int i = 0; i < globalDataCount; ++i)
+ fprintf(stderr, "\t%s\n", gTable.testData(i)->dataTag());
+ }
+ if (localDataCount == 0 && globalDataCount == 0)
+ fputs("Function has no data tags\n", stderr);
+}
+
/*!
\internal
@@ -1231,7 +1308,7 @@ public:
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;
@@ -1263,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 {
@@ -1271,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;
}
@@ -1282,13 +1360,6 @@ bool TestMethods::invokeTest(int index, QLatin1StringView tag, WatchDog *watchDo
return dataCount ? table.testData(index)->dataTag() : nullptr;
};
- // Data tag requested but none available?
- if (!tag.isEmpty() && !dataCount && !globalDataCount) {
- fprintf(stderr, "Unknown test data tag for function %s(): '%s'\n"
- "Function has no testdata.\n", name.constData(), tag.data());
- return false;
- }
-
/* For each entry in this test's data table, do: */
do {
QTestResult::setSkipCurrentTest(false);
@@ -1296,19 +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;
@@ -1321,20 +1401,9 @@ bool TestMethods::invokeTest(int index, QLatin1StringView tag, WatchDog *watchDo
} while (curGlobalDataIndex < globalDataCount);
if (!tag.isEmpty() && !foundFunction) {
- fprintf(stderr, "Unknown testdata for function %s(): '%s'\n", name.constData(), tag.data());
- if (table.dataCount()) {
- fputs("Available test-specific data tags:\n", stderr);
- for (int i = 0; i < table.dataCount(); ++i)
- fprintf(stderr, "\t%s\n", table.testData(i)->dataTag());
- }
- if (globalDataCount) {
- fputs("Available global data tags:\n", stderr);
- for (int i = 0; i < globalDataCount; ++i)
- fprintf(stderr, "\t%s\n", gTable->testData(i)->dataTag());
- }
- return false;
+ printUnknownDataTagError(QLatin1StringView(name), tag, table, *gTable);
+ QTestResult::addFailure(qPrintable("Data tag not found: %1"_L1.arg(tag)));
}
-
QTestResult::finishedCurrentTestFunction();
QTestResult::setSkipCurrentTest(false);
QTestResult::setBlacklistCurrentTest(false);
@@ -1390,8 +1459,6 @@ char *formatString(const char *prefix, const char *suffix, size_t numArguments,
}
/*!
- \fn char* QTest::toHexRepresentation(const char *ba, int length)
-
Returns a pointer to a string that is the string \a ba represented
as a space-separated sequence of hex characters. If the input is
considered too long, it is truncated. A trucation is indicated in
@@ -1401,7 +1468,7 @@ char *formatString(const char *prefix, const char *suffix, size_t numArguments,
\a length is the length of the string \a ba.
*/
-char *toHexRepresentation(const char *ba, int length)
+char *toHexRepresentation(const char *ba, qsizetype length)
{
if (length == 0)
return qstrdup("");
@@ -1413,12 +1480,12 @@ char *toHexRepresentation(const char *ba, int length)
* maxLen can't be for example 200 because Qt Test is sprinkled with fixed
* size char arrays.
* */
- const int maxLen = 50;
- const int len = qMin(maxLen, length);
+ const qsizetype maxLen = 50;
+ const qsizetype len = qMin(maxLen, length);
char *result = nullptr;
if (length > maxLen) {
- const int size = len * 3 + 4;
+ const qsizetype size = len * 3 + 4;
result = new char[size];
char *const forElipsis = result + size - 5;
@@ -1429,13 +1496,13 @@ char *toHexRepresentation(const char *ba, int length)
result[size - 1] = '\0';
}
else {
- const int size = len * 3;
+ const qsizetype size = len * 3;
result = new char[size];
result[size - 1] = '\0';
}
- int i = 0;
- int o = 0;
+ qsizetype i = 0;
+ qsizetype o = 0;
while (true) {
const char at = ba[i];
@@ -1460,7 +1527,7 @@ char *toHexRepresentation(const char *ba, int length)
Returns the same QByteArray but with only the ASCII characters still shown;
everything else is replaced with \c {\xHH}.
*/
-char *toPrettyCString(const char *p, int length)
+char *toPrettyCString(const char *p, qsizetype length)
{
bool trimmed = false;
auto buffer = std::make_unique<char[]>(256);
@@ -1539,76 +1606,78 @@ char *toPrettyCString(const char *p, int length)
return buffer.release();
}
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
-// this used to be the signature up to and including Qt 5.9
-// keep it for BC reasons:
-Q_TESTLIB_EXPORT
-char *toPrettyUnicode(const ushort *p, int length)
-{
- return toPrettyUnicode(QStringView(p, length));
-}
-#endif
-
/*!
+ \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++ = '"';
@@ -1626,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();
@@ -1655,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;
@@ -1666,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());
@@ -1679,317 +1745,34 @@ void TestMethods::invokeTests(QObject *testObject) const
QSignalDumper::endDump();
}
-} // namespace QTest
-
-namespace {
-#if defined(Q_OS_WIN)
-
-// Helper class for resolving symbol names by dynamically loading "dbghelp.dll".
-class DebugSymbolResolver
+#if QT_DEPRECATED_SINCE(6, 8)
+static const char *functionRefFormatter(const void *f)
{
- 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;
+ auto formatter = static_cast<const qxp::function_ref<const char *()> *>(f);
+ return (*formatter)();
};
-void DebugSymbolResolver::cleanup()
+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)
{
- 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();
-}
-
-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;
+ return QTestResult::reportResult(success, lhs, rhs, lhsFormatter, rhsFormatter,
+ lhsExpr, rhsExpr, op, file, line);
}
-
-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;
- printf("A crash occurred in %s.\n"
- "Function time: %dms Total time: %dms\n\n"
- "Exception address: 0x%p\n"
- "Exception code : 0x%lx\n",
- appName, msecsFunctionTime, msecsTotalTime,
- exceptionAddress, exInfo->ExceptionRecord->ExceptionCode);
-
- DebugSymbolResolver resolver(GetCurrentProcess());
- if (resolver.isValid()) {
- DebugSymbolResolver::Symbol exceptionSymbol = resolver.resolveSymbol(DWORD64(exceptionAddress));
- if (exceptionSymbol.name) {
- printf("Nearby symbol : %s\n", exceptionSymbol.name);
- delete [] exceptionSymbol.name;
- }
- void *stack[maxStackFrames];
- fputs("\nStack:\n", stdout);
- 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) {
- printf("#%3u: %s() - 0x%p\n", f + 1, symbol.name, (const void *)symbol.address);
- delete [] symbol.name;
- } else {
- printf("#%3u: Unable to obtain symbol\n", f + 1);
- }
- }
- }
-
- fputc('\n', stdout);
- fflush(stdout);
-
- return EXCEPTION_EXECUTE_HANDLER;
- }
-};
-using FatalSignalHandler = WindowsFaultHandler;
-
-#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
-class FatalSignalHandler
-{
-public:
- static constexpr std::array fatalSignals = {
- SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGPIPE, SIGTERM
- };
- 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;
-
-# ifdef SA_SIGINFO
- act.sa_flags |= SA_SIGINFO;
- act.sa_sigaction = FatalSignalHandler::actionHandler;
-# else
- act.sa_handler = FatalSignalHandler::regularHandler;
-# endif
-
- // 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
-# if defined(Q_CC_GNU) && defined(Q_OF_ELF)
- // Put the alternate stack in the .lbss (large BSS) section so that it doesn't
- // interfere with normal .bss symbols
- __attribute__((section(".lbss.altstack"), aligned(4096)))
-# endif
- static QVarLengthArray<char, 32 * 1024> alternateStack;
- alternateStack.resize(qMax(SIGSTKSZ, alternateStack.size()));
- stack_t stack;
- stack.ss_flags = 0;
- stack.ss_size = alternateStack.size();
- stack.ss_sp = alternateStack.data();
- sigaltstack(&stack, nullptr);
- act.sa_flags |= SA_ONSTACK;
-# 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);
- }
- }
-
-private:
- Q_DISABLE_COPY_MOVE(FatalSignalHandler)
-
- static OldActionsArray &oldActions()
- {
- Q_CONSTINIT static OldActionsArray oldActions {};
- return oldActions;
- }
-
- static void actionHandler(int signum, siginfo_t * /* info */, void * /* ucontext */)
- {
- const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
- const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
- if (signum != SIGINT) {
- generateStackTrace();
- if (pauseOnCrash) {
- writeToStderr("Pausing process ", asyncSafeToString(getpid()),
- " for debugging\n");
- raise(SIGSTOP);
- }
- }
-
- writeToStderr("Received signal ", asyncSafeToString(signum),
- "\n Function time: ", asyncSafeToString(msecsFunctionTime),
- "ms Total time: ", asyncSafeToString(msecsTotalTime), "ms\n");
-
- bool isCrashingSignal =
- std::find(crashingSignals.begin(), crashingSignals.end(), signum) != crashingSignals.end();
-
- // 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);
- }
- 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()
{
@@ -2027,7 +1810,7 @@ static void initEnvironment()
test that was executed with qExec() can't run another test via qExec() and
threads are not allowed to call qExec() simultaneously.
- If you have programatically created the arguments, as opposed to getting them
+ If you have programmatically created the arguments, as opposed to getting them
from the arguments in \c main(), it is likely of interest to use
QTest::qExec(QObject *, const QStringList &) since it is Unicode safe.
@@ -2043,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;
}
@@ -2051,6 +1842,7 @@ int QTest::qExec(QObject *testObject, int argc, char **argv)
void QTest::qInit(QObject *testObject, int argc, char **argv)
{
initEnvironment();
+ CrashHandler::maybeDisableCoreDump();
QBenchmarkGlobalData::current = new QBenchmarkGlobalData;
#if defined(Q_OS_MACOS)
@@ -2086,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
@@ -2120,27 +1909,46 @@ 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;
TestMethods::MetaMethods commandLineMethods;
commandLineMethods.reserve(static_cast<size_t>(QTest::testFunctions.size()));
- for (const QString &tf : qAsConst(QTest::testFunctions)) {
- const QByteArray tfB = tf.toLatin1();
- const QByteArray signature = tfB + QByteArrayLiteral("()");
- QMetaMethod m = TestMethods::findMethod(currentTestObject, signature.constData());
- if (!m.isValid() || !isValidSlot(m)) {
- fprintf(stderr, "Unknown test function: '%s'. Possible matches:\n", tfB.constData());
- qPrintTestSlots(stderr, tfB.constData());
- fprintf(stderr, "\n%s -functions\nlists all available test functions.\n", QTestResult::currentAppName());
- exit(1);
- }
+ for (const QString &tf : std::as_const(QTest::testFunctions)) {
+ const QByteArray tfB = tf.toLatin1();
+ const QByteArray signature = tfB + QByteArrayLiteral("()");
+ QMetaMethod m = TestMethods::findMethod(currentTestObject, signature.constData());
+ if (m.isValid() && isValidSlot(m)) {
commandLineMethods.push_back(m);
+ } else {
+ fprintf(stderr, "Unknown test function: '%s'.", tfB.constData());
+ if (!qPrintTestSlots(stderr, tfB.constData(), " Possible matches:\n"))
+ fputc('\n', stderr);
+ QTestResult::setCurrentTestFunction(tfB.constData());
+ QTestResult::addFailure(qPrintable("Function not found: %1"_L1.arg(tf)));
+ QTestResult::finishedCurrentTestFunction();
+ // Ditch the tag that came with tf as test function:
+ QTest::testTags.remove(commandLineMethods.size());
+ seenBad = true;
+ }
+ }
+ if (seenBad) {
+ // Provide relevant help to do better next time:
+ fprintf(stderr, "\n%s -functions\nlists all available test functions.\n\n",
+ QTestResult::currentAppName());
+ if (commandLineMethods.empty()) // All requested functions missing.
+ 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
@@ -2177,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;
@@ -2190,6 +1995,34 @@ void QTest::qCleanup()
#endif
}
+#if QT_CONFIG(batch_test_support) || defined(Q_QDOC)
+/*!
+ Registers the test \a name, with entry function \a entryFunction, in a
+ central test case registry for the current binary.
+
+ 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)
+{
+ QTest::TestRegistry::instance()->registerTest(name, entryFunction);
+}
+
+QList<QString> QTest::qGetTestCaseNames()
+{
+ return QTest::TestRegistry::instance()->getAllTestNames();
+}
+
+QTest::TestEntryFunction QTest::qGetTestCaseEntryFunction(const QString& name)
+{
+ return QTest::TestRegistry::instance()->getTestEntryFunction(name);
+}
+
+#endif // QT_CONFIG(batch_test_support)
+
/*!
\overload
\since 4.4
@@ -2199,7 +2032,7 @@ void QTest::qCleanup()
*/
int QTest::qExec(QObject *testObject, const QStringList &arguments)
{
- const int argc = arguments.count();
+ const int argc = arguments.size();
QVarLengthArray<char *> argv(argc);
QList<QByteArray> args;
@@ -2280,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)
/*!
@@ -2439,30 +2305,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.length());
+ 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;
@@ -2656,9 +2528,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
@@ -2669,14 +2545,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);
}
@@ -2684,13 +2561,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
@@ -2701,14 +2582,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];
@@ -2769,7 +2651,7 @@ const char *QTest::currentTestFunction()
/*!
Returns the name of the current test data. If the test doesn't
- have any assigned testdata, the function returns 0.
+ have any assigned testdata, the function returns \nullptr.
*/
const char *QTest::currentDataTag()
{
@@ -2777,13 +2659,46 @@ const char *QTest::currentDataTag()
}
/*!
- Returns \c true if the current test function failed, otherwise false.
+ Returns \c true if the current test function has failed, otherwise false.
+
+ \sa QTest::currentTestResolved()
*/
bool QTest::currentTestFailed()
{
return QTestResult::currentTestFailed();
}
+/*!
+ \since 6.5
+ Returns \c true if the current test function has failed or skipped.
+
+ 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 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.
+
+ \sa QTest::currentTestFailed()
+*/
+bool QTest::currentTestResolved()
+{
+ return QTestResult::currentTestFailed() || QTestResult::skipCurrentTest();
+}
+
+/*!
+ \internal
+ \since 6.4
+ Returns \c true during the run of the test-function and its set-up.
+
+ Used by the \c{QTRY_*} macros and \l QTestEventLoop to check whether to
+ return when QTest::currentTestResolved() is true.
+*/
+bool QTest::runningTest()
+{
+ return QTest::inTestFunction;
+}
+
/*! \internal
*/
QObject *QTest::testObject()
@@ -2804,7 +2719,9 @@ void QTest::setMainSourcePath(const char *file, const char *builddir)
QTest::mainSourcePath = fi.absolutePath();
}
+#if QT_DEPRECATED_SINCE(6, 4)
/*! \internal
+ \deprecated [6.4]
This function is called by various specializations of QTest::qCompare
to decide whether to report a failure and to produce verbose test output.
@@ -2812,15 +2729,91 @@ void QTest::setMainSourcePath(const char *file, const char *builddir)
will be output if the compare fails. If the compare succeeds, failureMsg
will not be output.
- If the caller has already passed a failure message showing the compared
- values, or if those values cannot be stringified, val1 and val2 can be null.
+ Using this function is not optimal, because it requires the string
+ representations of \a actualVal and \a expectedVal to be pre-calculated,
+ even though they will be used only if the comparison fails. Prefer using the
+ \l compare_helper() overload that takes qxp::function_ref() for such cases.
+
+ If the caller creates a custom failure message showing the compared values,
+ or if those values cannot be stringified, use the overload of the function
+ that takes no \a actualVal and \a expecetedVal parameters.
+*/
+bool QTest::compare_helper(bool success, const char *failureMsg,
+ char *actualVal, char *expectedVal,
+ const char *actual, const char *expected,
+ const char *file, int line)
+{
+ return QTestResult::compare(success, failureMsg, actualVal, expectedVal,
+ actual, expected, file, line);
+}
+#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
+ 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.
+
+ This overload of the function uses qxp::function_ref to defer conversion of
+ \a actualVal and \a expectedVal to strings until that is really needed
+ (when the comparison fails). This speeds up test case execution on success.
*/
bool QTest::compare_helper(bool success, const char *failureMsg,
- char *val1, char *val2,
+ qxp::function_ref<const char *()> actualVal,
+ qxp::function_ref<const char *()> expectedVal,
const char *actual, const char *expected,
const char *file, int line)
{
- return QTestResult::compare(success, failureMsg, val1, val2, actual, expected, file, line);
+ 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);
+}
+
+/*! \internal
+ \since 6.4
+ This function is called by various specializations of QTest::qCompare
+ to decide whether to report a failure and to produce verbose test output.
+
+ This overload should be used when there is no string representation of
+ actual and expected values, so only the \a failureMsg is shown when the
+ comparison fails. Because of that, \a failureMsg can't be \c {nullptr}.
+ If the comparison succeeds, \a failureMsg will not be output.
+*/
+bool QTest::compare_helper(bool success, const char *failureMsg, const char *actual,
+ const char *expected, const char *file, int line)
+{
+ return QTestResult::compare(success, failureMsg, actual, expected, file, line);
}
template <typename T>
@@ -2848,9 +2841,11 @@ 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)",
- toString(t1), toString(t2), actual, expected, file, line);
+ &t1, &t2, formatter, formatter,
+ actual, expected, file, line);
}
/*! \fn bool QTest::qCompare(const float &t1, const float &t2, const char *actual, const char *expected, const char *file, int line)
@@ -3114,11 +3109,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;
@@ -3171,8 +3161,10 @@ 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",
- toString(t1), toString(t2), actual, expected, file, line);
+ &t1, &t2, formatter, formatter,
+ actual, expected, file, line);
}
/*!
diff --git a/src/testlib/qtestcase.h b/src/testlib/qtestcase.h
index 892a10cef3..0ae1a787e2 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>
@@ -13,6 +14,7 @@
#include <QtCore/qsharedpointer.h>
#include <QtCore/qtemporarydir.h>
#include <QtCore/qthread.h>
+#include <QtCore/qxpfunctional.h>
#include <string.h>
@@ -22,38 +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)
+
+#define QCOMPARE_OP_IMPL(lhs, rhs, op, opId) \
+do { \
+ if (!QTest::qCompareOp<QTest::ComparisonOperation::opId>(lhs, rhs, #lhs, #rhs, __FILE__, __LINE__)) \
+ QTEST_FAIL_ACTION; \
} while (false)
+#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
@@ -62,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 */
@@ -84,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
@@ -121,9 +158,10 @@ inline void useVerifyThrowsException() {}
/* Ideally we would adapt qWaitFor(), or a variant on it, to implement roughly
* what the following provides as QTRY_LOOP_IMPL(); however, for now, the
* reporting of how much to increase the timeout to (if within a factor of two)
- * on failure and the check for QTest::currentTestFailed() go beyond
- * qWaitFor(). (We no longer care about the bug in MSVC < 2017 that precluded
- * using qWaitFor() in the implementation here, see QTBUG-59096.)
+ * on failure and the check for (QTest::runningTest() &&
+ * QTest::currentTestResolved()) go beyond qWaitFor(). (We no longer care about
+ * the bug in MSVC < 2017 that precluded using qWaitFor() in the implementation
+ * here, see QTBUG-59096.)
*/
// NB: not do {...} while (0) wrapped, as qt_test_i is accessed after it
@@ -132,55 +170,101 @@ inline void useVerifyThrowsException() {}
QTest::qWait(0); \
} \
int qt_test_i = 0; \
- for (; qt_test_i < timeoutValue && !(expr); qt_test_i += step) { \
+ for (; qt_test_i < timeoutValue && !(QTest::runningTest() && QTest::currentTestResolved()) \
+ && !(expr); qt_test_i += step) { \
QTest::qWait(step); \
}
+// Ends in a for-block, so doesn't want a following semicolon.
-#define QTRY_TIMEOUT_DEBUG_IMPL(expr, timeoutValue, step)\
- if (!(expr)) { \
- QTRY_LOOP_IMPL((expr), 2 * (timeoutValue), step); \
- if (expr) { \
- QFAIL(qPrintable(QTest::Internal::formatTryTimeoutDebugMessage(u8"" #expr, timeoutValue, timeoutValue + qt_test_i))); \
+#define QTRY_TIMEOUT_DEBUG_IMPL(expr, timeoutValue, step) \
+ if (!(QTest::runningTest() && QTest::currentTestResolved()) && !(expr)) { \
+ QTRY_LOOP_IMPL(expr, 2 * (timeoutValue), step) \
+ if ((expr)) { \
+ QFAIL(qPrintable(QTest::Internal::formatTryTimeoutDebugMessage(\
+ u8"" #expr, timeoutValue, timeoutValue + qt_test_i))); \
} \
}
-#define QTRY_IMPL(expr, timeout)\
- const int qt_test_step = timeout < 350 ? timeout / 7 + 1 : 50; \
- const int qt_test_timeoutValue = timeout; \
- { QTRY_LOOP_IMPL(QTest::currentTestFailed() || (expr), qt_test_timeoutValue, qt_test_step); } \
- QTRY_TIMEOUT_DEBUG_IMPL(QTest::currentTestFailed() || (expr), qt_test_timeoutValue, qt_test_step)
+#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.
// Will try to wait for the expression to become true while allowing event processing
#define QTRY_VERIFY_WITH_TIMEOUT(expr, timeout) \
do { \
- QTRY_IMPL((expr), timeout);\
+ QTRY_IMPL(expr, timeout) \
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) \
do { \
- QTRY_IMPL((expr), timeout);\
+ QTRY_IMPL(expr, timeout) \
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) \
do { \
- QTRY_IMPL(((expr) == (expected)), timeout);\
- QCOMPARE((expr), expected); \
+ QTRY_IMPL((expr) == (expected), timeout) \
+ 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(computed, baseline, op, opId, timeout) \
+do { \
+ 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(computed, baseline, timeout) \
+ QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, ==, Equal, timeout)
+
+#define QTRY_COMPARE_EQ(computed, baseline) QTRY_COMPARE_EQ_WITH_TIMEOUT(computed, baseline, 5s)
+
+#define QTRY_COMPARE_NE_WITH_TIMEOUT(computed, baseline, timeout) \
+ QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, !=, NotEqual, timeout)
+
+#define QTRY_COMPARE_NE(computed, baseline) QTRY_COMPARE_NE_WITH_TIMEOUT(computed, baseline, 5s)
+
+#define QTRY_COMPARE_LT_WITH_TIMEOUT(computed, baseline, timeout) \
+ QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, <, LessThan, timeout)
+
+#define QTRY_COMPARE_LT(computed, baseline) QTRY_COMPARE_LT_WITH_TIMEOUT(computed, baseline, 5s)
+
+#define QTRY_COMPARE_LE_WITH_TIMEOUT(computed, baseline, timeout) \
+ QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, <=, LessThanOrEqual, timeout)
+
+#define QTRY_COMPARE_LE(computed, baseline) QTRY_COMPARE_LE_WITH_TIMEOUT(computed, baseline, 5s)
+
+#define QTRY_COMPARE_GT_WITH_TIMEOUT(computed, baseline, timeout) \
+ QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, >, GreaterThan, timeout)
+
+#define QTRY_COMPARE_GT(computed, baseline) QTRY_COMPARE_GT_WITH_TIMEOUT(computed, baseline, 5s)
+
+#define QTRY_COMPARE_GE_WITH_TIMEOUT(computed, baseline, timeout) \
+ QTRY_COMPARE_OP_WITH_TIMEOUT_IMPL(computed, baseline, >=, GreaterThanOrEqual, timeout)
+
+#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)
@@ -188,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)\
@@ -200,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
@@ -222,74 +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
- inline typename std::enable_if<!QtPrivate::IsQEnumHelper<T>::Value && !std::is_enum_v<T>, char*>::type toString(const T &)
+ 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>
{
- 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::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, int length);
- Q_TESTLIB_EXPORT char *toPrettyCString(const char *unicode, int 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();
@@ -298,7 +379,42 @@ namespace QTest
Q_TESTLIB_EXPORT int qExec(QObject *testObject, int argc = 0, char **argv = nullptr);
Q_TESTLIB_EXPORT int qExec(QObject *testObject, const QStringList &arguments);
+#if QT_CONFIG(batch_test_support) || defined(Q_QDOC)
+ using TestEntryFunction = int (*)(int, char **);
+ Q_TESTLIB_EXPORT void qRegisterTestCase(const QString &name, TestEntryFunction entryFunction);
+#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);
@@ -309,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);
@@ -338,14 +456,41 @@ namespace QTest
Q_TESTLIB_EXPORT const char *currentTestFunction();
Q_TESTLIB_EXPORT const char *currentDataTag();
Q_TESTLIB_EXPORT bool currentTestFailed();
+ Q_TESTLIB_EXPORT bool currentTestResolved();
+ Q_TESTLIB_EXPORT bool runningTest(); // Internal, for use by macros and QTestEventLoop.
Q_TESTLIB_EXPORT Qt::Key asciiToKey(char ascii);
Q_TESTLIB_EXPORT char keyToAscii(Qt::Key key);
+#if QT_DEPRECATED_SINCE(6, 4)
+ 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,
+ char *actualVal, char *expectedVal,
+ 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,
- char *val1, char *val2,
+ 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);
+
Q_TESTLIB_EXPORT void addColumnInternal(int id, const char *name);
template <typename T>
@@ -358,17 +503,6 @@ namespace QTest
Q_TESTLIB_EXPORT QTestData &newRow(const char *dataTag);
Q_TESTLIB_EXPORT QTestData &addRow(const char *format, ...) Q_ATTRIBUTE_FORMAT_PRINTF(1, 2);
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
- // kept after adding implementation of <T1, T2> out of paranoia:
- template <typename T>
- inline bool qCompare(T const &t1, T const &t2, const char *actual, const char *expected,
- const char *file, int line)
- {
- return compare_helper(t1 == t2, "Compared values are not the same",
- toString(t1), toString(t2), actual, expected, file, line);
- }
-#endif
-
Q_TESTLIB_EXPORT bool qCompare(qfloat16 const &t1, qfloat16 const &t2,
const char *actual, const char *expected, const char *file, int line);
@@ -420,75 +554,72 @@ 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",
- toString(t1), 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",
- toString(t1), 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",
- toString(t1), 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",
- toString(nullptr), 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",
- toString(t1), 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",
- toString(nullptr), 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",
- toString(t1), toString(t2), actual, expected, file, line);
+ std::addressof(t1), std::addressof(t2),
+ genericToString<D1>, genericToString<D2>,
+ actual, expected, file, line);
}
inline bool qCompare(double const &t1, float const &t2, const char *actual,
@@ -574,9 +705,39 @@ namespace QTest
return qCompare(actual, *static_cast<const T *>(QTest::qElementData(elementName,
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 2133fec97c..6a067c351f 100644
--- a/src/testlib/qtestcase.qdoc
+++ b/src/testlib/qtestcase.qdoc
@@ -22,8 +22,10 @@
You can use \l QVERIFY2() when it is practical and valuable to put additional
information into the test failure report.
+//! [macro-usage-limitation]
\note This macro can only be used in a test function that is invoked
by the test framework.
+//! [macro-usage-limitation]
For example, the following code shows this macro being used to verify that a
\l QSignalSpy object is valid:
@@ -34,7 +36,8 @@
\c QVERIFY(x == y), because it reports both the expected and actual value
when the comparison fails.
- \sa QCOMPARE(), QTRY_VERIFY(), QSignalSpy, QEXPECT_FAIL()
+ \sa QCOMPARE(), QTRY_VERIFY(), QSignalSpy, QEXPECT_FAIL(), QCOMPARE_EQ(),
+ QCOMPARE_NE(), QCOMPARE_LT(), QCOMPARE_LE(), QCOMPARE_GT(), QCOMPARE_GE()
*/
/*! \macro QVERIFY2(condition, message)
@@ -73,7 +76,8 @@
\c {FAIL! : tst_QFile::open_write() 'opened' returned FALSE.
(open /tmp/qt.a3B42Cd: No space left on device)}
- \sa QVERIFY(), QCOMPARE(), QEXPECT_FAIL()
+ \sa QVERIFY(), QCOMPARE(), QEXPECT_FAIL(), QCOMPARE_EQ(), QCOMPARE_NE(),
+ QCOMPARE_LT(), QCOMPARE_LE(), QCOMPARE_GT(), QCOMPARE_GE()
*/
/*! \macro QCOMPARE(actual, expected)
@@ -99,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
@@ -109,16 +113,17 @@
can be passed as expected value:
\snippet code/src_qtestlib_qtestcase.cpp 34
- Note that using initializer lists requires a defining a helper macro
+ Note that using initializer lists requires defining a helper macro
to prevent the preprocessor from interpreting the commas as macro argument
delimiters:
\snippet code/src_qtestlib_qtestcase.cpp 35
- \note QCOMPARE() can only be used in a test function that is invoked
- by the test framework.
+ \include qtestcase.qdoc macro-usage-limitation
- For your own classes, you can use \l QTest::toString() to format values for
- outputting into the test log.
+//! [to-string-overload-desc]
+ For your own classes, you can overload \l QTest::toString() to format values
+ for output into the test log.
+//! [to-string-overload-desc]
Example:
\snippet code/src_qtestlib_qtestcase_snippet.cpp 34
@@ -127,7 +132,141 @@
be released with \c delete[] (rather than \c free() or plain \c delete) once
the calling code is done with it.
- \sa QVERIFY(), QTRY_COMPARE(), QTest::toString(), QEXPECT_FAIL()
+ \sa QVERIFY(), QTRY_COMPARE(), QTest::toString(), QEXPECT_FAIL(),
+ QCOMPARE_EQ(), QCOMPARE_NE(), QCOMPARE_LT(), QCOMPARE_LE(),
+ QCOMPARE_GT(), QCOMPARE_GE()
+*/
+
+/*! \macro QCOMPARE_EQ(computed, baseline)
+ \since 6.4
+
+ \relates QTest
+
+ 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(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
+
+ \include qtestcase.qdoc to-string-overload-desc
+
+ \note Unlike QCOMPARE(), this macro does not provide overloads for custom
+ types and pointers. So passing e.g. two \c {const char *} values as
+ parameters will compare \e pointers, while QCOMPARE() does a comparison of
+ C-style strings.
+
+ \sa QCOMPARE(), QCOMPARE_NE(), QCOMPARE_LT(), QCOMPARE_LE(), QCOMPARE_GT(),
+ QCOMPARE_GE()
+*/
+
+/*! \macro QCOMPARE_NE(computed, baseline)
+ \since 6.4
+
+ \relates QTest
+
+ 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(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
+
+ \include qtestcase.qdoc to-string-overload-desc
+
+ \sa QCOMPARE_EQ(), QCOMPARE_LT(), QCOMPARE_LE(), QCOMPARE_GT(), QCOMPARE_GE()
+*/
+
+/*! \macro QCOMPARE_LT(computed, baseline)
+ \since 6.4
+
+ \relates QTest
+
+ 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(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
+
+ \include qtestcase.qdoc to-string-overload-desc
+
+ \sa QCOMPARE_EQ(), QCOMPARE_NE(), QCOMPARE_LE(), QCOMPARE_GT(), QCOMPARE_GE()
+*/
+
+/*! \macro QCOMPARE_LE(computed, baseline)
+ \since 6.4
+
+ \relates QTest
+
+ 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(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
+
+ \include qtestcase.qdoc to-string-overload-desc
+
+ \sa QCOMPARE_EQ(), QCOMPARE_NE(), QCOMPARE_LT(), QCOMPARE_GT(), QCOMPARE_GE()
+*/
+
+/*! \macro QCOMPARE_GT(computed, baseline)
+ \since 6.4
+
+ \relates QTest
+
+ 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(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
+
+ \include qtestcase.qdoc to-string-overload-desc
+
+ \sa QCOMPARE_EQ(), QCOMPARE_NE(), QCOMPARE_LT(), QCOMPARE_LE(), QCOMPARE_GE()
+*/
+
+/*! \macro QCOMPARE_GE(computed, baseline)
+ \since 6.4
+
+ \relates QTest
+
+ 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(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
+
+ \include qtestcase.qdoc to-string-overload-desc
+
+ \sa QCOMPARE_EQ(), QCOMPARE_NE(), QCOMPARE_LT(), QCOMPARE_LE(), QCOMPARE_GT()
*/
/*! \macro QVERIFY_EXCEPTION_THROWN(expression, exceptiontype)
@@ -220,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.
@@ -251,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
@@ -274,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
@@ -295,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.
@@ -316,6 +463,180 @@
QEXPECT_FAIL()
*/
+/*! \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 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(computed, baseline)
+ \since 6.4
+ \relates QTest
+
+ 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
+
+ \sa QCOMPARE_EQ(), QTRY_COMPARE_EQ_WITH_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 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(computed, baseline)
+ \since 6.4
+ \relates QTest
+
+ 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
+
+ \sa QCOMPARE_NE(), QTRY_COMPARE_NE_WITH_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 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(computed, baseline)
+ \since 6.4
+ \relates QTest
+
+ 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
+
+ \sa QCOMPARE_LT(), QTRY_COMPARE_LT_WITH_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 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(computed, baseline)
+ \since 6.4
+ \relates QTest
+
+ 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
+
+ \sa QCOMPARE_LE(), QTRY_COMPARE_LE_WITH_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 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(computed, baseline)
+ \since 6.4
+ \relates QTest
+
+ 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
+
+ \sa QCOMPARE_GT(), QTRY_COMPARE_GT_WITH_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 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(computed, baseline)
+ \since 6.4
+ \relates QTest
+
+ 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
+
+ \sa QCOMPARE_GE(), QTRY_COMPARE_GE_WITH_TIMEOUT()
+*/
+
/*! \macro QFETCH(type, name)
\relates QTest
@@ -481,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
@@ -660,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}
@@ -929,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
@@ -940,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
@@ -953,18 +1275,27 @@
\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;
the default position is the center of the widget. If \a delay is
specified, the test will wait for the specified amount of
- milliseconds before releasing the button.
+ milliseconds before releasing the button; otherwise, it will wait for a
+ default amount of time (1 ms), which can be overridden via
+ \l {Testing Options}{command-line arguments}.
+
+ \note If you wish to test a double-click by sending events individually,
+ specify a short delay, greater than the default, on both mouse release events.
+ The total of the delays for the press, release, press and release must be
+ less than QStyleHints::mouseDoubleClickInterval(). But if you don't need
+ to check state between events, it's better to use QTest::mouseDClick().
+ \snippet code/src_qtestlib_qtestcase_snippet.cpp 35
\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
@@ -972,12 +1303,21 @@
on a \a window. The position of the release is defined by \a pos;
the default position is the center of the window. If \a delay is
specified, the test will wait for the specified amount of
- milliseconds before releasing the button.
+ milliseconds before releasing the button; otherwise, it will wait for a
+ default amount of time (1 ms), which can be overridden via
+ \l {Testing Options}{command-line arguments}.
+
+ \note If you wish to test a double-click by sending events individually,
+ specify a short delay, greater than the default, on both mouse release events.
+ The total of the delays for the press, release, press and release must be
+ less than QStyleHints::mouseDoubleClickInterval(). But if you don't need
+ to check state between events, it's better to use QTest::mouseDClick().
+ \snippet code/src_qtestlib_qtestcase_snippet.cpp 35
\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;
@@ -988,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
@@ -1001,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
@@ -1012,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
@@ -1043,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.
*/
/*!
@@ -1079,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.
@@ -1264,7 +1608,8 @@
*/
/*!
- \fn char *QTest::toString(QSizePolicy::ControlType ct)
+ \fn char *toString(QSizePolicy::ControlType ct)
+ \relates QTest
\overload
\since 5.5
@@ -1272,7 +1617,8 @@
*/
/*!
- \fn char *QTest::toString(QSizePolicy::ControlTypes cts)
+ \fn char *toString(QSizePolicy::ControlTypes cts)
+ \relates QTest
\overload
\since 5.5
@@ -1280,7 +1626,8 @@
*/
/*!
- \fn char *QTest::toString(QSizePolicy::Policy p)
+ \fn char *toString(QSizePolicy::Policy p)
+ \relates QTest
\overload
\since 5.5
@@ -1288,7 +1635,8 @@
*/
/*!
- \fn char *QTest::toString(QSizePolicy sp)
+ \fn char *toString(QSizePolicy sp)
+ \relates QTest
\overload
\since 5.5
@@ -1296,9 +1644,10 @@
*/
/*!
- \fn template <typename Tuple, int... I> char *QTest::toString(const Tuple &tuple, QtPrivate::IndexesList<I...> )
- \internal
- \since 5.12
+ \fn char *QTest::toString(const QKeySequence &ks)
+ \overload
+ \since 6.5
+ Returns a textual representation of the key sequence \a ks.
*/
/*!
@@ -1308,10 +1657,10 @@
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()
*/
@@ -1339,15 +1688,21 @@
*/
/*!
- \fn void QTest::QTouchEventSequence::commit(bool processEvents)
+ \fn bool QTest::QTouchEventSequence::commit(bool processEvents)
+
+ Commits this touch event to the event system, and returns whether it was
+ accepted after delivery.
+
+ Normally there is no need to call this function because it is called from
+ the destructor. However, if autoCommit is disabled, the events only get
+ committed upon explicitly calling this function. Another reason to call it
+ explicitly is to check the return value.
- Commits this sequence of touch events to the event system. Normally there is no need to call this
- function because it is called from the destructor. However, if autoCommit is disabled, the events
- only get committed upon explicitly calling this function.
+ In special cases, tests may want to disable the processing of the event.
+ This can be achieved by setting \a processEvents to false. This results in
+ merely queuing the event: the event loop will not be forced to process it.
- In special cases tests may want to disable the processing of the events. This can be achieved by
- setting \a processEvents to false. This results in merely queuing the events, the event loop will
- not be forced to process them.
+ Returns whether the event was accepted after delivery.
*/
/*!
diff --git a/src/testlib/qtestcase_p.h b/src/testlib/qtestcase_p.h
new file mode 100644
index 0000000000..ef3e083f88
--- /dev/null
+++ b/src/testlib/qtestcase_p.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2022 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 QTESTCASE_P_H
+#define QTESTCASE_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 <QtTest/qtestcase.h>
+#include <QtTest/qttestglobal.h>
+
+#include <QtCore/qstring.h>
+#include <QtCore/qnamespace.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QTest {
+#if QT_CONFIG(batch_test_support)
+ Q_TESTLIB_EXPORT QList<QString> qGetTestCaseNames();
+ Q_TESTLIB_EXPORT TestEntryFunction qGetTestCaseEntryFunction(const QString &name);
+#endif // QT_CONFIG(batch_test_support)
+} // namespace QTest
+
+QT_END_NAMESPACE
+
+#endif // QTESTCASE_P_H
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.h b/src/testlib/qtestevent.h
index 80745ed9bf..8f092cf8c9 100644
--- a/src/testlib/qtestevent.h
+++ b/src/testlib/qtestevent.h
@@ -132,7 +132,7 @@ class QTestEventList: public QList<QTestEvent *>
public:
inline QTestEventList() {}
inline QTestEventList(const QTestEventList &other): QList<QTestEvent *>()
- { for (int i = 0; i < other.count(); ++i) append(other.at(i)->clone()); }
+ { for (int i = 0; i < other.size(); ++i) append(other.at(i)->clone()); }
inline ~QTestEventList()
{ clear(); }
inline void clear()
@@ -182,7 +182,7 @@ public:
#ifdef QT_WIDGETS_LIB
inline void simulate(QWidget *w)
{
- for (int i = 0; i < count(); ++i)
+ for (int i = 0; i < size(); ++i)
at(i)->simulate(w);
}
#endif
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 32d186bd0e..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); }
@@ -36,7 +37,7 @@ public:
inline static QTestEventLoop &instance()
{
- static QPointer<QTestEventLoop> testLoop;
+ Q_CONSTINIT static QPointer<QTestEventLoop> testLoop;
if (testLoop.isNull())
testLoop = new QTestEventLoop(QCoreApplication::instance());
return *static_cast<QTestEventLoop *>(testLoop);
@@ -55,16 +56,18 @@ 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;
- if (QTest::currentTestFailed())
+ if (QTest::runningTest() && QTest::currentTestResolved())
return;
+ using namespace std::chrono_literals;
QEventLoop l;
- timerId = startTimer(ms);
+ // if tests want to measure sub-second precision, use a precise timer
+ timerId = startTimer(msecs, msecs < 1s ? Qt::PreciseTimer : Qt::CoarseTimer);
loop = &l;
l.exec();
diff --git a/src/testlib/qtestkeyboard.h b/src/testlib/qtestkeyboard.h
index efa11afbde..b051bd9124 100644
--- a/src/testlib/qtestkeyboard.h
+++ b/src/testlib/qtestkeyboard.h
@@ -259,7 +259,7 @@ namespace QTest
inline static void keyClicks(QWidget *widget, const QString &sequence,
Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay=-1)
{
- for (int i=0; i < sequence.length(); i++)
+ for (int i=0; i < sequence.size(); i++)
keyEvent(Click, widget, sequence.at(i).toLatin1(), modifier, delay);
}
diff --git a/src/testlib/qtestlog.cpp b/src/testlib/qtestlog.cpp
index a20082b630..4b6df54e91 100644
--- a/src/testlib/qtestlog.cpp
+++ b/src/testlib/qtestlog.cpp
@@ -24,6 +24,7 @@
#include <QtCore/qbytearray.h>
#include <QtCore/qelapsedtimer.h>
#include <QtCore/qlist.h>
+#include <QtCore/qmutex.h>
#include <QtCore/qvariant.h>
#if QT_CONFIG(regularexpression)
#include <QtCore/QRegularExpression>
@@ -66,10 +67,10 @@ static void saveCoverageTool(const char * appname, bool testfailed, bool install
#endif
}
-static QElapsedTimer elapsedFunctionTime;
-static QElapsedTimer elapsedTotalTime;
+Q_CONSTINIT static QElapsedTimer elapsedFunctionTime;
+Q_CONSTINIT static QElapsedTimer elapsedTotalTime;
-#define FOREACH_TEST_LOGGER for (const auto &logger : qAsConst(*QTest::loggers()))
+#define FOREACH_TEST_LOGGER for (const auto &logger : std::as_const(*QTest::loggers()))
namespace QTest {
@@ -115,7 +116,7 @@ namespace QTest {
// (the space was added automatically by ~QDebug() until Qt 5.3,
// so autotests still might expect it)
if (expected.endsWith(u' '))
- return actual == QStringView{expected}.left(expected.length() - 1);
+ return actual == QStringView{expected}.left(expected.size() - 1);
return false;
}
@@ -138,6 +139,7 @@ namespace QTest {
};
static IgnoreResultList *ignoreResultList = nullptr;
+ Q_CONSTINIT static QBasicMutex mutex;
static std::vector<QVariant> failOnWarningList;
@@ -151,6 +153,8 @@ namespace QTest {
static bool handleIgnoredMessage(QtMsgType type, const QString &message)
{
+ const QMutexLocker mutexLocker(&QTest::mutex);
+
if (!ignoreResultList)
return false;
IgnoreResultList *last = nullptr;
@@ -160,10 +164,8 @@ namespace QTest {
// remove the item from the list
if (last)
last->next = list->next;
- else if (list->next)
- ignoreResultList = list->next;
else
- ignoreResultList = nullptr;
+ ignoreResultList = list->next;
delete list;
return true;
@@ -177,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>()) {
@@ -270,6 +272,7 @@ void QTestLog::enterTestData(QTestData *data)
int QTestLog::unhandledIgnoreMessages()
{
+ const QMutexLocker mutexLocker(&QTest::mutex);
int i = 0;
QTest::IgnoreResultList *list = QTest::ignoreResultList;
while (list) {
@@ -290,6 +293,7 @@ void QTestLog::leaveTestFunction()
void QTestLog::printUnhandledIgnoreMessages()
{
+ const QMutexLocker mutexLocker(&QTest::mutex);
QString message;
QTest::IgnoreResultList *list = QTest::ignoreResultList;
while (list) {
@@ -310,10 +314,10 @@ void QTestLog::printUnhandledIgnoreMessages()
void QTestLog::clearIgnoreMessages()
{
+ const QMutexLocker mutexLocker(&QTest::mutex);
QTest::IgnoreResultList::clearList(QTest::ignoreResultList);
}
-
void QTestLog::clearFailOnWarnings()
{
QTest::failOnWarningList.clear();
@@ -321,6 +325,8 @@ void QTestLog::clearFailOnWarnings()
void QTestLog::clearCurrentTestState()
{
+ clearIgnoreMessages();
+ clearFailOnWarnings();
QTest::currentTestState = QTest::Unresolved;
}
@@ -466,10 +472,10 @@ void QTestLog::addSkip(const char *msg, const char *file, int line)
logger->addIncident(QAbstractTestLogger::Skip, msg, file, line);
}
-void QTestLog::addBenchmarkResult(const QBenchmarkResult &result)
+void QTestLog::addBenchmarkResults(const QList<QBenchmarkResult> &results)
{
FOREACH_TEST_LOGGER
- logger->addBenchmarkResult(result);
+ logger->addBenchmarkResults(results);
}
void QTestLog::startLogging()
@@ -554,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 {
@@ -594,6 +615,7 @@ void QTestLog::ignoreMessage(QtMsgType type, const char *msg)
{
QTEST_ASSERT(msg);
+ const QMutexLocker mutexLocker(&QTest::mutex);
QTest::IgnoreResultList::append(QTest::ignoreResultList, type, QString::fromUtf8(msg));
}
@@ -602,6 +624,7 @@ void QTestLog::ignoreMessage(QtMsgType type, const QRegularExpression &expressio
{
QTEST_ASSERT(expression.isValid());
+ const QMutexLocker mutexLocker(&QTest::mutex);
QTest::IgnoreResultList::append(QTest::ignoreResultList, type, QVariant(expression));
}
#endif // QT_CONFIG(regularexpression)
diff --git a/src/testlib/qtestlog_p.h b/src/testlib/qtestlog_p.h
index 8852609d7a..f9bbfa158d 100644
--- a/src/testlib/qtestlog_p.h
+++ b/src/testlib/qtestlog_p.h
@@ -63,7 +63,9 @@ public:
static void addBXPass(const char *msg, const char *file, int line);
static void addBXFail(const char *msg, const char *file, int line);
static void addSkip(const char *msg, const char *file, int line);
- static void addBenchmarkResult(const QBenchmarkResult &result);
+ static void addBenchmarkResult(const QList<QBenchmarkResult> &result)
+ { return addBenchmarkResults({ result }); }
+ static void addBenchmarkResults(const QList<QBenchmarkResult> &result);
static void ignoreMessage(QtMsgType type, const char *msg);
#ifndef QT_NO_REGULAREXPRESSION
@@ -89,6 +91,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 37e8d4eab5..567d80c521 100644
--- a/src/testlib/qtestmouse.h
+++ b/src/testlib/qtestmouse.h
@@ -57,6 +57,12 @@ namespace QTest
event. We expect all event-handling code to rely on the event
timestamps, not the system clock; therefore tests can be run faster
than real-time.
+
+ If \a delay is not given, a default minimum mouse delay is used, and
+ unintended double-click events are prevented by incrementing the
+ timestamp by 500ms after each mouse release. Therefore, to test
+ double-clicks, it's necessary to give a realistic \a delay value (for
+ example, 10ms).
*/
static void mouseEvent(MouseAction action, QWindow *window, Qt::MouseButton button,
Qt::KeyboardModifiers stateKey, QPoint pos, int delay=-1)
@@ -71,9 +77,8 @@ namespace QTest
pos.x(), pos.y(), windowSize.width(), windowSize.height());
}
- if (delay == -1 || delay < defaultMouseDelay())
- delay = defaultMouseDelay();
- lastMouseTimestamp += qMax(1, delay);
+ int actualDelay = (delay == -1 || delay < defaultMouseDelay()) ? defaultMouseDelay() : delay;
+ lastMouseTimestamp += qMax(1, actualDelay);
if (pos.isNull())
pos = QPoint(window->width() / 2, window->height() / 2);
@@ -108,7 +113,8 @@ namespace QTest
qtestMouseButtons.setFlag(button, false);
qt_handleMouseEvent(w, pos, global, qtestMouseButtons, button, QEvent::MouseButtonRelease,
stateKey, lastMouseTimestamp);
- lastMouseTimestamp += mouseDoubleClickInterval; // avoid double clicks being generated
+ if (delay == -1)
+ lastMouseTimestamp += mouseDoubleClickInterval; // avoid double clicks being generated
break;
case MouseMove:
qt_handleMouseEvent(w, pos, global, qtestMouseButtons, Qt::NoButton, QEvent::MouseMove,
@@ -169,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/qtestregistry.cpp b/src/testlib/qtestregistry.cpp
new file mode 100644
index 0000000000..ff1f8a57e6
--- /dev/null
+++ b/src/testlib/qtestregistry.cpp
@@ -0,0 +1,36 @@
+// Copyright (C) 2022 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 <QtTest/private/qtestregistry_p.h>
+
+QT_REQUIRE_CONFIG(batch_test_support);
+
+QT_BEGIN_NAMESPACE
+
+namespace QTest {
+Q_GLOBAL_STATIC(TestRegistry, g_registry);
+
+TestRegistry *TestRegistry::instance()
+{
+ return g_registry;
+}
+
+void TestRegistry::registerTest(const QString& name, TestEntryFunction entry)
+{
+ m_tests.emplace(name, std::move(entry));
+}
+
+TestRegistry::TestEntryFunction
+TestRegistry::getTestEntryFunction(const QString& name) const
+{
+ const auto it = m_tests.find(name);
+ return it != m_tests.end() ? it.value() : nullptr;
+}
+
+QStringList TestRegistry::getAllTestNames() const
+{
+ return m_tests.keys();
+}
+}
+
+QT_END_NAMESPACE
diff --git a/src/testlib/qtestregistry_p.h b/src/testlib/qtestregistry_p.h
new file mode 100644
index 0000000000..85e236cd04
--- /dev/null
+++ b/src/testlib/qtestregistry_p.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2022 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 QTESTREGISTRY_P_H
+#define QTESTREGISTRY_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/qstring.h>
+#include <QtCore/qhash.h>
+#include <QtTest/qttestglobal.h>
+
+QT_REQUIRE_CONFIG(batch_test_support);
+
+QT_BEGIN_NAMESPACE
+
+namespace QTest {
+class TestRegistry {
+public:
+ using TestEntryFunction = int(*)(int argv, char** argc);
+
+ static TestRegistry* instance();
+
+ void registerTest(const QString& name, TestEntryFunction data);
+ size_t total() const {
+ return m_tests.size();
+ }
+ TestEntryFunction getTestEntryFunction(const QString& name) const;
+ QStringList getAllTestNames() const;
+
+private:
+ QHash<QString, TestEntryFunction> m_tests;
+};
+} // namespace QTest
+
+QT_END_NAMESPACE
+
+#endif // QTESTREGISTRY_P_H
diff --git a/src/testlib/qtestresult.cpp b/src/testlib/qtestresult.cpp
index beb6291008..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,20 +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] = {'\0'};
+ 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 : "");
}
@@ -310,39 +315,57 @@ bool QTestResult::verify(bool statement, const char *statementStr,
return checkStatement(statement, msg, file, line);
}
-// Format failures using the toString() template
-template <class Actual, class Expected>
-void formatFailMessage(char *msg, size_t maxMsgLen,
- const char *failureMsg,
- const Actual &val1, const Expected &val2,
- const char *actual, const char *expected)
+static const char *leftArgNameForOp(QTest::ComparisonOperation op)
{
- auto val1S = QTest::toString(val1);
- auto val2S = QTest::toString(val2);
-
- size_t len1 = mbstowcs(nullptr, actual, maxMsgLen); // Last parameter is not ignored on QNX
- size_t len2 = mbstowcs(nullptr, expected, maxMsgLen); // (result is never larger than this).
- qsnprintf(msg, maxMsgLen, "%s\n Actual (%s)%*s %s\n Expected (%s)%*s %s",
- failureMsg,
- actual, qMax(len1, len2) - len1 + 1, ":", val1S ? val1S : "<null>",
- expected, qMax(len1, len2) - len2 + 1, ":", val2S ? val2S : "<null>");
+ return op == QTest::ComparisonOperation::CustomCompare ? "Actual " : "Computed ";
+}
- delete [] val1S;
- delete [] val2S;
+static const char *rightArgNameForOp(QTest::ComparisonOperation op)
+{
+ return op == QTest::ComparisonOperation::CustomCompare ? "Expected " : "Baseline ";
}
// Overload to format failures for "const char *" - no need to strdup().
void formatFailMessage(char *msg, size_t maxMsgLen,
const char *failureMsg,
const char *val1, const char *val2,
- const char *actual, const char *expected)
+ const char *actual, const char *expected,
+ QTest::ComparisonOperation op)
{
size_t len1 = mbstowcs(nullptr, actual, maxMsgLen); // Last parameter is not ignored on QNX
size_t len2 = mbstowcs(nullptr, expected, maxMsgLen); // (result is never larger than this).
- qsnprintf(msg, maxMsgLen, "%s\n Actual (%s)%*s %s\n Expected (%s)%*s %s",
- failureMsg,
- actual, qMax(len1, len2) - len1 + 1, ":", val1 ? val1 : "<null>",
- expected, qMax(len1, len2) - len2 + 1, ":", val2 ? val2 : "<null>");
+ const int written = qsnprintf(msg, maxMsgLen, "%s\n", failureMsg);
+ msg += written;
+ maxMsgLen -= written;
+
+ if (val1 || val2) {
+ qsnprintf(msg, maxMsgLen, " %s(%s)%*s %s\n %s(%s)%*s %s",
+ leftArgNameForOp(op), actual, qMax(len1, len2) - len1 + 1, ":",
+ val1 ? val1 : "<null>",
+ rightArgNameForOp(op), expected, qMax(len1, len2) - len2 + 1, ":",
+ val2 ? val2 : "<null>");
+ } else {
+ // only print variable names if neither value can be represented as a string
+ qsnprintf(msg, maxMsgLen, " %s: %s\n %s: %s",
+ leftArgNameForOp(op), actual, rightArgNameForOp(op), expected);
+ }
+}
+
+// Format failures using the toString() template
+template <class Actual, class Expected>
+void formatFailMessage(char *msg, size_t maxMsgLen,
+ const char *failureMsg,
+ const Actual &val1, const Expected &val2,
+ const char *actual, const char *expected,
+ QTest::ComparisonOperation op)
+{
+ const char *val1S = QTest::toString(val1);
+ const char *val2S = QTest::toString(val2);
+
+ formatFailMessage(msg, maxMsgLen, failureMsg, val1S, val2S, actual, expected, op);
+
+ delete [] val1S;
+ delete [] val2S;
}
template <class Actual, class Expected>
@@ -352,8 +375,8 @@ static bool compareHelper(bool success, const char *failureMsg,
const char *file, int line,
bool hasValues = true)
{
- const size_t maxMsgLen = 1024;
- char msg[maxMsgLen] = {'\0'};
+ char msg[maxMsgLen];
+ msg[0] = '\0';
QTEST_ASSERT(expected);
QTEST_ASSERT(actual);
@@ -380,11 +403,44 @@ static bool compareHelper(bool success, const char *failureMsg,
return checkStatement(success, msg, file, line);
}
- formatFailMessage(msg, maxMsgLen, failureMsg, val1, val2, actual, expected);
+ formatFailMessage(msg, maxMsgLen, failureMsg, val1, val2, actual, expected,
+ QTest::ComparisonOperation::CustomCompare);
return checkStatement(success, msg, file, line);
}
+// A simplified version of compareHelper that does not use string
+// representations of the values, and prints only failureMsg when the
+// comparison fails.
+static bool compareHelper(bool success, const char *failureMsg,
+ const char *actual, const char *expected,
+ const char *file, int line)
+{
+ const size_t maxMsgLen = 1024;
+ char msg[maxMsgLen];
+ msg[0] = '\0';
+
+ QTEST_ASSERT(expected);
+ QTEST_ASSERT(actual);
+ // failureMsg can be null, if we do not use it
+ QTEST_ASSERT(success || failureMsg);
+
+ if (QTestLog::verboseLevel() >= 2) {
+ qsnprintf(msg, maxMsgLen, "QCOMPARE(%s, %s)", actual, expected);
+ QTestLog::info(msg, file, line);
+ }
+
+ if (success) {
+ if (QTest::expectFailMode) {
+ qsnprintf(msg, maxMsgLen, "QCOMPARE(%s, %s) returned TRUE unexpectedly.",
+ actual, expected);
+ }
+ return checkStatement(success, msg, file, line);
+ }
+
+ return checkStatement(success, failureMsg, file, line);
+}
+
bool QTestResult::compare(bool success, const char *failureMsg,
char *val1, char *val2,
const char *actual, const char *expected,
@@ -469,10 +525,20 @@ bool QTestResult::compare(bool success, const char *failureMsg,
return compareHelper(success, failureMsg, val1, val2, actual, expected, file, line);
}
+// Simplified version of compare() that does not take the values, because they
+// can't be converted to strings (or the user didn't want to do that).
+bool QTestResult::compare(bool success, const char *failureMsg,
+ const char *actual, const char *expeceted,
+ const char *file, int line)
+{
+ return compareHelper(success, failureMsg, actual, expeceted, file, line);
+}
+
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);
@@ -518,4 +584,86 @@ const char *QTestResult::currentAppName()
return ::currentAppName;
}
+static const char *macroNameForOp(QTest::ComparisonOperation op)
+{
+ using namespace QTest;
+ switch (op) {
+ case ComparisonOperation::CustomCompare:
+ return "QCOMPARE"; /* not used */
+ case ComparisonOperation::Equal:
+ return "QCOMPARE_EQ";
+ case ComparisonOperation::NotEqual:
+ return "QCOMPARE_NE";
+ case ComparisonOperation::LessThan:
+ return "QCOMPARE_LT";
+ case ComparisonOperation::LessThanOrEqual:
+ return "QCOMPARE_LE";
+ case ComparisonOperation::GreaterThan:
+ return "QCOMPARE_GT";
+ case ComparisonOperation::GreaterThanOrEqual:
+ return "QCOMPARE_GE";
+ }
+ Q_UNREACHABLE_RETURN("");
+}
+
+static const char *failureMessageForOp(QTest::ComparisonOperation op)
+{
+ using namespace QTest;
+ switch (op) {
+ case ComparisonOperation::CustomCompare:
+ return "Compared values are not the same"; /* not used */
+ case ComparisonOperation::Equal:
+ return "The computed value is expected to be equal to the baseline, but is not";
+ case ComparisonOperation::NotEqual:
+ return "The computed value is expected to be different from the baseline, but is not";
+ case ComparisonOperation::LessThan:
+ return "The computed value is expected to be less than the baseline, but is not";
+ case ComparisonOperation::LessThanOrEqual:
+ return "The computed value is expected to be less than or equal to the baseline, but is not";
+ case ComparisonOperation::GreaterThan:
+ return "The computed value is expected to be greater than the baseline, but is not";
+ case ComparisonOperation::GreaterThanOrEqual:
+ 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, 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)
+{
+ char msg[maxMsgLen];
+ msg[0] = '\0';
+
+ QTEST_ASSERT(lhsExpr);
+ QTEST_ASSERT(rhsExpr);
+
+ if (QTestLog::verboseLevel() >= 2) {
+ qsnprintf(msg, maxMsgLen, "%s(%s, %s)", macroNameForOp(op), lhsExpr, rhsExpr);
+ QTestLog::info(msg, file, line);
+ }
+
+ if (success) {
+ if (QTest::expectFailMode) {
+ qsnprintf(msg, maxMsgLen, "%s(%s, %s) returned TRUE unexpectedly.",
+ macroNameForOp(op), lhsExpr, rhsExpr);
+ }
+ return checkStatement(success, msg, file, line);
+ }
+
+ const std::unique_ptr<const char[]> lhsPtr{ lhsFormatter(lhs) };
+ const std::unique_ptr<const char[]> rhsPtr{ rhsFormatter(rhs) };
+
+ if (!failureMessage)
+ failureMessage = failureMessageForOp(op);
+
+ formatFailMessage(msg, maxMsgLen, failureMessage, lhsPtr.get(), rhsPtr.get(),
+ lhsExpr, rhsExpr, op);
+
+ return checkStatement(success, msg, file, line);
+}
+
QT_END_NAMESPACE
diff --git a/src/testlib/qtestresult_p.h b/src/testlib/qtestresult_p.h
index fac81b5e80..e94de64c06 100644
--- a/src/testlib/qtestresult_p.h
+++ b/src/testlib/qtestresult_p.h
@@ -41,6 +41,9 @@ public:
static void setBlacklistCurrentTest(bool b);
static void addFailure(const char *message, const char *file = nullptr, int line = 0);
+ // ### TODO: Remove this overload when deprecated QTest::compare_overload
+ // is removed. Can't declare it deprecated, because it will unconditionally
+ // provide warnings.
static bool compare(bool success, const char *failureMsg,
char *val1, char *val2,
const char *actual, const char *expected,
@@ -79,6 +82,9 @@ public:
QStringView val1, const QLatin1StringView &val2,
const char *actual, const char *expected,
const char *file, int line);
+ static bool compare(bool success, const char *failureMsg,
+ const char *actual, const char *expeceted,
+ const char *file, int line);
static void setCurrentGlobalTestData(QTestData *data);
static void setCurrentTestData(QTestData *data);
static void setCurrentTestFunction(const char *func);
@@ -95,6 +101,13 @@ public:
static void setCurrentAppName(const char *appName);
static const char *currentAppName();
+ 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);
+
private:
Q_DISABLE_COPY(QTestResult)
};
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/qtesttouch.h b/src/testlib/qtesttouch.h
index 4b1f290a14..71133ff3ec 100644
--- a/src/testlib/qtesttouch.h
+++ b/src/testlib/qtesttouch.h
@@ -29,7 +29,7 @@ QT_BEGIN_NAMESPACE
namespace QTest
{
-#if defined(QT_WIDGETS_LIB) || defined(Q_CLANG_QDOC)
+#if defined(QT_WIDGETS_LIB) || defined(Q_QDOC)
inline
QTouchEventWidgetSequence touchEvent(QWidget *widget,
QPointingDevice *device,
diff --git a/src/testlib/qtestutil_macos.mm b/src/testlib/qtestutil_macos.mm
index f5b249a0da..74cfee70e9 100644
--- a/src/testlib/qtestutil_macos.mm
+++ b/src/testlib/qtestutil_macos.mm
@@ -52,7 +52,7 @@ namespace QTestPrivate {
/*! \internal
\class AppNapDisabler
- \brief Disables App Nap by registereing a bacground activity.
+ \brief Disables App Nap by registering a background activity.
App Nap remains disabled as long as the AppNapDisabler instance
exists.
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 4557eb5a07..8ede78c2a2 100644
--- a/src/testlib/qttestglobal.h
+++ b/src/testlib/qttestglobal.h
@@ -23,6 +23,15 @@ QT_BEGIN_NAMESPACE
namespace QTest
{
enum TestFailMode { Abort = 1, Continue = 2 };
+ enum class ComparisonOperation {
+ CustomCompare, /* Used for QCOMPARE() */
+ Equal,
+ NotEqual,
+ LessThan,
+ LessThanOrEqual,
+ GreaterThan,
+ GreaterThanOrEqual,
+ };
}
QT_END_NAMESPACE
diff --git a/src/testlib/qxmltestlogger.cpp b/src/testlib/qxmltestlogger.cpp
index ff4fff7c98..27da73ba52 100644
--- a/src/testlib/qxmltestlogger.cpp
+++ b/src/testlib/qxmltestlogger.cpp
@@ -255,14 +255,14 @@ void QXmlTestLogger::addBenchmarkResult(const QBenchmarkResult &result)
QTestCharBuffer quotedMetric;
QTestCharBuffer quotedTag;
- if (xmlQuote(&quotedMetric, benchmarkMetricName(result.metric))
+ if (xmlQuote(&quotedMetric, benchmarkMetricName(result.measurement.metric))
&& xmlQuote(&quotedTag, result.context.tag.toUtf8().constData())) {
QTestCharBuffer buf;
QTest::qt_asprintf(&buf,
QTest::benchmarkResultFormatString(),
quotedMetric.constData(),
quotedTag.constData(),
- result.value / double(result.iterations),
+ result.measurement.value / double(result.iterations),
result.iterations);
outputString(buf.constData());
}
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 83af041951..9addaf645b 100644
--- a/src/testlib/selfcover.cmake
+++ b/src/testlib/selfcover.cmake
@@ -1,3 +1,6 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
#
# Hand crafted file based on selfcover.pri
#
@@ -28,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}