summaryrefslogtreecommitdiffstats
path: root/src/sql/doc/src/sql-driver.qdoc
blob: 2747d934b250ec940c2891c0c78efab6f63ee746 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Free Documentation License Usage
** Alternatively, this file may be used under the terms of the GNU Free
** Documentation License version 1.3 as published by the Free Software
** Foundation and appearing in the file included in the packaging of
** this file. Please review the following information to ensure
** the GNU Free Documentation License version 1.3 requirements
** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
** $QT_END_LICENSE$
**
****************************************************************************/

/*!
    \page sql-driver.html
    \title SQL Database Drivers
    \brief How to configure and install Qt SQL drivers for supported databases.

    The Qt SQL module uses driver \l{How to Create Qt
    Plugins}{plugins} to communicate with the different database
    APIs. Since Qt's SQL Module API is database-independent, all
    database-specific code is contained within these drivers. Several
    drivers are supplied with Qt, and other drivers can be added. The
    driver source code is supplied and can be used as a model for
    \l{#development}{writing your own drivers}.

    \tableofcontents

    \section1 Supported Databases

    The table below lists the drivers included with Qt:

    \table
    \header \li Driver name \li DBMS
    \row \li \l{#QDB2}{QDB2} \li IBM DB2 (version 7.1 and above)
    \row \li \l{#QIBASE}{QIBASE} \li Borland InterBase
    \row \li \l{#QMYSQL}{QMYSQL / MARIADB} \li MySQL or MariaDB (version 5.0 and above)
    \row \li \l{#QOCI}{QOCI} \li Oracle Call Interface Driver
    \row \li \l{#QODBC}{QODBC}
         \li Open Database Connectivity (ODBC) - Microsoft SQL Server and other
            ODBC-compliant databases
    \row \li \l{#QPSQL}{QPSQL} \li PostgreSQL (versions 7.3 and above)
    \row \li \l{#QSQLITE}{QSQLITE} \li SQLite version 3
    \endtable

    SQLite is the in-process database system with the best test coverage
    and support on all platforms. Oracle via OCI, PostgreSQL, and MySQL
    through either ODBC or a native driver are well-tested on Windows and
    Linux. The completeness of the support for other systems depends on the
    availability and quality of client libraries.

    \b{Note:} To build a driver plugin you need to have the appropriate
    client library for your Database Management System (DBMS). This provides
    access to the API exposed by the DBMS, and is typically shipped with it.
    Most installation programs also allow you to install "development
    libraries", and these are what you need. These libraries are responsible
    for the low-level communication with the DBMS. Also make sure to install
    the correct database libraries for your Qt architecture (32 or 64 bit).

    \note When using Qt under Open Source terms but with a proprietary
    database, verify the client library's license compatibility with
    the LGPL.

    \target building
    \section1 Building the Drivers

    The Qt \c configure script tries to
    automatically detect the available client libraries on your
    machine. Run \c{configure -help} to see what drivers can be
    built. You should get an output similar to this:

    \snippet code/doc_src_sql-driver.qdoc 0

    The \c configure script cannot detect the necessary libraries
    and include files if they are not in the standard paths, so it
    may be necessary to specify these paths using the \c *_INCDIR=,
    \c *_LIBDIR=, or \c *_PREFIX= command-line options. For example,
    if your MySQL files are installed in \c /usr/local/mysql (or in
    \c{C:/Program Files/MySQL/MySQL Connector C 6.1} on Windows), then pass the
    following parameter to configure: \c MYSQL_PREFIX=/usr/local/mysql
    (or \c{MYSQL_PREFIX="C:/Program Files/MySQL/MySQL Connector C 6.1"} for Windows).
    The particulars for each driver are explained below.

    If something goes wrong and you want qmake to recheck your
    available drivers, you must remove \e{config.cache} in
    \e{<QTDIR>/qtbase/src/plugins/sqldrivers} - otherwise qmake will not
    search for the available drivers again. If you encounter an error during
    the qmake stage, open \e{config.log} to see what went wrong.

    A typical qmake run (in this case to configure for MySQL) looks like this:

    \snippet code/doc_src_sql-driver.qdoc 41

    Due to the practicalities of dealing with external dependencies,
    only the SQLite3 plugin is shipped with binary builds of Qt.
    To be able to add additional drivers to the Qt installation
    without re-building all of Qt, it is possible to configure
    and build the \c qtbase/src/plugins/sqldrivers directory outside
    of a full Qt build directory. Note that it is not possible to
    \e configure each driver separately, only all of them at once.
    Drivers can be \e built separately, though.
    If the Qt build is configured with \c{-prefix}, it is necessary to
    install the plugins after building them, too. For example:

    \snippet code/doc_src_sql-driver.qdoc 4

    \section1 Driver Specifics

    \target QMYSQL
    \section2 QMYSQL for MySQL or MariaDB 5 and higher

    MariaDB is a fork of MySQL intended to remain free and open-source software
    under the GNU General Public License. MariaDB intended to maintain high
    compatibility with MySQL, ensuring a drop-in replacement capability with
    library binary parity and exact matching with MySQL APIs and commands.
    Therefore the plugin for MySQL and MariaDB are combined into one Qt plugin.

    \section3 QMYSQL Stored Procedure Support

    MySQL 5 has stored procedure support at the SQL level, but no
    API to control IN, OUT, and INOUT parameters. Therefore, parameters
    have to be set and read using SQL commands instead of QSqlQuery::bindValue().

    Example stored procedure:

    \snippet code/doc_src_sql-driver.qdoc 1

    Source code to access the OUT values:

    \snippet code/doc_src_sql-driver.cpp 2

    \b{Note:} \c{@outval1} and \c{@outval2} are variables local to the current
    connection and will not be affected by queries sent from another host
    or connection.

    \section3 Embedded MySQL Server

    The MySQL embedded server is a drop-in replacement for the normal
    client library. With the embedded MySQL server, a MySQL server is
    not required to use MySQL functionality.

    To use the embedded MySQL server, simply link the Qt plugin to \c
    libmysqld instead of \c libmysqlclient. This can be done by adding
    \c MYSQL_LIBS=-lmysqld to the configure command line.

    Please refer to the MySQL documentation, chapter "libmysqld, the Embedded
    MySQL Server Library" for more information about the MySQL embedded server.

    \section3 How to Build the QMYSQL Plugin on Unix and \macos

    You need the MySQL / MariaDB header files, as well as the shared library
    \c{libmysqlclient.so} / \c{libmariadb.so}. Depending on your Linux distribution,
    you may need to install a package which is usually called "mysql-devel"
    or "mariadb-devel".

    Tell \l qmake where to find the MySQL / MariaDB header files and shared
    libraries (here it is assumed that MySQL / MariaDB is installed in
    \c{/usr/local}) and run \c{make}:

    \snippet code/doc_src_sql-driver.qdoc 3

    \section3 How to Build the QMYSQL Plugin on Windows

    You need to get the MySQL installation files (e.g.
    \l {https://dev.mysql.com/downloads/installer/}{mysql-installer-web-community-8.0.18.0.msi}) or
    \l {https://downloads.mariadb.org/connector-c/3.1.5/}{mariadb-connector-c-3.1.5-win64.msi}.
    Run the installer,
    select custom installation and install the MySQL C Connector
    which matches your Qt installation (x86 or x64).
    After installation check that the needed files are there:
    \list
    \li \c {<MySQL dir>/lib/libmysql.lib}
    \li \c {<MySQL dir>/lib/libmysql.dll}
    \li \c {<MySQL dir>/include/mysql.h}
    \endlist
    and for MariaDB
    \list
    \li \c {<MariaDB dir>/lib/libmariadb.lib}
    \li \c {<MariaDB dir>/lib/libmariadb.dll}
    \li \c {<MariaDB dir>/include/mysql.h}
    \endlist

    \note As of MySQL 8.0.19, the C Connector is no longer offered as a standalone
    installable component. Instead, you can get \c{mysql.h} and \c{libmysql.*} by
    installing the full MySQL Server (x64 only) or the
    \l{https://downloads.mariadb.org/connector-c/}{MariaDB C Connector}.

    Build the plugin as follows (here it is assumed that \c{<MySQL dir>} is
    \c{C:/Program Files/MySQL/MySQL Connector C 6.1}):

    \snippet code/doc_src_sql-driver.qdoc 5

    If you are not using a Microsoft compiler, replace \c nmake with \c
    mingw32-make above.

    When you distribute your application, remember to include \e libmysql.dll / \e libmariadb.dll
    in your installation package. It must be placed in the same folder
    as the application executable. \e libmysql.dll additionally needs the
    MSVC runtime libraries which can be installed with
    \l {https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads}{vcredist.exe}

    \target QOCI
    \section2 QOCI for the Oracle Call Interface (OCI)

    The Qt OCI plugin supports Oracle 9i, 10g and higher. After
    connecting to the Oracle server, the plugin will auto-detect the
    database version and enable features accordingly.

    It's possible to connect to a Oracle database without a tnsnames.ora file.
    This requires that the database SID is passed to the driver as the database
    name, and that a hostname is given.

    \section3 OCI User Authentication

    The Qt OCI plugin supports authentication using
    external credentials (OCI_CRED_EXT). Usually, this means that the database
    server will use the user authentication provided by the operating system
    instead of its own authentication mechanism.

    Leave the username and password empty when opening a connection with
    QSqlDatabase to use the external credentials authentication.

    \section3 OCI BLOB/LOB Support

    Binary Large Objects (BLOBs) can be read and written, but be aware
    that this process may require a lot of memory. You should use a forward
    only query to select LOB fields (see QSqlQuery::setForwardOnly()).

    Inserting BLOBs should be done using either a prepared query where the
    BLOBs are bound to placeholders or QSqlTableModel, which uses a prepared
    query to do this internally.

    \section3 How to Build the OCI Plugin on Unix and \macos

    For Oracle 10g, all you need is the "Instant Client Package - Basic" and
    "Instant Client Package - SDK". For Oracle prior to 10g, you require
    the standard Oracle client and the SDK packages.

    Oracle library files required to build the driver:

    \list
    \li \c libclntsh.so (all versions)
    \li \c libwtc9.so (only Oracle 9)
    \endlist

    Tell \c qmake where to find the Oracle header files and shared
    libraries and run make:

    For Oracle version 9:
    \snippet code/doc_src_sql-driver.qdoc 6

    For Oracle version 10, we assume that you installed the RPM packages of the
    Instant Client Package SDK (you need to adjust the version number accordingly):
    \snippet code/doc_src_sql-driver.qdoc 7

    \b{Note:} If you are using the Oracle Instant Client package,
    you will need to set LD_LIBRARY_PATH when building the OCI SQL plugin,
    and when running an application that uses the OCI SQL plugin. You can
    avoid this requirement by setting RPATH, and listing all of the
    libraries to link to. Here is an example:
    \snippet code/doc_src_sql-driver.qdoc 32

    If you wish to build the OCI plugin manually with this method, the procedure looks like this:
    \snippet code/doc_src_sql-driver.qdoc 33

    \section3 How to Build the OCI Plugin on Windows

    Choosing the option "Programmer" in the Oracle Client Installer from
    the Oracle Client Installation CD is generally sufficient to build the
    plugin.  For some versions of Oracle Client, you may also need to select
    the "Call Interface (OCI)" option if it is available.

    Build the plugin as follows (here it is assumed that Oracle Client is
    installed in \c{C:\oracle}):

    \snippet code/doc_src_sql-driver.qdoc 8

    If you are not using a Microsoft compiler, replace \c nmake with \c
    mingw32-make in the line above.

    When you run your application, you will also need to add the \c oci.dll
    path to your \c PATH environment variable:

    \snippet code/doc_src_sql-driver.qdoc 9

    \target QODBC
    \section2 QODBC for Open Database Connectivity (ODBC)

    ODBC is a general interface that allows you to connect to multiple
    DBMSs using a common interface. The QODBC driver allows you to connect
    to an ODBC driver manager and access the available data sources. Note
    that you also need to install and configure ODBC drivers for the ODBC
    driver manager that is installed on your system. The QODBC plugin
    then allows you to use these data sources in your Qt applications.

    \b{Note:} You should use the native driver, if it is available, instead
    of the ODBC driver. ODBC support can be used as a fallback for compliant
    databases if no native driver is available.

    On Windows, an ODBC driver manager should be installed by default.
    For Unix systems, there are some implementations which must be
    installed first. Note that every end user of your application is
    required to have an ODBC driver manager installed, otherwise the
    QODBC plugin will not work.

    When connecting to an ODBC datasource, you should pass the name
    of the ODBC datasource to the QSqlDatabase::setDatabaseName()
    function, rather than the actual database name.

    The QODBC Plugin needs an ODBC compliant driver manager version 2.0 or
    later. Some ODBC drivers claim to be version-2.0-compliant,
    but do not offer all the necessary functionality. The QODBC plugin
    therefore checks whether the data source can be used after a
    connection has been established, and refuses to work if the check
    fails. If you do not like this behavior, you can remove the \c{#define
    ODBC_CHECK_DRIVER} line from the file \c{qsql_odbc.cpp}. Do this at
    your own risk!

    By default, Qt instructs the ODBC driver to behave as an ODBC 2.x
    driver. However, for some \e{driver-manager/ODBC 3.x-driver}
    combinations (e.g., \e{unixODBC/MaxDB ODBC}), telling the ODBC
    driver to behave as a 2.x driver can cause the driver plugin to
    have unexpected behavior. To avoid this problem, instruct the ODBC
    driver to behave as a 3.x driver by
    \l{QSqlDatabase::setConnectOptions()} {setting the connect option}
    \c{"SQL_ATTR_ODBC_VERSION=SQL_OV_ODBC3"} before you
    \l{QSqlDatabase::open()} {open your database connection}. Note
    that this will affect multiple aspects of ODBC driver behavior,
    e.g., the SQLSTATEs.  Before setting this connect option, consult
    your ODBC documentation about behavior differences you can expect.

    When using the SAP HANA database, the connection has to be
    established using the option "SCROLLABLERESULT=TRUE", as the
    HANA ODBC driver does not provide scrollable results by default, e.g.:

    \snippet code/doc_src_sql-driver.cpp 39

    If you experience very slow access of the ODBC datasource, make sure
    that ODBC call tracing is turned off in the ODBC datasource manager.

    Some drivers do not support scrollable cursors. In that case, only
    queries in forwardOnly mode can be used successfully.

    \section3 ODBC Stored Procedure Support

    With Microsoft SQL Server the result set returned by a stored
    procedure that uses the return statement, or returns multiple result
    sets, will be accessible only if you set the query's forward only
    mode to \e forward using \l QSqlQuery::setForwardOnly().

    \snippet code/doc_src_sql-driver.cpp 10

    \b{Note:} The value returned by the stored procedure's return statement
    is discarded.

    \section3 ODBC Unicode Support

    The QODBC Plugin will use the Unicode API if UNICODE is defined. On
    Windows NT based systems, this is the default. Note that the ODBC
    driver and the DBMS must also support Unicode.

    For the Oracle 9 ODBC driver (Windows), it is necessary to check
    "SQL_WCHAR support" in the ODBC driver manager otherwise Oracle
    will convert all Unicode strings to local 8-bit.

    \section3 How to Build the ODBC Plugin on Unix and \macos

    It is recommended that you use unixODBC. You can find the latest
    version and ODBC drivers at \l http://www.unixodbc.org.
    You need the unixODBC header files and shared libraries.

    Tell \c qmake where to find the unixODBC header files and shared
    libraries (here it is assumed that unixODBC is installed in
    \c{/usr/local/unixODBC}) and run \c{make}:

    \snippet code/doc_src_sql-driver.qdoc 11

    \section3 How to Build the ODBC Plugin on Windows

    The ODBC header and include files should already be installed in the
    right directories. You just have to build the plugin as follows:

    \snippet code/doc_src_sql-driver.qdoc 12

    If you are not using a Microsoft compiler, replace \c nmake with \c
    mingw32-make in the line above.

    \target QPSQL
    \section2 QPSQL for PostgreSQL (Version 7.3 and Above)

    The QPSQL driver supports version 7.3 and higher of the PostgreSQL server.

    For more information about PostgreSQL visit \l http://www.postgresql.org.

    \section3 QPSQL Unicode Support

    The QPSQL driver automatically detects whether the PostgreSQL
    database you are connecting to supports Unicode or not. Unicode is
    automatically used if the server supports it. Note that the driver
    only supports the UTF-8 encoding. If your database uses any other
    encoding, the server must be compiled with Unicode conversion
    support.

    Unicode support was introduced in PostgreSQL version 7.1 and it will
    only work if both the server and the client library have been compiled
    with multibyte support. More information about how to set up a
    multibyte enabled PostgreSQL server can be found in the PostgreSQL
    Administrator Guide, Chapter 5.

    \section3 QPSQL Case Sensitivity

    PostgreSQL databases will only respect case sensitivity if the table or field
    name is quoted when the table is created. So for example, a SQL query such
    as:

    \snippet code/doc_src_sql-driver.qdoc 39

    will ensure that it can be accessed with the same case that was used. If the
    table or field name is not quoted when created, the actual table name
    or field name will be lower-case. When QSqlDatabase::record() or
    QSqlDatabase::primaryIndex() access a table or field that was unquoted
    when created, the name passed to the function must be lower-case to
    ensure it is found. For example:

    \snippet code/doc_src_sql-driver.qdoc 40

    \section3 QPSQL Forward-only query support

    To use forward-only queries, you must build the QPSQL plugin with
    PostreSQL client library version 9.2 or later. If the plugin is
    built with an older version, then forward-only mode will not be
    available - calling QSqlQuery::setForwardOnly() with \c true will
    have no effect.

    \warning If you build the QPSQL plugin with PostgreSQL version 9.2 or later,
    then you must distribute your application with libpq version 9.2 or later.
    Otherwise, loading the QPSQL plugin will fail with the following message:

    \snippet code/doc_src_sql-driver.qdoc 35

    While navigating the results in forward-only mode, the handle of
    QSqlResult may change. Applications that use the low-level handle of
    SQL result must get a new handle after each call to any of QSqlResult
    fetch functions. Example:

    \snippet code/doc_src_sql-driver_snippet.cpp 36

    While reading the results of a forward-only query with PostgreSQL,
    the database connection cannot be used to execute other queries.
    This is a limitation of libpq library. Example:

    \snippet code/doc_src_sql-driver.cpp 37

    This problem will not occur if query1 and query2 use different
    database connections, or if we execute query2 after the while loop.

    \note Some methods of QSqlDatabase like tables(), primaryIndex()
    implicity execute SQL queries, so these also cannot be used while
    navigating the results of forward-only query.

    \note QPSQL will print the following warning if it detects a loss of
    query results:

    \snippet code/doc_src_sql-driver.qdoc 38

    \section3 How to Build the QPSQL Plugin on Unix and \macos

    You need the PostgreSQL client library and headers installed.

    To make \c qmake find the PostgreSQL header files and shared
    libraries, run \c qmake the following way (assuming that the
    PostgreSQL client is installed in \c{/usr}):

    \snippet code/doc_src_sql-driver.qdoc 13

    \section3 How to Build the QPSQL Plugin on Windows

    Install the appropriate PostgreSQL developer libraries for your
    compiler. Assuming that PostgreSQL was installed in \c{C:\psql},
    build the plugin as follows:

    \snippet code/doc_src_sql-driver.qdoc 15

    Users of MinGW may wish to consult the following online document:
    \l{http://www.postgresql.org/docs/current/static/installation-platform-notes.html#INSTALLATION-NOTES-MINGW}{PostgreSQL MinGW/Native Windows}.

    When you distribute your application, remember to include libpq.dll
    in your installation package. It must be placed in the same folder
    as the application executable.

    \target QDB2
    \section2 QDB2 for IBM DB2 (Version 7.1 and Above)

    The Qt DB2 plugin makes it possible to access IBM DB2 databases. It
    has been tested with IBM DB2 v7.1 and 7.2. You must install the IBM
    DB2 development client library, which contains the header and library
    files necessary for compiling the QDB2 plugin.

    The QDB2 driver supports prepared queries, reading/writing of Unicode
    strings and reading/writing of BLOBs.

    We suggest using a forward-only query when calling stored procedures
    in DB2 (see QSqlQuery::setForwardOnly()).

    \section3 How to Build the QDB2 Plugin on Unix and \macos

    \snippet code/doc_src_sql-driver.qdoc 18

    \section3 How to Build the QDB2 Plugin on Windows

    The DB2 header and include files should already be installed in the
    right directories. You just have to build the plugin as follows:

    \snippet code/doc_src_sql-driver.qdoc 20

    If you are not using a Microsoft compiler, replace \c nmake
    with \c mingw32-make in the line above.

    \target QSQLITE
    \section2 QSQLITE for SQLite (Version 3 and Above)

    The Qt SQLite plugin makes it possible to access SQLite
    databases. SQLite is an in-process database, which means that it
    is not necessary to have a database server. SQLite operates on a
    single file, which must be set as the database name when opening
    a connection. If the file does not exist, SQLite will try to
    create it. SQLite also supports in-memory and temporary databases. Simply
    pass respectively ":memory:" or an empty string as the database name.

    SQLite has some restrictions regarding multiple users and
    multiple transactions. If you try to read/write on a resource from different
    transactions, your application might freeze until one transaction commits
    or rolls back. The Qt SQLite driver will retry to write to a locked resource
    until it runs into a timeout (see \c{QSQLITE_BUSY_TIMEOUT}
    at QSqlDatabase::setConnectOptions()).

    In SQLite any column, with the exception of an INTEGER PRIMARY KEY column,
    may be used to store any type of value. For instance, a column declared as
    INTEGER may contain an integer value in one row and a text value in the
    next. This is due to SQLite associating the type of a value with the value
    itself rather than with the column it is stored in. A consequence of this
    is that the type returned by QSqlField::type() only indicates the field's
    recommended type. No assumption of the actual type should be made from
    this and the type of the individual values should be checked.

    The driver is locked for updates while a select is executed. This
    may cause problems when using QSqlTableModel because Qt's item views
    fetch data as needed (with QSqlQuery::fetchMore() in the case of
    QSqlTableModel).

    You can find information about SQLite on \l{http://www.sqlite.org}.

    \section3 How to Build the QSQLITE Plugin

    SQLite version 3 is included as a third-party library within Qt.
    It can be built by passing the \c{-qt-sqlite} parameter to the
    configure script.

    If you do not want to use the SQLite library included with Qt, you
    can pass \c{-system-sqlite} to the configure script to use the SQLite
    libraries of the operating system. This is recommended whenever possible,
    as it reduces the installation size and removes one component for which
    you need to track security advisories.

    On Unix and \macos (replace \c $SQLITE with the directory where
    SQLite resides):

    \snippet code/doc_src_sql-driver.qdoc 21

    On Windows:

    \snippet code/doc_src_sql-driver.qdoc 23

    \section3 Enable REGEXP operator

    SQLite comes with a REGEXP operation. However the needed implementation must
    be provided by the user. For convenience a default implementation can be
    enabled by \l{QSqlDatabase::setConnectOptions()} {setting the connect
    option} \c{QSQLITE_ENABLE_REGEXP} before \l{QSqlDatabase::open()} {the
    database connection is opened}. Then a SQL statement like "column REGEXP
    'pattern'" basically expands to the Qt code

    \snippet code/doc_src_sql-driver.cpp 34

    For better performance the regular expressions are cached internally. By
    default the cache size is 25, but it can be changed through the option's
    value. For example passing "\c{QSQLITE_ENABLE_REGEXP=10}" reduces the
    cache size to 10.

    \section3 QSQLITE File Format Compatibility

    SQLite minor releases sometimes break file format forward compatibility.
    For example, SQLite 3.3 can read database files created with SQLite 3.2,
    but databases created with SQLite 3.3 cannot be read by SQLite 3.2.
    Please refer to the SQLite documentation and change logs for information about
    file format compatibility between versions.

    Qt minor releases usually follow the SQLite minor releases, while Qt patch releases
    follow SQLite patch releases. Patch releases are therefore both backward and forward
    compatible.

    To force SQLite to use a specific file format, it is necessary to build and
    ship your own database plugin with your own SQLite library as illustrated above.
    Some versions of SQLite can be forced to write a specific file format by setting
    the \c{SQLITE_DEFAULT_FILE_FORMAT} define when building SQLite.

    \target QIBASE
    \section2 QIBASE for Borland InterBase

    The Qt InterBase plugin makes it possible to access the InterBase and
    Firebird databases. InterBase can either be used as a client/server or
    without a server in which case it operates on local files. The
    database file must exist before a connection can be established. Firebird
    must be used with a server configuration.

    Note that InterBase requires you to specify the full path to the
    database file, no matter whether it is stored locally or on another
    server.

    \snippet code/doc_src_sql-driver.cpp 24

    You need the InterBase/Firebird development headers and libraries
    to build this plugin.

    Due to license incompatibilities with the GPL, users of the Qt Open Source
    Edition are not allowed to link this plugin to the commercial editions of
    InterBase. Please use Firebird or the free edition of InterBase.

    \section3 QIBASE Unicode Support and Text Encoding

    By default the driver connects to the database using UNICODE_FSS. This can
    be overridden by setting the ISC_DPB_LC_CTYPE parameter with
    QSqlDatabase::setConnectOptions() before opening the connection.

    \snippet code/doc_src_sql-driver.cpp 25

    If Qt does not support the given text encoding the driver will issue a
    warning message and connect to the database using UNICODE_FSS.

    Note that if the text encoding set when connecting to the database is
    not the same as in the database, problems with transliteration might arise.

    \section3 QIBASE Stored procedures

    InterBase/Firebird return OUT values as result set, so when calling stored
    procedure, only IN values need to be bound via QSqlQuery::bindValue(). The
    RETURN/OUT values can be retrieved via QSqlQuery::value(). Example:

    \snippet code/doc_src_sql-driver.cpp 26

    \section3 How to Build the QIBASE Plugin on Unix and \macos

    The following assumes InterBase or Firebird is installed in
    \c{/opt/interbase}:

    If you are using InterBase:

    \snippet code/doc_src_sql-driver.qdoc 27

    If you are using Firebird, the Firebird library has to be set explicitly:

    \snippet code/doc_src_sql-driver.qdoc 28

    \section3 How to Build the QIBASE Plugin on Windows

    The following assumes InterBase or Firebird is installed in
    \c{C:\interbase}:

    If you are using InterBase:

    \snippet code/doc_src_sql-driver.qdoc 29

    If you are using Firebird, the Firebird library has to be set explicitly:

    \snippet code/doc_src_sql-driver.qdoc 30

    If you are not using a Microsoft compiler, replace \c nmake
    with \c mingw32-make in the line above.

    Note that \c{C:\interbase\bin} must be in the \c PATH.

    \target troubleshooting
    \section1 Troubleshooting

    You should always use client libraries that have been compiled with
    the same compiler as you are using for your project. If you cannot get
    a source distibution to compile the client libraries yourself, you
    must make sure that the pre-compiled library is compatible with
    your compiler, otherwise you will get a lot of "undefined symbols"
    errors. Some compilers have tools to convert libraries, e.g. Borland
    ships the tool \c{COFF2OMF.EXE} to convert libraries that have been
    generated with Microsoft Visual C++.

    If the compilation of a plugin succeeds but it cannot be loaded,
    make sure that the following requirements are met:

    \list
    \li Ensure that the plugin is in the correct directory. You can use
        QApplication::libraryPaths() to determine where Qt looks for plugins.
    \li Ensure that the client libraries of the DBMS are available on the
        system. On Unix, run the command \c{ldd} and pass the name of the
        plugin as parameter, for example \c{ldd libqsqlmysql.so}. You will
        get a warning if any of the client libraries could not be found.
        On Windows, you can use Visual Studio's dependency walker. With
        Qt Creator, you can update the \c PATH environment variable in the
        \gui Run section of the \gui Project panel to include the path to
        the folder containing the client libraries.
    \li Compile Qt with \c{QT_DEBUG_COMPONENT} defined to get very verbose
        debug output when loading plugins.
    \endlist

    Make sure you have followed the guide to \l{Deploying Plugins}.

    \target development
    \section1 How to Write Your Own Database Driver

    QSqlDatabase is responsible for loading and managing database driver
    plugins. When a database is added (see QSqlDatabase::addDatabase()),
    the appropriate driver plugin is loaded (using QSqlDriverPlugin).
    QSqlDatabase relies on the driver plugin to provide interfaces for
    QSqlDriver and QSqlResult.

    QSqlDriver is an abstract base class which defines the functionality
    of a SQL database driver. This includes functions such as
    QSqlDriver::open() and QSqlDriver::close(). QSqlDriver is responsible
    for connecting to a database, establish the proper environment, etc.
    In addition, QSqlDriver can create QSqlQuery objects appropriate for
    the particular database API. QSqlDatabase forwards many of its
    function calls directly to QSqlDriver which provides the concrete
    implementation.

    QSqlResult is an abstract base class which defines the functionality
    of a SQL database query. This includes statements such as \c{SELECT},
    \c{UPDATE}, and \c{ALTER} \c{TABLE}. QSqlResult contains functions
    such as QSqlResult::next() and QSqlResult::value(). QSqlResult is
    responsible for sending queries to the database, returning result
    data, etc. QSqlQuery forwards many of its function calls directly to
    QSqlResult which provides the concrete implementation.

    QSqlDriver and QSqlResult are closely connected. When implementing a
    Qt SQL driver, both of these classes must to be subclassed and the
    abstract virtual methods in each class must be implemented.

    To implement a Qt SQL driver as a plugin (so that it is
    recognized and loaded by the Qt library at runtime), the driver
    must use the Q_PLUGIN_METADATA() macro. Read \l{How to Create Qt
    Plugins} for more information on this. You can also check out how
    this is done in the SQL plugins that are provided with Qt in
    \c{QTDIR/qtbase/src/plugins/sqldrivers}.

    The following code can be used as a skeleton for a SQL driver:

    \snippet sqldatabase/sqldatabase.cpp 47
    \codeline
    \snippet sqldatabase/sqldatabase.cpp 48
*/
/a> 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Copyright (C) 2016 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtTest module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtTest/qtestcase.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>
#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

