2017-06-03 16:04:28 +08:00
|
|
|
// QtPromise
|
|
|
|
#include <QtPromise>
|
|
|
|
|
|
|
|
// Qt
|
|
|
|
#include <QtTest>
|
|
|
|
|
2017-09-04 06:20:53 +08:00
|
|
|
#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
|
|
|
|
|
2017-06-03 16:04:28 +08:00
|
|
|
using namespace QtPromise;
|
|
|
|
|
2017-06-10 16:06:25 +08:00
|
|
|
class tst_benchmark : public QObject
|
2017-06-03 16:04:28 +08:00
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
2017-06-05 00:22:24 +08:00
|
|
|
private Q_SLOTS:
|
2017-06-03 16:04:28 +08:00
|
|
|
void valueResolve();
|
|
|
|
void valueReject();
|
|
|
|
void valueThen();
|
2017-08-23 16:56:39 +08:00
|
|
|
void valueFinally();
|
2017-08-24 00:12:57 +08:00
|
|
|
void valueTap();
|
2017-08-23 03:52:28 +08:00
|
|
|
void valueDelayed();
|
2017-06-03 16:04:28 +08:00
|
|
|
void errorReject();
|
|
|
|
void errorThen();
|
|
|
|
|
|
|
|
}; // class tst_benchmark
|
|
|
|
|
|
|
|
QTEST_MAIN(tst_benchmark)
|
|
|
|
#include "tst_benchmark.moc"
|
|
|
|
|
|
|
|
struct Logs {
|
|
|
|
int ctor = 0;
|
|
|
|
int copy = 0;
|
|
|
|
int move = 0;
|
|
|
|
int refs = 0;
|
|
|
|
|
|
|
|
void reset() {
|
|
|
|
ctor = 0;
|
|
|
|
copy = 0;
|
|
|
|
move = 0;
|
|
|
|
refs = 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Logger
|
|
|
|
{
|
|
|
|
Logger() { logs().ctor++; logs().refs++; }
|
|
|
|
Logger(const Logger&) { logs().copy++; logs().refs++; }
|
|
|
|
Logger(Logger&&) { logs().move++; logs().refs++; }
|
|
|
|
~Logger() { logs().refs--; }
|
|
|
|
|
|
|
|
Logger& operator=(const Logger&) { logs().copy++; return *this; }
|
|
|
|
Logger& operator=(Logger&&) { logs().move++; return *this; }
|
|
|
|
|
|
|
|
public: // STATICS
|
|
|
|
static Logs& logs() { static Logs logs; return logs; }
|
|
|
|
};
|
|
|
|
|
2017-06-10 16:06:25 +08:00
|
|
|
struct Data : public Logger
|
2017-06-03 16:04:28 +08:00
|
|
|
{
|
|
|
|
Data(int v): Logger(), m_value(v) {}
|
|
|
|
int value() const { return m_value; }
|
|
|
|
|
2017-09-04 06:20:53 +08:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2017-06-03 16:04:28 +08:00
|
|
|
private:
|
|
|
|
int m_value;
|
|
|
|
};
|
|
|
|
|
|
|
|
void tst_benchmark::valueResolve()
|
|
|
|
{
|
|
|
|
{ // should move the value when resolved by rvalue
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<Data>([&](const QPromiseResolve<Data>& resolve) {
|
|
|
|
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();
|
|
|
|
QPromise<Data>([&](const QPromiseResolve<Data>& resolve) {
|
|
|
|
Data value(42);
|
|
|
|
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
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<Data>([&](const QPromiseResolve<Data>&, const QPromiseReject<Data>& reject) {
|
|
|
|
reject(QString("foo"));
|
|
|
|
}).wait();
|
|
|
|
|
|
|
|
QCOMPARE(Data::logs().ctor, 0);
|
|
|
|
QCOMPARE(Data::logs().copy, 0);
|
|
|
|
QCOMPARE(Data::logs().move, 0);
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void tst_benchmark::valueThen()
|
|
|
|
{
|
|
|
|
{ // should not copy value on continutation if fulfilled
|
|
|
|
int value = -1;
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<Data>::resolve(Data(42)).then([&](const Data& res) {
|
|
|
|
value = res.value();
|
|
|
|
}).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);
|
|
|
|
QCOMPARE(value, 42);
|
|
|
|
}
|
|
|
|
{ // should not create value on continutation if rejected
|
|
|
|
int value = -1;
|
|
|
|
QString error;
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<Data>::reject(QString("foo")).then([&](const Data& res) {
|
|
|
|
value = res.value();
|
|
|
|
}, [&](const QString& err) {
|
|
|
|
error = err;
|
|
|
|
}).wait();
|
|
|
|
|
|
|
|
QCOMPARE(Data::logs().ctor, 0);
|
|
|
|
QCOMPARE(Data::logs().copy, 0);
|
2017-08-23 16:56:39 +08:00
|
|
|
QCOMPARE(Data::logs().move, 0);
|
2017-06-03 16:04:28 +08:00
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
QCOMPARE(error, QString("foo"));
|
|
|
|
QCOMPARE(value, -1);
|
|
|
|
}
|
|
|
|
{ // should move the returned value when fulfilled
|
|
|
|
int value = -1;
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<int>::resolve(42).then([&](int res) {
|
|
|
|
return Data(res+2);
|
|
|
|
}).then([&](const Data& res) {
|
|
|
|
value = res.value();
|
|
|
|
}).wait();
|
|
|
|
|
|
|
|
QCOMPARE(Data::logs().ctor, 1);
|
|
|
|
QCOMPARE(Data::logs().copy, 0);
|
|
|
|
QCOMPARE(Data::logs().move, 1); // move values to the next promise data
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
QCOMPARE(value, 44);
|
|
|
|
}
|
|
|
|
{ // should not create any data if handler throws
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<int>::resolve(42).then([&](int res) {
|
|
|
|
throw QString("foo");
|
|
|
|
return Data(res+2);
|
|
|
|
}).wait();
|
|
|
|
|
2017-08-23 03:52:28 +08:00
|
|
|
QCOMPARE(Data::logs().ctor, 0);
|
|
|
|
QCOMPARE(Data::logs().copy, 0);
|
|
|
|
QCOMPARE(Data::logs().move, 0);
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void tst_benchmark::valueDelayed()
|
|
|
|
{
|
|
|
|
{ // should not copy the value on continutation if fulfilled
|
|
|
|
int value = -1;
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<int>::resolve(42).then([&](int res) {
|
|
|
|
return QPromise<Data>::resolve(Data(res + 1));
|
|
|
|
}).then([&](const Data& res) {
|
|
|
|
value = res.value();
|
|
|
|
}).wait();
|
|
|
|
|
|
|
|
QCOMPARE(Data::logs().ctor, 1);
|
|
|
|
QCOMPARE(Data::logs().copy, 0);
|
|
|
|
QCOMPARE(Data::logs().move, 1); // move value to the input promise data
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
QCOMPARE(value, 43);
|
|
|
|
}
|
|
|
|
{ // should not create value on continutation if rejected
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<int>::resolve(42).then([&]() {
|
|
|
|
return QPromise<Data>::reject(QString("foo"));
|
|
|
|
}).wait();
|
|
|
|
|
2017-06-03 16:04:28 +08:00
|
|
|
QCOMPARE(Data::logs().ctor, 0);
|
|
|
|
QCOMPARE(Data::logs().copy, 0);
|
|
|
|
QCOMPARE(Data::logs().move, 0);
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-23 16:56:39 +08:00
|
|
|
void tst_benchmark::valueFinally()
|
|
|
|
{
|
|
|
|
{ // should not copy the value on continutation if fulfilled
|
|
|
|
int value = -1;
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<Data>::resolve(Data(42)).finally([&]() {
|
|
|
|
value = 42;
|
|
|
|
}).wait();
|
|
|
|
|
|
|
|
QCOMPARE(Data::logs().ctor, 1);
|
|
|
|
QCOMPARE(Data::logs().copy, 0);
|
|
|
|
QCOMPARE(Data::logs().move, 1); // move value to the input and output promise data
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
QCOMPARE(value, 42);
|
|
|
|
}
|
|
|
|
{ // should not create value on continutation if rejected
|
|
|
|
int value = -1;
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<Data>::reject(QString("foo")).finally([&]() {
|
|
|
|
value = 42;
|
|
|
|
}).wait();
|
|
|
|
|
|
|
|
QCOMPARE(Data::logs().ctor, 0);
|
|
|
|
QCOMPARE(Data::logs().copy, 0);
|
|
|
|
QCOMPARE(Data::logs().move, 0);
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
QCOMPARE(value, 42);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-24 00:12:57 +08:00
|
|
|
void tst_benchmark::valueTap()
|
|
|
|
{
|
|
|
|
{ // should not copy the value on continutation if fulfilled
|
|
|
|
int value = -1;
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<Data>::resolve(Data(42)).tap([&](const Data& res) {
|
|
|
|
value = res.value();
|
|
|
|
}).wait();
|
|
|
|
|
|
|
|
QCOMPARE(Data::logs().ctor, 1);
|
|
|
|
QCOMPARE(Data::logs().copy, 0);
|
|
|
|
QCOMPARE(Data::logs().move, 1); // move value to the input and output promise data
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
QCOMPARE(value, 42);
|
|
|
|
}
|
|
|
|
{ // should not create value on continutation if rejected
|
|
|
|
int value = -1;
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<Data>::reject(QString("foo")).tap([&](const Data& res) {
|
|
|
|
value = res.value();
|
|
|
|
}).wait();
|
|
|
|
|
|
|
|
QCOMPARE(Data::logs().ctor, 0);
|
|
|
|
QCOMPARE(Data::logs().copy, 0);
|
|
|
|
QCOMPARE(Data::logs().move, 0);
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
QCOMPARE(value, -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-03 16:04:28 +08:00
|
|
|
void tst_benchmark::errorReject()
|
|
|
|
{
|
|
|
|
{ // should create one copy of the error when rejected by rvalue
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<int>([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
|
|
|
|
reject(Data(42));
|
|
|
|
}).wait();
|
|
|
|
|
|
|
|
QCOMPARE(Data::logs().ctor, 1);
|
2017-09-04 06:20:53 +08:00
|
|
|
QCOMPARE(Data::logs().copy, 1 + EXCEPT_CALL_COPY_CTOR); // copy value in std::exception_ptr
|
2017-06-03 16:04:28 +08:00
|
|
|
QCOMPARE(Data::logs().move, 0);
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
}
|
|
|
|
{ // should create one copy of the error when rejected by lvalue (no extra copy)
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<int>([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
|
|
|
|
Data error(42);
|
|
|
|
reject(error);
|
|
|
|
}).wait();
|
|
|
|
|
|
|
|
QCOMPARE(Data::logs().ctor, 1);
|
2017-09-04 06:20:53 +08:00
|
|
|
QCOMPARE(Data::logs().copy, 1 + EXCEPT_CALL_COPY_CTOR); // copy value to the promise data
|
2017-06-03 16:04:28 +08:00
|
|
|
QCOMPARE(Data::logs().move, 0);
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void tst_benchmark::errorThen()
|
|
|
|
{
|
|
|
|
{ // should not copy error on continutation if rejected
|
|
|
|
int value = -1;
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<void>::reject(Data(42)).fail([&](const Data& res) {
|
|
|
|
value = res.value();
|
|
|
|
}).wait();
|
|
|
|
|
|
|
|
QCOMPARE(Data::logs().ctor, 1);
|
2017-09-04 06:20:53 +08:00
|
|
|
QCOMPARE(Data::logs().copy, 1 + 2 * EXCEPT_CALL_COPY_CTOR); // (initial) copy value in std::exception_ptr
|
2017-06-03 16:04:28 +08:00
|
|
|
QCOMPARE(Data::logs().move, 0);
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
QCOMPARE(value, 42);
|
|
|
|
}
|
|
|
|
{ // should not copy error on continutation if rethrown
|
|
|
|
int value = -1;
|
|
|
|
Data::logs().reset();
|
|
|
|
QPromise<void>::reject(Data(42)).fail([](const Data&) {
|
|
|
|
throw;
|
|
|
|
}).fail([&](const Data& res) {
|
|
|
|
value = res.value();
|
|
|
|
}).wait();
|
|
|
|
|
|
|
|
QCOMPARE(Data::logs().ctor, 1);
|
2017-09-04 06:20:53 +08:00
|
|
|
QCOMPARE(Data::logs().copy, 1 + 4 * EXCEPT_CALL_COPY_CTOR); // (initial) copy value in std::exception_ptr
|
2017-06-03 16:04:28 +08:00
|
|
|
QCOMPARE(Data::logs().move, 0);
|
|
|
|
QCOMPARE(Data::logs().refs, 0);
|
|
|
|
QCOMPARE(value, 42);
|
|
|
|
}
|
|
|
|
}
|