978 lines
34 KiB
C++
978 lines
34 KiB
C++
#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 <sstream>
|
|
#include <variant>
|
|
#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>
|
|
static inline T waitForValue(const Promise<T> &promise, const T &initial) {
|
|
T value(initial);
|
|
promise.then([&](const T &res) { value = res; }).wait();
|
|
return value;
|
|
}
|
|
|
|
template <typename T>
|
|
static inline T waitForValue(const Promise<void> &promise, const T &initial, const T &expected) {
|
|
T value(initial);
|
|
promise.then([&]() { value = expected; }).wait();
|
|
return value;
|
|
}
|
|
|
|
template <typename T, typename E>
|
|
static inline E waitForError(const 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) {
|
|
std::vector<std::variant<int, std::string>> values;
|
|
auto input = Promise<int>::resolve(42);
|
|
auto output = input.then([&](int res) {
|
|
values.push_back(res);
|
|
return std::to_string(res + 1);
|
|
});
|
|
output.then([&](const std::string &res) { values.push_back(res); }).then([&]() { values.push_back(44); }).wait();
|
|
|
|
BOOST_CHECK_EQUAL(values.size(), 3);
|
|
BOOST_CHECK_EQUAL(std::get<int>(values[0]), 42);
|
|
BOOST_CHECK_EQUAL(std::get<std::string>(values[1]), "43");
|
|
BOOST_CHECK_EQUAL(std::get<int>(values[2]), 44);
|
|
|
|
BOOST_CHECK_EQUAL(input.isFulfilled(), true);
|
|
BOOST_CHECK_EQUAL(output.isFulfilled(), true);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(ThenResolveAsync) {
|
|
std::thread thread;
|
|
auto promise = Promise<int>::resolve(42).then([&thread](int res) {
|
|
return Promise<std::string>{[&thread, res](const PromiseResolve<std::string> &resolve) {
|
|
thread = std::thread([resolve, res]() {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
|
std::ostringstream oss;
|
|
oss << "foo" << res;
|
|
resolve(oss.str());
|
|
});
|
|
}};
|
|
});
|
|
|
|
std::string value;
|
|
promise.then([&](const std::string &res) { value = res; }).wait();
|
|
|
|
static_assert((std::is_same_v<decltype(promise), Promise<std::string>>));
|
|
BOOST_CHECK_EQUAL(value, "foo42");
|
|
BOOST_CHECK_EQUAL(promise.isFulfilled(), true);
|
|
|
|
if (thread.joinable()) {
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(ThenRejectSync) {
|
|
auto input = Promise<int>::resolve(42);
|
|
auto output = input.then([](int res) {
|
|
std::ostringstream oss;
|
|
oss << "foo" << res;
|
|
throw oss.str();
|
|
return 42;
|
|
});
|
|
|
|
std::string error;
|
|
output.then([&](int res) { error += "bar" + std::to_string(res); }).fail([&](const std::string &err) { error += err; }).wait();
|
|
|
|
BOOST_CHECK_EQUAL(error, "foo42");
|
|
BOOST_CHECK_EQUAL(input.isFulfilled(), true);
|
|
BOOST_CHECK_EQUAL(output.isRejected(), true);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(ThenRejectAsync) {
|
|
std::thread thread;
|
|
auto p = Promise<int>::resolve(42).then([&thread](int res) {
|
|
return Promise<void>{[&thread, res](const PromiseResolve<void> &, const PromiseReject<void> &reject) {
|
|
thread = std::thread([reject, res]() {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
|
std::ostringstream oss;
|
|
oss << "foo" << res;
|
|
reject(oss.str());
|
|
});
|
|
}};
|
|
});
|
|
|
|
static_assert((std::is_same_v<decltype(p), Promise<void>>));
|
|
|
|
std::string error;
|
|
p.fail([&](const std::string &err) { error = err; }).wait();
|
|
|
|
BOOST_CHECK_EQUAL(error, "foo42");
|
|
BOOST_CHECK_EQUAL(p.isRejected(), true);
|
|
|
|
if (thread.joinable()) {
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(ThenSkipResult) {
|
|
auto p = Promise<int>::resolve(42);
|
|
|
|
int value = -1;
|
|
p.then([&]() { value = 43; }).wait();
|
|
|
|
static_assert((std::is_same<decltype(p), Promise<int>>::value));
|
|
BOOST_CHECK_EQUAL(value, 43);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(ThenNullHandler) {
|
|
{ // resolved
|
|
auto p = Promise<int>::resolve(42).then(nullptr);
|
|
|
|
int value;
|
|
p.then([&](const int &res) { value = res; }).wait();
|
|
|
|
BOOST_CHECK_EQUAL(value, 42);
|
|
BOOST_CHECK_EQUAL(p.isFulfilled(), true);
|
|
}
|
|
{ // rejected
|
|
auto p = Promise<int>::reject(std::string{"foo"}).then(nullptr);
|
|
|
|
std::string error;
|
|
p.fail([&](const std::string &err) {
|
|
error = err;
|
|
return 0;
|
|
}).wait();
|
|
BOOST_CHECK_EQUAL(error, "foo");
|
|
BOOST_CHECK_EQUAL(p.isRejected(), true);
|
|
}
|
|
}
|
|
|
|
const float kRes = 0.42f;
|
|
|
|
float fnNoArg() {
|
|
return kRes;
|
|
}
|
|
|
|
float fnArgByVal(float v) {
|
|
return v;
|
|
}
|
|
float fnArgByRef(const float &v) {
|
|
return v;
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(ThenFunctionPtrHandlers, PromiseContext) {
|
|
{ // Global functions.
|
|
auto p0 = Promise<void>::resolve().then(&fnNoArg);
|
|
auto p1 = Promise<float>::resolve(PromiseContext::kRes).then(&fnArgByVal);
|
|
auto p2 = Promise<float>::resolve(kRes).then(&fnArgByRef);
|
|
|
|
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), PromiseContext::kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), PromiseContext::kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), PromiseContext::kRes);
|
|
}
|
|
{ // Static member functions.
|
|
auto p0 = Promise<void>::resolve().then(&PromiseContext::kFnNoArg);
|
|
auto p1 = Promise<float>::resolve(PromiseContext::kRes).then(&PromiseContext::kFnArgByVal);
|
|
auto p2 = Promise<float>::resolve(PromiseContext::kRes).then(&PromiseContext::kFnArgByRef);
|
|
|
|
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), PromiseContext::kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), PromiseContext::kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), PromiseContext::kRes);
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(ThenStdFunctionHandlers) {
|
|
{ // lvalue.
|
|
std::function<float()> stdFnNoArg = fnNoArg;
|
|
std::function<float(float)> stdFnArgByVal = fnArgByVal;
|
|
std::function<float(const float &)> stdFnArgByRef = fnArgByRef;
|
|
|
|
auto p0 = Promise<void>::resolve().then(stdFnNoArg);
|
|
auto p1 = Promise<float>::resolve(kRes).then(stdFnArgByVal);
|
|
auto p2 = Promise<float>::resolve(kRes).then(stdFnArgByRef);
|
|
|
|
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes);
|
|
}
|
|
{ // const lvalue.
|
|
const std::function<float()> stdFnNoArg = fnNoArg;
|
|
const std::function<float(float)> stdFnArgByVal = fnArgByVal;
|
|
const std::function<float(const float &)> stdFnArgByRef = fnArgByRef;
|
|
|
|
auto p0 = Promise<void>::resolve().then(stdFnNoArg);
|
|
auto p1 = Promise<float>::resolve(kRes).then(stdFnArgByVal);
|
|
auto p2 = Promise<float>::resolve(kRes).then(stdFnArgByRef);
|
|
|
|
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes);
|
|
}
|
|
{ // rvalue.
|
|
auto p0 = Promise<void>::resolve().then(std::function<float()>{fnNoArg});
|
|
auto p1 = Promise<float>::resolve(kRes).then(std::function<float(float)>{fnArgByVal});
|
|
auto p2 = Promise<float>::resolve(kRes).then(std::function<float(const float &)>{fnArgByRef});
|
|
|
|
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes);
|
|
}
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(ThenStdBindHandlers, PromiseContext) {
|
|
using namespace std::placeholders;
|
|
const float value = 42.f;
|
|
this->setValue(value);
|
|
|
|
const std::function<float()> bindNoArg = std::bind(&PromiseContext::functionNoArg, this);
|
|
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);
|
|
|
|
auto p0 = Promise<void>::resolve().then(bindNoArg);
|
|
auto p1 = Promise<float>::resolve(kRes).then(bindArgByVal);
|
|
auto p2 = Promise<float>::resolve(kRes).then(bindArgByRef);
|
|
|
|
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), value);
|
|
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), value + kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), value + kRes);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(ThenLambdaHandlers) {
|
|
{ // lvalue.
|
|
auto lambdaNoArg = []() { return kRes; };
|
|
auto lambdaArgByVal = [](float v) { return v; };
|
|
auto lambdaArgByRef = [](const float &v) { return v; };
|
|
|
|
auto p0 = Promise<void>::resolve().then(lambdaNoArg);
|
|
auto p1 = Promise<float>::resolve(kRes).then(lambdaArgByVal);
|
|
auto p2 = Promise<float>::resolve(kRes).then(lambdaArgByRef);
|
|
|
|
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes);
|
|
}
|
|
{ // const lvalue.
|
|
const auto lambdaNoArg = []() { return kRes; };
|
|
const auto lambdaArgByVal = [](float v) { return v; };
|
|
const auto lambdaArgByRef = [](const float &v) { return v; };
|
|
|
|
auto p0 = Promise<void>::resolve().then(lambdaNoArg);
|
|
auto p1 = Promise<float>::resolve(kRes).then(lambdaArgByVal);
|
|
auto p2 = Promise<float>::resolve(kRes).then(lambdaArgByRef);
|
|
|
|
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes);
|
|
}
|
|
{ // rvalue.
|
|
auto p0 = Promise<void>::resolve().then([]() { return kRes; });
|
|
auto p1 = Promise<float>::resolve(kRes).then([](float v) { return v; });
|
|
auto p2 = Promise<float>::resolve(kRes).then([](const float &v) { return v; });
|
|
|
|
BOOST_CHECK_EQUAL(waitForValue(p0, kFail), kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes);
|
|
BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes);
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END() |