diff --git a/CHANGELOG.md b/CHANGELOG.md index d89d16703dc..09720ef01a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Internals * SubscriptionStore's should be initialized with an implict empty SubscriptionSet so users can wait for query version zero to finish synchronizing. ((#5166)[https://github.com/realm/realm-core/pull/5166]) +* Fixed `Future::on_completion()` and added missing testing for it ((#5181)[https://github.com/realm/realm-core/pull/5181]) ---------------------------------------------- diff --git a/src/realm/util/future.hpp b/src/realm/util/future.hpp index 63744dc4a1a..53e3ad9ac61 100644 --- a/src/realm/util/future.hpp +++ b/src/realm/util/future.hpp @@ -16,6 +16,8 @@ * **************************************************************************/ +#pragma once + #include #include #include @@ -793,7 +795,7 @@ class REALM_NODISCARD future_details::Future { using Wrapper = StatusOrStatusWith; using Result = NormalizedCallResult>; if constexpr (!is_future) { - return generalImpl( + return general_impl( // on ready success: [&](T&& val) { return Future::make_ready( @@ -801,7 +803,7 @@ class REALM_NODISCARD future_details::Future { }, // on ready failure: [&](Status&& status) { - return Future::make_Ready( + return Future::make_ready( no_throw_call(std::forward(func), Wrapper(std::move(status)))); }, // on not ready yet: @@ -809,16 +811,16 @@ class REALM_NODISCARD future_details::Future { return make_continuation( [func = std::forward(func)](SharedState* input, SharedState* output) mutable noexcept { - if (!input->status.is_ok()) - return output->set_from(no_throw_call(func, Wrapper(std::move(input->status)))); + if (!input->m_status.is_ok()) + return output->set_from(no_throw_call(func, Wrapper(std::move(input->m_status)))); - output->set_from(no_throw_call(func, Wrapper(std::move(*input->data)))); + output->set_from(no_throw_call(func, Wrapper(std::move(*input->m_data)))); }); }); } else { using UnwrappedResult = typename Result::value_type; - return generalImpl( + return general_impl( // on ready success: [&](T&& val) { try { @@ -844,23 +846,23 @@ class REALM_NODISCARD future_details::Future { return make_continuation( [func = std::forward(func)](SharedState* input, SharedState* output) mutable noexcept { - if (!input->status.isOK()) { + if (!input->m_status.is_ok()) { try { throwing_call(func, Wrapper(std::move(input->m_status))) .propagate_result_to(output); } catch (...) { - output->set_error(exception_to_status()); + output->set_status(exception_to_status()); } return; } try { - throwing_call(func, Wrapper(std::move(*input->data))).propagate_result_to(output); + throwing_call(func, Wrapper(std::move(*input->m_data))).propagate_result_to(output); } catch (...) { - output->set_error(exception_to_status()); + output->set_status(exception_to_status()); } }); }); @@ -937,6 +939,8 @@ class REALM_NODISCARD future_details::Future { } } + Future ignore_value() && noexcept; + private: template friend class Future; @@ -1115,7 +1119,13 @@ class REALM_NODISCARD future_details::Future { return std::move(inner).on_error(std::forward(func)); } - Future ignoreValue() && noexcept + template + auto on_completion(Func&& func) && noexcept + { + return std::move(inner).on_completion(std::forward(func)); + } + + Future ignore_value() && noexcept { return std::move(*this); } @@ -1207,4 +1217,10 @@ inline void Promise::set_from(Future&& future) noexcept }); } +template +inline Future Future::ignore_value() && noexcept +{ + return std::move(*this).then([](auto&&) {}); +} + } // namespace realm::util diff --git a/test/test_util_future.cpp b/test/test_util_future.cpp index 3b58a92fedd..a2570d304a0 100644 --- a/test/test_util_future.cpp +++ b/test/test_util_future.cpp @@ -1500,6 +1500,240 @@ TEST(Future_MoveOnly_Fail_onErrorFutureAsync) }); } +TEST(Future_MoveOnly_Success_onCompletionSimple) +{ + FUTURE_SUCCESS_TEST( + [] { + return Widget(1); + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([](StatusWith i) { + return i.get_value() + 2; + }) + .get(), + 3); + }); +} + +TEST(Future_MoveOnly_Success_onCompletionVoid) +{ + FUTURE_SUCCESS_TEST( + [] { + return Widget(1); + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](StatusWith i) { + CHECK_EQUAL(i.get_value(), 1); + }) + .on_completion([this](Status s) { + CHECK(s.is_ok()); + return Widget(3); + }) + .get(), + 3); + }); +} + +TEST(Future_MoveOnly_Success_onCompletionStatus) +{ + FUTURE_SUCCESS_TEST( + [] { + return Widget(1); + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](StatusWith i) { + CHECK_EQUAL(i.get_value(), 1); + return Status::OK(); + }) + .on_completion([this](Status s) { + CHECK(s.is_ok()); + return Widget(3); + }) + .get(), + 3); + }); +} + +TEST(Future_MoveOnly_Success_onCompletionError_Status) +{ + FUTURE_SUCCESS_TEST( + [] { + return Widget(1); + }, + [this](auto&& fut) { + auto fut2 = std::move(fut).on_completion([](StatusWith) { + return fail_status; + }); + static_assert(future_details::is_future); + static_assert(std::is_same_v); + CHECK_EQUAL(fut2.get_no_throw(), fail_status); + }); +} + +TEST(Future_MoveOnly_Success_onCompletionError_StatusWith) +{ + FUTURE_SUCCESS_TEST( + [] { + return Widget(1); + }, + [this](auto&& fut) { + auto fut2 = std::move(fut).on_completion([](StatusWith) { + return StatusWith(fail_status); + }); + static_assert(future_details::is_future); + static_assert(std::is_same_v); + CHECK_EQUAL(fut2.get_no_throw(), fail_status); + }); +} + +TEST(Future_MoveOnly_Success_onCompletionFutureImmediate) +{ + FUTURE_SUCCESS_TEST( + [] { + return Widget(1); + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([](StatusWith i) { + return Future::make_ready(Widget(i.get_value() + 2)); + }) + .get(), + 3); + }); +} + +TEST(Future_MoveOnly_Success_onCompletionFutureReady) +{ + FUTURE_SUCCESS_TEST( + [] { + return Widget(1); + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([](StatusWith i) { + auto pf = make_promise_future(); + pf.promise.emplace_value(i.get_value() + 2); + return std::move(pf.future); + }) + .get(), + 3); + }); +} + +TEST(Future_MoveOnly_Success_onCompletionFutureAsync) +{ + FUTURE_SUCCESS_TEST( + [] { + return Widget(1); + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([&](StatusWith i) { + return async([i = i.get_value().val] { + return Widget(i + 2); + }); + }) + .get(), + 3); + }); +} + +TEST(Future_MoveOnly_Success_onCompletionFutureAsyncThrow) +{ + FUTURE_SUCCESS_TEST( + [] { + return Widget(1); + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([](StatusWith) { + throw ExceptionForStatus(fail_status); + return Future(); + }) + .get_no_throw(), + fail_status); + }); +} + +TEST(Future_MoveOnly_Fail_onCompletionSimple) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](StatusWith i) { + CHECK_NOT(i.is_ok()); + return i.get_status(); + }) + .get_no_throw(), + fail_status); + }); +} + +TEST(Future_MoveOnly_Fail_onCompletionFutureAsync) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](StatusWith i) { + CHECK_NOT(i.is_ok()); + return i.get_status(); + }) + .get_no_throw(), + fail_status); + }); +} + +TEST(Future_MoveOnly_Fail_onCompletionError_throw) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + auto fut2 = std::move(fut).on_completion([this](StatusWith s) -> Widget { + CHECK_EQUAL(s.get_status(), fail_status); + throw ExceptionForStatus(fail_status_2); + }); + CHECK_EQUAL(std::move(fut2).get_no_throw(), fail_status_2); + }); +} + +TEST(Future_MoveOnly_Fail_onCompletionError_StatusWith) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + auto fut2 = std::move(fut).on_completion([this](StatusWith s) { + CHECK_EQUAL(s.get_status(), fail_status); + return StatusWith(fail_status_2); + }); + CHECK_EQUAL(std::move(fut2).get_no_throw(), fail_status_2); + }); +} + +TEST(Future_MoveOnly_Fail_onCompletionFutureImmediate) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](StatusWith s) { + CHECK_EQUAL(s.get_status(), fail_status); + return Future::make_ready(Widget(3)); + }) + .get(), + 3); + }); +} + +TEST(Future_MoveOnly_Fail_onCompletionFutureReady) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](StatusWith s) { + CHECK_EQUAL(s.get_status(), fail_status); + auto pf = make_promise_future(); + pf.promise.emplace_value(3); + return std::move(pf.future); + }) + .get(), + 3); + }); +} + // This is the motivating case for SharedStateBase::isJustForContinuation. Without that logic, there // would be a long chain of SharedStates, growing longer with each recursion. That logic exists to // limit it to a fixed-size chain. @@ -1586,6 +1820,421 @@ TEST(Promise_void_Fail_setFrom) }); } +TEST(Future_Success_onCompletionSimple) +{ + FUTURE_SUCCESS_TEST( + [] { + return 1; + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([](StatusWith i) -> int { + return i.get_value() + 2; + }) + .get(), + 3); + }); +} + +TEST(Future_Success_onCompletionVoid) +{ + FUTURE_SUCCESS_TEST( + [] { + return 1; + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](StatusWith i) { + CHECK_EQUAL(i.get_value(), 1); + }) + .on_completion([this](Status s) -> int { + CHECK(s.is_ok()); + return 3; + }) + .get(), + 3); + }); +} + +TEST(Future_Success_onCompletionStatus) +{ + FUTURE_SUCCESS_TEST( + [] { + return 1; + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](StatusWith i) -> Status { + CHECK_EQUAL(i, 1); + return Status::OK(); + }) + .on_completion([this](Status s) -> int { + CHECK(s.is_ok()); + return 3; + }) + .get(), + 3); + }); +} + +TEST(Future_Success_onCompletionError_Status) +{ + FUTURE_SUCCESS_TEST( + [] { + return 1; + }, + [this](auto&& fut) { + auto fut2 = std::move(fut).on_completion([](StatusWith) -> Status { + return fail_status; + }); +#ifndef _MSC_VER + static_assert(future_details::is_future); + static_assert(std::is_same_v); +#endif + CHECK_THROW_EX(fut2.get(), ExceptionForStatus, (e.to_status() == fail_status)); + }); +} + +TEST(Future_Success_onCompletionError_StatusWith) +{ + FUTURE_SUCCESS_TEST( + [] { + return 1; + }, + [this](auto&& fut) { + auto fut2 = std::move(fut).on_completion([](StatusWith) -> StatusWith { + return StatusWith(fail_status); + }); + static_assert(future_details::is_future); + static_assert(std::is_same_v); + CHECK_THROW_EX(fut2.get(), ExceptionForStatus, (e.to_status() == fail_status)); + }); +} + +TEST(Future_Success_onCompletionFutureImmediate) +{ + FUTURE_SUCCESS_TEST( + [] { + return 1; + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([](StatusWith i) -> Future { + return Future::make_ready(i.get_value() + 2); + }) + .get(), + 3); + }); +} + +TEST(Future_Success_onCompletionFutureReady) +{ + FUTURE_SUCCESS_TEST( + [] { + return 1; + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([](StatusWith i) -> Future { + auto pf = make_promise_future(); + pf.promise.emplace_value(i.get_value() + 2); + return std::move(pf.future); + }) + .get(), + 3); + }); +} + +TEST(Future_Success_onCompletionFutureAsync) +{ + FUTURE_SUCCESS_TEST( + [] { + return 1; + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([](StatusWith i) -> Future { + return async([i = i.get_value()] { + return i + 2; + }); + }) + .get(), + 3); + }); +} + +TEST(Future_Success_onCompletionFutureAsyncThrow) +{ + FUTURE_SUCCESS_TEST( + [] { + return 1; + }, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([](StatusWith) -> Future { + throw ExceptionForStatus(fail_status); + return Future(); + }) + .get_no_throw(), + fail_status); + }); +} + +TEST(Future_Fail_onCompletionSimple) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](StatusWith i) -> Status { + CHECK_NOT(i.is_ok()); + + return i.get_status(); + }) + .get_no_throw(), + fail_status); + }); +} + +TEST(Future_Fail_onCompletionError_throw) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + auto fut2 = std::move(fut).on_completion([this](StatusWith s) -> int { + CHECK_EQUAL(s.get_status(), fail_status); + throw ExceptionForStatus(fail_status); + }); + CHECK_EQUAL(fut2.get_no_throw(), fail_status); + }); +} + +TEST(Future_Fail_onCompletionError_StatusWith) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + auto fut2 = std::move(fut).on_completion([this](StatusWith s) -> StatusWith { + CHECK_EQUAL(s.get_status(), fail_status); + return StatusWith(fail_status); + }); + CHECK_EQUAL(fut2.get_no_throw(), fail_status); + }); +} + +TEST(Future_Fail_onCompletionFutureImmediate) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](StatusWith s) -> Future { + CHECK_EQUAL(s.get_status(), fail_status); + return Future::make_ready(3); + }) + .get(), + 3); + }); +} + +TEST(Future_Fail_onCompletionFutureReady) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](StatusWith s) -> Future { + CHECK_EQUAL(s.get_status(), fail_status); + auto pf = make_promise_future(); + pf.promise.emplace_value(3); + return std::move(pf.future); + }) + .get(), + 3); + }); +} + +TEST(Future_Void_Success_onCompletionSimple) +{ + FUTURE_SUCCESS_TEST([] {}, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](Status status) { + CHECK(status.is_ok()); + return 3; + }) + .get(), + 3); + }); +} + +TEST(Future_Void_Success_onCompletionVoid) +{ + FUTURE_SUCCESS_TEST([] {}, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](Status status) { + CHECK(status.is_ok()); + }) + .then([]() { + return 3; + }) + .get(), + 3); + }); +} + +TEST(Future_Void_Success_onCompletionError_Status) +{ + FUTURE_SUCCESS_TEST([] {}, + [this](auto&& fut) { + auto fut2 = std::move(fut).on_completion([this](Status status) { + CHECK(status.is_ok()); + return fail_status; + }); + static_assert(future_details::is_future); + static_assert(std::is_same_v); + CHECK_EQUAL(fut2.get_no_throw(), fail_status); + }); +} + +TEST(Future_Void_Success_onCompletionError_StatusWith) +{ + FUTURE_SUCCESS_TEST([] {}, + [this](auto&& fut) { + auto fut2 = std::move(fut).on_completion([this](Status status) { + CHECK(status.is_ok()); + return StatusWith(fail_status); + }); + static_assert(future_details::is_future); + static_assert(std::is_same_v); + CHECK_EQUAL(fut2.get_no_throw(), fail_status); + }); +} + +TEST(Future_Void_Success_onCompletionFutureImmediate) +{ + FUTURE_SUCCESS_TEST([] {}, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](Status status) { + CHECK(status.is_ok()); + return Future::make_ready(3); + }) + .get(), + 3); + }); +} + +TEST(Future_Void_Success_onCompletionFutureReady) +{ + FUTURE_SUCCESS_TEST([] {}, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](Status status) { + CHECK(status.is_ok()); + auto pf = make_promise_future(); + pf.promise.emplace_value(3); + return std::move(pf.future); + }) + .get(), + 3); + }); +} + +TEST(Future_Void_Success_onCompletionFutureAsync) +{ + FUTURE_SUCCESS_TEST([] {}, + [this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](Status status) { + CHECK(status.is_ok()); + return async([] { + return 3; + }); + }) + .get(), + 3); + }); +} + +TEST(Future_Void_Fail_onCompletionSimple) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](Status s) { + CHECK_EQUAL(s, fail_status); + }) + .then([] { + return 3; + }) + .get_no_throw(), + 3); + }); +} + +TEST(Future_Void_Fail_onCompletionError_throw) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + auto fut2 = std::move(fut).on_completion([this](Status s) { + CHECK_EQUAL(s, fail_status); + throw ExceptionForStatus(fail_status_2); + }); + CHECK_EQUAL(fut2.get_no_throw(), fail_status_2); + }); +} + +TEST(Future_Void_Fail_onCompletionError_Status) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + auto fut2 = std::move(fut).on_completion([this](Status s) { + CHECK_EQUAL(s, fail_status); + return fail_status_2; + }); + CHECK_EQUAL(fut2.get_no_throw(), fail_status_2); + }); +} + +TEST(Future_Void_Fail_onCompletionFutureImmediate) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](Status s) { + CHECK_EQUAL(s, fail_status); + return Future::make_ready(); + }) + .then([] { + return 3; + }) + .get(), + 3); + }); +} + +TEST(Future_Void_Fail_onCompletionFutureReady) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](Status s) { + CHECK_EQUAL(s, fail_status); + auto pf = make_promise_future(); + pf.promise.emplace_value(); + return std::move(pf.future); + }) + .then([] { + return 3; + }) + .get(), + 3); + }); +} + +TEST(Future_Void_Fail_onCompletionFutureAsync) +{ + FUTURE_FAIL_TEST([this](auto&& fut) { + CHECK_EQUAL(std::move(fut) + .on_completion([this](Status s) { + CHECK_EQUAL(s, fail_status); + return async([] {}); + }) + .then([] { + return 3; + }) + .get(), + 3); + }); +} + } // namespace } // namespace realm::util