add promise unit test.

This commit is contained in:
amass 2024-09-15 23:39:23 +08:00
parent c82f079bcb
commit d0a6ac56a4
12 changed files with 1038 additions and 98 deletions

View File

@ -37,7 +37,7 @@ add_executable(UnitTest main.cpp
) )
target_compile_definitions(UnitTest target_compile_definitions(UnitTest
PUBLIC LOG_FILTER_LEVEL=1 PUBLIC LOG_FILTER_LEVEL=2
) )
target_link_libraries(UnitTest target_link_libraries(UnitTest

View File

@ -1,9 +1,109 @@
#include "Promise.h" #include "BoostLog.h"
#include "IoContext.h"
#include <boost/asio/defer.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/strand.hpp>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <sstream> #include <sstream>
#include <variant> #include <variant>
#include <vector> #include <vector>
#include "Promise.h" // 在这之前 #include "IoContext.h" 以支持 delay
BOOST_AUTO_TEST_SUITE(PromiseTestCase)
class PromiseContext {
public:
PromiseContext() : m_ioContext(std::thread::hardware_concurrency()), m_guarder(m_ioContext.get_executor()) {
}
~PromiseContext() {
m_guarder.reset();
for (auto &thread : m_threads) {
thread.join();
}
}
void start() {
if (m_threads.empty()) {
auto threads = std::thread::hardware_concurrency();
for (int i = 0; i < threads; i++) {
m_threads.emplace_back([this]() { m_ioContext.run(); });
}
}
}
void setValue(float value) {
if (m_value != value) {
m_value = value;
}
}
inline static const float kRes = 0.42f;
float functionNoArg() const {
return m_value;
}
float functionArgByVal(float v) const {
return v + m_value;
}
float functionArgByRef(const float &v) const {
return v + m_value;
}
static float kFnNoArg() {
return kRes;
}
static float kFnArgByVal(float v) {
return v;
}
static float kFnArgByRef(const float &v) {
return v;
}
static float stringToFloatNoArg() {
return std::stof(kErr);
}
static float stringToFloatArgByVal(std::string e) {
return std::stof(e);
}
static float stringToFloatArgByRef(const std::string &e) {
return std::stof(e);
}
enum class Enum1 { Value0, Value1, Value2 };
enum class Enum2 { Value0, Value1, Value2 };
struct Foo {
Foo() = default;
Foo(int foo) : m_foo{foo} {
}
bool operator==(const Foo &rhs) const {
return m_foo == rhs.m_foo;
}
int m_foo{-1};
};
struct Bar {
Bar() = default;
Bar(const Foo &other) : m_bar{other.m_foo} {
}
bool operator==(const Bar &rhs) const {
return m_bar == rhs.m_bar;
}
int m_bar{-1};
};
protected:
boost::asio::io_context m_ioContext;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> m_guarder;
std::vector<std::thread> m_threads;
float m_value = 249.f;
inline static const std::string kErr{"0.42"};
};
template <typename T> template <typename T>
static inline T waitForValue(const Promise<T> &promise, const T &initial) { static inline T waitForValue(const Promise<T> &promise, const T &initial) {
T value(initial); T value(initial);
@ -18,6 +118,616 @@ static inline T waitForValue(const Promise<void> &promise, const T &initial, con
return value; return value;
} }
template <typename T, typename E>
static inline E waitForError(const Promise<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 Promise<void> &promise, const E &initial) {
E error(initial);
promise.fail([&](const E &err) { error = err; }).wait();
return error;
}
template <typename E, typename T>
static inline bool waitForRejected(const T &promise) {
bool result = false;
promise.tapFail([&](const E &) { result = true; }).wait();
return result;
}
BOOST_AUTO_TEST_CASE(ConstructResolveSyncOneArg) {
Promise<int> p{[](const PromiseResolve<int> &resolve) { resolve(42); }};
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{});
BOOST_CHECK_EQUAL(waitForValue(p, -1), 42);
}
BOOST_AUTO_TEST_CASE(ConstructResolveSyncOneArgVoid) {
Promise<void> p{[](const PromiseResolve<void> &resolve) { resolve(); }};
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{});
BOOST_CHECK_EQUAL(waitForValue(p, -1, 42), 42);
}
BOOST_AUTO_TEST_CASE(ConstructResolveSyncTwoArgs) {
Promise<int> p{[](const PromiseResolve<int> &resolve, const PromiseReject<int> &) { resolve(42); }};
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{});
BOOST_CHECK_EQUAL(waitForValue(p, -1), 42);
}
BOOST_AUTO_TEST_CASE(ConstructResolveSyncTwoArgsVoid) {
Promise<void> p{[](const PromiseResolve<void> &resolve, const PromiseReject<void> &) { resolve(); }};
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{});
BOOST_CHECK_EQUAL(waitForValue(p, -1, 42), 42);
}
BOOST_FIXTURE_TEST_CASE(ConstructResolveAsyncOneArg, PromiseContext) {
auto id = std::this_thread::get_id();
Promise<int> p{[this, &id](const PromiseResolve<int> &resolve) {
boost::asio::defer(m_ioContext, [&id, resolve]() {
resolve(42);
id = std::this_thread::get_id();
});
}};
BOOST_CHECK_EQUAL(p.isPending(), true);
start();
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{});
BOOST_CHECK_EQUAL(waitForValue(p, -1), 42);
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
BOOST_CHECK_NE(id, std::this_thread::get_id());
}
BOOST_FIXTURE_TEST_CASE(ConstructResolveAsyncOneArgVoid, PromiseContext) {
auto id = std::this_thread::get_id();
Promise<void> p{[&id, this](const PromiseResolve<void> &resolve) {
boost::asio::defer(m_ioContext, [&id, resolve]() {
resolve();
id = std::this_thread::get_id();
});
}};
BOOST_CHECK_EQUAL(p.isPending(), true);
start();
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{});
BOOST_CHECK_EQUAL(waitForValue(p, -1, 42), 42);
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
BOOST_CHECK_NE(id, std::this_thread::get_id());
}
BOOST_FIXTURE_TEST_CASE(ConstructResolveAsyncTwoArgs, PromiseContext) {
auto id = std::this_thread::get_id();
Promise<int> p{[&id, this](const PromiseResolve<int> &resolve, const PromiseReject<int> &) {
boost::asio::defer(m_ioContext, [&id, resolve]() {
resolve(42);
id = std::this_thread::get_id();
});
}};
BOOST_CHECK_EQUAL(p.isPending(), true);
start();
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{});
BOOST_CHECK_EQUAL(waitForValue(p, -1), 42);
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
BOOST_CHECK_NE(id, std::this_thread::get_id());
}
BOOST_FIXTURE_TEST_CASE(ConstructResolveAsyncTwoArgsVoid, PromiseContext) {
auto id = std::this_thread::get_id();
Promise<void> p{[&id, this](const PromiseResolve<void> &resolve, const PromiseReject<void> &) {
boost::asio::defer(m_ioContext, [&id, resolve]() {
resolve();
id = std::this_thread::get_id();
});
}};
BOOST_CHECK_EQUAL(p.isPending(), true);
start();
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{});
BOOST_CHECK_EQUAL(waitForValue(p, -1, 42), 42);
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
BOOST_CHECK_NE(id, std::this_thread::get_id());
}
BOOST_AUTO_TEST_CASE(ConstructRejectSync) {
Promise<int> p{[](const PromiseResolve<int> &, const PromiseReject<int> &reject) { reject(std::string{"foo"}); }};
BOOST_CHECK_EQUAL(p.isRejected(), true);
BOOST_CHECK_EQUAL(waitForValue(p, -1), -1);
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"});
}
BOOST_AUTO_TEST_CASE(ConstructRejectSyncVoid) {
Promise<void> p{[](const PromiseResolve<void> &, const PromiseReject<void> &reject) { reject(std::string{"foo"}); }};
BOOST_CHECK_EQUAL(p.isRejected(), true);
BOOST_CHECK_EQUAL(waitForValue(p, -1, 42), -1);
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"});
}
BOOST_FIXTURE_TEST_CASE(ConstructRejectAsync, PromiseContext) {
auto id = std::this_thread::get_id();
Promise<int> p{[this, &id](const PromiseResolve<int> &, const PromiseReject<int> &reject) {
boost::asio::defer(m_ioContext, [&id, reject]() {
reject(std::string{"foo"});
id = std::this_thread::get_id();
});
}};
BOOST_CHECK_EQUAL(p.isPending(), true);
start();
BOOST_CHECK_EQUAL(waitForValue(p, -1), -1);
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"});
BOOST_CHECK_EQUAL(p.isRejected(), true);
BOOST_CHECK_NE(id, std::this_thread::get_id());
}
BOOST_FIXTURE_TEST_CASE(ConstructRejectAsyncVoid, PromiseContext) {
auto id = std::this_thread::get_id();
Promise<void> p{[this, &id](const PromiseResolve<void> &, const PromiseReject<void> &reject) {
boost::asio::defer(m_ioContext, [&id, reject]() {
reject(std::string{"foo"});
id = std::this_thread::get_id();
});
}};
BOOST_CHECK_EQUAL(p.isPending(), true);
start();
BOOST_CHECK_EQUAL(waitForValue(p, -1, 42), -1);
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"});
BOOST_CHECK_EQUAL(p.isRejected(), true);
BOOST_CHECK_NE(id, std::this_thread::get_id());
}
BOOST_AUTO_TEST_CASE(ConstructRejectThrowOneArg) {
Promise<int> p{[](const PromiseResolve<int> &) { throw std::string{"foo"}; }};
BOOST_CHECK_EQUAL(p.isRejected(), true);
BOOST_CHECK_EQUAL(waitForValue(p, -1), -1);
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"});
}
BOOST_AUTO_TEST_CASE(ConstructRejectThrowOneArgVoid) {
Promise<void> p{[](const PromiseResolve<void> &) { throw std::string{"foo"}; }};
BOOST_CHECK_EQUAL(p.isRejected(), true);
BOOST_CHECK_EQUAL(waitForValue(p, -1, 42), -1);
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"});
}
BOOST_AUTO_TEST_CASE(ConstructRejectThrowTwoArgs) {
Promise<int> p{[](const PromiseResolve<int> &, const PromiseReject<int> &) { throw std::string{"foo"}; }};
BOOST_CHECK_EQUAL(p.isRejected(), true);
BOOST_CHECK_EQUAL(waitForValue(p, -1), -1);
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"});
}
BOOST_AUTO_TEST_CASE(ConstructRejectThrowTwoArgsVoid) {
Promise<void> p{[](const PromiseResolve<void> &, const PromiseReject<void> &) { throw std::string{"foo"}; }};
BOOST_CHECK_EQUAL(p.isRejected(), true);
BOOST_CHECK_EQUAL(waitForValue(p, -1, 42), -1);
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"});
}
BOOST_FIXTURE_TEST_CASE(ConstructRejectUndefined, PromiseContext) {
auto id = std::this_thread::get_id();
Promise<int> p{[this, &id](const PromiseResolve<int> &, const PromiseReject<int> &reject) {
boost::asio::defer(m_ioContext, [&id, reject]() {
reject();
id = std::this_thread::get_id();
});
}};
BOOST_CHECK_EQUAL(p.isPending(), true);
start();
BOOST_CHECK_EQUAL(waitForRejected<PromiseUndefinedException>(p), true);
BOOST_CHECK_NE(id, std::this_thread::get_id());
}
BOOST_FIXTURE_TEST_CASE(ConstructRejectUndefinedVoid, PromiseContext) {
auto id = std::this_thread::get_id();
Promise<void> p{[this, &id](const PromiseResolve<void> &, const PromiseReject<void> &reject) {
boost::asio::defer(m_ioContext, [&id, reject]() {
reject();
id = std::this_thread::get_id();
});
}};
BOOST_CHECK_EQUAL(p.isPending(), true);
start();
BOOST_CHECK_EQUAL(waitForRejected<PromiseUndefinedException>(p), true);
BOOST_CHECK_NE(id, std::this_thread::get_id());
}
BOOST_FIXTURE_TEST_CASE(ConvertFulfillTAsU, PromiseContext) {
// Static cast between primitive types.
{
auto p = Promise<float>::resolve(42.13).convert<int>();
static_assert((std::is_same<decltype(p), Promise<int>>::value));
BOOST_CHECK_EQUAL(waitForValue(p, -1), 42);
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
}
// Convert enum class to int.
{
auto p = Promise<Enum1>::resolve(Enum1::Value1).convert<int>();
static_assert((std::is_same<decltype(p), Promise<int>>::value));
BOOST_CHECK_EQUAL(waitForValue(p, -1), 1);
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
}
// Convert int to enum class.
{
auto p = Promise<int>::resolve(1).convert<Enum1>();
static_assert((std::is_same<decltype(p), Promise<Enum1>>::value));
BOOST_TEST((waitForValue(p, Enum1::Value0) == Enum1::Value1));
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
}
// Convert between enums
{
auto p = Promise<Enum1>::resolve(Enum1::Value1).convert<Enum2>();
static_assert((std::is_same<decltype(p), Promise<Enum2>>::value));
BOOST_TEST((waitForValue(p, Enum2::Value0) == Enum2::Value1));
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
}
// Converting constructor for non-Qt types.
// https://en.cppreference.com/w/cpp/language/converting_constructor
{
auto p = Promise<Foo>::resolve(Foo{42}).convert<Bar>();
static_assert((std::is_same<decltype(p), Promise<Bar>>::value));
BOOST_TEST((waitForValue(p, Bar{}) == Bar{42}));
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
}
{
auto p = Promise<int>::resolve(42).convert<std::any>();
static_assert((std::is_same<decltype(p), Promise<std::any>>::value));
BOOST_CHECK_EQUAL(std::any_cast<int>(waitForValue(p, std::any{})), 42);
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
}
{
using Variant = std::variant<int, Foo>;
auto p = Promise<Foo>::resolve(Foo{42}).convert<Variant>();
static_assert((std::is_same<decltype(p), Promise<Variant>>::value));
BOOST_TEST((std::get<Foo>(waitForValue(p, Variant{})) == Foo{42}));
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
}
}
BOOST_AUTO_TEST_CASE(ConvertFulfillTAsVoid) {
auto p = Promise<int>::resolve(42).convert<void>();
static_assert((std::is_same<decltype(p), Promise<void>>::value));
BOOST_CHECK_EQUAL(waitForValue(p, -1, 42), 42);
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
}
BOOST_AUTO_TEST_CASE(ConvertRejectUnconvertibleTypes) {
// A string incompatible with int due to its value.
{
auto p = Promise<std::string>::resolve(std::string{"42foo"}).convert<int>();
static_assert((std::is_same<decltype(p), Promise<int>>::value));
BOOST_TEST(waitForRejected<PromiseConversionException>(p));
}
// A standard library type unconvertible to a primitive type because there is no converter.
{
auto p = Promise<std::vector<int>>::resolve(std::vector<int>{42, -42}).convert<int>();
static_assert((std::is_same<decltype(p), Promise<int>>::value));
BOOST_TEST(waitForRejected<PromiseConversionException>(p));
}
}
BOOST_FIXTURE_TEST_CASE(DelayFulfilled, PromiseContext) {
using namespace std::chrono;
auto begin = system_clock::now();
int64_t elapsed = -1;
auto p = Promise<int>::resolve(42).delay(m_ioContext, 1000).finally([&]() {
elapsed = duration_cast<milliseconds>(system_clock::now() - begin).count();
});
start();
BOOST_CHECK_EQUAL(waitForValue(p, -1), 42);
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
BOOST_TEST(elapsed >= static_cast<int64_t>(1000 * 0.94));
BOOST_TEST(elapsed <= static_cast<int64_t>(1000 * 1.06));
}
BOOST_FIXTURE_TEST_CASE(DelayRejected, PromiseContext) {
using namespace std::chrono;
int64_t elapsed = -1;
auto begin = system_clock::now();
auto p = Promise<int>::reject(std::string{"foo"}).delay(m_ioContext, 1000).finally([&]() {
elapsed = duration_cast<milliseconds>(system_clock::now() - begin).count();
});
start();
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"});
BOOST_CHECK_EQUAL(p.isRejected(), true);
BOOST_TEST(elapsed <= 10);
}
BOOST_FIXTURE_TEST_CASE(DelayFulfilledStdChrono, PromiseContext) {
using namespace std::chrono;
int64_t elapsed = -1;
auto begin = system_clock::now();
auto p = Promise<int>::resolve(42).delay(m_ioContext, std::chrono::seconds{1}).finally([&]() {
elapsed = duration_cast<milliseconds>(system_clock::now() - begin).count();
});
start();
BOOST_CHECK_EQUAL(waitForValue(p, -1), 42);
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
BOOST_TEST(elapsed >= static_cast<int64_t>(1000 * 0.94));
BOOST_TEST(elapsed <= static_cast<int64_t>(1000 * 1.06));
}
BOOST_FIXTURE_TEST_CASE(DelayRejectedStdChrono, PromiseContext) {
using namespace std::chrono;
int64_t elapsed = -1;
auto begin = system_clock::now();
auto p = Promise<int>::reject(std::string{"foo"}).delay(m_ioContext, std::chrono::seconds{1}).finally([&]() {
elapsed = duration_cast<milliseconds>(system_clock::now() - begin).count();
});
start();
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"});
BOOST_CHECK_EQUAL(p.isRejected(), true);
BOOST_TEST(elapsed <= 10);
}
BOOST_AUTO_TEST_CASE(EachEmptySequence) {
std::vector<int> values;
auto p = Promise<std::vector<int>>::resolve({}).each([&](int v, ...) { values.push_back(v); });
static_assert((std::is_same<decltype(p), Promise<std::vector<int>>>::value));
BOOST_CHECK_EQUAL(waitForValue(p, std::vector<int>{}), std::vector<int>{});
BOOST_CHECK_EQUAL(values, (std::vector<int>{}));
}
BOOST_AUTO_TEST_CASE(EachPreserveValues) {
std::vector<int> values;
auto p = Promise<std::vector<int>>::resolve({42, 43, 44}).each([&](int v, ...) { values.push_back(v + 1); });
static_assert((std::is_same<decltype(p), Promise<std::vector<int>>>::value));
BOOST_CHECK_EQUAL(waitForValue(p, std::vector<int>{}), (std::vector<int>{42, 43, 44}));
BOOST_CHECK_EQUAL(values, (std::vector<int>{43, 44, 45}));
}
BOOST_AUTO_TEST_CASE(EachIgnoreResult) {
std::vector<int> values;
auto p = Promise<std::vector<int>>::resolve({42, 43, 44}).each([&](int v, ...) {
values.push_back(v + 1);
return "Foo";
});
static_assert((std::is_same<decltype(p), Promise<std::vector<int>>>::value));
BOOST_CHECK_EQUAL(waitForValue(p, std::vector<int>{}), (std::vector<int>{42, 43, 44}));
BOOST_CHECK_EQUAL(values, (std::vector<int>{43, 44, 45}));
}
BOOST_FIXTURE_TEST_CASE(EachDelayedFulfilled, PromiseContext) {
auto strand = boost::asio::make_strand(m_ioContext);
std::map<int, int> values;
auto p = Promise<std::vector<int>>::resolve({42, 43, 44}).each([&](int v, int index) {
return Promise<void>::resolve().delay(strand, 50).then([&values, v, index]() {
values[v] = index;
return 42;
});
});
start();
static_assert((std::is_same<decltype(p), Promise<std::vector<int>>>::value));
BOOST_CHECK_EQUAL(waitForValue(p, std::vector<int>{}), (std::vector<int>{42, 43, 44}));
std::map<int, int> expected{{42, 0}, {43, 1}, {44, 2}};
BOOST_CHECK_EQUAL(values, expected);
}
BOOST_FIXTURE_TEST_CASE(EachDelayedRejected, PromiseContext) {
auto id = std::this_thread::get_id();
auto p = Promise<std::vector<int>>::resolve({42, 43, 44}).each([&id, this](int v, ...) {
return Promise<int>{[&, this](const PromiseResolve<int> &resolve, const PromiseReject<int> &reject) {
boost::asio::defer(m_ioContext, [&id, v, reject, resolve]() {
if (v == 44) {
reject(std::string{"foo"});
} else {
resolve(v);
}
id = std::this_thread::get_id();
});
}};
});
start();
static_assert((std::is_same<decltype(p), Promise<std::vector<int>>>::value));
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"});
BOOST_CHECK_NE(id, std::this_thread::get_id());
}
BOOST_FIXTURE_TEST_CASE(EachFunctorThrows, PromiseContext) {
auto p = Promise<std::vector<int>>::resolve({42, 43, 44}).each([](int v, ...) {
if (v == 44) {
throw std::string{"foo"};
}
});
static_assert((std::is_same<decltype(p), Promise<std::vector<int>>>::value));
BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"});
}
BOOST_FIXTURE_TEST_CASE(EachFunctorArguments, PromiseContext) {
std::vector<int> values;
auto p = Promise<std::vector<int>>::resolve({42, 43, 44}).each([&](int v, int i) {
values.push_back(i);
values.push_back(v);
});
static_assert((std::is_same<decltype(p), Promise<std::vector<int>>>::value));
BOOST_CHECK_EQUAL(waitForValue(p, std::vector<int>{}), (std::vector<int>{42, 43, 44}));
BOOST_CHECK_EQUAL(values, (std::vector<int>{0, 42, 1, 43, 2, 44}));
}
template <class Sequence>
struct SequenceTester {
static void exec() {
std::vector<int> values;
auto p = PromiseHelper::resolve(Sequence{42, 43, 44})
.each([&](int v, int i) {
values.push_back(i);
values.push_back(v);
})
.each([&](int v, ...) {
values.push_back(v);
return std::string{"foo"};
})
.each([&](int v, ...) {
values.push_back(v + 1);
return PromiseHelper::resolve(std::string{"foo"}).then([&]() { values.push_back(-1); });
})
.each([&](int v, ...) { values.push_back(v + 2); });
static_assert((std::is_same<decltype(p), Promise<Sequence>>::value));
BOOST_CHECK_EQUAL(waitForValue(p, Sequence{}), (Sequence{42, 43, 44}));
std::vector<int> expected{0, 42, 1, 43, 2, 44, 42, 43, 44, 43, -1, 44, -1, 45, -1, 44, 45, 46};
BOOST_CHECK_EQUAL(values, expected);
}
};
BOOST_AUTO_TEST_CASE(EachSequenceTypes) {
SequenceTester<std::list<int>>::exec();
SequenceTester<std::vector<int>>::exec();
}
BOOST_AUTO_TEST_CASE(FailSameType) {
// http://en.cppreference.com/w/cpp/error/exception
auto p = Promise<int>::reject(std::out_of_range("foo"));
std::string error;
p.fail([&](const std::domain_error &e) {
error += std::string{e.what()} + "0";
return -1;
})
.fail([&](const std::out_of_range &e) {
error += std::string{e.what()} + "1";
return -1;
})
.fail([&](const std::exception &e) {
error += std::string{e.what()} + "2";
return -1;
})
.wait();
BOOST_CHECK_EQUAL(error, std::string{"foo1"});
}
BOOST_AUTO_TEST_CASE(FailBaseClass) {
// http://en.cppreference.com/w/cpp/error/exception
auto p = Promise<int>::reject(std::out_of_range("foo"));
std::string error;
p.fail([&](const std::runtime_error &e) {
error += std::string{e.what()} + "0";
return -1;
})
.fail([&](const std::logic_error &e) {
error += std::string{e.what()} + "1";
return -1;
})
.fail([&](const std::exception &e) {
error += std::string{e.what()} + "2";
return -1;
})
.wait();
BOOST_CHECK_EQUAL(error, std::string{"foo1"});
}
BOOST_AUTO_TEST_CASE(FailCatchAll) {
auto p = Promise<int>::reject(std::out_of_range("foo"));
std::string error;
p.fail([&](const std::runtime_error &e) {
error += std::string{e.what()} + "0";
return -1;
})
.fail([&]() {
error += "bar";
return -1;
})
.fail([&](const std::exception &e) {
error += std::string{e.what()} + "2";
return -1;
})
.wait();
BOOST_CHECK_EQUAL(error, std::string{"bar"});
}
const std::string kErr{"0.42"};
float stringToFloatNoArg() {
return std::stof(kErr);
}
float stringToFloatArgByVal(std::string e) {
return std::stof(e);
}
float stringToFloatArgByRef(const std::string &e) {
return std::stof(e);
}
const float kFail = -1.f;
BOOST_FIXTURE_TEST_CASE(FailFunctionPtrHandlers, PromiseContext) {
{ // Global functions.
auto p0 = Promise<float>::reject(kErr).fail(&stringToFloatNoArg);
auto p1 = Promise<float>::reject(kErr).fail(&stringToFloatArgByVal);
auto p2 = Promise<float>::reject(kErr).fail(&stringToFloatArgByRef);
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), kRes);
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes);
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes);
}
{ // Static member functions.
auto p0 = Promise<float>::reject(kErr).fail(&PromiseContext::stringToFloatNoArg);
auto p1 = Promise<float>::reject(kErr).fail(&PromiseContext::stringToFloatArgByVal);
auto p2 = Promise<float>::reject(kErr).fail(&PromiseContext::stringToFloatArgByRef);
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), kRes);
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes);
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes);
}
}
BOOST_AUTO_TEST_CASE(Then) { BOOST_AUTO_TEST_CASE(Then) {
std::vector<std::variant<int, std::string>> values; std::vector<std::variant<int, std::string>> values;
auto input = Promise<int>::resolve(42); auto input = Promise<int>::resolve(42);
@ -34,8 +744,6 @@ BOOST_AUTO_TEST_CASE(Then) {
BOOST_CHECK_EQUAL(input.isFulfilled(), true); BOOST_CHECK_EQUAL(input.isFulfilled(), true);
BOOST_CHECK_EQUAL(output.isFulfilled(), true); BOOST_CHECK_EQUAL(output.isFulfilled(), true);
// auto p= new int;
} }
BOOST_AUTO_TEST_CASE(ThenResolveAsync) { BOOST_AUTO_TEST_CASE(ThenResolveAsync) {
@ -140,7 +848,6 @@ BOOST_AUTO_TEST_CASE(ThenNullHandler) {
} }
const float kRes = 0.42f; const float kRes = 0.42f;
const float kFail = -1.f;
float fnNoArg() { float fnNoArg() {
return kRes; return kRes;
@ -153,54 +860,24 @@ float fnArgByRef(const float &v) {
return v; return v;
} }
class Klass { BOOST_FIXTURE_TEST_CASE(ThenFunctionPtrHandlers, PromiseContext) {
public: // STATICS
static float kFnNoArg() {
return kRes;
}
static float kFnArgByVal(float v) {
return v;
}
static float kFnArgByRef(const float &v) {
return v;
}
public:
Klass(float v) : m_v{v} {
}
float fnNoArg() const {
return m_v;
}
float fnArgByVal(float v) const {
return v + m_v;
}
float fnArgByRef(const float &v) const {
return v + m_v;
}
private:
const float m_v;
};
BOOST_AUTO_TEST_CASE(ThenFunctionPtrHandlers) {
{ // Global functions. { // Global functions.
auto p0 = Promise<void>::resolve().then(&fnNoArg); auto p0 = Promise<void>::resolve().then(&fnNoArg);
auto p1 = Promise<float>::resolve(kRes).then(&fnArgByVal); auto p1 = Promise<float>::resolve(PromiseContext::kRes).then(&fnArgByVal);
auto p2 = Promise<float>::resolve(kRes).then(&fnArgByRef); auto p2 = Promise<float>::resolve(kRes).then(&fnArgByRef);
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), kRes); BOOST_CHECK_EQUAL(waitForValue(p0, kFail), PromiseContext::kRes);
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes); BOOST_CHECK_EQUAL(waitForValue(p1, kFail), PromiseContext::kRes);
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes); BOOST_CHECK_EQUAL(waitForValue(p2, kFail), PromiseContext::kRes);
} }
{ // Static member functions. { // Static member functions.
auto p0 = Promise<void>::resolve().then(&Klass::kFnNoArg); auto p0 = Promise<void>::resolve().then(&PromiseContext::kFnNoArg);
auto p1 = Promise<float>::resolve(kRes).then(&Klass::kFnArgByVal); auto p1 = Promise<float>::resolve(PromiseContext::kRes).then(&PromiseContext::kFnArgByVal);
auto p2 = Promise<float>::resolve(kRes).then(&Klass::kFnArgByRef); auto p2 = Promise<float>::resolve(PromiseContext::kRes).then(&PromiseContext::kFnArgByRef);
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), kRes); BOOST_CHECK_EQUAL(waitForValue(p0, kFail), PromiseContext::kRes);
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes); BOOST_CHECK_EQUAL(waitForValue(p1, kFail), PromiseContext::kRes);
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes); BOOST_CHECK_EQUAL(waitForValue(p2, kFail), PromiseContext::kRes);
} }
} }
@ -242,23 +919,22 @@ BOOST_AUTO_TEST_CASE(ThenStdFunctionHandlers) {
} }
} }
BOOST_AUTO_TEST_CASE(ThenStdBindHandlers) { BOOST_FIXTURE_TEST_CASE(ThenStdBindHandlers, PromiseContext) {
using namespace std::placeholders; using namespace std::placeholders;
const float value = 42.f;
this->setValue(value);
const float val{42.f}; const std::function<float()> bindNoArg = std::bind(&PromiseContext::functionNoArg, this);
const Klass obj{val}; const std::function<float(float)> bindArgByVal = std::bind(&PromiseContext::functionArgByVal, this, _1);
const std::function<float(const float &)> bindArgByRef = std::bind(&PromiseContext::functionArgByRef, this, _1);
const std::function<float()> bindNoArg = std::bind(&Klass::fnNoArg, &obj);
const std::function<float(float)> bindArgByVal = std::bind(&Klass::fnArgByVal, &obj, _1);
const std::function<float(const float &)> bindArgByRef = std::bind(&Klass::fnArgByRef, &obj, _1);
auto p0 = Promise<void>::resolve().then(bindNoArg); auto p0 = Promise<void>::resolve().then(bindNoArg);
auto p1 = Promise<float>::resolve(kRes).then(bindArgByVal); auto p1 = Promise<float>::resolve(kRes).then(bindArgByVal);
auto p2 = Promise<float>::resolve(kRes).then(bindArgByRef); auto p2 = Promise<float>::resolve(kRes).then(bindArgByRef);
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), val); BOOST_CHECK_EQUAL(waitForValue(p0, kFail), value);
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), val + kRes); BOOST_CHECK_EQUAL(waitForValue(p1, kFail), value + kRes);
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), val + kRes); BOOST_CHECK_EQUAL(waitForValue(p2, kFail), value + kRes);
} }
BOOST_AUTO_TEST_CASE(ThenLambdaHandlers) { BOOST_AUTO_TEST_CASE(ThenLambdaHandlers) {
@ -297,4 +973,6 @@ BOOST_AUTO_TEST_CASE(ThenLambdaHandlers) {
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes); BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes);
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes); BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes);
} }
} }
BOOST_AUTO_TEST_SUITE_END()

