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/private/qtools_p.h>
|
||||||
#include <QtCore/qdiriterator.h>
|
#include <QtCore/qdiriterator.h>
|
||||||
#include <QtCore/qtemporarydir.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/qtestlog_p.h>
|
||||||
#include <QtTest/private/qtesttable_p.h>
|
#include <QtTest/private/qtesttable_p.h>
|
||||||
@ -70,6 +73,11 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#ifndef Q_OS_WINCE
|
#ifndef Q_OS_WINCE
|
||||||
# if !defined(Q_CC_MINGW) || (defined(Q_CC_MINGW) && defined(__MINGW64_VERSION_MAJOR))
|
# 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::toHexUpper;
|
||||||
using QtMiscUtils::fromHex;
|
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
|
\namespace QTest
|
||||||
\inmodule QtTest
|
\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
|
\internal
|
||||||
|
|
||||||
@ -2019,7 +2098,7 @@ static void qInvokeTestMethodDataEntry(char *slot)
|
|||||||
If the function was successfully called, true is returned, otherwise
|
If the function was successfully called, true is returned, otherwise
|
||||||
false.
|
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);
|
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)
|
QTestDataSetter s(curDataIndex >= dataCount ? static_cast<QTestData *>(0)
|
||||||
: table.testData(curDataIndex));
|
: table.testData(curDataIndex));
|
||||||
|
|
||||||
|
watchDog->beginTest();
|
||||||
qInvokeTestMethodDataEntry(slot);
|
qInvokeTestMethodDataEntry(slot);
|
||||||
|
watchDog->testFinished();
|
||||||
|
|
||||||
if (data)
|
if (data)
|
||||||
break;
|
break;
|
||||||
@ -2359,6 +2440,8 @@ static void qInvokeTestMethods(QObject *testObject)
|
|||||||
QTestTable::globalTestTable();
|
QTestTable::globalTestTable();
|
||||||
invokeMethod(testObject, "initTestCase_data()");
|
invokeMethod(testObject, "initTestCase_data()");
|
||||||
|
|
||||||
|
WatchDog watchDog;
|
||||||
|
|
||||||
if (!QTestResult::skipCurrentTest() && !QTest::currentTestFailed()) {
|
if (!QTestResult::skipCurrentTest() && !QTest::currentTestFailed()) {
|
||||||
invokeMethod(testObject, "initTestCase()");
|
invokeMethod(testObject, "initTestCase()");
|
||||||
|
|
||||||
@ -2373,7 +2456,7 @@ static void qInvokeTestMethods(QObject *testObject)
|
|||||||
if (QTest::testFuncs) {
|
if (QTest::testFuncs) {
|
||||||
for (int i = 0; i != QTest::testFuncCount; i++) {
|
for (int i = 0; i != QTest::testFuncCount; i++) {
|
||||||
if (!qInvokeTestMethod(metaObject->method(QTest::testFuncs[i].function()).methodSignature().constData(),
|
if (!qInvokeTestMethod(metaObject->method(QTest::testFuncs[i].function()).methodSignature().constData(),
|
||||||
QTest::testFuncs[i].data())) {
|
QTest::testFuncs[i].data(), &watchDog)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2386,7 +2469,7 @@ static void qInvokeTestMethods(QObject *testObject)
|
|||||||
for (int i = 0; i != methodCount; i++) {
|
for (int i = 0; i != methodCount; i++) {
|
||||||
if (!isValidSlot(testMethods[i]))
|
if (!isValidSlot(testMethods[i]))
|
||||||
continue;
|
continue;
|
||||||
if (!qInvokeTestMethod(testMethods[i].methodSignature().constData()))
|
if (!qInvokeTestMethod(testMethods[i].methodSignature().constData(), 0, &watchDog))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
delete[] testMethods;
|
delete[] testMethods;
|
||||||
@ -2422,6 +2505,8 @@ private:
|
|||||||
|
|
||||||
void FatalSignalHandler::signal(int signum)
|
void FatalSignalHandler::signal(int signum)
|
||||||
{
|
{
|
||||||
|
if (signum != SIGINT)
|
||||||
|
stackTrace();
|
||||||
qFatal("Received signal %d", signum);
|
qFatal("Received signal %d", signum);
|
||||||
#if defined(Q_OS_INTEGRITY)
|
#if defined(Q_OS_INTEGRITY)
|
||||||
{
|
{
|
||||||
|
@ -566,7 +566,9 @@ void tst_Selftests::doRunSubTest(QString const& subdir, QStringList const& logge
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
QProcess proc;
|
QProcess proc;
|
||||||
static const QProcessEnvironment environment = processEnvironment();
|
QProcessEnvironment environment = processEnvironment();
|
||||||
|
if (crashes)
|
||||||
|
environment.insert("QTEST_DISABLE_STACK_DUMP", "1");
|
||||||
proc.setProcessEnvironment(environment);
|
proc.setProcessEnvironment(environment);
|
||||||
const QString path = subdir + QLatin1Char('/') + subdir;
|
const QString path = subdir + QLatin1Char('/') + subdir;
|
||||||
proc.start(path, arguments);
|
proc.start(path, arguments);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user