diff --git a/.appveyor.yml b/.appveyor.yml index 6778ff46..e5c0c753 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -19,6 +19,15 @@ environment: B2_VARIANT: release,debug B2_CXXSTD: 11,14,17 matrix: + # Boost.Build (TODO: recover these when we figure out what happens with Asio 1.74 in Windows) + #- FLAVOR: Visual Studio 2019 + # APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + # PLATFORM: x86 + # B2_TOOLSET: msvc-14.2 + #- FLAVOR: Visual Studio 2019 + # APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + # PLATFORM: x64 + # B2_TOOLSET: msvc-14.2 # CMake - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 CMAKE_BUILD_TYPE: Debug @@ -32,13 +41,3 @@ environment: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 CMAKE_BUILD_TYPE: Release PLATFORM: x86 - # Boost.Build - - FLAVOR: Visual Studio 2019 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PLATFORM: x86 - B2_TOOLSET: msvc-14.2 - - FLAVOR: Visual Studio 2019 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PLATFORM: x64 - B2_TOOLSET: msvc-14.2 - diff --git a/.travis.yml b/.travis.yml index 3486ea37..989df2b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,20 @@ env: matrix: include: + - name: cmake_linux_clang10_debug + <<: *__linux_defaults + compiler: clang + env: + - CMAKE_BUILD_TYPE=Debug + - CMAKE_CXX_STANDARD=20 + - USE_COVERAGE=1 + - CMAKE_CXX_FLAGS="-stdlib=libc++ -DBOOST_ASIO_DISABLE_CONCEPTS" + - CMAKE_OPTIONS="-DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10" + - CMAKE_B2_OPTIONS="stdlib=libc++" + install: + - sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" + - sudo apt install clang-10 libc++-10-dev libc++abi-10-dev + # CMake OSX builds - name: cmake_osx_clang_debug <<: *__osx_defaults @@ -66,19 +80,6 @@ matrix: - B2_TOOLSET=clang - B2_VARIANT=debug,release # Linux CMake builds - - name: cmake_linux_clang10_debug - <<: *__linux_defaults - compiler: clang - env: - - CMAKE_BUILD_TYPE=Debug - - CMAKE_CXX_STANDARD=20 - - USE_COVERAGE=1 - - CMAKE_CXX_FLAGS="-stdlib=libc++ -DBOOST_ASIO_DISABLE_CONCEPTS" - - CMAKE_OPTIONS="-DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10" - install: - - sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" - - sudo apt install clang-10 libc++-10-dev libc++abi-10-dev - - name: cmake_linux_gcc_debug <<: *__linux_defaults compiler: gcc diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d93cbdc..b7551b0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,6 @@ endif() # Includes include(GNUInstallDirs) -include(FetchContent) # Dependencies find_package(Boost 1.72.0 REQUIRED COMPONENTS system) diff --git a/Jamfile b/Jamfile index afad3a4d..135aab9c 100644 --- a/Jamfile +++ b/Jamfile @@ -27,7 +27,7 @@ alias boost_mysql BOOST_ASIO_HAS_DEFAULT_FUNCTION_TEMPLATE_ARGUMENTS=1 BOOST_COROUTINES_NO_DEPRECATION_WARNING=1 BOOST_ALLOW_DEPRECATED_HEADERS=1 - msvc:"/bigobj" + msvc:"/bigobj /Zc:__cplusplus" msvc-14.1:"/permissive-" msvc-14.2:"/permissive-" msvc:_SCL_SECURE_NO_WARNINGS=1 diff --git a/TODO.txt b/TODO.txt index a3c72b26..dab207f0 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,4 +1,3 @@ -Remove gtest Better docs Breaking up the tutorial in pieces Explaining the different overloads and async methods available @@ -40,6 +39,12 @@ Other possible features CLIENT_OPTIONAL_RESULTSET_METADATA Status flags accessors in resultset (for OK_Packet) Technical debt + 1.74 asio breaking changes + Review strand names in unit tests (rebind_executor) + Review polymorphic executor name in examples + Recover Windows b2 builds + Review exception throw by value::get + Mock stream and recover channel unit tests Do we need --with-system --with-date_time Constexpr issues with value and optional Increase int serialization test coverage diff --git a/cmake/test_utils.cmake b/cmake/test_utils.cmake index 15759621..5f089cb5 100644 --- a/cmake/test_utils.cmake +++ b/cmake/test_utils.cmake @@ -5,8 +5,6 @@ # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # -find_package(Python3 REQUIRED) - # Utility function to set warnings and other compile properties of # our test targets function(common_target_settings TARGET_NAME) diff --git a/include/boost/mysql/detail/protocol/binary_serialization.hpp b/include/boost/mysql/detail/protocol/binary_serialization.hpp index 752fd731..484a6350 100644 --- a/include/boost/mysql/detail/protocol/binary_serialization.hpp +++ b/include/boost/mysql/detail/protocol/binary_serialization.hpp @@ -15,15 +15,15 @@ namespace boost { namespace mysql { namespace detail { -inline std::size_t get_binary_value_size( - const serialization_context& ctx, - const value& input -) noexcept; - -inline void serialize_binary_value( - serialization_context& ctx, - const value& input -) noexcept; +template <> +struct serialization_traits : + noop_deserialize +{ + static inline std::size_t get_size_(const serialization_context& ctx, + const value& input) noexcept; + static inline void serialize_(serialization_context& ctx, + const value& input) noexcept; +}; } // detail diff --git a/include/boost/mysql/detail/protocol/impl/binary_serialization.ipp b/include/boost/mysql/detail/protocol/impl/binary_serialization.ipp index e8ca4437..955cd91e 100644 --- a/include/boost/mysql/detail/protocol/impl/binary_serialization.ipp +++ b/include/boost/mysql/detail/protocol/impl/binary_serialization.ipp @@ -149,8 +149,11 @@ struct serialize_visitor } // mysql } // boost - -inline std::size_t boost::mysql::detail::get_binary_value_size( +inline std::size_t +boost::mysql::detail::serialization_traits< + boost::mysql::value, + boost::mysql::detail::serialization_tag::none +>::get_size_( const serialization_context& ctx, const value& input ) noexcept @@ -158,7 +161,11 @@ inline std::size_t boost::mysql::detail::get_binary_value_size( return boost::variant2::visit(size_visitor(ctx), input.to_variant()); } -inline void boost::mysql::detail::serialize_binary_value( +inline void +boost::mysql::detail::serialization_traits< + boost::mysql::value, + boost::mysql::detail::serialization_tag::none +>::serialize_( serialization_context& ctx, const value& input ) noexcept diff --git a/include/boost/mysql/detail/protocol/impl/prepared_statement_messages.hpp b/include/boost/mysql/detail/protocol/impl/prepared_statement_messages.hpp index 485fcd60..08b1b203 100644 --- a/include/boost/mysql/detail/protocol/impl/prepared_statement_messages.hpp +++ b/include/boost/mysql/detail/protocol/impl/prepared_statement_messages.hpp @@ -87,7 +87,7 @@ boost::mysql::detail::serialization_traits< res += get_size(ctx, com_stmt_execute_param_meta_packet{}) * num_params; for (auto it = value.params_begin; it != value.params_end; ++it) { - res += get_binary_value_size(ctx, *it); + res += get_size(ctx, *it); } return res; } @@ -143,7 +143,7 @@ boost::mysql::detail::serialization_traits< // actual values for (auto it = input.params_begin; it != input.params_end; ++it) { - serialize_binary_value(ctx, *it); + serialize(ctx, *it); } } diff --git a/include/boost/mysql/detail/protocol/null_bitmap_traits.hpp b/include/boost/mysql/detail/protocol/null_bitmap_traits.hpp index f8f84941..ea1b0239 100644 --- a/include/boost/mysql/detail/protocol/null_bitmap_traits.hpp +++ b/include/boost/mysql/detail/protocol/null_bitmap_traits.hpp @@ -10,6 +10,7 @@ #include #include +#include namespace boost { namespace mysql { diff --git a/include/boost/mysql/errc.hpp b/include/boost/mysql/errc.hpp index 28fd8387..042a7a89 100644 --- a/include/boost/mysql/errc.hpp +++ b/include/boost/mysql/errc.hpp @@ -8,6 +8,8 @@ #ifndef BOOST_MYSQL_ERRC_HPP #define BOOST_MYSQL_ERRC_HPP +#include + namespace boost { namespace mysql { @@ -1415,6 +1417,12 @@ enum class errc : int wrong_num_params = 65543, ///< The number of parameters passed to the prepared statement does not match the number of actual parameters. }; +/** + * \relates errc + * \brief Streams an error code. + */ +inline std::ostream& operator<<(std::ostream&, errc); + } // mysql } // boost diff --git a/include/boost/mysql/error.hpp b/include/boost/mysql/error.hpp index fb0a03a1..82fed22d 100644 --- a/include/boost/mysql/error.hpp +++ b/include/boost/mysql/error.hpp @@ -10,6 +10,7 @@ #include #include +#include #include "boost/mysql/errc.hpp" /** diff --git a/include/boost/mysql/field_type.hpp b/include/boost/mysql/field_type.hpp index fc17865b..70d12627 100644 --- a/include/boost/mysql/field_type.hpp +++ b/include/boost/mysql/field_type.hpp @@ -9,6 +9,7 @@ #define BOOST_MYSQL_FIELD_TYPE_HPP #include +#include namespace boost { namespace mysql { @@ -51,6 +52,41 @@ enum class field_type _not_computed, }; +/** + * \relates field_type + * \brief Streams a boost::mysql::field_type. + */ +inline std::ostream& operator<<(std::ostream& os, field_type t) +{ + switch (t) + { + case field_type::tinyint: return os << "tinyint"; + case field_type::smallint: return os << "smallint"; + case field_type::mediumint: return os << "mediumint"; + case field_type::int_: return os << "int_"; + case field_type::bigint: return os << "bigint"; + case field_type::float_: return os << "float_"; + case field_type::double_: return os << "double_"; + case field_type::decimal: return os << "decimal"; + case field_type::bit: return os << "bit"; + case field_type::year: return os << "year"; + case field_type::time: return os << "time"; + case field_type::date: return os << "date"; + case field_type::datetime: return os << "datetime"; + case field_type::timestamp: return os << "timestamp"; + case field_type::char_: return os << "char_"; + case field_type::varchar: return os << "varchar"; + case field_type::binary: return os << "binary"; + case field_type::varbinary: return os << "varbinary"; + case field_type::text: return os << "text"; + case field_type::blob: return os << "blob"; + case field_type::enum_: return os << "enum_"; + case field_type::set: return os << "set"; + case field_type::geometry: return os << "geometry"; + default: return os << ""; + } +} + } // mysql } // boost diff --git a/include/boost/mysql/impl/error.hpp b/include/boost/mysql/impl/error.hpp index ece88cc8..844791e8 100644 --- a/include/boost/mysql/impl/error.hpp +++ b/include/boost/mysql/impl/error.hpp @@ -98,5 +98,13 @@ struct error_block } // mysql } // boost +inline std::ostream& boost::mysql::operator<<( + std::ostream& os, + errc value +) +{ + return os << detail::error_to_string(value); +} + #endif diff --git a/include/boost/mysql/row.hpp b/include/boost/mysql/row.hpp index 96cbbd51..81ee7474 100644 --- a/include/boost/mysql/row.hpp +++ b/include/boost/mysql/row.hpp @@ -98,31 +98,6 @@ inline std::ostream& operator<<(std::ostream& os, const row& value) return os << '}'; } -// Allow comparisons between vectors of rows and owning rows -template < - typename RowTypeLeft, - typename RowTypeRight, - typename=typename std::enable_if< - std::is_base_of::value && std::is_base_of::value - >::type -> -inline bool operator==(const std::vector& lhs, const std::vector& rhs) -{ - return detail::container_equals(lhs, rhs); -} - -template < - typename RowTypeLeft, - typename RowTypeRight, - typename=typename std::enable_if< - std::is_base_of::value && std::is_base_of::value - >::type -> -inline bool operator!=(const std::vector& lhs, const std::vector& rhs) -{ - return !(lhs == rhs); -} - } // mysql } // boost diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d92a5477..73709ae4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -5,43 +5,41 @@ # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # -# GTest/GMock -FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.10.0 +find_package(Boost 1.72 REQUIRED COMPONENTS + unit_test_framework + coroutine ) -FetchContent_GetProperties(googletest) -if(NOT googletest_POPULATED) - FetchContent_Populate(googletest) - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Avoid GTest linking against wrong CRT - add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) -endif() +# Test common +add_library(boost_mysql_testing INTERFACE) +target_include_directories( + boost_mysql_testing + INTERFACE + common +) +target_link_libraries( + boost_mysql_testing + INTERFACE + Boost::unit_test_framework + Boost::mysql + Boost::disable_autolinking + Boost::dynamic_linking +) -# Unit testing. We do not use gtest_discover_tests because -# of the runtime penalty (specially considerable in integration tests) +# Unit testing add_executable( boost_mysql_unittests - common/test_main.cpp - unit/detail/auxiliar/static_string.cpp unit/detail/auth/auth_calculator.cpp - unit/detail/protocol/serialization_test_common.cpp - unit/detail/protocol/serialization.cpp - unit/detail/protocol/common_messages.cpp - unit/detail/protocol/handshake_messages.cpp - unit/detail/protocol/query_messages.cpp - unit/detail/protocol/prepared_statement_messages.cpp + unit/detail/auxiliar/static_string.cpp unit/detail/protocol/capabilities.cpp + unit/detail/protocol/date.cpp + unit/detail/protocol/null_bitmap_traits.cpp + unit/detail/protocol/serialization_test.cpp unit/detail/protocol/text_deserialization_value.cpp unit/detail/protocol/text_deserialization_error.cpp unit/detail/protocol/binary_deserialization_value.cpp unit/detail/protocol/binary_deserialization_error.cpp unit/detail/protocol/row_deserialization.cpp - unit/detail/protocol/binary_serialization.cpp - unit/detail/protocol/null_bitmap_traits.cpp - unit/detail/protocol/date.cpp - unit/detail/protocol/channel.cpp unit/metadata.cpp unit/value.cpp unit/row.cpp @@ -49,18 +47,12 @@ add_executable( unit/prepared_statement.cpp unit/resultset.cpp unit/connection.cpp -) -target_include_directories( - boost_mysql_unittests - PRIVATE - common + unit/entry_point.cpp ) target_link_libraries( boost_mysql_unittests PRIVATE - gtest - gmock - Boost::mysql + boost_mysql_testing ) common_target_settings(boost_mysql_unittests) @@ -77,11 +69,8 @@ else() endif() # Integration testing -find_package(Boost REQUIRED COMPONENTS coroutine) - add_executable( boost_mysql_integrationtests - common/test_main.cpp integration/network_functions/network_functions_impl.cpp integration/network_functions/sync.cpp integration/network_functions/async_callback.cpp @@ -98,23 +87,17 @@ add_executable( integration/close_statement.cpp integration/resultset.cpp integration/prepared_statement_lifecycle.cpp - integration/database_types.cpp integration/quit_connection.cpp integration/close_connection.cpp + integration/database_types.cpp + integration/entry_point.cpp ) target_link_libraries( boost_mysql_integrationtests PRIVATE - gtest - gmock - Boost::mysql + boost_mysql_testing Boost::coroutine ) -target_include_directories( - boost_mysql_integrationtests - PRIVATE - common -) common_target_settings(boost_mysql_integrationtests) # Regular integration tests @@ -122,17 +105,17 @@ add_test( NAME boost_mysql_integrationtests COMMAND ${CMAKE_CURRENT_BINARY_DIR}/boost_mysql_integrationtests - "--gtest_filter=-*RequiresSha256*" # Exclude anything using SHA256 + "-t" "!@sha256" # Exclude anything using SHA256 ) # If we are using memcheck, then run a subset of the integration tests # under valgrind. Coroutine tests don't work well under Valgrind, and # SSL tests are too slow. We do some other exclusions to reduce runtime if (BOOST_MYSQL_VALGRIND_TESTS) - set(GTEST_FILTER "--gtest_filter=-*RequiresSha256*:*sslrequire*:*coroutine*:*UNIX*:*noerrinfo*:*TCPDefaultToken*") + set(TEST_FILTER "!@sha256:!@sslrequire:!@async_coroutine_errinfo:!@async_coroutine_noerrinfo:!@unix:!@tcp_default_token") add_memcheck_test( NAME boost_mysql_integrationtests_memcheck - COMMAND ${CMAKE_CURRENT_BINARY_DIR}/boost_mysql_integrationtests ${GTEST_FILTER} + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/boost_mysql_integrationtests "-t" ${TEST_FILTER} ) endif() @@ -143,6 +126,6 @@ if (BOOST_MYSQL_SHA256_TESTS) NAME boost_mysql_integrationtests_sha256 COMMAND ${CMAKE_CURRENT_BINARY_DIR}/boost_mysql_integrationtests - "--gtest_filter=*RequiresSha256*" # Run only SHA256 stuff + "-t" "@sha256" # Run only SHA256 stuff ) endif() diff --git a/test/Jamfile b/test/Jamfile index f28120cc..0760ddeb 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -9,46 +9,39 @@ import os ; if [ os.environ BOOST_MYSQL_SHA256_TESTS ] { - GTEST_FILTER = "*" ; + TEST_FILTER = "*" ; } else { - GTEST_FILTER = "-*RequiresSha256*" ; + TEST_FILTER = "!@sha256" ; } alias boost_mysql_test : /boost/mysql//boost_mysql - /user-config//gtest - /user-config//gmock - common/test_main.cpp + /boost/test//boost_unit_test_framework : : : common + shared ; unit-test boost_mysql_unittests : boost_mysql_test - unit/detail/auxiliar/static_string.cpp unit/detail/auth/auth_calculator.cpp - unit/detail/protocol/serialization_test_common.cpp - unit/detail/protocol/serialization.cpp - unit/detail/protocol/common_messages.cpp - unit/detail/protocol/handshake_messages.cpp - unit/detail/protocol/query_messages.cpp - unit/detail/protocol/prepared_statement_messages.cpp + unit/detail/auxiliar/static_string.cpp unit/detail/protocol/capabilities.cpp + unit/detail/protocol/date.cpp + unit/detail/protocol/null_bitmap_traits.cpp + unit/detail/protocol/serialization_test.cpp unit/detail/protocol/text_deserialization_value.cpp unit/detail/protocol/text_deserialization_error.cpp unit/detail/protocol/binary_deserialization_value.cpp unit/detail/protocol/binary_deserialization_error.cpp unit/detail/protocol/row_deserialization.cpp - unit/detail/protocol/binary_serialization.cpp - unit/detail/protocol/null_bitmap_traits.cpp - unit/detail/protocol/channel.cpp unit/metadata.cpp unit/value.cpp unit/row.cpp @@ -56,6 +49,7 @@ unit-test boost_mysql_unittests unit/prepared_statement.cpp unit/resultset.cpp unit/connection.cpp + unit/entry_point.cpp ; unit-test boost_mysql_integrationtests @@ -78,11 +72,12 @@ unit-test boost_mysql_integrationtests integration/close_statement.cpp integration/resultset.cpp integration/prepared_statement_lifecycle.cpp - integration/database_types.cpp integration/quit_connection.cpp integration/close_connection.cpp + integration/database_types.cpp + integration/entry_point.cpp : - "--gtest_filter=$(GTEST_FILTER)" + "-t '$(TEST_FILTER)'" ; build-project ../example ; diff --git a/test/common/test_common.hpp b/test/common/test_common.hpp index 6f55d8b0..fd6ffe67 100644 --- a/test/common/test_common.hpp +++ b/test/common/test_common.hpp @@ -10,27 +10,22 @@ #include "boost/mysql/value.hpp" #include "boost/mysql/row.hpp" -#include "boost/mysql/error.hpp" #include "boost/mysql/connection_params.hpp" #include "boost/mysql/detail/auxiliar/stringize.hpp" -#include "boost/mysql/detail/protocol/date.hpp" -#include -#include -#include +#include #include #include -#include -#include #include #include -#include namespace boost { namespace mysql { namespace test { +using detail::stringize; + template -std::vector makevalues(Types&&... args) +std::vector make_value_vector(Types&&... args) { return std::vector{mysql::value(std::forward(args))...}; } @@ -38,21 +33,7 @@ std::vector makevalues(Types&&... args) template row makerow(Types&&... args) { - return row(makevalues(std::forward(args)...)); -} - -template -std::vector makerows(std::size_t row_size, Types&&... args) -{ - auto values = makevalues(std::forward(args)...); - assert(values.size() % row_size == 0); - std::vector res; - for (std::size_t i = 0; i < values.size(); i += row_size) - { - std::vector row_values (values.begin() + i, values.begin() + i + row_size); - res.push_back(row(std::move(row_values))); - } - return res; + return row(make_value_vector(std::forward(args)...)); } inline date makedate(int num_years, unsigned num_months, unsigned num_days) @@ -61,14 +42,15 @@ inline date makedate(int num_years, unsigned num_months, unsigned num_days) detail::year_month_day{num_years, num_months, num_days}))); } -inline datetime makedt(int years, unsigned months, unsigned days, int hours=0, int mins=0, int secs=0, int micros=0) +inline datetime makedt(int years, unsigned months, unsigned days, + int hours=0, int mins=0, int secs=0, int micros=0) { return datetime(makedate(years, months, days)) + std::chrono::hours(hours) + std::chrono::minutes(mins) + std::chrono::seconds(secs) + std::chrono::microseconds(micros); } -inline mysql::time maket(int hours, int mins, int secs, int micros=0) +inline time maket(int hours, int mins, int secs, int micros=0) { return std::chrono::hours(hours) + std::chrono::minutes(mins) + std::chrono::seconds(secs) + std::chrono::microseconds(micros); @@ -92,55 +74,29 @@ inline boost::string_view makesv(const std::uint8_t* value, std::size_t size) } -inline void validate_string_contains(std::string value, const std::vector& to_check) +inline void validate_string_contains( + std::string value, + const std::vector& to_check +) { std::transform(value.begin(), value.end(), value.begin(), &tolower); for (const auto& elm: to_check) { - EXPECT_THAT(value, testing::HasSubstr(elm)); + BOOST_TEST(value.find(elm) != std::string::npos, + "Substring '" << elm << "' not found in '" << value << "'"); } } -inline void validate_error_info(const boost::mysql::error_info& value, const std::vector& to_check) -{ - validate_string_contains(value.message(), to_check); -} - -inline std::string buffer_diff(boost::string_view s0, boost::string_view s1) -{ - std::ostringstream ss; - ss << std::hex; - for (std::size_t i = 0; i < std::min(s0.size(), s1.size()); ++i) - { - unsigned b0 = reinterpret_cast(s0.data())[i]; - unsigned b1 = reinterpret_cast(s1.data())[i]; - if (b0 != b1) - { - ss << "i=" << i << ": " << b0 << " != " << b1 << "\n"; - } - } - if (s0.size() != s1.size()) - { - ss << "sizes: " << s0.size() << " != " << s1.size() << "\n"; - } - return ss.str(); -} - -inline void compare_buffers(boost::string_view s0, boost::string_view s1, const char* msg = "") -{ - EXPECT_EQ(s0, s1) << msg << ":\n" << buffer_diff(s0, s1); -} - -inline void concat(std::vector& lhs, boost::asio::const_buffer rhs) +inline void concat(std::vector& lhs, const void* buff, std::size_t size) { auto current_size = lhs.size(); - lhs.resize(current_size + rhs.size()); - memcpy(lhs.data() + current_size, rhs.data(), rhs.size()); + lhs.resize(current_size + size); + memcpy(lhs.data() + current_size, buff, size); } inline void concat(std::vector& lhs, const std::vector& rhs) { - concat(lhs, boost::asio::buffer(rhs)); + concat(lhs, rhs.data(), rhs.size()); } inline std::vector concat_copy( @@ -163,40 +119,53 @@ inline const char* to_string(ssl_mode m) } } -// All the param types used in our parametric tests will have -// a name() member function convertible to std::string. GTest needs -// the parameter to be streamable; we tag our types with this named_tag -// so the operator<< is found by ADL. -struct named_tag {}; - -class named : public named_tag +inline const char* to_string(detail::protocol_field_type t) noexcept { - std::string name_; -public: - named(std::string&& n): name_(std::move(n)) {} - const std::string& name() const noexcept { return name_; } -}; - -template < - typename T, - typename=typename std::enable_if< - std::is_base_of::value - >::type -> -std::ostream& operator<<(std::ostream& os, const T& v) { return os << v.name(); } - -struct test_name_generator_t -{ - template - std::string operator()(const testing::TestParamInfo& param_info) const + switch (t) { - return param_info.param.name(); + case detail::protocol_field_type::decimal: return "decimal"; + case detail::protocol_field_type::tiny: return "tiny"; + case detail::protocol_field_type::short_: return "short_"; + case detail::protocol_field_type::long_: return "long_"; + case detail::protocol_field_type::float_: return "float_"; + case detail::protocol_field_type::double_: return "double_"; + case detail::protocol_field_type::null: return "null"; + case detail::protocol_field_type::timestamp: return "timestamp"; + case detail::protocol_field_type::longlong: return "longlong"; + case detail::protocol_field_type::int24: return "int24"; + case detail::protocol_field_type::date: return "date"; + case detail::protocol_field_type::time: return "time"; + case detail::protocol_field_type::datetime: return "datetime"; + case detail::protocol_field_type::year: return "year"; + case detail::protocol_field_type::varchar: return "varchar"; + case detail::protocol_field_type::bit: return "bit"; + case detail::protocol_field_type::newdecimal: return "newdecimal"; + case detail::protocol_field_type::enum_: return "enum_"; + case detail::protocol_field_type::set: return "set"; + case detail::protocol_field_type::tiny_blob: return "tiny_blob"; + case detail::protocol_field_type::medium_blob: return "medium_blob"; + case detail::protocol_field_type::long_blob: return "long_blob"; + case detail::protocol_field_type::blob: return "blob"; + case detail::protocol_field_type::var_string: return "var_string"; + case detail::protocol_field_type::string: return "string"; + case detail::protocol_field_type::geometry: return "geometry"; + default: return "unknown"; } -}; - -constexpr test_name_generator_t test_name_generator; +} } // test + +// make protocol_field_type streamable +namespace detail { + +inline std::ostream& operator<<(std::ostream& os, protocol_field_type t) +{ + return os << test::to_string(t); +} + +} // detail + + } // mysql } // boost diff --git a/test/common/test_main.cpp b/test/common/test_main.cpp deleted file mode 100644 index f84e6815..00000000 --- a/test/common/test_main.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// -// Copyright (c) 2019-2020 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#include -#include - -using namespace testing; - -namespace -{ - -class reduced_listener : public testing::TestEventListener -{ - std::unique_ptr default_listener_; -public: - explicit reduced_listener(std::unique_ptr default_listener) : - default_listener_(std::move(default_listener)) {} - - - void OnTestProgramStart(const UnitTest& unit_test) override - { - default_listener_->OnTestProgramStart(unit_test); - } - - void OnTestIterationStart(const UnitTest& unit_test, int iteration) override - { - default_listener_->OnTestIterationStart(unit_test, iteration); - } - - void OnEnvironmentsSetUpStart(const UnitTest&) override {} - - void OnEnvironmentsSetUpEnd(const UnitTest&) override {} - - void OnTestCaseStart(const TestCase&) override {} - - void OnTestStart(const TestInfo&) override {} - - void OnTestPartResult(const TestPartResult& part) override - { - // This is the code that prints what happened on a failure - if (part.failed() || part.skipped()) - { - default_listener_->OnTestPartResult(part); - } - } - - void OnTestEnd(const TestInfo& test_info) override - { - if (test_info.result()->Failed()) - { - default_listener_->OnTestEnd(test_info); - } - } - - void OnTestCaseEnd(const TestCase&) override {} - - void OnEnvironmentsTearDownStart(const UnitTest&) override {} - - void OnEnvironmentsTearDownEnd(const UnitTest&) override {} - - void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override - { - default_listener_->OnTestIterationEnd(unit_test, iteration); - } - - void OnTestProgramEnd(const UnitTest& unit_test) override - { - default_listener_->OnTestProgramEnd(unit_test); - } -}; - -} - -int main(int argc, char** argv) -{ - InitGoogleTest(&argc, argv); - - // remove the default listener and insert ours - TestEventListeners& listeners = UnitTest::GetInstance()->listeners(); - std::unique_ptr default_printer ( - listeners.Release(listeners.default_result_printer())); - listeners.Append(new reduced_listener(std::move(default_printer))); - - // run - return RUN_ALL_TESTS(); -} - diff --git a/test/integration/close_connection.cpp b/test/integration/close_connection.cpp index 1d556f0c..c759dfdf 100644 --- a/test/integration/close_connection.cpp +++ b/test/integration/close_connection.cpp @@ -11,64 +11,49 @@ using namespace boost::mysql::test; using boost::mysql::error_code; using boost::mysql::socket_connection; -namespace +BOOST_AUTO_TEST_SUITE(test_close_connection) + +BOOST_MYSQL_NETWORK_TEST(active_connection, network_fixture, network_ssl_gen) { + // Connect + this->connect(sample.ssl); -template -struct CloseConnectionTest : public NetworkTest + // Close + auto result = sample.net->close(this->conn); + result.validate_no_error(); + + // We are no longer able to query + auto query_result = sample.net->query(this->conn, "SELECT 1"); + query_result.validate_any_error(); + + // The stream is closed + BOOST_TEST(!this->conn.next_layer().is_open()); +} + +BOOST_MYSQL_NETWORK_TEST(double_close, network_fixture, network_ssl_gen) { - network_functions* net = NetworkTest::GetParam().net; + // Connect + this->connect(sample.ssl); - void ActiveConnection_ClosesIt() - { - // Connect - this->connect(this->GetParam().ssl); + // Close + auto result = sample.net->close(this->conn); + result.validate_no_error(); - // Close - auto result = net->close(this->conn); - result.validate_no_error(); + // The stream (socket) is closed + BOOST_TEST(!this->conn.next_layer().is_open()); - // We are no longer able to query - auto query_result = net->query(this->conn, "SELECT 1"); - query_result.validate_any_error(); + // Closing again returns OK (and does nothing) + result = sample.net->close(this->conn); + result.validate_no_error(); - // The stream is closed - EXPECT_FALSE(this->conn.next_layer().is_open()); - } - - void DoubleClose_Ok() - { - // Connect - this->connect(this->GetParam().ssl); - - // Close - auto result = net->close(this->conn); - result.validate_no_error(); - - // The stream (socket) is closed - EXPECT_FALSE(this->conn.next_layer().is_open()); - - // Closing again returns OK (and does nothing) - result = net->close(this->conn); - result.validate_no_error(); - - // Stream (socket) still closed - EXPECT_FALSE(this->conn.next_layer().is_open()); - } - - void CloseNotOpenedConnection_Ok() - { - auto result = net->close(this->conn); - result.validate_no_error(); - } -}; - -BOOST_MYSQL_NETWORK_TEST_SUITE(CloseConnectionTest) - -BOOST_MYSQL_NETWORK_TEST(CloseConnectionTest, ActiveConnection_ClosesIt) -BOOST_MYSQL_NETWORK_TEST(CloseConnectionTest, DoubleClose_Ok) -BOOST_MYSQL_NETWORK_TEST(CloseConnectionTest, CloseNotOpenedConnection_Ok) - -} // anon namespace + // Stream (socket) still closed + BOOST_TEST(!this->conn.next_layer().is_open()); +} +BOOST_MYSQL_NETWORK_TEST(not_open_connection, network_fixture, network_ssl_gen) +{ + auto result = sample.net->close(this->conn); + result.validate_no_error(); +} +BOOST_AUTO_TEST_SUITE_END() // test_close_connection diff --git a/test/integration/close_statement.cpp b/test/integration/close_statement.cpp index faf6f778..0131b8cb 100644 --- a/test/integration/close_statement.cpp +++ b/test/integration/close_statement.cpp @@ -9,48 +9,33 @@ using namespace boost::mysql::test; -namespace +BOOST_AUTO_TEST_SUITE(test_close_statement) + +BOOST_MYSQL_NETWORK_TEST(existing_or_closed_statement, network_fixture, network_ssl_gen) { + this->connect(sample.ssl); + auto* net = sample.net; + // Prepare a statement + auto stmt = net->prepare_statement(this->conn, "SELECT * FROM empty_table"); + stmt.validate_no_error(); -template -struct CloseStatementTest : NetworkTest -{ - CloseStatementTest() - { - this->connect(this->GetParam().ssl); - } + // Verify it works fine + auto exec_result = net->execute_statement(stmt.value, {}); + exec_result.validate_no_error(); + exec_result.value.fetch_all(); // clean all packets sent for this execution - void ExistingOrClosedStatement() - { - auto* net = this->GetParam().net; - - // Prepare a statement - auto stmt = net->prepare_statement(this->conn, "SELECT * FROM empty_table"); - stmt.validate_no_error(); - - // Verify it works fine - auto exec_result = net->execute_statement(stmt.value, {}); - exec_result.validate_no_error(); - exec_result.value.fetch_all(); // clean all packets sent for this execution - - // Close the statement - auto close_result = net->close_statement(stmt.value); - close_result.validate_no_error(); - - // Close it again - close_result = net->close_statement(stmt.value); - close_result.validate_no_error(); - - // Verify close took effect - exec_result = net->execute_statement(stmt.value, {}); - exec_result.validate_error(boost::mysql::errc::unknown_stmt_handler, {"unknown prepared statement"}); - } -}; - -BOOST_MYSQL_NETWORK_TEST_SUITE(CloseStatementTest) -BOOST_MYSQL_NETWORK_TEST(CloseStatementTest, ExistingOrClosedStatement) + // Close the statement + auto close_result = net->close_statement(stmt.value); + close_result.validate_no_error(); + // Close it again + close_result = net->close_statement(stmt.value); + close_result.validate_no_error(); + // Verify close took effect + exec_result = net->execute_statement(stmt.value, {}); + exec_result.validate_error(boost::mysql::errc::unknown_stmt_handler, {"unknown prepared statement"}); } +BOOST_AUTO_TEST_SUITE_END() // test_close_statement diff --git a/test/integration/connect.cpp b/test/integration/connect.cpp index 9f6916b4..60e4f6dc 100644 --- a/test/integration/connect.cpp +++ b/test/integration/connect.cpp @@ -12,58 +12,37 @@ using boost::mysql::socket_connection; using boost::mysql::errc; using boost::mysql::error_code; -namespace +BOOST_AUTO_TEST_SUITE(test_connect) + +BOOST_MYSQL_NETWORK_TEST(physical_and_handshake_ok, network_fixture, network_ssl_gen) { + auto ep = get_endpoint(endpoint_kind::localhost); + auto result = sample.net->connect(this->conn, ep, this->params); + result.validate_no_error(); -template -struct ConnectTest : NetworkTest -{ - network_functions* net; - socket_connection other_conn; - - ConnectTest(): - net(this->GetParam().net), - other_conn(this->ctx) - { - } - - void AllSucceeded_SuccessfulLogin() - { - auto ep = get_endpoint(endpoint_kind::localhost); - auto result = net->connect(other_conn, ep, this->params); - result.validate_no_error(); - - // We are able to query - auto query_result = net->query(other_conn, "SELECT 1"); - query_result.validate_no_error(); - query_result.value.fetch_all(); // discard any result - } - - void PhysicalConnectFailed_FailsClosesStream() - { - auto ep = get_endpoint(endpoint_kind::inexistent); - auto result = net->connect(other_conn, ep, this->params); - // depending on system and stream type, error code will be different - result.validate_any_error({"physical connect failed"}); - EXPECT_FALSE(other_conn.next_layer().is_open()); - } - - void PhysicalConnectSucceededHandshakeFailed_FailsClosesStream() - { - auto ep = get_endpoint(endpoint_kind::localhost); - this->set_credentials("integ_user", "bad_password"); - auto result = net->connect(other_conn, ep, this->params); - result.validate_error(errc::access_denied_error, {"access denied", "integ_user"}); - EXPECT_FALSE(other_conn.next_layer().is_open()); - } - -}; - -BOOST_MYSQL_NETWORK_TEST_SUITE(ConnectTest) - -BOOST_MYSQL_NETWORK_TEST(ConnectTest, AllSucceeded_SuccessfulLogin) -BOOST_MYSQL_NETWORK_TEST(ConnectTest, PhysicalConnectFailed_FailsClosesStream) -BOOST_MYSQL_NETWORK_TEST(ConnectTest, PhysicalConnectSucceededHandshakeFailed_FailsClosesStream) - + // We are able to query + auto query_result = sample.net->query(this->conn, "SELECT 1"); + query_result.validate_no_error(); + query_result.value.fetch_all(); // discard any result } +BOOST_MYSQL_NETWORK_TEST(physical_error, network_fixture, network_ssl_gen) +{ + auto ep = get_endpoint(endpoint_kind::inexistent); + auto result = sample.net->connect(this->conn, ep, this->params); + // depending on system and stream type, error code will be different + result.validate_any_error({"physical connect failed"}); + BOOST_TEST(!this->conn.next_layer().is_open()); +} + +BOOST_MYSQL_NETWORK_TEST(physical_ok_handshake_error, network_fixture, network_ssl_gen) +{ + auto ep = get_endpoint(endpoint_kind::localhost); + this->set_credentials("integ_user", "bad_password"); + auto result = sample.net->connect(this->conn, ep, this->params); + result.validate_error(errc::access_denied_error, {"access denied", "integ_user"}); + BOOST_TEST(!this->conn.next_layer().is_open()); +} + +BOOST_AUTO_TEST_SUITE_END() // test_connect + diff --git a/test/integration/connection.cpp b/test/integration/connection.cpp index 61484fd3..15eda841 100644 --- a/test/integration/connection.cpp +++ b/test/integration/connection.cpp @@ -7,25 +7,20 @@ #include "boost/mysql/connection.hpp" #include "integration_test_common.hpp" -#include +#include "get_endpoint.hpp" using namespace boost::mysql::test; using boost::mysql::connection; using boost::mysql::socket_connection; using boost::asio::ip::tcp; -namespace -{ +BOOST_AUTO_TEST_SUITE(test_connection) -struct ConnectionTest : IntegTest -{ -}; - -TEST_F(ConnectionTest, MoveConstructor_ConnectedConnection_UsableConnection) +BOOST_FIXTURE_TEST_CASE(move_constructor_connected_connection, network_fixture) { // First connection connection first (ctx); - EXPECT_TRUE(first.valid()); + BOOST_TEST(first.valid()); // Connect first.next_layer().connect(get_endpoint(endpoint_kind::localhost)); @@ -33,18 +28,18 @@ TEST_F(ConnectionTest, MoveConstructor_ConnectedConnection_UsableConnection) // Construct second connection connection second (std::move(first)); - EXPECT_FALSE(first.valid()); - EXPECT_TRUE(second.valid()); - EXPECT_NO_THROW(second.query("SELECT 1")); + BOOST_TEST(!first.valid()); + BOOST_TEST(second.valid()); + second.query("SELECT 1"); // doesn't throw } -TEST_F(ConnectionTest, MoveAssignment_FromConnectedConnection_UsableConnection) +BOOST_FIXTURE_TEST_CASE(move_assignment_from_connected_connection, network_fixture) { // First connection connection first (ctx); connection second (ctx); - EXPECT_TRUE(first.valid()); - EXPECT_TRUE(second.valid()); + BOOST_TEST(first.valid()); + BOOST_TEST(second.valid()); // Connect first.next_layer().connect(get_endpoint(endpoint_kind::localhost)); @@ -52,47 +47,47 @@ TEST_F(ConnectionTest, MoveAssignment_FromConnectedConnection_UsableConnection) // Move second = std::move(first); - EXPECT_FALSE(first.valid()); - EXPECT_TRUE(second.valid()); - EXPECT_NO_THROW(second.query("SELECT 1")); + BOOST_TEST(!first.valid()); + BOOST_TEST(second.valid()); + second.query("SELECT 1"); // doesn't throw } -struct SocketConnectionTest : IntegTest -{ -}; +BOOST_AUTO_TEST_SUITE_END() // test_connection -TEST_F(SocketConnectionTest, MoveConstructor_ConnectedConnection_UsableConnection) +BOOST_AUTO_TEST_SUITE(test_socket_connection) + +BOOST_FIXTURE_TEST_CASE(move_constructor_connected_connection, network_fixture) { // First connection socket_connection first (ctx); - EXPECT_TRUE(first.valid()); + BOOST_TEST(first.valid()); // Connect first.connect(get_endpoint(endpoint_kind::localhost), params); // Construct second connection socket_connection second (std::move(first)); - EXPECT_FALSE(first.valid()); - EXPECT_TRUE(second.valid()); - EXPECT_NO_THROW(second.query("SELECT 1")); + BOOST_TEST(!first.valid()); + BOOST_TEST(second.valid()); + second.query("SELECT 1"); // doesn't throw } -TEST_F(SocketConnectionTest, MoveAssignment_FromConnectedConnection_UsableConnection) +BOOST_FIXTURE_TEST_CASE(move_assignment_from_connected_connection, network_fixture) { // First connection socket_connection first (ctx); socket_connection second (ctx); - EXPECT_TRUE(first.valid()); - EXPECT_TRUE(second.valid()); + BOOST_TEST(first.valid()); + BOOST_TEST(second.valid()); // Connect first.connect(get_endpoint(endpoint_kind::localhost), params); // Move second = std::move(first); - EXPECT_FALSE(first.valid()); - EXPECT_TRUE(second.valid()); - EXPECT_NO_THROW(second.query("SELECT 1")); + BOOST_TEST(!first.valid()); + BOOST_TEST(second.valid()); + second.query("SELECT 1"); // doesn't throw } -} +BOOST_AUTO_TEST_SUITE_END() // test_socket_connection diff --git a/test/integration/database_types.cpp b/test/integration/database_types.cpp index 06cebb2b..bd6fc897 100644 --- a/test/integration/database_types.cpp +++ b/test/integration/database_types.cpp @@ -8,21 +8,21 @@ #include "integration_test_common.hpp" #include "metadata_validator.hpp" #include "test_common.hpp" -#include +#include +#include #include #include using namespace boost::mysql::test; -using namespace testing; +using namespace boost::unit_test; using boost::mysql::value; using boost::mysql::field_metadata; using boost::mysql::field_type; using boost::mysql::row; using boost::mysql::datetime; -using boost::mysql::detail::stringize; +using boost::mysql::make_values; -namespace -{ +BOOST_AUTO_TEST_SUITE(test_database_types) /** * These tests try to cover a range of the possible types and values MySQL support. @@ -30,7 +30,7 @@ namespace * we validate that we get the expected metadata and the expected value. The cases are * defined in SQL in db_setup.sql */ -struct database_types_testcase : named_tag +struct database_types_sample { std::string table; std::string field; @@ -38,13 +38,8 @@ struct database_types_testcase : named_tag value expected_value; meta_validator mvalid; - std::string name() const - { - return stringize(table, '_', field, '_', row_id); - } - template - database_types_testcase(std::string table, std::string field, std::string row_id, + database_types_sample(std::string table, std::string field, std::string row_id, ValueType&& expected_value, Args&&... args) : table(table), field(field), @@ -55,242 +50,159 @@ struct database_types_testcase : named_tag } }; -struct DatabaseTypesTest : - NetworkTest +std::ostream& operator<<(std::ostream& os, const database_types_sample& input) { - DatabaseTypesTest() + return os << input.table << '.' << input.field << '.' << input.row_id; +} + +// Fixture +struct database_types_fixture : network_fixture +{ + database_types_fixture() { connect(boost::mysql::ssl_mode::disable); } }; -TEST_P(DatabaseTypesTest, Query_MetadataAndValueCorrect) -{ - const auto& param = GetParam(); - - // Compose the query - std::ostringstream queryss; - queryss << "SELECT " << param.field - << " FROM " << param.table - << " WHERE id = '" << param.row_id << "'"; - auto query = queryss.str(); - - // Execute it - auto result = conn.query(query); - auto rows = result.fetch_all(); - - // Validate the received metadata - validate_meta(result.fields(), {param.mvalid}); - - // Validate the returned value - row expected_row ({param.expected_value}); - ASSERT_EQ(rows.size(), 1); - ASSERT_EQ(rows[0].values().size(), 1); - EXPECT_EQ(rows[0].values()[0], param.expected_value); -} - -TEST_P(DatabaseTypesTest, PreparedStatementExecuteResult_MetadataAndValueCorrect) -{ - // Parameter - const auto& param = GetParam(); - - // Prepare the statement - std::ostringstream stmtss; - stmtss << "SELECT " << param.field - << " FROM " << param.table - << " WHERE id = ?"; - auto stmt_sql = stmtss.str(); - auto stmt = conn.prepare_statement(stmt_sql); - - // Execute it with the provided parameters - auto result = stmt.execute(makevalues(param.row_id)); - auto rows = result.fetch_all(); - - // Validate the received metadata - validate_meta(result.fields(), {param.mvalid}); - - // Validate the returned value - ASSERT_EQ(rows.size(), 1); - ASSERT_EQ(rows[0].values().size(), 1); - EXPECT_EQ(rows[0].values()[0], param.expected_value); -} - -TEST_P(DatabaseTypesTest, PreparedStatementExecuteParam_ValueSerializedCorrectly) -{ - // This test is not applicable (yet) to nullptr values or bit values. - // Doing "field = ?" where ? is nullptr never matches anything. - // Bit values are returned as strings bit need to be sent as integers in - // prepared statements. TODO: make this better. - const auto& param = GetParam(); - if (param.expected_value == value(nullptr) || - param.mvalid.type() == field_type::bit) - { - return; - } - - // Prepare the statement - std::ostringstream stmtss; - stmtss << "SELECT " << param.field - << " FROM " << param.table - << " WHERE id = ? AND " << param.field << " = ?"; - auto stmt_sql = stmtss.str(); - auto stmt = conn.prepare_statement(stmt_sql); - - // Execute it with the provided parameters - auto result = stmt.execute(makevalues(param.row_id, param.expected_value)); - auto rows = result.fetch_all(); - - // Validate the returned value - row expected_row ({param.expected_value}); - ASSERT_EQ(rows.size(), 1); - EXPECT_EQ(static_cast(rows[0]), expected_row); // make gtest pick operator<< -} - +// Helpers using flagsvec = std::vector; const flagsvec no_flags {}; const flagsvec flags_unsigned { &field_metadata::is_unsigned }; const flagsvec flags_zerofill { &field_metadata::is_unsigned, &field_metadata::is_zerofill }; -// Integers -INSTANTIATE_TEST_SUITE_P(TINYINT, DatabaseTypesTest, Values( - database_types_testcase("types_tinyint", "field_signed", "regular", std::int64_t(20), field_type::tinyint), - database_types_testcase("types_tinyint", "field_signed", "negative", std::int64_t(-20), field_type::tinyint), - database_types_testcase("types_tinyint", "field_signed", "min", std::int64_t(-0x80), field_type::tinyint), - database_types_testcase("types_tinyint", "field_signed", "max", std::int64_t(0x7f), field_type::tinyint), +// Int cases +void add_int_samples_helper( + const char* table_name, + field_type type, + std::int64_t signed_min, + std::int64_t signed_max, + std::uint64_t unsigned_max, + std::vector& output +) +{ + output.emplace_back(table_name, "field_signed", "regular", std::int64_t(20), type); + output.emplace_back(table_name, "field_signed", "negative", std::int64_t(-20), type); + output.emplace_back(table_name, "field_signed", "min", signed_min, type); + output.emplace_back(table_name, "field_signed", "max", signed_max, type); - database_types_testcase("types_tinyint", "field_unsigned", "regular", std::uint64_t(20), field_type::tinyint, flags_unsigned), - database_types_testcase("types_tinyint", "field_unsigned", "min", std::uint64_t(0), field_type::tinyint, flags_unsigned), - database_types_testcase("types_tinyint", "field_unsigned", "max", std::uint64_t(0xff), field_type::tinyint, flags_unsigned), + output.emplace_back(table_name, "field_unsigned", "regular", std::uint64_t(20), type, flags_unsigned); + output.emplace_back(table_name, "field_unsigned", "min", std::uint64_t(0), type, flags_unsigned); + output.emplace_back(table_name, "field_unsigned", "max", unsigned_max, type, flags_unsigned); - database_types_testcase("types_tinyint", "field_width", "regular", std::int64_t(20), field_type::tinyint), - database_types_testcase("types_tinyint", "field_width", "negative", std::int64_t(-20), field_type::tinyint), + output.emplace_back(table_name, "field_width", "regular", std::int64_t(20), type); + output.emplace_back(table_name, "field_width", "negative", std::int64_t(-20), type); - database_types_testcase("types_tinyint", "field_zerofill", "regular", std::uint64_t(20), field_type::tinyint, flags_zerofill), - database_types_testcase("types_tinyint", "field_zerofill", "min", std::uint64_t(0), field_type::tinyint, flags_zerofill) -), test_name_generator); + output.emplace_back(table_name, "field_zerofill", "regular", std::uint64_t(20), type, flags_zerofill); + output.emplace_back(table_name, "field_zerofill", "min", std::uint64_t(0), type, flags_zerofill); +} -INSTANTIATE_TEST_SUITE_P(SMALLINT, DatabaseTypesTest, Values( - database_types_testcase("types_smallint", "field_signed", "regular", std::int64_t(20), field_type::smallint), - database_types_testcase("types_smallint", "field_signed", "negative", std::int64_t(-20), field_type::smallint), - database_types_testcase("types_smallint", "field_signed", "min", std::int64_t(-0x8000), field_type::smallint), - database_types_testcase("types_smallint", "field_signed", "max", std::int64_t(0x7fff), field_type::smallint), +void add_int_samples(std::vector& output) +{ + add_int_samples_helper("types_tinyint", field_type::tinyint, + -0x80, 0x7f, 0xff, output); + add_int_samples_helper("types_smallint", field_type::smallint, + -0x8000, 0x7fff, 0xffff, output); + add_int_samples_helper("types_mediumint", field_type::mediumint, + -0x800000, 0x7fffff, 0xffffff, output); + add_int_samples_helper("types_int", field_type::int_, + -0x80000000LL, 0x7fffffff, 0xffffffff, output); + add_int_samples_helper("types_bigint", field_type::bigint, + -0x7fffffffffffffff - 1, 0x7fffffffffffffff, 0xffffffffffffffff, output); +} - database_types_testcase("types_smallint", "field_unsigned", "regular", std::uint64_t(20), field_type::smallint, flags_unsigned), - database_types_testcase("types_smallint", "field_unsigned", "min", std::uint64_t(0), field_type::smallint, flags_unsigned), - database_types_testcase("types_smallint", "field_unsigned", "max", std::uint64_t(0xffff), field_type::smallint, flags_unsigned), +// Floating point cases +void add_float_samples(std::vector& output) +{ + output.emplace_back("types_float", "field_signed", "zero", + 0.0f, field_type::float_, no_flags, 31); + output.emplace_back("types_float", "field_signed", "int_positive", + 4.0f, field_type::float_, no_flags, 31); + output.emplace_back("types_float", "field_signed", "int_negative", + -4.0f, field_type::float_, no_flags, 31); + output.emplace_back("types_float", "field_signed", "fractional_positive", + 4.2f, field_type::float_, no_flags, 31); + output.emplace_back("types_float", "field_signed", "fractional_negative", + -4.2f, field_type::float_, no_flags, 31); + output.emplace_back("types_float", "field_signed", "positive_exp_positive_int", + 3e20f, field_type::float_, no_flags, 31); + output.emplace_back("types_float", "field_signed", "positive_exp_negative_int", + -3e20f, field_type::float_, no_flags, 31); + output.emplace_back("types_float", "field_signed", "positive_exp_positive_fractional", + 3.14e20f, field_type::float_, no_flags, 31); + output.emplace_back("types_float", "field_signed", "positive_exp_negative_fractional", + -3.14e20f, field_type::float_, no_flags, 31); + output.emplace_back("types_float", "field_signed", "negative_exp_positive_fractional", + 3.14e-20f, field_type::float_, no_flags, 31); - database_types_testcase("types_smallint", "field_width", "regular", std::int64_t(20), field_type::smallint), - database_types_testcase("types_smallint", "field_width", "negative", std::int64_t(-20), field_type::smallint), + output.emplace_back("types_float", "field_unsigned", "zero", + 0.0f, field_type::float_, flags_unsigned, 31); + output.emplace_back("types_float", "field_unsigned", "fractional_positive", + 4.2f, field_type::float_, flags_unsigned, 31); - database_types_testcase("types_smallint", "field_zerofill", "regular", std::uint64_t(20), field_type::smallint, flags_zerofill), - database_types_testcase("types_smallint", "field_zerofill", "min", std::uint64_t(0), field_type::smallint, flags_zerofill) -), test_name_generator); + output.emplace_back("types_float", "field_width", "zero", + 0.0f, field_type::float_, no_flags, 10); + output.emplace_back("types_float", "field_width", "fractional_positive", + 4.2f, field_type::float_, no_flags, 10); + output.emplace_back("types_float", "field_width", "fractional_negative", + -4.2f, field_type::float_, no_flags, 10); -INSTANTIATE_TEST_SUITE_P(MEDIUMINT, DatabaseTypesTest, Values( - database_types_testcase("types_mediumint", "field_signed", "regular", std::int64_t(20), field_type::mediumint), - database_types_testcase("types_mediumint", "field_signed", "negative", std::int64_t(-20), field_type::mediumint), - database_types_testcase("types_mediumint", "field_signed", "min", std::int64_t(-0x800000), field_type::mediumint), - database_types_testcase("types_mediumint", "field_signed", "max", std::int64_t(0x7fffff), field_type::mediumint), + output.emplace_back("types_float", "field_zerofill", "zero", + 0.0f, field_type::float_, flags_zerofill, 31); + output.emplace_back("types_float", "field_zerofill", "fractional_positive", + 4.2f, field_type::float_, flags_zerofill, 31); + output.emplace_back("types_float", "field_zerofill", "positive_exp_positive_fractional", + 3.14e20f, field_type::float_, flags_zerofill, 31); + output.emplace_back("types_float", "field_zerofill", "negative_exp_positive_fractional", + 3.14e-20f, field_type::float_, flags_zerofill, 31); +} - database_types_testcase("types_mediumint", "field_unsigned", "regular", std::uint64_t(20), field_type::mediumint, flags_unsigned), - database_types_testcase("types_mediumint", "field_unsigned", "min", std::uint64_t(0), field_type::mediumint, flags_unsigned), - database_types_testcase("types_mediumint", "field_unsigned", "max", std::uint64_t(0xffffff), field_type::mediumint, flags_unsigned), +void add_double_samples(std::vector& output) +{ + output.emplace_back("types_double", "field_signed", "zero", + 0.0, field_type::double_, no_flags, 31); + output.emplace_back("types_double", "field_signed", "int_positive", + 4.0, field_type::double_, no_flags, 31); + output.emplace_back("types_double", "field_signed", "int_negative", + -4.0, field_type::double_, no_flags, 31); + output.emplace_back("types_double", "field_signed", "fractional_positive", + 4.2, field_type::double_, no_flags, 31); + output.emplace_back("types_double", "field_signed", "fractional_negative", + -4.2, field_type::double_, no_flags, 31); + output.emplace_back("types_double", "field_signed", "positive_exp_positive_int", + 3e200, field_type::double_, no_flags, 31); + output.emplace_back("types_double", "field_signed", "positive_exp_negative_int", + -3e200, field_type::double_, no_flags, 31); + output.emplace_back("types_double", "field_signed", "positive_exp_positive_fractional", + 3.14e200, field_type::double_, no_flags, 31); + output.emplace_back("types_double", "field_signed", "positive_exp_negative_fractional", + -3.14e200, field_type::double_, no_flags, 31); + output.emplace_back("types_double", "field_signed", "negative_exp_positive_fractional", + 3.14e-200, field_type::double_, no_flags, 31); - database_types_testcase("types_mediumint", "field_width", "regular", std::int64_t(20), field_type::mediumint), - database_types_testcase("types_mediumint", "field_width", "negative", std::int64_t(-20), field_type::mediumint), + output.emplace_back("types_double", "field_unsigned", "zero", + 0.0, field_type::double_, flags_unsigned, 31); + output.emplace_back("types_double", "field_unsigned", "fractional_positive", + 4.2, field_type::double_, flags_unsigned, 31); - database_types_testcase("types_mediumint", "field_zerofill", "regular", std::uint64_t(20), field_type::mediumint, flags_zerofill), - database_types_testcase("types_mediumint", "field_zerofill", "min", std::uint64_t(0), field_type::mediumint, flags_zerofill) -), test_name_generator); + output.emplace_back("types_double", "field_width", "zero", + 0.0, field_type::double_, no_flags, 10); + output.emplace_back("types_double", "field_width", "fractional_positive", + 4.2, field_type::double_, no_flags, 10); + output.emplace_back("types_double", "field_width", "fractional_negative", + -4.2, field_type::double_, no_flags, 10); -INSTANTIATE_TEST_SUITE_P(INT, DatabaseTypesTest, Values( - database_types_testcase("types_int", "field_signed", "regular", std::int64_t(20), field_type::int_), - database_types_testcase("types_int", "field_signed", "negative", std::int64_t(-20), field_type::int_), - database_types_testcase("types_int", "field_signed", "min", std::int64_t(-0x80000000LL), field_type::int_), - database_types_testcase("types_int", "field_signed", "max", std::int64_t(0x7fffffff), field_type::int_), - - database_types_testcase("types_int", "field_unsigned", "regular", std::uint64_t(20), field_type::int_, flags_unsigned), - database_types_testcase("types_int", "field_unsigned", "min", std::uint64_t(0), field_type::int_, flags_unsigned), - database_types_testcase("types_int", "field_unsigned", "max", std::uint64_t(0xffffffff), field_type::int_, flags_unsigned), - - database_types_testcase("types_int", "field_width", "regular", std::int64_t(20), field_type::int_), - database_types_testcase("types_int", "field_width", "negative", std::int64_t(-20), field_type::int_), - - database_types_testcase("types_int", "field_zerofill", "regular", std::uint64_t(20), field_type::int_, flags_zerofill), - database_types_testcase("types_int", "field_zerofill", "min", std::uint64_t(0), field_type::int_, flags_zerofill) -), test_name_generator); - -INSTANTIATE_TEST_SUITE_P(BIGINT, DatabaseTypesTest, Values( - database_types_testcase("types_bigint", "field_signed", "regular", std::int64_t(20), field_type::bigint), - database_types_testcase("types_bigint", "field_signed", "negative", std::int64_t(-20), field_type::bigint), - database_types_testcase("types_bigint", "field_signed", "min", std::int64_t(-0x7fffffffffffffff - 1), field_type::bigint), - database_types_testcase("types_bigint", "field_signed", "max", std::int64_t(0x7fffffffffffffff), field_type::bigint), - - database_types_testcase("types_bigint", "field_unsigned", "regular", std::uint64_t(20), field_type::bigint, flags_unsigned), - database_types_testcase("types_bigint", "field_unsigned", "min", std::uint64_t(0), field_type::bigint, flags_unsigned), - database_types_testcase("types_bigint", "field_unsigned", "max", std::uint64_t(0xffffffffffffffff), field_type::bigint, flags_unsigned), - - database_types_testcase("types_bigint", "field_width", "regular", std::int64_t(20), field_type::bigint), - database_types_testcase("types_bigint", "field_width", "negative", std::int64_t(-20), field_type::bigint), - - database_types_testcase("types_bigint", "field_zerofill", "regular", std::uint64_t(20), field_type::bigint, flags_zerofill), - database_types_testcase("types_bigint", "field_zerofill", "min", std::uint64_t(0), field_type::bigint, flags_zerofill) -), test_name_generator); - -// Floating point -INSTANTIATE_TEST_SUITE_P(FLOAT, DatabaseTypesTest, Values( - database_types_testcase("types_float", "field_signed", "zero", 0.0f, field_type::float_, no_flags, 31), - database_types_testcase("types_float", "field_signed", "int_positive", 4.0f, field_type::float_, no_flags, 31), - database_types_testcase("types_float", "field_signed", "int_negative", -4.0f, field_type::float_, no_flags, 31), - database_types_testcase("types_float", "field_signed", "fractional_positive", 4.2f, field_type::float_, no_flags, 31), - database_types_testcase("types_float", "field_signed", "fractional_negative", -4.2f, field_type::float_, no_flags, 31), - database_types_testcase("types_float", "field_signed", "positive_exp_positive_int", 3e20f, field_type::float_, no_flags, 31), - database_types_testcase("types_float", "field_signed", "positive_exp_negative_int", -3e20f, field_type::float_, no_flags, 31), - database_types_testcase("types_float", "field_signed", "positive_exp_positive_fractional", 3.14e20f, field_type::float_, no_flags, 31), - database_types_testcase("types_float", "field_signed", "positive_exp_negative_fractional", -3.14e20f, field_type::float_, no_flags, 31), - database_types_testcase("types_float", "field_signed", "negative_exp_positive_fractional", 3.14e-20f, field_type::float_, no_flags, 31), - - database_types_testcase("types_float", "field_unsigned", "zero", 0.0f, field_type::float_, flags_unsigned, 31), - database_types_testcase("types_float", "field_unsigned", "fractional_positive", 4.2f, field_type::float_, flags_unsigned, 31), - - database_types_testcase("types_float", "field_width", "zero", 0.0f, field_type::float_, no_flags, 10), - database_types_testcase("types_float", "field_width", "fractional_positive", 4.2f, field_type::float_, no_flags, 10), - database_types_testcase("types_float", "field_width", "fractional_negative", -4.2f, field_type::float_, no_flags, 10), - - database_types_testcase("types_float", "field_zerofill", "zero", 0.0f, field_type::float_, flags_zerofill, 31), - database_types_testcase("types_float", "field_zerofill", "fractional_positive", 4.2f, field_type::float_, flags_zerofill, 31), - database_types_testcase("types_float", "field_zerofill", "positive_exp_positive_fractional", 3.14e20f, field_type::float_, flags_zerofill, 31), - database_types_testcase("types_float", "field_zerofill", "negative_exp_positive_fractional", 3.14e-20f, field_type::float_, flags_zerofill, 31) -), test_name_generator); - -INSTANTIATE_TEST_SUITE_P(DOUBLE, DatabaseTypesTest, Values( - database_types_testcase("types_double", "field_signed", "zero", 0.0, field_type::double_, no_flags, 31), - database_types_testcase("types_double", "field_signed", "int_positive", 4.0, field_type::double_, no_flags, 31), - database_types_testcase("types_double", "field_signed", "int_negative", -4.0, field_type::double_, no_flags, 31), - database_types_testcase("types_double", "field_signed", "fractional_positive", 4.2, field_type::double_, no_flags, 31), - database_types_testcase("types_double", "field_signed", "fractional_negative", -4.2, field_type::double_, no_flags, 31), - database_types_testcase("types_double", "field_signed", "positive_exp_positive_int", 3e200, field_type::double_, no_flags, 31), - database_types_testcase("types_double", "field_signed", "positive_exp_negative_int", -3e200, field_type::double_, no_flags, 31), - database_types_testcase("types_double", "field_signed", "positive_exp_positive_fractional", 3.14e200, field_type::double_, no_flags, 31), - database_types_testcase("types_double", "field_signed", "positive_exp_negative_fractional", -3.14e200, field_type::double_, no_flags, 31), - database_types_testcase("types_double", "field_signed", "negative_exp_positive_fractional", 3.14e-200, field_type::double_, no_flags, 31), - - database_types_testcase("types_double", "field_unsigned", "zero", 0.0, field_type::double_, flags_unsigned, 31), - database_types_testcase("types_double", "field_unsigned", "fractional_positive", 4.2, field_type::double_, flags_unsigned, 31), - - database_types_testcase("types_double", "field_width", "zero", 0.0, field_type::double_, no_flags, 10), - database_types_testcase("types_double", "field_width", "fractional_positive", 4.2, field_type::double_, no_flags, 10), - database_types_testcase("types_double", "field_width", "fractional_negative", -4.2, field_type::double_, no_flags, 10), - - database_types_testcase("types_double", "field_zerofill", "zero", 0.0, field_type::double_, flags_zerofill, 31), - database_types_testcase("types_double", "field_zerofill", "fractional_positive", 4.2, field_type::double_, flags_zerofill, 31), - database_types_testcase("types_double", "field_zerofill", "positive_exp_positive_fractional", 3.14e200, field_type::double_, flags_zerofill, 31), - database_types_testcase("types_double", "field_zerofill", "negative_exp_positive_fractional", 3.14e-200, field_type::double_, flags_zerofill, 31) -), test_name_generator); - -// Dates and times + output.emplace_back("types_double", "field_zerofill", "zero", + 0.0, field_type::double_, flags_zerofill, 31); + output.emplace_back("types_double", "field_zerofill", "fractional_positive", + 4.2, field_type::double_, flags_zerofill, 31); + output.emplace_back("types_double", "field_zerofill", "positive_exp_positive_fractional", + 3.14e200, field_type::double_, flags_zerofill, 31); + output.emplace_back("types_double", "field_zerofill", "negative_exp_positive_fractional", + 3.14e-200, field_type::double_, flags_zerofill, 31); +} +// Date cases // MySQL accepts zero and invalid dates. We represent them as NULL constexpr const char* invalid_date_cases [] = { "zero", @@ -305,35 +217,28 @@ constexpr const char* invalid_date_cases [] = { "yregular_invalid_date_leap100" }; -std::vector make_date_cases() +void add_date_samples(std::vector& output) { constexpr auto type = field_type::date; constexpr const char* table = "types_date"; constexpr const char* field = "field_date"; // Regular cases - std::vector res { - { table, field, "regular", makedate(2010, 3, 28), type }, - { table, field, "leap_regular", makedate(1788, 2, 29), type }, - { table, field, "leap_400", makedate(2000, 2, 29), type }, - { table, field, "min", makedate(0, 1, 1), type }, - { table, field, "max", makedate(9999, 12, 31), type } - }; + output.emplace_back(table, field, "regular", makedate(2010, 3, 28), type); + output.emplace_back(table, field, "leap_regular", makedate(1788, 2, 29), type); + output.emplace_back(table, field, "leap_400", makedate(2000, 2, 29), type); + output.emplace_back(table, field, "min", makedate(0, 1, 1), type); + output.emplace_back(table, field, "max", makedate(9999, 12, 31), type); // Invalid DATEs for (const char* invalid_case: invalid_date_cases) { - res.emplace_back(table, field, invalid_case, nullptr, type); + output.emplace_back(table, field, invalid_case, nullptr, type); } - - return res; } -INSTANTIATE_TEST_SUITE_P(DATE, DatabaseTypesTest, ValuesIn( - make_date_cases() -), test_name_generator); +// Datetime and time cases -// Infrastructure to generate DATETIME and TIMESTAMP test cases // Given a number of microseconds, removes the least significant part according to decimals int round_micros(int input, int decimals) { @@ -380,7 +285,7 @@ std::pair datetime_from_id(std::bitset<4> id, int decimal return {name, dt}; } -database_types_testcase create_datetime_testcase( +database_types_sample create_datetime_sample( int decimals, std::string id, value expected, @@ -394,7 +299,7 @@ database_types_testcase create_datetime_testcase( }; // Inconsistencies between Maria and MySQL in the unsigned flag // we don't really care here about signedness of timestamps - return database_types_testcase( + return database_types_sample( table_map.at(type), "field_" + std::to_string(decimals), std::move(id), @@ -447,12 +352,11 @@ std::pair time_from_id(std::bitset<6> id, int d } // shared between DATETIME and TIMESTAMP -std::vector generate_common_datetime_cases( - field_type type +void add_common_datetime_samples( + field_type type, + std::vector& output ) { - std::vector res; - for (int decimals = 0; decimals <= 6; ++decimals) { // Regular values @@ -463,31 +367,29 @@ std::vector generate_common_datetime_cases( if (bitset_id[3] && decimals == 0) continue; // cases with micros don't make sense for fields with no decimals auto dt = datetime_from_id(int_id, decimals); - res.push_back(create_datetime_testcase(decimals, move(dt.first), value(dt.second), type)); + output.push_back(create_datetime_sample(decimals, move(dt.first), value(dt.second), type)); } // Tests for leap years (valid dates) - res.push_back(create_datetime_testcase( + output.push_back(create_datetime_sample( decimals, "date_leap4", value(makedt(2004, 2, 29)), type)); - res.push_back(create_datetime_testcase( + output.push_back(create_datetime_sample( decimals, "date_leap400", value(makedt(2000, 2, 29)), type)); } - - return res; } -std::vector generate_datetime_cases() +void add_datetime_samples(std::vector& output) { - auto res = generate_common_datetime_cases(field_type::datetime); - + add_common_datetime_samples(field_type::datetime, output); for (int decimals = 0; decimals <= 6; ++decimals) { // min and max - res.push_back(create_datetime_testcase(decimals, "min", + output.push_back(create_datetime_sample(decimals, "min", value(makedt(0, 1, 1)), field_type::datetime)); - res.push_back(create_datetime_testcase(decimals, "max", - value(makedt(9999, 12, 31, 23, 59, 59, round_micros(999999, decimals))), field_type::datetime)); + output.push_back(create_datetime_sample(decimals, "max", + value(makedt(9999, 12, 31, 23, 59, 59, + round_micros(999999, decimals))), field_type::datetime)); // invalid dates const char* lengths [] = {"date", "hms", decimals ? "hmsu" : nullptr }; @@ -497,37 +399,32 @@ std::vector generate_datetime_cases() continue; for (const char* invalid_date_case: invalid_date_cases) { - res.push_back(create_datetime_testcase( + output.push_back(create_datetime_sample( decimals, - boost::mysql::detail::stringize(length, '_', invalid_date_case), + stringize(length, '_', invalid_date_case), value(nullptr), field_type::datetime )); } } } - - return res; } -std::vector generate_timestamp_cases() +void add_timestamp_samples(std::vector& output) { - auto res = generate_common_datetime_cases(field_type::timestamp); + add_common_datetime_samples(field_type::timestamp, output); // Only the full-zero TIMESTAMP is allowed - timestamps with // invalid date parts are converted to the zero TIMESTAMP for (int decimals = 0; decimals <= 6; ++decimals) { - res.push_back(create_datetime_testcase(decimals, "zero", value(nullptr), field_type::timestamp)); + output.push_back(create_datetime_sample(decimals, + "zero", value(nullptr), field_type::timestamp)); } - - return res; } -std::vector generate_time_cases() +void add_time_samples(std::vector& output) { - std::vector res; - for (int decimals = 0; decimals <= 6; ++decimals) { // Regular values @@ -538,88 +435,94 @@ std::vector generate_time_cases() if (bitset_id[5] && decimals == 0) continue; // cases with micros don't make sense for fields with no decimals if (bitset_id.to_ulong() == 1) continue; // negative zero does not make sense auto t = time_from_id(int_id, decimals); - res.push_back(create_datetime_testcase(decimals, move(t.first), value(t.second), field_type::time)); + output.push_back(create_datetime_sample( + decimals, move(t.first), value(t.second), field_type::time)); } // min and max auto max_value = decimals == 0 ? maket(838, 59, 59) : maket(838, 59, 58, round_micros(999999, decimals)); - res.push_back(create_datetime_testcase(decimals, "min", value(-max_value), field_type::time)); - res.push_back(create_datetime_testcase(decimals, "max", value(max_value), field_type::time)); + output.push_back(create_datetime_sample( + decimals, "min", value(-max_value), field_type::time)); + output.push_back(create_datetime_sample( + decimals, "max", value(max_value), field_type::time)); } - - return res; - } -INSTANTIATE_TEST_SUITE_P(DATETIME, DatabaseTypesTest, ValuesIn(generate_datetime_cases()), test_name_generator); -INSTANTIATE_TEST_SUITE_P(TIMESTAMP, DatabaseTypesTest, ValuesIn(generate_timestamp_cases()), test_name_generator); -INSTANTIATE_TEST_SUITE_P(TIME, DatabaseTypesTest, ValuesIn(generate_time_cases()), test_name_generator); +// Year +void add_year_samples(std::vector& output) +{ + output.emplace_back("types_year", "field_default", "regular", + std::uint64_t(2019), field_type::year, flags_zerofill); + output.emplace_back("types_year", "field_default", "min", + std::uint64_t(1901), field_type::year, flags_zerofill); + output.emplace_back("types_year", "field_default", "max", + std::uint64_t(2155), field_type::year, flags_zerofill); + output.emplace_back("types_year", "field_default", "zero", + std::uint64_t(0), field_type::year, flags_zerofill); +} -INSTANTIATE_TEST_SUITE_P(YEAR, DatabaseTypesTest, Values( - database_types_testcase("types_year", "field_default", "regular", std::uint64_t(2019), field_type::year, flags_zerofill), - database_types_testcase("types_year", "field_default", "min", std::uint64_t(1901), field_type::year, flags_zerofill), - database_types_testcase("types_year", "field_default", "max", std::uint64_t(2155), field_type::year, flags_zerofill), - database_types_testcase("types_year", "field_default", "zero", std::uint64_t(0), field_type::year, flags_zerofill) -), test_name_generator); +// String types +void add_string_samples(std::vector& output) +{ + output.emplace_back("types_string", "field_char", "regular", "test_char", field_type::char_); + output.emplace_back("types_string", "field_char", "utf8", "\xc3\xb1", field_type::char_); + output.emplace_back("types_string", "field_char", "empty", "", field_type::char_); -INSTANTIATE_TEST_SUITE_P(STRING, DatabaseTypesTest, Values( - database_types_testcase("types_string", "field_char", "regular", "test_char", field_type::char_), - database_types_testcase("types_string", "field_char", "utf8", "\xc3\xb1", field_type::char_), - database_types_testcase("types_string", "field_char", "empty", "", field_type::char_), + output.emplace_back("types_string", "field_varchar", "regular", "test_varchar", field_type::varchar); + output.emplace_back("types_string", "field_varchar", "utf8", "\xc3\x91", field_type::varchar); + output.emplace_back("types_string", "field_varchar", "empty", "", field_type::varchar); - database_types_testcase("types_string", "field_varchar", "regular", "test_varchar", field_type::varchar), - database_types_testcase("types_string", "field_varchar", "utf8", "\xc3\x91", field_type::varchar), - database_types_testcase("types_string", "field_varchar", "empty", "", field_type::varchar), + output.emplace_back("types_string", "field_tinytext", "regular", "test_tinytext", field_type::text); + output.emplace_back("types_string", "field_tinytext", "utf8", "\xc3\xa1", field_type::text); + output.emplace_back("types_string", "field_tinytext", "empty", "", field_type::text); - database_types_testcase("types_string", "field_tinytext", "regular", "test_tinytext", field_type::text), - database_types_testcase("types_string", "field_tinytext", "utf8", "\xc3\xa1", field_type::text), - database_types_testcase("types_string", "field_tinytext", "empty", "", field_type::text), + output.emplace_back("types_string", "field_text", "regular", "test_text", field_type::text); + output.emplace_back("types_string", "field_text", "utf8", "\xc3\xa9", field_type::text); + output.emplace_back("types_string", "field_text", "empty", "", field_type::text); - database_types_testcase("types_string", "field_text", "regular", "test_text", field_type::text), - database_types_testcase("types_string", "field_text", "utf8", "\xc3\xa9", field_type::text), - database_types_testcase("types_string", "field_text", "empty", "", field_type::text), + output.emplace_back("types_string", "field_mediumtext", "regular", "test_mediumtext", field_type::text); + output.emplace_back("types_string", "field_mediumtext", "utf8", "\xc3\xad", field_type::text); + output.emplace_back("types_string", "field_mediumtext", "empty", "", field_type::text); - database_types_testcase("types_string", "field_mediumtext", "regular", "test_mediumtext", field_type::text), - database_types_testcase("types_string", "field_mediumtext", "utf8", "\xc3\xad", field_type::text), - database_types_testcase("types_string", "field_mediumtext", "empty", "", field_type::text), + output.emplace_back("types_string", "field_longtext", "regular", "test_longtext", field_type::text); + output.emplace_back("types_string", "field_longtext", "utf8", "\xc3\xb3", field_type::text); + output.emplace_back("types_string", "field_longtext", "empty", "", field_type::text); - database_types_testcase("types_string", "field_longtext", "regular", "test_longtext", field_type::text), - database_types_testcase("types_string", "field_longtext", "utf8", "\xc3\xb3", field_type::text), - database_types_testcase("types_string", "field_longtext", "empty", "", field_type::text), + output.emplace_back("types_string", "field_enum", "regular", "red", field_type::enum_); - database_types_testcase("types_string", "field_enum", "regular", "red", field_type::enum_), + output.emplace_back("types_string", "field_set", "regular", "red,green", field_type::set); + output.emplace_back("types_string", "field_set", "empty", "", field_type::set); +} - database_types_testcase("types_string", "field_set", "regular", "red,green", field_type::set), - database_types_testcase("types_string", "field_set", "empty", "", field_type::set) -), test_name_generator); - -INSTANTIATE_TEST_SUITE_P(BINARY, DatabaseTypesTest, Values( +void add_binary_samples(std::vector& output) +{ // BINARY values get padded with zeros to the declared length - database_types_testcase("types_binary", "field_binary", "regular", makesv("\0_binary\0\0"), field_type::binary), - database_types_testcase("types_binary", "field_binary", "nonascii", makesv("\0\xff" "\0\0\0\0\0\0\0\0"), field_type::binary), - database_types_testcase("types_binary", "field_binary", "empty", makesv("\0\0\0\0\0\0\0\0\0\0"), field_type::binary), + output.emplace_back("types_binary", "field_binary", "regular", makesv("\0_binary\0\0"), field_type::binary); + output.emplace_back("types_binary", "field_binary", "nonascii", makesv("\0\xff" "\0\0\0\0\0\0\0\0"), field_type::binary); + output.emplace_back("types_binary", "field_binary", "empty", makesv("\0\0\0\0\0\0\0\0\0\0"), field_type::binary); - database_types_testcase("types_binary", "field_varbinary", "regular", makesv("\0_varbinary"), field_type::varbinary), - database_types_testcase("types_binary", "field_varbinary", "nonascii", makesv("\1\xfe"), field_type::varbinary), - database_types_testcase("types_binary", "field_varbinary", "empty", "", field_type::varbinary), + output.emplace_back("types_binary", "field_varbinary", "regular", makesv("\0_varbinary"), field_type::varbinary); + output.emplace_back("types_binary", "field_varbinary", "nonascii", makesv("\1\xfe"), field_type::varbinary); + output.emplace_back("types_binary", "field_varbinary", "empty", "", field_type::varbinary); - database_types_testcase("types_binary", "field_tinyblob", "regular", makesv("\0_tinyblob"), field_type::blob), - database_types_testcase("types_binary", "field_tinyblob", "nonascii", makesv("\2\xfd"), field_type::blob), - database_types_testcase("types_binary", "field_tinyblob", "empty", "", field_type::blob), + output.emplace_back("types_binary", "field_tinyblob", "regular", makesv("\0_tinyblob"), field_type::blob); + output.emplace_back("types_binary", "field_tinyblob", "nonascii", makesv("\2\xfd"), field_type::blob); + output.emplace_back("types_binary", "field_tinyblob", "empty", "", field_type::blob); - database_types_testcase("types_binary", "field_blob", "regular", makesv("\0_blob"), field_type::blob), - database_types_testcase("types_binary", "field_blob", "nonascii", makesv("\3\xfc"), field_type::blob), - database_types_testcase("types_binary", "field_blob", "empty", "", field_type::blob), + output.emplace_back("types_binary", "field_blob", "regular", makesv("\0_blob"), field_type::blob); + output.emplace_back("types_binary", "field_blob", "nonascii", makesv("\3\xfc"), field_type::blob); + output.emplace_back("types_binary", "field_blob", "empty", "", field_type::blob); - database_types_testcase("types_binary", "field_mediumblob", "regular", makesv("\0_mediumblob"), field_type::blob), - database_types_testcase("types_binary", "field_mediumblob", "nonascii", makesv("\4\xfb"), field_type::blob), - database_types_testcase("types_binary", "field_mediumblob", "empty", "", field_type::blob), + output.emplace_back("types_binary", "field_mediumblob", "regular", makesv("\0_mediumblob"), field_type::blob); + output.emplace_back("types_binary", "field_mediumblob", "nonascii", makesv("\4\xfb"), field_type::blob); + output.emplace_back("types_binary", "field_mediumblob", "empty", "", field_type::blob); - database_types_testcase("types_binary", "field_longblob", "regular", makesv("\0_longblob"), field_type::blob), - database_types_testcase("types_binary", "field_longblob", "nonascii", makesv("\5\xfa"), field_type::blob), - database_types_testcase("types_binary", "field_longblob", "empty", "", field_type::blob) -), test_name_generator); + output.emplace_back("types_binary", "field_longblob", "regular", makesv("\0_longblob"), field_type::blob); + output.emplace_back("types_binary", "field_longblob", "nonascii", makesv("\5\xfa"), field_type::blob); + output.emplace_back("types_binary", "field_longblob", "empty", "", field_type::blob); +} +// Tests for not implemented types // These types do not have a more concrete representation in the library yet. // Check we get them as strings and we get the metadata correctly std::uint8_t geometry_value [] = { @@ -629,27 +532,154 @@ std::uint8_t geometry_value [] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }; -INSTANTIATE_TEST_SUITE_P(NOT_IMPLEMENTED_TYPES, DatabaseTypesTest, Values( - database_types_testcase("types_not_implemented", "field_bit", "regular", "\xfe", field_type::bit, flags_unsigned), - database_types_testcase("types_not_implemented", "field_decimal", "regular", "300", field_type::decimal), - database_types_testcase("types_not_implemented", "field_geometry", "regular", makesv(geometry_value), field_type::geometry) -), test_name_generator); +void add_not_implemented_samples(std::vector& output) +{ + output.emplace_back("types_not_implemented", "field_bit", "regular", + "\xfe", field_type::bit, flags_unsigned); + output.emplace_back("types_not_implemented", "field_decimal", "regular", + "300", field_type::decimal); + output.emplace_back("types_not_implemented", "field_geometry", "regular", + makesv(geometry_value), field_type::geometry); +} // Tests for certain metadata flags and NULL values -INSTANTIATE_TEST_SUITE_P(METADATA_FLAGS, DatabaseTypesTest, Values( - database_types_testcase("types_flags", "field_timestamp", "default", nullptr, field_type::timestamp, - flagsvec{&field_metadata::is_set_to_now_on_update}, 0, flagsvec{&field_metadata::is_unsigned}), - database_types_testcase("types_flags", "field_primary_key", "default", std::int64_t(50), field_type::int_, - flagsvec{&field_metadata::is_primary_key, &field_metadata::is_not_null, - &field_metadata::is_auto_increment}), - database_types_testcase("types_flags", "field_not_null", "default", "char", field_type::char_, - flagsvec{&field_metadata::is_not_null}), - database_types_testcase("types_flags", "field_unique", "default", std::int64_t(21), field_type::int_, - flagsvec{&field_metadata::is_unique_key}), - database_types_testcase("types_flags", "field_indexed", "default", std::int64_t(42), field_type::int_, - flagsvec{&field_metadata::is_multiple_key}) -), test_name_generator); +void add_flags_samples(std::vector& output) +{ + output.emplace_back("types_flags", "field_timestamp", "default", + nullptr, field_type::timestamp, flagsvec{&field_metadata::is_set_to_now_on_update}, + 0, flagsvec{&field_metadata::is_unsigned}); + output.emplace_back("types_flags", "field_primary_key", "default", + std::int64_t(50), field_type::int_, + flagsvec{&field_metadata::is_primary_key, &field_metadata::is_not_null, + &field_metadata::is_auto_increment}); + output.emplace_back("types_flags", "field_not_null", "default", + "char", field_type::char_, flagsvec{&field_metadata::is_not_null}); + output.emplace_back("types_flags", "field_unique", "default", + std::int64_t(21), field_type::int_, flagsvec{&field_metadata::is_unique_key}); + output.emplace_back("types_flags", "field_indexed", "default", + std::int64_t(42), field_type::int_, flagsvec{&field_metadata::is_multiple_key}); +} + +std::vector make_all_samples() +{ + std::vector res; + add_int_samples(res); + add_float_samples(res); + add_double_samples(res); + add_date_samples(res); + add_datetime_samples(res); + add_timestamp_samples(res); + add_time_samples(res); + add_year_samples(res); + add_string_samples(res); + add_binary_samples(res); + add_not_implemented_samples(res); + add_flags_samples(res); + return res; +} + +std::vector all_samples = make_all_samples(); + +BOOST_DATA_TEST_CASE_F(database_types_fixture, query, data::make(all_samples)) +{ + // Compose the query + auto query = stringize( + "SELECT ", sample.field, + " FROM ", sample.table, + " WHERE id = '", sample.row_id, "'" + ); + + // Execute it + auto result = conn.query(query); + auto rows = result.fetch_all(); + + // Validate the received metadata + validate_meta(result.fields(), {sample.mvalid}); + + // Validate the returned value + BOOST_TEST_REQUIRE(rows.size() == 1); + BOOST_TEST_REQUIRE(rows[0].values().size() == 1); + BOOST_TEST(rows[0].values()[0] == sample.expected_value); +} + +BOOST_DATA_TEST_CASE_F(database_types_fixture, prepared_statement, data::make(all_samples)) +{ + // Prepare the statement + auto stmt_sql = stringize( + "SELECT ", sample.field, + " FROM ", sample.table, + " WHERE id = ?" + ); + auto stmt = conn.prepare_statement(stmt_sql); + + // Execute it with the provided parameters + auto result = stmt.execute(make_values(sample.row_id)); + auto rows = result.fetch_all(); + + // Validate the received metadata + validate_meta(result.fields(), {sample.mvalid}); + + // Validate the returned value + BOOST_TEST_REQUIRE(rows.size() == 1); + BOOST_TEST_REQUIRE(rows[0].values().size() == 1); + BOOST_TEST(rows[0].values()[0] == sample.expected_value); +} + +// The prepared statement param tests binary serialization. +// This test is not applicable (yet) to nullptr values or bit values. +// Doing "field = ?" where ? is nullptr never matches anything. +// Bit values are returned as strings bit need to be sent as integers in +// prepared statements. Filter the cases to remove the ones that +// are not applicable +std::vector +make_prepared_stmt_param_samples() +{ + std::vector res; + res.reserve(all_samples.size()); + for (const auto& test : all_samples) + { + if (!test.expected_value.is_null() && + test.mvalid.type() != field_type::bit) + { + res.push_back(test); + } + } + return res; +} + +BOOST_DATA_TEST_CASE_F(database_types_fixture, + prepared_statement_execute_param, + data::make(make_prepared_stmt_param_samples())) +{ + // Prepare the statement + auto stmt_sql = stringize( + "SELECT ", sample.field, + " FROM ", sample.table, + " WHERE id = ? AND ", sample.field, " = ?" + ); + auto stmt = conn.prepare_statement(stmt_sql); + + // Execute it with the provided parameters + auto result = stmt.execute(make_values(sample.row_id, sample.expected_value)); + auto rows = result.fetch_all(); + + // Validate the returned value + BOOST_TEST_REQUIRE(rows.size() == 1); + BOOST_TEST_REQUIRE(rows[0].values().size() == 1); + BOOST_TEST(rows[0].values()[0] == sample.expected_value); +} + +// Validate that the metadata we retrieve with certain queries is correct +BOOST_FIXTURE_TEST_CASE(aliased_table_metadata, database_types_fixture) +{ + auto result = conn.query( + "SELECT field_varchar AS field_alias FROM empty_table table_alias"); + std::vector validators { + { "table_alias", "empty_table", "field_alias", + "field_varchar", field_type::varchar } + }; + validate_meta(result.fields(), validators); +} -} // anon namespace - +BOOST_AUTO_TEST_SUITE_END() // test_database_types diff --git a/test/integration/entry_point.cpp b/test/integration/entry_point.cpp new file mode 100644 index 00000000..8a54725e --- /dev/null +++ b/test/integration/entry_point.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2019-2020 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#define BOOST_TEST_MODULE boost_mysql_integrationtests +#include + diff --git a/test/integration/execute_statement.cpp b/test/integration/execute_statement.cpp index d5ebc7fc..06e156bc 100644 --- a/test/integration/execute_statement.cpp +++ b/test/integration/execute_statement.cpp @@ -14,137 +14,131 @@ using boost::mysql::error_code; using boost::mysql::error_info; using boost::mysql::errc; using boost::mysql::prepared_statement; +using boost::mysql::socket_connection; +using boost::asio::ip::tcp; -namespace -{ +BOOST_AUTO_TEST_SUITE(test_execute_statement) template -struct ExecuteStatementTest : public NetworkTest +prepared_statement do_prepare( + network_functions* net, + socket_connection& conn, + boost::string_view stmt +) { - network_functions* net {this->GetParam().net}; + auto res = net->prepare_statement(conn, stmt); + res.validate_no_error(); + return std::move(res.value); +} - ExecuteStatementTest() - { - this->connect(this->GetParam().ssl); - } +// Iterator version +BOOST_MYSQL_NETWORK_TEST(iterator_ok_no_params, network_fixture, network_ssl_gen) +{ + this->connect(sample.ssl); + std::forward_list params; + auto stmt = do_prepare(sample.net, this->conn, "SELECT * FROM empty_table"); + auto result = sample.net->execute_statement(stmt, params.begin(), params.end()); // execute + result.validate_no_error(); + BOOST_TEST(result.value.valid()); +} - prepared_statement do_prepare(boost::string_view stmt) - { - auto res = this->GetParam().net->prepare_statement(this->conn, stmt); - res.validate_no_error(); - return std::move(res.value); - } +BOOST_MYSQL_NETWORK_TEST(iterator_ok_with_params, network_fixture, network_ssl_gen) +{ + this->connect(sample.ssl); + std::forward_list params { value("item"), value(42) }; + auto stmt = do_prepare(sample.net, this->conn, + "SELECT * FROM empty_table WHERE id IN (?, ?)"); + auto result = sample.net->execute_statement(stmt, params.begin(), params.end()); + result.validate_no_error(); + BOOST_TEST(result.value.valid()); +} - // Iterator version - void Iterator_OkNoParams() - { - std::forward_list params; - auto stmt = do_prepare("SELECT * FROM empty_table"); - auto result = net->execute_statement(stmt, params.begin(), params.end()); // execute - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); - } +BOOST_MYSQL_NETWORK_TEST(iterator_mismatched_num_params, network_fixture, network_ssl_gen) +{ + this->connect(sample.ssl); + std::forward_list params { value("item") }; + auto stmt = do_prepare(sample.net, this->conn, + "SELECT * FROM empty_table WHERE id IN (?, ?)"); + auto result = sample.net->execute_statement(stmt, params.begin(), params.end()); + result.validate_error(errc::wrong_num_params, + {"param", "2", "1", "statement", "execute"}); + BOOST_TEST(!result.value.valid()); +} - void Iterator_OkWithParams() - { - std::forward_list params { value("item"), value(42) }; - auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)"); - auto result = net->execute_statement(stmt, params.begin(), params.end()); - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); - } +BOOST_MYSQL_NETWORK_TEST(iterator_server_error, network_fixture, network_ssl_gen) +{ + this->connect(sample.ssl); + this->start_transaction(); + std::forward_list params { value("f0"), value("bad_date") }; + auto stmt = do_prepare(sample.net, this->conn, + "INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)"); + auto result = sample.net->execute_statement(stmt, params.begin(), params.end()); + result.validate_error(errc::truncated_wrong_value, + {"field_date", "bad_date", "incorrect date value"}); + BOOST_TEST(!result.value.valid()); +} - void Iterator_MismatchedNumParams() - { - std::forward_list params { value("item") }; - auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)"); - auto result = net->execute_statement(stmt, params.begin(), params.end()); - result.validate_error(errc::wrong_num_params, {"param", "2", "1", "statement", "execute"}); - EXPECT_FALSE(result.value.valid()); - } +// Container version +BOOST_MYSQL_NETWORK_TEST(container_ok_no_params, network_fixture, network_ssl_gen) +{ + this->connect(sample.ssl); + auto stmt = do_prepare(sample.net, this->conn, "SELECT * FROM empty_table"); + auto result = sample.net->execute_statement(stmt, std::vector()); // execute + result.validate_no_error(); + BOOST_TEST(result.value.valid()); +} - void Iterator_ServerError() - { - this->start_transaction(); - std::forward_list params { value("f0"), value("bad_date") }; - auto stmt = do_prepare("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)"); - auto result = net->execute_statement(stmt, params.begin(), params.end()); - result.validate_error(errc::truncated_wrong_value, {"field_date", "bad_date", "incorrect date value"}); - EXPECT_FALSE(result.value.valid()); - } - - // Container version - void Container_OkNoParams() - { - auto stmt = do_prepare("SELECT * FROM empty_table"); - auto result = net->execute_statement(stmt, std::vector()); // execute - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); - } - - void Container_OkWithParams() - { - std::vector params { value("item"), value(42) }; - auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)"); - auto result = net->execute_statement(stmt, params); - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); - } - - void Container_MismatchedNumParams() - { - std::vector params { value("item") }; - auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)"); - auto result = net->execute_statement(stmt, params); - result.validate_error(errc::wrong_num_params, {"param", "2", "1", "statement", "execute"}); - EXPECT_FALSE(result.value.valid()); - } - - void Container_ServerError() - { - this->start_transaction(); - auto stmt = do_prepare("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)"); - auto result = net->execute_statement(stmt, makevalues("f0", "bad_date")); - result.validate_error(errc::truncated_wrong_value, {"field_date", "bad_date", "incorrect date value"}); - EXPECT_FALSE(result.value.valid()); - } -}; - -BOOST_MYSQL_NETWORK_TEST_SUITE(ExecuteStatementTest) - -BOOST_MYSQL_NETWORK_TEST(ExecuteStatementTest, Iterator_OkNoParams) -BOOST_MYSQL_NETWORK_TEST(ExecuteStatementTest, Iterator_OkWithParams) -BOOST_MYSQL_NETWORK_TEST(ExecuteStatementTest, Iterator_MismatchedNumParams) -BOOST_MYSQL_NETWORK_TEST(ExecuteStatementTest, Iterator_ServerError) -BOOST_MYSQL_NETWORK_TEST(ExecuteStatementTest, Container_OkNoParams) -BOOST_MYSQL_NETWORK_TEST(ExecuteStatementTest, Container_OkWithParams) -BOOST_MYSQL_NETWORK_TEST(ExecuteStatementTest, Container_MismatchedNumParams) -BOOST_MYSQL_NETWORK_TEST(ExecuteStatementTest, Container_ServerError) +BOOST_MYSQL_NETWORK_TEST(container_ok_with_params, network_fixture, network_ssl_gen) +{ + this->connect(sample.ssl); + auto stmt = do_prepare(sample.net, this->conn, "SELECT * FROM empty_table WHERE id IN (?, ?)"); + auto result = sample.net->execute_statement(stmt, make_value_vector("item", 42)); + result.validate_no_error(); + BOOST_TEST(result.value.valid()); +} +BOOST_MYSQL_NETWORK_TEST(container_mismatched_num_params, network_fixture, network_ssl_gen) +{ + this->connect(sample.ssl); + auto stmt = do_prepare(sample.net, this->conn, + "SELECT * FROM empty_table WHERE id IN (?, ?)"); + auto result = sample.net->execute_statement(stmt, make_value_vector("item")); + result.validate_error(errc::wrong_num_params, + {"param", "2", "1", "statement", "execute"}); + BOOST_TEST(!result.value.valid()); +} +BOOST_MYSQL_NETWORK_TEST(container_server_error, network_fixture, network_ssl_gen) +{ + this->connect(sample.ssl); + this->start_transaction(); + auto stmt = do_prepare(sample.net, this->conn, + "INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)"); + auto result = sample.net->execute_statement(stmt, make_value_vector("f0", "bad_date")); + result.validate_error(errc::truncated_wrong_value, + {"field_date", "bad_date", "incorrect date value"}); + BOOST_TEST(!result.value.valid()); +} // Other containers -struct ExecuteStatementOtherContainersTest : IntegTest +BOOST_FIXTURE_TEST_CASE(no_statement_params_variable, network_fixture) { - ExecuteStatementOtherContainersTest() - { - connect(boost::mysql::ssl_mode::disable); - } -}; - -TEST_F(ExecuteStatementOtherContainersTest, NoParams_CanUseNoStatementParamsVariable) -{ - auto stmt = conn.prepare_statement("SELECT * FROM empty_table"); + this->connect(boost::mysql::ssl_mode::disable); + auto stmt = this->conn.prepare_statement( + "SELECT * FROM empty_table"); auto result = stmt.execute(boost::mysql::no_statement_params); - EXPECT_TRUE(result.valid()); + BOOST_TEST(result.valid()); } -TEST_F(ExecuteStatementOtherContainersTest, CArray) +BOOST_FIXTURE_TEST_CASE(c_array, network_fixture) { + this->connect(boost::mysql::ssl_mode::disable); value arr [] = { value("hola"), value(10) }; - auto stmt = conn.prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)"); + auto stmt = this->conn.prepare_statement( + "SELECT * FROM empty_table WHERE id IN (?, ?)"); auto result = stmt.execute(arr); - EXPECT_TRUE(result.valid()); + BOOST_TEST(result.valid()); } -} + +BOOST_AUTO_TEST_SUITE_END() // test_execute_statement diff --git a/test/integration/handshake.cpp b/test/integration/handshake.cpp index fb87b486..e0b7d0f8 100644 --- a/test/integration/handshake.cpp +++ b/test/integration/handshake.cpp @@ -8,148 +8,94 @@ #include "boost/mysql/connection.hpp" #include "integration_test_common.hpp" #include "test_common.hpp" -#include -#include -// Tests containing 'RequiresSha256' in their name require SHA256 functionality. -// This is used by a calling script to exclude these tests on systems +// Tests containing with label 'sha256' require SHA256 functionality. +// This is used by the build script to exclude these tests on databases // that do not support this functionality namespace net = boost::asio; -using namespace testing; using namespace boost::mysql::test; +using boost::mysql::socket_connection; using boost::mysql::ssl_mode; +using boost::mysql::ssl_options; using boost::mysql::detail::make_error_code; using boost::mysql::error_info; using boost::mysql::errc; using boost::mysql::error_code; using boost::mysql::tcp_connection; using boost::mysql::connection_params; -using boost::mysql::detail::stringize; -namespace +BOOST_AUTO_TEST_SUITE(test_handshake) + +template +network_result do_handshake( + socket_connection& conn, + connection_params params, + network_functions* net, + ssl_mode ssl +) { + params.set_ssl(boost::mysql::ssl_options(ssl)); + return net->handshake(conn, params); +} + +template +void do_handshake_ok( + socket_connection& conn, + connection_params params, + network_functions* net, + ssl_mode ssl +) +{ + auto result = do_handshake(conn, params, net, ssl); + result.validate_no_error(); + validate_ssl(conn, ssl); +} + // Handshake tests not depending on whether we use SSL or not template -struct SslIndifferentHandshakeTest : NetworkTest +struct handshake_fixture : network_fixture { - SslIndifferentHandshakeTest() + handshake_fixture() { this->physical_connect(); } - - network_result do_handshake() - { - this->params.set_ssl(boost::mysql::ssl_options(this->GetParam().ssl)); - return this->GetParam().net->handshake(this->conn, this->params); - } - - // does handshake and verifies it went OK - void do_handshake_ok() - { - auto result = do_handshake(); - result.validate_no_error(); - this->validate_ssl(this->GetParam().ssl); - } -}; - -template -struct SslSensitiveHandshakeTest : NetworkTest> -{ - SslSensitiveHandshakeTest() - { - this->physical_connect(); - } - - void set_ssl(ssl_mode m) - { - this->params.set_ssl(boost::mysql::ssl_options(m)); - } - - network_result do_handshake() - { - return this->GetParam().net->handshake(this->conn, this->params); - } - - void do_handshake_ok(ssl_mode m) - { - set_ssl(m); - auto result = do_handshake(); - result.validate_no_error(); - this->validate_ssl(m); - } }; // mysql_native_password -template -struct MysqlNativePasswordHandshakeTest : SslIndifferentHandshakeTest +BOOST_AUTO_TEST_SUITE(mysql_native_password) + +BOOST_MYSQL_NETWORK_TEST(regular_user, handshake_fixture, network_ssl_gen) { - void RegularUser_SuccessfulLogin() - { - this->set_credentials("mysqlnp_user", "mysqlnp_password"); - this->do_handshake_ok(); - } + this->set_credentials("mysqlnp_user", "mysqlnp_password"); + do_handshake_ok(this->conn, this->params, sample.net, sample.ssl); +} - void EmptyPassword_SuccessfulLogin() - { - this->set_credentials("mysqlnp_empty_password_user", ""); - this->do_handshake_ok(); - } - - void BadPassword_FailedLogin() - { - this->set_credentials("mysqlnp_user", "bad_password"); - auto result = this->do_handshake(); - result.validate_error(errc::access_denied_error, {"access denied", "mysqlnp_user"}); - } -}; - -BOOST_MYSQL_NETWORK_TEST_SUITE(MysqlNativePasswordHandshakeTest) - -BOOST_MYSQL_NETWORK_TEST(MysqlNativePasswordHandshakeTest, RegularUser_SuccessfulLogin) -BOOST_MYSQL_NETWORK_TEST(MysqlNativePasswordHandshakeTest, EmptyPassword_SuccessfulLogin) -BOOST_MYSQL_NETWORK_TEST(MysqlNativePasswordHandshakeTest, BadPassword_FailedLogin) - - -// Other handshake tests not depending on SSL mode -template -struct MiscSslIndifferentHandshakeTest : SslIndifferentHandshakeTest +BOOST_MYSQL_NETWORK_TEST(empty_password, handshake_fixture, network_ssl_gen) { - void NoDatabase_SuccessfulLogin() - { - this->params.set_database(""); - this->do_handshake_ok(); - } + this->set_credentials("mysqlnp_empty_password_user", ""); + do_handshake_ok(this->conn, this->params, sample.net, sample.ssl); +} - void BadDatabase_FailedLogin() - { - this->params.set_database("bad_database"); - auto result = this->do_handshake(); - result.validate_error(errc::dbaccess_denied_error, {"database", "bad_database"}); - } +BOOST_MYSQL_NETWORK_TEST(bad_password, handshake_fixture, network_ssl_gen) +{ + this->set_credentials("mysqlnp_user", "bad_password"); + auto result = do_handshake(this->conn, this->params, sample.net, sample.ssl); + result.validate_error(errc::access_denied_error, {"access denied", "mysqlnp_user"}); +} - void UnknownAuthPlugin_FailedLogin_RequiresSha256() - { - // Note: sha256_password is not supported, so it's an unknown plugin to us - this->set_credentials("sha2p_user", "sha2p_password"); - auto result = this->do_handshake(); - result.validate_error(errc::unknown_auth_plugin, {}); - } -}; - -BOOST_MYSQL_NETWORK_TEST_SUITE(MiscSslIndifferentHandshakeTest) - -BOOST_MYSQL_NETWORK_TEST(MiscSslIndifferentHandshakeTest, NoDatabase_SuccessfulLogin) -BOOST_MYSQL_NETWORK_TEST(MiscSslIndifferentHandshakeTest, BadDatabase_FailedLogin) -BOOST_MYSQL_NETWORK_TEST(MiscSslIndifferentHandshakeTest, UnknownAuthPlugin_FailedLogin_RequiresSha256) +BOOST_AUTO_TEST_SUITE_END() // mysql_native_password // caching_sha2_password +BOOST_TEST_DECORATOR(*boost::unit_test::label("sha256")) +BOOST_AUTO_TEST_SUITE(caching_sha2_password) + template -struct CachingSha2HandshakeTest : SslSensitiveHandshakeTest +struct caching_sha2_fixture : handshake_fixture { - void load_sha256_cache(const std::string& user, const std::string& password) + void load_sha256_cache(boost::string_view user, boost::string_view password) { tcp_connection conn (this->ctx); conn.connect( @@ -169,129 +115,128 @@ struct CachingSha2HandshakeTest : SslSensitiveHandshakeTest conn.query("FLUSH PRIVILEGES"); conn.close(); } - - // Actual tests - void SslOnCacheHit_SuccessfulLogin_RequiresSha256() - { - this->set_credentials("csha2p_user", "csha2p_password"); - load_sha256_cache("csha2p_user", "csha2p_password"); - this->do_handshake_ok(ssl_mode::require); - } - - void SslOffCacheHit_SuccessfulLogin_RequiresSha256() - { - // As we are sending password hashed, it is OK to not have SSL for this - this->set_credentials("csha2p_user", "csha2p_password"); - load_sha256_cache("csha2p_user", "csha2p_password"); - this->do_handshake_ok(ssl_mode::disable); - } - - void SslOnCacheMiss_SuccessfulLogin_RequiresSha256() - { - this->set_credentials("csha2p_user", "csha2p_password"); - clear_sha256_cache(); - this->do_handshake_ok(ssl_mode::require); - } - - void SslOffCacheMiss_FailedLogin_RequiresSha256() - { - // A cache miss would force us send a plaintext password over - // a non-TLS connection, so we fail - this->set_ssl(ssl_mode::disable); - this->set_credentials("csha2p_user", "csha2p_password"); - clear_sha256_cache(); - auto result = this->do_handshake(); - result.validate_error(errc::auth_plugin_requires_ssl, {}); - } - - void EmptyPasswordSslOnCacheHit_SuccessfulLogin_RequiresSha256() - { - this->set_credentials("csha2p_empty_password_user", ""); - load_sha256_cache("csha2p_empty_password_user", ""); - this->do_handshake_ok(ssl_mode::require); - } - - void EmptyPasswordSslOffCacheHit_SuccessfulLogin_RequiresSha256() - { - // Empty passwords are allowed over non-TLS connections - this->set_credentials("csha2p_empty_password_user", ""); - load_sha256_cache("csha2p_empty_password_user", ""); - this->do_handshake_ok(ssl_mode::disable); - } - - void EmptyPasswordSslOnCacheMiss_SuccessfulLogin_RequiresSha256() - { - this->set_credentials("csha2p_empty_password_user", ""); - clear_sha256_cache(); - this->do_handshake_ok(ssl_mode::require); - } - - void EmptyPasswordSslOffCacheMiss_SuccessfulLogin_RequiresSha256() - { - // Empty passwords are allowed over non-TLS connections - this->set_credentials("csha2p_empty_password_user", ""); - clear_sha256_cache(); - this->do_handshake_ok(ssl_mode::disable); - } - - void BadPasswordSslOnCacheMiss_FailedLogin_RequiresSha256() - { - this->set_ssl(ssl_mode::require); // Note: test over non-TLS would return "ssl required" - clear_sha256_cache(); - this->set_credentials("csha2p_user", "bad_password"); - auto result = this->do_handshake(); - result.validate_error(errc::access_denied_error, {"access denied", "csha2p_user"}); - } - - void BadPasswordSslOnCacheHit_FailedLogin_RequiresSha256() - { - this->set_ssl(ssl_mode::require); // Note: test over non-TLS would return "ssl required" - this->set_credentials("csha2p_user", "bad_password"); - load_sha256_cache("csha2p_user", "csha2p_password"); - auto result = this->do_handshake(); - result.validate_error(errc::access_denied_error, {"access denied", "csha2p_user"}); - } }; -BOOST_MYSQL_NETWORK_TEST_SUITE(CachingSha2HandshakeTest) - -BOOST_MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, SslOnCacheHit_SuccessfulLogin_RequiresSha256) -BOOST_MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, SslOffCacheHit_SuccessfulLogin_RequiresSha256) -BOOST_MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, SslOnCacheMiss_SuccessfulLogin_RequiresSha256) -BOOST_MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, SslOffCacheMiss_FailedLogin_RequiresSha256) -BOOST_MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, EmptyPasswordSslOnCacheHit_SuccessfulLogin_RequiresSha256) -BOOST_MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, EmptyPasswordSslOffCacheHit_SuccessfulLogin_RequiresSha256) -BOOST_MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, EmptyPasswordSslOnCacheMiss_SuccessfulLogin_RequiresSha256) -BOOST_MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, EmptyPasswordSslOffCacheMiss_SuccessfulLogin_RequiresSha256) -BOOST_MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, BadPasswordSslOnCacheMiss_FailedLogin_RequiresSha256) -BOOST_MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, BadPasswordSslOnCacheHit_FailedLogin_RequiresSha256) - -// Misc tests that are sensitive on SSL -template -struct MiscSslSensitiveHandshakeTest : SslSensitiveHandshakeTest +BOOST_MYSQL_NETWORK_TEST(ssl_on_cache_hit, caching_sha2_fixture, network_gen) { - void BadUser_FailedLogin() - { - // unreliable without SSL. If the default plugin requires SSL - // (like SHA256), this would fail with 'ssl required' - this->set_ssl(ssl_mode::require); - this->set_credentials("non_existing_user", "bad_password"); - auto result = this->do_handshake(); - result.validate_any_error(); // may be access denied or unknown auth plugin - } + this->set_credentials("csha2p_user", "csha2p_password"); + this->load_sha256_cache("csha2p_user", "csha2p_password"); + do_handshake_ok(this->conn, this->params, sample.net, ssl_mode::require); +} - void SslEnable_SuccessfulLogin() - { - // In all our CI systems, our servers support SSL, so - // ssl_mode::enable will do the same as ssl_mode::require. - // We test for this fact. - this->do_handshake_ok(ssl_mode::enable); - } -}; +BOOST_MYSQL_NETWORK_TEST(ssl_off_cache_hit, caching_sha2_fixture, network_gen) +{ + // As we are sending password hashed, it is OK to not have SSL for this + this->set_credentials("csha2p_user", "csha2p_password"); + this->load_sha256_cache("csha2p_user", "csha2p_password"); + do_handshake_ok(this->conn, this->params, sample.net, ssl_mode::disable); +} -BOOST_MYSQL_NETWORK_TEST_SUITE(MiscSslSensitiveHandshakeTest) +BOOST_MYSQL_NETWORK_TEST(ssl_on_cache_miss, caching_sha2_fixture, network_gen) +{ + this->set_credentials("csha2p_user", "csha2p_password"); + this->clear_sha256_cache(); + do_handshake_ok(this->conn, this->params, sample.net, ssl_mode::require); +} -BOOST_MYSQL_NETWORK_TEST(MiscSslSensitiveHandshakeTest, BadUser_FailedLogin) -BOOST_MYSQL_NETWORK_TEST(MiscSslSensitiveHandshakeTest, SslEnable_SuccessfulLogin) +BOOST_MYSQL_NETWORK_TEST(ssl_off_cache_miss, caching_sha2_fixture, network_gen) +{ + // A cache miss would force us send a plaintext password over + // a non-TLS connection, so we fail + this->set_credentials("csha2p_user", "csha2p_password"); + this->clear_sha256_cache(); + auto result = do_handshake(this->conn, this->params, sample.net, ssl_mode::disable); + result.validate_error(errc::auth_plugin_requires_ssl, {}); +} -} // anon namespace +BOOST_MYSQL_NETWORK_TEST(empty_password_ssl_on_cache_hit, caching_sha2_fixture, network_gen) +{ + this->set_credentials("csha2p_empty_password_user", ""); + this->load_sha256_cache("csha2p_empty_password_user", ""); + do_handshake_ok(this->conn, this->params, sample.net, ssl_mode::require); +} + +BOOST_MYSQL_NETWORK_TEST(empty_password_ssl_off_cache_hit, caching_sha2_fixture, network_gen) +{ + // Empty passwords are allowed over non-TLS connections + this->set_credentials("csha2p_empty_password_user", ""); + this->load_sha256_cache("csha2p_empty_password_user", ""); + do_handshake_ok(this->conn, this->params, sample.net, ssl_mode::disable); +} + +BOOST_MYSQL_NETWORK_TEST(empty_password_ssl_on_cache_miss, caching_sha2_fixture, network_gen) +{ + this->set_credentials("csha2p_empty_password_user", ""); + this->clear_sha256_cache(); + do_handshake_ok(this->conn, this->params, sample.net, ssl_mode::require); +} + +BOOST_MYSQL_NETWORK_TEST(empty_password_ssl_off_cache_miss, caching_sha2_fixture, network_gen) +{ + // Empty passwords are allowed over non-TLS connections + this->set_credentials("csha2p_empty_password_user", ""); + this->clear_sha256_cache(); + do_handshake_ok(this->conn, this->params, sample.net, ssl_mode::disable); +} + +BOOST_MYSQL_NETWORK_TEST(bad_password_ssl_on_cache_hit, caching_sha2_fixture, network_gen) +{ + // Note: test over non-TLS would return "ssl required" + this->set_credentials("csha2p_user", "bad_password"); + this->load_sha256_cache("csha2p_user", "csha2p_password"); + auto result = do_handshake(this->conn, this->params, sample.net, ssl_mode::require); + result.validate_error(errc::access_denied_error, {"access denied", "csha2p_user"}); +} + +BOOST_MYSQL_NETWORK_TEST(bad_password_ssl_on_cache_miss, caching_sha2_fixture, network_gen) +{ + // Note: test over non-TLS would return "ssl required" + this->set_credentials("csha2p_user", "bad_password"); + this->clear_sha256_cache(); + auto result = do_handshake(this->conn, this->params, sample.net, ssl_mode::require); + result.validate_error(errc::access_denied_error, {"access denied", "csha2p_user"}); +} + +BOOST_AUTO_TEST_SUITE_END() // caching_sha2_password + +// Other handshake tests +BOOST_MYSQL_NETWORK_TEST(no_database, handshake_fixture, network_ssl_gen) +{ + this->params.set_database(""); + do_handshake_ok(this->conn, this->params, sample.net, sample.ssl); +} + +BOOST_MYSQL_NETWORK_TEST(bad_database, handshake_fixture, network_ssl_gen) +{ + this->params.set_database("bad_database"); + auto result = do_handshake(this->conn, this->params, sample.net, sample.ssl); + result.validate_error(errc::dbaccess_denied_error, {"database", "bad_database"}); +} + +BOOST_TEST_DECORATOR(*boost::unit_test::label("sha256")) +BOOST_MYSQL_NETWORK_TEST(unknown_auth_plugin, handshake_fixture, network_ssl_gen) +{ + // Note: sha256_password is not supported, so it's an unknown plugin to us + this->set_credentials("sha2p_user", "sha2p_password"); + auto result = do_handshake(this->conn, this->params, sample.net, sample.ssl); + result.validate_error(errc::unknown_auth_plugin, {}); +} + +BOOST_MYSQL_NETWORK_TEST(bad_user, handshake_fixture, network_gen) +{ + // unreliable without SSL. If the default plugin requires SSL + // (like SHA256), this would fail with 'ssl required' + this->set_credentials("non_existing_user", "bad_password"); + auto result = do_handshake(this->conn, this->params, sample.net, ssl_mode::require); + result.validate_any_error(); // may be access denied or unknown auth plugin +} + +BOOST_MYSQL_NETWORK_TEST(ssl_enable, handshake_fixture, network_gen) +{ + // In all our CI systems, our servers support SSL, so + // ssl_mode::enable will do the same as ssl_mode::require. + // We test for this fact. + do_handshake_ok(this->conn, this->params, sample.net, ssl_mode::enable); +} + +BOOST_AUTO_TEST_SUITE_END() // test_handshake diff --git a/test/integration/integration_test_common.hpp b/test/integration/integration_test_common.hpp index b2d48530..67cc7538 100644 --- a/test/integration/integration_test_common.hpp +++ b/test/integration/integration_test_common.hpp @@ -8,44 +8,48 @@ #ifndef BOOST_MYSQL_TEST_INTEGRATION_INTEGRATION_TEST_COMMON_HPP #define BOOST_MYSQL_TEST_INTEGRATION_INTEGRATION_TEST_COMMON_HPP -#include #include "boost/mysql/connection.hpp" -#include "boost/mysql/detail/auxiliar/stringize.hpp" #include -#include -#include +#include #include -#include #include "test_common.hpp" #include "metadata_validator.hpp" #include "network_functions.hpp" #include "get_endpoint.hpp" +#include "network_test.hpp" namespace boost { namespace mysql { namespace test { +// Verifies that we are or are not using SSL, depending on what mode was requested. +template +void validate_ssl(const connection& conn, ssl_mode m) +{ + // All our test systems MUST support SSL to run these tests + bool should_use_ssl = + m == ssl_mode::enable || m == ssl_mode::require; + BOOST_TEST(conn.uses_ssl() == should_use_ssl); +} + /** - * The base of all integration tests. The fixture constructor creates - * a connection, an asio io_context and a thread to run it. It also - * performs the physical connect on the connection's underlying stream. - * - * The fixture is template-parameterized by a stream type, so that - * tests can be run using several stream types. For a new stream type, - * define a physical_connect() function applicable to that stream. + * Base fixture to use in integration tests. The fixture constructor creates + * a connection, an asio io_context and a thread to run it. + * The fixture is template-parameterized by a stream type, as required + * by BOOST_MYSQL_NETWORK_TEST. */ template -struct IntegTest : testing::Test +struct network_fixture { using stream_type = Stream; - mysql::connection_params params; + connection_params params; boost::asio::io_context ctx; - mysql::socket_connection conn; + socket_connection conn; boost::asio::executor_work_guard guard; std::thread runner; - IntegTest() : + network_fixture() : params("integ_user", "integ_password", "boost_mysql_integtests"), conn(ctx.get_executor()), guard(ctx.get_executor()), @@ -53,7 +57,7 @@ struct IntegTest : testing::Test { } - ~IntegTest() + ~network_fixture() { error_code code; error_info info; @@ -77,7 +81,7 @@ struct IntegTest : testing::Test { params.set_ssl(ssl_options(m)); conn.handshake(params); - validate_ssl(m); + validate_ssl(conn, m); } void connect(ssl_mode m) @@ -86,41 +90,6 @@ struct IntegTest : testing::Test handshake(m); } - static bool should_use_ssl(ssl_mode m) - { - return m == ssl_mode::enable || m == ssl_mode::require; - } - - // Verifies that we are or are not using SSL, depending on what mode was requested. - void validate_ssl(ssl_mode m) - { - if (should_use_ssl(m)) - { - // All our test systems MUST support SSL to run these tests - EXPECT_TRUE(conn.uses_ssl()); - } - else - { - EXPECT_FALSE(conn.uses_ssl()); - } - } - - void validate_eof( - const resultset& result, - int affected_rows=0, - int warnings=0, - int last_insert=0, - boost::string_view info="" - ) - { - EXPECT_TRUE(result.valid()); - EXPECT_TRUE(result.complete()); - EXPECT_EQ(result.affected_rows(), affected_rows); - EXPECT_EQ(result.warning_count(), warnings); - EXPECT_EQ(result.last_insert_id(), last_insert); - EXPECT_EQ(result.info(), info); - } - void validate_2fields_meta( const std::vector& fields, const std::string& table @@ -155,54 +124,88 @@ struct IntegTest : testing::Test } }; -// To be used as test parameter, when a test case should be run +// To be used as sample in data driven tests, when a test case should be run // over all different network_function's. template -struct network_testcase : named_tag +struct network_sample { network_functions* net; - network_testcase(network_functions* funs) : + network_sample(network_functions* funs) : net(funs) { } - const char* name() const { return net->name(); } - - static std::vector> make_all() + void set_test_attributes(boost::unit_test::test_case& test) const { - std::vector> res; - for (auto* net: make_all_network_functions()) + test.add_label(net->name()); + } +}; + +template +std::ostream& operator<<(std::ostream& os, const network_sample& value) +{ + return os << value.net->name(); +} + +// Data generator for network_sample +struct network_gen +{ + template + static std::vector> make_all() + { + std::vector> res; + for (auto* net: all_network_functions()) { res.emplace_back(net); } return res; } + + template + static const std::vector>& generate() + { + static std::vector> res = make_all(); + return res; + } }; -// To be used as test parameter, when a test should be run over +// To be used as sample in data driven tests, +// when a test should be run over // all network_function's and ssl_mode's template -struct network_testcase_with_ssl +struct network_ssl_sample { network_functions* net; ssl_mode ssl; - network_testcase_with_ssl(network_functions* funs, ssl_mode ssl) : + network_ssl_sample(network_functions* funs, ssl_mode ssl) : net(funs), ssl(ssl) { } - std::string name() const + void set_test_attributes(boost::unit_test::test_case& test) const { - return detail::stringize(net->name(), '_', to_string(ssl)); + test.add_label(net->name()); + test.add_label(to_string(ssl)); } +}; - static std::vector> make_all() +template +std::ostream& operator<<(std::ostream& os, const network_ssl_sample& value) +{ + return os << value.net->name() << '_' << to_string(value.ssl); +} + +// Data generator for network_ssl_sample +struct network_ssl_gen +{ + template + static std::vector> make_all() { - std::vector> res; - for (auto* net: make_all_network_functions()) + std::vector> res; + for (auto* net: all_network_functions()) { for (auto ssl: {ssl_mode::require, ssl_mode::disable}) { @@ -211,102 +214,17 @@ struct network_testcase_with_ssl } return res; } -}; -/** - * The base class for tests to be run over multiple stream types - * and multiple parameters (typically network_function's and ssl_mode's). - * - * To define test cases, please do: - * 1. Select a (possibly template) test parameter (e.g. network_testcase_with_ssl). - * The test parameter should have a std::string name() method, and a static - * std::vector make_all() method, that returns a vector with - * all test parameters to use. - * 2. Declare a test fixture, as a struct inheriting from NetworkTest. It should be - * a template, parameterized just by the stream type. - * 3. Define the tests, as methods of the fixture. They should have void() signature. - * 4. Use BOOST_MYSQL_NETWORK_TEST_SUITE(FixtureName) to instantiate the test suite. - * This will create as many suites as stream types officially supported, and call - * INSTANTIATE_TEST_SUITE_P. For example, for a test suite MySuite, it may generate - * suites MySuiteTCP and MySuiteUNIX, for TCP and UNIX sockets. - * 5. Use BOOST_MYSQL_NETWORK_TEST(FixtureName, TestName) for each test you defined - * as method of the fixture. This will call TEST_P once for each supported stream, - * calling the method you defined in the fixture. - */ -template < - typename Stream, - typename Param=network_testcase_with_ssl -> -struct NetworkTest : public IntegTest, - public testing::WithParamInterface -{ + template + static const std::vector>& generate() + { + static auto res = make_all(); + return res; + } }; } // test } // mysql } // boost -// Typedefs -#define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_HELPER(TestSuiteName, Suffix, Stream) \ - using TestSuiteName##Suffix = TestSuiteName; - -#define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_TCP(TestSuiteName) \ - BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_HELPER(TestSuiteName, TCP, boost::asio::ip::tcp::socket) - -#define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_TCP_DEFAULT_TOKEN(TestSuiteName) \ - BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_HELPER(TestSuiteName, TCPDefaultToken, boost::mysql::test::tcp_future_socket) - -#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS -#define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_UNIX(TestSuiteName) \ - BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_HELPER(TestSuiteName, UNIX, boost::asio::local::stream_protocol::socket) -#else -#define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_UNIX(TestSuiteName) -#endif - -#define BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS(TestSuiteName) \ - BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_TCP(TestSuiteName) \ - BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_TCP_DEFAULT_TOKEN(TestSuiteName) \ - BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS_UNIX(TestSuiteName) - -// Test definition -#define BOOST_MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, Suffix) \ - TEST_P(TestSuiteName##Suffix, TestName) { this->TestName(); } - -#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS -#define BOOST_MYSQL_NETWORK_TEST_UNIX(TestSuiteName, TestName) \ - BOOST_MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, UNIX) -#else -#define BOOST_MYSQL_NETWORK_TEST_UNIX(TestSuiteName, TestName) -#endif - -#define BOOST_MYSQL_NETWORK_TEST(TestSuiteName, TestName) \ - BOOST_MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, TCP) \ - BOOST_MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, TCPDefaultToken) \ - BOOST_MYSQL_NETWORK_TEST_UNIX(TestSuiteName, TestName) - -// Test suite instantiation -#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, Suffix) \ - INSTANTIATE_TEST_SUITE_P(Default, TestSuiteName##Suffix, testing::ValuesIn( \ - TestSuiteName##Suffix::ParamType::make_all() \ - ), test_name_generator); - -#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS -#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_UNIX(TestSuiteName) \ - BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, UNIX) -#else -#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_UNIX(TestSuiteName) -#endif - -#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE(TestSuiteName) \ - BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, TCP) \ - BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, TCPDefaultToken) \ - BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_UNIX(TestSuiteName) - -// Typedefs + Instantiation -#define BOOST_MYSQL_NETWORK_TEST_SUITE(TestSuiteName) \ - BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS(TestSuiteName) \ - BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE(TestSuiteName) - - - #endif /* TEST_INTEGRATION_INTEGRATION_TEST_COMMON_HPP_ */ diff --git a/test/integration/metadata_validator.cpp b/test/integration/metadata_validator.cpp index 9a9cc8d2..58699b2e 100644 --- a/test/integration/metadata_validator.cpp +++ b/test/integration/metadata_validator.cpp @@ -6,10 +6,11 @@ // #include "metadata_validator.hpp" -#include +#include using namespace boost::mysql::test; + #define MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(getter) \ { #getter, &boost::mysql::field_metadata::getter } @@ -29,6 +30,8 @@ static struct flag_entry MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_set_to_now_on_update) }; +BOOST_TEST_DONT_PRINT_LOG_VALUE(std::vector::iterator) + static bool contains( meta_validator::flag_getter flag, const std::vector& flagvec @@ -38,18 +41,18 @@ static bool contains( } void meta_validator::validate( - const mysql::field_metadata& value + const field_metadata& value ) const { // Fixed fields - EXPECT_EQ(value.database(), "boost_mysql_integtests"); - EXPECT_EQ(value.table(), table_); - EXPECT_EQ(value.original_table(), org_table_); - EXPECT_EQ(value.field_name(), field_); - EXPECT_EQ(value.original_field_name(), org_field_); - EXPECT_GT(value.column_length(), 0u); - EXPECT_EQ(value.type(), type_); - EXPECT_EQ(value.decimals(), decimals_); + BOOST_TEST(value.database() == "boost_mysql_integtests"); + BOOST_TEST(value.table() == table_); + BOOST_TEST(value.original_table() == org_table_); + BOOST_TEST(value.field_name() == field_); + BOOST_TEST(value.original_field_name() == org_field_); + BOOST_TEST(value.column_length() > 0u); + BOOST_TEST(value.type() == type_); + BOOST_TEST(value.decimals() == decimals_); // Flags std::vector all_flags (std::begin(flag_names), std::end(flag_names)); @@ -61,9 +64,9 @@ void meta_validator::validate( all_flags.end(), [true_flag](const flag_entry& entry) { return entry.getter == true_flag; } ); - ASSERT_NE(it, all_flags.end()); // no repeated flag - ASSERT_FALSE(contains(true_flag, ignore_flags_)); // ignore flags cannot be set to true - EXPECT_TRUE((value.*true_flag)()) << it->name; + BOOST_TEST_REQUIRE(it != all_flags.end()); // no repeated flag + BOOST_TEST_REQUIRE(!contains(true_flag, ignore_flags_)); // ignore flags cannot be set to true + BOOST_TEST((value.*true_flag)(), it->name); all_flags.erase(it); } @@ -71,7 +74,7 @@ void meta_validator::validate( { if (!contains(entry.getter, ignore_flags_)) { - EXPECT_FALSE((value.*entry.getter)()) << entry.name; + BOOST_TEST(!(value.*entry.getter)(), entry.name); } } } @@ -81,7 +84,7 @@ void boost::mysql::test::validate_meta( const std::vector& expected ) { - ASSERT_EQ(actual.size(), expected.size()); + BOOST_TEST_REQUIRE(actual.size() == expected.size()); for (std::size_t i = 0; i < actual.size(); ++i) { expected[i].validate(actual[i]); diff --git a/test/integration/network_functions.hpp b/test/integration/network_functions.hpp index 021753e2..f5f8001c 100644 --- a/test/integration/network_functions.hpp +++ b/test/integration/network_functions.hpp @@ -9,11 +9,9 @@ #define BOOST_MYSQL_TEST_INTEGRATION_NETWORK_FUNCTIONS_HPP #include "boost/mysql/connection.hpp" -#include "test_common.hpp" -#include +#include "tcp_future_socket.hpp" #include #include -#include /** * A mechanism to test all variants of a network algorithm (e.g. synchronous @@ -23,23 +21,14 @@ * All network algorithm variants are transformed to a single one: a synchronous * one, returning a network_result. A network_result contains a T, an error_code * and an error_info. network_functions is an interface, which each variant implements. - * Instead of directly calling connection, prepared_statement and resultset network - * functions directly, tests use the network_functions interface. Tests are then - * parameterized (e.g. TEST_P) and run over all possible implementations of - * network_functions. + * Instead of calling connection, prepared_statement and resultset network + * functions directly, tests use the network_functions interface. network_functions + * is also a template, allowing tests to be run over different stream types. + * network_functions is intended to be used as part of a test sample, together with + * BOOST_MYSQL_NETWORK_TEST. * - * To make things more interesting, network_functions interface is also a template, - * allowing tests to be run over different stream types (e.g. TCP, UNIX sockets...). - * Use BOOST_MYSQL_NETWORK_TEST* macros and NetworkTest to achieve this. - * - * The following variants are currently supported: - * - Synchronous with error codes. - * - Synchronous with exceptions. - * - Asynchronous, with callbacks, with error_info. - * - Asynchronous, with callbacks, without error_info. - * - Asynchronous, with coroutines, with error_info. - * - Asynchronous, with coroutines, without error_info. - * - Asynchronous, with futures. + * See the implementation of all_network_functions() for the list + * of supported network functions. */ namespace boost { @@ -48,55 +37,24 @@ namespace test { struct no_result {}; -template -struct network_result +struct network_result_base { error_code err; - boost::optional info; // some async initiators (futures) don't support this - T value; + boost::optional info; // some network_function's don't provide this - network_result() = default; + network_result_base() = default; + network_result_base(error_code ec) : err(ec) {} + network_result_base(error_code ec, error_info&& info): err(ec), info(std::move(info)) {} - network_result(error_code ec, error_info info, T&& value = {}): - err(ec), info(std::move(info)), value(std::move(value)) {} - - network_result(error_code ec, T&& value = {}): - err(ec), value(std::move(value)) {} - - void validate_no_error() const - { - ASSERT_EQ(err, error_code()) << "with error_info= " << - (info ? info->message().c_str() : ""); - if (info) - { - EXPECT_EQ(*info, error_info()); - } - } + void validate_no_error() const; // Use when you don't care or can't determine the kind of error - void validate_any_error( - const std::vector& expected_msg={} - ) const - { - ASSERT_NE(err, error_code()) << "with error_info= " << - (info ? info->message().c_str() : ""); - if (info) - { - validate_string_contains(info->message(), expected_msg); - } - } + void validate_any_error(const std::vector& expected_msg={}) const; void validate_error( error_code expected_errc, const std::vector& expected_msg - ) const - { - EXPECT_EQ(err, expected_errc); - if (info) - { - validate_string_contains(info->message(), expected_msg); - } - } + ) const; void validate_error( errc expected_errc, @@ -107,20 +65,19 @@ struct network_result } }; -using value_list_it = std::forward_list::const_iterator; - -// A TCP socket that has use_future as default completion token -class future_executor : public boost::asio::io_context::executor_type +template +struct network_result : network_result_base { -public: - future_executor(const boost::asio::io_context::executor_type& base) : - boost::asio::io_context::executor_type(base) {} - using default_completion_token_type = boost::asio::use_future_t<>; + T value; + + network_result() = default; + network_result(error_code ec, error_info&& info, T&& value = {}): + network_result_base(ec, std::move(info)), value(std::move(value)) {} + network_result(error_code ec, T&& value = {}): + network_result_base(ec), value(std::move(value)) {} }; -using tcp_future_socket = boost::asio::basic_stream_socket< - boost::asio::ip::tcp, - future_executor ->; + +using value_list_it = std::forward_list::const_iterator; template class network_functions @@ -151,11 +108,11 @@ public: }; template -std::vector*> make_all_network_functions(); +const std::vector*>& all_network_functions(); template <> -std::vector*> -make_all_network_functions(); +const std::vector*>& +all_network_functions(); } // test } // mysql diff --git a/test/integration/network_functions/async_callback.cpp b/test/integration/network_functions/async_callback.cpp index f601ed96..c85dcd4f 100644 --- a/test/integration/network_functions/async_callback.cpp +++ b/test/integration/network_functions/async_callback.cpp @@ -8,6 +8,7 @@ #include "network_functions_impl.hpp" #include +#include using namespace boost::mysql::test; using boost::mysql::connection_params; @@ -36,8 +37,8 @@ public: std::thread::id calling_thread() const { return calling_thread_; } void verify() { - EXPECT_EQ(call_count(), 1); // we call handler exactly once - EXPECT_NE(calling_thread(), std::this_thread::get_id()); // handler runs in the io_context thread + BOOST_TEST(call_count() == 1); // we call handler exactly once + BOOST_TEST(calling_thread() != std::this_thread::get_id()); // handler runs in the io_context thread } }; diff --git a/test/integration/network_functions/network_functions_impl.cpp b/test/integration/network_functions/network_functions_impl.cpp index fce28249..4146a20b 100644 --- a/test/integration/network_functions/network_functions_impl.cpp +++ b/test/integration/network_functions/network_functions_impl.cpp @@ -7,14 +7,62 @@ #include "network_functions_impl.hpp" #include // for BOOST_ASIO_HAS_CO_AWAIT +#include +#include "test_common.hpp" using namespace boost::mysql::test; -template -std::vector*> -boost::mysql::test::make_all_network_functions() +// network_result_base +using boost::mysql::test::network_result_base; + +static const char* get_message( + const boost::optional& info +) noexcept { - return { + return info ? info->message().c_str() : ""; +} + +void network_result_base::validate_no_error() const +{ + BOOST_TEST_REQUIRE(err == error_code(), + "with error_info= " << get_message(info)); + if (info) + { + BOOST_TEST(*info == error_info()); + } +} + +void network_result_base::validate_any_error( + const std::vector& expected_msg +) const +{ + BOOST_TEST_REQUIRE(err != error_code(), + "with error_info= " << get_message(info)); + if (info) + { + validate_string_contains(info->message(), expected_msg); + } +} + +void network_result_base::validate_error( + error_code expected_errc, + const std::vector& expected_msg +) const +{ + BOOST_TEST_REQUIRE(err == expected_errc, + "with error_info= " << get_message(info)); + if (info) + { + validate_string_contains(info->message(), expected_msg); + } +} + +// free functions +template +const std::vector*>& +boost::mysql::test::all_network_functions() +{ + static std::vector*> res { sync_errc_functions(), sync_exc_functions(), async_callback_errinfo_functions(), @@ -28,24 +76,26 @@ boost::mysql::test::make_all_network_functions() async_coroutinecpp20_noerrinfo_functions() #endif }; + return res; } // We implement a single variant for default completion tokens // Others do not add any value template <> -std::vector*> -boost::mysql::test::make_all_network_functions() +const std::vector*>& +boost::mysql::test::all_network_functions() { - return { + static std::vector*> res { async_future_errinfo_functions(), async_future_noerrinfo_functions(), }; + return res; } -template std::vector*> -boost::mysql::test::make_all_network_functions(); +template const std::vector*>& +boost::mysql::test::all_network_functions(); #ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS -template std::vector*> -boost::mysql::test::make_all_network_functions(); +template const std::vector*>& +boost::mysql::test::all_network_functions(); #endif diff --git a/test/integration/network_test.hpp b/test/integration/network_test.hpp new file mode 100644 index 00000000..a4393f11 --- /dev/null +++ b/test/integration/network_test.hpp @@ -0,0 +1,166 @@ +// +// Copyright (c) 2019-2020 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef TEST_INTEGRATION_NETWORK_TEST_HPP_ +#define TEST_INTEGRATION_NETWORK_TEST_HPP_ + +#include +#include +#include +#include +#include "tcp_future_socket.hpp" + +/** + * Defines the required infrastructure for network tests. + * This functionality is accessed via the BOOST_MYSQL_NETWORK_TEST + * macro. Network tests combine data driven with templated test + * functionality. They run the specified test across all samples + * and all the supported stream types (these are hard-wired in + * network_test_registrar). + * + * We can't employ built-in Boost.Test functionality because: + * - Data driven + templated tests are not supported. + * - We need tests generated with different samples to have + * specific names and labels. This is used to filter out + * tests using SSL when running under Valgrind, to reduce run time. + * + * BOOST_MYSQL_NETWORK_TEST is passed three arguments: + * - The test name. Should be a valid C++ identifier. + * - Fixture name. All network tests must have a single fixture. + * Only constructor/destructor fixtures are supported. The fixture + * should be a template with a single argument: the stream name. + * See network_fixture for the base class to use. + * - Data generator. The name of a class that specifies how to generate + * samples. It should have a static function with the following signature: + * template + * static SampleCollection generate(); // SampleCollection is a collection of samples. + * + * The generated samples may be different for each stream type. + * Samples should be streamable and have a + * void set_test_attributes(boost::unit_test::test_case&) const + * function, allowing the sample to add labels or other properties to the test. + * + * Usual generators are network_gen and network_ssl_gen, to run + * a single test over all network functions and SSL modes. See + * network_functions.hpp for more info on network functions. + * + * After calling BOOST_MYSQL_NETWORK_TEST, you should define + * a function body (like in BOOST_AUTO_TEST_CASE). The function + * will be templated with a Stream type argument, and will have a + * 'sample' parameter. + */ + +namespace boost { +namespace mysql { +namespace test { + +// The type of a sample generated by DataGenerator +// when running tests for stream type Stream. +template +using data_gen_sample_type = typename std::decay< + decltype(*std::begin(DataGenerator:: template generate())) +>::type; + +// Inspired in how Boost.Test auto-registers unit tests. +// BOOST_MYSQL_NETWORK_TEST defines a static variable of this +// type, which takes care of test registration. +template