Initial implementation

This commit is contained in:
Simon Brunel 2017-05-14 19:03:01 +02:00
commit 6a642446df
21 changed files with 1433 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/*.user

9
LICENSE.md Normal file
View File

@ -0,0 +1,9 @@
**The MIT License (MIT)**
Copyright (c) 2017 Simon Brunel
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

7
README.md Normal file
View File

@ -0,0 +1,7 @@
<a href="https://promisesaplus.com/" title="Promises/A+ 1.1"><img src="http://promisesaplus.com/assets/logo-small.png" alt="Promises/A+" align="right"/></a>
# QtPromise
[Promises/A+](https://promisesaplus.com/) implementation for [Qt](https://www.qt.io/).
## License
QtPromise is available under the [MIT license](LICENSE.md).

View File

@ -0,0 +1 @@
include(../../qtpromise.pri)

2
qtpromise.pri Normal file
View File

@ -0,0 +1,2 @@
INCLUDEPATH += $$PWD/src
DEPENDPATH += $$PWD/src

10
qtpromise.pro Normal file
View File

@ -0,0 +1,10 @@
TEMPLATE = subdirs
SUBDIRS = \
src \
tests
tests.depends = src
OTHER_FILES = \
package/features/*.prf \
qtpromise.pri

107
src/qtpromise/qpromise.h Normal file
View File

@ -0,0 +1,107 @@
#ifndef _QTPROMISE_QPROMISE_H
#define _QTPROMISE_QPROMISE_H
// QPromise
#include "qpromise_p.h"
#include "qpromiseglobal.h"
// Qt
#include <QExplicitlySharedDataPointer>
#include <QVariant>
namespace QtPromise {
using namespace QtPromisePrivate;
template <typename T>
class QPromiseBase
{
public:
using Type = T;
QPromiseBase() : m_d(new QPromiseData<T>()) { }
virtual ~QPromiseBase() { }
bool isFulfilled() const { return m_d->resolved && !m_d->rejected; }
bool isRejected() const { return m_d->resolved && m_d->rejected; }
bool isPending() const { return !m_d->resolved; }
template <typename TFulfilled, typename TRejected = std::nullptr_t>
inline auto then(TFulfilled fulfilled, TRejected rejected = nullptr)
-> typename QPromiseHandler<T, TFulfilled>::Promise;
template <typename TRejected>
inline auto fail(TRejected rejected)
-> typename QPromiseHandler<T, std::nullptr_t>::Promise;
template <typename TError>
inline QPromise<T> reject(const TError& error);
inline QPromise<T> wait() const;
protected:
QExplicitlySharedDataPointer<QPromiseData<T> > m_d;
virtual void notify(const typename QPromiseData<T>::HandlerList& handlers) const = 0;
inline void dispatch();
};
template <typename T>
class QPromise: public QPromiseBase<T>
{
public:
QPromise() : QPromiseBase<T>() {}
template <typename THandler>
inline QPromise<T> finally(THandler handler);
inline QPromise<T> fulfill(const T& value);
public: // STATIC
inline static QPromise<QVector<T> > all(const QVector<QPromise<T> >& promises);
protected:
inline void notify(const typename QPromiseData<T>::HandlerList& handlers) const Q_DECL_OVERRIDE;
private:
friend class QPromiseBase<T>;
QPromise(const QPromiseBase<T>& p) : QPromiseBase<T>(p) { }
};
template <>
class QPromise<void>: public QPromiseBase<void>
{
public:
QPromise(): QPromiseBase<void>() {}
template <typename THandler>
inline QPromise<void> finally(THandler handler);
inline QPromise<void> fulfill();
public: // STATIC
inline static QPromise<void> all(const QVector<QPromise<void> >& promises);
protected:
inline void notify(const typename QPromiseData<void>::HandlerList& handlers) const Q_DECL_OVERRIDE;
private:
friend class QPromiseBase<void>;
QPromise(const QPromiseBase<void>& p) : QPromiseBase<void>(p) { }
};
using QVariantPromise = QtPromise::QPromise<QVariant>;
} // namespace QtPromise
Q_DECLARE_TYPEINFO(QtPromise::QVariantPromise, Q_MOVABLE_TYPE);
Q_DECLARE_METATYPE(QtPromise::QVariantPromise)
#include "qpromise.inl"
#include "qpromise_qfuture.inl"
#endif // ifndef _QTPROMISE_QPROMISE_H

228
src/qtpromise/qpromise.inl Normal file
View File

@ -0,0 +1,228 @@
// Qt
#include <QCoreApplication>
#include <QSharedPointer>
namespace QtPromise {
template <typename T>
template <typename TFulfilled, typename TRejected>
inline auto QPromiseBase<T>::then(TFulfilled fulfilled, TRejected rejected)
-> typename QPromiseHandler<T, TFulfilled>::Promise
{
typename QPromiseHandler<T, TFulfilled>::Promise next;
m_d->handlers << QPromiseHandler<T, TFulfilled>::create(next, fulfilled);
m_d->catchers << QPromiseCatcher<T, TRejected>::create(next, rejected);
if (m_d->resolved) {
dispatch();
}
return next;
}
template <typename T>
template <typename TRejected>
inline auto QPromiseBase<T>::fail(TRejected rejected)
-> typename QPromiseHandler<T, std::nullptr_t>::Promise
{
return then(nullptr, rejected);
}
template <typename T>
template <typename TError>
inline QPromise<T> QPromiseBase<T>::reject(const TError& error)
{
if (!m_d->resolved) {
m_d->error = QtPromisePrivate::to_exception_ptr(error);
m_d->rejected = true;
m_d->resolved = true;
dispatch();
}
return *this;
}
template <typename T>
inline QPromise<T> QPromiseBase<T>::wait() const
{
// @TODO wait timeout + global timeout
while (!m_d->resolved) {
QCoreApplication::processEvents(QEventLoop::AllEvents);
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
}
return *this;
}
template <typename T>
inline void QPromiseBase<T>::dispatch()
{
Q_ASSERT(m_d->resolved);
typename QPromiseData<T>::HandlerList handlers;
typename QPromiseData<T>::CatcherList catchers;
handlers.swap(m_d->handlers);
catchers.swap(m_d->catchers);
if (m_d->rejected) {
const std::exception_ptr error = m_d->error;
qtpromise_defer([catchers, error]() {
for (auto catcher: catchers) {
catcher(error);
}
});
} else {
notify(handlers);
}
}
template <typename T>
template <typename THandler>
inline QPromise<T> QPromise<T>::finally(THandler handler)
{
return this->then([=](const T& res) {
return QPromise<void>().fulfill().then(handler).then([=](){
return res;
});
}, [=]() {
const auto exception = std::current_exception();
return QPromise<void>().fulfill().then(handler).then([=](){
std::rethrow_exception(exception);
return T();
});
});
}
template <typename T>
inline QPromise<T> QPromise<T>::fulfill(const T& value)
{
if (!this->m_d->resolved) {
this->m_d->rejected = false;
this->m_d->resolved = true;
this->m_d->value = value;
this->dispatch();
}
return *this;
}
template <typename T>
inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promises)
{
QPromise<QVector<T> > next;
const int count = promises.size();
if (count == 0) {
return next.fulfill({});
}
QSharedPointer<int> remaining(new int(count));
QSharedPointer<QVector<T> > results(new QVector<T>(count));
for (int i=0; i<count; ++i) {
QPromise<T>(promises[i]).then([=](const T& res) mutable {
if (next.isPending()) {
(*results)[i] = res;
if (--(*remaining) == 0) {
next.fulfill(*results);
}
}
}, [=]() mutable {
if (next.isPending()) {
next.reject(std::current_exception());
}
});
}
return next;
}
template <typename T>
inline void QPromise<T>::notify(const typename QPromiseData<T>::HandlerList& handlers) const
{
const T value = this->m_d->value;
qtpromise_defer([handlers, value]() {
for (auto handler: handlers) {
handler(value);
}
});
}
template <typename THandler>
inline QPromise<void> QPromise<void>::finally(THandler handler)
{
return this->then([=]() {
return QPromise<void>().fulfill().then(handler).then([](){});
}, [=]() {
const auto exception = std::current_exception();
return QPromise<void>().fulfill().then(handler).then([=](){
std::rethrow_exception(exception);
});
});
}
inline QPromise<void> QPromise<void>::fulfill()
{
if (!m_d->resolved) {
m_d->rejected = false;
m_d->resolved = true;
dispatch();
}
return *this;
}
inline QPromise<void> QPromise<void>::all(const QVector<QPromise<void> >& promises)
{
QPromise<void> next;
QSharedPointer<int> remaining(new int(promises.size()));
for (const auto& promise: promises) {
QPromise<void>(promise).then([=]() mutable {
if (next.isPending()) {
if (--(*remaining) == 0) {
next.fulfill();
}
}
}, [=]() mutable {
if (next.isPending()) {
next.reject(std::current_exception());
}
});
}
return next;
}
inline void QPromise<void>::notify(const typename QPromiseData<void>::HandlerList& handlers) const
{
qtpromise_defer([handlers]() {
for (const auto& handler: handlers) {
handler();
}
});
}
// Helpers
template <typename T>
QPromise<T> qPromise(const T& value)
{
return QPromise<T>().fulfill(value);
}
template <typename T>
QPromise<QVector<T> > qPromiseAll(const QVector<QPromise<T> >& promises)
{
return QPromise<T>::all(promises);
}
QPromise<void> qPromiseAll(const QVector<QPromise<void> >& promises)
{
return QPromise<void>::all(promises);
}
} // namespace QtPromise

318
src/qtpromise/qpromise_p.h Normal file
View File

@ -0,0 +1,318 @@
#ifndef _QTPROMISE_QPROMISE_P_H
#define _QTPROMISE_QPROMISE_P_H
// QPromise
#include "qpromiseglobal.h"
// Qt
#include <QCoreApplication>
#include <QTimer>
#include <QSharedData>
#include <QVector>
// STL
#include <functional>
namespace QtPromise {
template <typename T = void>
class QPromise;
}
namespace QtPromisePrivate {
using namespace QtPromise;
template <typename F>
inline void qtpromise_defer(F f)
{
QTimer::singleShot(0, f);
}
template <typename T>
struct QPromiseDeduce
{
using Type = QPromise<Unqualified<T> >;
};
template <typename T>
struct QPromiseDeduce<QPromise<T> >
: public QPromiseDeduce<T>
{ };
template <typename T, typename TPromise = typename QPromiseDeduce<T>::Type>
struct QPromiseFulfill
{
static void call(TPromise next, const T& value)
{
next.fulfill(value);
}
};
template <typename T>
struct QPromiseFulfill<QPromise<T>, QPromise<T> >
{
static void call(QPromise<T> next, QPromise<T> promise)
{
promise.then(
[=](const T& value) mutable {
next.fulfill(value);
},
[=]() mutable {
next.reject(std::current_exception());
});
}
};
template <>
struct QPromiseFulfill<QPromise<void>, QPromise<void> >
{
template <typename TPromise>
static void call(TPromise next, TPromise promise)
{
promise.then(
[=]() mutable {
next.fulfill();
},
[=]() mutable {
next.reject(std::current_exception());
});
}
};
template <typename T, typename TRes>
struct QPromiseDispatch
{
using Promise = typename QPromiseDeduce<TRes>::Type;
using ResType = Unqualified<TRes>;
template <typename TPromise, typename THandler>
static void call(TPromise next, THandler handler, const T& value)
{
ResType res;
try {
res = handler(value);
} catch (...) {
next.reject(std::current_exception());
return;
}
QPromiseFulfill<ResType>::call(next, res);
}
};
template <typename T>
struct QPromiseDispatch<T, void>
{
using Promise = QPromise<void>;
template <typename TPromise, typename THandler>
static void call(TPromise next, THandler handler, const T& value)
{
try {
handler(value);
} catch (...) {
next.reject(std::current_exception());
return;
}
next.fulfill();
}
};
template <typename TRes>
struct QPromiseDispatch<void, TRes>
{
using Promise = typename QPromiseDeduce<TRes>::Type;
using ResType = Unqualified<TRes>;
template <typename TPromise, typename THandler>
static void call(TPromise next, THandler handler)
{
ResType res;
try {
res = handler();
} catch (...) {
next.reject(std::current_exception());
return;
}
QPromiseFulfill<ResType>::call(next, res);
}
};
template <>
struct QPromiseDispatch<void, void>
{
using Promise = QPromise<void>;
template <typename TPromise, typename THandler>
static void call(TPromise next, THandler handler)
{
try {
handler();
} catch (...) {
next.reject(std::current_exception());
return;
}
next.fulfill();
}
};
template <typename T, typename THandler, typename TArg = typename ArgsOf<THandler>::first>
struct QPromiseHandler
{
using ResType = typename std::result_of<THandler(T)>::type;
using Promise = typename QPromiseDispatch<T, ResType>::Promise;
static std::function<void(T)> create(const Promise& next, THandler handler)
{
return [=](const T& value) mutable {
QPromiseDispatch<T, ResType>::call(next, handler, value);
};
}
};
template <typename T, typename THandler>
struct QPromiseHandler<T, THandler, void>
{
using ResType = typename std::result_of<THandler()>::type;
using Promise = typename QPromiseDispatch<T, ResType>::Promise;
static std::function<void(T)> create(const Promise& next, THandler handler)
{
return [=](const T&) mutable {
QPromiseDispatch<void, ResType>::call(next, handler);
};
}
};
template <typename THandler>
struct QPromiseHandler<void, THandler, void>
{
using ResType = typename std::result_of<THandler()>::type;
using Promise = typename QPromiseDispatch<void, ResType>::Promise;
static std::function<void()> create(const Promise& next, THandler handler)
{
return [=]() {
QPromiseDispatch<void, ResType>::call(next, handler);
};
}
};
template <typename T>
struct QPromiseHandler<T, std::nullptr_t, void>
{
using Promise = QPromise<T>;
static std::function<void(T)> create(const Promise& next, std::nullptr_t)
{
return [next](const T& value) mutable {
// 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled,
// promise2 must be fulfilled with the same value as promise1.
QPromiseFulfill<T>::call(next, value);
};
}
};
template <>
struct QPromiseHandler<void, std::nullptr_t, void>
{
using Promise = QPromise<void>;
template <typename TPromise>
static std::function<void()> create(const TPromise& next, std::nullptr_t)
{
return [=]() mutable {
// 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled,
// promise2 must be fulfilled with the same value as promise1.
TPromise(next).fulfill();
};
}
};
template <typename T, typename THandler, typename TArg = typename ArgsOf<THandler>::first>
struct QPromiseCatcher
{
using Type = std::function<void(std::exception_ptr)>;
using ResType = typename std::result_of<THandler(TArg)>::type;
template <typename TPromise>
static Type create(const TPromise& next, THandler handler)
{
return [=](const std::exception_ptr& eptr) mutable {
try {
std::rethrow_exception(eptr);
} catch (const TArg& error) {
QPromiseDispatch<TArg, ResType>::call(next, handler, error);
} catch(...) {
TPromise(next).reject(std::current_exception());
}
};
}
};
template <typename T, typename THandler>
struct QPromiseCatcher<T, THandler, void>
{
using Type = std::function<void(std::exception_ptr)>;
using ResType = typename std::result_of<THandler()>::type;
template <typename TPromise>
static Type create(const TPromise& next, THandler handler)
{
return [=](const std::exception_ptr& eptr) mutable {
try {
std::rethrow_exception(eptr);
} catch (...) {
QPromiseDispatch<void, ResType>::call(next, handler);
}
};
}
};
template <typename T>
struct QPromiseCatcher<T, std::nullptr_t, void>
{
using Type = std::function<void(std::exception_ptr)>;
template <typename TPromise>
static Type create(const TPromise& next, std::nullptr_t)
{
return [=](const std::exception_ptr& eptr) mutable {
// 2.2.7.4. If onRejected is not a function and promise1 is rejected,
// promise2 must be rejected with the same reason as promise1
TPromise(next).reject(eptr);
};
}
};
template <class T>
struct QPromiseDataBase: public QSharedData
{
using ErrorType = std::exception_ptr;
using CatcherList = QVector<std::function<void(ErrorType)> >;
bool resolved;
bool rejected;
ErrorType error;
CatcherList catchers;
};
template <typename T>
struct QPromiseData: QPromiseDataBase<T>
{
using HandlerList = QVector<std::function<void(T)> >;
HandlerList handlers;
T value;
};
template <>
struct QPromiseData<void>: QPromiseDataBase<void>
{
using HandlerList = QVector<std::function<void()> >;
HandlerList handlers;
};
} // namespace QtPromise
#endif // ifndef _QTPROMISE_QPROMISE_H

View File

@ -0,0 +1,79 @@
#include <QFutureWatcher>
#include <QFuture>
namespace QtPromisePrivate {
template <typename T>
struct QPromiseDeduce<QFuture<T> >
: public QPromiseDeduce<T>
{ };
template <typename T>
struct QPromiseFulfill<QFuture<T>, QPromise<T> >
{
static void call(QPromise<T> next, const QFuture<T>& future)
{
using Watcher = QFutureWatcher<T>;
Watcher* watcher = new Watcher();
QObject::connect(
watcher, &Watcher::finished,
[next, watcher]() mutable {
T res;
try {
res = watcher->result();
} catch(...) {
next.reject(std::current_exception());
}
watcher->deleteLater();
if (next.isPending()) {
QPromiseFulfill<T>::call(next, res);
}
});
watcher->setFuture(future);
}
};
template <>
struct QPromiseFulfill<QFuture<void>, QPromise<void> >
{
static void call(QPromise<void> next, const QFuture<void>& future)
{
using Watcher = QFutureWatcher<void>;
Watcher* watcher = new Watcher();
QObject::connect(
watcher, &Watcher::finished,
[next, watcher]() mutable {
try {
// let's rethrown possibe exception
watcher->waitForFinished();
} catch(...) {
next.reject(std::current_exception());
}
watcher->deleteLater();
if (next.isPending()) {
next.fulfill();
}
});
watcher->setFuture(future);
}
};
} // namespace QtPromisePrivate
namespace QtPromise {
template <typename T>
QPromise<T> qPromise(const QFuture<T>& future)
{
QPromise<T> next;
QPromiseFulfill<QFuture<T> >::call(next, future);
return next;
}
} // namespace QtPromise

View File

@ -0,0 +1,124 @@
#ifndef _QTPROMISE_QPROMISEGLOBAL_H
#define _QTPROMISE_QPROMISEGLOBAL_H
// QtCore
#include <QtGlobal>
// STL
#include <functional>
namespace QtPromisePrivate
{
// https://rmf.io/cxx11/even-more-traits#unqualified_types
template <typename T>
using Unqualified = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
/*!
* \struct ArgsOf
* http://stackoverflow.com/a/7943765
* http://stackoverflow.com/a/27885283
*/
template <typename... Args>
struct ArgsTraits
{
using types = std::tuple<Args...>;
using first = typename std::tuple_element<0, types>::type;
};
template <>
struct ArgsTraits<>
{
using types = std::tuple<>;
using first = void;
};
template <typename T>
struct ArgsOf : public ArgsOf<decltype(&T::operator())>
{ };
template <>
struct ArgsOf<std::nullptr_t> : public ArgsTraits<>
{ };
template <typename TReturn, typename... Args>
struct ArgsOf<TReturn(Args...)> : public ArgsTraits<Args...>
{ };
template <typename TReturn, typename... Args>
struct ArgsOf<TReturn(*)(Args...)> : public ArgsTraits<Args...>
{ };
template <typename T, typename TReturn, typename... Args>
struct ArgsOf<TReturn(T::*)(Args...)> : public ArgsTraits<Args...>
{ };
template <typename T, typename TReturn, typename... Args>
struct ArgsOf<TReturn(T::*)(Args...) const> : public ArgsTraits<Args...>
{ };
template <typename T, typename TReturn, typename... Args>
struct ArgsOf<TReturn(T::*)(Args...) volatile> : public ArgsTraits<Args...>
{ };
template <typename T, typename TReturn, typename... Args>
struct ArgsOf<TReturn(T::*)(Args...) const volatile> : public ArgsTraits<Args...>
{ };
template <typename T>
struct ArgsOf<std::function<T> > : public ArgsOf<T>
{ };
template <typename T>
struct ArgsOf<T&> : public ArgsOf<T>
{ };
template <typename T>
struct ArgsOf<const T&> : public ArgsOf<T>
{ };
template <typename T>
struct ArgsOf<volatile T&> : public ArgsOf<T>
{ };
template <typename T>
struct ArgsOf<const volatile T&> : public ArgsOf<T>
{ };
template <typename T>
struct ArgsOf<T&&> : public ArgsOf<T>
{ };
template <typename T>
struct ArgsOf<const T&&> : public ArgsOf<T>
{ };
template <typename T>
struct ArgsOf<volatile T&&> : public ArgsOf<T>
{ };
template <typename T>
struct ArgsOf<const volatile T&&> : public ArgsOf<T>
{ };
/*!
* \fn to_exception_ptr
*/
template <typename T>
std::exception_ptr to_exception_ptr(const T& value)
{
try {
throw value;
} catch(...) {
return std::current_exception();
}
}
template <>
std::exception_ptr to_exception_ptr(const std::exception_ptr& value)
{
return value;
}
} // namespace QtPromisePrivate
#endif // ifndef _QTPROMISE_QPROMISEGLOBAL_H

