// Copyright 2023 The Pigweed Authors // // 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 // // https://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 "pw_result/expected.h" #include #include "pw_unit_test/framework.h" namespace pw { namespace { struct Defaults { Defaults() = default; Defaults(const Defaults&) = default; Defaults(Defaults&&) = default; Defaults& operator=(const Defaults&) = default; Defaults& operator=(Defaults&&) = default; }; struct NoDefaultConstructor { NoDefaultConstructor() = delete; NoDefaultConstructor(std::nullptr_t) {} }; struct NoCopy { NoCopy(const NoCopy&) = delete; NoCopy(NoCopy&&) = default; NoCopy& operator=(const NoCopy&) = delete; NoCopy& operator=(NoCopy&&) = default; }; struct NoCopyNoMove { NoCopyNoMove(const NoCopyNoMove&) = delete; NoCopyNoMove(NoCopyNoMove&&) = delete; NoCopyNoMove& operator=(const NoCopyNoMove&) = delete; NoCopyNoMove& operator=(NoCopyNoMove&&) = delete; }; struct NonTrivialDestructor { ~NonTrivialDestructor() {} }; namespace test_constexpr { // Expected and unexpected are constexpr types. constexpr expected kExpectedConstexpr1; constexpr expected kExpectedConstexpr2{5}; constexpr expected kExpectedConstexpr3 = unexpected(42); constexpr unexpected kExpectedConstexprUnexpected{50}; static_assert(kExpectedConstexpr1.has_value()); static_assert(kExpectedConstexpr1.value() == 0); static_assert(kExpectedConstexpr2.has_value()); static_assert(kExpectedConstexpr2.value() == 5); static_assert(!kExpectedConstexpr3.has_value()); static_assert(kExpectedConstexpr3.error() == 42); static_assert(kExpectedConstexprUnexpected.error() == 50); } // namespace test_constexpr namespace test_default_construction { // Default constructible if and only if T is default constructible. static_assert( std::is_default_constructible>::value); static_assert(!std::is_default_constructible< expected>::value); static_assert(std::is_default_constructible< expected>::value); static_assert(!std::is_default_constructible< expected>::value); // Never default constructible. static_assert(!std::is_default_constructible>::value); static_assert( !std::is_default_constructible>::value); } // namespace test_default_construction namespace test_copy_construction { // Copy constructible if and only if both types are copy constructible. static_assert(std::is_copy_constructible>::value); static_assert(!std::is_copy_constructible>::value); static_assert(!std::is_copy_constructible>::value); static_assert(!std::is_copy_constructible>::value); // Copy constructible if and only if E is copy constructible. static_assert(std::is_copy_constructible>::value); static_assert(!std::is_copy_constructible>::value); } // namespace test_copy_construction namespace test_copy_assignment { // Copy assignable if and only if both types are copy assignable. static_assert(std::is_copy_assignable>::value); static_assert(!std::is_copy_assignable>::value); static_assert(!std::is_copy_assignable>::value); static_assert(!std::is_copy_assignable>::value); // Copy assignable if and only if E is copy assignable. static_assert(std::is_copy_assignable>::value); static_assert(!std::is_copy_assignable>::value); } // namespace test_copy_assignment namespace test_move_construction { // Move constructible if and only if both types are move constructible. static_assert(std::is_move_constructible>::value); static_assert( !std::is_move_constructible>::value); static_assert( !std::is_move_constructible>::value); static_assert( !std::is_move_constructible>::value); // Move constructible if and only if E is move constructible. static_assert(std::is_move_constructible>::value); static_assert(!std::is_move_constructible>::value); } // namespace test_move_construction namespace test_move_assignment { // Move assignable if and only if both types are move assignable. static_assert(std::is_move_assignable>::value); static_assert( !std::is_move_assignable>::value); static_assert( !std::is_move_assignable>::value); static_assert( !std::is_move_assignable>::value); // Move assignable if and only if E is move assignable. static_assert(std::is_move_assignable>::value); static_assert(!std::is_move_assignable>::value); } // namespace test_move_assignment namespace test_trivial_destructor { // Destructor is trivial if and only if both types are trivially destructible. static_assert( std::is_trivially_destructible>::value); static_assert(!std::is_trivially_destructible< expected>::value); static_assert(!std::is_trivially_destructible< expected>::value); static_assert(!std::is_trivially_destructible< expected>::value); // Destructor is trivial if and only if E is trivially destructible. static_assert(std::is_trivially_destructible>::value); static_assert( !std::is_trivially_destructible>::value); } // namespace test_trivial_destructor expected FailableFunction1(bool fail, int num) { if (fail) { return unexpected("FailableFunction1"); } return num; } expected FailableFunction2(bool fail, int num) { if (fail) { return unexpected("FailableFunction2"); } return std::to_string(num); } expected FailOnOdd(int x) { if (x % 2) { return unexpected("odd"); } return x; } expected ItoaFailOnNegative(int x) { if (x < 0) { return unexpected("negative"); } return std::to_string(x); } expected GetSecondChar(const std::string& s) { if (s.size() < 2) { return unexpected("string too small"); } return s[1]; } int Decrement(int x) { return x - 1; } template expected Consume(const expected& e) { return e.transform([](auto) {}); } TEST(ExpectedTest, HoldIntValueSuccess) { auto x = FailableFunction1(false, 10); ASSERT_TRUE(x.has_value()); EXPECT_EQ(x.value(), 10); EXPECT_EQ(*x, 10); EXPECT_EQ(x.value_or(33), 10); EXPECT_EQ(x.error_or("no error"), std::string("no error")); } TEST(ExpectedTest, HoldIntValueFail) { auto x = FailableFunction1(true, 10); ASSERT_FALSE(x.has_value()); EXPECT_EQ(x.error(), std::string("FailableFunction1")); EXPECT_EQ(x.value_or(33), 33); EXPECT_EQ(x.error_or("no error"), std::string("FailableFunction1")); } TEST(ExpectedTest, HoldStringValueSuccess) { auto x = FailableFunction2(false, 42); ASSERT_TRUE(x.has_value()); EXPECT_EQ(x.value(), std::string("42")); EXPECT_EQ(*x, std::string("42")); EXPECT_EQ(x.value_or("33"), std::string("42")); EXPECT_EQ(x.error_or("no error"), std::string("no error")); } TEST(ExpectedTest, HoldStringValueFail) { auto x = FailableFunction2(true, 42); ASSERT_FALSE(x.has_value()); EXPECT_EQ(x.error(), std::string("FailableFunction2")); EXPECT_EQ(x.value_or("33"), std::string("33")); EXPECT_EQ(x.error_or("no error"), std::string("FailableFunction2")); } TEST(ExpectedTest, MonadicOperation) { auto f = [](expected value) { return value.and_then(FailOnOdd) .transform(Decrement) .transform(Decrement) .and_then(ItoaFailOnNegative) .and_then(GetSecondChar); }; EXPECT_EQ(f(26).value_or(0), '4'); EXPECT_EQ(f(26).error_or(nullptr), nullptr); EXPECT_EQ(f(25).value_or(0), 0); EXPECT_EQ(f(25).error_or(nullptr), std::string("odd")); EXPECT_EQ(f(0).value_or(0), 0); EXPECT_EQ(f(0).error_or(nullptr), std::string("negative")); EXPECT_EQ(f(4).value_or(0), 0); EXPECT_EQ(f(4).error_or(nullptr), std::string("string too small")); EXPECT_TRUE(Consume(f(26)).has_value()); EXPECT_EQ(Consume(f(25)).error_or(nullptr), std::string("odd")); EXPECT_EQ(Consume(f(0)).error_or(nullptr), std::string("negative")); EXPECT_EQ(Consume(f(4)).error_or(nullptr), std::string("string too small")); } } // namespace } // namespace pw