View File

@ -26,8 +26,7 @@ void initialize(const std::string &filename, const std::string &target, boost::l
#else #else
std::ostringstream oss; std::ostringstream oss;
oss << filename << "_%Y-%m-%d_%H.%M.%S_%N.log"; oss << filename << "_%Y-%m-%d_%H.%M.%S_%N.log";
auto fileSink = auto fileSink = add_file_log(keywords::file_name = oss.str(), keywords::auto_flush = true, keywords::target = target);
add_file_log(keywords::file_name = oss.str(), keywords::auto_flush = true, keywords::target = target);
fileSink->set_formatter(&defaultFormatter); fileSink->set_formatter(&defaultFormatter);
#endif #endif
initialized = true; initialized = true;
@ -41,12 +40,11 @@ void defaultFormatter(const boost::log::record_view &record, boost::log::formatt
boost::log::formatting_ostream oss(level); boost::log::formatting_ostream oss(level);
oss << "[" << record[boost::log::trivial::severity] << "]"; oss << "[" << record[boost::log::trivial::severity] << "]";
auto dateTimeFormatter = expressions::stream << expressions::format_date_time<boost::posix_time::ptime>( auto dateTimeFormatter = expressions::stream
"TimeStamp", "[%m-%d %H:%M:%S.%f]"); << expressions::format_date_time<boost::posix_time::ptime>("TimeStamp", "[%m-%d %H:%M:%S.%f]");
dateTimeFormatter(record, ostream); dateTimeFormatter(record, ostream);
ostream << "[" << boost::log::extract<boost::log::attributes::current_thread_id::value_type>("ThreadID", record) ostream << "[" << boost::log::extract<boost::log::attributes::current_thread_id::value_type>("ThreadID", record) << "]";
<< "]";
ostream << std::setw(8) << std::left << level; ostream << std::setw(8) << std::left << level;
auto &&category = record[AmassKeywords::category]; auto &&category = record[AmassKeywords::category];
if (!category.empty()) ostream << " [" << category << "]"; if (!category.empty()) ostream << " [" << category << "]";
@ -82,3 +80,14 @@ BOOST_LOG_GLOBAL_LOGGER_INIT(location_logger, LocationLogger<boost::log::trivial
} }
return lg; return lg;
} }
namespace std {
ostream &operator<<(ostream &os, const map<int, int> &m) {
os << "{ ";
for (const auto &pair : m) {
os << "{" << pair.first << ", " << pair.second << "} ";
}
os << "}";
return os;
}
} // namespace std

