summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@qt.io>2017-02-07 14:09:04 +0100
committerTor Arne Vestbø <tor.arne.vestbo@qt.io>2017-09-25 12:19:18 +0000
commit292cb12e024e63f17c501611e021b6f8da7d6dcc (patch)
tree20b607b50fae0a42bcc75e51d31bb114f20cc213
parent0dbede2b174508d5cc56e7c4a26abcaac996bc13 (diff)
testlib: Add qWaitFor to wait for predicate
Reduces duplication of logic and allows other primitives to be built on top. Change-Id: Ia100014cfb0c09ac2f47c3a156d0c76f0fddafa8 Reviewed-by: Gatis Paeglis <gatis.paeglis@qt.io>
-rw-r--r--src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp8
-rw-r--r--src/testlib/qtestcase.h2
-rw-r--r--src/testlib/qtestcase.qdoc15
-rw-r--r--src/testlib/qtestsystem.h67
4 files changed, 64 insertions, 28 deletions
diff --git a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp
index 01ee8102f4..990b7a38d7 100644
--- a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp
+++ b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp
@@ -306,5 +306,13 @@ QTest::keyClick(myWindow, Qt::Key_Escape);
QTest::keyClick(myWindow, Qt::Key_Escape, Qt::ShiftModifier, 200);
//! [29]
+//! [30]
+MyObject obj;
+obj.startup();
+QTest::qWaitFor([&]() {
+ return obj.isReady();
+}, 3000);
+//! [30]
+
}
diff --git a/src/testlib/qtestcase.h b/src/testlib/qtestcase.h
index b738043cb7..2605325a94 100644
--- a/src/testlib/qtestcase.h
+++ b/src/testlib/qtestcase.h
@@ -147,6 +147,8 @@ do {\
} \
}
+// Ideally we'd use qWaitFor instead of QTRY_LOOP_IMPL, but due
+// to a compiler bug on MSVC < 2017 we can't (see QTBUG-59096)
#define QTRY_IMPL(expr, timeout)\
const int qt_test_step = 50; \
const int qt_test_timeoutValue = timeout; \
diff --git a/src/testlib/qtestcase.qdoc b/src/testlib/qtestcase.qdoc
index 2d1e27ec40..5b90419e28 100644
--- a/src/testlib/qtestcase.qdoc
+++ b/src/testlib/qtestcase.qdoc
@@ -1075,6 +1075,21 @@
\sa QTest::qSleep(), QSignalSpy::wait()
*/
+/*! \fn void QTest::qWaitFor(Functor predicate, int timeout)
+
+ Waits for \a timeout milliseconds or until the \a predicate returns true.
+
+ Returns \c true if the \a predicate returned true at any point, otherwise returns \c false.
+
+ Example:
+ \snippet code/src_qtestlib_qtestcase.cpp 30
+
+ The code above will wait for the object to become ready, for a
+ maximum of three seconds.
+
+ \since 5.10
+*/
+
/*! \fn bool QTest::qWaitForWindowExposed(QWindow *window, int timeout)
\since 5.0
diff --git a/src/testlib/qtestsystem.h b/src/testlib/qtestsystem.h
index f38a156936..04c9c574f7 100644
--- a/src/testlib/qtestsystem.h
+++ b/src/testlib/qtestsystem.h
@@ -54,41 +54,60 @@ QT_BEGIN_NAMESPACE
namespace QTest
{
- Q_DECL_UNUSED inline static void qWait(int ms)
+ template <typename Functor>
+ static Q_REQUIRED_RESULT bool qWaitFor(Functor predicate, int timeout = 5000)
{
- Q_ASSERT(QCoreApplication::instance());
+ // We should not spint the event loop in case the predicate is already true,
+ // otherwise we might send new events that invalidate the predicate.
+ if (predicate())
+ return true;
+
+ // qWait() is expected to spin the event loop, even when called with a small
+ // timeout like 1ms, so we we can't use a simple while-loop here based on
+ // the deadline timer not having timed out. Use do-while instead.
+
+ int remaining = timeout;
+ QDeadlineTimer deadline(remaining, Qt::PreciseTimer);
- QDeadlineTimer timer(ms, Qt::PreciseTimer);
- int remaining = ms;
do {
QCoreApplication::processEvents(QEventLoop::AllEvents, remaining);
- QCoreApplication::sendPostedEvents(Q_NULLPTR, QEvent::DeferredDelete);
- remaining = timer.remainingTime();
- if (remaining <= 0)
- break;
- QTest::qSleep(qMin(10, remaining));
- remaining = timer.remainingTime();
+ QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
+
+ remaining = deadline.remainingTime();
+ if (remaining > 0) {
+ QTest::qSleep(qMin(10, remaining));
+ remaining = deadline.remainingTime();
+ }
+
+ if (predicate())
+ return true;
+
+ remaining = deadline.remainingTime();
} while (remaining > 0);
+
+ return predicate(); // Last chance
+ }
+
+ Q_DECL_UNUSED inline static void qWait(int ms)
+ {
+ Q_ASSERT(QCoreApplication::instance());
+ auto unconditionalWait = []() { return false; };
+ bool timedOut = !qWaitFor(unconditionalWait, ms);
+ Q_UNUSED(timedOut);
}
#ifdef QT_GUI_LIB
inline static bool qWaitForWindowActive(QWindow *window, int timeout = 5000)
{
- QDeadlineTimer timer(timeout, Qt::PreciseTimer);
- int remaining = timeout;
- while (!window->isActive() && remaining > 0) {
- QCoreApplication::processEvents(QEventLoop::AllEvents, remaining);
- QCoreApplication::sendPostedEvents(Q_NULLPTR, QEvent::DeferredDelete);
- QTest::qSleep(10);
- remaining = timer.remainingTime();
- }
+ bool becameActive = qWaitFor([&]() { return window->isActive(); }, timeout);
+
// Try ensuring the platform window receives the real position.
// (i.e. that window->pos() reflects reality)
// isActive() ( == FocusIn in case of X) does not guarantee this. It seems some WMs randomly
// send the final ConfigureNotify (the one with the non-bogus 0,0 position) after the FocusIn.
// If we just let things go, every mapTo/FromGlobal call the tests perform directly after
// qWaitForWindowShown() will generate bogus results.
- if (window->isActive()) {
+ if (becameActive) {
int waitNo = 0; // 0, 0 might be a valid position after all, so do not wait for ever
while (window->position().isNull()) {
if (waitNo++ > timeout / 10)
@@ -101,15 +120,7 @@ namespace QTest
inline static bool qWaitForWindowExposed(QWindow *window, int timeout = 5000)
{
- QDeadlineTimer timer(timeout, Qt::PreciseTimer);
- int remaining = timeout;
- while (!window->isExposed() && remaining > 0) {
- QCoreApplication::processEvents(QEventLoop::AllEvents, remaining);
- QCoreApplication::sendPostedEvents(Q_NULLPTR, QEvent::DeferredDelete);
- QTest::qSleep(10);
- remaining = timer.remainingTime();
- }
- return window->isExposed();
+ return qWaitFor([&]() { return window->isExposed(); }, timeout);
}
#endif