diff options
Diffstat (limited to 'tests/auto/corelib/thread')
102 files changed, 8046 insertions, 2297 deletions
diff --git a/tests/auto/corelib/thread/CMakeLists.txt b/tests/auto/corelib/thread/CMakeLists.txt new file mode 100644 index 0000000000..d25d0205f5 --- /dev/null +++ b/tests/auto/corelib/thread/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(QT_BUILD_WASM_BATCHED_TESTS) # not all tests currently work in WebAssembly + add_subdirectory(qatomicint) + add_subdirectory(qatomicinteger) + add_subdirectory(qatomicpointer) + add_subdirectory(qfuturesynchronizer) + add_subdirectory(qmutexlocker) + add_subdirectory(qreadlocker) + add_subdirectory(qresultstore) + add_subdirectory(qwritelocker) + return() +endif() + +if(QT_FEATURE_thread) + add_subdirectory(qatomicint) + add_subdirectory(qatomicinteger) + add_subdirectory(qatomicpointer) + if(QT_FEATURE_future) + if(QT_FEATURE_concurrent AND NOT INTEGRITY) + add_subdirectory(qfuture) + endif() + add_subdirectory(qresultstore) + add_subdirectory(qfuturesynchronizer) + if(NOT INTEGRITY) + add_subdirectory(qpromise) + endif() + # QTBUG-87431 + if(TARGET Qt::Concurrent AND NOT INTEGRITY) + add_subdirectory(qfuturewatcher) + endif() + endif() + add_subdirectory(qmutex) + add_subdirectory(qmutexlocker) + add_subdirectory(qreadlocker) + add_subdirectory(qreadwritelock) + add_subdirectory(qsemaphore) + # QTBUG-85364 + if(NOT CMAKE_CROSSCOMPILING) + add_subdirectory(qthread) + endif() + add_subdirectory(qthreadonce) + add_subdirectory(qthreadpool) + add_subdirectory(qthreadstorage) + add_subdirectory(qwaitcondition) + add_subdirectory(qwritelocker) +endif() + diff --git a/tests/auto/corelib/thread/qatomicint/CMakeLists.txt b/tests/auto/corelib/thread/qatomicint/CMakeLists.txt new file mode 100644 index 0000000000..239f3cce87 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicint/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicint Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicint LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicint + SOURCES + tst_qatomicint.cpp +) diff --git a/tests/auto/corelib/thread/qatomicint/qatomicint.pro b/tests/auto/corelib/thread/qatomicint/qatomicint.pro deleted file mode 100644 index 89ac465e81..0000000000 --- a/tests/auto/corelib/thread/qatomicint/qatomicint.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qatomicint -QT = core testlib -SOURCES = tst_qatomicint.cpp diff --git a/tests/auto/corelib/thread/qatomicint/tst_qatomicint.cpp b/tests/auto/corelib/thread/qatomicint/tst_qatomicint.cpp index cc197cabba..63cb494c11 100644 --- a/tests/auto/corelib/thread/qatomicint/tst_qatomicint.cpp +++ b/tests/auto/corelib/thread/qatomicint/tst_qatomicint.cpp @@ -1,36 +1,12 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2016 Intel Corporation. -** 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) 2016 The Qt Company Ltd. +// Copyright (C) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <QAtomicInt> #include <QCoreApplication> +#include <QElapsedTimer> #include <limits.h> @@ -84,7 +60,7 @@ private: }; template <int I> -static inline void assemblyMarker(void *ptr = 0) +static inline void assemblyMarker(void *ptr = nullptr) { puts((char *)ptr + I); } @@ -104,12 +80,12 @@ static void warningFreeHelperTemplate() assemblyMarker<1>(&i); // the loads sometimes generate no assembly output - i.load(); + i.loadRelaxed(); assemblyMarker<11>(&i); i.loadAcquire(); assemblyMarker<12>(&i); - i.store(newValue); + i.storeRelaxed(newValue); assemblyMarker<21>(&i); i.storeRelease(newValue); assemblyMarker<22>(&i); @@ -153,17 +129,11 @@ template <bool> inline void booleanHelper() template <typename Atomic> static void constexprFunctionsHelperTemplate() { -#ifdef Q_COMPILER_CONSTEXPR // this is a compile-time test only - booleanHelper<Atomic::isReferenceCountingNative()>(); booleanHelper<Atomic::isReferenceCountingWaitFree()>(); - booleanHelper<Atomic::isTestAndSetNative()>(); booleanHelper<Atomic::isTestAndSetWaitFree()>(); - booleanHelper<Atomic::isFetchAndStoreNative()>(); booleanHelper<Atomic::isFetchAndStoreWaitFree()>(); - booleanHelper<Atomic::isFetchAndAddNative()>(); booleanHelper<Atomic::isFetchAndAddWaitFree()>(); -#endif } void tst_QAtomicInt::warningFreeHelper() @@ -176,10 +146,8 @@ void tst_QAtomicInt::warningFreeHelper() warningFreeHelperTemplate<unsigned int, QBasicAtomicInteger<unsigned int> >(); constexprFunctionsHelperTemplate<QBasicAtomicInteger<int> >(); constexprFunctionsHelperTemplate<QBasicAtomicInteger<unsigned int> >(); -# ifdef Q_COMPILER_UNICODE_STRINGS warningFreeHelperTemplate<qint16, QBasicAtomicInteger<char32_t> >(); constexprFunctionsHelperTemplate<QBasicAtomicInteger<char32_t> >(); -# endif // pointer-sized integers are always supported: warningFreeHelperTemplate<int, QBasicAtomicInteger<qptrdiff> >(); @@ -193,25 +161,19 @@ void tst_QAtomicInt::warningFreeHelper() constexprFunctionsHelperTemplate<QBasicAtomicInteger<long int> >(); constexprFunctionsHelperTemplate<QBasicAtomicInteger<unsigned long int> >(); -#ifdef Q_ATOMIC_INT16_IS_SUPPORTED warningFreeHelperTemplate<qint16, QBasicAtomicInteger<qint16> >(); warningFreeHelperTemplate<quint16, QBasicAtomicInteger<quint16> >(); constexprFunctionsHelperTemplate<QBasicAtomicInteger<qint16> >(); constexprFunctionsHelperTemplate<QBasicAtomicInteger<quint16> >(); -# ifdef Q_COMPILER_UNICODE_STRINGS warningFreeHelperTemplate<qint16, QBasicAtomicInteger<char16_t> >(); constexprFunctionsHelperTemplate<QBasicAtomicInteger<char16_t> >(); -# endif -#endif -#ifdef Q_ATOMIC_INT8_IS_SUPPORTED warningFreeHelperTemplate<char, QBasicAtomicInteger<char> >(); warningFreeHelperTemplate<signed char, QBasicAtomicInteger<signed char> >(); warningFreeHelperTemplate<unsigned char, QBasicAtomicInteger<unsigned char> >(); constexprFunctionsHelperTemplate<QBasicAtomicInteger<char> >(); constexprFunctionsHelperTemplate<QBasicAtomicInteger<signed char> >(); constexprFunctionsHelperTemplate<QBasicAtomicInteger<unsigned char> >(); -#endif #ifdef Q_ATOMIC_INT64_IS_SUPPORTED #if !defined(__i386__) || (defined(Q_CC_GNU) && defined(__OPTIMIZE__)) @@ -236,28 +198,16 @@ template <typename T> struct TypeInStruct { T type; }; void tst_QAtomicInt::alignment() { -#ifdef Q_ALIGNOF - // this will cause a build error if the alignment isn't the same - char dummy1[Q_ALIGNOF(QBasicAtomicInt) == Q_ALIGNOF(TypeInStruct<int>) ? 1 : -1]; - char dummy2[Q_ALIGNOF(QAtomicInt) == Q_ALIGNOF(TypeInStruct<int>) ? 1 : -1]; - (void)dummy1; (void)dummy2; + static_assert(alignof(QBasicAtomicInt) == alignof(TypeInStruct<int>)); + static_assert(alignof(QBasicAtomicInt) == alignof(TypeInStruct<int>)); -#ifdef Q_ATOMIC_INT32_IS_SUPPORTED - QCOMPARE(Q_ALIGNOF(QBasicAtomicInteger<int>), Q_ALIGNOF(TypeInStruct<int>)); -#endif - -#ifdef Q_ATOMIC_INT16_IS_SUPPORTED - QCOMPARE(Q_ALIGNOF(QBasicAtomicInteger<short>), Q_ALIGNOF(TypeInStruct<short>)); -#endif - -#ifdef Q_ATOMIC_INT8_IS_SUPPORTED - QCOMPARE(Q_ALIGNOF(QBasicAtomicInteger<char>), Q_ALIGNOF(TypeInStruct<char>)); -#endif - -#ifdef Q_ATOMIC_INT64_IS_SUPPORTED - QCOMPARE(Q_ALIGNOF(QBasicAtomicInteger<qlonglong>), Q_ALIGNOF(TypeInStruct<qlonglong>)); -#endif + QCOMPARE(alignof(QBasicAtomicInteger<int>), alignof(TypeInStruct<int>)); + QCOMPARE(alignof(QBasicAtomicInteger<short>), alignof(TypeInStruct<short>)); + QCOMPARE(alignof(QBasicAtomicInteger<char>), alignof(TypeInStruct<char>)); +#if !defined(Q_PROCESSOR_X86_32) && defined(Q_ATOMIC_INT64_IS_SUPPORTED) + // The alignment is different on x86_32 + QCOMPARE(alignof(QBasicAtomicInteger<qlonglong>), alignof(TypeInStruct<qlonglong>)); #endif } @@ -281,9 +231,9 @@ void tst_QAtomicInt::constructor() { QFETCH(int, value); QAtomicInt atomic1(value); - QCOMPARE(atomic1.load(), value); + QCOMPARE(atomic1.loadRelaxed(), value); QAtomicInt atomic2 = value; - QCOMPARE(atomic2.load(), value); + QCOMPARE(atomic2.loadRelaxed(), value); } void tst_QAtomicInt::copy_constructor_data() @@ -293,16 +243,16 @@ void tst_QAtomicInt::copy_constructor() { QFETCH(int, value); QAtomicInt atomic1(value); - QCOMPARE(atomic1.load(), value); + QCOMPARE(atomic1.loadRelaxed(), value); QAtomicInt atomic2(atomic1); - QCOMPARE(atomic2.load(), value); + QCOMPARE(atomic2.loadRelaxed(), value); QAtomicInt atomic3 = atomic1; - QCOMPARE(atomic3.load(), value); + QCOMPARE(atomic3.loadRelaxed(), value); QAtomicInt atomic4(atomic2); - QCOMPARE(atomic4.load(), value); + QCOMPARE(atomic4.loadRelaxed(), value); QAtomicInt atomic5 = atomic2; - QCOMPARE(atomic5.load(), value); + QCOMPARE(atomic5.loadRelaxed(), value); } void tst_QAtomicInt::assignment_operator_data() @@ -326,13 +276,13 @@ void tst_QAtomicInt::assignment_operator() { QAtomicInt atomic1 = value; atomic1 = newval; - QCOMPARE(atomic1.load(), newval); + QCOMPARE(atomic1.loadRelaxed(), newval); atomic1 = value; - QCOMPARE(atomic1.load(), value); + QCOMPARE(atomic1.loadRelaxed(), value); QAtomicInt atomic2 = newval; atomic1 = atomic2; - QCOMPARE(atomic1.load(), atomic2.load()); + QCOMPARE(atomic1.loadRelaxed(), atomic2.loadRelaxed()); } } @@ -400,7 +350,7 @@ void tst_QAtomicInt::ref() QFETCH(int, value); QAtomicInt x = value; QTEST(x.ref() ? 1 : 0, "result"); - QTEST(x.load(), "expected"); + QTEST(x.loadRelaxed(), "expected"); } void tst_QAtomicInt::deref_data() @@ -419,7 +369,7 @@ void tst_QAtomicInt::deref() QFETCH(int, value); QAtomicInt x = value; QTEST(x.deref() ? 1 : 0, "result"); - QTEST(x.load(), "expected"); + QTEST(x.loadRelaxed(), "expected"); } void tst_QAtomicInt::isTestAndSetNative() @@ -531,7 +481,6 @@ void tst_QAtomicInt::testAndSet() QTEST(atomic.testAndSetOrdered(expected, newval), "result"); } -#ifdef Q_ATOMIC_INT32_IS_SUPPORTED QFETCH(bool, result); // the new implementation has the version that loads the current value @@ -566,7 +515,6 @@ void tst_QAtomicInt::testAndSet() if (!result) QCOMPARE(currentval, value); } -#endif } void tst_QAtomicInt::isFetchAndStoreNative() @@ -635,25 +583,25 @@ void tst_QAtomicInt::fetchAndStore() { QAtomicInt atomic = value; QCOMPARE(atomic.fetchAndStoreRelaxed(newval), value); - QCOMPARE(atomic.load(), newval); + QCOMPARE(atomic.loadRelaxed(), newval); } { QAtomicInt atomic = value; QCOMPARE(atomic.fetchAndStoreAcquire(newval), value); - QCOMPARE(atomic.load(), newval); + QCOMPARE(atomic.loadRelaxed(), newval); } { QAtomicInt atomic = value; QCOMPARE(atomic.fetchAndStoreRelease(newval), value); - QCOMPARE(atomic.load(), newval); + QCOMPARE(atomic.loadRelaxed(), newval); } { QAtomicInt atomic = value; QCOMPARE(atomic.fetchAndStoreOrdered(newval), value); - QCOMPARE(atomic.load(), newval); + QCOMPARE(atomic.loadRelaxed(), newval); } } @@ -728,7 +676,6 @@ void tst_QAtomicInt::fetchAndAdd_data() QTest::newRow("7272+2181") << 7272 << 2181; QTest::newRow("0+-1") << 0 << -1; - QTest::newRow("1+0") << 1 << 0; QTest::newRow("1+-2") << 1 << -2; QTest::newRow("2+-1") << 2 << -1; QTest::newRow("10+-21") << 10 << -21; @@ -744,7 +691,6 @@ void tst_QAtomicInt::fetchAndAdd_data() QTest::newRow("5451+-4362") << 5451 << -4362; QTest::newRow("7272+-2181") << 7272 << -2181; - QTest::newRow("0+1") << 0 << 1; QTest::newRow("-1+0") << -1 << 0; QTest::newRow("-1+2") << -1 << 2; QTest::newRow("-2+1") << -2 << 1; @@ -772,28 +718,28 @@ void tst_QAtomicInt::fetchAndAdd() QAtomicInt atomic = value1; result = atomic.fetchAndAddRelaxed(value2); QCOMPARE(result, value1); - QCOMPARE(atomic.load(), value1 + value2); + QCOMPARE(atomic.loadRelaxed(), value1 + value2); } { QAtomicInt atomic = value1; result = atomic.fetchAndAddAcquire(value2); QCOMPARE(result, value1); - QCOMPARE(atomic.load(), value1 + value2); + QCOMPARE(atomic.loadRelaxed(), value1 + value2); } { QAtomicInt atomic = value1; result = atomic.fetchAndAddRelease(value2); QCOMPARE(result, value1); - QCOMPARE(atomic.load(), value1 + value2); + QCOMPARE(atomic.loadRelaxed(), value1 + value2); } { QAtomicInt atomic = value1; result = atomic.fetchAndAddOrdered(value2); QCOMPARE(result, value1); - QCOMPARE(atomic.load(), value1 + value2); + QCOMPARE(atomic.loadRelaxed(), value1 + value2); } } @@ -844,21 +790,24 @@ void tst_QAtomicInt::operators() QCOMPARE(int(atomic), x); QCOMPARE(int(atomic), 0x13); +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wself-assign-overloaded") x = (atomic ^= atomic); QCOMPARE(int(atomic), x); QCOMPARE(int(atomic), 0); +QT_WARNING_POP } void tst_QAtomicInt::testAndSet_loop() { - QTime stopWatch; + QElapsedTimer stopWatch; stopWatch.start(); int iterations = 10000000; QAtomicInt val=0; for (int i = 0; i < iterations; ++i) { - int v = val.load(); + int v = val.loadRelaxed(); QVERIFY(val.testAndSetRelaxed(v, v+1)); if ((i % 1000) == 999) { if (stopWatch.elapsed() > 60 * 1000) { @@ -881,14 +830,14 @@ void tst_QAtomicInt::fetchAndAdd_loop() QAtomicInt val=0; for (int i = 0; i < iterations; ++i) { const int prev = val.fetchAndAddRelaxed(1); - QCOMPARE(prev, val.load() -1); + QCOMPARE(prev, val.loadRelaxed() -1); } } class FetchAndAddThread : public QThread { public: - void run() + void run() override { for (int i = 0; i < iterations; ++i) @@ -919,7 +868,7 @@ void tst_QAtomicInt::fetchAndAdd_threadedLoop() t1.wait(); t2.wait(); - QCOMPARE(val.load(), 0); + QCOMPARE(val.loadRelaxed(), 0); } QTEST_MAIN(tst_QAtomicInt) diff --git a/tests/auto/corelib/thread/qatomicinteger/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/CMakeLists.txt new file mode 100644 index 0000000000..03a6323a1f --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(char) +add_subdirectory(char16_t) +add_subdirectory(char32_t) +add_subdirectory(int) +add_subdirectory(long) +add_subdirectory(qlonglong) +add_subdirectory(qptrdiff) +add_subdirectory(quintptr) +add_subdirectory(qulonglong) +add_subdirectory(schar) +add_subdirectory(short) +add_subdirectory(uchar) +add_subdirectory(uint) +add_subdirectory(ulong) +add_subdirectory(ushort) +add_subdirectory(wchar_t) diff --git a/tests/auto/corelib/thread/qatomicinteger/char/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/char/CMakeLists.txt new file mode 100644 index 0000000000..882a9298f6 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/char/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_char Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_char LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_char + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=char + tst_QAtomicIntegerXX=tst_QAtomicInteger_char +) diff --git a/tests/auto/corelib/thread/qatomicinteger/char/char.pro b/tests/auto/corelib/thread/qatomicinteger/char/char.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/char/char.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/char16_t/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/char16_t/CMakeLists.txt new file mode 100644 index 0000000000..8e53b59689 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/char16_t/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_char16_t Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_char16_t LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_char16_t + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=char16_t + tst_QAtomicIntegerXX=tst_QAtomicInteger_char16_t +) diff --git a/tests/auto/corelib/thread/qatomicinteger/char16_t/char16_t.pro b/tests/auto/corelib/thread/qatomicinteger/char16_t/char16_t.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/char16_t/char16_t.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/char32_t/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/char32_t/CMakeLists.txt new file mode 100644 index 0000000000..5881d475f4 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/char32_t/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_char32_t Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_char32_t LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_char32_t + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=char32_t + tst_QAtomicIntegerXX=tst_QAtomicInteger_char32_t +) diff --git a/tests/auto/corelib/thread/qatomicinteger/char32_t/char32_t.pro b/tests/auto/corelib/thread/qatomicinteger/char32_t/char32_t.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/char32_t/char32_t.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/int/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/int/CMakeLists.txt new file mode 100644 index 0000000000..0915e77a8d --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/int/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_int Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_int LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_int + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=int + tst_QAtomicIntegerXX=tst_QAtomicInteger_int +) diff --git a/tests/auto/corelib/thread/qatomicinteger/int/int.pro b/tests/auto/corelib/thread/qatomicinteger/int/int.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/int/int.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/long/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/long/CMakeLists.txt new file mode 100644 index 0000000000..adf6638bfa --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/long/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_long Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_long LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_long + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=long + tst_QAtomicIntegerXX=tst_QAtomicInteger_long +) diff --git a/tests/auto/corelib/thread/qatomicinteger/long/long.pro b/tests/auto/corelib/thread/qatomicinteger/long/long.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/long/long.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/qatomicinteger.pri b/tests/auto/corelib/thread/qatomicinteger/qatomicinteger.pri deleted file mode 100644 index f1030d41ef..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/qatomicinteger.pri +++ /dev/null @@ -1,11 +0,0 @@ -# Get our build type from the directory name -TYPE = $$basename(_PRO_FILE_PWD_) -dn = $$dirname(_PRO_FILE_PWD_) -FORCE = $$basename(dn) -suffix = $$TYPE - -CONFIG += testcase -QT = core testlib -TARGET = tst_qatomicinteger_$$lower($$suffix) -SOURCES = $$PWD/tst_qatomicinteger.cpp -DEFINES += QATOMIC_TEST_TYPE=$$TYPE tst_QAtomicIntegerXX=tst_QAtomicInteger_$$suffix diff --git a/tests/auto/corelib/thread/qatomicinteger/qatomicinteger.pro b/tests/auto/corelib/thread/qatomicinteger/qatomicinteger.pro deleted file mode 100644 index 09458bd9c3..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/qatomicinteger.pro +++ /dev/null @@ -1,18 +0,0 @@ -TEMPLATE=subdirs -SUBDIRS=\ - char \ - char16_t \ - char32_t \ - int \ - long \ - qlonglong \ - qptrdiff \ - quintptr \ - qulonglong \ - schar \ - short \ - uchar \ - uint \ - ulong \ - ushort \ - wchar_t \ diff --git a/tests/auto/corelib/thread/qatomicinteger/qlonglong/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/qlonglong/CMakeLists.txt new file mode 100644 index 0000000000..2ec977d7cb --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/qlonglong/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_qlonglong Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_qlonglong LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_qlonglong + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=qlonglong + tst_QAtomicIntegerXX=tst_QAtomicInteger_qlonglong +) diff --git a/tests/auto/corelib/thread/qatomicinteger/qlonglong/qlonglong.pro b/tests/auto/corelib/thread/qatomicinteger/qlonglong/qlonglong.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/qlonglong/qlonglong.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/qptrdiff/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/qptrdiff/CMakeLists.txt new file mode 100644 index 0000000000..a2450931d5 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/qptrdiff/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_qptrdiff Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_qptrdiff LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_qptrdiff + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=qptrdiff + tst_QAtomicIntegerXX=tst_QAtomicInteger_qptrdiff +) diff --git a/tests/auto/corelib/thread/qatomicinteger/qptrdiff/qptrdiff.pro b/tests/auto/corelib/thread/qatomicinteger/qptrdiff/qptrdiff.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/qptrdiff/qptrdiff.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/quintptr/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/quintptr/CMakeLists.txt new file mode 100644 index 0000000000..98302b5d07 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/quintptr/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_quintptr Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_quintptr LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_quintptr + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=quintptr + tst_QAtomicIntegerXX=tst_QAtomicInteger_quintptr +) diff --git a/tests/auto/corelib/thread/qatomicinteger/quintptr/quintptr.pro b/tests/auto/corelib/thread/qatomicinteger/quintptr/quintptr.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/quintptr/quintptr.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/qulonglong/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/qulonglong/CMakeLists.txt new file mode 100644 index 0000000000..13acfc3e2b --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/qulonglong/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_qulonglong Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_qulonglong LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_qulonglong + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=qulonglong + tst_QAtomicIntegerXX=tst_QAtomicInteger_qulonglong +) diff --git a/tests/auto/corelib/thread/qatomicinteger/qulonglong/qulonglong.pro b/tests/auto/corelib/thread/qatomicinteger/qulonglong/qulonglong.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/qulonglong/qulonglong.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/schar/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/schar/CMakeLists.txt new file mode 100644 index 0000000000..127f752cc2 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/schar/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_schar Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_schar LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_schar + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=schar + tst_QAtomicIntegerXX=tst_QAtomicInteger_schar +) diff --git a/tests/auto/corelib/thread/qatomicinteger/schar/schar.pro b/tests/auto/corelib/thread/qatomicinteger/schar/schar.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/schar/schar.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/short/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/short/CMakeLists.txt new file mode 100644 index 0000000000..df9d2af4c3 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/short/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_short Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_short LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_short + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=short + tst_QAtomicIntegerXX=tst_QAtomicInteger_short +) diff --git a/tests/auto/corelib/thread/qatomicinteger/short/short.pro b/tests/auto/corelib/thread/qatomicinteger/short/short.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/short/short.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/tst_qatomicinteger.cpp b/tests/auto/corelib/thread/qatomicinteger/tst_qatomicinteger.cpp index 32e5b8ee56..d1a8a8f729 100644 --- a/tests/auto/corelib/thread/qatomicinteger/tst_qatomicinteger.cpp +++ b/tests/auto/corelib/thread/qatomicinteger/tst_qatomicinteger.cpp @@ -1,62 +1,19 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Intel Corporation. -** 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$ -** -****************************************************************************/ - -#ifdef QT_ATOMIC_FORCE_CXX11 -// We need to check if this compiler has C++11 atomics and constexpr support. -// We can't rely on qcompilerdetection.h because it forces all of qglobal.h to -// be included, which causes qbasicatomic.h to be included too. -// Incomplete, but ok -# if defined(__INTEL_COMPILER) && __INTEL_COMPILER >= 1500 && (__cplusplus >= 201103L || defined(__INTEL_CXX11_MODE__)) -# elif defined(__clang__) && (__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)) -# if !__has_feature(cxx_constexpr) || !__has_feature(cxx_atomic) || !__has_include(<atomic>) -# undef QT_ATOMIC_FORCE_CXX11 -# endif -# elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && (__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)) -# elif defined(_MSC_VER) - // We need MSVC 2015 because of: atomics (2012), constexpr (2015), and unrestricted unions (2015). - // Support for constexpr is not working completely on MSVC 2015 but it's enough for the test. -# else -# undef QT_ATOMIC_FORCE_CXX11 -# endif - -# ifndef QT_ATOMIC_FORCE_CXX11 -# undef QATOMIC_TEST_TYPE -# define QATOMIC_TEST_TYPE unsupported -# endif -#endif +// Copyright (C) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include <QtTest> +#include <QTest> #include <QAtomicInt> #include <limits> #include <limits.h> #include <wchar.h> +#if !defined(Q_ATOMIC_INT8_IS_SUPPORTED) +# error "QAtomicInteger for 8-bit types must be supported!" +#endif +#if !defined(Q_ATOMIC_INT16_IS_SUPPORTED) +# error "QAtomicInteger for 16-bit types must be supported!" +#endif #if !defined(Q_ATOMIC_INT32_IS_SUPPORTED) # error "QAtomicInteger for 32-bit types must be supported!" #endif @@ -65,35 +22,21 @@ #endif // always supported types: +#define TYPE_SUPPORTED_char 1 +#define TYPE_SUPPORTED_uchar 1 +#define TYPE_SUPPORTED_schar 1 +#define TYPE_SUPPORTED_short 1 +#define TYPE_SUPPORTED_ushort 1 +#define TYPE_SUPPORTED_char16_t 1 +#define TYPE_SUPPORTED_wchar_t 1 #define TYPE_SUPPORTED_int 1 #define TYPE_SUPPORTED_uint 1 #define TYPE_SUPPORTED_long 1 #define TYPE_SUPPORTED_ulong 1 #define TYPE_SUPPORTED_qptrdiff 1 #define TYPE_SUPPORTED_quintptr 1 -#if (defined(__SIZEOF_WCHAR_T__) && (__SIZEOF_WCHAR_T__-0) > 2) \ - || (defined(WCHAR_MAX) && (WCHAR_MAX-0 > 0x10000)) -# define TYPE_SUPPORTED_wchar_t 1 -#endif -#ifdef Q_COMPILER_UNICODE_STRINGS -# define TYPE_SUPPORTED_char32_t 1 -#endif +#define TYPE_SUPPORTED_char32_t 1 -#ifdef Q_ATOMIC_INT8_IS_SUPPORTED -# define TYPE_SUPPORTED_char 1 -# define TYPE_SUPPORTED_uchar 1 -# define TYPE_SUPPORTED_schar 1 -#endif -#ifdef Q_ATOMIC_INT16_IS_SUPPORTED -# define TYPE_SUPPORTED_short 1 -# define TYPE_SUPPORTED_ushort 1 -# ifdef Q_COMPILER_UNICODE_STRINGS -# define TYPE_SUPPORTED_char16_t 1 -# endif -# ifndef TYPE_SUPPORTED_wchar_t -# define TYPE_SUPPORTED_wchar_t 1 -# endif -#endif #ifdef Q_ATOMIC_INT64_IS_SUPPORTED # define TYPE_SUPPORTED_qlonglong 1 # define TYPE_SUPPORTED_qulonglong 1 @@ -121,6 +64,7 @@ typedef signed char schar; typedef TEST_TYPE Type; typedef Type T; // shorthand +using U = std::make_unsigned_t<T>; enum { TypeIsUnsigned = Type(-1) > Type(0), TypeIsSigned = !TypeIsUnsigned @@ -131,6 +75,8 @@ template <> struct LargeIntTemplate<true> { typedef quint64 Type; }; template <> struct LargeIntTemplate<false> { typedef qint64 Type; }; typedef LargeIntTemplate<TypeIsUnsigned>::Type LargeInt; +namespace { + class tst_QAtomicIntegerXX : public QObject { Q_OBJECT @@ -188,7 +134,7 @@ template <bool> inline void booleanHelper() { } void tst_QAtomicIntegerXX::static_checks() { - Q_STATIC_ASSERT(sizeof(QAtomicInteger<T>) == sizeof(T)); + static_assert(sizeof(QAtomicInteger<T>) == sizeof(T)); // statements with no effect (void) QAtomicInteger<T>::isReferenceCountingNative(); @@ -200,17 +146,11 @@ void tst_QAtomicIntegerXX::static_checks() (void) QAtomicInteger<T>::isFetchAndAddNative(); (void) QAtomicInteger<T>::isFetchAndAddWaitFree(); -#ifdef Q_COMPILER_CONSTEXPR // this is a compile-time test only - booleanHelper<QAtomicInteger<T>::isReferenceCountingNative()>(); booleanHelper<QAtomicInteger<T>::isReferenceCountingWaitFree()>(); - booleanHelper<QAtomicInteger<T>::isTestAndSetNative()>(); booleanHelper<QAtomicInteger<T>::isTestAndSetWaitFree()>(); - booleanHelper<QAtomicInteger<T>::isFetchAndStoreNative()>(); booleanHelper<QAtomicInteger<T>::isFetchAndStoreWaitFree()>(); - booleanHelper<QAtomicInteger<T>::isFetchAndAddNative()>(); booleanHelper<QAtomicInteger<T>::isFetchAndAddWaitFree()>(); -#endif } void tst_QAtomicIntegerXX::addData() @@ -270,13 +210,13 @@ void tst_QAtomicIntegerXX::constructor() QFETCH(LargeInt, value); QAtomicInteger<T> atomic(value); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QAtomicInteger<T> atomic2 = value; - QCOMPARE(atomic2.load(), T(value)); + QCOMPARE(atomic2.loadRelaxed(), T(value)); - QVERIFY(atomic.load() >= std::numeric_limits<T>::min()); - QVERIFY(atomic.load() <= std::numeric_limits<T>::max()); + QVERIFY(atomic.loadRelaxed() >= std::numeric_limits<T>::min()); + QVERIFY(atomic.loadRelaxed() <= std::numeric_limits<T>::max()); } void tst_QAtomicIntegerXX::copy() @@ -285,17 +225,17 @@ void tst_QAtomicIntegerXX::copy() QAtomicInteger<T> atomic(value); QAtomicInteger<T> copy(atomic); - QCOMPARE(copy.load(), atomic.load()); + QCOMPARE(copy.loadRelaxed(), atomic.loadRelaxed()); QAtomicInteger<T> copy2 = atomic; - QCOMPARE(copy2.load(), atomic.load()); + QCOMPARE(copy2.loadRelaxed(), atomic.loadRelaxed()); // move - QAtomicInteger<T> copy3(qMove(copy)); - QCOMPARE(copy3.load(), atomic.load()); + QAtomicInteger<T> copy3(std::move(copy)); + QCOMPARE(copy3.loadRelaxed(), atomic.loadRelaxed()); - QAtomicInteger<T> copy4 = qMove(copy2); - QCOMPARE(copy4.load(), atomic.load()); + QAtomicInteger<T> copy4 = std::move(copy2); + QCOMPARE(copy4.loadRelaxed(), atomic.loadRelaxed()); } void tst_QAtomicIntegerXX::assign() @@ -305,24 +245,24 @@ void tst_QAtomicIntegerXX::assign() QAtomicInteger<T> atomic(value); QAtomicInteger<T> copy; copy = atomic; - QCOMPARE(copy.load(), atomic.load()); + QCOMPARE(copy.loadRelaxed(), atomic.loadRelaxed()); QAtomicInteger<T> copy2; copy2 = atomic; // operator=(const QAtomicInteger &) - QCOMPARE(copy2.load(), atomic.load()); + QCOMPARE(copy2.loadRelaxed(), atomic.loadRelaxed()); QAtomicInteger<T> copy2bis; - copy2bis = atomic.load(); // operator=(T) - QCOMPARE(copy2bis.load(), atomic.load()); + copy2bis = atomic.loadRelaxed(); // operator=(T) + QCOMPARE(copy2bis.loadRelaxed(), atomic.loadRelaxed()); // move QAtomicInteger<T> copy3; - copy3 = qMove(copy); - QCOMPARE(copy3.load(), atomic.load()); + copy3 = std::move(copy); + QCOMPARE(copy3.loadRelaxed(), atomic.loadRelaxed()); QAtomicInteger<T> copy4; - copy4 = qMove(copy2); - QCOMPARE(copy4.load(), atomic.load()); + copy4 = std::move(copy2); + QCOMPARE(copy4.loadRelaxed(), atomic.loadRelaxed()); } void tst_QAtomicIntegerXX::operatorInteger() @@ -331,7 +271,7 @@ void tst_QAtomicIntegerXX::operatorInteger() QAtomicInteger<T> atomic(value); T val2 = atomic; - QCOMPARE(val2, atomic.load()); + QCOMPARE(val2, atomic.loadRelaxed()); QCOMPARE(val2, T(value)); } @@ -346,53 +286,39 @@ void tst_QAtomicIntegerXX::loadAcquireStoreRelease() QCOMPARE(atomic.loadAcquire(), T(~value)); atomic.storeRelease(value); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); } void tst_QAtomicIntegerXX::refDeref() { QFETCH(LargeInt, value); - const bool needToPreventOverflow = TypeIsSigned && value == std::numeric_limits<T>::max(); - const bool needToPreventUnderflow = TypeIsSigned && value == std::numeric_limits<T>::min(); - T nextValue = T(value); - if (!needToPreventOverflow) - ++nextValue; - T prevValue = T(value); - if (!needToPreventUnderflow) - --prevValue; + + // We perform arithmetic using the unsigned type U to avoid signed + // integer overflows in the non-atomic portion (atomics have well-defined, + // two's complement overflow, even signed ones). + T nextValue = T(U(value) + 1); + T prevValue = T(U(value) - 1); QAtomicInteger<T> atomic(value); - if (!needToPreventOverflow) { QCOMPARE(atomic.ref(), (nextValue != 0)); - QCOMPARE(atomic.load(), nextValue); + QCOMPARE(atomic.loadRelaxed(), nextValue); QCOMPARE(atomic.deref(), (value != 0)); - } - QCOMPARE(atomic.load(), T(value)); - if (!needToPreventUnderflow) { + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.deref(), (prevValue != 0)); - QCOMPARE(atomic.load(), prevValue); + QCOMPARE(atomic.loadRelaxed(), prevValue); QCOMPARE(atomic.ref(), (value != 0)); - } - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); - if (!needToPreventOverflow) { QCOMPARE(++atomic, nextValue); QCOMPARE(--atomic, T(value)); - } - if (!needToPreventUnderflow) { QCOMPARE(--atomic, prevValue); QCOMPARE(++atomic, T(value)); - } - if (!needToPreventOverflow) { QCOMPARE(atomic++, T(value)); QCOMPARE(atomic--, nextValue); - } - if (!needToPreventUnderflow) { QCOMPARE(atomic--, T(value)); QCOMPARE(atomic++, prevValue); - } - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); } void tst_QAtomicIntegerXX::testAndSet() @@ -402,16 +328,16 @@ void tst_QAtomicIntegerXX::testAndSet() QAtomicInteger<T> atomic(value); QVERIFY(atomic.testAndSetRelaxed(value, newValue)); - QCOMPARE(atomic.load(), newValue); + QCOMPARE(atomic.loadRelaxed(), newValue); QVERIFY(!atomic.testAndSetRelaxed(value, newValue)); QVERIFY(atomic.testAndSetRelaxed(newValue, value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QVERIFY(atomic.testAndSetAcquire(value, newValue)); - QCOMPARE(atomic.load(), newValue); + QCOMPARE(atomic.loadRelaxed(), newValue); QVERIFY(!atomic.testAndSetAcquire(value, newValue)); QVERIFY(atomic.testAndSetAcquire(newValue, value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QVERIFY(atomic.testAndSetRelease(value, newValue)); QCOMPARE(atomic.loadAcquire(), newValue); @@ -434,18 +360,18 @@ void tst_QAtomicIntegerXX::testAndSet3() QAtomicInteger<T> atomic(value); QVERIFY(atomic.testAndSetRelaxed(value, newValue, oldValue)); - QCOMPARE(atomic.load(), newValue); + QCOMPARE(atomic.loadRelaxed(), newValue); QVERIFY(!atomic.testAndSetRelaxed(value, newValue, oldValue)); QCOMPARE(oldValue, newValue); QVERIFY(atomic.testAndSetRelaxed(newValue, value, oldValue)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QVERIFY(atomic.testAndSetAcquire(value, newValue, oldValue)); - QCOMPARE(atomic.load(), newValue); + QCOMPARE(atomic.loadRelaxed(), newValue); QVERIFY(!atomic.testAndSetAcquire(value, newValue, oldValue)); QCOMPARE(oldValue, newValue); QVERIFY(atomic.testAndSetAcquire(newValue, value, oldValue)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QVERIFY(atomic.testAndSetRelease(value, newValue, oldValue)); QCOMPARE(atomic.loadAcquire(), newValue); @@ -469,14 +395,14 @@ void tst_QAtomicIntegerXX::fetchAndStore() QAtomicInteger<T> atomic(value); QCOMPARE(atomic.fetchAndStoreRelaxed(newValue), T(value)); - QCOMPARE(atomic.load(), newValue); + QCOMPARE(atomic.loadRelaxed(), newValue); QCOMPARE(atomic.fetchAndStoreRelaxed(value), newValue); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndStoreAcquire(newValue), T(value)); - QCOMPARE(atomic.load(), newValue); + QCOMPARE(atomic.loadRelaxed(), newValue); QCOMPARE(atomic.fetchAndStoreAcquire(value), newValue); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndStoreRelease(newValue), T(value)); QCOMPARE(atomic.loadAcquire(), newValue); @@ -494,80 +420,55 @@ void tst_QAtomicIntegerXX::fetchAndAdd() QFETCH(LargeInt, value); QAtomicInteger<T> atomic(value); + // We perform the additions using the unsigned type U to avoid signed + // integer overflows in the non-atomic portion (atomics have well-defined, + // two's complement overflow, even signed ones). T parcel1 = 42; T parcel2 = T(0-parcel1); + T newValue1 = T(U(value) + parcel1); + T newValue2 = T(U(value) + parcel2); - const bool needToPreventOverflow = TypeIsSigned && value > std::numeric_limits<T>::max() + parcel2; - const bool needToPreventUnderflow = TypeIsSigned && value < std::numeric_limits<T>::min() + parcel1; - - T newValue1 = T(value); - if (!needToPreventOverflow) - newValue1 += parcel1; - T newValue2 = T(value); - if (!needToPreventUnderflow) - newValue2 += parcel2; - - if (!needToPreventOverflow) { QCOMPARE(atomic.fetchAndAddRelaxed(parcel1), T(value)); - QCOMPARE(atomic.load(), newValue1); + QCOMPARE(atomic.loadRelaxed(), newValue1); QCOMPARE(atomic.fetchAndAddRelaxed(parcel2), newValue1); - } - QCOMPARE(atomic.load(), T(value)); - if (!needToPreventUnderflow) { + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndAddRelaxed(parcel2), T(value)); - QCOMPARE(atomic.load(), newValue2); + QCOMPARE(atomic.loadRelaxed(), newValue2); QCOMPARE(atomic.fetchAndAddRelaxed(parcel1), newValue2); - } - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); - if (!needToPreventOverflow) { QCOMPARE(atomic.fetchAndAddAcquire(parcel1), T(value)); - QCOMPARE(atomic.load(), newValue1); + QCOMPARE(atomic.loadRelaxed(), newValue1); QCOMPARE(atomic.fetchAndAddAcquire(parcel2), newValue1); - } - QCOMPARE(atomic.load(), T(value)); - if (!needToPreventUnderflow) { + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndAddAcquire(parcel2), T(value)); - QCOMPARE(atomic.load(), newValue2); + QCOMPARE(atomic.loadRelaxed(), newValue2); QCOMPARE(atomic.fetchAndAddAcquire(parcel1), newValue2); - } - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); - if (!needToPreventOverflow) { QCOMPARE(atomic.fetchAndAddRelease(parcel1), T(value)); QCOMPARE(atomic.loadAcquire(), newValue1); QCOMPARE(atomic.fetchAndAddRelease(parcel2), newValue1); - } QCOMPARE(atomic.loadAcquire(), T(value)); - if (!needToPreventUnderflow) { QCOMPARE(atomic.fetchAndAddRelease(parcel2), T(value)); QCOMPARE(atomic.loadAcquire(), newValue2); QCOMPARE(atomic.fetchAndAddRelease(parcel1), newValue2); - } QCOMPARE(atomic.loadAcquire(), T(value)); - if (!needToPreventOverflow) { QCOMPARE(atomic.fetchAndAddOrdered(parcel1), T(value)); QCOMPARE(atomic.loadAcquire(), newValue1); QCOMPARE(atomic.fetchAndAddOrdered(parcel2), newValue1); - } QCOMPARE(atomic.loadAcquire(), T(value)); - if (!needToPreventUnderflow) { QCOMPARE(atomic.fetchAndAddOrdered(parcel2), T(value)); QCOMPARE(atomic.loadAcquire(), newValue2); QCOMPARE(atomic.fetchAndAddOrdered(parcel1), newValue2); - } QCOMPARE(atomic.loadAcquire(), T(value)); // operator+= - if (!needToPreventOverflow) { QCOMPARE(atomic += parcel1, newValue1); QCOMPARE(atomic += parcel2, T(value)); - } - if (!needToPreventUnderflow) { QCOMPARE(atomic += parcel2, newValue2); QCOMPARE(atomic += parcel1, T(value)); - } } void tst_QAtomicIntegerXX::fetchAndSub() @@ -575,80 +476,55 @@ void tst_QAtomicIntegerXX::fetchAndSub() QFETCH(LargeInt, value); QAtomicInteger<T> atomic(value); + // We perform the subtractions using the unsigned type U to avoid signed + // integer underrflows in the non-atomic portion (atomics have well-defined, + // two's complement underflow, even signed ones). T parcel1 = 42; T parcel2 = T(0-parcel1); + T newValue1 = T(U(value) - parcel1); + T newValue2 = T(U(value) - parcel2); - const bool needToPreventOverflow = TypeIsSigned && value > std::numeric_limits<T>::max() - parcel1; - const bool needToPreventUnderflow = TypeIsSigned && value < std::numeric_limits<T>::min() - parcel2; - - T newValue1 = T(value); - if (!needToPreventUnderflow) - newValue1 -= parcel1; - T newValue2 = T(value); - if (!needToPreventOverflow) - newValue2 -= parcel2; - - if (!needToPreventUnderflow) { QCOMPARE(atomic.fetchAndSubRelaxed(parcel1), T(value)); - QCOMPARE(atomic.load(), newValue1); + QCOMPARE(atomic.loadRelaxed(), newValue1); QCOMPARE(atomic.fetchAndSubRelaxed(parcel2), newValue1); - } - QCOMPARE(atomic.load(), T(value)); - if (!needToPreventOverflow) { + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndSubRelaxed(parcel2), T(value)); - QCOMPARE(atomic.load(), newValue2); + QCOMPARE(atomic.loadRelaxed(), newValue2); QCOMPARE(atomic.fetchAndSubRelaxed(parcel1), newValue2); - } - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); - if (!needToPreventUnderflow) { QCOMPARE(atomic.fetchAndSubAcquire(parcel1), T(value)); - QCOMPARE(atomic.load(), newValue1); + QCOMPARE(atomic.loadRelaxed(), newValue1); QCOMPARE(atomic.fetchAndSubAcquire(parcel2), newValue1); - } - QCOMPARE(atomic.load(), T(value)); - if (!needToPreventOverflow) { + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndSubAcquire(parcel2), T(value)); - QCOMPARE(atomic.load(), newValue2); + QCOMPARE(atomic.loadRelaxed(), newValue2); QCOMPARE(atomic.fetchAndSubAcquire(parcel1), newValue2); - } - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); - if (!needToPreventUnderflow) { QCOMPARE(atomic.fetchAndSubRelease(parcel1), T(value)); QCOMPARE(atomic.loadAcquire(), newValue1); QCOMPARE(atomic.fetchAndSubRelease(parcel2), newValue1); - } QCOMPARE(atomic.loadAcquire(), T(value)); - if (!needToPreventOverflow) { QCOMPARE(atomic.fetchAndSubRelease(parcel2), T(value)); QCOMPARE(atomic.loadAcquire(), newValue2); QCOMPARE(atomic.fetchAndSubRelease(parcel1), newValue2); - } QCOMPARE(atomic.loadAcquire(), T(value)); - if (!needToPreventUnderflow) { QCOMPARE(atomic.fetchAndSubOrdered(parcel1), T(value)); QCOMPARE(atomic.loadAcquire(), newValue1); QCOMPARE(atomic.fetchAndSubOrdered(parcel2), newValue1); - } QCOMPARE(atomic.loadAcquire(), T(value)); - if (!needToPreventOverflow) { QCOMPARE(atomic.fetchAndSubOrdered(parcel2), T(value)); QCOMPARE(atomic.loadAcquire(), newValue2); QCOMPARE(atomic.fetchAndSubOrdered(parcel1), newValue2); - } QCOMPARE(atomic.loadAcquire(), T(value)); // operator-= - if (!needToPreventUnderflow) { QCOMPARE(atomic -= parcel1, newValue1); QCOMPARE(atomic -= parcel2, T(value)); - } - if (!needToPreventOverflow) { QCOMPARE(atomic -= parcel2, newValue2); QCOMPARE(atomic -= parcel1, T(value)); - } } void tst_QAtomicIntegerXX::fetchAndOr() @@ -662,32 +538,32 @@ void tst_QAtomicIntegerXX::fetchAndOr() QCOMPARE(atomic.fetchAndOrRelaxed(zero), T(value)); QCOMPARE(atomic.fetchAndOrRelaxed(one), T(value)); - QCOMPARE(atomic.load(), T(value | 1)); + QCOMPARE(atomic.loadRelaxed(), T(value | 1)); QCOMPARE(atomic.fetchAndOrRelaxed(minusOne), T(value | 1)); - QCOMPARE(atomic.load(), minusOne); + QCOMPARE(atomic.loadRelaxed(), minusOne); - atomic.store(value); + atomic.storeRelaxed(value); QCOMPARE(atomic.fetchAndOrAcquire(zero), T(value)); QCOMPARE(atomic.fetchAndOrAcquire(one), T(value)); - QCOMPARE(atomic.load(), T(value | 1)); + QCOMPARE(atomic.loadRelaxed(), T(value | 1)); QCOMPARE(atomic.fetchAndOrAcquire(minusOne), T(value | 1)); - QCOMPARE(atomic.load(), minusOne); + QCOMPARE(atomic.loadRelaxed(), minusOne); - atomic.store(value); + atomic.storeRelaxed(value); QCOMPARE(atomic.fetchAndOrRelease(zero), T(value)); QCOMPARE(atomic.fetchAndOrRelease(one), T(value)); - QCOMPARE(atomic.load(), T(value | 1)); + QCOMPARE(atomic.loadRelaxed(), T(value | 1)); QCOMPARE(atomic.fetchAndOrRelease(minusOne), T(value | 1)); - QCOMPARE(atomic.load(), minusOne); + QCOMPARE(atomic.loadRelaxed(), minusOne); - atomic.store(value); + atomic.storeRelaxed(value); QCOMPARE(atomic.fetchAndOrOrdered(zero), T(value)); QCOMPARE(atomic.fetchAndOrOrdered(one), T(value)); - QCOMPARE(atomic.load(), T(value | 1)); + QCOMPARE(atomic.loadRelaxed(), T(value | 1)); QCOMPARE(atomic.fetchAndOrOrdered(minusOne), T(value | 1)); - QCOMPARE(atomic.load(), minusOne); + QCOMPARE(atomic.loadRelaxed(), minusOne); - atomic.store(value); + atomic.storeRelaxed(value); QCOMPARE(atomic |= zero, T(value)); QCOMPARE(atomic |= one, T(value | 1)); QCOMPARE(atomic |= minusOne, minusOne); @@ -703,37 +579,37 @@ void tst_QAtomicIntegerXX::fetchAndAnd() T minusOne = T(~0); QCOMPARE(atomic.fetchAndAndRelaxed(minusOne), T(value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndAndRelaxed(f), T(value)); - QCOMPARE(atomic.load(), T(value & 0xf)); + QCOMPARE(atomic.loadRelaxed(), T(value & 0xf)); QCOMPARE(atomic.fetchAndAndRelaxed(zero), T(value & 0xf)); - QCOMPARE(atomic.load(), zero); + QCOMPARE(atomic.loadRelaxed(), zero); - atomic.store(value); + atomic.storeRelaxed(value); QCOMPARE(atomic.fetchAndAndAcquire(minusOne), T(value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndAndAcquire(f), T(value)); - QCOMPARE(atomic.load(), T(value & 0xf)); + QCOMPARE(atomic.loadRelaxed(), T(value & 0xf)); QCOMPARE(atomic.fetchAndAndAcquire(zero), T(value & 0xf)); - QCOMPARE(atomic.load(), zero); + QCOMPARE(atomic.loadRelaxed(), zero); - atomic.store(value); + atomic.storeRelaxed(value); QCOMPARE(atomic.fetchAndAndRelease(minusOne), T(value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndAndRelease(f), T(value)); - QCOMPARE(atomic.load(), T(value & 0xf)); + QCOMPARE(atomic.loadRelaxed(), T(value & 0xf)); QCOMPARE(atomic.fetchAndAndRelease(zero), T(value & 0xf)); - QCOMPARE(atomic.load(), zero); + QCOMPARE(atomic.loadRelaxed(), zero); - atomic.store(value); + atomic.storeRelaxed(value); QCOMPARE(atomic.fetchAndAndOrdered(minusOne), T(value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndAndOrdered(f), T(value)); - QCOMPARE(atomic.load(), T(value & 0xf)); + QCOMPARE(atomic.loadRelaxed(), T(value & 0xf)); QCOMPARE(atomic.fetchAndAndOrdered(zero), T(value & 0xf)); - QCOMPARE(atomic.load(), zero); + QCOMPARE(atomic.loadRelaxed(), zero); - atomic.store(value); + atomic.storeRelaxed(value); QCOMPARE(atomic &= minusOne, T(value)); QCOMPARE(atomic &= f, T(value & 0xf)); QCOMPARE(atomic &= zero, zero); @@ -749,48 +625,48 @@ void tst_QAtomicIntegerXX::fetchAndXor() T minusOne = T(~0); QCOMPARE(atomic.fetchAndXorRelaxed(zero), T(value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndXorRelaxed(pattern), T(value)); - QCOMPARE(atomic.load(), T(value ^ pattern)); + QCOMPARE(atomic.loadRelaxed(), T(value ^ pattern)); QCOMPARE(atomic.fetchAndXorRelaxed(pattern), T(value ^ pattern)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndXorRelaxed(minusOne), T(value)); - QCOMPARE(atomic.load(), T(~value)); + QCOMPARE(atomic.loadRelaxed(), T(~value)); QCOMPARE(atomic.fetchAndXorRelaxed(minusOne), T(~value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndXorAcquire(zero), T(value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndXorAcquire(pattern), T(value)); - QCOMPARE(atomic.load(), T(value ^ pattern)); + QCOMPARE(atomic.loadRelaxed(), T(value ^ pattern)); QCOMPARE(atomic.fetchAndXorAcquire(pattern), T(value ^ pattern)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndXorAcquire(minusOne), T(value)); - QCOMPARE(atomic.load(), T(~value)); + QCOMPARE(atomic.loadRelaxed(), T(~value)); QCOMPARE(atomic.fetchAndXorAcquire(minusOne), T(~value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndXorRelease(zero), T(value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndXorRelease(pattern), T(value)); - QCOMPARE(atomic.load(), T(value ^ pattern)); + QCOMPARE(atomic.loadRelaxed(), T(value ^ pattern)); QCOMPARE(atomic.fetchAndXorRelease(pattern), T(value ^ pattern)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndXorRelease(minusOne), T(value)); - QCOMPARE(atomic.load(), T(~value)); + QCOMPARE(atomic.loadRelaxed(), T(~value)); QCOMPARE(atomic.fetchAndXorRelease(minusOne), T(~value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndXorOrdered(zero), T(value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndXorOrdered(pattern), T(value)); - QCOMPARE(atomic.load(), T(value ^ pattern)); + QCOMPARE(atomic.loadRelaxed(), T(value ^ pattern)); QCOMPARE(atomic.fetchAndXorOrdered(pattern), T(value ^ pattern)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic.fetchAndXorOrdered(minusOne), T(value)); - QCOMPARE(atomic.load(), T(~value)); + QCOMPARE(atomic.loadRelaxed(), T(~value)); QCOMPARE(atomic.fetchAndXorOrdered(minusOne), T(~value)); - QCOMPARE(atomic.load(), T(value)); + QCOMPARE(atomic.loadRelaxed(), T(value)); QCOMPARE(atomic ^= zero, T(value)); QCOMPARE(atomic ^= pattern, T(value ^ pattern)); @@ -798,8 +674,9 @@ void tst_QAtomicIntegerXX::fetchAndXor() QCOMPARE(atomic ^= minusOne, T(~value)); QCOMPARE(atomic ^= minusOne, T(value)); } - -#include "tst_qatomicinteger.moc" +} QTEST_APPLESS_MAIN(tst_QAtomicIntegerXX) +#include "tst_qatomicinteger.moc" + diff --git a/tests/auto/corelib/thread/qatomicinteger/uchar/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/uchar/CMakeLists.txt new file mode 100644 index 0000000000..95d88d31a6 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/uchar/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_uchar Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_uchar LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_uchar + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=uchar + tst_QAtomicIntegerXX=tst_QAtomicInteger_uchar +) diff --git a/tests/auto/corelib/thread/qatomicinteger/uchar/uchar.pro b/tests/auto/corelib/thread/qatomicinteger/uchar/uchar.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/uchar/uchar.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/uint/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/uint/CMakeLists.txt new file mode 100644 index 0000000000..2ab977ef6a --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/uint/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_uint Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_uint LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_uint + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=uint + tst_QAtomicIntegerXX=tst_QAtomicInteger_uint +) diff --git a/tests/auto/corelib/thread/qatomicinteger/uint/uint.pro b/tests/auto/corelib/thread/qatomicinteger/uint/uint.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/uint/uint.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/ulong/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/ulong/CMakeLists.txt new file mode 100644 index 0000000000..7707bd53b5 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/ulong/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_ulong Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_ulong LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_ulong + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=ulong + tst_QAtomicIntegerXX=tst_QAtomicInteger_ulong +) diff --git a/tests/auto/corelib/thread/qatomicinteger/ulong/ulong.pro b/tests/auto/corelib/thread/qatomicinteger/ulong/ulong.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/ulong/ulong.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/ushort/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/ushort/CMakeLists.txt new file mode 100644 index 0000000000..667e9eade6 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/ushort/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_ushort Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_ushort LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_ushort + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=ushort + tst_QAtomicIntegerXX=tst_QAtomicInteger_ushort +) diff --git a/tests/auto/corelib/thread/qatomicinteger/ushort/ushort.pro b/tests/auto/corelib/thread/qatomicinteger/ushort/ushort.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/ushort/ushort.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicinteger/wchar_t/CMakeLists.txt b/tests/auto/corelib/thread/qatomicinteger/wchar_t/CMakeLists.txt new file mode 100644 index 0000000000..0e2d084b58 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicinteger/wchar_t/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicinteger_wchar_t Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicinteger_wchar_t LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicinteger_wchar_t + SOURCES + ../tst_qatomicinteger.cpp + DEFINES + QATOMIC_TEST_TYPE=wchar_t + tst_QAtomicIntegerXX=tst_QAtomicInteger_wchar_t +) diff --git a/tests/auto/corelib/thread/qatomicinteger/wchar_t/wchar_t.pro b/tests/auto/corelib/thread/qatomicinteger/wchar_t/wchar_t.pro deleted file mode 100644 index 1e97d5cbae..0000000000 --- a/tests/auto/corelib/thread/qatomicinteger/wchar_t/wchar_t.pro +++ /dev/null @@ -1 +0,0 @@ -include(../qatomicinteger.pri) diff --git a/tests/auto/corelib/thread/qatomicpointer/CMakeLists.txt b/tests/auto/corelib/thread/qatomicpointer/CMakeLists.txt new file mode 100644 index 0000000000..cd8df9db66 --- /dev/null +++ b/tests/auto/corelib/thread/qatomicpointer/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qatomicpointer Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qatomicpointer LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qatomicpointer + SOURCES + tst_qatomicpointer.cpp +) diff --git a/tests/auto/corelib/thread/qatomicpointer/qatomicpointer.pro b/tests/auto/corelib/thread/qatomicpointer/qatomicpointer.pro deleted file mode 100644 index cce822da6e..0000000000 --- a/tests/auto/corelib/thread/qatomicpointer/qatomicpointer.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qatomicpointer -QT = core testlib -SOURCES = tst_qatomicpointer.cpp diff --git a/tests/auto/corelib/thread/qatomicpointer/tst_qatomicpointer.cpp b/tests/auto/corelib/thread/qatomicpointer/tst_qatomicpointer.cpp index 0200473cae..347831819e 100644 --- a/tests/auto/corelib/thread/qatomicpointer/tst_qatomicpointer.cpp +++ b/tests/auto/corelib/thread/qatomicpointer/tst_qatomicpointer.cpp @@ -1,33 +1,8 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2016 Intel Corporation. -** 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) 2016 The Qt Company Ltd. +// Copyright (C) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <qatomic.h> #include <limits.h> @@ -73,12 +48,12 @@ void tst_QAtomicPointer::warningFreeHelper() { qFatal("This code is bogus, and shouldn't be run. We're looking for compiler warnings only."); - QBasicAtomicPointer<WFHC> p = Q_BASIC_ATOMIC_INITIALIZER(0); + QBasicAtomicPointer<WFHC> p = Q_BASIC_ATOMIC_INITIALIZER(nullptr); - p.load()->bar(); + p.loadRelaxed()->bar(); - WFHC *expectedValue = 0; - WFHC *newValue = 0; + WFHC *expectedValue = nullptr; + WFHC *newValue = nullptr; qptrdiff valueToAdd = 0; p.testAndSetRelaxed(expectedValue, newValue); @@ -108,26 +83,22 @@ void tst_QAtomicPointer::warningFree() void tst_QAtomicPointer::alignment() { -#ifdef Q_ALIGNOF - // this will cause a build error if the alignment isn't the same - char dummy[Q_ALIGNOF(QBasicAtomicPointer<void>) == Q_ALIGNOF(void*) ? 1 : -1]; - (void)dummy; -#endif + static_assert(alignof(QBasicAtomicPointer<void>) == alignof(void*)); } void tst_QAtomicPointer::constructor() { void *one = this; QAtomicPointer<void> atomic1 = one; - QCOMPARE(atomic1.load(), one); + QCOMPARE(atomic1.loadRelaxed(), one); void *two = &one; QAtomicPointer<void> atomic2 = two; - QCOMPARE(atomic2.load(), two); + QCOMPARE(atomic2.loadRelaxed(), two); void *three = &two; QAtomicPointer<void> atomic3 = three; - QCOMPARE(atomic3.load(), three); + QCOMPARE(atomic3.loadRelaxed(), three); } void tst_QAtomicPointer::copy_constructor() @@ -135,20 +106,20 @@ void tst_QAtomicPointer::copy_constructor() void *one = this; QAtomicPointer<void> atomic1 = one; QAtomicPointer<void> atomic1_copy = atomic1; - QCOMPARE(atomic1_copy.load(), one); - QCOMPARE(atomic1_copy.load(), atomic1.load()); + QCOMPARE(atomic1_copy.loadRelaxed(), one); + QCOMPARE(atomic1_copy.loadRelaxed(), atomic1.loadRelaxed()); void *two = &one; QAtomicPointer<void> atomic2 = two; QAtomicPointer<void> atomic2_copy = atomic2; - QCOMPARE(atomic2_copy.load(), two); - QCOMPARE(atomic2_copy.load(), atomic2.load()); + QCOMPARE(atomic2_copy.loadRelaxed(), two); + QCOMPARE(atomic2_copy.loadRelaxed(), atomic2.loadRelaxed()); void *three = &two; QAtomicPointer<void> atomic3 = three; QAtomicPointer<void> atomic3_copy = atomic3; - QCOMPARE(atomic3_copy.load(), three); - QCOMPARE(atomic3_copy.load(), atomic3.load()); + QCOMPARE(atomic3_copy.loadRelaxed(), three); + QCOMPARE(atomic3_copy.loadRelaxed(), atomic3.loadRelaxed()); } void tst_QAtomicPointer::assignment_operator() @@ -161,17 +132,17 @@ void tst_QAtomicPointer::assignment_operator() QAtomicPointer<void> atomic2 = two; QAtomicPointer<void> atomic3 = three; - QCOMPARE(atomic1.load(), one); - QCOMPARE(atomic2.load(), two); - QCOMPARE(atomic3.load(), three); + QCOMPARE(atomic1.loadRelaxed(), one); + QCOMPARE(atomic2.loadRelaxed(), two); + QCOMPARE(atomic3.loadRelaxed(), three); atomic1 = two; atomic2 = three; atomic3 = one; - QCOMPARE(atomic1.load(), two); - QCOMPARE(atomic2.load(), three); - QCOMPARE(atomic3.load(), one); + QCOMPARE(atomic1.loadRelaxed(), two); + QCOMPARE(atomic2.loadRelaxed(), three); + QCOMPARE(atomic3.loadRelaxed(), one); } void tst_QAtomicPointer::isTestAndSetNative() @@ -234,17 +205,17 @@ void tst_QAtomicPointer::testAndSet() QAtomicPointer<void> atomic2 = two; QAtomicPointer<void> atomic3 = three; - QCOMPARE(atomic1.load(), one); - QCOMPARE(atomic2.load(), two); - QCOMPARE(atomic3.load(), three); + QCOMPARE(atomic1.loadRelaxed(), one); + QCOMPARE(atomic2.loadRelaxed(), two); + QCOMPARE(atomic3.loadRelaxed(), three); QVERIFY(atomic1.testAndSetRelaxed(one, two)); QVERIFY(atomic2.testAndSetRelaxed(two, three)); QVERIFY(atomic3.testAndSetRelaxed(three, one)); - QCOMPARE(atomic1.load(), two); - QCOMPARE(atomic2.load(), three); - QCOMPARE(atomic3.load(), one); + QCOMPARE(atomic1.loadRelaxed(), two); + QCOMPARE(atomic2.loadRelaxed(), three); + QCOMPARE(atomic3.loadRelaxed(), one); } { @@ -252,17 +223,17 @@ void tst_QAtomicPointer::testAndSet() QAtomicPointer<void> atomic2 = two; QAtomicPointer<void> atomic3 = three; - QCOMPARE(atomic1.load(), one); - QCOMPARE(atomic2.load(), two); - QCOMPARE(atomic3.load(), three); + QCOMPARE(atomic1.loadRelaxed(), one); + QCOMPARE(atomic2.loadRelaxed(), two); + QCOMPARE(atomic3.loadRelaxed(), three); QVERIFY(atomic1.testAndSetAcquire(one, two)); QVERIFY(atomic2.testAndSetAcquire(two, three)); QVERIFY(atomic3.testAndSetAcquire(three, one)); - QCOMPARE(atomic1.load(), two); - QCOMPARE(atomic2.load(), three); - QCOMPARE(atomic3.load(), one); + QCOMPARE(atomic1.loadRelaxed(), two); + QCOMPARE(atomic2.loadRelaxed(), three); + QCOMPARE(atomic3.loadRelaxed(), one); } { @@ -270,17 +241,17 @@ void tst_QAtomicPointer::testAndSet() QAtomicPointer<void> atomic2 = two; QAtomicPointer<void> atomic3 = three; - QCOMPARE(atomic1.load(), one); - QCOMPARE(atomic2.load(), two); - QCOMPARE(atomic3.load(), three); + QCOMPARE(atomic1.loadRelaxed(), one); + QCOMPARE(atomic2.loadRelaxed(), two); + QCOMPARE(atomic3.loadRelaxed(), three); QVERIFY(atomic1.testAndSetRelease(one, two)); QVERIFY(atomic2.testAndSetRelease(two, three)); QVERIFY(atomic3.testAndSetRelease(three, one)); - QCOMPARE(atomic1.load(), two); - QCOMPARE(atomic2.load(), three); - QCOMPARE(atomic3.load(), one); + QCOMPARE(atomic1.loadRelaxed(), two); + QCOMPARE(atomic2.loadRelaxed(), three); + QCOMPARE(atomic3.loadRelaxed(), one); } { @@ -288,17 +259,17 @@ void tst_QAtomicPointer::testAndSet() QAtomicPointer<void> atomic2 = two; QAtomicPointer<void> atomic3 = three; - QCOMPARE(atomic1.load(), one); - QCOMPARE(atomic2.load(), two); - QCOMPARE(atomic3.load(), three); + QCOMPARE(atomic1.loadRelaxed(), one); + QCOMPARE(atomic2.loadRelaxed(), two); + QCOMPARE(atomic3.loadRelaxed(), three); QVERIFY(atomic1.testAndSetOrdered(one, two)); QVERIFY(atomic2.testAndSetOrdered(two, three)); QVERIFY(atomic3.testAndSetOrdered(three, one)); - QCOMPARE(atomic1.load(), two); - QCOMPARE(atomic2.load(), three); - QCOMPARE(atomic3.load(), one); + QCOMPARE(atomic1.loadRelaxed(), two); + QCOMPARE(atomic2.loadRelaxed(), three); + QCOMPARE(atomic3.loadRelaxed(), one); } } @@ -362,17 +333,17 @@ void tst_QAtomicPointer::fetchAndStore() QAtomicPointer<void> atomic2 = two; QAtomicPointer<void> atomic3 = three; - QCOMPARE(atomic1.load(), one); - QCOMPARE(atomic2.load(), two); - QCOMPARE(atomic3.load(), three); + QCOMPARE(atomic1.loadRelaxed(), one); + QCOMPARE(atomic2.loadRelaxed(), two); + QCOMPARE(atomic3.loadRelaxed(), three); QCOMPARE(atomic1.fetchAndStoreRelaxed(two), one); QCOMPARE(atomic2.fetchAndStoreRelaxed(three), two); QCOMPARE(atomic3.fetchAndStoreRelaxed(one), three); - QCOMPARE(atomic1.load(), two); - QCOMPARE(atomic2.load(), three); - QCOMPARE(atomic3.load(), one); + QCOMPARE(atomic1.loadRelaxed(), two); + QCOMPARE(atomic2.loadRelaxed(), three); + QCOMPARE(atomic3.loadRelaxed(), one); } { @@ -380,17 +351,17 @@ void tst_QAtomicPointer::fetchAndStore() QAtomicPointer<void> atomic2 = two; QAtomicPointer<void> atomic3 = three; - QCOMPARE(atomic1.load(), one); - QCOMPARE(atomic2.load(), two); - QCOMPARE(atomic3.load(), three); + QCOMPARE(atomic1.loadRelaxed(), one); + QCOMPARE(atomic2.loadRelaxed(), two); + QCOMPARE(atomic3.loadRelaxed(), three); QCOMPARE(atomic1.fetchAndStoreAcquire(two), one); QCOMPARE(atomic2.fetchAndStoreAcquire(three), two); QCOMPARE(atomic3.fetchAndStoreAcquire(one), three); - QCOMPARE(atomic1.load(), two); - QCOMPARE(atomic2.load(), three); - QCOMPARE(atomic3.load(), one); + QCOMPARE(atomic1.loadRelaxed(), two); + QCOMPARE(atomic2.loadRelaxed(), three); + QCOMPARE(atomic3.loadRelaxed(), one); } { @@ -398,17 +369,17 @@ void tst_QAtomicPointer::fetchAndStore() QAtomicPointer<void> atomic2 = two; QAtomicPointer<void> atomic3 = three; - QCOMPARE(atomic1.load(), one); - QCOMPARE(atomic2.load(), two); - QCOMPARE(atomic3.load(), three); + QCOMPARE(atomic1.loadRelaxed(), one); + QCOMPARE(atomic2.loadRelaxed(), two); + QCOMPARE(atomic3.loadRelaxed(), three); QCOMPARE(atomic1.fetchAndStoreRelease(two), one); QCOMPARE(atomic2.fetchAndStoreRelease(three), two); QCOMPARE(atomic3.fetchAndStoreRelease(one), three); - QCOMPARE(atomic1.load(), two); - QCOMPARE(atomic2.load(), three); - QCOMPARE(atomic3.load(), one); + QCOMPARE(atomic1.loadRelaxed(), two); + QCOMPARE(atomic2.loadRelaxed(), three); + QCOMPARE(atomic3.loadRelaxed(), one); } { @@ -416,17 +387,17 @@ void tst_QAtomicPointer::fetchAndStore() QAtomicPointer<void> atomic2 = two; QAtomicPointer<void> atomic3 = three; - QCOMPARE(atomic1.load(), one); - QCOMPARE(atomic2.load(), two); - QCOMPARE(atomic3.load(), three); + QCOMPARE(atomic1.loadRelaxed(), one); + QCOMPARE(atomic2.loadRelaxed(), two); + QCOMPARE(atomic3.loadRelaxed(), three); QCOMPARE(atomic1.fetchAndStoreOrdered(two), one); QCOMPARE(atomic2.fetchAndStoreOrdered(three), two); QCOMPARE(atomic3.fetchAndStoreOrdered(one), three); - QCOMPARE(atomic1.load(), two); - QCOMPARE(atomic2.load(), three); - QCOMPARE(atomic3.load(), one); + QCOMPARE(atomic1.loadRelaxed(), two); + QCOMPARE(atomic2.loadRelaxed(), three); + QCOMPARE(atomic3.loadRelaxed(), one); } } @@ -530,66 +501,66 @@ void tst_QAtomicPointer::fetchAndAdd() // cast to void* in order to avoid QCOMPARE to compare string content of the char* QCOMPARE(static_cast<void*>(pointer1.fetchAndAddRelaxed(valueToAdd)), static_cast<void*>(pc)); QCOMPARE(static_cast<void*>(pointer1.fetchAndAddRelaxed(-valueToAdd)), static_cast<void*>(pc + valueToAdd)); - QCOMPARE(static_cast<void*>(pointer1.load()), static_cast<void*>(pc)); + QCOMPARE(static_cast<void*>(pointer1.loadRelaxed()), static_cast<void*>(pc)); QAtomicPointer<short> pointer2 = ps; QCOMPARE(pointer2.fetchAndAddRelaxed(valueToAdd), ps); QCOMPARE(pointer2.fetchAndAddRelaxed(-valueToAdd), ps + valueToAdd); - QCOMPARE(pointer2.load(), ps); + QCOMPARE(pointer2.loadRelaxed(), ps); QAtomicPointer<int> pointer3 = pi; QCOMPARE(pointer3.fetchAndAddRelaxed(valueToAdd), pi); QCOMPARE(pointer3.fetchAndAddRelaxed(-valueToAdd), pi + valueToAdd); - QCOMPARE(pointer3.load(), pi); + QCOMPARE(pointer3.loadRelaxed(), pi); } { QAtomicPointer<char> pointer1 = pc; QCOMPARE(static_cast<void*>(pointer1.fetchAndAddAcquire(valueToAdd)), static_cast<void*>(pc)); QCOMPARE(static_cast<void*>(pointer1.fetchAndAddAcquire(-valueToAdd)), static_cast<void*>(pc + valueToAdd)); - QCOMPARE(static_cast<void*>(pointer1.load()), static_cast<void*>(pc)); + QCOMPARE(static_cast<void*>(pointer1.loadRelaxed()), static_cast<void*>(pc)); QAtomicPointer<short> pointer2 = ps; QCOMPARE(pointer2.fetchAndAddAcquire(valueToAdd), ps); QCOMPARE(pointer2.fetchAndAddAcquire(-valueToAdd), ps + valueToAdd); - QCOMPARE(pointer2.load(), ps); + QCOMPARE(pointer2.loadRelaxed(), ps); QAtomicPointer<int> pointer3 = pi; QCOMPARE(pointer3.fetchAndAddAcquire(valueToAdd), pi); QCOMPARE(pointer3.fetchAndAddAcquire(-valueToAdd), pi + valueToAdd); - QCOMPARE(pointer3.load(), pi); + QCOMPARE(pointer3.loadRelaxed(), pi); } { QAtomicPointer<char> pointer1 = pc; QCOMPARE(static_cast<void*>(pointer1.fetchAndAddRelease(valueToAdd)), static_cast<void*>(pc)); QCOMPARE(static_cast<void*>(pointer1.fetchAndAddRelease(-valueToAdd)), static_cast<void*>(pc + valueToAdd)); - QCOMPARE(static_cast<void*>(pointer1.load()), static_cast<void*>(pc)); + QCOMPARE(static_cast<void*>(pointer1.loadRelaxed()), static_cast<void*>(pc)); QAtomicPointer<short> pointer2 = ps; QCOMPARE(pointer2.fetchAndAddRelease(valueToAdd), ps); QCOMPARE(pointer2.fetchAndAddRelease(-valueToAdd), ps + valueToAdd); - QCOMPARE(pointer2.load(), ps); + QCOMPARE(pointer2.loadRelaxed(), ps); QAtomicPointer<int> pointer3 = pi; QCOMPARE(pointer3.fetchAndAddRelease(valueToAdd), pi); QCOMPARE(pointer3.fetchAndAddRelease(-valueToAdd), pi + valueToAdd); - QCOMPARE(pointer3.load(), pi); + QCOMPARE(pointer3.loadRelaxed(), pi); } { QAtomicPointer<char> pointer1 = pc; QCOMPARE(static_cast<void*>(pointer1.fetchAndAddOrdered(valueToAdd)), static_cast<void*>(pc)); QCOMPARE(static_cast<void*>(pointer1.fetchAndAddOrdered(-valueToAdd)), static_cast<void*>(pc + valueToAdd)); - QCOMPARE(static_cast<void*>(pointer1.load()), static_cast<void*>(pc)); + QCOMPARE(static_cast<void*>(pointer1.loadRelaxed()), static_cast<void*>(pc)); QAtomicPointer<short> pointer2 = ps; QCOMPARE(pointer2.fetchAndAddOrdered(valueToAdd), ps); QCOMPARE(pointer2.fetchAndAddOrdered(-valueToAdd), ps + valueToAdd); - QCOMPARE(pointer2.load(), ps); + QCOMPARE(pointer2.loadRelaxed(), ps); QAtomicPointer<int> pointer3 = pi; QCOMPARE(pointer3.fetchAndAddOrdered(valueToAdd), pi); QCOMPARE(pointer3.fetchAndAddOrdered(-valueToAdd), pi + valueToAdd); - QCOMPARE(pointer3.load(), pi); + QCOMPARE(pointer3.loadRelaxed(), pi); } } template <typename T> void constAndVolatile_helper() { - T *one = 0; + T *one = nullptr; T *two = &one; T *three = &two; @@ -598,34 +569,34 @@ template <typename T> void constAndVolatile_helper() QAtomicPointer<T> atomic2 = two; QAtomicPointer<T> atomic3 = three; - QVERIFY(atomic1.load() == one); - QVERIFY(atomic2.load() == two); - QVERIFY(atomic3.load() == three); + QVERIFY(atomic1.loadRelaxed() == one); + QVERIFY(atomic2.loadRelaxed() == two); + QVERIFY(atomic3.loadRelaxed() == three); QVERIFY(atomic1.fetchAndStoreRelaxed(two) == one); QVERIFY(atomic2.fetchAndStoreRelaxed(three) == two); QVERIFY(atomic3.fetchAndStoreRelaxed(one) == three); - QVERIFY(atomic1.load() == two); - QVERIFY(atomic2.load() == three); - QVERIFY(atomic3.load() == one); + QVERIFY(atomic1.loadRelaxed() == two); + QVERIFY(atomic2.loadRelaxed() == three); + QVERIFY(atomic3.loadRelaxed() == one); } { QAtomicPointer<T> atomic1 = one; QAtomicPointer<T> atomic2 = two; QAtomicPointer<T> atomic3 = three; - QVERIFY(atomic1.load() == one); - QVERIFY(atomic2.load() == two); - QVERIFY(atomic3.load() == three); + QVERIFY(atomic1.loadRelaxed() == one); + QVERIFY(atomic2.loadRelaxed() == two); + QVERIFY(atomic3.loadRelaxed() == three); QVERIFY(atomic1.testAndSetRelaxed(one, two)); QVERIFY(atomic2.testAndSetRelaxed(two, three)); QVERIFY(atomic3.testAndSetRelaxed(three, one)); - QVERIFY(atomic1.load() == two); - QVERIFY(atomic2.load() == three); - QVERIFY(atomic3.load() == one); + QVERIFY(atomic1.loadRelaxed() == two); + QVERIFY(atomic2.loadRelaxed() == three); + QVERIFY(atomic3.loadRelaxed() == one); } } @@ -649,8 +620,8 @@ void tst_QAtomicPointer::forwardDeclared() // this is just a compilation test QAtomicPointer<ForwardDeclared> ptr; ContainsForwardDeclared cfd; - Q_UNUSED(ptr); - Q_UNUSED(cfd); + Q_UNUSED(ptr) + Q_UNUSED(cfd) QVERIFY(true); } @@ -665,7 +636,7 @@ template <typename T> static void operators_helper() { // Test that QBasicAtomicPointer also has operator= and cast operators // We've been using them for QAtomicPointer<T> elsewhere - QBasicAtomicPointer<T> atomic = Q_BASIC_ATOMIC_INITIALIZER(0); + QBasicAtomicPointer<T> atomic = Q_BASIC_ATOMIC_INITIALIZER(nullptr); atomic = one; QCOMPARE(Ptr(atomic), one); } diff --git a/tests/auto/corelib/thread/qfuture/CMakeLists.txt b/tests/auto/corelib/thread/qfuture/CMakeLists.txt new file mode 100644 index 0000000000..ba5730d5cf --- /dev/null +++ b/tests/auto/corelib/thread/qfuture/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qfuture Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qfuture LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qfuture + SOURCES + tst_qfuture.cpp + LIBRARIES + Qt::CorePrivate + Qt::TestPrivate +) + +qt_internal_extend_target(tst_qfuture CONDITION MSVC + COMPILE_OPTIONS + /bigobj +) + +qt_internal_undefine_global_definition(tst_qfuture QT_NO_JAVA_STYLE_ITERATORS) diff --git a/tests/auto/corelib/thread/qfuture/qfuture.pro b/tests/auto/corelib/thread/qfuture/qfuture.pro deleted file mode 100644 index b1667760d6..0000000000 --- a/tests/auto/corelib/thread/qfuture/qfuture.pro +++ /dev/null @@ -1,5 +0,0 @@ -CONFIG += testcase -TARGET = tst_qfuture -QT = core core-private testlib -SOURCES = tst_qfuture.cpp -DEFINES += QT_STRICT_ITERATORS diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index b8c82c2ea0..8a6ac6a61d 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -1,73 +1,191 @@ -/**************************************************************************** -** -** 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 <QCoreApplication> -#include <QDebug> +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses #define QFUTURE_TEST -#include <QtTest/QtTest> +#include <QCoreApplication> +#include <QDebug> +#include <QSemaphore> +#include <QTestEventLoop> +#include <QTimer> +#include <QSignalSpy> +#include <QVarLengthArray> +#include <QSet> +#include <QList> +#include <private/qobject_p.h> + +#include <QTest> +#include <QtTest/private/qcomparisontesthelper_p.h> #include <qfuture.h> #include <qfuturewatcher.h> #include <qresultstore.h> #include <qthreadpool.h> #include <qexception.h> #include <qrandom.h> +#include <QtConcurrent/qtconcurrentrun.h> #include <private/qfutureinterface_p.h> +#include <forward_list> +#include <list> +#include <vector> +#include <memory> +#include <set> + // COM interface macro. #if defined(Q_OS_WIN) && defined(interface) # undef interface #endif -struct ResultStoreInt : QtPrivate::ResultStoreBase +using namespace std::chrono_literals; +static constexpr auto DefaultWaitTime = 2s; + +using namespace Qt::StringLiterals; + +class SenderObject : public QObject +{ + Q_OBJECT + +public: + void emitNoArg() { emit noArgSignal(); } + void emitIntArg(int value) { emit intArgSignal(value); } + void emitConstRefArg(const QString &value) { emit constRefArg(value); } + void emitMultipleArgs(int value1, double value2, const QString &value3) + { + emit multipleArgs(value1, value2, value3); + } + void emitTupleArgSignal(const std::tuple<int, double, QString> &t) { emit tupleArgSignal(t); } + void emitMultiArgsWithTupleSignal1(int value, const std::tuple<int, double, QString> &t) + { + emit multiArgsWithTupleSignal1(value, t); + } + void emitMultiArgsWithTupleSignal2(const std::tuple<int, double, QString> &t, int value) + { + emit multiArgsWithTupleSignal2(t, value); + } + void emitMultiArgsWithPairSignal1(int value, const std::pair<int, double> &p) + { + emit multiArgsWithPairSignal1(value, p); + } + void emitMultiArgsWithPairSignal2(const std::pair<int, double> &p, int value) + { + emit multiArgsWithPairSignal2(p, value); + } + + void emitNoArgPrivateSignal() { emit noArgPrivateSignal(QPrivateSignal()); } + void emitIntArgPrivateSignal(int value) { emit intArgPrivateSignal(value, QPrivateSignal()); } + void emitMultiArgsPrivateSignal(int value1, double value2, const QString &value3) + { + emit multiArgsPrivateSignal(value1, value2, value3, QPrivateSignal()); + } + void emitTupleArgPrivateSignal(const std::tuple<int, double, QString> &t) + { + emit tupleArgPrivateSignal(t, QPrivateSignal()); + } + void emitMultiArgsWithTuplePrivateSignal1(int value, const std::tuple<int, double, QString> &t) + { + emit multiArgsWithTuplePrivateSignal1(value, t, QPrivateSignal()); + } + void emitMultiArgsWithTuplePrivateSignal2(const std::tuple<int, double, QString> &t, int value) + { + emit multiArgsWithTuplePrivateSignal2(t, value, QPrivateSignal()); + } + void emitMultiArgsWithPairPrivateSignal1(int value, const std::pair<int, double> &p) + { + emit multiArgsWithPairPrivateSignal1(value, p, QPrivateSignal()); + } + void emitMultiArgsWithPairPrivateSignal2(const std::pair<int, double> &p, int value) + { + emit multiArgsWithPairPrivateSignal2(p, value, QPrivateSignal()); + } + +signals: + void noArgSignal(); + void intArgSignal(int value); + void constRefArg(const QString &value); + void multipleArgs(int value1, double value2, const QString &value3); + void tupleArgSignal(const std::tuple<int, double, QString> &t); + void multiArgsWithTupleSignal1(int value, const std::tuple<int, double, QString> &t); + void multiArgsWithTupleSignal2(const std::tuple<int, double, QString> &t, int value); + void multiArgsWithPairSignal1(int value, const std::pair<int, double> &p); + void multiArgsWithPairSignal2(const std::pair<int, double> &p, int value); + + // Private signals + void noArgPrivateSignal(QPrivateSignal); + void intArgPrivateSignal(int value, QPrivateSignal); + void multiArgsPrivateSignal(int value1, double value2, const QString &value3, QPrivateSignal); + void tupleArgPrivateSignal(const std::tuple<int, double, QString> &t, QPrivateSignal); + void multiArgsWithTuplePrivateSignal1(int value, const std::tuple<int, double, QString> &t, + QPrivateSignal); + void multiArgsWithTuplePrivateSignal2(const std::tuple<int, double, QString> &t, int value, + QPrivateSignal); + void multiArgsWithPairPrivateSignal1(int value, const std::pair<int, double> &p, + QPrivateSignal); + void multiArgsWithPairPrivateSignal2(const std::pair<int, double> &p, int value, + QPrivateSignal); +}; + +class LambdaThread : public QThread { - ~ResultStoreInt() { clear<int>(); } +public: + LambdaThread(std::function<void ()> fn) + :m_fn(fn) + { + + } + + void run() override + { + m_fn(); + } + +private: + std::function<void ()> m_fn; }; +// Emulates QWidget behavior by deleting its children early in the destructor +// instead of leaving it to ~QObject() +class FakeQWidget : public QObject +{ + Q_OBJECT +public: + ~FakeQWidget() override { + auto *d = QObjectPrivate::get(this); + d->deleteChildren(); + } +}; + +using UniquePtr = std::unique_ptr<int>; + class tst_QFuture: public QObject { Q_OBJECT private slots: + void compareCompiles(); void resultStore(); void future(); + void futureToVoid(); void futureInterface(); void refcounting(); void cancel(); + void cancelAndFinish(); void statePropagation(); void multipleResults(); void indexedResults(); void progress(); + void setProgressRange(); + void progressWithRange(); void progressText(); void resultsAfterFinished(); void resultsAsList(); - void implicitConversions(); void iterators(); + void iteratorsThread(); +#if QT_DEPRECATED_SINCE(6, 0) void pause(); + void suspendCheckPaused(); +#endif + void suspend(); void throttling(); void voidConversions(); #ifndef QT_NO_EXCEPTIONS @@ -75,8 +193,94 @@ private slots: void nestedExceptions(); #endif void nonGlobalThreadPool(); + + void then(); + void thenForMoveOnlyTypes(); + void thenOnCanceledFuture(); +#ifndef QT_NO_EXCEPTIONS + void thenOnExceptionFuture(); + void thenThrows(); + void onFailed(); + void onFailedTestCallables(); + void onFailedForMoveOnlyTypes(); +#endif + void onCanceled(); + void cancelContinuations(); + void continuationsWithContext_data(); + void continuationsWithContext(); + void continuationsWithMoveOnlyLambda(); +#if 0 + // TODO: enable when QFuture::takeResults() is enabled + void takeResults(); +#endif + void takeResult(); + void runAndTake(); + void resultsReadyAt_data(); + void resultsReadyAt(); + void takeResultWorksForTypesWithoutDefaultCtor(); + void canceledFutureIsNotValid(); + void signalConnect(); + void waitForFinished(); + + void rejectResultOverwrite_data(); + void rejectResultOverwrite(); + void rejectPendingResultOverwrite_data() { rejectResultOverwrite_data(); } + void rejectPendingResultOverwrite(); + + void createReadyFutures(); + void continuationsAfterReadyFutures(); + + void getFutureInterface(); + void convertQMetaType(); + + void whenAllIterators(); + void whenAllIteratorsWithCanceled(); + void whenAllIteratorsWithFailed(); + void whenAllDifferentTypes(); + void whenAllDifferentTypesWithCanceled(); + void whenAllDifferentTypesWithFailed(); + void whenAnyIterators(); + void whenAnyIteratorsWithCanceled(); + void whenAnyIteratorsWithFailed(); + void whenAnyDifferentTypes(); + void whenAnyDifferentTypesWithCanceled(); + void whenAnyDifferentTypesWithFailed(); + + void continuationOverride(); + void continuationsDontLeak(); + void cancelAfterFinishWithContinuations(); + + void unwrap(); + +private: + using size_type = std::vector<int>::size_type; + + static void testSingleResult(const UniquePtr &p); + static void testSingleResult(const std::vector<int> &v); + template<class T> + static void testSingleResult(const T &unknown); + template<class T> + static void testFutureTaken(QFuture<T> &noMoreFuture); + template<class T> + static void testTakeResults(QFuture<T> future, size_type resultCount); }; +class IntResultsCleaner +{ +public: + IntResultsCleaner(QtPrivate::ResultStoreBase &s) : store(s) { } + ~IntResultsCleaner() { store.clear<int>(); } + +private: + QtPrivate::ResultStoreBase &store; +}; + +void tst_QFuture::compareCompiles() +{ + QTestPrivate::testEqualityOperatorsCompile<QFuture<int>::const_iterator>(); + QTestPrivate::testEqualityOperatorsCompile<QFuture<QString>::const_iterator>(); +} + void tst_QFuture::resultStore() { int int0 = 0; @@ -84,7 +288,9 @@ void tst_QFuture::resultStore() int int2 = 2; { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + QCOMPARE(store.begin(), store.end()); QCOMPARE(store.resultAt(0), store.end()); QCOMPARE(store.resultAt(1), store.end()); @@ -92,7 +298,9 @@ void tst_QFuture::resultStore() { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(-1, &int0); store.addResult(1, &int1); QtPrivate::ResultIteratorBase it = store.begin(); @@ -110,11 +318,13 @@ void tst_QFuture::resultStore() QVERIFY(it == store.end()); } - QVector<int> vec0 = QVector<int>() << 2 << 3; - QVector<int> vec1 = QVector<int>() << 4 << 5; + QList<int> vec0 = QList<int>() << 2 << 3; + QList<int> vec1 = QList<int>() << 4 << 5; { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(-1, &vec0, 2); store.addResults(-1, &vec1, 2); QtPrivate::ResultIteratorBase it = store.begin(); @@ -137,7 +347,9 @@ void tst_QFuture::resultStore() QCOMPARE(it, store.end()); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(-1, &int0); store.addResults(-1, &vec1, 2); store.addResult(-1, &int1); @@ -168,7 +380,9 @@ void tst_QFuture::resultStore() QCOMPARE(store.resultAt(4), store.end()); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(-1, &int0); store.addResults(-1, &vec0); store.addResult(-1, &int1); @@ -198,7 +412,9 @@ void tst_QFuture::resultStore() QCOMPARE(store.resultAt(3).value<int>(), int1); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(-1, &int0); store.addResults(-1, &vec0); store.addResult(200, &int1); @@ -210,7 +426,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(1, &int1); store.addResult(0, &int0); store.addResult(-1, &int2); @@ -221,7 +439,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + QCOMPARE(store.contains(0), false); QCOMPARE(store.contains(1), false); QCOMPARE(store.contains(INT_MAX), false); @@ -229,7 +449,9 @@ void tst_QFuture::resultStore() { // Test filter mode, where "gaps" in the result array aren't allowed. - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(0, &int0); @@ -263,7 +485,9 @@ void tst_QFuture::resultStore() { // test canceled results - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(0, &int0); @@ -300,7 +524,9 @@ void tst_QFuture::resultStore() { // test addResult return value - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(0, &int0); @@ -346,7 +572,9 @@ void tst_QFuture::resultStore() { // test resultCount in non-filtered mode. It should always be possible // to iterate through the results 0 to resultCount. - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(0, &int0); QCOMPARE(store.count(), 1); @@ -360,7 +588,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(2, &int0); QCOMPARE(store.count(), 0); @@ -372,7 +602,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(2, &vec1); QCOMPARE(store.count(), 0); @@ -384,7 +616,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(2, &vec1); QCOMPARE(store.count(), 0); @@ -392,7 +626,9 @@ void tst_QFuture::resultStore() QCOMPARE(store.count(), 4); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -404,7 +640,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -417,7 +655,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -427,7 +667,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -440,7 +682,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(1, &int0); store.addResult(3, &int0); store.addResults(6, &vec0); @@ -455,7 +699,9 @@ void tst_QFuture::resultStore() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(1, &int0); store.addResult(3, &int0); @@ -483,7 +729,9 @@ void tst_QFuture::resultStore() QCOMPARE(store.contains(7), false); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addCanceledResult(0); QCOMPARE(store.contains(0), false); @@ -519,6 +767,19 @@ void tst_QFuture::future() QCOMPARE(intFuture2.isFinished(), true); } +void tst_QFuture::futureToVoid() +{ + QPromise<int> p; + QFuture<int> future = p.future(); + + p.start(); + p.setProgressValue(42); + p.finish(); + + QFuture<void> voidFuture = QFuture<void>(future); + QCOMPARE(voidFuture.progressValue(), 42); +} + class IntResult : public QFutureInterface<int> { public: @@ -563,7 +824,7 @@ void tst_QFuture::futureInterface() { QFutureInterface<int> i; i.reportStarted(); - i.reportResult(10); + QVERIFY(i.reportResult(10)); future = i.future(); i.reportFinished(); } @@ -584,7 +845,7 @@ void tst_QFuture::futureInterface() QCOMPARE(intFuture.isStarted(), true); QCOMPARE(intFuture.isFinished(), false); - result.reportFinished(&value); + QVERIFY(result.reportFinished(&value)); QCOMPARE(intFuture.isStarted(), true); QCOMPARE(intFuture.isFinished(), true); @@ -608,6 +869,36 @@ void tst_QFuture::futureInterface() VoidResult a; a.run().waitForFinished(); } + + { + QFutureInterface<int> i1; + QVERIFY(i1.reportResult(1)); + QFutureInterface<int> i2; + QVERIFY(i2.reportResult(2)); + swap(i1, i2); // ADL must resolve this + QCOMPARE(i1.resultReference(0), 2); + QCOMPARE(i2.resultReference(0), 1); + } + + { + QFutureInterface<int> fi; + fi.reportStarted(); + QVERIFY(!fi.reportResults(QList<int> {})); + fi.reportFinished(); + + QVERIFY(fi.results().empty()); + } + + { + QFutureInterface<int> fi; + fi.reportStarted(); + QList<int> values = { 1, 2, 3 }; + QVERIFY(fi.reportResults(values)); + QVERIFY(!fi.reportResults(QList<int> {})); + fi.reportFinished(); + + QCOMPARE(fi.results(), values); + } } template <typename T> @@ -716,7 +1007,40 @@ void tst_QFuture::cancel() result = 3; futureInterface.reportResult(&result); futureInterface.reportFinished(); - QCOMPARE(f.results(), QList<int>()); + QVERIFY(f.results().isEmpty()); + } +} + +void tst_QFuture::cancelAndFinish() +{ + { + QFutureInterface<void> fi; + + fi.reportStarted(); + fi.cancelAndFinish(); + + QVERIFY(fi.isStarted()); + QVERIFY(!fi.isRunning()); + QVERIFY(!fi.isSuspended()); + QVERIFY(!fi.isSuspending()); + QVERIFY(fi.isCanceled()); + QVERIFY(fi.isFinished()); + } + + // The same with suspended state + { + QFutureInterface<void> fi; + + fi.reportStarted(); + fi.setSuspended(true); + fi.cancelAndFinish(); + + QVERIFY(fi.isStarted()); + QVERIFY(!fi.isRunning()); + QVERIFY(!fi.isSuspended()); + QVERIFY(!fi.isSuspending()); + QVERIFY(fi.isCanceled()); + QVERIFY(fi.isFinished()); } } @@ -767,15 +1091,15 @@ void tst_QFuture::multipleResults() int result; result = 1; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); QCOMPARE(f.resultAt(0), 1); result = 2; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); QCOMPARE(f.resultAt(1), 2); result = 3; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); result = 4; a.reportFinished(&result); @@ -786,13 +1110,13 @@ void tst_QFuture::multipleResults() QList<int> fasit = QList<int>() << 1 << 2 << 3 << 4; { QList<int> results; - foreach(int result, f) + for (int result : std::as_const(f)) results.append(result); QCOMPARE(results, fasit); } { QList<int> results; - foreach(int result, copy) + for (int result : std::as_const(copy)) results.append(result); QCOMPARE(results, fasit); } @@ -816,16 +1140,16 @@ void tst_QFuture::indexedResults() QChar result; result = 'B'; - Interface.reportResult(&result, 1); + QVERIFY(Interface.reportResult(&result, 1)); QCOMPARE(f.resultAt(1), result); result = 'A'; - Interface.reportResult(&result, 0); + QVERIFY(Interface.reportResult(&result, 0)); QCOMPARE(f.resultAt(0), result); result = 'C'; - Interface.reportResult(&result); // no index + QVERIFY(Interface.reportResult(&result)); // no index QCOMPARE(f.resultAt(2), result); Interface.reportFinished(); @@ -841,22 +1165,22 @@ void tst_QFuture::indexedResults() int result; result = 0; - Interface.reportResult(&result, 0); + QVERIFY(Interface.reportResult(&result, 0)); QVERIFY(f.isResultReadyAt(0)); QCOMPARE(f.resultAt(0), 0); result = 3; - Interface.reportResult(&result, 3); + QVERIFY(Interface.reportResult(&result, 3)); QVERIFY(f.isResultReadyAt(3)); QCOMPARE(f.resultAt(3), 3); result = 2; - Interface.reportResult(&result, 2); + QVERIFY(Interface.reportResult(&result, 2)); QVERIFY(f.isResultReadyAt(2)); QCOMPARE(f.resultAt(2), 2); result = 4; - Interface.reportResult(&result); // no index + QVERIFY(Interface.reportResult(&result)); // no index QVERIFY(f.isResultReadyAt(4)); QCOMPARE(f.resultAt(4), 4); @@ -887,6 +1211,55 @@ void tst_QFuture::progress() QCOMPARE (f.progressValue(), 50); } +void tst_QFuture::setProgressRange() +{ + QFutureInterface<int> i; + + QCOMPARE(i.progressMinimum(), 0); + QCOMPARE(i.progressMaximum(), 0); + + i.setProgressRange(10, 5); + + QCOMPARE(i.progressMinimum(), 10); + QCOMPARE(i.progressMaximum(), 10); + + i.setProgressRange(5, 10); + + QCOMPARE(i.progressMinimum(), 5); + QCOMPARE(i.progressMaximum(), 10); +} + +void tst_QFuture::progressWithRange() +{ + QFutureInterface<int> i; + QFuture<int> f; + + i.reportStarted(); + f = i.future(); + + QCOMPARE(i.progressValue(), 0); + + i.setProgressRange(5, 10); + + QCOMPARE(i.progressValue(), 5); + + i.setProgressValue(20); + + QCOMPARE(i.progressValue(), 5); + + i.setProgressValue(9); + + QCOMPARE(i.progressValue(), 9); + + i.setProgressRange(5, 7); + + QCOMPARE(i.progressValue(), 5); + + i.reportFinished(); + + QCOMPARE(f.progressValue(), 5); +} + void tst_QFuture::progressText() { QFutureInterface<void> i; @@ -913,7 +1286,7 @@ void tst_QFuture::resultsAfterFinished() QCOMPARE(f.resultCount(), 0); result = 1; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); QCOMPARE(f.resultAt(0), 1); a.reportFinished(); @@ -921,7 +1294,7 @@ void tst_QFuture::resultsAfterFinished() QCOMPARE(f.resultAt(0), 1); QCOMPARE(f.resultCount(), 1); result = 2; - a.reportResult(&result); + QVERIFY(!a.reportResult(&result)); QCOMPARE(f.resultCount(), 1); } // cancel it @@ -934,7 +1307,7 @@ void tst_QFuture::resultsAfterFinished() QCOMPARE(f.resultCount(), 0); result = 1; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); QCOMPARE(f.resultAt(0), 1); QCOMPARE(f.resultCount(), 1); @@ -944,7 +1317,7 @@ void tst_QFuture::resultsAfterFinished() QCOMPARE(f.resultCount(), 1); result = 2; - a.reportResult(&result); + QVERIFY(!a.reportResult(&result)); a.reportFinished(); } } @@ -957,9 +1330,9 @@ void tst_QFuture::resultsAsList() int result; result = 1; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); result = 2; - a.reportResult(&result); + QVERIFY(a.reportResult(&result)); a.reportFinished(); @@ -967,25 +1340,6 @@ void tst_QFuture::resultsAsList() QCOMPARE(results, QList<int>() << 1 << 2); } -/* - Test that QFuture<T> can be implicitly converted to T -*/ -void tst_QFuture::implicitConversions() -{ - QFutureInterface<QString> iface; - iface.reportStarted(); - - QFuture<QString> f(&iface); - - const QString input("FooBar 2000"); - iface.reportFinished(&input); - - const QString result = f; - QCOMPARE(result, input); - QCOMPARE(QString(f), input); - QCOMPARE(static_cast<QString>(f), input); -} - void tst_QFuture::iterators() { { @@ -1013,16 +1367,16 @@ void tst_QFuture::iterators() QFuture<int>::const_iterator i1 = f.begin(), i2 = i1 + 1; QFuture<int>::const_iterator c1 = i1, c2 = c1 + 1; - QCOMPARE(i1, i1); - QCOMPARE(i1, c1); - QCOMPARE(c1, i1); - QCOMPARE(c1, c1); - QCOMPARE(i2, i2); - QCOMPARE(i2, c2); - QCOMPARE(c2, i2); - QCOMPARE(c2, c2); - QCOMPARE(1 + i1, i1 + 1); - QCOMPARE(1 + c1, c1 + 1); + QT_TEST_EQUALITY_OPS(i1, i1, true); + QT_TEST_EQUALITY_OPS(i1, c1, true); + QT_TEST_EQUALITY_OPS(c1, i1, true); + QT_TEST_EQUALITY_OPS(c1, c1, true); + QT_TEST_EQUALITY_OPS(i2, i2, true); + QT_TEST_EQUALITY_OPS(i2, c2, true); + QT_TEST_EQUALITY_OPS(c2, i2, true); + QT_TEST_EQUALITY_OPS(c2, c2, true); + QT_TEST_EQUALITY_OPS(1 + i1, i1 + 1, true); + QT_TEST_EQUALITY_OPS(1 + c1, c1 + 1, true); QVERIFY(i1 != i2); QVERIFY(i1 != c2); @@ -1034,13 +1388,13 @@ void tst_QFuture::iterators() QVERIFY(c2 != c1); int x1 = *i1; - Q_UNUSED(x1); + Q_UNUSED(x1) int x2 = *i2; - Q_UNUSED(x2); + Q_UNUSED(x2) int y1 = *c1; - Q_UNUSED(y1); + Q_UNUSED(y1) int y2 = *c2; - Q_UNUSED(y2); + Q_UNUSED(y2) } { @@ -1092,10 +1446,10 @@ void tst_QFuture::iterators() QCOMPARE(x1, y1); QCOMPARE(x2, y2); - int i1Size = i1->size(); - int i2Size = i2->size(); - int c1Size = c1->size(); - int c2Size = c2->size(); + auto i1Size = i1->size(); + auto i2Size = i2->size(); + auto c1Size = c1->size(); + auto c2Size = c2->size(); QCOMPARE(i1Size, c1Size); QCOMPARE(i2Size, c2Size); @@ -1144,6 +1498,53 @@ void tst_QFuture::iterators() } } } +void tst_QFuture::iteratorsThread() +{ + const int expectedResultCount = 10; + QFutureInterface<int> futureInterface; + + // Create result producer thread. The results are + // produced with delays in order to make the consumer + // wait. + QSemaphore sem; + LambdaThread thread = {[=, &futureInterface, &sem](){ + for (int i = 1; i <= expectedResultCount; i += 2) { + int result = i; + futureInterface.reportResult(&result); + result = i + 1; + futureInterface.reportResult(&result); + } + + sem.acquire(2); + futureInterface.reportFinished(); + }}; + + futureInterface.reportStarted(); + QFuture<int> future = futureInterface.future(); + + // Iterate over results while the thread is producing them. + thread.start(); + int resultCount = 0; + int resultSum = 0; + for (int result : future) { + sem.release(); + ++resultCount; + resultSum += result; + } + thread.wait(); + + QCOMPARE(resultCount, expectedResultCount); + QCOMPARE(resultSum, expectedResultCount * (expectedResultCount + 1) / 2); + + // Reverse iterate + resultSum = 0; + QFutureIterator<int> it(future); + it.toBack(); + while (it.hasPrevious()) + resultSum += it.previous(); + + QCOMPARE(resultSum, expectedResultCount * (expectedResultCount + 1) / 2); +} class SignalSlotObject : public QObject { @@ -1190,6 +1591,9 @@ public: QSet<int> reportedProgress; }; +#if QT_DEPRECATED_SINCE(6, 0) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED void tst_QFuture::pause() { QFutureInterface<void> Interface; @@ -1210,6 +1614,102 @@ void tst_QFuture::pause() Interface.reportFinished(); } +void tst_QFuture::suspendCheckPaused() +{ + QFutureInterface<void> interface; + + interface.reportStarted(); + QFuture<void> f = interface.future(); + QVERIFY(!f.isSuspended()); + + interface.reportSuspended(); + QVERIFY(!f.isSuspended()); + + f.pause(); + QVERIFY(!f.isSuspended()); + QVERIFY(f.isPaused()); + + // resume when still pausing + f.resume(); + QVERIFY(!f.isSuspended()); + QVERIFY(!f.isPaused()); + + // pause again + f.pause(); + QVERIFY(!f.isSuspended()); + QVERIFY(f.isPaused()); + + interface.reportSuspended(); + QVERIFY(f.isSuspended()); + QVERIFY(f.isPaused()); + + // resume after suspended + f.resume(); + QVERIFY(!f.isSuspended()); + QVERIFY(!f.isPaused()); + + // pause again and cancel + f.pause(); + interface.reportSuspended(); + + interface.reportCanceled(); + QVERIFY(!f.isSuspended()); + QVERIFY(!f.isPaused()); + QVERIFY(f.isCanceled()); + + interface.reportFinished(); +} + +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 0) + +void tst_QFuture::suspend() +{ + QFutureInterface<void> interface; + + interface.reportStarted(); + QFuture<void> f = interface.future(); + QVERIFY(!f.isSuspended()); + + interface.reportSuspended(); + QVERIFY(!f.isSuspended()); + QVERIFY(!f.isSuspending()); + + f.suspend(); + QVERIFY(f.isSuspending()); + QVERIFY(!f.isSuspended()); + + // resume when still suspending + f.resume(); + QVERIFY(!f.isSuspending()); + QVERIFY(!f.isSuspended()); + + // suspend again + f.suspend(); + QVERIFY(f.isSuspending()); + QVERIFY(!f.isSuspended()); + + interface.reportSuspended(); + QVERIFY(!f.isSuspending()); + QVERIFY(f.isSuspended()); + + // resume after suspended + f.resume(); + QVERIFY(!f.isSuspending()); + QVERIFY(!f.isSuspended()); + + // suspend again and cancel + f.suspend(); + interface.reportSuspended(); + + interface.reportCanceled(); + QVERIFY(!f.isSuspending()); + QVERIFY(!f.isSuspended()); + QVERIFY(f.isCanceled()); + + interface.reportFinished(); +} + class ResultObject : public QObject { Q_OBJECT @@ -1254,12 +1754,10 @@ void tst_QFuture::voidConversions() QFuture<int> intFuture(&iface); int value = 10; - iface.reportFinished(&value); + QVERIFY(iface.reportFinished(&value)); QFuture<void> voidFuture(intFuture); voidFuture = intFuture; - - QVERIFY(voidFuture == intFuture); } { @@ -1269,7 +1767,7 @@ void tst_QFuture::voidConversions() iface.reportStarted(); QFuture<QList<int> > listFuture(&iface); - iface.reportResult(QList<int>() << 1 << 2 << 3); + QVERIFY(iface.reportResult(QList<int>() << 1 << 2 << 3)); voidFuture = listFuture; } QCOMPARE(voidFuture.resultCount(), 0); @@ -1324,6 +1822,23 @@ QFuture<void> createDerivedExceptionFuture() return f; } +struct TestException +{ +}; + +QFuture<int> createCustomExceptionFuture() +{ + QFutureInterface<int> i; + i.reportStarted(); + QFuture<int> f = i.future(); + int r = 0; + i.reportResult(r); + auto exception = std::make_exception_ptr(TestException()); + i.reportException(exception); + i.reportFinished(); + return f; +} + void tst_QFuture::exceptions() { // test throwing from waitForFinished @@ -1379,7 +1894,7 @@ void tst_QFuture::exceptions() bool caught = false; try { foreach (int e, f.results()) { - Q_UNUSED(e); + Q_UNUSED(e) QFAIL("did not get exception"); } } catch (QException &) { @@ -1408,6 +1923,18 @@ void tst_QFuture::exceptions() } QVERIFY(caught); } + + // Custom exceptions + { + QFuture<int> f = createCustomExceptionFuture(); + bool caught = false; + try { + f.result(); + } catch (const TestException &) { + caught = true; + } + QVERIFY(caught); + } } class MyClass @@ -1434,7 +1961,7 @@ void tst_QFuture::nestedExceptions() { try { MyClass m; - Q_UNUSED(m); + Q_UNUSED(m) throw 0; } catch (int) {} @@ -1445,7 +1972,7 @@ void tst_QFuture::nestedExceptions() void tst_QFuture::nonGlobalThreadPool() { - static Q_CONSTEXPR int Answer = 42; + static constexpr int Answer = 42; struct UselessTask : QRunnable, QFutureInterface<int> { @@ -1462,7 +1989,7 @@ void tst_QFuture::nonGlobalThreadPool() void run() override { const int ms = 100 + (QRandomGenerator::global()->bounded(100) - 100/2); - QThread::msleep(ms); + QThread::sleep(std::chrono::milliseconds{ms}); reportResult(Answer); reportFinished(); } @@ -1472,7 +1999,7 @@ void tst_QFuture::nonGlobalThreadPool() const int numTasks = QThread::idealThreadCount(); - QVector<QFuture<int> > futures; + QList<QFuture<int>> futures; futures.reserve(numTasks); for (int i = 0; i < numTasks; ++i) @@ -1490,5 +2017,3355 @@ void tst_QFuture::nonGlobalThreadPool() } } +void tst_QFuture::then() +{ + { + struct Add + { + + static int addTwo(int arg) { return arg + 2; } + + int operator()(int arg) const { return arg + 3; } + }; + + QFutureInterface<int> promise; + QFuture<int> then = promise.future() + .then([](int res) { return res + 1; }) // lambda + .then(Add::addTwo) // function + .then(Add()); // functor + + promise.reportStarted(); + QVERIFY(!then.isStarted()); + QVERIFY(!then.isFinished()); + + const int result = 0; + promise.reportResult(result); + promise.reportFinished(); + + then.waitForFinished(); + + QVERIFY(then.isStarted()); + QVERIFY(then.isFinished()); + QCOMPARE(then.result(), result + 6); + } + + // then() on a ready future + { + QFutureInterface<int> promise; + promise.reportStarted(); + + const int result = 0; + promise.reportResult(result); + promise.reportFinished(); + + QFuture<int> then = promise.future() + .then([](int res1) { return res1 + 1; }) + .then([](int res2) { return res2 + 2; }) + .then([](int res3) { return res3 + 3; }); + + then.waitForFinished(); + + QVERIFY(then.isStarted()); + QVERIFY(then.isFinished()); + QCOMPARE(then.result(), result + 6); + } + + // Continuation of QFuture<void> + { + int result = 0; + QFutureInterface<void> promise; + QFuture<void> then = promise.future() + .then([&]() { result += 1; }) + .then([&]() { result += 2; }) + .then([&]() { result += 3; }); + + promise.reportStarted(); + QVERIFY(!then.isStarted()); + QVERIFY(!then.isFinished()); + promise.reportFinished(); + + then.waitForFinished(); + + QVERIFY(then.isStarted()); + QVERIFY(then.isFinished()); + QCOMPARE(result, 6); + } + + // Continuation returns QFuture<void> + { + QFutureInterface<int> promise; + int value; + QFuture<void> then = + promise.future().then([](int res) { return res * 2; }).then([&](int prevResult) { + value = prevResult; + }); + + promise.reportStarted(); + QVERIFY(!then.isStarted()); + QVERIFY(!then.isFinished()); + + const int result = 5; + promise.reportResult(result); + promise.reportFinished(); + + then.waitForFinished(); + + QVERIFY(then.isStarted()); + QVERIFY(then.isFinished()); + QCOMPARE(value, result * 2); + } + + // Continuations taking a QFuture argument. + { + int value = 0; + QFutureInterface<int> promise; + QFuture<void> then = promise.future() + .then([](QFuture<int> f1) { return f1.result() + 1; }) + .then([&](QFuture<int> f2) { value = f2.result() + 2; }) + .then([&](QFuture<void> f3) { + QVERIFY(f3.isFinished()); + value += 3; + }); + + promise.reportStarted(); + QVERIFY(!then.isStarted()); + QVERIFY(!then.isFinished()); + + const int result = 0; + promise.reportResult(result); + promise.reportFinished(); + + then.waitForFinished(); + + QVERIFY(then.isStarted()); + QVERIFY(then.isFinished()); + QCOMPARE(value, 6); + } + + // Continuations use a new thread + { + Qt::HANDLE threadId1 = nullptr; + Qt::HANDLE threadId2 = nullptr; + QFutureInterface<void> promise; + QFuture<void> then = promise.future() + .then(QtFuture::Launch::Async, + [&]() { threadId1 = QThread::currentThreadId(); }) + .then([&]() { threadId2 = QThread::currentThreadId(); }); + + promise.reportStarted(); + QVERIFY(!then.isStarted()); + QVERIFY(!then.isFinished()); + + promise.reportFinished(); + + then.waitForFinished(); + + QVERIFY(then.isStarted()); + QVERIFY(then.isFinished()); + QVERIFY(threadId1 != QThread::currentThreadId()); + QVERIFY(threadId2 != QThread::currentThreadId()); + QVERIFY(threadId1 == threadId2); + } + + // Continuation inherits the launch policy of its parent (QtFuture::Launch::Sync) + { + Qt::HANDLE threadId1 = nullptr; + Qt::HANDLE threadId2 = nullptr; + QFutureInterface<void> promise; + QFuture<void> then = promise.future() + .then(QtFuture::Launch::Sync, + [&]() { threadId1 = QThread::currentThreadId(); }) + .then(QtFuture::Launch::Inherit, + [&]() { threadId2 = QThread::currentThreadId(); }); + + promise.reportStarted(); + QVERIFY(!then.isStarted()); + QVERIFY(!then.isFinished()); + + promise.reportFinished(); + + then.waitForFinished(); + + QVERIFY(then.isStarted()); + QVERIFY(then.isFinished()); + QVERIFY(threadId1 == QThread::currentThreadId()); + QVERIFY(threadId2 == QThread::currentThreadId()); + QVERIFY(threadId1 == threadId2); + } + + // Continuation inherits the launch policy of its parent (QtFuture::Launch::Async) + { + Qt::HANDLE threadId1 = nullptr; + Qt::HANDLE threadId2 = nullptr; + QFutureInterface<void> promise; + QFuture<void> then = promise.future() + .then(QtFuture::Launch::Async, + [&]() { threadId1 = QThread::currentThreadId(); }) + .then(QtFuture::Launch::Inherit, + [&]() { threadId2 = QThread::currentThreadId(); }); + + promise.reportStarted(); + QVERIFY(!then.isStarted()); + QVERIFY(!then.isFinished()); + + promise.reportFinished(); + + then.waitForFinished(); + + QVERIFY(then.isStarted()); + QVERIFY(then.isFinished()); + QVERIFY(threadId1 != QThread::currentThreadId()); + QVERIFY(threadId2 != QThread::currentThreadId()); + } + + // Continuations use a custom thread pool + { + QFutureInterface<void> promise; + QThreadPool pool; + QVERIFY(pool.waitForDone(0)); // pool is not busy yet + QSemaphore semaphore; + QFuture<void> then = promise.future().then(&pool, [&]() { semaphore.acquire(); }); + + promise.reportStarted(); + promise.reportFinished(); + + // Make sure the custom thread pool is busy on running the continuation + QVERIFY(!pool.waitForDone(0)); + semaphore.release(); + then.waitForFinished(); + + QVERIFY(then.isStarted()); + QVERIFY(then.isFinished()); + QCOMPARE(then.d.threadPool(), &pool); + } + + // Continuation inherits parent's thread pool + { + Qt::HANDLE threadId1 = nullptr; + Qt::HANDLE threadId2 = nullptr; + QFutureInterface<void> promise; + + QThreadPool pool; + QFuture<void> then1 = promise.future().then(&pool, [&]() { + threadId1 = QThread::currentThreadId(); + }); + + promise.reportStarted(); + promise.reportFinished(); + + then1.waitForFinished(); + QVERIFY(pool.waitForDone()); // The pool is not busy after the first continuation is done + + QSemaphore semaphore; + QFuture<void> then2 = then1.then(QtFuture::Launch::Inherit, [&]() { + semaphore.acquire(); + threadId2 = QThread::currentThreadId(); + }); + + QVERIFY(!pool.waitForDone(0)); // The pool is busy running the 2nd continuation + + semaphore.release(); + then2.waitForFinished(); + + QVERIFY(then2.isStarted()); + QVERIFY(then2.isFinished()); + QCOMPARE(then1.d.threadPool(), then2.d.threadPool()); + QCOMPARE(then2.d.threadPool(), &pool); + QVERIFY(threadId1 != QThread::currentThreadId()); + QVERIFY(threadId2 != QThread::currentThreadId()); + } + + // QTBUG-106083 & QTBUG-105182 + { + QThread thread; + thread.start(); + + QObject context; + context.moveToThread(&thread); + + auto future = QtConcurrent::run([] { + return 42; + }).then([] (int result) { + return result + 1; + }).then(&context, [] (int result) { + return result + 1; + }); + QCOMPARE(future.result(), 44); + thread.quit(); + thread.wait(); + } +} + +template<class Type, class Callable> +bool runThenForMoveOnly(Callable &&callable) +{ + QFutureInterface<Type> promise; + auto future = promise.future(); + + auto then = future.then(std::forward<Callable>(callable)); + + promise.reportStarted(); + if constexpr (!std::is_same_v<Type, void>) + promise.reportAndMoveResult(std::make_unique<int>(42)); + promise.reportFinished(); + then.waitForFinished(); + + bool success = true; + if constexpr (!std::is_same_v<decltype(then), QFuture<void>>) + success &= *then.takeResult() == 42; + + if constexpr (!std::is_same_v<Type, void>) + success &= !future.isValid(); + + return success; +} + +void tst_QFuture::thenForMoveOnlyTypes() +{ + QVERIFY(runThenForMoveOnly<UniquePtr>([](UniquePtr res) { return res; })); + QVERIFY(runThenForMoveOnly<UniquePtr>([](UniquePtr res) { Q_UNUSED(res); })); + QVERIFY(runThenForMoveOnly<UniquePtr>([](QFuture<UniquePtr> res) { return res.takeResult(); })); + QVERIFY(runThenForMoveOnly<void>([] { return std::make_unique<int>(42); })); +} + +template<class T> +QFuture<T> createCanceledFuture() +{ + QFutureInterface<T> promise; + promise.reportStarted(); + promise.reportCanceled(); + promise.reportFinished(); + return promise.future(); +} + +void tst_QFuture::thenOnCanceledFuture() +{ + // Continuations on a canceled future + { + int thenResult = 0; + QFuture<void> then = createCanceledFuture<void>().then([&]() { ++thenResult; }).then([&]() { + ++thenResult; + }); + + QVERIFY(then.isCanceled()); + QCOMPARE(thenResult, 0); + } + + // QFuture gets canceled after continuations are set + { + QFutureInterface<void> promise; + + int thenResult = 0; + QFuture<void> then = + promise.future().then([&]() { ++thenResult; }).then([&]() { ++thenResult; }); + + promise.reportStarted(); + promise.reportCanceled(); + promise.reportFinished(); + + QVERIFY(then.isCanceled()); + QCOMPARE(thenResult, 0); + } + + // Same with QtFuture::Launch::Async + + // Continuations on a canceled future + { + int thenResult = 0; + QFuture<void> then = createCanceledFuture<void>() + .then(QtFuture::Launch::Async, [&]() { ++thenResult; }) + .then([&]() { ++thenResult; }); + + QVERIFY(then.isCanceled()); + QCOMPARE(thenResult, 0); + } + + // QFuture gets canceled after continuations are set + { + QFutureInterface<void> promise; + + int thenResult = 0; + QFuture<void> then = + promise.future().then(QtFuture::Launch::Async, [&]() { ++thenResult; }).then([&]() { + ++thenResult; + }); + + promise.reportStarted(); + promise.reportCanceled(); + promise.reportFinished(); + + QVERIFY(then.isCanceled()); + QCOMPARE(thenResult, 0); + } +} + +#ifndef QT_NO_EXCEPTIONS +void tst_QFuture::thenOnExceptionFuture() +{ + { + QFutureInterface<int> promise; + + int thenResult = 0; + QFuture<void> then = promise.future().then([&](int res) { thenResult = res; }); + + promise.reportStarted(); + QException e; + promise.reportException(e); + promise.reportFinished(); + + bool caught = false; + try { + then.waitForFinished(); + } catch (QException &) { + caught = true; + } + QVERIFY(caught); + QCOMPARE(thenResult, 0); + } + + // Exception handled inside the continuation + { + QFutureInterface<int> promise; + + bool caught = false; + bool caughtByContinuation = false; + bool success = false; + int thenResult = 0; + QFuture<void> then = promise.future() + .then([&](QFuture<int> res) { + try { + thenResult = res.result(); + } catch (QException &) { + caughtByContinuation = true; + } + }) + .then([&]() { success = true; }); + + promise.reportStarted(); + QException e; + promise.reportException(e); + promise.reportFinished(); + + try { + then.waitForFinished(); + } catch (QException &) { + caught = true; + } + + QCOMPARE(thenResult, 0); + QVERIFY(!caught); + QVERIFY(caughtByContinuation); + QVERIFY(success); + } + + // Exception future + { + QFutureInterface<int> promise; + promise.reportStarted(); + QException e; + promise.reportException(e); + promise.reportFinished(); + + int thenResult = 0; + QFuture<void> then = promise.future().then([&](int res) { thenResult = res; }); + + bool caught = false; + try { + then.waitForFinished(); + } catch (QException &) { + caught = true; + } + QVERIFY(caught); + QCOMPARE(thenResult, 0); + } + + // Same with QtFuture::Launch::Async + { + QFutureInterface<int> promise; + + int thenResult = 0; + QFuture<void> then = + promise.future().then(QtFuture::Launch::Async, [&](int res) { thenResult = res; }); + + promise.reportStarted(); + QException e; + promise.reportException(e); + promise.reportFinished(); + + bool caught = false; + try { + then.waitForFinished(); + } catch (QException &) { + caught = true; + } + QVERIFY(caught); + QCOMPARE(thenResult, 0); + } + + // Exception future + { + QFutureInterface<int> promise; + promise.reportStarted(); + QException e; + promise.reportException(e); + promise.reportFinished(); + + int thenResult = 0; + QFuture<void> then = + promise.future().then(QtFuture::Launch::Async, [&](int res) { thenResult = res; }); + + bool caught = false; + try { + then.waitForFinished(); + } catch (QException &) { + caught = true; + } + QVERIFY(caught); + QCOMPARE(thenResult, 0); + } +} + +template<class Exception, bool hasTestMsg = false> +QFuture<void> createExceptionContinuation(QtFuture::Launch policy = QtFuture::Launch::Sync) +{ + QFutureInterface<void> promise; + + auto then = promise.future().then(policy, [] { + if constexpr (hasTestMsg) + throw Exception("TEST"); + else + throw Exception(); + }); + + promise.reportStarted(); + promise.reportFinished(); + + return then; +} + +void tst_QFuture::thenThrows() +{ + // Continuation throws a QException + { + auto future = createExceptionContinuation<QException>(); + + bool caught = false; + try { + future.waitForFinished(); + } catch (const QException &) { + caught = true; + } + QVERIFY(caught); + } + + // Continuation throws an exception derived from QException + { + auto future = createExceptionContinuation<DerivedException>(); + + bool caught = false; + try { + future.waitForFinished(); + } catch (const QException &) { + caught = true; + } catch (const std::exception &) { + QFAIL("The exception should be caught by the above catch block."); + } + + QVERIFY(caught); + } + + // Continuation throws std::exception + { + auto future = createExceptionContinuation<std::runtime_error, true>(); + + bool caught = false; + try { + future.waitForFinished(); + } catch (const QException &) { + QFAIL("The exception should be caught by the below catch block."); + } catch (const std::exception &e) { + QCOMPARE(e.what(), "TEST"); + caught = true; + } + + QVERIFY(caught); + } + + // Same with QtFuture::Launch::Async + { + auto future = createExceptionContinuation<QException>(QtFuture::Launch::Async); + + bool caught = false; + try { + future.waitForFinished(); + } catch (const QException &) { + caught = true; + } + QVERIFY(caught); + } +} + +void tst_QFuture::onFailed() +{ + // Ready exception void future + { + int checkpoint = 0; + auto future = createExceptionFuture().then([&] { checkpoint = 1; }).onFailed([&] { + checkpoint = 2; + }); + + try { + future.waitForFinished(); + } catch (...) { + checkpoint = 3; + } + QCOMPARE(checkpoint, 2); + } + + // std::exception handler + { + QFutureInterface<int> promise; + + int checkpoint = 0; + auto then = promise.future() + .then([&](int res) { + throw std::exception(); + return res; + }) + .then([&](int res) { return res + 1; }) + .onFailed([&](const QException &) { + checkpoint = 1; + return -1; + }) + .onFailed([&](const std::exception &) { + checkpoint = 2; + return -1; + }) + .onFailed([&] { + checkpoint = 3; + return -1; + }); + + promise.reportStarted(); + promise.reportResult(1); + promise.reportFinished(); + + int res = 0; + try { + res = then.result(); + } catch (...) { + checkpoint = 4; + } + QCOMPARE(checkpoint, 2); + QCOMPARE(res, -1); + } + + // then() throws an exception derived from QException + { + QFutureInterface<int> promise; + + int checkpoint = 0; + auto then = promise.future() + .then([&](int res) { + throw DerivedException(); + return res; + }) + .then([&](int res) { return res + 1; }) + .onFailed([&](const QException &) { + checkpoint = 1; + return -1; + }) + .onFailed([&](const std::exception &) { + checkpoint = 2; + return -1; + }) + .onFailed([&] { + checkpoint = 3; + return -1; + }); + + promise.reportStarted(); + promise.reportResult(1); + promise.reportFinished(); + + int res = 0; + try { + res = then.result(); + } catch (...) { + checkpoint = 4; + } + QCOMPARE(checkpoint, 1); + QCOMPARE(res, -1); + } + + // then() throws a custom exception + { + QFutureInterface<int> promise; + + int checkpoint = 0; + auto then = promise.future() + .then([&](int res) { + throw TestException(); + return res; + }) + .then([&](int res) { return res + 1; }) + .onFailed([&](const QException &) { + checkpoint = 1; + return -1; + }) + .onFailed([&](const std::exception &) { + checkpoint = 2; + return -1; + }) + .onFailed([&] { + checkpoint = 3; + return -1; + }); + + promise.reportStarted(); + promise.reportResult(1); + promise.reportFinished(); + + int res = 0; + try { + res = then.result(); + } catch (...) { + checkpoint = 4; + } + QCOMPARE(checkpoint, 3); + QCOMPARE(res, -1); + } + + // Custom exception handler + { + struct TestException + { + }; + + QFutureInterface<int> promise; + + int checkpoint = 0; + auto then = promise.future() + .then([&](int res) { + throw TestException(); + return res; + }) + .then([&](int res) { return res + 1; }) + .onFailed([&](const QException &) { + checkpoint = 1; + return -1; + }) + .onFailed([&](const TestException &) { + checkpoint = 2; + return -1; + }) + .onFailed([&] { + checkpoint = 3; + return -1; + }); + + promise.reportStarted(); + promise.reportResult(1); + promise.reportFinished(); + + int res = 0; + try { + res = then.result(); + } catch (...) { + checkpoint = 4; + } + QCOMPARE(checkpoint, 2); + QCOMPARE(res, -1); + } + + // Handle all exceptions + { + QFutureInterface<int> promise; + + int checkpoint = 0; + auto then = promise.future() + .then([&](int res) { + throw QException(); + return res; + }) + .then([&](int res) { return res + 1; }) + .onFailed([&] { + checkpoint = 1; + return -1; + }) + .onFailed([&](const QException &) { + checkpoint = 2; + return -1; + }); + + promise.reportStarted(); + promise.reportResult(1); + promise.reportFinished(); + + int res = 0; + try { + res = then.result(); + } catch (...) { + checkpoint = 3; + } + QCOMPARE(checkpoint, 1); + QCOMPARE(res, -1); + } + + // Handler throws exception + { + QFutureInterface<int> promise; + + int checkpoint = 0; + auto then = promise.future() + .then([&](int res) { + throw QException(); + return res; + }) + .then([&](int res) { return res + 1; }) + .onFailed([&](const QException &) { + checkpoint = 1; + throw QException(); + return -1; + }) + .onFailed([&] { + checkpoint = 2; + return -1; + }); + + promise.reportStarted(); + promise.reportResult(1); + promise.reportFinished(); + + int res = 0; + try { + res = then.result(); + } catch (...) { + checkpoint = 3; + } + QCOMPARE(checkpoint, 2); + QCOMPARE(res, -1); + } + + // No handler for exception + { + QFutureInterface<int> promise; + + int checkpoint = 0; + auto then = promise.future() + .then([&](int res) { + throw QException(); + return res; + }) + .then([&](int res) { return res + 1; }) + .onFailed([&](const std::exception &) { + checkpoint = 1; + throw std::exception(); + return -1; + }) + .onFailed([&](QException &) { + checkpoint = 2; + return -1; + }); + + promise.reportStarted(); + promise.reportResult(1); + promise.reportFinished(); + + int res = 0; + try { + res = then.result(); + } catch (...) { + checkpoint = 3; + } + QCOMPARE(checkpoint, 3); + QCOMPARE(res, 0); + } + + // onFailed on a canceled future + { + auto future = createCanceledFuture<int>() + .then([](int) { return 42; }) + .onCanceled([] { return -1; }) + .onFailed([] { return -2; }); + QCOMPARE(future.result(), -1); + } +} + +template<class Callable> +bool runForCallable(Callable &&handler) +{ + QFuture<int> future = createExceptionResultFuture() + .then([&](int) { return 1; }) + .onFailed(std::forward<Callable>(handler)); + + int res = 0; + try { + res = future.result(); + } catch (...) { + return false; + } + return res == -1; +} + +int foo() +{ + return -1; +} + +void tst_QFuture::onFailedTestCallables() +{ + QVERIFY(runForCallable([&] { return -1; })); + QVERIFY(runForCallable(foo)); + QVERIFY(runForCallable(&foo)); + + std::function<int()> func = foo; + QVERIFY(runForCallable(func)); + + struct Functor1 + { + int operator()() { return -1; } + static int foo() { return -1; } + }; + QVERIFY(runForCallable(Functor1())); + QVERIFY(runForCallable(Functor1::foo)); + + struct Functor2 + { + int operator()() const { return -1; } + static int foo() { return -1; } + }; + QVERIFY(runForCallable(Functor2())); + + struct Functor3 + { + int operator()() const noexcept { return -1; } + static int foo() { return -1; } + }; + QVERIFY(runForCallable(Functor3())); +} + +template<class Callable> +bool runOnFailedForMoveOnly(Callable &&callable) +{ + QFutureInterface<UniquePtr> promise; + auto future = promise.future(); + + auto failedFuture = future.onFailed(std::forward<Callable>(callable)); + + promise.reportStarted(); + QException e; + promise.reportException(e); + promise.reportFinished(); + + return *failedFuture.takeResult() == -1; +} + +void tst_QFuture::onFailedForMoveOnlyTypes() +{ + QVERIFY(runOnFailedForMoveOnly([](const QException &) { return std::make_unique<int>(-1); })); + QVERIFY(runOnFailedForMoveOnly([] { return std::make_unique<int>(-1); })); +} + +#endif // QT_NO_EXCEPTIONS + +void tst_QFuture::onCanceled() +{ + // Canceled int future + { + auto future = createCanceledFuture<int>().then([](int) { return 42; }).onCanceled([] { + return -1; + }); + QCOMPARE(future.result(), -1); + } + + // Canceled void future + { + int checkpoint = 0; + auto future = createCanceledFuture<void>().then([&] { checkpoint = 42; }).onCanceled([&] { + checkpoint = -1; + }); + QCOMPARE(checkpoint, -1); + } + + // onCanceled propagates result + { + QFutureInterface<int> promise; + auto future = + promise.future().then([](int res) { return res; }).onCanceled([] { return -1; }); + + promise.reportStarted(); + promise.reportResult(42); + promise.reportFinished(); + QCOMPARE(future.result(), 42); + } + + // onCanceled propagates move-only result + { + QFutureInterface<UniquePtr> promise; + auto future = promise.future().then([](UniquePtr res) { return res; }).onCanceled([] { + return std::make_unique<int>(-1); + }); + + promise.reportStarted(); + promise.reportAndMoveResult(std::make_unique<int>(42)); + promise.reportFinished(); + QCOMPARE(*future.takeResult(), 42); + } + +#ifndef QT_NO_EXCEPTIONS + // onCanceled propagates exceptions + { + QFutureInterface<int> promise; + auto future = promise.future() + .then([](int res) { + throw std::runtime_error("error"); + return res; + }) + .onCanceled([] { return 2; }) + .onFailed([] { return 3; }); + + promise.reportStarted(); + promise.reportResult(1); + promise.reportFinished(); + QCOMPARE(future.result(), 3); + } + + // onCanceled throws + { + auto future = createCanceledFuture<int>() + .then([](int) { return 42; }) + .onCanceled([] { + throw std::runtime_error("error"); + return -1; + }) + .onFailed([] { return -2; }); + + QCOMPARE(future.result(), -2); + } + +#endif // QT_NO_EXCEPTIONS +} + +void tst_QFuture::cancelContinuations() +{ + // The chain is cancelled in the middle of execution of continuations + { + QPromise<int> promise; + + int checkpoint = 0; + auto future = promise.future().then([&](int value) { + ++checkpoint; + return value + 1; + }).then([&](int value) { + ++checkpoint; + promise.future().cancel(); + return value + 1; + }).then([&](int value) { + ++checkpoint; + return value + 1; + }).onCanceled([] { + return -1; + }); + + promise.start(); + promise.addResult(42); + promise.finish(); + + QCOMPARE(future.result(), -1); + QCOMPARE(checkpoint, 2); + } + + // The chain is cancelled before the execution of continuations + { + auto f = QtFuture::makeReadyValueFuture(42); + f.cancel(); + + int checkpoint = 0; + auto future = f.then([&](int value) { + ++checkpoint; + return value + 1; + }).then([&](int value) { + ++checkpoint; + return value + 1; + }).then([&](int value) { + ++checkpoint; + return value + 1; + }).onCanceled([] { + return -1; + }); + + QCOMPARE(future.result(), -1); + QCOMPARE(checkpoint, 0); + } + + // The chain is canceled partially, through an intermediate future + { + QPromise<int> promise; + + int checkpoint = 0; + auto intermediate = promise.future().then([&](int value) { + ++checkpoint; + return value + 1; + }); + + auto future = intermediate.then([&](int value) { + ++checkpoint; + return value + 1; + }).then([&](int value) { + ++checkpoint; + return value + 1; + }).onCanceled([] { + return -1; + }); + + promise.start(); + promise.addResult(42); + + // This should cancel only the chain starting from intermediate + intermediate.cancel(); + + promise.finish(); + + QCOMPARE(future.result(), -1); + QCOMPARE(checkpoint, 1); + } + +#ifndef QT_NO_EXCEPTIONS + // The chain is cancelled in the middle of execution of continuations, + // while there's an exception in the chain, which is handled inside + // the continuations. + { + QPromise<int> promise; + + int checkpoint = 0; + auto future = promise.future().then([&](int value) { + ++checkpoint; + throw QException(); + return value + 1; + }).then([&](QFuture<int> future) { + try { + auto res = future.result(); + Q_UNUSED(res); + } catch (const QException &) { + ++checkpoint; + } + return 2; + }).then([&](int value) { + ++checkpoint; + promise.future().cancel(); + return value + 1; + }).then([&](int value) { + ++checkpoint; + return value + 1; + }).onCanceled([] { + return -1; + }); + + promise.start(); + promise.addResult(42); + promise.finish(); + + QCOMPARE(future.result(), -1); + QCOMPARE(checkpoint, 3); + } +#endif // QT_NO_EXCEPTIONS + + // Check notifications from QFutureWatcher + { + QPromise<void> p; + auto f = p.future(); + + auto f1 = f.then([] {}); + auto f2 = f1.then([] {}); + + QFutureWatcher<void> watcher1, watcher2; + int state = 0; + QObject::connect(&watcher1, &QFutureWatcher<void>::started, [&] { + QCOMPARE(state, 0); + ++state; + }); + QObject::connect(&watcher1, &QFutureWatcher<void>::canceled, [&] { + QCOMPARE(state, 1); + ++state; + }); + QObject::connect(&watcher1, &QFutureWatcher<void>::finished, [&] { + QCOMPARE(state, 2); + ++state; + }); + QObject::connect(&watcher2, &QFutureWatcher<void>::started, [&] { + QCOMPARE(state, 3); + ++state; + }); + QObject::connect(&watcher2, &QFutureWatcher<void>::canceled, [&] { + QCOMPARE(state, 4); + ++state; + }); + QObject::connect(&watcher2, &QFutureWatcher<int>::finished, [&] { + QCOMPARE(state, 5); + ++state; + }); + + watcher1.setFuture(f1); + watcher2.setFuture(f2); + + p.start(); + f.cancel(); + p.finish(); + + qApp->processEvents(); + + QCOMPARE(state, 6); + QVERIFY(watcher1.isFinished()); + QVERIFY(watcher1.isCanceled()); + QVERIFY(watcher2.isFinished()); + QVERIFY(watcher2.isCanceled()); + } + + // Cancel continuations with context (QTBUG-108790) + { + // This test should pass with ASan + auto future = QtConcurrent::run([] {}); + future.then(this, [] {}); + future.waitForFinished(); + future.cancel(); + } +} + +void tst_QFuture::continuationsWithContext_data() +{ + QTest::addColumn<bool>("inOtherThread"); + QTest::addRow("in-other-thread") << true; + QTest::addRow("in-main-thread-qtbug119406") << false; +} + +void tst_QFuture::continuationsWithContext() +{ + QFETCH(bool, inOtherThread); + + auto tstThread = QThread::currentThread(); + QThread *thread = inOtherThread ? new QThread + : tstThread; + auto context = new QObject(); + + const auto cleanupGuard = qScopeGuard([&] { + context->deleteLater(); + if (thread != tstThread) { + thread->quit(); + thread->wait(); + delete thread; + } + }); + + if (inOtherThread) { + thread->start(); + context->moveToThread(thread); + } + + // .then() + { + QPromise<int> promise; + auto future = promise.future() + .then([&](int val) { + if (QThread::currentThread() != tstThread) + return 0; + return val + 1; + }) + .then(context, + [&](int val) { + if (QThread::currentThread() != thread) + return 0; + return val + 1; + }) + .then([&](int val) { + if (QThread::currentThread() != thread) + return 0; + return val + 1; + }); + promise.start(); + promise.addResult(0); + promise.finish(); + QCOMPARE(future.result(), 3); + } + + // .onCanceled + { + QPromise<int> promise; + auto future = promise.future() + .onCanceled(context, + [&] { + if (QThread::currentThread() != thread) + return 0; + return 1; + }) + .then([&](int val) { + if (QThread::currentThread() != thread) + return 0; + return val + 1; + }); + promise.start(); + promise.future().cancel(); + promise.finish(); + QCOMPARE(future.result(), 2); + } + + // Cancellation when the context object is destroyed + { + // Use something like QWidget which deletes its children early, i.e. + // before ~QObject() runs. This behavior can lead to side-effects + // like QPointers to the parent not being set to nullptr during child + // object destruction. + QPointer shortLivedContext = new FakeQWidget(); + shortLivedContext->moveToThread(thread); + + QPromise<int> promise; + auto future = promise.future() + .then(shortLivedContext, [&](int val) { + if (QThread::currentThread() != thread) + return 0; + return val + 1000; + }) + .onCanceled([&, ptr=QPointer(shortLivedContext)] { + if (QThread::currentThread() != thread) + return 0; + if (ptr) + return 1; + return 2; + }); + promise.start(); + + QMetaObject::invokeMethod(shortLivedContext, [&]() { + delete shortLivedContext; + }, inOtherThread ? Qt::BlockingQueuedConnection + : Qt::DirectConnection); + + promise.finish(); + QCOMPARE(future.result(), 2); + } + +#ifndef QT_NO_EXCEPTIONS + // .onFaled() + { + QPromise<void> promise; + auto future = promise.future() + .then([&] { + if (QThread::currentThread() != tstThread) + return 0; + throw std::runtime_error("error"); + }) + .onFailed(context, + [&] { + if (QThread::currentThread() != thread) + return 0; + return 1; + }) + .then([&](int val) { + if (QThread::currentThread() != thread) + return 0; + return val + 1; + }); + promise.start(); + promise.finish(); + QCOMPARE(future.result(), 2); + } +#endif // QT_NO_EXCEPTIONS +} + +void tst_QFuture::continuationsWithMoveOnlyLambda() +{ + // .then() + { + std::unique_ptr<int> uniquePtr(new int(42)); + auto future = QtFuture::makeReadyVoidFuture() + .then([p = std::move(uniquePtr)] { return *p; }); + QCOMPARE(future.result(), 42); + } + // .then() with thread pool + { + QThreadPool pool; + + std::unique_ptr<int> uniquePtr(new int(42)); + auto future = QtFuture::makeReadyVoidFuture() + .then(&pool, [p = std::move(uniquePtr)] { return *p; }); + QCOMPARE(future.result(), 42); + } + // .then() with context + { + QObject object; + + std::unique_ptr<int> uniquePtr(new int(42)); + auto future = QtFuture::makeReadyVoidFuture() + .then(&object, [p = std::move(uniquePtr)] { return *p; }); + QCOMPARE(future.result(), 42); + } + + // .onCanceled() + { + std::unique_ptr<int> uniquePtr(new int(42)); + auto future = + createCanceledFuture<int>().onCanceled([p = std::move(uniquePtr)] { return *p; }); + QCOMPARE(future.result(), 42); + } + + // .onCanceled() with context + { + QObject object; + + std::unique_ptr<int> uniquePtr(new int(42)); + auto future = createCanceledFuture<int>().onCanceled( + &object, [p = std::move(uniquePtr)] { return *p; }); + QCOMPARE(future.result(), 42); + } + +#ifndef QT_NO_EXCEPTIONS + // .onFailed() + { + std::unique_ptr<int> uniquePtr(new int(42)); + auto future = QtFuture::makeExceptionalFuture<int>(QException()) + .onFailed([p = std::move(uniquePtr)] { return *p; }); + QCOMPARE(future.result(), 42); + } + // .onFailed() with context + { + QObject object; + + std::unique_ptr<int> uniquePtr(new int(42)); + auto future = QtFuture::makeExceptionalFuture<int>(QException()) + .onFailed(&object, [p = std::move(uniquePtr)] { return *p; }); + QCOMPARE(future.result(), 42); + } +#endif // QT_NO_EXCEPTIONS +} + +void tst_QFuture::testSingleResult(const UniquePtr &p) +{ + QVERIFY(p.get() != nullptr); +} + +void tst_QFuture::testSingleResult(const std::vector<int> &v) +{ + QVERIFY(!v.empty()); +} + +template<class T> +void tst_QFuture::testSingleResult(const T &unknown) +{ + Q_UNUSED(unknown) +} + + +template<class T> +void tst_QFuture::testFutureTaken(QFuture<T> &noMoreFuture) +{ + QCOMPARE(noMoreFuture.isValid(), false); + QCOMPARE(noMoreFuture.resultCount(), 0); + QCOMPARE(noMoreFuture.progressValue(), 0); +} + +template<class T> +void tst_QFuture::testTakeResults(QFuture<T> future, size_type resultCount) +{ + auto copy = future; + QVERIFY(future.isFinished()); + QVERIFY(future.isValid()); + QCOMPARE(size_type(future.resultCount()), resultCount); + QVERIFY(copy.isFinished()); + QVERIFY(copy.isValid()); + QCOMPARE(size_type(copy.resultCount()), resultCount); + + auto vec = future.takeResults(); + QCOMPARE(vec.size(), resultCount); + + for (const auto &r : vec) { + testSingleResult(r); + if (QTest::currentTestFailed()) + return; + } + + testFutureTaken(future); + if (QTest::currentTestFailed()) + return; + testFutureTaken(copy); +} + +#if 0 +void tst_QFuture::takeResults() +{ + // Test takeResults() for movable types (whether or not copyable). + + // std::unique_ptr<int> supports only move semantics: + QFutureInterface<UniquePtr> moveIface; + moveIface.reportStarted(); + + // std::vector<int> supports both copy and move: + QFutureInterface<std::vector<int>> copyIface; + copyIface.reportStarted(); + + const int expectedCount = 10; + + for (int i = 0; i < expectedCount; ++i) { + QVERIFY(moveIface.reportAndMoveResult(UniquePtr{new int(0b101010)}, i)); + QVERIFY(copyIface.reportAndMoveResult(std::vector<int>{1,2,3,4,5}, i)); + } + + moveIface.reportFinished(); + copyIface.reportFinished(); + + testTakeResults(moveIface.future(), size_type(expectedCount)); + if (QTest::currentTestFailed()) + return; + + testTakeResults(copyIface.future(), size_type(expectedCount)); +} +#endif + +void tst_QFuture::takeResult() +{ + QFutureInterface<UniquePtr> iface; + iface.reportStarted(); + QVERIFY(iface.reportAndMoveResult(UniquePtr{new int(0b101010)}, 0)); + iface.reportFinished(); + + auto future = iface.future(); + QVERIFY(future.isFinished()); + QVERIFY(future.isValid()); + QCOMPARE(future.resultCount(), 1); + + auto result = future.takeResult(); + testFutureTaken(future); + if (QTest::currentTestFailed()) + return; + testSingleResult(result); +} + +void tst_QFuture::runAndTake() +{ + // Test if a 'moving' future can be used by + // QtConcurrent::run. + + auto rabbit = [](){ + // Let's wait a bit to give the test below some time + // to sync up with us with its watcher. + QThread::currentThread()->sleep(std::chrono::milliseconds{100}); + return UniquePtr(new int(10)); + }; + + QTestEventLoop loop; + QFutureWatcher<UniquePtr> watcha; + connect(&watcha, &QFutureWatcher<UniquePtr>::finished, [&loop](){ + loop.exitLoop(); + }); + + auto gotcha = QtConcurrent::run(rabbit); + watcha.setFuture(gotcha); + + loop.enterLoop(500ms); + if (loop.timeout()) + QSKIP("Failed to run the task, nothing to test"); + + gotcha = watcha.future(); +#if 0 + // TODO: enable when QFuture::takeResults() is enabled + testTakeResults(gotcha, size_type(1)); +#endif +} + +void tst_QFuture::resultsReadyAt_data() +{ + QTest::addColumn<bool>("testMove"); + + QTest::addRow("reportResult") << false; + QTest::addRow("reportAndMoveResult") << true; +} + +void tst_QFuture::resultsReadyAt() +{ + QFETCH(const bool, testMove); + + 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, testMove](int index) + { + int dummyResult = 0b101010; + if (testMove) + QVERIFY(iface.reportAndMoveResult(std::move(dummyResult), index)); + else + QVERIFY(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.enterLoop(DefaultWaitTime); + QVERIFY(!eventProcessor.timeout()); + if (QTest::currentTestFailed()) // Failed in our lambda observing 'ready at' + return; + + QCOMPARE(reported, nExpectedResults); + QCOMPARE(nExpectedResults, iface.future().resultCount()); + QCOMPARE(readyCounter.size(), 3); + QCOMPARE(taken, 0b1111); +} + +template <class T> +auto makeFutureInterface(T &&result) +{ + QFutureInterface<T> f; + + f.reportStarted(); + f.reportResult(std::forward<T>(result)); + f.reportFinished(); + + return f; +} + +void tst_QFuture::takeResultWorksForTypesWithoutDefaultCtor() +{ + struct Foo + { + Foo() = delete; + explicit Foo(int i) : _i(i) {} + + int _i = -1; + }; + + auto f = makeFutureInterface(Foo(42)); + + QCOMPARE(f.takeResult()._i, 42); +} +void tst_QFuture::canceledFutureIsNotValid() +{ + auto f = makeFutureInterface(42); + + f.cancel(); + + QVERIFY(!f.isValid()); +} + +void tst_QFuture::signalConnect() +{ + const int intValue = 42; + const double doubleValue = 42.5; + const QString stringValue = "42"; + + using TupleType = std::tuple<int, double, QString>; + const TupleType tuple(intValue, doubleValue, stringValue); + + using PairType = std::pair<int, double>; + const PairType pair(intValue, doubleValue); + + // No arg + { + SenderObject sender; + auto future = + QtFuture::connect(&sender, &SenderObject::noArgSignal).then([] { return true; }); + sender.emitNoArg(); + QCOMPARE(future.result(), true); + } + + // One arg + { + SenderObject sender; + auto future = QtFuture::connect(&sender, &SenderObject::intArgSignal).then([](int value) { + return value; + }); + sender.emitIntArg(42); + QCOMPARE(future.result(), 42); + } + + // Const ref arg + { + SenderObject sender; + auto future = + QtFuture::connect(&sender, &SenderObject::constRefArg).then([](QString value) { + return value; + }); + sender.emitConstRefArg(QString("42")); + QCOMPARE(future.result(), "42"); + } + + // Multiple args + { + SenderObject sender; + auto future = + QtFuture::connect(&sender, &SenderObject::multipleArgs).then([](TupleType values) { + return values; + }); + sender.emitMultipleArgs(intValue, doubleValue, stringValue); + auto result = future.result(); + QCOMPARE(result, tuple); + } + + // Single std::tuple arg + { + SenderObject sender; + QFuture<TupleType> future = QtFuture::connect(&sender, &SenderObject::tupleArgSignal); + sender.emitTupleArgSignal(tuple); + auto result = future.result(); + QCOMPARE(result, tuple); + } + + // Multi-args signal(int, std::tuple) + { + SenderObject sender; + QFuture<std::tuple<int, TupleType>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithTupleSignal1); + sender.emitMultiArgsWithTupleSignal1(142, tuple); + const auto [v, t] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(t, tuple); + } + + // Multi-args signal(std::tuple, int) + { + SenderObject sender; + QFuture<std::tuple<TupleType, int>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithTupleSignal2); + sender.emitMultiArgsWithTupleSignal2(tuple, 142); + const auto [t, v] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(t, tuple); + } + + // Multi-args signal(int, std::pair) + { + SenderObject sender; + QFuture<std::tuple<int, PairType>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithPairSignal1); + sender.emitMultiArgsWithPairSignal1(142, pair); + const auto [v, p] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(p, pair); + } + + // Multi-args signal(std::pair, int) + { + SenderObject sender; + QFuture<std::tuple<PairType, int>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithPairSignal2); + sender.emitMultiArgsWithPairSignal2(pair, 142); + const auto [p, v] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(p, pair); + } + + // No arg private signal + { + SenderObject sender; + auto future = QtFuture::connect(&sender, &SenderObject::noArgPrivateSignal).then([] { + return true; + }); + sender.emitNoArgPrivateSignal(); + QCOMPARE(future.result(), true); + } + + // One arg private signal + { + SenderObject sender; + auto future = + QtFuture::connect(&sender, &SenderObject::intArgPrivateSignal).then([](int value) { + return value; + }); + sender.emitIntArgPrivateSignal(42); + QCOMPARE(future.result(), 42); + } + + // Multi-args private signal + { + SenderObject sender; + auto future = QtFuture::connect(&sender, &SenderObject::multiArgsPrivateSignal) + .then([](TupleType values) { return values; }); + sender.emitMultiArgsPrivateSignal(intValue, doubleValue, stringValue); + auto result = future.result(); + QCOMPARE(result, tuple); + } + + // Single std::tuple arg private signal + { + SenderObject sender; + QFuture<TupleType> future = + QtFuture::connect(&sender, &SenderObject::tupleArgPrivateSignal); + sender.emitTupleArgPrivateSignal(tuple); + auto result = future.result(); + QCOMPARE(result, tuple); + } + + // Multi-args private signal(int, std::tuple) + { + SenderObject sender; + QFuture<std::tuple<int, TupleType>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithTuplePrivateSignal1); + sender.emitMultiArgsWithTuplePrivateSignal1(142, tuple); + const auto [v, t] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(t, tuple); + } + + // Multi-args private signal(std::tuple, int) + { + SenderObject sender; + QFuture<std::tuple<TupleType, int>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithTuplePrivateSignal2); + sender.emitMultiArgsWithTuplePrivateSignal2(tuple, 142); + const auto [t, v] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(t, tuple); + } + + // Multi-args private signal(int, std::pair) + { + SenderObject sender; + QFuture<std::tuple<int, PairType>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithPairPrivateSignal1); + sender.emitMultiArgsWithPairPrivateSignal1(142, pair); + const auto [v, p] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(p, pair); + } + + // Multi-args private signal(std::pair, int) + { + SenderObject sender; + QFuture<std::tuple<PairType, int>> future = + QtFuture::connect(&sender, &SenderObject::multiArgsWithPairPrivateSignal2); + sender.emitMultiArgsWithPairPrivateSignal2(pair, 142); + const auto [p, v] = future.result(); + QCOMPARE(v, 142); + QCOMPARE(p, pair); + } + + // Sender destroyed + { + SenderObject *sender = new SenderObject(); + + auto future = QtFuture::connect(sender, &SenderObject::intArgSignal); + + QSignalSpy spy(sender, &QObject::destroyed); + sender->deleteLater(); + + spy.wait(); + + QVERIFY(future.isCanceled()); + QVERIFY(!future.isValid()); + } + + // Signal emitted, causing Sender to be destroyed + { + SenderObject *sender = new SenderObject(); + + auto future = QtFuture::connect(sender, &SenderObject::intArgSignal); + future.then([sender](int) { + // Scenario: Sender no longer needed, so it's deleted + delete sender; + }); + + QSignalSpy spy(sender, &SenderObject::destroyed); + emit sender->intArgSignal(5); + spy.wait(); + + QVERIFY(future.isFinished()); + QVERIFY(!future.isCanceled()); + QVERIFY(future.isValid()); + } + + // Connect to nullptr + { + SenderObject *sender = nullptr; + auto future = QtFuture::connect(sender, &SenderObject::intArgSignal); + QVERIFY(future.isFinished()); + QVERIFY(future.isCanceled()); + QVERIFY(!future.isValid()); + } + + // Connect to non-signal + { + SenderObject sender; + +#if defined(Q_CC_MSVC_ONLY) && (Q_CC_MSVC < 1940 || __cplusplus < 202002L) +#define EXPECT_FUTURE_CONNECT_FAIL() QEXPECT_FAIL("", "QTBUG-101761, test fails on Windows/MSVC", Continue) +#else + QTest::ignoreMessage(QtWarningMsg, "QObject::connect: signal not found in SenderObject"); +#define EXPECT_FUTURE_CONNECT_FAIL() +#endif + + auto future = QtFuture::connect(&sender, &SenderObject::emitNoArg); + EXPECT_FUTURE_CONNECT_FAIL(); + QVERIFY(future.isFinished()); + EXPECT_FUTURE_CONNECT_FAIL(); + QVERIFY(future.isCanceled()); + EXPECT_FUTURE_CONNECT_FAIL(); + QVERIFY(!future.isValid()); +#undef EXPECT_FUTURE_CONNECT_FAIL + } +} + +void tst_QFuture::waitForFinished() +{ + QFutureInterface<void> fi; + auto future = fi.future(); + + QScopedPointer<QThread> waitingThread (QThread::create([&] { + future.waitForFinished(); + })); + + waitingThread->start(); + + QVERIFY(!waitingThread->wait(200)); + QVERIFY(!waitingThread->isFinished()); + + fi.reportStarted(); + QVERIFY(!waitingThread->wait(200)); + QVERIFY(!waitingThread->isFinished()); + + fi.reportFinished(); + + QVERIFY(waitingThread->wait()); + QVERIFY(waitingThread->isFinished()); +} + +void tst_QFuture::rejectResultOverwrite_data() +{ + QTest::addColumn<bool>("filterMode"); + QTest::addColumn<QList<int>>("initResults"); + + QTest::addRow("filter-mode-on-1-result") << true << QList<int>({ 456 }); + QTest::addRow("filter-mode-on-N-results") << true << QList<int>({ 456, 789 }); + QTest::addRow("filter-mode-off-1-result") << false << QList<int>({ 456 }); + QTest::addRow("filter-mode-off-N-results") << false << QList<int>({ 456, 789 }); +} + +void tst_QFuture::rejectResultOverwrite() +{ + QFETCH(bool, filterMode); + QFETCH(QList<int>, initResults); + + QFutureInterface<int> iface; + iface.setFilterMode(filterMode); + auto f = iface.future(); + QFutureWatcher<int> watcher; + watcher.setFuture(f); + + QTestEventLoop eventProcessor; + // control the loop by suspend + connect(&watcher, &QFutureWatcher<int>::suspending, &eventProcessor, &QTestEventLoop::exitLoop); + // internal machinery always emits resultsReadyAt + QSignalSpy resultCounter(&watcher, &QFutureWatcher<int>::resultsReadyAt); + + // init + if (initResults.size() == 1) + QVERIFY(iface.reportResult(initResults[0])); + else + QVERIFY(iface.reportResults(initResults)); + QCOMPARE(f.resultCount(), initResults.size()); + QCOMPARE(f.resultAt(0), initResults[0]); + QCOMPARE(f.results(), initResults); + + QTimer::singleShot(50, [&f]() { + f.suspend(); // should exit the loop + }); + // Run event loop, QCoreApplication::postEvent is in use + // in QFutureInterface: + eventProcessor.enterLoop(DefaultWaitTime); + QVERIFY(!eventProcessor.timeout()); + QCOMPARE(resultCounter.size(), 1); + f.resume(); + + // overwrite with lvalue + { + int result = -1; + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResult(result, 0)); + QCOMPARE(f.resultCount(), originalCount); + QCOMPARE(f.resultAt(0), initResults[0]); + } + // overwrite with rvalue + { + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResult(-1, 0)); + QCOMPARE(f.resultCount(), originalCount); + QCOMPARE(f.resultAt(0), initResults[0]); + } + // overwrite with array + { + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResults(QList<int> { -1, -2, -3 }, 0)); + QCOMPARE(f.resultCount(), originalCount); + QCOMPARE(f.resultAt(0), initResults[0]); + } + + // special case: add result by different index, overlapping with the vector + if (initResults.size() > 1) { + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResult(-1, 1)); + QCOMPARE(f.resultCount(), originalCount); + QCOMPARE(f.resultAt(1), initResults[1]); + } + + QTimer::singleShot(50, [&f]() { + f.suspend(); // should exit the loop + }); + eventProcessor.enterLoop(DefaultWaitTime); + QVERIFY(!eventProcessor.timeout()); + QCOMPARE(resultCounter.size(), 1); + f.resume(); + QCOMPARE(f.results(), initResults); +} + +void tst_QFuture::rejectPendingResultOverwrite() +{ + QFETCH(bool, filterMode); + QFETCH(QList<int>, initResults); + + QFutureInterface<int> iface; + iface.setFilterMode(filterMode); + auto f = iface.future(); + QFutureWatcher<int> watcher; + watcher.setFuture(f); + + QTestEventLoop eventProcessor; + // control the loop by suspend + connect(&watcher, &QFutureWatcher<int>::suspending, &eventProcessor, &QTestEventLoop::exitLoop); + // internal machinery always emits resultsReadyAt + QSignalSpy resultCounter(&watcher, &QFutureWatcher<int>::resultsReadyAt); + + // init + if (initResults.size() == 1) + QVERIFY(iface.reportResult(initResults[0], 1)); + else + QVERIFY(iface.reportResults(initResults, 1)); + QCOMPARE(f.resultCount(), 0); // not visible yet + if (!filterMode) { + QCOMPARE(f.resultAt(1), initResults[0]); + QCOMPARE(f.results(), initResults); + + QTimer::singleShot(50, [&f]() { + f.suspend(); // should exit the loop + }); + // Run event loop, QCoreApplication::postEvent is in use + // in QFutureInterface: + eventProcessor.enterLoop(DefaultWaitTime); + QVERIFY(!eventProcessor.timeout()); + QCOMPARE(resultCounter.size(), 1); + f.resume(); + } + + // overwrite with lvalue + { + int result = -1; + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResult(result, 1)); + QCOMPARE(f.resultCount(), originalCount); + if (!filterMode) + QCOMPARE(f.resultAt(1), initResults[0]); + } + // overwrite with rvalue + { + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResult(-1, 1)); + QCOMPARE(f.resultCount(), originalCount); + if (!filterMode) + QCOMPARE(f.resultAt(1), initResults[0]); + } + // overwrite with array + { + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResults(QList<int> { -1, -2 }, 1)); + QCOMPARE(f.resultCount(), originalCount); + if (!filterMode) + QCOMPARE(f.resultAt(1), initResults[0]); + } + // special case: add result by different index, overlapping with the vector + if (initResults.size() > 1) { + const auto originalCount = f.resultCount(); + QVERIFY(!iface.reportResult(-1, 2)); + QCOMPARE(f.resultCount(), originalCount); + if (!filterMode) + QCOMPARE(f.resultAt(2), initResults[1]); + } + + if (!filterMode) { + QTimer::singleShot(50, [&f]() { + f.suspend(); // should exit the loop + }); + eventProcessor.enterLoop(DefaultWaitTime); + QVERIFY(!eventProcessor.timeout()); + QCOMPARE(resultCounter.size(), 1); + f.resume(); + } + + QVERIFY(iface.reportResult(123, 0)); // make results at 0 and 1 accessible + QCOMPARE(f.resultCount(), initResults.size() + 1); + QCOMPARE(f.resultAt(1), initResults[0]); + initResults.prepend(123); + QCOMPARE(f.results(), initResults); +} + +void tst_QFuture::createReadyFutures() +{ +#if QT_DEPRECATED_SINCE(6, 10) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + // using const T & + { + const int val = 42; + QFuture<int> f = QtFuture::makeReadyFuture(val); + QCOMPARE(f.result(), val); + } + + // using T + { + int val = 42; + QFuture<int> f = QtFuture::makeReadyFuture(val); + QCOMPARE(f.result(), val); + } + + // using T && + { + auto f = QtFuture::makeReadyFuture(std::make_unique<int>(42)); + QCOMPARE(*f.takeResult(), 42); + } + + // using void + { + auto f = QtFuture::makeReadyFuture(); + QVERIFY(f.isStarted()); + QVERIFY(!f.isRunning()); + QVERIFY(f.isFinished()); + } + + // using const QList<T> & + { + const QList<int> values { 1, 2, 3 }; + auto f = QtFuture::makeReadyFuture(values); + QCOMPARE(f.resultCount(), 3); + QCOMPARE(f.results(), values); + } +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 10) + + // test makeReadyValueFuture<T>() + { + const int val = 42; + auto f = QtFuture::makeReadyValueFuture(val); + QCOMPARE_EQ(f.result(), val); + + int otherVal = 42; + f = QtFuture::makeReadyValueFuture(otherVal); + QCOMPARE_EQ(f.result(), otherVal); + } + { + auto f = QtFuture::makeReadyValueFuture(std::make_unique<int>(42)); + QCOMPARE(*f.takeResult(), 42); + } + // test makeReadyVoidFuture() + { + auto f = QtFuture::makeReadyVoidFuture(); + QVERIFY(f.isStarted()); + QVERIFY(!f.isRunning()); + QVERIFY(f.isFinished()); + } + +#ifndef QT_NO_EXCEPTIONS + // using QException + { + QException e; + auto f = QtFuture::makeExceptionalFuture<int>(e); + bool caught = false; + try { + f.result(); + } catch (QException &) { + caught = true; + } + QVERIFY(caught); + } + + // using std::exception_ptr and QFuture<void> + { + auto exception = std::make_exception_ptr(TestException()); + auto f = QtFuture::makeExceptionalFuture(exception); + bool caught = false; + try { + f.waitForFinished(); + } catch (TestException &) { + caught = true; + } + QVERIFY(caught); + } +#endif + + // testing makeReadyRangeFuture with various containers + { + const QList<int> expectedResult{1, 2, 3}; + + const QList<int> list{1, 2, 3}; + auto f = QtFuture::makeReadyRangeFuture(list); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + QVarLengthArray<int> varArray{1, 2, 3}; + f = QtFuture::makeReadyRangeFuture(varArray); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + std::vector<int> vec{1, 2, 3}; + f = QtFuture::makeReadyRangeFuture(std::move(vec)); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + f = QtFuture::makeReadyRangeFuture(std::array<int, 3>{1, 2, 3}); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + f = QtFuture::makeReadyRangeFuture(std::list<int>{1, 2, 3}); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + std::forward_list<int> fwdlist{1, 2, 3}; + f = QtFuture::makeReadyRangeFuture(fwdlist); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + const QSet<int> qset{1, 2, 3}; + f = QtFuture::makeReadyRangeFuture(qset); + QCOMPARE_EQ(f.resultCount(), 3); + auto result = f.results(); + std::sort(result.begin(), result.end()); + QCOMPARE_EQ(result, expectedResult); + + const QMap<QString, int> qmap{ + {"one", 1}, + {"two", 2}, + {"three", 3} + }; + f = QtFuture::makeReadyRangeFuture(qmap); + QCOMPARE_EQ(f.resultCount(), 3); + result = f.results(); + std::sort(result.begin(), result.end()); + QCOMPARE_EQ(result, expectedResult); + + std::set<int> stdset{1, 2, 3}; + f = QtFuture::makeReadyRangeFuture(stdset); + QCOMPARE_EQ(f.resultCount(), 3); + result = f.results(); + std::sort(result.begin(), result.end()); + QCOMPARE_EQ(result, expectedResult); + + // testing ValueType[N] overload + const int c_array[] = {1, 2, 3}; + f = QtFuture::makeReadyRangeFuture(c_array); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + f = QtFuture::makeReadyRangeFuture({1, 2, 3}); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + } + // testing makeReadyRangeFuture with a more complex underlying type + { + QObject obj1; + QObject obj2; + QObject obj3; + + const QList<QObject*> expectedResult{&obj1, &obj2, &obj3}; + + const QList<QObject*> list{&obj1, &obj2, &obj3}; + auto f = QtFuture::makeReadyRangeFuture(list); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + std::list<QObject*> stdlist{&obj1, &obj2, &obj3}; + f = QtFuture::makeReadyRangeFuture(std::move(stdlist)); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + + QObject* const c_array[] = {&obj1, &obj2, &obj3}; + f = QtFuture::makeReadyRangeFuture(c_array); + QCOMPARE_EQ(f.resultCount(), 3); + QCOMPARE_EQ(f.results(), expectedResult); + } +} + +void tst_QFuture::continuationsAfterReadyFutures() +{ + // continuations without a context + { + QFuture<int> f = QtFuture::makeReadyValueFuture(42) + .then([](int val) { + return val + 10; + }) + .onCanceled([]() { + return -1; + }); + QCOMPARE(f.result(), 52); + } + { + auto rangeF = QtFuture::makeReadyRangeFuture({1, 2, 3}); + QFuture<int> f = rangeF + .then([vals = rangeF.results()](auto) { + return vals.last(); + }) + .onCanceled([]() { + return -1; + }); + QCOMPARE(f.result(), 3); + } + { + QFuture<int> f = QtFuture::makeReadyVoidFuture() + .then([]() { + return 1; + }) + .onCanceled([]() { + return -1; + }); + QCOMPARE(f.result(), 1); + } +#ifndef QT_NO_EXCEPTIONS + { + QException e; + QFuture<int> f = QtFuture::makeExceptionalFuture<int>(e) + .then([](int) { + return 1; + }) + .onCanceled([]() { + return -1; + }) + .onFailed([](const QException &) { + return -2; + }); + QCOMPARE(f.result(), -2); + } +#endif + + // continuations with a context + QObject context; + { + QFuture<int> f = QtFuture::makeReadyValueFuture(42) + .then(&context, [](int val) { + return val + 10; + }) + .onCanceled([]() { + return -1; + }); + QCOMPARE(f.result(), 52); + } + { + auto rangeF = QtFuture::makeReadyRangeFuture({1, 2, 3}); + QFuture<int> f = rangeF + .then(&context, [vals = rangeF.results()](auto) { + return vals.last(); + }) + .onCanceled([]() { + return -1; + }); + QCOMPARE(f.result(), 3); + } + { + QFuture<int> f = QtFuture::makeReadyVoidFuture() + .then(&context, []() { + return 1; + }) + .onCanceled([]() { + return -1; + }); + QCOMPARE(f.result(), 1); + } +#ifndef QT_NO_EXCEPTIONS + { + QException e; + QFuture<int> f = QtFuture::makeExceptionalFuture<int>(e) + .then(&context, [](int) { + return 1; + }) + .onCanceled([]() { + return -1; + }) + .onFailed([](const QException &) { + return -2; + }); + QCOMPARE(f.result(), -2); + } +#endif +} + +void tst_QFuture::getFutureInterface() +{ + const int val = 42; + QFuture<int> f = QtFuture::makeReadyValueFuture(val); + + auto interface = QFutureInterfaceBase::get(f); + QCOMPARE(interface.resultCount(), 1); +} + +void tst_QFuture::convertQMetaType() +{ + const auto intType = QMetaType::fromType<QFuture<int>>(); + const auto voidType = QMetaType::fromType<QFuture<void>>(); + + QVERIFY(QMetaType::canConvert(intType, voidType)); + + const int val = 42; + QFuture<int> f = QtFuture::makeReadyValueFuture(val); + auto variant = QVariant::fromValue(f); + QVERIFY(variant.convert(voidType)); + + const auto voidFuture = variant.value<QFuture<void>>(); + QVERIFY(voidFuture.isValid()); + QVERIFY(voidFuture.isFinished()); +} + +template<class OutputContainer> +void testWhenAllIterators() +{ + QPromise<int> p0; + QPromise<int> p1; + QPromise<int> p2; + QList<QFuture<int>> futures = { p0.future(), p1.future(), p2.future() }; + + bool finished = false; + QFuture<OutputContainer> whenAll; + if constexpr (std::is_same_v<QList<QFuture<int>>, OutputContainer>) + whenAll = QtFuture::whenAll(futures.begin(), futures.end()); + else + whenAll = QtFuture::whenAll<OutputContainer>(futures.begin(), futures.end()); + whenAll.then([&](const OutputContainer &output) { + QCOMPARE(output.size(), 3u); + QCOMPARE(output[0].result(), 0); + QCOMPARE(output[1].result(), 1); + QCOMPARE(output[2].result(), 2); + finished = true; + }); + QVERIFY(whenAll.isRunning()); + + p0.start(); + p0.addResult(0); + p0.finish(); + QVERIFY(whenAll.isRunning()); + + p2.start(); + p2.addResult(2); + p2.finish(); + QVERIFY(whenAll.isRunning()); + + p1.start(); + p1.addResult(1); + p1.finish(); + QVERIFY(!whenAll.isRunning()); + QVERIFY(finished); + + // Try with empty sequence + QFuture<OutputContainer> whenAllEmpty; + if constexpr (std::is_same_v<QList<QFuture<int>>, OutputContainer>) + whenAllEmpty = QtFuture::whenAll(futures.end(), futures.end()); + else + whenAllEmpty = QtFuture::whenAll<OutputContainer>(futures.end(), futures.end()); + QVERIFY(whenAllEmpty.isStarted()); + QVERIFY(whenAllEmpty.isFinished()); + QVERIFY(whenAllEmpty.result().empty()); +} + +void tst_QFuture::whenAllIterators() +{ + // Try with different output containers + testWhenAllIterators<QList<QFuture<int>>>(); + if (QTest::currentTestFailed()) + QSKIP("testWhenAllIterators() with QList failed!"); + + testWhenAllIterators<std::vector<QFuture<int>>>(); + if (QTest::currentTestFailed()) + QSKIP("testWhenAllIterators() with std::vector failed!"); + + testWhenAllIterators<QVarLengthArray<QFuture<int>>>(); + if (QTest::currentTestFailed()) + QSKIP("testWhenAllIterators() with QVarLengthArray failed!"); +} + +void tst_QFuture::whenAllIteratorsWithCanceled() +{ + QPromise<int> p0; + QPromise<int> p1; + QList<QFuture<int>> futures = { p0.future(), p1.future() }; + bool finished = false; + auto whenAll = QtFuture::whenAll(futures.begin(), futures.end()) + .then([&](const QList<QFuture<int>> &results) { + QCOMPARE(results.size(), 2); + QVERIFY(results[0].isCanceled()); + QVERIFY(!results[1].isCanceled()); + QCOMPARE(results[1].result(), 1); + finished = true; + }); + + p0.start(); + p0.future().cancel(); + p0.finish(); + QVERIFY(!finished); + + p1.start(); + p1.addResult(1); + p1.finish(); + QVERIFY(finished); +} + +void tst_QFuture::whenAllIteratorsWithFailed() +{ +#ifndef QT_NO_EXCEPTIONS + QPromise<int> p0; + QPromise<int> p1; + QList<QFuture<int>> futures = { p0.future(), p1.future() }; + bool finished = false; + auto whenAll = QtFuture::whenAll(futures.begin(), futures.end()) + .then([&](QList<QFuture<int>> results) { + QCOMPARE(results.size(), 2); + QCOMPARE(results[1].result(), 1); + // A shorter way of handling the exception + results[0].onFailed([&](const QException &) { + finished = true; + return 0; + }); + }); + + p0.start(); + p0.setException(QException()); + p0.finish(); + QVERIFY(!finished); + + p1.start(); + p1.addResult(1); + p1.finish(); + QVERIFY(finished); +#else + QSKIP("Exceptions are disabled, skipping the test"); +#endif +} + +// A helper for std::visit, see https://en.cppreference.com/w/cpp/utility/variant/visit +template<class... Ts> +struct overloaded : public Ts... +{ + using Ts::operator()...; +}; + +// explicit deduction guide +template<class... Ts> +overloaded(Ts...)->overloaded<Ts...>; + +template<class OutputContainer> +void testWhenAllDifferentTypes() +{ + QPromise<int> pInt1; + QPromise<int> pInt2; + QPromise<void> pVoid; + + using Futures = std::variant<QFuture<int>, QFuture<int>, QFuture<void>>; + + QFuture<OutputContainer> whenAll; + if constexpr (std::is_same_v<QList<Futures>, OutputContainer>) { + whenAll = QtFuture::whenAll(pInt1.future(), pInt2.future(), pVoid.future()); + } else { + whenAll = + QtFuture::whenAll<OutputContainer>(pInt1.future(), pInt2.future(), pVoid.future()); + } + + int sumOfInts = 0; + whenAll.then([&](const OutputContainer &results) { + for (auto future : results) { + std::visit(overloaded { + [&](const QFuture<int> &f) { + QVERIFY(f.isFinished()); + sumOfInts += f.result(); + }, + [](const QFuture<void> &f) { QVERIFY(f.isFinished()); }, + }, + future); + } + }); + + pVoid.start(); + pVoid.finish(); + QVERIFY(whenAll.isRunning()); + + pInt2.start(); + pInt2.addResult(2); + pInt2.finish(); + QVERIFY(whenAll.isRunning()); + QCOMPARE(sumOfInts, 0); + + pInt1.start(); + pInt1.addResult(1); + pInt1.finish(); + QVERIFY(!whenAll.isRunning()); + QCOMPARE(sumOfInts, 3); +} + +void tst_QFuture::whenAllDifferentTypes() +{ +#ifdef Q_OS_VXWORKS + QSKIP("std::variant implementation on VxWorks 24.03 is broken and doesn't work with duplicated types"); +#endif + using Futures = std::variant<QFuture<int>, QFuture<int>, QFuture<void>>; + testWhenAllDifferentTypes<QList<Futures>>(); + if (QTest::currentTestFailed()) + QSKIP("testWhenAllDifferentTypes() with QList failed!"); + + testWhenAllDifferentTypes<std::vector<Futures>>(); + if (QTest::currentTestFailed()) + QSKIP("testWhenAllDifferentTypes() with std::vector failed!"); + + testWhenAllDifferentTypes<QVarLengthArray<Futures>>(); + if (QTest::currentTestFailed()) + QSKIP("testWhenAllDifferentTypes() with QVarLengthArray failed!"); +} + +void tst_QFuture::whenAllDifferentTypesWithCanceled() +{ + QPromise<int> pInt; + QPromise<QString> pString; + + const QString someValue = u"some value"_s; + + bool finished = false; + using Futures = std::variant<QFuture<int>, QFuture<QString>>; + auto whenAll = QtFuture::whenAll(pInt.future(), pString.future()) + .then([&](const QList<Futures> &results) { + finished = true; + for (auto future : results) { + std::visit(overloaded { + [](const QFuture<int> &f) { + QVERIFY(f.isFinished()); + QVERIFY(f.isCanceled()); + }, + [&](const QFuture<QString> &f) { + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), someValue); + }, + }, + future); + } + }); + + pString.start(); + pString.addResult(someValue); + pString.finish(); + QVERIFY(!finished); + + pInt.start(); + pInt.future().cancel(); + pInt.finish(); + QVERIFY(finished); +} + +void tst_QFuture::whenAllDifferentTypesWithFailed() +{ +#ifndef QT_NO_EXCEPTIONS + QPromise<int> pInt; + QPromise<QString> pString; + + const QString someValue = u"some value"_s; + + bool finished = false; + using Futures = std::variant<QFuture<int>, QFuture<QString>>; + auto whenAll = QtFuture::whenAll(pInt.future(), pString.future()) + .then([&](const QList<Futures> &results) { + finished = true; + for (auto future : results) { + std::visit(overloaded { + [](QFuture<int> f) { + QVERIFY(f.isFinished()); + bool failed = false; + // A shorter way of handling the exception + f.onFailed([&](const QException &) { + failed = true; + return -1; + }); + QVERIFY(failed); + }, + [&](const QFuture<QString> &f) { + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), someValue); + }, + }, + future); + } + }); + + pInt.start(); + pInt.setException(QException()); + pInt.finish(); + QVERIFY(!finished); + + pString.start(); + pString.addResult(someValue); + pString.finish(); + QVERIFY(finished); +#else + QSKIP("Exceptions are disabled, skipping the test") +#endif +} + +void tst_QFuture::whenAnyIterators() +{ + QPromise<int> p0; + QPromise<int> p1; + QPromise<int> p2; + QList<QFuture<int>> futures = { p0.future(), p1.future(), p2.future() }; + + auto whenAny = QtFuture::whenAny(futures.begin(), futures.end()); + int count = 0; + whenAny.then([&](const QtFuture::WhenAnyResult<int> &result) { + QCOMPARE(result.index, 1); + QCOMPARE(result.future.result(), 1); + QVERIFY(!futures[0].isFinished()); + QVERIFY(futures[1].isFinished()); + QVERIFY(!futures[2].isFinished()); + ++count; + }); + + p0.start(); + p1.start(); + p2.start(); + p0.addResult(0); + p1.addResult(1); + p2.addResult(2); + QVERIFY(!whenAny.isFinished()); + QCOMPARE(count, 0); + + p1.finish(); + QVERIFY(whenAny.isFinished()); + QCOMPARE(count, 1); + + p0.finish(); + QCOMPARE(count, 1); + + p2.finish(); + QCOMPARE(count, 1); + + auto whenAnyEmpty = QtFuture::whenAny(futures.end(), futures.end()); + QVERIFY(whenAnyEmpty.isStarted()); + QVERIFY(whenAnyEmpty.isFinished()); + QCOMPARE(whenAnyEmpty.result().index, -1); + auto whenAnyEmptyResult = whenAnyEmpty.result().future; + QVERIFY(whenAnyEmptyResult.isStarted()); + QVERIFY(whenAnyEmptyResult.isFinished()); + QVERIFY(whenAnyEmptyResult.isCanceled()); +} + +void tst_QFuture::whenAnyIteratorsWithCanceled() +{ + QPromise<int> p0; + QPromise<int> p1; + QList<QFuture<int>> futures = { p0.future(), p1.future() }; + int count = 0; + auto whenAny = QtFuture::whenAny(futures.begin(), futures.end()) + .then([&](const QtFuture::WhenAnyResult<int> &result) { + QCOMPARE(result.index, 1); + QVERIFY(result.future.isCanceled()); + QVERIFY(!futures[0].isFinished()); + QVERIFY(futures[1].isFinished()); + ++count; + }); + + p1.start(); + p1.future().cancel(); + p1.finish(); + QVERIFY(whenAny.isFinished()); + QCOMPARE(count, 1); + + p0.start(); + p0.addResult(0); + p0.finish(); + QCOMPARE(count, 1); +} + +void tst_QFuture::whenAnyIteratorsWithFailed() +{ +#ifndef QT_NO_EXCEPTIONS + QPromise<int> p0; + QPromise<int> p1; + QList<QFuture<int>> futures = { p0.future(), p1.future() }; + int count = 0; + auto whenAny = QtFuture::whenAny(futures.begin(), futures.end()) + .then([&](QtFuture::WhenAnyResult<int> result) { + QCOMPARE(result.index, 1); + QVERIFY(p1.future().isFinished()); + QVERIFY(!p0.future().isFinished()); + // A shorter way of handling the exception + result.future.onFailed([&](const QException &) { + ++count; + return 0; + }); + }); + + p1.start(); + p1.setException(QException()); + p1.finish(); + QCOMPARE(count, 1); + + p0.start(); + p0.addResult(0); + p0.finish(); + QCOMPARE(count, 1); +#else + QSKIP("Exceptions are disabled, skipping the test") +#endif +} + +void tst_QFuture::whenAnyDifferentTypes() +{ +#ifdef Q_OS_VXWORKS + QSKIP("std::variant implementation on VxWorks 24.03 is broken and doesn't work with duplicated types"); +#endif + QPromise<int> pInt1; + QPromise<int> pInt2; + QPromise<void> pVoid; + + auto whenAny = QtFuture::whenAny(pInt1.future(), pInt2.future(), pVoid.future()); + int count = 0; + whenAny.then([&](const std::variant<QFuture<int>, QFuture<int>, QFuture<void>> &result) { + QCOMPARE(result.index(), 1u); + std::visit(overloaded { [&](const QFuture<int> &future) { + QVERIFY(future.isFinished()); + QCOMPARE(future.result(), 2); + ++count; + }, + [](auto) { QFAIL("The wrong future completed."); } + }, + result); + }); + + pInt2.start(); + pInt1.start(); + pVoid.start(); + pInt1.addResult(1); + pInt2.addResult(2); + + QVERIFY(!whenAny.isFinished()); + QCOMPARE(count, 0); + + pInt2.finish(); + QVERIFY(whenAny.isFinished()); + QCOMPARE(count, 1); + + pInt1.finish(); + QCOMPARE(count, 1); + + pVoid.finish(); + QCOMPARE(count, 1); +} + +void tst_QFuture::whenAnyDifferentTypesWithCanceled() +{ + QPromise<int> pInt; + QPromise<void> pVoid; + + int count = 0; + auto whenAny = QtFuture::whenAny(pInt.future(), pVoid.future()) + .then([&](const std::variant<QFuture<int>, QFuture<void>> &result) { + QCOMPARE(result.index(), 0u); + std::visit(overloaded { [&](const QFuture<int> &future) { + QVERIFY(future.isFinished()); + QVERIFY(future.isCanceled()); + ++count; + }, + [](auto) { + QFAIL("The wrong future completed."); + } + }, + result); + }); + + pInt.start(); + pInt.future().cancel(); + pInt.finish(); + QCOMPARE(count, 1); + + pVoid.start(); + pVoid.finish(); + QCOMPARE(count, 1); +} + +void tst_QFuture::whenAnyDifferentTypesWithFailed() +{ +#ifndef QT_NO_EXCEPTIONS + QPromise<int> pInt; + QPromise<void> pVoid; + + int count = 0; + auto whenAny = QtFuture::whenAny(pInt.future(), pVoid.future()) + .then([&](const std::variant<QFuture<int>, QFuture<void>> &result) { + QCOMPARE(result.index(), 0u); + std::visit(overloaded { [&](QFuture<int> future) { + QVERIFY(future.isFinished()); + // A shorter way of handling the exception + future.onFailed([&](const QException &) { + ++count; + return -1; + }); + }, + [](auto) { + QFAIL("The wrong future completed."); + } + }, + result); + }); + + pInt.start(); + pInt.setException(QException()); + pInt.finish(); + QCOMPARE(count, 1); + + pVoid.start(); + pVoid.finish(); + QCOMPARE(count, 1); +#else + QSKIP("Exceptions are disabled, skipping the test") +#endif +} + +void tst_QFuture::continuationOverride() +{ + QPromise<int> p; + bool firstExecuted = false; + bool secondExecuted = false; + + QTest::ignoreMessage(QtWarningMsg, + "Adding a continuation to a future which already has a continuation. " + "The existing continuation is overwritten."); + + QFuture<int> f1 = p.future(); + f1.then([&firstExecuted](int) { + firstExecuted = true; + }); + + QFuture<int> f2 = p.future(); + f2.then([&secondExecuted](int) { + secondExecuted = true; + }); + + p.start(); + p.addResult(42); + p.finish(); + + QVERIFY(p.future().isFinished()); + QVERIFY(!firstExecuted); + QVERIFY(secondExecuted); +} + +struct InstanceCounter +{ + InstanceCounter() { ++count; } + InstanceCounter(const InstanceCounter &) { ++count; } + ~InstanceCounter() { --count; } + static int count; +}; +int InstanceCounter::count = 0; + +void tst_QFuture::continuationsDontLeak() +{ + { + // QFuture isn't started and isn't finished (has no state) + QPromise<InstanceCounter> promise; + auto future = promise.future(); + + bool continuationIsRun = false; + future.then([future, &continuationIsRun](InstanceCounter) { continuationIsRun = true; }); + + promise.addResult(InstanceCounter {}); + + QVERIFY(!continuationIsRun); + } + QCOMPARE(InstanceCounter::count, 0); + + { + // QFuture is started, but not finished + QPromise<InstanceCounter> promise; + auto future = promise.future(); + + bool continuationIsRun = false; + future.then([future, &continuationIsRun](InstanceCounter) { continuationIsRun = true; }); + + promise.start(); + promise.addResult(InstanceCounter {}); + + QVERIFY(!continuationIsRun); + } + QCOMPARE(InstanceCounter::count, 0); + + { + // QFuture is started and finished, the continuation is run + QPromise<InstanceCounter> promise; + auto future = promise.future(); + + bool continuationIsRun = false; + future.then([future, &continuationIsRun](InstanceCounter) { + QVERIFY(future.isFinished()); + continuationIsRun = true; + }); + + promise.start(); + promise.addResult(InstanceCounter {}); + promise.finish(); + + QVERIFY(continuationIsRun); + } + QCOMPARE(InstanceCounter::count, 0); + + { + // QTBUG-116731: Must pass with ASan enabled + bool continuationIsRun = false; + auto f = QtFuture::makeReadyValueFuture(42); + QtFuture::whenAll(f).then([&](auto) { continuationIsRun = true; }); + QVERIFY(continuationIsRun); + } + + { + // QTBUG-116731: Must pass with ASan enabled + bool continuationIsRun = false; + auto f = QtFuture::makeReadyValueFuture(42); + QList fs{f}; + QtFuture::whenAll(fs.begin(), fs.end()).then([&](auto) { continuationIsRun = true; }); + QVERIFY(continuationIsRun); + } + + { + // QTBUG-116731: Must pass with ASan enabled + bool continuationIsRun = false; + auto f = QtFuture::makeReadyValueFuture(42); + QtFuture::whenAny(f).then([&](auto) { continuationIsRun = true; }); + QVERIFY(continuationIsRun); + } + + { + // QTBUG-116731: Must pass with ASan enabled + bool continuationIsRun = false; + auto f = QtFuture::makeReadyValueFuture(42); + QList fs{f}; + QtFuture::whenAny(fs.begin(), fs.end()).then([&](auto) { continuationIsRun = true; }); + QVERIFY(continuationIsRun); + } +} + +// This test checks that we do not get use-after-free +void tst_QFuture::cancelAfterFinishWithContinuations() +{ + QFuture<void> future; + bool continuationIsRun = false; + bool cancelCalled = false; + { + QPromise<void> promise; + future = promise.future(); + + future.then([&continuationIsRun]() { + continuationIsRun = true; + }).onCanceled([&cancelCalled]() { + cancelCalled = true; + }); + + promise.start(); + promise.finish(); + } + + QVERIFY(continuationIsRun); + future.cancel(); + QVERIFY(!cancelCalled); +} + +void tst_QFuture::unwrap() +{ + // The nested future succeeds + { + QPromise<int> p; + QFuture<QFuture<int>> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + return value + 1; + }); + return nested; + }); + + QFuture<int> unwrapped = f.unwrap(); + QVERIFY(!unwrapped.isStarted()); + QVERIFY(!unwrapped.isFinished()); + + p.start(); + p.addResult(42); + p.finish(); + + unwrapped.waitForFinished(); + + QVERIFY(unwrapped.isStarted()); + QVERIFY(unwrapped.isFinished()); + QCOMPARE(unwrapped.result(), 43); + } + + // The nested future succeeds with multiple results + { + QPromise<int> p; + QFuture<QFuture<int>> f = p.future().then([] (int value) { + QPromise<int> nested; + nested.start(); + nested.addResult(++value); + nested.addResult(++value); + nested.addResult(++value); + nested.finish(); + return nested.future(); + }); + + QFuture<int> unwrapped = f.unwrap(); + QVERIFY(!unwrapped.isStarted()); + QVERIFY(!unwrapped.isFinished()); + + p.start(); + p.addResult(42); + p.finish(); + + f.waitForFinished(); + + QVERIFY(unwrapped.isStarted()); + QVERIFY(unwrapped.isFinished()); + QCOMPARE(unwrapped.results(), QList<int>() << 43 << 44 << 45); + } + + // The chain is canceled, check that unwrap() propagates the cancellation. + { + QPromise<int> p; + QFuture<int> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + return value + 1; + }); + return nested; + }).unwrap().then([] (int result) { + return result; + }).onCanceled([] { + return -1; + }); + + p.start(); + p.future().cancel(); + p.finish(); + + f.waitForFinished(); + + QVERIFY(f.isStarted()); + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), -1); + } + +#ifndef QT_NO_EXCEPTIONS + // The chain has an exception, check that unwrap() propagates it. + { + QPromise<int> p; + QFuture<int> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + return value + 1; + }); + return nested; + }).unwrap().then([] (int result) { + return result; + }).onFailed([] (QException &) { + return -1; + }); + + p.start(); + p.setException(QException()); + p.finish(); + + f.waitForFinished(); + + QVERIFY(f.isStarted()); + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), -1); + } + +#endif // QT_NO_EXCEPTIONS + + // The nested future is canceled + { + QPromise<int> p; + QFuture<int> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + return value + 1; + }); + nested.cancel(); + return nested; + }).unwrap().then([] (int result) { + return result; + }).onCanceled([] { + return -1; + }); + + p.start(); + p.addResult(42); + p.finish(); + + f.waitForFinished(); + + QVERIFY(f.isStarted()); + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), -1); + } + +#ifndef QT_NO_EXCEPTIONS + // The nested future fails with an exception + { + QPromise<int> p; + QFuture<int> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + throw QException(); + return value + 1; + }); + return nested; + }).unwrap().then([] (int result) { + return result; + }).onFailed([] (QException &) { + return -1; + }); + + p.start(); + p.addResult(42); + p.finish(); + + f.waitForFinished(); + + QVERIFY(f.isStarted()); + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), -1); + } +#endif // QT_NO_EXCEPTIONS + + // Check that continuations are called in the right order + { + QPromise<void> p; + + std::atomic<bool> firstThenInvoked = false; + std::atomic<bool> secondThenInvoked = false; + std::atomic<bool> nestedThenInvoked = false; + auto f = p.future().then([&] { + if (!firstThenInvoked && !secondThenInvoked && !nestedThenInvoked) + firstThenInvoked = true; + QFuture<void> nested = QtConcurrent::run([&] { + QVERIFY(firstThenInvoked); + QVERIFY(!nestedThenInvoked); + QVERIFY(!secondThenInvoked); + nestedThenInvoked = true; + }); + return nested; + }).unwrap().then([&] { + QVERIFY(firstThenInvoked); + QVERIFY(nestedThenInvoked); + QVERIFY(!secondThenInvoked); + secondThenInvoked = true; + }); + + QVERIFY(!firstThenInvoked); + QVERIFY(!nestedThenInvoked); + QVERIFY(!secondThenInvoked); + + p.start(); + p.finish(); + + f.waitForFinished(); + + if (QTest::currentTestFailed()) + return; + + QVERIFY(firstThenInvoked); + QVERIFY(nestedThenInvoked); + QVERIFY(secondThenInvoked); + } + + // Unwrap multiple nested futures + { + QPromise<int> p; + QFuture<QFuture<QFuture<int>>> f = p.future().then([] (int value) { + QFuture<QFuture<int>> nested = QtConcurrent::run([value] { + QFuture<int> doubleNested = QtConcurrent::run([value] { + return value + 1; + }); + return doubleNested; + }); + return nested; + }); + + QFuture<int> unwrapped = f.unwrap(); + QVERIFY(!unwrapped.isStarted()); + QVERIFY(!unwrapped.isFinished()); + + p.start(); + p.addResult(42); + p.finish(); + + unwrapped.waitForFinished(); + + QVERIFY(unwrapped.isStarted()); + QVERIFY(unwrapped.isFinished()); + QCOMPARE(unwrapped.result(), 43); + } + + // Unwrap multiple nested void futures + { + QPromise<void> p; + std::atomic<bool> nestedInvoked = false; + std::atomic<bool> doubleNestedInvoked = false; + QFuture<QFuture<QFuture<void>>> f = p.future().then([&] { + QFuture<QFuture<void>> nested = QtConcurrent::run([&] { + QFuture<void> doubleNested = QtConcurrent::run([&] { + doubleNestedInvoked = true; + }); + nestedInvoked = true; + return doubleNested; + }); + return nested; + }); + + QFuture<void> unwrapped = f.unwrap(); + QVERIFY(!nestedInvoked); + QVERIFY(!doubleNestedInvoked); + QVERIFY(!unwrapped.isStarted()); + QVERIFY(!unwrapped.isFinished()); + + p.start(); + p.finish(); + + unwrapped.waitForFinished(); + + QVERIFY(unwrapped.isStarted()); + QVERIFY(unwrapped.isFinished()); + QVERIFY(nestedInvoked); + QVERIFY(doubleNestedInvoked); + } +} + QTEST_MAIN(tst_QFuture) #include "tst_qfuture.moc" diff --git a/tests/auto/corelib/thread/qfuturesynchronizer/CMakeLists.txt b/tests/auto/corelib/thread/qfuturesynchronizer/CMakeLists.txt new file mode 100644 index 0000000000..c0f4561c51 --- /dev/null +++ b/tests/auto/corelib/thread/qfuturesynchronizer/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qfuturesynchronizer Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qfuturesynchronizer LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qfuturesynchronizer + SOURCES + tst_qfuturesynchronizer.cpp +) diff --git a/tests/auto/corelib/thread/qfuturesynchronizer/qfuturesynchronizer.pro b/tests/auto/corelib/thread/qfuturesynchronizer/qfuturesynchronizer.pro deleted file mode 100644 index 0d20117ed0..0000000000 --- a/tests/auto/corelib/thread/qfuturesynchronizer/qfuturesynchronizer.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qfuturesynchronizer -QT = core testlib -SOURCES = tst_qfuturesynchronizer.cpp diff --git a/tests/auto/corelib/thread/qfuturesynchronizer/tst_qfuturesynchronizer.cpp b/tests/auto/corelib/thread/qfuturesynchronizer/tst_qfuturesynchronizer.cpp index 2a8a340925..62ad4f872a 100644 --- a/tests/auto/corelib/thread/qfuturesynchronizer/tst_qfuturesynchronizer.cpp +++ b/tests/auto/corelib/thread/qfuturesynchronizer/tst_qfuturesynchronizer.cpp @@ -1,32 +1,7 @@ -/**************************************************************************** -** -** 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> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <QtCore/qfuturesynchronizer.h> #include <QtCore/qfuture.h> @@ -38,6 +13,7 @@ class tst_QFutureSynchronizer : public QObject private Q_SLOTS: void construction(); + void setFutureAliasingExistingMember(); void addFuture(); void cancelOnWait(); void clearFutures(); @@ -58,6 +34,38 @@ void tst_QFutureSynchronizer::construction() QCOMPARE(synchronizerWithFuture.futures().size(), 1); } +void tst_QFutureSynchronizer::setFutureAliasingExistingMember() +{ + // + // GIVEN: a QFutureSynchronizer with one QFuture: + // + QFutureSynchronizer synchronizer(QtFuture::makeReadyValueFuture(42)); + + // + // WHEN: calling setFuture() with an alias of the QFuture already in `synchronizer`: + // + for (int i = 0; i < 2; ++i) { + // The next line triggers -Wdangling-reference, but it's a FP because + // of implicit sharing. We cannot keep a copy of synchronizer.futures() + // around to avoid the warning, as the extra copy would cause a detach() + // of m_futures inside setFuture() with the consequence that `f` no longer + // aliases an element in m_futures, which is the goal of this test. +QT_WARNING_PUSH +#if defined(Q_CC_GNU_ONLY) && Q_CC_GNU >= 1301 +QT_WARNING_DISABLE_GCC("-Wdangling-reference") +#endif + const auto &f = synchronizer.futures().constFirst(); +QT_WARNING_POP + synchronizer.setFuture(f); + } + + // + // THEN: it didn't crash + // + QCOMPARE(synchronizer.futures().size(), 1); + QCOMPARE(synchronizer.futures().constFirst().result(), 42); +} + void tst_QFutureSynchronizer::addFuture() { QFutureSynchronizer<void> synchronizer; @@ -107,7 +115,7 @@ void tst_QFutureSynchronizer::futures() synchronizer.addFuture(future); } - QCOMPARE(futures, synchronizer.futures()); + QCOMPARE(futures.size(), synchronizer.futures().size()); } void tst_QFutureSynchronizer::setFuture() @@ -122,7 +130,6 @@ void tst_QFutureSynchronizer::setFuture() QFuture<void> future; synchronizer.setFuture(future); QCOMPARE(synchronizer.futures().size(), 1); - QCOMPARE(synchronizer.futures().first(), future); } void tst_QFutureSynchronizer::waitForFinished() diff --git a/tests/auto/corelib/thread/qfuturewatcher/CMakeLists.txt b/tests/auto/corelib/thread/qfuturewatcher/CMakeLists.txt new file mode 100644 index 0000000000..65417199a3 --- /dev/null +++ b/tests/auto/corelib/thread/qfuturewatcher/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qfuturewatcher Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qfuturewatcher LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qfuturewatcher + SOURCES + tst_qfuturewatcher.cpp + LIBRARIES + Qt::Concurrent + Qt::CorePrivate +) diff --git a/tests/auto/corelib/thread/qfuturewatcher/qfuturewatcher.pro b/tests/auto/corelib/thread/qfuturewatcher/qfuturewatcher.pro deleted file mode 100644 index d0e8b4c982..0000000000 --- a/tests/auto/corelib/thread/qfuturewatcher/qfuturewatcher.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qfuturewatcher -QT = core core-private testlib concurrent -SOURCES = tst_qfuturewatcher.cpp diff --git a/tests/auto/corelib/thread/qfuturewatcher/tst_qfuturewatcher.cpp b/tests/auto/corelib/thread/qfuturewatcher/tst_qfuturewatcher.cpp index b2ef516b4e..40aa89ded4 100644 --- a/tests/auto/corelib/thread/qfuturewatcher/tst_qfuturewatcher.cpp +++ b/tests/auto/corelib/thread/qfuturewatcher/tst_qfuturewatcher.cpp @@ -1,40 +1,17 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QCoreApplication> #include <QDebug> -#include <QtTest/QtTest> +#include <QElapsedTimer> +#include <QSignalSpy> #include <QtConcurrent> #include <private/qfutureinterface_p.h> using namespace QtConcurrent; +using namespace std::chrono_literals; -#include <QtTest/QtTest> +#include <QTest> //#define PRINT @@ -45,8 +22,11 @@ private slots: void startFinish(); void progressValueChanged(); void canceled(); + void cancelAndFinish_data(); + void cancelAndFinish(); void resultAt(); void resultReadyAt(); + void orderedResultReadyAt(); void futureSignals(); void watchFinishedFuture(); void watchCanceledFuture(); @@ -56,14 +36,20 @@ private slots: void sharedFutureInterface(); void changeFuture(); void cancelEvents(); +#if QT_DEPRECATED_SINCE(6, 0) void pauseEvents(); - void finishedState(); + void pausedSuspendedOrder(); +#endif + void suspendEvents(); + void suspended(); + void suspendedEventsOrder(); void throttling(); void incrementalMapResults(); void incrementalFilterResults(); void qfutureSynchronizer(); void warnRace(); void matchFlags(); + void checkStateConsistency(); }; void sleeper() @@ -75,20 +61,31 @@ void tst_QFutureWatcher::startFinish() { QFutureWatcher<void> futureWatcher; - QSignalSpy startedSpy(&futureWatcher, &QFutureWatcher<void>::started); - QSignalSpy finishedSpy(&futureWatcher, &QFutureWatcher<void>::finished); - - QVERIFY(startedSpy.isValid()); - QVERIFY(finishedSpy.isValid()); + int startedCount = 0; + int finishedCount = 0; + QObject::connect(&futureWatcher, &QFutureWatcher<void>::started, + [&startedCount, &finishedCount](){ + ++startedCount; + QCOMPARE(startedCount, 1); + QCOMPARE(finishedCount, 0); + }); + QObject::connect(&futureWatcher, &QFutureWatcher<void>::finished, + [&startedCount, &finishedCount](){ + ++finishedCount; + QCOMPARE(startedCount, 1); + QCOMPARE(finishedCount, 1); + }); futureWatcher.setFuture(QtConcurrent::run(sleeper)); - QVERIFY(startedSpy.wait()); - QCOMPARE(startedSpy.count(), 1); - QCOMPARE(finishedSpy.count(), 0); futureWatcher.future().waitForFinished(); - QVERIFY(finishedSpy.wait()); - QCOMPARE(startedSpy.count(), 1); - QCOMPARE(finishedSpy.count(), 1); + + // waitForFinished() may unblock before asynchronous + // started() and finished() signals are delivered to the main thread. + // prosessEvents() should empty the pending queue. + qApp->processEvents(); + + QCOMPARE(startedCount, 1); + QCOMPARE(finishedCount, 1); } void mapSleeper(int &) @@ -96,9 +93,9 @@ void mapSleeper(int &) QTest::qSleep(100); } -QSet<int> progressValues; -QSet<QString> progressTexts; -QMutex mutex; +static QSet<int> progressValues; +static QSet<QString> progressTexts; +static QMutex mutex; class ProgressObject : public QObject { Q_OBJECT @@ -173,7 +170,7 @@ class CancelObject : public QObject Q_OBJECT public: bool wasCanceled; - CancelObject() : wasCanceled(false) {}; + CancelObject() : wasCanceled(false) {} public slots: void cancel(); }; @@ -211,12 +208,48 @@ void tst_QFutureWatcher::canceled() future.waitForFinished(); } -class IntTask : public RunFunctionTask<int> +void tst_QFutureWatcher::cancelAndFinish_data() +{ + QTest::addColumn<bool>("isCanceled"); + QTest::addColumn<bool>("isFinished"); + + QTest::addRow("running") << false << false; + QTest::addRow("canceled") << true << false; + QTest::addRow("finished") << false << true; + QTest::addRow("canceledAndFinished") << true << true; +} + +void tst_QFutureWatcher::cancelAndFinish() +{ + QFETCH(bool, isCanceled); + QFETCH(bool, isFinished); + + QFutureInterface<void> fi; + QFutureWatcher<void> futureWatcher; + QSignalSpy finishedSpy(&futureWatcher, &QFutureWatcher<void>::finished); + QSignalSpy canceledSpy(&futureWatcher, &QFutureWatcher<void>::canceled); + futureWatcher.setFuture(fi.future()); + + fi.reportStarted(); + + if (isCanceled) + fi.cancel(); + if (isFinished) + fi.reportFinished(); + + fi.cancelAndFinish(); + + // The signals should be emitted only once + QTRY_COMPARE(canceledSpy.size(), 1); + QTRY_COMPARE(finishedSpy.size(), 1); +} + +class IntTask : public RunFunctionTaskBase<int> { public: - void runFunctor() + void runFunctor() override { - result = 10; + promise.reportResult(10); } }; @@ -248,6 +281,28 @@ void tst_QFutureWatcher::resultReadyAt() QVERIFY(resultSpy.wait()); } +void tst_QFutureWatcher::orderedResultReadyAt() +{ + for (int i = 0; i < 1000; ++i) { + QObject context; + QFuture<QString> f = run([](QPromise<QString> &fi) { + fi.addResult("First"); + fi.addResult("Second"); + }); + QList<int> actualIndices; + + QFutureWatcher<QString> watcher; + connect(&watcher, &QFutureWatcherBase::resultReadyAt, &context, + [&actualIndices](int index) { actualIndices.append(index); }); + watcher.setFuture(f); + f.waitForFinished(); + QCoreApplication::processEvents(); + const QList<int> expectedIndices{0, 1}; + QCOMPARE(actualIndices.size(), expectedIndices.size()); + QCOMPARE(actualIndices, expectedIndices); + } +} + class SignalSlotObject : public QObject { Q_OBJECT @@ -318,21 +373,21 @@ void tst_QFutureWatcher::futureSignals() const int progress = 1; a.setProgressValue(progress); - QTRY_COMPARE(progressSpy.count(), 2); + QTRY_COMPARE(progressSpy.size(), 2); QCOMPARE(progressSpy.takeFirst().at(0).toInt(), 0); QCOMPARE(progressSpy.takeFirst().at(0).toInt(), 1); const int result = 10; a.reportResult(&result); QVERIFY(resultReadySpy.wait()); - QCOMPARE(resultReadySpy.count(), 1); + QCOMPARE(resultReadySpy.size(), 1); a.reportFinished(&result); - QTRY_COMPARE(resultReadySpy.count(), 2); + QTRY_COMPARE(resultReadySpy.size(), 2); QCOMPARE(resultReadySpy.takeFirst().at(0).toInt(), 0); // check the index QCOMPARE(resultReadySpy.takeFirst().at(0).toInt(), 1); - QCOMPARE(finishedSpy.count(), 1); + QCOMPARE(finishedSpy.size(), 1); } } @@ -371,10 +426,10 @@ void tst_QFutureWatcher::watchFinishedFuture() watcher.setFuture(f); QVERIFY(finishedSpy.wait()); - QCOMPARE(startedSpy.count(), 1); - QCOMPARE(finishedSpy.count(), 1); - QCOMPARE(resultReadySpy.count(), 1); - QCOMPARE(canceledSpy.count(), 0); + QCOMPARE(startedSpy.size(), 1); + QCOMPARE(finishedSpy.size(), 1); + QCOMPARE(resultReadySpy.size(), 1); + QCOMPARE(canceledSpy.size(), 0); } void tst_QFutureWatcher::watchCanceledFuture() @@ -405,10 +460,10 @@ void tst_QFutureWatcher::watchCanceledFuture() watcher.setFuture(f); QVERIFY(finishedSpy.wait()); - QCOMPARE(startedSpy.count(), 1); - QCOMPARE(finishedSpy.count(), 1); - QCOMPARE(resultReadySpy.count(), 0); - QCOMPARE(canceledSpy.count(), 1); + QCOMPARE(startedSpy.size(), 1); + QCOMPARE(finishedSpy.size(), 1); + QCOMPARE(resultReadySpy.size(), 0); + QCOMPARE(canceledSpy.size(), 1); } void tst_QFutureWatcher::disconnectRunningFuture() @@ -431,28 +486,28 @@ void tst_QFutureWatcher::disconnectRunningFuture() const int result = 10; a.reportResult(&result); QVERIFY(resultReadySpy.wait()); - QCOMPARE(resultReadySpy.count(), 1); + QCOMPARE(resultReadySpy.size(), 1); delete watcher; a.reportResult(&result); QTest::qWait(10); - QCOMPARE(resultReadySpy.count(), 1); + QCOMPARE(resultReadySpy.size(), 1); a.reportFinished(&result); QTest::qWait(10); - QCOMPARE(finishedSpy.count(), 0); + QCOMPARE(finishedSpy.size(), 0); } const int maxProgress = 100000; -class ProgressEmitterTask : public RunFunctionTask<void> +class ProgressEmitterTask : public RunFunctionTaskBase<void> { public: - void runFunctor() + void runFunctor() override { - setProgressRange(0, maxProgress); + promise.setProgressRange(0, maxProgress); for (int p = 0; p <= maxProgress; ++p) - setProgressValue(p); + promise.setProgressValue(p); } }; @@ -469,30 +524,31 @@ void tst_QFutureWatcher::tooMuchProgress() QObject::connect(&f, SIGNAL(progressValueChanged(int)), &o, SLOT(registerProgress(int))); f.setFuture((new ProgressEmitterTask())->start()); - QTestEventLoop::instance().enterLoop(5); + // Android reports ca. 10k progressValueChanged per second + QTestEventLoop::instance().enterLoop(15); QVERIFY(!QTestEventLoop::instance().timeout()); QVERIFY(progressValues.contains(maxProgress)); } template <typename T> -class ProgressTextTask : public RunFunctionTask<T> +class ProgressTextTask : public RunFunctionTaskBase<T> { public: - void runFunctor() + void runFunctor() override { - this->setProgressValueAndText(1, QLatin1String("Foo 1")); + this->promise.setProgressValueAndText(1, QLatin1String("Foo 1")); - while (this->isProgressUpdateNeeded() == false) + while (this->promise.isProgressUpdateNeeded() == false) QTest::qSleep(1); - this->setProgressValueAndText(2, QLatin1String("Foo 2")); + this->promise.setProgressValueAndText(2, QLatin1String("Foo 2")); - while (this->isProgressUpdateNeeded() == false) + while (this->promise.isProgressUpdateNeeded() == false) QTest::qSleep(1); - this->setProgressValueAndText(3, QLatin1String("Foo 3")); + this->promise.setProgressValueAndText(3, QLatin1String("Foo 3")); - while (this->isProgressUpdateNeeded() == false) + while (this->promise.isProgressUpdateNeeded() == false) QTest::qSleep(1); - this->setProgressValueAndText(4, QLatin1String("Foo 4")); + this->promise.setProgressValueAndText(4, QLatin1String("Foo 4")); } }; @@ -545,12 +601,21 @@ void callInterface(T &obj) obj.isFinished(); obj.isRunning(); obj.isCanceled(); - obj.isPaused(); + obj.isSuspended(); + obj.isSuspending(); obj.cancel(); - obj.pause(); + obj.suspend(); obj.resume(); + obj.toggleSuspended(); +#if QT_DEPRECATED_SINCE(6, 0) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + obj.isPaused(); + obj.pause(); obj.togglePaused(); +QT_WARNING_POP +#endif obj.waitForFinished(); const T& objConst = obj; @@ -563,7 +628,14 @@ void callInterface(T &obj) objConst.isFinished(); objConst.isRunning(); objConst.isCanceled(); + objConst.isSuspending(); + objConst.isSuspended(); +#if QT_DEPRECATED_SINCE(6, 0) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED objConst.isPaused(); +QT_WARNING_POP +#endif } template <typename T> @@ -624,14 +696,14 @@ void tst_QFutureWatcher::changeFuture() watcher.setFuture(b); // But oh no! we're switching to another future QTest::qWait(10); // before the event gets delivered. - QCOMPARE(resultReadySpy.count(), 0); + QCOMPARE(resultReadySpy.size(), 0); watcher.setFuture(a); watcher.setFuture(b); watcher.setFuture(a); // setting it back gets us one event, not two. QVERIFY(resultReadySpy.wait()); - QCOMPARE(resultReadySpy.count(), 1); + QCOMPARE(resultReadySpy.size(), 1); } // Test that events aren't delivered from canceled futures @@ -659,9 +731,12 @@ void tst_QFutureWatcher::cancelEvents() QVERIFY(finishedSpy.wait()); - QCOMPARE(resultReadySpy.count(), 0); + QCOMPARE(resultReadySpy.size(), 0); } +#if QT_DEPRECATED_SINCE(6, 0) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED // Tests that events from paused futures are saved and // delivered on resume. void tst_QFutureWatcher::pauseEvents() @@ -677,18 +752,22 @@ void tst_QFutureWatcher::pauseEvents() QSignalSpy resultReadySpy(&watcher, &QFutureWatcher<int>::resultReadyAt); QVERIFY(resultReadySpy.isValid()); + QSignalSpy pauseSpy(&watcher, &QFutureWatcher<int>::paused); + QVERIFY(pauseSpy.isValid()); + watcher.setFuture(iface.future()); watcher.pause(); + QTRY_COMPARE(pauseSpy.size(), 1); + int value = 0; iface.reportFinished(&value); - QTest::qWait(10); - QCOMPARE(resultReadySpy.count(), 0); + // A result is reported, although the watcher is paused. + // The corresponding event should be also reported. + QTRY_COMPARE(resultReadySpy.size(), 1); watcher.resume(); - QTRY_VERIFY2(!resultReadySpy.isEmpty(), "Result didn't arrive"); - QCOMPARE(resultReadySpy.count(), 1); } { QFutureInterface<int> iface; @@ -714,32 +793,209 @@ void tst_QFutureWatcher::pauseEvents() a.resume(); // should give us no results. QTest::qWait(10); - QCOMPARE(resultReadySpy.count(), 0); + QCOMPARE(resultReadySpy.size(), 0); } } -// Test that the finished state for the watcher gets -// set when the finished event is delivered. -// This means it will lag the finished state for the future, -// but makes it more useful. -void tst_QFutureWatcher::finishedState() +void tst_QFutureWatcher::pausedSuspendedOrder() { - QFutureInterface<int> iface; + QFutureInterface<void> iface; iface.reportStarted(); - QFuture<int> future = iface.future(); + + QFutureWatcher<void> watcher; + + QSignalSpy pausedSpy(&watcher, &QFutureWatcher<void>::paused); + QVERIFY(pausedSpy.isValid()); + + QSignalSpy suspendedSpy(&watcher, &QFutureWatcher<void>::suspended); + QVERIFY(suspendedSpy.isValid()); + + bool pausedBeforeSuspended = false; + bool notSuspendedBeforePaused = false; + connect(&watcher, &QFutureWatcher<void>::paused, + [&] { notSuspendedBeforePaused = (suspendedSpy.size() == 0); }); + connect(&watcher, &QFutureWatcher<void>::suspended, + [&] { pausedBeforeSuspended = (pausedSpy.size() == 1); }); + + watcher.setFuture(iface.future()); + iface.reportSuspended(); + + // Make sure reportPaused() is ignored if the state is not paused + pausedSpy.wait(100); + QCOMPARE(pausedSpy.size(), 0); + QCOMPARE(suspendedSpy.size(), 0); + + iface.setPaused(true); + iface.reportSuspended(); + + QTRY_COMPARE(suspendedSpy.size(), 1); + QCOMPARE(pausedSpy.size(), 1); + QVERIFY(notSuspendedBeforePaused); + QVERIFY(pausedBeforeSuspended); + + iface.reportFinished(); +} +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 0) + +// Tests that events from suspended futures are saved and +// delivered on resume. +void tst_QFutureWatcher::suspendEvents() +{ + { + QFutureInterface<int> iface; + iface.reportStarted(); + + QFutureWatcher<int> watcher; + + SignalSlotObject object; + connect(&watcher, &QFutureWatcher<int>::resultReadyAt, &object, + &SignalSlotObject::resultReadyAt); + QSignalSpy resultReadySpy(&watcher, &QFutureWatcher<int>::resultReadyAt); + QVERIFY(resultReadySpy.isValid()); + + QSignalSpy suspendingSpy(&watcher, &QFutureWatcher<int>::suspending); + QVERIFY(suspendingSpy.isValid()); + + watcher.setFuture(iface.future()); + watcher.suspend(); + + QTRY_COMPARE(suspendingSpy.size(), 1); + + int value = 0; + iface.reportFinished(&value); + + // A result is reported, although the watcher is paused. + // The corresponding event should be also reported. + QTRY_COMPARE(resultReadySpy.size(), 1); + + watcher.resume(); + } + { + QFutureInterface<int> iface; + iface.reportStarted(); + + QFuture<int> a = iface.future(); + + QFutureWatcher<int> watcher; + + SignalSlotObject object; + connect(&watcher, &QFutureWatcher<int>::resultReadyAt, &object, + &SignalSlotObject::resultReadyAt); + QSignalSpy resultReadySpy(&watcher, &QFutureWatcher<int>::resultReadyAt); + QVERIFY(resultReadySpy.isValid()); + + watcher.setFuture(a); + a.suspend(); + + int value = 0; + iface.reportFinished(&value); + + QFuture<int> b; + watcher.setFuture(b); // If we watch b instead, resuming a + a.resume(); // should give us no results. + + QTest::qWait(10); + QCOMPARE(resultReadySpy.size(), 0); + } +} + +void tst_QFutureWatcher::suspended() +{ QFutureWatcher<int> watcher; - QSignalSpy startedSpy(&watcher, &QFutureWatcher<int>::started); + QSignalSpy resultReadySpy(&watcher, &QFutureWatcher<int>::resultReadyAt); +#if QT_DEPRECATED_SINCE(6, 0) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + QSignalSpy pausedSpy(&watcher, &QFutureWatcher<int>::paused); +QT_WARNING_POP +#endif + QSignalSpy suspendingSpy(&watcher, &QFutureWatcher<int>::suspending); + QSignalSpy suspendedSpy(&watcher, &QFutureWatcher<int>::suspended); QSignalSpy finishedSpy(&watcher, &QFutureWatcher<int>::finished); + const int numValues = 25; + std::vector<int> values(numValues, 0); + std::atomic_int count = 0; + + QThreadPool pool; + pool.setMaxThreadCount(3); + + QFuture<int> future = QtConcurrent::mapped(&pool, values, [&](int value) { + ++count; + // Sleep, to make sure not all threads will start at once. + QThread::sleep(50ms); + return value; + }); watcher.setFuture(future); - QVERIFY(startedSpy.wait()); - iface.reportFinished(); - QVERIFY(future.isFinished()); - QVERIFY(!watcher.isFinished()); + // Allow some threads to start before suspending. + QThread::sleep(200ms); - QVERIFY(finishedSpy.wait()); - QVERIFY(watcher.isFinished()); + watcher.suspend(); + watcher.suspend(); + QTRY_COMPARE(suspendedSpy.size(), 1); // suspended() should be emitted only once + QCOMPARE(suspendingSpy.size(), 2); // suspending() is emitted as many times as requested +#if QT_DEPRECATED_SINCE(6, 0) + QCOMPARE(pausedSpy.size(), 2); // paused() is emitted as many times as requested +#endif + + // Make sure QFutureWatcher::resultReadyAt() is emitted only for already started threads. + const auto resultReadyAfterPaused = resultReadySpy.size(); + QCOMPARE(resultReadyAfterPaused, count); + + // Make sure no more results are reported before resuming. + QThread::sleep(200ms); + QCOMPARE(resultReadyAfterPaused, resultReadySpy.size()); + resultReadySpy.clear(); + + watcher.resume(); + QTRY_COMPARE(finishedSpy.size(), 1); + + // Make sure that no more suspended() signals have been emitted. + QCOMPARE(suspendedSpy.size(), 1); + + // Make sure the rest of results were reported after resume. + QCOMPARE(resultReadySpy.size(), numValues - resultReadyAfterPaused); +} + +void tst_QFutureWatcher::suspendedEventsOrder() +{ + QFutureInterface<void> iface; + iface.reportStarted(); + + QFutureWatcher<void> watcher; + + QSignalSpy suspendingSpy(&watcher, &QFutureWatcher<void>::suspending); + QVERIFY(suspendingSpy.isValid()); + + QSignalSpy suspendedSpy(&watcher, &QFutureWatcher<void>::suspended); + QVERIFY(suspendedSpy.isValid()); + + bool suspendingBeforeSuspended = false; + bool notSuspendedBeforeSuspending = false; + connect(&watcher, &QFutureWatcher<void>::suspending, + [&] { notSuspendedBeforeSuspending = (suspendedSpy.size() == 0); }); + connect(&watcher, &QFutureWatcher<void>::suspended, + [&] { suspendingBeforeSuspended = (suspendingSpy.size() == 1); }); + + watcher.setFuture(iface.future()); + iface.reportSuspended(); + + // Make sure reportPaused() is ignored if the state is not paused + suspendingSpy.wait(100); + QCOMPARE(suspendingSpy.size(), 0); + QCOMPARE(suspendedSpy.size(), 0); + + iface.setSuspended(true); + iface.reportSuspended(); + + QTRY_COMPARE(suspendedSpy.size(), 1); + QCOMPARE(suspendingSpy.size(), 1); + QVERIFY(notSuspendedBeforeSuspending); + QVERIFY(suspendingBeforeSuspended); + + iface.reportFinished(); } /* @@ -765,7 +1021,7 @@ void tst_QFutureWatcher::throttling() QVERIFY(iface.isThrottled()); - QTRY_COMPARE(resultSpy.count(), resultCount); // Process the results + QTRY_COMPARE(resultSpy.size(), resultCount); // Process the results QVERIFY(!iface.isThrottled()); @@ -878,7 +1134,7 @@ void tst_QFutureWatcher::incrementalFilterResults() void tst_QFutureWatcher::qfutureSynchronizer() { int taskCount = 1000; - QTime t; + QElapsedTimer t; t.start(); { @@ -907,7 +1163,7 @@ public: void tst_QFutureWatcher::warnRace() { -#ifndef Q_OS_MAC //I don't know why it is not working on mac +#ifndef Q_OS_DARWIN // I don't know why it is not working on mac #ifndef QT_NO_DEBUG QTest::ignoreMessage(QtWarningMsg, "QFutureWatcher::connect: connecting after calling setFuture() is likely to produce race"); #endif @@ -936,6 +1192,55 @@ void tst_QFutureWatcher::matchFlags() QCOMPARE(watcher.isFinished(), future.isFinished()); } +void tst_QFutureWatcher::checkStateConsistency() +{ +#define CHECK_FAIL(state) \ + do { \ + if (QTest::currentTestFailed()) \ + QFAIL("checkState() failed, QFutureWatcher has inconistent state after " state "!"); \ + } while (false) + + QFutureWatcher<void> futureWatcher; + + auto checkState = [&futureWatcher] { + QCOMPARE(futureWatcher.isStarted(), futureWatcher.future().isStarted()); + QCOMPARE(futureWatcher.isRunning(), futureWatcher.future().isRunning()); + QCOMPARE(futureWatcher.isCanceled(), futureWatcher.future().isCanceled()); + QCOMPARE(futureWatcher.isSuspended(), futureWatcher.future().isSuspended()); + QCOMPARE(futureWatcher.isSuspending(), futureWatcher.future().isSuspending()); + QCOMPARE(futureWatcher.isFinished(), futureWatcher.future().isFinished()); + }; + + checkState(); + CHECK_FAIL("default-constructing"); + + QFutureInterface<void> fi; + futureWatcher.setFuture(fi.future()); + checkState(); + CHECK_FAIL("setting future"); + + fi.reportStarted(); + checkState(); + CHECK_FAIL("starting"); + + fi.future().suspend(); + checkState(); + CHECK_FAIL("suspending"); + + fi.reportSuspended(); + checkState(); + CHECK_FAIL("suspended"); + + fi.reportCanceled(); + checkState(); + CHECK_FAIL("canceling"); + + fi.reportFinished(); + checkState(); + CHECK_FAIL("finishing"); + +#undef CHECK_FAIL +} QTEST_MAIN(tst_QFutureWatcher) #include "tst_qfuturewatcher.moc" diff --git a/tests/auto/corelib/thread/qmutex/CMakeLists.txt b/tests/auto/corelib/thread/qmutex/CMakeLists.txt new file mode 100644 index 0000000000..5a92a2bffd --- /dev/null +++ b/tests/auto/corelib/thread/qmutex/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qmutex Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qmutex LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qmutex + SOURCES + tst_qmutex.cpp + LIBRARIES + Qt::CorePrivate +) diff --git a/tests/auto/corelib/thread/qmutex/qmutex.pro b/tests/auto/corelib/thread/qmutex/qmutex.pro deleted file mode 100644 index cb9d364b71..0000000000 --- a/tests/auto/corelib/thread/qmutex/qmutex.pro +++ /dev/null @@ -1,5 +0,0 @@ -CONFIG += testcase -TARGET = tst_qmutex -QT = core testlib -SOURCES = tst_qmutex.cpp -win32:QT += core-private diff --git a/tests/auto/corelib/thread/qmutex/tst_qmutex.cpp b/tests/auto/corelib/thread/qmutex/tst_qmutex.cpp index 7fb9a861d7..4753444ab9 100644 --- a/tests/auto/corelib/thread/qmutex/tst_qmutex.cpp +++ b/tests/auto/corelib/thread/qmutex/tst_qmutex.cpp @@ -1,40 +1,20 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2016 Intel Corporation. -** 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) 2016 The Qt Company Ltd. +// Copyright (C) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> +#include <QSemaphore> #include <qatomic.h> #include <qcoreapplication.h> #include <qelapsedtimer.h> #include <qmutex.h> #include <qthread.h> +#include <qvarlengtharray.h> #include <qwaitcondition.h> +#include <private/qvolatile_p.h> + +using namespace std::chrono_literals; class tst_QMutex : public QObject { @@ -46,11 +26,9 @@ public: Milliseconds, Seconds, }; - Q_ENUM(TimeUnit); + Q_ENUM(TimeUnit) private slots: - void convertToMilliseconds_data(); - void convertToMilliseconds(); void tryLock_non_recursive(); void try_lock_for_non_recursive(); void try_lock_until_non_recursive(); @@ -68,10 +46,11 @@ private slots: static const int iterations = 100; -QAtomicInt lockCount(0); -QMutex normalMutex, recursiveMutex(QMutex::Recursive); -QSemaphore testsTurn; -QSemaphore threadsTurn; +static QAtomicInt lockCount(0); +static QMutex normalMutex; +static QRecursiveMutex recursiveMutex; +static QSemaphore testsTurn; +static QSemaphore threadsTurn; /* Depending on the OS, tryWaits may return early than expected because of the @@ -82,128 +61,22 @@ QSemaphore threadsTurn; enum { #ifdef Q_OS_WIN systemTimersResolution = 16, +#elif defined(Q_OS_QNX) + systemTimersResolution = 10, #else systemTimersResolution = 1, #endif waitTime = 100 }; -#if QT_HAS_INCLUDE(<chrono>) -static Q_CONSTEXPR std::chrono::milliseconds waitTimeAsDuration(waitTime); -#endif - -void tst_QMutex::convertToMilliseconds_data() -{ - QTest::addColumn<TimeUnit>("unit"); - QTest::addColumn<double>("doubleValue"); - QTest::addColumn<qint64>("intValue"); - QTest::addColumn<qint64>("expected"); - -#if !QT_HAS_INCLUDE(<chrono>) - QSKIP("This test requires <chrono>"); -#endif - - auto add = [](TimeUnit unit, double d, long long i, qint64 expected) { - const QScopedArrayPointer<char> enumName(QTest::toString(unit)); - QTest::addRow("%s:%f:%lld", enumName.data(), d, i) - << unit << d << qint64(i) << expected; - }; - - auto forAllUnitsAdd = [=](double d, long long i, qint64 expected) { - for (auto unit : {TimeUnit::Nanoseconds, TimeUnit::Microseconds, TimeUnit::Milliseconds, TimeUnit::Seconds}) - add(unit, d, i, expected); - }; - - forAllUnitsAdd(-0.5, -1, 0); // all negative values result in 0 - - forAllUnitsAdd(0, 0, 0); - - add(TimeUnit::Nanoseconds, 1, 1, 1); - add(TimeUnit::Nanoseconds, 1000 * 1000, 1000 * 1000, 1); - add(TimeUnit::Nanoseconds, 1000 * 1000 + 0.5, 1000 * 1000 + 1, 2); - - add(TimeUnit::Microseconds, 1, 1, 1); - add(TimeUnit::Microseconds, 1000, 1000, 1); - add(TimeUnit::Microseconds, 1000 + 0.5, 1000 + 1, 2); - - add(TimeUnit::Milliseconds, 1, 1, 1); - add(TimeUnit::Milliseconds, 1.5, 2, 2); - - add(TimeUnit::Seconds, 0.9991, 1, 1000); - - // - // overflowing int results in INT_MAX (equivalent to a spurious wakeup after ~24 days); check it: - // - - // spot on: - add(TimeUnit::Nanoseconds, INT_MAX * 1000. * 1000, INT_MAX * Q_INT64_C(1000) * 1000, INT_MAX); - add(TimeUnit::Microseconds, INT_MAX * 1000., INT_MAX * Q_INT64_C(1000), INT_MAX); - add(TimeUnit::Milliseconds, INT_MAX, INT_MAX, INT_MAX); - - // minimally above: - add(TimeUnit::Nanoseconds, INT_MAX * 1000. * 1000 + 1, INT_MAX * Q_INT64_C(1000) * 1000 + 1, INT_MAX); - add(TimeUnit::Microseconds, INT_MAX * 1000. + 1, INT_MAX * Q_INT64_C(1000) + 1, INT_MAX); - add(TimeUnit::Milliseconds, INT_MAX + 1., INT_MAX + Q_INT64_C(1), INT_MAX); - add(TimeUnit::Seconds, INT_MAX / 1000. + 1, INT_MAX / 1000 + 1, INT_MAX); - - // minimally below: - add(TimeUnit::Nanoseconds, INT_MAX * 1000. * 1000 - 1, INT_MAX * Q_INT64_C(1000) * 1000 - 1, INT_MAX); - add(TimeUnit::Microseconds, INT_MAX * 1000. - 1, INT_MAX * Q_INT64_C(1000) - 1, INT_MAX); - add(TimeUnit::Milliseconds, INT_MAX - 0.1, INT_MAX , INT_MAX); - -} - -void tst_QMutex::convertToMilliseconds() -{ -#if !QT_HAS_INCLUDE(<chrono>) - QSKIP("This test requires <chrono>"); -#else - QFETCH(TimeUnit, unit); - QFETCH(double, doubleValue); - QFETCH(qint64, intValue); - QFETCH(qint64, expected); - - Q_CONSTEXPR qint64 maxShort = std::numeric_limits<short>::max(); - Q_CONSTEXPR qint64 maxInt = std::numeric_limits<int>::max(); - Q_CONSTEXPR qint64 maxUInt = std::numeric_limits<uint>::max(); - - switch (unit) { -#define CASE(Unit, Period) \ - case TimeUnit::Unit: \ - DO(double, Period, doubleValue); \ - if (intValue < maxShort) \ - DO(short, Period, short(intValue)); \ - if (intValue < maxInt) \ - DO(int, Period, int(intValue)); \ - DO(qint64, Period, intValue); \ - if (intValue >= 0) { \ - if (intValue < maxUInt) \ - DO(uint, Period, uint(intValue)); \ - DO(quint64, Period, quint64(intValue)); \ - } \ - break -#define DO(Rep, Period, val) \ - do { \ - const std::chrono::duration<Rep, Period> wait((val)); \ - QCOMPARE(QMutex::convertToMilliseconds(wait), expected); \ - } while (0) - - CASE(Nanoseconds, std::nano); - CASE(Microseconds, std::micro); - CASE(Milliseconds, std::milli); - CASE(Seconds, std::ratio<1>); -#undef DO -#undef CASE - } -#endif -} +static constexpr std::chrono::milliseconds waitTimeAsDuration(waitTime); void tst_QMutex::tryLock_non_recursive() { class Thread : public QThread { public: - void run() + void run() override { testsTurn.release(); @@ -226,19 +99,19 @@ void tst_QMutex::tryLock_non_recursive() QElapsedTimer timer; timer.start(); QVERIFY(!normalMutex.tryLock(waitTime)); - QVERIFY(timer.elapsed() >= waitTime - systemTimersResolution); + QCOMPARE_GE(timer.elapsed(), waitTime - systemTimersResolution); testsTurn.release(); // TEST 4: thread can acquire lock, timeout = waitTime threadsTurn.acquire(); timer.start(); QVERIFY(normalMutex.tryLock(waitTime)); - QVERIFY(timer.elapsed() <= waitTime + systemTimersResolution); + QCOMPARE_LE(timer.elapsed(), waitTime + systemTimersResolution); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); timer.start(); // it's non-recursive, so the following lock needs to fail QVERIFY(!normalMutex.tryLock(waitTime)); - QVERIFY(timer.elapsed() >= waitTime - systemTimersResolution); + QCOMPARE_GE(timer.elapsed(), waitTime - systemTimersResolution); QVERIFY(lockCount.testAndSetRelaxed(1, 0)); normalMutex.unlock(); testsTurn.release(); @@ -252,7 +125,7 @@ void tst_QMutex::tryLock_non_recursive() threadsTurn.acquire(); timer.start(); QVERIFY(normalMutex.tryLock(0)); - QVERIFY(timer.elapsed() < waitTime + systemTimersResolution); + QCOMPARE_LT(timer.elapsed(), waitTime + systemTimersResolution); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(!normalMutex.tryLock(0)); QVERIFY(lockCount.testAndSetRelaxed(1, 0)); @@ -263,7 +136,7 @@ void tst_QMutex::tryLock_non_recursive() threadsTurn.acquire(); timer.start(); QVERIFY(normalMutex.tryLock(3000)); - QVERIFY(timer.elapsed() < 3000 + systemTimersResolution); + QCOMPARE_LT(timer.elapsed(), 3000 + systemTimersResolution); normalMutex.unlock(); testsTurn.release(); @@ -274,47 +147,47 @@ void tst_QMutex::tryLock_non_recursive() Thread thread; thread.start(); - // TEST 1: thread can't acquire lock + qDebug("TEST 1: thread can't acquire lock"); testsTurn.acquire(); normalMutex.lock(); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); threadsTurn.release(); - // TEST 2: thread can acquire lock + qDebug("TEST 2: thread can acquire lock"); testsTurn.acquire(); QVERIFY(lockCount.testAndSetRelaxed(1, 0)); normalMutex.unlock(); threadsTurn.release(); - // TEST 3: thread can't acquire lock, timeout = waitTime + qDebug("TEST 3: thread can't acquire lock, timeout = waitTime"); testsTurn.acquire(); normalMutex.lock(); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); threadsTurn.release(); - // TEST 4: thread can acquire lock, timeout = waitTime + qDebug("TEST 4: thread can acquire lock, timeout = waitTime"); testsTurn.acquire(); QVERIFY(lockCount.testAndSetRelaxed(1, 0)); normalMutex.unlock(); threadsTurn.release(); - // TEST 5: thread can't acquire lock, timeout = 0 + qDebug("TEST 5: thread can't acquire lock, timeout = 0"); testsTurn.acquire(); normalMutex.lock(); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); threadsTurn.release(); - // TEST 6: thread can acquire lock, timeout = 0 + qDebug("TEST 6: thread can acquire lock, timeout = 0"); testsTurn.acquire(); QVERIFY(lockCount.testAndSetRelaxed(1, 0)); normalMutex.unlock(); threadsTurn.release(); - // TEST 7: thread can acquire lock, timeout = 3000 (QTBUG-24795) + qDebug("TEST 7: thread can acquire lock, timeout = 3000 (QTBUG-24795)"); testsTurn.acquire(); normalMutex.lock(); threadsTurn.release(); - QThread::msleep(100); + QThread::sleep(100ms); normalMutex.unlock(); // wait for thread to finish @@ -323,14 +196,12 @@ void tst_QMutex::tryLock_non_recursive() thread.wait(); } -void tst_QMutex::try_lock_for_non_recursive() { -#if !QT_HAS_INCLUDE(<chrono>) - QSKIP("This test requires <chrono>"); -#else +void tst_QMutex::try_lock_for_non_recursive() +{ class Thread : public QThread { public: - void run() + void run() override { testsTurn.release(); @@ -353,19 +224,19 @@ void tst_QMutex::try_lock_for_non_recursive() { QElapsedTimer timer; timer.start(); QVERIFY(!normalMutex.try_lock_for(waitTimeAsDuration)); - QVERIFY(timer.elapsed() >= waitTime - systemTimersResolution); + QCOMPARE_GE(timer.elapsed(), waitTime - systemTimersResolution); testsTurn.release(); // TEST 4: thread can acquire lock, timeout = waitTime threadsTurn.acquire(); timer.start(); QVERIFY(normalMutex.try_lock_for(waitTimeAsDuration)); - QVERIFY(timer.elapsed() <= waitTime + systemTimersResolution); + QCOMPARE_LE(timer.elapsed(), waitTime + systemTimersResolution); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); timer.start(); // it's non-recursive, so the following lock needs to fail QVERIFY(!normalMutex.try_lock_for(waitTimeAsDuration)); - QVERIFY(timer.elapsed() >= waitTime - systemTimersResolution); + QCOMPARE_GE(timer.elapsed(), waitTime - systemTimersResolution); QVERIFY(lockCount.testAndSetRelaxed(1, 0)); normalMutex.unlock(); testsTurn.release(); @@ -379,7 +250,7 @@ void tst_QMutex::try_lock_for_non_recursive() { threadsTurn.acquire(); timer.start(); QVERIFY(normalMutex.try_lock_for(std::chrono::milliseconds::zero())); - QVERIFY(timer.elapsed() < waitTime + systemTimersResolution); + QCOMPARE_LT(timer.elapsed(), waitTime + systemTimersResolution); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(!normalMutex.try_lock_for(std::chrono::milliseconds::zero())); QVERIFY(lockCount.testAndSetRelaxed(1, 0)); @@ -390,7 +261,7 @@ void tst_QMutex::try_lock_for_non_recursive() { threadsTurn.acquire(); timer.start(); QVERIFY(normalMutex.try_lock_for(std::chrono::milliseconds(3000))); - QVERIFY(timer.elapsed() < 3000 + systemTimersResolution); + QCOMPARE_LT(timer.elapsed(), 3000 + systemTimersResolution); normalMutex.unlock(); testsTurn.release(); @@ -441,25 +312,21 @@ void tst_QMutex::try_lock_for_non_recursive() { testsTurn.acquire(); normalMutex.lock(); threadsTurn.release(); - QThread::msleep(100); + QThread::sleep(100ms); normalMutex.unlock(); // wait for thread to finish testsTurn.acquire(); threadsTurn.release(); thread.wait(); -#endif } void tst_QMutex::try_lock_until_non_recursive() { -#if !QT_HAS_INCLUDE(<chrono>) - QSKIP("This test requires <chrono>"); -#else class Thread : public QThread { public: - void run() + void run() override { const std::chrono::milliseconds systemTimersResolutionAsDuration(systemTimersResolution); testsTurn.release(); @@ -482,19 +349,19 @@ void tst_QMutex::try_lock_until_non_recursive() threadsTurn.acquire(); auto endTimePoint = std::chrono::steady_clock::now() + waitTimeAsDuration; QVERIFY(!normalMutex.try_lock_until(endTimePoint)); - QVERIFY(std::chrono::steady_clock::now() >= endTimePoint - systemTimersResolutionAsDuration); + QCOMPARE_GE(std::chrono::steady_clock::now(), endTimePoint - systemTimersResolutionAsDuration); testsTurn.release(); // TEST 4: thread can acquire lock, timeout = waitTime threadsTurn.acquire(); endTimePoint = std::chrono::steady_clock::now() + waitTimeAsDuration; QVERIFY(normalMutex.try_lock_until(endTimePoint)); - QVERIFY(std::chrono::steady_clock::now() <= endTimePoint + systemTimersResolutionAsDuration); + QCOMPARE_LE(std::chrono::steady_clock::now(), endTimePoint + systemTimersResolutionAsDuration); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); endTimePoint = std::chrono::steady_clock::now() + waitTimeAsDuration; // it's non-recursive, so the following lock needs to fail QVERIFY(!normalMutex.try_lock_until(endTimePoint)); - QVERIFY(std::chrono::steady_clock::now() >= endTimePoint - systemTimersResolutionAsDuration); + QCOMPARE_GE(std::chrono::steady_clock::now(), endTimePoint - systemTimersResolutionAsDuration); QVERIFY(lockCount.testAndSetRelaxed(1, 0)); normalMutex.unlock(); testsTurn.release(); @@ -508,7 +375,7 @@ void tst_QMutex::try_lock_until_non_recursive() threadsTurn.acquire(); endTimePoint = std::chrono::steady_clock::now() + waitTimeAsDuration; QVERIFY(normalMutex.try_lock_until(std::chrono::steady_clock::now())); - QVERIFY(std::chrono::steady_clock::now() < endTimePoint + systemTimersResolutionAsDuration); + QCOMPARE_LT(std::chrono::steady_clock::now(), endTimePoint + systemTimersResolutionAsDuration); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(!normalMutex.try_lock_until(std::chrono::steady_clock::now())); QVERIFY(lockCount.testAndSetRelaxed(1, 0)); @@ -519,7 +386,7 @@ void tst_QMutex::try_lock_until_non_recursive() threadsTurn.acquire(); endTimePoint = std::chrono::steady_clock::now() + std::chrono::milliseconds(3000); QVERIFY(normalMutex.try_lock_until(endTimePoint)); - QVERIFY(std::chrono::steady_clock::now() < endTimePoint + systemTimersResolutionAsDuration); + QCOMPARE_LT(std::chrono::steady_clock::now(), endTimePoint + systemTimersResolutionAsDuration); normalMutex.unlock(); testsTurn.release(); @@ -570,14 +437,13 @@ void tst_QMutex::try_lock_until_non_recursive() testsTurn.acquire(); normalMutex.lock(); threadsTurn.release(); - QThread::msleep(100); + QThread::sleep(100ms); normalMutex.unlock(); // wait for thread to finish testsTurn.acquire(); threadsTurn.release(); thread.wait(); -#endif } void tst_QMutex::tryLock_recursive() @@ -585,7 +451,7 @@ void tst_QMutex::tryLock_recursive() class Thread : public QThread { public: - void run() + void run() override { testsTurn.release(); @@ -608,14 +474,14 @@ void tst_QMutex::tryLock_recursive() QElapsedTimer timer; timer.start(); QVERIFY(!recursiveMutex.tryLock(waitTime)); - QVERIFY(timer.elapsed() >= waitTime - systemTimersResolution); + QCOMPARE_GE(timer.elapsed(), waitTime - systemTimersResolution); QVERIFY(!recursiveMutex.tryLock(0)); testsTurn.release(); threadsTurn.acquire(); timer.start(); QVERIFY(recursiveMutex.tryLock(waitTime)); - QVERIFY(timer.elapsed() <= waitTime + systemTimersResolution); + QCOMPARE_LE(timer.elapsed(), waitTime + systemTimersResolution); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(recursiveMutex.tryLock(waitTime)); QVERIFY(lockCount.testAndSetRelaxed(1, 2)); @@ -633,7 +499,7 @@ void tst_QMutex::tryLock_recursive() threadsTurn.acquire(); timer.start(); QVERIFY(recursiveMutex.tryLock(0)); - QVERIFY(timer.elapsed() < waitTime + systemTimersResolution); + QCOMPARE_LT(timer.elapsed(), waitTime + systemTimersResolution); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(recursiveMutex.tryLock(0)); QVERIFY(lockCount.testAndSetRelaxed(1, 2)); @@ -706,15 +572,11 @@ void tst_QMutex::tryLock_recursive() void tst_QMutex::try_lock_for_recursive() { -#if !QT_HAS_INCLUDE(<chrono>) - QSKIP("This test requires <chrono>"); -#else class Thread : public QThread { public: - void run() + void run() override { - const std::chrono::milliseconds systemTimersResolutionAsDuration(systemTimersResolution); testsTurn.release(); threadsTurn.acquire(); @@ -736,14 +598,14 @@ void tst_QMutex::try_lock_for_recursive() QElapsedTimer timer; timer.start(); QVERIFY(!recursiveMutex.try_lock_for(waitTimeAsDuration)); - QVERIFY(timer.elapsed() >= waitTime - systemTimersResolution); + QCOMPARE_GE(timer.elapsed(), waitTime - systemTimersResolution); QVERIFY(!recursiveMutex.try_lock_for(std::chrono::milliseconds::zero())); testsTurn.release(); threadsTurn.acquire(); timer.start(); QVERIFY(recursiveMutex.try_lock_for(waitTimeAsDuration)); - QVERIFY(timer.elapsed() <= waitTime + systemTimersResolution); + QCOMPARE_LE(timer.elapsed(), waitTime + systemTimersResolution); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(recursiveMutex.try_lock_for(waitTimeAsDuration)); QVERIFY(lockCount.testAndSetRelaxed(1, 2)); @@ -761,7 +623,7 @@ void tst_QMutex::try_lock_for_recursive() threadsTurn.acquire(); timer.start(); QVERIFY(recursiveMutex.try_lock_for(std::chrono::milliseconds::zero())); - QVERIFY(timer.elapsed() < waitTime + systemTimersResolution); + QCOMPARE_LT(timer.elapsed(), waitTime + systemTimersResolution); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(recursiveMutex.try_lock_for(std::chrono::milliseconds::zero())); QVERIFY(lockCount.testAndSetRelaxed(1, 2)); @@ -830,18 +692,14 @@ void tst_QMutex::try_lock_for_recursive() testsTurn.acquire(); threadsTurn.release(); thread.wait(); -#endif } void tst_QMutex::try_lock_until_recursive() { -#if !QT_HAS_INCLUDE(<chrono>) - QSKIP("This test requires <chrono>"); -#else class Thread : public QThread { public: - void run() + void run() override { const std::chrono::milliseconds systemTimersResolutionAsDuration(systemTimersResolution); testsTurn.release(); @@ -864,14 +722,14 @@ void tst_QMutex::try_lock_until_recursive() threadsTurn.acquire(); auto endTimePoint = std::chrono::steady_clock::now() + waitTimeAsDuration; QVERIFY(!recursiveMutex.try_lock_until(endTimePoint)); - QVERIFY(std::chrono::steady_clock::now() >= endTimePoint - systemTimersResolutionAsDuration); + QCOMPARE_GE(std::chrono::steady_clock::now(), endTimePoint - systemTimersResolutionAsDuration); QVERIFY(!recursiveMutex.try_lock()); testsTurn.release(); threadsTurn.acquire(); endTimePoint = std::chrono::steady_clock::now() + waitTimeAsDuration; QVERIFY(recursiveMutex.try_lock_until(endTimePoint)); - QVERIFY(std::chrono::steady_clock::now() <= endTimePoint + systemTimersResolutionAsDuration); + QCOMPARE_LE(std::chrono::steady_clock::now(), endTimePoint + systemTimersResolutionAsDuration); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); endTimePoint = std::chrono::steady_clock::now() + waitTimeAsDuration; QVERIFY(recursiveMutex.try_lock_until(endTimePoint)); @@ -890,7 +748,7 @@ void tst_QMutex::try_lock_until_recursive() threadsTurn.acquire(); endTimePoint = std::chrono::steady_clock::now() + waitTimeAsDuration; QVERIFY(recursiveMutex.try_lock_until(std::chrono::steady_clock::now())); - QVERIFY(std::chrono::steady_clock::now() <= endTimePoint + systemTimersResolutionAsDuration); + QCOMPARE_LE(std::chrono::steady_clock::now(), endTimePoint + systemTimersResolutionAsDuration); QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(recursiveMutex.try_lock_until(std::chrono::steady_clock::now())); QVERIFY(lockCount.testAndSetRelaxed(1, 2)); @@ -959,7 +817,6 @@ void tst_QMutex::try_lock_until_recursive() testsTurn.acquire(); threadsTurn.release(); thread.wait(); -#endif } class mutex_Thread : public QThread @@ -972,7 +829,7 @@ public: inline mutex_Thread(QMutex &m) : test_mutex(m) { } - void run() + void run() override { test_mutex.lock(); @@ -993,11 +850,11 @@ public: QMutex mutex; QWaitCondition cond; - QMutex &test_mutex; + QRecursiveMutex &test_mutex; - inline rmutex_Thread(QMutex &m) : test_mutex(m) { } + inline rmutex_Thread(QRecursiveMutex &m) : test_mutex(m) { } - void run() + void run() override { test_mutex.lock(); test_mutex.lock(); @@ -1024,7 +881,7 @@ void tst_QMutex::lock_unlock_locked_tryLock() QMutex mutex; mutex_Thread thread(mutex); - QMutex rmutex(QMutex::Recursive); + QRecursiveMutex rmutex; rmutex_Thread rthread(rmutex); for (int i = 0; i < iterations; ++i) { @@ -1085,8 +942,8 @@ void tst_QMutex::lock_unlock_locked_tryLock() } } -enum { one_minute = 6 * 1000, //not really one minute, but else it is too long. - threadCount = 10 }; +constexpr int one_minute = 6 * 1000; // not really one minute, but else it is too long. +constexpr int threadCount = 10; class StressTestThread : public QThread { @@ -1101,7 +958,7 @@ public: t.start(); QThread::start(); } - void run() + void run() override { while (t.elapsed() < one_minute) { mutex.lock(); @@ -1132,7 +989,7 @@ void tst_QMutex::stressTest() for (int i = 1; i < threadCount; ++i) QVERIFY(threads[i].wait(10000)); QCOMPARE(StressTestThread::errorCount, 0); - qDebug("locked %d times", int(StressTestThread::lockCount.load())); + qDebug("locked %d times", int(StressTestThread::lockCount.loadRelaxed())); } class TryLockRaceThread : public QThread @@ -1140,7 +997,7 @@ class TryLockRaceThread : public QThread public: static QMutex mutex; - void run() + void run() override { QElapsedTimer t; t.start(); @@ -1186,12 +1043,13 @@ void tst_QMutex::tryLockDeadlock() struct TrylockThread : QThread { TrylockThread(QMutex &mut) : mut(mut) {} QMutex &mut; - void run() { + void run() override + { for (int i = 0; i < 100000; ++i) { if (mut.tryLock(0)) { - if ((++tryLockDeadlockCounter) != 1) + if (QtPrivate::volatilePreIncrement(tryLockDeadlockCounter) != 1) ++tryLockDeadlockFailureCount; - if ((--tryLockDeadlockCounter) != 0) + if (QtPrivate::volatilePreDecrement(tryLockDeadlockCounter) != 0) ++tryLockDeadlockFailureCount; mut.unlock(); } @@ -1208,9 +1066,9 @@ void tst_QMutex::tryLockDeadlock() for (int i = 0; i < 100000; ++i) { mut.lock(); - if ((++tryLockDeadlockCounter) != 1) + if (QtPrivate::volatilePreIncrement(tryLockDeadlockCounter) != 1) ++tryLockDeadlockFailureCount; - if ((--tryLockDeadlockCounter) != 0) + if (QtPrivate::volatilePreDecrement(tryLockDeadlockCounter) != 0) ++tryLockDeadlockFailureCount; mut.unlock(); } @@ -1239,7 +1097,8 @@ void tst_QMutex::tryLockNegative() QMutex &mut; int timeout; int tryLockResult; - void run() { + void run() override + { tryLockResult = mut.tryLock(timeout); mut.unlock(); } @@ -1281,33 +1140,33 @@ public: t.start(); QThread::start(); } - void run() + void run() override { quint64 i = 0; while (t.elapsed() < one_minute) { i++; - uint nb = (i * 9 + lockCount.load() * 13) % threadCount; + uint nb = (i * 9 + uint(lockCount.loadRelaxed()) * 13) % threadCount; QMutexLocker locker(&mutex[nb]); - if (sentinel[nb].load()) errorCount.ref(); + if (sentinel[nb].loadRelaxed()) errorCount.ref(); if (sentinel[nb].fetchAndAddRelaxed(5)) errorCount.ref(); if (!sentinel[nb].testAndSetRelaxed(5, 0)) errorCount.ref(); - if (sentinel[nb].load()) errorCount.ref(); + if (sentinel[nb].loadRelaxed()) errorCount.ref(); lockCount.ref(); - nb = (nb * 17 + i * 5 + lockCount.load() * 3) % threadCount; + nb = (nb * 17 + i * 5 + uint(lockCount.loadRelaxed()) * 3) % threadCount; if (mutex[nb].tryLock()) { - if (sentinel[nb].load()) errorCount.ref(); + if (sentinel[nb].loadRelaxed()) errorCount.ref(); if (sentinel[nb].fetchAndAddRelaxed(16)) errorCount.ref(); if (!sentinel[nb].testAndSetRelaxed(16, 0)) errorCount.ref(); - if (sentinel[nb].load()) errorCount.ref(); + if (sentinel[nb].loadRelaxed()) errorCount.ref(); lockCount.ref(); mutex[nb].unlock(); } - nb = (nb * 15 + i * 47 + lockCount.load() * 31) % threadCount; + nb = (nb * 15 + i * 47 + uint(lockCount.loadRelaxed()) * 31) % threadCount; if (mutex[nb].tryLock(2)) { - if (sentinel[nb].load()) errorCount.ref(); + if (sentinel[nb].loadRelaxed()) errorCount.ref(); if (sentinel[nb].fetchAndAddRelaxed(53)) errorCount.ref(); if (!sentinel[nb].testAndSetRelaxed(53, 0)) errorCount.ref(); - if (sentinel[nb].load()) errorCount.ref(); + if (sentinel[nb].loadRelaxed()) errorCount.ref(); lockCount.ref(); mutex[nb].unlock(); } @@ -1321,14 +1180,15 @@ QAtomicInt MoreStressTestThread::errorCount = 0; void tst_QMutex::moreStress() { - MoreStressTestThread threads[threadCount]; - for (int i = 0; i < threadCount; ++i) - threads[i].start(); + QVarLengthArray<MoreStressTestThread, threadCount> threads(qMin(QThread::idealThreadCount(), + int(threadCount))); + for (auto &thread : threads) + thread.start(); QVERIFY(threads[0].wait(one_minute + 10000)); - for (int i = 1; i < threadCount; ++i) - QVERIFY(threads[i].wait(10000)); - qDebug("locked %d times", MoreStressTestThread::lockCount.load()); - QCOMPARE(MoreStressTestThread::errorCount.load(), 0); + for (auto &thread : threads) + QVERIFY(thread.wait(10000)); + qDebug("locked %d times", MoreStressTestThread::lockCount.loadRelaxed()); + QCOMPARE(MoreStressTestThread::errorCount.loadRelaxed(), 0); } diff --git a/tests/auto/corelib/thread/qmutexlocker/CMakeLists.txt b/tests/auto/corelib/thread/qmutexlocker/CMakeLists.txt new file mode 100644 index 0000000000..7b2d6dc1c2 --- /dev/null +++ b/tests/auto/corelib/thread/qmutexlocker/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qmutexlocker Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qmutexlocker LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qmutexlocker + SOURCES + tst_qmutexlocker.cpp +) diff --git a/tests/auto/corelib/thread/qmutexlocker/qmutexlocker.pro b/tests/auto/corelib/thread/qmutexlocker/qmutexlocker.pro deleted file mode 100644 index 76ec0471ca..0000000000 --- a/tests/auto/corelib/thread/qmutexlocker/qmutexlocker.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qmutexlocker -QT = core testlib -SOURCES = tst_qmutexlocker.cpp diff --git a/tests/auto/corelib/thread/qmutexlocker/tst_qmutexlocker.cpp b/tests/auto/corelib/thread/qmutexlocker/tst_qmutexlocker.cpp index 67db5d1458..6ccab04c27 100644 --- a/tests/auto/corelib/thread/qmutexlocker/tst_qmutexlocker.cpp +++ b/tests/auto/corelib/thread/qmutexlocker/tst_qmutexlocker.cpp @@ -1,32 +1,7 @@ -/**************************************************************************** -** -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <QCoreApplication> #include <QMutexLocker> @@ -36,7 +11,7 @@ class tst_QMutexLockerThread : public QThread { public: - QMutex mutex; + QRecursiveMutex mutex; QSemaphore semaphore, testSemaphore; void waitForTest() @@ -45,10 +20,6 @@ public: testSemaphore.acquire(); } - tst_QMutexLockerThread() - : mutex(QMutex::Recursive) - { - } }; class tst_QMutexLocker : public QObject @@ -71,6 +42,7 @@ private slots: void scopeTest(); void unlockAndRelockTest(); void lockerStateTest(); + void moveSemantics(); }; void tst_QMutexLocker::scopeTest() @@ -78,12 +50,13 @@ void tst_QMutexLocker::scopeTest() class ScopeTestThread : public tst_QMutexLockerThread { public: - void run() + void run() override { waitForTest(); { QMutexLocker locker(&mutex); + QVERIFY(locker.isLocked()); waitForTest(); } @@ -114,7 +87,7 @@ void tst_QMutexLocker::scopeTest() QVERIFY(thread->wait()); delete thread; - thread = 0; + thread = nullptr; } @@ -123,19 +96,26 @@ void tst_QMutexLocker::unlockAndRelockTest() class UnlockAndRelockThread : public tst_QMutexLockerThread { public: - void run() + void run() override { QMutexLocker locker(&mutex); + QVERIFY(locker.isLocked()); waitForTest(); + QVERIFY(locker.isLocked()); locker.unlock(); + QVERIFY(!locker.isLocked()); waitForTest(); + QVERIFY(!locker.isLocked()); locker.relock(); + QVERIFY(locker.isLocked()); waitForTest(); + + QVERIFY(locker.isLocked()); } }; @@ -161,7 +141,7 @@ void tst_QMutexLocker::unlockAndRelockTest() QVERIFY(thread->wait()); delete thread; - thread = 0; + thread = nullptr; } void tst_QMutexLocker::lockerStateTest() @@ -169,14 +149,17 @@ void tst_QMutexLocker::lockerStateTest() class LockerStateThread : public tst_QMutexLockerThread { public: - void run() + void run() override { { QMutexLocker locker(&mutex); - locker.relock(); + QVERIFY(locker.isLocked()); + locker.unlock(); + QVERIFY(!locker.isLocked()); waitForTest(); + QVERIFY(!locker.isLocked()); } waitForTest(); @@ -201,7 +184,73 @@ void tst_QMutexLocker::lockerStateTest() QVERIFY(thread->wait()); delete thread; - thread = 0; + thread = nullptr; +} + +void tst_QMutexLocker::moveSemantics() +{ + { + QMutexLocker<QMutex> locker(nullptr); + QVERIFY(!locker.isLocked()); + QCOMPARE(locker.mutex(), nullptr); + + QMutexLocker locker2(std::move(locker)); + QVERIFY(!locker.isLocked()); + QVERIFY(!locker2.isLocked()); + QCOMPARE(locker.mutex(), nullptr); + QCOMPARE(locker2.mutex(), nullptr); + } + + QMutex mutex; + + { + QMutexLocker locker(&mutex); + QVERIFY(locker.isLocked()); + QCOMPARE(locker.mutex(), &mutex); + QVERIFY(!mutex.tryLock()); + + QMutexLocker locker2(std::move(locker)); + QVERIFY(!locker.isLocked()); + QVERIFY(locker2.isLocked()); + QCOMPARE(locker.mutex(), nullptr); + QCOMPARE(locker2.mutex(), &mutex); + QVERIFY(!mutex.tryLock()); + } + + QVERIFY(mutex.tryLock()); + mutex.unlock(); + + { + QMutex mutex; + QMutexLocker locker(&mutex); + QVERIFY(locker.isLocked()); + QCOMPARE(locker.mutex(), &mutex); + QVERIFY(!mutex.tryLock()); + + locker.unlock(); + QVERIFY(!locker.isLocked()); + QCOMPARE(locker.mutex(), &mutex); + QVERIFY(mutex.tryLock()); + mutex.unlock(); + + QMutexLocker locker2(std::move(locker)); + QVERIFY(!locker.isLocked()); + QVERIFY(!locker2.isLocked()); + QCOMPARE(locker.mutex(), nullptr); + QCOMPARE(locker2.mutex(), &mutex); + QVERIFY(mutex.tryLock()); + mutex.unlock(); + + locker2.relock(); + QVERIFY(!locker.isLocked()); + QVERIFY(locker2.isLocked()); + QCOMPARE(locker.mutex(), nullptr); + QCOMPARE(locker2.mutex(), &mutex); + QVERIFY(!mutex.tryLock()); + } + + QVERIFY(mutex.tryLock()); + mutex.unlock(); } QTEST_MAIN(tst_QMutexLocker) diff --git a/tests/auto/corelib/thread/qpromise/CMakeLists.txt b/tests/auto/corelib/thread/qpromise/CMakeLists.txt new file mode 100644 index 0000000000..c1ca30b34a --- /dev/null +++ b/tests/auto/corelib/thread/qpromise/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qpromise Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qpromise LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qpromise + SOURCES + tst_qpromise.cpp + LIBRARIES + Qt::CorePrivate +) diff --git a/tests/auto/corelib/thread/qpromise/snippet_qpromise.cpp b/tests/auto/corelib/thread/qpromise/snippet_qpromise.cpp new file mode 100644 index 0000000000..2cef0dca95 --- /dev/null +++ b/tests/auto/corelib/thread/qpromise/snippet_qpromise.cpp @@ -0,0 +1,158 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +// Note: this file is published under a license that is different from a default +// test sources license. This is intentional to comply with default +// snippet license. + +#include <QCoreApplication> +#include <QTest> + +#include <qfuture.h> +#include <qfuturewatcher.h> +#include <qpromise.h> +#include <qscopedpointer.h> +#include <qsharedpointer.h> + +class snippet_QPromise +{ +public: + static void basicExample(); + static void multithreadExample(); + static void suspendExample(); +}; + +void snippet_QPromise::basicExample() +{ +#if QT_CONFIG(cxx11_future) +//! [basic] + QPromise<int> promise; + QFuture<int> future = promise.future(); + + QScopedPointer<QThread> thread(QThread::create([] (QPromise<int> promise) { + promise.start(); // notifies QFuture that the computation is started + promise.addResult(42); + promise.finish(); // notifies QFuture that the computation is finished + }, std::move(promise))); + thread->start(); + + future.waitForFinished(); // blocks until QPromise::finish is called + future.result(); // returns 42 +//! [basic] + + QCOMPARE(future.result(), 42); + thread->wait(); +#endif +} + +void snippet_QPromise::multithreadExample() +{ +#if QT_CONFIG(cxx11_future) +//! [multithread_init] + QSharedPointer<QPromise<int>> sharedPromise(new QPromise<int>()); + QFuture<int> future = sharedPromise->future(); + + // ... + + sharedPromise->start(); +//! [multithread_init] + +//! [multithread_main] + // here, QPromise is shared between threads via a smart pointer + QScopedPointer<QThread> threads[] = { + QScopedPointer<QThread>(QThread::create([] (auto sharedPromise) { + sharedPromise->addResult(0, 0); // adds value 0 by index 0 + }, sharedPromise)), + QScopedPointer<QThread>(QThread::create([] (auto sharedPromise) { + sharedPromise->addResult(-1, 1); // adds value -1 by index 1 + }, sharedPromise)), + QScopedPointer<QThread>(QThread::create([] (auto sharedPromise) { + sharedPromise->addResult(-2, 2); // adds value -2 by index 2 + }, sharedPromise)), + // ... + }; + // start all threads + for (auto& t : threads) + t->start(); + + // ... + + future.resultAt(0); // waits until result at index 0 becomes available. returns value 0 + future.resultAt(1); // waits until result at index 1 becomes available. returns value -1 + future.resultAt(2); // waits until result at index 2 becomes available. returns value -2 +//! [multithread_main] + + QCOMPARE(future.resultAt(0), 0); + QCOMPARE(future.resultAt(1), -1); + QCOMPARE(future.resultAt(2), -2); + + for (auto& t : threads) + t->wait(); +//! [multithread_cleanup] + sharedPromise->finish(); +//! [multithread_cleanup] +#endif +} + +void snippet_QPromise::suspendExample() +{ +#if QT_CONFIG(cxx11_future) +//! [suspend_start] + // Create promise and future + QPromise<int> promise; + QFuture<int> future = promise.future(); + + promise.start(); + // Start a computation thread that supports suspension and cancellation + QScopedPointer<QThread> thread(QThread::create([] (QPromise<int> promise) { + for (int i = 0; i < 100; ++i) { + promise.addResult(i); + promise.suspendIfRequested(); // support suspension + if (promise.isCanceled()) // support cancellation + break; + } + promise.finish(); + }, std::move(promise))); + thread->start(); +//! [suspend_start] + +//! [suspend_suspend] + future.suspend(); +//! [suspend_suspend] + + // wait in calling thread until future.isSuspended() becomes true or do + // something meanwhile + while (!future.isSuspended()) { + QThread::msleep(50); + } + +//! [suspend_intermediateResults] + future.resultCount(); // returns some number between 0 and 100 + for (int i = 0; i < future.resultCount(); ++i) { + // process results available before suspension + } +//! [suspend_intermediateResults] + + // at least one result is available due to the logic inside a thread + QVERIFY(future.resultCount() > 0); + QVERIFY(future.resultCount() <= 100); + for (int i = 0; i < future.resultCount(); ++i) { + QCOMPARE(future.resultAt(i), i); + } + +//! [suspend_end] + future.resume(); // resumes computation, this call will unblock the promise + // alternatively, call future.cancel() to stop the computation + + future.waitForFinished(); + future.results(); // returns all computation results - array of values from 0 to 99 +//! [suspend_end] + + thread->wait(); + + QCOMPARE(future.resultCount(), 100); + QList<int> expected(100); + std::iota(expected.begin(), expected.end(), 0); + QCOMPARE(future.results(), expected); +#endif +} diff --git a/tests/auto/corelib/thread/qpromise/tst_qpromise.cpp b/tests/auto/corelib/thread/qpromise/tst_qpromise.cpp new file mode 100644 index 0000000000..2c12e41c93 --- /dev/null +++ b/tests/auto/corelib/thread/qpromise/tst_qpromise.cpp @@ -0,0 +1,784 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include <QCoreApplication> +#include <QDebug> + +#define QPROMISE_TEST + +#include <QTest> +#include <qfuture.h> +#include <qfuturewatcher.h> +#include <qpromise.h> + +#include <algorithm> +#include <memory> +#include <chrono> + +using namespace std::chrono_literals; + +class tst_QPromise : public QObject +{ + Q_OBJECT +private slots: + // simple test cases + void promise(); + void futureFromPromise(); + void addResult(); + void addResultWithBracedInitializer(); + void addResultOutOfOrder(); +#ifndef QT_NO_EXCEPTIONS + void setException(); +#endif + void cancel(); + void progress(); + + // complicated test cases + void addInThread(); + void addInThreadMoveOnlyObject(); // separate test case - QTBUG-84736 + void reportFromMultipleThreads(); + void reportFromMultipleThreadsByMovedPromise(); + void doNotCancelWhenFinished(); +#ifndef QT_NO_EXCEPTIONS + void cancelWhenDestroyed(); +#endif + void cancelWhenReassigned(); + void cancelWhenDestroyedWithoutStarting(); + void cancelWhenDestroyedRunsContinuations(); + void cancelWhenDestroyedWithFailureHandler(); // QTBUG-114606 + void continuationsRunWhenFinished(); + void finishWhenSwapped(); + void cancelWhenMoved(); + void waitUntilResumed(); + void waitUntilCanceled(); + + // snippets (external): + void snippet_basicExample(); + void snippet_multithreadExample(); + void snippet_suspendExample(); +}; + +struct TrivialType { int field = 0; }; +struct CopyOnlyType { + constexpr CopyOnlyType(int field = 0) noexcept : field(field) {} + CopyOnlyType(const CopyOnlyType &) = default; + CopyOnlyType& operator=(const CopyOnlyType &) = default; + ~CopyOnlyType() = default; + + int field; +}; +struct MoveOnlyType { + Q_DISABLE_COPY(MoveOnlyType) + constexpr MoveOnlyType(int field = 0) noexcept : field(field) {} + MoveOnlyType(MoveOnlyType &&) = default; + MoveOnlyType& operator=(MoveOnlyType &&) = default; + ~MoveOnlyType() = default; + + int field; +}; +bool operator==(const CopyOnlyType &a, const CopyOnlyType &b) { return a.field == b.field; } +bool operator==(const MoveOnlyType &a, const MoveOnlyType &b) { return a.field == b.field; } + +// A wrapper for a test function, calls the function, if it fails, reports failure +#define RUN_TEST_FUNC(test, ...) \ +do { \ + test(__VA_ARGS__); \ + if (QTest::currentTestFailed()) \ + QFAIL("Test case " #test "(" #__VA_ARGS__ ") failed"); \ +} while (false) + +#if QT_CONFIG(cxx11_future) +// std::thread-like wrapper that ensures that the thread is joined at the end of +// a scope to prevent potential std::terminate +struct ThreadWrapper +{ + std::unique_ptr<QThread> t; + template<typename Function> + ThreadWrapper(Function &&f) : t(QThread::create(std::forward<Function>(f))) + { + t->start(); + } + void join() { t->wait(); } + ~ThreadWrapper() + { + t->wait(); + } +}; +#endif + +void tst_QPromise::promise() +{ + const auto testCanCreatePromise = [] (auto promise) { + promise.start(); + promise.suspendIfRequested(); // should not block on its own + promise.finish(); + }; + + RUN_TEST_FUNC(testCanCreatePromise, QPromise<void>()); + RUN_TEST_FUNC(testCanCreatePromise, QPromise<int>()); + RUN_TEST_FUNC(testCanCreatePromise, QPromise<QList<float>>()); + RUN_TEST_FUNC(testCanCreatePromise, QPromise<TrivialType>()); + RUN_TEST_FUNC(testCanCreatePromise, QPromise<CopyOnlyType>()); + RUN_TEST_FUNC(testCanCreatePromise, QPromise<MoveOnlyType>()); +} + +void tst_QPromise::futureFromPromise() +{ + const auto testCanCreateFutureFromPromise = [] (auto promise) { + auto future = promise.future(); + QVERIFY(!future.isValid()); + + promise.start(); + QCOMPARE(future.isStarted(), true); + QVERIFY(future.isValid()); + + promise.finish(); + QCOMPARE(future.isFinished(), true); + QVERIFY(future.isValid()); + + future.waitForFinished(); + }; + + RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<void>()); + RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<double>()); + RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<QList<int>>()); + RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<TrivialType>()); + RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<CopyOnlyType>()); + RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<MoveOnlyType>()); +} + +void tst_QPromise::addResult() +{ + QPromise<int> promise; + auto f = promise.future(); + + // add as lvalue + int resultAt0 = 456; + { + QVERIFY(promise.addResult(resultAt0)); + QCOMPARE(f.resultCount(), 1); + QCOMPARE(f.result(), resultAt0); + QCOMPARE(f.resultAt(0), resultAt0); + } + // add as rvalue + { + int result = 789; + QVERIFY(promise.addResult(789)); + QCOMPARE(f.resultCount(), 2); + QCOMPARE(f.resultAt(1), result); + } + // add at position + { + int result = 56238; + QVERIFY(promise.addResult(result, 2)); + QCOMPARE(f.resultCount(), 3); + QCOMPARE(f.resultAt(2), result); + } + // add multiple results in one go: + { + QList results = {42, 4242, 424242}; + QVERIFY(promise.addResults(results)); + QCOMPARE(f.resultCount(), 6); + QCOMPARE(f.resultAt(3), 42); + QCOMPARE(f.resultAt(4), 4242); + QCOMPARE(f.resultAt(5), 424242); + } + // add as lvalue at position and overwrite + { + int result = -1; + const auto originalCount = f.resultCount(); + QVERIFY(!promise.addResult(result, 0)); + QCOMPARE(f.resultCount(), originalCount); + QCOMPARE(f.resultAt(0), resultAt0); // overwrite does not work + } + // add as rvalue at position and overwrite + { + const auto originalCount = f.resultCount(); + QVERIFY(!promise.addResult(-1, 0)); + QCOMPARE(f.resultCount(), originalCount); + QCOMPARE(f.resultAt(0), resultAt0); // overwrite does not work + } +} + +void tst_QPromise::addResultWithBracedInitializer() // QTBUG-111826 +{ + struct MyClass + { + QString strValue; + int intValue = 0; +#ifndef __cpp_aggregate_paren_init // make emplacement work with MyClass + MyClass(QString s, int i) : strValue(std::move(s)), intValue(i) {} +#endif + }; + + { + QPromise<MyClass> myPromise; + myPromise.addResult({"bar", 1}); + } + + { + QPromise<MyClass> myPromise; + myPromise.emplaceResult("bar", 1); + } +} + +void tst_QPromise::addResultOutOfOrder() +{ + // Compare results available in QFuture to expected results + const auto compareResults = [] (const auto &future, auto expected) { + QCOMPARE(future.resultCount(), expected.size()); + // index based loop + for (int i = 0; i < future.resultCount(); ++i) + QCOMPARE(future.resultAt(i), expected.at(i)); + // iterator based loop + QVERIFY(std::equal(future.begin(), future.end(), expected.begin())); + }; + + // out of order results without a gap + { + QPromise<int> promise; + auto f = promise.future(); + QVERIFY(promise.addResult(456, 1)); + QCOMPARE(f.resultCount(), 0); + QVERIFY(promise.addResult(123, 0)); + + QList<int> expected({123, 456}); + RUN_TEST_FUNC(compareResults, f, expected); + QCOMPARE(f.results(), expected); + } + + // out of order results with a gap that is closed "later" + { + QPromise<int> promise; + auto f = promise.future(); + QVERIFY(promise.addResult(0, 0)); + QVERIFY(promise.addResult(1, 1)); + QVERIFY(promise.addResult(3, 3)); // intentional gap here + + QList<int> expectedWhenGapExists({0, 1}); + RUN_TEST_FUNC(compareResults, f, expectedWhenGapExists); + QCOMPARE(f.resultAt(3), 3); + + QList<int> expectedWhenNoGap({0, 1, 2, 3}); + QVERIFY(promise.addResult(2, 2)); // fill a gap with a value + RUN_TEST_FUNC(compareResults, f, expectedWhenNoGap); + QCOMPARE(f.results(), expectedWhenNoGap); + } +} + +#ifndef QT_NO_EXCEPTIONS +void tst_QPromise::setException() +{ + struct TestException {}; // custom exception class + const auto testExceptionCaught = [] (auto promise, const auto& exception) { + auto f = promise.future(); + promise.start(); + promise.setException(exception); + promise.finish(); + + bool caught = false; + try { + f.waitForFinished(); + } catch (const QException&) { + caught = true; + } catch (const TestException&) { + caught = true; + } + QVERIFY(caught); + }; + + RUN_TEST_FUNC(testExceptionCaught, QPromise<void>(), QException()); + RUN_TEST_FUNC(testExceptionCaught, QPromise<int>(), QException()); + RUN_TEST_FUNC(testExceptionCaught, QPromise<void>(), + std::make_exception_ptr(TestException())); + RUN_TEST_FUNC(testExceptionCaught, QPromise<int>(), + std::make_exception_ptr(TestException())); + RUN_TEST_FUNC(testExceptionCaught, QPromise<CopyOnlyType>(), + std::make_exception_ptr(TestException())); + RUN_TEST_FUNC(testExceptionCaught, QPromise<MoveOnlyType>(), + std::make_exception_ptr(TestException())); +} +#endif + +void tst_QPromise::cancel() +{ + const auto testCancel = [] (auto promise) { + auto f = promise.future(); + f.cancel(); + QCOMPARE(promise.isCanceled(), true); + }; + + testCancel(QPromise<void>()); + testCancel(QPromise<int>()); + testCancel(QPromise<CopyOnlyType>()); + testCancel(QPromise<MoveOnlyType>()); +} + +void tst_QPromise::progress() +{ + const auto testProgress = [] (auto promise) { + auto f = promise.future(); + + promise.setProgressRange(0, 2); + QCOMPARE(f.progressMinimum(), 0); + QCOMPARE(f.progressMaximum(), 2); + + QCOMPARE(f.progressValue(), 0); + promise.setProgressValue(1); + QCOMPARE(f.progressValue(), 1); + promise.setProgressValue(0); // decrement + QCOMPARE(f.progressValue(), 1); + promise.setProgressValue(10); // out of range + QCOMPARE(f.progressValue(), 1); + + promise.setProgressRange(0, 100); + promise.setProgressValueAndText(50, u8"50%"); + QCOMPARE(f.progressValue(), 50); + QCOMPARE(f.progressText(), u8"50%"); + }; + + RUN_TEST_FUNC(testProgress, QPromise<void>()); + RUN_TEST_FUNC(testProgress, QPromise<int>()); + RUN_TEST_FUNC(testProgress, QPromise<CopyOnlyType>()); + RUN_TEST_FUNC(testProgress, QPromise<MoveOnlyType>()); +} + +void tst_QPromise::addInThread() +{ +#if QT_CONFIG(cxx11_future) + const auto testAddResult = [] (auto promise, const auto &result) { + promise.start(); + auto f = promise.future(); + // move construct QPromise + ThreadWrapper thr([p = std::move(promise), &result] () mutable { + p.addResult(result); + }); + // Waits for result first + QCOMPARE(f.result(), result); + QCOMPARE(f.resultAt(0), result); + }; + + RUN_TEST_FUNC(testAddResult, QPromise<int>(), 42); + RUN_TEST_FUNC(testAddResult, QPromise<QString>(), u8"42"); + RUN_TEST_FUNC(testAddResult, QPromise<CopyOnlyType>(), CopyOnlyType{99}); +#endif +} + +void tst_QPromise::addInThreadMoveOnlyObject() +{ +#if QT_CONFIG(cxx11_future) + QPromise<MoveOnlyType> promise; + promise.start(); + auto f = promise.future(); + + ThreadWrapper thr([p = std::move(promise)] () mutable { + p.addResult(MoveOnlyType{-11}); + }); + + // Iterators wait for result first + for (auto& result : f) + QCOMPARE(result, MoveOnlyType{-11}); +#endif +} + +void tst_QPromise::reportFromMultipleThreads() +{ +#if QT_CONFIG(cxx11_future) + QPromise<int> promise; + auto f = promise.future(); + promise.start(); + + ThreadWrapper threads[] = { + ThreadWrapper([&promise] () mutable { promise.addResult(42); }), + ThreadWrapper([&promise] () mutable { promise.addResult(43); }), + ThreadWrapper([&promise] () mutable { promise.addResult(44); }), + }; + for (auto& t : threads) + t.join(); + promise.finish(); + + QList<int> expected = {42, 43, 44}; + for (auto actual : f.results()) { + QVERIFY(std::find(expected.begin(), expected.end(), actual) != expected.end()); + expected.removeOne(actual); + } +#endif +} + +void tst_QPromise::reportFromMultipleThreadsByMovedPromise() +{ +#if QT_CONFIG(cxx11_future) + QPromise<int> initialPromise; + auto f = initialPromise.future(); + { + // Move QPromise into local scope: local QPromise (as being + // move-constructed) must be able to set results, QFuture must still + // hold correct references to results. + auto promise = std::move(initialPromise); + promise.start(); + ThreadWrapper threads[] = { + ThreadWrapper([&promise] () mutable { promise.addResult(42); }), + ThreadWrapper([&promise] () mutable { promise.addResult(43); }), + ThreadWrapper([&promise] () mutable { promise.addResult(44); }), + }; + for (auto& t : threads) + t.join(); + promise.finish(); + } + + QCOMPARE(f.isFinished(), true); + QCOMPARE(f.isValid(), true); + + QList<int> expected = {42, 43, 44}; + for (auto actual : f.results()) { + QVERIFY(std::find(expected.begin(), expected.end(), actual) != expected.end()); + expected.removeOne(actual); + } +#endif +} + +void tst_QPromise::doNotCancelWhenFinished() +{ +#if QT_CONFIG(cxx11_future) + const auto testFinishedPromise = [] (auto promise) { + auto f = promise.future(); + promise.start(); + + // Finish QPromise inside thread, destructor must not call cancel() + ThreadWrapper([p = std::move(promise)] () mutable { p.finish(); }).join(); + + f.waitForFinished(); + + QCOMPARE(f.isFinished(), true); + QCOMPARE(f.isCanceled(), false); + }; + + RUN_TEST_FUNC(testFinishedPromise, QPromise<void>()); + RUN_TEST_FUNC(testFinishedPromise, QPromise<int>()); + RUN_TEST_FUNC(testFinishedPromise, QPromise<QString>()); + RUN_TEST_FUNC(testFinishedPromise, QPromise<CopyOnlyType>()); + RUN_TEST_FUNC(testFinishedPromise, QPromise<MoveOnlyType>()); +#endif +} + +#ifndef QT_NO_EXCEPTIONS +void tst_QPromise::cancelWhenDestroyed() +{ +#if QT_CONFIG(cxx11_future) + QPromise<int> initialPromise; + auto f = initialPromise.future(); + + try { + // Move QPromise to local scope. On destruction, it must call cancel(). + auto promise = std::move(initialPromise); + promise.start(); + ThreadWrapper threads[] = { + ThreadWrapper([&promise] () mutable { promise.addResult(42); }), + ThreadWrapper([&promise] () mutable { promise.addResult(43); }), + ThreadWrapper([&promise] () mutable { promise.addResult(44); }), + }; + for (auto& t : threads) + t.join(); + throw "Throw in the middle, we lose our promise here, finish() not called!"; + promise.finish(); + } catch (...) {} + + QCOMPARE(f.isFinished(), true); + QCOMPARE(f.isCanceled(), true); + + // Results are still available despite throw + QList<int> expected = {42, 43, 44}; + for (auto actual : f.results()) { + QVERIFY(std::find(expected.begin(), expected.end(), actual) != expected.end()); + expected.removeOne(actual); + } +#endif +} +#endif + +void tst_QPromise::cancelWhenReassigned() +{ +#if QT_CONFIG(cxx11_future) + QPromise<int> promise; + auto f = promise.future(); + promise.start(); + + ThreadWrapper thr([p = std::move(promise)] () mutable { + QThread::sleep(100ms); + p = QPromise<int>(); // assign new promise, old must be correctly destroyed + }); + + f.waitForFinished(); // wait for the old promise + + QCOMPARE(f.isFinished(), true); + QCOMPARE(f.isCanceled(), true); +#endif +} + +template <typename T> +static inline void testCancelWhenDestroyedWithoutStarting() +{ + QFuture<T> future; + { + QPromise<T> promise; + future = promise.future(); + } + future.waitForFinished(); + QVERIFY(!future.isStarted()); + QVERIFY(future.isCanceled()); + QVERIFY(future.isFinished()); +} + +void tst_QPromise::cancelWhenDestroyedWithoutStarting() +{ + testCancelWhenDestroyedWithoutStarting<void>(); + testCancelWhenDestroyedWithoutStarting<int>(); + testCancelWhenDestroyedWithoutStarting<CopyOnlyType>(); + testCancelWhenDestroyedWithoutStarting<MoveOnlyType>(); +} + +template <typename T> +static inline void testCancelWhenDestroyedRunsContinuations() +{ + QFuture<T> future; + bool onCanceledCalled = false; + bool thenCalled = false; + { + QPromise<T> promise; + future = promise.future(); + future.then([&] (auto&&) { + thenCalled = true; + }).onCanceled([&] () { + onCanceledCalled = true; + }); + } + QVERIFY(future.isFinished()); + QVERIFY(!thenCalled); + QVERIFY(onCanceledCalled); +} + +void tst_QPromise::cancelWhenDestroyedRunsContinuations() +{ + testCancelWhenDestroyedRunsContinuations<void>(); + testCancelWhenDestroyedRunsContinuations<int>(); + testCancelWhenDestroyedRunsContinuations<CopyOnlyType>(); + testCancelWhenDestroyedRunsContinuations<MoveOnlyType>(); +} + +template <typename T> +static inline void testCancelWhenDestroyedWithFailureHandler() +{ + QFuture<T> future; + bool onFailedCalled = false; + bool thenCalled = false; + { + QPromise<T> promise; + future = promise.future(); + future + .onFailed([&] () { + onFailedCalled = true; + if constexpr (!std::is_same_v<void, T>) + return T{}; + }) + .then([&] (auto&&) { + thenCalled = true; + }); + } + QVERIFY(future.isFinished()); + QVERIFY(!onFailedCalled); + QVERIFY(!thenCalled); +} + +void tst_QPromise::cancelWhenDestroyedWithFailureHandler() +{ +#ifndef QT_NO_EXCEPTIONS + testCancelWhenDestroyedWithFailureHandler<void>(); + testCancelWhenDestroyedWithFailureHandler<int>(); + testCancelWhenDestroyedWithFailureHandler<CopyOnlyType>(); + testCancelWhenDestroyedWithFailureHandler<MoveOnlyType>(); +#else + QSKIP("Exceptions are disabled, skipping the test"); +#endif +} + +template <typename T> +static inline void testContinuationsRunWhenFinished() +{ + QPromise<T> promise; + QFuture<T> future = promise.future(); + + bool thenCalled = false; + future.then([&] (auto&&) { + thenCalled = true; + }); + + promise.start(); + if constexpr (!std::is_void_v<T>) { + promise.addResult(T{}); + } + promise.finish(); + + QVERIFY(thenCalled); +} + +void tst_QPromise::continuationsRunWhenFinished() +{ + testContinuationsRunWhenFinished<void>(); + testContinuationsRunWhenFinished<int>(); + testContinuationsRunWhenFinished<CopyOnlyType>(); + testContinuationsRunWhenFinished<MoveOnlyType>(); +} + +void tst_QPromise::finishWhenSwapped() +{ +#if QT_CONFIG(cxx11_future) + QPromise<int> promise1; + auto f1 = promise1.future(); + promise1.start(); + + QPromise<int> promise2; + auto f2 = promise2.future(); + promise2.start(); + + ThreadWrapper thr([&promise1, &promise2] () mutable { + QThread::sleep(100ms); + promise1.addResult(0); + promise2.addResult(1); + swap(promise1, promise2); // ADL must resolve this + promise1.addResult(2); + promise2.addResult(3); + promise1.finish(); // this finish is for future #2 + promise2.finish(); // this finish is for future #1 + }); + + f1.waitForFinished(); + f2.waitForFinished(); + + // Future #1 and #2 are finished inside thread + QCOMPARE(f1.isFinished(), true); + QCOMPARE(f1.isCanceled(), false); + + QCOMPARE(f2.isFinished(), true); + QCOMPARE(f2.isCanceled(), false); + + QCOMPARE(f1.resultAt(0), 0); + QCOMPARE(f1.resultAt(1), 3); + + QCOMPARE(f2.resultAt(0), 1); + QCOMPARE(f2.resultAt(1), 2); +#endif +} + +template <typename T> +void testCancelWhenMoved() +{ +#if QT_CONFIG(cxx11_future) + QPromise<T> promise1; + auto f1 = promise1.future(); + promise1.start(); + + QPromise<T> promise2; + auto f2 = promise2.future(); + promise2.start(); + + // Move promises to local scope to test cancellation behavior + ThreadWrapper thr([p1 = std::move(promise1), p2 = std::move(promise2)] () mutable { + QThread::sleep(100ms); + p1 = std::move(p2); + p1.finish(); // this finish is for future #2 + }); + + f1.waitForFinished(); + f2.waitForFinished(); + + // Future #1 is implicitly cancelled inside thread + QCOMPARE(f1.isFinished(), true); + QCOMPARE(f1.isCanceled(), true); + + // Future #2 is explicitly finished inside thread + QCOMPARE(f2.isFinished(), true); + QCOMPARE(f2.isCanceled(), false); +#endif +} + +void tst_QPromise::cancelWhenMoved() +{ + testCancelWhenMoved<void>(); + testCancelWhenMoved<int>(); + testCancelWhenMoved<CopyOnlyType>(); + testCancelWhenMoved<MoveOnlyType>(); +} + +void tst_QPromise::waitUntilResumed() +{ +#if !QT_CONFIG(cxx11_future) + QSKIP("This test requires QThread::create"); +#else + QPromise<int> promise; + promise.start(); + auto f = promise.future(); + f.suspend(); + + ThreadWrapper thr([p = std::move(promise)] () mutable { + p.suspendIfRequested(); + p.addResult(42); // result added after suspend + p.finish(); + }); + + while (!f.isSuspended()) { // busy wait until worker thread suspends + QCOMPARE(f.isFinished(), false); // exit condition in case of failure + QThread::sleep(50ms); // allow another thread to actually carry on + } + + f.resume(); + f.waitForFinished(); + + QCOMPARE(f.resultCount(), 1); + QCOMPARE(f.result(), 42); +#endif +} + +void tst_QPromise::waitUntilCanceled() +{ +#if QT_CONFIG(cxx11_future) + QPromise<int> promise; + promise.start(); + auto f = promise.future(); + f.suspend(); + + ThreadWrapper thr([p = std::move(promise)] () mutable { + p.suspendIfRequested(); + p.addResult(42); // result not added due to QFuture::cancel() + p.finish(); + }); + + while (!f.isSuspended()) { // busy wait until worker thread suspends + QCOMPARE(f.isFinished(), false); // exit condition in case of failure + QThread::sleep(50ms); // allow another thread to actually carry on + } + + f.cancel(); + f.waitForFinished(); + + QCOMPARE(f.resultCount(), 0); +#endif +} + +// Below is a quick and dirty hack to make snippets a part of a test suite +#include "snippet_qpromise.cpp" +void tst_QPromise::snippet_basicExample() +{ + snippet_QPromise::basicExample(); +} + +void tst_QPromise::snippet_multithreadExample() +{ + snippet_QPromise::multithreadExample(); +} + +void tst_QPromise::snippet_suspendExample() +{ + snippet_QPromise::suspendExample(); +} + +QTEST_MAIN(tst_QPromise) +#include "tst_qpromise.moc" diff --git a/tests/auto/corelib/thread/qreadlocker/CMakeLists.txt b/tests/auto/corelib/thread/qreadlocker/CMakeLists.txt new file mode 100644 index 0000000000..7e80a4df81 --- /dev/null +++ b/tests/auto/corelib/thread/qreadlocker/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qreadlocker Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qreadlocker LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qreadlocker + SOURCES + tst_qreadlocker.cpp +) diff --git a/tests/auto/corelib/thread/qreadlocker/qreadlocker.pro b/tests/auto/corelib/thread/qreadlocker/qreadlocker.pro deleted file mode 100644 index ba46786750..0000000000 --- a/tests/auto/corelib/thread/qreadlocker/qreadlocker.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qreadlocker -QT = core testlib -SOURCES = tst_qreadlocker.cpp diff --git a/tests/auto/corelib/thread/qreadlocker/tst_qreadlocker.cpp b/tests/auto/corelib/thread/qreadlocker/tst_qreadlocker.cpp index 7d1b3709c2..e21f80c05d 100644 --- a/tests/auto/corelib/thread/qreadlocker/tst_qreadlocker.cpp +++ b/tests/auto/corelib/thread/qreadlocker/tst_qreadlocker.cpp @@ -1,32 +1,7 @@ -/**************************************************************************** -** -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <QCoreApplication> #include <QReadLocker> @@ -73,7 +48,7 @@ void tst_QReadLocker::scopeTest() class ScopeTestThread : public tst_QReadLockerThread { public: - void run() + void run() override { waitForTest(); @@ -109,7 +84,7 @@ void tst_QReadLocker::scopeTest() QVERIFY(thread->wait()); delete thread; - thread = 0; + thread = nullptr; } @@ -118,7 +93,7 @@ void tst_QReadLocker::unlockAndRelockTest() class UnlockAndRelockThread : public tst_QReadLockerThread { public: - void run() + void run() override { QReadLocker locker(&lock); @@ -156,7 +131,7 @@ void tst_QReadLocker::unlockAndRelockTest() QVERIFY(thread->wait()); delete thread; - thread = 0; + thread = nullptr; } void tst_QReadLocker::lockerStateTest() @@ -164,7 +139,7 @@ void tst_QReadLocker::lockerStateTest() class LockerStateThread : public tst_QReadLockerThread { public: - void run() + void run() override { { QReadLocker locker(&lock); @@ -196,7 +171,7 @@ void tst_QReadLocker::lockerStateTest() QVERIFY(thread->wait()); delete thread; - thread = 0; + thread = nullptr; } QTEST_MAIN(tst_QReadLocker) diff --git a/tests/auto/corelib/thread/qreadwritelock/CMakeLists.txt b/tests/auto/corelib/thread/qreadwritelock/CMakeLists.txt new file mode 100644 index 0000000000..5ed3012e04 --- /dev/null +++ b/tests/auto/corelib/thread/qreadwritelock/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qreadwritelock Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qreadwritelock LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qreadwritelock + SOURCES + tst_qreadwritelock.cpp + LIBRARIES + Qt::CorePrivate + Qt::TestPrivate +) diff --git a/tests/auto/corelib/thread/qreadwritelock/qreadwritelock.pro b/tests/auto/corelib/thread/qreadwritelock/qreadwritelock.pro deleted file mode 100644 index c108c3d8af..0000000000 --- a/tests/auto/corelib/thread/qreadwritelock/qreadwritelock.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qreadwritelock -QT = core testlib -SOURCES = tst_qreadwritelock.cpp diff --git a/tests/auto/corelib/thread/qreadwritelock/tst_qreadwritelock.cpp b/tests/auto/corelib/thread/qreadwritelock/tst_qreadwritelock.cpp index 8e97229752..86dfa5faff 100644 --- a/tests/auto/corelib/thread/qreadwritelock/tst_qreadwritelock.cpp +++ b/tests/auto/corelib/thread/qreadwritelock/tst_qreadwritelock.cpp @@ -1,49 +1,20 @@ -/**************************************************************************** -** -** 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 <QSemaphore> #include <qcoreapplication.h> #include <qreadwritelock.h> +#include <qelapsedtimer.h> #include <qmutex.h> #include <qthread.h> #include <qwaitcondition.h> +#include <private/qemulationdetector_p.h> +#include <private/qvolatile_p.h> #ifdef Q_OS_UNIX #include <unistd.h> #endif -#if defined(Q_OS_WIN) -# include <qt_windows.h> -# ifndef Q_OS_WINRT -# define sleep(X) Sleep(X) -# else -# define sleep(X) WaitForSingleObjectEx(GetCurrentThread(), X, FALSE); -# endif -#endif //on solaris, threads that loop on the release bool variable //needs to sleep more than 1 usec. @@ -55,6 +26,8 @@ #include <stdio.h> +using namespace std::chrono_literals; + class tst_QReadWriteLock : public QObject { Q_OBJECT @@ -187,10 +160,10 @@ void tst_QReadWriteLock::readWriteLockUnlockLoop() } -QAtomicInt lockCount(0); -QReadWriteLock readWriteLock; -QSemaphore testsTurn; -QSemaphore threadsTurn; +static QAtomicInt lockCount(0); +static QReadWriteLock readWriteLock; +static QSemaphore testsTurn; +static QSemaphore threadsTurn; void tst_QReadWriteLock::tryReadLock() @@ -217,7 +190,7 @@ void tst_QReadWriteLock::tryReadLock() class Thread : public QThread { public: - void run() + void run() override { testsTurn.release(); @@ -237,7 +210,7 @@ void tst_QReadWriteLock::tryReadLock() testsTurn.release(); threadsTurn.acquire(); - QTime timer; + QElapsedTimer timer; timer.start(); QVERIFY(!readWriteLock.tryLockForRead(1000)); QVERIFY(timer.elapsed() >= 1000); @@ -334,7 +307,7 @@ void tst_QReadWriteLock::tryWriteLock() { public: Thread() : failureCount(0) { } - void run() + void run() override { testsTurn.release(); @@ -406,8 +379,8 @@ void tst_QReadWriteLock::tryWriteLock() } } -bool threadDone; -QAtomicInt release; +static bool threadDone; +static QAtomicInt release; /* write-lock @@ -419,7 +392,7 @@ class WriteLockThread : public QThread public: QReadWriteLock &testRwlock; inline WriteLockThread(QReadWriteLock &l) : testRwlock(l) { } - void run() + void run() override { testRwlock.lockForWrite(); testRwlock.unlock(); @@ -437,7 +410,7 @@ class ReadLockThread : public QThread public: QReadWriteLock &testRwlock; inline ReadLockThread(QReadWriteLock &l) : testRwlock(l) { } - void run() + void run() override { testRwlock.lockForRead(); testRwlock.unlock(); @@ -454,10 +427,10 @@ class WriteLockReleasableThread : public QThread public: QReadWriteLock &testRwlock; inline WriteLockReleasableThread(QReadWriteLock &l) : testRwlock(l) { } - void run() + void run() override { testRwlock.lockForWrite(); - while(release.load()==false) { + while (release.loadRelaxed() == false) { RWTESTSLEEP } testRwlock.unlock(); @@ -474,10 +447,10 @@ class ReadLockReleasableThread : public QThread public: QReadWriteLock &testRwlock; inline ReadLockReleasableThread(QReadWriteLock &l) : testRwlock(l) { } - void run() + void run() override { testRwlock.lockForRead(); - while(release.load()==false) { + while (release.loadRelaxed() == false) { RWTESTSLEEP } testRwlock.unlock(); @@ -497,10 +470,10 @@ class ReadLockLoopThread : public QThread public: QReadWriteLock &testRwlock; int runTime; - int holdTime; - int waitTime; + std::chrono::milliseconds holdTime; + std::chrono::milliseconds waitTime; bool print; - QTime t; + QElapsedTimer t; inline ReadLockLoopThread(QReadWriteLock &l, int runTime, int holdTime=0, int waitTime=0, bool print=false) :testRwlock(l) ,runTime(runTime) @@ -508,15 +481,15 @@ public: ,waitTime(waitTime) ,print(print) { } - void run() + void run() override { t.start(); while (t.elapsed()<runTime) { testRwlock.lockForRead(); if(print) printf("reading\n"); - if (holdTime) msleep(holdTime); + if (holdTime > 0ms) sleep(holdTime); testRwlock.unlock(); - if (waitTime) msleep(waitTime); + if (waitTime > 0ms) sleep(waitTime); } } }; @@ -533,10 +506,10 @@ class WriteLockLoopThread : public QThread public: QReadWriteLock &testRwlock; int runTime; - int holdTime; - int waitTime; + std::chrono::milliseconds holdTime; + std::chrono::milliseconds waitTime; bool print; - QTime t; + QElapsedTimer t; inline WriteLockLoopThread(QReadWriteLock &l, int runTime, int holdTime=0, int waitTime=0, bool print=false) :testRwlock(l) ,runTime(runTime) @@ -544,20 +517,20 @@ public: ,waitTime(waitTime) ,print(print) { } - void run() + void run() override { t.start(); while (t.elapsed() < runTime) { testRwlock.lockForWrite(); if (print) printf("."); - if (holdTime) msleep(holdTime); + if (holdTime > 0ms) sleep(holdTime); testRwlock.unlock(); - if (waitTime) msleep(waitTime); + if (waitTime > 0ms) sleep(waitTime); } } }; -volatile int count=0; +static volatile int count = 0; /* for(runTime msecs) @@ -572,16 +545,16 @@ class WriteLockCountThread : public QThread public: QReadWriteLock &testRwlock; int runTime; - int waitTime; + std::chrono::milliseconds waitTime; int maxval; - QTime t; + QElapsedTimer t; inline WriteLockCountThread(QReadWriteLock &l, int runTime, int waitTime, int maxval) :testRwlock(l) ,runTime(runTime) ,waitTime(waitTime) ,maxval(maxval) { } - void run() + void run() override { t.start(); while (t.elapsed() < runTime) { @@ -589,15 +562,11 @@ public: if(count) qFatal("Non-zero count at start of write! (%d)",count ); // printf("."); - int i; - for(i=0; i<maxval; ++i) { - volatile int lc=count; - ++lc; - count=lc; - } + for (int i = 0; i < maxval; ++i) + QtPrivate::volatilePreIncrement(count); count=0; testRwlock.unlock(); - msleep(waitTime); + sleep(waitTime); } } }; @@ -614,14 +583,14 @@ class ReadLockCountThread : public QThread public: QReadWriteLock &testRwlock; int runTime; - int waitTime; - QTime t; + std::chrono::milliseconds waitTime; + QElapsedTimer t; inline ReadLockCountThread(QReadWriteLock &l, int runTime, int waitTime) :testRwlock(l) ,runTime(runTime) ,waitTime(waitTime) { } - void run() + void run() override { t.start(); while (t.elapsed() < runTime) { @@ -629,7 +598,7 @@ public: if(count) qFatal("Non-zero count at Read! (%d)",count ); testRwlock.unlock(); - msleep(waitTime); + sleep(waitTime); } } }; @@ -646,7 +615,7 @@ void tst_QReadWriteLock::readLockBlockRelease() threadDone=false; ReadLockThread rlt(testLock); rlt.start(); - sleep(1); + QThread::sleep(1s); testLock.unlock(); rlt.wait(); QVERIFY(threadDone); @@ -663,7 +632,7 @@ void tst_QReadWriteLock::writeLockBlockRelease() threadDone=false; WriteLockThread wlt(testLock); wlt.start(); - sleep(1); + QThread::sleep(1s); testLock.unlock(); wlt.wait(); QVERIFY(threadDone); @@ -676,17 +645,17 @@ void tst_QReadWriteLock::multipleReadersBlockRelease() { QReadWriteLock testLock; - release.store(false); + release.storeRelaxed(false); threadDone=false; ReadLockReleasableThread rlt1(testLock); ReadLockReleasableThread rlt2(testLock); rlt1.start(); rlt2.start(); - sleep(1); + QThread::sleep(1s); WriteLockThread wlt(testLock); wlt.start(); - sleep(1); - release.store(true); + QThread::sleep(1s); + release.storeRelaxed(true); wlt.wait(); rlt1.wait(); rlt2.wait(); @@ -698,27 +667,29 @@ void tst_QReadWriteLock::multipleReadersBlockRelease() */ void tst_QReadWriteLock::multipleReadersLoop() { - int time=500; - int hold=250; - int wait=0; + if (QTestPrivate::isRunningArmOnX86()) + QSKIP("Flaky on QEMU, QTBUG-96103"); + + constexpr int time = 500; + constexpr int hold = 250; + constexpr int wait = 0; #if defined (Q_OS_HPUX) - const int numthreads=50; + constexpr int NumThreads = 50; #elif defined(Q_OS_VXWORKS) - const int numthreads=40; + constexpr int NumThreads = 40; #else - const int numthreads=75; + constexpr int NumThreads = 75; #endif QReadWriteLock testLock; - ReadLockLoopThread *threads[numthreads]; - int i; - for (i=0; i<numthreads; ++i) - threads[i] = new ReadLockLoopThread(testLock, time, hold, wait); - for (i=0; i<numthreads; ++i) - threads[i]->start(); - for (i=0; i<numthreads; ++i) - threads[i]->wait(); - for (i=0; i<numthreads; ++i) - delete threads[i]; + ReadLockLoopThread *threads[NumThreads]; + for (auto &thread : threads) + thread = new ReadLockLoopThread(testLock, time, hold, wait); + for (auto thread : threads) + thread->start(); + for (auto thread : threads) + thread->wait(); + for (auto thread : threads) + delete thread; } /* @@ -726,21 +697,20 @@ void tst_QReadWriteLock::multipleReadersLoop() */ void tst_QReadWriteLock::multipleWritersLoop() { - int time=500; - int wait=0; - int hold=0; - const int numthreads=50; - QReadWriteLock testLock; - WriteLockLoopThread *threads[numthreads]; - int i; - for (i=0; i<numthreads; ++i) - threads[i] = new WriteLockLoopThread(testLock, time, hold, wait); - for (i=0; i<numthreads; ++i) - threads[i]->start(); - for (i=0; i<numthreads; ++i) - threads[i]->wait(); - for (i=0; i<numthreads; ++i) - delete threads[i]; + constexpr int time = 500; + constexpr int wait = 0; + constexpr int hold = 0; + constexpr int numthreads = 50; + QReadWriteLock testLock; + WriteLockLoopThread *threads[numthreads]; + for (auto &thread : threads) + thread = new WriteLockLoopThread(testLock, time, hold, wait); + for (auto thread : threads) + thread->start(); + for (auto thread : threads) + thread->wait(); + for (auto thread : threads) + delete thread; } /* @@ -748,40 +718,36 @@ void tst_QReadWriteLock::multipleWritersLoop() */ void tst_QReadWriteLock::multipleReadersWritersLoop() { - //int time=INT_MAX; - int time=10000; - int readerThreads=20; - int readerWait=0; - int readerHold=1; - - int writerThreads=2; - int writerWait=500; - int writerHold=50; - - QReadWriteLock testLock; - ReadLockLoopThread *readers[1024]; - WriteLockLoopThread *writers[1024]; - int i; - - for (i=0; i<readerThreads; ++i) - readers[i] = new ReadLockLoopThread(testLock, time, readerHold, readerWait, false); - for (i=0; i<writerThreads; ++i) - writers[i] = new WriteLockLoopThread(testLock, time, writerHold, writerWait, false); - - for (i=0; i<readerThreads; ++i) - readers[i]->start(QThread::NormalPriority); - for (i=0; i<writerThreads; ++i) - writers[i]->start(QThread::IdlePriority); - - for (i=0; i<readerThreads; ++i) - readers[i]->wait(); - for (i=0; i<writerThreads; ++i) - writers[i]->wait(); - - for (i=0; i<readerThreads; ++i) - delete readers[i]; - for (i=0; i<writerThreads; ++i) - delete writers[i]; + constexpr int time = 10000; // INT_MAX + constexpr int readerThreads = 20; + constexpr int readerWait = 0; + constexpr int readerHold = 1; + + constexpr int writerThreads = 2; + constexpr int writerWait = 500; + constexpr int writerHold = 50; + + QReadWriteLock testLock; + ReadLockLoopThread *readers[readerThreads]; + WriteLockLoopThread *writers[writerThreads]; + + for (auto &thread : readers) + thread = new ReadLockLoopThread(testLock, time, readerHold, readerWait, false); + for (auto &thread : writers) + thread = new WriteLockLoopThread(testLock, time, writerHold, writerWait, false); + for (auto thread : readers) + thread->start(QThread::NormalPriority); + for (auto thread : writers) + thread->start(QThread::IdlePriority); + + for (auto thread : readers) + thread->wait(); + for (auto thread : writers) + thread->wait(); + for (auto thread : readers) + delete thread; + for (auto thread : writers) + delete thread; } /* @@ -790,39 +756,35 @@ void tst_QReadWriteLock::multipleReadersWritersLoop() */ void tst_QReadWriteLock::countingTest() { - //int time=INT_MAX; - int time=10000; - int readerThreads=20; - int readerWait=1; - - int writerThreads=3; - int writerWait=150; - int maxval=10000; - - QReadWriteLock testLock; - ReadLockCountThread *readers[1024]; - WriteLockCountThread *writers[1024]; - int i; - - for (i=0; i<readerThreads; ++i) - readers[i] = new ReadLockCountThread(testLock, time, readerWait); - for (i=0; i<writerThreads; ++i) - writers[i] = new WriteLockCountThread(testLock, time, writerWait, maxval); - - for (i=0; i<readerThreads; ++i) - readers[i]->start(QThread::NormalPriority); - for (i=0; i<writerThreads; ++i) - writers[i]->start(QThread::LowestPriority); - - for (i=0; i<readerThreads; ++i) - readers[i]->wait(); - for (i=0; i<writerThreads; ++i) - writers[i]->wait(); - - for (i=0; i<readerThreads; ++i) - delete readers[i]; - for (i=0; i<writerThreads; ++i) - delete writers[i]; + constexpr int time = 10000; // INT_MAX + constexpr int readerThreads = 20; + constexpr int readerWait = 1; + + constexpr int writerThreads = 3; + constexpr int writerWait = 150; + constexpr int maxval = 10000; + + QReadWriteLock testLock; + ReadLockCountThread *readers[readerThreads]; + WriteLockCountThread *writers[writerThreads]; + + for (auto &thread : readers) + thread = new ReadLockCountThread(testLock, time, readerWait); + for (auto &thread : writers) + thread = new WriteLockCountThread(testLock, time, writerWait, maxval); + for (auto thread : readers) + thread->start(QThread::NormalPriority); + for (auto thread : writers) + thread->start(QThread::LowestPriority); + + for (auto thread : readers) + thread->wait(); + for (auto thread : writers) + thread->wait(); + for (auto thread : readers) + delete thread; + for (auto thread : writers) + delete thread; } void tst_QReadWriteLock::limitedReaders() @@ -848,7 +810,7 @@ class DeleteOnUnlockThread : public QThread public: DeleteOnUnlockThread(QReadWriteLock **lock, QWaitCondition *startup, QMutex *waitMutex) :m_lock(lock), m_startup(startup), m_waitMutex(waitMutex) {} - void run() + void run() override { m_waitMutex->lock(); m_startup->wakeAll(); @@ -867,13 +829,13 @@ private: void tst_QReadWriteLock::deleteOnUnlock() { - QReadWriteLock *lock = 0; + QReadWriteLock *lock = nullptr; QWaitCondition startup; QMutex waitMutex; DeleteOnUnlockThread thread2(&lock, &startup, &waitMutex); - QTime t; + QElapsedTimer t; t.start(); while(t.elapsed() < 4000) { lock = new QReadWriteLock(); @@ -899,7 +861,7 @@ void tst_QReadWriteLock::uncontendedLocks() uint count=0; int millisecs=1000; { - QTime t; + QElapsedTimer t; t.start(); while(t.elapsed() <millisecs) { @@ -908,7 +870,7 @@ void tst_QReadWriteLock::uncontendedLocks() } { QReadWriteLock rwlock; - QTime t; + QElapsedTimer t; t.start(); while(t.elapsed() <millisecs) { @@ -919,7 +881,7 @@ void tst_QReadWriteLock::uncontendedLocks() } { QReadWriteLock rwlock; - QTime t; + QElapsedTimer t; t.start(); while(t.elapsed() <millisecs) { @@ -946,7 +908,7 @@ void tst_QReadWriteLock::recursiveReadLock() QReadWriteLock *lock; bool tryLockForWriteResult; - void run() + void run() override { testsTurn.release(); @@ -1041,7 +1003,7 @@ void tst_QReadWriteLock::recursiveWriteLock() QReadWriteLock *lock; bool tryLockForReadResult; - void run() + void run() override { testsTurn.release(); diff --git a/tests/auto/corelib/thread/qresultstore/CMakeLists.txt b/tests/auto/corelib/thread/qresultstore/CMakeLists.txt new file mode 100644 index 0000000000..5abfc14ac6 --- /dev/null +++ b/tests/auto/corelib/thread/qresultstore/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qresultstore Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qresultstore LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qresultstore + SOURCES + tst_qresultstore.cpp + LIBRARIES + Qt::CorePrivate + Qt::TestPrivate +) diff --git a/tests/auto/corelib/thread/qresultstore/qresultstore.pro b/tests/auto/corelib/thread/qresultstore/qresultstore.pro deleted file mode 100644 index bbebe0976b..0000000000 --- a/tests/auto/corelib/thread/qresultstore/qresultstore.pro +++ /dev/null @@ -1,5 +0,0 @@ -CONFIG += testcase -TARGET = tst_qresultstore -QT = core-private testlib -SOURCES = tst_qresultstore.cpp -DEFINES += QT_STRICT_ITERATORS diff --git a/tests/auto/corelib/thread/qresultstore/tst_qresultstore.cpp b/tests/auto/corelib/thread/qresultstore/tst_qresultstore.cpp index fba617e34d..722184a72a 100644 --- a/tests/auto/corelib/thread/qresultstore/tst_qresultstore.cpp +++ b/tests/auto/corelib/thread/qresultstore/tst_qresultstore.cpp @@ -1,40 +1,20 @@ -/**************************************************************************** -** -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include <QTest> +#include <QtTest/private/qcomparisontesthelper_p.h> #include <qresultstore.h> using namespace QtPrivate; -struct ResultStoreInt : ResultStoreBase +class IntResultsCleaner { - ~ResultStoreInt() { clear<int>(); } +public: + IntResultsCleaner(QtPrivate::ResultStoreBase &s) : store(s) { } + ~IntResultsCleaner() { store.clear<int>(); } + +private: + QtPrivate::ResultStoreBase &store; }; class tst_QtConcurrentResultStore : public QObject @@ -43,6 +23,7 @@ class tst_QtConcurrentResultStore : public QObject public slots: void init(); private slots: + void compareCompiles(); void construction(); void iterators(); void addResult(); @@ -53,12 +34,14 @@ private slots: void filterMode(); void addCanceledResult(); void count(); + void pendingResultsDoNotLeak_data(); + void pendingResultsDoNotLeak(); private: int int0; int int1; int int2; - QVector<int> vec0; - QVector<int> vec1; + QList<int> vec0; + QList<int> vec1; }; void tst_QtConcurrentResultStore::init() @@ -66,8 +49,13 @@ void tst_QtConcurrentResultStore::init() int0 = 0; int1 = 1; int2 = 2; - vec0 = QVector<int>() << 2 << 3; - vec1 = QVector<int>() << 4 << 5; + vec0 = QList<int> { 2, 3 }; + vec1 = QList<int> { 4, 5 }; +} + +void tst_QtConcurrentResultStore::compareCompiles() +{ + QTestPrivate::testEqualityOperatorsCompile<ResultIteratorBase>(); } void tst_QtConcurrentResultStore::construction() @@ -85,22 +73,27 @@ void tst_QtConcurrentResultStore::iterators() QCOMPARE(store.resultAt(1), store.end()); } { - ResultStoreInt storebase; + QtPrivate::ResultStoreBase storebase; + IntResultsCleaner cleanGuard(storebase); + storebase.addResult(-1, &int0); // note to self: adding a pointer to the stack here is ok since storebase.addResult(1, &int1); // ResultStoreBase does not take ownership, only ResultStore<> does. ResultIteratorBase it = storebase.begin(); QCOMPARE(it.resultIndex(), 0); - QCOMPARE(it, storebase.begin()); + QT_TEST_EQUALITY_OPS(it, storebase.begin(), true); QVERIFY(it != storebase.end()); ++it; QCOMPARE(it.resultIndex(), 1); QVERIFY(it != storebase.begin()); QVERIFY(it != storebase.end()); + QT_TEST_EQUALITY_OPS(it, storebase.begin(), false); + QT_TEST_EQUALITY_OPS(it, storebase.end(), false); ++it; QVERIFY(it != storebase.begin()); QCOMPARE(it, storebase.end()); + QT_TEST_EQUALITY_OPS(it, storebase.end(), true); } } @@ -108,7 +101,9 @@ void tst_QtConcurrentResultStore::addResult() { { // test addResult return value - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); QCOMPARE(store.addResult(0, &int0), 0); @@ -154,13 +149,15 @@ void tst_QtConcurrentResultStore::addResult() void tst_QtConcurrentResultStore::addResults() { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(-1, &vec0); store.addResults(-1, &vec1); ResultIteratorBase it = store.begin(); QCOMPARE(it.resultIndex(), 0); - QCOMPARE(it, store.begin()); - QVERIFY(it != store.end()); + QT_TEST_EQUALITY_OPS(it, store.begin(), true); + QT_TEST_EQUALITY_OPS(it, store.end(), false); ++it; QCOMPARE(it.resultIndex(), 1); @@ -174,34 +171,44 @@ void tst_QtConcurrentResultStore::addResults() QCOMPARE(it.resultIndex(), 3); ++it; - QCOMPARE(it, store.end()); + QT_TEST_EQUALITY_OPS(it, store.end(), true); + + QList<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() { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(-1, &int0); store.addResults(-1, &vec0); store.addResult(-1, &int1); ResultIteratorBase it = store.begin(); QCOMPARE(it.resultIndex(), 0); - QVERIFY(it == store.begin()); - QVERIFY(it != store.end()); + QT_TEST_EQUALITY_OPS(it, store.begin(), true); + QT_TEST_EQUALITY_OPS(it, store.end(), false); ++it; QCOMPARE(it.resultIndex(), 1); - QVERIFY(it != store.begin()); - QVERIFY(it != store.end()); + QT_TEST_EQUALITY_OPS(it, store.begin(), false); + QT_TEST_EQUALITY_OPS(it, store.end(), false); ++it; QCOMPARE(it.resultIndex(), 2); - QVERIFY(it != store.end()); + QT_TEST_EQUALITY_OPS(it, store.end(), false); ++it; QCOMPARE(it.resultIndex(), 3); - QVERIFY(it != store.end()); + QT_TEST_EQUALITY_OPS(it, store.end(), false); ++it; - QVERIFY(it == store.end()); + QT_TEST_EQUALITY_OPS(it, store.end(), true); QCOMPARE(store.resultAt(0).value<int>(), int0); QCOMPARE(store.resultAt(1).value<int>(), vec0[0]); @@ -212,7 +219,9 @@ void tst_QtConcurrentResultStore::resultIndex() void tst_QtConcurrentResultStore::resultAt() { { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(-1, &int0); store.addResults(-1, &vec0); store.addResult(200, &int1); @@ -223,7 +232,9 @@ void tst_QtConcurrentResultStore::resultAt() QCOMPARE(store.resultAt(200).value<int>(), int1); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(1, &int1); store.addResult(0, &int0); store.addResult(-1, &int2); @@ -237,7 +248,9 @@ void tst_QtConcurrentResultStore::resultAt() void tst_QtConcurrentResultStore::contains() { { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + QCOMPARE(store.contains(0), false); QCOMPARE(store.contains(1), false); QCOMPARE(store.contains(INT_MAX), false); @@ -249,7 +262,9 @@ void tst_QtConcurrentResultStore::contains() QVERIFY(store.contains(int2)); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(1, &int0); store.addResult(3, &int0); store.addResults(6, &vec0); @@ -264,7 +279,9 @@ void tst_QtConcurrentResultStore::contains() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(1, &int0); store.addResult(3, &int0); @@ -292,7 +309,9 @@ void tst_QtConcurrentResultStore::contains() QCOMPARE(store.contains(7), false); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addCanceledResult(0); QCOMPARE(store.contains(0), false); @@ -306,7 +325,9 @@ void tst_QtConcurrentResultStore::contains() void tst_QtConcurrentResultStore::filterMode() { // Test filter mode, where "gaps" in the result array aren't allowed. - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + QCOMPARE(store.filterMode(), false); store.setFilterMode(true); QVERIFY(store.filterMode()); @@ -338,12 +359,22 @@ void tst_QtConcurrentResultStore::filterMode() QCOMPARE(store.contains(6), true); QCOMPARE(store.contains(7), true); QCOMPARE(store.contains(8), false); + + QList<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() { // test canceled results - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResult(0, &int0); @@ -383,7 +414,9 @@ void tst_QtConcurrentResultStore::count() { // test resultCount in non-filtered mode. It should always be possible // to iterate through the results 0 to resultCount. - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(0, &int0); QCOMPARE(store.count(), 1); @@ -397,7 +430,9 @@ void tst_QtConcurrentResultStore::count() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResult(2, &int0); QCOMPARE(store.count(), 0); @@ -409,7 +444,9 @@ void tst_QtConcurrentResultStore::count() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(2, &vec1); QCOMPARE(store.count(), 0); @@ -421,7 +458,9 @@ void tst_QtConcurrentResultStore::count() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(2, &vec1); QCOMPARE(store.count(), 0); @@ -429,7 +468,9 @@ void tst_QtConcurrentResultStore::count() QCOMPARE(store.count(), 4); } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -441,7 +482,9 @@ void tst_QtConcurrentResultStore::count() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -454,7 +497,9 @@ void tst_QtConcurrentResultStore::count() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -464,7 +509,9 @@ void tst_QtConcurrentResultStore::count() } { - ResultStoreInt store; + QtPrivate::ResultStoreBase store; + IntResultsCleaner cleanGuard(store); + store.setFilterMode(true); store.addResults(3, &vec1); QCOMPARE(store.count(), 0); @@ -477,5 +524,75 @@ void tst_QtConcurrentResultStore::count() } } +// simplified version of CountedObject from tst_qarraydata.cpp +struct CountedObject +{ + CountedObject() : id(liveCount++) + { } + + CountedObject(const CountedObject &other) : id(other.id) + { + ++liveCount; + } + + ~CountedObject() + { + --liveCount; + } + + CountedObject &operator=(const CountedObject &) = default; + + struct LeakChecker + { + LeakChecker() + : previousLiveCount(liveCount) + { + } + + ~LeakChecker() + { + QCOMPARE(liveCount, previousLiveCount); + } + + private: + const size_t previousLiveCount; + }; + + size_t id = 0; + static size_t liveCount; +}; + +size_t CountedObject::liveCount = 0; + +void tst_QtConcurrentResultStore::pendingResultsDoNotLeak_data() +{ + QTest::addColumn<bool>("filterMode"); + + QTest::addRow("filter-mode-off") << false; + QTest::addRow("filter-mode-on") << true; +} + +void tst_QtConcurrentResultStore::pendingResultsDoNotLeak() +{ + QFETCH(bool, filterMode); + CountedObject::LeakChecker leakChecker; Q_UNUSED(leakChecker) + + QtPrivate::ResultStoreBase store; + auto cleanGaurd = qScopeGuard([&] { store.clear<CountedObject>(); }); + + store.setFilterMode(filterMode); + + // lvalue + auto lvalueObj = CountedObject(); + store.addResult(42, &lvalueObj); + + // rvalue + store.moveResult(43, CountedObject()); + + // array + auto lvalueListOfObj = QList<CountedObject>({CountedObject(), CountedObject()}); + store.addResults(44, &lvalueListOfObj); +} + QTEST_MAIN(tst_QtConcurrentResultStore) #include "tst_qresultstore.moc" diff --git a/tests/auto/corelib/thread/qsemaphore/BLACKLIST b/tests/auto/corelib/thread/qsemaphore/BLACKLIST index 0786f50417..ecd42cff7c 100644 --- a/tests/auto/corelib/thread/qsemaphore/BLACKLIST +++ b/tests/auto/corelib/thread/qsemaphore/BLACKLIST @@ -1,8 +1,2 @@ -[tryAcquireWithTimeout:0.2s] -windows -osx-10.12 -osx-10.13 -[tryAcquireWithTimeout:2s] -windows -osx-10.12 -osx-10.13 +[tryAcquireWithTimeout] +macos diff --git a/tests/auto/corelib/thread/qsemaphore/CMakeLists.txt b/tests/auto/corelib/thread/qsemaphore/CMakeLists.txt new file mode 100644 index 0000000000..9f8a87558a --- /dev/null +++ b/tests/auto/corelib/thread/qsemaphore/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qsemaphore Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qsemaphore LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qsemaphore + SOURCES + tst_qsemaphore.cpp +) diff --git a/tests/auto/corelib/thread/qsemaphore/qsemaphore.pro b/tests/auto/corelib/thread/qsemaphore/qsemaphore.pro deleted file mode 100644 index 5a0f0337e6..0000000000 --- a/tests/auto/corelib/thread/qsemaphore/qsemaphore.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qsemaphore -QT = core testlib -SOURCES = tst_qsemaphore.cpp diff --git a/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp b/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp index b7134d0454..3bb1e1960c 100644 --- a/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp +++ b/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp @@ -1,37 +1,16 @@ -/**************************************************************************** -** -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <qcoreapplication.h> #include <qthread.h> #include <qsemaphore.h> +#include <chrono> + +using namespace std::chrono_literals; + class tst_QSemaphore : public QObject { Q_OBJECT @@ -47,9 +26,10 @@ private slots: void tryAcquireWithTimeoutForever(); void producerConsumer(); void raii(); + void stdCompat(); }; -static QSemaphore *semaphore = 0; +static QSemaphore *semaphore = nullptr; class ThreadOne : public QThread { @@ -57,7 +37,7 @@ public: ThreadOne() {} protected: - void run() + void run() override { int i = 0; while ( i < 100 ) { @@ -76,7 +56,7 @@ public: ThreadN(int n) :N(n) { } protected: - void run() + void run() override { int i = 0; while ( i < 100 ) { @@ -106,7 +86,7 @@ void tst_QSemaphore::acquire() QVERIFY(t2.wait(4000)); delete semaphore; - semaphore = 0; + semaphore = nullptr; } // old incrementN() test @@ -126,7 +106,7 @@ void tst_QSemaphore::acquire() QVERIFY(t2.wait(4000)); delete semaphore; - semaphore = 0; + semaphore = nullptr; } QSemaphore semaphore; @@ -166,7 +146,7 @@ void tst_QSemaphore::multiRelease() }; QSemaphore sem; - QVector<Thread *> threads; + QList<Thread *> threads; threads.resize(4); for (Thread *&t : threads) @@ -177,7 +157,7 @@ void tst_QSemaphore::multiRelease() // wait for all threads to reach the sem.acquire() and then // release them all QTest::qSleep(1); - sem.release(threads.size()); + sem.release(int(threads.size())); for (Thread *&t : threads) t->wait(); @@ -200,7 +180,7 @@ void tst_QSemaphore::multiAcquireRelease() }; QSemaphore sem; - QVector<Thread *> threads; + QList<Thread *> threads; threads.resize(4); for (Thread *&t : threads) @@ -323,69 +303,69 @@ void tst_QSemaphore::tryAcquireWithTimeout() QCOMPARE(semaphore.available(), 1); time.start(); QVERIFY(!semaphore.tryAcquire(2, timeout)); - FUZZYCOMPARE(time.elapsed(), timeout); + FUZZYCOMPARE(int(time.elapsed()), timeout); QCOMPARE(semaphore.available(), 1); semaphore.release(); QCOMPARE(semaphore.available(), 2); time.start(); QVERIFY(!semaphore.tryAcquire(3, timeout)); - FUZZYCOMPARE(time.elapsed(), timeout); + FUZZYCOMPARE(int(time.elapsed()), timeout); QCOMPARE(semaphore.available(), 2); semaphore.release(10); QCOMPARE(semaphore.available(), 12); time.start(); QVERIFY(!semaphore.tryAcquire(100, timeout)); - FUZZYCOMPARE(time.elapsed(), timeout); + FUZZYCOMPARE(int(time.elapsed()), timeout); QCOMPARE(semaphore.available(), 12); semaphore.release(10); QCOMPARE(semaphore.available(), 22); time.start(); QVERIFY(!semaphore.tryAcquire(100, timeout)); - FUZZYCOMPARE(time.elapsed(), timeout); + FUZZYCOMPARE(int(time.elapsed()), timeout); QCOMPARE(semaphore.available(), 22); time.start(); QVERIFY(semaphore.tryAcquire(1, timeout)); - FUZZYCOMPARE(time.elapsed(), 0); + FUZZYCOMPARE(int(time.elapsed()), 0); QCOMPARE(semaphore.available(), 21); time.start(); QVERIFY(semaphore.tryAcquire(1, timeout)); - FUZZYCOMPARE(time.elapsed(), 0); + FUZZYCOMPARE(int(time.elapsed()), 0); QCOMPARE(semaphore.available(), 20); time.start(); QVERIFY(semaphore.tryAcquire(10, timeout)); - FUZZYCOMPARE(time.elapsed(), 0); + FUZZYCOMPARE(int(time.elapsed()), 0); QCOMPARE(semaphore.available(), 10); time.start(); QVERIFY(semaphore.tryAcquire(10, timeout)); - FUZZYCOMPARE(time.elapsed(), 0); + FUZZYCOMPARE(int(time.elapsed()), 0); QCOMPARE(semaphore.available(), 0); // should not be able to acquire more time.start(); QVERIFY(!semaphore.tryAcquire(1, timeout)); - FUZZYCOMPARE(time.elapsed(), timeout); + FUZZYCOMPARE(int(time.elapsed()), timeout); QCOMPARE(semaphore.available(), 0); time.start(); QVERIFY(!semaphore.tryAcquire(1, timeout)); - FUZZYCOMPARE(time.elapsed(), timeout); + FUZZYCOMPARE(int(time.elapsed()), timeout); QCOMPARE(semaphore.available(), 0); time.start(); QVERIFY(!semaphore.tryAcquire(10, timeout)); - FUZZYCOMPARE(time.elapsed(), timeout); + FUZZYCOMPARE(int(time.elapsed()), timeout); QCOMPARE(semaphore.available(), 0); time.start(); QVERIFY(!semaphore.tryAcquire(10, timeout)); - FUZZYCOMPARE(time.elapsed(), timeout); + FUZZYCOMPARE(int(time.elapsed()), timeout); QCOMPARE(semaphore.available(), 0); #undef FUZZYCOMPARE @@ -400,7 +380,7 @@ void tst_QSemaphore::tryAcquireWithTimeoutStarvation() QSemaphore *semaphore; int amountToConsume, timeout; - void run() + void run() override { startup.release(); forever { @@ -481,7 +461,7 @@ const char alphabet[] = "ACGTH"; const int AlphabetSize = sizeof(alphabet) - 1; const int BufferSize = 4096; // GCD of BufferSize and alphabet size must be 1 -char buffer[BufferSize]; +static char buffer[BufferSize]; const int ProducerChunkSize = 3; const int ConsumerChunkSize = 7; @@ -491,16 +471,16 @@ const int Multiplier = 10; // ProducerChunkSize, ConsumerChunkSize, and BufferSize const int DataSize = ProducerChunkSize * ConsumerChunkSize * BufferSize * Multiplier; -QSemaphore freeSpace(BufferSize); -QSemaphore usedSpace; +static QSemaphore freeSpace(BufferSize); +static QSemaphore usedSpace; class Producer : public QThread { public: - void run(); + void run() override; }; -static const int Timeout = 60 * 1000; // 1min +static const auto Timeout = 1min; void Producer::run() { @@ -521,7 +501,7 @@ void Producer::run() class Consumer : public QThread { public: - void run(); + void run() override; }; void Consumer::run() @@ -599,5 +579,34 @@ void tst_QSemaphore::raii() QCOMPARE(sem.available(), 49); } +void tst_QSemaphore::stdCompat() +{ + QSemaphore sem(1); + + auto now = [] { return std::chrono::steady_clock::now(); }; + + QVERIFY(sem.try_acquire()); + QCOMPARE(sem.available(), 0); + QVERIFY(!sem.try_acquire_for(10ms)); + QCOMPARE(sem.available(), 0); + QVERIFY(!sem.try_acquire_until(now() + 10ms)); + QCOMPARE(sem.available(), 0); + + sem.release(2); + + QVERIFY(sem.try_acquire()); + QVERIFY(sem.try_acquire_for(5ms)); + QCOMPARE(sem.available(), 0); + QVERIFY(!sem.try_acquire_until(now() + 5ms)); + QCOMPARE(sem.available(), 0); + + sem.release(3); + + QVERIFY(sem.try_acquire()); + QVERIFY(sem.try_acquire_for(5s)); + QVERIFY(sem.try_acquire_until(now() + 5s)); + QCOMPARE(sem.available(), 0); +} + QTEST_MAIN(tst_QSemaphore) #include "tst_qsemaphore.moc" diff --git a/tests/auto/corelib/thread/qthread/BLACKLIST b/tests/auto/corelib/thread/qthread/BLACKLIST index d75249454f..08e9912455 100644 --- a/tests/auto/corelib/thread/qthread/BLACKLIST +++ b/tests/auto/corelib/thread/qthread/BLACKLIST @@ -1,2 +1,3 @@ [wait3_slowDestructor] -windows +windows-10 + diff --git a/tests/auto/corelib/thread/qthread/CMakeLists.txt b/tests/auto/corelib/thread/qthread/CMakeLists.txt new file mode 100644 index 0000000000..abcea1ef9c --- /dev/null +++ b/tests/auto/corelib/thread/qthread/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qthread Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qthread LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qthread + SOURCES + tst_qthread.cpp + LIBRARIES + Qt::TestPrivate +) + +## Scopes: +##################################################################### diff --git a/tests/auto/corelib/thread/qthread/qthread.pro b/tests/auto/corelib/thread/qthread/qthread.pro deleted file mode 100644 index 37552f1fca..0000000000 --- a/tests/auto/corelib/thread/qthread/qthread.pro +++ /dev/null @@ -1,9 +0,0 @@ -CONFIG += testcase -TARGET = tst_qthread -QT = core testlib -SOURCES = tst_qthread.cpp -qtConfig(c++14):CONFIG += c++14 -qtConfig(c++1z):CONFIG += c++1z - -INCLUDEPATH += ../../../../shared/ -HEADERS += ../../../../shared/emulationdetector.h diff --git a/tests/auto/corelib/thread/qthread/tst_qthread.cpp b/tests/auto/corelib/thread/qthread/tst_qthread.cpp index d73dcc1b6d..18c8d5fbd5 100644 --- a/tests/auto/corelib/thread/qthread/tst_qthread.cpp +++ b/tests/auto/corelib/thread/qthread/tst_qthread.cpp @@ -1,47 +1,32 @@ -/**************************************************************************** -** -** 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 <qdatetime.h> +#include <qelapsedtimer.h> #include <qmutex.h> #include <qthread.h> #include <qtimer.h> #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,10 +95,23 @@ 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 }; +template <class Int> +static QString msgElapsed(Int elapsed) +{ + return QString::fromLatin1("elapsed: %1").arg(elapsed); +} + class SignalRecorder : public QObject { Q_OBJECT @@ -121,7 +123,7 @@ public: { } bool wasActivated() - { return activationCount.load() > 0; } + { return activationCount.loadRelaxed() > 0; } public slots: void slot(); @@ -135,11 +137,13 @@ class Current_Thread : public QThread public: Qt::HANDLE id; QThread *thread; + bool runCalledInCurrentThread = false; - void run() + void run() override { id = QThread::currentThreadId(); thread = QThread::currentThread(); + runCalledInCurrentThread = thread->isCurrentThread(); } }; @@ -149,7 +153,7 @@ public: QMutex mutex; QWaitCondition cond; - void run() + void run() override { QMutexLocker locker(&mutex); cond.wakeOne(); @@ -174,7 +178,7 @@ public: int code; int result; - void run() + void run() override { Simple_Thread::run(); if (object) { @@ -189,7 +193,7 @@ public: class Terminate_Thread : public Simple_Thread { public: - void run() + void run() override { setTerminationEnabled(false); { @@ -218,7 +222,7 @@ public: Quit_Object *object; int result; - void run() + void run() override { Simple_Thread::run(); if (object) { @@ -235,29 +239,31 @@ public: enum SleepType { Second, Millisecond, Microsecond }; SleepType sleepType; - int interval; + ulong interval; - int elapsed; // result, in *MILLISECONDS* + qint64 elapsed; // result, in *MILLISECONDS* - void run() + void run() override { QMutexLocker locker(&mutex); elapsed = 0; - QTime time; - time.start(); + 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; } - elapsed = time.elapsed(); + sleep(dur); + elapsed = timer.elapsed(); cond.wakeOne(); } @@ -266,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() @@ -423,7 +434,7 @@ void tst_QThread::exit() delete thread.object; Exit_Thread thread2; - thread2.object = 0; + thread2.object = nullptr; thread2.code = 53; thread2.result = 0; QMutexLocker locker2(&thread2.mutex); @@ -463,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_WINRT) || defined(Q_OS_ANDROID) - QSKIP("Thread termination is not supported on WinRT or Android."); +#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); @@ -499,7 +564,7 @@ void tst_QThread::quit() delete thread.object; Quit_Thread thread2; - thread2.object = 0; + thread2.object = nullptr; thread2.result = -1; QMutexLocker locker2(&thread2.mutex); thread2.start(); @@ -531,9 +596,13 @@ void tst_QThread::finished() void tst_QThread::terminated() { -#if defined(Q_OS_WINRT) || defined(Q_OS_ANDROID) - QSKIP("Thread termination is not supported on WinRT or Android."); +#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); @@ -557,7 +626,7 @@ void tst_QThread::exec() MultipleExecThread() : res1(-2), res2(-2) { } - void run() + void run() override { { Exit_Object o; @@ -591,7 +660,7 @@ void tst_QThread::sleep() thread.interval = 2; thread.start(); QVERIFY(thread.wait(five_minutes)); - QVERIFY(thread.elapsed >= 2000); + QVERIFY2(thread.elapsed >= 2000, qPrintable(msgElapsed(thread.elapsed))); } void tst_QThread::msleep() @@ -601,11 +670,10 @@ void tst_QThread::msleep() thread.interval = 120; thread.start(); QVERIFY(thread.wait(five_minutes)); -#if defined (Q_OS_WIN) - // Since the resolution of QTime is so coarse... - QVERIFY(thread.elapsed >= 100); +#if defined (Q_OS_WIN) // May no longer be needed + QVERIFY2(thread.elapsed >= 100, qPrintable(msgElapsed(thread.elapsed))); #else - QVERIFY(thread.elapsed >= 120); + QVERIFY2(thread.elapsed >= 120, qPrintable(msgElapsed(thread.elapsed))); #endif } @@ -616,11 +684,10 @@ void tst_QThread::usleep() thread.interval = 120000; thread.start(); QVERIFY(thread.wait(five_minutes)); -#if defined (Q_OS_WIN) - // Since the resolution of QTime is so coarse... - QVERIFY(thread.elapsed >= 100); +#if defined (Q_OS_WIN) // May no longer be needed + QVERIFY2(thread.elapsed >= 100, qPrintable(msgElapsed(thread.elapsed))); #else - QVERIFY(thread.elapsed >= 120); + QVERIFY2(thread.elapsed >= 120, qPrintable(msgElapsed(thread.elapsed))); #endif } @@ -642,9 +709,9 @@ void noop(void*) { } class NativeThreadWrapper { public: - NativeThreadWrapper() : qthread(0), waitForStop(false) {} - void start(FunctionPointer functionPointer = noop, void *data = 0); - void startAndWait(FunctionPointer functionPointer = noop, void *data = 0); + NativeThreadWrapper() : qthread(nullptr), waitForStop(false) {} + void start(FunctionPointer functionPointer = noop, void *data = nullptr); + void startAndWait(FunctionPointer functionPointer = noop, void *data = nullptr); void join(); void setWaitForStop() { waitForStop = true; } void stop(); @@ -668,10 +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); -#elif defined(Q_OS_WINRT) - nativeThreadHandle = CreateThread(NULL, 0 , (LPTHREAD_START_ROUTINE)NativeThreadWrapper::runWin , this, 0, NULL); + 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); @@ -688,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); @@ -718,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) @@ -734,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() @@ -767,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() @@ -881,7 +947,7 @@ void tst_QThread::adoptMultipleThreads() #else const int numThreads = 5; #endif - QVector<NativeThreadWrapper*> nativeThreads; + QList<NativeThreadWrapper*> nativeThreads; SignalRecorder recorder; @@ -902,7 +968,7 @@ void tst_QThread::adoptMultipleThreads() QTestEventLoop::instance().enterLoop(5); QVERIFY(!QTestEventLoop::instance().timeout()); - QCOMPARE(recorder.activationCount.load(), numThreads); + QCOMPARE(recorder.activationCount.loadRelaxed(), numThreads); } void tst_QThread::adoptMultipleThreadsOverlap() @@ -913,7 +979,7 @@ void tst_QThread::adoptMultipleThreadsOverlap() #else const int numThreads = 5; #endif - QVector<NativeThreadWrapper*> nativeThreads; + QList<NativeThreadWrapper*> nativeThreads; SignalRecorder recorder; @@ -939,18 +1005,32 @@ void tst_QThread::adoptMultipleThreadsOverlap() QTestEventLoop::instance().enterLoop(5); QVERIFY(!QTestEventLoop::instance().timeout()); - QCOMPARE(recorder.activationCount.load(), numThreads); + 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."); - QTime t; - t.start(); - while (t.elapsed() < one_minute) { + QElapsedTimer timer; + timer.start(); + while (timer.elapsed() < one_minute) { Current_Thread t; t.start(); t.wait(one_minute); @@ -1000,7 +1080,8 @@ void tst_QThread::exitAndExec() QSemaphore sem1; QSemaphore sem2; volatile int value; - void run() { + void run() override + { sem1.acquire(); value = exec(); //First entrence sem2.release(); @@ -1051,7 +1132,7 @@ public: QWaitCondition cond1; QWaitCondition cond2; - void run() + void run() override { QMutexLocker locker(&mutex); cond1.wait(&mutex); @@ -1067,17 +1148,19 @@ void tst_QThread::wait2() timer.start(); QVERIFY(!thread.wait(Waiting_Thread::WaitTime)); qint64 elapsed = timer.elapsed(); // On Windows, we sometimes get (WaitTime - 9). - QVERIFY2(elapsed >= Waiting_Thread::WaitTime - 10, qPrintable(QString::fromLatin1("elapsed: %1").arg(elapsed))); + QVERIFY2(elapsed >= Waiting_Thread::WaitTime - 10, + qPrintable(msgElapsed(elapsed))); timer.start(); thread.cond1.wakeOne(); QVERIFY(thread.wait(/*Waiting_Thread::WaitTime * 1.4*/)); elapsed = timer.elapsed(); - QVERIFY2(elapsed - Waiting_Thread::WaitTime >= -1, qPrintable(QString::fromLatin1("elapsed: %1").arg(elapsed))); + QVERIFY2(elapsed - Waiting_Thread::WaitTime >= -1, + qPrintable(msgElapsed(elapsed))); } - -class SlowSlotObject : public QObject { +class SlowSlotObject : public QObject +{ Q_OBJECT public: QMutex mutex; @@ -1093,28 +1176,29 @@ 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)); } void tst_QThread::destroyFinishRace() { - class Thread : public QThread { void run() {} }; + class Thread : public QThread { void run() override {} }; for (int i = 0; i < 15; i++) { Thread *thr = new Thread; connect(thr, SIGNAL(finished()), thr, SLOT(deleteLater())); @@ -1134,9 +1218,10 @@ void tst_QThread::startFinishRace() class Thread : public QThread { public: Thread() : i (50) {} - void run() { + void run() override + { i--; - if (!i) disconnect(this, SIGNAL(finished()), 0, 0); + if (!i) disconnect(this, SIGNAL(finished()), nullptr, nullptr); } int i; }; @@ -1157,7 +1242,7 @@ void tst_QThread::startFinishRace() void tst_QThread::startAndQuitCustomEventLoop() { struct Thread : QThread { - void run() { QEventLoop().exec(); } + void run() override { QEventLoop().exec(); } }; for (int i = 0; i < 5; i++) { @@ -1202,32 +1287,33 @@ 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) { - visited.store(true); + bool processEvents(QEventLoop::ProcessEventsFlags) override { + visited.storeRelaxed(true); emit awake(); QCoreApplication::sendPostedEvents(); return false; } - bool hasPendingEvents() { - return qGlobalPostedEventsCount(); + void registerSocketNotifier(QSocketNotifier *) override {} + void unregisterSocketNotifier(QSocketNotifier *) override {} + 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; } - void registerSocketNotifier(QSocketNotifier *) {} - void unregisterSocketNotifier(QSocketNotifier *) {} - void registerTimer(int, int, Qt::TimerType, QObject *) {} - bool unregisterTimer(int ) { return false; } - bool unregisterTimers(QObject *) { return false; } - QList<TimerInfo> registeredTimers(QObject *) const { return QList<TimerInfo>(); } - int remainingTime(int) { return 0; } - void wakeUp() {} - void interrupt() {} - void flush() {} + bool unregisterTimers(QObject *) override { return false; } + QList<TimerInfoV2> timersForObject(QObject *) const override { return {}; } + Duration remainingTime(Qt::TimerId) const override { return 0s; } + void wakeUp() override {} + void interrupt() override {} #ifdef Q_OS_WIN bool registerEventNotifier(QWinEventNotifier *) { return false; } @@ -1235,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); @@ -1262,32 +1370,46 @@ 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.load()); + 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 { Q_OBJECT public: - Job(QThread *thread, int deleteDelay, bool *flag, QObject *parent = 0) + Job(QThread *thread, int deleteDelay, bool *flag, QObject *parent = nullptr) : QObject(parent), quitLocker(thread), exitThreadCalled(*flag) { exitThreadCalled = false; @@ -1330,13 +1452,12 @@ void tst_QThread::quitLock() QCOMPARE(job->thread(), &thread); loop.exec(); QVERIFY(exitThreadCalled); + + delete job; } 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)); @@ -1443,7 +1564,6 @@ void tst_QThread::create() QCOMPARE(i, 42); } -#if defined(__cpp_init_captures) && __cpp_init_captures >= 201304 { int i = 0; MoveOnlyValue mo(123); @@ -1455,9 +1575,7 @@ void tst_QThread::create() QVERIFY(thread->wait()); QCOMPARE(i, 123); } -#endif // __cpp_init_captures -#ifdef QTHREAD_HAS_VARIADIC_CREATE { int i = 0; const auto &function = [&i](MoveOnlyValue &&mo) { i = mo.v; }; @@ -1480,10 +1598,8 @@ void tst_QThread::create() QVERIFY(thread->wait()); QCOMPARE(i, -1); } -#endif // QTHREAD_HAS_VARIADIC_CREATE } -#ifdef QTHREAD_HAS_VARIADIC_CREATE { // simple parameter passing int i = 0; @@ -1577,12 +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 // QTHREAD_HAS_VARIADIC_CREATE -#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 @@ -1623,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" diff --git a/tests/auto/corelib/thread/qthreadonce/CMakeLists.txt b/tests/auto/corelib/thread/qthreadonce/CMakeLists.txt new file mode 100644 index 0000000000..2c92ca002e --- /dev/null +++ b/tests/auto/corelib/thread/qthreadonce/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qthreadonce Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qthreadonce LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qthreadonce + SOURCES + qthreadonce.cpp + tst_qthreadonce.cpp + LIBRARIES + Qt::TestPrivate +) diff --git a/tests/auto/corelib/thread/qthreadonce/qthreadonce.cpp b/tests/auto/corelib/thread/qthreadonce/qthreadonce.cpp index d27884197a..b32f455241 100644 --- a/tests/auto/corelib/thread/qthreadonce/qthreadonce.cpp +++ b/tests/auto/corelib/thread/qthreadonce/qthreadonce.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qplatformdefs.h" @@ -32,7 +7,7 @@ #include "qmutex.h" -Q_GLOBAL_STATIC_WITH_ARGS(QMutex, onceInitializationMutex, (QMutex::Recursive)) +Q_GLOBAL_STATIC(QRecursiveMutex, onceInitializationMutex) enum QOnceExtra { MustRunCode = 0x01, diff --git a/tests/auto/corelib/thread/qthreadonce/qthreadonce.h b/tests/auto/corelib/thread/qthreadonce/qthreadonce.h index e5918b8fa5..1f804433e4 100644 --- a/tests/auto/corelib/thread/qthreadonce/qthreadonce.h +++ b/tests/auto/corelib/thread/qthreadonce/qthreadonce.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef QTHREADONCE_H diff --git a/tests/auto/corelib/thread/qthreadonce/qthreadonce.pro b/tests/auto/corelib/thread/qthreadonce/qthreadonce.pro deleted file mode 100644 index e8b95a06a7..0000000000 --- a/tests/auto/corelib/thread/qthreadonce/qthreadonce.pro +++ /dev/null @@ -1,12 +0,0 @@ -CONFIG += testcase -TARGET = tst_qthreadonce -QT = core testlib -SOURCES = tst_qthreadonce.cpp - -# Don't use gcc's threadsafe statics -# Note: some versions of gcc generate invalid code with this option... -# Some versions of gcc don't even have it, so disable it -#*-g++*:QMAKE_CXXFLAGS += -fno-threadsafe-statics - -# Temporary: -SOURCES += qthreadonce.cpp diff --git a/tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp b/tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp index a9af182ed8..37e1f744f3 100644 --- a/tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp +++ b/tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp @@ -1,33 +1,9 @@ -/**************************************************************************** -** -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + + +#include <QTest> +#include <QSemaphore> #include <qcoreapplication.h> #include <qmutex.h> @@ -35,11 +11,14 @@ #include <qwaitcondition.h> #include "qthreadonce.h" +#include <QtTest/private/qemulationdetector_p.h> + class tst_QThreadOnce : public QObject { Q_OBJECT private slots: + void initTestCase(); void sameThread(); void sameThread_data(); void multipleThreads(); @@ -51,12 +30,18 @@ private slots: #endif }; +void tst_QThreadOnce::initTestCase() +{ + if (QTestPrivate::isRunningArmOnX86()) + QSKIP("Flaky on QEMU, QTBUG-94737"); +} + class SingletonObject: public QObject { Q_OBJECT public: static int runCount; - SingletonObject() { val.store(42); ++runCount; } + SingletonObject() { val.storeRelaxed(42); ++runCount; } ~SingletonObject() { } QBasicAtomicInt val; @@ -77,7 +62,7 @@ public: ~IncrementThread() { wait(); } protected: - void run() + void run() override { sem2.release(); sem1.acquire(); // synchronize @@ -112,7 +97,7 @@ void tst_QThreadOnce::sameThread() QCOMPARE(controlVariable, 1); static QSingleton<SingletonObject> s; - QTEST((int)s->val.load(), "expectedValue"); + QTEST(int(s->val.loadRelaxed()), "expectedValue"); s->val.ref(); QCOMPARE(SingletonObject::runCount, 1); @@ -134,7 +119,7 @@ void tst_QThreadOnce::multipleThreads() QCOMPARE(controlVariable, 0); // nothing must have set them yet SingletonObject::runCount = 0; - IncrementThread::runCount.store(0); + IncrementThread::runCount.storeRelaxed(0); // wait for all of them to be ready sem2.acquire(NumberOfThreads); @@ -145,7 +130,7 @@ void tst_QThreadOnce::multipleThreads() delete parent; QCOMPARE(controlVariable, 1); - QCOMPARE((int)IncrementThread::runCount.load(), NumberOfThreads); + QCOMPARE(int(IncrementThread::runCount.loadRelaxed()), NumberOfThreads); QCOMPARE(SingletonObject::runCount, 1); } diff --git a/tests/auto/corelib/thread/qthreadpool/BLACKLIST b/tests/auto/corelib/thread/qthreadpool/BLACKLIST deleted file mode 100644 index fc49731687..0000000000 --- a/tests/auto/corelib/thread/qthreadpool/BLACKLIST +++ /dev/null @@ -1,3 +0,0 @@ -[expiryTimeoutRace] -osx -linux diff --git a/tests/auto/corelib/thread/qthreadpool/CMakeLists.txt b/tests/auto/corelib/thread/qthreadpool/CMakeLists.txt new file mode 100644 index 0000000000..fee9c541db --- /dev/null +++ b/tests/auto/corelib/thread/qthreadpool/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qthreadpool Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qthreadpool LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qthreadpool + SOURCES + tst_qthreadpool.cpp +) diff --git a/tests/auto/corelib/thread/qthreadpool/qthreadpool.pro b/tests/auto/corelib/thread/qthreadpool/qthreadpool.pro deleted file mode 100644 index eac4ef9365..0000000000 --- a/tests/auto/corelib/thread/qthreadpool/qthreadpool.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qthreadpool -QT = core testlib -SOURCES = tst_qthreadpool.cpp diff --git a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp index 838431cd5a..2006016d47 100644 --- a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp +++ b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp @@ -1,33 +1,12 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2016 Intel Corporation. -** 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> -#include <qdatetime.h> +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> +#include <QSemaphore> + +#include <qelapsedtimer.h> +#include <qrunnable.h> #include <qthreadpool.h> #include <qstring.h> #include <qmutex.h> @@ -36,6 +15,8 @@ #include <unistd.h> #endif +using namespace std::chrono_literals; + typedef void (*FunctionPointer)(); class FunctionPointerTask : public QRunnable @@ -43,7 +24,7 @@ class FunctionPointerTask : public QRunnable public: FunctionPointerTask(FunctionPointer function) :function(function) {} - void run() { function(); } + void run() override { function(); } private: FunctionPointer function; }; @@ -64,6 +45,8 @@ public: private slots: void runFunction(); + void runFunction2(); + void runFunction3(); void createThreadRunFunction(); void runMultiple(); void waitcomplete(); @@ -71,6 +54,7 @@ private slots: void singleton(); void destruction(); void threadRecycling(); + void threadPriority(); void expiryTimeout(); void expiryTimeoutRace(); #ifndef QT_NO_EXCEPTIONS @@ -84,6 +68,8 @@ private slots: void releaseThread_data(); void releaseThread(); void reserveAndStart(); + void reserveAndStart2(); + void releaseAndBlock(); void start(); void tryStart(); void tryStartPeakThreadCount(); @@ -92,7 +78,7 @@ private slots: void priorityStart(); void waitForDone(); void clear(); - void cancel(); + void clearWithAutoDelete(); void tryTake(); void waitForDoneTimeout(); void destroyingWaitsForTasksToFinish(); @@ -100,13 +86,15 @@ private slots: void stressTest(); void takeAllAndIncreaseMaxThreadCount(); void waitForDoneAfterTake(); + void threadReuse(); + void nullFunctions(); private: QMutex m_functionTestMutex; }; -QMutex *tst_QThreadPool::functionTestMutex = 0; +QMutex *tst_QThreadPool::functionTestMutex = nullptr; tst_QThreadPool::tst_QThreadPool() { @@ -115,10 +103,10 @@ tst_QThreadPool::tst_QThreadPool() tst_QThreadPool::~tst_QThreadPool() { - tst_QThreadPool::functionTestMutex = 0; + tst_QThreadPool::functionTestMutex = nullptr; } -int testFunctionCount; +static int testFunctionCount; void sleepTestFunction() { @@ -153,22 +141,64 @@ void noSleepTestFunctionMutex() tst_QThreadPool::functionTestMutex->unlock(); } +constexpr int DefaultWaitForDoneTimeout = 1 * 60 * 1000; // 1min +// Using qFatal instead of QVERIFY to force exit if threads are still running after timeout. +// Otherwise, QCoreApplication will still wait for the stale threads and never exit the test. +#define WAIT_FOR_DONE(manager) \ + if ((manager).waitForDone(DefaultWaitForDoneTimeout)) {} else \ + qFatal("waitForDone returned false. Aborting to stop background threads.") + +// uses explicit timeout in dtor's waitForDone() to avoid tests hanging overly long +class TestThreadPool : public QThreadPool +{ +public: + using QThreadPool::QThreadPool; + ~TestThreadPool() { WAIT_FOR_DONE(*this); } +}; + void tst_QThreadPool::runFunction() { { - QThreadPool manager; + TestThreadPool manager; testFunctionCount = 0; - manager.start(createTask(noSleepTestFunction)); + manager.start(noSleepTestFunction); } QCOMPARE(testFunctionCount, 1); } +void tst_QThreadPool::runFunction2() +{ + int localCount = 0; + { + TestThreadPool manager; + manager.start([&]() { ++localCount; }); + } + QCOMPARE(localCount, 1); +} + +struct DeleteCheck +{ + static bool s_deleted; + ~DeleteCheck() { s_deleted = true; } +}; +bool DeleteCheck::s_deleted = false; + +void tst_QThreadPool::runFunction3() +{ + std::unique_ptr<DeleteCheck> ptr(new DeleteCheck); + { + TestThreadPool manager; + manager.start([my_ptr = std::move(ptr)]() { }); + } + QVERIFY(DeleteCheck::s_deleted); +} + void tst_QThreadPool::createThreadRunFunction() { { - QThreadPool manager; + TestThreadPool manager; testFunctionCount = 0; - manager.start(createTask(noSleepTestFunction)); + manager.start(noSleepTestFunction); } QCOMPARE(testFunctionCount, 1); @@ -179,27 +209,27 @@ void tst_QThreadPool::runMultiple() const int runs = 10; { - QThreadPool manager; + TestThreadPool manager; testFunctionCount = 0; for (int i = 0; i < runs; ++i) { - manager.start(createTask(sleepTestFunctionMutex)); + manager.start(sleepTestFunctionMutex); } } QCOMPARE(testFunctionCount, runs); { - QThreadPool manager; + TestThreadPool manager; testFunctionCount = 0; for (int i = 0; i < runs; ++i) { - manager.start(createTask(noSleepTestFunctionMutex)); + manager.start(noSleepTestFunctionMutex); } } QCOMPARE(testFunctionCount, runs); { - QThreadPool manager; + TestThreadPool manager; for (int i = 0; i < 500; ++i) - manager.start(createTask(emptyFunct)); + manager.start(emptyFunct); } } @@ -208,28 +238,29 @@ void tst_QThreadPool::waitcomplete() testFunctionCount = 0; const int runs = 500; for (int i = 0; i < 500; ++i) { + // TestThreadPool pool; // no, we're checking ~QThreadPool()'s waitForDone() QThreadPool pool; - pool.start(createTask(noSleepTestFunction)); + pool.start(noSleepTestFunction); } QCOMPARE(testFunctionCount, runs); } -QAtomicInt ran; // bool +static QAtomicInt ran; // bool class TestTask : public QRunnable { public: - void run() + void run() override { - ran.store(true); + ran.storeRelaxed(true); } }; void tst_QThreadPool::runTask() { - QThreadPool manager; - ran.store(false); + TestThreadPool manager; + ran.storeRelaxed(false); manager.start(new TestTask()); - QTRY_VERIFY(ran.load()); + QTRY_VERIFY(ran.loadRelaxed()); } /* @@ -237,16 +268,16 @@ void tst_QThreadPool::runTask() */ void tst_QThreadPool::singleton() { - ran.store(false); + ran.storeRelaxed(false); QThreadPool::globalInstance()->start(new TestTask()); - QTRY_VERIFY(ran.load()); + QTRY_VERIFY(ran.loadRelaxed()); } -QAtomicInt *value = 0; +static QAtomicInt *value = nullptr; class IntAccessor : public QRunnable { public: - void run() + void run() override { for (int i = 0; i < 100; ++i) { value->ref(); @@ -267,16 +298,16 @@ void tst_QThreadPool::destruction() threadManager->start(new IntAccessor()); delete threadManager; delete value; - value = 0; + value = nullptr; } -QSemaphore threadRecyclingSemaphore; -QThread *recycledThread = 0; +static QSemaphore threadRecyclingSemaphore; +static QThread *recycledThread = nullptr; class ThreadRecorderTask : public QRunnable { public: - void run() + void run() override { recycledThread = QThread::currentThread(); threadRecyclingSemaphore.release(); @@ -288,7 +319,7 @@ public: */ void tst_QThreadPool::threadRecycling() { - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.start(new ThreadRecorderTask()); threadRecyclingSemaphore.acquire(); @@ -309,6 +340,25 @@ void tst_QThreadPool::threadRecycling() QCOMPARE(thread2, thread3); } +/* + Test that the thread priority from the thread created by the pool matches + the one configured on the pool. +*/ +void tst_QThreadPool::threadPriority() +{ + QThread::Priority priority = QThread::HighPriority; + TestThreadPool threadPool; + threadPool.setThreadPriority(priority); + + threadPool.start(new ThreadRecorderTask()); + threadRecyclingSemaphore.acquire(); + QThread *thread = recycledThread; + + QTest::qSleep(100); + + QCOMPARE(thread->priority(), priority); +} + class ExpiryTimeoutTask : public QRunnable { public: @@ -317,12 +367,12 @@ public: QSemaphore semaphore; ExpiryTimeoutTask() - : thread(0), runCount(0) + : thread(nullptr), runCount(0) { setAutoDelete(false); } - void run() + void run() override { thread = QThread::currentThread(); runCount.ref(); @@ -334,7 +384,7 @@ void tst_QThreadPool::expiryTimeout() { ExpiryTimeoutTask task; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setMaxThreadCount(1); int expiryTimeout = threadPool.expiryTimeout(); @@ -344,7 +394,7 @@ void tst_QThreadPool::expiryTimeout() // run the task threadPool.start(&task); QVERIFY(task.semaphore.tryAcquire(1, 10000)); - QCOMPARE(task.runCount.load(), 1); + QCOMPARE(task.runCount.loadRelaxed(), 1); QVERIFY(!task.thread->wait(100)); // thread should expire QThread *firstThread = task.thread; @@ -353,7 +403,7 @@ void tst_QThreadPool::expiryTimeout() // run task again, thread should be restarted threadPool.start(&task); QVERIFY(task.semaphore.tryAcquire(1, 10000)); - QCOMPARE(task.runCount.load(), 2); + QCOMPARE(task.runCount.loadRelaxed(), 2); QVERIFY(!task.thread->wait(100)); // thread should expire again QVERIFY(task.thread->wait(10000)); @@ -368,21 +418,18 @@ void tst_QThreadPool::expiryTimeout() void tst_QThreadPool::expiryTimeoutRace() // QTBUG-3786 { -#ifdef Q_OS_WIN - QSKIP("This test is unstable on Windows. See QTBUG-3786."); -#endif ExpiryTimeoutTask task; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setMaxThreadCount(1); threadPool.setExpiryTimeout(50); const int numTasks = 20; for (int i = 0; i < numTasks; ++i) { threadPool.start(&task); - QThread::msleep(50); // exactly the same as the expiry timeout + QThread::sleep(50ms); // exactly the same as the expiry timeout } QVERIFY(task.semaphore.tryAcquire(numTasks, 10000)); - QCOMPARE(task.runCount.load(), numTasks); + QCOMPARE(task.runCount.loadRelaxed(), numTasks); QVERIFY(threadPool.waitForDone(2000)); } @@ -390,7 +437,7 @@ void tst_QThreadPool::expiryTimeoutRace() // QTBUG-3786 class ExceptionTask : public QRunnable { public: - void run() + void run() override { throw new int; } @@ -400,7 +447,7 @@ void tst_QThreadPool::exceptions() { ExceptionTask task; { - QThreadPool threadPool; + TestThreadPool threadPool; // Uncomment this for a nice crash. // threadPool.start(&task); } @@ -429,6 +476,9 @@ void tst_QThreadPool::setMaxThreadCount() QFETCH(int, limit); QThreadPool *threadPool = QThreadPool::globalInstance(); int savedLimit = threadPool->maxThreadCount(); + auto restoreThreadCount = qScopeGuard([=]{ + threadPool->setMaxThreadCount(savedLimit); + }); // maxThreadCount() should always return the previous argument to // setMaxThreadCount(), regardless of input @@ -441,7 +491,7 @@ void tst_QThreadPool::setMaxThreadCount() // setting the limit on children should have no effect on the parent { - QThreadPool threadPool2(threadPool); + TestThreadPool threadPool2(threadPool); savedLimit = threadPool2.maxThreadCount(); // maxThreadCount() should always return the previous argument to @@ -464,65 +514,64 @@ void tst_QThreadPool::setMaxThreadCountStartsAndStopsThreads() WaitingTask() { setAutoDelete(false); } - void run() + void run() override { waitForStarted.release(); waitToFinish.acquire(); } }; - QThreadPool threadPool; - threadPool.setMaxThreadCount(1); + TestThreadPool threadPool; + threadPool.setMaxThreadCount(-1); // docs say we'll always start at least one - WaitingTask *task = new WaitingTask; - threadPool.start(task); - QVERIFY(task->waitForStarted.tryAcquire(1, 1000)); + WaitingTask task; + threadPool.start(&task); + QVERIFY(task.waitForStarted.tryAcquire(1, 1000)); // thread limit is 1, cannot start more tasks - threadPool.start(task); - QVERIFY(!task->waitForStarted.tryAcquire(1, 1000)); + threadPool.start(&task); + QVERIFY(!task.waitForStarted.tryAcquire(1, 1000)); // increasing the limit by 1 should start the task immediately threadPool.setMaxThreadCount(2); - QVERIFY(task->waitForStarted.tryAcquire(1, 1000)); + QVERIFY(task.waitForStarted.tryAcquire(1, 1000)); // ... but we still cannot start more tasks - threadPool.start(task); - QVERIFY(!task->waitForStarted.tryAcquire(1, 1000)); + threadPool.start(&task); + QVERIFY(!task.waitForStarted.tryAcquire(1, 1000)); // increasing the limit should be able to start more than one at a time - threadPool.start(task); + threadPool.start(&task); threadPool.setMaxThreadCount(4); - QVERIFY(task->waitForStarted.tryAcquire(2, 1000)); + QVERIFY(task.waitForStarted.tryAcquire(2, 1000)); // ... but we still cannot start more tasks - threadPool.start(task); - threadPool.start(task); - QVERIFY(!task->waitForStarted.tryAcquire(2, 1000)); + threadPool.start(&task); + threadPool.start(&task); + QVERIFY(!task.waitForStarted.tryAcquire(2, 1000)); // decreasing the thread limit should cause the active thread count to go down threadPool.setMaxThreadCount(2); QCOMPARE(threadPool.activeThreadCount(), 4); - task->waitToFinish.release(2); + task.waitToFinish.release(2); QTest::qWait(1000); QCOMPARE(threadPool.activeThreadCount(), 2); // ... and we still cannot start more tasks - threadPool.start(task); - threadPool.start(task); - QVERIFY(!task->waitForStarted.tryAcquire(2, 1000)); + threadPool.start(&task); + threadPool.start(&task); + QVERIFY(!task.waitForStarted.tryAcquire(2, 1000)); // start all remaining tasks - threadPool.start(task); - threadPool.start(task); - threadPool.start(task); - threadPool.start(task); + threadPool.start(&task); + threadPool.start(&task); + threadPool.start(&task); + threadPool.start(&task); threadPool.setMaxThreadCount(8); - QVERIFY(task->waitForStarted.tryAcquire(6, 1000)); + QVERIFY(task.waitForStarted.tryAcquire(6, 1000)); - task->waitToFinish.release(10); + task.waitToFinish.release(10); threadPool.waitForDone(); - delete task; } void tst_QThreadPool::reserveThread_data() @@ -534,7 +583,11 @@ void tst_QThreadPool::reserveThread() { QFETCH(int, limit); QThreadPool *threadpool = QThreadPool::globalInstance(); - int savedLimit = threadpool->maxThreadCount(); + const int savedLimit = threadpool->maxThreadCount(); + auto restoreThreadCount = qScopeGuard([=]{ + threadpool->setMaxThreadCount(savedLimit); + }); + threadpool->setMaxThreadCount(limit); // reserve up to the limit @@ -556,7 +609,7 @@ void tst_QThreadPool::reserveThread() // reserving threads in children should not effect the parent { - QThreadPool threadpool2(threadpool); + TestThreadPool threadpool2(threadpool); threadpool2.setMaxThreadCount(limit); // reserve up to the limit @@ -583,9 +636,6 @@ void tst_QThreadPool::reserveThread() while (threadpool2.activeThreadCount() > 0) threadpool2.releaseThread(); } - - // reset limit on global QThreadPool - threadpool->setMaxThreadCount(savedLimit); } void tst_QThreadPool::releaseThread_data() @@ -597,7 +647,10 @@ void tst_QThreadPool::releaseThread() { QFETCH(int, limit); QThreadPool *threadpool = QThreadPool::globalInstance(); - int savedLimit = threadpool->maxThreadCount(); + const int savedLimit = threadpool->maxThreadCount(); + auto restoreThreadCount = qScopeGuard([=]{ + threadpool->setMaxThreadCount(savedLimit); + }); threadpool->setMaxThreadCount(limit); // reserve up to the limit @@ -620,7 +673,7 @@ void tst_QThreadPool::releaseThread() // releasing threads in children should not effect the parent { - QThreadPool threadpool2(threadpool); + TestThreadPool threadpool2(threadpool); threadpool2.setMaxThreadCount(limit); // reserve up to the limit @@ -645,9 +698,6 @@ void tst_QThreadPool::releaseThread() QCOMPARE(threadpool2.activeThreadCount(), 0); QCOMPARE(threadpool->activeThreadCount(), 0); } - - // reset limit on global QThreadPool - threadpool->setMaxThreadCount(savedLimit); } void tst_QThreadPool::reserveAndStart() // QTBUG-21051 @@ -661,7 +711,7 @@ void tst_QThreadPool::reserveAndStart() // QTBUG-21051 WaitingTask() { setAutoDelete(false); } - void run() + void run() override { count.ref(); waitForStarted.release(); @@ -672,6 +722,10 @@ void tst_QThreadPool::reserveAndStart() // QTBUG-21051 // Set up QThreadPool *threadpool = QThreadPool::globalInstance(); int savedLimit = threadpool->maxThreadCount(); + auto restoreThreadCount = qScopeGuard([=]{ + threadpool->setMaxThreadCount(savedLimit); + }); + threadpool->setMaxThreadCount(1); QCOMPARE(threadpool->activeThreadCount(), 0); @@ -679,40 +733,137 @@ void tst_QThreadPool::reserveAndStart() // QTBUG-21051 threadpool->reserveThread(); QCOMPARE(threadpool->activeThreadCount(), 1); - // start a task, to get a running thread - WaitingTask *task = new WaitingTask; - threadpool->start(task); + // start a task, to get a running thread, works since one thread is always allowed + WaitingTask task; + threadpool->start(&task); QCOMPARE(threadpool->activeThreadCount(), 2); - task->waitForStarted.acquire(); - task->waitBeforeDone.release(); - QTRY_COMPARE(task->count.load(), 1); + // tryStart() will fail since activeThreadCount() >= maxThreadCount() and one thread is already running + QVERIFY(!threadpool->tryStart(&task)); + QTRY_COMPARE(threadpool->activeThreadCount(), 2); + task.waitForStarted.acquire(); + task.waitBeforeDone.release(); + QTRY_COMPARE(task.count.loadRelaxed(), 1); QTRY_COMPARE(threadpool->activeThreadCount(), 1); - // now the thread is waiting, but tryStart() will fail since activeThreadCount() >= maxThreadCount() - QVERIFY(!threadpool->tryStart(task)); + // start() will wake up the waiting thread. + threadpool->start(&task); + QTRY_COMPARE(threadpool->activeThreadCount(), 2); + QTRY_COMPARE(task.count.loadRelaxed(), 2); + WaitingTask task2; + // startOnReservedThread() will try to take the reserved task, but end up waiting instead + threadpool->startOnReservedThread(&task2); + QTRY_COMPARE(threadpool->activeThreadCount(), 1); + task.waitForStarted.acquire(); + task.waitBeforeDone.release(); QTRY_COMPARE(threadpool->activeThreadCount(), 1); + task2.waitForStarted.acquire(); + task2.waitBeforeDone.release(); - // start() will therefore do a failing tryStart(), followed by enqueueTask() - // which will actually wake up the waiting thread. - threadpool->start(task); - QTRY_COMPARE(threadpool->activeThreadCount(), 2); - task->waitForStarted.acquire(); - task->waitBeforeDone.release(); - QTRY_COMPARE(task->count.load(), 2); + QTRY_COMPARE(threadpool->activeThreadCount(), 0); +} + +void tst_QThreadPool::reserveAndStart2() +{ + class WaitingTask : public QRunnable + { + public: + QSemaphore waitBeforeDone; + + WaitingTask() { setAutoDelete(false); } + + void run() override + { + waitBeforeDone.acquire(); + } + }; + + // Set up + QThreadPool *threadpool = QThreadPool::globalInstance(); + int savedLimit = threadpool->maxThreadCount(); + auto restoreThreadCount = qScopeGuard([=]{ + threadpool->setMaxThreadCount(savedLimit); + }); + threadpool->setMaxThreadCount(2); + + // reserve + threadpool->reserveThread(); + + // start two task, to get a running thread and one queued + WaitingTask task1, task2, task3; + threadpool->start(&task1); + // one running thread, one reserved: + QCOMPARE(threadpool->activeThreadCount(), 2); + // task2 starts queued + threadpool->start(&task2); + QCOMPARE(threadpool->activeThreadCount(), 2); + // startOnReservedThread() will take the reserved thread however, bypassing the queue + threadpool->startOnReservedThread(&task3); + // two running threads, none reserved: + QCOMPARE(threadpool->activeThreadCount(), 2); + task3.waitBeforeDone.release(); + // task3 can finish even if all other tasks are blocking + // then task2 will use the previously reserved thread + task2.waitBeforeDone.release(); QTRY_COMPARE(threadpool->activeThreadCount(), 1); + task1.waitBeforeDone.release(); + QTRY_COMPARE(threadpool->activeThreadCount(), 0); +} + +void tst_QThreadPool::releaseAndBlock() +{ + class WaitingTask : public QRunnable + { + public: + QSemaphore waitBeforeDone; + WaitingTask() { setAutoDelete(false); } + + void run() override + { + waitBeforeDone.acquire(); + } + }; + + // Set up + QThreadPool *threadpool = QThreadPool::globalInstance(); + const int savedLimit = threadpool->maxThreadCount(); + auto restoreThreadCount = qScopeGuard([=]{ + threadpool->setMaxThreadCount(savedLimit); + }); + + threadpool->setMaxThreadCount(1); + QCOMPARE(threadpool->activeThreadCount(), 0); + + // start a task, to get a running thread, works since one thread is always allowed + WaitingTask task1, task2; + threadpool->start(&task1); + QCOMPARE(threadpool->activeThreadCount(), 1); + + // tryStart() will fail since activeThreadCount() >= maxThreadCount() and one thread is already running + QVERIFY(!threadpool->tryStart(&task2)); + QCOMPARE(threadpool->activeThreadCount(), 1); + + // Use release without reserve to account for the blocking thread. threadpool->releaseThread(); QTRY_COMPARE(threadpool->activeThreadCount(), 0); - delete task; + // Now we can start task2 + QVERIFY(threadpool->tryStart(&task2)); + QCOMPARE(threadpool->activeThreadCount(), 1); + task2.waitBeforeDone.release(); + QTRY_COMPARE(threadpool->activeThreadCount(), 0); - threadpool->setMaxThreadCount(savedLimit); + threadpool->reserveThread(); + QCOMPARE(threadpool->activeThreadCount(), 1); + task1.waitBeforeDone.release(); + QTRY_COMPARE(threadpool->activeThreadCount(), 0); } -QAtomicInt count; +static QAtomicInt count; class CountingRunnable : public QRunnable { - public: void run() +public: + void run() override { count.ref(); } @@ -721,14 +872,14 @@ class CountingRunnable : public QRunnable void tst_QThreadPool::start() { const int runs = 1000; - count.store(0); + count.storeRelaxed(0); { - QThreadPool threadPool; + TestThreadPool threadPool; for (int i = 0; i< runs; ++i) { threadPool.start(new CountingRunnable()); } } - QCOMPARE(count.load(), runs); + QCOMPARE(count.loadRelaxed(), runs); } void tst_QThreadPool::tryStart() @@ -740,29 +891,29 @@ void tst_QThreadPool::tryStart() WaitingTask() { setAutoDelete(false); } - void run() + void run() override { semaphore.acquire(); count.ref(); } }; - count.store(0); + count.storeRelaxed(0); WaitingTask task; - QThreadPool threadPool; + TestThreadPool threadPool; for (int i = 0; i < threadPool.maxThreadCount(); ++i) { threadPool.start(&task); } QVERIFY(!threadPool.tryStart(&task)); task.semaphore.release(threadPool.maxThreadCount()); - threadPool.waitForDone(); - QCOMPARE(count.load(), threadPool.maxThreadCount()); + WAIT_FOR_DONE(threadPool); + QCOMPARE(count.loadRelaxed(), threadPool.maxThreadCount()); } -QMutex mutex; -QAtomicInt activeThreads; -QAtomicInt peakActiveThreads; +static QMutex mutex; +static QAtomicInt activeThreads; +static QAtomicInt peakActiveThreads; void tst_QThreadPool::tryStartPeakThreadCount() { class CounterTask : public QRunnable @@ -770,12 +921,12 @@ void tst_QThreadPool::tryStartPeakThreadCount() public: CounterTask() { setAutoDelete(false); } - void run() + void run() override { { QMutexLocker lock(&mutex); activeThreads.ref(); - peakActiveThreads.store(qMax(peakActiveThreads.load(), activeThreads.load())); + peakActiveThreads.storeRelaxed(qMax(peakActiveThreads.loadRelaxed(), activeThreads.loadRelaxed())); } QTest::qWait(100); @@ -787,19 +938,19 @@ void tst_QThreadPool::tryStartPeakThreadCount() }; CounterTask task; - QThreadPool threadPool; + TestThreadPool 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); } - QCOMPARE(peakActiveThreads.load(), QThread::idealThreadCount()); + QCOMPARE(peakActiveThreads.loadRelaxed(), QThread::idealThreadCount()); for (int i = 0; i < 20; ++i) { if (threadPool.tryStart(&task) == false) QTest::qWait(10); } - QCOMPARE(peakActiveThreads.load(), QThread::idealThreadCount()); + QCOMPARE(peakActiveThreads.loadRelaxed(), QThread::idealThreadCount()); } void tst_QThreadPool::tryStartCount() @@ -809,14 +960,14 @@ void tst_QThreadPool::tryStartCount() public: SleeperTask() { setAutoDelete(false); } - void run() + void run() override { QTest::qWait(50); } }; SleeperTask task; - QThreadPool threadPool; + TestThreadPool threadPool; const int runs = 5; for (int i = 0; i < runs; ++i) { @@ -844,7 +995,7 @@ void tst_QThreadPool::priorityStart() public: QSemaphore &sem; Holder(QSemaphore &sem) : sem(sem) {} - void run() + void run() override { sem.acquire(); } @@ -854,9 +1005,9 @@ void tst_QThreadPool::priorityStart() public: QAtomicPointer<QRunnable> &ptr; Runner(QAtomicPointer<QRunnable> &ptr) : ptr(ptr) {} - void run() + void run() override { - ptr.testAndSetRelaxed(0, this); + ptr.testAndSetRelaxed(nullptr, this); } }; @@ -864,7 +1015,7 @@ void tst_QThreadPool::priorityStart() QSemaphore sem; QAtomicPointer<QRunnable> firstStarted; QRunnable *expected; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setMaxThreadCount(1); // start only one thread at a time // queue the holder first @@ -876,36 +1027,37 @@ void tst_QThreadPool::priorityStart() threadPool.start(expected = new Runner(firstStarted), 1); // priority 1 sem.release(); - QVERIFY(threadPool.waitForDone()); - QCOMPARE(firstStarted.load(), expected); + WAIT_FOR_DONE(threadPool); + QCOMPARE(firstStarted.loadRelaxed(), expected); } void tst_QThreadPool::waitForDone() { - QTime total, pass; + QElapsedTimer total, pass; total.start(); + pass.start(); - QThreadPool threadPool; + TestThreadPool threadPool; while (total.elapsed() < 10000) { int runs; - count.store(runs = 0); + count.storeRelaxed(runs = 0); pass.restart(); while (pass.elapsed() < 100) { threadPool.start(new CountingRunnable()); ++runs; } - threadPool.waitForDone(); - QCOMPARE(count.load(), runs); + WAIT_FOR_DONE(threadPool); + QCOMPARE(count.loadRelaxed(), runs); - count.store(runs = 0); + count.storeRelaxed(runs = 0); pass.restart(); while (pass.elapsed() < 100) { threadPool.start(new CountingRunnable()); threadPool.start(new CountingRunnable()); runs += 2; } - threadPool.waitForDone(); - QCOMPARE(count.load(), runs); + WAIT_FOR_DONE(threadPool); + QCOMPARE(count.loadRelaxed(), runs); } } @@ -918,7 +1070,7 @@ void tst_QThreadPool::waitForDoneTimeout() QMutex &mutex; explicit BlockedTask(QMutex &m) : mutex(m) {} - void run() + void run() override { mutex.lock(); mutex.unlock(); @@ -926,7 +1078,7 @@ void tst_QThreadPool::waitForDoneTimeout() } }; - QThreadPool threadPool; + TestThreadPool threadPool; mutex.lock(); threadPool.start(new BlockedTask(mutex)); @@ -943,96 +1095,48 @@ void tst_QThreadPool::clear() public: QSemaphore & sem; BlockingRunnable(QSemaphore & sem) : sem(sem){} - void run() + void run() override { sem.acquire(); count.ref(); } }; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setMaxThreadCount(10); int runs = 2 * threadPool.maxThreadCount(); - count.store(0); + count.storeRelaxed(0); for (int i = 0; i <= runs; i++) { threadPool.start(new BlockingRunnable(sem)); } threadPool.clear(); sem.release(threadPool.maxThreadCount()); - threadPool.waitForDone(); - QCOMPARE(count.load(), threadPool.maxThreadCount()); + WAIT_FOR_DONE(threadPool); + QCOMPARE(count.loadRelaxed(), threadPool.maxThreadCount()); } -void tst_QThreadPool::cancel() +void tst_QThreadPool::clearWithAutoDelete() { - QSemaphore sem(0); - QSemaphore startedThreads(0); - - class BlockingRunnable : public QRunnable + class MyRunnable : public QRunnable { public: - QSemaphore & sem; - QSemaphore &startedThreads; - QAtomicInt &dtorCounter; - QAtomicInt &runCounter; - int dummy; - - explicit BlockingRunnable(QSemaphore &s, QSemaphore &started, QAtomicInt &c, QAtomicInt &r) - : sem(s), startedThreads(started), dtorCounter(c), runCounter(r){} - - ~BlockingRunnable() - { - dtorCounter.fetchAndAddRelaxed(1); - } - - void run() - { - startedThreads.release(); - runCounter.fetchAndAddRelaxed(1); - sem.acquire(); - count.ref(); - } - }; - - enum { - MaxThreadCount = 3, - OverProvisioning = 2, - runs = MaxThreadCount * OverProvisioning + MyRunnable() {} + void run() override { QThread::sleep(30us); } }; - QThreadPool threadPool; - threadPool.setMaxThreadCount(MaxThreadCount); - BlockingRunnable *runnables[runs]; - - // ensure that the QThreadPool doesn't deadlock if any of the checks fail - // and cause an early return: - const QSemaphoreReleaser semReleaser(sem, runs); - - count.store(0); - QAtomicInt dtorCounter = 0; - QAtomicInt runCounter = 0; - for (int i = 0; i < runs; i++) { - runnables[i] = new BlockingRunnable(sem, startedThreads, dtorCounter, runCounter); - runnables[i]->setAutoDelete(i != 0 && i != (runs-1)); //one which will run and one which will not - threadPool.cancel(runnables[i]); //verify NOOP for jobs not in the queue - threadPool.start(runnables[i]); - } - // wait for all worker threads to have started up: - QVERIFY(startedThreads.tryAcquire(MaxThreadCount, 60*1000 /* 1min */)); - - for (int i = 0; i < runs; i++) { - threadPool.cancel(runnables[i]); + TestThreadPool threadPool; + threadPool.setMaxThreadCount(4); + const int loopCount = 20; + const int batchSize = 500; + // Should not crash see QTBUG-87092 + for (int i = 0; i < loopCount; i++) { + threadPool.clear(); + for (int j = 0; j < batchSize; j++) { + auto *runnable = new MyRunnable(); + runnable->setAutoDelete(true); + threadPool.start(runnable); + } } - runnables[0]->dummy = 0; //valgrind will catch this if cancel() is crazy enough to delete currently running jobs - runnables[runs-1]->dummy = 0; - QCOMPARE(dtorCounter.load(), runs - threadPool.maxThreadCount() - 1); - sem.release(threadPool.maxThreadCount()); - threadPool.waitForDone(); - QCOMPARE(runCounter.load(), threadPool.maxThreadCount()); - QCOMPARE(count.load(), threadPool.maxThreadCount()); - QCOMPARE(dtorCounter.load(), runs - 2); - delete runnables[0]; //if the pool deletes them then we'll get double-free crash - delete runnables[runs-1]; } void tst_QThreadPool::tryTake() @@ -1052,7 +1156,7 @@ void tst_QThreadPool::tryTake() explicit BlockingRunnable(QSemaphore &s, QSemaphore &started, QAtomicInt &c, QAtomicInt &r) : sem(s), startedThreads(started), dtorCounter(c), runCounter(r) {} - ~BlockingRunnable() + ~BlockingRunnable() override { dtorCounter.fetchAndAddRelaxed(1); } @@ -1072,7 +1176,7 @@ void tst_QThreadPool::tryTake() Runs = MaxThreadCount * OverProvisioning }; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setMaxThreadCount(MaxThreadCount); BlockingRunnable *runnables[Runs]; @@ -1080,7 +1184,7 @@ void tst_QThreadPool::tryTake() // and cause an early return: const QSemaphoreReleaser semReleaser(sem, Runs); - count.store(0); + count.storeRelaxed(0); QAtomicInt dtorCounter = 0; QAtomicInt runCounter = 0; for (int i = 0; i < Runs; i++) { @@ -1102,36 +1206,37 @@ void tst_QThreadPool::tryTake() } runnables[0]->dummy = 0; // valgrind will catch this if tryTake() is crazy enough to delete currently running jobs - QCOMPARE(dtorCounter.load(), int(Runs - MaxThreadCount)); + QCOMPARE(dtorCounter.loadRelaxed(), int(Runs - MaxThreadCount)); sem.release(MaxThreadCount); - threadPool.waitForDone(); - QCOMPARE(runCounter.load(), int(MaxThreadCount)); - QCOMPARE(count.load(), int(MaxThreadCount)); - QCOMPARE(dtorCounter.load(), int(Runs - 1)); + WAIT_FOR_DONE(threadPool); + QCOMPARE(runCounter.loadRelaxed(), int(MaxThreadCount)); + QCOMPARE(count.loadRelaxed(), int(MaxThreadCount)); + QCOMPARE(dtorCounter.loadRelaxed(), int(Runs - 1)); delete runnables[0]; // if the pool deletes them then we'll get double-free crash } void tst_QThreadPool::destroyingWaitsForTasksToFinish() { - QTime total, pass; + QElapsedTimer total, pass; total.start(); + pass.start(); while (total.elapsed() < 10000) { int runs; - count.store(runs = 0); + count.storeRelaxed(runs = 0); { - QThreadPool threadPool; + TestThreadPool threadPool; pass.restart(); while (pass.elapsed() < 100) { threadPool.start(new CountingRunnable()); ++runs; } } - QCOMPARE(count.load(), runs); + QCOMPARE(count.loadRelaxed(), runs); - count.store(runs = 0); + count.storeRelaxed(runs = 0); { - QThreadPool threadPool; + TestThreadPool threadPool; pass.restart(); while (pass.elapsed() < 100) { threadPool.start(new CountingRunnable()); @@ -1139,7 +1244,7 @@ void tst_QThreadPool::destroyingWaitsForTasksToFinish() runs += 2; } } - QCOMPARE(count.load(), runs); + QCOMPARE(count.loadRelaxed(), runs); } } @@ -1167,16 +1272,16 @@ void tst_QThreadPool::stackSize() } - void run() + void run() override { *stackSize = QThread::currentThread()->stackSize(); } }; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setStackSize(targetStackSize); threadPool.start(new StackSizeChecker(&threadStackSize)); - QVERIFY(threadPool.waitForDone(30000)); // 30s timeout + WAIT_FOR_DONE(threadPool); QCOMPARE(threadStackSize, targetStackSize); } @@ -1198,13 +1303,13 @@ void tst_QThreadPool::stressTest() semaphore.acquire(); } - void run() + void run() override { semaphore.release(); } }; - QTime total; + QElapsedTimer total; total.start(); while (total.elapsed() < 30000) { Task t; @@ -1224,7 +1329,8 @@ void tst_QThreadPool::takeAllAndIncreaseMaxThreadCount() { setAutoDelete(false); } - void run() { + void run() override + { m_mainBarrier->release(); m_threadBarrier->acquire(); } @@ -1236,24 +1342,24 @@ void tst_QThreadPool::takeAllAndIncreaseMaxThreadCount() { QSemaphore mainBarrier; QSemaphore taskBarrier; - QThreadPool threadPool; + TestThreadPool threadPool; threadPool.setMaxThreadCount(1); - Task *task1 = new Task(&mainBarrier, &taskBarrier); - Task *task2 = new Task(&mainBarrier, &taskBarrier); - Task *task3 = new Task(&mainBarrier, &taskBarrier); + Task task1(&mainBarrier, &taskBarrier); + Task task2(&mainBarrier, &taskBarrier); + Task task3(&mainBarrier, &taskBarrier); - threadPool.start(task1); - threadPool.start(task2); - threadPool.start(task3); + threadPool.start(&task1); + threadPool.start(&task2); + threadPool.start(&task3); mainBarrier.acquire(1); QCOMPARE(threadPool.activeThreadCount(), 1); - QVERIFY(!threadPool.tryTake(task1)); - QVERIFY(threadPool.tryTake(task2)); - QVERIFY(threadPool.tryTake(task3)); + QVERIFY(!threadPool.tryTake(&task1)); + QVERIFY(threadPool.tryTake(&task2)); + QVERIFY(threadPool.tryTake(&task3)); // A bad queue implementation can segfault here because two consecutive items in the queue // have been taken @@ -1267,13 +1373,9 @@ void tst_QThreadPool::takeAllAndIncreaseMaxThreadCount() { taskBarrier.release(1); - threadPool.waitForDone(); + WAIT_FOR_DONE(threadPool); QCOMPARE(threadPool.activeThreadCount(), 0); - - delete task1; - delete task2; - delete task3; } void tst_QThreadPool::waitForDoneAfterTake() @@ -1286,7 +1388,7 @@ void tst_QThreadPool::waitForDoneAfterTake() , m_threadBarrier(threadBarrier) {} - void run() + void run() override { m_mainBarrier->release(); m_threadBarrier->acquire(); @@ -1304,7 +1406,7 @@ void tst_QThreadPool::waitForDoneAfterTake() // Blocks the tasks from completing their run function QSemaphore threadBarrier; - QThreadPool manager; + TestThreadPool manager; manager.setMaxThreadCount(threadCount); // Fill all the threads with runnables that wait for the threadBarrier @@ -1319,9 +1421,9 @@ void tst_QThreadPool::waitForDoneAfterTake() // This sets the queue elements to nullptr in QThreadPool and we want to test that // the threads keep going through the queue after encountering a nullptr. for (int i = 0; i < threadCount; i++) { - QRunnable *runnable = createTask(emptyFunct); - manager.start(runnable); - QVERIFY(manager.tryTake(runnable)); + QScopedPointer<QRunnable> runnable(createTask(emptyFunct)); + manager.start(runnable.get()); + QVERIFY(manager.tryTake(runnable.get())); } // Add another runnable that will not be removed @@ -1335,12 +1437,55 @@ void tst_QThreadPool::waitForDoneAfterTake() // Release runnables that are waiting and expect all runnables to complete threadBarrier.release(threadCount); +} - // Using qFatal instead of QVERIFY to force exit if threads are still running after timeout. - // Otherwise, QCoreApplication will still wait for the stale threads and never exit the test. - if (!manager.waitForDone(5 * 60 * 1000)) - qFatal("waitForDone returned false. Aborting to stop background threads."); +/* + Try trigger reuse of expired threads and check that all tasks execute. + This is a regression test for QTBUG-72872. +*/ +void tst_QThreadPool::threadReuse() +{ + TestThreadPool 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(); + } +} + +void tst_QThreadPool::nullFunctions() +{ + const auto expectWarning = [] { + QTest::ignoreMessage(QtMsgType::QtWarningMsg, + "Trying to create null QRunnable. This may stop working."); + }; + // Note this is not necessarily testing intended behavior, only undocumented behavior. + // If this is changed it should be noted in Behavioral Changes. + FunctionPointer nullFunction = nullptr; + std::function<void()> nullStdFunction(nullptr); + { + TestThreadPool manager; + // should not crash: + expectWarning(); + manager.start(nullFunction); + expectWarning(); + manager.start(nullStdFunction); + // should fail (and not leak): + expectWarning(); + QVERIFY(!manager.tryStart(nullStdFunction)); + expectWarning(); + QVERIFY(!manager.tryStart(nullFunction)); + } } QTEST_MAIN(tst_QThreadPool); diff --git a/tests/auto/corelib/thread/qthreadstorage/CMakeLists.txt b/tests/auto/corelib/thread/qthreadstorage/CMakeLists.txt new file mode 100644 index 0000000000..14d8d7dd40 --- /dev/null +++ b/tests/auto/corelib/thread/qthreadstorage/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qthreadstorage Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qthreadstorage LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qthreadstorage + SOURCES + tst_qthreadstorage.cpp +) + +## Scopes: +##################################################################### + +if(NOT ANDROID) + add_subdirectory(crashonexit) + if(QT_FEATURE_process) + add_dependencies(tst_qthreadstorage crashOnExit_helper) + endif() +endif() diff --git a/tests/auto/corelib/thread/qthreadstorage/crashonexit/CMakeLists.txt b/tests/auto/corelib/thread/qthreadstorage/crashonexit/CMakeLists.txt new file mode 100644 index 0000000000..4d62e61a36 --- /dev/null +++ b/tests/auto/corelib/thread/qthreadstorage/crashonexit/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## crashonexit Binary: +##################################################################### + +qt_internal_add_executable(crashOnExit_helper + OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.." + INSTALL_DIRECTORY "${INSTALL_TESTSDIR}/tst_qthreadstorage/crashOnExit_helper" + SOURCES + crashOnExit.cpp +) diff --git a/tests/auto/corelib/thread/qthreadstorage/crashonexit/crashOnExit.cpp b/tests/auto/corelib/thread/qthreadstorage/crashonexit/crashOnExit.cpp index 5a42156b85..3b3a4d4813 100644 --- a/tests/auto/corelib/thread/qthreadstorage/crashonexit/crashOnExit.cpp +++ b/tests/auto/corelib/thread/qthreadstorage/crashonexit/crashOnExit.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtCore/QCoreApplication> #include <QtCore/QThreadStorage> diff --git a/tests/auto/corelib/thread/qthreadstorage/crashonexit/crashonexit.pro b/tests/auto/corelib/thread/qthreadstorage/crashonexit/crashonexit.pro deleted file mode 100644 index 57bd78bcee..0000000000 --- a/tests/auto/corelib/thread/qthreadstorage/crashonexit/crashonexit.pro +++ /dev/null @@ -1,16 +0,0 @@ -SOURCES += crashOnExit.cpp -debug_and_release { - CONFIG(debug, debug|release) { - TARGET = ../../debug/crashOnExit_helper - } else { - TARGET = ../../release/crashOnExit_helper - } -} else { - TARGET = ../crashOnExit_helper -} -QT = core -CONFIG += cmdline - -# This app is testdata for tst_qthreadstorage -target.path = $$[QT_INSTALL_TESTS]/tst_qthreadstorage/$$TARGET -INSTALLS += target diff --git a/tests/auto/corelib/thread/qthreadstorage/qthreadstorage.pro b/tests/auto/corelib/thread/qthreadstorage/qthreadstorage.pro deleted file mode 100644 index 432c564ba1..0000000000 --- a/tests/auto/corelib/thread/qthreadstorage/qthreadstorage.pro +++ /dev/null @@ -1,8 +0,0 @@ -TEMPLATE = subdirs - -!android:!winrt { - test.depends = crashonexit - SUBDIRS += crashonexit -} - -SUBDIRS += test diff --git a/tests/auto/corelib/thread/qthreadstorage/test/test.pro b/tests/auto/corelib/thread/qthreadstorage/test/test.pro deleted file mode 100644 index d2f21f48f0..0000000000 --- a/tests/auto/corelib/thread/qthreadstorage/test/test.pro +++ /dev/null @@ -1,16 +0,0 @@ -CONFIG += testcase -debug_and_release { - CONFIG(debug, debug|release) { - TARGET = ../../debug/tst_qthreadstorage - !android:!winrt: TEST_HELPER_INSTALLS = ../../debug/crashonexit_helper - } else { - TARGET = ../../release/tst_qthreadstorage - !android:!winrt: TEST_HELPER_INSTALLS = ../../release/crashonexit_helper - } -} else { - TARGET = ../tst_qthreadstorage - !android:!winrt: TEST_HELPER_INSTALLS = ../crashonexit_helper -} -CONFIG += console -QT = core testlib -SOURCES = ../tst_qthreadstorage.cpp diff --git a/tests/auto/corelib/thread/qthreadstorage/tst_qthreadstorage.cpp b/tests/auto/corelib/thread/qthreadstorage/tst_qthreadstorage.cpp index ef5d3452d5..ca382cf60c 100644 --- a/tests/auto/corelib/thread/qthreadstorage/tst_qthreadstorage.cpp +++ b/tests/auto/corelib/thread/qthreadstorage/tst_qthreadstorage.cpp @@ -1,32 +1,11 @@ -/**************************************************************************** -** -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> +#if QT_CONFIG(process) +#include <QProcess> +#endif +#include <QTestEventLoop> #include <qcoreapplication.h> #include <qmutex.h> @@ -76,7 +55,7 @@ void tst_QThreadStorage::hasLocalData() QVERIFY(!pointers.hasLocalData()); pointers.setLocalData(new Pointer); QVERIFY(pointers.hasLocalData()); - pointers.setLocalData(0); + pointers.setLocalData(nullptr); QVERIFY(!pointers.hasLocalData()); } @@ -88,8 +67,8 @@ void tst_QThreadStorage::localData() pointers.setLocalData(p); QVERIFY(pointers.hasLocalData()); QCOMPARE(pointers.localData(), p); - pointers.setLocalData(0); - QCOMPARE(pointers.localData(), (Pointer *)0); + pointers.setLocalData(nullptr); + QCOMPARE(pointers.localData(), nullptr); QVERIFY(!pointers.hasLocalData()); } @@ -102,8 +81,8 @@ void tst_QThreadStorage::localData_const() pointers.setLocalData(p); QVERIFY(pointers.hasLocalData()); QCOMPARE(const_pointers.localData(), p); - pointers.setLocalData(0); - QCOMPARE(const_pointers.localData(), (Pointer *)0); + pointers.setLocalData(nullptr); + QCOMPARE(const_pointers.localData(), nullptr); QVERIFY(!pointers.hasLocalData()); } @@ -113,7 +92,7 @@ void tst_QThreadStorage::setLocalData() QVERIFY(!pointers.hasLocalData()); pointers.setLocalData(new Pointer); QVERIFY(pointers.hasLocalData()); - pointers.setLocalData(0); + pointers.setLocalData(nullptr); QVERIFY(!pointers.hasLocalData()); } @@ -129,7 +108,7 @@ public: : pointers(p) { } - void run() + void run() override { pointers.setLocalData(new Pointer); @@ -157,7 +136,7 @@ void tst_QThreadStorage::autoDelete() QCOMPARE(Pointer::count, c); } -bool threadStorageOk; +static bool threadStorageOk; void testAdoptedThreadStorageWin(void *p) { QThreadStorage<Pointer *> *pointers = reinterpret_cast<QThreadStorage<Pointer *> *>(p); @@ -180,17 +159,10 @@ void testAdoptedThreadStorageWin(void *p) } QObject::connect(QThread::currentThread(), SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); } -#ifdef Q_OS_WINRT -unsigned __stdcall testAdoptedThreadStorageWinRT(void *p) -{ - testAdoptedThreadStorageWin(p); - return 0; -} -#endif void *testAdoptedThreadStorageUnix(void *pointers) { testAdoptedThreadStorageWin(pointers); - return 0; + return nullptr; } void tst_QThreadStorage::adoptedThreads() { @@ -201,14 +173,9 @@ void tst_QThreadStorage::adoptedThreads() { #ifdef Q_OS_UNIX pthread_t thread; - const int state = pthread_create(&thread, 0, testAdoptedThreadStorageUnix, &pointers); + const int state = pthread_create(&thread, nullptr, testAdoptedThreadStorageUnix, &pointers); QCOMPARE(state, 0); - pthread_join(thread, 0); -#elif defined Q_OS_WINRT - HANDLE thread; - thread = (HANDLE) _beginthreadex(NULL, 0, testAdoptedThreadStorageWinRT, &pointers, 0, 0); - QVERIFY(thread); - WaitForSingleObjectEx(thread, INFINITE, FALSE); + pthread_join(thread, nullptr); #elif defined Q_OS_WIN HANDLE thread; thread = (HANDLE)_beginthread(testAdoptedThreadStorageWin, 0, &pointers); @@ -224,7 +191,7 @@ void tst_QThreadStorage::adoptedThreads() QTRY_COMPARE(Pointer::count, c); } -QBasicAtomicInt cleanupOrder = Q_BASIC_ATOMIC_INITIALIZER(0); +static QBasicAtomicInt cleanupOrder = Q_BASIC_ATOMIC_INITIALIZER(0); class First { @@ -261,7 +228,7 @@ void tst_QThreadStorage::ensureCleanupOrder() : first(first), second(second) { } - void run() + void run() override { // set in reverse order, but shouldn't matter, the data // will be deleted in the order the thread storage objects @@ -305,11 +272,14 @@ static inline bool runCrashOnExit(const QString &binary, QString *errorMessage) void tst_QThreadStorage::crashOnExit() { +#ifdef Q_OS_ANDROID + QSKIP("Can't start QProcess to run a custom user binary on Android"); +#endif #if !QT_CONFIG(process) QSKIP("No qprocess support", SkipAll); #else QString errorMessage; - QVERIFY2(runCrashOnExit("crashOnExit_helper", &errorMessage), + QVERIFY2(runCrashOnExit("./crashOnExit_helper", &errorMessage), qPrintable(errorMessage)); #endif } @@ -352,14 +322,14 @@ void tst_QThreadStorage::leakInDestructor() Thread(QThreadStorage<ThreadStorageLocalDataTester *> &t) : tls(t) { } - void run() + void run() override { QVERIFY(!tls.hasLocalData()); tls.setLocalData(new ThreadStorageLocalDataTester); QVERIFY(tls.hasLocalData()); } }; - int c = SPointer::count.load(); + int c = SPointer::count.loadRelaxed(); QThreadStorage<ThreadStorageLocalDataTester *> tls; @@ -383,7 +353,7 @@ void tst_QThreadStorage::leakInDestructor() QVERIFY(t3.wait()); //check all the constructed things have been destructed - QCOMPARE(int(SPointer::count.load()), c); + QCOMPARE(int(SPointer::count.loadRelaxed()), c); } class ThreadStorageResetLocalDataTester { @@ -404,14 +374,14 @@ void tst_QThreadStorage::resetInDestructor() class Thread : public QThread { public: - void run() + void run() override { QVERIFY(!ThreadStorageResetLocalDataTesterTls()->hasLocalData()); ThreadStorageResetLocalDataTesterTls()->setLocalData(new ThreadStorageResetLocalDataTester); QVERIFY(ThreadStorageResetLocalDataTesterTls()->hasLocalData()); } }; - int c = SPointer::count.load(); + int c = SPointer::count.loadRelaxed(); Thread t1; Thread t2; @@ -424,7 +394,7 @@ void tst_QThreadStorage::resetInDestructor() QVERIFY(t3.wait()); //check all the constructed things have been destructed - QCOMPARE(int(SPointer::count.load()), c); + QCOMPARE(int(SPointer::count.loadRelaxed()), c); } @@ -440,7 +410,8 @@ void tst_QThreadStorage::valueBased() Thread(QThreadStorage<SPointer> &t1, QThreadStorage<QString> &t2, QThreadStorage<int> &t3) : tlsSPointer(t1), tlsString(t2), tlsInt(t3) { } - void run() { + void run() override + { /*QVERIFY(!tlsSPointer.hasLocalData()); QVERIFY(!tlsString.hasLocalData()); QVERIFY(!tlsInt.hasLocalData());*/ @@ -475,7 +446,7 @@ void tst_QThreadStorage::valueBased() QThreadStorage<QString> tlsString; QThreadStorage<int> tlsInt; - int c = SPointer::count.load(); + int c = SPointer::count.loadRelaxed(); Thread t1(tlsSPointer, tlsString, tlsInt); Thread t2(tlsSPointer, tlsString, tlsInt); @@ -495,7 +466,7 @@ void tst_QThreadStorage::valueBased() QVERIFY(t2.wait()); QVERIFY(t3.wait()); - QCOMPARE(c, int(SPointer::count.load())); + QCOMPARE(c, int(SPointer::count.loadRelaxed())); } diff --git a/tests/auto/corelib/thread/qwaitcondition/BLACKLIST b/tests/auto/corelib/thread/qwaitcondition/BLACKLIST deleted file mode 100644 index 3ff336576b..0000000000 --- a/tests/auto/corelib/thread/qwaitcondition/BLACKLIST +++ /dev/null @@ -1,2 +0,0 @@ -[wakeOne] -windows diff --git a/tests/auto/corelib/thread/qwaitcondition/CMakeLists.txt b/tests/auto/corelib/thread/qwaitcondition/CMakeLists.txt new file mode 100644 index 0000000000..0a2830622e --- /dev/null +++ b/tests/auto/corelib/thread/qwaitcondition/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qwaitcondition Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qwaitcondition LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qwaitcondition + SOURCES + tst_qwaitcondition.cpp +) diff --git a/tests/auto/corelib/thread/qwaitcondition/qwaitcondition.pro b/tests/auto/corelib/thread/qwaitcondition/qwaitcondition.pro deleted file mode 100644 index 2098d9dd2f..0000000000 --- a/tests/auto/corelib/thread/qwaitcondition/qwaitcondition.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qwaitcondition -QT = core testlib -SOURCES = tst_qwaitcondition.cpp diff --git a/tests/auto/corelib/thread/qwaitcondition/tst_qwaitcondition.cpp b/tests/auto/corelib/thread/qwaitcondition/tst_qwaitcondition.cpp index 126cb6b180..4e3413afe8 100644 --- a/tests/auto/corelib/thread/qwaitcondition/tst_qwaitcondition.cpp +++ b/tests/auto/corelib/thread/qwaitcondition/tst_qwaitcondition.cpp @@ -1,32 +1,8 @@ -/**************************************************************************** -** -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> +#include <QReadWriteLock> #include <qatomic.h> #include <qcoreapplication.h> @@ -77,7 +53,7 @@ public: inline wait_QMutex_Thread_1() { } - void run() + void run() override { mutex.lock(); cond.wakeOne(); @@ -95,10 +71,10 @@ public: QWaitCondition *cond; inline wait_QMutex_Thread_2() - : mutex(0), cond(0) + : mutex(nullptr), cond(nullptr) { } - void run() + void run() override { mutex->lock(); started.wakeOne(); @@ -116,7 +92,7 @@ public: inline wait_QReadWriteLock_Thread_1() { } - void run() + void run() override { readWriteLock.lockForWrite(); cond.wakeOne(); @@ -134,10 +110,10 @@ public: QWaitCondition *cond; inline wait_QReadWriteLock_Thread_2() - : readWriteLock(0), cond(0) + : readWriteLock(nullptr), cond(nullptr) { } - void run() + void run() override { readWriteLock->lockForRead(); started.wakeOne(); @@ -392,13 +368,13 @@ public: QWaitCondition *cond; inline wake_Thread() - : mutex(0), cond(0) + : mutex(nullptr), cond(nullptr) { } static inline void sleep(ulong s) - { QThread::sleep(s); } + { QThread::sleep(std::chrono::seconds{s}); } - void run() + void run() override { Q_ASSERT(count); Q_ASSERT(mutex); @@ -424,13 +400,13 @@ public: QWaitCondition *cond; inline wake_Thread_2() - : readWriteLock(0), cond(0) + : readWriteLock(nullptr), cond(nullptr) { } static inline void sleep(ulong s) - { QThread::sleep(s); } + { QThread::sleep(std::chrono::seconds{s}); } - void run() + void run() override { Q_ASSERT(count); Q_ASSERT(readWriteLock); @@ -481,7 +457,7 @@ void tst_QWaitCondition::wakeOne() } mutex.unlock(); - QCOMPARE(count.load(), ThreadCount); + QCOMPARE(count.loadRelaxed(), ThreadCount); // wake up threads one at a time for (x = 0; x < ThreadCount; ++x) { @@ -502,10 +478,10 @@ void tst_QWaitCondition::wakeOne() } QCOMPARE(exited, 1); - QCOMPARE(count.load(), ThreadCount - (x + 1)); + QCOMPARE(count.loadRelaxed(), ThreadCount - (x + 1)); } - QCOMPARE(count.load(), 0); + QCOMPARE(count.loadRelaxed(), 0); // QReadWriteLock QReadWriteLock readWriteLock; @@ -530,7 +506,7 @@ void tst_QWaitCondition::wakeOne() } readWriteLock.unlock(); - QCOMPARE(count.load(), ThreadCount); + QCOMPARE(count.loadRelaxed(), ThreadCount); // wake up threads one at a time for (x = 0; x < ThreadCount; ++x) { @@ -551,10 +527,10 @@ void tst_QWaitCondition::wakeOne() } QCOMPARE(exited, 1); - QCOMPARE(count.load(), ThreadCount - (x + 1)); + QCOMPARE(count.loadRelaxed(), ThreadCount - (x + 1)); } - QCOMPARE(count.load(), 0); + QCOMPARE(count.loadRelaxed(), 0); } // wake up threads, two at a time @@ -585,7 +561,7 @@ void tst_QWaitCondition::wakeOne() } mutex.unlock(); - QCOMPARE(count.load(), ThreadCount); + QCOMPARE(count.loadRelaxed(), ThreadCount); // wake up threads one at a time for (x = 0; x < ThreadCount; x += 2) { @@ -608,10 +584,10 @@ void tst_QWaitCondition::wakeOne() } QCOMPARE(exited, 2); - QCOMPARE(count.load(), ThreadCount - (x + 2)); + QCOMPARE(count.loadRelaxed(), ThreadCount - (x + 2)); } - QCOMPARE(count.load(), 0); + QCOMPARE(count.loadRelaxed(), 0); // QReadWriteLock QReadWriteLock readWriteLock; @@ -636,7 +612,7 @@ void tst_QWaitCondition::wakeOne() } readWriteLock.unlock(); - QCOMPARE(count.load(), ThreadCount); + QCOMPARE(count.loadRelaxed(), ThreadCount); // wake up threads one at a time for (x = 0; x < ThreadCount; x += 2) { @@ -659,10 +635,10 @@ void tst_QWaitCondition::wakeOne() } QCOMPARE(exited, 2); - QCOMPARE(count.load(), ThreadCount - (x + 2)); + QCOMPARE(count.loadRelaxed(), ThreadCount - (x + 2)); } - QCOMPARE(count.load(), 0); + QCOMPARE(count.loadRelaxed(), 0); } } @@ -692,7 +668,7 @@ void tst_QWaitCondition::wakeAll() } mutex.unlock(); - QCOMPARE(count.load(), ThreadCount); + QCOMPARE(count.loadRelaxed(), ThreadCount); // wake up all threads at once mutex.lock(); @@ -707,7 +683,7 @@ void tst_QWaitCondition::wakeAll() } QCOMPARE(exited, ThreadCount); - QCOMPARE(count.load(), 0); + QCOMPARE(count.loadRelaxed(), 0); // QReadWriteLock QReadWriteLock readWriteLock; @@ -728,7 +704,7 @@ void tst_QWaitCondition::wakeAll() } readWriteLock.unlock(); - QCOMPARE(count.load(), ThreadCount); + QCOMPARE(count.loadRelaxed(), ThreadCount); // wake up all threads at once readWriteLock.lockForWrite(); @@ -743,7 +719,7 @@ void tst_QWaitCondition::wakeAll() } QCOMPARE(exited, ThreadCount); - QCOMPARE(count.load(), 0); + QCOMPARE(count.loadRelaxed(), 0); } } @@ -764,7 +740,8 @@ public: QWaitCondition *startup; QWaitCondition *waitCondition; - void run() { + void run() override + { mutex->lock(); ready = true; @@ -796,7 +773,8 @@ public: QWaitCondition *startup; QWaitCondition *waitCondition; - void run() { + void run() override + { readWriteLock->lockForWrite(); ready = true; diff --git a/tests/auto/corelib/thread/qwritelocker/CMakeLists.txt b/tests/auto/corelib/thread/qwritelocker/CMakeLists.txt new file mode 100644 index 0000000000..5345522ea5 --- /dev/null +++ b/tests/auto/corelib/thread/qwritelocker/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qwritelocker Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qwritelocker LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qwritelocker + SOURCES + tst_qwritelocker.cpp +) diff --git a/tests/auto/corelib/thread/qwritelocker/qwritelocker.pro b/tests/auto/corelib/thread/qwritelocker/qwritelocker.pro deleted file mode 100644 index a6c4f70b8d..0000000000 --- a/tests/auto/corelib/thread/qwritelocker/qwritelocker.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qwritelocker -QT = core testlib -SOURCES = tst_qwritelocker.cpp diff --git a/tests/auto/corelib/thread/qwritelocker/tst_qwritelocker.cpp b/tests/auto/corelib/thread/qwritelocker/tst_qwritelocker.cpp index 876c18c721..b4e6b45dbd 100644 --- a/tests/auto/corelib/thread/qwritelocker/tst_qwritelocker.cpp +++ b/tests/auto/corelib/thread/qwritelocker/tst_qwritelocker.cpp @@ -1,32 +1,7 @@ -/**************************************************************************** -** -** 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) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <QCoreApplication> #include <QWriteLocker> @@ -73,7 +48,7 @@ void tst_QWriteLocker::scopeTest() class ScopeTestThread : public tst_QWriteLockerThread { public: - void run() + void run() override { waitForTest(); @@ -109,7 +84,7 @@ void tst_QWriteLocker::scopeTest() QVERIFY(thread->wait()); delete thread; - thread = 0; + thread = nullptr; } @@ -118,7 +93,7 @@ void tst_QWriteLocker::unlockAndRelockTest() class UnlockAndRelockThread : public tst_QWriteLockerThread { public: - void run() + void run() override { QWriteLocker locker(&lock); @@ -156,7 +131,7 @@ void tst_QWriteLocker::unlockAndRelockTest() QVERIFY(thread->wait()); delete thread; - thread = 0; + thread = nullptr; } void tst_QWriteLocker::lockerStateTest() @@ -164,7 +139,7 @@ void tst_QWriteLocker::lockerStateTest() class LockerStateThread : public tst_QWriteLockerThread { public: - void run() + void run() override { { QWriteLocker locker(&lock); @@ -196,7 +171,7 @@ void tst_QWriteLocker::lockerStateTest() QVERIFY(thread->wait()); delete thread; - thread = 0; + thread = nullptr; } QTEST_MAIN(tst_QWriteLocker) diff --git a/tests/auto/corelib/thread/thread.pro b/tests/auto/corelib/thread/thread.pro deleted file mode 100644 index 90b8d6806e..0000000000 --- a/tests/auto/corelib/thread/thread.pro +++ /dev/null @@ -1,27 +0,0 @@ -TEMPLATE=subdirs - -qtConfig(thread) { - SUBDIRS=\ - qatomicint \ - qatomicinteger \ - qatomicpointer \ - qresultstore \ - qfuture \ - qfuturesynchronizer \ - qmutex \ - qmutexlocker \ - qreadlocker \ - qreadwritelock \ - qsemaphore \ - qthread \ - qthreadonce \ - qthreadpool \ - qthreadstorage \ - qwaitcondition \ - qwritelocker -} - -qtHaveModule(concurrent) { - SUBDIRS += \ - qfuturewatcher -} |