View File

@ -21,7 +21,9 @@ public:
} }
return ret; return ret;
} }
inline bool doOnceInterface() const { return static_cast<const Derived *>(this)->doOnce(); } inline bool doOnceInterface() const {
return static_cast<const Derived *>(this)->doOnce();
}
protected: protected:
mutable bool m_status = true; mutable bool m_status = true;
@ -29,8 +31,11 @@ protected:
class NumberPredicator : public LogPredicator<NumberPredicator> { class NumberPredicator : public LogPredicator<NumberPredicator> {
public: public:
NumberPredicator(int limit) : m_limit(limit) {} NumberPredicator(int limit) : m_limit(limit) {
inline bool doOnce() const { return (m_current++ % m_limit) == 0; } }
inline bool doOnce() const {
return (m_current++ % m_limit) == 0;
}
private: private:
int m_limit; int m_limit;
@ -39,8 +44,11 @@ private:
class CountPredicator : public LogPredicator<CountPredicator> { class CountPredicator : public LogPredicator<CountPredicator> {
public: public:
CountPredicator(int limit) : m_limit(limit) {} CountPredicator(int limit) : m_limit(limit) {
inline bool doOnce() const { return m_current++ < m_limit; } }
inline bool doOnce() const {
return m_current++ < m_limit;
}
private: private:
int m_limit; int m_limit;
@ -49,7 +57,8 @@ private:
class TimePredicator : public LogPredicator<TimePredicator> { class TimePredicator : public LogPredicator<TimePredicator> {
public: public:
TimePredicator(int milliseconds) : m_limit(std::chrono::milliseconds(milliseconds)) {} TimePredicator(int milliseconds) : m_limit(std::chrono::milliseconds(milliseconds)) {
}
inline bool doOnce() const { inline bool doOnce() const {
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
@ -65,8 +74,7 @@ private:
}; };
template <typename BaseT> template <typename BaseT>
CategoryTaggerFeature<BaseT>::CategoryTaggerFeature(const CategoryTaggerFeature &obj) CategoryTaggerFeature<BaseT>::CategoryTaggerFeature(const CategoryTaggerFeature &obj) : BaseT(static_cast<const BaseT &>(obj)) {
: BaseT(static_cast<const BaseT &>(obj)) {
} }
template <typename BaseT> template <typename BaseT>
@ -96,13 +104,13 @@ boost::log::record CategoryTaggerFeature<BaseT>::open_record_unlocked(const Args
} }
template <typename BaseT> template <typename BaseT>
FilenameTaggerFeature<BaseT>::FilenameTaggerFeature(const FilenameTaggerFeature &obj) FilenameTaggerFeature<BaseT>::FilenameTaggerFeature(const FilenameTaggerFeature &obj) : BaseT(static_cast<const BaseT &>(obj)) {
: BaseT(static_cast<const BaseT &>(obj)) {
} }
template <typename BaseT> template <typename BaseT>
template <typename ArgsT> template <typename ArgsT>
FilenameTaggerFeature<BaseT>::FilenameTaggerFeature(const ArgsT &args) : BaseT(args) {} FilenameTaggerFeature<BaseT>::FilenameTaggerFeature(const ArgsT &args) : BaseT(args) {
}
template <typename BaseT> template <typename BaseT>
template <typename ArgsT> template <typename ArgsT>
@ -126,13 +134,13 @@ boost::log::record FilenameTaggerFeature<BaseT>::open_record_unlocked(const Args
} }
template <typename BaseT> template <typename BaseT>
FunctionTaggerFeature<BaseT>::FunctionTaggerFeature(const FunctionTaggerFeature &obj) FunctionTaggerFeature<BaseT>::FunctionTaggerFeature(const FunctionTaggerFeature &obj) : BaseT(static_cast<const BaseT &>(obj)) {
: BaseT(static_cast<const BaseT &>(obj)) {
} }
template <typename BaseT> template <typename BaseT>
template <typename ArgsT> template <typename ArgsT>
FunctionTaggerFeature<BaseT>::FunctionTaggerFeature(const ArgsT &args) : BaseT(args) {} FunctionTaggerFeature<BaseT>::FunctionTaggerFeature(const ArgsT &args) : BaseT(args) {
}
template <typename BaseT> template <typename BaseT>
template <typename ArgsT> template <typename ArgsT>
@ -157,10 +165,12 @@ boost::log::record FunctionTaggerFeature<BaseT>::open_record_unlocked(const Args
template <typename BaseT> template <typename BaseT>
template <typename ArgsT> template <typename ArgsT>
LineTaggerFeature<BaseT>::LineTaggerFeature(const ArgsT &args) : BaseT(args) {} LineTaggerFeature<BaseT>::LineTaggerFeature(const ArgsT &args) : BaseT(args) {
}
template <typename BaseT> template <typename BaseT>
LineTaggerFeature<BaseT>::LineTaggerFeature(const LineTaggerFeature &obj) : BaseT(static_cast<const BaseT &>(obj)) {} LineTaggerFeature<BaseT>::LineTaggerFeature(const LineTaggerFeature &obj) : BaseT(static_cast<const BaseT &>(obj)) {
}
template <typename BaseT> template <typename BaseT>
template <typename ArgsT> template <typename ArgsT>
@ -182,4 +192,8 @@ boost::log::record LineTaggerFeature<BaseT>::open_record_unlocked(const ArgsT &a
return BaseT::open_record_unlocked(args); return BaseT::open_record_unlocked(args);
} }
namespace std {
ostream &operator<<(ostream &os, const map<int, int> &m);
}
#endif // BOOSTLOG_INL #endif // BOOSTLOG_INL

View File

@ -124,9 +124,6 @@ typename FunctionTraits<std::decay_t<Function>>::StlFunctionType makeStlFunction
return static_cast<typename FunctionTraits<std::decay_t<Function>>::StlFunctionType>(std::forward<Function>(lambda)); return static_cast<typename FunctionTraits<std::decay_t<Function>>::StlFunctionType>(std::forward<Function>(lambda));
} }
template <typename T>
using Unqualified = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
/*! /*!
* \struct HasCallOperator * \struct HasCallOperator
* http://stackoverflow.com/a/5117641 * http://stackoverflow.com/a/5117641

View File

@ -3,6 +3,8 @@
#include "Singleton.h" #include "Singleton.h"
#include <boost/asio/io_context.hpp> #include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/steady_timer.hpp>
#include <thread> #include <thread>
class IoContext { class IoContext {
@ -12,7 +14,9 @@ public:
Asynchronous, Asynchronous,
}; };
friend class Amass::Singleton<IoContext, Amass::LocalInstance>; friend class Amass::Singleton<IoContext, Amass::LocalInstance>;
std::shared_ptr<boost::asio::io_context> ioContext() const { return m_ioContext; } std::shared_ptr<boost::asio::io_context> ioContext() const {
return m_ioContext;
}
template <Mode mode = Mode::Asynchronous> template <Mode mode = Mode::Asynchronous>
void run() { void run() {
if constexpr (mode == Mode::Asynchronous) { if constexpr (mode == Mode::Asynchronous) {
@ -24,9 +28,28 @@ public:
void stop(); void stop();
~IoContext(); ~IoContext();
/**
* @brief
*
* @tparam Executor boost::asio::io_contextboost::asio::strand( boost::asio::make_strand(io_context); )
*/
template <class Executor, class Func, class Rep, class Period>
static void postDelayed(Executor &ioContext, Func &&task, const std::chrono::duration<Rep, Period> &duration) {
auto timer = std::make_shared<boost::asio::steady_timer>(ioContext, duration);
timer->async_wait([timer, task = std::forward<Func>(task)](const boost::system::error_code & /*e*/) mutable {
boost::asio::post(timer->get_executor(), std::move(task));
});
}
template <class Executor, class Func>
static void postDelayed(Executor &ioContext, Func &&task, int milliseconds) {
return postDelayed(ioContext, std::forward(task), std::chrono::milliseconds(milliseconds));
}
protected: protected:
template <typename... Args> template <typename... Args>
IoContext(Args &&... args) : m_ioContext(std::make_shared<boost::asio::io_context>(std::forward<Args>(args)...)) {} IoContext(Args &&...args) : m_ioContext(std::make_shared<boost::asio::io_context>(std::forward<Args>(args)...)) {
}
void runIoContext(); void runIoContext();
private: private:

View File

@ -180,4 +180,35 @@ protected:
} }
}; };
template <typename T, typename U, bool IsConvertibleViaStaticCast>
struct PromiseConverterBase;
template <typename T, typename U>
struct PromiseConverterBase<T, U, true> {
static std::function<U(const T &)> create() {
return [](const T &value) { return static_cast<U>(value); };
}
};
template <typename T, typename U>
struct PromiseConverterBase<T, U, false> {
static std::function<U(const T &)> create() {
return [](const T &value) {
throw PromiseConversionException{};
return U{};
};
}
};
template <typename T, typename U>
struct PromiseConverter : PromiseConverterBase<T, U,
// Fundamental types and converting constructors.
std::is_convertible<T, U>::value ||
// Conversion to void.
std::is_same<U, void>::value ||
// Conversion between enums and arithmetic types.
((std::is_enum<T>::value && std::is_arithmetic<U>::value) ||
(std::is_arithmetic<T>::value && std::is_enum<U>::value) ||
(std::is_enum<T>::value && std::is_enum<U>::value))> {};
#endif // __PROMISEDATA_H__ #endif // __PROMISEDATA_H__

