diff --git a/UnitTest/CMakeLists.txt b/UnitTest/CMakeLists.txt index 83cf9f3..ab6864d 100644 --- a/UnitTest/CMakeLists.txt +++ b/UnitTest/CMakeLists.txt @@ -37,7 +37,7 @@ add_executable(UnitTest main.cpp ) target_compile_definitions(UnitTest - PUBLIC LOG_FILTER_LEVEL=1 + PUBLIC LOG_FILTER_LEVEL=2 ) target_link_libraries(UnitTest diff --git a/UnitTest/Universal/PromiseTest.cpp b/UnitTest/Universal/PromiseTest.cpp index 803e35c..b1994d2 100644 --- a/UnitTest/Universal/PromiseTest.cpp +++ b/UnitTest/Universal/PromiseTest.cpp @@ -1,9 +1,109 @@ -#include "Promise.h" +#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); @@ -18,6 +118,616 @@ static inline T waitForValue(const Promise &promise, const T &initial, con 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); @@ -34,8 +744,6 @@ BOOST_AUTO_TEST_CASE(Then) { BOOST_CHECK_EQUAL(input.isFulfilled(), true); BOOST_CHECK_EQUAL(output.isFulfilled(), true); - - // auto p= new int; } BOOST_AUTO_TEST_CASE(ThenResolveAsync) { @@ -140,7 +848,6 @@ BOOST_AUTO_TEST_CASE(ThenNullHandler) { } const float kRes = 0.42f; -const float kFail = -1.f; float fnNoArg() { return kRes; @@ -153,54 +860,24 @@ float fnArgByRef(const float &v) { return v; } -class Klass { -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) { +BOOST_FIXTURE_TEST_CASE(ThenFunctionPtrHandlers, PromiseContext) { { // Global functions. auto p0 = Promise::resolve().then(&fnNoArg); - auto p1 = Promise::resolve(kRes).then(&fnArgByVal); + auto p1 = Promise::resolve(PromiseContext::kRes).then(&fnArgByVal); auto p2 = Promise::resolve(kRes).then(&fnArgByRef); - BOOST_CHECK_EQUAL(waitForValue(p0, kFail), kRes); - BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes); - BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes); + 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(&Klass::kFnNoArg); - auto p1 = Promise::resolve(kRes).then(&Klass::kFnArgByVal); - auto p2 = Promise::resolve(kRes).then(&Klass::kFnArgByRef); + 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), kRes); - BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes); - BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes); + BOOST_CHECK_EQUAL(waitForValue(p0, kFail), PromiseContext::kRes); + BOOST_CHECK_EQUAL(waitForValue(p1, kFail), PromiseContext::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; + const float value = 42.f; + this->setValue(value); - const float val{42.f}; - const Klass obj{val}; - - const std::function bindNoArg = std::bind(&Klass::fnNoArg, &obj); - const std::function bindArgByVal = std::bind(&Klass::fnArgByVal, &obj, _1); - const std::function bindArgByRef = std::bind(&Klass::fnArgByRef, &obj, _1); + 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), val); - BOOST_CHECK_EQUAL(waitForValue(p1, kFail), val + kRes); - BOOST_CHECK_EQUAL(waitForValue(p2, kFail), val + kRes); + 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) { @@ -297,4 +973,6 @@ BOOST_AUTO_TEST_CASE(ThenLambdaHandlers) { BOOST_CHECK_EQUAL(waitForValue(p1, kFail), kRes); BOOST_CHECK_EQUAL(waitForValue(p2, kFail), kRes); } -} \ No newline at end of file +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/Universal/BoostLog.cpp b/Universal/BoostLog.cpp index 0200be6..54ef8b6 100644 --- a/Universal/BoostLog.cpp +++ b/Universal/BoostLog.cpp @@ -26,8 +26,7 @@ void initialize(const std::string &filename, const std::string &target, boost::l #else std::ostringstream oss; oss << filename << "_%Y-%m-%d_%H.%M.%S_%N.log"; - auto fileSink = - add_file_log(keywords::file_name = oss.str(), keywords::auto_flush = true, keywords::target = target); + auto fileSink = add_file_log(keywords::file_name = oss.str(), keywords::auto_flush = true, keywords::target = target); fileSink->set_formatter(&defaultFormatter); #endif initialized = true; @@ -41,12 +40,11 @@ void defaultFormatter(const boost::log::record_view &record, boost::log::formatt boost::log::formatting_ostream oss(level); oss << "[" << record[boost::log::trivial::severity] << "]"; - auto dateTimeFormatter = expressions::stream << expressions::format_date_time( - "TimeStamp", "[%m-%d %H:%M:%S.%f]"); + auto dateTimeFormatter = expressions::stream + << expressions::format_date_time("TimeStamp", "[%m-%d %H:%M:%S.%f]"); dateTimeFormatter(record, ostream); - ostream << "[" << boost::log::extract("ThreadID", record) - << "]"; + ostream << "[" << boost::log::extract("ThreadID", record) << "]"; ostream << std::setw(8) << std::left << level; auto &&category = record[AmassKeywords::category]; if (!category.empty()) ostream << " [" << category << "]"; @@ -82,3 +80,14 @@ BOOST_LOG_GLOBAL_LOGGER_INIT(location_logger, LocationLogger &m) { + os << "{ "; + for (const auto &pair : m) { + os << "{" << pair.first << ", " << pair.second << "} "; + } + os << "}"; + return os; +} +} // namespace std \ No newline at end of file diff --git a/Universal/BoostLog.inl b/Universal/BoostLog.inl index 66b8c4f..f474318 100644 --- a/Universal/BoostLog.inl +++ b/Universal/BoostLog.inl @@ -21,7 +21,9 @@ public: } return ret; } - inline bool doOnceInterface() const { return static_cast(this)->doOnce(); } + inline bool doOnceInterface() const { + return static_cast(this)->doOnce(); + } protected: mutable bool m_status = true; @@ -29,8 +31,11 @@ protected: class NumberPredicator : public LogPredicator { public: - NumberPredicator(int limit) : m_limit(limit) {} - inline bool doOnce() const { return (m_current++ % m_limit) == 0; } + NumberPredicator(int limit) : m_limit(limit) { + } + inline bool doOnce() const { + return (m_current++ % m_limit) == 0; + } private: int m_limit; @@ -39,8 +44,11 @@ private: class CountPredicator : public LogPredicator { public: - CountPredicator(int limit) : m_limit(limit) {} - inline bool doOnce() const { return m_current++ < m_limit; } + CountPredicator(int limit) : m_limit(limit) { + } + inline bool doOnce() const { + return m_current++ < m_limit; + } private: int m_limit; @@ -49,7 +57,8 @@ private: class TimePredicator : public LogPredicator { public: - TimePredicator(int milliseconds) : m_limit(std::chrono::milliseconds(milliseconds)) {} + TimePredicator(int milliseconds) : m_limit(std::chrono::milliseconds(milliseconds)) { + } inline bool doOnce() const { auto now = std::chrono::system_clock::now(); @@ -65,8 +74,7 @@ private: }; template -CategoryTaggerFeature::CategoryTaggerFeature(const CategoryTaggerFeature &obj) - : BaseT(static_cast(obj)) { +CategoryTaggerFeature::CategoryTaggerFeature(const CategoryTaggerFeature &obj) : BaseT(static_cast(obj)) { } template @@ -96,13 +104,13 @@ boost::log::record CategoryTaggerFeature::open_record_unlocked(const Args } template -FilenameTaggerFeature::FilenameTaggerFeature(const FilenameTaggerFeature &obj) - : BaseT(static_cast(obj)) { +FilenameTaggerFeature::FilenameTaggerFeature(const FilenameTaggerFeature &obj) : BaseT(static_cast(obj)) { } template template -FilenameTaggerFeature::FilenameTaggerFeature(const ArgsT &args) : BaseT(args) {} +FilenameTaggerFeature::FilenameTaggerFeature(const ArgsT &args) : BaseT(args) { +} template template @@ -126,13 +134,13 @@ boost::log::record FilenameTaggerFeature::open_record_unlocked(const Args } template -FunctionTaggerFeature::FunctionTaggerFeature(const FunctionTaggerFeature &obj) - : BaseT(static_cast(obj)) { +FunctionTaggerFeature::FunctionTaggerFeature(const FunctionTaggerFeature &obj) : BaseT(static_cast(obj)) { } template template -FunctionTaggerFeature::FunctionTaggerFeature(const ArgsT &args) : BaseT(args) {} +FunctionTaggerFeature::FunctionTaggerFeature(const ArgsT &args) : BaseT(args) { +} template template @@ -157,10 +165,12 @@ boost::log::record FunctionTaggerFeature::open_record_unlocked(const Args template template -LineTaggerFeature::LineTaggerFeature(const ArgsT &args) : BaseT(args) {} +LineTaggerFeature::LineTaggerFeature(const ArgsT &args) : BaseT(args) { +} template -LineTaggerFeature::LineTaggerFeature(const LineTaggerFeature &obj) : BaseT(static_cast(obj)) {} +LineTaggerFeature::LineTaggerFeature(const LineTaggerFeature &obj) : BaseT(static_cast(obj)) { +} template template @@ -182,4 +192,8 @@ boost::log::record LineTaggerFeature::open_record_unlocked(const ArgsT &a return BaseT::open_record_unlocked(args); } +namespace std { +ostream &operator<<(ostream &os, const map &m); +} + #endif // BOOSTLOG_INL diff --git a/Universal/FunctionTraits.h b/Universal/FunctionTraits.h index 8673075..2a85653 100644 --- a/Universal/FunctionTraits.h +++ b/Universal/FunctionTraits.h @@ -124,9 +124,6 @@ typename FunctionTraits>::StlFunctionType makeStlFunction return static_cast>::StlFunctionType>(std::forward(lambda)); } -template -using Unqualified = typename std::remove_cv::type>::type; - /*! * \struct HasCallOperator * http://stackoverflow.com/a/5117641 diff --git a/Universal/IoContext.h b/Universal/IoContext.h index 86ececf..bc53a2f 100644 --- a/Universal/IoContext.h +++ b/Universal/IoContext.h @@ -3,6 +3,8 @@ #include "Singleton.h" #include +#include +#include #include class IoContext { @@ -12,7 +14,9 @@ public: Asynchronous, }; friend class Amass::Singleton; - std::shared_ptr ioContext() const { return m_ioContext; } + std::shared_ptr ioContext() const { + return m_ioContext; + } template void run() { if constexpr (mode == Mode::Asynchronous) { @@ -24,9 +28,28 @@ public: void stop(); ~IoContext(); + /** + * @brief + * + * @tparam Executor boost::asio::io_context、boost::asio::strand(通过 boost::asio::make_strand(io_context); 得到) + */ + template + static void postDelayed(Executor &ioContext, Func &&task, const std::chrono::duration &duration) { + auto timer = std::make_shared(ioContext, duration); + timer->async_wait([timer, task = std::forward(task)](const boost::system::error_code & /*e*/) mutable { + boost::asio::post(timer->get_executor(), std::move(task)); + }); + } + + template + static void postDelayed(Executor &ioContext, Func &&task, int milliseconds) { + return postDelayed(ioContext, std::forward(task), std::chrono::milliseconds(milliseconds)); + } + protected: template - IoContext(Args &&... args) : m_ioContext(std::make_shared(std::forward(args)...)) {} + IoContext(Args &&...args) : m_ioContext(std::make_shared(std::forward(args)...)) { + } void runIoContext(); private: diff --git a/Universal/Private/PromiseData.h b/Universal/Private/PromiseData.h index 28f2271..d817c39 100644 --- a/Universal/Private/PromiseData.h +++ b/Universal/Private/PromiseData.h @@ -180,4 +180,35 @@ protected: } }; +template +struct PromiseConverterBase; + +template +struct PromiseConverterBase { + static std::function create() { + return [](const T &value) { return static_cast(value); }; + } +}; + +template +struct PromiseConverterBase { + static std::function create() { + return [](const T &value) { + throw PromiseConversionException{}; + return U{}; + }; + } +}; + +template +struct PromiseConverter : PromiseConverterBase::value || + // Conversion to void. + std::is_same::value || + // Conversion between enums and arithmetic types. + ((std::is_enum::value && std::is_arithmetic::value) || + (std::is_arithmetic::value && std::is_enum::value) || + (std::is_enum::value && std::is_enum::value))> {}; + #endif // __PROMISEDATA_H__ \ No newline at end of file diff --git a/Universal/Private/PromiseException.h b/Universal/Private/PromiseException.h deleted file mode 100644 index a3b5b1b..0000000 --- a/Universal/Private/PromiseException.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __PROMISEEXCEPTION_H__ -#define __PROMISEEXCEPTION_H__ - -#include - -class PromiseUndefinedException : public std::exception { -public: -}; - -#endif // __PROMISEEXCEPTION_H__ \ No newline at end of file diff --git a/Universal/Private/PromiseHandler.h b/Universal/Private/PromiseHandler.h index 5504592..ae77cbc 100644 --- a/Universal/Private/PromiseHandler.h +++ b/Universal/Private/PromiseHandler.h @@ -2,9 +2,9 @@ #define __PROMISEHANDLER_H__ #include "../FunctionTraits.h" -#include -#include #include "PromiseData.h" +#include +#include template class Promise; @@ -26,6 +26,12 @@ struct PromiseDeduce : public PromiseDeduce {}; template struct PromiseDeduce> : public PromiseDeduce {}; +template +struct PromiseFunctor { + using ResultType = typename std::invoke_result_t; + using PromiseType = typename PromiseDeduce::Type; +}; + template struct PromiseFulfill { template diff --git a/Universal/Private/PromiseResolver.h b/Universal/Private/PromiseResolver.h index b17d489..0fb5e32 100644 --- a/Universal/Private/PromiseResolver.h +++ b/Universal/Private/PromiseResolver.h @@ -1,8 +1,6 @@ #ifndef __PROMISERESOLVER_H__ #define __PROMISERESOLVER_H__ -#include "PromiseException.h" - template class Promise; diff --git a/Universal/Promise.h b/Universal/Promise.h index eba3f55..d3372af 100644 --- a/Universal/Promise.h +++ b/Universal/Promise.h @@ -1,6 +1,16 @@ #ifndef __PROMISE_H__ #define __PROMISE_H__ +#include + +class PromiseUndefinedException : public std::exception { +public: +}; + +class PromiseConversionException : public std::exception { +public: +}; + #include "Private/PromiseHandler.h" #include "Private/PromiseResolver.h" #include @@ -42,6 +52,25 @@ public: swap(other); } + virtual ~PromiseBase() { + } + + PromiseBase &operator=(const PromiseBase &other) { + m_d = other.m_d; + return *this; + } + PromiseBase &operator=(PromiseBase &&other) { + PromiseBase(std::move(other)).swap(*this); + return *this; + } + + bool operator==(const PromiseBase &other) const { + return (m_d == other.m_d); + } + bool operator!=(const PromiseBase &other) const { + return (m_d != other.m_d); + } + void swap(PromiseBase &other) { std::swap(m_d, other.m_d); } @@ -78,6 +107,12 @@ public: return then(std::forward(fulfilled), nullptr); } + template + Promise finally(THandler handler) const { + Promise p = *this; + return p.then(handler, handler).then([=]() { return p; }); + } + template typename PromiseHandler::PromiseType fail(TRejected &&rejected) const { return then(nullptr, std::forward(rejected)); @@ -95,10 +130,112 @@ public: return Promise{[&](const PromiseResolve &, const PromiseReject &reject) { reject(std::forward(error)); }}; } + template + Promise tapFail(THandler handler) const { + Promise p = *this; + return p.then([]() {}, handler).then([=]() { return p; }); + } + + template + Promise tap(THandler handler) const { + Promise p = *this; + return p.then(handler).then([=]() { return p; }); + } + +#ifdef BOOST_ASIO_EXECUTION_EXECUTOR + template + Promise delay(Executor &ioContext, const std::chrono::duration &duration) const { + return tap([&]() { + return Promise{[&](const PromiseResolve &resolve) { + auto timer = std::make_shared(ioContext, duration); + timer->async_wait([timer, resolve](const boost::system::error_code & /*e*/) mutable { + boost::asio::post(timer->get_executor(), resolve); + }); + }}; + }); + } + + template + Promise delay(Executor &ioContext, int milliseconds) const { + return delay(ioContext, std::chrono::milliseconds(milliseconds)); + } +#endif + protected: std::shared_ptr> m_d; }; +namespace PromiseHelper { +template +typename PromiseDeduce::Type resolve(T &&value) { + using PromiseType = typename PromiseDeduce::Type; + using ValueType = typename PromiseType::Type; + using ResolveType = PromiseResolve; + using RejectType = PromiseReject; + + return PromiseType{[&](ResolveType &&resolve, RejectType &&reject) { + PromiseFulfill>::call(std::forward(value), std::forward(resolve), std::forward(reject)); + }}; +} + +Promise resolve(); + +template +typename PromiseFunctor::PromiseType attempt(Functor &&fn, Args &&...args) { + using FunctorType = PromiseFunctor; + using PromiseType = typename FunctorType::PromiseType; + using ValueType = typename PromiseType::Type; + + // NOTE: std::forward>: 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; + using RejectType = PromiseReject; + + return PromiseType{[&](ResolveType &&resolve, RejectType &&reject) { + PromiseDispatch::call(std::forward(resolve), std::forward(reject), + std::forward(fn), std::forward(args)...); + }}; +} + +template class Sequence = std::vector, typename... Args> +Promise> all(const Sequence, Args...> &promises) { + const int count = static_cast(promises.size()); + if (count == 0) { + return PromiseHelper::resolve(std::vector{}); + } + + return Promise>{[=](const PromiseResolve> &resolve, const PromiseReject> &reject) { + auto remaining = std::make_shared(count); + auto results = std::make_shared>(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