diff options
Diffstat (limited to 'src/testlib/qtestcase.cpp')
-rw-r--r-- | src/testlib/qtestcase.cpp | 336 |
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) |