summaryrefslogtreecommitdiffstats
path: root/src/testlib/qtestcase.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/testlib/qtestcase.cpp')
-rw-r--r--src/testlib/qtestcase.cpp336
1 files changed, 323 insertions, 13 deletions
diff --git a/src/testlib/qtestcase.cpp b/src/testlib/qtestcase.cpp
index efc18180db..0847d639fd 100644
--- a/src/testlib/qtestcase.cpp
+++ b/src/testlib/qtestcase.cpp
@@ -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,12 @@
#include <stdio.h>
#include <stdlib.h>
+#if defined(Q_OS_LINUX)
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.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 +102,40 @@ 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 full\n"
+ "detach\n"
+ "quit\n"
+ "EOF\n",
+ (int)getpid());
+ if (system(cmd) == -1)
+ fprintf(stderr, "calling gdb failed\n");
+ fprintf(stderr, "========= End of stack trace ==============\n");
+#elif defined(Q_OS_OSX)
+ fprintf(stderr, "\n========= Received signal, dumping stack ==============\n");
+ char cmd[512];
+ qsnprintf(cmd, 512, "lldb -p %d 2>/dev/null <<EOF\n"
+ "bt all\n"
+ "quit\n"
+ "EOF\n",
+ (int)getpid());
+ if (system(cmd) == -1)
+ fprintf(stderr, "calling lldb failed\n");
+ fprintf(stderr, "========= End of stack trace ==============\n");
+#endif
+}
+
/*!
\namespace QTest
\inmodule QtTest
@@ -205,7 +248,7 @@ using QtMiscUtils::fromHex;
\note This macro can only be used in a test function that is invoked
by the test framework.
- \sa QTRY_VERIFY(), QVERIFY(), QCOMPARE(), QTRY_COMPARE()
+ \sa QTRY_VERIFY(), QTRY_VERIFY2_WITH_TIMEOUT(), QVERIFY(), QCOMPARE(), QTRY_COMPARE()
*/
@@ -219,7 +262,47 @@ using QtMiscUtils::fromHex;
\note This macro can only be used in a test function that is invoked
by the test framework.
- \sa QTRY_VERIFY_WITH_TIMEOUT(), QVERIFY(), QCOMPARE(), QTRY_COMPARE()
+ \sa QTRY_VERIFY_WITH_TIMEOUT(), QTRY_VERIFY2(), QVERIFY(), QCOMPARE(), QTRY_COMPARE()
+*/
+
+/*! \macro QTRY_VERIFY2_WITH_TIMEOUT(condition, message, timeout)
+ \since 5.6
+
+ \relates QTest
+
+ The QTRY_VERIFY2_WITH_TIMEOUT macro is similar to QTRY_VERIFY_WITH_TIMEOUT()
+ except that it outputs a verbose \a message when \a condition is still false
+ after the specified \a timeout. The \a message is a plain C string.
+
+ Example:
+ \code
+ QTRY_VERIFY2_WITH_TIMEOUT(list.size() > 2, QByteArray::number(list.size()).constData(), 10000);
+ \endcode
+
+ \note This macro can only be used in a test function that is invoked
+ by the test framework.
+
+ \sa QTRY_VERIFY(), QTRY_VERIFY_WITH_TIMEOUT(), QVERIFY(), QCOMPARE(), QTRY_COMPARE()
+*/
+
+/*! \macro QTRY_VERIFY2(condition, message)
+ \since 5.6
+
+ \relates QTest
+
+ Checks the \a condition by invoking QTRY_VERIFY2_WITH_TIMEOUT() with a timeout
+ of five seconds. If \a condition is then still false, \a message is output.
+ The \a message is a plain C string.
+
+ Example:
+ \code
+ QTRY_VERIFY2_WITH_TIMEOUT(list.size() > 2, QByteArray::number(list.size()).constData());
+ \endcode
+
+ \note This macro can only be used in a test function that is invoked
+ by the test framework.
+
+ \sa QTRY_VERIFY2_WITH_TIMEOUT(), QTRY_VERIFY2(), QVERIFY(), QCOMPARE(), QTRY_COMPARE()
*/
/*! \macro QTRY_COMPARE_WITH_TIMEOUT(actual, expected, timeout)
@@ -1276,7 +1359,7 @@ using QtMiscUtils::fromHex;
*/
/*!
- \fn QTouchEventSequence QTest::touchEvent(QWindow *window, QTouchDevice *device, bool autoCommit = true)
+ \fn QTouchEventSequence QTest::touchEvent(QWindow *window, QTouchDevice *device, bool autoCommit)
\since 5.0
Creates and returns a QTouchEventSequence for the \a device to
@@ -1293,7 +1376,7 @@ using QtMiscUtils::fromHex;
*/
/*!
- \fn QTouchEventSequence QTest::touchEvent(QWidget *widget, QTouchDevice *device, bool autoCommit = true)
+ \fn QTouchEventSequence QTest::touchEvent(QWidget *widget, QTouchDevice *device, bool autoCommit)
Creates and returns a QTouchEventSequence for the \a device to
simulate events for \a widget.
@@ -1883,7 +1966,7 @@ Q_TESTLIB_EXPORT void qtest_qParseArgs(int argc, char *argv[], bool qml)
QTestLog::addLogger(logFormat, logFilename);
}
-QBenchmarkResult qMedian(const QList<QBenchmarkResult> &container)
+QBenchmarkResult qMedian(const QVector<QBenchmarkResult> &container)
{
const int count = container.count();
if (count == 0)
@@ -1892,7 +1975,7 @@ QBenchmarkResult qMedian(const QList<QBenchmarkResult> &container)
if (count == 1)
return container.front();
- QList<QBenchmarkResult> containerCopy = container;
+ QVector<QBenchmarkResult> containerCopy = container;
std::sort(containerCopy.begin(), containerCopy.end());
const int middle = count / 2;
@@ -1929,7 +2012,7 @@ static void qInvokeTestMethodDataEntry(char *slot)
bool isBenchmark = false;
int i = (QBenchmarkGlobalData::current->measurer->needsWarmupIteration()) ? -1 : 0;
- QList<QBenchmarkResult> results;
+ QVector<QBenchmarkResult> results;
bool minimumTotalReached = false;
do {
QBenchmarkTestMethodData::current->beginDataRun();
@@ -2011,6 +2094,58 @@ static void qInvokeTestMethodDataEntry(char *slot)
}
}
+class WatchDog : public QThread
+{
+public:
+ WatchDog()
+ {
+ QMutexLocker locker(&mutex);
+ timeout.store(-1);
+ start();
+ waitCondition.wait(&mutex);
+ }
+ ~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);
+ waitCondition.wakeAll();
+ 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
@@ -2020,7 +2155,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);
@@ -2079,7 +2214,11 @@ static bool qInvokeTestMethod(const char *slotName, const char *data=0)
QTestDataSetter s(curDataIndex >= dataCount ? static_cast<QTestData *>(0)
: table.testData(curDataIndex));
+ if (watchDog)
+ watchDog->beginTest();
qInvokeTestMethodDataEntry(slot);
+ if (watchDog)
+ watchDog->testFinished();
if (data)
break;
@@ -2351,6 +2490,37 @@ char *toPrettyUnicode(const ushort *p, int length)
return buffer.take();
}
+static bool debuggerPresent()
+{
+#if defined(Q_OS_LINUX)
+ int fd = open("/proc/self/status", O_RDONLY);
+ if (fd == -1)
+ return false;
+ char buffer[2048];
+ ssize_t size = read(fd, buffer, sizeof(buffer));
+ if (size == -1) {
+ close(fd);
+ return false;
+ }
+ buffer[size] = 0;
+ const char tracerPidToken[] = "\nTracerPid:";
+ char *tracerPid = strstr(buffer, tracerPidToken);
+ if (!tracerPid) {
+ close(fd);
+ return false;
+ }
+ tracerPid += sizeof(tracerPidToken);
+ long int pid = strtol(tracerPid, &tracerPid, 10);
+ close(fd);
+ return pid != 0;
+#elif defined(Q_OS_WIN)
+ return IsDebuggerPresent();
+#else
+ // TODO
+ return false;
+#endif
+}
+
static void qInvokeTestMethods(QObject *testObject)
{
const QMetaObject *metaObject = testObject->metaObject();
@@ -2360,6 +2530,10 @@ static void qInvokeTestMethods(QObject *testObject)
QTestTable::globalTestTable();
invokeMethod(testObject, "initTestCase_data()");
+ QScopedPointer<WatchDog> watchDog;
+ if (!debuggerPresent())
+ watchDog.reset(new WatchDog);
+
if (!QTestResult::skipCurrentTest() && !QTest::currentTestFailed()) {
invokeMethod(testObject, "initTestCase()");
@@ -2374,7 +2548,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.data())) {
break;
}
}
@@ -2387,7 +2561,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.data()))
break;
}
delete[] testMethods;
@@ -2423,7 +2597,13 @@ private:
void FatalSignalHandler::signal(int signum)
{
- qFatal("Received signal %d", signum);
+ const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
+ const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
+ if (signum != SIGINT)
+ stackTrace();
+ qFatal("Received signal %d\n"
+ " Function time: %dms Total time: %dms",
+ signum, msecsFunctionTime, msecsTotalTime);
#if defined(Q_OS_INTEGRITY)
{
struct sigaction act;
@@ -2517,13 +2697,141 @@ FatalSignalHandler::~FatalSignalHandler()
} // namespace
#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT)
+
+// Helper class for resolving symbol names by dynamically loading "dbghelp.dll".
+class DebugSymbolResolver
+{
+ Q_DISABLE_COPY(DebugSymbolResolver)
+public:
+ struct Symbol {
+ Symbol() : name(Q_NULLPTR), address(0) {}
+
+ const char *name; // Must be freed by caller.
+ DWORD64 address;
+ };
+
+ explicit DebugSymbolResolver(HANDLE process);
+ ~DebugSymbolResolver() { cleanup(); }
+
+ bool isValid() const { return m_symFromAddr; }
+
+ Symbol resolveSymbol(DWORD64 address) const;
+
+private:
+ // typedefs from DbgHelp.h/.dll
+ struct DBGHELP_SYMBOL_INFO { // SYMBOL_INFO
+ ULONG SizeOfStruct;
+ ULONG TypeIndex; // Type Index of symbol
+ ULONG64 Reserved[2];
+ ULONG Index;
+ ULONG Size;
+ ULONG64 ModBase; // Base Address of module comtaining this symbol
+ ULONG Flags;
+ ULONG64 Value; // Value of symbol, ValuePresent should be 1
+ ULONG64 Address; // Address of symbol including base address of module
+ ULONG Register; // register holding value or pointer to value
+ ULONG Scope; // scope of the symbol
+ ULONG Tag; // pdb classification
+ ULONG NameLen; // Actual length of name
+ ULONG MaxNameLen;
+ CHAR Name[1]; // Name of symbol
+ };
+
+ typedef BOOL (__stdcall *SymInitializeType)(HANDLE, PCSTR, BOOL);
+ typedef BOOL (__stdcall *SymFromAddrType)(HANDLE, DWORD64, PDWORD64, DBGHELP_SYMBOL_INFO *);
+
+ void cleanup();
+
+ const HANDLE m_process;
+ HMODULE m_dbgHelpLib;
+ SymFromAddrType m_symFromAddr;
+};
+
+void DebugSymbolResolver::cleanup()
+{
+ if (m_dbgHelpLib)
+ FreeLibrary(m_dbgHelpLib);
+ m_dbgHelpLib = 0;
+ m_symFromAddr = Q_NULLPTR;
+}
+
+DebugSymbolResolver::DebugSymbolResolver(HANDLE process)
+ : m_process(process), m_dbgHelpLib(0), m_symFromAddr(Q_NULLPTR)
+{
+ bool success = false;
+ m_dbgHelpLib = LoadLibraryW(L"dbghelp.dll");
+ if (m_dbgHelpLib) {
+ SymInitializeType symInitialize = (SymInitializeType)(GetProcAddress(m_dbgHelpLib, "SymInitialize"));
+ m_symFromAddr = (SymFromAddrType)(GetProcAddress(m_dbgHelpLib, "SymFromAddr"));
+ success = symInitialize && m_symFromAddr && symInitialize(process, NULL, TRUE);
+ }
+ if (!success)
+ cleanup();
+}
+
+DebugSymbolResolver::Symbol DebugSymbolResolver::resolveSymbol(DWORD64 address) const
+{
+ // reserve additional buffer where SymFromAddr() will store the name
+ struct NamedSymbolInfo : public DBGHELP_SYMBOL_INFO {
+ enum { symbolNameLength = 255 };
+
+ char name[symbolNameLength + 1];
+ };
+
+ Symbol result;
+ if (!isValid())
+ return result;
+ NamedSymbolInfo symbolBuffer;
+ memset(&symbolBuffer, 0, sizeof(NamedSymbolInfo));
+ symbolBuffer.MaxNameLen = NamedSymbolInfo::symbolNameLength;
+ symbolBuffer.SizeOfStruct = sizeof(DBGHELP_SYMBOL_INFO);
+ if (!m_symFromAddr(m_process, address, 0, &symbolBuffer))
+ return result;
+ result.name = qstrdup(symbolBuffer.Name);
+ result.address = symbolBuffer.Address;
+ return result;
+}
+
static LONG WINAPI windowsFaultHandler(struct _EXCEPTION_POINTERS *exInfo)
{
+ enum { maxStackFrames = 100 };
char appName[MAX_PATH];
if (!GetModuleFileNameA(NULL, appName, MAX_PATH))
appName[0] = 0;
- fprintf(stderr, "A crash occurred in %s (exception code 0x%lx).",
- appName, exInfo->ExceptionRecord->ExceptionCode);
+ const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
+ const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
+ const void *exceptionAddress = exInfo->ExceptionRecord->ExceptionAddress;
+ printf("A crash occurred in %s.\n"
+ "Function time: %dms Total time: %dms\n\n"
+ "Exception address: 0x%p\n"
+ "Exception code : 0x%lx\n",
+ appName, msecsFunctionTime, msecsTotalTime,
+ exceptionAddress, exInfo->ExceptionRecord->ExceptionCode);
+
+ DebugSymbolResolver resolver(GetCurrentProcess());
+ if (resolver.isValid()) {
+ DebugSymbolResolver::Symbol exceptionSymbol = resolver.resolveSymbol(DWORD64(exceptionAddress));
+ if (exceptionSymbol.name) {
+ printf("Nearby symbol : %s\n", exceptionSymbol.name);
+ delete [] exceptionSymbol.name;
+ }
+ void *stack[maxStackFrames];
+ fputs("\nStack:\n", stdout);
+ const unsigned frameCount = CaptureStackBackTrace(0, DWORD(maxStackFrames), stack, NULL);
+ for (unsigned f = 0; f < frameCount; ++f) {
+ DebugSymbolResolver::Symbol symbol = resolver.resolveSymbol(DWORD64(stack[f]));
+ if (symbol.name) {
+ printf("#%3u: %s() - 0x%p\n", f + 1, symbol.name, (const void *)symbol.address);
+ delete [] symbol.name;
+ } else {
+ printf("#%3u: Unable to obtain symbol\n", f + 1);
+ }
+ }
+ }
+
+ fputc('\n', stdout);
+ fflush(stdout);
+
return EXCEPTION_EXECUTE_HANDLER;
}
#endif // Q_OS_WIN) && !Q_OS_WINCE && !Q_OS_WINRT
@@ -3237,6 +3545,8 @@ TO_STRING_IMPL(quint64, %llu)
#endif
TO_STRING_IMPL(bool, %d)
TO_STRING_IMPL(char, %c)
+TO_STRING_IMPL(signed char, %hhd)
+TO_STRING_IMPL(unsigned char, %hhu)
TO_STRING_IMPL(float, %g)
TO_STRING_IMPL(double, %lg)