diff --git a/include/boost/mysql/impl/value.hpp b/include/boost/mysql/impl/value.hpp index dcab9910..01ae5232 100644 --- a/include/boost/mysql/impl/value.hpp +++ b/include/boost/mysql/impl/value.hpp @@ -71,10 +71,36 @@ constexpr std::optional get_optional_noconv( return res ? std::optional(*res) : std::optional(); } +// Helper to make integer constructors work +template +constexpr decltype(auto) value_cast_int(const T& v) noexcept +{ + if constexpr (std::is_unsigned_v) + { + return std::uint64_t(v); + } + else if constexpr (std::is_integral_v && std::is_signed_v) + { + return std::int64_t(v); + } + else + { + return v; + } +} + } // detail } // mysql } // boost +template +constexpr boost::mysql::value::value( + const T& v +) noexcept : + repr_(detail::value_cast_int(v)) +{ +} + template constexpr std::optional boost::mysql::value::get_optional() const noexcept { diff --git a/include/boost/mysql/value.hpp b/include/boost/mysql/value.hpp index ac7c5c1b..b34a6511 100644 --- a/include/boost/mysql/value.hpp +++ b/include/boost/mysql/value.hpp @@ -16,6 +16,7 @@ #include #include #include +#include "boost/mysql/detail/auxiliar/tmp.hpp" /** * \defgroup values Values @@ -186,15 +187,12 @@ public: time // TIME >; - // Default constrctor: makes it a NULL value + // Default constructor: makes it a NULL value constexpr value() = default; // Initialization constructor accepting any of the variant alternatives template - explicit constexpr value(const T& v) noexcept : repr_(v) {} - - explicit constexpr value(int v) noexcept : repr_(std::int64_t(v)) {} - explicit constexpr value(unsigned v) noexcept : repr_(std::uint64_t(v)) {} + explicit constexpr value(const T& v) noexcept; // Tests for NULL constexpr bool is_null() const noexcept { return std::holds_alternative(repr_); } @@ -204,7 +202,7 @@ public: constexpr bool is() const noexcept { return std::holds_alternative(repr_); } template - constexpr bool is_convertible_to() const noexcept { return get_optional(); } + constexpr bool is_convertible_to() const noexcept { return get_optional().has_value(); } // Retrieves the stored value. If the stored value is not a T or cannot // be converted to T without loss of precision, throws. diff --git a/test/unit/value.cpp b/test/unit/value.cpp index c4fee463..9d3ae313 100644 --- a/test/unit/value.cpp +++ b/test/unit/value.cpp @@ -6,7 +6,9 @@ // #include +#include #include +#include #include "boost/mysql/value.hpp" #include "test_common.hpp" @@ -15,11 +17,294 @@ using namespace testing; using namespace date::literals; using namespace std::chrono; using boost::mysql::value; +using boost::typeindex::type_index; +using boost::typeindex::type_id; namespace { -// tests for operator== and operator!= +using vt = value::variant_type; + +template +std::string type_name() { return type_id().pretty_name(); } + +// Constructors +struct value_constructor_testcase : named_param +{ + std::string name; + value v; + vt expected; + + value_constructor_testcase(std::string name, value v, vt expected) : + name(std::move(name)), v(v), expected(expected) {} +}; + +struct ValueConstructorTest : public TestWithParam +{ +}; + +TEST_P(ValueConstructorTest, Constructor_Trivial_ContainedVariantMatches) +{ + EXPECT_EQ(GetParam().v.to_variant(), GetParam().expected); +} + +std::string test_string ("test"); + +template +T non_const_int = 42; + +template +const T const_int = 42; + +INSTANTIATE_TEST_SUITE_P(Default, ValueConstructorTest, Values( + value_constructor_testcase("default_constructor", value(), vt(nullptr)), + value_constructor_testcase("from_nullptr", value(nullptr), vt(nullptr)), + value_constructor_testcase("from_u8", value(std::uint8_t(0xff)), vt(std::uint64_t(0xff))), + value_constructor_testcase("from_u8_const_lvalue", value(const_int), vt(std::uint64_t(42))), + value_constructor_testcase("from_u8_lvalue", value(non_const_int), vt(std::uint64_t(42))), + value_constructor_testcase("from_u16", value(std::uint16_t(0xffff)), vt(std::uint64_t(0xffff))), + value_constructor_testcase("from_u16_const_lvalue", value(const_int), vt(std::uint64_t(42))), + value_constructor_testcase("from_u16_lvalue", value(non_const_int), vt(std::uint64_t(42))), + value_constructor_testcase("from_ushort", value((unsigned short)(0xffff)), vt(std::uint64_t(0xffff))), + value_constructor_testcase("from_u32", value(std::uint32_t(42)), vt(std::uint64_t(42))), + value_constructor_testcase("from_u32_const_lvalue", value(const_int), vt(std::uint64_t(42))), + value_constructor_testcase("from_u32_lvalue", value(non_const_int), vt(std::uint64_t(42))), + value_constructor_testcase("from_uint", value(42u), vt(std::uint64_t(42))), + value_constructor_testcase("from_ulong", value(42uL), vt(std::uint64_t(42))), + value_constructor_testcase("from_ulonglong", value(42uLL), vt(std::uint64_t(42))), + value_constructor_testcase("from_u64", value(std::uint64_t(42)), vt(std::uint64_t(42))), + value_constructor_testcase("from_u64_const_lvalue", value(const_int), vt(std::uint64_t(42))), + value_constructor_testcase("from_u64_lvalue", value(non_const_int), vt(std::uint64_t(42))), + value_constructor_testcase("from_s8", value(std::int8_t(-42)), vt(std::int64_t(-42))), + value_constructor_testcase("from_s8_const_lvalue", value(const_int), vt(std::int64_t(42))), + value_constructor_testcase("from_s8_lvalue", value(non_const_int), vt(std::int64_t(42))), + value_constructor_testcase("from_s16", value(std::int16_t(-42)), vt(std::int64_t(-42))), + value_constructor_testcase("from_s16_const_lvalue", value(const_int), vt(std::int64_t(42))), + value_constructor_testcase("from_s16_lvalue", value(non_const_int), vt(std::int64_t(42))), + value_constructor_testcase("from_sshort", value(short(-42)), vt(std::int64_t(-42))), + value_constructor_testcase("from_s32", value(std::int32_t(-42)), vt(std::int64_t(-42))), + value_constructor_testcase("from_s32_const_lvalue", value(const_int), vt(std::int64_t(42))), + value_constructor_testcase("from_s32_lvalue", value(non_const_int), vt(std::int64_t(42))), + value_constructor_testcase("from_sint", value(-42), vt(std::int64_t(-42))), + value_constructor_testcase("from_slong", value(-42L), vt(std::int64_t(-42))), + value_constructor_testcase("from_slonglong", value(-42LL), vt(std::int64_t(-42))), + value_constructor_testcase("from_s64", value(std::int64_t(-42)), vt(std::int64_t(-42))), + value_constructor_testcase("from_s64_const_lvalue", value(const_int), vt(std::int64_t(42))), + value_constructor_testcase("from_s64_lvalue", value(non_const_int), vt(std::int64_t(42))), + value_constructor_testcase("from_string_view", value(std::string_view("test")), vt("test")), + value_constructor_testcase("from_string", value(test_string), vt("test")), + value_constructor_testcase("from_const_char", value("test"), vt("test")), + value_constructor_testcase("from_float", value(4.2f), vt(4.2f)), + value_constructor_testcase("from_double", value(4.2), vt(4.2)), + value_constructor_testcase("from_date", value(makedate(2020, 1, 10)), vt(makedate(2020, 1, 10))), + value_constructor_testcase("from_datetime", value(makedt(2020, 1, 10, 5)), vt(makedt(2020, 1, 10, 5))), + value_constructor_testcase("from_time", value(maket(1, 2, 3)), vt(maket(1, 2, 3))) +), test_name_generator); + +// Copy and move +TEST(ValueTest, CopyConstructor_FromNonConstLValue_Copies) +{ + value v (10); + value v2 (v); + EXPECT_EQ(v2.to_variant(), vt(std::int64_t(10))); +} + +TEST(ValueTest, CopyConstructor_FromConstLValue_Copies) +{ + const value v (10); + value v2 (v); + EXPECT_EQ(v2.to_variant(), vt(std::int64_t(10))); +} + +TEST(ValueTest, MoveConstructor_Trivial_Copies) +{ + value v (10); + value v2 (std::move(v)); + EXPECT_EQ(v2.to_variant(), vt(std::int64_t(10))); +} + +TEST(ValueTest, CopyAssignment_FromNonConstLValue_Copies) +{ + value v (10); + value v2; + v2 = v; + EXPECT_EQ(v2.to_variant(), vt(std::int64_t(10))); +} + +TEST(ValueTest, CopyAssignament_FromConstLValue_Copies) +{ + const value v (10); + value v2; + v2 = v; + EXPECT_EQ(v2.to_variant(), vt(std::int64_t(10))); +} + +TEST(ValueTest, MoveAssignment_Trivial_Copies) +{ + value v (10); + value v2; + v2 = std::move(v); + EXPECT_EQ(v2.to_variant(), vt(std::int64_t(10))); +} + +// accessors: is, is_convertible_to, is_null, get, get_optional +constexpr vt all_types [] = { + vt(nullptr), + vt(std::uint64_t{}), + vt(std::int64_t{}), + vt(std::string_view{}), + vt(float{}), + vt(double{}), + vt(boost::mysql::date{}), + vt(boost::mysql::datetime{}), + vt(boost::mysql::time{}) +}; + +template +void for_each_type(Callable&& cb) +{ + for (auto type_variant : all_types) + { + std::visit(std::forward(cb), type_variant); + } +} + +struct accessors_testcase : named_param +{ + std::string name; + value v; + type_index is_type; // the type for which is() should return true + std::map conversions; + + accessors_testcase(std::string&& name, value v, type_index is, std::map&& convs) : + name(std::move(name)), + v(v), + is_type(is), + conversions(std::move(convs)) + { + } +}; + +template +std::map make_conversions(const Types&... types) +{ + return { { type_id(), types }... }; +} + +template +accessors_testcase make_default_accessors_testcase( + std::string&& name, + const T& v +) +{ + return accessors_testcase(std::move(name), value(v), type_id(), make_conversions(v)); +} + +struct ValueAccessorsTest : TestWithParam +{ +}; + +TEST_P(ValueAccessorsTest, IsNull_Trivial_ReturnsTrueOnlyForNullptrAlternative) +{ + bool expected = GetParam().is_type == type_id(); + EXPECT_EQ(GetParam().v.is_null(), expected); +} + +TEST_P(ValueAccessorsTest, Is_Trivial_ReturnsTrueOnlyIfTypeMatches) +{ + for_each_type([](auto type_value) { + using T = decltype(type_value); + bool expected = GetParam().is_type == type_id(); + EXPECT_EQ(GetParam().v.is(), expected) << type_name(); + }); +} + +TEST_P(ValueAccessorsTest, IsConvertibleTo_TypeAllowsConversions_ReturnsTrue) +{ + for_each_type([](auto type_value) { + using T = decltype(type_value); + auto it = GetParam().conversions.find(type_id()); + bool expected = it != GetParam().conversions.end(); + EXPECT_EQ(GetParam().v.is_convertible_to(), expected) << type_name(); + }); +} + +TEST_P(ValueAccessorsTest, Get_TypeConvertibleToTarget_ReturnsConvertedValue) +{ + for_each_type([](auto type_value) { + using T = decltype(type_value); + auto it = GetParam().conversions.find(type_id()); + if (it != GetParam().conversions.end()) + { + T expected = std::get(it->second); + EXPECT_EQ(GetParam().v.get(), expected) << type_name(); + } + }); +} + +TEST_P(ValueAccessorsTest, Get_TypeNotConvertibleToTarget_Throws) +{ + for_each_type([](auto type_value) { + using T = decltype(type_value); + auto it = GetParam().conversions.find(type_id()); + if (it == GetParam().conversions.end()) + { + EXPECT_THROW(GetParam().v.get(), std::bad_variant_access) << type_name(); + } + }); +} + +TEST_P(ValueAccessorsTest, GetOptional_TypeConvertibleToTarget_ReturnsConvertedValue) +{ + for_each_type([](auto type_value) { + using T = decltype(type_value); + auto it = GetParam().conversions.find(type_id()); + if (it != GetParam().conversions.end()) + { + auto expected = std::get(it->second); + auto opt = GetParam().v.get_optional(); + ASSERT_TRUE(opt) << type_name(); + EXPECT_EQ(*opt, expected) << type_name(); + } + }); +} + +TEST_P(ValueAccessorsTest, GetOptional_TypeNotConvertibleToTarget_ReturnsEmptyOptional) +{ + for_each_type([](auto type_value) { + using T = decltype(type_value); + auto it = GetParam().conversions.find(type_id()); + if (it == GetParam().conversions.end()) + { + EXPECT_FALSE(GetParam().v.get_optional()) << type_name(); + } + }); +} + +INSTANTIATE_TEST_SUITE_P(Default, ValueAccessorsTest, Values( + make_default_accessors_testcase("null", nullptr), + accessors_testcase("i64_positive", value(std::int64_t(42)), type_id(), + make_conversions(std::int64_t(42), std::uint64_t(42))), + accessors_testcase("i64_negative", value(std::int64_t(-42)), type_id(), + make_conversions(std::int64_t(-42))), + accessors_testcase("i64_zero", value(std::int64_t(0)), type_id(), + make_conversions(std::int64_t(0), std::uint64_t(0))), + accessors_testcase("u64_small", value(std::uint64_t(42)), type_id(), + make_conversions(std::int64_t(42), std::uint64_t(42))), + accessors_testcase("u64_big", value(std::uint64_t(0xfffffffffffffffe)), type_id(), + make_conversions(std::uint64_t(0xfffffffffffffffe))), + accessors_testcase("u64_zero", value(std::uint64_t(0)), type_id(), + make_conversions(std::int64_t(0), std::uint64_t(0))), + make_default_accessors_testcase("string_view", makesv("test")), + accessors_testcase("float", value(4.2f), type_id(), + make_conversions(4.2f, double(4.2f))), + make_default_accessors_testcase("double", 4.2), + make_default_accessors_testcase("date", makedate(2020, 10, 5)), + make_default_accessors_testcase("datetime", makedt(2020, 10, 5, 10, 20, 30)), + make_default_accessors_testcase("time", maket(10, 20, 30)) +), test_name_generator); + +// operator== and operator!= struct ValueEqualityTest : public Test { std::vector values = makevalues( @@ -35,20 +320,6 @@ struct ValueEqualityTest : public Test std::uint32_t(2010), nullptr ); - std::vector values_copy = values; - std::vector other_values = makevalues( - std::int32_t(10), - std::int64_t(-22), - std::uint32_t(0xff6723), - std::uint64_t(222), - -3.0f, - 8e24, - boost::mysql::date(1_d/9/2019_y), - boost::mysql::date(1_d/9/2019_y) + std::chrono::hours(10), - boost::mysql::time(std::chrono::seconds(10)), - std::uint32_t(1900), - nullptr - ); }; TEST_F(ValueEqualityTest, OperatorsEqNe_DifferentType_ReturnNotEquals) @@ -65,6 +336,20 @@ TEST_F(ValueEqualityTest, OperatorsEqNe_DifferentType_ReturnNotEquals) TEST_F(ValueEqualityTest, OperatorsEqNe_SameTypeDifferentValue_ReturnNotEquals) { + auto other_values = makevalues( + std::int32_t(10), + std::int64_t(-22), + std::uint32_t(0xff6723), + std::uint64_t(222), + -3.0f, + 8e24, + boost::mysql::date(1_d/9/2019_y), + boost::mysql::date(1_d/9/2019_y) + std::chrono::hours(10), + boost::mysql::time(std::chrono::seconds(10)), + std::uint32_t(1900), + nullptr + ); + // Note: nullptr_t (the last value) can't have other value than nullptr // so it is excluded from this test for (std::size_t i = 0; i < values.size() - 1; ++i) @@ -76,6 +361,7 @@ TEST_F(ValueEqualityTest, OperatorsEqNe_SameTypeDifferentValue_ReturnNotEquals) TEST_F(ValueEqualityTest, OperatorsEqNe_SameTypeSameValue_ReturnEquals) { + std::vector values_copy = values; for (std::size_t i = 0; i < values.size(); ++i) { EXPECT_TRUE(values.at(i) == values_copy.at(i)) << "i=" << i; @@ -83,18 +369,23 @@ TEST_F(ValueEqualityTest, OperatorsEqNe_SameTypeSameValue_ReturnEquals) } } -// Tests for operator<< -struct ValueStreamParams +// operator<< +struct stream_testcase : named_param { + std::string name; value input; std::string expected; template - ValueStreamParams(T input, std::string expected): input(input), expected(std::move(expected)) {} + stream_testcase(std::string&& name, T input, std::string&& expected) : + name(std::move(name)), + input(input), + expected(std::move(expected)) + { + } }; -std::ostream& operator<<(std::ostream& os, const ValueStreamParams& v) { return os << v.expected; } -struct ValueStreamTest : TestWithParam +struct ValueStreamTest : TestWithParam { }; @@ -106,27 +397,27 @@ TEST_P(ValueStreamTest, OutputStream_Trivial_ProducesExpectedString) } INSTANTIATE_TEST_SUITE_P(Default, ValueStreamTest, Values( - ValueStreamParams(std::int32_t(42), "42"), - ValueStreamParams(std::int32_t(-90), "-90"), - ValueStreamParams(std::uint32_t(42), "42"), - ValueStreamParams(std::int64_t(42), "42"), - ValueStreamParams(std::int64_t(-90), "-90"), - ValueStreamParams(std::uint64_t(42), "42"), - ValueStreamParams("a_string", "a_string"), - ValueStreamParams(2.43f, "2.43"), - ValueStreamParams(8.12, "8.12"), - ValueStreamParams(makedate(2019, 9, 1), "2019-09-01"), - ValueStreamParams(maket(0, 0, 0), "00:00:00:000000"), - ValueStreamParams(maket(24, 0, 0), "24:00:00:000000"), - ValueStreamParams(maket(210, 59, 59, 100), "210:59:59:000100"), - ValueStreamParams(-maket(839, 20, 35, 999999), "-839:20:35:999999"), - ValueStreamParams(maket(0, 2, 5), "00:02:05:000000"), - ValueStreamParams(-maket(0, 21, 45), "-00:21:45:000000"), - ValueStreamParams(maket(0, 0, 1, 234000), "00:00:01:234000"), - ValueStreamParams(-maket(0, 0, 1, 234000), "-00:00:01:234000"), - ValueStreamParams(makedt(2019, 1, 8, 9, 20, 11, 123), "2019-01-08 09:20:11.000123"), - ValueStreamParams(makedt(2019, 1, 8), "2019-01-08 00:00:00.000000"), - ValueStreamParams(nullptr, "") + stream_testcase("null", nullptr, ""), + stream_testcase("i64_positive", std::int64_t(42), "42"), + stream_testcase("i64_negative", std::int64_t(-90), "-90"), + stream_testcase("i64_zero", std::int64_t(0), "0"), + stream_testcase("u64_positive", std::uint64_t(42), "42"), + stream_testcase("u64_zero", std::uint64_t(0), "0"), + stream_testcase("string_view", "a_string", "a_string"), + stream_testcase("float", 2.43f, "2.43"), + stream_testcase("double", 8.12, "8.12"), + stream_testcase("date_regular", makedate(2019, 9, 1), "2019-09-01"), + stream_testcase("date_zero", makedate(0, 9, 1), "0000-09-01"), + stream_testcase("datetime_dhmsu", makedt(2019, 1, 8, 9, 20, 11, 123), "2019-01-08 09:20:11.000123"), + stream_testcase("datetime_d", makedt(2019, 1, 8), "2019-01-08 00:00:00.000000"), + stream_testcase("time_zero", maket(0, 0, 0), "00:00:00:000000"), + stream_testcase("time_positive_h", maket(24, 0, 0), "24:00:00:000000"), + stream_testcase("time_positive_hmsu", maket(210, 59, 59, 100), "210:59:59:000100"), + stream_testcase("time_negative_hmsu", -maket(839, 20, 35, 999999), "-839:20:35:999999"), + stream_testcase("time_positive_ms", maket(0, 2, 5), "00:02:05:000000"), + stream_testcase("time_negative_ms", -maket(0, 21, 45), "-00:21:45:000000"), + stream_testcase("time_positive_su", maket(0, 0, 1, 234000), "00:00:01:234000"), + stream_testcase("time_negative_su", -maket(0, 0, 1, 234000), "-00:00:01:234000") )); }