#include <cmath>
#include <numeric>
#include <algorithm>
#include <condition_variable>
#include <mutex>
#include <chrono>
#include <memory>

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

#if defined(Q_OS_LINUX)
#include <sys/types.h>
#include <fcntl.h>
#endif

#ifdef Q_OS_WIN
# if !defined(Q_CC_MINGW) || (defined(Q_CC_MINGW) && defined(__MINGW64_VERSION_MAJOR))
#  include <crtdbg.h>
# endif
#include <qt_windows.h> // for Sleep
#endif
#ifdef Q_OS_UNIX
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
# if !defined(Q_OS_INTEGRITY)
#  include <sys/resource.h>
# 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

#include <vector>

QT_BEGIN_NAMESPACE

using QtMiscUtils::toHexUpper;
using QtMiscUtils::fromHex;

static bool debuggerPresent()
{
#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
}

#if !defined(Q_OS_WASM)
static bool hasSystemCrashReporter()
{
#if defined(Q_OS_MACOS)
    return QTestPrivate::macCrashReporterWillShowDialog();
#else
    return false;
#endif
}

static void disableCoreDump()
{
    bool ok = false;
    const int disableCoreDump = qEnvironmentVariableIntValue("QTEST_DISABLE_CORE_DUMP", &ok);
    if (ok && disableCoreDump) {
#if defined(Q_OS_UNIX) && !defined(Q_OS_INTEGRITY)
        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 void stackTrace()
{
    bool ok = false;
    const int disableStackDump = qEnvironmentVariableIntValue("QTEST_DISABLE_STACK_DUMP", &ok);
    if (ok && disableStackDump)
        return;

    if (debuggerPresent() || hasSystemCrashReporter())
        return;

#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)

#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

    const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
    const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
    fprintf(stderr, "\n=== Received signal at function time: %dms, total time: %dms, dumping stack ===\n",
            msecsFunctionTime, msecsTotalTime);

#  ifdef Q_OS_LINUX
    char cmd[512];
    qsnprintf(cmd, 512, "gdb --pid %d 1>&2 2>/dev/null <<EOF\n"
                         "set prompt\n"
                         "set height 0\n"
                         "thread apply all where full\n"
                         "detach\n"
                         "quit\n"
                         "EOF\n",
                         static_cast<int>(getpid()));
    if (system(cmd) == -1)
        fprintf(stderr, "calling gdb failed\n");
    fprintf(stderr, "=== End of stack trace ===\n");
#  elif defined(Q_OS_MACOS)
    char cmd[512];
    qsnprintf(cmd, 512, "lldb -p %d 1>&2 2>/dev/null <<EOF\n"
                         "bt all\n"
                         "quit\n"
                         "EOF\n",
                         static_cast<int>(getpid()));
    if (system(cmd) == -1)
        fprintf(stderr, "calling lldb failed\n");
    fprintf(stderr, "=== End of stack trace ===\n");
#  endif

#endif
}
#endif // !Q_OS_WASM

static bool installCoverageTool(const char * appname, const char * testname)
{
#if defined(__COVERAGESCANNER__) && !QT_CONFIG(testlib_selfcover)
    if (!qEnvironmentVariableIsEmpty("QT_TESTCOCOON_ACTIVE"))
        return false;
    // Set environment variable QT_TESTCOCOON_ACTIVE to prevent an eventual subtest from
    // being considered as a stand-alone test regarding the coverage analysis.
    qputenv("QT_TESTCOCOON_ACTIVE", "1");

    // Install Coverage Tool
    __coveragescanner_install(appname);
    __coveragescanner_testname(testname);
    __coveragescanner_clear();
    return true;
#else
    Q_UNUSED(appname);
    Q_UNUSED(testname);
    return false;
#endif
}

static bool isValidSlot(const QMetaMethod &sl)
{
    if (sl.access() != QMetaMethod::Private || sl.parameterCount() != 0
        || sl.returnType() != QMetaType::Void || sl.methodType() != QMetaMethod::Slot)
        return false;
    const QByteArray name = sl.name();
    return !(name.isEmpty() || name.endsWith("_data")
        || name == "initTestCase" || name == "cleanupTestCase"
        || name == "init" || name == "cleanup");
}

namespace QTestPrivate
{
    Q_TESTLIB_EXPORT Qt::MouseButtons qtestMouseButtons = Qt::NoButton;
}

namespace QTest
{
extern Q_TESTLIB_EXPORT int lastMouseTimestamp;

class WatchDog;

static QObject *currentTestObject = nullptr;
static QString mainSourcePath;

#if defined(Q_OS_MACOS)
static IOPMAssertionID macPowerSavingDisabled = 0;
#endif

class TestMethods {
public:
    Q_DISABLE_COPY_MOVE(TestMethods)

    using MetaMethods = std::vector<QMetaMethod>;

    explicit TestMethods(const QObject *o, const MetaMethods &m = MetaMethods());

    void invokeTests(QObject *testObject) const;

    static QMetaMethod findMethod(const QObject *obj, const char *signature);

private:
    bool invokeTest(int index, const char *data, WatchDog *watchDog) const;
    void invokeTestOnData(int index) const;

    QMetaMethod m_initTestCaseMethod; // might not exist, check isValid().
    QMetaMethod m_initTestCaseDataMethod;
    QMetaMethod m_cleanupTestCaseMethod;
    QMetaMethod m_initMethod;
    QMetaMethod m_cleanupMethod;

    MetaMethods m_methods;
};

TestMethods::TestMethods(const QObject *o, const MetaMethods &m)
    : m_initTestCaseMethod(TestMethods::findMethod(o, "initTestCase()"))
    , m_initTestCaseDataMethod(TestMethods::findMethod(o, "initTestCase_data()"))
    , m_cleanupTestCaseMethod(TestMethods::findMethod(o, "cleanupTestCase()"))
    , m_initMethod(TestMethods::findMethod(o, "init()"))
    , m_cleanupMethod(TestMethods::findMethod(o, "cleanup()"))
    , m_methods(m)
{
    if (m.empty()) {
        const QMetaObject *metaObject = o->metaObject();
        const int count = metaObject->methodCount();
        m_methods.reserve(count);
        for (int i = 0; i < count; ++i) {
            const QMetaMethod me = metaObject->method(i);
            if (isValidSlot(me))
                m_methods.push_back(me);
        }
    }
}

QMetaMethod TestMethods::findMethod(const QObject *obj, const char *signature)
{
    const QMetaObject *metaObject = obj->metaObject();
    const int funcIndex = metaObject->indexOfMethod(signature);
    return funcIndex >= 0 ? metaObject->method(funcIndex) : QMetaMethod();
}

static int keyDelay = -1;
static int mouseDelay = -1;
static int eventDelay = -1;
#if QT_CONFIG(thread)
static int timeout = -1;
#endif
static bool noCrashHandler = 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)
{
    const QMetaObject *metaObject = obj->metaObject();
    int funcIndex = metaObject->indexOfMethod(methodName);
    if (funcIndex >= 0) {
        QMetaMethod method = metaObject->method(funcIndex);
        method.invoke(obj, Qt::DirectConnection);
    }
}

int defaultEventDelay()
{
    if (eventDelay == -1) {
        const QByteArray env = qgetenv("QTEST_EVENT_DELAY");
        if (!env.isEmpty())
            eventDelay = atoi(env.constData());
        else
            eventDelay = 0;
    }
    return eventDelay;
}

int Q_TESTLIB_EXPORT defaultMouseDelay()
{
    if (mouseDelay == -1) {
        const QByteArray env = qgetenv("QTEST_MOUSEEVENT_DELAY");
        if (!env.isEmpty())
            mouseDelay = atoi(env.constData());
        else
            mouseDelay = defaultEventDelay();
    }
    return mouseDelay;
}

int Q_TESTLIB_EXPORT defaultKeyDelay()
{
    if (keyDelay == -1) {
        const QByteArray env = qgetenv("QTEST_KEYEVENT_DELAY");
        if (!env.isEmpty())
            keyDelay = atoi(env.constData());
        else
            keyDelay = defaultEventDelay();
    }
    return keyDelay;
}
#if QT_CONFIG(thread)
static std::chrono::milliseconds defaultTimeout()
{
    if (timeout == -1) {
        bool ok = false;
        timeout = qEnvironmentVariableIntValue("QTEST_FUNCTION_TIMEOUT", &ok);

        if (!ok || timeout <= 0)
            timeout = 5*60*1000;
    }
    return std::chrono::milliseconds{timeout};
}
#endif

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)
{
    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 || QString::fromLatin1(signature).contains(QLatin1String(filter), Qt::CaseInsensitive))
                fprintf(stream, "%s\n", signature.constData());
        }
    }
}

