// Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "google/cloud/future.h" #include "google/cloud/internal/throw_delegate.h" #include "google/cloud/testing_util/chrono_literals.h" #include "google/cloud/testing_util/expect_future_error.h" #include #include namespace google { namespace cloud { inline namespace GOOGLE_CLOUD_CPP_NS { namespace { using ::testing::HasSubstr; using testing_util::chrono_literals::operator"" _ms; using testing_util::ExpectFutureError; TEST(FutureTestVoid, ThenSimple) { promise p; future fut = p.get_future(); EXPECT_TRUE(fut.valid()); bool called = false; future next = fut.then([&called](future) { called = true; }); EXPECT_FALSE(fut.valid()); EXPECT_TRUE(next.valid()); EXPECT_FALSE(called); p.set_value(); EXPECT_TRUE(called); EXPECT_TRUE(next.valid()); ASSERT_EQ(std::future_status::ready, next.wait_for(0_ms)); next.get(); SUCCEED(); EXPECT_FALSE(next.valid()); } TEST(FutureTestVoid, ThenException) { promise p; future fut = p.get_future(); EXPECT_TRUE(fut.valid()); bool called = false; future next = fut.then([&called](future) { called = true; internal::ThrowRuntimeError("test message"); }); EXPECT_FALSE(fut.valid()); EXPECT_TRUE(next.valid()); EXPECT_FALSE(called); #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS p.set_value(); EXPECT_TRUE(called); EXPECT_TRUE(next.valid()); ASSERT_EQ(std::future_status::ready, next.wait_for(0_ms)); EXPECT_THROW( try { next.get(); } catch (std::runtime_error const& ex) { EXPECT_THAT(ex.what(), HasSubstr("test message")); throw; }, std::runtime_error); EXPECT_FALSE(next.valid()); #else EXPECT_DEATH_IF_SUPPORTED(p.set_value(), "test message"); #endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS } TEST(FutureTestVoid, ThenUnwrap) { promise p; future fut = p.get_future(); EXPECT_TRUE(fut.valid()); promise pp; bool called = false; auto cont = [&pp, &called](future) { called = true; return pp.get_future(); }; future next = fut.then(std::move(cont)); EXPECT_FALSE(fut.valid()); EXPECT_TRUE(next.valid()); EXPECT_FALSE(next.is_ready()); p.set_value(); EXPECT_TRUE(called); EXPECT_FALSE(next.is_ready()); pp.set_value("value=42"); EXPECT_TRUE(next.is_ready()); EXPECT_EQ("value=42", next.get()); EXPECT_FALSE(next.valid()); } TEST(FutureTestVoid, ThenMoveOnlyCallable) { class MoveOnlyCallable { public: explicit MoveOnlyCallable(bool& called) : called_(&called) {} MoveOnlyCallable(MoveOnlyCallable&&) = default; MoveOnlyCallable& operator=(MoveOnlyCallable&&) = default; MoveOnlyCallable(MoveOnlyCallable const&) = delete; MoveOnlyCallable& operator=(MoveOnlyCallable const&) = delete; void operator()(future) { *called_ = true; } private: bool* called_; }; promise p; future fut = p.get_future(); EXPECT_TRUE(fut.valid()); bool called = false; MoveOnlyCallable cb{called}; future next = fut.then(std::move(cb)); EXPECT_FALSE(fut.valid()); EXPECT_TRUE(next.valid()); EXPECT_FALSE(called); p.set_value(); EXPECT_TRUE(called); EXPECT_TRUE(next.valid()); ASSERT_EQ(std::future_status::ready, next.wait_for(0_ms)); next.get(); EXPECT_FALSE(next.valid()); } TEST(FutureTestVoid, ThenByCopy) { promise p; future fut = p.get_future(); EXPECT_TRUE(fut.valid()); bool called = false; auto callable = [&called](future) { called = true; }; future next = fut.then(callable); EXPECT_FALSE(fut.valid()); EXPECT_TRUE(next.valid()); EXPECT_FALSE(called); p.set_value(); EXPECT_TRUE(called); EXPECT_TRUE(next.valid()); ASSERT_EQ(std::future_status::ready, next.wait_for(0_ms)); next.get(); EXPECT_FALSE(next.valid()); } // The following tests reference the technical specification: // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0159r0.html // The test names match the section and paragraph from the TS. /// @test Verify the behavior around cancellation. TEST(FutureTestVoid, CancelThroughContinuation) { bool cancelled = false; promise p0([&cancelled] { cancelled = true; }); auto f0 = p0.get_future(); auto f1 = f0.then([](future) { return 7; }); EXPECT_TRUE(f1.cancel()); EXPECT_TRUE(cancelled); p0.set_value(); EXPECT_EQ(7, f1.get()); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_2_a) { // future should have an unwrapping constructor. promise> p; future> f = p.get_future(); future unwrapped(std::move(f)); EXPECT_FALSE(noexcept(future(p.get_future()))); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_3_a) { // A future created via the unwrapping constructor becomes satisfied // when both become satisfied. promise> p; future unwrapped(p.get_future()); EXPECT_TRUE(unwrapped.valid()); EXPECT_FALSE(unwrapped.is_ready()); promise p2; p.set_value(p2.get_future()); EXPECT_FALSE(unwrapped.is_ready()); p2.set_value(); EXPECT_TRUE(unwrapped.is_ready()); unwrapped.get(); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_3_b) { // A future created via the unwrapping constructor becomes satisfied // when the wrapped future is satisfied by an exception. promise> p; future unwrapped(p.get_future()); EXPECT_TRUE(unwrapped.valid()); EXPECT_FALSE(unwrapped.is_ready()); #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS p.set_exception(std::make_exception_ptr(std::runtime_error("test message"))); EXPECT_TRUE(unwrapped.is_ready()); EXPECT_THROW( try { unwrapped.get(); } catch (std::runtime_error const& ex) { EXPECT_THAT(ex.what(), HasSubstr("test message")); throw; }, std::runtime_error); #else EXPECT_DEATH_IF_SUPPORTED( p.set_exception( std::make_exception_ptr(std::runtime_error("test message"))), "future::get\\(\\) had an exception but exceptions are disabled"); #endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_3_c) { // A future created via the unwrapping constructor becomes satisfied // when the inner future is satisfied by an exception. promise> p; future unwrapped(p.get_future()); EXPECT_TRUE(unwrapped.valid()); EXPECT_FALSE(unwrapped.is_ready()); promise p2; p.set_value(p2.get_future()); EXPECT_FALSE(unwrapped.is_ready()); #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS p2.set_exception(std::make_exception_ptr(std::runtime_error("test message"))); EXPECT_TRUE(unwrapped.is_ready()); EXPECT_THROW( try { unwrapped.get(); } catch (std::runtime_error const& ex) { EXPECT_THAT(ex.what(), HasSubstr("test message")); throw; }, std::runtime_error); #else std::string expected = "future_error\\["; expected += std::make_error_code(std::future_errc::promise_already_satisfied) .message(); expected += "\\]"; EXPECT_DEATH_IF_SUPPORTED(p.set_exception(std::make_exception_ptr( std::runtime_error("test message"))), expected); #endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_3_d) { // A future created via the unwrapping constructor becomes satisfied // when the inner future is invalid. promise> p; future unwrapped(p.get_future()); EXPECT_TRUE(unwrapped.valid()); EXPECT_FALSE(unwrapped.is_ready()); promise p2; p.set_value(future{}); EXPECT_TRUE(unwrapped.is_ready()); #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS EXPECT_THROW( try { unwrapped.get(); } catch (std::future_error const& ex) { EXPECT_EQ(std::future_errc::broken_promise, ex.code()); throw; }, std::future_error); #else EXPECT_DEATH_IF_SUPPORTED( unwrapped.get(), "future::get\\(\\) had an exception but exceptions are disabled"); #endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_4) { // future should leaves the source invalid. promise> p; future> f = p.get_future(); future unwrapped(std::move(f)); EXPECT_TRUE(unwrapped.valid()); EXPECT_FALSE(f.valid()); // NOLINT(bugprone-use-after-move) } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_5) { // future::then() is a template member function that takes callables. future f; auto callable = [](future f) -> void { f.get(); }; EXPECT_TRUE((std::is_same, decltype(f.then(callable))>::value)); auto c2 = [](future f) -> int { f.get(); return 42; }; EXPECT_TRUE((std::is_same, decltype(f.then(c2))>::value)); auto c3 = [](future f) -> std::string { f.get(); return ""; }; EXPECT_TRUE((std::is_same, decltype(f.then(c3))>::value)); } // Use SFINAE to test if future::then() accepts a T parameter. template auto test_then(int) -> decltype(std::declval>().then(T()), std::true_type{}) { return std::true_type{}; } template auto test_then(long) -> std::false_type { // NOLINT(google-runtime-int) return std::false_type{}; } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_7) { // future::then() requires callables that take future as a // parameter. future f; using valid_callable_type = std::function)>; using invalid_callable_type = std::function; EXPECT_TRUE(decltype(test_then(0))::value); EXPECT_FALSE(decltype(test_then(0))::value); EXPECT_TRUE(decltype(test_then)>>(0))::value); EXPECT_FALSE(decltype(test_then>(0))::value); EXPECT_TRUE( decltype(test_then)>>(0))::value); EXPECT_FALSE(decltype(test_then>(0))::value); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_8_a) { // future::then() creates a future with a valid shared state. promise p; future f = p.get_future(); future next = f.then([&](future) {}); EXPECT_TRUE(next.valid()); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_8_b) { // future::then() calls the functor when the future becomes ready. promise p; future f = p.get_future(); bool called = false; future next = f.then([&](future) { called = true; }); EXPECT_TRUE(next.valid()); EXPECT_FALSE(called); p.set_value(); EXPECT_TRUE(called); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_8_c) { // future::then() calls the functor if the future was ready. promise p; future f = p.get_future(); p.set_value(); bool called = false; future next = f.then([&](future) { called = true; }); EXPECT_TRUE(next.valid()); EXPECT_TRUE(called); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_8_d) { // future::then() propagates the value from the functor to the returned // future. promise p; future f = p.get_future(); future next = f.then([&](future) -> int { return 42; }); EXPECT_TRUE(next.valid()); p.set_value(); ASSERT_EQ(std::future_status::ready, next.wait_for(0_ms)); EXPECT_EQ(42, next.get()); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_8_e) { // future::then() propagates exceptions raised by the functor to the // returned future. promise p; future f = p.get_future(); future next = f.then([&](future) { internal::ThrowRuntimeError("test exception in functor"); }); EXPECT_TRUE(next.valid()); #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS p.set_value(); ASSERT_EQ(std::future_status::ready, next.wait_for(0_ms)); EXPECT_THROW( try { next.get(); } catch (std::runtime_error const& ex) { EXPECT_THAT(ex.what(), HasSubstr("test exception in functor")); throw; }, std::runtime_error); EXPECT_FALSE(next.valid()); #else EXPECT_DEATH_IF_SUPPORTED(p.set_value(), "test exception in functor"); #endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_9_a) { // future::then() returns a functor containing the type of the value // returned by the functor. promise p; future f = p.get_future(); auto returns_void = [](future) -> void {}; EXPECT_TRUE( (std::is_same, decltype(f.then(returns_void))>::value)); auto returns_int = [](future) -> int { return 42; }; EXPECT_TRUE( (std::is_same, decltype(f.then(returns_int))>::value)); auto returns_string = [](future) -> std::string { return "42"; }; EXPECT_TRUE((std::is_same, decltype(f.then(returns_string))>::value)); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_9_b) { // future::then() implicitly unwraps the future type when a functor // returns a future<>. promise p; future f = p.get_future(); auto returns_void = [](future) -> future { return promise().get_future(); }; EXPECT_TRUE( (std::is_same, decltype(f.then(returns_void))>::value)); auto returns_int = [](future) -> future { return promise().get_future(); }; EXPECT_TRUE( (std::is_same, decltype(f.then(returns_int))>::value)); // The spec says the returned type must be future *exactly*, references do // not count. promise p_int; future f_int = p_int.get_future(); auto returns_int_ref = [&f_int](future) -> future& { return f_int; }; EXPECT_FALSE( (std::is_same, decltype(f.then(returns_int_ref))>::value)); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_9_c) { // future::then() implicitly unwrapping captures the returned value. promise p; future f = p.get_future(); promise p2; bool called = false; future r = f.then([&p2, &called](future f) { called = true; f.get(); return p2.get_future(); }); EXPECT_TRUE(r.valid()); EXPECT_FALSE(r.is_ready()); EXPECT_FALSE(f.valid()); p.set_value(); EXPECT_TRUE(called); EXPECT_FALSE(r.is_ready()); p2.set_value(42); EXPECT_TRUE(r.is_ready()); EXPECT_EQ(42, r.get()); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_9_d) { // future::then() implicitly unwrapping captures exceptions. promise p; future f = p.get_future(); promise p2; bool called = false; future r = f.then([&p2, &called](future f) { called = true; f.get(); return p2.get_future(); }); EXPECT_TRUE(r.valid()); EXPECT_FALSE(r.is_ready()); EXPECT_FALSE(f.valid()); #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS p.set_exception(std::make_exception_ptr(std::runtime_error("test message"))); EXPECT_TRUE(called); EXPECT_TRUE(r.is_ready()); EXPECT_THROW( try { r.get(); } catch (std::runtime_error const& ex) { EXPECT_THAT(ex.what(), HasSubstr("test message")); throw; }, std::runtime_error); #else // With exceptions disabled the program terminates as soon as the exception is // set. EXPECT_DEATH_IF_SUPPORTED( p.set_exception( std::make_exception_ptr(std::runtime_error("test message"))), "future::get\\(\\) had an exception but exceptions are disabled"); #endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_9_e) { // future::then() implicitly unwrapping raises on invalid future // returned by continuation. promise p; future f = p.get_future(); bool called = false; future r = f.then([&called](future f) { called = true; f.get(); return future{}; }); EXPECT_TRUE(r.valid()); EXPECT_FALSE(r.is_ready()); EXPECT_FALSE(f.valid()); p.set_value(); EXPECT_TRUE(called); EXPECT_TRUE(r.is_ready()); #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS EXPECT_THROW( try { r.get(); } catch (std::future_error const& ex) { EXPECT_EQ(std::future_errc::broken_promise, ex.code()); throw; }, std::future_error); #else // With exceptions disabled setting the value immediately terminates. EXPECT_DEATH_IF_SUPPORTED( r.get(), "future::get\\(\\) had an exception but exceptions are disabled"); #endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_10) { // future::then() invalidates the source future. promise p; future f = p.get_future(); future r = f.then([](future f) { f.get(); }); EXPECT_TRUE(r.valid()); EXPECT_FALSE(r.is_ready()); EXPECT_FALSE(f.valid()); p.set_value(); EXPECT_TRUE(r.is_ready()); r.get(); SUCCEED(); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_11_a) { // future::is_ready() returns false for futures that are not ready. promise p; future const f = p.get_future(); EXPECT_FALSE(f.is_ready()); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_11_b) { // future::is_ready() returns true for futures that are ready. promise p; future const f = p.get_future(); p.set_value(); EXPECT_TRUE(f.is_ready()); } /// @test Verify conformance with section 2.3 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_3_11_c) { // future::is_ready() raises for futures that are not valid. future const f; ExpectFutureError([&] { f.is_ready(); }, std::future_errc::no_state); } /// @test Verify conformance with section 2.10 of the Concurrency TS. // NOLINTNEXTLINE(google-readability-avoid-underscore-in-googletest-name) TEST(FutureTestVoid, conform_2_10_4) { future f = make_ready_future(); EXPECT_TRUE(f.valid()); ASSERT_EQ(std::future_status::ready, f.wait_for(0_ms)); f.get(); SUCCEED(); } class MockFunctor { public: MockFunctor() = default; MockFunctor(MockFunctor&& other) noexcept : moved_from_(other.moved_from_) { other.moved_from_ = true; } MockFunctor(MockFunctor const& other) = default; void operator()(future) {} bool moved_from_{false}; }; TEST(FutureTestVoid, RValueThenFunctorIsMoved) { promise promise; future fut = promise.get_future(); MockFunctor fun; fut.then(std::move(fun)); promise.set_value(); EXPECT_TRUE(fun.moved_from_); // NOLINT(bugprone-use-after-move) } TEST(FutureTestVoid, LValueThenFunctorIsCopied) { promise promise; future fut = promise.get_future(); MockFunctor fun; fut.then(fun); promise.set_value(); EXPECT_FALSE(fun.moved_from_); } class MockUnwrapFunctor { public: MockUnwrapFunctor() = default; MockUnwrapFunctor(MockUnwrapFunctor&& other) noexcept : moved_from_(other.moved_from_) { other.moved_from_ = true; } MockUnwrapFunctor(MockUnwrapFunctor const& other) = default; future operator()(future) { return make_ready_future(); } bool moved_from_{false}; }; TEST(FutureTestVoid, RValueThenUnwrapFunctorIsMoved) { promise promise; future fut = promise.get_future(); MockUnwrapFunctor fun; fut.then(std::move(fun)); promise.set_value(); EXPECT_TRUE(fun.moved_from_); // NOLINT(bugprone-use-after-move) } TEST(FutureTestVoid, LValueThenUnwrapFunctorIsCopied) { promise promise; future fut = promise.get_future(); MockUnwrapFunctor fun; fut.then(fun); promise.set_value(); EXPECT_FALSE(fun.moved_from_); } } // namespace } // namespace GOOGLE_CLOUD_CPP_NS } // namespace cloud } // namespace google