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:
Simon Brunel 2019-03-04 13:58:01 +01:00
parent 47b90fb532
commit 1f30224578
22 changed files with 686 additions and 2 deletions

View File

@ -11,7 +11,8 @@ Requires [Qt 5.6](https://www.qt.io/download/) (or later) with [C++11 support en
## Documentation ## Documentation
* [Getting Started](https://qtpromise.netlify.com/qtpromise/getting-started.html) * [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) * [Thread-Safety](https://qtpromise.netlify.com/qtpromise/thread-safety.html)
* [API Reference](https://qtpromise.netlify.com/qtpromise/api-reference.html) * [API Reference](https://qtpromise.netlify.com/qtpromise/api-reference.html)

View File

@ -17,6 +17,7 @@ module.exports = {
sidebar: [ sidebar: [
'qtpromise/getting-started', 'qtpromise/getting-started',
'qtpromise/qtconcurrent', 'qtpromise/qtconcurrent',
'qtpromise/qtsignals',
'qtpromise/thread-safety', 'qtpromise/thread-safety',
'qtpromise/api-reference', 'qtpromise/api-reference',
{ {
@ -46,6 +47,7 @@ module.exports = {
title: 'Helpers', title: 'Helpers',
children: [ children: [
'qtpromise/helpers/attempt', 'qtpromise/helpers/attempt',
'qtpromise/helpers/connect',
'qtpromise/helpers/each', 'qtpromise/helpers/each',
'qtpromise/helpers/filter', 'qtpromise/helpers/filter',
'qtpromise/helpers/map', 'qtpromise/helpers/map',
@ -57,6 +59,7 @@ module.exports = {
title: 'Exceptions', title: 'Exceptions',
children: [ children: [
'qtpromise/exceptions/canceled', 'qtpromise/exceptions/canceled',
'qtpromise/exceptions/context',
'qtpromise/exceptions/timeout', 'qtpromise/exceptions/timeout',
'qtpromise/exceptions/undefined' 'qtpromise/exceptions/undefined'
] ]

View File

@ -27,6 +27,7 @@
## Helpers ## Helpers
* [`QtPromise::attempt`](helpers/attempt.md) * [`QtPromise::attempt`](helpers/attempt.md)
* [`QtPromise::connect`](helpers/connect.md)
* [`QtPromise::each`](helpers/each.md) * [`QtPromise::each`](helpers/each.md)
* [`QtPromise::filter`](helpers/filter.md) * [`QtPromise::filter`](helpers/filter.md)
* [`QtPromise::map`](helpers/map.md) * [`QtPromise::map`](helpers/map.md)

View 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.
})
```

View 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.

View File

@ -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. QtPromise integrates with [QtConcurrent](https://doc.qt.io/qt-5/qtconcurrent-index.html) to make easy chaining QFuture with QPromise.

View 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.

View File

@ -2,6 +2,7 @@
#define QTPROMISE_MODULE_H #define QTPROMISE_MODULE_H
#include "../src/qtpromise/qpromise.h" #include "../src/qtpromise/qpromise.h"
#include "../src/qtpromise/qpromiseconnections.h"
#include "../src/qtpromise/qpromisefuture.h" #include "../src/qtpromise/qpromisefuture.h"
#include "../src/qtpromise/qpromisehelpers.h" #include "../src/qtpromise/qpromisehelpers.h"

View 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

View File

@ -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 class QPromiseTimeoutException : public QException
{ {
public: public:

View File

@ -2,6 +2,7 @@
#define QTPROMISE_QPROMISEHELPERS_H #define QTPROMISE_QPROMISEHELPERS_H
#include "qpromise_p.h" #include "qpromise_p.h"
#include "qpromisehelpers_p.h"
namespace QtPromise { 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> template <typename Sequence, typename Functor>
static inline QPromise<Sequence> each(const Sequence& values, Functor&& fn) static inline QPromise<Sequence> each(const Sequence& values, Functor&& fn)
{ {

View 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

View File

@ -2,8 +2,10 @@ HEADERS += \
$$PWD/qpromise.h \ $$PWD/qpromise.h \
$$PWD/qpromise.inl \ $$PWD/qpromise.inl \
$$PWD/qpromise_p.h \ $$PWD/qpromise_p.h \
$$PWD/qpromiseconnections.h \
$$PWD/qpromiseexceptions.h \ $$PWD/qpromiseexceptions.h \
$$PWD/qpromisefuture.h \ $$PWD/qpromisefuture.h \
$$PWD/qpromiseglobal.h \ $$PWD/qpromiseglobal.h \
$$PWD/qpromisehelpers.h \ $$PWD/qpromisehelpers.h \
$$PWD/qpromisehelpers_p.h \
$$PWD/qpromiseresolver.h $$PWD/qpromiseresolver.h

View File

@ -15,6 +15,7 @@ class tst_exceptions : public QObject
private Q_SLOTS: private Q_SLOTS:
void canceled(); void canceled();
void context();
void timeout(); void timeout();
void undefined(); void undefined();
@ -41,6 +42,11 @@ void tst_exceptions::canceled()
verify<QPromiseCanceledException>(); verify<QPromiseCanceledException>();
} }
void tst_exceptions::context()
{
verify<QPromiseContextException>();
}
void tst_exceptions::timeout() void tst_exceptions::timeout()
{ {
verify<QPromiseTimeoutException>(); verify<QPromiseTimeoutException>();

View File

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

View 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);
}

View File

@ -2,6 +2,7 @@ TEMPLATE = subdirs
SUBDIRS += \ SUBDIRS += \
all \ all \
attempt \ attempt \
connect \
each \ each \
filter \ filter \
map \ map \

View File

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

View File

@ -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);
}

View File

@ -21,6 +21,7 @@ coverage {
} }
HEADERS += \ HEADERS += \
$$PWD/shared/object.h \
$$PWD/shared/utils.h $$PWD/shared/utils.h
include(../../../qtpromise.pri) include(../../../qtpromise.pri)

View File

@ -5,5 +5,6 @@ SUBDIRS += \
future \ future \
helpers \ helpers \
qpromise \ qpromise \
qpromiseconnections \
requirements \ requirements \
thread thread

View 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