diff options
author | Lee Clagett <code@leeclagett.com> | 2018-09-10 21:25:15 -0400 |
---|---|---|
committer | Lee Clagett <code@leeclagett.com> | 2018-09-12 22:44:34 +0000 |
commit | 55c7cd14582323b4403bed88bd02e2f62d2a3169 (patch) | |
tree | 6680eb713cd4c98925212a0339037f774eb79718 | |
parent | Merge pull request #4290 (diff) | |
download | monero-55c7cd14582323b4403bed88bd02e2f62d2a3169.tar.xz |
Adding expect<T> - a value-or-error implementation
-rw-r--r-- | src/common/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/common/error.cpp | 75 | ||||
-rw-r--r-- | src/common/error.h | 52 | ||||
-rw-r--r-- | src/common/expect.cpp | 70 | ||||
-rw-r--r-- | src/common/expect.h | 447 | ||||
-rw-r--r-- | tests/unit_tests/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/unit_tests/expect.cpp | 915 |
7 files changed, 1564 insertions, 0 deletions
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 <string> + +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<T> 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 <system_error> +#include <type_traits> + +enum class common_error : int +{ + // 0 is reserved for no error, as per expect<T> + kInvalidArgument = 1, //!< A function argument is invalid + kInvalidErrorCode //!< Default `std::error_code` given to `expect<T>` +}; + +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 <easylogging++.h> +#include <string> + +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 <cassert> +#include <system_error> +#include <type_traits> +#include <utility> + +#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<void>` and return errors in current scope. +#define MONERO_CHECK(...) \ + do \ + { \ + const ::expect<void> result = __VA_ARGS__ ; \ + if (!result) \ + return result.error(); \ + } while (0) + +/*! Get `T` from `expect<T>` by `std::move` as-if by function call. + `expect<void>` returns nothing. + + \throw std::system_error with `expect<T>::error()`, filename and line + number when `expect<T>::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<typename> class expect; + +namespace detail +{ + // Shortens the characters in the places that `enable_if` is used below. + template<bool C> + using enable_if = typename std::enable_if<C>::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<typename T> + static T unwrap(::expect<T>&& 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<void>&& result, const char* error_msg, const char* file, unsigned line); + }; +} + +/*! + `expect<T>` 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<T>` is implicitly convertible from `T` + or `std::error_code`, and one `expect<T>` object type is implicitly + convertible to another `expect<U>` 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<int>{100} == expect<short>{100});`). Comparisons can also be + done against `std::error_code` objects or error code enums directly (i.e. + `assert(expect<int>{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<int>{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<void>` 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<void>`. + + \note See `src/common/error.h` for creating a custom error enum. + */ +template<typename T> +class expect +{ + static_assert(std::is_nothrow_destructible<T>(), "T must have a nothrow destructor"); + + template<typename U> + static constexpr bool is_convertible() noexcept + { + return std::is_constructible<T, U>() && + std::is_convertible<U, T>(); + } + + // MEMBERS + std::error_code code_; + typename std::aligned_storage<sizeof(T), alignof(T)>::type storage_; + // MEMBERS + + T& get() noexcept + { + assert(has_value()); + return *reinterpret_cast<T*>(std::addressof(storage_)); + } + + T const& get() const noexcept + { + assert(has_value()); + return *reinterpret_cast<T const*>(std::addressof(storage_)); + } + + template<typename U> + void store(U&& value) noexcept(std::is_nothrow_constructible<T, U>()) + { + new (std::addressof(storage_)) T{std::forward<U>(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<T>()) + : code_(), storage_() + { + store(std::move(val)); + } + + expect(expect const& src) noexcept(std::is_nothrow_copy_constructible<T>()) + : code_(src.error()), storage_() + { + if (src.has_value()) + store(src.get()); + } + + //! Copy conversion from `U` to `T`. + template<typename U, typename = detail::enable_if<is_convertible<U const&>()>> + expect(expect<U> const& src) noexcept(std::is_nothrow_constructible<T, U const&>()) + : code_(src.error()), storage_() + { + if (src.has_value()) + store(*src); + } + + expect(expect&& src) noexcept(std::is_nothrow_move_constructible<T>()) + : code_(src.error()), storage_() + { + if (src.has_value()) + store(std::move(src.get())); + } + + //! Move conversion from `U` to `T`. + template<typename U, typename = detail::enable_if<is_convertible<U>()>> + expect(expect<U>&& src) noexcept(std::is_nothrow_constructible<T, U>()) + : 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<T>() && std::is_nothrow_copy_assignable<T>()) + { + 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<T>() && std::is_nothrow_move_assignable<T>()) + { + 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<typename U> + bool equal(expect<U> const& rhs) const noexcept(noexcept(*std::declval<expect<T>>() == *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<typename U, typename = detail::enable_if<!std::is_constructible<std::error_code, U>::value>> + bool equal(U const& rhs) const noexcept(noexcept(*std::declval<expect<T>>() == 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<void> +{ + 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<void>` object with `!has_error()`. +inline expect<void> success() noexcept { return expect<void>{}; } + +template<typename T, typename U> +inline +bool operator==(expect<T> const& lhs, expect<U> const& rhs) noexcept(noexcept(lhs.equal(rhs))) +{ + return lhs.equal(rhs); +} + +template<typename T, typename U> +inline +bool operator==(expect<T> const& lhs, U const& rhs) noexcept(noexcept(lhs.equal(rhs))) +{ + return lhs.equal(rhs); +} + +template<typename T, typename U> +inline +bool operator==(T const& lhs, expect<U> const& rhs) noexcept(noexcept(rhs.equal(lhs))) +{ + return rhs.equal(lhs); +} + +template<typename T, typename U> +inline +bool operator!=(expect<T> const& lhs, expect<U> const& rhs) noexcept(noexcept(lhs.equal(rhs))) +{ + return !lhs.equal(rhs); +} + +template<typename T, typename U> +inline +bool operator!=(expect<T> const& lhs, U const& rhs) noexcept(noexcept(lhs.equal(rhs))) +{ + return !lhs.equal(rhs); +} + +template<typename T, typename U> +inline +bool operator!=(T const& lhs, expect<U> const& rhs) noexcept(noexcept(rhs.equal(lhs))) +{ + return !rhs.equal(lhs); +} + +namespace detail +{ + inline void expect::unwrap(::expect<void>&& 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 <gtest/gtest.h> + +#include <boost/algorithm/string/predicate.hpp> +#include <boost/utility/string_ref.hpp> +#include <string> +#include <system_error> +#include <type_traits> + +#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<typename T> + void construction_bench() + { + EXPECT_TRUE(std::is_copy_constructible<expect<T>>()); + EXPECT_TRUE(std::is_move_constructible<expect<T>>()); + EXPECT_TRUE(std::is_copy_assignable<expect<T>>()); + EXPECT_TRUE(std::is_move_assignable<expect<T>>()); + EXPECT_TRUE(std::is_destructible<expect<T>>()); + } + + template<typename T> + void noexcept_bench() + { + EXPECT_TRUE(std::is_nothrow_copy_constructible<expect<T>>()); + EXPECT_TRUE(std::is_nothrow_move_constructible<expect<T>>()); + EXPECT_TRUE(std::is_nothrow_copy_assignable<expect<T>>()); + EXPECT_TRUE(std::is_nothrow_move_assignable<expect<T>>()); + EXPECT_TRUE(std::is_nothrow_destructible<expect<T>>()); + + EXPECT_TRUE(noexcept(bool(std::declval<expect<T>>()))); + EXPECT_TRUE(noexcept(std::declval<expect<T>>().has_error())); + EXPECT_TRUE(noexcept(std::declval<expect<T>>().error())); + EXPECT_TRUE(noexcept(std::declval<expect<T>>().equal(std::declval<expect<T>>()))); + EXPECT_TRUE(noexcept(std::declval<expect<T>>() == std::declval<expect<T>>())); + EXPECT_TRUE(noexcept(std::declval<expect<T>>() != std::declval<expect<T>>())); + } + + template<typename T> + void conversion_bench() + { + EXPECT_TRUE((std::is_convertible<std::error_code, expect<T>>())); + EXPECT_TRUE((std::is_convertible<std::error_code&&, expect<T>>())); + EXPECT_TRUE((std::is_convertible<std::error_code&, expect<T>>())); + EXPECT_TRUE((std::is_convertible<std::error_code const&, expect<T>>())); + + EXPECT_TRUE((std::is_constructible<expect<T>, std::error_code>())); + EXPECT_TRUE((std::is_constructible<expect<T>, std::error_code&&>())); + EXPECT_TRUE((std::is_constructible<expect<T>, std::error_code&>())); + EXPECT_TRUE((std::is_constructible<expect<T>, std::error_code const&>())); + } +} + + +TEST(Expect, Constructions) +{ + construction_bench<void>(); + construction_bench<int>(); + + EXPECT_TRUE(std::is_constructible<expect<void>>()); + + EXPECT_TRUE((std::is_constructible<expect<throw_construct>, expect<int>>())); + + EXPECT_TRUE(std::is_move_constructible<expect<move_only>>()); + EXPECT_TRUE(std::is_move_assignable<expect<move_only>>()); +} + +TEST(Expect, Conversions) +{ + struct implicit { implicit(int) {} }; + struct explicit_only { explicit explicit_only(int) {} }; + + conversion_bench<void>(); + conversion_bench<int>(); + + EXPECT_TRUE((std::is_convertible<int, expect<int>>())); + EXPECT_TRUE((std::is_convertible<int&&, expect<int>>())); + EXPECT_TRUE((std::is_convertible<int&, expect<int>>())); + EXPECT_TRUE((std::is_convertible<int const, expect<int>>())); + EXPECT_TRUE((std::is_convertible<expect<unsigned>, expect<int>>())); + EXPECT_TRUE((std::is_convertible<expect<unsigned>&&, expect<int>>())); + EXPECT_TRUE((std::is_convertible<expect<unsigned>&, expect<int>>())); + EXPECT_TRUE((std::is_convertible<expect<unsigned> const&, expect<int>>())); + EXPECT_TRUE((std::is_convertible<expect<int>, expect<implicit>>())); + EXPECT_TRUE((std::is_convertible<expect<int>&&, expect<implicit>>())); + EXPECT_TRUE((std::is_convertible<expect<int>&, expect<implicit>>())); + EXPECT_TRUE((std::is_convertible<expect<int> const&, expect<implicit>>())); + EXPECT_TRUE(!(std::is_convertible<expect<int>, expect<explicit_only>>())); + EXPECT_TRUE(!(std::is_convertible<expect<int>&&, expect<explicit_only>>())); + EXPECT_TRUE(!(std::is_convertible<expect<int>&, expect<explicit_only>>())); + EXPECT_TRUE(!(std::is_convertible<expect<int> const&, expect<explicit_only>>())); + + EXPECT_TRUE((std::is_constructible<expect<int>, int>())); + EXPECT_TRUE((std::is_constructible<expect<int>, int&&>())); + EXPECT_TRUE((std::is_constructible<expect<int>, int&>())); + EXPECT_TRUE((std::is_constructible<expect<int>, int const&>())); + EXPECT_TRUE((std::is_constructible<expect<int>, expect<unsigned>>())); + EXPECT_TRUE((std::is_constructible<expect<int>, expect<unsigned>&&>())); + EXPECT_TRUE((std::is_constructible<expect<int>, expect<unsigned>&>())); + EXPECT_TRUE((std::is_constructible<expect<int>, expect<unsigned> const&>())); + EXPECT_TRUE((std::is_constructible<expect<implicit>, expect<int>>())); + EXPECT_TRUE((std::is_constructible<expect<implicit>, expect<int>&&>())); + EXPECT_TRUE((std::is_constructible<expect<implicit>, expect<int>&>())); + EXPECT_TRUE((std::is_constructible<expect<implicit>, expect<int> const&>())); + EXPECT_TRUE(!(std::is_constructible<expect<explicit_only>, expect<int>>())); + EXPECT_TRUE(!(std::is_constructible<expect<explicit_only>, expect<int>&&>())); + EXPECT_TRUE(!(std::is_constructible<expect<explicit_only>, expect<int>&>())); + EXPECT_TRUE(!(std::is_constructible<expect<explicit_only>, expect<int> const&>())); + + EXPECT_EQ(expect<int>{expect<short>{100}}.value(), 100); + + expect<std::string> val1{std::string{}}; + expect<const char*> val2{"foo"}; + + EXPECT_EQ(val1.value(), std::string{}); + EXPECT_EQ(val2.value(), std::string{"foo"}); + + const expect<std::string> 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<void>(); + noexcept_bench<int>(); + + EXPECT_TRUE(std::is_nothrow_constructible<expect<void>>()); + + EXPECT_TRUE((std::is_nothrow_constructible<expect<int>, int>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<int>, expect<unsigned>>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<int>, expect<unsigned>&&>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<int>, expect<unsigned>&>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<int>, expect<unsigned> const&>())); + + EXPECT_TRUE(noexcept(expect<int>{std::declval<expect<unsigned>&&>()})); + EXPECT_TRUE(noexcept(expect<int>{std::declval<expect<unsigned> const&>()})); + EXPECT_TRUE(noexcept(std::declval<expect<int>>().has_value())); + EXPECT_TRUE(noexcept(*std::declval<expect<int>>())); + EXPECT_TRUE(noexcept(std::declval<expect<int>>().equal(std::declval<expect<unsigned>>()))); + EXPECT_TRUE(noexcept(std::declval<expect<unsigned>>().equal(std::declval<expect<int>>()))); + EXPECT_TRUE(noexcept(std::declval<expect<int>>().equal(0))); + EXPECT_TRUE(noexcept(std::declval<expect<int>>() == std::declval<expect<unsigned>>())); + EXPECT_TRUE(noexcept(std::declval<expect<unsigned>>() == std::declval<expect<int>>())); + EXPECT_TRUE(noexcept(std::declval<expect<int>>() == 0)); + EXPECT_TRUE(noexcept(0 == std::declval<expect<int>>())); + EXPECT_TRUE(noexcept(std::declval<expect<int>>() != std::declval<expect<unsigned>>())); + EXPECT_TRUE(noexcept(std::declval<expect<unsigned>>() != std::declval<expect<int>>())); + EXPECT_TRUE(noexcept(std::declval<expect<int>>() != 0)); + EXPECT_TRUE(noexcept(0 != std::declval<expect<int>>())); + + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_construct>, std::error_code>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_construct>, std::error_code&&>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_construct>, std::error_code&>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_construct>, std::error_code const&>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_construct>, throw_construct>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_construct>, throw_construct&&>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_construct>, throw_construct&>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_construct>, throw_construct const&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible<expect<throw_construct>, expect<int>>())); + EXPECT_TRUE(!(std::is_nothrow_constructible<expect<throw_construct>, expect<int>&&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible<expect<throw_construct>, expect<int>&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible<expect<throw_construct>, expect<int> const&>())); + EXPECT_TRUE(std::is_nothrow_copy_constructible<expect<throw_construct>>()); + EXPECT_TRUE(std::is_nothrow_move_constructible<expect<throw_construct>>()); + EXPECT_TRUE(std::is_nothrow_copy_assignable<expect<throw_construct>>()); + EXPECT_TRUE(std::is_nothrow_move_assignable<expect<throw_construct>>()); + EXPECT_TRUE(std::is_nothrow_destructible<expect<throw_construct>>()); + + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_copies>, std::error_code>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_copies>, std::error_code&&>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_copies>, std::error_code&>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_copies>, std::error_code const&>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_copies>, throw_copies>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_copies>, throw_copies&&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible<expect<throw_copies>, throw_copies&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible<expect<throw_copies>, throw_copies const&>())); + EXPECT_TRUE(!std::is_nothrow_copy_constructible<expect<throw_copies>>()); + EXPECT_TRUE(std::is_nothrow_move_constructible<expect<throw_copies>>()); + EXPECT_TRUE(!std::is_nothrow_copy_assignable<expect<throw_copies>>()); + EXPECT_TRUE(std::is_nothrow_move_assignable<expect<throw_copies>>()); + EXPECT_TRUE(std::is_nothrow_destructible<expect<throw_copies>>()); + EXPECT_TRUE(noexcept(std::declval<expect<throw_copies>>().equal(std::declval<expect<throw_copies>>()))); + EXPECT_TRUE(noexcept(std::declval<expect<throw_copies>>().equal(std::declval<throw_copies>()))); + EXPECT_TRUE(noexcept(std::declval<expect<throw_copies>>() == std::declval<expect<throw_copies>>())); + EXPECT_TRUE(noexcept(std::declval<expect<throw_copies>>() == std::declval<throw_copies>())); + EXPECT_TRUE(noexcept(std::declval<throw_copies>() == std::declval<expect<throw_copies>>())); + EXPECT_TRUE(noexcept(std::declval<expect<throw_copies>>() != std::declval<expect<throw_copies>>())); + EXPECT_TRUE(noexcept(std::declval<expect<throw_copies>>() != std::declval<throw_copies>())); + EXPECT_TRUE(noexcept(std::declval<throw_copies>() != std::declval<expect<throw_copies>>())); + EXPECT_TRUE(noexcept(std::declval<expect<throw_copies>>().equal(std::declval<expect<throw_moves>>()))); + EXPECT_TRUE(noexcept(std::declval<expect<throw_copies>>().equal(std::declval<throw_moves>()))); + EXPECT_TRUE(noexcept(std::declval<expect<throw_copies>>() == std::declval<expect<throw_moves>>())); + EXPECT_TRUE(noexcept(std::declval<expect<throw_copies>>() == std::declval<throw_moves>())); + EXPECT_TRUE(noexcept(std::declval<throw_moves>() == std::declval<expect<throw_copies>>())); + EXPECT_TRUE(noexcept(std::declval<expect<throw_copies>>() != std::declval<expect<throw_moves>>())); + EXPECT_TRUE(noexcept(std::declval<expect<throw_copies>>() != std::declval<throw_moves>())); + EXPECT_TRUE(noexcept(std::declval<throw_moves>() != std::declval<expect<throw_copies>>())); + + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_moves>, std::error_code>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_moves>, std::error_code&&>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_moves>, std::error_code&>())); + EXPECT_TRUE((std::is_nothrow_constructible<expect<throw_moves>, std::error_code const&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible<expect<throw_moves>, throw_moves>())); + EXPECT_TRUE(!(std::is_nothrow_constructible<expect<throw_moves>, throw_moves&&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible<expect<throw_moves>, throw_moves&>())); + EXPECT_TRUE(!(std::is_nothrow_constructible<expect<throw_moves>, throw_moves const&>())); + EXPECT_TRUE(std::is_nothrow_copy_constructible<expect<throw_moves>>()); + EXPECT_TRUE(!std::is_nothrow_move_constructible<expect<throw_moves>>()); + EXPECT_TRUE(std::is_nothrow_copy_assignable<expect<throw_moves>>()); + EXPECT_TRUE(!std::is_nothrow_move_assignable<expect<throw_moves>>()); + EXPECT_TRUE(std::is_nothrow_destructible<expect<throw_copies>>()); + EXPECT_TRUE(!noexcept(std::declval<expect<throw_moves>>().equal(std::declval<expect<throw_moves>>()))); + EXPECT_TRUE(!noexcept(std::declval<expect<throw_moves>>().equal(std::declval<throw_moves>()))); + EXPECT_TRUE(!noexcept(std::declval<expect<throw_moves>>() == std::declval<expect<throw_moves>>())); + EXPECT_TRUE(!noexcept(std::declval<expect<throw_moves>>() == std::declval<throw_moves>())); + EXPECT_TRUE(!noexcept(std::declval<throw_moves>() == std::declval<expect<throw_moves>>())); + EXPECT_TRUE(!noexcept(std::declval<expect<throw_moves>>() != std::declval<expect<throw_moves>>())); + EXPECT_TRUE(!noexcept(std::declval<expect<throw_moves>>() != std::declval<throw_moves>())); + EXPECT_TRUE(!noexcept(std::declval<throw_moves>() != std::declval<expect<throw_moves>>())); + EXPECT_TRUE(!noexcept(std::declval<expect<throw_moves>>().equal(std::declval<expect<throw_copies>>()))); + EXPECT_TRUE(!noexcept(std::declval<expect<throw_moves>>().equal(std::declval<throw_copies>()))); + EXPECT_TRUE(!noexcept(std::declval<expect<throw_moves>>() == std::declval<expect<throw_copies>>())); + EXPECT_TRUE(!noexcept(std::declval<expect<throw_moves>>() == std::declval<throw_copies>())); + EXPECT_TRUE(!noexcept(std::declval<throw_copies>() == std::declval<expect<throw_moves>>())); + EXPECT_TRUE(!noexcept(std::declval<expect<throw_moves>>() != std::declval<expect<throw_copies>>())); + EXPECT_TRUE(!noexcept(std::declval<expect<throw_moves>>() != std::declval<throw_copies>())); + EXPECT_TRUE(!noexcept(std::declval<throw_copies>() != std::declval<expect<throw_moves>>())); +} + +TEST(Expect, Trivial) +{ + EXPECT_TRUE(std::is_trivially_copy_constructible<expect<void>>()); + EXPECT_TRUE(std::is_trivially_move_constructible<expect<void>>()); + EXPECT_TRUE(std::is_trivially_destructible<expect<void>>()); +} + +TEST(Expect, Assignment) +{ + expect<std::string> val1{std::string{}}; + expect<std::string> 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<throw_on_move> val1{expect<const char*>{"foobar"}}; + expect<throw_on_move> 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<const char*>{"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<std::string> val1{std::string{}}; + expect<std::string> val2{"barfoo"}; + expect<boost::string_ref> 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<std::string>{"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<void> val1; + expect<void> 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<throw_on_copy> val1{expect<int>{0}}; + expect<throw_on_copy> val2{expect<int>{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<throw_on_copy> 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<void> { + MONERO_PRECOND(true); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidErrorCode + ); + EXPECT_TRUE( + [] () -> expect<void> { + MONERO_PRECOND(false); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidArgument + ); + EXPECT_TRUE( + [] () -> expect<int> { + MONERO_PRECOND(true); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidErrorCode + ); + EXPECT_TRUE( + [] () -> expect<int> { + MONERO_PRECOND(false); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidArgument + ); + + EXPECT_TRUE( + [] () -> std::error_code { + MONERO_CHECK(expect<void>{}); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidErrorCode + ); + EXPECT_TRUE( + [] () -> std::error_code { + MONERO_CHECK(expect<void>{common_error::kInvalidArgument}); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidArgument + ); + EXPECT_TRUE( + [] () -> expect<void> { + MONERO_CHECK(expect<void>{}); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidErrorCode + ); + EXPECT_TRUE( + [] () -> expect<void> { + MONERO_CHECK(expect<void>{common_error::kInvalidArgument}); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidArgument + ); + EXPECT_TRUE( + [] () -> expect<int> { + MONERO_CHECK(expect<void>{}); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidErrorCode + ); + EXPECT_TRUE( + [] () -> expect<int> { + MONERO_CHECK(expect<void>{common_error::kInvalidArgument}); + return {common_error::kInvalidErrorCode}; + } () == common_error::kInvalidArgument + ); + + EXPECT_NO_THROW(MONERO_UNWRAP(success())); + EXPECT_NO_THROW(MONERO_UNWRAP(expect<void>{})); + EXPECT_NO_THROW(MONERO_UNWRAP(expect<int>{0})); + EXPECT_THROW( + MONERO_UNWRAP(expect<void>{common_error::kInvalidArgument}), std::system_error + ); + EXPECT_THROW( + MONERO_UNWRAP(expect<int>{common_error::kInvalidArgument}), std::system_error + ); +} + |