blob: 0bdadedfaca5f61eedb16daec619e95e4bbb8d0e [file] [log] [blame]
/****************************************************************************
**
** 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>
#define QFUTURE_TEST
#include <QtTest/QtTest>
#include <qfuture.h>
#include <qfuturewatcher.h>
#include <qresultstore.h>
#include <qthreadpool.h>
#include <qexception.h>
#include <qrandom.h>
#include <private/qfutureinterface_p.h>
// COM interface macro.
#if defined(Q_OS_WIN) && defined(interface)
# undef interface
#endif
struct ResultStoreInt : QtPrivate::ResultStoreBase
{
~ResultStoreInt() { clear<int>(); }
};
class LambdaThread : public QThread
{
public:
LambdaThread(std::function<void ()> fn)
:m_fn(fn)
{
}
void run() override
{
m_fn();
}
private:
std::function<void ()> m_fn;
};
class tst_QFuture: public QObject
{
Q_OBJECT
private slots:
void resultStore();
void future();
void futureInterface();
void refcounting();
void cancel();
void statePropagation();
void multipleResults();
void indexedResults();
void progress();
void progressText();
void resultsAfterFinished();
void resultsAsList();
void implicitConversions();
void iterators();
void iteratorsThread();
void pause();
void throttling();
void voidConversions();
#ifndef QT_NO_EXCEPTIONS
void exceptions();
void nestedExceptions();
#endif
void nonGlobalThreadPool();
};
void tst_QFuture::resultStore()
{
int int0 = 0;
int int1 = 1;
int int2 = 2;
{
ResultStoreInt store;
QCOMPARE(store.begin(), store.end());
QCOMPARE(store.resultAt(0), store.end());
QCOMPARE(store.resultAt(1), store.end());
}
{
ResultStoreInt store;
store.addResult(-1, &int0);
store.addResult(1, &int1);
QtPrivate::ResultIteratorBase it = store.begin();
QCOMPARE(it.resultIndex(), 0);
QVERIFY(it == store.begin());
QVERIFY(it != store.end());
++it;
QCOMPARE(it.resultIndex(), 1);
QVERIFY(it != store.begin());
QVERIFY(it != store.end());
++it;
QVERIFY(it != store.begin());
QVERIFY(it == store.end());
}
QVector<int> vec0 = QVector<int>() << 2 << 3;
QVector<int> vec1 = QVector<int>() << 4 << 5;
{
ResultStoreInt store;
store.addResults(-1, &vec0, 2);
store.addResults(-1, &vec1, 2);
QtPrivate::ResultIteratorBase it = store.begin();
QCOMPARE(it.resultIndex(), 0);
QCOMPARE(it, store.begin());
QVERIFY(it != store.end());
++it;
QCOMPARE(it.resultIndex(), 1);
QVERIFY(it != store.begin());
QVERIFY(it != store.end());
++it;
QCOMPARE(it.resultIndex(), 2);
++it;
QCOMPARE(it.resultIndex(), 3);
++it;
QCOMPARE(it, store.end());
}
{
ResultStoreInt store;
store.addResult(-1, &int0);
store.addResults(-1, &vec1, 2);
store.addResult(-1, &int1);
QtPrivate::ResultIteratorBase it = store.begin();
QCOMPARE(it.resultIndex(), 0);
QVERIFY(it == store.begin());
QVERIFY(it != store.end());
++it;
QCOMPARE(it.resultIndex(), 1);
QVERIFY(it != store.begin());
QVERIFY(it != store.end());
++it;
QCOMPARE(it.resultIndex(), 2);
QVERIFY(it != store.end());
++it;
QCOMPARE(it.resultIndex(), 3);
QVERIFY(it != store.end());
++it;
QVERIFY(it == store.end());
QCOMPARE(store.resultAt(0).resultIndex(), 0);
QCOMPARE(store.resultAt(1).resultIndex(), 1);
QCOMPARE(store.resultAt(2).resultIndex(), 2);
QCOMPARE(store.resultAt(3).resultIndex(), 3);
QCOMPARE(store.resultAt(4), store.end());
}
{
ResultStoreInt store;
store.addResult(-1, &int0);
store.addResults(-1, &vec0);
store.addResult(-1, &int1);
QtPrivate::ResultIteratorBase it = store.begin();
QCOMPARE(it.resultIndex(), 0);
QVERIFY(it == store.begin());
QVERIFY(it != store.end());
++it;
QCOMPARE(it.resultIndex(), 1);
QVERIFY(it != store.begin());
QVERIFY(it != store.end());
++it;
QCOMPARE(it.resultIndex(), 2);
QVERIFY(it != store.end());
++it;
QCOMPARE(it.resultIndex(), 3);
QVERIFY(it != store.end());
++it;
QVERIFY(it == store.end());
QCOMPARE(store.resultAt(0).value<int>(), int0);
QCOMPARE(store.resultAt(1).value<int>(), vec0[0]);
QCOMPARE(store.resultAt(2).value<int>(), vec0[1]);
QCOMPARE(store.resultAt(3).value<int>(), int1);
}
{
ResultStoreInt store;
store.addResult(-1, &int0);
store.addResults(-1, &vec0);
store.addResult(200, &int1);
QCOMPARE(store.resultAt(0).value<int>(), int0);
QCOMPARE(store.resultAt(1).value<int>(), vec0[0]);
QCOMPARE(store.resultAt(2).value<int>(), vec0[1]);
QCOMPARE(store.resultAt(200).value<int>(), int1);
}
{
ResultStoreInt store;
store.addResult(1, &int1);
store.addResult(0, &int0);
store.addResult(-1, &int2);
QCOMPARE(store.resultAt(0).value<int>(), int0);
QCOMPARE(store.resultAt(1).value<int>(), int1);
QCOMPARE(store.resultAt(2).value<int>(), int2);
}
{
ResultStoreInt store;
QCOMPARE(store.contains(0), false);
QCOMPARE(store.contains(1), false);
QCOMPARE(store.contains(INT_MAX), false);
}
{
// Test filter mode, where "gaps" in the result array aren't allowed.
ResultStoreInt store;
store.setFilterMode(true);
store.addResult(0, &int0);
QCOMPARE(store.contains(0), true);
store.addResult(2, &int2); // add result at index 2
QCOMPARE(store.contains(2), false); // but 1 is missing, so this 2 won't be reported yet.
store.addResult(1, &int1);
QCOMPARE(store.contains(1), true);
QCOMPARE(store.contains(2), true); // 2 should be visible now.
store.addResult(4, &int0);
store.addResult(5, &int0);
store.addResult(7, &int0);
QCOMPARE(store.contains(4), false);
QCOMPARE(store.contains(5), false);
QCOMPARE(store.contains(7), false);
store.addResult(3, &int0); // adding 3 makes 4 and 5 visible
QCOMPARE(store.contains(4), true);
QCOMPARE(store.contains(5), true);
QCOMPARE(store.contains(7), false);
store.addResult(6, &int0); // adding 6 makes 7 visible
QCOMPARE(store.contains(6), true);
QCOMPARE(store.contains(7), true);
QCOMPARE(store.contains(8), false);
}
{
// test canceled results
ResultStoreInt store;
store.setFilterMode(true);
store.addResult(0, &int0);
QCOMPARE(store.contains(0), true);
store.addResult(2, &int0);
QCOMPARE(store.contains(2), false);
store.addCanceledResult(1); // report no result at 1
QCOMPARE(store.contains(0), true);
QCOMPARE(store.contains(1), true); // 2 gets renamed to 1
QCOMPARE(store.contains(2), false);
store.addResult(3, &int0);
QCOMPARE(store.contains(2), true); //3 gets renamed to 2
store.addResult(6, &int0);
store.addResult(7, &int0);
QCOMPARE(store.contains(3), false);
store.addCanceledResult(4);
store.addCanceledResult(5);
QCOMPARE(store.contains(3), true); //6 gets renamed to 3
QCOMPARE(store.contains(4), true); //7 gets renamed to 4
store.addResult(8, &int0);
QCOMPARE(store.contains(5), true); //8 gets renamed to 4
QCOMPARE(store.contains(6), false);
QCOMPARE(store.contains(7), false);
}
{
// test addResult return value
ResultStoreInt store;
store.setFilterMode(true);
store.addResult(0, &int0);
QCOMPARE(store.count(), 1); // result 0 becomes available
QCOMPARE(store.contains(0), true);
store.addResult(2, &int0);
QCOMPARE(store.count(), 1);
QCOMPARE(store.contains(2), false);
store.addCanceledResult(1);
QCOMPARE(store.count(), 2); // result 2 is renamed to 1 and becomes available
QCOMPARE(store.contains(0), true);
QCOMPARE(store.contains(1), true);
QCOMPARE(store.contains(2), false);
store.addResult(3, &int0);
QCOMPARE(store.count(), 3);
QCOMPARE(store.contains(2), true);
store.addResult(6, &int0);
QCOMPARE(store.count(), 3);
store.addResult(7, &int0);
QCOMPARE(store.count(), 3);
QCOMPARE(store.contains(3), false);
store.addCanceledResult(4);
store.addCanceledResult(5);
QCOMPARE(store.count(), 5); // 6 and 7 is renamed to 3 and 4 and becomes available
QCOMPARE(store.contains(3), true);
QCOMPARE(store.contains(4), true);
store.addResult(8, &int0);
QCOMPARE(store.contains(5), true);
QCOMPARE(store.count(), 6);
QCOMPARE(store.contains(6), false);
QCOMPARE(store.contains(7), false);
}
{
// test resultCount in non-filtered mode. It should always be possible
// to iterate through the results 0 to resultCount.
ResultStoreInt store;
store.addResult(0, &int0);
QCOMPARE(store.count(), 1);
store.addResult(2, &int0);
QCOMPARE(store.count(), 1);
store.addResult(1, &int0);
QCOMPARE(store.count(), 3);
}
{
ResultStoreInt store;
store.addResult(2, &int0);
QCOMPARE(store.count(), 0);
store.addResult(1, &int0);
QCOMPARE(store.count(), 0);
store.addResult(0, &int0);
QCOMPARE(store.count(), 3);
}
{
ResultStoreInt store;
store.addResults(2, &vec1);
QCOMPARE(store.count(), 0);
store.addResult(1, &int0);
QCOMPARE(store.count(), 0);
store.addResult(0, &int0);
QCOMPARE(store.count(), 4);
}
{
ResultStoreInt store;
store.addResults(2, &vec1);
QCOMPARE(store.count(), 0);
store.addResults(0, &vec0);
QCOMPARE(store.count(), 4);
}
{
ResultStoreInt store;
store.addResults(3, &vec1);
QCOMPARE(store.count(), 0);
store.addResults(0, &vec0);
QCOMPARE(store.count(), 2);
store.addResult(2, &int0);
QCOMPARE(store.count(), 5);
}
{
ResultStoreInt store;
store.setFilterMode(true);
store.addResults(3, &vec1);
QCOMPARE(store.count(), 0);
store.addResults(0, &vec0);
QCOMPARE(store.count(), 2);
store.addCanceledResult(2);
QCOMPARE(store.count(), 4);
}
{
ResultStoreInt store;
store.setFilterMode(true);
store.addResults(3, &vec1);
QCOMPARE(store.count(), 0);
store.addCanceledResults<int>(0, 3);
QCOMPARE(store.count(), 2);
}
{
ResultStoreInt store;
store.setFilterMode(true);
store.addResults(3, &vec1);
QCOMPARE(store.count(), 0);
store.addCanceledResults<int>(0, 3);
QCOMPARE(store.count(), 2); // results at 3 and 4 become available at index 0, 1
store.addResult(5, &int0);
QCOMPARE(store.count(), 3);// result 5 becomes available at index 2
}
{
ResultStoreInt store;
store.addResult(1, &int0);
store.addResult(3, &int0);
store.addResults(6, &vec0);
QCOMPARE(store.contains(0), false);
QCOMPARE(store.contains(1), true);
QCOMPARE(store.contains(2), false);
QCOMPARE(store.contains(3), true);
QCOMPARE(store.contains(4), false);
QCOMPARE(store.contains(5), false);
QCOMPARE(store.contains(6), true);
QCOMPARE(store.contains(7), true);
}
{
ResultStoreInt store;
store.setFilterMode(true);
store.addResult(1, &int0);
store.addResult(3, &int0);
store.addResults(6, &vec0);
QCOMPARE(store.contains(0), false);
QCOMPARE(store.contains(1), false);
QCOMPARE(store.contains(2), false);
QCOMPARE(store.contains(3), false);
QCOMPARE(store.contains(4), false);
QCOMPARE(store.contains(5), false);
QCOMPARE(store.contains(6), false);
QCOMPARE(store.contains(7), false);
store.addCanceledResult(0);
store.addCanceledResult(2);
store.addCanceledResults<int>(4, 2);
QCOMPARE(store.contains(0), true);
QCOMPARE(store.contains(1), true);
QCOMPARE(store.contains(2), true);
QCOMPARE(store.contains(3), true);
QCOMPARE(store.contains(4), false);
QCOMPARE(store.contains(5), false);
QCOMPARE(store.contains(6), false);
QCOMPARE(store.contains(7), false);
}
{
ResultStoreInt store;
store.setFilterMode(true);
store.addCanceledResult(0);
QCOMPARE(store.contains(0), false);
store.addResult(1, &int0);
QCOMPARE(store.contains(0), true);
QCOMPARE(store.contains(1), false);
}
}
void tst_QFuture::future()
{
// default constructors
QFuture<int> intFuture;
intFuture.waitForFinished();
QFuture<QString> stringFuture;
stringFuture.waitForFinished();
QFuture<void> voidFuture;
voidFuture.waitForFinished();
QFuture<void> defaultVoidFuture;
defaultVoidFuture.waitForFinished();
// copy constructor
QFuture<int> intFuture2(intFuture);
QFuture<void> voidFuture2(defaultVoidFuture);
// assigmnent operator
intFuture2 = QFuture<int>();
voidFuture2 = QFuture<void>();
// state
QCOMPARE(intFuture2.isStarted(), true);
QCOMPARE(intFuture2.isFinished(), true);
}
class IntResult : public QFutureInterface<int>
{
public:
QFuture<int> run()
{
this->reportStarted();
QFuture<int> future = QFuture<int>(this);
int res = 10;
reportFinished(&res);
return future;
}
};
int value = 10;
class VoidResult : public QFutureInterfaceBase
{
public:
QFuture<void> run()
{
this->reportStarted();
QFuture<void> future = QFuture<void>(this);
reportFinished();
return future;
}
};
void tst_QFuture::futureInterface()
{
{
QFuture<void> future;
{
QFutureInterface<void> i;
i.reportStarted();
future = i.future();
i.reportFinished();
}
}
{
QFuture<int> future;
{
QFutureInterface<int> i;
i.reportStarted();
i.reportResult(10);
future = i.future();
i.reportFinished();
}
QCOMPARE(future.resultAt(0), 10);
}
{
QFuture<int> intFuture;
QCOMPARE(intFuture.isStarted(), true);
QCOMPARE(intFuture.isFinished(), true);
IntResult result;
result.reportStarted();
intFuture = result.future();
QCOMPARE(intFuture.isStarted(), true);
QCOMPARE(intFuture.isFinished(), false);
result.reportFinished(&value);
QCOMPARE(intFuture.isStarted(), true);
QCOMPARE(intFuture.isFinished(), true);
int e = intFuture.result();
QCOMPARE(intFuture.isStarted(), true);
QCOMPARE(intFuture.isFinished(), true);
QCOMPARE(intFuture.isCanceled(), false);
QCOMPARE(e, value);
intFuture.waitForFinished();
IntResult intAlgo;
intFuture = intAlgo.run();
QFuture<int> intFuture2(intFuture);
QCOMPARE(intFuture.result(), value);
QCOMPARE(intFuture2.result(), value);
intFuture.waitForFinished();
VoidResult a;
a.run().waitForFinished();
}
}
template <typename T>
void testRefCounting()
{
QFutureInterface<T> interface;
QCOMPARE(interface.d->refCount.load(), 1);
{
interface.reportStarted();
QFuture<T> f = interface.future();
QCOMPARE(interface.d->refCount.load(), 2);
QFuture<T> f2(f);
QCOMPARE(interface.d->refCount.load(), 3);
QFuture<T> f3;
f3 = f2;
QCOMPARE(interface.d->refCount.load(), 4);
interface.reportFinished(0);
QCOMPARE(interface.d->refCount.load(), 4);
}
QCOMPARE(interface.d->refCount.load(), 1);
}
void tst_QFuture::refcounting()
{
testRefCounting<int>();
}
void tst_QFuture::cancel()
{
{
QFuture<void> f;
QFutureInterface<void> result;
result.reportStarted();
f = result.future();
QVERIFY(!f.isCanceled());
result.reportCanceled();
QVERIFY(f.isCanceled());
result.reportFinished();
QVERIFY(f.isCanceled());
f.waitForFinished();
QVERIFY(f.isCanceled());
}
// Cancel from the QFuture side and test if the result
// interface detects it.
{
QFutureInterface<void> result;
QFuture<void> f;
QVERIFY(f.isStarted());
result.reportStarted();
f = result.future();
QVERIFY(f.isStarted());
QVERIFY(!result.isCanceled());
f.cancel();
QVERIFY(result.isCanceled());
result.reportFinished();
}
// Test that finished futures can be canceled.
{
QFutureInterface<void> result;
QFuture<void> f;
QVERIFY(f.isStarted());
result.reportStarted();
f = result.future();
QVERIFY(f.isStarted());
result.reportFinished();
f.cancel();
QVERIFY(result.isCanceled());
QVERIFY(f.isCanceled());
}
// Results reported after canceled is called should not be propagated.
{
QFutureInterface<int> futureInterface;
futureInterface.reportStarted();
QFuture<int> f = futureInterface.future();
int result = 0;
futureInterface.reportResult(&result);
result = 1;
futureInterface.reportResult(&result);
f.cancel();
result = 2;
futureInterface.reportResult(&result);
result = 3;
futureInterface.reportResult(&result);
futureInterface.reportFinished();
QVERIFY(f.results().isEmpty());
}
}
void tst_QFuture::statePropagation()
{
QFuture<void> f1;
QFuture<void> f2;
QCOMPARE(f1.isStarted(), true);
QFutureInterface<void> result;
result.reportStarted();
f1 = result.future();
f2 = f1;
QCOMPARE(f2.isStarted(), true);
result.reportCanceled();
QCOMPARE(f2.isStarted(), true);
QCOMPARE(f2.isCanceled(), true);
QFuture<void> f3 = f2;
QCOMPARE(f3.isStarted(), true);
QCOMPARE(f3.isCanceled(), true);
result.reportFinished();
QCOMPARE(f2.isStarted(), true);
QCOMPARE(f2.isCanceled(), true);
QCOMPARE(f3.isStarted(), true);
QCOMPARE(f3.isCanceled(), true);
}
/*
Tests that a QFuture can return multiple results.
*/
void tst_QFuture::multipleResults()
{
IntResult a;
a.reportStarted();
QFuture<int> f = a.future();
QFuture<int> copy = f;
int result;
result = 1;
a.reportResult(&result);
QCOMPARE(f.resultAt(0), 1);
result = 2;
a.reportResult(&result);
QCOMPARE(f.resultAt(1), 2);
result = 3;
a.reportResult(&result);
result = 4;
a.reportFinished(&result);
QCOMPARE(f.results(), QList<int>() << 1 << 2 << 3 << 4);
// test foreach
QList<int> fasit = QList<int>() << 1 << 2 << 3 << 4;
{
QList<int> results;
foreach(int result, f)
results.append(result);
QCOMPARE(results, fasit);
}
{
QList<int> results;
foreach(int result, copy)
results.append(result);
QCOMPARE(results, fasit);
}
}
/*
Test out-of-order result reporting using indexes
*/
void tst_QFuture::indexedResults()
{
{
QFutureInterface<QChar> Interface;
QFuture<QChar> f;
QVERIFY(f.isStarted());
Interface.reportStarted();
f = Interface.future();
QVERIFY(f.isStarted());
QChar result;
result = 'B';
Interface.reportResult(&result, 1);
QCOMPARE(f.resultAt(1), result);
result = 'A';
Interface.reportResult(&result, 0);
QCOMPARE(f.resultAt(0), result);
result = 'C';
Interface.reportResult(&result); // no index
QCOMPARE(f.resultAt(2), result);
Interface.reportFinished();
QCOMPARE(f.results(), QList<QChar>() << 'A' << 'B' << 'C');
}
{
// Test result reporting with a missing result in the middle
QFutureInterface<int> Interface;
Interface.reportStarted();
QFuture<int> f = Interface.future();
int result;
result = 0;
Interface.reportResult(&result, 0);
QVERIFY(f.isResultReadyAt(0));
QCOMPARE(f.resultAt(0), 0);
result = 3;
Interface.reportResult(&result, 3);
QVERIFY(f.isResultReadyAt(3));
QCOMPARE(f.resultAt(3), 3);
result = 2;
Interface.reportResult(&result, 2);
QVERIFY(f.isResultReadyAt(2));
QCOMPARE(f.resultAt(2), 2);
result = 4;
Interface.reportResult(&result); // no index
QVERIFY(f.isResultReadyAt(4));
QCOMPARE(f.resultAt(4), 4);
Interface.reportFinished();
QCOMPARE(f.results(), QList<int>() << 0 << 2 << 3 << 4);
}
}
void tst_QFuture::progress()
{
QFutureInterface<QChar> result;
QFuture<QChar> f;
QCOMPARE (f.progressValue(), 0);
result.reportStarted();
f = result.future();
QCOMPARE (f.progressValue(), 0);
result.setProgressValue(50);
QCOMPARE (f.progressValue(), 50);
result.reportFinished();
QCOMPARE (f.progressValue(), 50);
}
void tst_QFuture::progressText()
{
QFutureInterface<void> i;
i.reportStarted();
QFuture<void> f = i.future();
QCOMPARE(f.progressText(), QLatin1String(""));
i.setProgressValueAndText(1, QLatin1String("foo"));
QCOMPARE(f.progressText(), QLatin1String("foo"));
i.reportFinished();
}
/*
Test that results reported after finished are ignored.
*/
void tst_QFuture::resultsAfterFinished()
{
{
IntResult a;
a.reportStarted();
QFuture<int> f = a.future();
int result;
QCOMPARE(f.resultCount(), 0);
result = 1;
a.reportResult(&result);
QCOMPARE(f.resultAt(0), 1);
a.reportFinished();
QCOMPARE(f.resultAt(0), 1);
QCOMPARE(f.resultCount(), 1);
result = 2;
a.reportResult(&result);
QCOMPARE(f.resultCount(), 1);
}
// cancel it
{
IntResult a;
a.reportStarted();
QFuture<int> f = a.future();
int result;
QCOMPARE(f.resultCount(), 0);
result = 1;
a.reportResult(&result);
QCOMPARE(f.resultAt(0), 1);
QCOMPARE(f.resultCount(), 1);
a.reportCanceled();
QCOMPARE(f.resultAt(0), 1);
QCOMPARE(f.resultCount(), 1);
result = 2;
a.reportResult(&result);
a.reportFinished();
}
}
void tst_QFuture::resultsAsList()
{
IntResult a;
a.reportStarted();
QFuture<int> f = a.future();
int result;
result = 1;
a.reportResult(&result);
result = 2;
a.reportResult(&result);
a.reportFinished();
QList<int> results = f.results();
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()
{
{
QFutureInterface<int> e;
e.reportStarted();
QFuture<int> f = e.future();
int result;
result = 1;
e.reportResult(&result);
result = 2;
e.reportResult(&result);
result = 3;
e.reportResult(&result);
e.reportFinished();
QList<int> results;
QFutureIterator<int> i(f);
while (i.hasNext()) {
results.append(i.next());
}
QCOMPARE(results, f.results());
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);
QVERIFY(i1 != i2);
QVERIFY(i1 != c2);
QVERIFY(c1 != i2);
QVERIFY(c1 != c2);
QVERIFY(i2 != i1);
QVERIFY(i2 != c1);
QVERIFY(c2 != i1);
QVERIFY(c2 != c1);
int x1 = *i1;
Q_UNUSED(x1);
int x2 = *i2;
Q_UNUSED(x2);
int y1 = *c1;
Q_UNUSED(y1);
int y2 = *c2;
Q_UNUSED(y2);
}
{
QFutureInterface<QString> e;
e.reportStarted();
QFuture<QString> f = e.future();
e.reportResult(QString("one"));
e.reportResult(QString("two"));
e.reportResult(QString("three"));
e.reportFinished();
QList<QString> results;
QFutureIterator<QString> i(f);
while (i.hasNext()) {
results.append(i.next());
}
QCOMPARE(results, f.results());
QFuture<QString>::const_iterator i1 = f.begin(), i2 = i1 + 1;
QFuture<QString>::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);
QVERIFY(i1 != i2);
QVERIFY(i1 != c2);
QVERIFY(c1 != i2);
QVERIFY(c1 != c2);
QVERIFY(i2 != i1);
QVERIFY(i2 != c1);
QVERIFY(c2 != i1);
QVERIFY(c2 != c1);
QString x1 = *i1;
QString x2 = *i2;
QString y1 = *c1;
QString y2 = *c2;
QCOMPARE(x1, y1);
QCOMPARE(x2, y2);
int i1Size = i1->size();
int i2Size = i2->size();
int c1Size = c1->size();
int c2Size = c2->size();
QCOMPARE(i1Size, c1Size);
QCOMPARE(i2Size, c2Size);
}
{
const int resultCount = 20;
QFutureInterface<int> e;
e.reportStarted();
QFuture<int> f = e.future();
for (int i = 0; i < resultCount; ++i) {
e.reportResult(i);
}
e.reportFinished();
{
QFutureIterator<int> it(f);
QFutureIterator<int> it2(it);
}
{
QFutureIterator<int> it(f);
for (int i = 0; i < resultCount - 1; ++i) {
QVERIFY(it.hasNext());
QCOMPARE(it.peekNext(), i);
QCOMPARE(it.next(), i);
}
QVERIFY(it.hasNext());
QCOMPARE(it.peekNext(), resultCount - 1);
QCOMPARE(it.next(), resultCount - 1);
QVERIFY(!it.hasNext());
}
{
QFutureIterator<int> it(f);
QVERIFY(it.hasNext());
it.toBack();
QVERIFY(!it.hasNext());
it.toFront();
QVERIFY(it.hasNext());
}
}
}
void tst_QFuture::iteratorsThread()
{
const int expectedResultCount = 10;
const int delay = 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
{
Q_OBJECT
public:
SignalSlotObject()
: finishedCalled(false),
canceledCalled(false),
rangeBegin(0),
rangeEnd(0) { }
public slots:
void finished()
{
finishedCalled = true;
}
void canceled()
{
canceledCalled = true;
}
void resultReady(int index)
{
results.insert(index);
}
void progressRange(int begin, int end)
{
rangeBegin = begin;
rangeEnd = end;
}
void progress(int progress)
{
reportedProgress.insert(progress);
}
public:
bool finishedCalled;
bool canceledCalled;
QSet<int> results;
int rangeBegin;
int rangeEnd;
QSet<int> reportedProgress;
};
void tst_QFuture::pause()
{
QFutureInterface<void> Interface;
Interface.reportStarted();
QFuture<void> f = Interface.future();
QVERIFY(!Interface.isPaused());
f.pause();
QVERIFY(Interface.isPaused());
f.resume();
QVERIFY(!Interface.isPaused());
f.togglePaused();
QVERIFY(Interface.isPaused());
f.togglePaused();
QVERIFY(!Interface.isPaused());
Interface.reportFinished();
}
class ResultObject : public QObject
{
Q_OBJECT
public slots:
void resultReady(int)
{
}
public:
};
// Test that that the isPaused() on future result interface returns true
// if we report a lot of results that are not handled.
void tst_QFuture::throttling()
{
{
QFutureInterface<void> i;
i.reportStarted();
QFuture<void> f = i.future();
QVERIFY(!i.isThrottled());
i.setThrottled(true);
QVERIFY(i.isThrottled());
i.setThrottled(false);
QVERIFY(!i.isThrottled());
i.setThrottled(true);
QVERIFY(i.isThrottled());
i.reportFinished();
}
}
void tst_QFuture::voidConversions()
{
{
QFutureInterface<int> iface;
iface.reportStarted();
QFuture<int> intFuture(&iface);
int value = 10;
iface.reportFinished(&value);
QFuture<void> voidFuture(intFuture);
voidFuture = intFuture;
QVERIFY(voidFuture == intFuture);
}
{
QFuture<void> voidFuture;
{
QFutureInterface<QList<int> > iface;
iface.reportStarted();
QFuture<QList<int> > listFuture(&iface);
iface.reportResult(QList<int>() << 1 << 2 << 3);
voidFuture = listFuture;
}
QCOMPARE(voidFuture.resultCount(), 0);
}
}
#ifndef QT_NO_EXCEPTIONS
QFuture<void> createExceptionFuture()
{
QFutureInterface<void> i;
i.reportStarted();
QFuture<void> f = i.future();
QException e;
i.reportException(e);
i.reportFinished();
return f;
}
QFuture<int> createExceptionResultFuture()
{
QFutureInterface<int> i;
i.reportStarted();
QFuture<int> f = i.future();
int r = 0;
i.reportResult(r);
QException e;
i.reportException(e);
i.reportFinished();
return f;
}
class DerivedException : public QException
{
public:
void raise() const override { throw *this; }
DerivedException *clone() const override { return new DerivedException(*this); }
};
QFuture<void> createDerivedExceptionFuture()
{
QFutureInterface<void> i;
i.reportStarted();
QFuture<void> f = i.future();
DerivedException e;
i.reportException(e);
i.reportFinished();
return f;
}
void tst_QFuture::exceptions()
{
// test throwing from waitForFinished
{
QFuture<void> f = createExceptionFuture();
bool caught = false;
try {
f.waitForFinished();
} catch (QException &) {
caught = true;
}
QVERIFY(caught);
}
// test result()
{
QFuture<int> f = createExceptionResultFuture();
bool caught = false;
try {
f.result();
} catch (QException &) {
caught = true;
}
QVERIFY(caught);
}
// test result() and destroy
{
bool caught = false;
try {
createExceptionResultFuture().result();
} catch (QException &) {
caught = true;
}
QVERIFY(caught);
}
// test results()
{
QFuture<int> f = createExceptionResultFuture();
bool caught = false;
try {
f.results();
} catch (QException &) {
caught = true;
}
QVERIFY(caught);
}
// test foreach
{
QFuture<int> f = createExceptionResultFuture();
bool caught = false;
try {
foreach (int e, f.results()) {
Q_UNUSED(e);
QFAIL("did not get exception");
}
} catch (QException &) {
caught = true;
}
QVERIFY(caught);
}
// catch derived exceptions
{
bool caught = false;
try {
createDerivedExceptionFuture().waitForFinished();
} catch (QException &) {
caught = true;
}
QVERIFY(caught);
}
{
bool caught = false;
try {
createDerivedExceptionFuture().waitForFinished();
} catch (DerivedException &) {
caught = true;
}
QVERIFY(caught);
}
}
class MyClass
{
public:
~MyClass()
{
QFuture<void> f = createExceptionFuture();
try {
f.waitForFinished();
} catch (QException &) {
caught = true;
}
}
static bool caught;
};
bool MyClass::caught = false;
// This is a regression test for QTBUG-18149. where QFuture did not throw
// exceptions if called from destructors when the stack was already unwinding
// due to an exception having been thrown.
void tst_QFuture::nestedExceptions()
{
try {
MyClass m;
Q_UNUSED(m);
throw 0;
} catch (int) {}
QVERIFY(MyClass::caught);
}
#endif // QT_NO_EXCEPTIONS
void tst_QFuture::nonGlobalThreadPool()
{
static Q_CONSTEXPR int Answer = 42;
struct UselessTask : QRunnable, QFutureInterface<int>
{
QFuture<int> start(QThreadPool *pool)
{
setRunnable(this);
setThreadPool(pool);
reportStarted();
QFuture<int> f = future();
pool->start(this);
return f;
}
void run() override
{
const int ms = 100 + (QRandomGenerator::global()->bounded(100) - 100/2);
QThread::msleep(ms);
reportResult(Answer);
reportFinished();
}
};
QThreadPool pool;
const int numTasks = QThread::idealThreadCount();
QVector<QFuture<int> > futures;
futures.reserve(numTasks);
for (int i = 0; i < numTasks; ++i)
futures.push_back((new UselessTask)->start(&pool));
QVERIFY(!pool.waitForDone(0)); // pool is busy (meaning our tasks did end up executing there)
QVERIFY(pool.waitForDone(10000)); // max sleep time in UselessTask::run is 150ms, so 10s should be enough
// (and the call returns as soon as all tasks finished anyway, so the
// maximum wait time only matters when the test fails)
Q_FOREACH (const QFuture<int> &future, futures) {
QVERIFY(future.isFinished());
QCOMPARE(future.result(), Answer);
}
}
QTEST_MAIN(tst_QFuture)
#include "tst_qfuture.moc"