| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/fasync/future.h> |
| #include <lib/stdcompat/type_traits.h> |
| |
| #include <functional> |
| |
| #include <zxtest/zxtest.h> |
| |
| #define ASSERT_CRITICAL(expr) \ |
| do { \ |
| if (!(expr)) { \ |
| printf("Line %u: abort, %s failed\n", __LINE__, #expr); \ |
| __builtin_abort(); \ |
| } \ |
| } while (false) |
| |
| namespace { |
| |
| class fake_context final : public fasync::context { |
| public: |
| fasync::executor& executor() const override { return const_cast<noop_executor&>(executor_); } |
| fasync::suspended_task suspend_task() override { |
| __builtin_abort(); |
| return fasync::suspended_task(); |
| } |
| |
| private: |
| class noop_executor final : public fasync::executor { |
| void schedule(fasync::pending_task&& task) override {} |
| }; |
| |
| noop_executor executor_; |
| }; |
| |
| template <typename E, typename... Ts> |
| class capture_result_wrapper { |
| public: |
| template <typename F> |
| decltype(auto) wrap(F&& future) { |
| static_assert(cpp17::is_same_v<E, fasync::future_error_t<F>>, ""); |
| if constexpr (sizeof...(Ts) == 1) { |
| static_assert(cpp17::is_same_v<fasync::internal::first_t<Ts...>, fasync::future_value_t<F>>, |
| ""); |
| } |
| return std::forward<F>(future) | fasync::then([this](fitx::result<E, Ts...> result) { |
| last_result = fasync::ready(std::move(result)); |
| }); |
| } |
| |
| fasync::try_poll<E, Ts...> last_result = fasync::pending(); |
| }; |
| |
| struct move_only { |
| move_only(const move_only&) = delete; |
| move_only(move_only&&) = default; |
| move_only& operator=(const move_only&) = delete; |
| move_only& operator=(move_only&&) = default; |
| }; |
| |
| void resume_in_a_little_while(fasync::suspended_task task) { |
| std::thread([task]() mutable { |
| std::this_thread::sleep_for(std::chrono::milliseconds(50)); |
| task.resume(); |
| }).detach(); |
| } |
| |
| fasync::future<> sleep_for_a_little_while() { |
| // This is a rather inefficient way to wait for time to pass but it |
| // is sufficient for our examples. |
| return fasync::make_future([waited = false](fasync::context& context) mutable { |
| if (waited) |
| return; |
| waited = true; |
| resume_in_a_little_while(context.suspend_task()); |
| }); |
| } |
| |
| // Just a simple test to put the future through its paces. |
| // Other tests go into more detail to cover the API surface. |
| TEST(FutureTests, basics) { |
| for (int i = 0; i < 5; i++) { |
| // Make a future that calculates half the square of a number. |
| // Produces an error if the square is odd. |
| auto future = |
| fasync::make_future([i] { |
| // Pretend that squaring numbers is hard and takes time |
| // to finish... |
| return sleep_for_a_little_while() | fasync::then([i] { return fitx::ok(i * i); }); |
| }) | |
| fasync::then([](auto square) -> fitx::result<const char*, int> { |
| if (square.value() % 2 == 0) |
| return fitx::ok(square.value() / 2); |
| return fitx::error("square is odd"); |
| }); |
| |
| // Needs single_threaded_executor.h (coming) |
| #if 0 |
| // Evaluate the future. |
| fitx::result<const char*, int> result = fasync::block(std::move(future)).value(); |
| if (i % 2 == 0) { |
| EXPECT_TRUE(result.is_ok()); |
| EXPECT_EQ(i * i / 2, result.value()); |
| } else { |
| EXPECT_TRUE(result.is_error()); |
| EXPECT_STREQ("square is odd", result.error_value()); |
| } |
| #endif |
| } |
| } |
| |
| TEST(FutureTests, invocation) { |
| uint64_t run_count = 0; |
| fake_context fake_context; |
| fasync::try_future<fitx::failed> future( |
| [&](fasync::context& context) -> fasync::try_poll<fitx::failed> { |
| ASSERT_CRITICAL(&context == &fake_context); |
| if (++run_count == 2) |
| return fasync::ready(fitx::ok()); |
| return fasync::pending(); |
| }); |
| // EXPECT_TRUE(future); |
| |
| fasync::try_poll<fitx::failed> poll = future(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| // EXPECT_TRUE(future); |
| |
| poll = future(fake_context); |
| EXPECT_EQ(2, run_count); |
| EXPECT_TRUE(poll.output().is_ok()); |
| // EXPECT_FALSE(future); |
| } |
| |
| TEST(FutureTests, assignment_and_swap) { |
| fake_context fake_context; |
| |
| fasync::future<> empty = fasync::make_future([] {}); |
| |
| uint64_t run_count = 0; |
| fasync::try_future<fitx::failed> future( |
| [&](fasync::context& context) -> fasync::try_poll<fitx::failed> { |
| run_count++; |
| return fasync::pending(); |
| }); |
| |
| fasync::future<> x(std::move(empty)); |
| |
| fasync::try_future<fitx::failed> y(std::move(future)); |
| static_cast<void>(y(fake_context)); |
| EXPECT_EQ(1, run_count); |
| |
| y = [&](fasync::context& context) -> fasync::try_poll<fitx::failed> { |
| run_count *= 2; |
| return fasync::pending(); |
| }; |
| static_cast<void>(y(fake_context)); |
| EXPECT_EQ(2, run_count); |
| |
| x = std::move(y); |
| static_cast<void>(x(fake_context)); |
| EXPECT_EQ(4, run_count); |
| } |
| |
| TEST(FutureTests, make_future) { |
| fake_context fake_context; |
| |
| // Handler signature: void(). |
| { |
| uint64_t run_count = 0; |
| auto f = fasync::make_future([&] { run_count++; }); |
| fasync::poll<> poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.is_ready()); |
| } |
| |
| // Handler signature: fitx::result<int, char>(). |
| { |
| uint64_t run_count = 0; |
| auto f = fasync::make_future([&]() -> fitx::result<char, int> { |
| run_count++; |
| return fitx::ok(42); |
| }); |
| static_assert(cpp17::is_same_v<char, fasync::future_error_t<decltype(f)>>, ""); |
| static_assert(cpp17::is_same_v<int, fasync::future_value_t<decltype(f)>>, ""); |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(42, poll.output().value()); |
| } |
| |
| // Handler signature: fitx::ok<int>(). |
| { |
| uint64_t run_count = 0; |
| auto f = fasync::make_future([&] { |
| run_count++; |
| return fitx::ok(42); |
| }); |
| static_assert(fasync::is_future_v<decltype(f)>, ""); |
| static_assert(fasync::is_try_future_v<decltype(f)>, ""); |
| static_assert(cpp17::is_same_v<fitx::failed, fasync::future_error_t<decltype(f)>>, ""); |
| static_assert(cpp17::is_same_v<int, fasync::future_value_t<decltype(f)>>, ""); |
| fasync::try_poll<fitx::failed, int> poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(42, poll.output().value()); |
| } |
| |
| { |
| uint64_t run_count = 0; |
| auto f = fasync::make_future([&] { |
| run_count++; |
| return fitx::error(42); |
| }); |
| static_assert(cpp17::is_same_v<int, fasync::future_error_t<decltype(f)>>, ""); |
| fasync::try_poll<int> poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.output().is_error()); |
| EXPECT_EQ(42, poll.output().error_value()); |
| } |
| |
| // Handler signature: fasync::pending(). |
| { |
| uint64_t run_count = 0; |
| auto f = fasync::make_future([&] { |
| run_count++; |
| return fasync::pending(); |
| }); |
| fasync::poll<> poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| } |
| |
| // Handler signature: fasync::unboxed_future<...>. |
| { |
| uint64_t run_count = 0; |
| uint64_t run_count2 = 0; |
| auto f = fasync::make_future([&] { |
| run_count++; |
| return fasync::make_future([&]() -> fasync::try_poll<char, int> { |
| if (++run_count2 == 2) |
| return fasync::ready(fitx::ok(42)); |
| return fasync::pending(); |
| }); |
| }); |
| static_assert(cpp17::is_same_v<char, fasync::future_error_t<decltype(f)>>, ""); |
| static_assert(cpp17::is_same_v<int, fasync::future_value_t<decltype(f)>>, ""); |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_EQ(1, run_count2); |
| EXPECT_TRUE(poll.is_pending()); |
| poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_EQ(2, run_count2); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(42, poll.output().value()); |
| } |
| |
| // Handler signature: void(context&). |
| { |
| uint64_t run_count = 0; |
| auto f = fasync::make_future([&](fasync::context& context) { |
| ASSERT_CRITICAL(&context == &fake_context); |
| run_count++; |
| }); |
| fasync::poll<> poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.is_ready()); |
| } |
| } |
| |
| // This is a bit lower level than fasync::make_future() in that there's no automatic adaptation of |
| // the handler type. |
| TEST(FutureTests, make_future_with_continuation) { |
| uint64_t run_count = 0; |
| fake_context fake_context; |
| auto f = fasync::make_future([&](fasync::context& context) -> fitx::result<char, int> { |
| ASSERT_CRITICAL(&context == &fake_context); |
| run_count++; |
| return fitx::ok(42); |
| }); |
| static_assert(cpp17::is_same_v<char, fasync::future_error_t<decltype(f)>>, ""); |
| static_assert(cpp17::is_same_v<int, fasync::future_value_t<decltype(f)>>, ""); |
| |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(42, poll.output().value()); |
| } |
| |
| TEST(FutureTests, make_try_future) { |
| fake_context fake_context; |
| |
| // Argument type: fitx::result<int, char> |
| { |
| auto f = fasync::make_try_future(fitx::result<char, int>(fitx::ok(42))); |
| static_assert(cpp17::is_same_v<char, fasync::future_error_t<decltype(f)>>, ""); |
| static_assert(cpp17::is_same_v<int, fasync::future_value_t<decltype(f)>>, ""); |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(42, poll.output().value()); |
| } |
| |
| // Argument type: fitx::result<fitx::failed, int> with inferred types |
| { |
| auto f = fasync::make_ok_future(42); |
| static_assert(cpp17::is_same_v<fitx::failed, fasync::future_error_t<decltype(f)>>, ""); |
| static_assert(cpp17::is_same_v<int, fasync::future_value_t<decltype(f)>>, ""); |
| fasync::try_poll<fitx::failed, int> poll = f(fake_context); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(42, poll.output().value()); |
| } |
| |
| // Argument type: fitx::result<char, int> with explicit types |
| { |
| auto f = fasync::make_try_future<char, int>(fitx::ok(42)); |
| static_assert(cpp17::is_same_v<char, fasync::future_error_t<decltype(f)>>, ""); |
| static_assert(cpp17::is_same_v<int, fasync::future_value_t<decltype(f)>>, ""); |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(42, poll.output().value()); |
| } |
| |
| // Argument type: fitx::result<char> with inferred types |
| { |
| auto f = fasync::make_error_future('x'); |
| static_assert(cpp17::is_same_v<char, fasync::future_error_t<decltype(f)>>, ""); |
| fasync::try_poll<char> poll = f(fake_context); |
| EXPECT_TRUE(poll.output().is_error()); |
| EXPECT_EQ('x', poll.output().error_value()); |
| } |
| |
| // Argument type: fitx::result<char, int> with explicit types |
| { |
| auto f = fasync::make_try_future<char, int>(fitx::error('x')); |
| static_assert(cpp17::is_same_v<char, fasync::future_error_t<decltype(f)>>, ""); |
| static_assert(cpp17::is_same_v<int, fasync::future_value_t<decltype(f)>>, ""); |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_TRUE(poll.output().is_error()); |
| EXPECT_EQ('x', poll.output().error_value()); |
| } |
| |
| // Argument type: fitx::pending with inferred types |
| { |
| auto f = fasync::make_pending_future(); |
| fasync::poll<> poll = f(fake_context); |
| EXPECT_TRUE(poll.is_pending()); |
| } |
| |
| // Argument type: fasync::pending with explicit types |
| { |
| auto f = fasync::make_pending_try_future<char, int>(); |
| static_assert(cpp17::is_same_v<char, fasync::future_error_t<decltype(f)>>, ""); |
| static_assert(cpp17::is_same_v<int, fasync::future_value_t<decltype(f)>>, ""); |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_TRUE(poll.is_pending()); |
| } |
| } |
| |
| TEST(FutureTests, make_ok_future) { |
| fake_context fake_context; |
| |
| // Argument type: int |
| { |
| auto f = fasync::make_ok_future(42); |
| static_assert(cpp17::is_same_v<fitx::failed, fasync::future_error_t<decltype(f)>>, ""); |
| static_assert(cpp17::is_same_v<int, fasync::future_value_t<decltype(f)>>, ""); |
| fasync::try_poll<fitx::failed, int> poll = f(fake_context); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(42, poll.output().value()); |
| } |
| |
| // Argument type: none (void) |
| { |
| auto f = fasync::make_ok_future(); |
| static_assert(cpp17::is_same_v<fitx::failed, fasync::future_error_t<decltype(f)>>, ""); |
| fasync::try_poll<fitx::failed> poll = f(fake_context); |
| EXPECT_TRUE(poll.output().is_ok()); |
| } |
| } |
| |
| TEST(FutureTests, make_error_future) { |
| fake_context fake_context; |
| |
| // Argument type: char |
| { |
| auto f = fasync::make_error_future('x'); |
| static_assert(cpp17::is_same_v<char, fasync::future_error_t<decltype(f)>>, ""); |
| fasync::try_poll<char> poll = f(fake_context); |
| EXPECT_TRUE(poll.output().is_error()); |
| EXPECT_EQ('x', poll.output().error_value()); |
| } |
| |
| // Argument type: none (void) |
| { |
| [[maybe_unused]] auto f = fasync::make_failed_future(); |
| static_assert(cpp17::is_same_v<fitx::failed, fasync::future_error_t<decltype(f)>>, ""); |
| fasync::try_poll<fitx::failed> poll = f(fake_context); |
| EXPECT_TRUE(poll.output().is_error()); |
| } |
| } |
| |
| auto make_checked_ok_future(int value) { |
| return fasync::make_future([value, count = 0]() mutable -> fitx::result<char, int> { |
| ASSERT_CRITICAL(count == 0); |
| ++count; |
| return fitx::ok(value); |
| }); |
| } |
| |
| auto make_move_only_future(int value) { |
| return fasync::make_future( |
| [value, count = 0]() mutable -> fitx::result<char, std::unique_ptr<int>> { |
| ASSERT_CRITICAL(count == 0); |
| ++count; |
| return fitx::ok(std::make_unique<int>(value)); |
| }); |
| } |
| |
| auto make_checked_error_future(char error) { |
| return fasync::make_future([error, count = 0]() mutable -> fitx::result<char, int> { |
| ASSERT_CRITICAL(count == 0); |
| ++count; |
| return fitx::error(error); |
| }); |
| } |
| |
| auto make_delayed_ok_future(int value) { |
| return fasync::make_future([value, count = 0]() mutable -> fasync::try_poll<char, int> { |
| ASSERT_CRITICAL(count <= 1); |
| if (++count == 2) |
| return fasync::ready(fitx::ok(value)); |
| return fasync::pending(); |
| }); |
| } |
| |
| auto make_delayed_error_future(char error) { |
| return fasync::make_future([error, count = 0]() mutable -> fasync::try_poll<char, int> { |
| ASSERT_CRITICAL(count <= 1); |
| if (++count == 2) |
| return fasync::ready(fitx::error(error)); |
| return fasync::pending(); |
| }); |
| } |
| |
| // To keep these tests manageable, we only focus on argument type adaptation since return type |
| // adaptation logic is already covered by |make_future()| and by the examples. |
| TEST(FutureTests, then_combinator) { |
| fake_context fake_context; |
| |
| // Chaining on OK. |
| // Handler signature: fitx::result<fitx::failed>(const fitx::result<int, char>&). |
| { |
| uint64_t run_count = 0; |
| auto f = |
| make_delayed_ok_future(42) | |
| fasync::then([&](const fitx::result<char, int>& result) -> fasync::try_poll<fitx::failed> { |
| ASSERT_CRITICAL(result.value() == 42); |
| if (++run_count == 2) |
| return fasync::ready(fitx::ok()); |
| return fasync::pending(); |
| }); |
| |
| fasync::try_poll<fitx::failed> poll = f(fake_context); |
| EXPECT_EQ(0, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_EQ(2, run_count); |
| EXPECT_TRUE(poll.output().is_ok()); |
| } |
| |
| // Chaining on ERROR. |
| // Handler signature: fitx::result<fitx::failed>(const fitx::result<int, char>&). |
| { |
| uint64_t run_count = 0; |
| auto f = |
| make_delayed_error_future('x') | |
| fasync::then([&](const fitx::result<char, int>& result) -> fasync::try_poll<fitx::failed> { |
| ASSERT_CRITICAL(result.error_value() == 'x'); |
| if (++run_count == 2) |
| return fasync::ready(fitx::ok()); |
| return fasync::pending(); |
| }); |
| |
| fasync::try_poll<fitx::failed> poll = f(fake_context); |
| EXPECT_EQ(0, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_EQ(2, run_count); |
| EXPECT_TRUE(poll.output().is_ok()); |
| } |
| |
| // Cover all handler argument signatures, more briefly. |
| { |
| uint64_t run_count = 0; |
| auto f = make_checked_ok_future(42) | |
| fasync::then([&](fitx::result<char, int>& result) -> fitx::result<char, int> { |
| run_count++; |
| return fitx::ok(result.value() + 1); |
| }) | |
| fasync::then([&](const fitx::result<char, int>& result) -> fitx::result<char, int> { |
| run_count++; |
| return fitx::ok(result.value() + 1); |
| }) | |
| fasync::then([&](fasync::context& context, |
| fitx::result<char, int>& result) -> fitx::result<char, int> { |
| ASSERT_CRITICAL(&context == &fake_context); |
| run_count++; |
| return fitx::ok(result.value() + 1); |
| }) | |
| fasync::then([&](fasync::context& context, |
| const fitx::result<char, int>& result) -> fitx::result<char, int> { |
| ASSERT_CRITICAL(&context == &fake_context); |
| run_count++; |
| return fitx::ok(result.value() + 1); |
| }); |
| |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_EQ(4, run_count); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(46, poll.output().value()); |
| } |
| } |
| |
| TEST(FutureTests, and_then_combinator) { |
| fake_context fake_context; |
| |
| // Chaining on OK. |
| // Handler signature: fitx::result<fitx::failed>(const int&). |
| { |
| uint64_t run_count = 0; |
| auto f = make_delayed_ok_future(42) | |
| fasync::and_then([&](const int& value) -> fasync::try_poll<char> { |
| ASSERT_CRITICAL(value == 42); |
| if (++run_count == 2) |
| return fasync::ready(fitx::error('y')); |
| return fasync::pending(); |
| }); |
| |
| fasync::try_poll<char> poll = f(fake_context); |
| EXPECT_EQ(0, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_EQ(2, run_count); |
| EXPECT_TRUE(poll.output().is_error()); |
| EXPECT_EQ('y', poll.output().error_value()); |
| } |
| |
| // Chaining on ERROR. |
| // Handler signature: fitx::result<fitx::failed>(const int&). |
| { |
| uint64_t run_count = 0; |
| auto f = make_delayed_error_future('x') | |
| fasync::and_then([&](const int& value) -> fasync::try_poll<char> { |
| run_count++; |
| return fasync::pending(); |
| }); |
| |
| fasync::try_poll<char> poll = f(fake_context); |
| EXPECT_EQ(0, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_EQ(0, run_count); |
| EXPECT_TRUE(poll.output().is_error()); |
| EXPECT_EQ('x', poll.output().error_value()); |
| } |
| |
| // Cover all handler argument signatures, more briefly. |
| { |
| uint64_t run_count = 0; |
| auto f = make_checked_ok_future(42) | |
| fasync::and_then([&](int& value) -> fitx::result<char, int> { |
| run_count++; |
| return fitx::ok(value + 1); |
| }) | |
| fasync::and_then([&](const int& value) -> fitx::result<char, int> { |
| run_count++; |
| return fitx::ok(value + 1); |
| }) | |
| fasync::and_then([&](fasync::context& context, int& value) -> fitx::result<char, int> { |
| ASSERT_CRITICAL(&context == &fake_context); |
| run_count++; |
| return fitx::ok(value + 1); |
| }) | |
| fasync::and_then( |
| [&](fasync::context& context, const int& value) -> fitx::result<char, int> { |
| ASSERT_CRITICAL(&context == &fake_context); |
| run_count++; |
| return fitx::ok(value + 1); |
| }); |
| |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_EQ(4, run_count); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(46, poll.output().value()); |
| } |
| } |
| |
| TEST(FutureTests, or_else_combinator) { |
| fake_context fake_context; |
| |
| // Chaining on OK. |
| // Handler signature: fitx::result<fitx::failed>(const char&). |
| { |
| uint64_t run_count = 0; |
| auto f = make_delayed_ok_future(42) | |
| fasync::or_else([&](const char& error) -> fasync::try_poll<fitx::failed, int> { |
| run_count++; |
| return fasync::pending(); |
| }); |
| |
| fasync::try_poll<fitx::failed, int> poll = f(fake_context); |
| EXPECT_EQ(0, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_EQ(0, run_count); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(42, poll.output().value()); |
| } |
| |
| // Chaining on ERROR. |
| // Handler signature: fitx::result<fitx::failed>(const char&). |
| { |
| uint64_t run_count = 0; |
| auto f = make_delayed_error_future('x') | |
| fasync::or_else([&](const char& error) -> fasync::try_poll<fitx::failed, int> { |
| ASSERT_CRITICAL(error == 'x'); |
| if (++run_count == 2) |
| return fasync::ready(fitx::ok(43)); |
| return fasync::pending(); |
| }); |
| |
| fasync::try_poll<fitx::failed, int> poll = f(fake_context); |
| EXPECT_EQ(0, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_EQ(2, run_count); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(43, poll.output().value()); |
| } |
| |
| // Cover all handler argument signatures, more briefly. |
| { |
| uint64_t run_count = 0; |
| auto f = make_checked_error_future('a') | |
| fasync::or_else([&](char& error) -> fitx::result<char, int> { |
| run_count++; |
| return fitx::error(static_cast<char>(error + 1)); |
| }) | |
| fasync::or_else([&](const char& error) -> fitx::result<char, int> { |
| run_count++; |
| return fitx::error(static_cast<char>(error + 1)); |
| }) | |
| fasync::or_else([&](fasync::context& context, char& error) -> fitx::result<char, int> { |
| ASSERT_CRITICAL(&context == &fake_context); |
| run_count++; |
| return fitx::error(static_cast<char>(error + 1)); |
| }) | |
| fasync::or_else( |
| [&](fasync::context& context, const char& error) -> fitx::result<char, int> { |
| ASSERT_CRITICAL(&context == &fake_context); |
| run_count++; |
| return fitx::error(static_cast<char>(error + 1)); |
| }); |
| |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_EQ(4, run_count); |
| EXPECT_TRUE(poll.output().is_error()); |
| EXPECT_EQ('e', poll.output().error_value()); |
| } |
| } |
| |
| TEST(FutureTests, inspect_combinator) { |
| fake_context fake_context; |
| |
| // Chaining on OK. |
| // Handler signature: void(const fitx::result<int, char>&). |
| { |
| uint64_t run_count = 0; |
| auto f = |
| make_delayed_ok_future(42) | fasync::inspect([&](const fitx::result<char, int>& result) { |
| ASSERT_CRITICAL(result.value() == 42); |
| run_count++; |
| }); |
| |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_EQ(0, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(42, poll.output().value()); |
| } |
| |
| // Chaining on ERROR. |
| // Handler signature: void(const fitx::result<int, char>&). |
| { |
| uint64_t run_count = 0; |
| auto f = make_delayed_error_future('x') | |
| fasync::inspect([&](const fitx::result<char, int>& result) { |
| ASSERT_CRITICAL(result.error_value() == 'x'); |
| run_count++; |
| }); |
| |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_EQ(0, run_count); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_EQ(1, run_count); |
| EXPECT_TRUE(poll.output().is_error()); |
| EXPECT_EQ('x', poll.output().error_value()); |
| } |
| |
| // Cover all handler argument signatures, more briefly. |
| { |
| uint64_t run_count = 0; |
| auto f = make_checked_ok_future(42) | |
| fasync::inspect([&](const fitx::result<char, int>& result) { |
| ASSERT_CRITICAL(result.value() == 42); |
| run_count++; |
| }) | |
| fasync::inspect([&](const fitx::result<char, int>& result) { run_count++; }) | |
| fasync::inspect([&](fasync::context& context, const fitx::result<char, int>& result) { |
| ASSERT_CRITICAL(&context == &fake_context); |
| run_count++; |
| }) | |
| fasync::inspect([&](fasync::context& context, const fitx::result<char, int>& result) { |
| ASSERT_CRITICAL(&context == &fake_context); |
| run_count++; |
| }); |
| |
| fasync::try_poll<char, int> poll = f(fake_context); |
| EXPECT_EQ(4, run_count); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(42, poll.output().value()); |
| } |
| } |
| |
| TEST(FutureTests, discard_result_combinator) { |
| fake_context fake_context; |
| |
| // Chaining on OK. |
| { |
| auto f = make_delayed_ok_future(42) | fasync::discard; |
| static_assert(cpp17::is_same_v<void, fasync::future_output_t<decltype(f)>>, ""); |
| |
| fasync::poll<> poll = f(fake_context); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_TRUE(poll.is_ready()); |
| } |
| |
| // Chaining on ERROR. |
| { |
| auto f = make_delayed_error_future('x') | fasync::discard; |
| static_assert(cpp17::is_same_v<void, fasync::future_output_t<decltype(f)>>, ""); |
| |
| fasync::poll<> poll = f(fake_context); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| poll = f(fake_context); |
| EXPECT_TRUE(poll.is_ready()); |
| } |
| } |
| |
| TEST(FutureTests, wrap_with_combinator) { |
| fake_context fake_context; |
| capture_result_wrapper<char, int> wrapper; |
| uint64_t successor_run_count = 0; |
| |
| // Apply a wrapper which steals a future's result th |
| auto f = make_delayed_ok_future(42) | fasync::wrap_with(wrapper) | |
| fasync::then([&] { successor_run_count++; }); |
| |
| fasync::poll<> poll = f(fake_context); |
| EXPECT_TRUE(poll.is_pending()); |
| EXPECT_TRUE(wrapper.last_result.is_pending()); |
| EXPECT_EQ(0, successor_run_count); |
| |
| poll = f(fake_context); |
| EXPECT_TRUE(poll.is_ready()); |
| EXPECT_TRUE(wrapper.last_result.output().is_ok()); |
| EXPECT_EQ(42, wrapper.last_result.output().value()); |
| EXPECT_EQ(1, successor_run_count); |
| } |
| |
| TEST(FutureTests, box_combinator) { |
| fake_context fake_context; |
| |
| auto f = fasync::make_future([&]() -> fitx::result<char, int> { return fitx::ok(42); }); |
| static_assert(!cpp17::is_same_v<fasync::try_future<char, int>, decltype(f)>, ""); |
| |
| auto q = std::move(f) | fasync::box; |
| static_assert(cpp17::is_same_v<fasync::try_future<char, int>, decltype(q)>, ""); |
| |
| fasync::try_poll<char, int> poll = q(fake_context); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(42, poll.output().value()); |
| } |
| |
| TEST(FutureTests, join_combinator) { |
| fake_context fake_context; |
| |
| auto f = fasync::join(make_checked_ok_future(42), |
| make_checked_error_future('x') | |
| fasync::or_else([](const char& error) { return fitx::error('y'); }), |
| make_delayed_ok_future(55)); |
| |
| using output = fasync::future_output_t<decltype(f)>; |
| static_assert(cpp17::is_same_v<std::tuple_element_t<0, output>, fitx::result<char, int>>, ""); |
| static_assert(cpp17::is_same_v<std::tuple_element_t<1, output>, fitx::result<char, int>>, ""); |
| static_assert(cpp17::is_same_v<std::tuple_element_t<2, output>, fitx::result<char, int>>, ""); |
| |
| fasync::poll< |
| std::tuple<fitx::result<char, int>, fitx::result<char, int>, fitx::result<char, int>>> |
| poll = f(fake_context); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| fasync::poll poll2 = f(fake_context); |
| EXPECT_TRUE(poll2.is_ready()); |
| EXPECT_EQ(42, std::get<0>(poll2.output()).value()); |
| EXPECT_EQ('y', std::get<1>(poll2.output()).error_value()); |
| EXPECT_EQ(55, std::get<2>(poll2.output()).value()); |
| } |
| |
| TEST(FutureTests, join_combinator_move_only_result) { |
| fake_context fake_context; |
| |
| // Add 1 + 2 to get 3, using a join combinator with a "then" continuation to demonstrate how to |
| // optionally return an error. |
| auto f = fasync::join(make_move_only_future(1), make_move_only_future(2)) | |
| fasync::then([](std::tuple<fitx::result<char, std::unique_ptr<int>>, |
| fitx::result<char, std::unique_ptr<int>>>& results) |
| -> fitx::result<char, std::unique_ptr<int>> { |
| if (std::get<0>(results).is_error() || std::get<1>(results).is_error()) { |
| return fitx::error('e'); |
| } else { |
| int value = *std::get<0>(results).value() + *std::get<1>(results).value(); |
| return fitx::ok(std::make_unique<int>(value)); |
| } |
| }); |
| |
| fasync::try_poll<char, std::unique_ptr<int>> poll = f(fake_context); |
| EXPECT_TRUE(poll.output().is_ok()); |
| EXPECT_EQ(3, *poll.output().value()); |
| } |
| |
| TEST(FutureTests, join_vector_combinator) { |
| fake_context fake_context; |
| |
| std::vector<fasync::try_future<char, int>> futures; |
| futures.push_back(make_checked_ok_future(42)); |
| futures.push_back(make_checked_error_future('x') | |
| fasync::or_else([](const char& error) { return fitx::error('y'); })); |
| futures.push_back(make_delayed_ok_future(55)); |
| futures.push_back(fasync::try_future<char, int>(make_checked_ok_future(42))); |
| futures.push_back(fasync::try_future<char, int>(make_checked_error_future('x')) | |
| fasync::or_else([](const char& error) { return fitx::error('y'); })); |
| futures.push_back(fasync::try_future<char, int>(make_checked_error_future('y'))); |
| futures.push_back(fasync::try_future<char, int>(make_delayed_ok_future(55))); |
| auto f = fasync::join(std::move(futures)); |
| |
| fasync::poll<std::vector<fitx::result<char, int>>> poll = f(fake_context); |
| EXPECT_TRUE(poll.is_pending()); |
| |
| auto poll2 = f(fake_context); |
| EXPECT_TRUE(poll2.is_ready()); |
| EXPECT_EQ(42, poll2.output()[0].value()); |
| EXPECT_EQ('y', poll2.output()[1].error_value()); |
| EXPECT_EQ(55, poll2.output()[2].value()); |
| } |
| |
| // Test predicate which is used internally to improve the quality of compilation errors when an |
| // invalid continuation type is encountered. |
| namespace is_future_test { |
| |
| static_assert(fasync::is_future_v<::fit::function<fasync::poll<>(fasync::context&)>>, ""); |
| static_assert(!fasync::is_future_v<::fit::function<void(fasync::context&)>>, ""); |
| static_assert(!fasync::is_future_v<::fit::function<fasync::poll<>()>>, ""); |
| static_assert(!fasync::is_future_v<void>, ""); |
| |
| [[maybe_unused]] auto continuation_lambda = [](fasync::context&) -> fasync::poll<> { |
| return fasync::pending(); |
| }; |
| [[maybe_unused]] auto invalid_lambda = [] {}; |
| |
| static_assert(fasync::is_future_v<decltype(continuation_lambda)>, ""); |
| static_assert(!fasync::is_future_v<decltype(invalid_lambda)>, ""); |
| |
| } // namespace is_future_test |
| |
| } // namespace |