mirror of
https://github.com/simonbrunel/qtpromise.git
synced 2024-11-21 18:24:29 +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
|
||||
#define _QTPROMISE_QPROMISEFUTURE_P_H
|
||||
|
||||
// Qt
|
||||
#include <QFutureWatcher>
|
||||
#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 {
|
||||
|
||||
template <typename T>
|
||||
@ -24,8 +39,18 @@ struct PromiseFulfill<QFuture<T> >
|
||||
Watcher* watcher = new Watcher();
|
||||
QObject::connect(watcher, &Watcher::finished, [=]() mutable {
|
||||
try {
|
||||
T res = watcher->result();
|
||||
PromiseFulfill<T>::call(res, resolve, reject);
|
||||
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();
|
||||
PromiseFulfill<T>::call(res, resolve, reject);
|
||||
}
|
||||
} catch(...) {
|
||||
reject(std::current_exception());
|
||||
}
|
||||
@ -50,9 +75,13 @@ struct PromiseFulfill<QFuture<void> >
|
||||
Watcher* watcher = new Watcher();
|
||||
QObject::connect(watcher, &Watcher::finished, [=]() mutable {
|
||||
try {
|
||||
// let's rethrown possibe exception
|
||||
watcher->waitForFinished();
|
||||
resolve();
|
||||
if (watcher->isCanceled()) {
|
||||
// let's rethrown potential exception
|
||||
watcher->waitForFinished();
|
||||
reject(QtPromise::QPromiseCanceledException());
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
} catch(...) {
|
||||
reject(std::current_exception());
|
||||
}
|
||||
@ -64,6 +93,6 @@ struct PromiseFulfill<QFuture<void> >
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QtPromise
|
||||
} // namespace QtPromisePrivate
|
||||
|
||||
#endif // _QTPROMISE_QPROMISEFUTURE_P_H
|
||||
|
@ -1,5 +1,6 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS += \
|
||||
future \
|
||||
helpers \
|
||||
qpromise \
|
||||
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