diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index f0df05b0d..606f6fc9a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -33,6 +33,8 @@ set(common_sources command_line.cpp dns_utils.cpp download.cpp + error.cpp + expect.cpp util.cpp i18n.cpp password.cpp @@ -54,6 +56,8 @@ set(common_private_headers common_fwd.h dns_utils.h download.h + error.h + expect.h http_connection.h int-util.h pod-class.h diff --git a/src/common/error.cpp b/src/common/error.cpp new file mode 100644 index 000000000..e091e4478 --- /dev/null +++ b/src/common/error.cpp @@ -0,0 +1,75 @@ +// Copyright (c) 2018, The Monero Project +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "error.h" + +#include + +namespace +{ + struct category final : std::error_category + { + virtual const char* name() const noexcept override final + { + return "common_category()"; + } + + virtual std::string message(int value) const override final + { + switch (common_error(value)) + { + case common_error::kInvalidArgument: + return make_error_code(std::errc::invalid_argument).message(); + case common_error::kInvalidErrorCode: + return "expect was given an error value of zero"; + default: + break; + } + return "Unknown basic_category() value"; + } + + virtual std::error_condition default_error_condition(int value) const noexcept override final + { + // maps specific errors to generic `std::errc` cases. + switch (common_error(value)) + { + case common_error::kInvalidArgument: + case common_error::kInvalidErrorCode: + return std::errc::invalid_argument; + default: + break; + } + return std::error_condition{value, *this}; + } + }; +} + +std::error_category const& common_category() noexcept +{ + static const category instance{}; + return instance; +} + diff --git a/src/common/error.h b/src/common/error.h new file mode 100644 index 000000000..6fef3eb4b --- /dev/null +++ b/src/common/error.h @@ -0,0 +1,52 @@ +// Copyright (c) 2018, The Monero Project +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#pragma once + +#include +#include + +enum class common_error : int +{ + // 0 is reserved for no error, as per expect + kInvalidArgument = 1, //!< A function argument is invalid + kInvalidErrorCode //!< Default `std::error_code` given to `expect` +}; + +std::error_category const& common_category() noexcept; + +inline std::error_code make_error_code(::common_error value) noexcept +{ + return std::error_code{int(value), common_category()}; +} + +namespace std +{ + template<> + struct is_error_code_enum<::common_error> + : true_type + {}; +} diff --git a/src/common/expect.cpp b/src/common/expect.cpp new file mode 100644 index 000000000..c86e23e95 --- /dev/null +++ b/src/common/expect.cpp @@ -0,0 +1,70 @@ +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "expect.h" + +#include +#include + +namespace detail +{ + namespace + { + std::string generate_error(const char* msg, const char* file, unsigned line) + { + std::string error_msg{}; + if (msg) + { + error_msg.append(msg); + if (file) + error_msg.append(" ("); + } + if (file) + { + error_msg.append("thrown at "); + + // remove path, get just filename + extension + char buff[256] = {0}; + el::base::utils::File::buildBaseFilename(file, buff, sizeof(buff) - 1); + error_msg.append(buff); + + error_msg.push_back(':'); + error_msg.append(std::to_string(line)); + } + if (msg && file) + error_msg.push_back(')'); + return error_msg; + } + } + + void expect::throw_(std::error_code ec, const char* msg, const char* file, unsigned line) + { + if (msg || file) + throw std::system_error{ec, generate_error(msg, file, line)}; + throw std::system_error{ec}; + } +} // detail diff --git a/src/common/expect.h b/src/common/expect.h new file mode 100644 index 000000000..326242502 --- /dev/null +++ b/src/common/expect.h @@ -0,0 +1,447 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include + +#include "common/error.h" + +//! If precondition fails, return `::error::kInvalidArgument` in current scope. +#define MONERO_PRECOND(...) \ + do \ + { \ + if (!( __VA_ARGS__ )) \ + return {::common_error::kInvalidArgument}; \ + } while (0) + +//! Check `expect` and return errors in current scope. +#define MONERO_CHECK(...) \ + do \ + { \ + const ::expect result = __VA_ARGS__ ; \ + if (!result) \ + return result.error(); \ + } while (0) + +/*! Get `T` from `expect` by `std::move` as-if by function call. + `expect` returns nothing. + + \throw std::system_error with `expect::error()`, filename and line + number when `expect::has_error() == true`.*/ +#define MONERO_UNWRAP(...) \ + ::detail::expect::unwrap( __VA_ARGS__ , nullptr, __FILE__ , __LINE__ ) + +/* \throw std::system_error with `code` and `msg` as part of the details. The +filename and line number will automatically be injected into the explanation +string. `code` can be any enum convertible to `std::error_code`. */ +#define MONERO_THROW(code, msg) \ + ::detail::expect::throw_( code , msg , __FILE__ , __LINE__ ) + + +template class expect; + +namespace detail +{ + // Shortens the characters in the places that `enable_if` is used below. + template + using enable_if = typename std::enable_if::type; + + struct expect + { + //! \throw std::system_error with `ec`, optional `msg` and/or optional `file` + `line`. + static void throw_(std::error_code ec, const char* msg, const char* file, unsigned line); + + //! If `result.has_error()` call `throw_`. Otherwise, \return `*result` by move. + template + static T unwrap(::expect&& result, const char* error_msg, const char* file, unsigned line) + { + if (!result) + throw_(result.error(), error_msg, file, line); + return std::move(*result); + } + + //! If `result.has_error()` call `throw_`. + static void unwrap(::expect&& result, const char* error_msg, const char* file, unsigned line); + }; +} + +/*! + `expect` is a value or error implementation, similar to Rust std::result + or various C++ proposals (boost::expected, boost::outcome). This + implementation currently has a strict error type, `std::error_code`, and a + templated value type `T`. `expect` is implicitly convertible from `T` + or `std::error_code`, and one `expect` object type is implicitly + convertible to another `expect` object iff the destination value type + can be implicitly constructed from the source value type (i.e. + `struct U { ... U(T src) { ...} ... };`). + + `operator==` and `operator!=` are the only comparison operators provided; + comparison between different value types is allowed provided the two values + types have a `operator==` defined between them (i.e. + `assert(expect{100} == expect{100});`). Comparisons can also be + done against `std::error_code` objects or error code enums directly (i.e. + `assert(expect{make_error_code(common_error::kInvalidArgument)} == error::kInvalidArgument)`). + Comparison of default constructed `std::error_code` will always fail. + "Generic" comparisons can be done with `std::error_condition` via the `matches` + method only (i.e. + `assert(expect{make_error_code{common_error::kInvalidErrorCode}.matches(std::errc::invalid_argument))`), + `operator==` and `operator!=` will not work with `std::errc` or + `std::error_condition`. A comparison with `matches` is more expensive + because an equivalency between error categories is computed, but is + recommended when an error can be one of several categories (this is going + to be the case in nearly every situation when calling a function from + another C++ struct/class). + + `expect` is a special case with no stored value. It is used by + functions that can fail, but otherwise would return `void`. It is useful + for consistency; all macros, standalone functions, and comparison operators + work with `expect`. + + \note See `src/common/error.h` for creating a custom error enum. + */ +template +class expect +{ + static_assert(std::is_nothrow_destructible(), "T must have a nothrow destructor"); + + template + static constexpr bool is_convertible() noexcept + { + return std::is_constructible() && + std::is_convertible(); + } + + // MEMBERS + std::error_code code_; + typename std::aligned_storage::type storage_; + // MEMBERS + + T& get() noexcept + { + assert(has_value()); + return *reinterpret_cast(std::addressof(storage_)); + } + + T const& get() const noexcept + { + assert(has_value()); + return *reinterpret_cast(std::addressof(storage_)); + } + + template + void store(U&& value) noexcept(std::is_nothrow_constructible()) + { + new (std::addressof(storage_)) T{std::forward(value)}; + code_ = std::error_code{}; + } + + void maybe_throw() const + { + if (has_error()) + ::detail::expect::throw_(error(), nullptr, nullptr, 0); + } + +public: + using value_type = T; + using error_type = std::error_code; + + expect() = delete; + + /*! Store an error, `code`, in the `expect` object. If `code` creates a + `std::error_code` object whose `.value() == 0`, then `error()` will be set + to `::common_error::kInvalidErrorCode`. */ + expect(std::error_code const& code) noexcept + : code_(code), storage_() + { + if (!has_error()) + code_ = ::common_error::kInvalidErrorCode; + } + + //! Store a value, `val`, in the `expect` object. + expect(T val) noexcept(std::is_nothrow_move_constructible()) + : code_(), storage_() + { + store(std::move(val)); + } + + expect(expect const& src) noexcept(std::is_nothrow_copy_constructible()) + : code_(src.error()), storage_() + { + if (src.has_value()) + store(src.get()); + } + + //! Copy conversion from `U` to `T`. + template()>> + expect(expect const& src) noexcept(std::is_nothrow_constructible()) + : code_(src.error()), storage_() + { + if (src.has_value()) + store(*src); + } + + expect(expect&& src) noexcept(std::is_nothrow_move_constructible()) + : code_(src.error()), storage_() + { + if (src.has_value()) + store(std::move(src.get())); + } + + //! Move conversion from `U` to `T`. + template()>> + expect(expect&& src) noexcept(std::is_nothrow_constructible()) + : code_(src.error()), storage_() + { + if (src.has_value()) + store(std::move(*src)); + } + + ~expect() noexcept + { + if (has_value()) + get().~T(); + } + + expect& operator=(expect const& src) noexcept(std::is_nothrow_copy_constructible() && std::is_nothrow_copy_assignable()) + { + if (this != std::addressof(src)) + { + if (has_value() && src.has_value()) + get() = src.get(); + else if (has_value()) + get().~T(); + else if (src.has_value()) + store(src.get()); + code_ = src.error(); + } + return *this; + } + + /*! Move `src` into `this`. If `src.has_value() && addressof(src) != this` + then `src.value() will be in a "moved from state". */ + expect& operator=(expect&& src) noexcept(std::is_nothrow_move_constructible() && std::is_nothrow_move_assignable()) + { + if (this != std::addressof(src)) + { + if (has_value() && src.has_value()) + get() = std::move(src.get()); + else if (has_value()) + get().~T(); + else if (src.has_value()) + store(std::move(src.get())); + code_ = src.error(); + } + return *this; + } + + //! \return True if `this` is storing a value instead of an error. + explicit operator bool() const noexcept { return has_value(); } + + //! \return True if `this` is storing an error instead of a value. + bool has_error() const noexcept { return bool(code_); } + + //! \return True if `this` is storing a value instead of an error. + bool has_value() const noexcept { return !has_error(); } + + //! \return Error - always safe to call. Empty when `!has_error()`. + std::error_code error() const noexcept { return code_; } + + //! \return Value if `has_value()` otherwise \throw `std::system_error{error()}`. + T& value() & + { + maybe_throw(); + return get(); + } + + //! \return Value if `has_value()` otherwise \throw `std::system_error{error()}`. + T const& value() const & + { + maybe_throw(); + return get(); + } + + /*! Same as other overloads, but expressions such as `foo(bar().value())` + will automatically perform moves with no copies. */ + T&& value() && + { + maybe_throw(); + return std::move(get()); + } + + //! \return Value, \pre `has_value()`. + T* operator->() noexcept { return std::addressof(get()); } + //! \return Value, \pre `has_value()`. + T const* operator->() const noexcept { return std::addressof(get()); } + //! \return Value, \pre `has_value()`. + T& operator*() noexcept { return get(); } + //! \return Value, \pre `has_value()`. + T const& operator*() const noexcept { return get(); } + + /*! + \note This function is `noexcept` when `U == T` is `noexcept`. + \return True if `has_value() == rhs.has_value()` and if values or errors are equal. + */ + template + bool equal(expect const& rhs) const noexcept(noexcept(*std::declval>() == *rhs)) + { + return has_value() && rhs.has_value() ? + get() == *rhs : error() == rhs.error(); + } + + //! \return False if `has_value()`, otherwise `error() == rhs`. + bool equal(std::error_code const& rhs) const noexcept + { + return has_error() && error() == rhs; + } + + /*! + \note This function is `noexcept` when `U == T` is `noexcept`. + \return False if `has_error()`, otherwise `value() == rhs`. + */ + template::value>> + bool equal(U const& rhs) const noexcept(noexcept(*std::declval>() == rhs)) + { + return has_value() && get() == rhs; + } + + //! \return False if `has_value()`, otherwise `error() == rhs`. + bool matches(std::error_condition const& rhs) const noexcept + { + return has_error() && error() == rhs; + } +}; + +template<> +class expect +{ + std::error_code code_; + +public: + using value_type = void; + using error_type = std::error_code; + + //! Create a successful object. + expect() = default; + + expect(std::error_code const& code) noexcept + : code_(code) + { + if (!has_error()) + code_ = ::common_error::kInvalidErrorCode; + } + + expect(expect const&) = default; + ~expect() = default; + expect& operator=(expect const&) = default; + + //! \return True if `this` is storing a value instead of an error. + explicit operator bool() const noexcept { return !has_error(); } + + //! \return True if `this` is storing an error instead of a value. + bool has_error() const noexcept { return bool(code_); } + + //! \return Error - alway + std::error_code error() const noexcept { return code_; } + + //! \return `error() == rhs.error()`. + bool equal(expect const& rhs) const noexcept + { + return error() == rhs.error(); + } + + //! \return `has_error() && error() == rhs`. + bool equal(std::error_code const& rhs) const noexcept + { + return has_error() && error() == rhs; + } + + //! \return False if `has_value()`, otherwise `error() == rhs`. + bool matches(std::error_condition const& rhs) const noexcept + { + return has_error() && error() == rhs; + } +}; + +//! \return An `expect` object with `!has_error()`. +inline expect success() noexcept { return expect{}; } + +template +inline +bool operator==(expect const& lhs, expect const& rhs) noexcept(noexcept(lhs.equal(rhs))) +{ + return lhs.equal(rhs); +} + +template +inline +bool operator==(expect const& lhs, U const& rhs) noexcept(noexcept(lhs.equal(rhs))) +{ + return lhs.equal(rhs); +} + +template +inline +bool operator==(T const& lhs, expect const& rhs) noexcept(noexcept(rhs.equal(lhs))) +{ + return rhs.equal(lhs); +} + +template +inline +bool operator!=(expect const& lhs, expect const& rhs) noexcept(noexcept(lhs.equal(rhs))) +{ + return !lhs.equal(rhs); +} + +template +inline +bool operator!=(expect const& lhs, U const& rhs) noexcept(noexcept(lhs.equal(rhs))) +{ + return !lhs.equal(rhs); +} + +template +inline +bool operator!=(T const& lhs, expect const& rhs) noexcept(noexcept(rhs.equal(lhs))) +{ + return !rhs.equal(lhs); +} + +namespace detail +{ + inline void expect::unwrap(::expect&& result, const char* error_msg, const char* file, unsigned line) + { + if (!result) + throw_(result.error(), error_msg, file, line); + } +} + diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 076a4a873..0fa22b4e1 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -47,6 +47,7 @@ set(unit_tests_sources epee_boosted_tcp_server.cpp epee_levin_protocol_handler_async.cpp epee_utils.cpp + expect.cpp fee.cpp json_serialization.cpp get_xtype_from_string.cpp diff --git a/tests/unit_tests/expect.cpp b/tests/unit_tests/expect.cpp new file mode 100644 index 000000000..efa843496 --- /dev/null +++ b/tests/unit_tests/expect.cpp @@ -0,0 +1,915 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include +#include +#include +#include + +#include "common/expect.h" + +namespace +{ + struct move_only; + struct throw_construct; + struct throw_copies; + struct throw_moves; + + struct move_only + { + move_only() = default; + move_only(move_only const&) = delete; + move_only(move_only&&) = default; + ~move_only() = default; + move_only& operator=(move_only const&) = delete; + move_only& operator=(move_only&&) = default; + }; + + struct throw_construct + { + throw_construct() {} + throw_construct(int) {} + throw_construct(throw_construct const&) = default; + throw_construct(throw_construct&&) = default; + ~throw_construct() = default; + throw_construct& operator=(throw_construct const&) = default; + throw_construct& operator=(throw_construct&&) = default; + }; + + struct throw_copies + { + throw_copies() noexcept {} + throw_copies(throw_copies const&) {} + throw_copies(throw_copies&&) = default; + ~throw_copies() = default; + throw_copies& operator=(throw_copies const&) { return *this; } + throw_copies& operator=(throw_copies&&) = default; + bool operator==(throw_copies const&) noexcept { return true; } + bool operator==(throw_moves const&) noexcept { return true; } + }; + + struct throw_moves + { + throw_moves() noexcept {} + throw_moves(throw_moves const&) = default; + throw_moves(throw_moves&&) {} + ~throw_moves() = default; + throw_moves& operator=(throw_moves const&) = default; + throw_moves& operator=(throw_moves&&) { return *this; } + bool operator==(throw_moves const&) { return true; } + bool operator==(throw_copies const&) { return true; } + }; + + template + void construction_bench() + { + EXPECT_TRUE(std::is_copy_constructible>()); + EXPECT_TRUE(std::is_move_constructible>()); + EXPECT_TRUE(std::is_copy_assignable>()); + EXPECT_TRUE(std::is_move_assignable>()); + EXPECT_TRUE(std::is_destructible>()); + } + + template + void noexcept_bench() + { + EXPECT_TRUE(std::is_nothrow_copy_constructible>()); + EXPECT_TRUE(std::is_nothrow_move_constructible>()); + EXPECT_TRUE(std::is_nothrow_copy_assignable>()); + EXPECT_TRUE(std::is_nothrow_move_assignable>()); + EXPECT_TRUE(std::is_nothrow_destructible>()); + + EXPECT_TRUE(noexcept(bool(std::declval>()))); + EXPECT_TRUE(noexcept(std::declval>().has_error())); + EXPECT_TRUE(noexcept(std::declval>().error())); + EXPECT_TRUE(noexcept(std::declval>().equal(std::declval>()))); + EXPECT_TRUE(noexcept(std::declval>() == std::declval>())); + EXPECT_TRUE(noexcept(std::declval>() != std::declval>())); + } + + template + void conversion_bench() + { + EXPECT_TRUE((std::is_convertible>())); + EXPECT_TRUE((std::is_convertible>())); + EXPECT_TRUE((std::is_convertible>())); + EXPECT_TRUE((std::is_convertible>())); + + EXPECT_TRUE((std::is_constructible, std::error_code>())); + EXPECT_TRUE((std::is_constructible, std::error_code&&>())); + EXPECT_TRUE((std::is_constructible, std::error_code&>())); + EXPECT_TRUE((std::is_constructible, std::error_code const&>())); + } +} + + +TEST(Expect, Constructions) +{ + construction_bench(); + construction_bench(); + + EXPECT_TRUE(std::is_constructible>()); + + EXPECT_TRUE((std::is_constructible, expect>())); + + EXPECT_TRUE(std::is_move_constructible>()); + EXPECT_TRUE(std::is_move_assignable>()); +} + +TEST(Expect, Conversions) +{ + struct implicit { implicit(int) {} }; + struct explicit_only { explicit explicit_only(int) {} }; + + conversion_bench(); + conversion_bench(); + + EXPECT_TRUE((std::is_convertible>())); + EXPECT_TRUE((std::is_convertible>())); + EXPECT_TRUE((std::is_convertible>())); + EXPECT_TRUE((std::is_convertible>())); + EXPECT_TRUE((std::is_convertible, expect>())); + EXPECT_TRUE((std::is_convertible&&, expect>())); + EXPECT_TRUE((std::is_convertible&, expect>())); + EXPECT_TRUE((std::is_convertible const&, expect>())); + EXPECT_TRUE((std::is_convertible, expect>())); + EXPECT_TRUE((std::is_convertible&&, expect>())); + EXPECT_TRUE((std::is_convertible&, expect>())); + EXPECT_TRUE((std::is_convertible const&, expect>())); + EXPECT_TRUE(!(std::is_convertible, expect>())); + EXPECT_TRUE(!(std::is_convertible&&, expect>())); + EXPECT_TRUE(!(std::is_convertible&, expect>())); + EXPECT_TRUE(!(std::is_convertible const&, expect>())); + + EXPECT_TRUE((std::is_constructible, int>())); + EXPECT_TRUE((std::is_constructible, int&&>())); + EXPECT_TRUE((std::is_constructible, int&>())); + EXPECT_TRUE((std::is_constructible, int const&>())); + EXPECT_TRUE((std::is_constructible, expect>())); + EXPECT_TRUE((std::is_constructible, expect&&>())); + EXPECT_TRUE((std::is_constructible, expect&>())); + EXPECT_TRUE((std::is_constructible, expect const&>())); + EXPECT_TRUE((std::is_constructible, expect>())); + EXPECT_TRUE((std::is_constructible, expect&&>())); + EXPECT_TRUE((std::is_constructible, expect&>())); + EXPECT_TRUE((std::is_constructible, expect const&>())); + EXPECT_TRUE(!(std::is_constructible, expect>())); + EXPECT_TRUE(!(std::is_constructible, expect&&>())); + EXPECT_TRUE(!(std::is_constructible, expect&>())); + EXPECT_TRUE(!(std::is_constructible, expect const&>())); + + EXPECT_EQ(expect{expect{100}}.value(), 100); + + expect val1{std::string{}}; + expect val2{"foo"}; + + EXPECT_EQ(val1.value(), std::string{}); + EXPECT_EQ(val2.value(), std::string{"foo"}); + + const expect val3{val2}; + + EXPECT_EQ(val1.value(), std::string{}); + EXPECT_EQ(val2.value(), std::string{"foo"}); + EXPECT_EQ(val3.value(), std::string{"foo"}); + + val1 = val2; + + EXPECT_EQ(val1.value(), "foo"); + EXPECT_EQ(val2.value(), std::string{"foo"}); + EXPECT_EQ(val3.value(), "foo"); +} + +TEST(Expect, NoExcept) +{ + noexcept_bench(); + noexcept_bench(); + + EXPECT_TRUE(std::is_nothrow_constructible>()); + + EXPECT_TRUE((std::is_nothrow_constructible, int>())); + EXPECT_TRUE((std::is_nothrow_constructible, expect>())); + EXPECT_TRUE((std::is_nothrow_constructible, expect&&>())); + EXPECT_TRUE((std::is_nothrow_constructible, expect&>())); + EXPECT_TRUE((std::is_nothrow_constructible, expect const&>())); + + EXPECT_TRUE(noexcept(expect{std::declval&&>()})); + EXPECT_TRUE(noexcept(expect{std::declval const&>()})); + EXPECT_TRUE(noexcept(std::declval>().has_value())); + EXPECT_TRUE(noexcept(*std::declval>())); + EXPECT_TRUE(noexcept(std::declval>().equal(std::declval>()))); + EXPECT_TRUE(noexcept(std::declval>().equal(std::declval>()))); + EXPECT_TRUE(noexcept(std::declval>().equal(0))); + EXPECT_TRUE(noexcept(std::declval>() == std::declval>())); + EXPECT_TRUE(noexcept(std::declval>() == std::declval>())); + EXPECT_TRUE(noexcept(std::declval>() == 0)); + EXPECT_TRUE(noexcept(0 == std::declval>())); + EXPECT_TRUE(noexcept(std::declval>() != std::declval>())); + EXPECT_TRUE(noexcept(std::declval>() != std::declval>())); + EXPECT_TRUE(noexcept(std::declval>() != 0)); + EXPECT_TRUE(noexcept(0 != std::declval>())); + + EXPECT_TRUE((std::is_nothrow_constructible, std::error_code>())); + EXPECT_TRUE((std::is_nothrow_constructible, std::error_code&&>())); + EXPECT_TRUE((std::is_nothrow_constructible, std::error_code&>())); + EXPECT_TRUE((std::is_nothrow_constructible, std::error_code const&>())); + EXPECT_TRUE((std::is_nothrow_constructible, throw_construct>())); + EXPECT_TRUE((std::is_nothrow_constructible, throw_construct&&>())); + EXPECT_TRUE((std::is_nothrow_constructible, throw_construct&>())); + EXPECT_TRUE((std::is_nothrow_constructible, throw_construct const&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible, expect>())); + EXPECT_TRUE(!(std::is_nothrow_constructible, expect&&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible, expect&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible, expect const&>())); + EXPECT_TRUE(std::is_nothrow_copy_constructible>()); + EXPECT_TRUE(std::is_nothrow_move_constructible>()); + EXPECT_TRUE(std::is_nothrow_copy_assignable>()); + EXPECT_TRUE(std::is_nothrow_move_assignable>()); + EXPECT_TRUE(std::is_nothrow_destructible>()); + + EXPECT_TRUE((std::is_nothrow_constructible, std::error_code>())); + EXPECT_TRUE((std::is_nothrow_constructible, std::error_code&&>())); + EXPECT_TRUE((std::is_nothrow_constructible, std::error_code&>())); + EXPECT_TRUE((std::is_nothrow_constructible, std::error_code const&>())); + EXPECT_TRUE((std::is_nothrow_constructible, throw_copies>())); + EXPECT_TRUE((std::is_nothrow_constructible, throw_copies&&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible, throw_copies&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible, throw_copies const&>())); + EXPECT_TRUE(!std::is_nothrow_copy_constructible>()); + EXPECT_TRUE(std::is_nothrow_move_constructible>()); + EXPECT_TRUE(!std::is_nothrow_copy_assignable>()); + EXPECT_TRUE(std::is_nothrow_move_assignable>()); + EXPECT_TRUE(std::is_nothrow_destructible>()); + EXPECT_TRUE(noexcept(std::declval>().equal(std::declval>()))); + EXPECT_TRUE(noexcept(std::declval>().equal(std::declval()))); + EXPECT_TRUE(noexcept(std::declval>() == std::declval>())); + EXPECT_TRUE(noexcept(std::declval>() == std::declval())); + EXPECT_TRUE(noexcept(std::declval() == std::declval>())); + EXPECT_TRUE(noexcept(std::declval>() != std::declval>())); + EXPECT_TRUE(noexcept(std::declval>() != std::declval())); + EXPECT_TRUE(noexcept(std::declval() != std::declval>())); + EXPECT_TRUE(noexcept(std::declval>().equal(std::declval>()))); + EXPECT_TRUE(noexcept(std::declval>().equal(std::declval()))); + EXPECT_TRUE(noexcept(std::declval>() == std::declval>())); + EXPECT_TRUE(noexcept(std::declval>() == std::declval())); + EXPECT_TRUE(noexcept(std::declval() == std::declval>())); + EXPECT_TRUE(noexcept(std::declval>() != std::declval>())); + EXPECT_TRUE(noexcept(std::declval>() != std::declval())); + EXPECT_TRUE(noexcept(std::declval() != std::declval>())); + + EXPECT_TRUE((std::is_nothrow_constructible, std::error_code>())); + EXPECT_TRUE((std::is_nothrow_constructible, std::error_code&&>())); + EXPECT_TRUE((std::is_nothrow_constructible, std::error_code&>())); + EXPECT_TRUE((std::is_nothrow_constructible, std::error_code const&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible, throw_moves>())); + EXPECT_TRUE(!(std::is_nothrow_constructible, throw_moves&&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible, throw_moves&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible, throw_moves const&>())); + EXPECT_TRUE(std::is_nothrow_copy_constructible>()); + EXPECT_TRUE(!std::is_nothrow_move_constructible>()); + EXPECT_TRUE(std::is_nothrow_copy_assignable>()); + EXPECT_TRUE(!std::is_nothrow_move_assignable>()); + EXPECT_TRUE(std::is_nothrow_destructible>()); + EXPECT_TRUE(!noexcept(std::declval>().equal(std::declval>()))); + EXPECT_TRUE(!noexcept(std::declval>().equal(std::declval()))); + EXPECT_TRUE(!noexcept(std::declval>() == std::declval>())); + EXPECT_TRUE(!noexcept(std::declval>() == std::declval())); + EXPECT_TRUE(!noexcept(std::declval() == std::declval>())); + EXPECT_TRUE(!noexcept(std::declval>() != std::declval>())); + EXPECT_TRUE(!noexcept(std::declval>() != std::declval())); + EXPECT_TRUE(!noexcept(std::declval() != std::declval>())); + EXPECT_TRUE(!noexcept(std::declval>().equal(std::declval>()))); + EXPECT_TRUE(!noexcept(std::declval>().equal(std::declval()))); + EXPECT_TRUE(!noexcept(std::declval>() == std::declval>())); + EXPECT_TRUE(!noexcept(std::declval>() == std::declval())); + EXPECT_TRUE(!noexcept(std::declval() == std::declval>())); + EXPECT_TRUE(!noexcept(std::declval>() != std::declval>())); + EXPECT_TRUE(!noexcept(std::declval>() != std::declval())); + EXPECT_TRUE(!noexcept(std::declval() != std::declval>())); +} + +TEST(Expect, Trivial) +{ + EXPECT_TRUE(std::is_trivially_copy_constructible>()); + EXPECT_TRUE(std::is_trivially_move_constructible>()); + EXPECT_TRUE(std::is_trivially_destructible>()); +} + +TEST(Expect, Assignment) +{ + expect val1{std::string{}}; + expect val2{"foobar"}; + + ASSERT_TRUE(val1.has_value()); + ASSERT_TRUE(val2.has_value()); + EXPECT_TRUE(bool(val1)); + EXPECT_TRUE(bool(val2)); + EXPECT_TRUE(!val1.has_error()); + EXPECT_TRUE(!val2.has_error()); + EXPECT_EQ(val1.value(), std::string{}); + EXPECT_TRUE(*val1 == std::string{}); + EXPECT_TRUE(boost::equals(val1->c_str(), "")); + EXPECT_TRUE(val2.value() == "foobar"); + EXPECT_TRUE(*val2 == "foobar"); + EXPECT_TRUE(boost::equals(val2->c_str(), "foobar")); + EXPECT_EQ(val1.error(), std::error_code{}); + EXPECT_EQ(val2.error(), std::error_code{}); + EXPECT_TRUE(!val1.equal(std::error_code{})); + EXPECT_TRUE(!val2.equal(std::error_code{})); + EXPECT_TRUE(!(val1 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val1)); + EXPECT_TRUE(!(val2 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val2)); + EXPECT_TRUE(val1 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val1); + EXPECT_TRUE(val2 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val2); + EXPECT_TRUE(!val1.matches(std::error_condition{})); + EXPECT_TRUE(!val2.matches(std::error_condition{})); + + val1 = std::move(val2); + + ASSERT_TRUE(val1.has_value()); + ASSERT_TRUE(val2.has_value()); + EXPECT_TRUE(bool(val1)); + EXPECT_TRUE(bool(val2)); + EXPECT_TRUE(!val1.has_error()); + EXPECT_TRUE(!val2.has_error()); + EXPECT_EQ(val1.value(), "foobar"); + EXPECT_TRUE(*val1 == "foobar"); + EXPECT_TRUE(boost::equals(val1->c_str(), "foobar")); + EXPECT_EQ(val2.value(), std::string{}); + EXPECT_TRUE(*val2 == std::string{}); + EXPECT_TRUE(boost::equals(val2->c_str(), "")); + EXPECT_EQ(val1.error(), std::error_code{}); + EXPECT_EQ(val2.error(), std::error_code{}); + EXPECT_TRUE(!val1.equal(std::error_code{})); + EXPECT_TRUE(!val2.equal(std::error_code{})); + EXPECT_TRUE(!(val1 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val1)); + EXPECT_TRUE(!(val2 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val2)); + EXPECT_TRUE(val1 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val1); + EXPECT_TRUE(val2 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val2); + EXPECT_TRUE(!val1.matches(std::error_condition{})); + EXPECT_TRUE(!val2.matches(std::error_condition{})); + + val2 = val1; + + ASSERT_TRUE(val1.has_value()); + ASSERT_TRUE(val2.has_value()); + EXPECT_TRUE(bool(val1)); + EXPECT_TRUE(bool(val2)); + EXPECT_TRUE(!val1.has_error()); + EXPECT_TRUE(!val2.has_error()); + EXPECT_EQ(val1.value(), "foobar"); + EXPECT_TRUE(*val1 == "foobar"); + EXPECT_TRUE(boost::equals(val1->c_str(), "foobar")); + EXPECT_EQ(val2.value(), "foobar"); + EXPECT_TRUE(*val2 == "foobar"); + EXPECT_TRUE(boost::equals(val2->c_str(), "foobar")); + EXPECT_EQ(val1.error(), std::error_code{}); + EXPECT_EQ(val2.error(), std::error_code{}); + EXPECT_TRUE(!val1.equal(std::error_code{})); + EXPECT_TRUE(!val2.equal(std::error_code{})); + EXPECT_TRUE(!(val1 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val1)); + EXPECT_TRUE(!(val2 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val2)); + EXPECT_TRUE(val1 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val1); + EXPECT_TRUE(val2 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val2); + EXPECT_TRUE(!val1.matches(std::error_condition{})); + EXPECT_TRUE(!val2.matches(std::error_condition{})); + + val1 = make_error_code(common_error::kInvalidArgument); + + ASSERT_TRUE(val1.has_error()); + ASSERT_TRUE(val2.has_value()); + EXPECT_TRUE(!val1); + EXPECT_TRUE(bool(val2)); + EXPECT_TRUE(!val1.has_value()); + EXPECT_TRUE(!val2.has_error()); + EXPECT_EQ(val1.error(), common_error::kInvalidArgument); + EXPECT_TRUE(val1 == common_error::kInvalidArgument); + EXPECT_TRUE(common_error::kInvalidArgument == val1); + EXPECT_STREQ(val2.value().c_str(), "foobar"); + EXPECT_TRUE(*val2 == "foobar"); + EXPECT_TRUE(boost::equals(val2->c_str(), "foobar")); + EXPECT_NE(val1.error(), std::error_code{}); + EXPECT_EQ(val2.error(), std::error_code{}); + EXPECT_TRUE(val1.equal(common_error::kInvalidArgument)); + EXPECT_TRUE(!val2.equal(std::error_code{})); + EXPECT_TRUE(!(val1 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val1)); + EXPECT_TRUE(!(val2 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val2)); + EXPECT_TRUE(val1 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val1); + EXPECT_TRUE(val2 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val2); + EXPECT_TRUE(val1.matches(std::errc::invalid_argument)); + EXPECT_TRUE(!val1.matches(std::error_condition{})); + EXPECT_TRUE(!val2.matches(std::error_condition{})); + + val2 = val1; + + ASSERT_TRUE(val1.has_error()); + ASSERT_TRUE(val2.has_error()); + EXPECT_TRUE(!val1); + EXPECT_TRUE(!val2); + EXPECT_TRUE(!val1.has_value()); + EXPECT_TRUE(!val2.has_value()); + EXPECT_EQ(val1.error(), common_error::kInvalidArgument); + EXPECT_TRUE(val1 == common_error::kInvalidArgument); + EXPECT_TRUE(common_error::kInvalidArgument == val1); + EXPECT_EQ(val2.error(), common_error::kInvalidArgument); + EXPECT_TRUE(val2 == common_error::kInvalidArgument); + EXPECT_TRUE(common_error::kInvalidArgument == val2); + EXPECT_NE(val1.error(), std::error_code{}); + EXPECT_NE(val2.error(), std::error_code{}); + EXPECT_TRUE(val1.equal(common_error::kInvalidArgument)); + EXPECT_TRUE(val2.equal(common_error::kInvalidArgument)); + EXPECT_TRUE(!(val1 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val1)); + EXPECT_TRUE(!(val2 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val2)); + EXPECT_TRUE(val1 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val1); + EXPECT_TRUE(val2 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val2); + EXPECT_TRUE(val1.matches(std::errc::invalid_argument)); + EXPECT_TRUE(val2.matches(std::errc::invalid_argument)); + EXPECT_TRUE(!val1.matches(std::error_condition{})); + EXPECT_TRUE(!val2.matches(std::error_condition{})); + + val1 = std::string{"barfoo"}; + + ASSERT_TRUE(val1.has_value()); + ASSERT_TRUE(val2.has_error()); + EXPECT_TRUE(bool(val1)); + EXPECT_TRUE(!val2); + EXPECT_TRUE(!val1.has_error()); + EXPECT_TRUE(!val2.has_value()); + EXPECT_STREQ(val1.value().c_str(), "barfoo"); + EXPECT_TRUE(*val1 == "barfoo"); + EXPECT_TRUE(boost::equals(val1->c_str(), "barfoo")); + EXPECT_EQ(val2.error(), common_error::kInvalidArgument); + EXPECT_TRUE(val2 == common_error::kInvalidArgument); + EXPECT_TRUE(common_error::kInvalidArgument == val2); + EXPECT_EQ(val1.error(), std::error_code{}); + EXPECT_NE(val2.error(), std::error_code{}); + EXPECT_TRUE(!val1.equal(std::error_code{})); + EXPECT_TRUE(val2.equal(common_error::kInvalidArgument)); + EXPECT_TRUE(!(val1 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val1)); + EXPECT_TRUE(!(val2 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val2)); + EXPECT_TRUE(val1 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val1); + EXPECT_TRUE(val2 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val2); + EXPECT_TRUE(val2.matches(std::errc::invalid_argument)); + EXPECT_TRUE(!val1.matches(std::error_condition{})); + EXPECT_TRUE(!val2.matches(std::error_condition{})); + + val2 = val1; + + ASSERT_TRUE(val1.has_value()); + ASSERT_TRUE(val2.has_value()); + EXPECT_TRUE(bool(val1)); + EXPECT_TRUE(bool(val2)); + EXPECT_TRUE(!val1.has_error()); + EXPECT_TRUE(!val2.has_error()); + EXPECT_EQ(val1.value(), "barfoo"); + EXPECT_TRUE(*val1 == "barfoo"); + EXPECT_TRUE(boost::equals(val1->c_str(), "barfoo")); + EXPECT_EQ(val2.value(), "barfoo"); + EXPECT_TRUE(*val2 == "barfoo"); + EXPECT_TRUE(boost::equals(val2->c_str(), "barfoo")); + EXPECT_EQ(val1.error(), std::error_code{}); + EXPECT_EQ(val2.error(), std::error_code{}); + EXPECT_TRUE(!val1.equal(std::error_code{})); + EXPECT_TRUE(!val2.equal(std::error_code{})); + EXPECT_TRUE(!(val1 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val1)); + EXPECT_TRUE(!(val2 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val2)); + EXPECT_TRUE(val1 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val1); + EXPECT_TRUE(val2 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val2); + EXPECT_TRUE(!val1.matches(std::error_condition{})); + EXPECT_TRUE(!val2.matches(std::error_condition{})); +} + +TEST(Expect, AssignmentThrowsOnMove) +{ + struct construct_error {}; + struct assignment_error {}; + + struct throw_on_move { + std::string msg; + + throw_on_move(const char* msg) : msg(msg) {} + throw_on_move(throw_on_move&&) { + throw construct_error{}; + } + throw_on_move(throw_on_move const&) = default; + ~throw_on_move() = default; + throw_on_move& operator=(throw_on_move&&) { + throw assignment_error{}; + } + throw_on_move& operator=(throw_on_move const&) = default; + }; + + expect val1{expect{"foobar"}}; + expect val2{common_error::kInvalidArgument}; + + ASSERT_TRUE(val1.has_value()); + ASSERT_TRUE(val2.has_error()); + EXPECT_TRUE(!val1.has_error()); + EXPECT_TRUE(!val2.has_value()); + EXPECT_STREQ(val1->msg.c_str(), "foobar"); + EXPECT_EQ(val2.error(), common_error::kInvalidArgument); + + EXPECT_THROW(val2 = std::move(val1), construct_error); + + ASSERT_TRUE(val1.has_value()); + ASSERT_TRUE(val2.has_error()); + EXPECT_TRUE(!val1.has_error()); + EXPECT_TRUE(!val2.has_value()); + EXPECT_STREQ(val1->msg.c_str(), "foobar"); + EXPECT_EQ(val2.error(), common_error::kInvalidArgument); + + EXPECT_THROW(val1 = expect{"barfoo"}, assignment_error); + + ASSERT_TRUE(val1.has_value()); + ASSERT_TRUE(val2.has_error()); + EXPECT_TRUE(!val1.has_error()); + EXPECT_TRUE(!val2.has_value()); + EXPECT_STREQ(val1->msg.c_str(), "foobar"); + EXPECT_EQ(val2.error(), common_error::kInvalidArgument); + + EXPECT_NO_THROW(val2 = val1); + + ASSERT_TRUE(val1.has_value()); + ASSERT_TRUE(val2.has_value()); + EXPECT_TRUE(!val1.has_error()); + EXPECT_TRUE(!val2.has_error()); + EXPECT_STREQ(val1->msg.c_str(), "foobar"); + EXPECT_STREQ(val2->msg.c_str(), "foobar"); +} + +TEST(Expect, EqualWithStrings) +{ + expect val1{std::string{}}; + expect val2{"barfoo"}; + expect val3{boost::string_ref{}}; + + EXPECT_TRUE(!val1.equal(val2)); + EXPECT_TRUE(val1.equal(val3)); + EXPECT_TRUE(!val2.equal(val1)); + EXPECT_TRUE(!val2.equal(val3)); + EXPECT_TRUE(val3.equal(val1)); + EXPECT_TRUE(!val3.equal(val2)); + EXPECT_TRUE(!(val1 == val2)); + EXPECT_TRUE(!(val2 == val1)); + EXPECT_TRUE(val1 == val3); + EXPECT_TRUE(val3 == val1); + EXPECT_TRUE(!(val2 == val3)); + EXPECT_TRUE(!(val3 == val2)); + EXPECT_TRUE(val1 != val2); + EXPECT_TRUE(val2 != val1); + EXPECT_TRUE(!(val1 != val3)); + EXPECT_TRUE(!(val3 != val1)); + EXPECT_TRUE(val2 != val3); + EXPECT_TRUE(val3 != val2); + + EXPECT_TRUE(val1.equal("")); + EXPECT_TRUE(val2.equal("barfoo")); + EXPECT_TRUE(val3.equal("")); + EXPECT_TRUE(!val1.equal(std::error_code{})); + EXPECT_TRUE(!val2.equal(std::error_code{})); + EXPECT_TRUE(!val3.equal(std::error_code{})); + EXPECT_TRUE(val1 == ""); + EXPECT_TRUE("" == val1); + EXPECT_TRUE(val2 == "barfoo"); + EXPECT_TRUE("barfoo" == val2); + EXPECT_TRUE(val3 == ""); + EXPECT_TRUE("" == val3); + EXPECT_TRUE(!(val1 != "")); + EXPECT_TRUE(!("" != val1)); + EXPECT_TRUE(!(val2 != "barfoo")); + EXPECT_TRUE(!("barfoo" != val2)); + EXPECT_TRUE(!(val3 != "")); + EXPECT_TRUE(!("" != val3)); + EXPECT_TRUE(!(val1 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val1)); + EXPECT_TRUE(!(val2 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val2)); + EXPECT_TRUE(!(val3 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val3)); + EXPECT_TRUE(val1 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val1); + EXPECT_TRUE(val2 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val2); + EXPECT_TRUE(val3 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val3); + EXPECT_TRUE(!val1.matches(std::error_condition{})); + EXPECT_TRUE(!val2.matches(std::error_condition{})); + EXPECT_TRUE(!val3.matches(std::error_condition{})); + + val2 = make_error_code(common_error::kInvalidArgument); + + EXPECT_TRUE(!val1.equal(val2)); + EXPECT_TRUE(val1.equal(val3)); + EXPECT_TRUE(!val2.equal(val1)); + EXPECT_TRUE(!val2.equal(val3)); + EXPECT_TRUE(val3.equal(val1)); + EXPECT_TRUE(!val3.equal(val2)); + EXPECT_TRUE(!(val1 == val2)); + EXPECT_TRUE(!(val2 == val1)); + EXPECT_TRUE(val1 == val3); + EXPECT_TRUE(val3 == val1); + EXPECT_TRUE(!(val2 == val3)); + EXPECT_TRUE(!(val3 == val2)); + EXPECT_TRUE(val1 != val2); + EXPECT_TRUE(val2 != val1); + EXPECT_TRUE(!(val1 != val3)); + EXPECT_TRUE(!(val3 != val1)); + EXPECT_TRUE(val2 != val3); + EXPECT_TRUE(val3 != val2); + + EXPECT_TRUE(!val1.equal(common_error::kInvalidArgument)); + EXPECT_TRUE(val2.equal(common_error::kInvalidArgument)); + EXPECT_TRUE(!val3.equal(common_error::kInvalidArgument)); + EXPECT_TRUE(val2 == common_error::kInvalidArgument); + EXPECT_TRUE(common_error::kInvalidArgument == val2); + EXPECT_TRUE(!(val2 != common_error::kInvalidArgument)); + EXPECT_TRUE(!(common_error::kInvalidArgument != val2)); + EXPECT_TRUE(val2.matches(std::errc::invalid_argument)); + EXPECT_TRUE(!val2.matches(std::error_condition{})); + + val1 = expect{"barfoo"}; + + EXPECT_TRUE(!val1.equal(val2)); + EXPECT_TRUE(!val1.equal(val3)); + EXPECT_TRUE(!val2.equal(val1)); + EXPECT_TRUE(!val2.equal(val3)); + EXPECT_TRUE(!val3.equal(val1)); + EXPECT_TRUE(!val3.equal(val2)); + EXPECT_TRUE(!(val1 == val2)); + EXPECT_TRUE(!(val2 == val1)); + EXPECT_TRUE(!(val1 == val3)); + EXPECT_TRUE(!(val3 == val1)); + EXPECT_TRUE(!(val2 == val3)); + EXPECT_TRUE(!(val3 == val2)); + EXPECT_TRUE(val1 != val2); + EXPECT_TRUE(val2 != val1); + EXPECT_TRUE(val1 != val3); + EXPECT_TRUE(val3 != val1); + EXPECT_TRUE(val2 != val3); + EXPECT_TRUE(val3 != val2); + + EXPECT_TRUE(val1.equal("barfoo")); + EXPECT_TRUE(val1 == "barfoo"); + EXPECT_TRUE("barfoo" == val1); + EXPECT_TRUE(!(val1 != "barfoo")); + EXPECT_TRUE(!("barfoo" != val1)); + EXPECT_TRUE(!val1.equal(common_error::kInvalidArgument)); + EXPECT_TRUE(!(val1 == common_error::kInvalidArgument)); + EXPECT_TRUE(!(common_error::kInvalidArgument == val1)); + EXPECT_TRUE(!(val1 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val1)); + EXPECT_TRUE(!val1.matches(std::error_condition{})); + EXPECT_TRUE(!val1.matches(std::errc::invalid_argument)); +} + +TEST(Expect, EqualWithVoid) +{ + const expect val1; + expect val2; + + EXPECT_TRUE(val1.equal(val2)); + EXPECT_TRUE(val2.equal(val1)); + EXPECT_TRUE(!val1.equal(std::error_code{})); + EXPECT_TRUE(!val2.equal(std::error_code{})); + EXPECT_TRUE(val1 == val2); + EXPECT_TRUE(val2 == val1); + EXPECT_TRUE(!(val1 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val1)); + EXPECT_TRUE(!(val2 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val2)); + EXPECT_TRUE(!(val1 != val2)); + EXPECT_TRUE(!(val2 != val1)); + EXPECT_TRUE(val1 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val1); + EXPECT_TRUE(!(val2 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val2)); + + val2 = make_error_code(common_error::kInvalidArgument); + + EXPECT_TRUE(!val1.equal(val2)); + EXPECT_TRUE(!val2.equal(val1)); + EXPECT_TRUE(!val1.equal(common_error::kInvalidArgument)); + EXPECT_TRUE(val2.equal(common_error::kInvalidArgument)); + EXPECT_TRUE(!val2.equal(std::error_code{})); + EXPECT_TRUE(!(val1 == val2)); + EXPECT_TRUE(!(val2 == val1)); + EXPECT_TRUE(val2 == common_error::kInvalidArgument); + EXPECT_TRUE(common_error::kInvalidArgument == val2); + EXPECT_TRUE(!(val2 == std::error_code{})); + EXPECT_TRUE(!(std::error_code{} == val2)); + EXPECT_TRUE(val1 != val2); + EXPECT_TRUE(val2 != val1); + EXPECT_TRUE(!(val2 != common_error::kInvalidArgument)); + EXPECT_TRUE(!(common_error::kInvalidArgument != val2)); + EXPECT_TRUE(val2 != std::error_code{}); + EXPECT_TRUE(std::error_code{} != val2); +} + +TEST(Expect, EqualNoCopies) +{ + struct copy_error {}; + + struct throw_on_copy { + throw_on_copy() = default; + throw_on_copy(int) noexcept {} + throw_on_copy(throw_on_copy const&) { + throw copy_error{}; + } + ~throw_on_copy() = default; + throw_on_copy& operator=(throw_on_copy const&) { + throw copy_error{}; + } + + bool operator==(throw_on_copy const&) const noexcept { return true; } + }; + + expect val1{expect{0}}; + expect val2{expect{0}}; + + EXPECT_TRUE(val1.equal(val2)); + EXPECT_TRUE(val2.equal(val1)); + EXPECT_TRUE(val1 == val2); + EXPECT_TRUE(val2 == val1); + EXPECT_TRUE(!(val1 != val2)); + EXPECT_TRUE(!(val2 != val1)); + + EXPECT_TRUE(val1.equal(throw_on_copy{})); + EXPECT_TRUE(val1 == throw_on_copy{}); + EXPECT_TRUE(throw_on_copy{} == val1); + EXPECT_TRUE(!(val1 != throw_on_copy{})); + EXPECT_TRUE(!(throw_on_copy{} != val1)); + + throw_on_copy val3; + + EXPECT_TRUE(val1.equal(val3)); + EXPECT_TRUE(val1 == val3); + EXPECT_TRUE(val3 == val1); + EXPECT_TRUE(!(val1 != val3)); + EXPECT_TRUE(!(val3 != val1)); + + expect val4{common_error::kInvalidArgument}; + + EXPECT_TRUE(!val4.equal(throw_on_copy{})); + EXPECT_TRUE(!(val4 == throw_on_copy{})); + EXPECT_TRUE(!(throw_on_copy{} == val4)); + EXPECT_TRUE(val4 != throw_on_copy{}); + EXPECT_TRUE(throw_on_copy{} != val4); + EXPECT_TRUE(!val4.equal(val3)); + EXPECT_TRUE(!(val4 == val3)); + EXPECT_TRUE(!(val3 == val4)); + EXPECT_TRUE(val4 != val3); + EXPECT_TRUE(val3 != val4); +} + +TEST(Expect, Macros) { + EXPECT_TRUE( + [] () -> ::common_error { + MONERO_PRECOND(true); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidErrorCode + ); + EXPECT_TRUE( + [] () -> ::common_error { + MONERO_PRECOND(false); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidArgument + ); + EXPECT_TRUE( + [] () -> std::error_code { + MONERO_PRECOND(true); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidErrorCode + ); + EXPECT_TRUE( + [] () -> std::error_code { + MONERO_PRECOND(false); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidArgument + ); + EXPECT_TRUE( + [] () -> expect { + MONERO_PRECOND(true); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidErrorCode + ); + EXPECT_TRUE( + [] () -> expect { + MONERO_PRECOND(false); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidArgument + ); + EXPECT_TRUE( + [] () -> expect { + MONERO_PRECOND(true); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidErrorCode + ); + EXPECT_TRUE( + [] () -> expect { + MONERO_PRECOND(false); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidArgument + ); + + EXPECT_TRUE( + [] () -> std::error_code { + MONERO_CHECK(expect{}); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidErrorCode + ); + EXPECT_TRUE( + [] () -> std::error_code { + MONERO_CHECK(expect{common_error::kInvalidArgument}); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidArgument + ); + EXPECT_TRUE( + [] () -> expect { + MONERO_CHECK(expect{}); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidErrorCode + ); + EXPECT_TRUE( + [] () -> expect { + MONERO_CHECK(expect{common_error::kInvalidArgument}); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidArgument + ); + EXPECT_TRUE( + [] () -> expect { + MONERO_CHECK(expect{}); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidErrorCode + ); + EXPECT_TRUE( + [] () -> expect { + MONERO_CHECK(expect{common_error::kInvalidArgument}); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidArgument + ); + + EXPECT_NO_THROW(MONERO_UNWRAP(success())); + EXPECT_NO_THROW(MONERO_UNWRAP(expect{})); + EXPECT_NO_THROW(MONERO_UNWRAP(expect{0})); + EXPECT_THROW( + MONERO_UNWRAP(expect{common_error::kInvalidArgument}), std::system_error + ); + EXPECT_THROW( + MONERO_UNWRAP(expect{common_error::kInvalidArgument}), std::system_error + ); +} +