static void qPrintDataTags(FILE *stream)
{
    // Avoid invoking the actual test functions, and also avoid printing irrelevant output:
    QTestLog::setPrintAvailableTagsMode();

    // Get global data tags:
    QTestTable::globalTestTable();
    invokeMethod(QTest::currentTestObject, "initTestCase_data()");
    const QTestTable *gTable = QTestTable::globalTestTable();

    const QMetaObject *currTestMetaObj = QTest::currentTestObject->metaObject();

    // Process test functions:
    for (int i = 0; i < currTestMetaObj->methodCount(); ++i) {
        QMetaMethod tf = currTestMetaObj->method(i);

        if (isValidSlot(tf)) {

            // Retrieve local tags:
            QStringList localTags;
            QTestTable table;
            char *slot = qstrdup(tf.methodSignature().constData());
            slot[strlen(slot) - 2] = '\0';
            QByteArray member;
            member.resize(qstrlen(slot) + qstrlen("_data()") + 1);
            qsnprintf(member.data(), member.size(), "%s_data()", slot);
            invokeMethod(QTest::currentTestObject, member.constData());
            const int dataCount = table.dataCount();
            localTags.reserve(dataCount);
            for (int j = 0; j < dataCount; ++j)
                localTags << QLatin1String(table.testData(j)->dataTag());

            // Print all tag combinations:
            if (gTable->dataCount() == 0) {
                if (localTags.count() == 0) {
                    // No tags at all, so just print the test function:
                    fprintf(stream, "%s %s\n", currTestMetaObj->className(), slot);
                } else {
                    // Only local tags, so print each of them:
                    for (int k = 0; k < localTags.size(); ++k)
                        fprintf(
                            stream, "%s %s %s\n",
                            currTestMetaObj->className(), slot, localTags.at(k).toLatin1().data());
                }
            } else {
                for (int j = 0; j < gTable->dataCount(); ++j) {
                    if (localTags.count() == 0) {
                        // Only global tags, so print the current one:
                        fprintf(
                            stream, "%s %s __global__ %s\n",
                            currTestMetaObj->className(), slot, gTable->testData(j)->dataTag());
                    } else {
                        // Local and global tags, so print each of the local ones and
                        // the current global one:
                        for (int k = 0; k < localTags.size(); ++k)
                            fprintf(
                                stream, "%s %s %s __global__ %s\n", currTestMetaObj->className(), slot,
                                localTags.at(k).toLatin1().data(), gTable->testData(j)->dataTag());
                    }
                }
            }

            delete[] slot;
        }
    }
}

