Kylin/UnitTest/Universal/PromiseTest.cpp
2024-09-15 23:39:23 +08:00

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()