// Copyright (c) 2023-2024, 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 "common/variant.h" #include #include #include #include "gtest/gtest.h" #include #include #include using tools::optional_variant; using tools::variant; using tools::variant_static_visitor; namespace { //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- template using strip_all_t = std::remove_reference_t>; //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- template using strip_same = std::is_same, strip_all_t>; //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- template < typename PositiveType, typename TestType, typename... VariantTypes, class VecTypes = boost::mpl::vector, class VecBegin = typename boost::mpl::begin::type, class VecIndexT = typename boost::mpl::find::type, size_t TYPE_INDEX = boost::mpl::distance::value, bool LAST_VARIANT_TYPE = TYPE_INDEX == sizeof...(VariantTypes) - 1 > static std::enable_if_t test_is_type_match(const variant& v) { constexpr bool expected = strip_same(); const bool actual = v.template is_type(); EXPECT_EQ(expected, actual); EXPECT_FALSE(v.template is_type()); } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- template < typename PositiveType, typename TestType, typename... VariantTypes, class VecTypes = boost::mpl::vector, class VecBegin = typename boost::mpl::begin::type, class VecIndexT = typename boost::mpl::find::type, size_t TYPE_INDEX = boost::mpl::distance::value, bool LAST_VARIANT_TYPE = TYPE_INDEX == sizeof...(VariantTypes) - 1 > static std::enable_if_t test_is_type_match(const variant& v) { constexpr bool expected = strip_same(); const bool actual = v.template is_type(); EXPECT_EQ(expected, actual); using NextTypeIt = typename boost::mpl::advance>::type; using NextTestType = typename boost::mpl::deref::type; test_is_type_match(v); } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- template < typename VariantType0, typename... VariantTypesRest, typename AssignType > static void test_is_type_ref ( variant& v, AssignType&& val ) { v = val; test_is_type_match(v); } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- template < typename VariantType0, typename... VariantTypesRest, typename AssignType0, typename... AssignTypesRest > static void test_is_type_ref ( variant& v, AssignType0&& val_0, AssignTypesRest&&... val_rest ) { v = val_0; test_is_type_match(v); test_is_type_ref(v, val_rest...); } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- template static void test_is_type_full(VariantTypes&&... test_vals) { variant v; test_is_type_ref(v, test_vals...); } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- template < size_t IJ = 0, typename... VariantTypes, bool END = IJ == sizeof...(VariantTypes) * sizeof...(VariantTypes) > static std::enable_if_t test_same_type_ref ( variant& v1, variant& v2, const std::tuple& tup_i, const std::tuple& tup_j ) { /* trivial end case */ } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- template < size_t IJ = 0, typename... VariantTypes, bool END = IJ == sizeof...(VariantTypes) * sizeof...(VariantTypes) > static std::enable_if_t test_same_type_ref ( variant& v1, variant& v2, const std::tuple& tup_i, const std::tuple& tup_j ) { constexpr size_t I = IJ / sizeof...(VariantTypes); constexpr size_t J = IJ % sizeof...(VariantTypes); constexpr bool expected = I == J; v1 = std::get(tup_i); v2 = std::get(tup_j); const bool actual = variant::same_type(v1, v2); EXPECT_EQ(expected, actual); test_same_type_ref(v1, v2, tup_i, tup_j); } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- template static void test_same_type_full ( const std::tuple& vals_i, const std::tuple& vals_j ) { using Variant = variant; Variant v_i; Variant v_j; test_same_type_ref(v_i, v_j, vals_i, vals_j); } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- struct test_stringify_visitor: public variant_static_visitor { template static std::string stringify(const T& t) { std::stringstream ss; ss << typeid(T).name(); ss << "::"; ss << t; return ss.str(); } template static void test_visitation(const Variant& v, const T& t) { EXPECT_EQ(test_stringify_visitor::stringify(t), v.visit(test_stringify_visitor())); } // Make sure boost::blank errors using variant_static_visitor::operator(); // Visitation implementation template std::string operator()(const T& t) const { return test_stringify_visitor::stringify(t); } }; //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- } // anonymous namespace //------------------------------------------------------------------------------------------------------------------- TEST(variant, operatorbool) { optional_variant v; EXPECT_FALSE(v); v = (int16_t) 2023; EXPECT_TRUE(v); v = (int16_t) 0; EXPECT_TRUE(v); v = boost::blank{}; EXPECT_FALSE(v); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, is_empty) { optional_variant v; EXPECT_TRUE(v.is_empty()); v = (int16_t) 2023; EXPECT_FALSE(v.is_empty()); v = (int16_t) 0; EXPECT_FALSE(v.is_empty()); v = boost::blank{}; EXPECT_TRUE(v.is_empty()); optional_variant<> v2; EXPECT_TRUE(v2.is_empty()); v2 = boost::blank{}; EXPECT_TRUE(v2.is_empty()); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, is_type) { variant v; EXPECT_TRUE(v.is_type()); v = (int16_t) 2023; EXPECT_TRUE(v.is_type()); test_is_type_full((uint32_t) 2023, (char) '\n', std::string("HOWDY")); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, try_unwrap) { variant v; EXPECT_TRUE(v.try_unwrap()); v = (int16_t) 5252; ASSERT_TRUE(v.try_unwrap()); EXPECT_EQ(5252, *v.try_unwrap()); EXPECT_FALSE(v.try_unwrap()); EXPECT_FALSE(v.try_unwrap()); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, unwrap) { variant v; EXPECT_EQ(0, v.unwrap()); v = (int16_t) 5252; EXPECT_EQ(5252, v.unwrap()); EXPECT_THROW(v.unwrap(), std::runtime_error); EXPECT_THROW(v.unwrap(), std::runtime_error); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, mutation) { variant v; v = (uint8_t) 5; EXPECT_EQ(5, v.unwrap()); uint8_t &intref{v.unwrap()}; intref = 10; EXPECT_EQ(10, v.unwrap()); EXPECT_TRUE(v.try_unwrap()); uint8_t *intptr{v.try_unwrap()}; *intptr = 15; EXPECT_EQ(15, v.unwrap()); const variant &v_ref{v}; EXPECT_EQ(15, v_ref.unwrap()); EXPECT_TRUE(v_ref.try_unwrap()); EXPECT_EQ(15, *(v_ref.try_unwrap())); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, index) { variant v; EXPECT_EQ(0, v.index()); v = (int8_t) 7; EXPECT_EQ(0, v.index()); v = (uint8_t) 7; EXPECT_EQ(1, v.index()); v = (int16_t) 7; EXPECT_EQ(2, v.index()); v = (uint16_t) 7; EXPECT_EQ(3, v.index()); v = "verifiable variant vying for vengence versus visa"; EXPECT_EQ(4, v.index()); optional_variant vo; EXPECT_EQ(0, vo.index()); vo = (int8_t) 7; EXPECT_EQ(1, vo.index()); vo = (uint8_t) 7; EXPECT_EQ(2, vo.index()); vo = (int16_t) 7; EXPECT_EQ(3, vo.index()); vo = (uint16_t) 7; EXPECT_EQ(4, vo.index()); vo = "verifiable variant vying for vengence versus visa"; EXPECT_EQ(5, vo.index()); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, type_index_of) { variant v; EXPECT_EQ(0, decltype(v)::type_index_of()); EXPECT_EQ(1, decltype(v)::type_index_of()); EXPECT_EQ(2, decltype(v)::type_index_of()); EXPECT_EQ(3, decltype(v)::type_index_of()); EXPECT_EQ(4, decltype(v)::type_index_of()); optional_variant vo; EXPECT_EQ(0, decltype(vo)::type_index_of()); EXPECT_EQ(1, decltype(vo)::type_index_of()); EXPECT_EQ(2, decltype(vo)::type_index_of()); EXPECT_EQ(3, decltype(vo)::type_index_of()); EXPECT_EQ(4, decltype(vo)::type_index_of()); EXPECT_EQ(5, decltype(vo)::type_index_of()); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, constexpr_type_index_of) { variant v; constexpr int TINDEX2 = decltype(v)::type_index_of(); EXPECT_EQ(2, TINDEX2); constexpr int TINDEX4 = decltype(v)::type_index_of(); EXPECT_EQ(4, TINDEX4); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, same_type) { const std::tuple vals_i(77840, "Hullubaloo", '\0'); const std::tuple vals_j(1876, "Canneck", '\t'); test_same_type_full(vals_i, vals_j); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, visit) { variant v; v = "Rev"; test_stringify_visitor::test_visitation(v, std::string("Rev")); v = (int16_t) 2001; test_stringify_visitor::test_visitation(v, (int16_t) 2001); EXPECT_NE(test_stringify_visitor::stringify((uint16_t) 2001), v.visit(test_stringify_visitor())); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, visit_lambda) { const auto stringify_lambda = [](auto x) -> std::string { if constexpr (std::is_same_v) return x; else if constexpr (std::is_same_v) throw std::runtime_error("boost blank cannot be stringified"); else return std::to_string(x); }; variant v; EXPECT_THROW(v.visit(stringify_lambda), std::runtime_error); v = "Rev"; EXPECT_EQ("Rev", v.visit(stringify_lambda)); v = (int16_t) 2001; EXPECT_EQ("2001", v.visit(stringify_lambda)); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, visit_ref_passthru) { struct A { int x; }; struct B { int x; }; struct x_ref_visitor: tools::variant_static_visitor { using tools::variant_static_visitor::operator(); const int& operator()(const A &a) const { return a.x; } const int& operator()(const B &b) const { return b.x; } }; tools::variant v; EXPECT_THROW(v.visit(x_ref_visitor{}), std::runtime_error); // A very hairy looking test, but we're just testing that the reference returned from our static // visitor is actually pointing to something in the same stack space as our variant operand. // This will let us catch mistakes where we take a reference to a locally created variable if // the visit() method is changed subtlely. v = A { 2024 }; const char * const px = reinterpret_cast(std::addressof(v.visit(x_ref_visitor{}))); const char * const pv = reinterpret_cast(&v); EXPECT_LT(px - pv, sizeof(v)); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, value_initialize_to_type_index) { variant v; for (int i = 0; i < 6; ++i) { v.value_initialize_to_type_index(i); EXPECT_EQ(i, v.index()); } v = (int8_t) 69; EXPECT_EQ(1, v.index()); EXPECT_EQ(69, v.unwrap()); v.value_initialize_to_type_index(1); EXPECT_EQ(1, v.index()); EXPECT_EQ(0, v.unwrap()); v = (uint8_t) 69; EXPECT_EQ(2, v.index()); EXPECT_EQ(69, v.unwrap()); v.value_initialize_to_type_index(2); EXPECT_EQ(2, v.index()); EXPECT_EQ(0, v.unwrap()); v = (int16_t) 69; EXPECT_EQ(3, v.index()); EXPECT_EQ(69, v.unwrap()); v.value_initialize_to_type_index(3); EXPECT_EQ(3, v.index()); EXPECT_EQ(0, v.unwrap()); v = (uint16_t) 69; EXPECT_EQ(4, v.index()); EXPECT_EQ(69, v.unwrap()); v.value_initialize_to_type_index(4); EXPECT_EQ(4, v.index()); EXPECT_EQ(0, v.unwrap()); v = std::string("69"); EXPECT_EQ(5, v.index()); EXPECT_EQ("69", v.unwrap()); v.value_initialize_to_type_index(5); EXPECT_EQ(5, v.index()); EXPECT_EQ("", v.unwrap()); v = (int16_t) 69; v.value_initialize_to_type_index(5); EXPECT_EQ("", v.unwrap()); EXPECT_THROW(v.value_initialize_to_type_index(-1), std::runtime_error); EXPECT_THROW(v.value_initialize_to_type_index(6), std::runtime_error); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, ad_hoc_recursion) { struct left_t; struct right_t; using twisty = optional_variant, boost::recursive_wrapper>; struct left_t { twisty l; }; struct right_t { twisty r; }; auto right = [](twisty&& t = {}) -> twisty { right_t r; r.r = t; return r; }; auto left = [](twisty&& t = {}) -> twisty { left_t l; l.l = t; return l; }; struct twisty_counter: variant_static_visitor> { std::pair operator()(boost::blank) const { return {0, 0}; } std::pair operator()(const left_t& l) const { auto count = l.l.visit(twisty_counter()); count.first += 1; return count; } std::pair operator()(const right_t& r) const { auto count = r.r.visit(twisty_counter()); count.second += 1; return count; } }; const twisty tw = left(left(right(right(left(right(left(right(left())))))))); int left_count, right_count; std::tie(left_count, right_count) = tw.visit(twisty_counter()); EXPECT_EQ(5, left_count); EXPECT_EQ(4, right_count); } //-------------------------------------------------------------------------------------------------------------------