static int qToInt(const char *str)
{
    char *pEnd;
    int l = static_cast<int>(strtol(str, &pEnd, 10));
    if (*pEnd != 0) {
        fprintf(stderr, "Invalid numeric parameter: '%s'\n", str);
        exit(1);
    }
    return l;
}

Q_TESTLIB_EXPORT void qtest_qParseArgs(int argc, const char *const argv[], bool qml)
{
    int logFormat = -1; // Not set
    const char *logFilename = nullptr;

    QTest::testFunctions.clear();
    QTest::testTags.clear();

#if defined(Q_OS_MAC) && defined(HAVE_XCTEST)
    if (QXcodeTestLogger::canLogTestProgress())
        logFormat = QTestLog::XCTest;
#endif

    const char *testOptions =
         " New-style logging options:\n"
         " -o filename,format  : Output results to file in the specified format\n"
         "                       Use - to output to stdout\n"
         "                       Valid formats are:\n"
         "                         txt      : Plain text\n"
         "                         csv      : CSV format (suitable for benchmarks)\n"
         "                         junitxml : XML JUnit document\n"
         "                         xml      : XML document\n"
         "                         lightxml : A stream of XML tags\n"
         "                         teamcity : TeamCity format\n"
         "                         tap      : Test Anything Protocol\n"
         "\n"
         "     *** Multiple loggers can be specified, but at most one can log to stdout.\n"
         "\n"
         " Old-style logging options:\n"
         " -o filename         : Write the output into file\n"
         " -txt                : Output results in Plain Text\n"
         " -csv                : Output results in a CSV format (suitable for benchmarks)\n"
         " -junitxml           : Output results as XML JUnit document\n"
         " -xml                : Output results as XML document\n"
         " -lightxml           : Output results as stream of XML tags\n"
         " -teamcity           : Output results in TeamCity format\n"
         " -tap                : Output results in Test Anything Protocol format\n"
         "\n"
         "     *** If no output file is specified, stdout is assumed.\n"
         "     *** If no output format is specified, -txt is assumed.\n"
         "\n"
         " Test log detail options:\n"
         " -silent             : Log failures and fatal errors only\n"
         " -v1                 : Log the start of each testfunction\n"
         " -v2                 : Log each QVERIFY/QCOMPARE/QTEST (implies -v1)\n"
         " -vs                 : Log every signal emission and resulting slot invocations\n"
         "\n"
         "     *** The -silent and -v1 options only affect plain text output.\n"
         "\n"
         " Testing options:\n"
         " -functions          : Returns a list of current testfunctions\n"
         " -datatags           : Returns a list of current data tags.\n"
         "                       A global data tag is preceded by ' __global__ '.\n"
         " -eventdelay ms      : Set default delay for mouse and keyboard simulation to ms milliseconds\n"
         " -keydelay ms        : Set default delay for keyboard simulation to ms milliseconds\n"
         " -mousedelay ms      : Set default delay for mouse simulation to ms milliseconds\n"
         " -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"
         "\n"
         " Benchmarking options:\n"
#if QT_CONFIG(valgrind)
         " -callgrind          : Use callgrind to time benchmarks\n"
#endif
#ifdef QTESTLIB_USE_PERF_EVENTS
         " -perf               : Use Linux perf events to time benchmarks\n"
         " -perfcounter name   : Use the counter named 'name'\n"
         " -perfcounterlist    : Lists the counters available\n"
#endif
#ifdef HAVE_TICK_COUNTER
         " -tickcounter        : Use CPU tick counters to time benchmarks\n"
#endif
         " -eventcounter       : Counts events received during benchmarks\n"
         " -minimumvalue n     : Sets the minimum acceptable measurement value\n"
         " -minimumtotal n     : Sets the minimum acceptable total for repeated executions of a test function\n"
         " -iterations  n      : Sets the number of accumulation iterations.\n"
         " -median  n          : Sets the number of median iterations.\n"
         " -vb                 : Print out verbose benchmarking information.\n";

    for (int i = 1; i < argc; ++i) {
        if (strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0
            || strcmp(argv[i], "/?") == 0) {
            printf(" Usage: %s [options] [testfunction[:testdata]]...\n"
                   "    By default, all testfunctions will be run.\n\n"
                   "%s", argv[0], testOptions);

            if (qml) {
                printf ("\n"
                        " QmlTest options:\n"
                        " -import dir         : Specify an import directory.\n"
                        " -plugins dir        : Specify a directory where to search for plugins.\n"
                        " -input dir/file     : Specify the root directory for test cases or a single test case file.\n"
                        " -translation file   : Specify the translation file.\n"
                        " -file-selector dir  : Specify a file selector for the QML engine.\n"
                        );
            }

            printf("\n"
                   " -help               : This help\n");
            exit(0);
        } else if (strcmp(argv[i], "-functions") == 0) {
            if (qml) {
                QTest::printAvailableFunctions = true;
            } else {
                qPrintTestSlots(stdout);
                exit(0);
            }
        } else if (strcmp(argv[i], "-datatags") == 0) {
            if (!qml) {
                qPrintDataTags(stdout);
                exit(0);
            }
        } else if (strcmp(argv[i], "-txt") == 0) {
            logFormat = QTestLog::Plain;
        } else if (strcmp(argv[i], "-csv") == 0) {
            logFormat = QTestLog::CSV;
        } else if (strcmp(argv[i], "-junitxml") == 0)  {
            logFormat = QTestLog::JUnitXML;
        } else if (strcmp(argv[i], "-xunitxml") == 0)  {
            fprintf(stderr, "WARNING: xunitxml is deprecated. Please use junitxml.\n");
            logFormat = QTestLog::JUnitXML;
        } else if (strcmp(argv[i], "-xml") == 0) {
            logFormat = QTestLog::XML;
        } else if (strcmp(argv[i], "-lightxml") == 0) {
            logFormat = QTestLog::LightXML;
        } else if (strcmp(argv[i], "-teamcity") == 0) {
            logFormat = QTestLog::TeamCity;
        } else if (strcmp(argv[i], "-tap") == 0) {
            logFormat = QTestLog::TAP;
        } else if (strcmp(argv[i], "-silent") == 0) {
            QTestLog::setVerboseLevel(-1);
        } else if (strcmp(argv[i], "-v1") == 0) {
            QTestLog::setVerboseLevel(1);
        } else if (strcmp(argv[i], "-v2") == 0) {
            QTestLog::setVerboseLevel(2);
        } else if (strcmp(argv[i], "-vs") == 0) {
            QSignalDumper::setEnabled(true);
        } else if (strcmp(argv[i], "-o") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "-o needs an extra parameter specifying the filename and optional format\n");
                exit(1);
            }
            ++i;
            // Do we have the old or new style -o option?
            char *filename = new char[strlen(argv[i])+1];
            char *format = new char[strlen(argv[i])+1];
            if (sscanf(argv[i], "%[^,],%s", filename, format) == 1) {
                // Old-style
                logFilename = argv[i];
            } else {
                // New-style
                if (strcmp(format, "txt") == 0)
                    logFormat = QTestLog::Plain;
                else if (strcmp(format, "csv") == 0)
                    logFormat = QTestLog::CSV;
                else if (strcmp(format, "lightxml") == 0)
                    logFormat = QTestLog::LightXML;
                else if (strcmp(format, "xml") == 0)
                    logFormat = QTestLog::XML;
                else if (strcmp(format, "junitxml") == 0)
                    logFormat = QTestLog::JUnitXML;
                else if (strcmp(format, "xunitxml") == 0) {
                    fprintf(stderr, "WARNING: xunitxml is deprecated. Please use junitxml.\n");
                    logFormat = QTestLog::JUnitXML;
                } else if (strcmp(format, "teamcity") == 0)
                    logFormat = QTestLog::TeamCity;
                else if (strcmp(format, "tap") == 0)
                    logFormat = QTestLog::TAP;
                else {
                    fprintf(stderr, "output format must be one of txt, csv, lightxml, xml, tap, teamcity or junitxml\n");
                    exit(1);
                }
                if (strcmp(filename, "-") == 0 && QTestLog::loggerUsingStdout()) {
                    fprintf(stderr, "only one logger can log to stdout\n");
                    exit(1);
                }
                QTestLog::addLogger(QTestLog::LogMode(logFormat), filename);
            }
            delete [] filename;
            delete [] format;
        } else if (strcmp(argv[i], "-eventdelay") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "-eventdelay needs an extra parameter to indicate the delay(ms)\n");
                exit(1);
            } else {
                QTest::eventDelay = qToInt(argv[++i]);
            }
        } else if (strcmp(argv[i], "-keydelay") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "-keydelay needs an extra parameter to indicate the delay(ms)\n");
                exit(1);
            } else {
                QTest::keyDelay = qToInt(argv[++i]);
            }
        } else if (strcmp(argv[i], "-mousedelay") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "-mousedelay needs an extra parameter to indicate the delay(ms)\n");
                exit(1);
            } else {
                QTest::mouseDelay = qToInt(argv[++i]);
            }
        } else if (strcmp(argv[i], "-maxwarnings") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "-maxwarnings needs an extra parameter with the amount of warnings\n");
                exit(1);
            } else {
                QTestLog::setMaxWarnings(qToInt(argv[++i]));
            }
        } else if (strcmp(argv[i], "-nocrashhandler") == 0) {
            QTest::noCrashHandler = true;
#if QT_CONFIG(valgrind)
        } else if (strcmp(argv[i], "-callgrind") == 0) {
            if (QBenchmarkValgrindUtils::haveValgrind())
                if (QFileInfo(QDir::currentPath()).isWritable()) {
                    QBenchmarkGlobalData::current->setMode(QBenchmarkGlobalData::CallgrindParentProcess);
                } else {
                    fprintf(stderr, "WARNING: Current directory not writable. Using the walltime measurer.\n");
                }
            else {
                fprintf(stderr, "WARNING: Valgrind not found or too old. Make sure it is installed and in your path. "
                       "Using the walltime measurer.\n");
            }
        } else if (strcmp(argv[i], "-callgrindchild") == 0) { // "private" option
            QBenchmarkGlobalData::current->setMode(QBenchmarkGlobalData::CallgrindChildProcess);
            QBenchmarkGlobalData::current->callgrindOutFileBase =
                QBenchmarkValgrindUtils::outFileBase();
#endif
#ifdef QTESTLIB_USE_PERF_EVENTS
        } else if (strcmp(argv[i], "-perf") == 0) {
            if (QBenchmarkPerfEventsMeasurer::isAvailable()) {
                // perf available
                QBenchmarkGlobalData::current->setMode(QBenchmarkGlobalData::PerfCounter);
            } else {
                fprintf(stderr, "WARNING: Linux perf events not available. Using the walltime measurer.\n");
            }
        } else if (strcmp(argv[i], "-perfcounter") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "-perfcounter needs an extra parameter with the name of the counter\n");
                exit(1);
            } else {
                QBenchmarkPerfEventsMeasurer::setCounter(argv[++i]);
            }
        } else if (strcmp(argv[i], "-perfcounterlist") == 0) {
            QBenchmarkPerfEventsMeasurer::listCounters();
            exit(0);
#endif
#ifdef HAVE_TICK_COUNTER
        } else if (strcmp(argv[i], "-tickcounter") == 0) {
            QBenchmarkGlobalData::current->setMode(QBenchmarkGlobalData::TickCounter);
#endif
        } else if (strcmp(argv[i], "-eventcounter") == 0) {
            QBenchmarkGlobalData::current->setMode(QBenchmarkGlobalData::EventCounter);
        } else if (strcmp(argv[i], "-minimumvalue") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "-minimumvalue needs an extra parameter to indicate the minimum time(ms)\n");
                exit(1);
            } else {
                QBenchmarkGlobalData::current->walltimeMinimum = qToInt(argv[++i]);
            }
        } else if (strcmp(argv[i], "-minimumtotal") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "-minimumtotal needs an extra parameter to indicate the minimum total measurement\n");
                exit(1);
            } else {
                QBenchmarkGlobalData::current->minimumTotal = qToInt(argv[++i]);
            }
        } else if (strcmp(argv[i], "-iterations") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "-iterations needs an extra parameter to indicate the number of iterations\n");
                exit(1);
            } else {
                QBenchmarkGlobalData::current->iterationCount = qToInt(argv[++i]);
            }
        } else if (strcmp(argv[i], "-median") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "-median needs an extra parameter to indicate the number of median iterations\n");
                exit(1);
            } else {
                QBenchmarkGlobalData::current->medianIterationCount = qToInt(argv[++i]);
            }

        } else if (strcmp(argv[i], "-vb") == 0) {
            QBenchmarkGlobalData::current->verboseOutput = true;
#if defined(Q_OS_DARWIN)
        } else if (strncmp(argv[i], "-Apple", 6) == 0) {
            i += 1; // Skip Apple-specific user preferences
            continue;
# if defined(HAVE_XCTEST)
        } else if (int skip = QXcodeTestLogger::parseCommandLineArgument(argv[i])) {
            i += (skip - 1); // Eating argv[i] with a continue counts towards skips
            continue;
# endif
#endif
        } else if (argv[i][0] == '-') {
            fprintf(stderr, "Unknown option: '%s'\n\n%s", argv[i], testOptions);
            if (qml) {
                fprintf(stderr, "\nqmltest related options:\n"
                                " -import    : Specify an import directory.\n"
                                " -plugins   : Specify a directory where to search for plugins.\n"
                                " -input     : Specify the root directory for test cases.\n"
                       );
            }

            fprintf(stderr, "\n"
                            " -help      : This help\n");
            exit(1);
        } else {
            // We can't check the availability of test functions until
            // we load the QML files.  So just store the data for now.
            int colon = -1;
            int offset;
            for (offset = 0; argv[i][offset]; ++offset) {
                if (argv[i][offset] == ':') {
                    if (argv[i][offset + 1] == ':') {
                        // "::" is used as a test name separator.
                        // e.g. "ClickTests::test_click:row1".
                        ++offset;
                    } else {
                        colon = offset;
                        break;
                    }
                }
            }
            if (colon == -1) {
                QTest::testFunctions += QString::fromLatin1(argv[i]);
                QTest::testTags += QString();
            } else {
                QTest::testFunctions +=
                    QString::fromLatin1(argv[i], colon);
                QTest::testTags +=
                    QString::fromLatin1(argv[i] + colon + 1);
            }
        }
    }

    bool installedTestCoverage = installCoverageTool(QTestResult::currentAppName(), QTestResult::currentTestObjectName());
    QTestLog::setInstalledTestCoverage(installedTestCoverage);

    // If no loggers were created by the long version of the -o command-line
    // option, but a logger was requested via the old-style option, add it.
    const bool explicitLoggerRequested = logFormat != -1;
    if (QTestLog::loggerCount() == 0 && explicitLoggerRequested)
        QTestLog::addLogger(QTestLog::LogMode(logFormat), logFilename);

    bool addFallbackLogger = !explicitLoggerRequested;

