/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include using namespace folly; namespace { struct Exn {}; // not default-constructible, thereby preventing Expected from being // default-constructible, forcing our implementation to handle such cases class Err { private: enum class Type { Bad, Badder, Baddest }; Type type_; constexpr Err(Type type) : type_(type) {} public: Err(Err const&) = default; Err(Err&&) = default; Err& operator=(Err const&) = default; Err& operator=(Err&&) = default; friend bool operator==(Err a, Err b) { return a.type_ == b.type_; } friend bool operator!=(Err a, Err b) { return a.type_ != b.type_; } static constexpr Err bad() { return Type::Bad; } static constexpr Err badder() { return Type::Badder; } static constexpr Err baddest() { return Type::Baddest; } }; Expected f1() { return 7; } Expected f2(int x) { return 2.0 * x; } // move-only type Expected, Err> f3(int x, double y) { return std::make_unique(int(x + y)); } // error result Expected f4(int, double, Err err) { return makeUnexpected(err); } // exception Expected throws() { throw Exn{}; } } // namespace #if FOLLY_HAS_COROUTINES TEST(Expected, CoroutineSuccess) { auto r0 = []() -> Expected { auto x = co_await f1(); EXPECT_EQ(7, x); auto y = co_await f2(x); EXPECT_EQ(2.0 * 7, y); auto z = co_await f3(x, y); EXPECT_EQ(int(2.0 * 7 + 7), *z); co_return *z; }(); EXPECT_TRUE(r0.hasValue()); EXPECT_EQ(21, *r0); } TEST(Expected, CoroutineFailure) { auto r1 = []() -> Expected { auto x = co_await f1(); auto y = co_await f2(x); auto z = co_await f4(x, y, Err::badder()); ADD_FAILURE(); co_return z; }(); EXPECT_TRUE(r1.hasError()); EXPECT_NE(Err::bad(), r1.error()); EXPECT_EQ(Err::badder(), r1.error()); EXPECT_NE(Err::baddest(), r1.error()); } TEST(Expected, CoroutineAwaitUnexpected) { auto r1 = []() -> Expected { co_await makeUnexpected(Err::badder()); throw std::logic_error("should have been unreachable"); }(); EXPECT_TRUE(r1.hasError()); EXPECT_EQ(Err::badder(), r1.error()); } TEST(Expected, CoroutineReturnUnexpected) { auto r1 = []() -> Expected { co_return makeUnexpected(Err::badder()); }(); EXPECT_TRUE(r1.hasError()); EXPECT_EQ(Err::badder(), r1.error()); } TEST(Expected, CoroutineException) { EXPECT_THROW( ([]() -> Expected { auto x = co_await throws(); ADD_FAILURE(); co_return x; }()), Exn); } // this test makes sure that the coroutine is destroyed properly TEST(Expected, CoroutineCleanedUp) { int count_dest = 0; auto r = [&]() -> Expected { SCOPE_EXIT { ++count_dest; }; auto x = co_await Expected(makeUnexpected(Err::badder())); ADD_FAILURE() << "Should not be resuming"; co_return x; }(); EXPECT_FALSE(r.hasValue()); EXPECT_EQ(1, count_dest); } #endif