#include "BoostLog.h" #include "IoContext.h" #include #include #include #include #include #include #include #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 m_guarder; std::vector m_threads; float m_value = 249.f; inline static const std::string kErr{"0.42"}; }; template static inline T waitForValue(const Promise &promise, const T &initial) { T value(initial); promise.then([&](const T &res) { value = res; }).wait(); return value; } template static inline T waitForValue(const Promise &promise, const T &initial, const T &expected) { T value(initial); promise.then([&]() { value = expected; }).wait(); return value; } template static inline E waitForError(const Promise &promise, const E &initial) { E error(initial); promise .fail([&](const E &err) { error = err; return T(); }) .wait(); return error; } template static inline E waitForError(const Promise &promise, const E &initial) { E error(initial); promise.fail([&](const E &err) { error = err; }).wait(); return error; } template 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 p{[](const PromiseResolve &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 p{[](const PromiseResolve &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 p{[](const PromiseResolve &resolve, const PromiseReject &) { 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 p{[](const PromiseResolve &resolve, const PromiseReject &) { 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 p{[this, &id](const PromiseResolve &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 p{[&id, this](const PromiseResolve &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 p{[&id, this](const PromiseResolve &resolve, const PromiseReject &) { 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 p{[&id, this](const PromiseResolve &resolve, const PromiseReject &) { 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 p{[](const PromiseResolve &, const PromiseReject &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 p{[](const PromiseResolve &, const PromiseReject &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 p{[this, &id](const PromiseResolve &, const PromiseReject &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 p{[this, &id](const PromiseResolve &, const PromiseReject &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 p{[](const PromiseResolve &) { 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 p{[](const PromiseResolve &) { 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 p{[](const PromiseResolve &, const PromiseReject &) { 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 p{[](const PromiseResolve &, const PromiseReject &) { 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 p{[this, &id](const PromiseResolve &, const PromiseReject &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(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 p{[this, &id](const PromiseResolve &, const PromiseReject &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(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::resolve(42.13).convert(); static_assert((std::is_same>::value)); BOOST_CHECK_EQUAL(waitForValue(p, -1), 42); BOOST_CHECK_EQUAL(p.isFulfilled(), true); } // Convert enum class to int. { auto p = Promise::resolve(Enum1::Value1).convert(); static_assert((std::is_same>::value)); BOOST_CHECK_EQUAL(waitForValue(p, -1), 1); BOOST_CHECK_EQUAL(p.isFulfilled(), true); } // Convert int to enum class. { auto p = Promise::resolve(1).convert(); static_assert((std::is_same>::value)); BOOST_TEST((waitForValue(p, Enum1::Value0) == Enum1::Value1)); BOOST_CHECK_EQUAL(p.isFulfilled(), true); } // Convert between enums { auto p = Promise::resolve(Enum1::Value1).convert(); static_assert((std::is_same>::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::resolve(Foo{42}).convert(); static_assert((std::is_same>::value)); BOOST_TEST((waitForValue(p, Bar{}) == Bar{42})); BOOST_CHECK_EQUAL(p.isFulfilled(), true); } { auto p = Promise::resolve(42).convert(); static_assert((std::is_same>::value)); BOOST_CHECK_EQUAL(std::any_cast(waitForValue(p, std::any{})), 42); BOOST_CHECK_EQUAL(p.isFulfilled(), true); } { using Variant = std::variant; auto p = Promise::resolve(Foo{42}).convert(); static_assert((std::is_same>::value)); BOOST_TEST((std::get(waitForValue(p, Variant{})) == Foo{42})); BOOST_CHECK_EQUAL(p.isFulfilled(), true); } } BOOST_AUTO_TEST_CASE(ConvertFulfillTAsVoid) { auto p = Promise::resolve(42).convert(); static_assert((std::is_same>::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::resolve(std::string{"42foo"}).convert(); static_assert((std::is_same>::value)); BOOST_TEST(waitForRejected(p)); } // A standard library type unconvertible to a primitive type because there is no converter. { auto p = Promise>::resolve(std::vector{42, -42}).convert(); static_assert((std::is_same>::value)); BOOST_TEST(waitForRejected(p)); } } BOOST_FIXTURE_TEST_CASE(DelayFulfilled, PromiseContext) { using namespace std::chrono; auto begin = system_clock::now(); int64_t elapsed = -1; auto p = Promise::resolve(42).delay(m_ioContext, 1000).finally([&]() { elapsed = duration_cast(system_clock::now() - begin).count(); }); start(); BOOST_CHECK_EQUAL(waitForValue(p, -1), 42); BOOST_CHECK_EQUAL(p.isFulfilled(), true); BOOST_TEST(elapsed >= static_cast(1000 * 0.94)); BOOST_TEST(elapsed <= static_cast(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::reject(std::string{"foo"}).delay(m_ioContext, 1000).finally([&]() { elapsed = duration_cast(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::resolve(42).delay(m_ioContext, std::chrono::seconds{1}).finally([&]() { elapsed = duration_cast(system_clock::now() - begin).count(); }); start(); BOOST_CHECK_EQUAL(waitForValue(p, -1), 42); BOOST_CHECK_EQUAL(p.isFulfilled(), true); BOOST_TEST(elapsed >= static_cast(1000 * 0.94)); BOOST_TEST(elapsed <= static_cast(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::reject(std::string{"foo"}).delay(m_ioContext, std::chrono::seconds{1}).finally([&]() { elapsed = duration_cast(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 values; auto p = Promise>::resolve({}).each([&](int v, ...) { values.push_back(v); }); static_assert((std::is_same>>::value)); BOOST_CHECK_EQUAL(waitForValue(p, std::vector{}), std::vector{}); BOOST_CHECK_EQUAL(values, (std::vector{})); } BOOST_AUTO_TEST_CASE(EachPreserveValues) { std::vector values; auto p = Promise>::resolve({42, 43, 44}).each([&](int v, ...) { values.push_back(v + 1); }); static_assert((std::is_same>>::value)); BOOST_CHECK_EQUAL(waitForValue(p, std::vector{}), (std::vector{42, 43, 44})); BOOST_CHECK_EQUAL(values, (std::vector{43, 44, 45})); } BOOST_AUTO_TEST_CASE(EachIgnoreResult) { std::vector values; auto p = Promise>::resolve({42, 43, 44}).each([&](int v, ...) { values.push_back(v + 1); return "Foo"; }); static_assert((std::is_same>>::value)); BOOST_CHECK_EQUAL(waitForValue(p, std::vector{}), (std::vector{42, 43, 44})); BOOST_CHECK_EQUAL(values, (std::vector{43, 44, 45})); } BOOST_FIXTURE_TEST_CASE(EachDelayedFulfilled, PromiseContext) { auto strand = boost::asio::make_strand(m_ioContext); std::map values; auto p = Promise>::resolve({42, 43, 44}).each([&](int v, int index) { return Promise::resolve().delay(strand, 50).then([&values, v, index]() { values[v] = index; return 42; }); }); start(); static_assert((std::is_same>>::value)); BOOST_CHECK_EQUAL(waitForValue(p, std::vector{}), (std::vector{42, 43, 44})); std::map 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>::resolve({42, 43, 44}).each([&id, this](int v, ...) { return Promise{[&, this](const PromiseResolve &resolve, const PromiseReject &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>>::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>::resolve({42, 43, 44}).each([](int v, ...) { if (v == 44) { throw std::string{"foo"}; } }); static_assert((std::is_same>>::value)); BOOST_CHECK_EQUAL(waitForError(p, std::string{}), std::string{"foo"}); } BOOST_FIXTURE_TEST_CASE(EachFunctorArguments, PromiseContext) { std::vector values; auto p = Promise>::resolve({42, 43, 44}).each([&](int v, int i) { values.push_back(i); values.push_back(v); }); static_assert((std::is_same>>::value)); BOOST_CHECK_EQUAL(waitForValue(p, std::vector{}), (std::vector{42, 43, 44})); BOOST_CHECK_EQUAL(values, (std::vector{0, 42, 1, 43, 2, 44})); } template struct SequenceTester { static void exec() { std::vector 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>::value)); BOOST_CHECK_EQUAL(waitForValue(p, Sequence{}), (Sequence{42, 43, 44})); std::vector 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>::exec(); SequenceTester>::exec(); } BOOST_AUTO_TEST_CASE(FailSameType) { // http://en.cppreference.com/w/cpp/error/exception auto p = Promise::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::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::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::reject(kErr).fail(&stringToFloatNoArg); auto p1 = Promise::reject(kErr).fail(&stringToFloatArgByVal); auto p2 = Promise::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::reject(kErr).fail(&PromiseContext::stringToFloatNoArg); auto p1 = Promise::reject(kErr).fail(&PromiseContext::stringToFloatArgByVal); auto p2 = Promise::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> values; auto input = Promise::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(values[0]), 42); BOOST_CHECK_EQUAL(std::get(values[1]), "43"); BOOST_CHECK_EQUAL(std::get(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::resolve(42).then([&thread](int res) { return Promise{[&thread, res](const PromiseResolve &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>)); BOOST_CHECK_EQUAL(value, "foo42"); BOOST_CHECK_EQUAL(promise.isFulfilled(), true); if (thread.joinable()) { thread.join(); } } BOOST_AUTO_TEST_CASE(ThenRejectSync) { auto input = Promise::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::resolve(42).then([&thread](int res) { return Promise{[&thread, res](const PromiseResolve &, const PromiseReject &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>)); 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::resolve(42); int value = -1; p.then([&]() { value = 43; }).wait(); static_assert((std::is_same>::value)); BOOST_CHECK_EQUAL(value, 43); } BOOST_AUTO_TEST_CASE(ThenNullHandler) { { // resolved auto p = Promise::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::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::resolve().then(&fnNoArg); auto p1 = Promise::resolve(PromiseContext::kRes).then(&fnArgByVal); auto p2 = Promise::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::resolve().then(&PromiseContext::kFnNoArg); auto p1 = Promise::resolve(PromiseContext::kRes).then(&PromiseContext::kFnArgByVal); auto p2 = Promise::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 stdFnNoArg = fnNoArg; std::function stdFnArgByVal = fnArgByVal; std::function stdFnArgByRef = fnArgByRef; auto p0 = Promise::resolve().then(stdFnNoArg); auto p1 = Promise::resolve(kRes).then(stdFnArgByVal); auto p2 = Promise::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 stdFnNoArg = fnNoArg; const std::function stdFnArgByVal = fnArgByVal; const std::function stdFnArgByRef = fnArgByRef; auto p0 = Promise::resolve().then(stdFnNoArg); auto p1 = Promise::resolve(kRes).then(stdFnArgByVal); auto p2 = Promise::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::resolve().then(std::function{fnNoArg}); auto p1 = Promise::resolve(kRes).then(std::function{fnArgByVal}); auto p2 = Promise::resolve(kRes).then(std::function{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 bindNoArg = std::bind(&PromiseContext::functionNoArg, this); const std::function bindArgByVal = std::bind(&PromiseContext::functionArgByVal, this, _1); const std::function bindArgByRef = std::bind(&PromiseContext::functionArgByRef, this, _1); auto p0 = Promise::resolve().then(bindNoArg); auto p1 = Promise::resolve(kRes).then(bindArgByVal); auto p2 = Promise::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::resolve().then(lambdaNoArg); auto p1 = Promise::resolve(kRes).then(lambdaArgByVal); auto p2 = Promise::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::resolve().then(lambdaNoArg); auto p1 = Promise::resolve(kRes).then(lambdaArgByVal); auto p2 = Promise::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::resolve().then([]() { return kRes; }); auto p1 = Promise::resolve(kRes).then([](float v) { return v; }); auto p2 = Promise::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()