mirror of
https://github.com/simonbrunel/qtpromise.git
synced 2024-11-22 02:34:30 +08:00
Enhance QFuture integration and add unit tests
QFuture canceled with `QFuture::cancel()` now rejects attached promises with `QPromiseCanceledException`. In case the future is canceled because an exception (e) has been thrown, the promise is rejected with the same (e) exception (or `QUnhandledException` if not a subclass of `QException`).
This commit is contained in:
parent
596855f579
commit
4919a68959
@ -1,9 +1,24 @@
|
|||||||
#ifndef _QTPROMISE_QPROMISEFUTURE_P_H
|
#ifndef _QTPROMISE_QPROMISEFUTURE_P_H
|
||||||
#define _QTPROMISE_QPROMISEFUTURE_P_H
|
#define _QTPROMISE_QPROMISEFUTURE_P_H
|
||||||
|
|
||||||
|
// Qt
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
|
|
||||||
|
namespace QtPromise {
|
||||||
|
|
||||||
|
class QPromiseCanceledException: public QException
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void raise() const Q_DECL_OVERRIDE { throw *this; }
|
||||||
|
QPromiseCanceledException* clone() const Q_DECL_OVERRIDE
|
||||||
|
{
|
||||||
|
return new QPromiseCanceledException(*this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QtPromise
|
||||||
|
|
||||||
namespace QtPromisePrivate {
|
namespace QtPromisePrivate {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -24,8 +39,18 @@ struct PromiseFulfill<QFuture<T> >
|
|||||||
Watcher* watcher = new Watcher();
|
Watcher* watcher = new Watcher();
|
||||||
QObject::connect(watcher, &Watcher::finished, [=]() mutable {
|
QObject::connect(watcher, &Watcher::finished, [=]() mutable {
|
||||||
try {
|
try {
|
||||||
|
if (watcher->isCanceled()) {
|
||||||
|
// A QFuture is canceled if cancel() has been explicitly called OR if an
|
||||||
|
// exception has been thrown from the associated thread. Trying to call
|
||||||
|
// result() in the first case causes a "read access violation", so let's
|
||||||
|
// rethrown potential exceptions using waitForFinished() and thus detect
|
||||||
|
// if the future has been canceled by the user or an exception.
|
||||||
|
watcher->waitForFinished();
|
||||||
|
reject(QtPromise::QPromiseCanceledException());
|
||||||
|
} else {
|
||||||
T res = watcher->result();
|
T res = watcher->result();
|
||||||
PromiseFulfill<T>::call(res, resolve, reject);
|
PromiseFulfill<T>::call(res, resolve, reject);
|
||||||
|
}
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
reject(std::current_exception());
|
reject(std::current_exception());
|
||||||
}
|
}
|
||||||
@ -50,9 +75,13 @@ struct PromiseFulfill<QFuture<void> >
|
|||||||
Watcher* watcher = new Watcher();
|
Watcher* watcher = new Watcher();
|
||||||
QObject::connect(watcher, &Watcher::finished, [=]() mutable {
|
QObject::connect(watcher, &Watcher::finished, [=]() mutable {
|
||||||
try {
|
try {
|
||||||
// let's rethrown possibe exception
|
if (watcher->isCanceled()) {
|
||||||
|
// let's rethrown potential exception
|
||||||
watcher->waitForFinished();
|
watcher->waitForFinished();
|
||||||
|
reject(QtPromise::QPromiseCanceledException());
|
||||||
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
|
}
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
reject(std::current_exception());
|
reject(std::current_exception());
|
||||||
}
|
}
|
||||||
@ -64,6 +93,6 @@ struct PromiseFulfill<QFuture<void> >
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QtPromise
|
} // namespace QtPromisePrivate
|
||||||
|
|
||||||
#endif // _QTPROMISE_QPROMISEFUTURE_P_H
|
#endif // _QTPROMISE_QPROMISEFUTURE_P_H
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
TEMPLATE = subdirs
|
TEMPLATE = subdirs
|
||||||
SUBDIRS += \
|
SUBDIRS += \
|
||||||
|
future \
|
||||||
helpers \
|
helpers \
|
||||||
qpromise \
|
qpromise \
|
||||||
requirements
|
requirements
|
||||||
|
4
tests/auto/future/future.pro
Normal file
4
tests/auto/future/future.pro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
TARGET = tst_future
|
||||||
|
SOURCES += $$PWD/tst_future.cpp
|
||||||
|
|
||||||
|
include(../tests.pri)
|
346
tests/auto/future/tst_future.cpp
Normal file
346
tests/auto/future/tst_future.cpp
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
// QtPromise
|
||||||
|
#include <QtPromise>
|
||||||
|
|
||||||
|
// Qt
|
||||||
|
#include <QtConcurrent>
|
||||||
|
#include <QtTest>
|
||||||
|
|
||||||
|
using namespace QtPromise;
|
||||||
|
|
||||||
|
class tst_future: public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void fulfilled();
|
||||||
|
void fulfilled_void();
|
||||||
|
void rejected();
|
||||||
|
void rejected_void();
|
||||||
|
void unhandled();
|
||||||
|
void unhandled_void();
|
||||||
|
void canceled();
|
||||||
|
void canceled_void();
|
||||||
|
void canceledFromThread();
|
||||||
|
void then();
|
||||||
|
void then_void();
|
||||||
|
void fail();
|
||||||
|
void fail_void();
|
||||||
|
void finally();
|
||||||
|
void finallyRejected();
|
||||||
|
|
||||||
|
}; // class tst_future
|
||||||
|
|
||||||
|
class MyException: public QException
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MyException(const QString& error)
|
||||||
|
: m_error(error)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
const QString& error() const { return m_error; }
|
||||||
|
|
||||||
|
void raise() const { throw *this; }
|
||||||
|
MyException* clone() const { return new MyException(*this); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_MAIN(tst_future)
|
||||||
|
#include "tst_future.moc"
|
||||||
|
|
||||||
|
void tst_future::fulfilled()
|
||||||
|
{
|
||||||
|
int result = -1;
|
||||||
|
auto p = qPromise(QtConcurrent::run([]() {
|
||||||
|
return 42;
|
||||||
|
}));
|
||||||
|
|
||||||
|
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
|
||||||
|
QCOMPARE(p.isPending(), true);
|
||||||
|
|
||||||
|
p.then([&](int res) {
|
||||||
|
result = res;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(p.isFulfilled(), true);
|
||||||
|
QCOMPARE(result, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::fulfilled_void()
|
||||||
|
{
|
||||||
|
int result = -1;
|
||||||
|
auto p = qPromise(QtConcurrent::run([]() { }));
|
||||||
|
|
||||||
|
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||||
|
QCOMPARE(p.isPending(), true);
|
||||||
|
|
||||||
|
p.then([&]() {
|
||||||
|
result = 42;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(p.isFulfilled(), true);
|
||||||
|
QCOMPARE(result, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::rejected()
|
||||||
|
{
|
||||||
|
QString error;
|
||||||
|
auto p = qPromise(QtConcurrent::run([]() {
|
||||||
|
throw MyException("foo");
|
||||||
|
return 42;
|
||||||
|
}));
|
||||||
|
|
||||||
|
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
|
||||||
|
QCOMPARE(p.isPending(), true);
|
||||||
|
|
||||||
|
p.fail([&](const MyException& e) {
|
||||||
|
error = e.error();
|
||||||
|
return -1;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(p.isRejected(), true);
|
||||||
|
QCOMPARE(error, QString("foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::rejected_void()
|
||||||
|
{
|
||||||
|
QString error;
|
||||||
|
auto p = qPromise(QtConcurrent::run([]() {
|
||||||
|
throw MyException("foo");
|
||||||
|
}));
|
||||||
|
|
||||||
|
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||||
|
|
||||||
|
QCOMPARE(p.isPending(), true);
|
||||||
|
|
||||||
|
p.fail([&](const MyException& e) {
|
||||||
|
error = e.error();
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(p.isRejected(), true);
|
||||||
|
QCOMPARE(error, QString("foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::unhandled()
|
||||||
|
{
|
||||||
|
QString error;
|
||||||
|
auto p = qPromise(QtConcurrent::run([]() {
|
||||||
|
throw QString("foo");
|
||||||
|
return 42;
|
||||||
|
}));
|
||||||
|
|
||||||
|
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
|
||||||
|
|
||||||
|
QCOMPARE(p.isPending(), true);
|
||||||
|
|
||||||
|
p.fail([&](const QString& err) {
|
||||||
|
error += err;
|
||||||
|
return -1;
|
||||||
|
}).fail([&](const QUnhandledException&) {
|
||||||
|
error += "bar";
|
||||||
|
return -1;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(p.isRejected(), true);
|
||||||
|
QCOMPARE(error, QString("bar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::unhandled_void()
|
||||||
|
{
|
||||||
|
QString error;
|
||||||
|
auto p = qPromise(QtConcurrent::run([]() {
|
||||||
|
throw QString("foo");
|
||||||
|
}));
|
||||||
|
|
||||||
|
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||||
|
QCOMPARE(p.isPending(), true);
|
||||||
|
|
||||||
|
p.fail([&](const QString& err) {
|
||||||
|
error += err;
|
||||||
|
}).fail([&](const QUnhandledException&) {
|
||||||
|
error += "bar";
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(p.isRejected(), true);
|
||||||
|
QCOMPARE(error, QString("bar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::canceled()
|
||||||
|
{
|
||||||
|
QString error;
|
||||||
|
auto p = qPromise(QFuture<int>()); // Constructs an empty, canceled future.
|
||||||
|
|
||||||
|
QCOMPARE(p.isPending(), true);
|
||||||
|
|
||||||
|
p.fail([&](const QPromiseCanceledException&) {
|
||||||
|
error = "canceled";
|
||||||
|
return -1;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(p.isRejected(), true);
|
||||||
|
QCOMPARE(error, QString("canceled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::canceled_void()
|
||||||
|
{
|
||||||
|
QString error;
|
||||||
|
auto p = qPromise(QFuture<void>()); // Constructs an empty, canceled future.
|
||||||
|
|
||||||
|
QCOMPARE(p.isPending(), true);
|
||||||
|
|
||||||
|
p.fail([&](const QPromiseCanceledException&) {
|
||||||
|
error = "canceled";
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(p.isRejected(), true);
|
||||||
|
QCOMPARE(error, QString("canceled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::canceledFromThread()
|
||||||
|
{
|
||||||
|
QString error;
|
||||||
|
auto p = qPromise(QtConcurrent::run([]() {
|
||||||
|
throw QPromiseCanceledException();
|
||||||
|
}));
|
||||||
|
|
||||||
|
QCOMPARE(p.isPending(), true);
|
||||||
|
|
||||||
|
p.fail([&](const QPromiseCanceledException&) {
|
||||||
|
error = "bar";
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(p.isRejected(), true);
|
||||||
|
QCOMPARE(error, QString("bar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::then()
|
||||||
|
{
|
||||||
|
QString result;
|
||||||
|
auto input = qPromise(42);
|
||||||
|
auto output = input.then([](int res) {
|
||||||
|
return QtConcurrent::run([=]() {
|
||||||
|
return QString("foo%1").arg(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QCOMPARE(input.isFulfilled(), true);
|
||||||
|
QCOMPARE(output.isPending(), true);
|
||||||
|
|
||||||
|
output.then([&](const QString& res) {
|
||||||
|
result = res;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(output.isFulfilled(), true);
|
||||||
|
QCOMPARE(result, QString("foo42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::then_void()
|
||||||
|
{
|
||||||
|
QString result;
|
||||||
|
auto input = qPromise();
|
||||||
|
auto output = input.then([&]() {
|
||||||
|
return QtConcurrent::run([&]() {
|
||||||
|
result = "foo";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QCOMPARE(input.isFulfilled(), true);
|
||||||
|
QCOMPARE(output.isPending(), true);
|
||||||
|
|
||||||
|
output.then([&]() {
|
||||||
|
result += "bar";
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(input.isFulfilled(), true);
|
||||||
|
QCOMPARE(result, QString("foobar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::fail()
|
||||||
|
{
|
||||||
|
QString result;
|
||||||
|
auto input = QPromise<QString>::reject(MyException("bar"));
|
||||||
|
auto output = input.fail([](const MyException& e) {
|
||||||
|
return QtConcurrent::run([=]() {
|
||||||
|
return QString("foo") + e.error();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QCOMPARE(input.isRejected(), true);
|
||||||
|
QCOMPARE(output.isPending(), true);
|
||||||
|
|
||||||
|
output.then([&](const QString& res) {
|
||||||
|
result = res;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(output.isFulfilled(), true);
|
||||||
|
QCOMPARE(result, QString("foobar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::fail_void()
|
||||||
|
{
|
||||||
|
QString result;
|
||||||
|
auto input = QPromise<void>::reject(MyException("bar"));
|
||||||
|
auto output = input.fail([&](const MyException& e) {
|
||||||
|
return QtConcurrent::run([&]() {
|
||||||
|
result = e.error();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QCOMPARE(input.isRejected(), true);
|
||||||
|
QCOMPARE(output.isPending(), true);
|
||||||
|
|
||||||
|
output.then([&]() {
|
||||||
|
result = result.prepend("foo");
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(output.isFulfilled(), true);
|
||||||
|
QCOMPARE(result, QString("foobar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::finally()
|
||||||
|
{
|
||||||
|
auto input = qPromise(42);
|
||||||
|
auto output = input.finally([]() {
|
||||||
|
return QtConcurrent::run([]() {
|
||||||
|
return QString("foo");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Q_STATIC_ASSERT((std::is_same<decltype(output), QPromise<int> >::value));
|
||||||
|
|
||||||
|
QCOMPARE(input.isFulfilled(), true);
|
||||||
|
QCOMPARE(output.isPending(), true);
|
||||||
|
|
||||||
|
int value = -1;
|
||||||
|
output.then([&](int res) {
|
||||||
|
value = res;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(output.isFulfilled(), true);
|
||||||
|
QCOMPARE(value, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_future::finallyRejected()
|
||||||
|
{
|
||||||
|
auto input = qPromise(42);
|
||||||
|
auto output = input.finally([]() {
|
||||||
|
return QtConcurrent::run([]() {
|
||||||
|
throw MyException("foo");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Q_STATIC_ASSERT((std::is_same<decltype(output), QPromise<int> >::value));
|
||||||
|
|
||||||
|
QCOMPARE(input.isFulfilled(), true);
|
||||||
|
QCOMPARE(output.isPending(), true);
|
||||||
|
|
||||||
|
QString error;
|
||||||
|
output.fail([&](const MyException& e) {
|
||||||
|
error = e.error();
|
||||||
|
return -1;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QCOMPARE(output.isRejected(), true);
|
||||||
|
QCOMPARE(error, QString("foo"));
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user