View File

@ -1,10 +0,0 @@
#ifndef __PROMISEEXCEPTION_H__
#define __PROMISEEXCEPTION_H__
#include <exception>
class PromiseUndefinedException : public std::exception {
public:
};
#endif // __PROMISEEXCEPTION_H__

View File

@ -2,9 +2,9 @@
#define __PROMISEHANDLER_H__ #define __PROMISEHANDLER_H__
#include "../FunctionTraits.h" #include "../FunctionTraits.h"
#include <type_traits>
#include <exception>
#include "PromiseData.h" #include "PromiseData.h"
#include <exception>
#include <type_traits>
template <typename T> template <typename T>
class Promise; class Promise;
@ -26,6 +26,12 @@ struct PromiseDeduce<const volatile T> : public PromiseDeduce<T> {};
template <typename T> template <typename T>
struct PromiseDeduce<Promise<T>> : public PromiseDeduce<T> {}; struct PromiseDeduce<Promise<T>> : public PromiseDeduce<T> {};
template <typename Functor, typename... Args>
struct PromiseFunctor {
using ResultType = typename std::invoke_result_t<Functor, Args...>;
using PromiseType = typename PromiseDeduce<ResultType>::Type;
};
template <typename T> template <typename T>
struct PromiseFulfill { struct PromiseFulfill {
template <typename V, typename TResolve, typename TReject> template <typename V, typename TResolve, typename TReject>

View File

@ -1,8 +1,6 @@
#ifndef __PROMISERESOLVER_H__ #ifndef __PROMISERESOLVER_H__
#define __PROMISERESOLVER_H__ #define __PROMISERESOLVER_H__
#include "PromiseException.h"
template <typename T> template <typename T>
class Promise; class Promise;

View File

@ -1,6 +1,16 @@
#ifndef __PROMISE_H__ #ifndef __PROMISE_H__
#define __PROMISE_H__ #define __PROMISE_H__
#include <exception>
class PromiseUndefinedException : public std::exception {
public:
};
class PromiseConversionException : public std::exception {
public:
};
#include "Private/PromiseHandler.h" #include "Private/PromiseHandler.h"
#include "Private/PromiseResolver.h" #include "Private/PromiseResolver.h"
#include <thread> #include <thread>
@ -42,6 +52,25 @@ public:
swap(other); swap(other);
} }
virtual ~PromiseBase() {
}
PromiseBase<T> &operator=(const PromiseBase<T> &other) {
m_d = other.m_d;
return *this;
}
PromiseBase<T> &operator=(PromiseBase<T> &&other) {
PromiseBase<T>(std::move(other)).swap(*this);
return *this;
}
bool operator==(const PromiseBase<T> &other) const {
return (m_d == other.m_d);
}
bool operator!=(const PromiseBase<T> &other) const {
return (m_d != other.m_d);
}
void swap(PromiseBase<T> &other) { void swap(PromiseBase<T> &other) {
std::swap(m_d, other.m_d); std::swap(m_d, other.m_d);
} }
@ -78,6 +107,12 @@ public:
return then(std::forward<TFulfilled>(fulfilled), nullptr); return then(std::forward<TFulfilled>(fulfilled), nullptr);
} }
template <typename THandler>
Promise<T> finally(THandler handler) const {
Promise<T> p = *this;
return p.then(handler, handler).then([=]() { return p; });
}
template <typename TRejected> template <typename TRejected>
typename PromiseHandler<T, std::nullptr_t>::PromiseType fail(TRejected &&rejected) const { typename PromiseHandler<T, std::nullptr_t>::PromiseType fail(TRejected &&rejected) const {
return then(nullptr, std::forward<TRejected>(rejected)); return then(nullptr, std::forward<TRejected>(rejected));
@ -95,10 +130,112 @@ public:
return Promise<T>{[&](const PromiseResolve<T> &, const PromiseReject<T> &reject) { reject(std::forward<E>(error)); }}; return Promise<T>{[&](const PromiseResolve<T> &, const PromiseReject<T> &reject) { reject(std::forward<E>(error)); }};
} }
template <typename THandler>
Promise<T> tapFail(THandler handler) const {
Promise<T> p = *this;
return p.then([]() {}, handler).then([=]() { return p; });
}
template <typename THandler>
Promise<T> tap(THandler handler) const {
Promise<T> p = *this;
return p.then(handler).then([=]() { return p; });
}
#ifdef BOOST_ASIO_EXECUTION_EXECUTOR
template <class Executor, class Rep, class Period>
Promise<T> delay(Executor &ioContext, const std::chrono::duration<Rep, Period> &duration) const {
return tap([&]() {
return Promise<void>{[&](const PromiseResolve<void> &resolve) {
auto timer = std::make_shared<boost::asio::steady_timer>(ioContext, duration);
timer->async_wait([timer, resolve](const boost::system::error_code & /*e*/) mutable {
boost::asio::post(timer->get_executor(), resolve);
});
}};
});
}
template <class Executor>
Promise<T> delay(Executor &ioContext, int milliseconds) const {
return delay(ioContext, std::chrono::milliseconds(milliseconds));
}
#endif
protected: protected:
std::shared_ptr<PromiseData<T>> m_d; std::shared_ptr<PromiseData<T>> m_d;
}; };
namespace PromiseHelper {
template <typename T>
typename PromiseDeduce<T>::Type resolve(T &&value) {
using PromiseType = typename PromiseDeduce<T>::Type;
using ValueType = typename PromiseType::Type;
using ResolveType = PromiseResolve<ValueType>;
using RejectType = PromiseReject<ValueType>;
return PromiseType{[&](ResolveType &&resolve, RejectType &&reject) {
PromiseFulfill<std::decay_t<T>>::call(std::forward<T>(value), std::forward<ResolveType>(resolve), std::forward<RejectType>(reject));
}};
}
Promise<void> resolve();
template <typename Functor, typename... Args>
typename PromiseFunctor<Functor, Args...>::PromiseType attempt(Functor &&fn, Args &&...args) {
using FunctorType = PromiseFunctor<Functor, Args...>;
using PromiseType = typename FunctorType::PromiseType;
using ValueType = typename PromiseType::Type;
// NOTE: std::forward<T<U>>: MSVC 2013 fails when forwarding
// template type (error: "expects 4 arguments - 0 provided").
// However it succeeds with type alias.
// TODO: should we expose QPromise::ResolveType & RejectType?
using ResolveType = PromiseResolve<ValueType>;
using RejectType = PromiseReject<ValueType>;
return PromiseType{[&](ResolveType &&resolve, RejectType &&reject) {
PromiseDispatch<typename FunctorType::ResultType>::call(std::forward<ResolveType>(resolve), std::forward<RejectType>(reject),
std::forward<Functor>(fn), std::forward<Args>(args)...);
}};
}
template <typename T, template <typename, typename...> class Sequence = std::vector, typename... Args>
Promise<std::vector<T>> all(const Sequence<Promise<T>, Args...> &promises) {
const int count = static_cast<int>(promises.size());
if (count == 0) {
return PromiseHelper::resolve(std::vector<T>{});
}
return Promise<std::vector<T>>{[=](const PromiseResolve<std::vector<T>> &resolve, const PromiseReject<std::vector<T>> &reject) {
auto remaining = std::make_shared<int>(count);
auto results = std::make_shared<std::vector<T>>(count);
int i = 0;
for (const auto &promise : promises) {
promise.then(
[=](const T &res) mutable {
(*results)[i] = res;
if (--(*remaining) == 0) {
resolve(*results);
}
},
[=]() mutable {
if (*remaining != -1) {
*remaining = -1;
reject(std::current_exception());
}
});
i++;
}
}};
}
template <template <typename, typename...> class Sequence = std::vector, typename... Args>
Promise<void> all(const Sequence<Promise<void>, Args...> &promises);
}; // namespace PromiseHelper
template <typename T> template <typename T>
class Promise : public PromiseBase<T> { class Promise : public PromiseBase<T> {
friend class PromiseResolver<T>; friend class PromiseResolver<T>;
@ -108,6 +245,30 @@ public:
Promise(F &&resolver) : PromiseBase<T>(std::forward<F>(resolver)) { Promise(F &&resolver) : PromiseBase<T>(std::forward<F>(resolver)) {
} }
template <typename U>
Promise<U> convert() const {
return PromiseBase<T>::then(PromiseConverter<T, U>::create());
}
template <typename Functor>
Promise<T> each(Functor fn) {
return this->tap([=](const T &values) {
int i = 0;
std::vector<Promise<void>> promises;
for (const auto &v : values) {
promises.push_back(PromiseHelper::attempt(fn, v, i).then([]() {
// Cast to void in case fn returns a non promise value.
// TODO remove when implicit cast is implemented.
}));
i++;
}
return PromiseHelper::all(promises);
});
}
static Promise<T> resolve(const T &value) { static Promise<T> resolve(const T &value) {
return Promise<T>{[&](const PromiseResolve<T> &resolve) { resolve(value); }}; return Promise<T>{[&](const PromiseResolve<T> &resolve) { resolve(value); }};
} }
@ -131,4 +292,36 @@ private:
friend class PromiseBase<void>; friend class PromiseBase<void>;
}; };
namespace PromiseHelper {
Promise<void> resolve() {
return Promise<void>{[](const PromiseResolve<void> &resolve) { resolve(); }};
}
template <template <typename, typename...> class Sequence = std::vector, typename... Args>
Promise<void> all(const Sequence<Promise<void>, Args...> &promises) {
const int count = static_cast<int>(promises.size());
if (count == 0) {
return PromiseHelper::resolve();
}
return Promise<void>{[=](const PromiseResolve<void> &resolve, const PromiseReject<void> &reject) {
auto remaining = std::make_shared<int>(count);
for (const auto &promise : promises) {
promise.then(
[=]() {
if (--(*remaining) == 0) {
resolve();
}
},
[=]() {
if (*remaining != -1) {
*remaining = -1;
reject(std::current_exception());
}
});
}
}};
}
} // namespace PromiseHelper
#endif // __PROMISE_H__ #endif // __PROMISE_H__

View File

@ -67,6 +67,7 @@ function build() {
exit 1 exit 1
fi fi
$build_path/UnitTest/UnitTest $build_path/UnitTest/UnitTest
# valgrind --leak-check=full $build_path/UnitTest/UnitTest
} }
function main() { function main() {