19 Commits

Author SHA1 Message Date
5523597e7c - wip -
- wip -
2018-05-10 11:51:54 +02:00
26a2110a14 Fix GCC -Wold-style-cast warnings
Explicitly enable -Wold-style-cast and make all warnings into errors in tests (-Werror on GCC, -WX on MSVC).
2018-05-10 11:51:17 +02:00
fa987a5044 Cleanup promise captured in resolve/reject
Make sure that QPromiseResolve and QPromiseReject release their associated promise as soon as one of them is resolved or rejected, ensuring that the promise data is cleaned up once resolved (that fixes cases where one or both of them are captured in a signal/slot connection lambda)
2018-05-10 11:51:17 +02:00
7b0cba5b9d Allow QSharedPointer as rejection reason
Embed the promise fulfillment value and rejection reason in respectively PromiseValue and PromiseError private wrappers, both storing the data in a shared pointer (QPromiseError is now deprecated).
2018-05-10 11:51:14 +02:00
2c8ed6e676 Remove extra space between closing angle brackets
The extra space between template closing angle brackets is not anymore required in C++11 (https://en.wikipedia.org/wiki/C%2B%2B11#Right_angle_bracket)
2018-05-10 09:21:21 +02:00
d128a5fa8d Clarify QPromise::all fulfillment values order 2018-04-30 19:05:21 +02:00
dcbb2ef860 Fix clang "unused type alias 'FType'" warning 2018-04-26 16:06:10 +02:00
50bae380be Implement QPromise::tapFail(handler) 2018-03-29 09:08:19 +02:00
d279fb4391 Bump version to 0.3.0 2018-03-01 22:32:46 +01:00
313d3882d7 Fix dispatching when app (or thread) terminated
Make sure to **not** notify handlers if the captured thread doesn't exist anymore, which would potentially result in dispatching to the wrong thread (ie. nullptr == current thread). This also applies when the app is shutting down and the even loop is not anymore available. In both cases, we should not trigger any error and skip notifications.
2018-02-24 12:43:52 +01:00
f794916be6 Enhance QPromise<T>::wait documentation 2018-02-17 15:41:46 +01:00
9d2a4ca00f Add QPromise<T>::timeout documentation 2018-02-17 12:59:05 +01:00
4af2740d80 Enable QPromise<T>::resolve() by reference 2018-02-14 22:36:22 +01:00
18739bd8e0 New documentation based on GitBook CLI
Split the root README.md in multiple Markdown files (in the `docs/` folder) to make easier reading, editing and extending the documentation. An online version is also available on netlify (https://qtpromise.netlify.com). Building it requires Node.js installed, then:

- npm install -g gitbook-cli
- gitbook install ./
- gitbook build . dist/docs
2018-02-11 19:02:14 +01:00
c34316243e Make QPromise::all accept more container types
Currently, QPromise can't be added dynamically to QVector (::push_* | ::append) because it doesn't expose a default constructor. Until deciding if a default constructor should be added (private/public?), let's make the `QPromise::all` method work with any container types that are STL compatible.
2018-02-11 16:26:27 +01:00
d306423159 Add QPromise assignment and equality operators 2018-02-10 18:26:08 +01:00
d3b69f1248 Split QPromise tests per feature in separate .pro 2017-10-26 23:03:16 +02:00
36a0eed12a Move tests under their own qtpromise sub folder 2017-09-19 09:39:13 +02:00
931d5d5b13 Fix MSVC 2013 compilation issues 2017-09-04 11:17:26 +02:00
78 changed files with 3142 additions and 1498 deletions

11
.gitignore vendored
View File

@ -1,7 +1,16 @@
_book
dist
node_modules
*.gcno
*.gcda
*.moc
*.o
*.obj
*.exe
*.user
Makefile
Makefile*
moc_*.cpp
moc_*.h
coverage.info
package-lock.json
target_wrapper.bat

427
README.md
View File

@ -1,431 +1,18 @@
<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>
<a href="https://promisesaplus.com/" title="Promises/A+ 1.1"><img src="https://promisesaplus.com/assets/logo-small.png" alt="Promises/A+" align="right"/></a>
# QtPromise
[![qpm](https://img.shields.io/github/release/simonbrunel/qtpromise.svg?style=flat-square&label=qpm&colorB=4CAF50)](http://www.qpm.io/packages/com.github.simonbrunel.qtpromise/index.html) [![Travis](https://img.shields.io/travis/simonbrunel/qtpromise.svg?style=flat-square)](https://travis-ci.org/simonbrunel/qtpromise) [![coverage](https://img.shields.io/codecov/c/github/simonbrunel/qtpromise.svg?style=flat-square)](https://codecov.io/gh/simonbrunel/qtpromise)
[![qpm](https://img.shields.io/github/release/simonbrunel/qtpromise.svg?style=flat-square&label=qpm&colorB=4CAF50)](https://www.qpm.io/packages/com.github.simonbrunel.qtpromise/index.html) [![Travis](https://img.shields.io/travis/simonbrunel/qtpromise/master.svg?style=flat-square)](https://travis-ci.org/simonbrunel/qtpromise) [![coverage](https://img.shields.io/codecov/c/github/simonbrunel/qtpromise.svg?style=flat-square)](https://codecov.io/gh/simonbrunel/qtpromise)
[Promises/A+](https://promisesaplus.com/) implementation for [Qt/C++](https://www.qt.io/).
Requires [Qt 5.4](https://www.qt.io/download/) (or later) with [C++11 support enabled](https://wiki.qt.io/How_to_use_C++11_in_your_Qt_Projects).
## Getting Started
### Installation
QtPromise is a [header-only](https://en.wikipedia.org/wiki/Header-only) library, simply download the [latest release](https://github.com/simonbrunel/qtpromise/releases/latest) (or [`git submodule`](https://git-scm.com/docs/git-submodule])) and include `qtpromise.pri` from your project `.pro`.
## Documentation
### qpm
Alternatively and **only** if your project relies on [qpm](http://www.qpm.io/), you can install QtPromise as follow:
```bash
qpm install com.github.simonbrunel.qtpromise
```
### Usage
The recommended way to use QtPromise is to include the single module header:
```cpp
#include <QtPromise>
```
### Example
Let's first make the code more readable by using the library namespace:
```cpp
using namespace QtPromise;
```
This `download` function creates a [promise from callbacks](#qpromise-qpromise) which will be resolved when the network request is finished:
```cpp
QPromise<QByteArray> download(const QUrl& url)
{
return QPromise<QByteArray>([&](
const QPromiseResolve<QByteArray>& resolve,
const QPromiseReject<QByteArray>& reject) {
QNetworkReply* reply = manager->get(QNetworkRequest(url));
QObject::connect(reply, &QNetworkReply::finished, [=]() {
if (reply->error() == QNetworkReply::NoError) {
resolve(reply->readAll());
} else {
reject(reply->error());
}
reply->deleteLater();
});
});
}
```
The following method `uncompress` data in a separate thread and returns a [promise from QFuture](#qtconcurrent):
```cpp
QPromise<Entries> uncompress(const QByteArray& data)
{
return qPromise(QtConcurrent::run([](const QByteArray& data) {
Entries entries;
// {...} uncompress data and parse content.
if (error) {
throw MalformedException();
}
return entries;
}, data));
}
```
It's then easy to chain the whole asynchronous process using promises:
- initiate the promise chain by downloading a specific URL,
- [`then`](#qpromise-then) *and only if download succeeded*, uncompress received data,
- [`then`](#qpromise-then) validate and process the uncompressed entries,
- [`finally`](#qpromise-finally) perform operations whatever the process succeeded or failed,
- and handle specific errors using [`fail`](#qpromise-fail).
```cpp
download(url).then(&uncompress).then([](const Entries& entries) {
if (entries.isEmpty()) {
throw UpdateException("No entries");
}
// {...} process entries
}).finally([]() {
// {...} cleanup
}).fail([](QNetworkReply::NetworkError err) {
// {...} handle network error
}).fail([](const UpdateException& err) {
// {...} handle update error
}).fail([]() {
// {...} catch all
});
```
## QtConcurrent
QtPromise integrates with [QtConcurrent](http://doc.qt.io/qt-5/qtconcurrent-index.html) to make easy chaining QFuture with QPromise.
### <a name="qtconcurrent-convert"></a> Convert
Converting `QFuture<T>` to `QPromise<T>` is done using the [`qPromise`](#helpers-qpromise) helper:
```cpp
QFuture<int> future = QtConcurrent::run([]() {
// {...}
return 42;
});
QPromise<int> promise = qPromise(future);
```
or simply:
```cpp
auto promise = qPromise(QtConcurrent::run([]() {
// {...}
}));
```
### Chain
Returning a `QFuture<T>` in [`then`](#qpromise-then) or [`fail`](#qpromise-fail) automatically translate to `QPromise<T>`:
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
return QtConcurrent::run([]() {
// {...}
return QString("42");
});
});
// output type: QPromise<QString>
output.then([](const QString& res) {
// {...}
});
```
The `output` promise is resolved when the `QFuture` is [finished](http://doc.qt.io/qt-5/qfuture.html#isFinished).
### Error
Exceptions thrown from a QtConcurrent thread reject the associated promise with the exception as the reason. Note that if you throw an exception that is not a subclass of `QException`, the promise with be rejected with [`QUnhandledException`](http://doc.qt.io/qt-5/qunhandledexception.html#details) (this restriction only applies to exceptions thrown from a QtConcurrent thread, [read more](http://doc.qt.io/qt-5/qexception.html#details)).
```cpp
QPromise<int> promise = ...
promise.then([](int res) {
return QtConcurrent::run([]() {
// {...}
if (!success) {
throw CustomException();
}
return QString("42");
});
}).fail(const CustomException& err) {
// {...}
});
```
## Thread-Safety
QPromise is thread-safe and can be copied and accessed across different threads. QPromise relies on [explicitly data sharing](http://doc.qt.io/qt-5/qexplicitlyshareddatapointer.html#details) and thus `auto p2 = p1` represents the same promise: when `p1` resolves, handlers registered on `p1` and `p2` are called, the fulfilled value being shared between both instances.
> **Note:** while it's safe to access the resolved value from different threads using [`then`](#qpromise-then), QPromise provides no guarantee about the object being pointed to. Thread-safety and reentrancy rules for that object still apply.
## QPromise
### <a name="qpromise-qpromise"></a> `QPromise<T>::QPromise(resolver)`
Creates a new promise that will be fulfilled or rejected by the given `resolver` lambda:
```cpp
QPromise<int> promise([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>& reject) {
async_method([=](bool success, int result) {
if (success) {
resolve(result);
} else {
reject(customException());
}
});
});
```
> **Note:** `QPromise<void>` is specialized to not contain any value, meaning that the `resolve` callback takes no argument.
**C++14**
```cpp
QPromise<int> promise([](const auto& resolve, const auto& reject) {
// {...}
});
```
### <a name="qpromise-then"></a> `QPromise<T>::then(onFulfilled, onRejected) -> QPromise<R>`
See [Promises/A+ `.then`](https://promisesaplus.com/#the-then-method) for details.
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
// called with the 'input' result if fulfilled
}, [](const ReasonType& reason) {
// called with the 'input' reason if rejected
// see QPromise<T>::fail for details
});
```
> **Note**: `onRejected` handler is optional, `output` will be rejected with the same reason as `input`.
> **Note**: it's recommended to use the [`fail`](#qpromise-fail) shorthand to handle errors.
The type `<R>` of the `output` promise depends on the return type of the `onFulfilled` handler:
```cpp
QPromise<int> input = {...}
auto output = input.then([](int res) {
return QString::number(res); // -> QPromise<QString>
});
// output type: QPromise<QString>
output.then([](const QString& res) {
// {...}
});
```
> **Note**: only `onFulfilled` can change the promise type, `onRejected` **must** return the same type as `onFulfilled`. That also means if `onFulfilled` is `nullptr`, `onRejected` must return the same type as the `input` promise.
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
return res + 4;
}, [](const ReasonType& reason) {
return -1;
});
```
If `onFulfilled` doesn't return any value, the `output` type is `QPromise<void>`:
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
// {...}
});
// output type: QPromise<void>
output.then([]() {
// `QPromise<void>` `onFulfilled` handler has no argument
});
```
You can also decide to skip the promise result by omitting the handler argument:
```cpp
QPromise<int> input = {...}
auto output = input.then([]( /* skip int result */ ) {
// {...}
});
```
The `output` promise can be *rejected* by throwing an exception in either `onFulfilled` or `onRejected`:
```cpp
QPromise<int> input = {...}
auto output = input.then([](int res) {
if (res == -1) {
throw ReasonType();
} else {
return res;
}
});
// output.isRejected() is true
```
If an handler returns a promise (or QFuture), the `output` promise is delayed and will be resolved by the returned promise.
### <a name="qpromise-fail"></a> `QPromise<T>::fail(onRejected) -> QPromise<T>`
Shorthand to `promise.then(nullptr, onRejected)`, similar to the [`catch` statement](http://en.cppreference.com/w/cpp/language/try_catch):
```cpp
promise.fail([](const MyException&) {
// {...}
}).fail(const QException&) {
// {...}
}).fail(const std::exception&) {
// {...}
}).fail() {
// {...} catch-all
});
```
### <a name="qpromise-finally"></a> `QPromise<T>::finally(handler) -> QPromise<T>`
This `handler` is **always** called, without any argument and whatever the `input` promise state (fulfilled or rejected). The `output` promise has the same type as the `input` one but also the same value or error. The finally `handler` **can not modify the fulfilled value** (the returned value is ignored), however, if `handler` throws, `output` is rejected with the new exception.
```cpp
auto output = input.finally([]() {
// {...}
});
```
If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned promise is resolved and under the same conditions: the delayed value is ignored, the error transmitted to the `output` promise.
### <a name="qpromise-tap"></a> `QPromise<T>::tap(handler) -> QPromise<T>`
This `handler` allows to observe the value of the `input` promise, without changing the propagated value. The `output` promise will be resolved with the same value as the `input` promise (the `handler` returned value will be ignored). However, if `handler` throws, `output` is rejected with the new exception. Unlike [`finally`](#qpromise-finally), this handler is **not** called for rejections.
```cpp
QPromise<int> input = {...}
auto output = input.tap([](int res) {
log(res);
}).then([](int res) {
// {...}
});
```
If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned promise is resolved and under the same conditions: the delayed value is ignored, the error transmitted to the `output` promise.
### <a name="qpromise-delay"></a> `QPromise<T>::delay(handler) -> QPromise<T>`
This method returns a promise that will be fulfilled with the same value as the `input` promise and after at least `msec` milliseconds. If the `input` promise is rejected, the `output` promise is immediately rejected with the same reason.
```cpp
QPromise<int> input = {...}
auto output = input.delay(2000).then([](int res) {
// called 2 seconds after `input` is fulfilled
});
```
### <a name="qpromise-wait"></a> `QPromise<T>::wait() -> QPromise<T>`
This method holds the execution of the remaining code **without** blocking the event loop of the current thread:
```cpp
int result = -1;
QPromise<int> input = qPromise(QtConcurrent::run([]() { return 42; }));
auto output = input.then([&](int res) {
result = res;
});
// output.isPending() is true && result is -1
output.wait();
// output.isPending() is false && result is 42
```
### `QPromise<T>::isPending() -> bool`
Returns `true` if the promise is pending (not fulfilled or rejected), otherwise returns `false`.
### `QPromise<T>::isFulfilled() -> bool`
Returns `true` if the promise is fulfilled, otherwise returns `false`.
### `QPromise<T>::isRejected() -> bool`
Returns `true` if the promise is rejected, otherwise returns `false`.
## QPromise (statics)
### <a name="qpromise-resolve"></a> `[static] QPromise<T>::resolve(value) -> QPromise<T>`
Creates a `QPromise<T>` that is fulfilled with the given `value` of type `T`:
```cpp
QPromise<int> compute(const QString& type)
{
if (type == "magic") {
return QPromise<int>::resolve(42);
}
return QPromise<int>([](const QPromiseResolve<int>& resolve) {
// {...}
});
}
```
See also: [`qPromise`](#helpers-qpromise)
### <a name="qpromise-reject"></a> `[static] QPromise<T>::reject(reason) -> QPromise<T>`
Creates a `QPromise<T>` that is rejected with the given `reason` of *whatever type*:
```cpp
QPromise<int> compute(const QString& type)
{
if (type == "foobar") {
return QPromise<int>::reject(QString("Unknown type: %1").arg(type));
}
return QPromise<int>([](const QPromiseResolve<int>& resolve) {
// {...}
});
}
```
### <a name="qpromise-all"></a> `[static] QPromise<T>::all(QVector<QPromise<T>>) -> QPromise<QVector<T>>`
Returns a `QPromise<QVector<T>>` that fulfills when **all** `promises` of (the same) type `T` have been fulfilled. The `output` value is a vector containing **all** the values of `promises`, in the same order. If any of the given `promises` fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
```cpp
QVector<QPromise<QByteArray> > promises{
download(QUrl("http://a...")),
download(QUrl("http://b...")),
download(QUrl("http://c..."))
};
auto output = QPromise<QByteArray>::all(promises);
// output type: QPromise<QVector<QByteArray>>
output.then([](const QVector<QByteArray>& res) {
// {...}
});
```
See also: [`qPromiseAll`](#helpers-qpromiseall)
## Helpers
### <a name="helpers-qpromise"></a> `qPromise(T value) -> QPromise<R>`
Similar to the `QPromise<T>::resolve` static method, creates a promise resolved from a given `value` without the extra typing:
```cpp
auto promise = qPromise(); // QPromise<void>
auto promise = qPromise(42); // QPromise<int>
auto promise = qPromise(QString("foo")); // QPromise<QString>
```
This method also allows to convert `QFuture<T>` to `QPromise<T>` delayed until the `QFuture` is finished ([read more](#qtconcurrent-convert)).
### <a name="helpers-qpromiseall"></a> `qPromiseAll(QVector<QPromise<T> promises) -> QPromise<QVector<T>>`
This method simply calls the appropriated [`QPromise<T>::all`](#qpromise-all) static method based on the given `QVector` type. In some cases, this method is more convenient than the static one since it avoid some extra typing:
```cpp
QVector<QPromise<QByteArray> > promises{...}
auto output = qPromiseAll(promises);
// eq. QPromise<QByteArray>::all(promises)
```
* [Getting Started](https://qtpromise.netlify.com/qtpromise/getting-started)
* [Thread-Safety](https://qtpromise.netlify.com/qtpromise/thread-safety)
* [QtConcurrent](https://qtpromise.netlify.com/qtpromise/qtconcurrent)
* [API Reference](https://qtpromise.netlify.com/qtpromise/api-reference)
## License
QtPromise is available under the [MIT license](LICENSE).

40
book.json Normal file
View File

@ -0,0 +1,40 @@
{
"title": "QtPromise",
"description": "Promises/A+ implementation for Qt/C++",
"author": "Simon Brunel",
"gitbook": "3.2.3",
"root": "docs",
"plugins": [
"-lunr",
"-search",
"search-plus",
"anchorjs",
"edit-link",
"expand-active-chapter",
"ga",
"github"
],
"pluginsConfig": {
"anchorjs": {
"icon": "#",
"placement": "left",
"visible": "always"
},
"edit-link": {
"base": "https://github.com/simonbrunel/qtpromise/edit/master/docs"
},
"ga": {
"token": "UA-113899811-1",
"configuration": "auto"
},
"github": {
"url": "https://github.com/simonbrunel/qtpromise"
},
"theme-default": {
"showLevel": false,
"styles": {
"website": "assets/style.css"
}
}
}
}

15
docs/README.md Normal file
View File

@ -0,0 +1,15 @@
<a href="https://promisesaplus.com/" title="Promises/A+ 1.1"><img src="https://promisesaplus.com/assets/logo-small.png" alt="Promises/A+" align="right"/></a>
# QtPromise
[Promises/A+](https://promisesaplus.com/) implementation for [Qt/C++](https://www.qt.io/).
Requires [Qt 5.4](https://www.qt.io/download/) (or later) with [C++11 support enabled](https://wiki.qt.io/How_to_use_C++11_in_your_Qt_Projects).
## QtPromise for C++
* [Getting Started](qtpromise/getting-started.md)
* [Thread-Safety](qtpromise/thread-safety.md)
* [QtConcurrent](qtpromise/qtconcurrent.md)
* [API Reference](qtpromise/api-reference.md)
## License
QtPromise is available under the [MIT license](https://github.com/simonbrunel/qtpromise/blob/master/LICENSE).

22
docs/SUMMARY.md Normal file
View File

@ -0,0 +1,22 @@
### QtPromise for C++
* [Getting Started](qtpromise/getting-started.md)
* [QtConcurrent](qtpromise/qtconcurrent.md)
* [Thread-Safety](qtpromise/thread-safety.md)
* [API Reference](qtpromise/api-reference.md)
* [QPromise](qtpromise/qpromise/constructor.md)
* [.delay](qtpromise/qpromise/delay.md)
* [.fail](qtpromise/qpromise/fail.md)
* [.finally](qtpromise/qpromise/finally.md)
* [.isFulfilled](qtpromise/qpromise/isfulfilled.md)
* [.isPending](qtpromise/qpromise/ispending.md)
* [.isRejected](qtpromise/qpromise/isrejected.md)
* [.tap](qtpromise/qpromise/tap.md)
* [.tapFail](qtpromise/qpromise/tapfail.md)
* [.then](qtpromise/qpromise/then.md)
* [.timeout](qtpromise/qpromise/timeout.md)
* [.wait](qtpromise/qpromise/wait.md)
* [::all (static)](qtpromise/qpromise/all.md)
* [::reject (static)](qtpromise/qpromise/reject.md)
* [::resolve (static)](qtpromise/qpromise/resolve.md)
* [qPromise](qtpromise/helpers/qpromise.md)
* [qPromiseAll](qtpromise/helpers/qpromiseall.md)

15
docs/assets/style.css Normal file
View File

@ -0,0 +1,15 @@
a.anchorjs-link {
color: rgba(65, 131, 196, 0.1);
font-weight: 400;
text-decoration: none;
transition: color 100ms ease-out;
z-index: 999;
}
a.anchorjs-link:hover {
color: rgba(65, 131, 196, 1);
}
sup {
font-size: 0.75em !important;
}

View File

@ -0,0 +1,27 @@
## QPromise
### Public Members
* [`QPromise<T>::QPromise`](qpromise/constructor.md)
* [`QPromise<T>::delay`](qpromise/delay.md)
* [`QPromise<T>::fail`](qpromise/fail.md)
* [`QPromise<T>::finally`](qpromise/finally.md)
* [`QPromise<T>::isFulfilled`](qpromise/isfulfilled.md)
* [`QPromise<T>::isPending`](qpromise/ispending.md)
* [`QPromise<T>::isRejected`](qpromise/isrejected.md)
* [`QPromise<T>::tap`](qpromise/tap.md)
* [`QPromise<T>::tapFail`](qpromise/tapfail.md)
* [`QPromise<T>::then`](qpromise/then.md)
* [`QPromise<T>::timeout`](qpromise/timeout.md)
* [`QPromise<T>::wait`](qpromise/wait.md)
### Public Static Members
* [`[static] QPromise<T>::all`](qpromise/all.md)
* [`[static] QPromise<T>::reject`](qpromise/reject.md)
* [`[static] QPromise<T>::resolve`](qpromise/resolve.md)
## Helpers
* [`qPromise`](helpers/qpromise.md)
* [`qPromiseAll`](helpers/qpromiseall.md)

View File

@ -0,0 +1,93 @@
## Installation
QtPromise is a [header-only](https://en.wikipedia.org/wiki/Header-only) library, simply download the [latest release](https://github.com/simonbrunel/qtpromise/releases/latest) (or [`git submodule`](https://git-scm.com/docs/git-submodule)) and include `qtpromise.pri` from your project `.pro`.
## qpm
Alternatively and **only** if your project relies on [qpm](https://www.qpm.io/), you can install QtPromise as follow:
```bash
qpm install com.github.simonbrunel.qtpromise
```
## Usage
The recommended way to use QtPromise is to include the single module header:
```cpp
#include <QtPromise>
```
## Example
Let's first make the code more readable by using the library namespace:
```cpp
using namespace QtPromise;
```
This `download` function creates a [promise from callbacks](qpromise/constructor.md) which will be resolved when the network request is finished:
```cpp
QPromise<QByteArray> download(const QUrl& url)
{
return QPromise<QByteArray>([&](
const QPromiseResolve<QByteArray>& resolve,
const QPromiseReject<QByteArray>& reject) {
QNetworkReply* reply = manager->get(QNetworkRequest(url));
QObject::connect(reply, &QNetworkReply::finished, [=]() {
if (reply->error() == QNetworkReply::NoError) {
resolve(reply->readAll());
} else {
reject(reply->error());
}
reply->deleteLater();
});
});
}
```
The following method `uncompress` data in a separate thread and returns a [promise from QFuture](qtconcurrent.md):
```cpp
QPromise<Entries> uncompress(const QByteArray& data)
{
return qPromise(QtConcurrent::run([](const QByteArray& data) {
Entries entries;
// {...} uncompress data and parse content.
if (error) {
throw MalformedException();
}
return entries;
}, data));
}
```
It's then easy to chain the whole asynchronous process using promises:
- initiate the promise chain by downloading a specific URL,
- [`then`](qpromise/then.md) *and only if download succeeded*, uncompress received data,
- [`then`](qpromise/then.md) validate and process the uncompressed entries,
- [`finally`](qpromise/finally.md) perform operations whatever the process succeeded or failed,
- and handle specific errors using [`fail`](qpromise/fail.md).
```cpp
download(url).then(&uncompress).then([](const Entries& entries) {
if (entries.isEmpty()) {
throw UpdateException("No entries");
}
// {...} process entries
}).finally([]() {
// {...} cleanup
}).fail([](QNetworkReply::NetworkError err) {
// {...} handle network error
}).fail([](const UpdateException& err) {
// {...} handle update error
}).fail([]() {
// {...} catch all
});
```

View File

@ -0,0 +1,15 @@
## `qPromise`
```
qPromise(T value) -> QPromise<R>
```
Similar to the [`QPromise<T>::resolve`](../qpromise/resolve.md) static method, creates a promise resolved from a given `value` without the extra typing:
```cpp
auto promise = qPromise(); // QPromise<void>
auto promise = qPromise(42); // QPromise<int>
auto promise = qPromise(QString("foo")); // QPromise<QString>
```
This method also allows to convert `QFuture<T>` to `QPromise<T>` delayed until the `QFuture` is finished ([read more](../qtconcurrent.md#convert)).

View File

@ -0,0 +1,16 @@
## `qPromiseAll`
```
qPromiseAll(Sequence<QPromise<T>> promises) -> QPromise<QVector<T>>
qPromiseAll(Sequence<QPromise<void>> promises) -> QPromise<void>
```
This method simply calls the appropriated [`QPromise<T>::all`](../qpromise/all.md) static method based on the given `QVector` type. In some cases, this method is more convenient than the static one since it avoid some extra typing:
```cpp
QVector<QPromise<QByteArray> > promises{...}
auto output = qPromiseAll(promises);
// eq. QPromise<QByteArray>::all(promises)
```

View File

@ -0,0 +1,28 @@
## `[static] QPromise<T>::all`
```
[static] QPromise<T>::all(Sequence<QPromise<T>> promises) -> QPromise<QVector<T>>
```
Returns a `QPromise<QVector<T>>` that fulfills when **all** `promises` of (the same) type `T` have been fulfilled. The `output` value is a vector containing all the values of `promises`, in the same order, i.e., at the respective positions to the original sequence, regardless of completion order.
If any of the given `promises` fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
`Sequence` is any STL compatible container (eg. `QVector`, `QList`, `std::vector`, etc.)
```cpp
QVector<QPromise<QByteArray> > promises{
download(QUrl("http://a...")),
download(QUrl("http://b...")),
download(QUrl("http://c..."))
};
auto output = QPromise<QByteArray>::all(promises);
// output type: QPromise<QVector<QByteArray>>
output.then([](const QVector<QByteArray>& res) {
// {...}
});
```
See also: [`qPromiseAll`](../helpers/qpromiseall.md)

View File

@ -0,0 +1,29 @@
## `QPromise<T>::QPromise`
```
QPromise<T>::QPromise(Function resolver)
```
Creates a new promise that will be fulfilled or rejected by the given `resolver` lambda:
```cpp
QPromise<int> promise([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>& reject) {
async_method([=](bool success, int result) {
if (success) {
resolve(result);
} else {
reject(customException());
}
});
});
```
> **Note:** `QPromise<void>` is specialized to not contain any value, meaning that the `resolve` callback takes no argument.
**C++14**
```cpp
QPromise<int> promise([](const auto& resolve, const auto& reject) {
// {...}
});
```

View File

@ -0,0 +1,14 @@
## `QPromise<T>::delay`
```
QPromise<T>::delay(int msec) -> QPromise<T>
```
This method returns a promise that will be fulfilled with the same value as the `input` promise and after at least `msec` milliseconds. If the `input` promise is rejected, the `output` promise is immediately rejected with the same reason.
```cpp
QPromise<int> input = {...}
auto output = input.delay(2000).then([](int res) {
// called 2 seconds after `input` is fulfilled
});
```

View File

@ -0,0 +1,19 @@
## `QPromise<T>::fail`
```
QPromise<T>::fail(Function onRejected) -> QPromise<T>
```
Shorthand to `promise.then(nullptr, onRejected)`, similar to the [`catch` statement](http://en.cppreference.com/w/cpp/language/try_catch):
```cpp
promise.fail([](const MyException&) {
// {...}
}).fail(const QException&) {
// {...}
}).fail(const std::exception&) {
// {...}
}).fail() {
// {...} catch-all
});
```

View File

@ -0,0 +1,15 @@
## `QPromise<T>::finally`
```
QPromise<T>::finally(Function handler) -> QPromise<T>
```
This `handler` is **always** called, without any argument and whatever the `input` promise state (fulfilled or rejected). The `output` promise has the same type as the `input` one but also the same value or error. The finally `handler` **can not modify the fulfilled value** (the returned value is ignored), however, if `handler` throws, `output` is rejected with the new exception.
```cpp
auto output = input.finally([]() {
// {...}
});
```
If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned promise is resolved and under the same conditions: the delayed value is ignored, the error transmitted to the `output` promise.

View File

@ -0,0 +1,7 @@
# `QPromise<T>::isFulfilled`
```
QPromise<T>::isFulfilled() -> bool
```
Returns `true` if the promise is fulfilled, otherwise returns `false`.

View File

@ -0,0 +1,7 @@
# `QPromise<T>::isPending`
```
QPromise<T>::isPending() -> bool
```
Returns `true` if the promise is pending (not fulfilled or rejected), otherwise returns `false`.

View File

@ -0,0 +1,8 @@
# `QPromise<T>::isRejected`
```
QPromise<T>::isRejected() -> bool
```
Returns `true` if the promise is rejected, otherwise returns `false`.

View File

@ -0,0 +1,20 @@
## `[static] QPromise<T>::reject`
```
[static] QPromise<T>::reject(any reason) -> QPromise<T>
```
Creates a `QPromise<T>` that is rejected with the given `reason` of *whatever type*:
```cpp
QPromise<int> compute(const QString& type)
{
if (type == "foobar") {
return QPromise<int>::reject(QString("Unknown type: %1").arg(type));
}
return QPromise<int>([](const QPromiseResolve<int>& resolve) {
// {...}
});
}
```

View File

@ -0,0 +1,22 @@
## `[static] QPromise<T>::resolve`
```
[static] QPromise<T>::resolve(T value) -> QPromise<T>
```
Creates a `QPromise<T>` that is fulfilled with the given `value` of type `T`:
```cpp
QPromise<int> compute(const QString& type)
{
if (type == "magic") {
return QPromise<int>::resolve(42);
}
return QPromise<int>([](const QPromiseResolve<int>& resolve) {
// {...}
});
}
```
See also: [`qPromise`](../helpers/qpromise.md)

View File

@ -0,0 +1,18 @@
## `QPromise<T>::tap`
```
QPromise<T>::tap(Function handler) -> QPromise<T>
```
This `handler` allows to observe the value of the `input` promise, without changing the propagated value. The `output` promise will be resolved with the same value as the `input` promise (the `handler` returned value will be ignored). However, if `handler` throws, `output` is rejected with the new exception. Unlike [`finally`](finally.md), this handler is **not** called for rejections.
```cpp
QPromise<int> input = {...}
auto output = input.tap([](int res) {
log(res);
}).then([](int res) {
// {...}
});
```
If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned promise is resolved and under the same conditions: the delayed value is ignored, the error transmitted to the `output` promise.

View File

@ -0,0 +1,21 @@
## `QPromise<T>::tapFail`
```
QPromise<T>::tapFail(Function handler) -> QPromise<T>
```
This `handler` allows to observe errors of the `input` promise without handling them - similar to [`finally`](finally.md) but **only** called on rejections. The `output` promise has the same type as the `input` one but also the same value or error. However, if `handler` throws, `output` is rejected with the new exception.
```cpp
QPromise<int> input = {...}
auto output = input.tapFail([](Error err) {
log(err);
}).then([](int res) {
return process(res);
}).fail([](Error err) {
handle(err);
return -1;
});
```
If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned promise is resolved and under the same conditions: the delayed value is ignored, the error transmitted to the `output` promise.

View File

@ -0,0 +1,87 @@
## `QPromise<T>::then`
```
QPromise<T>::then(Function onFulfilled, Function onRejected) -> QPromise<R>
QPromise<T>::then(Function onFulfilled) -> QPromise<R>
```
See [Promises/A+ `.then`](https://promisesaplus.com/#the-then-method) for details.
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
// called with the 'input' result if fulfilled
}, [](const ReasonType& reason) {
// called with the 'input' reason if rejected
// see QPromise<T>::fail for details
});
```
> **Note**: `onRejected` handler is optional, `output` will be rejected with the same reason as `input`.
> **Note**: it's recommended to use the [`fail`](fail.md) shorthand to handle errors.
The type `<R>` of the `output` promise depends on the return type of the `onFulfilled` handler:
```cpp
QPromise<int> input = {...}
auto output = input.then([](int res) {
return QString::number(res); // -> QPromise<QString>
});
// output type: QPromise<QString>
output.then([](const QString& res) {
// {...}
});
```
> **Note**: only `onFulfilled` can change the promise type, `onRejected` **must** return the same type as `onFulfilled`. That also means if `onFulfilled` is `nullptr`, `onRejected` must return the same type as the `input` promise.
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
return res + 4;
}, [](const ReasonType& reason) {
return -1;
});
```
If `onFulfilled` doesn't return any value, the `output` type is `QPromise<void>`:
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
// {...}
});
// output type: QPromise<void>
output.then([]() {
// `QPromise<void>` `onFulfilled` handler has no argument
});
```
You can also decide to skip the promise result by omitting the handler argument:
```cpp
QPromise<int> input = {...}
auto output = input.then([]( /* skip int result */ ) {
// {...}
});
```
The `output` promise can be *rejected* by throwing an exception in either `onFulfilled` or `onRejected`:
```cpp
QPromise<int> input = {...}
auto output = input.then([](int res) {
if (res == -1) {
throw ReasonType();
} else {
return res;
}
});
// output.isRejected() is true
```
If an handler returns a promise (or QFuture), the `output` promise is delayed and will be resolved by the returned promise.

View File

@ -0,0 +1,18 @@
## `QPromise<T>::timeout`
```
QPromise<T>::timeout(int msec, any error = QPromiseTimeoutException) -> QPromise<T>
```
This method returns a promise that will be resolved with the `input` promise's fulfillment value or rejection reason. However, if the `input` promise is not fulfilled or rejected within `msec` milliseconds, the `output` promise is rejected with `error` as the reason (`QPromiseTimeoutException` by default).
```cpp
QPromise<int> input = {...}
auto output = input.timeout(2000)
.then([](int res) {
// operation succeeded within 2 seconds
})
.fail([](const QPromiseTimeoutException& e) {
// operation timed out!
});
```

View File

@ -0,0 +1,23 @@
## `QPromise<T>::wait`
```
QPromise<T>::wait() -> QPromise<T>
```
This method holds the execution of the remaining code until the `input` promise is resolved (either fulfilled or rejected), **without** blocking the event loop of the current thread:
```cpp
int result = -1;
QPromise<int> input = qPromise(QtConcurrent::run([]() {
return 42;
})).tap([&](int res) {
result = res;
});
// input.isPending() is true && result is -1
input.wait();
// input.isPending() is false && result is 42
```

View File

@ -0,0 +1,66 @@
## QtConcurrent
QtPromise integrates with [QtConcurrent](https://doc.qt.io/qt-5/qtconcurrent-index.html) to make easy chaining QFuture with QPromise.
## <a name="qtconcurrent-convert"></a> Convert
Converting `QFuture<T>` to `QPromise<T>` is done using the [`qPromise`](helpers/qpromise.md) helper:
```cpp
QFuture<int> future = QtConcurrent::run([]() {
// {...}
return 42;
});
QPromise<int> promise = qPromise(future);
```
or simply:
```cpp
auto promise = qPromise(QtConcurrent::run([]() {
// {...}
}));
```
## Chain
Returning a `QFuture<T>` in [`then`](qpromise/then.md) or [`fail`](qpromise/fail.md) automatically translate to `QPromise<T>`:
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
return QtConcurrent::run([]() {
// {...}
return QString("42");
});
});
// output type: QPromise<QString>
output.then([](const QString& res) {
// {...}
});
```
The `output` promise is resolved when the `QFuture` is [finished](https://doc.qt.io/qt-5/qfuture.html#isFinished).
## Error
Exceptions thrown from a QtConcurrent thread reject the associated promise with the exception as the reason. Note that if you throw an exception that is not a subclass of `QException`, the promise with be rejected with [`QUnhandledException`](https://doc.qt.io/qt-5/qunhandledexception.html#details) (this restriction only applies to exceptions thrown from a QtConcurrent thread, [read more](https://doc.qt.io/qt-5/qexception.html#details)).
```cpp
QPromise<int> promise = ...
promise.then([](int res) {
return QtConcurrent::run([]() {
// {...}
if (!success) {
throw CustomException();
}
return QString("42");
});
}).fail(const CustomException& err) {
// {...}
});
```

View File

@ -0,0 +1,5 @@
## Thread-Safety
QPromise is thread-safe and can be copied and accessed across different threads. QPromise relies on [explicitly data sharing](https://doc.qt.io/qt-5/qexplicitlyshareddatapointer.html#details) and thus `auto p2 = p1` represents the same promise: when `p1` resolves, handlers registered on `p1` and `p2` are called, the fulfilled value being shared between both instances.
> **Note:** while it's safe to access the resolved value from different threads using [`then`](qpromise/then.md), QPromise provides no guarantee about the object being pointed to. Thread-safety and reentrancy rules for that object still apply.

View File

@ -10,7 +10,7 @@
"url": "https://github.com/simonbrunel/qtpromise.git"
},
"version": {
"label": "0.2.0"
"label": "0.3.0"
},
"license": "MIT",
"pri_filename": "qtpromise.pri",

View File

@ -3,6 +3,7 @@
// QtPromise
#include "qpromise_p.h"
#include "qpromiseerror.h"
#include "qpromiseglobal.h"
// Qt
@ -16,25 +17,41 @@ class QPromiseBase
public:
using Type = T;
QPromiseBase(const QPromiseBase<T>& other): m_d(other.m_d) {}
QPromiseBase(const QPromise<T>& other): m_d(other.m_d) {}
QPromiseBase(QPromiseBase<T>&& other) { swap(other); }
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count == 1, int>::type = 0>
inline QPromiseBase(F resolver);
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count != 1, int>::type = 0>
inline QPromiseBase(F resolver);
template <typename U>
inline QPromiseBase(const QPromise<U>& other);
QPromiseBase(const QPromiseBase<T>& other): m_d(other.m_d) {}
QPromiseBase(const QPromise<T>& other): m_d(other.m_d) {}
QPromiseBase(QPromiseBase<T>&& other) Q_DECL_NOEXCEPT { swap(other); }
virtual ~QPromiseBase() { }
QPromiseBase<T>& operator=(const QPromiseBase<T>& other) { m_d = other.m_d; return *this;}
QPromiseBase<T>& operator=(QPromiseBase<T>&& other) Q_DECL_NOEXCEPT
{ QPromiseBase<T>(std::move(other)).swap(*this); return *this; }
bool operator==(const QPromiseBase<T>& other) const { return (m_d == other.m_d); }
bool operator!=(const QPromiseBase<T>& other) const { return (m_d != other.m_d); }
void swap(QPromiseBase<T>& other) Q_DECL_NOEXCEPT { qSwap(m_d, other.m_d); }
bool isFulfilled() const { return m_d->isFulfilled(); }
bool isRejected() const { return m_d->isRejected(); }
bool isPending() const { return m_d->isPending(); }
template <typename TFulfilled, typename TRejected = std::nullptr_t>
template <typename TFulfilled, typename TRejected>
inline typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise
then(const TFulfilled& fulfilled, const TRejected& rejected = nullptr) const;
then(const TFulfilled& fulfilled, const TRejected& rejected) const;
template <typename TFulfilled>
inline typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise
then(TFulfilled&& fulfilled) const;
template <typename TRejected>
inline typename QtPromisePrivate::PromiseHandler<T, std::nullptr_t>::Promise
@ -46,50 +63,61 @@ public:
template <typename THandler>
inline QPromise<T> tap(THandler handler) const;
template <typename THandler>
inline QPromise<T> tapFail(THandler handler) const;
template <typename E = QPromiseTimeoutException>
inline QPromise<T> timeout(int msec, E&& error = E()) const;
inline QPromise<T> delay(int msec) const;
inline QPromise<T> wait() const;
void swap(QPromiseBase<T>& other) { qSwap(m_d, other.m_d); }
public: // STATIC
template <typename E>
inline static QPromise<T> reject(E&& error);
protected:
friend struct QtPromisePrivate::PromiseFulfill<QPromise<T> >;
friend class QPromiseResolve<T>;
friend class QPromiseReject<T>;
friend struct QtPromisePrivate::PromiseFulfill<QPromise<T>>;
friend class QtPromisePrivate::PromiseResolver<T>;
QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T> > m_d;
QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T>> m_d;
};
template <typename T>
class QPromise: public QPromiseBase<T>
class QPromise : public QPromiseBase<T>
{
public:
template <typename F>
QPromise(F&& resolver): QPromiseBase<T>(std::forward<F>(resolver)) { }
public: // STATIC
inline static QPromise<QVector<T> > all(const QVector<QPromise<T> >& promises);
template <template <typename, typename...> class Sequence = QVector, typename ...Args>
inline static QPromise<QVector<T>> all(const Sequence<QPromise<T>, Args...>& promises);
inline static QPromise<T> resolve(const T& value);
inline static QPromise<T> resolve(T&& value);
template <typename U>
operator QPromise<U>();
private:
friend class QPromiseBase<T>;
};
template <>
class QPromise<void>: public QPromiseBase<void>
class QPromise<void> : public QPromiseBase<void>
{
public:
template <typename F>
QPromise(F&& resolver): QPromiseBase<void>(std::forward<F>(resolver)) { }
template <typename T>
QPromise(const QPromise<T>& other);
public: // STATIC
inline static QPromise<void> all(const QVector<QPromise<void> >& promises);
template <template <typename, typename...> class Sequence = QVector, typename ...Args>
inline static QPromise<void> all(const Sequence<QPromise<void>, Args...>& promises);
inline static QPromise<void> resolve();
private:

View File

@ -9,95 +9,82 @@ template <class T>
class QPromiseResolve
{
public:
QPromiseResolve(QPromise<T> p)
: m_promise(new QPromise<T>(std::move(p)))
QPromiseResolve(QtPromisePrivate::PromiseResolver<T> resolver)
: m_resolver(std::move(resolver))
{ }
template <typename V>
void operator()(V&& value) const
{
Q_ASSERT(!m_promise.isNull());
if (m_promise->isPending()) {
m_promise->m_d->resolve(std::forward<V>(value));
m_promise->m_d->dispatch();
}
m_resolver.resolve(std::forward<V>(value));
}
private:
QSharedPointer<QPromise<T> > m_promise;
};
template <>
class QPromiseResolve<void>
{
public:
QPromiseResolve(QPromise<void> p)
: m_promise(new QPromise<void>(std::move(p)))
{ }
void operator()() const
{
Q_ASSERT(!m_promise.isNull());
if (m_promise->isPending()) {
m_promise->m_d->resolve();
m_promise->m_d->dispatch();
}
m_resolver.resolve();
}
private:
QSharedPointer<QPromise<void> > m_promise;
mutable QtPromisePrivate::PromiseResolver<T> m_resolver;
};
template <class T>
class QPromiseReject
{
public:
QPromiseReject(QPromise<T> p)
: m_promise(new QPromise<T>(std::move(p)))
QPromiseReject(QtPromisePrivate::PromiseResolver<T> resolver)
: m_resolver(std::move(resolver))
{ }
template <typename E>
void operator()(E&& error) const
{
Q_ASSERT(!m_promise.isNull());
if (m_promise->isPending()) {
m_promise->m_d->reject(std::forward<E>(error));
m_promise->m_d->dispatch();
}
m_resolver.reject(std::forward<E>(error));
}
private:
QSharedPointer<QPromise<T> > m_promise;
mutable QtPromisePrivate::PromiseResolver<T> m_resolver;
};
template <typename T>
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count == 1, int>::type>
inline QPromiseBase<T>::QPromiseBase(F resolver)
inline QPromiseBase<T>::QPromiseBase(F callback)
: m_d(new QtPromisePrivate::PromiseData<T>())
{
QPromiseResolve<T> resolve(*this);
QPromiseReject<T> reject(*this);
QtPromisePrivate::PromiseResolver<T> resolver(*this);
try {
resolver(resolve);
callback(QPromiseResolve<T>(resolver));
} catch (...) {
reject(std::current_exception());
resolver.reject(std::current_exception());
}
}
template <typename T>
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count != 1, int>::type>
inline QPromiseBase<T>::QPromiseBase(F resolver)
inline QPromiseBase<T>::QPromiseBase(F callback)
: m_d(new QtPromisePrivate::PromiseData<T>())
{
QtPromisePrivate::PromiseResolver<T> resolver(*this);
try {
callback(QPromiseResolve<T>(resolver), QPromiseReject<T>(resolver));
} catch (...) {
resolver.reject(std::current_exception());
}
}
template <typename T>
template <typename U>
inline QPromiseBase<T>::QPromiseBase(const QPromise<U>& other)
: m_d(new QtPromisePrivate::PromiseData<T>())
{
using namespace QtPromisePrivate;
QPromiseResolve<T> resolve(*this);
QPromiseReject<T> reject(*this);
try {
resolver(resolve, reject);
} catch (...) {
reject(std::current_exception());
}
PromiseFulfill<QPromise<U> >::call(other, PromiseCast<U, T>::apply(resolve), reject);
}
template <typename T>
@ -122,6 +109,14 @@ QPromiseBase<T>::then(const TFulfilled& fulfilled, const TRejected& rejected) co
return next;
}
template <typename T>
template <typename TFulfilled>
inline typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise
QPromiseBase<T>::then(TFulfilled&& fulfilled) const
{
return then(std::forward<TFulfilled>(fulfilled), nullptr);
}
template <typename T>
template <typename TRejected>
inline typename QtPromisePrivate::PromiseHandler<T, std::nullptr_t>::Promise
@ -150,6 +145,16 @@ inline QPromise<T> QPromiseBase<T>::tap(THandler handler) const
});
}
template <typename T>
template <typename THandler>
inline QPromise<T> QPromiseBase<T>::tapFail(THandler handler) const
{
QPromise<T> p = *this;
return p.then([](){}, handler).then([=]() {
return p;
});
}
template <typename T>
template <typename E>
inline QPromise<T> QPromiseBase<T>::timeout(int msec, E&& error) const
@ -166,7 +171,7 @@ inline QPromise<T> QPromiseBase<T>::timeout(int msec, E&& error) const
reject(std::move(error));
});
QtPromisePrivate::PromiseFulfill<QPromise<T> >::call(p, resolve, reject);
QtPromisePrivate::PromiseFulfill<QPromise<T>>::call(p, resolve, reject);
});
}
@ -202,22 +207,24 @@ inline QPromise<T> QPromiseBase<T>::reject(E&& error)
}
template <typename T>
inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promises)
template <template <typename, typename...> class Sequence, typename ...Args>
inline QPromise<QVector<T>> QPromise<T>::all(const Sequence<QPromise<T>, Args...>& promises)
{
const int count = promises.size();
const int count = static_cast<int>(promises.size());
if (count == 0) {
return QPromise<QVector<T> >::resolve({});
return QPromise<QVector<T>>::resolve({});
}
return QPromise<QVector<T> >([=](
const QPromiseResolve<QVector<T> >& resolve,
const QPromiseReject<QVector<T> >& reject) {
return QPromise<QVector<T>>([=](
const QPromiseResolve<QVector<T>>& resolve,
const QPromiseReject<QVector<T>>& reject) {
QSharedPointer<int> remaining(new int(count));
QSharedPointer<QVector<T> > results(new QVector<T>(count));
QSharedPointer<QVector<T>> results(new QVector<T>(count));
for (int i=0; i<count; ++i) {
promises[i].then([=](const T& res) mutable {
int i = 0;
for (const auto& promise: promises) {
promise.then([=](const T& res) mutable {
(*results)[i] = res;
if (--(*remaining) == 0) {
resolve(*results);
@ -228,10 +235,20 @@ inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promi
reject(std::current_exception());
}
});
i++;
}
});
}
template <typename T>
inline QPromise<T> QPromise<T>::resolve(const T& value)
{
return QPromise<T>([&](const QPromiseResolve<T>& resolve) {
resolve(value);
});
}
template <typename T>
inline QPromise<T> QPromise<T>::resolve(T&& value)
{
@ -240,9 +257,25 @@ inline QPromise<T> QPromise<T>::resolve(T&& value)
});
}
inline QPromise<void> QPromise<void>::all(const QVector<QPromise<void> >& promises)
template <typename T>
template <typename U>
QPromise<T>::operator QPromise<U>()
{
const int count = promises.size();
return QPromise<U>::resolve(U());
}
template <typename T>
QPromise<void>::QPromise(const QPromise<T>& other)
: QPromiseBase<void>([&](const QPromiseResolve<void>& resolve, const QPromiseReject<void>& reject) {
QtPromisePrivate::PromiseFulfill<QPromise<T> >::call(other, resolve, reject);
})
{
}
template <template <typename, typename...> class Sequence, typename ...Args>
inline QPromise<void> QPromise<void>::all(const Sequence<QPromise<void>, Args...>& promises)
{
const int count = static_cast<int>(promises.size());
if (count == 0) {
return QPromise<void>::resolve();
}
@ -251,7 +284,7 @@ inline QPromise<void> QPromise<void>::all(const QVector<QPromise<void> >& promis
const QPromiseResolve<void>& resolve,
const QPromiseReject<void>& reject) {
QSharedPointer<int> remaining(new int(promises.size()));
QSharedPointer<int> remaining(new int(count));
for (const auto& promise: promises) {
promise.then([=]() {

View File

@ -2,7 +2,6 @@
#define QTPROMISE_QPROMISE_P_H
// QtPromise
#include "qpromiseerror.h"
#include "qpromiseglobal.h"
// Qt
@ -32,52 +31,112 @@ namespace QtPromisePrivate {
// https://stackoverflow.com/a/21653558
template <typename F>
static void qtpromise_defer(F&& f, QThread* thread = nullptr)
static void qtpromise_defer(F&& f, const QPointer<QThread>& thread)
{
using FType = typename std::decay<F>::type;
struct Event : public QEvent
{
using FType = typename std::decay<F>::type;
Event(FType&& f) : QEvent(QEvent::None), m_f(std::move(f)) { }
Event(const FType& f) : QEvent(QEvent::None), m_f(f) { }
~Event() { m_f(); }
FType m_f;
};
if (!thread || thread->isFinished()) {
// Make sure to not call `f` if the captured thread doesn't exist anymore,
// which would potentially result in dispatching to the wrong thread (ie.
// nullptr == current thread). Since the target thread is gone, it should
// be safe to simply skip that notification.
return;
}
QObject* target = QAbstractEventDispatcher::instance(thread);
if (!target && QCoreApplication::closingDown()) {
// When the app is shutting down, the even loop is not anymore available
// so we don't have any way to dispatch `f`. This case can happen when a
// promise is resolved after the app is requested to close, in which case
// we should not trigger any error and skip that notification.
return;
}
Q_ASSERT_X(target, "postMetaCall", "Target thread must have an event loop");
QCoreApplication::postEvent(target, new Event(std::forward<F>(f)));
}
template <typename T>
struct PromiseDeduce
template <typename F>
static void qtpromise_defer(F&& f)
{
using Type = QtPromise::QPromise<Unqualified<T> >;
Q_ASSERT(QThread::currentThread());
qtpromise_defer(std::forward<F>(f), QThread::currentThread());
}
template <typename T>
class PromiseValue
{
public:
PromiseValue() { }
PromiseValue(const T& data) : m_data(new T(data)) { }
PromiseValue(T&& data) : m_data(new T(std::move(data))) { }
bool isNull() const { return m_data.isNull(); }
const T& data() const { return *m_data; }
private:
QSharedPointer<T> m_data;
};
class PromiseError
{
public:
template <typename T>
PromiseError(const T& value)
{
try {
throw value;
} catch (...) {
m_data = std::current_exception();
}
}
PromiseError() { }
PromiseError(const std::exception_ptr& exception) : m_data(exception) { }
void rethrow() const { std::rethrow_exception(m_data); }
bool isNull() const { return m_data == nullptr; }
private:
// NOTE(SB) std::exception_ptr is already a shared pointer
std::exception_ptr m_data;
};
template <typename T>
struct PromiseDeduce<QtPromise::QPromise<T> >
struct PromiseDeduce
{
using Type = QtPromise::QPromise<Unqualified<T>>;
};
template <typename T>
struct PromiseDeduce<QtPromise::QPromise<T>>
: public PromiseDeduce<T>
{ };
template <typename T>
struct PromiseFulfill
{
static void call(
T&& value,
const QtPromise::QPromiseResolve<T>& resolve,
const QtPromise::QPromiseReject<T>&)
template <typename TResolve, typename TReject>
static void call(T&& value, const TResolve& resolve, const TReject&)
{
resolve(std::move(value));
}
};
template <typename T>
struct PromiseFulfill<QtPromise::QPromise<T> >
struct PromiseFulfill<QtPromise::QPromise<T>>
{
template <typename TResolve, typename TReject>
static void call(
const QtPromise::QPromise<T>& promise,
const QtPromise::QPromiseResolve<T>& resolve,
const QtPromise::QPromiseReject<T>& reject)
const TResolve& resolve,
const TReject& reject)
{
if (promise.isFulfilled()) {
resolve(promise.m_d->value());
@ -94,11 +153,11 @@ struct PromiseFulfill<QtPromise::QPromise<T> >
};
template <>
struct PromiseFulfill<QtPromise::QPromise<void> >
struct PromiseFulfill<QtPromise::QPromise<void>>
{
template <typename TPromise, typename TResolve, typename TReject>
template <typename TResolve, typename TReject>
static void call(
const TPromise& promise,
const QtPromise::QPromise<void>& promise,
const TResolve& resolve,
const TReject& reject)
{
@ -282,12 +341,12 @@ struct PromiseCatcher
using ResType = typename std::result_of<THandler(TArg)>::type;
template <typename TResolve, typename TReject>
static std::function<void(const QtPromise::QPromiseError&)> create(
static std::function<void(const PromiseError&)> create(
const THandler& handler,
const TResolve& resolve,
const TReject& reject)
{
return [=](const QtPromise::QPromiseError& error) {
return [=](const PromiseError& error) {
try {
error.rethrow();
} catch (const TArg& error) {
@ -305,12 +364,12 @@ struct PromiseCatcher<T, THandler, void>
using ResType = typename std::result_of<THandler()>::type;
template <typename TResolve, typename TReject>
static std::function<void(const QtPromise::QPromiseError&)> create(
static std::function<void(const PromiseError&)> create(
const THandler& handler,
const TResolve& resolve,
const TReject& reject)
{
return [=](const QtPromise::QPromiseError& error) {
return [=](const PromiseError& error) {
try {
error.rethrow();
} catch (...) {
@ -324,12 +383,12 @@ template <typename T>
struct PromiseCatcher<T, std::nullptr_t, void>
{
template <typename TResolve, typename TReject>
static std::function<void(const QtPromise::QPromiseError&)> create(
static std::function<void(const PromiseError&)> create(
std::nullptr_t,
const TResolve&,
const TReject& reject)
{
return [=](const QtPromise::QPromiseError& error) {
return [=](const PromiseError& error) {
// 2.2.7.4. If onRejected is not a function and promise1 is rejected,
// promise2 must be rejected with the same reason as promise1
reject(error);
@ -337,15 +396,75 @@ struct PromiseCatcher<T, std::nullptr_t, void>
}
};
template <typename T, typename U>
struct PromiseCast
{
template <typename F>
static auto apply(const F& resolve)
{
return [=](const QSharedPointer<T>& value) {
resolve(static_cast<T>(*value));
};
}
};
template <typename T>
struct PromiseCast<T, void>
{
template <typename F>
static auto apply(const F& resolve)
{
return [=](const QSharedPointer<T>&) {
resolve();
};
}
};
/*
template <typename T>
struct PromiseCast<T, QVariant>
{
static QtPromise::QPromise<QVariant> cast(
const QtPromise::QPromiseBase<T>& input)
{
return input.then([](const T& res) {
return QVariant::fromValue(res);
});
}
};
template <typename U>
struct PromiseCast<QVariant, U>
{
static QtPromise::QPromise<U> cast(
const QtPromise::QPromiseBase<QVariant>& input)
{
return input.then([](const QVariant& res) {
return res.value<U>();
});
}
};
template <>
struct PromiseCast<void, QVariant>
{
static QtPromise::QPromise<void> cast(
const QtPromise::QPromiseBase<QVariant>& input)
{
return input.then([]() {
});
}
};
*/
template <typename T> class PromiseData;
template <typename T, typename F>
class PromiseDataBase : public QSharedData
{
public:
using Error = QtPromise::QPromiseError;
using Handler = std::pair<QPointer<QThread>, std::function<F> >;
using Catcher = std::pair<QPointer<QThread>, std::function<void(const Error&)> >;
using Handler = std::pair<QPointer<QThread>, std::function<F>>;
using Catcher = std::pair<QPointer<QThread>, std::function<void(const PromiseError&)>>;
virtual ~PromiseDataBase() {}
@ -371,29 +490,22 @@ public:
m_handlers.append({QThread::currentThread(), std::move(handler)});
}
void addCatcher(std::function<void(const Error&)> catcher)
void addCatcher(std::function<void(const PromiseError&)> catcher)
{
QWriteLocker lock(&m_lock);
m_catchers.append({QThread::currentThread(), std::move(catcher)});
}
void reject(Error error)
template <typename E>
void reject(E&& error)
{
Q_ASSERT(isPending());
Q_ASSERT(m_error.isNull());
m_error.reset(new Error(std::move(error)));
m_error = PromiseError(std::forward<E>(error));
setSettled();
}
void reject(const QSharedPointer<Error>& error)
{
Q_ASSERT(isPending());
Q_ASSERT(m_error.isNull());
m_error = error;
this->setSettled();
}
const QSharedPointer<Error>& error() const
const PromiseError& error() const
{
Q_ASSERT(isRejected());
return m_error;
@ -422,13 +534,13 @@ public:
return;
}
QSharedPointer<Error> error = m_error;
PromiseError error(m_error);
Q_ASSERT(!error.isNull());
for (const auto& catcher: catchers) {
const auto& fn = catcher.second;
qtpromise_defer([=]() {
fn(*error);
fn(error);
}, catcher.first);
}
}
@ -449,7 +561,7 @@ private:
bool m_settled = false;
QVector<Handler> m_handlers;
QVector<Catcher> m_catchers;
QSharedPointer<Error> m_error;
PromiseError m_error;
};
template <typename T>
@ -458,31 +570,16 @@ class PromiseData : public PromiseDataBase<T, void(const T&)>
using Handler = typename PromiseDataBase<T, void(const T&)>::Handler;
public:
void resolve(T&& value)
template <typename V>
void resolve(V&& value)
{
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value.reset(new T(std::move(value)));
m_value = PromiseValue<T>(std::forward<V>(value));
this->setSettled();
}
void resolve(const T& value)
{
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value.reset(new T(value));
this->setSettled();
}
void resolve(const QSharedPointer<T>& value)
{
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value = value;
this->setSettled();
}
const QSharedPointer<T>& value() const
const PromiseValue<T>& value() const
{
Q_ASSERT(this->isFulfilled());
return m_value;
@ -490,25 +587,25 @@ public:
void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE
{
QSharedPointer<T> value(m_value);
PromiseValue<T> value(m_value);
Q_ASSERT(!value.isNull());
for (const auto& handler: handlers) {
const auto& fn = handler.second;
qtpromise_defer([=]() {
fn(*value);
fn(value.data());
}, handler.first);
}
}
private:
QSharedPointer<T> m_value;
PromiseValue<T> m_value;
};
template <>
class PromiseData<void> : public PromiseDataBase<void, void()>
{
using Handler = typename PromiseDataBase<void, void()>::Handler;
using Handler = PromiseDataBase<void, void()>::Handler;
public:
void resolve()
@ -525,6 +622,68 @@ protected:
}
};
template <typename T>
class PromiseResolver
{
public:
PromiseResolver(QtPromise::QPromise<T> promise)
: m_d(new Data())
{
m_d->promise = new QtPromise::QPromise<T>(std::move(promise));
}
template <typename E>
void reject(E&& error)
{
auto promise = m_d->promise;
if (promise) {
Q_ASSERT(promise->isPending());
promise->m_d->reject(std::forward<E>(error));
promise->m_d->dispatch();
release();
}
}
template <typename V>
void resolve(V&& value)
{
auto promise = m_d->promise;
if (promise) {
Q_ASSERT(promise->isPending());
promise->m_d->resolve(std::forward<V>(value));
promise->m_d->dispatch();
release();
}
}
void resolve()
{
auto promise = m_d->promise;
if (promise) {
Q_ASSERT(promise->isPending());
promise->m_d->resolve();
promise->m_d->dispatch();
release();
}
}
private:
struct Data : public QSharedData
{
QtPromise::QPromise<T>* promise = nullptr;
};
QExplicitlySharedDataPointer<Data> m_d;
void release()
{
Q_ASSERT(m_d->promise);
Q_ASSERT(!m_d->promise->isPending());
delete m_d->promise;
m_d->promise = nullptr;
}
};
} // namespace QtPromise
#endif // ifndef QTPROMISE_QPROMISE_H
#endif // ifndef QTPROMISE_QPROMISE_P_H

View File

@ -2,6 +2,7 @@
#define QTPROMISE_QPROMISEERROR_H
// QtPromise
#include "qpromise_p.h"
#include "qpromiseglobal.h"
// Qt
@ -9,53 +10,6 @@
namespace QtPromise {
class QPromiseError
{
public:
template <typename T>
QPromiseError(const T& value)
{
try {
throw value;
} catch (...) {
m_exception = std::current_exception();
}
}
QPromiseError(const std::exception_ptr& exception)
: m_exception(exception)
{ }
QPromiseError(const QPromiseError& error)
: m_exception(error.m_exception)
{ }
QPromiseError(QPromiseError&& other)
: m_exception(nullptr)
{
swap(other);
}
QPromiseError& operator =(QPromiseError other)
{
swap(other);
return *this;
}
void swap(QPromiseError& other)
{
qSwap(m_exception, other.m_exception);
}
void rethrow() const
{
std::rethrow_exception(m_exception);
}
private:
std::exception_ptr m_exception;
};
class QPromiseTimeoutException : public QException
{
public:
@ -66,6 +20,12 @@ public:
}
};
// QPromiseError is provided for backward compatibility and will be
// removed in the next major version: it wasn't intended to be used
// directly and thus should not be part of the public API.
// TODO Remove QPromiseError at version 1.0
using QPromiseError = QtPromisePrivate::PromiseError;
} // namespace QtPromise
#endif // QTPROMISE_QPROMISEERROR_H

View File

@ -22,12 +22,12 @@ public:
namespace QtPromisePrivate {
template <typename T>
struct PromiseDeduce<QFuture<T> >
struct PromiseDeduce<QFuture<T>>
: public PromiseDeduce<T>
{ };
template <typename T>
struct PromiseFulfill<QFuture<T> >
struct PromiseFulfill<QFuture<T>>
{
static void call(
const QFuture<T>& future,
@ -62,7 +62,7 @@ struct PromiseFulfill<QFuture<T> >
};
template <>
struct PromiseFulfill<QFuture<void> >
struct PromiseFulfill<QFuture<void>>
{
static void call(
const QFuture<void>& future,

View File

@ -16,18 +16,18 @@ using Unqualified = typename std::remove_cv<typename std::remove_reference<T>::t
/*!
* \struct HasCallOperator
* http://stackoverflow.com/a/5839442
* http://stackoverflow.com/a/5117641
*/
template <typename T>
struct HasCallOperator
{
template <class U>
static auto check(const U* u)
-> decltype(&U::operator(), char(0));
template <typename U>
static char check(decltype(&U::operator(), char(0)));
static std::array<char, 2> check(...);
template <typename U>
static char (&check(...))[2];
static const bool value = (sizeof(check((T*)0)) == 1);
static const bool value = (sizeof(check<T>(0)) == 1);
};
/*!
@ -85,7 +85,7 @@ struct ArgsOf<TReturn(T::*)(Args...) const volatile> : public ArgsTraits<Args...
{ };
template <typename T>
struct ArgsOf<std::function<T> > : public ArgsOf<T>
struct ArgsOf<std::function<T>> : public ArgsOf<T>
{ };
template <typename T>

View File

@ -26,13 +26,14 @@ static inline QPromise<void> qPromise()
});
}
template <typename T>
static inline QPromise<QVector<T> > qPromiseAll(const QVector<QPromise<T> >& promises)
template <typename T, template <typename, typename...> class Sequence = QVector, typename ...Args>
static inline QPromise<QVector<T>> qPromiseAll(const Sequence<QPromise<T>, Args...>& promises)
{
return QPromise<T>::all(promises);
}
static inline QPromise<void> qPromiseAll(const QVector<QPromise<void> >& promises)
template <template <typename, typename...> class Sequence = QVector, typename ...Args>
static inline QPromise<void> qPromiseAll(const Sequence<QPromise<void>, Args...>& promises)
{
return QPromise<void>::all(promises);
}

View File

@ -1,8 +1,2 @@
TEMPLATE = subdirs
SUBDIRS += \
benchmark \
future \
helpers \
qpromise \
requirements \
thread
SUBDIRS += qtpromise

View File

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

View File

@ -1,766 +0,0 @@
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
#include <QElapsedTimer>
using namespace QtPromise;
using namespace QtPromisePrivate;
class tst_qpromise : public QObject
{
Q_OBJECT
private Q_SLOTS:
void resolveSync();
void resolveSync_void();
void resolveDelayed();
void rejectSync();
void rejectDelayed();
void rejectThrows();
void thenReturns();
void thenThrows();
void thenNullPtr();
void thenSkipResult();
void thenDelayedResolved();
void thenDelayedRejected();
void failSameType();
void failBaseClass();
void failCatchAll();
void finallyFulfilled();
void finallyFulfilled_void();
void finallyRejected();
void finallyRejected_void();
void finallyThrows();
void finallyThrows_void();
void finallyDelayedResolved();
void finallyDelayedRejected();
void tapFulfilled();
void tapFulfilled_void();
void tapRejected();
void tapRejected_void();
void tapThrows();
void tapThrows_void();
void tapDelayedResolved();
void tapDelayedRejected();
void timeoutFulfilled();
void timeoutRejected();
void timeoutReject();
void delayFulfilled();
void delayRejected();
}; // class tst_qpromise
QTEST_MAIN(tst_qpromise)
#include "tst_qpromise.moc"
template <typename T>
T waitForValue(const QPromise<T>& promise, const T& initial)
{
T value(initial);
promise.then([&](const T& res) {
value = res;
}).wait();
return value;
}
template <typename T>
T waitForValue(const QPromise<void>& promise, const T& initial, const T& expected)
{
T value(initial);
promise.then([&]() {
value = expected;
}).wait();
return value;
}
template <typename T, typename E>
E waitForError(const QPromise<T>& promise, const E& initial)
{
E error(initial);
promise.fail([&](const E& err) {
error = err;
return T();
}).wait();
return error;
}
template <typename E>
E waitForError(const QPromise<void>& promise, const E& initial)
{
E error(initial);
promise.fail([&](const E& err) {
error = err;
}).wait();
return error;
}
void tst_qpromise::resolveSync()
{
{ // resolver(resolve)
QPromise<int> p([](const QPromiseResolve<int>& resolve) {
resolve(42);
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
}
{ // resolver(resolve, reject)
QPromise<int> p([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>&) {
resolve(42);
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
}
}
void tst_qpromise::resolveSync_void()
{
{ // resolver(resolve)
QPromise<void> p([](const QPromiseResolve<void>& resolve) {
resolve();
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1, 42), 42);
}
{ // resolver(resolve, reject)
QPromise<void> p([](const QPromiseResolve<void>& resolve, const QPromiseReject<void>&) {
resolve();
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1, 42), 42);
}
}
void tst_qpromise::resolveDelayed()
{
{ // resolver(resolve)
QPromise<int> p([](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(42);
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
}
{ // resolver(resolve, reject)
QPromise<int> p([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>&) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(42);
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
}
}
void tst_qpromise::rejectSync()
{
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
reject(QString("foo"));
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_qpromise::rejectDelayed()
{
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise::rejectThrows()
{
{ // resolver(resolve)
QPromise<int> p([](const QPromiseResolve<int>&) {
throw QString("foo");
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
{ // resolver(resolve, reject)
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>&) {
throw QString("foo");
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
}
void tst_qpromise::thenReturns()
{
auto p = QPromise<int>::resolve(42);
QVariantList values;
p.then([&](int res) {
values << res;
return QString::number(res+1);
}).then([&](const QString& res) {
values << res;
}).then([&]() {
values << 44;
}).wait();
QCOMPARE(values, QVariantList({42, QString("43"), 44}));
}
void tst_qpromise::thenThrows()
{
auto input = QPromise<int>::resolve(42);
auto output = input.then([](int res) {
throw QString("foo%1").arg(res);
return 42;
});
QString error;
output.then([&](int res) {
error += "bar" + QString::number(res);
}).fail([&](const QString& err) {
error += err;
}).wait();
QCOMPARE(input.isFulfilled(), true);
QCOMPARE(output.isRejected(), true);
QCOMPARE(error, QString("foo42"));
}
void tst_qpromise::thenNullPtr()
{
{ // resolved
auto p = QPromise<int>::resolve(42).then(nullptr);
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
}
{ // rejected
auto p = QPromise<int>::reject(QString("foo")).then(nullptr);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
}
void tst_qpromise::thenSkipResult()
{
auto p = QPromise<int>::resolve(42);
int value = -1;
p.then([&]() {
value = 43;
}).wait();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
QCOMPARE(value, 43);
}
void tst_qpromise::thenDelayedResolved()
{
auto p = QPromise<int>::resolve(42).then([](int res) {
return QPromise<QString>([=](const QPromiseResolve<QString>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(QString("foo%1").arg(res));
});
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString> >::value));
QCOMPARE(waitForValue(p, QString()), QString("foo42"));
}
void tst_qpromise::thenDelayedRejected()
{
auto p = QPromise<int>::resolve(42).then([](int res) {
return QPromise<void>([=](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo%1").arg(res));
});
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(waitForError(p, QString()), QString("foo42"));
}
void tst_qpromise::failSameType()
{
// http://en.cppreference.com/w/cpp/error/exception
auto p = QPromise<int>::reject(std::out_of_range("foo"));
QString error;
p.fail([&](const std::domain_error& e) {
error += QString(e.what()) + "0";
return -1;
}).fail([&](const std::out_of_range& e) {
error += QString(e.what()) + "1";
return -1;
}).fail([&](const std::exception& e) {
error += QString(e.what()) + "2";
return -1;
}).wait();
QCOMPARE(error, QString("foo1"));
}
void tst_qpromise::failBaseClass()
{
// http://en.cppreference.com/w/cpp/error/exception
auto p = QPromise<int>::reject(std::out_of_range("foo"));
QString error;
p.fail([&](const std::runtime_error& e) {
error += QString(e.what()) + "0";
return -1;
}).fail([&](const std::logic_error& e) {
error += QString(e.what()) + "1";
return -1;
}).fail([&](const std::exception& e) {
error += QString(e.what()) + "2";
return -1;
}).wait();
QCOMPARE(error, QString("foo1"));
}
void tst_qpromise::failCatchAll()
{
auto p = QPromise<int>::reject(std::out_of_range("foo"));
QString error;
p.fail([&](const std::runtime_error& e) {
error += QString(e.what()) + "0";
return -1;
}).fail([&]() {
error += "bar";
return -1;
}).fail([&](const std::exception& e) {
error += QString(e.what()) + "2";
return -1;
}).wait();
QCOMPARE(error, QString("bar"));
}
void tst_qpromise::finallyFulfilled()
{
int value = -1;
auto p = QPromise<int>::resolve(42).finally([&]() {
value = 8;
return 16; // ignored!
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, 8);
}
void tst_qpromise::finallyFulfilled_void()
{
int value = -1;
auto p = QPromise<void>::resolve().finally([&]() {
value = 8;
return 16; // ignored!
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, 8);
}
void tst_qpromise::finallyRejected()
{
int value = -1;
auto p = QPromise<int>::reject(QString("foo")).finally([&]() {
value = 8;
return 16; // ignored!
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(value, 8);
}
void tst_qpromise::finallyRejected_void()
{
int value = -1;
auto p = QPromise<void>::reject(QString("foo")).finally([&]() {
value = 8;
return 16; // ignored!
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(value, 8);
}
void tst_qpromise::finallyThrows()
{
{ // fulfilled
auto p = QPromise<int>::resolve(42).finally([&]() {
throw QString("bar");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
{ // rejected
auto p = QPromise<int>::reject(QString("foo")).finally([&]() {
throw QString("bar");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
}
void tst_qpromise::finallyThrows_void()
{
{ // fulfilled
auto p = QPromise<void>::resolve().finally([&]() {
throw QString("bar");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
{ // rejected
auto p = QPromise<void>::reject(QString("foo")).finally([&]() {
throw QString("bar");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
}
void tst_qpromise::finallyDelayedResolved()
{
{ // fulfilled
QVector<int> values;
auto p = QPromise<int>::resolve(42).finally([&]() {
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
qtpromise_defer([=, &values]() {
values << 64;
resolve(16); // ignored!
});
});
values << 8;
return p;
});
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(values, QVector<int>({8, 64}));
}
{ // rejected
QVector<int> values;
auto p = QPromise<int>::reject(QString("foo")).finally([&]() {
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
qtpromise_defer([=, &values]() {
values << 64;
resolve(16); // ignored!
});
});
values << 8;
return p;
});
p.then([&](int r) {
values << r;
}).wait();
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(values, QVector<int>({8, 64}));
}
}
void tst_qpromise::finallyDelayedRejected()
{
{ // fulfilled
auto p = QPromise<int>::resolve(42).finally([]() {
return QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
qtpromise_defer([=]() {
reject(QString("bar"));
});
});
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
{ // rejected
auto p = QPromise<int>::reject(QString("foo")).finally([]() {
return QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
qtpromise_defer([=]() {
reject(QString("bar"));
});
});
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
}
void tst_qpromise::tapFulfilled()
{
int value = -1;
auto p = QPromise<int>::resolve(42).tap([&](int res) {
value = res + 1;
return 8;
});
QCOMPARE(waitForValue(p, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, 43);
}
void tst_qpromise::tapFulfilled_void()
{
int value = -1;
auto p = QPromise<void>::resolve().tap([&]() {
value = 43;
return 8;
});
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, 43);
}
void tst_qpromise::tapRejected()
{
int value = -1;
auto p = QPromise<int>::reject(QString("foo")).tap([&](int res) {
value = res + 1;
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(value, -1);
}
void tst_qpromise::tapRejected_void()
{
int value = -1;
auto p = QPromise<void>::reject(QString("foo")).tap([&]() {
value = 43;
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(value, -1);
}
void tst_qpromise::tapThrows()
{
auto p = QPromise<int>::resolve(42).tap([&](int) {
throw QString("foo");
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise::tapThrows_void()
{
auto p = QPromise<void>::resolve().tap([&]() {
throw QString("foo");
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise::tapDelayedResolved()
{
QVector<int> values;
auto p = QPromise<int>::resolve(1).tap([&](int) {
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
qtpromise_defer([=, &values]() {
values << 3;
resolve(4); // ignored!
});
});
values << 2;
return p;
});
p.then([&](int r) {
values << r;
}).wait();
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(values, QVector<int>({2, 3, 1}));
}
void tst_qpromise::tapDelayedRejected()
{
QVector<int> values;
auto p = QPromise<int>::resolve(1).tap([&](int) {
QPromise<int> p([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
qtpromise_defer([=, &values]() {
values << 3;
reject(QString("foo"));
});
});
values << 2;
return p;
});
p.then([&](int r) {
values << r;
}).wait();
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(values, QVector<int>({2, 3}));
}
void tst_qpromise::timeoutFulfilled()
{
QElapsedTimer timer;
qint64 elapsed = -1;
timer.start();
auto p = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QTimer::singleShot(1000, [=]() {
resolve(42);
});
}).timeout(2000).finally([&]() {
elapsed = timer.elapsed();
});
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
QVERIFY(elapsed < 2000);
}
void tst_qpromise::timeoutRejected()
{
QElapsedTimer timer;
qint64 elapsed = -1;
timer.start();
auto p = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QTimer::singleShot(1000, [=]() {
reject(QString("foo"));
});
}).timeout(2000).finally([&]() {
elapsed = timer.elapsed();
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QVERIFY(elapsed < 2000);
}
void tst_qpromise::timeoutReject()
{
QElapsedTimer timer;
qint64 elapsed = -1;
bool failed = false;
timer.start();
auto p = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QTimer::singleShot(4000, [=]() {
resolve(42);
});
}).timeout(2000).finally([&]() {
elapsed = timer.elapsed();
});
p.fail([&](const QPromiseTimeoutException&) {
failed = true;
return -1;
}).wait();
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(p.isRejected(), true);
QCOMPARE(failed, true);
QVERIFY(elapsed >= 2000 * 0.95); // Qt::CoarseTimer (default) Coarse timers try to
QVERIFY(elapsed <= 2000 * 1.05); // keep accuracy within 5% of the desired interval.
}
void tst_qpromise::delayFulfilled()
{
QElapsedTimer timer;
qint64 elapsed = -1;
timer.start();
auto p = QPromise<int>::resolve(42).delay(1000).finally([&]() {
elapsed = timer.elapsed();
});
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
QVERIFY(elapsed >= 1000 * 0.95); // Qt::CoarseTimer (default) Coarse timers try to
QVERIFY(elapsed <= 1000 * 1.05); // keep accuracy within 5% of the desired interval.
}
void tst_qpromise::delayRejected()
{
QElapsedTimer timer;
qint64 elapsed = -1;
timer.start();
auto p = QPromise<int>::reject(QString("foo")).delay(1000).finally([&]() {
elapsed = timer.elapsed();
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QVERIFY(elapsed < 5);
}

View File

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

View File

@ -4,6 +4,14 @@
// Qt
#include <QtTest>
#ifdef Q_CC_MSVC
// MSVC calls the copy constructor on std::current_exception AND std::rethrow_exception
// https://stackoverflow.com/a/31820854
#define EXCEPT_CALL_COPY_CTOR 1
#else
#define EXCEPT_CALL_COPY_CTOR 0
#endif
using namespace QtPromise;
class tst_benchmark : public QObject
@ -12,6 +20,7 @@ class tst_benchmark : public QObject
private Q_SLOTS:
void valueResolve();
void valueResolveStatic();
void valueReject();
void valueThen();
void valueFinally();
@ -58,6 +67,35 @@ struct Data : public Logger
Data(int v): Logger(), m_value(v) {}
int value() const { return m_value; }
// MSVC 2013 doesn't support implicit generation of the move constructor and
// operator, so we need to explicitly define these methods and thus the copy
// constructor and operator also need to be explicitly defined (error C2280).
// https://stackoverflow.com/a/26581337
Data(const Data& other)
: Logger(other)
, m_value(other.m_value)
{ }
Data(Data&& other) : Logger(std::forward<Data>(other))
{
qSwap(m_value, other.m_value);
}
Data& operator=(const Data& other)
{
Logger::operator=(other);
m_value = other.m_value;
return *this;
}
Data& operator=(Data&& other)
{
Logger::operator=(std::forward<Data>(other));
qSwap(m_value, other.m_value);
return *this;
}
private:
int m_value;
};
@ -89,6 +127,31 @@ void tst_benchmark::valueResolve()
}
}
void tst_benchmark::valueResolveStatic()
{
{ // should move the value when resolved by rvalue
Data::logs().reset();
QPromise<Data>::resolve(Data(42)).wait();
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 0);
QCOMPARE(Data::logs().move, 1); // move value to the promise data
QCOMPARE(Data::logs().refs, 0);
}
{ // should create one copy of the value when resolved by lvalue
{
Data::logs().reset();
Data value(42);
QPromise<Data>::resolve(value).wait();
}
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 1); // copy value to the promise data
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
}
}
void tst_benchmark::valueReject()
{
{ // should not create any data if rejected
@ -264,7 +327,7 @@ void tst_benchmark::errorReject()
}).wait();
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 1); // copy value in std::exception_ptr
QCOMPARE(Data::logs().copy, 1 + EXCEPT_CALL_COPY_CTOR); // copy value in std::exception_ptr
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
}
@ -276,7 +339,7 @@ void tst_benchmark::errorReject()
}).wait();
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 1); // copy value to the promise data
QCOMPARE(Data::logs().copy, 1 + EXCEPT_CALL_COPY_CTOR); // copy value to the promise data
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
}
@ -292,7 +355,7 @@ void tst_benchmark::errorThen()
}).wait();
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 1); // (initial) copy value in std::exception_ptr
QCOMPARE(Data::logs().copy, 1 + 2 * EXCEPT_CALL_COPY_CTOR); // (initial) copy value in std::exception_ptr
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
QCOMPARE(value, 42);
@ -307,7 +370,7 @@ void tst_benchmark::errorThen()
}).wait();
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 1); // (initial) copy value in std::exception_ptr
QCOMPARE(Data::logs().copy, 1 + 4 * EXCEPT_CALL_COPY_CTOR); // (initial) copy value in std::exception_ptr
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
QCOMPARE(value, 42);

View File

@ -2,4 +2,4 @@ QT += concurrent
TARGET = tst_future
SOURCES += $$PWD/tst_future.cpp
include(../tests.pri)
include(../qtpromise.pri)

View File

@ -56,7 +56,7 @@ void tst_future::fulfilled()
return 42;
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isPending(), true);
p.then([&](int res) {
@ -72,7 +72,7 @@ void tst_future::fulfilled_void()
int result = -1;
auto p = qPromise(QtConcurrent::run([]() { }));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
p.then([&]() {
@ -91,7 +91,7 @@ void tst_future::rejected()
return 42;
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isPending(), true);
p.fail([&](const MyException& e) {
@ -110,7 +110,7 @@ void tst_future::rejected_void()
throw MyException("foo");
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
@ -130,7 +130,7 @@ void tst_future::unhandled()
return 42;
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isPending(), true);
@ -153,7 +153,7 @@ void tst_future::unhandled_void()
throw QString("foo");
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
p.fail([&](const QString& err) {
@ -261,9 +261,9 @@ 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();
});
return QtConcurrent::run([](const QString& error) {
return QString("foo%1").arg(error);
}, e.error());
});
QCOMPARE(input.isRejected(), true);
@ -282,9 +282,9 @@ 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();
});
return QtConcurrent::run([&](const QString& error) {
result = error;
}, e.error());
});
QCOMPARE(input.isRejected(), true);
@ -307,7 +307,7 @@ void tst_future::finally()
});
});
Q_STATIC_ASSERT((std::is_same<decltype(output), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(output), QPromise<int>>::value));
QCOMPARE(input.isFulfilled(), true);
QCOMPARE(output.isPending(), true);
@ -330,7 +330,7 @@ void tst_future::finallyRejected()
});
});
Q_STATIC_ASSERT((std::is_same<decltype(output), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(output), QPromise<int>>::value));
QCOMPARE(input.isFulfilled(), true);
QCOMPARE(output.isPending(), true);

View File

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

View File

@ -33,7 +33,7 @@ void tst_helpers::resolve()
int value = -1;
auto p = QtPromise::qPromise(42);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isFulfilled(), true);
@ -49,7 +49,7 @@ void tst_helpers::resolve_void()
int value = -1;
auto p = QtPromise::qPromise();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isFulfilled(), true);
@ -70,7 +70,7 @@ void tst_helpers::resolve_promise()
});
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(p.isPending(), true);
@ -93,7 +93,7 @@ void tst_helpers::resolve_promise_void()
});
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
@ -115,9 +115,9 @@ void tst_helpers::allFulfilled()
});
});
auto p = qPromiseAll(QVector<QPromise<int> >{p0, p2, p1});
auto p = qPromiseAll(QVector<QPromise<int>>{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int> > >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(p0.isFulfilled(), true);
@ -144,9 +144,9 @@ void tst_helpers::allFulfilled_void()
});
});
auto p = qPromiseAll(QVector<QPromise<void> >{p0, p2, p1});
auto p = qPromiseAll(QVector<QPromise<void>>{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(p0.isFulfilled(), true);
@ -169,9 +169,9 @@ void tst_helpers::allRejected()
});
});
auto p = qPromiseAll(QVector<QPromise<int> >{p0, p2, p1});
auto p = qPromiseAll(QVector<QPromise<int>>{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int> > >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(p0.isFulfilled(), true);
@ -199,9 +199,9 @@ void tst_helpers::allRejected_void()
});
});
auto p = qPromiseAll(QVector<QPromise<void> >{p0, p2, p1});
auto p = qPromiseAll(QVector<QPromise<void>>{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(p0.isFulfilled(), true);
@ -220,9 +220,9 @@ void tst_helpers::allRejected_void()
void tst_helpers::allEmpty()
{
auto p = qPromiseAll(QVector<QPromise<int> >());
auto p = qPromiseAll(QVector<QPromise<int>>());
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int> > >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p.isFulfilled(), true);
@ -236,9 +236,9 @@ void tst_helpers::allEmpty()
void tst_helpers::allEmpty_void()
{
auto p = qPromiseAll(QVector<QPromise<void> >());
auto p = qPromiseAll(QVector<QPromise<void>>());
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isFulfilled(), true);
}

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_all
SOURCES += $$PWD/tst_all.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,103 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_all : public QObject
{
Q_OBJECT
private Q_SLOTS:
void qList();
//void qVector();
void stdList();
void stdVector();
};
QTEST_MAIN(tst_qpromise_all)
#include "tst_all.moc"
namespace {
template <class Sequence>
struct SequenceTester
{
};
template <template <typename, typename...> class Sequence, typename ...Args>
struct SequenceTester<Sequence<QPromise<int>, Args...>>
{
static void exec()
{
Sequence<QPromise<int>, Args...> promises{
QPromise<int>::resolve(42),
QPromise<int>::resolve(43),
QPromise<int>::resolve(44)
};
promises.push_back(QPromise<int>::resolve(45));
promises.insert(++promises.begin(), QPromise<int>::resolve(46));
promises.pop_back();
auto p = QPromise<int>::all(promises);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 46, 43, 44}));
}
};
template <template <typename, typename...> class Sequence, typename ...Args>
struct SequenceTester<Sequence<QPromise<void>, Args...>>
{
static void exec()
{
Sequence<QPromise<void>, Args...> promises{
QPromise<void>::resolve(),
QPromise<void>::resolve(),
QPromise<void>::resolve()
};
promises.push_back(QPromise<void>::resolve());
promises.insert(++promises.begin(), QPromise<void>::resolve());
promises.pop_back();
auto p = QPromise<void>::all(promises);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(waitForValue(p, -1, 42), 42);
}
};
} // anonymous namespace
void tst_qpromise_all::qList()
{
SequenceTester<QList<QPromise<int>>>::exec();
SequenceTester<QList<QPromise<void>>>::exec();
}
// QVector::push_back/append isn't supported since it requires a default
// constructor (see https://github.com/simonbrunel/qtpromise/issues/3)
//void tst_qpromise_all::qVector()
//{
// SequenceTester<QVector<QPromise<int>>>::exec();
// SequenceTester<QVector<QPromise<void>>>::exec();
//}
void tst_qpromise_all::stdList()
{
SequenceTester<std::list<QPromise<int>>>::exec();
SequenceTester<std::list<QPromise<void>>>::exec();
}
void tst_qpromise_all::stdVector()
{
SequenceTester<std::vector<QPromise<int>>>::exec();
SequenceTester<std::vector<QPromise<void>>>::exec();
}

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_cast
SOURCES += $$PWD/tst_cast.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,135 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_cast : public QObject
{
Q_OBJECT
private Q_SLOTS:
void typeToVoid();
//void castToVoidOperator();
//void typeToVariant(); // QPromise<T>.cast<QVariant>()
//void variantToType();
//void voidToVariant(); // QPromise<void>.cast<QVariant>()
//void variantToVoid();
//void jsonValueToJsonObject();
};
QTEST_MAIN(tst_qpromise_cast)
#include "tst_cast.moc"
void tst_qpromise_cast::typeToVoid()
{
QPromise<void> p0 = QPromise<int>::resolve(42);
QPromise<void> p1 = QPromise<QString>::resolve(QString("foo"));
QVERIFY(p0.isFulfilled());
QVERIFY(p1.isFulfilled());
}
//void tst_qpromise_cast::castToVoidOperator()
//{
//auto p0 = QPromise<int>::resolve(42);
//QPromise<double> p1(p0);
//QPromise<void> p2(p0);
//auto p4 = QPromise<QString>::resolve("foo");
//
//p0.then([](int res) { qDebug() << res; });
//p1.then([](double res) { qDebug() << res; });
//p2.then([]() { qDebug() << "done"; });
//
//foo().then([]() {
// qDebug() << "done";
//}).wait();
//
//QPromise<QVariant>::all({p0, p1, p4}).then([](const QVector<QVariant>& res) {
// qDebug() << "all done!" << res;
//}).wait();
//}
/*
namespace {
template <typename T, typename U>
void test_qpromise_cast(const T& t, const U& u)
{
auto p = QPromise<T>::resolve(t).cast<U>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<U> >::value));
QCOMPARE(waitForValue(p, U()), u);
}
template <typename U>
void test_qpromise_cast(const U& u)
{
auto p = QPromise<void>::resolve().cast<U>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<U> >::value));
QCOMPARE(waitForValue(p, U()), u);
}
} // anonymous namespace
void tst_qpromise_cast::typeToVariant()
{
test_qpromise_cast(42, QVariant(42));
test_qpromise_cast(4.2, QVariant(4.2));
test_qpromise_cast(true, QVariant(true));
test_qpromise_cast(QString("foo"), QVariant(QString("foo")));
test_qpromise_cast(QUrl("http://x.y.z"), QVariant(QUrl("http://x.y.z")));
test_qpromise_cast(QSize(128, 256), QVariant(QSize(128, 256)));
test_qpromise_cast(QDate(2018, 1, 1), QVariant(QDate(2018, 1, 1)));
test_qpromise_cast(QJsonValue("foo"), QVariant(QJsonValue("foo")));
test_qpromise_cast(QStringList{"foo", "bar"}, QVariant(QStringList{"foo", "bar"}));
test_qpromise_cast(QList<QVariant>{"foo", 42}, QVariant(QList<QVariant>{"foo", 42}));
test_qpromise_cast(QMap<QString, QVariant>{{"foo", 42}}, QVariant(QVariantMap{{"foo", 42}}));
}
void tst_qpromise_cast::voidToVariant()
{
test_qpromise_cast(QVariant());
}
void tst_qpromise_cast::variantToType()
{
// invalid
// int
// QString
// QColor
// QList
}
/*
void tst_qpromise_cast::jsonValueToJsonObject()
{
{ // QJsonValue(Null) -> QJsonObject
auto p = QPromise<QJsonValue>::resolve(QJsonValue()).cast<QJsonObject>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QJsonObject> >::value));
const QJsonObject res = waitForValue(p, QJsonObject());
QVERIFY(res.isEmpty());
}
{ // QJsonValue(int) -> QJsonObject
auto p = QPromise<QJsonValue>::resolve(QJsonValue(42)).cast<QJsonObject>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QJsonObject> >::value));
const QJsonObject res = waitForValue(p, QJsonObject());
QVERIFY(res.isEmpty());
}
{ // QJsonValue(QJsonObject) -> QJsonObject
const QJsonObject object{{"magic", 42}};
auto p = QPromise<QJsonValue>::resolve(QJsonValue(object)).cast<QJsonObject>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QJsonObject> >::value));
const QJsonObject res = waitForValue(p, QJsonObject());
QCOMPARE(res.value("magic"), 42);
}
}
*/

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_construct
SOURCES += $$PWD/tst_construct.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,309 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
// STL
#include <memory>
using namespace QtPromise;
class tst_qpromise_construct : public QObject
{
Q_OBJECT
private Q_SLOTS:
void resolveSyncOneArg();
void resolveSyncOneArg_void();
void resolveSyncTwoArgs();
void resolveSyncTwoArgs_void();
void resolveAsyncOneArg();
void resolveAsyncOneArg_void();
void resolveAsyncTwoArgs();
void resolveAsyncTwoArgs_void();
void rejectThrowOneArg();
void rejectThrowOneArg_void();
void rejectThrowTwoArgs();
void rejectThrowTwoArgs_void();
void rejectSync();
void rejectSync_void();
void rejectAsync();
void rejectAsync_void();
void connectAndResolve();
void connectAndReject();
};
QTEST_MAIN(tst_qpromise_construct)
#include "tst_construct.moc"
void tst_qpromise_construct::resolveSyncOneArg()
{
QPromise<int> p([](const QPromiseResolve<int>& resolve) {
resolve(42);
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
}
void tst_qpromise_construct::resolveSyncOneArg_void()
{
QPromise<void> p([](const QPromiseResolve<void>& resolve) {
resolve();
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1, 42), 42);
}
void tst_qpromise_construct::resolveSyncTwoArgs()
{
QPromise<int> p([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>&) {
resolve(42);
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
}
void tst_qpromise_construct::resolveSyncTwoArgs_void()
{
QPromise<void> p([](const QPromiseResolve<void>& resolve, const QPromiseReject<void>&) {
resolve();
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1, 42), 42);
}
void tst_qpromise_construct::resolveAsyncOneArg()
{
QPromise<int> p([](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(42);
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
}
void tst_qpromise_construct::resolveAsyncOneArg_void()
{
QPromise<void> p([](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve();
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p.isFulfilled(), true);
}
void tst_qpromise_construct::resolveAsyncTwoArgs()
{
QPromise<int> p([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>&) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(42);
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
}
void tst_qpromise_construct::resolveAsyncTwoArgs_void()
{
QPromise<void> p([](const QPromiseResolve<void>& resolve, const QPromiseReject<void>&) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve();
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p.isFulfilled(), true);
}
void tst_qpromise_construct::rejectSync()
{
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
reject(QString("foo"));
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_qpromise_construct::rejectSync_void()
{
QPromise<void> p([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
reject(QString("foo"));
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1, 42), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_qpromise_construct::rejectAsync()
{
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_construct::rejectAsync_void()
{
QPromise<void> p([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1, 42), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_construct::rejectThrowOneArg()
{
QPromise<int> p([](const QPromiseResolve<int>&) {
throw QString("foo");
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_qpromise_construct::rejectThrowOneArg_void()
{
QPromise<void> p([](const QPromiseResolve<void>&) {
throw QString("foo");
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1, 42), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_qpromise_construct::rejectThrowTwoArgs()
{
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>&) {
throw QString("foo");
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_qpromise_construct::rejectThrowTwoArgs_void()
{
QPromise<void> p([](const QPromiseResolve<void>&, const QPromiseReject<void>&) {
throw QString("foo");
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1, 42), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_qpromise_construct::connectAndResolve()
{
QScopedPointer<QObject> object(new QObject());
std::weak_ptr<int> wptr;
{
auto p = QPromise<std::shared_ptr<int>>([&](
const QPromiseResolve<std::shared_ptr<int>>& resolve,
const QPromiseReject<std::shared_ptr<int>>& reject) {
connect(object.data(), &QObject::objectNameChanged,
[=, &wptr](const QString& name) {
std::shared_ptr<int> sptr(new int(42));
wptr = sptr;
if (name == "foobar") {
resolve(sptr);
} else {
reject(42);
}
});
});
QCOMPARE(p.isPending(), true);
object->setObjectName("foobar");
QCOMPARE(waitForValue(p, std::shared_ptr<int>()), wptr.lock());
QCOMPARE(wptr.use_count(), 1l); // "p" still holds a reference
}
QCOMPARE(wptr.use_count(), 0l);
}
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_qpromise_construct::connectAndReject()
{
QScopedPointer<QObject> object(new QObject());
std::weak_ptr<int> wptr;
{
auto p = QPromise<int>([&](
const QPromiseResolve<int>& resolve,
const QPromiseReject<int>& reject) {
connect(object.data(), &QObject::objectNameChanged,
[=, &wptr](const QString& name) {
std::shared_ptr<int> sptr(new int(42));
wptr = sptr;
if (name == "foobar") {
reject(sptr);
} else {
resolve(42);
}
});
});
QCOMPARE(p.isPending(), true);
object->setObjectName("foobar");
QCOMPARE(waitForError(p, std::shared_ptr<int>()), wptr.lock());
QCOMPARE(wptr.use_count(), 1l); // "p" still holds a reference
}
QCOMPARE(wptr.use_count(), 0l);
}

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_delay
SOURCES += $$PWD/tst_delay.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,55 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_delay : public QObject
{
Q_OBJECT
private Q_SLOTS:
void fulfilled();
void rejected();
};
QTEST_MAIN(tst_qpromise_delay)
#include "tst_delay.moc"
void tst_qpromise_delay::fulfilled()
{
QElapsedTimer timer;
qint64 elapsed = -1;
timer.start();
auto p = QPromise<int>::resolve(42).delay(1000).finally([&]() {
elapsed = timer.elapsed();
});
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
QVERIFY(elapsed >= 1000 * 0.95); // Qt::CoarseTimer (default) Coarse timers try to
QVERIFY(elapsed <= 1000 * 1.05); // keep accuracy within 5% of the desired interval.
}
void tst_qpromise_delay::rejected()
{
QElapsedTimer timer;
qint64 elapsed = -1;
timer.start();
auto p = QPromise<int>::reject(QString("foo")).delay(1000).finally([&]() {
elapsed = timer.elapsed();
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QVERIFY(elapsed < 5);
}

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_fail
SOURCES += $$PWD/tst_fail.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,83 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_fail : public QObject
{
Q_OBJECT
private Q_SLOTS:
void sameType();
void baseClass();
void catchAll();
// TODO: sync / async
};
QTEST_MAIN(tst_qpromise_fail)
#include "tst_fail.moc"
void tst_qpromise_fail::sameType()
{
// http://en.cppreference.com/w/cpp/error/exception
auto p = QPromise<int>::reject(std::out_of_range("foo"));
QString error;
p.fail([&](const std::domain_error& e) {
error += QString(e.what()) + "0";
return -1;
}).fail([&](const std::out_of_range& e) {
error += QString(e.what()) + "1";
return -1;
}).fail([&](const std::exception& e) {
error += QString(e.what()) + "2";
return -1;
}).wait();
QCOMPARE(error, QString("foo1"));
}
void tst_qpromise_fail::baseClass()
{
// http://en.cppreference.com/w/cpp/error/exception
auto p = QPromise<int>::reject(std::out_of_range("foo"));
QString error;
p.fail([&](const std::runtime_error& e) {
error += QString(e.what()) + "0";
return -1;
}).fail([&](const std::logic_error& e) {
error += QString(e.what()) + "1";
return -1;
}).fail([&](const std::exception& e) {
error += QString(e.what()) + "2";
return -1;
}).wait();
QCOMPARE(error, QString("foo1"));
}
void tst_qpromise_fail::catchAll()
{
auto p = QPromise<int>::reject(std::out_of_range("foo"));
QString error;
p.fail([&](const std::runtime_error& e) {
error += QString(e.what()) + "0";
return -1;
}).fail([&]() {
error += "bar";
return -1;
}).fail([&](const std::exception& e) {
error += QString(e.what()) + "2";
return -1;
}).wait();
QCOMPARE(error, QString("bar"));
}

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_finally
SOURCES += $$PWD/tst_finally.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,204 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_finally : public QObject
{
Q_OBJECT
private Q_SLOTS:
void fulfilledSync();
void fulfilledSync_void();
void fulfilledThrows();
void fulfilledThrows_void();
void fulfilledAsyncResolve();
void fulfilledAsyncReject();
void rejectedSync();
void rejectedSync_void();
void rejectedThrows();
void rejectedThrows_void();
void rejectedAsyncResolve();
void rejectedAsyncReject();
};
QTEST_MAIN(tst_qpromise_finally)
#include "tst_finally.moc"
void tst_qpromise_finally::fulfilledSync()
{
int value = -1;
auto p = QPromise<int>::resolve(42).finally([&]() {
value = 8;
return 16; // ignored!
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, 8);
}
void tst_qpromise_finally::fulfilledSync_void()
{
int value = -1;
auto p = QPromise<void>::resolve().finally([&]() {
value = 8;
return 16; // ignored!
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, 8);
}
void tst_qpromise_finally::fulfilledThrows()
{
auto p = QPromise<int>::resolve(42).finally([&]() {
throw QString("bar");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_finally::fulfilledThrows_void()
{
auto p = QPromise<void>::resolve().finally([&]() {
throw QString("bar");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_finally::fulfilledAsyncResolve()
{
QVector<int> values;
auto p = QPromise<int>::resolve(42).finally([&]() {
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=, &values]() {
values << 64;
resolve(16); // ignored!
});
});
values << 8;
return p;
});
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(values, QVector<int>({8, 64}));
}
void tst_qpromise_finally::fulfilledAsyncReject()
{
auto p = QPromise<int>::resolve(42).finally([]() {
return QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("bar"));
});
});
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_finally::rejectedSync()
{
int value = -1;
auto p = QPromise<int>::reject(QString("foo")).finally([&]() {
value = 8;
return 16; // ignored!
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(value, 8);
}
void tst_qpromise_finally::rejectedSync_void()
{
int value = -1;
auto p = QPromise<void>::reject(QString("foo")).finally([&]() {
value = 8;
return 16; // ignored!
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(value, 8);
}
void tst_qpromise_finally::rejectedThrows()
{
auto p = QPromise<int>::reject(QString("foo")).finally([&]() {
throw QString("bar");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_finally::rejectedThrows_void()
{
auto p = QPromise<void>::reject(QString("foo")).finally([&]() {
throw QString("bar");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_finally::rejectedAsyncResolve()
{
QVector<int> values;
auto p = QPromise<int>::reject(QString("foo")).finally([&]() {
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=, &values]() {
values << 64;
resolve(16); // ignored!
});
});
values << 8;
return p;
});
p.then([&](int r) {
values << r;
}).wait();
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(values, QVector<int>({8, 64}));
}
void tst_qpromise_finally::rejectedAsyncReject()
{
auto p = QPromise<int>::reject(QString("foo")).finally([]() {
return QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("bar"));
});
});
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_operators
SOURCES += $$PWD/tst_operators.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,230 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_operators : public QObject
{
Q_OBJECT
private Q_SLOTS:
void move();
void move_void();
void copy();
void copy_void();
void equalTo();
void equalTo_void();
void notEqualTo();
void notEqualTo_void();
void chaining();
void chaining_void();
};
QTEST_MAIN(tst_qpromise_operators)
#include "tst_operators.moc"
void tst_qpromise_operators::move()
{
auto p0 = QPromise<int>::resolve(42);
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(waitForValue(p0, -1), 42);
p0 = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
QCOMPARE(p0.isPending(), true);
QCOMPARE(waitForError(p0, QString()), QString("foo"));
p0 = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(43);
});
});
QCOMPARE(p0.isPending(), true);
QCOMPARE(waitForValue(p0, -1), 43);
}
void tst_qpromise_operators::move_void()
{
auto p0 = QPromise<void>::resolve();
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(waitForValue(p0, -1, 42), 42);
p0 = QPromise<void>([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
QCOMPARE(p0.isPending(), true);
QCOMPARE(waitForError(p0, QString()), QString("foo"));
p0 = QPromise<void>([](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve();
});
});
QCOMPARE(p0.isPending(), true);
QCOMPARE(waitForValue(p0, -1, 43), 43);
}
void tst_qpromise_operators::copy()
{
auto p0 = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
auto p1 = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(42);
});
});
QCOMPARE(p0 == p1, false);
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
p0 = p1;
QCOMPARE(p0 == p1, true);
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForValue(p0, -1), 42);
QCOMPARE(waitForValue(p1, -1), 42);
}
void tst_qpromise_operators::copy_void()
{
auto p0 = QPromise<void>([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
auto p1 = QPromise<void>([](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve();
});
});
QCOMPARE(p0 == p1, false);
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
p0 = p1;
QCOMPARE(p0 == p1, true);
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
p0.wait();
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
}
void tst_qpromise_operators::equalTo()
{
auto p0 = QPromise<int>::resolve(42);
auto p1 = QPromise<int>::resolve(42);
auto p2 = p1;
auto p3(p2);
QCOMPARE(p0 == p1, false);
QCOMPARE(p0 == p2, false);
QCOMPARE(p0 == p3, false);
QCOMPARE(p1 == p2, true);
QCOMPARE(p1 == p3, true);
QCOMPARE(p2 == p3, true);
}
void tst_qpromise_operators::equalTo_void()
{
auto p0 = QPromise<void>::resolve();
auto p1 = QPromise<void>::resolve();
auto p2 = p1;
auto p3(p2);
QCOMPARE(p0 == p1, false);
QCOMPARE(p0 == p2, false);
QCOMPARE(p0 == p3, false);
QCOMPARE(p1 == p2, true);
QCOMPARE(p1 == p3, true);
QCOMPARE(p2 == p3, true);
}
void tst_qpromise_operators::notEqualTo()
{
auto p0 = QPromise<int>::resolve(42);
auto p1 = QPromise<int>::resolve(42);
auto p2 = p1;
auto p3(p2);
QCOMPARE(p0 != p1, true);
QCOMPARE(p0 != p2, true);
QCOMPARE(p0 != p3, true);
QCOMPARE(p1 != p2, false);
QCOMPARE(p1 != p3, false);
QCOMPARE(p2 != p3, false);
}
void tst_qpromise_operators::notEqualTo_void()
{
auto p0 = QPromise<void>::resolve();
auto p1 = QPromise<void>::resolve();
auto p2 = p1;
auto p3(p2);
QCOMPARE(p0 != p1, true);
QCOMPARE(p0 != p2, true);
QCOMPARE(p0 != p3, true);
QCOMPARE(p1 != p2, false);
QCOMPARE(p1 != p3, false);
QCOMPARE(p2 != p3, false);
}
void tst_qpromise_operators::chaining()
{
auto p = QPromise<int>::resolve(1);
for (int i=0; i<4; ++i) {
p = p.then([](int res) {
return QPromise<int>::resolve(res * 2);
});
}
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1), 16);
}
void tst_qpromise_operators::chaining_void()
{
QVector<int> values;
auto p = QPromise<void>::resolve();
for (int i=0; i<4; ++i) {
p = p.then([i, &values]() {
values.append(i * 2);
return QPromise<void>::resolve();
});
}
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(values, QVector<int>({0, 2, 4, 6}));
}

View File

@ -0,0 +1,15 @@
TEMPLATE = subdirs
SUBDIRS += \
all \
cast \
construct \
delay \
fail \
finally \
operators \
reject \
resolve \
tap \
tapfail \
then \
timeout

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_reject
SOURCES += $$PWD/tst_reject.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,74 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
// STL
#include <memory>
using namespace QtPromise;
class tst_qpromise_reject : public QObject
{
Q_OBJECT
private Q_SLOTS:
void rejectWithValue();
void rejectWithQSharedPtr();
void rejectWithStdSharedPtr();
};
QTEST_MAIN(tst_qpromise_reject)
#include "tst_reject.moc"
void tst_qpromise_reject::rejectWithValue()
{
auto p = QPromise<int>::reject(42);
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForError(p, -1), 42);
}
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_qpromise_reject::rejectWithQSharedPtr()
{
QWeakPointer<int> wptr;
{
QSharedPointer<int> sptr(new int(42));
auto p = QPromise<int>::reject(sptr);
QCOMPARE(waitForError(p, QSharedPointer<int>()), sptr);
wptr = sptr;
sptr.reset();
QCOMPARE(wptr.isNull(), false); // "p" still holds a reference
}
QCOMPARE(wptr.isNull(), true);
}
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_qpromise_reject::rejectWithStdSharedPtr()
{
std::weak_ptr<int> wptr;
{
std::shared_ptr<int> sptr(new int(42));
auto p = QPromise<int>::reject(sptr);
QCOMPARE(waitForError(p, std::shared_ptr<int>()), sptr);
wptr = sptr;
sptr.reset();
QCOMPARE(wptr.use_count(), 1l); // "p" still holds a reference
}
QCOMPARE(wptr.use_count(), 0l);
}

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_resolve
SOURCES += $$PWD/tst_resolve.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,86 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
// STL
#include <memory>
using namespace QtPromise;
class tst_qpromise_resolve : public QObject
{
Q_OBJECT
private Q_SLOTS:
void resolveWithValue();
void resolveWithNoValue();
void resolveWithQSharedPtr();
void resolveWithStdSharedPtr();
};
QTEST_MAIN(tst_qpromise_resolve)
#include "tst_resolve.moc"
void tst_qpromise_resolve::resolveWithValue()
{
const int value = 42;
auto p0 = QPromise<int>::resolve(value);
auto p1 = QPromise<int>::resolve(43);
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(waitForValue(p0, -1), 42);
QCOMPARE(waitForValue(p1, -1), 43);
}
void tst_qpromise_resolve::resolveWithNoValue()
{
auto p = QPromise<void>::resolve();
QCOMPARE(p.isFulfilled(), true);
}
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_qpromise_resolve::resolveWithQSharedPtr()
{
QWeakPointer<int> wptr;
{
QSharedPointer<int> sptr(new int(42));
auto p = QPromise<QSharedPointer<int>>::resolve(sptr);
QCOMPARE(waitForValue(p, QSharedPointer<int>()), sptr);
wptr = sptr;
sptr.reset();
QCOMPARE(wptr.isNull(), false); // "p" still holds a reference
}
QCOMPARE(wptr.isNull(), true);
}
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_qpromise_resolve::resolveWithStdSharedPtr()
{
std::weak_ptr<int> wptr;
{
std::shared_ptr<int> sptr(new int(42));
auto p = QPromise<std::shared_ptr<int>>::resolve(sptr);
QCOMPARE(waitForValue(p, std::shared_ptr<int>()), sptr);
wptr = sptr;
sptr.reset();
QCOMPARE(wptr.use_count(), 1l); // "p" still holds a reference
}
QCOMPARE(wptr.use_count(), 0l);
}

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_tap
SOURCES += $$PWD/tst_tap.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,145 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_tap : public QObject
{
Q_OBJECT
private Q_SLOTS:
void fulfilledSync();
void fulfilledSync_void();
void fulfilledThrows();
void fulfilledThrows_void();
void fulfilledAsyncResolve();
void fulfilledAsyncReject();
void rejectedSync();
void rejectedSync_void();
};
QTEST_MAIN(tst_qpromise_tap)
#include "tst_tap.moc"
void tst_qpromise_tap::fulfilledSync()
{
int value = -1;
auto p = QPromise<int>::resolve(42).tap([&](int res) {
value = res + 1;
return 8;
});
QCOMPARE(waitForValue(p, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, 43);
}
void tst_qpromise_tap::fulfilledSync_void()
{
int value = -1;
auto p = QPromise<void>::resolve().tap([&]() {
value = 43;
return 8;
});
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, 43);
}
void tst_qpromise_tap::fulfilledThrows()
{
auto p = QPromise<int>::resolve(42).tap([&](int) {
throw QString("foo");
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_tap::fulfilledThrows_void()
{
auto p = QPromise<void>::resolve().tap([&]() {
throw QString("foo");
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_tap::fulfilledAsyncResolve()
{
QVector<int> values;
auto p = QPromise<int>::resolve(1).tap([&](int) {
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=, &values]() {
values << 3;
resolve(4); // ignored!
});
});
values << 2;
return p;
});
p.then([&](int r) {
values << r;
}).wait();
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(values, QVector<int>({2, 3, 1}));
}
void tst_qpromise_tap::fulfilledAsyncReject()
{
QVector<int> values;
auto p = QPromise<int>::resolve(1).tap([&](int) {
QPromise<int> p([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=, &values]() {
values << 3;
reject(QString("foo"));
});
});
values << 2;
return p;
});
p.then([&](int r) {
values << r;
}).wait();
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(values, QVector<int>({2, 3}));
}
void tst_qpromise_tap::rejectedSync()
{
int value = -1;
auto p = QPromise<int>::reject(QString("foo")).tap([&](int res) {
value = res + 1;
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(value, -1);
}
void tst_qpromise_tap::rejectedSync_void()
{
int value = -1;
auto p = QPromise<void>::reject(QString("foo")).tap([&]() {
value = 43;
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(value, -1);
}

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_tapfail
SOURCES += $$PWD/tst_tapfail.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,159 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_tapfail : public QObject
{
Q_OBJECT
private Q_SLOTS:
void fulfilled();
void fulfilled_void();
void rejected();
void rejected_void();
void throws();
void throws_void();
void delayedResolved();
void delayedRejected();
};
QTEST_MAIN(tst_qpromise_tapfail)
#include "tst_tapfail.moc"
void tst_qpromise_tapfail::fulfilled()
{
int value = -1;
auto p = QPromise<int>::resolve(42).tapFail([&]() {
value = 43;
});
QCOMPARE(waitForValue(p, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, -1);
}
void tst_qpromise_tapfail::fulfilled_void()
{
int value = -1;
auto p = QPromise<void>::resolve().tapFail([&]() {
value = 43;
});
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, -1);
}
void tst_qpromise_tapfail::rejected()
{
QStringList errors;
auto p0 = QPromise<int>::reject(QString("foo"))
.tapFail([&](const QString& err) {
errors << "1:" + err;
});
auto p1 = p0
.fail([&](const QString& err) {
errors << "2:" + err;
return 43;
});
QCOMPARE(waitForError(p0, QString()), QString("foo"));
QCOMPARE(waitForValue(p1, -1), 43);
QCOMPARE(p0.isRejected(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(errors, QStringList() << "1:foo" << "2:foo");
}
void tst_qpromise_tapfail::rejected_void()
{
QStringList errors;
auto p0 = QPromise<void>::reject(QString("foo"))
.tapFail([&](const QString& err) {
errors << "1:" + err;
});
auto p1 = p0
.fail([&](const QString& err) {
errors << "2:" + err;
});
QCOMPARE(waitForError(p0, QString()), QString("foo"));
QCOMPARE(waitForValue(p1, -1, 43), 43);
QCOMPARE(p0.isRejected(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(errors, QStringList() << "1:foo" << "2:foo");
}
void tst_qpromise_tapfail::throws()
{
auto p = QPromise<int>::reject(QString("foo"))
.tapFail([&]() {
throw QString("bar");
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_tapfail::throws_void()
{
auto p = QPromise<void>::reject(QString("foo"))
.tapFail([&]() {
throw QString("bar");
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_tapfail::delayedResolved()
{
QVector<int> values;
auto p = QPromise<int>::reject(QString("foo"))
.tapFail([&]() {
QPromise<void> p([&](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=, &values]() {
values << 3;
resolve(); // ignored!
});
});
values << 2;
return p;
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(values, QVector<int>({2, 3}));
}
void tst_qpromise_tapfail::delayedRejected()
{
QVector<int> values;
auto p = QPromise<int>::reject(QString("foo"))
.tapFail([&]() {
QPromise<void> p([&](
const QPromiseResolve<void>&,
const QPromiseReject<void>& reject){
QtPromisePrivate::qtpromise_defer([=, &values]() {
values << 3;
reject(QString("bar"));
});
});
values << 2;
return p;
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(values, QVector<int>({2, 3}));
}

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_then
SOURCES += $$PWD/tst_then.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,126 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_then : public QObject
{
Q_OBJECT
private Q_SLOTS:
void resolveSync();
void resolveAsync();
void rejectSync();
void rejectAsync();
void skipResult();
void noHandler();
};
QTEST_MAIN(tst_qpromise_then)
#include "tst_then.moc"
void tst_qpromise_then::resolveSync()
{
QVariantList values;
auto input = QPromise<int>::resolve(42);
auto output = input.then([&](int res) {
values << res;
return QString::number(res+1);
});
output.then([&](const QString& res) {
values << res;
}).then([&]() {
values << 44;
}).wait();
QCOMPARE(values, QVariantList({42, QString("43"), 44}));
QCOMPARE(input.isFulfilled(), true);
QCOMPARE(output.isFulfilled(), true);
}
void tst_qpromise_then::resolveAsync()
{
auto p = QPromise<int>::resolve(42).then([](int res) {
return QPromise<QString>([=](const QPromiseResolve<QString>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(QString("foo%1").arg(res));
});
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(waitForValue(p, QString()), QString("foo42"));
QCOMPARE(p.isFulfilled(), true);
}
void tst_qpromise_then::rejectSync()
{
auto input = QPromise<int>::resolve(42);
auto output = input.then([](int res) {
throw QString("foo%1").arg(res);
return 42;
});
QString error;
output.then([&](int res) {
error += "bar" + QString::number(res);
}).fail([&](const QString& err) {
error += err;
}).wait();
QCOMPARE(error, QString("foo42"));
QCOMPARE(input.isFulfilled(), true);
QCOMPARE(output.isRejected(), true);
}
void tst_qpromise_then::rejectAsync()
{
auto p = QPromise<int>::resolve(42).then([](int res) {
return QPromise<void>([=](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo%1").arg(res));
});
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(waitForError(p, QString()), QString("foo42"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_then::skipResult()
{
auto p = QPromise<int>::resolve(42);
int value = -1;
p.then([&]() {
value = 43;
}).wait();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(value, 43);
}
void tst_qpromise_then::noHandler()
{
{ // resolved
auto p = QPromise<int>::resolve(42).then(nullptr);
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
}
{ // rejected
auto p = QPromise<int>::reject(QString("foo")).then(nullptr);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
}

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_timeout
SOURCES += $$PWD/tst_timeout.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,92 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_timeout : public QObject
{
Q_OBJECT
private Q_SLOTS:
void fulfilled();
void rejected();
void timeout();
};
QTEST_MAIN(tst_qpromise_timeout)
#include "tst_timeout.moc"
void tst_qpromise_timeout::fulfilled()
{
QElapsedTimer timer;
qint64 elapsed = -1;
timer.start();
auto p = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QTimer::singleShot(1000, [=]() {
resolve(42);
});
}).timeout(2000).finally([&]() {
elapsed = timer.elapsed();
});
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
QVERIFY(elapsed < 2000);
}
void tst_qpromise_timeout::rejected()
{
QElapsedTimer timer;
qint64 elapsed = -1;
timer.start();
auto p = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QTimer::singleShot(1000, [=]() {
reject(QString("foo"));
});
}).timeout(2000).finally([&]() {
elapsed = timer.elapsed();
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QVERIFY(elapsed < 2000);
}
void tst_qpromise_timeout::timeout()
{
QElapsedTimer timer;
qint64 elapsed = -1;
bool failed = false;
timer.start();
auto p = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QTimer::singleShot(4000, [=]() {
resolve(42);
});
}).timeout(2000).finally([&]() {
elapsed = timer.elapsed();
});
p.fail([&](const QPromiseTimeoutException&) {
failed = true;
return -1;
}).wait();
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(p.isRejected(), true);
QCOMPARE(failed, true);
QVERIFY(elapsed >= 2000 * 0.95); // Qt::CoarseTimer (default) Coarse timers try to
QVERIFY(elapsed <= 2000 * 1.05); // keep accuracy within 5% of the desired interval.
}

View File

@ -1,12 +1,17 @@
TEMPLATE = app
CONFIG += testcase
CONFIG += testcase warn_on
QT += testlib
QT -= gui
DEFINES += QT_DEPRECATED_WARNINGS
coverage: {
gcc: {
# Additional warnings and make all warnings into errors
# https://github.com/simonbrunel/qtpromise/issues/10
gcc:QMAKE_CXXFLAGS += -Werror -Wold-style-cast
msvc:QMAKE_CXXFLAGS -= -WX
coverage {
gcc {
message("Code coverage enabled (gcov)")
QMAKE_CXXFLAGS += --coverage -O0 -g
QMAKE_LFLAGS += --coverage -O0 -g
@ -15,4 +20,7 @@ coverage: {
}
}
include(../../qtpromise.pri)
HEADERS += \
$$PWD/shared/utils.h
include(../../../qtpromise.pri)

View File

@ -0,0 +1,8 @@
TEMPLATE = subdirs
SUBDIRS += \
benchmark \
future \
helpers \
qpromise \
requirements \
thread

View File

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

View File

@ -5,7 +5,6 @@
#include <QtTest>
using namespace QtPromise;
using namespace QtPromisePrivate;
// https://promisesaplus.com/#requirements
class tst_requirements : public QObject
@ -41,7 +40,7 @@ void tst_requirements::statePending()
// 2.1.1.1. may transition to either the fulfilled state
{
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
qtpromise_defer([=]() { resolve(42); });
QtPromisePrivate::qtpromise_defer([=]() { resolve(42); });
});
QVERIFY(p.isPending());
@ -58,7 +57,7 @@ void tst_requirements::statePending()
// 2.1.1.1. ... or the rejected state
{
QPromise<int> p([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
qtpromise_defer([=]() { reject(QString("foo")); });
QtPromisePrivate::qtpromise_defer([=]() { reject(QString("foo")); });
});
QVERIFY(p.isPending());
@ -82,7 +81,7 @@ void tst_requirements::stateFulfilled()
QPromise<int> p([](
const QPromiseResolve<int>& resolve,
const QPromiseReject<int>& reject) {
qtpromise_defer([=]() {
QtPromisePrivate::qtpromise_defer([=]() {
// 2.1.2.2. must have a value, which must not change.
resolve(42);
resolve(43);
@ -115,7 +114,7 @@ void tst_requirements::stateRejected()
QPromise<int> p([](
const QPromiseResolve<int>& resolve,
const QPromiseReject<int>& reject) {
qtpromise_defer([=]() {
QtPromisePrivate::qtpromise_defer([=]() {
// 2.1.3.2. must have a reason, which must not change.
reject(QString("foo"));
reject(QString("bar"));
@ -196,7 +195,7 @@ void tst_requirements::thenOnFulfilled()
// 2.2.2. If onFulfilled is a function:
QVector<int> values;
QPromise<int> p0([](const QPromiseResolve<int>& resolve) {
qtpromise_defer([=]() {
QtPromisePrivate::qtpromise_defer([=]() {
// 2.2.2.3. it must not be called more than once
resolve(42);
resolve(43);
@ -216,7 +215,7 @@ void tst_requirements::thenOnFulfilled()
// with promises value as its first argument.
QVERIFY(p0.isFulfilled());
QVERIFY(p1.isFulfilled());
QCOMPARE(values, QVector<int>({42}));
QCOMPARE(values, QVector<int>{42});
}
void tst_requirements::thenOnRejected()
@ -224,7 +223,7 @@ void tst_requirements::thenOnRejected()
// 2.2.3. If onRejected is a function:
QStringList errors;
QPromise<void> p0([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
qtpromise_defer([=]() {
QtPromisePrivate::qtpromise_defer([=]() {
// 2.2.3.3. it must not be called more than once.
reject(QString("foo"));
reject(QString("bar"));
@ -275,12 +274,12 @@ void tst_requirements::thenMultipleCalls()
{
QVector<int> values;
QPromise<int> p([](const QPromiseResolve<int>& resolve) {
qtpromise_defer([=]() {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(42);
});
});
qPromiseAll(QVector<QPromise<void> >{
qPromiseAll(QVector<QPromise<void>>{
p.then([&](int r) { values << r + 1; }),
p.then([&](int r) { values << r + 2; }),
p.then([&](int r) { values << r + 3; })
@ -294,12 +293,12 @@ void tst_requirements::thenMultipleCalls()
{
QVector<int> values;
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
qtpromise_defer([=]() {
QtPromisePrivate::qtpromise_defer([=]() {
reject(8);
});
});
qPromiseAll(QVector<QPromise<int> >{
qPromiseAll(QVector<QPromise<int>>{
p.then(nullptr, [&](int r) { values << r + 1; return r + 1; }),
p.then(nullptr, [&](int r) { values << r + 2; return r + 2; }),
p.then(nullptr, [&](int r) { values << r + 3; return r + 3; })
@ -315,9 +314,9 @@ void tst_requirements::thenHandlers()
{
auto handler = [](){ return 42; };
auto p1 = QPromise<int>::resolve(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));
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
@ -352,7 +351,7 @@ void tst_requirements::thenHandlers()
QString value;
auto p1 = QPromise<QString>::resolve("42");
auto p2 = p1.then(nullptr, [](){ return QString(); });
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<QString> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<QString>>::value));
p2.then([&](const QString& e) { value = e; }).wait();
QVERIFY(p1.isFulfilled());

View File

@ -0,0 +1,47 @@
#ifndef QTPROMISE_TESTS_AUTO_SHARED_UTILS_H
#define QTPROMISE_TESTS_AUTO_SHARED_UTILS_H
#include <QtPromise>
template <typename T>
static inline T waitForValue(const QtPromise::QPromise<T>& promise, const T& initial)
{
T value(initial);
promise.then([&](const T& res) {
value = res;
}).wait();
return value;
}
template <typename T>
static inline T waitForValue(const QtPromise::QPromise<void>& promise, const T& initial, const T& expected)
{
T value(initial);
promise.then([&]() {
value = expected;
}).wait();
return value;
}
template <typename T, typename E>
static inline E waitForError(const QtPromise::QPromise<T>& promise, const E& initial)
{
E error(initial);
promise.fail([&](const E& err) {
error = err;
return T();
}).wait();
return error;
}
template <typename E>
static inline E waitForError(const QtPromise::QPromise<void>& promise, const E& initial)
{
E error(initial);
promise.fail([&](const E& err) {
error = err;
}).wait();
return error;
}
#endif // QTPROMISE_TESTS_AUTO_SHARED_UTILS_H

View File

@ -2,4 +2,4 @@ QT += concurrent
TARGET = tst_thread
SOURCES += $$PWD/tst_thread.cpp
include(../tests.pri)
include(../qtpromise.pri)

View File

@ -28,159 +28,159 @@ QTEST_MAIN(tst_thread)
void tst_thread::resolve()
{
int value = -1;
size_t target = 0;
size_t source = 0;
QThread* target = nullptr;
QThread* source = nullptr;
QPromise<int>([&](const QPromiseResolve<int>& resolve) {
QtConcurrent::run([=, &source]() {
source = (size_t)QThread::currentThread();
source = QThread::currentThread();
resolve(42);
});
}).then([&](int res) {
target = (size_t)QThread::currentThread();
target = QThread::currentThread();
value = res;
}).wait();
QVERIFY(source != 0);
QVERIFY(source != nullptr);
QVERIFY(source != target);
QCOMPARE(target, (size_t)QThread::currentThread());
QCOMPARE(target, QThread::currentThread());
QCOMPARE(value, 42);
}
void tst_thread::resolve_void()
{
int value = -1;
size_t target = 0;
size_t source = 0;
QThread* target = nullptr;
QThread* source = nullptr;
QPromise<void>([&](const QPromiseResolve<void>& resolve) {
QtConcurrent::run([=, &source]() {
source = (size_t)QThread::currentThread();
source = QThread::currentThread();
resolve();
});
}).then([&]() {
target = (size_t)QThread::currentThread();
target = QThread::currentThread();
value = 43;
}).wait();
QVERIFY(source != 0);
QVERIFY(source != nullptr);
QVERIFY(source != target);
QCOMPARE(target, (size_t)QThread::currentThread());
QCOMPARE(target, QThread::currentThread());
QCOMPARE(value, 43);
}
void tst_thread::reject()
{
QString error;
size_t target = 0;
size_t source = 0;
QThread* target = nullptr;
QThread* source = nullptr;
QPromise<int>([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtConcurrent::run([=, &source]() {
source = (size_t)QThread::currentThread();
source = QThread::currentThread();
reject(QString("foo"));
});
}).fail([&](const QString& err) {
target = (size_t)QThread::currentThread();
target = QThread::currentThread();
error = err;
return -1;
}).wait();
QVERIFY(source != 0);
QVERIFY(source != nullptr);
QVERIFY(source != target);
QCOMPARE(target, (size_t)QThread::currentThread());
QCOMPARE(target, QThread::currentThread());
QCOMPARE(error, QString("foo"));
}
void tst_thread::then()
{
size_t source;
QThread* source = nullptr;
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
source = (size_t)QThread::currentThread();
source = QThread::currentThread();
resolve(42);
});
size_t target;
int value = -1;
QThread* target = nullptr;
qPromise(QtConcurrent::run([&](const QPromise<int>& p) {
p.then([&](int res) {
target = (size_t)QThread::currentThread();
target = QThread::currentThread();
value = res;
}).wait();
}, p)).wait();
QVERIFY(target != 0);
QVERIFY(target != nullptr);
QVERIFY(source != target);
QCOMPARE(source, (size_t)QThread::currentThread());
QCOMPARE(source, QThread::currentThread());
QCOMPARE(value, 42);
}
void tst_thread::then_void()
{
size_t source;
QThread* source = nullptr;
QPromise<void> p([&](const QPromiseResolve<void>& resolve) {
source = (size_t)QThread::currentThread();
source = QThread::currentThread();
resolve();
});
size_t target;
int value = -1;
QThread* target = nullptr;
qPromise(QtConcurrent::run([&](const QPromise<void>& p) {
p.then([&]() {
target = (size_t)QThread::currentThread();
target = QThread::currentThread();
value = 43;
}).wait();
}, p)).wait();
QVERIFY(target != 0);
QVERIFY(target != nullptr);
QVERIFY(source != target);
QCOMPARE(source, (size_t)QThread::currentThread());
QCOMPARE(source, QThread::currentThread());
QCOMPARE(value, 43);
}
void tst_thread::fail()
{
size_t source;
QThread* source = nullptr;
QPromise<int> p([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
source = (size_t)QThread::currentThread();
source = QThread::currentThread();
reject(QString("foo"));
});
size_t target;
QString error;
QThread* target = nullptr;
qPromise(QtConcurrent::run([&](const QPromise<int>& p) {
p.fail([&](const QString& err) {
target = (size_t)QThread::currentThread();
target = QThread::currentThread();
error = err;
return -1;
}).wait();
}, p)).wait();
QVERIFY(target != 0);
QVERIFY(target != nullptr);
QVERIFY(source != target);
QCOMPARE(source, (size_t)QThread::currentThread());
QCOMPARE(source, QThread::currentThread());
QCOMPARE(error, QString("foo"));
}
void tst_thread::finally()
{
size_t source;
QThread* source = nullptr;
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
source = (size_t)QThread::currentThread();
source = QThread::currentThread();
resolve(42);
});
size_t target;
int value = -1;
QThread* target = nullptr;
qPromise(QtConcurrent::run([&](const QPromise<int>& p) {
p.finally([&]() {
target = (size_t)QThread::currentThread();
target = QThread::currentThread();
value = 43;
}).wait();
}, p)).wait();
QVERIFY(target != 0);
QVERIFY(target != nullptr);
QVERIFY(source != target);
QCOMPARE(source, (size_t)QThread::currentThread());
QCOMPARE(source, QThread::currentThread());
QCOMPARE(value, 43);
}