Skip to content

Commit

Permalink
Improve error handling in qtestlib
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
laknoll authored and Simon Hausmann committed Apr 21, 2015
1 parent 05d1693 commit d3d10cf
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 4 deletions.
91 changes: 88 additions & 3 deletions src/testlib/qtestcase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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))
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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);

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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()");

Expand All @@ -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;
}
}
Expand All @@ -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;
Expand Down Expand Up @@ -2422,6 +2505,8 @@ class FatalSignalHandler

void FatalSignalHandler::signal(int signum)
{
if (signum != SIGINT)
stackTrace();
qFatal("Received signal %d", signum);
#if defined(Q_OS_INTEGRITY)
{
Expand Down
4 changes: 3 additions & 1 deletion tests/auto/testlib/selftests/tst_selftests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit d3d10cf

Please sign in to comment.