From 187b9ceb53979cd44ae7396412b8a93228a23830 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Sat, 27 Oct 2018 21:20:40 -0700 Subject: [PATCH 1/5] [wip] Implement either type --- src/cpp-utils/either.h | 236 ++++++++++++++++++++ test/cpp-utils/CMakeLists.txt | 1 + test/cpp-utils/either_test.cpp | 128 +++++++++++ test/cpp-utils/value_type/ValueTypeTest.cpp | 2 + 4 files changed, 367 insertions(+) create mode 100644 src/cpp-utils/either.h create mode 100644 test/cpp-utils/either_test.cpp diff --git a/src/cpp-utils/either.h b/src/cpp-utils/either.h new file mode 100644 index 00000000..384b6be9 --- /dev/null +++ b/src/cpp-utils/either.h @@ -0,0 +1,236 @@ +#pragma once +#ifndef MESSMER_CPPUTILS_EITHER_H +#define MESSMER_CPPUTILS_EITHER_H + +#include +#include +#include "assert/assert.h" + +namespace cpputils { + + template + class either final { + public: + //TODO Try allowing construction with any type that std::is_convertible to Left or Right. + either(const Left &left) noexcept(noexcept(std::declval>()._construct_left(left))) + : _side(Side::left) { + _construct_left(left); + } + + either(Left &&left) noexcept(noexcept(std::declval>()._construct_left(std::move(left)))) + : _side(Side::left) { + _construct_left(std::move(left)); + } + + either(const Right &right) noexcept(noexcept(std::declval>()._construct_right(right))) + : _side(Side::right) { + _construct_right(right); + } + + either(Right &&right) noexcept(noexcept(std::declval>()._construct_right(std::move(right)))) + : _side(Side::right) { + _construct_right(std::move(right)); + } + + //TODO Try allowing copy-construction when Left/Right types are std::is_convertible + either(const either &rhs) noexcept(noexcept(std::declval>()._construct_left(rhs._left)) && noexcept(std::declval>()._construct_right(rhs._right))) + : _side(rhs._side) { + if(_side == Side::left) { + _construct_left(rhs._left); + } else { + _construct_right(rhs._right); + } + } + + either(either &&rhs) noexcept(noexcept(_construct_left(std::move(rhs._left))) && noexcept(_construct_right(std::move(rhs._right)))) + : _side(rhs._side) { + if(_side == Side::left) { + _construct_left(std::move(rhs._left)); + } else { + _construct_right(std::move(rhs._right)); + } + } + + ~either() { + _destruct(); + } + + //TODO Try allowing copy-assignment when Left/Right types are std::is_convertible + either &operator=(const either &rhs) noexcept(noexcept(_construct_left(rhs._left)) && noexcept(_construct_right(rhs._right))) { + _destruct(); + _side = rhs._side; + if (_side == Side::left) { + _construct_left(rhs._left); + } else { + _construct_right(rhs._right); + } + return *this; + } + + either &operator=(either &&rhs) noexcept(noexcept(_construct_left(std::move(rhs._left))) && noexcept(_construct_right(std::move(rhs._right)))) { + _destruct(); + _side = rhs._side; + if (_side == Side::left) { + _construct_left(std::move(rhs._left)); + } else { + _construct_right(std::move(rhs._right)); + } + return *this; + } + + //TODO fold, map_left, map_right, left_or_else(val), right_or_else(val), left_or_else(func), right_or_else(func) + + bool is_left() const noexcept { + return _side == Side::left; + } + + bool is_right() const noexcept { + return _side == Side::right; + } + + const Left &left() const& { + ASSERT(is_left(), "Tried to get left side of an either which is right."); + return _left; + } + Left &left() & { + return const_cast(const_cast*>(this)->left()); + } + Left &&left() && { + return std::move(left()); + } + + const Right &right() const& { + ASSERT(is_right(), "Tried to get right side of an either which is left."); + return _right; + } + Right &right() & { + return const_cast(const_cast*>(this)->right()); + } + Right &&right() && { + return std::move(right()); + } + + boost::optional left_opt() const& noexcept { + if (_side == Side::left) { + return _left; + } else { + return boost::none; + } + } + boost::optional left_opt() & noexcept { + if (_side == Side::left) { + return _left; + } else { + return boost::none; + } + } + boost::optional left_opt() && noexcept { + if (_side == Side::left) { + return std::move(_left); + } else { + return boost::none; + } + } + + boost::optional right_opt() const& noexcept { + if (_side == Side::right) { + return _right; + } else { + return boost::none; + } + } + boost::optional right_opt() & noexcept { + if (_side == Side::right) { + return _right; + } else { + return boost::none; + } + } + boost::optional right_opt() && noexcept { + if (_side == Side::right) { + return std::move(_right); + } else { + return boost::none; + } + } + + private: + union { + Left _left; + Right _right; + }; + enum class Side : uint8_t {left, right} _side; + + explicit either(Side side) noexcept : _side(side) {} + + template + void _construct_left(Args&&... args) noexcept(noexcept(new Left(std::forward(args)...))) { + new(&_left)Left(std::forward(args)...); + } + template + void _construct_right(Args&&... args) noexcept(noexcept(new Right(std::forward(args)...))) { + new(&_right)Right(std::forward(args)...); + } + void _destruct() noexcept { + if (_side == Side::left) { + _left.~Left(); + } else { + _right.~Right(); + } + } + + template static constexpr bool left_noexcept_constructible(Args&&... args) { + return noexcept(_construct_left(std::forward(args)...)); + } + + template + friend either make_left(Args&&... args) noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...))); + + template + friend either make_right(Args&&... args) noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...))); + }; + + template + bool operator==(const either &lhs, const either &rhs) noexcept(noexcept(std::declval() == std::declval()) && noexcept(std::declval() == std::declval())) { + if (lhs.is_left() != rhs.is_left()) { + return false; + } + if (lhs.is_left()) { + return lhs.left() == rhs.left(); + } else { + return lhs.right() == rhs.right(); + } + } + + template + bool operator!=(const either &lhs, const either &rhs) noexcept(noexcept(operator==(lhs, rhs))) { + return !operator==(lhs, rhs); + } + + template + std::ostream &operator<<(std::ostream &stream, const either &value) { + if (value.is_left()) { + stream << "Left(" << value.left() << ")"; + } else { + stream << "Right(" << value.right() << ")"; + } + return stream; + } + + template + either make_left(Args&&... args) noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...))) { + either result(either::Side::left); + result._construct_left(std::forward(args)...); + return result; + } + + template + either make_right(Args&&... args) noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...))) { + either result(either::Side::right); + result._construct_right(std::forward(args)...); + return result; + } +} + + +#endif diff --git a/test/cpp-utils/CMakeLists.txt b/test/cpp-utils/CMakeLists.txt index 4afd0818..e3a3bb89 100644 --- a/test/cpp-utils/CMakeLists.txt +++ b/test/cpp-utils/CMakeLists.txt @@ -54,6 +54,7 @@ set(SOURCES system/HomedirTest.cpp system/EnvTest.cpp value_type/ValueTypeTest.cpp + either_test.cpp ) add_executable(${PROJECT_NAME}_exit_status process/exit_status.cpp) diff --git a/test/cpp-utils/either_test.cpp b/test/cpp-utils/either_test.cpp new file mode 100644 index 00000000..78920e6c --- /dev/null +++ b/test/cpp-utils/either_test.cpp @@ -0,0 +1,128 @@ +#include +#include + +using std::string; +using cpputils::either; +using cpputils::make_left; +using cpputils::make_right; + +namespace { +class MovableOnly final { +public: + explicit MovableOnly(int value): _value(value) {} + MovableOnly(const MovableOnly&) = delete; + MovableOnly(MovableOnly&&) = default; + MovableOnly& operator=(const MovableOnly&) = delete; + MovableOnly& operator=(MovableOnly&&) = default; + + int value() { + return _value; + } + +private: + int _value; +}; +} + +TEST(EitherTest, givenLeft_thenIsLeft) { + either a(4); + EXPECT_TRUE(a.is_left()); +} + +TEST(EitherTest, givenLeft_thenIsNotRight) { + either a(4); + EXPECT_FALSE(a.is_right()); +} + +TEST(EitherTest, givenLeft_whenQueryingLeft_thenValueIsCorrect) { + either a(4); + EXPECT_EQ(4, a.left()); +} + +TEST(EitherTest, givenLeft_whenQueryingRight_thenThrows) { + either a(4); + EXPECT_ANY_THROW(a.right()); +} + +TEST(EitherTest, givenLeft_whenQueryingOptLeft_thenValueIsCorrect) { + either a(4); + EXPECT_EQ(4, a.left_opt().value()); +} + +TEST(EitherTest, givenLeft_whenQueryingOptRight_thenIsNone) { + either a(4); + EXPECT_EQ(boost::none, a.right_opt()); +} + +TEST(EitherTest, givenRight_thenIsRight) { + either a("4"); + EXPECT_TRUE(a.is_right()); +} + +TEST(EitherTest, givenRight_thenIsNotLeft) { + either a("4"); + EXPECT_FALSE(a.is_left()); +} + +TEST(EitherTest, givenRight_whenQueryingRight_thenValueIsCorrect) { + either a("4"); + EXPECT_EQ("4", a.right()); +} + +TEST(EitherTest, givenRight_whenQueryingLeft_thenThrows) { + either a("4"); + EXPECT_ANY_THROW(a.left()); +} + +TEST(EitherTest, givenRight_whenQueryingRightOpt_thenValueIsCorrect) { + either a("4"); + EXPECT_EQ("4", a.right_opt().value()); +} + +TEST(EitherTest, givenRight_whenQueryingLeftOpt_thenThrows) { + either a("4"); + EXPECT_EQ(boost::none, a.left_opt()); +} + +TEST(EitherTest, whenCopyConstructingLeft_thenValueIsCorrect) { + string a = "4"; + either b(a); + EXPECT_EQ(a, b.left()); +} + +TEST(EitherTest, whenMoveConstructingLeft_thenValueIsCorrect) { + string a = "4"; + either b(std::move(a)); + EXPECT_EQ("4", b.left()); +} + +TEST(EitherTest, whenCopyConstructingRight_thenValueIsCorrect) { + string a = "4"; + either b(a); + EXPECT_EQ(a, b.right()); +} + +TEST(EitherTest, whenMoveConstructingRight_thenValueIsCorrect) { + string a = "4"; + either b(std::move(a)); + EXPECT_EQ("4", b.right()); +} + +//TEST(EitherTest, whenMakingLeft_thenIsLeft) { +// auto a = make_left(4); +// EXPECT_TRUE(a.is_left()); +//} +// +//TEST(EitherTest, whenMakingRight_thenIsRight) { +// auto a = make_right("4"); +// EXPECT_TRUE(a.is_right()); +//} + + +// TODO Test MovableOnly content type +// TODO Test Left == Right (Same type) +// TODO Test operator== and != +// TODO Test copy/move constructor +// TODO Test destruction +// TODO Test make_left / make_right +// TODO Test noexcept tags are correct diff --git a/test/cpp-utils/value_type/ValueTypeTest.cpp b/test/cpp-utils/value_type/ValueTypeTest.cpp index 54d0cd82..53959f91 100644 --- a/test/cpp-utils/value_type/ValueTypeTest.cpp +++ b/test/cpp-utils/value_type/ValueTypeTest.cpp @@ -9,6 +9,8 @@ using cpputils::value_type::OrderedIdValueType; using cpputils::value_type::QuantityValueType; using cpputils::value_type::FlagsValueType; +// TODO Test that noexcept flags are set correctly + namespace { struct MyIdValueType : IdValueType { From 0b03326ca27f5e7e81dfedf19a38fb38feec2be7 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Sat, 22 Dec 2018 10:18:08 +0100 Subject: [PATCH 2/5] Exit after error message when run on Windows 7 --- src/cryfs-cli/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cryfs-cli/main.cpp b/src/cryfs-cli/main.cpp index 3b4cc667..42287cbd 100644 --- a/src/cryfs-cli/main.cpp +++ b/src/cryfs-cli/main.cpp @@ -22,6 +22,7 @@ int main(int argc, const char *argv[]) { #if defined(_MSC_VER) if (!IsWindows10OrGreater()) { std::cerr << "CryFS is currently only supported on Windows 10 (or later)." << std::endl; + exit(1); } #endif From fcc9d45f4199e175219b9f2016358288387593d7 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Sat, 22 Dec 2018 10:25:47 +0100 Subject: [PATCH 3/5] Apply a fix for Win 7 --- src/cpp-utils/network/WinHttpClient.cpp | 4 +++- src/cryfs-cli/main.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cpp-utils/network/WinHttpClient.cpp b/src/cpp-utils/network/WinHttpClient.cpp index 8f26ab94..14458e5c 100644 --- a/src/cpp-utils/network/WinHttpClient.cpp +++ b/src/cpp-utils/network/WinHttpClient.cpp @@ -8,6 +8,7 @@ #include #include #include +#include using boost::none; using boost::optional; @@ -212,7 +213,8 @@ namespace cpputils { namespace { cpputils::unique_ref create_session() { - HttpHandleRAII session_handle = WinHttpOpen(L"cpputils::HttpClient", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + const DWORD dwAccessType = IsWindows8Point1OrGreater() ? WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY; + HttpHandleRAII session_handle = WinHttpOpen(L"cpputils::HttpClient", dwAccessType, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if(nullptr == session_handle.handle) { throw std::runtime_error("Error calling WinHttpOpen. Error code: " + std::to_string(GetLastError())); } diff --git a/src/cryfs-cli/main.cpp b/src/cryfs-cli/main.cpp index 42287cbd..d5831aea 100644 --- a/src/cryfs-cli/main.cpp +++ b/src/cryfs-cli/main.cpp @@ -20,8 +20,8 @@ using std::cerr; int main(int argc, const char *argv[]) { #if defined(_MSC_VER) - if (!IsWindows10OrGreater()) { - std::cerr << "CryFS is currently only supported on Windows 10 (or later)." << std::endl; + if (!IsWindows7SP1OrGreater()) { + std::cerr << "CryFS is currently only supported on Windows 7 SP1 (or later)." << std::endl; exit(1); } #endif From 59d6a10d287a50fb6b2ff7a380273e7c69c1b86d Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Mon, 24 Dec 2018 18:15:08 +0100 Subject: [PATCH 4/5] Fix and add test cases --- src/cpp-utils/either.h | 65 +- test/cpp-utils/either_test.cpp | 1275 ++++++++++++++++++++++++++++++-- 2 files changed, 1224 insertions(+), 116 deletions(-) diff --git a/src/cpp-utils/either.h b/src/cpp-utils/either.h index 384b6be9..d428294d 100644 --- a/src/cpp-utils/either.h +++ b/src/cpp-utils/either.h @@ -11,25 +11,16 @@ namespace cpputils { template class either final { public: - //TODO Try allowing construction with any type that std::is_convertible to Left or Right. - either(const Left &left) noexcept(noexcept(std::declval>()._construct_left(left))) + template::value && !std::is_constructible::value>* = nullptr> + either(Head&& construct_left_head_arg, Tail&&... construct_left_tail_args) /* TODO noexcept(noexcept(std::declval>()._construct_left(std::forward(construct_left_head_arg), std::forward(construct_left_tail_args)...))) */ : _side(Side::left) { - _construct_left(left); + _construct_left(std::forward(construct_left_head_arg), std::forward(construct_left_tail_args)...); } - either(Left &&left) noexcept(noexcept(std::declval>()._construct_left(std::move(left)))) - : _side(Side::left) { - _construct_left(std::move(left)); - } - - either(const Right &right) noexcept(noexcept(std::declval>()._construct_right(right))) - : _side(Side::right) { - _construct_right(right); - } - - either(Right &&right) noexcept(noexcept(std::declval>()._construct_right(std::move(right)))) - : _side(Side::right) { - _construct_right(std::move(right)); + template::value && std::is_constructible::value>* = nullptr> + either(Head&& construct_right_head_arg, Tail&&... construct_right_tail_args) /* TODO noexcept(noexcept(std::declval>()._construct_right(std::forward(construct_right_head_arg), std::forward(construct_right_tail_args)...))) */ + : _side(Side::right) { + _construct_right(std::forward(construct_right_head_arg), std::forward(construct_right_tail_args)...); } //TODO Try allowing copy-construction when Left/Right types are std::is_convertible @@ -42,7 +33,7 @@ namespace cpputils { } } - either(either &&rhs) noexcept(noexcept(_construct_left(std::move(rhs._left))) && noexcept(_construct_right(std::move(rhs._right)))) + either(either &&rhs) /* TODO noexcept(noexcept(_construct_left(std::move(rhs._left))) && noexcept(_construct_right(std::move(rhs._right)))) */ : _side(rhs._side) { if(_side == Side::left) { _construct_left(std::move(rhs._left)); @@ -56,7 +47,7 @@ namespace cpputils { } //TODO Try allowing copy-assignment when Left/Right types are std::is_convertible - either &operator=(const either &rhs) noexcept(noexcept(_construct_left(rhs._left)) && noexcept(_construct_right(rhs._right))) { + either &operator=(const either &rhs) /* TODO noexcept(noexcept(_construct_left(rhs._left)) && noexcept(_construct_right(rhs._right))) */ { _destruct(); _side = rhs._side; if (_side == Side::left) { @@ -67,7 +58,7 @@ namespace cpputils { return *this; } - either &operator=(either &&rhs) noexcept(noexcept(_construct_left(std::move(rhs._left))) && noexcept(_construct_right(std::move(rhs._right)))) { + either &operator=(either &&rhs) /* TODO noexcept(noexcept(_construct_left(std::move(rhs._left))) && noexcept(_construct_right(std::move(rhs._right)))) */ { _destruct(); _side = rhs._side; if (_side == Side::left) { @@ -124,13 +115,7 @@ namespace cpputils { return boost::none; } } - boost::optional left_opt() && noexcept { - if (_side == Side::left) { - return std::move(_left); - } else { - return boost::none; - } - } + // left_opt()&& not offered because optional doesn't work boost::optional right_opt() const& noexcept { if (_side == Side::right) { @@ -146,13 +131,7 @@ namespace cpputils { return boost::none; } } - boost::optional right_opt() && noexcept { - if (_side == Side::right) { - return std::move(_right); - } else { - return boost::none; - } - } + // right_opt()&& not offered because optional doesn't work private: union { @@ -179,19 +158,15 @@ namespace cpputils { } } - template static constexpr bool left_noexcept_constructible(Args&&... args) { - return noexcept(_construct_left(std::forward(args)...)); - } + template + friend either make_left(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...)))*/; template - friend either make_left(Args&&... args) noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...))); - - template - friend either make_right(Args&&... args) noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...))); + friend either make_right(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...)))*/; }; template - bool operator==(const either &lhs, const either &rhs) noexcept(noexcept(std::declval() == std::declval()) && noexcept(std::declval() == std::declval())) { + inline bool operator==(const either &lhs, const either &rhs) noexcept(noexcept(std::declval() == std::declval()) && noexcept(std::declval() == std::declval())) { if (lhs.is_left() != rhs.is_left()) { return false; } @@ -203,12 +178,12 @@ namespace cpputils { } template - bool operator!=(const either &lhs, const either &rhs) noexcept(noexcept(operator==(lhs, rhs))) { + inline bool operator!=(const either &lhs, const either &rhs) noexcept(noexcept(operator==(lhs, rhs))) { return !operator==(lhs, rhs); } template - std::ostream &operator<<(std::ostream &stream, const either &value) { + inline std::ostream &operator<<(std::ostream &stream, const either &value) { if (value.is_left()) { stream << "Left(" << value.left() << ")"; } else { @@ -218,14 +193,14 @@ namespace cpputils { } template - either make_left(Args&&... args) noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...))) { + inline either make_left(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...))) */ { either result(either::Side::left); result._construct_left(std::forward(args)...); return result; } template - either make_right(Args&&... args) noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...))) { + inline either make_right(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...))) */ { either result(either::Side::right); result._construct_right(std::forward(args)...); return result; diff --git a/test/cpp-utils/either_test.cpp b/test/cpp-utils/either_test.cpp index 78920e6c..b5c0a8ce 100644 --- a/test/cpp-utils/either_test.cpp +++ b/test/cpp-utils/either_test.cpp @@ -1,128 +1,1261 @@ #include #include +#include +#include +#include using std::string; +using std::vector; +using std::pair; +using std::tuple; +using std::ostringstream; using cpputils::either; using cpputils::make_left; using cpputils::make_right; +// TODO Test noexcept tags are correct + namespace { class MovableOnly final { public: explicit MovableOnly(int value): _value(value) {} MovableOnly(const MovableOnly&) = delete; - MovableOnly(MovableOnly&&) = default; MovableOnly& operator=(const MovableOnly&) = delete; - MovableOnly& operator=(MovableOnly&&) = default; - int value() { + MovableOnly(MovableOnly&& rhs): _value(rhs._value) { + rhs._value = 0; + } + + MovableOnly& operator=(MovableOnly&& rhs) { + _value = rhs._value; + rhs._value = 0; + return *this; + } + + int value() const { return _value; } private: int _value; }; + +bool operator==(const MovableOnly& lhs, const MovableOnly& rhs) { + return lhs.value() == rhs.value(); } -TEST(EitherTest, givenLeft_thenIsLeft) { - either a(4); - EXPECT_TRUE(a.is_left()); +template +void test_with_matrix(std::vector)>> setups, std::vector> expectations) { + for (const auto& setup: setups) { + for (const auto& expectation: expectations) { + setup(expectation); + } + } } -TEST(EitherTest, givenLeft_thenIsNotRight) { - either a(4); - EXPECT_FALSE(a.is_right()); +template +std::vector&)>> EXPECT_IS_LEFT(const Left& expected) { + return { + [&] (auto& obj) { + EXPECT_TRUE(obj.is_left()); + }, [&] (auto& obj) { + EXPECT_FALSE(obj.is_right()); + }, [&] (auto& obj) { + EXPECT_EQ(expected, obj.left()); + }, [&] (auto& obj) { + EXPECT_EQ(expected, std::move(obj).left()); + }, [&] (auto& obj) { + EXPECT_ANY_THROW(obj.right()); + }, [&] (auto& obj) { + EXPECT_ANY_THROW(std::move(obj).right()); + }, [&] (auto& obj) { + EXPECT_EQ(expected, obj.left_opt().value()); + }, [&] (auto& obj) { + EXPECT_EQ(expected, std::move(obj).left_opt().value()); + }, [&] (auto& obj) { + EXPECT_EQ(boost::none, obj.right_opt()); + }, [&] (auto& obj) { + EXPECT_EQ(boost::none, std::move(obj).right_opt()); + } + }; } -TEST(EitherTest, givenLeft_whenQueryingLeft_thenValueIsCorrect) { - either a(4); - EXPECT_EQ(4, a.left()); +template +std::vector&)>> EXPECT_IS_RIGHT(const Right& expected) { + return { + [&] (auto& obj) { + EXPECT_FALSE(obj.is_left()); + }, [&] (auto& obj) { + EXPECT_TRUE(obj.is_right()); + }, [&] (auto& obj) { + EXPECT_EQ(expected, obj.right()); + }, [&] (auto& obj) { + EXPECT_EQ(expected, std::move(obj).right()); + }, [&] (auto& obj) { + EXPECT_ANY_THROW(obj.left()); + }, [&] (auto& obj) { + EXPECT_ANY_THROW(std::move(obj).left()); + }, [&] (auto& obj) { + EXPECT_EQ(expected, obj.right_opt().value()); + }, [&] (auto& obj) { + EXPECT_EQ(expected, std::move(obj).right_opt().value()); + }, [&] (auto& obj) { + EXPECT_EQ(boost::none, obj.left_opt()); + }, [&] (auto& obj) { + EXPECT_EQ(boost::none, std::move(obj).left_opt()); + } + }; } -TEST(EitherTest, givenLeft_whenQueryingRight_thenThrows) { - either a(4); - EXPECT_ANY_THROW(a.right()); +template +std::vector> EXPECT_IS(const Value& v) { + return { + [&] (auto& obj) { + return obj == v; + } + }; } -TEST(EitherTest, givenLeft_whenQueryingOptLeft_thenValueIsCorrect) { - either a(4); - EXPECT_EQ(4, a.left_opt().value()); +template +struct StoreWith1ByteFlag { + T val; + char flag; +}; + +template +void TestSpaceUsage() { + EXPECT_EQ(std::max(sizeof(StoreWith1ByteFlag), sizeof(StoreWith1ByteFlag)), sizeof(either)); +} } -TEST(EitherTest, givenLeft_whenQueryingOptRight_thenIsNone) { - either a(4); - EXPECT_EQ(boost::none, a.right_opt()); +TEST(EitherTest, SpaceUsage) { + TestSpaceUsage(); + TestSpaceUsage(); + TestSpaceUsage(); + TestSpaceUsage(); + TestSpaceUsage>(); } -TEST(EitherTest, givenRight_thenIsRight) { - either a("4"); - EXPECT_TRUE(a.is_right()); +TEST(EitherTest, givenLeft) { + test_with_matrix({ + [] (const auto& test) { + either a(4); + test(a); + }, [] (const auto& test) { + either a = 4; + test(a); + }, + }, + EXPECT_IS_LEFT(4) + ); } -TEST(EitherTest, givenRight_thenIsNotLeft) { - either a("4"); - EXPECT_FALSE(a.is_left()); +TEST(EitherTest, givenRight) { + test_with_matrix({ + [] (const auto& test) { + either a("4"); + test(a); + }, [] (const auto& test) { + either a = string("4"); + test(a); + } + }, + EXPECT_IS_RIGHT("4") + ); } -TEST(EitherTest, givenRight_whenQueryingRight_thenValueIsCorrect) { - either a("4"); - EXPECT_EQ("4", a.right()); +TEST(EitherTest, givenMakeLeft) { + test_with_matrix({ + [] (const auto& test) { + either a = make_left(4); + test(a); + }, [] (const auto& test) { + auto a = make_left(4); + test(a); + }, + }, + EXPECT_IS_LEFT(4) + ); } -TEST(EitherTest, givenRight_whenQueryingLeft_thenThrows) { - either a("4"); - EXPECT_ANY_THROW(a.left()); +TEST(EitherTest, givenMakeLeftWithSameType) { + test_with_matrix({ + [] (const auto& test) { + either a = make_left(4); + test(a); + }, [] (const auto& test) { + auto a = make_left(4); + test(a); + }, + }, + EXPECT_IS_LEFT(4) + ); } -TEST(EitherTest, givenRight_whenQueryingRightOpt_thenValueIsCorrect) { - either a("4"); - EXPECT_EQ("4", a.right_opt().value()); +TEST(EitherTest, givenMakeRight) { + test_with_matrix({ + [] (const auto& test) { + either a = make_right("4"); + test(a); + }, [] (const auto& test) { + auto a = make_right("4"); + test(a); + } + }, + EXPECT_IS_RIGHT("4") + ); } -TEST(EitherTest, givenRight_whenQueryingLeftOpt_thenThrows) { - either a("4"); - EXPECT_EQ(boost::none, a.left_opt()); +TEST(EitherTest, givenMakeRightWithSameType) { + test_with_matrix({ + [] (const auto& test) { + either a = make_right("4"); + test(a); + }, [] (const auto& test) { + auto a = make_right("4"); + test(a); + } + }, + EXPECT_IS_RIGHT("4") + ); } -TEST(EitherTest, whenCopyConstructingLeft_thenValueIsCorrect) { - string a = "4"; - either b(a); - EXPECT_EQ(a, b.left()); +TEST(EitherTest, givenMovableOnlyMakeLeft) { + test_with_matrix({ + [] (const auto& test) { + either a = make_left(3); + test(a); + }, [] (const auto& test) { + auto a = make_left(3); + test(a); + }, + }, + EXPECT_IS_LEFT(MovableOnly(3)) + ); } -TEST(EitherTest, whenMoveConstructingLeft_thenValueIsCorrect) { - string a = "4"; - either b(std::move(a)); - EXPECT_EQ("4", b.left()); +TEST(EitherTest, givenMovableOnlyMakeRight) { + test_with_matrix({ + [] (const auto& test) { + either a = make_right(3); + test(a); + }, [] (const auto& test) { + auto a = make_right(3); + test(a); + } + }, + EXPECT_IS_RIGHT(MovableOnly(3)) + ); } -TEST(EitherTest, whenCopyConstructingRight_thenValueIsCorrect) { - string a = "4"; - either b(a); - EXPECT_EQ(a, b.right()); +TEST(EitherTest, givenMultiParamMakeLeft) { + test_with_matrix({ + [] (const auto& test) { + either, string> a = make_left, string>(5, 6); + test(a); + }, [] (const auto& test) { + auto a = make_left, string>(5, 6); + test(a); + }, + }, + EXPECT_IS_LEFT, string>(pair(5, 6)) + ); } -TEST(EitherTest, whenMoveConstructingRight_thenValueIsCorrect) { - string a = "4"; - either b(std::move(a)); - EXPECT_EQ("4", b.right()); +TEST(EitherTest, givenMultiParamMakeRight) { + test_with_matrix({ + [] (const auto& test) { + either> a = make_right>(5, 6); + test(a); + }, [] (const auto& test) { + auto a = make_right>(5, 6); + test(a); + } + }, + EXPECT_IS_RIGHT>(pair(5, 6)) + ); } -//TEST(EitherTest, whenMakingLeft_thenIsLeft) { -// auto a = make_left(4); -// EXPECT_TRUE(a.is_left()); -//} -// -//TEST(EitherTest, whenMakingRight_thenIsRight) { -// auto a = make_right("4"); -// EXPECT_TRUE(a.is_right()); -//} +TEST(EitherTest, givenLeftCopyConstructedFromValue_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + string a = "4"; + either b(a); + test(b); + } + }, + EXPECT_IS_LEFT("4") + ); +} + +TEST(EitherTest, givenLeftCopyConstructedFromValue_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + string a = "4"; + either b(a); + test(a); + } + }, + EXPECT_IS("4") + ); +} + +TEST(EitherTest, givenRightCopyConstructedFromValue_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + string a = "4"; + either b(a); + test(b); + } + }, + EXPECT_IS_RIGHT("4") + ); +} + +TEST(EitherTest, givenRightCopyConstructedFromValue_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + string a = "4"; + either b(a); + test(a); + } + }, + EXPECT_IS("4") + ); +} + +TEST(EitherTest, givenLeftMoveConstructedFromValue_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + MovableOnly a(3); + either b(std::move(a)); + test(b); + } + }, + EXPECT_IS_LEFT(MovableOnly(3)) + ); +} + +TEST(EitherTest, givenLeftMoveConstructedFromValue_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + MovableOnly a(3); + either b(std::move(a)); + test(a); + } + }, + EXPECT_IS(MovableOnly(0)) // 0 is moved-from value + ); +} + +TEST(EitherTest, givenRightMoveConstructedFromValue_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + MovableOnly a(3); + either b(std::move(a)); + test(b); + } + }, + EXPECT_IS_RIGHT(MovableOnly(3)) + ); +} + +TEST(EitherTest, givenRightMoveConstructedFromValue_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + MovableOnly a(3); + either b(std::move(a)); + test(a); + } + }, + EXPECT_IS(MovableOnly(0)) // 0 is moved-from value + ); +} + +TEST(EitherTest, givenLeftCopyAssignedFromValue_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + string a = "4"; + either b(2); + b = a; + test(b); + }, [] (const auto& test) { + string a = "4"; + either b("2"); + b = a; + test(b); + } + }, + EXPECT_IS_LEFT("4") + ); +} + +TEST(EitherTest, givenLeftCopyAssignedFromValue_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + string a = "4"; + either b(2); + b = a; + test(a); + }, [] (const auto& test) { + string a = "4"; + either b("2"); + b = a; + test(a); + } + }, + EXPECT_IS("4") + ); +} + +TEST(EitherTest, givenRightCopyAssignedFromValue_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + string a = "4"; + either b(2); + b = a; + test(b); + }, [] (const auto& test) { + string a = "4"; + either b("2"); + b = a; + test(b); + } + }, + EXPECT_IS_RIGHT("4") + ); +} + +TEST(EitherTest, givenRightCopyAssignedFromValue_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + string a = "4"; + either b(2); + b = a; + test(a); + }, [] (const auto& test) { + string a = "4"; + either b("2"); + b = a; + test(a); + } + }, + EXPECT_IS("4") + ); +} + +TEST(EitherTest, givenLeftMoveAssignedFromValue_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + MovableOnly a(3); + either b(2); + b = std::move(a); + test(b); + }, [] (const auto& test) { + MovableOnly a(3); + either b(MovableOnly(2)); + b = std::move(a); + test(b); + } + }, + EXPECT_IS_LEFT(MovableOnly(3)) + ); +} + +TEST(EitherTest, givenLeftMoveAssignedFromValue_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + MovableOnly a(3); + either b("2"); + b = std::move(a); + test(a); + }, [] (const auto& test) { + MovableOnly a(3); + either b(MovableOnly(0)); + b = std::move(a); + test(a); + } + }, + EXPECT_IS(MovableOnly(0)) + ); +} + +TEST(EitherTest, givenRightMoveAssignedFromValue_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + MovableOnly a(3); + either b("2"); + b = std::move(a); + test(b); + }, [] (const auto& test) { + MovableOnly a(3); + either b(MovableOnly(2)); + b = std::move(a); + test(b); + } + }, + EXPECT_IS_RIGHT(MovableOnly(3)) + ); +} + +TEST(EitherTest, givenRightMoveAssignedFromValue_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + MovableOnly a(3); + either b("2"); + b = std::move(a); + test(a); + }, [] (const auto& test) { + MovableOnly a(3); + either b(MovableOnly(2)); + b = std::move(a); + test(a); + } + }, + EXPECT_IS(MovableOnly(0)) // 0 is moved-from value + ); +} + +TEST(EitherTest, givenLeftCopyConstructed_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a("4"); + either b(a); + test(b); + } + }, + EXPECT_IS_LEFT("4") + ); +} + +TEST(EitherTest, givenLeftCopyConstructed_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a("4"); + either b(a); + test(a); + } + }, + EXPECT_IS_LEFT("4") + ); +} + +TEST(EitherTest, givenLeftCopyConstructed_withSameType_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_left("4"); + either b(a); + test(b); + } + }, + EXPECT_IS_LEFT("4") + ); +} + +TEST(EitherTest, givenLeftCopyConstructed_withSameType_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_left("4"); + either b(a); + test(a); + } + }, + EXPECT_IS_LEFT("4") + ); +} + +TEST(EitherTest, givenRightCopyConstructed_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a("4"); + either b(a); + test(b); + } + }, + EXPECT_IS_RIGHT("4") + ); +} -// TODO Test MovableOnly content type -// TODO Test Left == Right (Same type) -// TODO Test operator== and != -// TODO Test copy/move constructor -// TODO Test destruction -// TODO Test make_left / make_right -// TODO Test noexcept tags are correct +TEST(EitherTest, givenRightCopyConstructed_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a("4"); + either b(a); + test(a); + } + }, + EXPECT_IS_RIGHT("4") + ); +} + +TEST(EitherTest, givenRightCopyConstructed_withSameType_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_right("4"); + either b(a); + test(b); + } + }, + EXPECT_IS_RIGHT("4") + ); +} + + +TEST(EitherTest, givenRightCopyConstructed_withSameType_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_right("4"); + either b(a); + test(a); + } + }, + EXPECT_IS_RIGHT("4") + ); +} + +TEST(EitherTest, givenLeftMoveConstructed_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a(MovableOnly(3)); + either b(std::move(a)); + test(b); + } + }, + EXPECT_IS_LEFT(MovableOnly(3)) + ); +} + +TEST(EitherTest, givenLeftMoveConstructed_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a(MovableOnly(3)); + either b(std::move(a)); + test(a); + } + }, + EXPECT_IS_LEFT(MovableOnly(0)) // 0 is moved-from value + ); +} + +TEST(EitherTest, givenLeftMoveConstructed_withSameType_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_left(MovableOnly(3)); + either b(std::move(a)); + test(b); + } + }, + EXPECT_IS_LEFT(MovableOnly(3)) + ); +} + +TEST(EitherTest, givenLeftMoveConstructed_withSameType_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_left(MovableOnly(3)); + either b(std::move(a)); + test(a); + } + }, + EXPECT_IS_LEFT(MovableOnly(0)) // 0 is moved-from value + ); +} + +TEST(EitherTest, givenRightMoveConstructed_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a(MovableOnly(3)); + either b(std::move(a)); + test(b); + } + }, + EXPECT_IS_RIGHT(MovableOnly(3)) + ); +} + +TEST(EitherTest, givenRightMoveConstructed_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a(MovableOnly(3)); + either b(std::move(a)); + test(a); + } + }, + EXPECT_IS_RIGHT(MovableOnly(0)) // 0 is moved-from value + ); +} + +TEST(EitherTest, givenRightMoveConstructed_withSameType_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_right(MovableOnly(3)); + either b(std::move(a)); + test(b); + } + }, + EXPECT_IS_RIGHT(MovableOnly(3)) + ); +} + +TEST(EitherTest, givenRightMoveConstructed_withSameType_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_right(MovableOnly(3)); + either b(std::move(a)); + test(a); + } + }, + EXPECT_IS_RIGHT(MovableOnly(0)) // 0 is moved-from value + ); +} + +TEST(EitherTest, givenLeftCopyAssigned_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a("4"); + either b(2); + b = a; + test(b); + }, [] (const auto& test) { + either a("4"); + either b("2"); + b = a; + test(b); + } + }, + EXPECT_IS_LEFT("4") + ); +} + +TEST(EitherTest, givenLeftCopyAssigned_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a("4"); + either b(2); + b = a; + test(a); + }, [] (const auto& test) { + either a("4"); + either b("2"); + b = a; + test(a); + } + }, + EXPECT_IS_LEFT("4") + ); +} + +TEST(EitherTest, givenLeftCopyAssigned_withSameType_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_left("4"); + either b = make_right("2"); + b = a; + test(b); + }, [] (const auto& test) { + either a = make_left("4"); + either b = make_left("2"); + b = a; + test(b); + } + }, + EXPECT_IS_LEFT("4") + ); +} + +TEST(EitherTest, givenLeftCopyAssigned_withSameType_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_left("4"); + either b = make_right("2"); + b = a; + test(a); + }, [] (const auto& test) { + either a = make_left("4"); + either b = make_left("2"); + b = a; + test(a); + } + }, + EXPECT_IS_LEFT("4") + ); +} + +TEST(EitherTest, givenRightCopyAssigned_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a("4"); + either b(2); + b = a; + test(b); + }, [] (const auto& test) { + either a("4"); + either b("2"); + b = a; + test(b); + } + }, + EXPECT_IS_RIGHT("4") + ); +} + +TEST(EitherTest, givenRightCopyAssigned_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a("4"); + either b(2); + b = a; + test(a); + }, [] (const auto& test) { + either a("4"); + either b("2"); + b = a; + test(a); + } + }, + EXPECT_IS_RIGHT("4") + ); +} + +TEST(EitherTest, givenRightCopyAssigned_withSameType_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_right("4"); + either b = make_left("2"); + b = a; + test(b); + }, [] (const auto& test) { + either a = make_right("4"); + either b = make_right("2"); + b = a; + test(b); + } + }, + EXPECT_IS_RIGHT("4") + ); +} + +TEST(EitherTest, givenRightCopyAssigned_withSameType_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_right("4"); + either b = make_left("2"); + b = a; + test(a); + }, [] (const auto& test) { + either a = make_right("4"); + either b = make_right("2"); + b = a; + test(a); + } + }, + EXPECT_IS_RIGHT("4") + ); +} + +TEST(EitherTest, givenLeftMoveAssigned_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a(MovableOnly(3)); + either b(2); + b = std::move(a); + test(b); + }, [] (const auto& test) { + either a(MovableOnly(3)); + either b(MovableOnly(2)); + b = std::move(a); + test(b); + } + }, + EXPECT_IS_LEFT(MovableOnly(3)) + ); +} + +TEST(EitherTest, givenLeftMoveAssigned_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a(MovableOnly(3)); + either b(2); + b = std::move(a); + test(a); + }, [] (const auto& test) { + either a(MovableOnly(3)); + either b(MovableOnly(2)); + b = std::move(a); + test(a); + } + }, + EXPECT_IS_LEFT(MovableOnly(0)) // 0 is moved-from value + ); +} + +TEST(EitherTest, givenLeftMoveAssigned_withSameType_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_left(3); + either b = make_right(2); + b = std::move(a); + test(b); + }, [] (const auto& test) { + either a = make_left(3); + either b = make_left(2); + b = std::move(a); + test(b); + } + }, + EXPECT_IS_LEFT(MovableOnly(3)) + ); +} + +TEST(EitherTest, givenLeftMoveAssigned_withSameType_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_left(3); + either b = make_right(2); + b = std::move(a); + test(a); + }, [] (const auto& test) { + either a = make_left(3); + either b = make_left(2); + b = std::move(a); + test(a); + } + }, + EXPECT_IS_LEFT(MovableOnly(0)) // 0 is moved-from value + ); +} + +TEST(EitherTest, givenRightMoveAssigned_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a(MovableOnly(3)); + either b("2"); + b = std::move(a); + test(b); + }, [] (const auto& test) { + either a(MovableOnly(3)); + either b(MovableOnly(2)); + b = std::move(a); + test(b); + } + }, + EXPECT_IS_RIGHT(MovableOnly(3)) + ); +} + +TEST(EitherTest, givenRightMoveAssigned_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a(MovableOnly(3)); + either b("2"); + b = std::move(a); + test(a); + }, [] (const auto& test) { + either a(MovableOnly(3)); + either b(MovableOnly(2)); + b = std::move(a); + test(a); + } + }, + EXPECT_IS_RIGHT(MovableOnly(0)) // 0 is moved-from value + ); +} + +TEST(EitherTest, givenRightMoveAssigned_withSameType_thenNewIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_right(3); + either b = make_left(2); + b = std::move(a); + test(b); + }, [] (const auto& test) { + either a = make_right(3); + either b = make_right(2); + b = std::move(a); + test(b); + } + }, + EXPECT_IS_RIGHT(MovableOnly(3)) + ); +} + +TEST(EitherTest, givenRightMoveAssigned_withSameType_thenOldIsCorrect) { + test_with_matrix({ + [] (const auto& test) { + either a = make_right(3); + either b = make_left(2); + b = std::move(a); + test(a); + }, [] (const auto& test) { + either a = make_right(3); + either b = make_right(2); + b = std::move(a); + test(a); + } + }, + EXPECT_IS_RIGHT(MovableOnly(0)) // 0 is moved-from value + ); +} + +TEST(EitherTest, givenLeft_whenModified_thenValueIsChanged) { + test_with_matrix({ + [] (const auto& test) { + either a(4); + a.left() = 5; + test(a); + }, [] (const auto& test) { + either a(4); + *a.left_opt() = 5; + test(a); + } + }, + EXPECT_IS_LEFT(5) + ); +} + +TEST(EitherTest, givenRight_whenModified_thenValueIsChanged) { + test_with_matrix({ + [] (const auto& test) { + either a("4"); + a.right() = "5"; + test(a); + }, [] (const auto& test) { + either a("4"); + *a.right_opt() = "5"; + test(a); + } + }, + EXPECT_IS_RIGHT("5") + ); +} + +TEST(EitherTest, canEmplaceConstructLeft) { + test_with_matrix({ + [] (const auto& test) { + either, tuple> a(2, 3); + test(a); + } + }, + EXPECT_IS_LEFT, tuple>(tuple(2, 3)) + ); +} + +TEST(EitherTest, canEmplaceConstructRight) { + test_with_matrix({ + [] (const auto& test) { + either, tuple> a(2, 3, 4); + test(a); + } + }, + EXPECT_IS_RIGHT, tuple>(tuple(2, 3, 4)) + ); +} + +TEST(EitherTest, givenEqualLefts_thenAreEqual) { + either a("3"); + either b("3"); + EXPECT_TRUE(a == b); +} + +TEST(EitherTest, givenEqualLefts_thenAreNotUnequal) { + either a("3"); + either b("3"); + EXPECT_FALSE(a != b); +} + +TEST(EitherTest, givenEqualRights_thenAreEqual) { + either a(3); + either b(3); + EXPECT_TRUE(a == b); +} + +TEST(EitherTest, givenEqualRights_thenAreNotUnequal) { + either a(3); + either b(3); + EXPECT_FALSE(a != b); +} + +TEST(EitherTest, givenLeftAndRight_thenAreNotEqual) { + either a("3"); + either b(3); + EXPECT_FALSE(a == b); + EXPECT_FALSE(b == a); +} + +TEST(EitherTest, givenLeftAndRight_thenAreUnequal) { + either a("3"); + either b(3); + EXPECT_TRUE(a != b); + EXPECT_TRUE(b != a); +} + +TEST(EitherTest, OutputLeft) { + ostringstream str; + str << either("mystring"); + EXPECT_EQ("Left(mystring)", str.str()); +} + +TEST(EitherTest, OutputRight) { + ostringstream str; + str << either("mystring"); + EXPECT_EQ("Right(mystring)", str.str()); +} + +TEST(EitherTest, givenLeftAndRightWithSameType_thenAreNotEqual) { + either a = make_left("3"); + either b = make_right("3"); + EXPECT_FALSE(a == b); + EXPECT_FALSE(b == a); +} + +TEST(EitherTest, givenLeftAndRightWithSameType_thenAreUnequal) { + either a = make_left("3"); + either b = make_right("3"); + EXPECT_TRUE(a != b); + EXPECT_TRUE(b != a); +} + + +namespace { +class DestructorCallback { +public: + MOCK_CONST_METHOD0(call, void()); + + void EXPECT_CALLED(int times = 1) { + EXPECT_CALL(*this, call()).Times(times); + } +}; +class ClassWithDestructorCallback { +public: + ClassWithDestructorCallback(const DestructorCallback *destructorCallback) : _destructorCallback(destructorCallback) {} + ClassWithDestructorCallback(const ClassWithDestructorCallback &rhs): _destructorCallback(rhs._destructorCallback) {} + + ~ClassWithDestructorCallback() { + _destructorCallback->call(); + } + +private: + const DestructorCallback *_destructorCallback; + + ClassWithDestructorCallback &operator=(const ClassWithDestructorCallback &rhs) = delete; +}; +class OnlyMoveableClassWithDestructorCallback { +public: + OnlyMoveableClassWithDestructorCallback(const DestructorCallback *destructorCallback) : _destructorCallback(destructorCallback) { } + OnlyMoveableClassWithDestructorCallback(OnlyMoveableClassWithDestructorCallback &&source): _destructorCallback(source._destructorCallback) {} + + ~OnlyMoveableClassWithDestructorCallback() { + _destructorCallback->call(); + } + +private: + DISALLOW_COPY_AND_ASSIGN(OnlyMoveableClassWithDestructorCallback); + const DestructorCallback *_destructorCallback; +}; + +} + +TEST(EitherTest_Destructor, LeftDestructorIsCalled) { + DestructorCallback destructorCallback; + destructorCallback.EXPECT_CALLED(2); //Once for the temp object, once when the either class destructs + + ClassWithDestructorCallback temp(&destructorCallback); + either var = temp; +} + +TEST(EitherTest_Destructor, RightDestructorIsCalled) { + DestructorCallback destructorCallback; + destructorCallback.EXPECT_CALLED(2); //Once for the temp object, once when the either class destructs + + ClassWithDestructorCallback temp(&destructorCallback); + either var = temp; +} + +TEST(EitherTest_Destructor, LeftDestructorIsCalledAfterCopying) { + DestructorCallback destructorCallback; + destructorCallback.EXPECT_CALLED(3); //Once for the temp object, once for var1 and once for var2 + + ClassWithDestructorCallback temp(&destructorCallback); + either var1 = temp; + either var2 = var1; +} + +TEST(EitherTest_Destructor, RightDestructorIsCalledAfterCopying) { + DestructorCallback destructorCallback; + destructorCallback.EXPECT_CALLED(3); //Once for the temp object, once for var1 and once for var2 + + ClassWithDestructorCallback temp(&destructorCallback); + either var1 = temp; + either var2 = var1; +} + +TEST(EitherTest_Destructor, LeftDestructorIsCalledAfterMoving) { + DestructorCallback destructorCallback; + destructorCallback.EXPECT_CALLED(3); //Once for the temp object, once for var1 and once for var2 + + OnlyMoveableClassWithDestructorCallback temp(&destructorCallback); + either var1 = std::move(temp); + either var2 = std::move(var1); +} + +TEST(EitherTest_Destructor, RightDestructorIsCalledAfterMoving) { + DestructorCallback destructorCallback; + destructorCallback.EXPECT_CALLED(3); //Once for the temp object, once for var1 and once for var2 + + OnlyMoveableClassWithDestructorCallback temp(&destructorCallback); + either var1 = std::move(temp); + either var2 = std::move(var1); +} + +TEST(EitherTest_Destructor, LeftDestructorIsCalledAfterAssignment) { + DestructorCallback destructorCallback1; + DestructorCallback destructorCallback2; + destructorCallback1.EXPECT_CALLED(2); //Once for the temp1 object, once at the assignment + destructorCallback2.EXPECT_CALLED(3); //Once for the temp2 object, once in destructor of var2, once in destructor of var1 + + ClassWithDestructorCallback temp1(&destructorCallback1); + either var1 = temp1; + ClassWithDestructorCallback temp2(&destructorCallback2); + either var2 = temp2; + var1 = var2; +} + +TEST(EitherTest_Destructor, RightDestructorIsCalledAfterAssignment) { + DestructorCallback destructorCallback1; + DestructorCallback destructorCallback2; + destructorCallback1.EXPECT_CALLED(2); //Once for the temp1 object, once at the assignment + destructorCallback2.EXPECT_CALLED(3); //Once for the temp2 object, once in destructor of var2, once in destructor of var1 + + ClassWithDestructorCallback temp1(&destructorCallback1); + either var1 = temp1; + ClassWithDestructorCallback temp2(&destructorCallback2); + either var2 = temp2; + var1 = var2; +} + +TEST(EitherTest_Destructor, LeftDestructorIsCalledAfterMoveAssignment) { + DestructorCallback destructorCallback1; + DestructorCallback destructorCallback2; + destructorCallback1.EXPECT_CALLED(2); //Once for the temp1 object, once at the assignment + destructorCallback2.EXPECT_CALLED(3); //Once for the temp2 object, once in destructor of var2, once in destructor of var1 + + OnlyMoveableClassWithDestructorCallback temp1(&destructorCallback1); + either var1 = std::move(temp1); + OnlyMoveableClassWithDestructorCallback temp2(&destructorCallback2); + either var2 = std::move(temp2); + var1 = std::move(var2); +} + +TEST(EitherTest_Destructor, RightDestructorIsCalledAfterMoveAssignment) { + DestructorCallback destructorCallback1; + DestructorCallback destructorCallback2; + destructorCallback1.EXPECT_CALLED(2); //Once for the temp1 object, once at the assignment + destructorCallback2.EXPECT_CALLED(3); //Once for the temp2 object, once in destructor of var2, once in destructor of var1 + + OnlyMoveableClassWithDestructorCallback temp1(&destructorCallback1); + either var1 = std::move(temp1); + OnlyMoveableClassWithDestructorCallback temp2(&destructorCallback2); + either var2 = std::move(temp2); + var1 = std::move(var2); +} From 28783139934e9a9ac364eb33957717d2f6db0fd3 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Mon, 24 Dec 2018 18:34:42 +0100 Subject: [PATCH 5/5] More noexcept specifiers --- src/cpp-utils/either.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cpp-utils/either.h b/src/cpp-utils/either.h index d428294d..88778498 100644 --- a/src/cpp-utils/either.h +++ b/src/cpp-utils/either.h @@ -12,13 +12,13 @@ namespace cpputils { class either final { public: template::value && !std::is_constructible::value>* = nullptr> - either(Head&& construct_left_head_arg, Tail&&... construct_left_tail_args) /* TODO noexcept(noexcept(std::declval>()._construct_left(std::forward(construct_left_head_arg), std::forward(construct_left_tail_args)...))) */ + either(Head&& construct_left_head_arg, Tail&&... construct_left_tail_args) noexcept(noexcept(std::declval>()._construct_left(std::forward(construct_left_head_arg), std::forward(construct_left_tail_args)...))) : _side(Side::left) { _construct_left(std::forward(construct_left_head_arg), std::forward(construct_left_tail_args)...); } template::value && std::is_constructible::value>* = nullptr> - either(Head&& construct_right_head_arg, Tail&&... construct_right_tail_args) /* TODO noexcept(noexcept(std::declval>()._construct_right(std::forward(construct_right_head_arg), std::forward(construct_right_tail_args)...))) */ + either(Head&& construct_right_head_arg, Tail&&... construct_right_tail_args) noexcept(noexcept(std::declval>()._construct_right(std::forward(construct_right_head_arg), std::forward(construct_right_tail_args)...))) : _side(Side::right) { _construct_right(std::forward(construct_right_head_arg), std::forward(construct_right_tail_args)...); } @@ -33,7 +33,7 @@ namespace cpputils { } } - either(either &&rhs) /* TODO noexcept(noexcept(_construct_left(std::move(rhs._left))) && noexcept(_construct_right(std::move(rhs._right)))) */ + either(either &&rhs) noexcept(noexcept(_construct_left(std::move(rhs._left))) && noexcept(_construct_right(std::move(rhs._right)))) : _side(rhs._side) { if(_side == Side::left) { _construct_left(std::move(rhs._left)); @@ -47,7 +47,7 @@ namespace cpputils { } //TODO Try allowing copy-assignment when Left/Right types are std::is_convertible - either &operator=(const either &rhs) /* TODO noexcept(noexcept(_construct_left(rhs._left)) && noexcept(_construct_right(rhs._right))) */ { + either &operator=(const either &rhs) noexcept(noexcept(_construct_left(rhs._left)) && noexcept(_construct_right(rhs._right))) { _destruct(); _side = rhs._side; if (_side == Side::left) { @@ -58,7 +58,7 @@ namespace cpputils { return *this; } - either &operator=(either &&rhs) /* TODO noexcept(noexcept(_construct_left(std::move(rhs._left))) && noexcept(_construct_right(std::move(rhs._right)))) */ { + either &operator=(either &&rhs) noexcept(noexcept(_construct_left(std::move(rhs._left))) && noexcept(_construct_right(std::move(rhs._right)))) { _destruct(); _side = rhs._side; if (_side == Side::left) { @@ -159,10 +159,10 @@ namespace cpputils { } template - friend either make_left(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...)))*/; + friend either make_left(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...))) */; template - friend either make_right(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...)))*/; + friend either make_right(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...))) */; }; template