diff options
Diffstat (limited to 'tests/auto/corelib/thread/qthread/tst_qthread.cpp')
-rw-r--r-- | tests/auto/corelib/thread/qthread/tst_qthread.cpp | 520 |
1 files changed, 423 insertions, 97 deletions
diff --git a/tests/auto/corelib/thread/qthread/tst_qthread.cpp b/tests/auto/corelib/thread/qthread/tst_qthread.cpp index 1e8380fbba..18c8d5fbd5 100644 --- a/tests/auto/corelib/thread/qthread/tst_qthread.cpp +++ b/tests/auto/corelib/thread/qthread/tst_qthread.cpp @@ -1,32 +1,14 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtTest/QtTest> +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> +#include <QTestEventLoop> +#include <QSignalSpy> +#include <QSemaphore> +#include <QAbstractEventDispatcher> +#if defined(Q_OS_WIN32) +#include <QWinEventNotifier> +#endif #include <qcoreapplication.h> #include <qelapsedtimer.h> @@ -36,12 +18,15 @@ #include <qwaitcondition.h> #include <qdebug.h> #include <qmetaobject.h> +#include <qscopeguard.h> +#include <private/qobject_p.h> +#include <private/qthread_p.h> #ifdef Q_OS_UNIX #include <pthread.h> #endif #if defined(Q_OS_WIN) -#include <windows.h> +#include <qt_windows.h> #if defined(Q_OS_WIN32) #include <process.h> #endif @@ -51,7 +36,9 @@ #include <exception> #endif -#include "emulationdetector.h" +#include <QtTest/private/qemulationdetector_p.h> + +using namespace std::chrono_literals; class tst_QThread : public QObject { @@ -66,6 +53,7 @@ private slots: void setStackSize(); void exit(); void start(); + void startSlotUsedInStringBasedLookups(); void terminate(); void quit(); void started(); @@ -85,6 +73,7 @@ private slots: void adoptedThreadExecFinished(); void adoptMultipleThreads(); void adoptMultipleThreadsOverlap(); + void adoptedThreadBindingStatus(); void exitAndStart(); void exitAndExec(); @@ -106,6 +95,13 @@ private slots: void quitLock(); void create(); + void createDestruction(); + void threadIdReuse(); + + void terminateAndPrematureDestruction(); + void terminateAndDoubleDestruction(); + + void bindingListCleanupAfterDelete(); }; enum { one_minute = 60 * 1000, five_minutes = 5 * one_minute }; @@ -141,11 +137,13 @@ class Current_Thread : public QThread public: Qt::HANDLE id; QThread *thread; + bool runCalledInCurrentThread = false; void run() override { id = QThread::currentThreadId(); thread = QThread::currentThread(); + runCalledInCurrentThread = thread->isCurrentThread(); } }; @@ -241,9 +239,9 @@ public: enum SleepType { Second, Millisecond, Microsecond }; SleepType sleepType; - int interval; + ulong interval; - int elapsed; // result, in *MILLISECONDS* + qint64 elapsed; // result, in *MILLISECONDS* void run() override { @@ -252,17 +250,19 @@ public: elapsed = 0; QElapsedTimer timer; timer.start(); + std::chrono::nanoseconds dur{0}; switch (sleepType) { case Second: - sleep(interval); + dur = std::chrono::seconds{interval}; break; case Millisecond: - msleep(interval); + dur = std::chrono::milliseconds{interval}; break; case Microsecond: - usleep(interval); + dur = std::chrono::microseconds{interval}; break; } + sleep(dur); elapsed = timer.elapsed(); cond.wakeOne(); @@ -272,25 +272,30 @@ public: void tst_QThread::currentThreadId() { Current_Thread thread; - thread.id = 0; - thread.thread = 0; + thread.id = nullptr; + thread.thread = nullptr; thread.start(); QVERIFY(thread.wait(five_minutes)); - QVERIFY(thread.id != 0); + QVERIFY(thread.id != nullptr); QVERIFY(thread.id != QThread::currentThreadId()); + QVERIFY(!thread.isCurrentThread()); + QVERIFY(!thread.thread->isCurrentThread()); + QVERIFY(thread.QThread::thread()->isCurrentThread()); + QVERIFY(thread.runCalledInCurrentThread); + QVERIFY(qApp->thread()->isCurrentThread()); } void tst_QThread::currentThread() { - QVERIFY(QThread::currentThread() != 0); + QVERIFY(QThread::currentThread() != nullptr); QCOMPARE(QThread::currentThread(), thread()); Current_Thread thread; - thread.id = 0; - thread.thread = 0; + thread.id = nullptr; + thread.thread = nullptr; thread.start(); QVERIFY(thread.wait(five_minutes)); - QCOMPARE(thread.thread, (QThread *)&thread); + QCOMPARE(thread.thread, static_cast<QThread *>(&thread)); } void tst_QThread::idealThreadCount() @@ -469,11 +474,65 @@ void tst_QThread::start() } } +class QThreadStarter : public QObject +{ + Q_OBJECT +public: + using QObject::QObject; +Q_SIGNALS: + void start(QThread::Priority); +}; + +class QThreadSelfStarter : public QThread +{ + Q_OBJECT +public: + using QThread::QThread; + + void check() + { + QVERIFY(connect(this, SIGNAL(starting(Priority)), + this, SLOT(start(Priority)))); + QVERIFY(QMetaObject::invokeMethod(this, "start", Q_ARG(Priority, IdlePriority))); + } + +Q_SIGNALS: + void starting(Priority); +}; + +void tst_QThread::startSlotUsedInStringBasedLookups() +{ + // QTBUG-124723 + + QThread thread; + { + QThreadStarter starter; + QVERIFY(QObject::connect(&starter, SIGNAL(start(QThread::Priority)), + &thread, SLOT(start(QThread::Priority)))); + } + { + QThreadSelfStarter selfStarter; + selfStarter.check(); + if (QTest::currentTestFailed()) + return; + selfStarter.exit(); + selfStarter.wait(30s); + } + QVERIFY(QMetaObject::invokeMethod(&thread, "start", + Q_ARG(QThread::Priority, QThread::IdlePriority))); + thread.exit(); + thread.wait(30s); +} + void tst_QThread::terminate() { #if defined(Q_OS_ANDROID) QSKIP("Thread termination is not supported on Android."); #endif +#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer) + QSKIP("Thread termination might result in stack underflow address sanitizer errors."); +#endif + Terminate_Thread thread; { QMutexLocker locker(&thread.mutex); @@ -540,6 +599,10 @@ void tst_QThread::terminated() #if defined(Q_OS_ANDROID) QSKIP("Thread termination is not supported on Android."); #endif +#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer) + QSKIP("Thread termination might result in stack underflow address sanitizer errors."); +#endif + SignalRecorder recorder; Terminate_Thread thread; connect(&thread, SIGNAL(finished()), &recorder, SLOT(slot()), Qt::DirectConnection); @@ -646,7 +709,7 @@ void noop(void*) { } class NativeThreadWrapper { public: - NativeThreadWrapper() : qthread(0), waitForStop(false) {} + NativeThreadWrapper() : qthread(nullptr), waitForStop(false) {} void start(FunctionPointer functionPointer = noop, void *data = nullptr); void startAndWait(FunctionPointer functionPointer = noop, void *data = nullptr); void join(); @@ -672,8 +735,8 @@ void NativeThreadWrapper::start(FunctionPointer functionPointer, void *data) this->functionPointer = functionPointer; this->data = data; #if defined Q_OS_UNIX - const int state = pthread_create(&nativeThreadHandle, 0, NativeThreadWrapper::runUnix, this); - Q_UNUSED(state); + const int state = pthread_create(&nativeThreadHandle, nullptr, NativeThreadWrapper::runUnix, this); + Q_UNUSED(state) #elif defined Q_OS_WIN unsigned thrdid = 0; nativeThreadHandle = (Qt::HANDLE) _beginthreadex(NULL, 0, NativeThreadWrapper::runWin, this, 0, &thrdid); @@ -690,7 +753,7 @@ void NativeThreadWrapper::startAndWait(FunctionPointer functionPointer, void *da void NativeThreadWrapper::join() { #if defined Q_OS_UNIX - pthread_join(nativeThreadHandle, 0); + pthread_join(nativeThreadHandle, nullptr); #elif defined Q_OS_WIN WaitForSingleObjectEx(nativeThreadHandle, INFINITE, FALSE); CloseHandle(nativeThreadHandle); @@ -720,7 +783,7 @@ void *NativeThreadWrapper::runUnix(void *that) nativeThreadWrapper->stopCondition.wait(lock.mutex()); } - return 0; + return nullptr; } unsigned WIN_FIX_STDCALL NativeThreadWrapper::runWin(void *data) @@ -736,12 +799,12 @@ void NativeThreadWrapper::stop() stopCondition.wakeOne(); } -bool threadAdoptedOk = false; -QThread *mainThread; +static bool threadAdoptedOk = false; +static QThread *mainThread; void testNativeThreadAdoption(void *) { - threadAdoptedOk = (QThread::currentThreadId() != 0 - && QThread::currentThread() != 0 + threadAdoptedOk = (QThread::currentThreadId() != nullptr + && QThread::currentThread() != nullptr && QThread::currentThread() != mainThread); } void tst_QThread::nativeThreadAdoption() @@ -769,14 +832,15 @@ void adoptedThreadAffinityFunction(void *arg) void tst_QThread::adoptedThreadAffinity() { - QThread *affinity[2] = { 0, 0 }; + QThread *affinity[2] = { nullptr, nullptr }; NativeThreadWrapper thread; thread.startAndWait(adoptedThreadAffinityFunction, affinity); thread.join(); - // adopted thread should have affinity to itself - QCOMPARE(affinity[0], affinity[1]); + // adopted thread (deleted) should have affinity to itself + QCOMPARE(static_cast<const void *>(affinity[0]), + static_cast<const void *>(affinity[1])); } void tst_QThread::adoptedThreadSetPriority() @@ -944,10 +1008,24 @@ void tst_QThread::adoptMultipleThreadsOverlap() QCOMPARE(recorder.activationCount.loadRelaxed(), numThreads); } +void tst_QThread::adoptedThreadBindingStatus() +{ + NativeThreadWrapper nativeThread; + nativeThread.setWaitForStop(); + + nativeThread.startAndWait(); + QVERIFY(nativeThread.qthread); + auto privThread = static_cast<QThreadPrivate *>(QObjectPrivate::get(nativeThread.qthread)); + QVERIFY(privThread->m_statusOrPendingObjects.bindingStatus()); + + nativeThread.stop(); + nativeThread.join(); +} + // Disconnects on WinCE void tst_QThread::stressTest() { - if (EmulationDetector::isRunningArmOnX86()) + if (QTestPrivate::isRunningArmOnX86()) QSKIP("Qemu uses too much memory for each thread. Test would run out of memory."); QElapsedTimer timer; @@ -1081,8 +1159,8 @@ void tst_QThread::wait2() qPrintable(msgElapsed(elapsed))); } - -class SlowSlotObject : public QObject { +class SlowSlotObject : public QObject +{ Q_OBJECT public: QMutex mutex; @@ -1098,22 +1176,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: + QScopeGuard wakeSlow([&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)); } @@ -1142,7 +1221,7 @@ void tst_QThread::startFinishRace() void run() override { i--; - if (!i) disconnect(this, SIGNAL(finished()), 0, 0); + if (!i) disconnect(this, SIGNAL(finished()), nullptr, nullptr); } int i; }; @@ -1208,13 +1287,10 @@ void tst_QThread::isRunningInFinished() } } -QT_BEGIN_NAMESPACE -Q_CORE_EXPORT uint qGlobalPostedEventsCount(); -QT_END_NAMESPACE - -class DummyEventDispatcher : public QAbstractEventDispatcher { +class DummyEventDispatcher : public QAbstractEventDispatcherV2 +{ + Q_OBJECT public: - DummyEventDispatcher() : QAbstractEventDispatcher() {} bool processEvents(QEventLoop::ProcessEventsFlags) override { visited.storeRelaxed(true); emit awake(); @@ -1223,11 +1299,19 @@ public: } void registerSocketNotifier(QSocketNotifier *) override {} void unregisterSocketNotifier(QSocketNotifier *) override {} - void registerTimer(int, qint64, Qt::TimerType, QObject *) override {} - bool unregisterTimer(int) override { return false; } + void registerTimer(Qt::TimerId id, Duration, Qt::TimerType, QObject *) override + { + if (registeredTimerId <= Qt::TimerId::Invalid) + registeredTimerId = id; + } + bool unregisterTimer(Qt::TimerId id) override + { + Qt::TimerId oldId = std::exchange(registeredTimerId, Qt::TimerId::Invalid); + return id == oldId; + } bool unregisterTimers(QObject *) override { return false; } - QList<TimerInfo> registeredTimers(QObject *) const override { return QList<TimerInfo>(); } - int remainingTime(int) override { return 0; } + QList<TimerInfoV2> timersForObject(QObject *) const override { return {}; } + Duration remainingTime(Qt::TimerId) const override { return 0s; } void wakeUp() override {} void interrupt() override {} @@ -1237,25 +1321,47 @@ public: #endif QBasicAtomicInt visited; // bool + Qt::TimerId registeredTimerId = Qt::TimerId::Invalid; }; -class ThreadObj : public QObject +struct ThreadLocalContent { - Q_OBJECT -public slots: - void visit() { - emit visited(); + static inline const QMetaObject *atStart; + static inline const QMetaObject *atEnd; + QSemaphore *sem; + QBasicTimer timer; + + ThreadLocalContent(QObject *obj, QSemaphore *sem) + : sem(sem) + { + ensureEventDispatcher(); + atStart = QAbstractEventDispatcher::instance()->metaObject(); + timer.start(10s, obj); + } + ~ThreadLocalContent() + { + ensureEventDispatcher(); + atEnd = QAbstractEventDispatcher::instance()->metaObject(); + timer.stop(); + sem->release(); + } + + void ensureEventDispatcher() + { + // QEventLoop's constructor has a call to QThreadData::ensureEventDispatcher() + QEventLoop dummy; } -signals: - void visited(); }; void tst_QThread::customEventDispatcher() { + ThreadLocalContent::atStart = ThreadLocalContent::atEnd = nullptr; + QThread thr; // there should be no ED yet QVERIFY(!thr.eventDispatcher()); DummyEventDispatcher *ed = new DummyEventDispatcher; + QPointer<DummyEventDispatcher> weak_ed(ed); thr.setEventDispatcher(ed); // the new ED should be set QCOMPARE(thr.eventDispatcher(), ed); @@ -1264,25 +1370,39 @@ void tst_QThread::customEventDispatcher() thr.start(); // start() should not overwrite the ED QCOMPARE(thr.eventDispatcher(), ed); + QVERIFY(!weak_ed.isNull()); - ThreadObj obj; + QObject obj; obj.moveToThread(&thr); // move was successful? QCOMPARE(obj.thread(), &thr); - QEventLoop loop; - connect(&obj, SIGNAL(visited()), &loop, SLOT(quit()), Qt::QueuedConnection); - QMetaObject::invokeMethod(&obj, "visit", Qt::QueuedConnection); - loop.exec(); + + QSemaphore threadLocalSemaphore; + QMetaObject::invokeMethod(&obj, [&]() { +#ifndef Q_OS_WIN + // On Windows, the thread_locals are unsequenced between DLLs, so this + // could run after QThreadPrivate::finish() + static thread_local +#endif + ThreadLocalContent d(&obj, &threadLocalSemaphore); + }, Qt::BlockingQueuedConnection); + // test that the ED has really been used QVERIFY(ed->visited.loadRelaxed()); + // and it's ours + QCOMPARE(ThreadLocalContent::atStart->className(), "DummyEventDispatcher"); - QPointer<DummyEventDispatcher> weak_ed(ed); QVERIFY(!weak_ed.isNull()); thr.quit(); + // wait for thread to be stopped QVERIFY(thr.wait(30000)); + QVERIFY(threadLocalSemaphore.tryAcquire(1, 30s)); + // test that ED has been deleted QVERIFY(weak_ed.isNull()); + // test that ED was ours + QCOMPARE(ThreadLocalContent::atEnd->className(), "DummyEventDispatcher"); } class Job : public QObject @@ -1338,9 +1458,6 @@ void tst_QThread::quitLock() void tst_QThread::create() { -#if !QT_CONFIG(cxx11_future) - QSKIP("This test requires QThread::create"); -#else { const auto &function = [](){}; QScopedPointer<QThread> thread(QThread::create(function)); @@ -1576,11 +1693,70 @@ void tst_QThread::create() const auto &function = [](const ThrowWhenCopying &){}; QScopedPointer<QThread> thread; ThrowWhenCopying t; - QVERIFY_EXCEPTION_THROWN(thread.reset(QThread::create(function, t)), ThreadException); + QVERIFY_THROWS_EXCEPTION(ThreadException, thread.reset(QThread::create(function, t))); QVERIFY(!thread); } #endif // QT_NO_EXCEPTIONS -#endif // QT_CONFIG(cxx11_future) +} + +void tst_QThread::createDestruction() +{ + for (int delay : {0, 10, 20}) { + auto checkForInterruptions = []() { + for (;;) { + if (QThread::currentThread()->isInterruptionRequested()) + return; + QThread::sleep(1ms); + } + }; + + QScopedPointer<QThread> thread(QThread::create(checkForInterruptions)); + QSignalSpy finishedSpy(thread.get(), &QThread::finished); + QVERIFY(finishedSpy.isValid()); + + thread->start(); + if (delay) + QThread::msleep(delay); + thread.reset(); + + QCOMPARE(finishedSpy.size(), 1); + } + + for (int delay : {0, 10, 20}) { + auto runEventLoop = []() { + QEventLoop loop; + loop.exec(); + }; + + QScopedPointer<QThread> thread(QThread::create(runEventLoop)); + QSignalSpy finishedSpy(thread.get(), &QThread::finished); + QVERIFY(finishedSpy.isValid()); + + thread->start(); + if (delay) + QThread::msleep(delay); + thread.reset(); + + QCOMPARE(finishedSpy.size(), 1); + } + + for (int delay : {0, 10, 20}) { + auto runEventLoop = [delay]() { + if (delay) + QThread::msleep(delay); + QEventLoop loop; + loop.exec(); + }; + + QScopedPointer<QThread> thread(QThread::create(runEventLoop)); + QSignalSpy finishedSpy(thread.get(), &QThread::finished); + QVERIFY(finishedSpy.isValid()); + + thread->start(); + thread.reset(); + + QCOMPARE(finishedSpy.size(), 1); + } } class StopableJob : public QObject @@ -1621,5 +1797,155 @@ 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() +{ + // It's important that those thread ID's are not accessed concurrently + Qt::HANDLE threadId1; + + auto thread1Fn = [&threadId1]() -> void { threadId1 = QThread::currentThreadId(); }; + QScopedPointer<QThread> thread1(QThread::create(thread1Fn)); + 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::sleep(1ms); + + Qt::HANDLE threadId2; + bool waitOk = false; + + auto waitForThread1 = [&thread1, &threadId2, &waitOk]() -> void { + threadId2 = QThread::currentThreadId(); + waitOk = thread1->wait(); + }; + + QScopedPointer<QThread> thread2(QThread::create(waitForThread1)); + thread2->start(); + QVERIFY(thread2->wait()); + QVERIFY(waitOk); + + if (threadId1 == threadId2) { + qDebug("Thread ID reused at iteration %d", i); + threadIdReused = true; + break; + } + } + + if (!threadIdReused) { + QSKIP("Thread ID was not reused"); + } +} + +class WaitToRun_Thread : public QThread +{ + Q_OBJECT +public: + void run() override + { + emit running(); + QThread::exec(); + } + +Q_SIGNALS: + void running(); +}; + + +void tst_QThread::terminateAndPrematureDestruction() +{ +#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer) + QSKIP("Thread termination might result in stack underflow address sanitizer errors."); +#endif + + WaitToRun_Thread thread; + QSignalSpy spy(&thread, &WaitToRun_Thread::running); + thread.start(); + QVERIFY(spy.wait(500)); + + QScopedPointer<QObject> obj(new QObject); + QPointer<QObject> pObj(obj.data()); + obj->deleteLater(); + + thread.terminate(); + QVERIFY2(pObj, "object was deleted prematurely!"); + thread.wait(500); +} + +void tst_QThread::terminateAndDoubleDestruction() +{ +#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer) + QSKIP("Thread termination might result in stack underflow address sanitizer errors."); +#endif + + class ChildObject : public QObject + { + public: + ChildObject(QObject *parent) + : QObject(parent) + { + QSignalSpy spy(&thread, &WaitToRun_Thread::running); + thread.start(); + spy.wait(500); + } + + ~ChildObject() + { + QVERIFY2(!inDestruction, "Double object destruction!"); + inDestruction = true; + thread.terminate(); + thread.wait(500); + } + + bool inDestruction = false; + WaitToRun_Thread thread; + }; + + class TestObject : public QObject + { + public: + TestObject() + : child(new ChildObject(this)) + { + } + + ~TestObject() + { + child->deleteLater(); + } + + ChildObject *child = nullptr; + }; + + TestObject obj; +} + +void tst_QThread::bindingListCleanupAfterDelete() +{ + QThread t; + auto optr = std::make_unique<QObject>(); + optr->moveToThread(&t); + auto threadPriv = static_cast<QThreadPrivate *>(QObjectPrivate::get(&t)); + auto list = threadPriv->m_statusOrPendingObjects.list(); + QVERIFY(list); + optr.reset(); + QVERIFY(list->empty()); +} + QTEST_MAIN(tst_QThread) #include "tst_qthread.moc" |