QTestLib: modernize and stream-line WatchDog

... by porting from (QWaitCondition,QMutex) to std::{condition_variable,
mutex} and making the code more accessible by introducing explicit
states.

This patch was originally starting out to just replace QWaitCondition
with std::condition_variable, which is faster and more compact, and
the patch still does that, too. The focus, however, has shifted
towards improving the accessibility of the code, in particular its
states and the transitions between them.

Due to the way QWaitCondition prevents spurious wakeups, this code
could use its QWaitCondition as a semaphore, e.g. where it was calling
wait() in the ctor to be released only when the thread it started has
entered run(). That makes the code unnecessarily hard to follow.

Fix by introducing an enum Expectation which tells what the watch-dog
is currently waiting for, and unmistakably determines the state the
watch-dog is in: ThreadStart, TestFunctionStart, ... The timeout value
is now selected based on the Expectation value, so the timeout is no
longer serving as the implicit state, either.

Also port the defaultTimeout() function to return a
std::chrono::milliseconds directly. Elsewhere in Qt, we guard against
lack of <chrono>, so this unguarded use will also serve as a guide to
see whether all supported platforms now, eight Qt releases after we
formally require C++11 for Qt usage, provide it. Without <chrono>,
there's no timed waiting in the standard library.

Change-Id: If97b601c4090a2a2926fa58c903cfe3ec2656324
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Marc Mutz 2019-09-13 10:29:49 +02:00
parent 35cdcddd60
commit e0cad1aab5

View File

@ -58,8 +58,7 @@
#include <QtCore/qdiriterator.h>
#include <QtCore/qtemporarydir.h>
#include <QtCore/qthread.h>
#include <QtCore/qwaitcondition.h>
#include <QtCore/qmutex.h>
#include <QtCore/private/qlocking_p.h>
#include <QtCore/qtestsupport_core.h>
@ -85,6 +84,9 @@
#include <cmath>
#include <numeric>
#include <algorithm>
#include <condition_variable>
#include <mutex>
#include <chrono>
#include <stdarg.h>
#include <stdio.h>
@ -406,7 +408,7 @@ int Q_TESTLIB_EXPORT defaultKeyDelay()
return keyDelay;
}
#if QT_CONFIG(thread)
static int defaultTimeout()
static std::chrono::milliseconds defaultTimeout()
{
if (timeout == -1) {
bool ok = false;
@ -415,7 +417,7 @@ static int defaultTimeout()
if (!ok || timeout <= 0)
timeout = 5*60*1000;
}
return timeout;
return std::chrono::milliseconds{timeout};
}
#endif
@ -1008,53 +1010,81 @@ void TestMethods::invokeTestOnData(int index) const
class WatchDog : public QThread
{
enum Expectation {
ThreadStart,
TestFunctionStart,
TestFunctionEnd,
ThreadEnd,
};
bool waitFor(std::unique_lock<std::mutex> &m, Expectation e) {
auto expectation = [this, e] { return expecting != e; };
switch (e) {
case TestFunctionEnd:
return waitCondition.wait_for(m, defaultTimeout(), expectation);
case ThreadStart:
case ThreadEnd:
case TestFunctionStart:
waitCondition.wait(m, expectation);
return true;
}
Q_UNREACHABLE();
return false;
}
public:
WatchDog()
{
QMutexLocker locker(&mutex);
timeout.storeRelaxed(-1);
std::unique_lock<std::mutex> locker(mutex);
expecting = ThreadStart;
start();
waitCondition.wait(&mutex);
waitFor(locker, ThreadStart);
}
~WatchDog() {
{
QMutexLocker locker(&mutex);
timeout.storeRelaxed(0);
waitCondition.wakeAll();
const auto locker = qt_scoped_lock(mutex);
expecting = ThreadEnd;
waitCondition.notify_all();
}
wait();
}
void beginTest() {
QMutexLocker locker(&mutex);
timeout.storeRelaxed(defaultTimeout());
waitCondition.wakeAll();
const auto locker = qt_scoped_lock(mutex);
expecting = TestFunctionEnd;
waitCondition.notify_all();
}
void testFinished() {
QMutexLocker locker(&mutex);
timeout.storeRelaxed(-1);
waitCondition.wakeAll();
const auto locker = qt_scoped_lock(mutex);
expecting = TestFunctionStart;
waitCondition.notify_all();
}
void run() override {
QMutexLocker locker(&mutex);
waitCondition.wakeAll();
std::unique_lock<std::mutex> locker(mutex);
expecting = TestFunctionStart;
waitCondition.notify_all();
while (true) {
int t = timeout.loadRelaxed();
if (!t)
break;
if (Q_UNLIKELY(!waitCondition.wait(&mutex, t))) {
stackTrace();
qFatal("Test function timed out");
switch (expecting) {
case ThreadEnd:
return;
case ThreadStart:
Q_UNREACHABLE();
case TestFunctionStart:
case TestFunctionEnd:
if (Q_UNLIKELY(!waitFor(locker, expecting))) {
stackTrace();
qFatal("Test function timed out");
}
}
}
}
private:
QBasicAtomicInt timeout;
QMutex mutex;
QWaitCondition waitCondition;
std::mutex mutex;
std::condition_variable waitCondition;
Expectation expecting;
};
#else // !QT_CONFIG(thread)