View File

@ -0,0 +1,6 @@
HEADERS += \
$$PWD/qpromise.h \
$$PWD/qpromise.inl \
$$PWD/qpromise_p.h \
$$PWD/qpromise_qfuture.inl \
$$PWD/qpromiseglobal.h

View File

@ -0,0 +1,5 @@
TEMPLATE = lib
CONFIG += c++11 qt thread warn_on
DEFINES += QT_DEPRECATED_WARNINGS
include(qtpromise.pri)

2
src/src.pro Normal file
View File

@ -0,0 +1,2 @@
TEMPLATE = subdirs
SUBDIRS = qtpromise

4
tests/auto/auto.pro Normal file
View File

@ -0,0 +1,4 @@
TEMPLATE = subdirs
SUBDIRS += \
qpromise \
requirements

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise
SOURCES += $$PWD/tst_qpromise.cpp
include(../tests.pri)

View File

@ -0,0 +1,199 @@
// QtPromise
#include <qtpromise/qpromise.h>
// Qt
#include <QtTest>
using namespace QtPromise;
static const int ASYNC_DELAY = 256;
class tst_qpromise: public QObject
{
Q_OBJECT
private Q_SLOTS:
void finallyReturns();
void finallyThrows();
void finallyDelayedFulfilled();
void finallyDelayedRejected();
}; // class tst_qpromise
QTEST_MAIN(tst_qpromise)
#include "tst_qpromise.moc"
void tst_qpromise::finallyReturns()
{
{
QPromise<int> p;
QVector<int> values;
auto next = p.finally([&values]() {
values << 8;
return 16;
});
p.fulfill(42);
next.then([&values](int r) {
values << r;
}).wait();
QVERIFY(p.isFulfilled());
QVERIFY(next.isFulfilled());
QCOMPARE(values, QVector<int>({8, 42}));
}
{
QPromise<int> p;
QVector<int> values;
auto next = p.finally([&values]() {
values << 8;
return 16;
});
p.reject(QString("foo"));
next.then([&values](int r) {
values << r;
}).wait();
QVERIFY(p.isRejected());
QVERIFY(next.isRejected());
QCOMPARE(values, QVector<int>({8}));
}
}
void tst_qpromise::finallyThrows()
{
{
QPromise<int> p;
QString error;
auto next = p.finally([]() {
throw QString("bar");
});
p.fulfill(42);
next.fail([&error](const QString& err) {
error = err;
return 0;
}).wait();
QVERIFY(p.isFulfilled());
QVERIFY(next.isRejected());
QCOMPARE(error, QString("bar"));
}
{
QPromise<int> p;
QString error;
auto next = p.finally([]() {
throw QString("bar");
});
p.reject(QString("foo"));
next.fail([&error](const QString& err) {
error = err;
return 0;
}).wait();
QVERIFY(p.isRejected());
QVERIFY(next.isRejected());
QCOMPARE(error, QString("bar"));
}
}
void tst_qpromise::finallyDelayedFulfilled()
{
{
QPromise<int> p0;
QVector<int> values;
auto next = p0.finally([&values]() {
QPromise<int> p1;
QTimer::singleShot(ASYNC_DELAY, [p1, &values]() mutable {
values << 64;
p1.fulfill(16);
});
values << 8;
return p1;
});
p0.fulfill(42);
next.then([&values](int r) {
values << r;
}).wait();
QVERIFY(p0.isFulfilled());
QVERIFY(next.isFulfilled());
QCOMPARE(values, QVector<int>({8, 64, 42}));
}
{
QPromise<int> p0;
QVector<int> values;
auto next = p0.finally([&values]() {
QPromise<int> p1;
QTimer::singleShot(ASYNC_DELAY, [p1, &values]() mutable {
values << 64;
p1.fulfill(16);
});
values << 8;
return p1;
});
p0.reject(QString("foo"));
next.then([&values](int r) {
values << r;
}).wait();
QVERIFY(p0.isRejected());
QVERIFY(next.isRejected());
QCOMPARE(values, QVector<int>({8, 64}));
}
}
void tst_qpromise::finallyDelayedRejected()
{
{
QPromise<int> p0;
QString error;
auto next = p0.finally([]() {
QPromise<int> p1;
QTimer::singleShot(ASYNC_DELAY, [p1]() mutable {
p1.reject(QString("bar"));
});
return p1;
});
p0.fulfill(42);
next.fail([&error](const QString& err) {
error = err;
return 0;
}).wait();
QVERIFY(p0.isFulfilled());
QVERIFY(next.isRejected());
QCOMPARE(error, QString("bar"));
}
{
QPromise<int> p0;
QString error;
auto next = p0.finally([]() {
QPromise<int> p1;
QTimer::singleShot(ASYNC_DELAY, [p1]() mutable {
p1.reject(QString("bar"));
});
return p1;
});
p0.reject(QString("foo"));
next.fail([&error](const QString& err) {
error = err;
return 0;
}).wait();
QVERIFY(p0.isRejected());
QVERIFY(next.isRejected());
QCOMPARE(error, QString("bar"));
}
}

