summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/thread
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/corelib/thread')
-rw-r--r--tests/auto/corelib/thread/qfuture/tst_qfuture.cpp79
-rw-r--r--tests/auto/corelib/thread/qresultstore/tst_qresultstore.cpp16
-rw-r--r--tests/auto/corelib/thread/qthread/tst_qthread.cpp95
-rw-r--r--tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp27
-rw-r--r--tests/auto/corelib/thread/qthreadstorage/tst_qthreadstorage.cpp13
5 files changed, 215 insertions, 15 deletions
diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
index 0bdadedfac..49c8ec2113 100644
--- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
+++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
@@ -94,6 +94,7 @@ private slots:
void nestedExceptions();
#endif
void nonGlobalThreadPool();
+ void resultsReadyAt();
};
void tst_QFuture::resultStore()
@@ -627,6 +628,26 @@ void tst_QFuture::futureInterface()
VoidResult a;
a.run().waitForFinished();
}
+
+ {
+ QFutureInterface<int> fi;
+ fi.reportStarted();
+ fi.reportResults(QVector<int> {});
+ fi.reportFinished();
+
+ QVERIFY(fi.results().empty());
+ }
+
+ {
+ QFutureInterface<int> fi;
+ fi.reportStarted();
+ QVector<int> values = { 1, 2, 3 };
+ fi.reportResults(values);
+ fi.reportResults(QVector<int> {});
+ fi.reportFinished();
+
+ QCOMPARE(fi.results(), values.toList());
+ }
}
template <typename T>
@@ -1557,5 +1578,63 @@ void tst_QFuture::nonGlobalThreadPool()
}
}
+void tst_QFuture::resultsReadyAt()
+{
+ QFutureInterface<int> iface;
+ QFutureWatcher<int> watcher;
+ watcher.setFuture(iface.future());
+
+ QTestEventLoop eventProcessor;
+ connect(&watcher, &QFutureWatcher<int>::finished, &eventProcessor, &QTestEventLoop::exitLoop);
+
+ const int nExpectedResults = 4;
+ int reported = 0;
+ int taken = 0;
+ connect(&watcher, &QFutureWatcher<int>::resultsReadyAt,
+ [&iface, &reported, &taken](int begin, int end)
+ {
+ auto future = iface.future();
+ QVERIFY(end - begin > 0);
+ for (int i = begin; i < end; ++i, ++reported) {
+ QVERIFY(future.isResultReadyAt(i));
+ taken |= 1 << i;
+ }
+ });
+
+ auto report = [&iface](int index)
+ {
+ int dummyResult = 0b101010;
+ iface.reportResult(&dummyResult, index);
+ };
+
+ const QSignalSpy readyCounter(&watcher, &QFutureWatcher<int>::resultsReadyAt);
+ QTimer::singleShot(0, [&iface, &report]{
+ // With filter mode == true, the result may go into the pending results.
+ // Reporting it as ready will allow an application to try and access the
+ // result, crashing on invalid (store.end()) iterator dereferenced.
+ iface.setFilterMode(true);
+ iface.reportStarted();
+ report(0);
+ report(1);
+ // This one - should not be reported (it goes into pending):
+ report(3);
+ // Let's close the 'gap' and make them all ready:
+ report(-1);
+ iface.reportFinished();
+ });
+
+ // Run event loop, QCoreApplication::postEvent is in use
+ // in QFutureInterface:
+ eventProcessor.enterLoopMSecs(2000);
+ QVERIFY(!eventProcessor.timeout());
+ if (QTest::currentTestFailed()) // Failed in our lambda observing 'ready at'
+ return;
+
+ QCOMPARE(reported, nExpectedResults);
+ QCOMPARE(nExpectedResults, iface.future().resultCount());
+ QCOMPARE(readyCounter.count(), 3);
+ QCOMPARE(taken, 0b1111);
+}
+
QTEST_MAIN(tst_QFuture)
#include "tst_qfuture.moc"
diff --git a/tests/auto/corelib/thread/qresultstore/tst_qresultstore.cpp b/tests/auto/corelib/thread/qresultstore/tst_qresultstore.cpp
index fba617e34d..a1742b3182 100644
--- a/tests/auto/corelib/thread/qresultstore/tst_qresultstore.cpp
+++ b/tests/auto/corelib/thread/qresultstore/tst_qresultstore.cpp
@@ -175,6 +175,14 @@ void tst_QtConcurrentResultStore::addResults()
++it;
QCOMPARE(it, store.end());
+
+ QVector<int> empty;
+ const auto countBefore = store.count();
+ QCOMPARE(store.addResults(countBefore, &empty), -1);
+ QCOMPARE(store.count(), countBefore);
+
+ QCOMPARE(store.addResults(countBefore, &vec1), countBefore);
+ QCOMPARE(store.count(), countBefore + vec1.size());
}
void tst_QtConcurrentResultStore::resultIndex()
@@ -338,6 +346,14 @@ void tst_QtConcurrentResultStore::filterMode()
QCOMPARE(store.contains(6), true);
QCOMPARE(store.contains(7), true);
QCOMPARE(store.contains(8), false);
+
+ QVector<int> empty;
+ const auto countBefore = store.count();
+ QCOMPARE(store.addResults(countBefore, &empty), -1);
+ QCOMPARE(store.count(), countBefore);
+
+ QCOMPARE(store.addResult(countBefore, &int2), countBefore);
+ QCOMPARE(store.count(), countBefore + 1);
}
void tst_QtConcurrentResultStore::addCanceledResult()
diff --git a/tests/auto/corelib/thread/qthread/tst_qthread.cpp b/tests/auto/corelib/thread/qthread/tst_qthread.cpp
index 7be2f48758..b868f65268 100644
--- a/tests/auto/corelib/thread/qthread/tst_qthread.cpp
+++ b/tests/auto/corelib/thread/qthread/tst_qthread.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
@@ -36,6 +36,7 @@
#include <qwaitcondition.h>
#include <qdebug.h>
#include <qmetaobject.h>
+#include <qscopeguard.h>
#ifdef Q_OS_UNIX
#include <pthread.h>
@@ -106,6 +107,7 @@ private slots:
void quitLock();
void create();
+ void threadIdReuse();
};
enum { one_minute = 60 * 1000, five_minutes = 5 * one_minute };
@@ -1082,8 +1084,8 @@ void tst_QThread::wait2()
qPrintable(msgElapsed(elapsed)));
}
-
-class SlowSlotObject : public QObject {
+class SlowSlotObject : public QObject
+{
Q_OBJECT
public:
QMutex mutex;
@@ -1099,22 +1101,23 @@ void tst_QThread::wait3_slowDestructor()
{
SlowSlotObject slow;
QThread thread;
- QObject::connect(&thread, SIGNAL(finished()), &slow, SLOT(slowSlot()), Qt::DirectConnection);
-
- enum { WaitTime = 1800 };
+ QObject::connect(&thread, &QThread::finished,
+ &slow, &SlowSlotObject::slowSlot, Qt::DirectConnection);
QElapsedTimer timer;
thread.start();
thread.quit();
- //the quit function will cause the thread to finish and enter the slowSlot that is blocking
+ // Calling quit() will cause the thread to finish and enter the blocking slowSlot().
timer.start();
- QVERIFY(!thread.wait(Waiting_Thread::WaitTime));
- qint64 elapsed = timer.elapsed();
- QVERIFY2(elapsed >= Waiting_Thread::WaitTime - 1, qPrintable(QString::fromLatin1("elapsed: %1").arg(elapsed)));
-
- slow.cond.wakeOne();
- //now the thread should finish quickly
+ {
+ // Ensure thread finishes quickly after the checks - regardless of success:
+ const auto wakeSlow = qScopeGuard([&slow]() -> void { slow.cond.wakeOne(); });
+ QVERIFY(!thread.wait(Waiting_Thread::WaitTime));
+ const qint64 elapsed = timer.elapsed();
+ QVERIFY2(elapsed >= Waiting_Thread::WaitTime - 1,
+ qPrintable(QString::fromLatin1("elapsed: %1").arg(elapsed)));
+ }
QVERIFY(thread.wait(one_minute));
}
@@ -1631,5 +1634,71 @@ void tst_QThread::requestTermination()
QVERIFY(!thread.isInterruptionRequested());
}
+/*
+ This is a regression test for QTBUG-96846.
+
+ Incorrect system thread ID cleanup can cause QThread::wait() to report that
+ a thread is trying to wait for itself.
+*/
+void tst_QThread::threadIdReuse()
+{
+ class Thread1 : public QThread {
+ public:
+ // It's important that those thread ID's are not accessed concurrently
+ Qt::HANDLE savedThreadId;
+
+ void run() override { savedThreadId = QThread::currentThreadId(); }
+ };
+
+ class Thread2 : public Thread1 {
+ public:
+ bool waitOk;
+ Thread2(QThread *otherThread) : Thread1(), waitOk(false), otherThread(otherThread) {}
+
+ void run() override {
+ Thread1::run();
+ waitOk = otherThread->wait();
+ }
+
+ private:
+ QThread *const otherThread;
+ };
+
+ Thread1 thread1;
+ thread1.start();
+ QVERIFY(thread1.wait());
+
+ // If the system thread allocated for thread1 is destroyed before thread2 is
+ // started, at least on some versions of Linux the system thread ID for
+ // thread2 would be the same as one that was used for thread1.
+
+ // The system thread may be alive for some time after returning from
+ // QThread::wait() because the implementation is using detachable threads, so
+ // some additional time is required for the system thread to terminate. Not
+ // waiting long enough here would result in a new system thread ID being
+ // allocated for thread2 and this test passing even without a fix for
+ // QTBUG-96846.
+ bool threadIdReused = false;
+
+ for (int i = 0; i < 42; i++) {
+ QThread::msleep(1);
+
+ Thread2 thread2(&thread1);
+ thread2.start();
+ QVERIFY(thread2.wait());
+ QVERIFY(thread2.waitOk);
+
+ if (thread1.savedThreadId == thread2.savedThreadId) {
+ qDebug("Thread ID reused at iteration %d", i);
+ threadIdReused = true;
+ break;
+ }
+ }
+
+ if (!threadIdReused) {
+ QSKIP("Thread ID was not reused");
+ }
+}
+
QTEST_MAIN(tst_QThread)
#include "tst_qthread.moc"
diff --git a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp
index cd245030db..e85fb5ea88 100644
--- a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp
+++ b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp
@@ -104,6 +104,7 @@ private slots:
void stressTest();
void takeAllAndIncreaseMaxThreadCount();
void waitForDoneAfterTake();
+ void threadReuse();
private:
QMutex m_functionTestMutex;
@@ -803,7 +804,7 @@ void tst_QThreadPool::tryStartPeakThreadCount()
CounterTask task;
QThreadPool threadPool;
- for (int i = 0; i < 20; ++i) {
+ for (int i = 0; i < 4*QThread::idealThreadCount(); ++i) {
if (threadPool.tryStart(&task) == false)
QTest::qWait(10);
}
@@ -1385,5 +1386,29 @@ void tst_QThreadPool::waitForDoneAfterTake()
}
+/*
+ Try trigger reuse of expired threads and check that all tasks execute.
+
+ This is a regression test for QTBUG-72872.
+*/
+void tst_QThreadPool::threadReuse()
+{
+ QThreadPool manager;
+ manager.setExpiryTimeout(-1);
+ manager.setMaxThreadCount(1);
+
+ constexpr int repeatCount = 10000;
+ constexpr int timeoutMs = 1000;
+ QSemaphore sem;
+
+ for (int i = 0; i < repeatCount; i++) {
+ manager.start([&sem]() { sem.release(); });
+ manager.start([&sem]() { sem.release(); });
+ manager.releaseThread();
+ QVERIFY(sem.tryAcquire(2, timeoutMs));
+ manager.reserveThread();
+ }
+}
+
QTEST_MAIN(tst_QThreadPool);
#include "tst_qthreadpool.moc"
diff --git a/tests/auto/corelib/thread/qthreadstorage/tst_qthreadstorage.cpp b/tests/auto/corelib/thread/qthreadstorage/tst_qthreadstorage.cpp
index 3538d90803..c9120d7576 100644
--- a/tests/auto/corelib/thread/qthreadstorage/tst_qthreadstorage.cpp
+++ b/tests/auto/corelib/thread/qthreadstorage/tst_qthreadstorage.cpp
@@ -309,7 +309,18 @@ void tst_QThreadStorage::crashOnExit()
QSKIP("No qprocess support", SkipAll);
#else
QString errorMessage;
- QVERIFY2(runCrashOnExit("crashOnExit_helper", &errorMessage),
+
+ // Add the executable's directory to path so that we can find the test helper next to it
+ // in a cross-platform way. We must do this because the CWD is not pointing to this directory
+ // in debug-and-release builds.
+ QByteArray path = qgetenv("PATH");
+ qputenv("PATH",
+ path + QDir::listSeparator().toLatin1()
+ + QCoreApplication::applicationDirPath().toLocal8Bit());
+ auto restore = qScopeGuard([&] { qputenv("PATH", path); });
+
+ QString binary = QStringLiteral("crashOnExit_helper");
+ QVERIFY2(runCrashOnExit(binary, &errorMessage),
qPrintable(errorMessage));
#endif
}