#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();
    if (safeToAddAppleLogger && QAppleTestLogger::debugLoggingEnabled()) {
        QTestLog::addLogger(QTestLog::Apple, nullptr);
        if (AppleUnifiedLogger::willMirrorToStderr() && !logFilename)
            addFallbackLogger = false; // Prevent plain test logger fallback below
    }
#endif

    if (addFallbackLogger)
        QTestLog::addLogger(QTestLog::Plain, logFilename);
}

// Temporary, backwards compatibility, until qtdeclarative's use of it is converted
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)
{
    const int count = container.count();
    if (count == 0)
        return QBenchmarkResult();

    if (count == 1)
        return container.front();

    QList<QBenchmarkResult> containerCopy = container;
    std::sort(containerCopy.begin(), containerCopy.end());

    const int middle = count / 2;

    // ### handle even-sized containers here by doing an aritmetic mean of the two middle items.
    return containerCopy.at(middle);
}

struct QTestDataSetter
{
    QTestDataSetter(QTestData *data)
    {
        QTestResult::setCurrentTestData(data);
    }
    ~QTestDataSetter()
    {
        QTestResult::setCurrentTestData(nullptr);
    }
};

namespace {

qreal addResult(qreal current, const QBenchmarkResult& r)
{
    return current + r.value;
}

}

void TestMethods::invokeTestOnData(int index) const
{
    /* Benchmarking: for each median iteration*/

    bool isBenchmark = false;
    int i = (QBenchmarkGlobalData::current->measurer->needsWarmupIteration()) ? -1 : 0;

    QList<QBenchmarkResult> results;
    bool minimumTotalReached = false;
    do {
        QBenchmarkTestMethodData::current->beginDataRun();

        /* Benchmarking: for each accumulation iteration*/
        bool invokeOk;
        do {
            if (m_initMethod.isValid())
                m_initMethod.invoke(QTest::currentTestObject, Qt::DirectConnection);
            if (QTestResult::skipCurrentTest() || QTestResult::currentTestFailed())
                break;

            QBenchmarkTestMethodData::current->result = QBenchmarkResult();
            QBenchmarkTestMethodData::current->resultAccepted = false;

            QBenchmarkGlobalData::current->context.tag =
                QLatin1String(
                    QTestResult::currentDataTag()
                    ? QTestResult::currentDataTag() : "");

            invokeOk = m_methods[index].invoke(QTest::currentTestObject, Qt::DirectConnection);
            if (!invokeOk)
                QTestResult::addFailure("Unable to execute slot", __FILE__, __LINE__);

            isBenchmark = QBenchmarkTestMethodData::current->isBenchmark();

            QTestResult::finishedCurrentTestData();

            if (m_cleanupMethod.isValid())
                m_cleanupMethod.invoke(QTest::currentTestObject, Qt::DirectConnection);

            // Process any deleteLater(), like event-loop based apps would do. Fixes memleak reports.
            if (QCoreApplication::instance())
                QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);

            // If the test isn't a benchmark, finalize the result after cleanup() has finished.
            if (!isBenchmark)
                QTestResult::finishedCurrentTestDataCleanup();

            // If this test method has a benchmark, repeat until all measurements are
            // acceptable.
            // The QBENCHMARK macro increases the number of iterations for each run until
            // this happens.
        } while (invokeOk && isBenchmark
                 && QBenchmarkTestMethodData::current->resultsAccepted() == false
                 && !QTestResult::skipCurrentTest() && !QTestResult::currentTestFailed());

        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);
                }
            }
        }

        // Verify if the minimum total measurement is 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);
            minimumTotalReached = (total >= QBenchmarkGlobalData::current->minimumTotal);
        }
    } while (isBenchmark
             && ((++i < QBenchmarkGlobalData::current->adjustMedianIterationCount()) || !minimumTotalReached)
             && !QTestResult::skipCurrentTest() && !QTestResult::currentTestFailed());

    // If the test is a benchmark, finalize the result after all iterations have finished.
    if (isBenchmark) {
        bool testPassed = !QTestResult::skipCurrentTest() && !QTestResult::currentTestFailed();
        QTestResult::finishedCurrentTestDataCleanup();
        // Only report benchmark figures if the test passed
        if (testPassed && QBenchmarkTestMethodData::current->resultsAccepted())
            QTestLog::addBenchmarkResult(qMedian(results));
    }
}

#if QT_CONFIG(thread)

class WatchDog : public QThread
{
    enum Expectation {
        ThreadStart,
        TestFunctionStart,
        TestFunctionEnd,
        ThreadEnd,
    };

    bool waitFor(std::unique_lock<QtPrivate::mutex> &m, Expectation e) {
        auto expectationChanged = [this, e] { return expecting.load(std::memory_order_relaxed) != e; };
        switch (e) {
        case TestFunctionEnd:
            return waitCondition.wait_for(m, defaultTimeout(), expectationChanged);
        case ThreadStart:
        case ThreadEnd:
        case TestFunctionStart:
            waitCondition.wait(m, expectationChanged);
            return true;
        }
        Q_UNREACHABLE();
        return false;
    }

public:
    WatchDog()
    {
        setObjectName(QLatin1String("QtTest Watchdog"));
        auto locker = qt_unique_lock(mutex);
        expecting.store(ThreadStart, std::memory_order_relaxed);
        start();
        waitFor(locker, ThreadStart);
    }
    ~WatchDog() {
        {
            const auto locker = qt_scoped_lock(mutex);
            expecting.store(ThreadEnd, std::memory_order_relaxed);
            waitCondition.notify_all();
        }
        wait();
    }

    void beginTest() {
        const auto locker = qt_scoped_lock(mutex);
        expecting.store(TestFunctionEnd, std::memory_order_relaxed);
        waitCondition.notify_all();
    }

    void testFinished() {
        const auto locker = qt_scoped_lock(mutex);
        expecting.store(TestFunctionStart, std::memory_order_relaxed);
        waitCondition.notify_all();
    }

    void run() override {
        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) {
            case ThreadEnd:
                return;
            case ThreadStart:
                Q_UNREACHABLE();
            case TestFunctionStart:
            case TestFunctionEnd:
                if (Q_UNLIKELY(!waitFor(locker, e))) {
#ifndef Q_OS_WASM
                    stackTrace();
#endif
                    qFatal("Test function timed out");
                }
            }
        }
    }

private:
    QtPrivate::mutex mutex;
    QtPrivate::condition_variable waitCondition;
    std::atomic<Expectation> expecting;
};

#else // !QT_CONFIG(thread)

class WatchDog : public QObject
{
public:
    void beginTest() {};
    void testFinished() {};
};

#endif


/*!
    \internal

    Call slot_data(), init(), slot(), cleanup(), init(), slot(), cleanup(), ...
    If data is set then it is the only test that is performed

    If the function was successfully called, true is returned, otherwise
    false.
 */