View File

@ -0,0 +1,4 @@
TARGET = tst_requirements
SOURCES += $$PWD/tst_requirements.cpp
include(../tests.pri)

View File

@ -0,0 +1,315 @@
// QtPromise
#include <qtpromise/qpromise.h>
// Qt
#include <QtTest>
using namespace QtPromise;
// https://promisesaplus.com/#requirements
class tst_requirements: public QObject
{
Q_OBJECT
private Q_SLOTS:
// 2.1. Promise States
void statePending(); // 2.1.1.
void stateFulfilled(); // 2.1.2.
void stateRejected(); // 2.1.3.
// 2.2. The then Method
void thenArguments(); // 2.2.1.
void thenOnFulfilled(); // 2.2.2.
void thenOnRejected(); // 2.2.3.
void thenAsynchronous(); // 2.2.4.
// 2.2.5. NOT APPLICABLE
void thenMultipleCalls(); // 2.2.6.
void thenHandlers(); // 2.2.7.
// 2.3. The Promise Resolution Procedure
}; // class tst_requirements
QTEST_MAIN(tst_requirements)
#include "tst_requirements.moc"
void tst_requirements::statePending()
{
// 2.1.1. When pending, a promise:
{
QPromise<> p;
QVERIFY(p.isPending());
QVERIFY(!p.isFulfilled());
QVERIFY(!p.isRejected());
}
// 2.1.1.1. may transition to either the fulfilled state
{
QPromise<> p;
p.fulfill();
QVERIFY(!p.isPending());
QVERIFY(p.isFulfilled());
}
// 2.1.1.1. ... or the rejected state
{
QPromise<> p;
p.reject("foo");
QVERIFY(!p.isPending());
QVERIFY(p.isRejected());
}
}
void tst_requirements::stateFulfilled()
{
QVector<int> values;
auto log_value = [&values](int res) { values << res; };
QPromise<int> p;
QVERIFY(p.isPending());
// 2.1.2. When fulfilled, a promise:
p.fulfill(42).then(log_value).wait();
QVERIFY(p.isFulfilled());
QVERIFY(!p.isRejected());
// 2.1.2.1. must not transition to any other state.
p.reject("foo").then(log_value).wait();
QVERIFY(p.isFulfilled());
QVERIFY(!p.isRejected());
// 2.1.2.2. must have a value, which must not change.
p.fulfill(51).then(log_value).wait();
QVERIFY(p.isFulfilled());
QVERIFY(!p.isRejected());
QCOMPARE(values, QVector<int>({42, 42, 42}));
}
void tst_requirements::stateRejected()
{
QStringList errors;
auto log_error = [&errors](const QString& err) { errors << err; return -1; };
QPromise<int> p;
QVERIFY(p.isPending());
// 2.1.3 When rejected, a promise:
p.reject(QString("foo")).then(nullptr, log_error).wait();
QVERIFY(!p.isFulfilled());
QVERIFY(p.isRejected());
// 2.1.3.1. must not transition to any other state.
p.fulfill(51).then(nullptr, log_error).wait();
QVERIFY(!p.isFulfilled());
QVERIFY(p.isRejected());
// 2.1.3.2. must have a reason, which must not change.
p.reject(QString("bar")).then(nullptr, log_error).wait();
QVERIFY(!p.isFulfilled());
QVERIFY(p.isRejected());
QCOMPARE(errors, QStringList({"foo", "foo", "foo"}));
}
void tst_requirements::thenArguments()
{
// Both onFulfilled and onRejected are given
{
int value = 0;
QPromise<int> p;
p.fulfill(42).then([&value](int res) { value = res; }, [](const QString&){}).wait();
QVERIFY(p.isFulfilled());
QCOMPARE(value, 42);
}
{
QString error;
QPromise<int> p;
p.reject(QString("foo")).then([](int) {}, [&error](const QString& err){ error = err; }).wait();
QVERIFY(p.isRejected());
QCOMPARE(error, QString("foo"));
}
// 2.2.1. onFulfilled is an optional arguments:
{
QString error;
QPromise<int> p;
p.reject(QString("foo")).then(nullptr, [&error](const QString& err){ error = err; return 42; }).wait();
QVERIFY(p.isRejected());
QCOMPARE(error, QString("foo"));
}
// 2.2.1. onRejected is an optional arguments:
{
int value = 0;
QPromise<int> p;
p.fulfill(42).then([&value](int res) { value = res; }).wait();
QVERIFY(p.isFulfilled());
QCOMPARE(value, 42);
}
// 2.2.1.1. If onFulfilled is not a function, it must be ignored.
// 2.2.1.2. If onRejected is not a function, it must be ignored.
// -> compilation fails on type check.
}
void tst_requirements::thenOnFulfilled()
{
// 2.2.2. If onFulfilled is a function:
QPromise<int> p0;
QVector<int> values;
auto p1 = p0.then([&values](int res) { values << res; }); // @TODO .wait(10)
// 2.2.2.2. it must not be called before promise is fulfilled.
QVERIFY(p0.isPending());
QVERIFY(p1.isPending());
QVERIFY(values.isEmpty());
// 2.2.2.1. it must be called after promise is fulfilled,
// with promises value as its first argument.
p0.fulfill(42);
p1.wait();
QVERIFY(p0.isFulfilled());
QVERIFY(p1.isFulfilled());
QCOMPARE(values, QVector<int>({42}));
// 2.2.2.3. it must not be called more than once
p0.fulfill(12);
p1.wait();
QCOMPARE(values, QVector<int>({42}));
}
void tst_requirements::thenOnRejected()
{
// 2.2.3. If onRejected is a function:
QPromise<> p0;
QStringList errors;
auto p1 = p0.then(nullptr, [&errors](const QString& err) { errors << err; }); // @TODO .wait(10)
// 2.2.3.2. it must not be called before promise is rejected.
QVERIFY(p0.isPending());
QVERIFY(p1.isPending());
QVERIFY(errors.isEmpty());
// 2.2.3.1. it must be called after promise is rejected,
// with promises reason as its first argument.
p0.reject(QString("foo"));
p1.wait();
QVERIFY(p0.isRejected());
QVERIFY(p1.isFulfilled());
QCOMPARE(errors, QStringList({"foo"}));
// 2.2.2.3. it must not be called more than once.
p0.reject(12);
p1.wait();
QCOMPARE(errors, QStringList({"foo"}));
}
void tst_requirements::thenAsynchronous()
{
// 2.2.4. onFulfilled or onRejected must not be called until the execution context
// stack contains only platform code (ie. executed asynchronously, after the event
// loop turn in which then is called, and with a fresh stack).
int value = 0;
QPromise<int> p0;
p0.fulfill(42);
QVERIFY(p0.isFulfilled());
auto p1 = p0.then([&value](int res){ value = res; });
QVERIFY(p1.isPending());
QCOMPARE(value, 0);
p1.wait();
QVERIFY(p1.isFulfilled());
QCOMPARE(value, 42);
}
void tst_requirements::thenMultipleCalls()
{
// 2.2.6. then may be called multiple times on the same promise:
// 2.2.6.1. If/when promise is fulfilled, all respective onFulfilled callbacks
// must execute in the order of their originating calls to then:
{
QPromise<int> p;
QVector<int> values;
auto all = qPromiseAll(QVector<QPromise<void> >{
p.then([&values](int r) { values << r + 1; }),
p.then([&values](int r) { values << r + 2; }),
p.then([&values](int r) { values << r + 3; })
});
p.fulfill(42);
all.wait();
QCOMPARE(values, QVector<int>({43, 44, 45}));
}
// 2.2.6.2. If/when promise is rejected, all respective onRejected callbacks
// must execute in the order of their originating calls to then:
{
QPromise<int> p;
QVector<int> values;
auto all = qPromiseAll(QVector<QPromise<int> >{
p.then(nullptr, [&values](int r) { values << r + 1; return r + 1; }),
p.then(nullptr, [&values](int r) { values << r + 2; return r + 2; }),
p.then(nullptr, [&values](int r) { values << r + 3; return r + 3; })
});
p.reject(8);
all.wait();
QCOMPARE(values, QVector<int>({9, 10, 11}));
}
}
void tst_requirements::thenHandlers()
{
// 2.2.7. then must return a promise: p2 = p1.then(onFulfilled, onRejected);
{
QPromise<int> p1;
auto handler = [](){ return 42; };
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(handler, nullptr)), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(nullptr, handler)), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(handler, handler)), QPromise<int> >::value));
}
// 2.2.7.1. If either onFulfilled or onRejected returns a value x, run the
// Promise Resolution Procedure [[Resolve]](p2, x) -> See 2.3.
// 2.2.7.2. If either onFulfilled or onRejected throws an exception e,
// p2 must be rejected with e as the reason.
{
QString reason;
QPromise<int> p1;
auto p2 = p1.fulfill(42).then([](){ throw QString("foo"); });
p2.then(nullptr, [&reason](const QString& e) { reason = e; }).wait();
QVERIFY(p2.isRejected());
QCOMPARE(reason, QString("foo"));
}
{
QString reason;
QPromise<int> p1;
auto p2 = p1.reject(QString("foo")).then(nullptr, [](){ throw QString("bar"); return 42; });
p2.then(nullptr, [&reason](const QString& e) { reason = e; return 0; }).wait();
QVERIFY(p2.isRejected());
QCOMPARE(reason, QString("bar"));
}
// 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled,
// p2 must be fulfilled with the same value as promise1
{
QString value;
QPromise<QString> p1;
auto p2 = p1.fulfill("42").then(nullptr, [](){ return QString(); });
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<QString> >::value));
p2.then([&value](const QString& e) { value = e; }).wait();
QVERIFY(p2.isFulfilled());
QCOMPARE(value, QString("42"));
}
}

6
tests/auto/tests.pri Normal file
View File

@ -0,0 +1,6 @@
TEMPLATE = app
CONFIG += testcase
QT += testlib concurrent
DEFINES += QT_DEPRECATED_WARNINGS
include(../../qtpromise.pri)

2
tests/tests.pro Normal file
View File

@ -0,0 +1,2 @@
TEMPLATE = subdirs
SUBDIRS += auto