Improve error handling in qtestlib
Dump a stack trace on Linux if the process crashes. This will give us much better log output in the new CI system. In addition, create a watch dog thread, that kills the test if the test function times out (ie. hangs) Implementations of the stack trace dumping for Mac and Windows are still pending. Change-Id: I65426c5afe290a0a2019b881436a0c278f1cafaf Reviewed-by: Simon Hausmann <simon.hausmann@theqtcompany.com>
This commit is contained in:
parent
05d1693793
commit
d3d10cf23d
@ -50,6 +50,9 @@
|
||||
#include <QtCore/private/qtools_p.h>
|
||||
#include <QtCore/qdiriterator.h>
|
||||
#include <QtCore/qtemporarydir.h>
|
||||
#include <QtCore/qthread.h>
|
||||
#include <QtCore/qwaitcondition.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
|
||||
#include <QtTest/private/qtestlog_p.h>
|
||||
#include <QtTest/private/qtesttable_p.h>
|
||||
@ -70,6 +73,11 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifndef Q_OS_WINCE
|
||||
# if !defined(Q_CC_MINGW) || (defined(Q_CC_MINGW) && defined(__MINGW64_VERSION_MAJOR))
|
||||
@ -93,6 +101,28 @@ QT_BEGIN_NAMESPACE
|
||||
using QtMiscUtils::toHexUpper;
|
||||
using QtMiscUtils::fromHex;
|
||||
|
||||
|
||||
static void stackTrace()
|
||||
{
|
||||
bool ok = false;
|
||||
const int disableStackDump = qEnvironmentVariableIntValue("QTEST_DISABLE_STACK_DUMP", &ok);
|
||||
if (ok && disableStackDump == 1)
|
||||
return;
|
||||
#ifdef Q_OS_LINUX
|
||||
fprintf(stderr, "\n========= Received signal, dumping stack ==============\n");
|
||||
char cmd[512];
|
||||
qsnprintf(cmd, 512, "gdb --pid %d 2>/dev/null <<EOF\n"
|
||||
"set prompt\n"
|
||||
"thread apply all where\n"
|
||||
"detach\n"
|
||||
"quit\n"
|
||||
"EOF\n",
|
||||
(int)getpid());
|
||||
system(cmd);
|
||||
fprintf(stderr, "========= End of stack trace ==============\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
\namespace QTest
|
||||
\inmodule QtTest
|
||||
@ -2010,6 +2040,55 @@ static void qInvokeTestMethodDataEntry(char *slot)
|
||||
}
|
||||
}
|
||||
|
||||
class WatchDog : public QThread
|
||||
{
|
||||
public:
|
||||
WatchDog()
|
||||
{
|
||||
timeout.store(-1);
|
||||
start();
|
||||
}
|
||||
~WatchDog() {
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
timeout.store(0);
|
||||
waitCondition.wakeAll();
|
||||
}
|
||||
wait();
|
||||
}
|
||||
|
||||
void beginTest() {
|
||||
QMutexLocker locker(&mutex);
|
||||
timeout.store(5*60*1000);
|
||||
waitCondition.wakeAll();
|
||||
}
|
||||
|
||||
void testFinished() {
|
||||
QMutexLocker locker(&mutex);
|
||||
timeout.store(-1);
|
||||
waitCondition.wakeAll();
|
||||
}
|
||||
|
||||
void run() {
|
||||
QMutexLocker locker(&mutex);
|
||||
while (1) {
|
||||
int t = timeout.load();
|
||||
if (!t)
|
||||
break;
|
||||
if (!waitCondition.wait(&mutex, t)) {
|
||||
stackTrace();
|
||||
qFatal("Test function timed out");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QBasicAtomicInt timeout;
|
||||
QMutex mutex;
|
||||
QWaitCondition waitCondition;
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
\internal
|
||||
|
||||
@ -2019,7 +2098,7 @@ static void qInvokeTestMethodDataEntry(char *slot)
|
||||
If the function was successfully called, true is returned, otherwise
|
||||
false.
|
||||
*/
|
||||
static bool qInvokeTestMethod(const char *slotName, const char *data=0)
|
||||
static bool qInvokeTestMethod(const char *slotName, const char *data, WatchDog *watchDog)
|
||||
{
|
||||
QTEST_ASSERT(slotName);
|
||||
|
||||
@ -2078,7 +2157,9 @@ static bool qInvokeTestMethod(const char *slotName, const char *data=0)
|
||||
QTestDataSetter s(curDataIndex >= dataCount ? static_cast<QTestData *>(0)
|
||||
: table.testData(curDataIndex));
|
||||
|
||||
watchDog->beginTest();
|
||||
qInvokeTestMethodDataEntry(slot);
|
||||
watchDog->testFinished();
|
||||
|
||||
if (data)
|
||||
break;
|
||||
@ -2359,6 +2440,8 @@ static void qInvokeTestMethods(QObject *testObject)
|
||||
QTestTable::globalTestTable();
|
||||
invokeMethod(testObject, "initTestCase_data()");
|
||||
|
||||
WatchDog watchDog;
|
||||
|
||||
if (!QTestResult::skipCurrentTest() && !QTest::currentTestFailed()) {
|
||||
invokeMethod(testObject, "initTestCase()");
|
||||
|
||||
@ -2373,7 +2456,7 @@ static void qInvokeTestMethods(QObject *testObject)
|
||||
if (QTest::testFuncs) {
|
||||
for (int i = 0; i != QTest::testFuncCount; i++) {
|
||||
if (!qInvokeTestMethod(metaObject->method(QTest::testFuncs[i].function()).methodSignature().constData(),
|
||||
QTest::testFuncs[i].data())) {
|
||||
QTest::testFuncs[i].data(), &watchDog)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2386,7 +2469,7 @@ static void qInvokeTestMethods(QObject *testObject)
|
||||
for (int i = 0; i != methodCount; i++) {
|
||||
if (!isValidSlot(testMethods[i]))
|
||||
continue;
|
||||
if (!qInvokeTestMethod(testMethods[i].methodSignature().constData()))
|
||||
if (!qInvokeTestMethod(testMethods[i].methodSignature().constData(), 0, &watchDog))
|
||||
break;
|
||||
}
|
||||
delete[] testMethods;
|
||||
@ -2422,6 +2505,8 @@ private:
|
||||
|
||||
void FatalSignalHandler::signal(int signum)
|
||||
{
|
||||
if (signum != SIGINT)
|
||||
stackTrace();
|
||||
qFatal("Received signal %d", signum);
|
||||
#if defined(Q_OS_INTEGRITY)
|
||||
{
|
||||
|
@ -566,7 +566,9 @@ void tst_Selftests::doRunSubTest(QString const& subdir, QStringList const& logge
|
||||
#endif
|
||||
|
||||
QProcess proc;
|
||||
static const QProcessEnvironment environment = processEnvironment();
|
||||
QProcessEnvironment environment = processEnvironment();
|
||||
if (crashes)
|
||||
environment.insert("QTEST_DISABLE_STACK_DUMP", "1");
|
||||
proc.setProcessEnvironment(environment);
|
||||
const QString path = subdir + QLatin1Char('/') + subdir;
|
||||
proc.start(path, arguments);
|
||||
|
Loading…
Reference in New Issue
Block a user