bool TestMethods::invokeTest(int index, const char *data, WatchDog *watchDog) const
{
    QBenchmarkTestMethodData benchmarkData;
    QBenchmarkTestMethodData::current = &benchmarkData;

    const QByteArray &name = m_methods[index].name();
    QBenchmarkGlobalData::current->context.slotName = QLatin1String(name) + QLatin1String("()");

    char member[512];
    QTestTable table;

    QTestResult::setCurrentTestFunction(name.constData());

    const QTestTable *gTable = QTestTable::globalTestTable();
    const int globalDataCount = gTable->dataCount();
    int curGlobalDataIndex = 0;

    /* For each entry in the global data table, do: */
    do {
        if (!gTable->isEmpty())
            QTestResult::setCurrentGlobalTestData(gTable->testData(curGlobalDataIndex));

        if (curGlobalDataIndex == 0) {
            qsnprintf(member, 512, "%s_data()", name.constData());
            invokeMethod(QTest::currentTestObject, member);
            if (QTestResult::skipCurrentTest())
                break;
        }

        bool foundFunction = false;
        int curDataIndex = 0;
        const int dataCount = table.dataCount();

        // Data tag requested but none available?
        if (data && !dataCount) {
            // Let empty data tag through.
            if (!*data)
                data = nullptr;
            else {
                fprintf(stderr, "Unknown testdata for function %s(): '%s'\n", name.constData(), data);
                fprintf(stderr, "Function has no testdata.\n");
                return false;
            }
        }

        /* For each entry in this test's data table, do: */
        do {
            QTestResult::setSkipCurrentTest(false);
            QTestResult::setBlacklistCurrentTest(false);
            if (!data || !qstrcmp(data, table.testData(curDataIndex)->dataTag())) {
                foundFunction = true;

                QTestPrivate::checkBlackLists(name.constData(), dataCount ? table.testData(curDataIndex)->dataTag() : nullptr);

                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 (data)
                    break;
            }
            ++curDataIndex;
        } while (curDataIndex < dataCount);

        if (data && !foundFunction) {
            fprintf(stderr, "Unknown testdata for function %s: '%s()'\n", name.constData(), data);
            fprintf(stderr, "Available testdata:\n");
            for (int i = 0; i < table.dataCount(); ++i)
                fprintf(stderr, "%s\n", table.testData(i)->dataTag());
            return false;
        }

        QTestResult::setCurrentGlobalTestData(nullptr);
        ++curGlobalDataIndex;
    } while (curGlobalDataIndex < globalDataCount);

    QTestResult::finishedCurrentTestFunction();
    QTestResult::setSkipCurrentTest(false);
    QTestResult::setBlacklistCurrentTest(false);
    QTestResult::setCurrentTestData(nullptr);

    return true;
}

void *fetchData(QTestData *data, const char *tagName, int typeId)
{
    QTEST_ASSERT(typeId);
    QTEST_ASSERT_X(data, "QTest::fetchData()", "Test data requested, but no testdata available.");
    QTEST_ASSERT(data->parent());

    int idx = data->parent()->indexOf(tagName);

    if (Q_UNLIKELY(idx == -1 || idx >= data->dataCount())) {
        qFatal("QFETCH: Requested testdata '%s' not available, check your _data function.",
                tagName);
    }

    if (Q_UNLIKELY(typeId != data->parent()->elementTypeId(idx))) {
        qFatal("Requested type '%s' does not match available type '%s'.",
               QMetaType(typeId).name(),
               QMetaType(data->parent()->elementTypeId(idx)).name());
    }

    return data->data(idx);
}

/*!
 * \internal
 */
char *formatString(const char *prefix, const char *suffix, size_t numArguments, ...)
{
    va_list ap;
    va_start(ap, numArguments);

    QByteArray arguments;
    arguments += prefix;

    if (numArguments > 0) {
        arguments += va_arg(ap, const char *);

        for (size_t i = 1; i < numArguments; ++i) {
            arguments += ", ";
            arguments += va_arg(ap, const char *);
        }
    }

    va_end(ap);
    arguments += suffix;
    return qstrdup(arguments.constData());
}

/*!
  \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
  the returned string as an ellipsis at the end. The caller has
  ownership of the returned pointer and must ensure it is later passed
  to operator delete[].

  \a length is the length of the string \a ba.
 */
char *toHexRepresentation(const char *ba, int length)
{
    if (length == 0)
        return qstrdup("");

    /* We output at maximum about maxLen characters in order to avoid
     * running out of memory and flooding things when the byte array
     * is large.
     *
     * 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);
    char *result = nullptr;

    if (length > maxLen) {
        const int size = len * 3 + 4;
        result = new char[size];

        char *const forElipsis = result + size - 5;
        forElipsis[0] = ' ';
        forElipsis[1] = '.';
        forElipsis[2] = '.';
        forElipsis[3] = '.';
        result[size - 1] = '\0';
    }
    else {
        const int size = len * 3;
        result = new char[size];
        result[size - 1] = '\0';
    }

    int i = 0;
    int o = 0;

    while (true) {
        const char at = ba[i];

        result[o] = toHexUpper(at >> 4);
        ++o;
        result[o] = toHexUpper(at);

        ++i;
        ++o;
        if (i == len)
            break;
        result[o] = ' ';
        ++o;
    }

    return result;
}

/*!
    \internal
    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)
{
    bool trimmed = false;
    auto buffer = std::make_unique<char[]>(256);
    const char *end = p + length;
    char *dst = buffer.get();

    bool lastWasHexEscape = false;
    *dst++ = '"';
    for ( ; p != end; ++p) {
        // we can add:
        //  1 byte: a single character
        //  2 bytes: a simple escape sequence (\n)
        //  3 bytes: "" and a character
        //  4 bytes: an hex escape sequence (\xHH)
        if (dst - buffer.get() > 246) {
            // plus the quote, the three dots and NUL, it's 255 in the worst case
            trimmed = true;
            break;
        }

        // check if we need to insert "" to break an hex escape sequence
        if (Q_UNLIKELY(lastWasHexEscape)) {
            if (fromHex(*p) != -1) {
                // yes, insert it
                *dst++ = '"';
                *dst++ = '"';
            }
            lastWasHexEscape = false;
        }

        if (*p < 0x7f && *p >= 0x20 && *p != '\\' && *p != '"') {
            *dst++ = *p;
            continue;
        }

        // write as an escape sequence
        // this means we may advance dst to buffer.data() + 247 or 250
        *dst++ = '\\';
        switch (*p) {
        case 0x5c:
        case 0x22:
            *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:
            // print as hex escape
            *dst++ = 'x';
            *dst++ = toHexUpper(uchar(*p) >> 4);
            *dst++ = toHexUpper(uchar(*p));
            lastWasHexEscape = true;
            break;
        }
    }

    *dst++ = '"';
    if (trimmed) {
        *dst++ = '.';
        *dst++ = '.';
        *dst++ = '.';
    }
    *dst++ = '\0';
    return buffer.release();
}

/*!
    \internal
    Returns the same QString but with only the ASCII characters still shown;
    everything else is replaced with \c {\uXXXX}.

    Similar to QDebug::putString().
*/
char *toPrettyUnicode(QStringView string)
{
    auto p = reinterpret_cast<const ushort *>(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);
    const ushort *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
            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++ = '"';
    if (trimmed) {
        *dst++ = '.';
        *dst++ = '.';
        *dst++ = '.';
    }
    *dst++ = '\0';
    return buffer.release();
}

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);

    QScopedPointer<WatchDog> watchDog;
    if (!debuggerPresent()
#if QT_CONFIG(valgrind)
        && QBenchmarkGlobalData::current->mode() != QBenchmarkGlobalData::CallgrindChildProcess
#endif
       ) {
        watchDog.reset(new WatchDog);
    }

    QSignalDumper::startDump();

    if (!QTestResult::skipCurrentTest() && !QTest::currentTestFailed()) {
        if (m_initTestCaseMethod.isValid())
            m_initTestCaseMethod.invoke(testObject, Qt::DirectConnection);

        // finishedCurrentTestDataCleanup() resets QTestResult::currentTestFailed(), so use a local copy.
        const bool previousFailed = QTestResult::currentTestFailed();
        QTestResult::finishedCurrentTestData();
        QTestResult::finishedCurrentTestDataCleanup();
        QTestResult::finishedCurrentTestFunction();

        if (!QTestResult::skipCurrentTest() && !previousFailed) {
            for (int i = 0, count = int(m_methods.size()); i < count; ++i) {
                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, data, watchDog.data());
                delete [] data;
                if (!ok)
                    break;
            }
        }

        QTestResult::setSkipCurrentTest(false);
        QTestResult::setBlacklistCurrentTest(false);
        QTestResult::setCurrentTestFunction("cleanupTestCase");
        if (m_cleanupTestCaseMethod.isValid())
            m_cleanupTestCaseMethod.invoke(testObject, Qt::DirectConnection);
        QTestResult::finishedCurrentTestData();
        QTestResult::finishedCurrentTestDataCleanup();
    }
    QTestResult::finishedCurrentTestFunction();
    QTestResult::setCurrentTestFunction(nullptr);

    QSignalDumper::endDump();
}

#if defined(Q_OS_WIN)

// Helper class for resolving symbol names by dynamically loading "dbghelp.dll".
class DebugSymbolResolver
{
    Q_DISABLE_COPY_MOVE(DebugSymbolResolver)
public:
    struct Symbol {
        Symbol() : name(nullptr), address(0) {}

        const char *name; // Must be freed by caller.
        DWORD64 address;
    };

    explicit DebugSymbolResolver(HANDLE process);
    ~DebugSymbolResolver() { cleanup(); }

    bool isValid() const { return m_symFromAddr; }

    Symbol resolveSymbol(DWORD64 address) const;

private:
    // typedefs from DbgHelp.h/.dll
    struct DBGHELP_SYMBOL_INFO { // SYMBOL_INFO
        ULONG       SizeOfStruct;
        ULONG       TypeIndex;        // Type Index of symbol
        ULONG64     Reserved[2];
        ULONG       Index;
        ULONG       Size;
        ULONG64     ModBase;          // Base Address of module comtaining this symbol
        ULONG       Flags;
        ULONG64     Value;            // Value of symbol, ValuePresent should be 1
        ULONG64     Address;          // Address of symbol including base address of module
        ULONG       Register;         // register holding value or pointer to value
        ULONG       Scope;            // scope of the symbol
        ULONG       Tag;              // pdb classification
        ULONG       NameLen;          // Actual length of name
        ULONG       MaxNameLen;
        CHAR        Name[1];          // Name of symbol
    };

    typedef BOOL (__stdcall *SymInitializeType)(HANDLE, PCSTR, BOOL);
    typedef BOOL (__stdcall *SymFromAddrType)(HANDLE, DWORD64, PDWORD64, DBGHELP_SYMBOL_INFO *);

    void cleanup();

    const HANDLE m_process;
    HMODULE m_dbgHelpLib;
    SymFromAddrType m_symFromAddr;
};

void DebugSymbolResolver::cleanup()
{
    if (m_dbgHelpLib)
        FreeLibrary(m_dbgHelpLib);
    m_dbgHelpLib = 0;
    m_symFromAddr = nullptr;
}

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;
}

#endif // Q_OS_WIN

class FatalSignalHandler
{
public:
    FatalSignalHandler()
    {
#if defined(Q_OS_WIN)
#  if !defined(Q_CC_MINGW)
        _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);
#  endif
        SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX);
        SetUnhandledExceptionFilter(windowsFaultHandler);
#elif defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
        sigemptyset(&handledSignals);

        const int fatalSignals[] = {
             SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGPIPE, SIGTERM };

        struct sigaction act;
        memset(&act, 0, sizeof(act));
#  ifdef SA_SIGINFO
        act.sa_flags |= SA_SIGINFO;
        act.sa_sigaction = FatalSignalHandler::signal;
#  else
        act.sa_handler = FatalSignalHandler::signal;
#  endif

        // Remove the handler after it is invoked.
#  if !defined(Q_OS_INTEGRITY)
        act.sa_flags = SA_RESETHAND;
#  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 char alternate_stack[16 * 1024];
        stack_t stack;
        stack.ss_flags = 0;
        stack.ss_size = sizeof alternate_stack;
        stack.ss_sp = alternate_stack;
        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);

        // The destructor can only restore SIG_DFL, so only register for signals
        // that had default handling previously.
        const auto isDefaultHandler = [](const struct sigaction &old) {
#  ifdef SA_SIGINFO
            // void sa_sigaction(int, siginfo_t *, void *) is never the default:
            if (old.sa_flags & SA_SIGINFO)
                return false;
#  endif
            // Otherwise, the handler is void sa_handler(int) but may be
            // SIG_DFL (default action) or SIG_IGN (ignore signal):
            return old.sa_handler == SIG_DFL;
        };

        struct sigaction oldact;
        for (int signal : fatalSignals) {
            // Registering reveals the existing handler:
            if (sigaction(signal, &act, &oldact))
                continue; // Failed to set our handler; nothing to restore.
            if (isDefaultHandler(oldact))
                sigaddset(&handledSignals, signal);
            else // Restore non-default handler:
                sigaction(signal, &oldact, nullptr);
        }
