mirror of
https://github.com/simonbrunel/qtpromise.git
synced 2024-11-22 02:34:30 +08:00
Add support for creating promises from Qt signals (#25)
Introduce a new `QtPromise::connect()` helper that allows to create a promise resolved from a single signal and optionally, rejected by another one (from a different object or not). The promise type is determined by the type of the first signal argument (other arguments are currently ignored). A `QPromise<void>` is returned if the resolve signal doesn't provide any argument. If the rejection is emitted before the promise is resolved, the promise will be rejected with the value of the first argument (other arguments being ignored). If the rejection signal doesn't provide any argument, the promise will be rejected with `QPromiseUndefinedException` if the signal is emitted. Additionally, the promise will be automatically rejected with `QPromiseContextException` if the source object is destroyed before the promise is resolved.
This commit is contained in:
parent
47b90fb532
commit
1f30224578
@ -11,7 +11,8 @@ Requires [Qt 5.6](https://www.qt.io/download/) (or later) with [C++11 support en
|
||||
## Documentation
|
||||
|
||||
* [Getting Started](https://qtpromise.netlify.com/qtpromise/getting-started.html)
|
||||
* [QtConcurrent](https://qtpromise.netlify.com/qtpromise/qtconcurrent.html)
|
||||
* [Qt Concurrent](https://qtpromise.netlify.com/qtpromise/qtconcurrent.html)
|
||||
* [Qt Signals](https://qtpromise.netlify.com/qtpromise/qtsignals.html)
|
||||
* [Thread-Safety](https://qtpromise.netlify.com/qtpromise/thread-safety.html)
|
||||
* [API Reference](https://qtpromise.netlify.com/qtpromise/api-reference.html)
|
||||
|
||||
|
@ -17,6 +17,7 @@ module.exports = {
|
||||
sidebar: [
|
||||
'qtpromise/getting-started',
|
||||
'qtpromise/qtconcurrent',
|
||||
'qtpromise/qtsignals',
|
||||
'qtpromise/thread-safety',
|
||||
'qtpromise/api-reference',
|
||||
{
|
||||
@ -46,6 +47,7 @@ module.exports = {
|
||||
title: 'Helpers',
|
||||
children: [
|
||||
'qtpromise/helpers/attempt',
|
||||
'qtpromise/helpers/connect',
|
||||
'qtpromise/helpers/each',
|
||||
'qtpromise/helpers/filter',
|
||||
'qtpromise/helpers/map',
|
||||
@ -57,6 +59,7 @@ module.exports = {
|
||||
title: 'Exceptions',
|
||||
children: [
|
||||
'qtpromise/exceptions/canceled',
|
||||
'qtpromise/exceptions/context',
|
||||
'qtpromise/exceptions/timeout',
|
||||
'qtpromise/exceptions/undefined'
|
||||
]
|
||||
|
@ -27,6 +27,7 @@
|
||||
## Helpers
|
||||
|
||||
* [`QtPromise::attempt`](helpers/attempt.md)
|
||||
* [`QtPromise::connect`](helpers/connect.md)
|
||||
* [`QtPromise::each`](helpers/each.md)
|
||||
* [`QtPromise::filter`](helpers/filter.md)
|
||||
* [`QtPromise::map`](helpers/map.md)
|
||||
|
17
docs/qtpromise/exceptions/context.md
Normal file
17
docs/qtpromise/exceptions/context.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
title: QPromiseContextException
|
||||
---
|
||||
|
||||
# QPromiseContextException
|
||||
|
||||
*Since: 0.5.0*
|
||||
|
||||
When a promise is created using [`QtPromise::connect()`](../helpers/connect.md), this exception is thrown when the `sender` object is destroyed, for example:
|
||||
|
||||
```cpp
|
||||
auto promise = QtPromise::connect(sender, &Object::finished, &Object::error);
|
||||
|
||||
promise.fail([](const QPromiseContextException&) {
|
||||
// 'sender' has been destroyed.
|
||||
})
|
||||
```
|
48
docs/qtpromise/helpers/connect.md
Normal file
48
docs/qtpromise/helpers/connect.md
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
title: connect
|
||||
---
|
||||
|
||||
# QtPromise::connect
|
||||
|
||||
*Since: 0.5.0*
|
||||
|
||||
```cpp
|
||||
(1) QtPromise::connect(QObject* sender, Signal(T) resolver) -> QPromise<T>
|
||||
(2) QtPromise::connect(QObject* sender, Signal(T) resolver, Signal(R) rejecter) -> QPromise<T>
|
||||
(3) QtPromise::connect(QObject* sender, Signal(T) resolver, QObject* sender2, Signal(R) rejecter) -> QPromise<T>
|
||||
```
|
||||
|
||||
Creates a `QPromise<T>` that will be fulfilled with the `resolver` signal's first argument, or a `QPromise<void>` if `resolver` doesn't provide any argument.
|
||||
|
||||
The second `(2)` and third `(3)` variants of this method will reject the `output` promise when the `rejecter` signal is emitted. The rejection reason is the value of the `rejecter` signal's first argument or [`QPromiseUndefinedException`](../exceptions/undefined) if `rejected` doesn't provide any argument.
|
||||
|
||||
Additionally, the `output` promise will be automatically rejected with [`QPromiseContextException`](../exceptions/context.md) if `sender` is destroyed before the promise is resolved (that doesn't apply to `sender2`).
|
||||
|
||||
```cpp
|
||||
class Sender : public QObject
|
||||
{
|
||||
Q_SIGNALS:
|
||||
void finished(const QByteArray&);
|
||||
void error(ErrorCode);
|
||||
};
|
||||
|
||||
auto sender = new Sender();
|
||||
auto output = QtPromise::connect(sender, &Sender::finished, &Sender::error);
|
||||
|
||||
// 'output' resolves as soon as one of the following events happens:
|
||||
// - the 'sender' object is destroyed, the promise is rejected
|
||||
// - the 'finished' signal is emitted, the promise is fulfilled
|
||||
// - the 'error' signal is emitted, the promise is rejected
|
||||
|
||||
// 'output' type: QPromise<QByteArray>
|
||||
output.then([](const QByteArray& res) {
|
||||
// 'res' is the first argument of the 'finished' signal.
|
||||
}).fail([](ErrorCode err) {
|
||||
// 'err' is the first argument of the 'error' signal.
|
||||
}).fail([](const QPromiseContextException& err) {
|
||||
// the 'sender' object has been destroyed before any of
|
||||
// the 'finished' or 'error' signals have been emitted.
|
||||
});
|
||||
```
|
||||
|
||||
See also the [`Qt Signals`](../qtsignals.md) section for more examples.
|
@ -1,4 +1,4 @@
|
||||
# QtConcurrent
|
||||
# Qt Concurrent
|
||||
|
||||
QtPromise integrates with [QtConcurrent](https://doc.qt.io/qt-5/qtconcurrent-index.html) to make easy chaining QFuture with QPromise.
|
||||
|
||||
|
89
docs/qtpromise/qtsignals.md
Normal file
89
docs/qtpromise/qtsignals.md
Normal file
@ -0,0 +1,89 @@
|
||||
# Qt Signals
|
||||
|
||||
QtPromise supports creating promises that are resolved or rejected by regular [Qt signals](https://doc.qt.io/qt-5/signalsandslots.html).
|
||||
|
||||
::: warning IMPORTANT
|
||||
A promise connected to a signal will be resolved (fulfilled or rejected) **only one time**, no matter if the signals are emitted multiple times. Internally, the promise is disconnected from all signals as soon as one signal is emitted.
|
||||
:::
|
||||
|
||||
## Resolve Signal
|
||||
|
||||
The [`QtPromise::connect()`](helpers/connect.md) helper allows to create a promise resolved from a single signal:
|
||||
|
||||
```cpp
|
||||
// [signal] Object::finished(const QByteArray&)
|
||||
auto output = QtPromise::connect(obj, &Object::finished);
|
||||
|
||||
// output type: QPromise<QByteArray>
|
||||
output.then([](const QByteArray& data) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
If the signal doesn't provide any argument, a `QPromise<void>` is returned:
|
||||
|
||||
```cpp
|
||||
// [signal] Object::done()
|
||||
auto output = QtPromise::connect(obj, &Object::done);
|
||||
|
||||
// output type: QPromise<void>
|
||||
output.then([]() {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
::: tip NOTE
|
||||
QtPromise currently only supports single argument signals, which means that only the first argument is used to fulfill or reject the connected promise, other arguments being ignored.
|
||||
:::
|
||||
|
||||
## Reject Signal
|
||||
|
||||
The [`QtPromise::connect()`](helpers/connect.md) helper also allows to reject the promise from another signal:
|
||||
|
||||
```cpp
|
||||
// [signal] Object::finished(const QByteArray& data)
|
||||
// [signal] Object::error(ObjectError error)
|
||||
auto output = QtPromise::connect(obj, &Object::finished, &Object::error);
|
||||
|
||||
// output type: QPromise<QByteArray>
|
||||
output.then([](const QByteArray& data) {
|
||||
// {...}
|
||||
}).fail(const ObjectError& error) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
If the rejection signal doesn't provide any argument, the promise will be rejected
|
||||
with [`QPromiseUndefinedException`](../exceptions/undefined), for example:
|
||||
|
||||
```cpp
|
||||
// [signal] Object::finished()
|
||||
// [signal] Object::error()
|
||||
auto output = QtPromise::connect(obj, &Object::finished, &Object::error);
|
||||
|
||||
// output type: QPromise<QByteArray>
|
||||
output.then([]() {
|
||||
// {...}
|
||||
}).fail(const QPromiseUndefinedException& error) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
A third variant allows to connect the resolve and reject signals from different objects:
|
||||
|
||||
```cpp
|
||||
// [signal] ObjectA::finished(const QByteArray& data)
|
||||
// [signal] ObjectB::error(ObjectBError error)
|
||||
auto output = QtPromise::connect(objA, &ObjectA::finished, objB, &ObjectB::error);
|
||||
|
||||
// output type: QPromise<QByteArray>
|
||||
output.then([](const QByteArray& data) {
|
||||
// {...}
|
||||
}).fail(const ObjectBError& error) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
Additionally to the rejection signal, promises created using [`QtPromise::connect()`](helpers/connect.md) are automatically rejected with [`QPromiseContextException`](exceptions/context.md) if the sender is destroyed before fulfilling the promise.
|
||||
|
||||
See [`QtPromise::connect()`](helpers/connect.md) for more details.
|
@ -2,6 +2,7 @@
|
||||
#define QTPROMISE_MODULE_H
|
||||
|
||||
#include "../src/qtpromise/qpromise.h"
|
||||
#include "../src/qtpromise/qpromiseconnections.h"
|
||||
#include "../src/qtpromise/qpromisefuture.h"
|
||||
#include "../src/qtpromise/qpromisehelpers.h"
|
||||
|
||||
|
48
src/qtpromise/qpromiseconnections.h
Normal file
48
src/qtpromise/qpromiseconnections.h
Normal file
@ -0,0 +1,48 @@
|
||||
#ifndef QTPROMISE_QPROMISECONNECTIONS_H
|
||||
#define QTPROMISE_QPROMISECONNECTIONS_H
|
||||
|
||||
// Qt
|
||||
#include <QSharedPointer>
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
class QPromiseConnections
|
||||
{
|
||||
public:
|
||||
QPromiseConnections() : m_d(new Data()) { }
|
||||
|
||||
int count() const { return m_d->connections.count(); }
|
||||
|
||||
void disconnect() const { m_d->disconnect(); }
|
||||
|
||||
void operator<<(QMetaObject::Connection&& other) const
|
||||
{
|
||||
m_d->connections.append(std::move(other));
|
||||
}
|
||||
|
||||
private:
|
||||
struct Data
|
||||
{
|
||||
QVector<QMetaObject::Connection> connections;
|
||||
|
||||
~Data() {
|
||||
if (!connections.empty()) {
|
||||
qWarning("QPromiseConnections: destroyed with unhandled connections.");
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
for (const auto& connection: connections) {
|
||||
QObject::disconnect(connection);
|
||||
}
|
||||
connections.clear();
|
||||
}
|
||||
};
|
||||
|
||||
QSharedPointer<Data> m_d;
|
||||
};
|
||||
|
||||
} // namespace QtPromise
|
||||
|
||||
#endif // QTPROMISE_QPROMISECONNECTIONS_H
|
@ -19,6 +19,16 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class QPromiseContextException : public QException
|
||||
{
|
||||
public:
|
||||
void raise() const Q_DECL_OVERRIDE { throw *this; }
|
||||
QPromiseContextException* clone() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return new QPromiseContextException(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class QPromiseTimeoutException : public QException
|
||||
{
|
||||
public:
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define QTPROMISE_QPROMISEHELPERS_H
|
||||
|
||||
#include "qpromise_p.h"
|
||||
#include "qpromisehelpers_p.h"
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
@ -63,6 +64,44 @@ attempt(Functor&& fn, Args&&... args)
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Sender, typename Signal>
|
||||
static inline typename QtPromisePrivate::PromiseFromSignal<Signal>
|
||||
connect(const Sender* sender, Signal signal)
|
||||
{
|
||||
using namespace QtPromisePrivate;
|
||||
using T = typename PromiseFromSignal<Signal>::Type;
|
||||
|
||||
return QPromise<T>(
|
||||
[&](const QPromiseResolve<T>& resolve, const QPromiseReject<T>& reject) {
|
||||
QPromiseConnections connections;
|
||||
connectSignalToResolver(connections, resolve, sender, signal);
|
||||
connectDestroyedToReject(connections, reject, sender);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename FSender, typename FSignal, typename RSender, typename RSignal>
|
||||
static inline typename QtPromisePrivate::PromiseFromSignal<FSignal>
|
||||
connect(const FSender* fsender, FSignal fsignal, const RSender* rsender, RSignal rsignal)
|
||||
{
|
||||
using namespace QtPromisePrivate;
|
||||
using T = typename PromiseFromSignal<FSignal>::Type;
|
||||
|
||||
return QPromise<T>(
|
||||
[&](const QPromiseResolve<T>& resolve, const QPromiseReject<T>& reject) {
|
||||
QPromiseConnections connections;
|
||||
connectSignalToResolver(connections, resolve, fsender, fsignal);
|
||||
connectSignalToResolver(connections, reject, rsender, rsignal);
|
||||
connectDestroyedToReject(connections, reject, fsender);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Sender, typename FSignal, typename RSignal>
|
||||
static inline typename QtPromisePrivate::PromiseFromSignal<FSignal>
|
||||
connect(const Sender* sender, FSignal fsignal, RSignal rsignal)
|
||||
{
|
||||
return connect(sender, fsignal, sender, rsignal);
|
||||
}
|
||||
|
||||
template <typename Sequence, typename Functor>
|
||||
static inline QPromise<Sequence> each(const Sequence& values, Functor&& fn)
|
||||
{
|
||||
|
91
src/qtpromise/qpromisehelpers_p.h
Normal file
91
src/qtpromise/qpromisehelpers_p.h
Normal file
@ -0,0 +1,91 @@
|
||||
#ifndef QTPROMISE_QPROMISEHELPERS_P_H
|
||||
#define QTPROMISE_QPROMISEHELPERS_P_H
|
||||
|
||||
#include "qpromiseconnections.h"
|
||||
#include "qpromiseexceptions.h"
|
||||
|
||||
namespace QtPromisePrivate {
|
||||
|
||||
// TODO: Suppress QPrivateSignal trailing private signal args
|
||||
// TODO: Support deducing tuple from args (might require MSVC2017)
|
||||
|
||||
template <typename Signal>
|
||||
using PromiseFromSignal = typename QtPromise::QPromise<Unqualified<typename ArgsOf<Signal>::first>>;
|
||||
|
||||
// Connect signal() to QPromiseResolve
|
||||
template <typename Sender, typename Signal>
|
||||
typename std::enable_if<(ArgsOf<Signal>::count == 0)>::type
|
||||
connectSignalToResolver(
|
||||
const QtPromise::QPromiseConnections& connections,
|
||||
const QtPromise::QPromiseResolve<void>& resolve,
|
||||
const Sender* sender,
|
||||
Signal signal)
|
||||
{
|
||||
connections << QObject::connect(sender, signal, [=]() {
|
||||
connections.disconnect();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
// Connect signal() to QPromiseReject
|
||||
template <typename T, typename Sender, typename Signal>
|
||||
typename std::enable_if<(ArgsOf<Signal>::count == 0)>::type
|
||||
connectSignalToResolver(
|
||||
const QtPromise::QPromiseConnections& connections,
|
||||
const QtPromise::QPromiseReject<T>& reject,
|
||||
const Sender* sender,
|
||||
Signal signal)
|
||||
{
|
||||
connections << QObject::connect(sender, signal, [=]() {
|
||||
connections.disconnect();
|
||||
reject(QtPromise::QPromiseUndefinedException());
|
||||
});
|
||||
}
|
||||
|
||||
// Connect signal(args...) to QPromiseResolve
|
||||
template <typename T, typename Sender, typename Signal>
|
||||
typename std::enable_if<(ArgsOf<Signal>::count >= 1)>::type
|
||||
connectSignalToResolver(
|
||||
const QtPromise::QPromiseConnections& connections,
|
||||
const QtPromise::QPromiseResolve<T>& resolve,
|
||||
const Sender* sender,
|
||||
Signal signal)
|
||||
{
|
||||
connections << QObject::connect(sender, signal, [=](const T& value) {
|
||||
connections.disconnect();
|
||||
resolve(value);
|
||||
});
|
||||
}
|
||||
|
||||
// Connect signal(args...) to QPromiseReject
|
||||
template <typename T, typename Sender, typename Signal>
|
||||
typename std::enable_if<(ArgsOf<Signal>::count >= 1)>::type
|
||||
connectSignalToResolver(
|
||||
const QtPromise::QPromiseConnections& connections,
|
||||
const QtPromise::QPromiseReject<T>& reject,
|
||||
const Sender* sender,
|
||||
Signal signal)
|
||||
{
|
||||
using V = Unqualified<typename ArgsOf<Signal>::first>;
|
||||
connections << QObject::connect(sender, signal, [=](const V& value) {
|
||||
connections.disconnect();
|
||||
reject(value);
|
||||
});
|
||||
}
|
||||
|
||||
// Connect QObject::destroyed signal to QPromiseReject
|
||||
template <typename T, typename Sender>
|
||||
void connectDestroyedToReject(
|
||||
const QtPromise::QPromiseConnections& connections,
|
||||
const QtPromise::QPromiseReject<T>& reject,
|
||||
const Sender* sender)
|
||||
{
|
||||
connections << QObject::connect(sender, &QObject::destroyed, [=]() {
|
||||
connections.disconnect();
|
||||
reject(QtPromise::QPromiseContextException());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace QtPromisePrivate
|
||||
|
||||
#endif // QTPROMISE_QPROMISEHELPERS_P_H
|
@ -2,8 +2,10 @@ HEADERS += \
|
||||
$$PWD/qpromise.h \
|
||||
$$PWD/qpromise.inl \
|
||||
$$PWD/qpromise_p.h \
|
||||
$$PWD/qpromiseconnections.h \
|
||||
$$PWD/qpromiseexceptions.h \
|
||||
$$PWD/qpromisefuture.h \
|
||||
$$PWD/qpromiseglobal.h \
|
||||
$$PWD/qpromisehelpers.h \
|
||||
$$PWD/qpromisehelpers_p.h \
|
||||
$$PWD/qpromiseresolver.h
|
||||
|
@ -15,6 +15,7 @@ class tst_exceptions : public QObject
|
||||
|
||||
private Q_SLOTS:
|
||||
void canceled();
|
||||
void context();
|
||||
void timeout();
|
||||
void undefined();
|
||||
|
||||
@ -41,6 +42,11 @@ void tst_exceptions::canceled()
|
||||
verify<QPromiseCanceledException>();
|
||||
}
|
||||
|
||||
void tst_exceptions::context()
|
||||
{
|
||||
verify<QPromiseContextException>();
|
||||
}
|
||||
|
||||
void tst_exceptions::timeout()
|
||||
{
|
||||
verify<QPromiseTimeoutException>();
|
||||
|
4
tests/auto/qtpromise/helpers/connect/connect.pro
Normal file
4
tests/auto/qtpromise/helpers/connect/connect.pro
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET = tst_helpers_connect
|
||||
SOURCES += $$PWD/tst_connect.cpp
|
||||
|
||||
include(../../qtpromise.pri)
|
211
tests/auto/qtpromise/helpers/connect/tst_connect.cpp
Normal file
211
tests/auto/qtpromise/helpers/connect/tst_connect.cpp
Normal file
@ -0,0 +1,211 @@
|
||||
#include "../../shared/object.h"
|
||||
#include "../../shared/utils.h"
|
||||
|
||||
// QtPromise
|
||||
#include <QtPromise>
|
||||
|
||||
// Qt
|
||||
#include <QtTest>
|
||||
|
||||
using namespace QtPromise;
|
||||
|
||||
class tst_helpers_connect : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
// connect(QObject* sender, Signal resolver)
|
||||
void resolveOneSenderNoArg();
|
||||
void resolveOneSenderOneArg();
|
||||
void resolveOneSenderManyArgs();
|
||||
|
||||
// connect(QObject* sender, Signal resolver, Signal rejecter)
|
||||
void rejectOneSenderNoArg();
|
||||
void rejectOneSenderOneArg();
|
||||
void rejectOneSenderManyArgs();
|
||||
void rejectOneSenderDestroyed();
|
||||
|
||||
// connect(QObject* s0, Signal resolver, QObject* s1, Signal rejecter)
|
||||
void rejectTwoSendersNoArg();
|
||||
void rejectTwoSendersOneArg();
|
||||
void rejectTwoSendersManyArgs();
|
||||
void rejectTwoSendersDestroyed();
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_helpers_connect)
|
||||
#include "tst_connect.moc"
|
||||
|
||||
void tst_helpers_connect::resolveOneSenderNoArg()
|
||||
{
|
||||
Object sender;
|
||||
QtPromisePrivate::qtpromise_defer([&]() {
|
||||
Q_EMIT sender.noArgSignal();
|
||||
});
|
||||
|
||||
auto p = QtPromise::connect(&sender, &Object::noArgSignal);
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
|
||||
QCOMPARE(sender.hasConnections(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
QCOMPARE(sender.hasConnections(), false);
|
||||
}
|
||||
|
||||
void tst_helpers_connect::resolveOneSenderOneArg()
|
||||
{
|
||||
Object sender;
|
||||
QtPromisePrivate::qtpromise_defer([&]() {
|
||||
Q_EMIT sender.oneArgSignal("foo");
|
||||
});
|
||||
|
||||
auto p = QtPromise::connect(&sender, &Object::oneArgSignal);
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
|
||||
QCOMPARE(sender.hasConnections(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, QString()), QString("foo"));
|
||||
QCOMPARE(sender.hasConnections(), false);
|
||||
}
|
||||
|
||||
void tst_helpers_connect::resolveOneSenderManyArgs()
|
||||
{
|
||||
Object sender;
|
||||
QtPromisePrivate::qtpromise_defer([&]() {
|
||||
Q_EMIT sender.twoArgsSignal(42, "foo");
|
||||
});
|
||||
|
||||
auto p = QtPromise::connect(&sender, &Object::twoArgsSignal);
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
|
||||
QCOMPARE(sender.hasConnections(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, -1), 42);
|
||||
QCOMPARE(sender.hasConnections(), false);
|
||||
}
|
||||
|
||||
void tst_helpers_connect::rejectOneSenderNoArg()
|
||||
{
|
||||
Object sender;
|
||||
QtPromisePrivate::qtpromise_defer([&]() {
|
||||
Q_EMIT sender.noArgSignal();
|
||||
});
|
||||
|
||||
auto p = QtPromise::connect(&sender, &Object::oneArgSignal, &Object::noArgSignal);
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
|
||||
QCOMPARE(sender.hasConnections(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForRejected<QPromiseUndefinedException>(p), true);
|
||||
QCOMPARE(sender.hasConnections(), false);
|
||||
}
|
||||
|
||||
void tst_helpers_connect::rejectOneSenderOneArg()
|
||||
{
|
||||
Object sender;
|
||||
QtPromisePrivate::qtpromise_defer([&]() {
|
||||
Q_EMIT sender.oneArgSignal("bar");
|
||||
});
|
||||
|
||||
auto p = QtPromise::connect(&sender, &Object::noArgSignal, &Object::oneArgSignal);
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
|
||||
QCOMPARE(sender.hasConnections(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForError(p, QString()), QString("bar"));
|
||||
QCOMPARE(sender.hasConnections(), false);
|
||||
}
|
||||
|
||||
void tst_helpers_connect::rejectOneSenderManyArgs()
|
||||
{
|
||||
Object sender;
|
||||
QtPromisePrivate::qtpromise_defer([&]() {
|
||||
Q_EMIT sender.twoArgsSignal(42, "bar");
|
||||
});
|
||||
|
||||
auto p = QtPromise::connect(&sender, &Object::noArgSignal, &Object::twoArgsSignal);
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
|
||||
QCOMPARE(sender.hasConnections(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForError(p, -1), 42);
|
||||
QCOMPARE(sender.hasConnections(), false);
|
||||
}
|
||||
|
||||
void tst_helpers_connect::rejectOneSenderDestroyed()
|
||||
{
|
||||
Object* sender = new Object();
|
||||
QtPromisePrivate::qtpromise_defer([&]() {
|
||||
sender->deleteLater();
|
||||
});
|
||||
|
||||
auto p = QtPromise::connect(sender, &Object::twoArgsSignal);
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForRejected<QPromiseContextException>(p), true);
|
||||
}
|
||||
|
||||
void tst_helpers_connect::rejectTwoSendersNoArg()
|
||||
{
|
||||
Object s0, s1;
|
||||
QtPromisePrivate::qtpromise_defer([&]() {
|
||||
Q_EMIT s1.noArgSignal();
|
||||
});
|
||||
|
||||
auto p = QtPromise::connect(&s0, &Object::noArgSignal, &s1, &Object::noArgSignal);
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
|
||||
QCOMPARE(s0.hasConnections(), true);
|
||||
QCOMPARE(s1.hasConnections(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForRejected<QPromiseUndefinedException>(p), true);
|
||||
QCOMPARE(s0.hasConnections(), false);
|
||||
QCOMPARE(s1.hasConnections(), false);
|
||||
}
|
||||
|
||||
void tst_helpers_connect::rejectTwoSendersOneArg()
|
||||
{
|
||||
Object s0, s1;
|
||||
QtPromisePrivate::qtpromise_defer([&]() {
|
||||
Q_EMIT s1.oneArgSignal("bar");
|
||||
});
|
||||
|
||||
auto p = QtPromise::connect(&s0, &Object::noArgSignal, &s1, &Object::oneArgSignal);
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
|
||||
QCOMPARE(s0.hasConnections(), true);
|
||||
QCOMPARE(s1.hasConnections(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForError(p, QString()), QString("bar"));
|
||||
QCOMPARE(s0.hasConnections(), false);
|
||||
QCOMPARE(s1.hasConnections(), false);
|
||||
}
|
||||
|
||||
void tst_helpers_connect::rejectTwoSendersManyArgs()
|
||||
{
|
||||
Object s0, s1;
|
||||
QtPromisePrivate::qtpromise_defer([&]() {
|
||||
Q_EMIT s1.twoArgsSignal(42, "bar");
|
||||
});
|
||||
|
||||
auto p = QtPromise::connect(&s0, &Object::noArgSignal, &s1, &Object::twoArgsSignal);
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
|
||||
QCOMPARE(s0.hasConnections(), true);
|
||||
QCOMPARE(s1.hasConnections(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForError(p, -1), 42);
|
||||
QCOMPARE(s0.hasConnections(), false);
|
||||
QCOMPARE(s1.hasConnections(), false);
|
||||
}
|
||||
|
||||
void tst_helpers_connect::rejectTwoSendersDestroyed()
|
||||
{
|
||||
Object* s0 = new Object();
|
||||
Object* s1 = new Object();
|
||||
|
||||
QtPromisePrivate::qtpromise_defer([&]() {
|
||||
QObject::connect(s1, &QObject::destroyed, [&]() {
|
||||
// Let's first delete s1, then resolve s0 and make sure
|
||||
// we don't reject when the rejecter object is destroyed.
|
||||
Q_EMIT s0->noArgSignal();
|
||||
});
|
||||
|
||||
s1->deleteLater();
|
||||
});
|
||||
|
||||
auto p = QtPromise::connect(s0, &Object::noArgSignal, s1, &Object::twoArgsSignal);
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
}
|
@ -2,6 +2,7 @@ TEMPLATE = subdirs
|
||||
SUBDIRS += \
|
||||
all \
|
||||
attempt \
|
||||
connect \
|
||||
each \
|
||||
filter \
|
||||
map \
|
||||
|
@ -0,0 +1,4 @@
|
||||
TARGET = tst_qpromiseconnections
|
||||
SOURCES += $$PWD/tst_qpromiseconnections.cpp
|
||||
|
||||
include(../qtpromise.pri)
|
@ -0,0 +1,81 @@
|
||||
#include "../shared/object.h"
|
||||
#include "../shared/utils.h"
|
||||
|
||||
// QtPromise
|
||||
#include <QtPromise>
|
||||
|
||||
// Qt
|
||||
#include <QtTest>
|
||||
|
||||
using namespace QtPromise;
|
||||
|
||||
class tst_qpromiseconnections : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void connections();
|
||||
void destruction();
|
||||
void senderDestroyed();
|
||||
|
||||
}; // class tst_qpromiseconnections
|
||||
|
||||
QTEST_MAIN(tst_qpromiseconnections)
|
||||
#include "tst_qpromiseconnections.moc"
|
||||
|
||||
void tst_qpromiseconnections::connections()
|
||||
{
|
||||
Object sender;
|
||||
|
||||
QPromiseConnections connections;
|
||||
QCOMPARE(sender.hasConnections(), false);
|
||||
QCOMPARE(connections.count(), 0);
|
||||
|
||||
connections << connect(&sender, &Object::noArgSignal, [=]() {});
|
||||
QCOMPARE(sender.hasConnections(), true);
|
||||
QCOMPARE(connections.count(), 1);
|
||||
|
||||
connections << connect(&sender, &Object::twoArgsSignal, [=]() {});
|
||||
QCOMPARE(sender.hasConnections(), true);
|
||||
QCOMPARE(connections.count(), 2);
|
||||
|
||||
connections.disconnect();
|
||||
QCOMPARE(sender.hasConnections(), false);
|
||||
QCOMPARE(connections.count(), 0);
|
||||
}
|
||||
|
||||
void tst_qpromiseconnections::destruction()
|
||||
{
|
||||
Object sender;
|
||||
|
||||
{
|
||||
QPromiseConnections connections;
|
||||
QCOMPARE(sender.hasConnections(), false);
|
||||
QCOMPARE(connections.count(), 0);
|
||||
|
||||
connections << connect(&sender, &Object::noArgSignal, [=]() {});
|
||||
QCOMPARE(sender.hasConnections(), true);
|
||||
QCOMPARE(connections.count(), 1);
|
||||
}
|
||||
|
||||
QCOMPARE(sender.hasConnections(), false);
|
||||
}
|
||||
|
||||
void tst_qpromiseconnections::senderDestroyed()
|
||||
{
|
||||
QPromiseConnections connections;
|
||||
QCOMPARE(connections.count(), 0);
|
||||
|
||||
{
|
||||
Object sender;
|
||||
QCOMPARE(sender.hasConnections(), false);
|
||||
|
||||
connections << connect(&sender, &Object::noArgSignal, [=]() {});
|
||||
QCOMPARE(sender.hasConnections(), true);
|
||||
QCOMPARE(connections.count(), 1);
|
||||
}
|
||||
|
||||
// should not throw
|
||||
connections.disconnect();
|
||||
QCOMPARE(connections.count(), 0);
|
||||
}
|
@ -21,6 +21,7 @@ coverage {
|
||||
}
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/shared/object.h \
|
||||
$$PWD/shared/utils.h
|
||||
|
||||
include(../../../qtpromise.pri)
|
||||
|
@ -5,5 +5,6 @@ SUBDIRS += \
|
||||
future \
|
||||
helpers \
|
||||
qpromise \
|
||||
qpromiseconnections \
|
||||
requirements \
|
||||
thread
|
||||
|
25
tests/auto/qtpromise/shared/object.h
Normal file
25
tests/auto/qtpromise/shared/object.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef QTPROMISE_TESTS_AUTO_SHARED_SENDER_H
|
||||
#define QTPROMISE_TESTS_AUTO_SHARED_SENDER_H
|
||||
|
||||
// Qt
|
||||
#include <QObject>
|
||||
|
||||
class Object : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
bool hasConnections() const { return m_connections > 0; }
|
||||
|
||||
Q_SIGNALS:
|
||||
void noArgSignal();
|
||||
void oneArgSignal(const QString& v);
|
||||
void twoArgsSignal(int v1, const QString& v0);
|
||||
|
||||
protected:
|
||||
int m_connections = 0;
|
||||
void connectNotify(const QMetaMethod&) Q_DECL_OVERRIDE { ++m_connections; }
|
||||
void disconnectNotify(const QMetaMethod&) Q_DECL_OVERRIDE { --m_connections; }
|
||||
};
|
||||
|
||||
#endif // ifndef QTPROMISE_TESTS_AUTO_SHARED_SENDER_H
|
Loading…
Reference in New Issue
Block a user