1// Copyright (C) 2021 The Qt Company Ltd.
2// Copyright (C) 2020 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#ifndef QTEST_H
6#define QTEST_H
7
8#if 0
9#pragma qt_class(QTest)
10#endif
11
12#include <QtTest/qttestglobal.h>
13#include <QtTest/qtestcase.h>
14#include <QtTest/qtestdata.h>
15#include <QtTest/qbenchmark.h>
16
17#include <QtCore/qbitarray.h>
18#include <QtCore/qbytearray.h>
19#include <QtCore/qcborarray.h>
20#include <QtCore/qcborcommon.h>
21#include <QtCore/qcbormap.h>
22#include <QtCore/qcborvalue.h>
23#include <QtCore/qstring.h>
24#include <QtCore/qstringlist.h>
25#include <QtCore/qcborcommon.h>
26#include <QtCore/qdatetime.h>
27#if QT_CONFIG(itemmodel)
28#include <QtCore/qabstractitemmodel.h>
29#endif
30#include <QtCore/qobject.h>
31#include <QtCore/qvariant.h>
32#include <QtCore/qurl.h>
33#include <QtCore/quuid.h>
34
35#if defined(TESTCASE_LOWDPI)
36#include <QtCore/qcoreapplication.h>
37#endif
38
39#include <QtCore/qpoint.h>
40#include <QtCore/qsize.h>
41#include <QtCore/qrect.h>
42
43#include <initializer_list>
44#include <memory>
45
46QT_BEGIN_NAMESPACE
47
48namespace QTest
49{
50
51template <> inline char *toString(const QStringView &str)
52{
53 return QTest::toPrettyUnicode(string: str);
54}
55
56template<> inline char *toString(const QString &str)
57{
58 return toString(str: QStringView(str));
59}
60
61template<> inline char *toString(const QLatin1StringView &str)
62{
63 return toString(str: QString(str));
64}
65
66template<> inline char *toString(const QByteArray &ba)
67{
68 return QTest::toPrettyCString(unicode: ba.constData(), length: ba.size());
69}
70
71template<> inline char *toString(const QBitArray &ba)
72{
73 qsizetype size = ba.size();
74 char *str = new char[size + 1];
75 for (qsizetype i = 0; i < size; ++i)
76 str[i] = "01"[ba.testBit(i)];
77 str[size] = '\0';
78 return str;
79}
80
81#if QT_CONFIG(datestring)
82template<> inline char *toString(const QTime &time)
83{
84 return time.isValid()
85 ? qstrdup(qPrintable(time.toString(u"hh:mm:ss.zzz")))
86 : qstrdup("Invalid QTime");
87}
88
89template<> inline char *toString(const QDate &date)
90{
91 return date.isValid()
92 ? qstrdup(qPrintable(date.toString(u"yyyy/MM/dd")))
93 : qstrdup("Invalid QDate");
94}
95
96template<> inline char *toString(const QDateTime &dateTime)
97{
98 return dateTime.isValid()
99 ? qstrdup(qPrintable(dateTime.toString(u"yyyy/MM/dd hh:mm:ss.zzz[t]")))
100 : qstrdup("Invalid QDateTime");
101}
102#endif // datestring
103
104template<> inline char *toString(const QCborError &c)
105{
106 // use the Q_ENUM formatting
107 return toString(t: c.c);
108}
109
110template<> inline char *toString(const QChar &c)
111{
112 const ushort uc = c.unicode();
113 if (uc < 128) {
114 char msg[32] = {'\0'};
115 qsnprintf(str: msg, n: sizeof(msg), fmt: "QChar: '%c' (0x%x)", char(uc), unsigned(uc));
116 return qstrdup(msg);
117 }
118 return qstrdup(qPrintable(QString::fromLatin1("QChar: '%1' (0x%2)").arg(c).arg(QString::number(static_cast<int>(c.unicode()), 16))));
119}
120
121#if QT_CONFIG(itemmodel)
122template<> inline char *toString(const QModelIndex &idx)
123{
124 char msg[128];
125 qsnprintf(str: msg, n: sizeof(msg), fmt: "QModelIndex(%d,%d,%p,%p)", idx.row(), idx.column(), idx.internalPointer(), idx.model());
126 return qstrdup(msg);
127}
128#endif
129
130template<> inline char *toString(const QPoint &p)
131{
132 char msg[128] = {'\0'};
133 qsnprintf(str: msg, n: sizeof(msg), fmt: "QPoint(%d,%d)", p.x(), p.y());
134 return qstrdup(msg);
135}
136
137template<> inline char *toString(const QSize &s)
138{
139 char msg[128] = {'\0'};
140 qsnprintf(str: msg, n: sizeof(msg), fmt: "QSize(%dx%d)", s.width(), s.height());
141 return qstrdup(msg);
142}
143
144template<> inline char *toString(const QRect &s)
145{
146 char msg[256] = {'\0'};
147 qsnprintf(str: msg, n: sizeof(msg), fmt: "QRect(%d,%d %dx%d) (bottomright %d,%d)",
148 s.left(), s.top(), s.width(), s.height(), s.right(), s.bottom());
149 return qstrdup(msg);
150}
151
152template<> inline char *toString(const QPointF &p)
153{
154 char msg[64] = {'\0'};
155 qsnprintf(str: msg, n: sizeof(msg), fmt: "QPointF(%g,%g)", p.x(), p.y());
156 return qstrdup(msg);
157}
158
159template<> inline char *toString(const QSizeF &s)
160{
161 char msg[64] = {'\0'};
162 qsnprintf(str: msg, n: sizeof(msg), fmt: "QSizeF(%gx%g)", s.width(), s.height());
163 return qstrdup(msg);
164}
165
166template<> inline char *toString(const QRectF &s)
167{
168 char msg[256] = {'\0'};
169 qsnprintf(str: msg, n: sizeof(msg), fmt: "QRectF(%g,%g %gx%g) (bottomright %g,%g)",
170 s.left(), s.top(), s.width(), s.height(), s.right(), s.bottom());
171 return qstrdup(msg);
172}
173
174template<> inline char *toString(const QUrl &uri)
175{
176 if (!uri.isValid())
177 return qstrdup(qPrintable(QLatin1StringView("Invalid URL: ") + uri.errorString()));
178 return qstrdup(uri.toEncoded().constData());
179}
180
181template <> inline char *toString(const QUuid &uuid)
182{
183 return qstrdup(uuid.toByteArray().constData());
184}
185
186template<> inline char *toString(const QVariant &v)
187{
188 QByteArray vstring("QVariant(");
189 if (v.isValid()) {
190 QByteArray type(v.typeName());
191 if (type.isEmpty()) {
192 type = QByteArray::number(v.userType());
193 }
194 vstring.append(a: type);
195 if (!v.isNull()) {
196 vstring.append(c: ',');
197 if (v.canConvert<QString>()) {
198 vstring.append(a: v.toString().toLocal8Bit());
199 }
200 else {
201 vstring.append(s: "<value not representable as string>");
202 }
203 }
204 }
205 vstring.append(c: ')');
206
207 return qstrdup(vstring.constData());
208}
209
210template<> inline char *toString(const QPartialOrdering &o)
211{
212 if (o == QPartialOrdering::Less)
213 return qstrdup("Less");
214 if (o == QPartialOrdering::Equivalent)
215 return qstrdup("Equivalent");
216 if (o == QPartialOrdering::Greater)
217 return qstrdup("Greater");
218 if (o == QPartialOrdering::Unordered)
219 return qstrdup("Unordered");
220 return qstrdup("<invalid>");
221}
222
223namespace Internal {
224struct QCborValueFormatter
225{
226 enum { BufferLen = 256 };
227 static char *formatSimpleType(QCborSimpleType st)
228 {
229 char *buf = new char[BufferLen];
230 qsnprintf(str: buf, n: BufferLen, fmt: "QCborValue(QCborSimpleType(%d))", int(st));
231 return buf;
232 }
233
234 static char *formatTag(QCborTag tag, const QCborValue &taggedValue)
235 {
236 QScopedArrayPointer<char> hold(format(v: taggedValue));
237 char *buf = new char[BufferLen];
238 qsnprintf(str: buf, n: BufferLen, fmt: "QCborValue(QCborTag(%llu), %s)", tag, hold.get());
239 return buf;
240 }
241
242 static char *innerFormat(QCborValue::Type t, const char *str)
243 {
244 static const QMetaEnum typeEnum = []() {
245 int idx = QCborValue::staticMetaObject.indexOfEnumerator(name: "Type");
246 return QCborValue::staticMetaObject.enumerator(index: idx);
247 }();
248
249 char *buf = new char[BufferLen];
250 const char *typeName = typeEnum.valueToKey(value: t);
251 if (typeName)
252 qsnprintf(str: buf, n: BufferLen, fmt: "QCborValue(%s, %s)", typeName, str);
253 else
254 qsnprintf(str: buf, n: BufferLen, fmt: "QCborValue(<unknown type 0x%02x>)", t);
255 return buf;
256 }
257
258 template<typename T> static char *format(QCborValue::Type type, const T &t)
259 {
260 QScopedArrayPointer<char> hold(QTest::toString(t));
261 return innerFormat(t: type, str: hold.get());
262 }
263
264 static char *format(const QCborValue &v)
265 {
266 switch (v.type()) {
267 case QCborValue::Integer:
268 return format(type: v.type(), t: v.toInteger());
269 case QCborValue::ByteArray:
270 return format(type: v.type(), t: v.toByteArray());
271 case QCborValue::String:
272 return format(type: v.type(), t: v.toString());
273 case QCborValue::Array:
274 return innerFormat(t: v.type(), str: QScopedArrayPointer<char>(format(a: v.toArray())).get());
275 case QCborValue::Map:
276 return innerFormat(t: v.type(), str: QScopedArrayPointer<char>(format(m: v.toMap())).get());
277 case QCborValue::Tag:
278 return formatTag(tag: v.tag(), taggedValue: v.taggedValue());
279 case QCborValue::SimpleType:
280 break;
281 case QCborValue::True:
282 return qstrdup("QCborValue(true)");
283 case QCborValue::False:
284 return qstrdup("QCborValue(false)");
285 case QCborValue::Null:
286 return qstrdup("QCborValue(nullptr)");
287 case QCborValue::Undefined:
288 return qstrdup("QCborValue()");
289 case QCborValue::Double:
290 return format(type: v.type(), t: v.toDouble());
291 case QCborValue::DateTime:
292 case QCborValue::Url:
293 case QCborValue::RegularExpression:
294 return format(type: v.type(), t: v.taggedValue().toString());
295 case QCborValue::Uuid:
296 return format(type: v.type(), t: v.toUuid());
297 case QCborValue::Invalid:
298 return qstrdup("QCborValue(<invalid>)");
299 }
300
301 if (v.isSimpleType())
302 return formatSimpleType(st: v.toSimpleType());
303 return innerFormat(t: v.type(), str: "");
304 }
305
306 static char *format(const QCborArray &a)
307 {
308 QByteArray out(1, '[');
309 const char *comma = "";
310 for (QCborValueConstRef v : a) {
311 QScopedArrayPointer<char> s(format(v));
312 out += comma;
313 out += s.get();
314 comma = ", ";
315 }
316 out += ']';
317 return qstrdup(out.constData());
318 }
319
320 static char *format(const QCborMap &m)
321 {
322 QByteArray out(1, '{');
323 const char *comma = "";
324 for (auto pair : m) {
325 QScopedArrayPointer<char> key(format(v: pair.first));
326 QScopedArrayPointer<char> value(format(v: pair.second));
327 out += comma;
328 out += key.get();
329 out += ": ";
330 out += value.get();
331 comma = ", ";
332 }
333 out += '}';
334 return qstrdup(out.constData());
335 }
336};
337}
338
339template<> inline char *toString(const QCborValue &v)
340{
341 return Internal::QCborValueFormatter::format(v);
342}
343
344template<> inline char *toString(const QCborValueRef &v)
345{
346 return toString(v: QCborValue(v));
347}
348
349template<> inline char *toString(const QCborArray &a)
350{
351 return Internal::QCborValueFormatter::format(a);
352}
353
354template<> inline char *toString(const QCborMap &m)
355{
356 return Internal::QCborValueFormatter::format(m);
357}
358
359template <typename Rep, typename Period> char *toString(std::chrono::duration<Rep, Period> dur)
360{
361 QString r;
362 QDebug d(&r);
363 d.nospace() << qSetRealNumberPrecision(precision: 9) << dur;
364 if constexpr (Period::num != 1 || Period::den != 1) {
365 // include the equivalent value in seconds, in parentheses
366 using namespace std::chrono;
367 d << " (" << duration_cast<duration<qreal>>(dur).count() << "s)";
368 }
369 return qstrdup(std::move(r).toUtf8().constData());
370}
371
372template <typename T1, typename T2>
373inline char *toString(const std::pair<T1, T2> &pair)
374{
375 const QScopedArrayPointer<char> first(toString(pair.first));
376 const QScopedArrayPointer<char> second(toString(pair.second));
377 return formatString(prefix: "std::pair(", suffix: ")", numArguments: 2, first.data(), second.data());
378}
379
380template <typename Tuple, int... I>
381inline char *toString(const Tuple & tuple, QtPrivate::IndexesList<I...>) {
382 using UP = std::unique_ptr<char[]>;
383 // Generate a table of N + 1 elements where N is the number of
384 // elements in the tuple.
385 // The last element is needed to support the empty tuple use case.
386 const UP data[] = {
387 UP(toString(std::get<I>(tuple)))..., UP{}
388 };
389 return formatString("std::tuple(", ")", sizeof...(I), data[I].get()...);
390}
391
392template <class... Types>
393inline char *toString(const std::tuple<Types...> &tuple)
394{
395 static const std::size_t params_count = sizeof...(Types);
396 return toString(tuple, typename QtPrivate::Indexes<params_count>::Value());
397}
398
399inline char *toString(std::nullptr_t)
400{
401 return toString(QStringLiteral("nullptr"));
402}
403
404template<>
405inline bool qCompare(QString const &t1, QLatin1StringView const &t2, const char *actual,
406 const char *expected, const char *file, int line)
407{
408 return qCompare(t1, t2: QString(t2), actual, expected, file, line);
409}
410template<>
411inline bool qCompare(QLatin1StringView const &t1, QString const &t2, const char *actual,
412 const char *expected, const char *file, int line)
413{
414 return qCompare(t1: QString(t1), t2, actual, expected, file, line);
415}
416
417// Compare sequences of equal size
418template <typename ActualIterator, typename ExpectedIterator>
419bool _q_compareSequence(ActualIterator actualIt, ActualIterator actualEnd,
420 ExpectedIterator expectedBegin, ExpectedIterator expectedEnd,
421 const char *actual, const char *expected,
422 const char *file, int line)
423{
424 char msg[1024];
425 msg[0] = '\0';
426
427 const qsizetype actualSize = actualEnd - actualIt;
428 const qsizetype expectedSize = expectedEnd - expectedBegin;
429 bool isOk = actualSize == expectedSize;
430
431 if (!isOk) {
432 qsnprintf(str: msg, n: sizeof(msg), fmt: "Compared lists have different sizes.\n"
433 " Actual (%s) size: %zd\n"
434 " Expected (%s) size: %zd", actual, actualSize,
435 expected, expectedSize);
436 }
437
438 for (auto expectedIt = expectedBegin; isOk && expectedIt < expectedEnd; ++actualIt, ++expectedIt) {
439 if (!(*actualIt == *expectedIt)) {
440 const qsizetype i = qsizetype(expectedIt - expectedBegin);
441 char *val1 = toString(*actualIt);
442 char *val2 = toString(*expectedIt);
443
444 qsnprintf(str: msg, n: sizeof(msg), fmt: "Compared lists differ at index %zd.\n"
445 " Actual (%s): %s\n"
446 " Expected (%s): %s", i, actual, val1 ? val1 : "<null>",
447 expected, val2 ? val2 : "<null>");
448 isOk = false;
449
450 delete [] val1;
451 delete [] val2;
452 }
453 }
454 return compare_helper(success: isOk, failureMsg: msg, actual, expected, file, line);
455}
456
457namespace Internal {
458
459#if defined(TESTCASE_LOWDPI)
460void disableHighDpi()
461{
462 qputenv("QT_ENABLE_HIGHDPI_SCALING", "0");
463}
464Q_CONSTRUCTOR_FUNCTION(disableHighDpi);
465#endif
466
467} // namespace Internal
468
469template <typename T>
470inline bool qCompare(QList<T> const &t1, QList<T> const &t2, const char *actual, const char *expected,
471 const char *file, int line)
472{
473 return _q_compareSequence(t1.cbegin(), t1.cend(), t2.cbegin(), t2.cend(),
474 actual, expected, file, line);
475}
476
477template <typename T, int N>
478bool qCompare(QList<T> const &t1, std::initializer_list<T> t2,
479 const char *actual, const char *expected,
480 const char *file, int line)
481{
482 return _q_compareSequence(t1.cbegin(), t1.cend(), t2.cbegin(), t2.cend(),
483 actual, expected, file, line);
484}
485
486// Compare QList against array
487template <typename T, int N>
488bool qCompare(QList<T> const &t1, const T (& t2)[N],
489 const char *actual, const char *expected,
490 const char *file, int line)
491{
492 return _q_compareSequence(t1.cbegin(), t1.cend(), t2, t2 + N,
493 actual, expected, file, line);
494}
495
496template <typename T>
497inline bool qCompare(QFlags<T> const &t1, T const &t2, const char *actual, const char *expected,
498 const char *file, int line)
499{
500 using Int = typename QFlags<T>::Int;
501 return qCompare(Int(t1), Int(t2), actual, expected, file, line);
502}
503
504template <typename T>
505inline bool qCompare(QFlags<T> const &t1, int const &t2, const char *actual, const char *expected,
506 const char *file, int line)
507{
508 using Int = typename QFlags<T>::Int;
509 return qCompare(Int(t1), Int(t2), actual, expected, file, line);
510}
511
512template<>
513inline bool qCompare(qint64 const &t1, qint32 const &t2, const char *actual,
514 const char *expected, const char *file, int line)
515{
516 return qCompare(t1, t2: static_cast<qint64>(t2), actual, expected, file, line);
517}
518
519template<>
520inline bool qCompare(qint64 const &t1, quint32 const &t2, const char *actual,
521 const char *expected, const char *file, int line)
522{
523 return qCompare(t1, t2: static_cast<qint64>(t2), actual, expected, file, line);
524}
525
526template<>
527inline bool qCompare(quint64 const &t1, quint32 const &t2, const char *actual,
528 const char *expected, const char *file, int line)
529{
530 return qCompare(t1, t2: static_cast<quint64>(t2), actual, expected, file, line);
531}
532
533template<>
534inline bool qCompare(qint32 const &t1, qint64 const &t2, const char *actual,
535 const char *expected, const char *file, int line)
536{
537 return qCompare(t1: static_cast<qint64>(t1), t2, actual, expected, file, line);
538}
539
540template<>
541inline bool qCompare(quint32 const &t1, qint64 const &t2, const char *actual,
542 const char *expected, const char *file, int line)
543{
544 return qCompare(t1: static_cast<qint64>(t1), t2, actual, expected, file, line);
545}
546
547template<>
548inline bool qCompare(quint32 const &t1, quint64 const &t2, const char *actual,
549 const char *expected, const char *file, int line)
550{
551 return qCompare(t1: static_cast<quint64>(t1), t2, actual, expected, file, line);
552}
553namespace Internal {
554
555template <typename T>
556class HasInitMain // SFINAE test for the presence of initMain()
557{
558private:
559 using YesType = char[1];
560 using NoType = char[2];
561
562 template <typename C> static YesType& test( decltype(&C::initMain) ) ;
563 template <typename C> static NoType& test(...);
564
565public:
566 enum { value = sizeof(test<T>(nullptr)) == sizeof(YesType) };
567};
568
569template<typename T>
570typename std::enable_if<HasInitMain<T>::value, void>::type callInitMain()
571{
572 T::initMain();
573}
574
575template<typename T>
576typename std::enable_if<!HasInitMain<T>::value, void>::type callInitMain()
577{
578}
579
580} // namespace Internal
581
582} // namespace QTest
583QT_END_NAMESPACE
584
585#ifdef QT_TESTCASE_BUILDDIR
586# define QTEST_SET_MAIN_SOURCE_PATH QTest::setMainSourcePath(__FILE__, QT_TESTCASE_BUILDDIR);
587#else
588# define QTEST_SET_MAIN_SOURCE_PATH QTest::setMainSourcePath(__FILE__);
589#endif
590
591// Hooks for coverage-testing of QTestLib itself:
592#if QT_CONFIG(testlib_selfcover) && defined(__COVERAGESCANNER__)
593struct QtCoverageScanner
594{
595 QtCoverageScanner(const char *name)
596 {
597 __coveragescanner_clear();
598 __coveragescanner_testname(name);
599 }
600 ~QtCoverageScanner()
601 {
602 __coveragescanner_save();
603 __coveragescanner_testname("");
604 }
605};
606#define TESTLIB_SELFCOVERAGE_START(name) QtCoverageScanner _qtCoverageScanner(name);
607#else
608#define TESTLIB_SELFCOVERAGE_START(name)
609#endif
610
611#if !defined(QTEST_BATCH_TESTS)
612// Internal (but used by some testlib selftests to hack argc and argv).
613// Tests should normally implement initMain() if they have set-up to do before
614// instantiating the test class.
615#define QTEST_MAIN_WRAPPER(TestObject, ...) \
616int main(int argc, char *argv[]) \
617{ \
618 TESTLIB_SELFCOVERAGE_START(#TestObject) \
619 QT_PREPEND_NAMESPACE(QTest::Internal::callInitMain)<TestObject>(); \
620 __VA_ARGS__ \
621 TestObject tc; \
622 QTEST_SET_MAIN_SOURCE_PATH \
623 return QTest::qExec(&tc, argc, argv); \
624}
625#else
626// BATCHED_TEST_NAME is defined for each test in a batch in cmake. Some odd
627// targets, like snippets, don't define it though. Play safe by providing a
628// default value.
629#if !defined(BATCHED_TEST_NAME)
630#define BATCHED_TEST_NAME "other"
631#endif
632#define QTEST_MAIN_WRAPPER(TestObject, ...) \
633\
634void qRegister##TestObject() \
635{ \
636 auto runTest = [](int argc, char** argv) -> int { \
637 TESTLIB_SELFCOVERAGE_START(TestObject) \
638 QT_PREPEND_NAMESPACE(QTest::Internal::callInitMain)<TestObject>(); \
639 __VA_ARGS__ \
640 TestObject tc; \
641 QTEST_SET_MAIN_SOURCE_PATH \
642 return QTest::qExec(&tc, argc, argv); \
643 }; \
644 QTest::qRegisterTestCase(QStringLiteral(BATCHED_TEST_NAME), runTest); \
645} \
646\
647Q_CONSTRUCTOR_FUNCTION(qRegister##TestObject)
648#endif
649
650// For when you don't even want a QApplication:
651#define QTEST_APPLESS_MAIN(TestObject) QTEST_MAIN_WRAPPER(TestObject)
652
653#include <QtTest/qtestsystem.h>
654
655#if defined(QT_NETWORK_LIB)
656# include <QtTest/qtest_network.h>
657#endif
658
659// Internal
660#define QTEST_QAPP_SETUP(klaz) \
661 klaz app(argc, argv); \
662 app.setAttribute(Qt::AA_Use96Dpi, true);
663
664#if defined(QT_WIDGETS_LIB)
665# include <QtTest/qtest_widgets.h>
666# ifdef QT_KEYPAD_NAVIGATION
667# define QTEST_DISABLE_KEYPAD_NAVIGATION QApplication::setNavigationMode(Qt::NavigationModeNone);
668# else
669# define QTEST_DISABLE_KEYPAD_NAVIGATION
670# endif
671// Internal
672# define QTEST_MAIN_SETUP() QTEST_QAPP_SETUP(QApplication) QTEST_DISABLE_KEYPAD_NAVIGATION
673#elif defined(QT_GUI_LIB)
674# include <QtTest/qtest_gui.h>
675// Internal
676# define QTEST_MAIN_SETUP() QTEST_QAPP_SETUP(QGuiApplication)
677#else
678// Internal
679# define QTEST_MAIN_SETUP() QTEST_QAPP_SETUP(QCoreApplication)
680#endif // QT_GUI_LIB
681
682// For most tests:
683#define QTEST_MAIN(TestObject) QTEST_MAIN_WRAPPER(TestObject, QTEST_MAIN_SETUP())
684
685// For command-line tests
686#define QTEST_GUILESS_MAIN(TestObject) \
687 QTEST_MAIN_WRAPPER(TestObject, QTEST_QAPP_SETUP(QCoreApplication))
688
689#endif
690

source code of qtbase/src/testlib/qtest.h