#endif // defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
    }

    ~FatalSignalHandler()
    {
#if defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
        // Restore the default signal handler in place of ours.
        // If ours has been replaced, leave the replacement alone.
        struct sigaction act;
        memset(&act, 0, sizeof(act));
        act.sa_handler = SIG_DFL;

        auto isOurs = [](const struct sigaction &old) {
#  ifdef SA_SIGINFO
            return (old.sa_flags & SA_SIGINFO) && old.sa_sigaction == FatalSignalHandler::signal;
#  else
            return old.sa_handler == FatalSignalHandler::signal;
#  endif
        };
        struct sigaction action;

        for (int signum = 1; signum < 32; ++signum) {
            if (!sigismember(&handledSignals, signum))
                continue;
            if (sigaction(signum, nullptr, &action))
                continue; // Failed to query present handler

            if (isOurs(action))
                sigaction(signum, &act, nullptr);
        }
#endif
    }

private:
#if defined(Q_OS_WIN)
    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;
    }
#endif // defined(Q_OS_WIN)

#if defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
#  ifdef SA_SIGINFO
    static void signal(int signum, siginfo_t * /* info */, void * /* ucontext */)
#  else
    static void signal(int signum)
#endif
    {
        const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
        const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
        if (signum != SIGINT) {
            stackTrace();
            if (qEnvironmentVariableIsSet("QTEST_PAUSE_ON_CRASH")) {
                fprintf(stderr, "Pausing process %d for debugging\n", getpid());
                raise(SIGSTOP);
            }
        }
        qFatal("Received signal %d\n"
               "         Function time: %dms Total time: %dms",
               signum, msecsFunctionTime, msecsTotalTime);
#  if defined(Q_OS_INTEGRITY)
        {
            struct sigaction act;
            memset(&act, 0, sizeof(struct sigaction));
            act.sa_handler = SIG_DFL;
            sigaction(signum, &act, NULL);
        }
#  endif
    }

    sigset_t handledSignals;
#endif // defined(Q_OS_UNIX) && !defined(Q_OS_WASM)
};

} // namespace

static void initEnvironment()
{
    qputenv("QT_QTESTLIB_RUNNING", "1");
}

/*!
    Executes tests declared in \a testObject. In addition, the private slots
    \c{initTestCase()}, \c{cleanupTestCase()}, \c{init()} and \c{cleanup()}
    are executed if they exist. See \l{Creating a Test} for more details.

    Optionally, the command line arguments \a argc and \a argv can be provided.
    For a list of recognized arguments, read \l {Qt Test Command Line Arguments}.

    The following example will run all tests in \c MyTestObject:

    \snippet code/src_qtestlib_qtestcase.cpp 18

    This function returns 0 if no tests failed, or a value other than 0 if one
    or more tests failed or in case of unhandled exceptions.  (Skipped tests do
    not influence the return value.)

    For stand-alone test applications, the convenience macro \l QTEST_MAIN() can
    be used to declare a main() function that parses the command line arguments
    and executes the tests, avoiding the need to call this function explicitly.

    The return value from this function is also the exit code of the test
    application when the \l QTEST_MAIN() macro is used.

    For stand-alone test applications, this function should not be called more
    than once, as command-line options for logging test output to files and
    executing individual test functions will not behave correctly.

    Note: This function is not reentrant, only one test can run at a time. A
    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
    from the arguments in \c main(), it is likely of interest to use
    QTest::qExec(QObject *, const QStringList &) since it is Unicode safe.

    \sa QTEST_MAIN()
*/

int QTest::qExec(QObject *testObject, int argc, char **argv)
{
    qInit(testObject, argc, argv);
    int ret = qRun();
    qCleanup();
    return ret;
}

/*! \internal
 */
void QTest::qInit(QObject *testObject, int argc, char **argv)
{
    initEnvironment();
    QBenchmarkGlobalData::current = new QBenchmarkGlobalData;

#if defined(Q_OS_MACOS)
    // Don't restore saved window state for auto tests
    QTestPrivate::disableWindowRestore();

    // Disable App Nap which may cause tests to stall
    QTestPrivate::AppNapDisabler appNapDisabler;

    if (qApp && (qstrcmp(qApp->metaObject()->className(), "QApplication") == 0)) {
        IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
            kIOPMAssertionLevelOn, CFSTR("QtTest running tests"),
            &macPowerSavingDisabled);
    }
#endif

    QTestPrivate::parseBlackList();
    QTestResult::reset();

    QTEST_ASSERT(testObject);
    QTEST_ASSERT(!currentTestObject);
    currentTestObject = testObject;

    const QMetaObject *metaObject = testObject->metaObject();
    QTEST_ASSERT(metaObject);

    QTestResult::setCurrentTestObject(metaObject->className());
    if (argc > 0)
        QTestResult::setCurrentAppName(argv[0]);

    qtest_qParseArgs(argc, argv, false);

    QTestTable::globalTestTable();
    QTestLog::startLogging();
}

/*! \internal
 */
int QTest::qRun()
{
    QTEST_ASSERT(currentTestObject);

#if QT_CONFIG(valgrind)
    int callgrindChildExitCode = 0;
#endif

#ifndef QT_NO_EXCEPTIONS
    try {
#endif

#if QT_CONFIG(valgrind)
    if (QBenchmarkGlobalData::current->mode() == QBenchmarkGlobalData::CallgrindParentProcess) {
        if (Q_UNLIKELY(!qApp))
            qFatal("QtTest: -callgrind option is not available with QTEST_APPLESS_MAIN");

        const QStringList origAppArgs(QCoreApplication::arguments());
        if (!QBenchmarkValgrindUtils::rerunThroughCallgrind(origAppArgs, callgrindChildExitCode))
            return -1;

        QBenchmarkValgrindUtils::cleanup();

    } else
#endif
    {
        QScopedPointer<FatalSignalHandler> handler;
        if (!noCrashHandler)
            handler.reset(new FatalSignalHandler);

        bool seenBad = false;
        TestMethods::MetaMethods commandLineMethods;
        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)) {
                commandLineMethods.push_back(m);
            } else {
                fprintf(stderr, "Unknown test function: '%s'. Possible matches:\n",
                        tfB.constData());
                qPrintTestSlots(stderr, tfB.constData());
                QTestResult::setCurrentTestFunction(tfB.constData());
                QTestResult::addFailure(qPrintable(
                                            QLatin1String("Function not found: %1").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, commandLineMethods);
        test.invokeTests(currentTestObject);
    }

#ifndef QT_NO_EXCEPTIONS
    } catch (...) {
        QTestResult::addFailure("Caught unhandled exception", __FILE__, __LINE__);
        if (QTestResult::currentTestFunction()) {
            QTestResult::finishedCurrentTestFunction();
            QTestResult::setCurrentTestFunction(nullptr);
        }

        qCleanup();

        // Re-throw exception to make debugging easier
        throw;
        return 1;
    }
#endif

#if QT_CONFIG(valgrind)
    if (QBenchmarkGlobalData::current->mode() == QBenchmarkGlobalData::CallgrindParentProcess)
        return callgrindChildExitCode;
#endif
    // make sure our exit code is never going above 127
    // since that could wrap and indicate 0 test fails
    return qMin(QTestLog::failCount(), 127);
}

/*! \internal
 */
void QTest::qCleanup()
{
    currentTestObject = nullptr;

    QTestTable::clearGlobalTestTable();
    QTestLog::stopLogging();

    delete QBenchmarkGlobalData::current;
    QBenchmarkGlobalData::current = nullptr;

#if defined(Q_OS_MACOS)
    IOPMAssertionRelease(macPowerSavingDisabled);
#endif
}

/*!
  \overload
  \since 4.4

  Behaves identically to qExec(QObject *, int, char**) but takes a
  QStringList of \a arguments instead of a \c char** list.
 */
int QTest::qExec(QObject *testObject, const QStringList &arguments)
{
    const int argc = arguments.count();
    QVarLengthArray<char *> argv(argc);

    QList<QByteArray> args;
    args.reserve(argc);

    for (int i = 0; i < argc; ++i)
    {
        args.append(arguments.at(i).toLocal8Bit().constData());
        argv[i] = args.last().data();
    }

    return qExec(testObject, argc, argv.data());
}

/*! \internal
 */
void QTest::qFail(const char *statementStr, const char *file, int line)
{
    QTestResult::addFailure(statementStr, file, line);
}

/*! \internal
 */
bool QTest::qVerify(bool statement, const char *statementStr, const char *description,
                   const char *file, int line)
{
    return QTestResult::verify(statement, statementStr, description, file, line);
}

/*! \fn void QTest::qSkip(const char *message, const char *file, int line)
\internal
 */
void QTest::qSkip(const char *message, const char *file, int line)
{
    QTestResult::addSkip(message, file, line);
    QTestResult::setSkipCurrentTest(true);
}

/*! \fn bool QTest::qExpectFail(const char *dataIndex, const char *comment, TestFailMode mode, const char *file, int line)
\internal
 */
bool QTest::qExpectFail(const char *dataIndex, const char *comment,
                       QTest::TestFailMode mode, const char *file, int line)
{
    return QTestResult::expectFail(dataIndex, qstrdup(comment), mode, file, line);
}

/*! \internal
 */
void QTest::qWarn(const char *message, const char *file, int line)
{
    QTestLog::warn(message, file, line);
}

/*!
    Ignores messages created by qDebug(), qInfo() or qWarning(). If the \a message
    with the corresponding \a type is outputted, it will be removed from the
    test log. If the test finished and the \a message was not outputted,
    a test failure is appended to the test log.

    \b {Note:} Invoking this function will only ignore one message.
    If the message you want to ignore is outputted twice, you have to
    call ignoreMessage() twice, too.

    Example:
    \snippet code/src_qtestlib_qtestcase.cpp 19

    The example above tests that QDir::mkdir() outputs the right warning when invoked
    with an invalid file name.
*/
void QTest::ignoreMessage(QtMsgType type, const char *message)
{
    QTestLog::ignoreMessage(type, message);
}

#if QT_CONFIG(regularexpression)
/*!
    \overload

    Ignores messages created by qDebug(), qInfo() or qWarning(). If the message
    matching \a messagePattern
    with the corresponding \a type is outputted, it will be removed from the
    test log. If the test finished and the message was not outputted,
    a test failure is appended to the test log.

    \b {Note:} Invoking this function will only ignore one message.
    If the message you want to ignore is outputted twice, you have to
    call ignoreMessage() twice, too.

    \since 5.3
*/
void QTest::ignoreMessage(QtMsgType type, const QRegularExpression &messagePattern)
{
    QTestLog::ignoreMessage(type, messagePattern);
}
#endif // QT_CONFIG(regularexpression)

/*! \internal
 */

#ifdef Q_OS_WIN
static inline bool isWindowsBuildDirectory(const QString &dirName)
{
    return dirName.compare(QLatin1String("Debug"), Qt::CaseInsensitive) == 0
           || dirName.compare(QLatin1String("Release"), Qt::CaseInsensitive) == 0;
}
#endif

#if QT_CONFIG(temporaryfile)
/*!
    Extracts a directory from resources to disk. The content is extracted
    recursively to a temporary folder. The extracted content is removed
    automatically once the last reference to the return value goes out of scope.

    \a dirName is the name of the directory to extract from resources.

    Returns the temporary directory where the data was extracted or null in case of
    errors.
 */
QSharedPointer<QTemporaryDir> QTest::qExtractTestData(const QString &dirName)
{
      QSharedPointer<QTemporaryDir> result; // null until success, then == tempDir

      QSharedPointer<QTemporaryDir> tempDir = QSharedPointer<QTemporaryDir>::create();

      tempDir->setAutoRemove(true);

      if (!tempDir->isValid())
          return result;

      const QString dataPath = tempDir->path();
      const QString resourcePath = QLatin1Char(':') + dirName;
      const QFileInfo fileInfo(resourcePath);

      if (!fileInfo.isDir()) {
          qWarning("Resource path '%s' is not a directory.", qPrintable(resourcePath));
          return result;
      }

      QDirIterator it(resourcePath, QDirIterator::Subdirectories);
      if (!it.hasNext()) {
          qWarning("Resource directory '%s' is empty.", qPrintable(resourcePath));
          return result;
      }

      while (it.hasNext()) {
          it.next();

          QFileInfo fileInfo = it.fileInfo();

          if (!fileInfo.isDir()) {
              const QString destination = dataPath + QLatin1Char('/') + QStringView{fileInfo.filePath()}.mid(resourcePath.length());
              QFileInfo destinationFileInfo(destination);
              QDir().mkpath(destinationFileInfo.path());
              if (!QFile::copy(fileInfo.filePath(), destination)) {
                  qWarning("Failed to copy '%s'.", qPrintable(fileInfo.filePath()));
                  return result;
              }
              if (!QFile::setPermissions(destination, QFile::ReadUser | QFile::WriteUser | QFile::ReadGroup)) {
                  qWarning("Failed to set permissions on '%s'.", qPrintable(destination));
                  return result;
              }
          }
      }

      result = std::move(tempDir);

      return result;
}
#endif // QT_CONFIG(temporaryfile)

