blob: a356935e5d72ba281ff0fd6851771397e91455db [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 "iceberg/compat/expected.h"
#include <functional>
#include <memory>
#include <type_traits>
#include <vector>
#include <gtest/gtest.h>
TEST(ExpectedTest, DefaultCons) {
iceberg::expected<int, int> e1;
iceberg::expected<int, int> e2{};
EXPECT_EQ(e1.value(), 0);
EXPECT_EQ(e2.value(), 0);
}
TEST(ExpectedTest, InplaceCons) {
iceberg::expected<void, int> e1{iceberg::unexpect};
iceberg::expected<void, int> e2{iceberg::unexpect, 42};
iceberg::expected<int, int> e3{iceberg::unexpect};
iceberg::expected<int, int> e4{iceberg::unexpect, 42};
EXPECT_EQ(e1.error(), 0);
EXPECT_EQ(e2.error(), 42);
EXPECT_EQ(e3.error(), 0);
EXPECT_EQ(e4.error(), 42);
}
TEST(ExpectedTest, ImplicitConversion) {
iceberg::expected<int, int> e = 42;
EXPECT_EQ(e.value(), 42);
}
TEST(ExpectedTest, ExplicitConversion) {
iceberg::expected<std::string, int> e1 = iceberg::unexpected<int>(42);
iceberg::expected<std::string, int> e2{iceberg::unexpect, 42};
EXPECT_EQ(e1.error(), 42);
EXPECT_EQ(e2.error(), 42);
}
TEST(ExpectedTest, ImplicitConversionFrom) {
EXPECT_FALSE((std::is_convertible_v<iceberg::expected<std::string, int>, int>));
}
TEST(ExpectedTest, ConversionToValueWithoutDefaultConstructor) {
int value = 42;
iceberg::expected<std::reference_wrapper<int>, int> ref = std::ref(value);
iceberg::expected<std::reference_wrapper<const int>, int> from_value = ref;
ASSERT_TRUE(from_value.has_value());
EXPECT_EQ(from_value->get(), 42);
iceberg::expected<std::reference_wrapper<const int>, int> from_error =
iceberg::expected<std::reference_wrapper<int>, int>{iceberg::unexpect, 7};
ASSERT_FALSE(from_error.has_value());
EXPECT_EQ(from_error.error(), 7);
EXPECT_FALSE((std::is_default_constructible_v<
iceberg::expected<std::reference_wrapper<int>, int>>));
}
TEST(ExpectedTest, SupportsMoveOnlyContainerValue) {
using MoveOnlyContainer = std::vector<std::unique_ptr<int>>;
iceberg::expected<MoveOnlyContainer, int> value{std::in_place};
EXPECT_TRUE(value.has_value());
}
namespace {
// Counts live objects, to catch a value constructed without being destroyed.
struct LiveCount {
static inline int live = 0;
LiveCount() { ++live; }
explicit LiveCount(int) { ++live; }
LiveCount(const LiveCount&) { ++live; }
LiveCount(LiveCount&&) noexcept { ++live; }
LiveCount& operator=(const LiveCount&) = default;
LiveCount& operator=(LiveCount&&) noexcept = default;
~LiveCount() { --live; }
};
} // namespace
TEST(ExpectedTest, ConversionConstructsValueOnce) {
{
iceberg::expected<int, int> value = 1;
iceberg::expected<LiveCount, int> from_value(value);
iceberg::expected<int, int> error = iceberg::unexpected(2);
iceberg::expected<LiveCount, int> from_error(error);
EXPECT_EQ(LiveCount::live, 1);
}
EXPECT_EQ(LiveCount::live, 0);
}
TEST(ExpectedTest, ExplicitVoidE) {
iceberg::expected<void, int> e1 = {};
iceberg::expected<void, int> e2 = iceberg::unexpected{42};
EXPECT_TRUE(e1.has_value());
EXPECT_TRUE((std::is_same_v<decltype(e1.value()), void>));
e1.value();
EXPECT_FALSE(e2.has_value());
EXPECT_EQ(e2.error(), 42);
}
TEST(ExpectedTest, Emplace) {
struct S {
S(int i, double d) noexcept : i(i), d(d) {}
int i;
double d;
};
iceberg::expected<S, std::nullopt_t> e{iceberg::unexpect, std::nullopt};
e.emplace(42, 3.14);
EXPECT_EQ(e.value().i, 42);
EXPECT_EQ(e.value().d, 3.14);
}
TEST(ExpectedTest, Equality) {
iceberg::expected<int, int> const v1;
iceberg::expected<int, int> const v2{42};
iceberg::expected<int, int> const v3 = 42;
iceberg::expected<int, int> const e1{iceberg::unexpect, 0};
iceberg::expected<int, int> const e2{iceberg::unexpect, 42};
iceberg::expected<int, int> const e3 = iceberg::unexpected(42);
EXPECT_TRUE(v1 != v2);
EXPECT_TRUE(v2 == v3);
EXPECT_TRUE(v1 != e1);
EXPECT_TRUE(v1 != e2);
EXPECT_TRUE(e1 != e2);
EXPECT_TRUE(e2 == e3);
EXPECT_TRUE(e1 != v1);
EXPECT_TRUE(e1 != v2);
}
TEST(ExpectedTest, UnexpectedError) {
auto e = iceberg::unexpected(42);
EXPECT_EQ(e.error(), 42);
}
TEST(ExpectedTest, UnexpectedEquality) {
const iceberg::unexpected<std::string> error1("error1");
const iceberg::unexpected<std::string> error1_copy("error1");
const iceberg::unexpected<std::string> error2("error2");
EXPECT_EQ(error1, error1_copy);
EXPECT_NE(error1, error2);
}
TEST(ExpectedTest, BadExpectedAccess) {
iceberg::expected<void, int> e = iceberg::unexpected(42);
EXPECT_THROW(
{
try {
e.value();
} catch (const iceberg::bad_expected_access<int>& ex) {
// and this tests that it has the correct message
EXPECT_EQ(ex.error(), 42);
throw;
}
},
iceberg::bad_expected_access<int>);
}
TEST(ExpectedTest, ExpectedVoidEEmplace) {
iceberg::expected<void, int> e1{std::in_place};
iceberg::expected<void, int> e2;
e2.emplace();
}
TEST(ExpectedTest, Swap) {
iceberg::expected<int, int> e1{42};
iceberg::expected<int, int> e2{1'337};
e1.swap(e2);
EXPECT_EQ(e1.value(), 1'337);
EXPECT_EQ(e2.value(), 42);
swap(e1, e2);
EXPECT_EQ(e1.value(), 42);
EXPECT_EQ(e2.value(), 1'337);
}
TEST(ExpectedTest, CopyInitialization) {
{
iceberg::expected<std::string, int> e = {};
EXPECT_TRUE(e);
}
}
TEST(ExpectedTest, BraceContructedValue) {
{ // non-void
iceberg::expected<int, int> e{{}};
EXPECT_TRUE(e);
}
#if !defined(__GNUC__)
{ // std::string
iceberg::expected<std::string, int> e{{}};
EXPECT_TRUE(e);
}
#endif
}
TEST(ExpectedTest, LWG3836) {
struct BaseError {};
struct DerivedError : BaseError {};
iceberg::expected<bool, DerivedError> e1(false);
iceberg::expected<bool, BaseError> e2(e1);
EXPECT_TRUE(!e2.value());
iceberg::expected<void, DerivedError> e3{};
iceberg::expected<void, BaseError> e4(e3);
EXPECT_TRUE(e4.has_value());
}
TEST(ExpectedTest, Assignment) {
// non-void
{
iceberg::expected<int, int> e1{iceberg::unexpect, 1'337};
iceberg::expected<int, int> e2{42};
e1 = e2;
EXPECT_TRUE(e1);
}
// void
{
iceberg::expected<void, int> e1{iceberg::unexpect, 1'337};
iceberg::expected<void, int> e2{};
e1 = e2;
EXPECT_TRUE(e1);
}
}
TEST(ExpectedTest, Move) {
{
struct user_provided_move {
user_provided_move() = default;
user_provided_move(user_provided_move&&) noexcept {}
};
struct defaulted_move {
defaulted_move() = default;
defaulted_move(defaulted_move&&) = default;
};
{
using Expected = iceberg::expected<user_provided_move, int>;
Expected t1;
Expected tm1(std::move(t1));
EXPECT_TRUE(tm1);
}
{
using Expected = iceberg::expected<defaulted_move, int>;
Expected t2;
Expected tm2(std::move(t2)); // should compile
EXPECT_TRUE(tm2);
}
}
{
class MoveOnly {
public:
MoveOnly() = default;
// Non-copyable
MoveOnly(const MoveOnly&) = delete;
MoveOnly& operator=(const MoveOnly&) = delete;
// Movable trivially
MoveOnly(MoveOnly&&) = default;
MoveOnly& operator=(MoveOnly&&) = default;
};
{
using Expected = iceberg::expected<MoveOnly, int>;
Expected a{};
Expected b = std::move(a); // should compile
}
}
}
namespace {
template <bool ShouldEqual, typename T, typename U>
constexpr bool EqualityTester(T const& lhs, U const& rhs) {
return (lhs == rhs) == ShouldEqual && (lhs != rhs) != ShouldEqual &&
(rhs == lhs) == ShouldEqual && (rhs != lhs) != ShouldEqual;
}
struct Type1 {
std::string value;
explicit Type1(std::string v) : value(std::move(v)) {}
explicit Type1(const char* v) : value(v) {}
Type1(Type1 const&) = delete;
Type1& operator=(Type1 const&) = delete;
Type1(Type1&& other) = default;
Type1& operator=(Type1&&) = default;
~Type1() = default;
bool operator==(Type1 const& rhs) const { return value == rhs.value; }
};
struct Type2 {
std::string value;
explicit Type2(std::string v) : value(std::move(v)) {}
explicit Type2(const char* v) : value(v) {}
Type2(Type2 const&) = delete;
Type2& operator=(Type2 const&) = delete;
Type2(Type2&& other) = default;
Type2& operator=(Type2&&) = default;
~Type2() = default;
bool operator==(Type2 const& rhs) const { return value == rhs.value; }
};
inline bool operator==(Type1 const& lhs, Type2 const& rhs) {
return lhs.value == rhs.value;
}
inline bool operator==(Type2 const& lhs, Type1 const& rhs) { return rhs == lhs; }
} // namespace
TEST(ExpectedTest, EqualityTest) {
// expected<T, E> (T is not void)
{
using T = Type1;
using E = Type2;
using Expected = iceberg::expected<T, E>;
// compare with same type expected<T, E>
{
Expected const value1("value1");
Expected const value2("value2");
Expected const value1Copy("value1");
Expected const error1 = iceberg::unexpected<E>("error1");
Expected const error2 = iceberg::unexpected<E>("error2");
Expected const error1Copy = iceberg::unexpected<E>("error1");
EXPECT_TRUE(EqualityTester<true>(value1, value1Copy));
EXPECT_TRUE(EqualityTester<false>(value1, value2));
EXPECT_TRUE(EqualityTester<true>(error1, error1Copy));
EXPECT_TRUE(EqualityTester<false>(error1, error2));
EXPECT_TRUE(EqualityTester<false>(value1, error1));
}
// compare with different type expected<T2, E2>
{
using T2 = Type2;
using E2 = Type1;
ASSERT_FALSE((std::is_same_v<T, T2>));
ASSERT_FALSE((std::is_same_v<E, E2>));
using Expected2 = iceberg::expected<T2, E2>;
Expected const value1("value1");
Expected2 const value2("value2");
Expected2 const value1Same("value1");
Expected const error1 = iceberg::unexpected<E>("error1");
Expected2 const error2 = iceberg::unexpected<E2>("error2");
Expected2 const error1Same = iceberg::unexpected<E2>("error1");
EXPECT_TRUE(EqualityTester<true>(value1, value1Same));
EXPECT_TRUE(EqualityTester<false>(value1, value2));
EXPECT_TRUE(EqualityTester<true>(error1, error1Same));
EXPECT_TRUE(EqualityTester<false>(error1, error2));
EXPECT_TRUE(EqualityTester<false>(value1, error1));
}
// compare with same value type T
{
Expected const value1("value1");
Expected const error1 = iceberg::unexpected<E>("error1");
T const value2("value2");
T const value1Same("value1");
EXPECT_TRUE(EqualityTester<true>(value1, value1Same));
EXPECT_TRUE(EqualityTester<false>(value1, value2));
EXPECT_TRUE(EqualityTester<false>(error1, value2));
}
// compare with different value type T2
{
using T2 = Type2;
ASSERT_FALSE((std::is_same_v<T, T2>));
Expected const value1("value1");
Expected const error1 = iceberg::unexpected<E>("error1");
T2 const value2("value2");
T2 const value1Same("value1");
EXPECT_TRUE(EqualityTester<true>(value1, value1Same));
EXPECT_TRUE(EqualityTester<false>(value1, value2));
EXPECT_TRUE(EqualityTester<false>(error1, value2));
}
// compare with same error type unexpected<E>
{
Expected const value1("value1");
Expected const error1 = iceberg::unexpected<E>("error1");
auto const error2 = iceberg::unexpected<E>("error2");
auto const error1Same = iceberg::unexpected<E>("error1");
EXPECT_TRUE(EqualityTester<true>(error1, error1Same));
EXPECT_TRUE(EqualityTester<false>(error1, error2));
EXPECT_TRUE(EqualityTester<false>(value1, error2));
}
// compare with different error type unexpected<E2>
{
using E2 = Type1;
ASSERT_FALSE((std::is_same_v<E, E2>));
Expected const value1("value1");
Expected const error1 = iceberg::unexpected<E>("error1");
auto const error2 = iceberg::unexpected<E2>("error2");
auto const error1Same = iceberg::unexpected<E2>("error1");
EXPECT_TRUE(EqualityTester<true>(error1, error1Same));
EXPECT_TRUE(EqualityTester<false>(error1, error2));
EXPECT_TRUE(EqualityTester<false>(value1, error2));
}
}
// expected<void, E>
{
using E = Type1;
using Expected = iceberg::expected<void, E>;
// compare with same type expected<void, E>
{
Expected const value1;
Expected const value2;
Expected const error1 = iceberg::unexpected<E>("error1");
Expected const error2 = iceberg::unexpected<E>("error2");
Expected const error1Copy = iceberg::unexpected<E>("error1");
EXPECT_TRUE(EqualityTester<true>(value1, value2));
EXPECT_TRUE(EqualityTester<true>(error1, error1Copy));
EXPECT_TRUE(EqualityTester<false>(error1, error2));
EXPECT_TRUE(EqualityTester<false>(value1, error1));
}
// compare with different type expected<void, E2>
{
using E2 = Type2;
ASSERT_FALSE((std::is_same_v<E, E2>));
using Expected2 = iceberg::expected<void, E2>;
Expected const value1;
Expected2 const value2;
Expected const error1 = iceberg::unexpected<E>("error1");
Expected2 const error2 = iceberg::unexpected<E2>("error2");
Expected2 const error1Same = iceberg::unexpected<E2>("error1");
EXPECT_TRUE(EqualityTester<true>(value1, value2));
EXPECT_TRUE(EqualityTester<true>(error1, error1Same));
EXPECT_TRUE(EqualityTester<false>(error1, error2));
EXPECT_TRUE(EqualityTester<false>(value1, error1));
}
// compare with same error type unexpected<E>
{
Expected const value1;
Expected const error1 = iceberg::unexpected<E>("error1");
auto const error2 = iceberg::unexpected<E>("error2");
auto const error1Same = iceberg::unexpected<E>("error1");
EXPECT_TRUE(EqualityTester<true>(error1, error1Same));
EXPECT_TRUE(EqualityTester<false>(error1, error2));
EXPECT_TRUE(EqualityTester<false>(value1, error2));
}
// compare with different error type unexpected<E2>
{
using E2 = Type2;
ASSERT_FALSE((std::is_same_v<E, E2>));
Expected const value1;
Expected const error1 = iceberg::unexpected<E>("error1");
auto const error2 = iceberg::unexpected<E2>("error2");
auto const error1Same = iceberg::unexpected<E2>("error1");
EXPECT_TRUE(EqualityTester<true>(error1, error1Same));
EXPECT_TRUE(EqualityTester<false>(error1, error2));
EXPECT_TRUE(EqualityTester<false>(value1, error2));
}
}
}
TEST(ExpectedTest, AndThen) {
using T = std::string;
using Expected = iceberg::expected<int, T>;
{ // non-void
using Expected2 = iceberg::expected<double, T>;
Expected e = 42;
auto const newVal = e.and_then([](int x) { return Expected2{x * 2}; });
EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, Expected2>));
}
{ // void
using Expected2 = iceberg::expected<void, T>;
Expected e = 42;
auto const newVal = e.and_then([](int) -> Expected2 { return Expected2{}; });
EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, Expected2>));
}
}
TEST(ExpectedTest, VoidAndThen) {
using T = std::string;
using Expected = iceberg::expected<void, T>;
{ // non-void
using Expected2 = iceberg::expected<double, T>;
Expected e{};
auto const newVal = e.and_then([]() { return Expected2{}; });
EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, Expected2>));
}
{ // void
using Expected2 = iceberg::expected<void, T>;
Expected e{};
auto const newVal = e.and_then([]() -> Expected2 { return Expected2{}; });
EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, Expected2>));
}
}
TEST(ExpectedTest, OrElse) {
using T = std::string;
using Expected = iceberg::expected<T, int>;
{ // non-void
using Expected2 = iceberg::expected<T, double>;
Expected e{};
auto const newVal = e.or_else([](auto&&) { return Expected2{}; });
EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, Expected2>));
}
}
TEST(ExpectedTest, VoidOrElse) {
using Expected = iceberg::expected<void, int>;
{ // void
using Expected2 = iceberg::expected<void, double>;
Expected e{};
auto const newVal = e.or_else([](auto&&) { return Expected2{}; });
EXPECT_TRUE(newVal.has_value());
EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, Expected2>));
}
}
TEST(ExpectedTest, Transform) {
using Expected = iceberg::expected<int, int>;
{
Expected e{42};
auto const newVal = e.transform([](int n) { return n + 100; });
EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, Expected>));
EXPECT_EQ(newVal.value(), 142);
}
{
Expected e = iceberg::unexpected{-1};
auto const newVal = e.transform_error([](int n) { return n + 100; });
EXPECT_EQ(newVal.error(), 99);
}
}
namespace {
struct FromType {};
enum class NoThrowConvertible {
Yes,
No,
};
template <NoThrowConvertible N>
struct ToType;
template <>
struct ToType<NoThrowConvertible::Yes> {
ToType() = default;
ToType(ToType const&) = default;
ToType(ToType&&) = default;
explicit ToType(FromType const&) noexcept {}
};
template <>
struct ToType<NoThrowConvertible::No> {
ToType() = default;
ToType(ToType const&) = default;
ToType(ToType&&) = default;
explicit ToType(FromType const&) noexcept(false) {}
};
} // namespace
TEST(ExpectedTest, ValueOrNoexcept) {
{ // noexcept(true)
using T = ToType<NoThrowConvertible::Yes>;
using E = int;
using Expected = iceberg::expected<T, E>;
Expected e;
EXPECT_TRUE(noexcept(e.value_or(FromType{})));
}
{ // noexcept(false)
using T = ToType<NoThrowConvertible::No>;
using E = int;
using Expected = iceberg::expected<T, E>;
Expected e;
EXPECT_FALSE(noexcept(e.value_or(FromType{})));
}
}
TEST(ExpectedTest, ErrorOrNoexcept) {
{ // noexcept(true)
using T = int;
using E = ToType<NoThrowConvertible::Yes>;
using Expected = iceberg::expected<T, E>;
Expected e;
EXPECT_TRUE(noexcept(e.error_or(FromType{})));
}
{ // noexcept(false)
using T = int;
using E = ToType<NoThrowConvertible::No>;
using Expected = iceberg::expected<T, E>;
Expected e;
EXPECT_FALSE(noexcept(e.error_or(FromType{})));
}
}
TEST(ExpectedTest, VoidTErrorOrNoxcept) {
{ // noexcept(true)
using T = void;
using E = ToType<NoThrowConvertible::Yes>;
using Expected = iceberg::expected<T, E>;
Expected e;
EXPECT_TRUE(noexcept(e.error_or(FromType{})));
}
{ // noexcept(false)
using T = void;
using E = ToType<NoThrowConvertible::No>;
using Expected = iceberg::expected<T, E>;
Expected e;
EXPECT_FALSE(noexcept(e.error_or(FromType{})));
}
}