/*! \internal
 */

QString QTest::qFindTestData(const QString& base, const char *file, int line, const char *builddir,
                             const char *sourcedir)
{
    QString found;

    // Testdata priorities:

    //  1. relative to test binary.
    if (qApp) {
        QDir binDirectory(QCoreApplication::applicationDirPath());
        if (binDirectory.exists(base)) {
            found = binDirectory.absoluteFilePath(base);
        }
#ifdef Q_OS_WIN
        // Windows: The executable is typically located in one of the
        // 'Release' or 'Debug' directories.
        else if (isWindowsBuildDirectory(binDirectory.dirName())
                 && binDirectory.cdUp() && binDirectory.exists(base)) {
            found = binDirectory.absoluteFilePath(base);
        }
#endif // Q_OS_WIN
        else if (QTestLog::verboseLevel() >= 2) {
            const QString candidate = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + QLatin1Char('/') + base);
            QTestLog::info(qPrintable(
                QString::fromLatin1("testdata %1 not found relative to test binary [%2]; "
                                    "checking next location").arg(base, candidate)),
                file, line);
        }
    }

    //  2. installed path.
    if (found.isEmpty()) {
        const char *testObjectName = QTestResult::currentTestObjectName();
        if (testObjectName) {
            const QString testsPath = QLibraryInfo::path(QLibraryInfo::TestsPath);
            const QString candidate = QString::fromLatin1("%1/%2/%3")
                .arg(testsPath, QFile::decodeName(testObjectName).toLower(), base);
            if (QFileInfo::exists(candidate)) {
                found = candidate;
            } else if (QTestLog::verboseLevel() >= 2) {
                QTestLog::info(qPrintable(
                    QString::fromLatin1("testdata %1 not found in tests install path [%2]; "
                                        "checking next location")
                        .arg(base, QDir::toNativeSeparators(candidate))),
                    file, line);
            }
        }
    }

    //  3. relative to test source.
    if (found.isEmpty() && qstrncmp(file, ":/", 2) != 0) {
        // srcdir is the directory containing the calling source file.
        QFileInfo srcdir(QFileInfo(QFile::decodeName(file)).path());

        // If the srcdir is relative, that means it is relative to the current working
        // directory of the compiler at compile time, which should be passed in as `builddir'.
        if (!srcdir.isAbsolute() && builddir) {
            srcdir.setFile(QFile::decodeName(builddir) + QLatin1String("/") + srcdir.filePath());
        }

        const QString canonicalPath = srcdir.canonicalFilePath();
        const QString candidate = QString::fromLatin1("%1/%2").arg(canonicalPath, base);
        if (!canonicalPath.isEmpty() && QFileInfo::exists(candidate)) {
            found = candidate;
        } else if (QTestLog::verboseLevel() >= 2) {
            QTestLog::info(qPrintable(
                QString::fromLatin1("testdata %1 not found relative to source path [%2]")
                    .arg(base, QDir::toNativeSeparators(candidate))),
                file, line);
        }
    }

    // 4. Try resources
    if (found.isEmpty()) {
        const QString candidate = QString::fromLatin1(":/%1").arg(base);
        if (QFileInfo::exists(candidate)) {
            found = candidate;
        } else if (QTestLog::verboseLevel() >= 2) {
            QTestLog::info(qPrintable(
                QString::fromLatin1("testdata %1 not found in resources [%2]")
                    .arg(base, QDir::toNativeSeparators(candidate))),
                file, line);
        }
    }

    // 5. Try current directory
    if (found.isEmpty()) {
        const QString candidate = QDir::currentPath() + QLatin1Char('/') + base;
        if (QFileInfo::exists(candidate)) {
            found = candidate;
        } else if (QTestLog::verboseLevel() >= 2) {
            QTestLog::info(qPrintable(
                QString::fromLatin1("testdata %1 not found in current directory [%2]")
                    .arg(base, QDir::toNativeSeparators(candidate))),
                file, line);
        }
    }

    // 6. Try main source directory
    if (found.isEmpty()) {
        const QString candidate = QTest::mainSourcePath % QLatin1Char('/') % base;
        if (QFileInfo::exists(candidate)) {
            found = candidate;
        } else if (QTestLog::verboseLevel() >= 2) {
            QTestLog::info(qPrintable(
                QString::fromLatin1("testdata %1 not found in main source directory [%2]")
                    .arg(base, QDir::toNativeSeparators(candidate))),
                file, line);
        }
    }

    // 7. Try the supplied source directory
    if (found.isEmpty() && sourcedir) {
        const QString candidate = QFile::decodeName(sourcedir) % QLatin1Char('/') % base;
        if (QFileInfo::exists(candidate)) {
            found = candidate;
        } else if (QTestLog::verboseLevel() >= 2) {
            QTestLog::info(qPrintable(
                QString::fromLatin1("testdata %1 not found in supplied source directory [%2]")
                    .arg(base, QDir::toNativeSeparators(candidate))),
                file, line);
        }
    }


    if (found.isEmpty()) {
        QTest::qWarn(qPrintable(
            QString::fromLatin1("testdata %1 could not be located!").arg(base)),
            file, line);
    } else if (QTestLog::verboseLevel() >= 1) {
        QTestLog::info(qPrintable(
            QString::fromLatin1("testdata %1 was located at %2").arg(base, QDir::toNativeSeparators(found))),
            file, line);
    }

    return found;
}

/*! \internal
 */
QString QTest::qFindTestData(const char *base, const char *file, int line, const char *builddir,
                             const char *sourcedir)
{
    return qFindTestData(QFile::decodeName(base), file, line, builddir, sourcedir);
}

/*! \internal
 */
void *QTest::qData(const char *tagName, int typeId)
{
    return fetchData(QTestResult::currentTestData(), tagName, typeId);
}

/*! \internal
 */
void *QTest::qGlobalData(const char *tagName, int typeId)
{
    return fetchData(QTestResult::currentGlobalTestData(), tagName, typeId);
}

/*! \internal
 */
void *QTest::qElementData(const char *tagName, int metaTypeId)
{
    QTEST_ASSERT(tagName);
    QTestData *data = QTestResult::currentTestData();
    QTEST_ASSERT(data);
    QTEST_ASSERT(data->parent());

    int idx = data->parent()->indexOf(tagName);
    QTEST_ASSERT(idx != -1);
    QTEST_ASSERT(data->parent()->elementTypeId(idx) == metaTypeId);

    return data->data(data->parent()->indexOf(tagName));
}

/*! \internal
 */
void QTest::addColumnInternal(int id, const char *name)
{
    QTestTable *tbl = QTestTable::currentTestTable();
    QTEST_ASSERT_X(tbl, "QTest::addColumn()", "Cannot add testdata outside of a _data slot.");

    tbl->addColumn(id, 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.

    Example:
    \snippet code/src_qtestlib_qtestcase.cpp 20

    \b {Note:} This macro can only be used in a test's data function
    that is invoked by the test framework.

    See \l {Chapter 2: Data Driven Testing}{Data Driven Testing} for
    a more extensive example.

    \sa 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.");

    return *tbl->newData(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.

    The formatted string will appear as the name of this test data in the test output.

    Returns a QTestData reference that can be used to stream in data.

    Example:
    \snippet code/src_qtestlib_qtestcase.cpp addRow

    \b {Note:} This function can only be used in a test's data function
    that is invoked by the test framework.

    See \l {Chapter 2: Data Driven Testing}{Data Driven Testing} for
    a more extensive example.

    \sa 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.");

    char buf[1024];

    va_list va;
    va_start(va, format);
    // we don't care about failures, we accept truncation, as well as trailing garbage.
    // Names with more than 1K characters are nonsense, anyway.
    (void)qvsnprintf(buf, sizeof buf, format, va);
    buf[sizeof buf - 1] = '\0';
    va_end(va);

    return *tbl->newData(buf);
}

/*! \fn template <typename T> void QTest::addColumn(const char *name, T *dummy = 0)

    Adds a column with type \c{T} to the current test data.
    \a name is the name of the column. \a dummy is a workaround
    for buggy compilers and can be ignored.

    To populate the column with values, newRow() can be used. Use
    \l QFETCH() to fetch the data in the actual test.

    Example:
    \snippet code/src_qtestlib_qtestcase.cpp 21

    To add custom types to the testdata, the type must be registered with
    QMetaType via \l Q_DECLARE_METATYPE().

    \b {Note:} This macro can only be used in a test's data function
    that is invoked by the test framework.

    See \l {Chapter 2: Data Driven Testing}{Data Driven Testing} for
    a more extensive example.

    \sa QTest::newRow(), QFETCH(), QMetaType
*/

/*!
    Returns the name of the binary that is currently executed.
*/
const char *QTest::currentAppName()
{
    return QTestResult::currentAppName();
}

/*!
    Returns the name of the test function that is currently executed.

    Example:

    \snippet code/src_qtestlib_qtestcase.cpp 22
*/
const char *QTest::currentTestFunction()
{
    return QTestResult::currentTestFunction();
}

/*!
    Returns the name of the current test data. If the test doesn't
    have any assigned testdata, the function returns 0.
*/
const char *QTest::currentDataTag()
{
    return QTestResult::currentDataTag();
}

/*!
    Returns \c true if the current test function failed, otherwise false.
*/
bool QTest::currentTestFailed()
{
    return QTestResult::currentTestFailed();
}

/*! \internal
 */
QObject *QTest::testObject()
{
    return currentTestObject;
}

/*! \internal
 */
void QTest::setMainSourcePath(const char *file, const char *builddir)
{
    QString mainSourceFile = QFile::decodeName(file);
    QFileInfo fi;
    if (builddir)
        fi.setFile(QDir(QFile::decodeName(builddir)), mainSourceFile);
    else
        fi.setFile(mainSourceFile);
    QTest::mainSourcePath = fi.absolutePath();
}

/*! \internal
    This function is called by various specializations of QTest::qCompare
    to decide whether to report a failure and to produce verbose test output.

    The failureMsg parameter can be null, in which case a default message
    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.
 */
bool QTest::compare_helper(bool success, const char *failureMsg,
                           char *val1, char *val2,
                           const char *actual, const char *expected,
                           const char *file, int line)
{
    return QTestResult::compare(success, failureMsg, val1, val2, actual, expected, file, line);
}

template <typename T>
static bool floatingCompare(const T &actual, const T &expected)
{
    switch (qFpClassify(expected))
    {
    case FP_INFINITE:
        return (expected < 0) == (actual < 0) && qFpClassify(actual) == FP_INFINITE;
    case FP_NAN:
        return qFpClassify(actual) == FP_NAN;
    default:
        if (!qFuzzyIsNull(expected))
            return qFuzzyCompare(actual, expected);
        Q_FALLTHROUGH();
    case FP_SUBNORMAL: // subnormal is always fuzzily null
    case FP_ZERO:
        return qFuzzyIsNull(actual);
    }
}

/*! \fn bool QTest::qCompare(const qfloat16 &t1, const qfloat16 &t2, const char *actual, const char *expected, const char *file, int line)
    \internal
 */
bool QTest::qCompare(qfloat16 const &t1, qfloat16 const &t2, const char *actual, const char *expected,
                     const char *file, int line)
{
    return compare_helper(floatingCompare(t1, t2),
                          "Compared qfloat16s are not the same (fuzzy compare)",
                          toString(t1), toString(t2), 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)
    \internal
 */
bool QTest::qCompare(float const &t1, float const &t2, const char *actual, const char *expected,
                    const char *file, int line)
{
    return QTestResult::compare(floatingCompare(t1, t2),
                                "Compared floats are not the same (fuzzy compare)",
                                t1, t2, actual, expected, file, line);
}

/*! \fn bool QTest::qCompare(const double &t1, const double &t2, const char *actual, const char *expected, const char *file, int line)
    \internal
 */
bool QTest::qCompare(double const &t1, double const &t2, const char *actual, const char *expected,
                    const char *file, int line)
{
    return QTestResult::compare(floatingCompare(t1, t2),