diff --git a/TODO.txt b/TODO.txt index e640d9ff..d8b5a6b8 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,6 +1,7 @@ UNIX socket - Network functions for UNIX sockets (generic would be better) - Test suite for UNIX sockets (and maybe SSL too) + Test suite for UNIX sockets + Rework names to be BOOST_ and not NETWORK_ + Unify test name generators Conditionally build/run stuff for UNIX sockets Config files for CIs to know where UNIX socket is Docs diff --git a/test/integration/close_statement.cpp b/test/integration/close_statement.cpp index db2abcfc..14fef0b9 100644 --- a/test/integration/close_statement.cpp +++ b/test/integration/close_statement.cpp @@ -12,37 +12,41 @@ using namespace boost::mysql::test; namespace { -struct CloseStatementTest : NetworkTest + +template +struct CloseStatementTest : NetworkTest { + + 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"}); + } }; -TEST_P(CloseStatementTest, ExistingOrClosedStatement) -{ - auto* net = GetParam().net; +MYSQL_NETWORK_TEST_SUITE2(CloseStatementTest) +MYSQL_NETWORK_TEST(CloseStatementTest, ExistingOrClosedStatement) - // Prepare a statement - auto stmt = net->prepare_statement(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"}); -} - -MYSQL_NETWORK_TEST_SUITE(CloseStatementTest); } diff --git a/test/integration/database_types.cpp b/test/integration/database_types.cpp index 6ee60e60..54313427 100644 --- a/test/integration/database_types.cpp +++ b/test/integration/database_types.cpp @@ -55,7 +55,7 @@ std::ostream& operator<<(std::ostream& os, const database_types_testcase& v) return os << v.table << "." << v.field << "." << v.row_id; } -struct DatabaseTypesTest : IntegTestAfterHandshake, +struct DatabaseTypesTest : IntegTestAfterHandshake, WithParamInterface { DatabaseTypesTest(): IntegTestAfterHandshake(boost::mysql::ssl_mode::disable) {} diff --git a/test/integration/execute_statement.cpp b/test/integration/execute_statement.cpp index a29a133f..3c9b2977 100644 --- a/test/integration/execute_statement.cpp +++ b/test/integration/execute_statement.cpp @@ -13,92 +13,119 @@ using boost::mysql::value; using boost::mysql::error_code; using boost::mysql::error_info; using boost::mysql::errc; -using boost::mysql::tcp_resultset; -using boost::mysql::tcp_prepared_statement; +using boost::mysql::prepared_statement; namespace { -struct ExecuteStatementTest : public NetworkTest +template +struct ExecuteStatementTest : public NetworkTest { + prepared_statement do_prepare(std::string_view stmt) + { + auto res = this->GetParam().net->prepare_statement(this->conn, stmt); + res.validate_no_error(); + return std::move(res.value); + } + + auto do_execute(prepared_statement& stmt, value_list_it first, value_list_it last) + { + return this->GetParam().net->execute_statement(stmt, first, last); + } + + auto do_execute(prepared_statement& stmt, const std::vector& params) + { + return this->GetParam().net->execute_statement(stmt, params); + } + + // Iterator version + void Iterator_OkNoParams() + { + std::forward_list params; + auto stmt = do_prepare("SELECT * FROM empty_table"); + auto result = do_execute(stmt, params.begin(), params.end()); // execute + result.validate_no_error(); + EXPECT_TRUE(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 = do_execute(stmt, params.begin(), params.end()); + result.validate_no_error(); + EXPECT_TRUE(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 = do_execute(stmt, params.begin(), params.end()); + result.validate_error(errc::wrong_num_params, {"param", "2", "1", "statement", "execute"}); + EXPECT_FALSE(result.value.valid()); + } + + void Iterator_ServerError() + { + std::forward_list params { value("f0"), value("bad_date") }; + auto stmt = do_prepare("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)"); + auto result = do_execute(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 = do_execute(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 = do_execute(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 = do_execute(stmt, params); + result.validate_error(errc::wrong_num_params, {"param", "2", "1", "statement", "execute"}); + EXPECT_FALSE(result.value.valid()); + } + + void Container_ServerError() + { + auto stmt = do_prepare("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)"); + auto result = do_execute(stmt, makevalues("f0", "bad_date")); + result.validate_error(errc::truncated_wrong_value, {"field_date", "bad_date", "incorrect date value"}); + EXPECT_FALSE(result.value.valid()); + } }; -// Iterator version -TEST_P(ExecuteStatementTest, Iterator_OkNoParams) -{ - std::forward_list params; - auto stmt = conn.prepare_statement("SELECT * FROM empty_table"); - auto result = GetParam().net->execute_statement(stmt, params.begin(), params.end()); // execute - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); -} +MYSQL_NETWORK_TEST_SUITE2(ExecuteStatementTest); -TEST_P(ExecuteStatementTest, Iterator_OkWithParams) -{ - std::forward_list params { value("item"), value(42) }; - auto stmt = conn.prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)"); - auto result = GetParam().net->execute_statement(stmt, params.begin(), params.end()); - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); -} +MYSQL_NETWORK_TEST(ExecuteStatementTest, Iterator_OkNoParams) +MYSQL_NETWORK_TEST(ExecuteStatementTest, Iterator_OkWithParams) +MYSQL_NETWORK_TEST(ExecuteStatementTest, Iterator_MismatchedNumParams) +MYSQL_NETWORK_TEST(ExecuteStatementTest, Iterator_ServerError) +MYSQL_NETWORK_TEST(ExecuteStatementTest, Container_OkNoParams) +MYSQL_NETWORK_TEST(ExecuteStatementTest, Container_OkWithParams) +MYSQL_NETWORK_TEST(ExecuteStatementTest, Container_MismatchedNumParams) +MYSQL_NETWORK_TEST(ExecuteStatementTest, Container_ServerError) -TEST_P(ExecuteStatementTest, Iterator_MismatchedNumParams) -{ - std::forward_list params { value("item") }; - auto stmt = conn.prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)"); - auto result = GetParam().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()); -} -TEST_P(ExecuteStatementTest, Iterator_ServerError) -{ - std::forward_list params { value("f0"), value("bad_date") }; - auto stmt = conn.prepare_statement("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)"); - auto result = GetParam().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 -TEST_P(ExecuteStatementTest, Container_OkNoParams) -{ - auto stmt = conn.prepare_statement("SELECT * FROM empty_table"); - auto result = GetParam().net->execute_statement(stmt, std::vector()); // execute - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); -} - -TEST_P(ExecuteStatementTest, Container_OkWithParams) -{ - std::vector params { value("item"), value(42) }; - auto stmt = conn.prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)"); - auto result = GetParam().net->execute_statement(stmt, params); - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); -} - -TEST_P(ExecuteStatementTest, Container_MismatchedNumParams) -{ - std::vector params { value("item") }; - auto stmt = conn.prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)"); - auto result = GetParam().net->execute_statement(stmt, params); - result.validate_error(errc::wrong_num_params, {"param", "2", "1", "statement", "execute"}); - EXPECT_FALSE(result.value.valid()); -} - -TEST_P(ExecuteStatementTest, Container_ServerError) -{ - auto stmt = conn.prepare_statement("INSERT INTO inserts_table (field_varchar, field_date) VALUES (?, ?)"); - auto result = GetParam().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()); -} - -MYSQL_NETWORK_TEST_SUITE(ExecuteStatementTest); // Other containers -struct ExecuteStatementOtherContainersTest : IntegTestAfterHandshake {}; +struct ExecuteStatementOtherContainersTest : IntegTestAfterHandshake {}; TEST_F(ExecuteStatementOtherContainersTest, NoParams_CanUseNoStatementParamsVariable) { diff --git a/test/integration/handshake.cpp b/test/integration/handshake.cpp index db9fc53b..ce8df8ba 100644 --- a/test/integration/handshake.cpp +++ b/test/integration/handshake.cpp @@ -11,6 +11,10 @@ #include #include +// Tests containing 'RequiresSha256' in their name require SHA256 functionality. +// This is used by a calling script to exclude these tests on systems +// that do not support this functionality + namespace net = boost::asio; using namespace testing; using namespace boost::mysql::test; @@ -26,13 +30,14 @@ namespace { // Handshake tests not depending on whether we use SSL or not -struct SslIndifferentHandshakeTest : public IntegTest, - public testing::WithParamInterface +template +struct SslIndifferentHandshakeTest : public IntegTest, + public testing::WithParamInterface> { auto do_handshake() { - connection_params.set_ssl(boost::mysql::ssl_options(GetParam().ssl)); - return GetParam().net->handshake(conn, connection_params); + this->connection_params.set_ssl(boost::mysql::ssl_options(this->GetParam().ssl)); + return this->GetParam().net->handshake(this->conn, this->connection_params); } // does handshake and verifies it went OK @@ -40,21 +45,22 @@ struct SslIndifferentHandshakeTest : public IntegTest, { auto result = do_handshake(); result.validate_no_error(); - validate_ssl(GetParam().ssl); + this->validate_ssl(this->GetParam().ssl); } }; -struct SslSensitiveHandshakeTest : public IntegTest, - public testing::WithParamInterface +template +struct SslSensitiveHandshakeTest : public IntegTest, + public testing::WithParamInterface*> { void set_ssl(ssl_mode m) { - connection_params.set_ssl(boost::mysql::ssl_options(m)); + this->connection_params.set_ssl(boost::mysql::ssl_options(m)); } auto do_handshake() { - return GetParam()->handshake(conn, connection_params); + return this->GetParam()->handshake(this->conn, this->connection_params); } void do_handshake_ok(ssl_mode m) @@ -62,61 +68,76 @@ struct SslSensitiveHandshakeTest : public IntegTest, set_ssl(m); auto result = do_handshake(); result.validate_no_error(); - validate_ssl(m); + this->validate_ssl(m); } }; // mysql_native_password -struct MysqlNativePasswordHandshakeTest : SslIndifferentHandshakeTest {}; - -TEST_P(MysqlNativePasswordHandshakeTest, RegularUser_SuccessfulLogin) +template +struct MysqlNativePasswordHandshakeTest : SslIndifferentHandshakeTest { - set_credentials("mysqlnp_user", "mysqlnp_password"); - do_handshake_ok(); -} + void RegularUser_SuccessfulLogin() + { + this->set_credentials("mysqlnp_user", "mysqlnp_password"); + this->do_handshake_ok(); + } -TEST_P(MysqlNativePasswordHandshakeTest, EmptyPassword_SuccessfulLogin) -{ - set_credentials("mysqlnp_empty_password_user", ""); - do_handshake_ok(); -} + void EmptyPassword_SuccessfulLogin() + { + this->set_credentials("mysqlnp_empty_password_user", ""); + this->do_handshake_ok(); + } -TEST_P(MysqlNativePasswordHandshakeTest, BadPassword_FailedLogin) -{ - set_credentials("mysqlnp_user", "bad_password"); - auto result = do_handshake(); - result.validate_error(errc::access_denied_error, {"access denied", "mysqlnp_user"}); -} + 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"}); + } +}; + +MYSQL_NETWORK_TEST_SUITE2(MysqlNativePasswordHandshakeTest); + +MYSQL_NETWORK_TEST(MysqlNativePasswordHandshakeTest, RegularUser_SuccessfulLogin) +MYSQL_NETWORK_TEST(MysqlNativePasswordHandshakeTest, EmptyPassword_SuccessfulLogin) +MYSQL_NETWORK_TEST(MysqlNativePasswordHandshakeTest, BadPassword_FailedLogin) -MYSQL_NETWORK_TEST_SUITE(MysqlNativePasswordHandshakeTest); // Other handshake tests not depending on SSL mode -struct MiscSslIndifferentHandshakeTest : SslIndifferentHandshakeTest {}; - -TEST_P(MiscSslIndifferentHandshakeTest, NoDatabase_SuccessfulLogin) +template +struct MiscSslIndifferentHandshakeTest : SslIndifferentHandshakeTest { - connection_params.set_database(""); - do_handshake_ok(); -} + void NoDatabase_SuccessfulLogin() + { + this->connection_params.set_database(""); + this->do_handshake_ok(); + } -TEST_P(MiscSslIndifferentHandshakeTest, BadDatabase_FailedLogin) -{ - connection_params.set_database("bad_database"); - auto result = do_handshake(); - result.validate_error(errc::dbaccess_denied_error, {"database", "bad_database"}); -} + void BadDatabase_FailedLogin() + { + this->connection_params.set_database("bad_database"); + auto result = this->do_handshake(); + result.validate_error(errc::dbaccess_denied_error, {"database", "bad_database"}); + } -TEST_P(MiscSslIndifferentHandshakeTest, UnknownAuthPlugin_FailedLogin_RequiresSha256) -{ - set_credentials("sha2p_user", "sha2p_password"); - auto result = do_handshake(); - result.validate_error(errc::unknown_auth_plugin, {}); -} + 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, {}); + } +}; -MYSQL_NETWORK_TEST_SUITE(MiscSslIndifferentHandshakeTest); +MYSQL_NETWORK_TEST_SUITE2(MiscSslIndifferentHandshakeTest); + +MYSQL_NETWORK_TEST(MiscSslIndifferentHandshakeTest, NoDatabase_SuccessfulLogin) +MYSQL_NETWORK_TEST(MiscSslIndifferentHandshakeTest, BadDatabase_FailedLogin) +MYSQL_NETWORK_TEST(MiscSslIndifferentHandshakeTest, UnknownAuthPlugin_FailedLogin_RequiresSha256) // caching_sha2_password -struct CachingSha2HandshakeTest : SslSensitiveHandshakeTest +template +struct CachingSha2HandshakeTest : SslSensitiveHandshakeTest { void load_sha256_cache(const std::string& user, const std::string& password) { @@ -134,120 +155,133 @@ struct CachingSha2HandshakeTest : SslSensitiveHandshakeTest { check_call("mysql -u root -e \"FLUSH PRIVILEGES\""); } + + // 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"}); + } }; -// Tests containing 'RequiresSha256' in their name require SHA256 functionality. -// This is used by a calling script to exclude these tests on systems -// that do not support this functionality -TEST_P(CachingSha2HandshakeTest, SslOnCacheHit_SuccessfulLogin_RequiresSha256) -{ - set_credentials("csha2p_user", "csha2p_password"); - load_sha256_cache("csha2p_user", "csha2p_password"); - do_handshake_ok(ssl_mode::require); -} +BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS(CachingSha2HandshakeTest); -TEST_P(CachingSha2HandshakeTest, SslOffCacheHit_SuccessfulLogin_RequiresSha256) -{ - // As we are sending password hashed, it is OK to not have SSL for this - set_credentials("csha2p_user", "csha2p_password"); - load_sha256_cache("csha2p_user", "csha2p_password"); - do_handshake_ok(ssl_mode::disable); -} +MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, SslOnCacheHit_SuccessfulLogin_RequiresSha256) +MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, SslOffCacheHit_SuccessfulLogin_RequiresSha256) +MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, SslOnCacheMiss_SuccessfulLogin_RequiresSha256) +MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, SslOffCacheMiss_FailedLogin_RequiresSha256) +MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, EmptyPasswordSslOnCacheHit_SuccessfulLogin_RequiresSha256) +MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, EmptyPasswordSslOffCacheHit_SuccessfulLogin_RequiresSha256) +MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, EmptyPasswordSslOnCacheMiss_SuccessfulLogin_RequiresSha256) +MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, EmptyPasswordSslOffCacheMiss_SuccessfulLogin_RequiresSha256) +MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, BadPasswordSslOnCacheMiss_FailedLogin_RequiresSha256) +MYSQL_NETWORK_TEST(CachingSha2HandshakeTest, BadPasswordSslOnCacheHit_FailedLogin_RequiresSha256) -TEST_P(CachingSha2HandshakeTest, SslOnCacheMiss_SuccessfulLogin_RequiresSha256) -{ - set_credentials("csha2p_user", "csha2p_password"); - clear_sha256_cache(); - do_handshake_ok(ssl_mode::require); -} - -TEST_P(CachingSha2HandshakeTest, SslOffCacheMiss_FailedLogin_RequiresSha256) -{ - // A cache miss would force us send a plaintext password over - // a non-TLS connection, so we fail - set_ssl(ssl_mode::disable); - set_credentials("csha2p_user", "csha2p_password"); - clear_sha256_cache(); - auto result = do_handshake(); - result.validate_error(errc::auth_plugin_requires_ssl, {}); -} - -TEST_P(CachingSha2HandshakeTest, EmptyPasswordSslOnCacheHit_SuccessfulLogin_RequiresSha256) -{ - set_credentials("csha2p_empty_password_user", ""); - load_sha256_cache("csha2p_empty_password_user", ""); - do_handshake_ok(ssl_mode::require); -} - -TEST_P(CachingSha2HandshakeTest, EmptyPasswordSslOffCacheHit_SuccessfulLogin_RequiresSha256) -{ - // Empty passwords are allowed over non-TLS connections - set_credentials("csha2p_empty_password_user", ""); - load_sha256_cache("csha2p_empty_password_user", ""); - do_handshake_ok(ssl_mode::disable); -} - -TEST_P(CachingSha2HandshakeTest, EmptyPasswordSslOnCacheMiss_SuccessfulLogin_RequiresSha256) -{ - set_credentials("csha2p_empty_password_user", ""); - clear_sha256_cache(); - do_handshake_ok(ssl_mode::require); -} - -TEST_P(CachingSha2HandshakeTest, EmptyPasswordSslOffCacheMiss_SuccessfulLogin_RequiresSha256) -{ - // Empty passwords are allowed over non-TLS connections - set_credentials("csha2p_empty_password_user", ""); - clear_sha256_cache(); - do_handshake_ok(ssl_mode::disable); -} - -TEST_P(CachingSha2HandshakeTest, BadPasswordSslOnCacheMiss_FailedLogin_RequiresSha256) -{ - set_ssl(ssl_mode::require); // Note: test over non-TLS would return "ssl required" - clear_sha256_cache(); - set_credentials("csha2p_user", "bad_password"); - auto result = do_handshake(); - result.validate_error(errc::access_denied_error, {"access denied", "csha2p_user"}); -} - -TEST_P(CachingSha2HandshakeTest, BadPasswordSslOnCacheHit_FailedLogin_RequiresSha256) -{ - set_ssl(ssl_mode::require); // Note: test over non-TLS would return "ssl required" - set_credentials("csha2p_user", "bad_password"); - load_sha256_cache("csha2p_user", "csha2p_password"); - auto result = do_handshake(); - result.validate_error(errc::access_denied_error, {"access denied", "csha2p_user"}); -} - -INSTANTIATE_TEST_SUITE_P(Default, CachingSha2HandshakeTest, testing::ValuesIn( - all_network_functions -), [](const auto& param_info) { return param_info.param->name(); }); +BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE(CachingSha2HandshakeTest, make_all_network_functions); // Misc tests that are sensitive on SSL -struct MiscSslSensitiveHandshakeTest : SslSensitiveHandshakeTest {}; - -TEST_P(MiscSslSensitiveHandshakeTest, BadUser_FailedLogin) +template +struct MiscSslSensitiveHandshakeTest : SslSensitiveHandshakeTest { - // unreliable without SSL. If the default plugin requires SSL - // (like SHA256), this would fail with 'ssl required' - set_ssl(ssl_mode::require); - set_credentials("non_existing_user", "bad_password"); - auto result = do_handshake(); - result.validate_error(errc::access_denied_error, {"access denied", "non_existing_user"}); -} + 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_error(errc::access_denied_error, {"access denied", "non_existing_user"}); + } -TEST_P(MiscSslSensitiveHandshakeTest, 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. - do_handshake_ok(ssl_mode::enable); -} + 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); + } +}; -INSTANTIATE_TEST_SUITE_P(Default, MiscSslSensitiveHandshakeTest, testing::ValuesIn( - all_network_functions -), [](const auto& param_info) { return param_info.param->name(); }); +BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS(MiscSslSensitiveHandshakeTest); +MYSQL_NETWORK_TEST(MiscSslSensitiveHandshakeTest, BadUser_FailedLogin) +MYSQL_NETWORK_TEST(MiscSslSensitiveHandshakeTest, SslEnable_SuccessfulLogin) + +BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE(MiscSslSensitiveHandshakeTest, make_all_network_functions) } // anon namespace diff --git a/test/integration/integration_test_common.hpp b/test/integration/integration_test_common.hpp index 095c3e74..84b377b3 100644 --- a/test/integration/integration_test_common.hpp +++ b/test/integration/integration_test_common.hpp @@ -17,11 +17,28 @@ namespace boost { namespace mysql { namespace test { +inline void physical_connect(tcp_connection& conn) +{ + boost::asio::ip::tcp::endpoint endpoint (boost::asio::ip::address_v4::loopback(), 3306); + conn.next_layer().connect(endpoint); +} + +#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS +inline void physical_connect(unix_connection& conn) +{ + boost::asio::local::stream_protocol::endpoint endpoint ("/tmp/mysql.sock"); + conn.next_layer().connect(endpoint); +} +#endif + +template struct IntegTest : testing::Test { + using stream_type = Stream; + mysql::connection_params connection_params {"integ_user", "integ_password", "awesome"}; boost::asio::io_context ctx; - mysql::connection conn {ctx}; + mysql::connection conn {ctx}; boost::asio::executor_work_guard guard { ctx.get_executor() }; std::thread runner {[this]{ ctx.run(); } }; @@ -29,8 +46,7 @@ struct IntegTest : testing::Test { try { - boost::asio::ip::tcp::endpoint endpoint (boost::asio::ip::address_v4::loopback(), 3306); - conn.next_layer().connect(endpoint); + physical_connect(conn); } catch (...) // prevent terminate without an active exception on connect error { @@ -81,7 +97,7 @@ struct IntegTest : testing::Test } void validate_eof( - const tcp_resultset& result, + const resultset& result, int affected_rows=0, int warnings=0, int last_insert=0, @@ -108,7 +124,7 @@ struct IntegTest : testing::Test } void validate_2fields_meta( - const tcp_resultset& result, + const resultset& result, const std::string& table ) const { @@ -117,14 +133,16 @@ struct IntegTest : testing::Test }; -struct IntegTestAfterHandshake : IntegTest +template +struct IntegTestAfterHandshake : IntegTest { - IntegTestAfterHandshake(ssl_mode m = ssl_mode::require) { handshake(m); } + IntegTestAfterHandshake(ssl_mode m = ssl_mode::require) { this->handshake(m); } }; +template struct network_testcase { - network_functions* net; + network_functions* net; ssl_mode ssl; std::string name() const @@ -133,33 +151,109 @@ struct network_testcase } }; -inline std::vector make_all_network_testcases() +template +inline std::vector> make_all_network_testcases() { - std::vector res; - for (auto* net: all_network_functions) + std::vector> res; + for (auto* net: make_all_network_functions()) { for (auto ssl: {ssl_mode::require, ssl_mode::disable}) { - res.push_back(network_testcase{net, ssl}); + res.push_back(network_testcase{net, ssl}); } } return res; } -struct NetworkTest : public IntegTestAfterHandshake, - public testing::WithParamInterface +template +struct NetworkTest : public IntegTestAfterHandshake, + public testing::WithParamInterface> { - NetworkTest(): IntegTestAfterHandshake(GetParam().ssl) {} + NetworkTest(): IntegTestAfterHandshake(NetworkTest::GetParam().ssl) {} +}; + +struct universal_name_generator +{ + template >> + std::string operator()(const T& v) const { return v.name(); } + + template + std::string operator()(const T* v) const { return v->name(); } }; } // test } // mysql } // boost +// Old #define MYSQL_NETWORK_TEST_SUITE(TestSuiteName) \ INSTANTIATE_TEST_SUITE_P(Default, TestSuiteName, testing::ValuesIn( \ - make_all_network_testcases() \ + make_all_network_testcases() \ ), [](const auto& param_info) { return param_info.param.name(); }) +// 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) + +#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_UNIX(TestSuiteName) + +// Test definition +#define MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, Suffix) \ + TEST_P(TestSuiteName##Suffix, TestName) { this->TestName(); } + +#define MYSQL_NETWORK_TEST_TCP(TestSuiteName, TestName) \ + MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, TCP) + +#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS +#define MYSQL_NETWORK_TEST_UNIX(TestSuiteName, TestName) \ + MYSQL_NETWORK_TEST_HELPER(TestSuiteName, TestName, UNIX) +#else +#define MYSQL_NETWORK_TEST_UNIX(TestSuiteName, TestName) +#endif + +#define MYSQL_NETWORK_TEST(TestSuiteName, TestName) \ + MYSQL_NETWORK_TEST_TCP(TestSuiteName, TestName) \ + MYSQL_NETWORK_TEST_UNIX(TestSuiteName, TestName) + +// Test suite instantiation +#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, Generator, Suffix) \ + INSTANTIATE_TEST_SUITE_P(Default, TestSuiteName##Suffix, testing::ValuesIn( \ + Generator() \ + ), [](const auto& param_info) { \ + return ::boost::mysql::test::universal_name_generator()(param_info.param); \ + }); + +#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_TCP(TestSuiteName, Generator) \ + BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, Generator, TCP) + +#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS +#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_UNIX(TestSuiteName, Generator) \ + BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_HELPER(TestSuiteName, Generator, UNIX) +#else +#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_UNIX(TestSuiteName, Generator) +#endif + +#define BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE(TestSuiteName, Generator) \ + BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_TCP(TestSuiteName, Generator) \ + BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE_UNIX(TestSuiteName, Generator) + +// Typedefs + Instantiation +#define MYSQL_NETWORK_TEST_SUITE2(TestSuiteName) \ + BOOST_MYSQL_NETWORK_TEST_SUITE_TYPEDEFS(TestSuiteName) \ + BOOST_MYSQL_INSTANTIATE_NETWORK_TEST_SUITE(TestSuiteName, make_all_network_testcases) + + #endif /* TEST_INTEGRATION_INTEGRATION_TEST_COMMON_HPP_ */ diff --git a/test/integration/network_functions.cpp b/test/integration/network_functions.cpp index 6100ddf0..3916b56a 100644 --- a/test/integration/network_functions.cpp +++ b/test/integration/network_functions.cpp @@ -1,12 +1,10 @@ #include "network_functions.hpp" #include #include +#include #include using namespace boost::mysql::test; -using boost::mysql::tcp_prepared_statement; -using boost::mysql::tcp_resultset; -using boost::mysql::tcp_connection; using boost::mysql::error_info; using boost::mysql::error_code; using boost::mysql::detail::make_error_code; @@ -31,7 +29,8 @@ error_code make_initial_error_code() return make_error_code(errc::no); } -class sync_errc : public network_functions +template +class sync_errc : public network_functions { template static auto impl(Callable&& cb) { @@ -41,9 +40,13 @@ class sync_errc : public network_functions return res; } public: + using connection_type = typename network_functions::connection_type; + using prepared_statement_type = typename network_functions::prepared_statement_type; + using resultset_type = typename network_functions::resultset_type; + const char* name() const override { return "sync_errc"; } network_result handshake( - tcp_connection& conn, + connection_type& conn, const boost::mysql::connection_params& params ) override { @@ -52,8 +55,8 @@ public: return no_result(); }); } - network_result query( - tcp_connection& conn, + network_result query( + connection_type& conn, std::string_view query ) override { @@ -61,8 +64,8 @@ public: return conn.query(query, code, info); }); } - network_result prepare_statement( - tcp_connection& conn, + network_result prepare_statement( + connection_type& conn, std::string_view statement ) override { @@ -70,8 +73,8 @@ public: return conn.prepare_statement(statement, err, info); }); } - network_result execute_statement( - tcp_prepared_statement& stmt, + network_result execute_statement( + prepared_statement_type& stmt, value_list_it params_first, value_list_it params_last ) override @@ -80,8 +83,8 @@ public: return stmt.execute(params_first, params_last, err, info); }); } - network_result execute_statement( - tcp_prepared_statement& stmt, + network_result execute_statement( + prepared_statement_type& stmt, const std::vector& values ) override { @@ -90,7 +93,7 @@ public: }); } network_result close_statement( - tcp_prepared_statement& stmt + prepared_statement_type& stmt ) override { return impl([&](error_code& code, error_info& info) { @@ -99,7 +102,7 @@ public: }); } network_result fetch_one( - tcp_resultset& r + resultset_type& r ) override { return impl([&](error_code& code, error_info& info) { @@ -107,7 +110,7 @@ public: }); } network_result> fetch_many( - tcp_resultset& r, + resultset_type& r, std::size_t count ) override { @@ -116,7 +119,7 @@ public: }); } network_result> fetch_all( - tcp_resultset& r + resultset_type& r ) override { return impl([&](error_code& code, error_info& info) { @@ -125,7 +128,8 @@ public: } }; -class sync_exc : public network_functions +template +class sync_exc : public network_functions { template static auto impl(Callable&& cb) { @@ -143,9 +147,13 @@ class sync_exc : public network_functions return res; } public: + using connection_type = typename network_functions::connection_type; + using prepared_statement_type = typename network_functions::prepared_statement_type; + using resultset_type = typename network_functions::resultset_type; + const char* name() const override { return "sync_exc"; } network_result handshake( - tcp_connection& conn, + connection_type& conn, const boost::mysql::connection_params& params ) override { @@ -154,8 +162,8 @@ public: return no_result(); }); } - network_result query( - tcp_connection& conn, + network_result query( + connection_type& conn, std::string_view query ) override { @@ -163,8 +171,8 @@ public: return conn.query(query); }); } - network_result prepare_statement( - tcp_connection& conn, + network_result prepare_statement( + connection_type& conn, std::string_view statement ) override { @@ -172,8 +180,8 @@ public: return conn.prepare_statement(statement); }); } - network_result execute_statement( - tcp_prepared_statement& stmt, + network_result execute_statement( + prepared_statement_type& stmt, value_list_it params_first, value_list_it params_last ) override @@ -182,8 +190,8 @@ public: return stmt.execute(params_first, params_last); }); } - network_result execute_statement( - tcp_prepared_statement& stmt, + network_result execute_statement( + prepared_statement_type& stmt, const std::vector& values ) override { @@ -192,7 +200,7 @@ public: }); } network_result close_statement( - tcp_prepared_statement& stmt + prepared_statement_type& stmt ) override { return impl([&] { @@ -201,7 +209,7 @@ public: }); } network_result fetch_one( - tcp_resultset& r + resultset_type& r ) override { return impl([&] { @@ -209,7 +217,7 @@ public: }); } network_result> fetch_many( - tcp_resultset& r, + resultset_type& r, std::size_t count ) override { @@ -218,7 +226,7 @@ public: }); } network_result> fetch_all( - tcp_resultset& r + resultset_type& r ) override { return impl([&] { @@ -227,7 +235,8 @@ public: } }; -class async_callback : public network_functions +template +class async_callback : public network_functions { // This allows for two versions of the tests: one where we pass a // non-nullptr error_info* to the initiating function, and another @@ -277,13 +286,17 @@ class async_callback : public network_functions } public: + using connection_type = typename network_functions::connection_type; + using prepared_statement_type = typename network_functions::prepared_statement_type; + using resultset_type = typename network_functions::resultset_type; + async_callback(bool use_errinfo): use_errinfo_(use_errinfo) {} const char* name() const override { return use_errinfo_ ? "async_callback_errinfo" : "async_callback_noerrinfo"; } network_result handshake( - tcp_connection& conn, + connection_type& conn, const boost::mysql::connection_params& params ) override { @@ -291,45 +304,45 @@ public: return conn.async_handshake(params, std::forward(token), info); }); } - network_result query( - tcp_connection& conn, + network_result query( + connection_type& conn, std::string_view query ) override { - return impl([&](auto&& token, error_info* info) { + return impl([&](auto&& token, error_info* info) { return conn.async_query(query, std::forward(token), info); }); } - network_result prepare_statement( - tcp_connection& conn, + network_result prepare_statement( + connection_type& conn, std::string_view statement ) override { - return impl([&conn, statement](auto&& token, error_info* info) { + return impl([&conn, statement](auto&& token, error_info* info) { return conn.async_prepare_statement(statement, std::forward(token), info); }); } - network_result execute_statement( - tcp_prepared_statement& stmt, + network_result execute_statement( + prepared_statement_type& stmt, value_list_it params_first, value_list_it params_last ) override { - return impl([&](auto&& token, error_info* info) { + return impl([&](auto&& token, error_info* info) { return stmt.async_execute(params_first, params_last, std::forward(token), info); }); } - network_result execute_statement( - tcp_prepared_statement& stmt, + network_result execute_statement( + prepared_statement_type& stmt, const std::vector& values ) override { - return impl([&](auto&& token, error_info* info) { + return impl([&](auto&& token, error_info* info) { return stmt.async_execute(values, std::forward(token), info); }); } network_result close_statement( - tcp_prepared_statement& stmt + prepared_statement_type& stmt ) override { return impl([&](auto&& token, error_info* info) { @@ -337,7 +350,7 @@ public: }); } network_result fetch_one( - tcp_resultset& r + resultset_type& r ) override { return impl([&](auto&& token, error_info* info) { @@ -345,7 +358,7 @@ public: }); } network_result> fetch_many( - tcp_resultset& r, + resultset_type& r, std::size_t count ) override { @@ -354,7 +367,7 @@ public: }); } network_result> fetch_all( - tcp_resultset& r + resultset_type& r ) override { return impl>([&](auto&& token, error_info* info) { @@ -363,7 +376,8 @@ public: } }; -class async_coroutine : public network_functions +template +class async_coroutine : public network_functions { bool use_errinfo_; @@ -394,13 +408,17 @@ class async_coroutine : public network_functions } public: + using connection_type = typename network_functions::connection_type; + using prepared_statement_type = typename network_functions::prepared_statement_type; + using resultset_type = typename network_functions::resultset_type; + async_coroutine(bool use_errinfo): use_errinfo_(use_errinfo) {} const char* name() const override { return use_errinfo_ ? "async_coroutine_errinfo" : "async_coroutine_noerrinfo"; } network_result handshake( - tcp_connection& conn, + connection_type& conn, const boost::mysql::connection_params& params ) override { @@ -409,8 +427,8 @@ public: return no_result(); }); } - network_result query( - tcp_connection& conn, + network_result query( + connection_type& conn, std::string_view query ) override { @@ -418,8 +436,8 @@ public: return conn.async_query(query, yield, info); }); } - network_result prepare_statement( - tcp_connection& conn, + network_result prepare_statement( + connection_type& conn, std::string_view statement ) override { @@ -427,8 +445,8 @@ public: return conn.async_prepare_statement(statement, yield, info); }); } - network_result execute_statement( - tcp_prepared_statement& stmt, + network_result execute_statement( + prepared_statement_type& stmt, value_list_it params_first, value_list_it params_last ) override @@ -437,8 +455,8 @@ public: return stmt.async_execute(params_first, params_last, yield, info); }); } - network_result execute_statement( - tcp_prepared_statement& stmt, + network_result execute_statement( + prepared_statement_type& stmt, const std::vector& values ) override { @@ -447,7 +465,7 @@ public: }); } network_result close_statement( - tcp_prepared_statement& stmt + prepared_statement_type& stmt ) override { return impl(stmt, [&](yield_context yield, error_info* info) { @@ -456,7 +474,7 @@ public: }); } network_result fetch_one( - tcp_resultset& r + resultset_type& r ) override { return impl(r, [&](yield_context yield, error_info* info) { @@ -464,7 +482,7 @@ public: }); } network_result> fetch_many( - tcp_resultset& r, + resultset_type& r, std::size_t count ) override { @@ -473,7 +491,7 @@ public: }); } network_result> fetch_all( - tcp_resultset& r + resultset_type& r ) override { return impl(r, [&](yield_context yield, error_info* info) { @@ -482,8 +500,8 @@ public: } }; - -class async_future : public network_functions +template +class async_future : public network_functions { template static auto impl(Callable&& cb) { @@ -520,9 +538,13 @@ class async_future : public network_functions } } public: + using connection_type = typename network_functions::connection_type; + using prepared_statement_type = typename network_functions::prepared_statement_type; + using resultset_type = typename network_functions::resultset_type; + const char* name() const override { return "async_future_noerrinfo"; } network_result handshake( - tcp_connection& conn, + connection_type& conn, const boost::mysql::connection_params& params ) override { @@ -530,8 +552,8 @@ public: return conn.async_handshake(params, use_future); }); } - network_result query( - tcp_connection& conn, + network_result query( + connection_type& conn, std::string_view query ) override { @@ -539,8 +561,8 @@ public: return conn.async_query(query, use_future); }); } - network_result prepare_statement( - tcp_connection& conn, + network_result prepare_statement( + connection_type& conn, std::string_view statement ) override { @@ -548,8 +570,8 @@ public: return conn.async_prepare_statement(statement, use_future); }); } - network_result execute_statement( - tcp_prepared_statement& stmt, + network_result execute_statement( + prepared_statement_type& stmt, value_list_it params_first, value_list_it params_last ) override @@ -558,8 +580,8 @@ public: return stmt.async_execute(params_first, params_last, use_future); }); } - network_result execute_statement( - tcp_prepared_statement& stmt, + network_result execute_statement( + prepared_statement_type& stmt, const std::vector& values ) override { @@ -568,7 +590,7 @@ public: }); } network_result close_statement( - tcp_prepared_statement& stmt + prepared_statement_type& stmt ) override { return impl_no_result([&] { @@ -576,7 +598,7 @@ public: }); } network_result fetch_one( - tcp_resultset& r + resultset_type& r ) override { return impl([&] { @@ -584,7 +606,7 @@ public: }); } network_result> fetch_many( - tcp_resultset& r, + resultset_type& r, std::size_t count ) override { @@ -593,7 +615,7 @@ public: }); } network_result> fetch_all( - tcp_resultset& r + resultset_type& r ) override { return impl([&] { @@ -603,21 +625,37 @@ public: }; // Global objects to be exposed -sync_errc sync_errc_obj; -sync_exc sync_exc_obj; -async_callback async_callback_errinfo_obj (true); -async_callback async_callback_noerrinfo_obj (false); -async_coroutine async_coroutine_errinfo_obj (true); -async_coroutine async_coroutine_noerrinfo_obj (false); -async_future async_future_obj; +template sync_errc sync_errc_obj; +template sync_exc sync_exc_obj; +template async_callback async_callback_errinfo_obj (true); +template async_callback async_callback_noerrinfo_obj (false); +template async_coroutine async_coroutine_errinfo_obj (true); +template async_coroutine async_coroutine_noerrinfo_obj (false); +template async_future async_future_obj; + } // Visible stuff -network_functions* boost::mysql::test::sync_errc_network_functions = &sync_errc_obj; -network_functions* boost::mysql::test::sync_exc_network_functions = &sync_exc_obj; -network_functions* boost::mysql::test::async_callback_errinfo_network_functions = &async_callback_errinfo_obj; -network_functions* boost::mysql::test::async_callback_noerrinfo_network_functions = &async_callback_noerrinfo_obj; -network_functions* boost::mysql::test::async_coroutine_errinfo_network_functions = &async_coroutine_errinfo_obj; -network_functions* boost::mysql::test::async_coroutine_noerrinfo_network_functions = &async_coroutine_noerrinfo_obj; -network_functions* boost::mysql::test::async_future_noerrinfo_network_functions = &async_future_obj; +template +boost::mysql::test::network_function_array +boost::mysql::test::make_all_network_functions() +{ + return { + &sync_errc_obj, + &sync_exc_obj, + &async_callback_errinfo_obj, + &async_callback_noerrinfo_obj, + &async_coroutine_errinfo_obj, + &async_coroutine_noerrinfo_obj, + &async_future_obj + }; +} + +template boost::mysql::test::network_function_array +boost::mysql::test::make_all_network_functions(); + +#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS +template boost::mysql::test::network_function_array +boost::mysql::test::make_all_network_functions(); +#endif diff --git a/test/integration/network_functions.hpp b/test/integration/network_functions.hpp index 819dd770..46819952 100644 --- a/test/integration/network_functions.hpp +++ b/test/integration/network_functions.hpp @@ -1,4 +1,4 @@ -#ifndef TEST_INTEGRATION_NETWORK_FUNCTIONS_HPP_ + #ifndef TEST_INTEGRATION_NETWORK_FUNCTIONS_HPP_ #define TEST_INTEGRATION_NETWORK_FUNCTIONS_HPP_ #include "boost/mysql/connection.hpp" @@ -60,43 +60,35 @@ struct network_result using value_list_it = std::forward_list::const_iterator; +template class network_functions { public: + using connection_type = connection; + using prepared_statement_type = prepared_statement; + using resultset_type = resultset; + virtual ~network_functions() = default; virtual const char* name() const = 0; - virtual network_result handshake(tcp_connection&, const connection_params&) = 0; - virtual network_result query(tcp_connection&, std::string_view query) = 0; - virtual network_result prepare_statement( - tcp_connection&, std::string_view statement) = 0; - virtual network_result execute_statement( - tcp_prepared_statement&, value_list_it params_first, value_list_it params_last) = 0; - virtual network_result execute_statement( - tcp_prepared_statement&, const std::vector&) = 0; - virtual network_result close_statement(tcp_prepared_statement&) = 0; - virtual network_result fetch_one(tcp_resultset&) = 0; - virtual network_result> fetch_many(tcp_resultset&, std::size_t count) = 0; - virtual network_result> fetch_all(tcp_resultset&) = 0; + virtual network_result handshake(connection_type&, const connection_params&) = 0; + virtual network_result query(connection_type&, std::string_view query) = 0; + virtual network_result prepare_statement( + connection_type&, std::string_view statement) = 0; + virtual network_result execute_statement( + prepared_statement_type&, value_list_it params_first, value_list_it params_last) = 0; + virtual network_result execute_statement( + prepared_statement_type&, const std::vector&) = 0; + virtual network_result close_statement(prepared_statement_type&) = 0; + virtual network_result fetch_one(resultset_type&) = 0; + virtual network_result> fetch_many(resultset_type&, std::size_t count) = 0; + virtual network_result> fetch_all(resultset_type&) = 0; }; -extern network_functions* sync_errc_network_functions; -extern network_functions* sync_exc_network_functions; -extern network_functions* async_callback_errinfo_network_functions; -extern network_functions* async_callback_noerrinfo_network_functions; -extern network_functions* async_coroutine_errinfo_network_functions; -extern network_functions* async_coroutine_noerrinfo_network_functions; -extern network_functions* async_future_noerrinfo_network_functions; - -inline network_functions* all_network_functions [] = { - sync_errc_network_functions, - sync_exc_network_functions, - async_callback_errinfo_network_functions, - async_callback_noerrinfo_network_functions, - async_coroutine_errinfo_network_functions, - async_coroutine_noerrinfo_network_functions, - async_future_noerrinfo_network_functions -}; +template +using network_function_array = std::array*, 7>; +template +network_function_array make_all_network_functions(); } // test } // mysql diff --git a/test/integration/prepare_statement.cpp b/test/integration/prepare_statement.cpp index 8a8db28b..91d4498f 100644 --- a/test/integration/prepare_statement.cpp +++ b/test/integration/prepare_statement.cpp @@ -11,41 +11,51 @@ using namespace boost::mysql::test; using boost::mysql::error_code; using boost::mysql::error_info; using boost::mysql::errc; -using boost::mysql::tcp_prepared_statement; -using boost::mysql::tcp_connection; +using boost::mysql::prepared_statement; +using boost::mysql::connection; namespace { -struct PrepareStatementTest : public NetworkTest +template +struct PrepareStatementTest : public NetworkTest { + auto do_prepare(std::string_view stmt) + { + return this->GetParam().net->prepare_statement(this->conn, stmt); + } + + void OkNoParams() + { + auto stmt = do_prepare("SELECT * FROM empty_table"); + stmt.validate_no_error(); + ASSERT_TRUE(stmt.value.valid()); + EXPECT_GT(stmt.value.id(), 0u); + EXPECT_EQ(stmt.value.num_params(), 0); + } + + void OkWithParams() + { + auto stmt = do_prepare("SELECT * FROM empty_table WHERE id IN (?, ?)"); + stmt.validate_no_error(); + ASSERT_TRUE(stmt.value.valid()); + EXPECT_GT(stmt.value.id(), 0u); + EXPECT_EQ(stmt.value.num_params(), 2); + } + + void InvalidStatement() + { + auto stmt = do_prepare("SELECT * FROM bad_table WHERE id IN (?, ?)"); + stmt.validate_error(errc::no_such_table, {"table", "doesn't exist", "bad_table"}); + EXPECT_FALSE(stmt.value.valid()); + } }; -TEST_P(PrepareStatementTest, OkNoParams) -{ - auto stmt = GetParam().net->prepare_statement(conn, "SELECT * FROM empty_table"); - stmt.validate_no_error(); - ASSERT_TRUE(stmt.value.valid()); - EXPECT_GT(stmt.value.id(), 0u); - EXPECT_EQ(stmt.value.num_params(), 0); -} +MYSQL_NETWORK_TEST_SUITE2(PrepareStatementTest); -TEST_P(PrepareStatementTest, OkWithParams) -{ - auto stmt = GetParam().net->prepare_statement(conn, "SELECT * FROM empty_table WHERE id IN (?, ?)"); - stmt.validate_no_error(); - ASSERT_TRUE(stmt.value.valid()); - EXPECT_GT(stmt.value.id(), 0u); - EXPECT_EQ(stmt.value.num_params(), 2); -} +MYSQL_NETWORK_TEST(PrepareStatementTest, OkNoParams) +MYSQL_NETWORK_TEST(PrepareStatementTest, OkWithParams) +MYSQL_NETWORK_TEST(PrepareStatementTest, InvalidStatement) -TEST_P(PrepareStatementTest, Error) -{ - auto stmt = GetParam().net->prepare_statement(conn, "SELECT * FROM bad_table WHERE id IN (?, ?)"); - stmt.validate_error(errc::no_such_table, {"table", "doesn't exist", "bad_table"}); - EXPECT_FALSE(stmt.value.valid()); -} - -MYSQL_NETWORK_TEST_SUITE(PrepareStatementTest); } diff --git a/test/integration/prepared_statement_lifecycle.cpp b/test/integration/prepared_statement_lifecycle.cpp index 9f0f7bdc..631c117e 100644 --- a/test/integration/prepared_statement_lifecycle.cpp +++ b/test/integration/prepared_statement_lifecycle.cpp @@ -1,4 +1,4 @@ -/* + /* * prepared_statement_lifecycle.cpp * * Created on: Feb 18, 2020 @@ -14,224 +14,232 @@ using boost::mysql::value; namespace { -struct PreparedStatementLifecycleTest : NetworkTest +template +struct PreparedStatementLifecycleTest : NetworkTest { std::int64_t get_table_size(const std::string& table) { return std::get( - conn.query("SELECT COUNT(*) FROM " + table).fetch_all().at(0).values().at(0)); + this->conn.query("SELECT COUNT(*) FROM " + table).fetch_all().at(0).values().at(0)); } value get_updates_table_value(const std::string& field_varchar="f0") { - return conn.query("SELECT field_int FROM updates_table WHERE field_varchar = '" + field_varchar + "'") + return this->conn.query("SELECT field_int FROM updates_table WHERE field_varchar = '" + field_varchar + "'") .fetch_all().at(0).values().at(0); } + + void SelectWithParametersMultipleExecutions() + { + auto* net = this->GetParam().net; + + // Prepare a statement + auto stmt = net->prepare_statement( + this->conn, + "SELECT * FROM two_rows_table WHERE id = ? OR field_varchar = ?" + ); + stmt.validate_no_error(); + + // Execute it. Only one row will be returned (because of the id) + auto result = net->execute_statement(stmt.value, makevalues(1, "non_existent")); + result.validate_no_error(); + EXPECT_TRUE(result.value.valid()); + EXPECT_FALSE(result.value.complete()); + this->validate_2fields_meta(result.value, "two_rows_table"); + + auto rows = net->fetch_all(result.value); + rows.validate_no_error(); + ASSERT_EQ(rows.value.size(), 1); + EXPECT_EQ(static_cast(rows.value[0]), (makerow(1, "f0"))); + EXPECT_TRUE(result.value.complete()); + + // Execute it again, but with different values. This time, two rows are returned + result = net->execute_statement(stmt.value, makevalues(1, "f1")); + result.validate_no_error(); + EXPECT_TRUE(result.value.valid()); + EXPECT_FALSE(result.value.complete()); + this->validate_2fields_meta(result.value, "two_rows_table"); + + rows = net->fetch_all(result.value); + rows.validate_no_error(); + ASSERT_EQ(rows.value.size(), 2); + EXPECT_EQ(static_cast(rows.value[0]), (makerow(1, "f0"))); + EXPECT_EQ(static_cast(rows.value[1]), (makerow(2, "f1"))); + EXPECT_TRUE(result.value.complete()); + + // Close it + auto close_result = net->close_statement(stmt.value); + close_result.validate_no_error(); + } + + void InsertWithParametersMultipleExecutions() + { + auto* net = this->GetParam().net; + + // Get the number of rows before insertion + auto rows_before = get_table_size("inserts_table"); + + // Prepare a statement + auto stmt = net->prepare_statement( + this->conn, + "INSERT INTO inserts_table (field_varchar) VALUES (?)" + ); + stmt.validate_no_error(); + + // Insert one value + auto result = net->execute_statement(stmt.value, makevalues("value0")); + result.validate_no_error(); + EXPECT_TRUE(result.value.valid()); + EXPECT_TRUE(result.value.complete()); + EXPECT_TRUE(result.value.fields().empty()); + + // Insert another one + result = net->execute_statement(stmt.value, makevalues("value1")); + result.validate_no_error(); + EXPECT_TRUE(result.value.valid()); + EXPECT_TRUE(result.value.complete()); + EXPECT_TRUE(result.value.fields().empty()); + + // Validate we did something + auto rows_after = get_table_size("inserts_table"); + EXPECT_EQ(rows_after, rows_before + 2); + + // Close it + auto close_result = net->close_statement(stmt.value); + close_result.validate_no_error(); + } + + void UpdateWithParametersMultipleExecutions() + { + auto* net = this->GetParam().net; + + // Prepare a statement + auto stmt = net->prepare_statement( + this->conn, + "UPDATE updates_table SET field_int = ? WHERE field_varchar = ?" + ); + stmt.validate_no_error(); + + // Set field_int to something + auto result = net->execute_statement(stmt.value, makevalues(200, "f0")); + result.validate_no_error(); + EXPECT_TRUE(result.value.valid()); + EXPECT_TRUE(result.value.complete()); + EXPECT_TRUE(result.value.fields().empty()); + + // Verify that took effect + EXPECT_EQ(get_updates_table_value(), value(std::int32_t(200))); + + // Set field_int to something different + result = net->execute_statement(stmt.value, makevalues(250, "f0")); + result.validate_no_error(); + EXPECT_TRUE(result.value.valid()); + EXPECT_TRUE(result.value.complete()); + EXPECT_TRUE(result.value.fields().empty()); + + // Verify that took effect + EXPECT_EQ(get_updates_table_value(), value(std::int32_t(250))); + + // Close the statement + auto close_result = net->close_statement(stmt.value); + close_result.validate_no_error(); + } + + void MultipleStatements() + { + auto* net = this->GetParam().net; + + // Prepare an update + auto stmt_update = net->prepare_statement( + this->conn, + "UPDATE updates_table SET field_int = ? WHERE field_varchar = ?" + ); + stmt_update.validate_no_error(); + + // Prepare a select + auto stmt_select = net->prepare_statement( + this->conn, + "SELECT field_int FROM updates_table WHERE field_varchar = ?" + ); + stmt_select.validate_no_error(); + + // They have different IDs + EXPECT_NE(stmt_update.value.id(), stmt_select.value.id()); + + // Execute update + auto update_result = net->execute_statement(stmt_update.value, makevalues(210, "f0")); + update_result.validate_no_error(); + EXPECT_TRUE(update_result.value.complete()); + + // Execute select + auto select_result = net->execute_statement(stmt_select.value, makevalues("f0")); + select_result.validate_no_error(); + auto rows = net->fetch_all(select_result.value); + rows.validate_no_error(); + EXPECT_EQ(rows.value.size(), 1); + EXPECT_EQ(static_cast(rows.value[0]), makerow(210)); + + // Execute update again + update_result = net->execute_statement(stmt_update.value, makevalues(220, "f0")); + update_result.validate_no_error(); + EXPECT_TRUE(update_result.value.complete()); + + // Update no longer needed, close it + auto close_result = net->close_statement(stmt_update.value); + close_result.validate_no_error(); + + // Execute select again + select_result = net->execute_statement(stmt_select.value, makevalues("f0")); + select_result.validate_no_error(); + rows = net->fetch_all(select_result.value); + rows.validate_no_error(); + EXPECT_EQ(rows.value.size(), 1); + EXPECT_EQ(static_cast(rows.value[0]), makerow(220)); + + // Close select + close_result = net->close_statement(stmt_select.value); + close_result.validate_no_error(); + } + + void InsertWithNullValues() + { + auto* net = this->GetParam().net; + + // Statement to perform the updates + auto stmt = net->prepare_statement( + this->conn, + "UPDATE updates_table SET field_int = ? WHERE field_varchar = 'fnull'" + ); + stmt.validate_no_error(); + + // Set the value we will be updating to something non-NULL + auto result = net->execute_statement(stmt.value, makevalues(42)); + result.validate_no_error(); + + // Verify it took effect + ASSERT_EQ(get_updates_table_value("fnull"), value(std::int32_t(42))); + + // Update the value to NULL + result = net->execute_statement(stmt.value, makevalues(nullptr)); + result.validate_no_error(); + + // Verify it took effect + ASSERT_EQ(get_updates_table_value("fnull"), value(nullptr)); + + // Close statement + auto close_result = net->close_statement(stmt.value); + close_result.validate_no_error(); + } }; -TEST_P(PreparedStatementLifecycleTest, SelectWithParametersMultipleExecutions) -{ - auto* net = GetParam().net; +MYSQL_NETWORK_TEST_SUITE2(PreparedStatementLifecycleTest); - // Prepare a statement - auto stmt = net->prepare_statement( - conn, - "SELECT * FROM two_rows_table WHERE id = ? OR field_varchar = ?" - ); - stmt.validate_no_error(); +MYSQL_NETWORK_TEST(PreparedStatementLifecycleTest, SelectWithParametersMultipleExecutions) +MYSQL_NETWORK_TEST(PreparedStatementLifecycleTest, InsertWithParametersMultipleExecutions) +MYSQL_NETWORK_TEST(PreparedStatementLifecycleTest, UpdateWithParametersMultipleExecutions) +MYSQL_NETWORK_TEST(PreparedStatementLifecycleTest, MultipleStatements) +MYSQL_NETWORK_TEST(PreparedStatementLifecycleTest, InsertWithNullValues) - // Execute it. Only one row will be returned (because of the id) - auto result = net->execute_statement(stmt.value, makevalues(1, "non_existent")); - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); - EXPECT_FALSE(result.value.complete()); - validate_2fields_meta(result.value, "two_rows_table"); - - auto rows = net->fetch_all(result.value); - rows.validate_no_error(); - ASSERT_EQ(rows.value.size(), 1); - EXPECT_EQ(static_cast(rows.value[0]), (makerow(1, "f0"))); - EXPECT_TRUE(result.value.complete()); - - // Execute it again, but with different values. This time, two rows are returned - result = net->execute_statement(stmt.value, makevalues(1, "f1")); - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); - EXPECT_FALSE(result.value.complete()); - validate_2fields_meta(result.value, "two_rows_table"); - - rows = net->fetch_all(result.value); - rows.validate_no_error(); - ASSERT_EQ(rows.value.size(), 2); - EXPECT_EQ(static_cast(rows.value[0]), (makerow(1, "f0"))); - EXPECT_EQ(static_cast(rows.value[1]), (makerow(2, "f1"))); - EXPECT_TRUE(result.value.complete()); - - // Close it - auto close_result = net->close_statement(stmt.value); - close_result.validate_no_error(); -} - -TEST_P(PreparedStatementLifecycleTest, InsertWithParametersMultipleExecutions) -{ - auto* net = GetParam().net; - - // Get the number of rows before insertion - auto rows_before = get_table_size("inserts_table"); - - // Prepare a statement - auto stmt = net->prepare_statement( - conn, - "INSERT INTO inserts_table (field_varchar) VALUES (?)" - ); - stmt.validate_no_error(); - - // Insert one value - auto result = net->execute_statement(stmt.value, makevalues("value0")); - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); - EXPECT_TRUE(result.value.complete()); - EXPECT_TRUE(result.value.fields().empty()); - - // Insert another one - result = net->execute_statement(stmt.value, makevalues("value1")); - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); - EXPECT_TRUE(result.value.complete()); - EXPECT_TRUE(result.value.fields().empty()); - - // Validate we did something - auto rows_after = get_table_size("inserts_table"); - EXPECT_EQ(rows_after, rows_before + 2); - - // Close it - auto close_result = net->close_statement(stmt.value); - close_result.validate_no_error(); -} - -TEST_P(PreparedStatementLifecycleTest, UpdateWithParametersMultipleExecutions) -{ - auto* net = GetParam().net; - - // Prepare a statement - auto stmt = net->prepare_statement( - conn, - "UPDATE updates_table SET field_int = ? WHERE field_varchar = ?" - ); - stmt.validate_no_error(); - - // Set field_int to something - auto result = net->execute_statement(stmt.value, makevalues(200, "f0")); - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); - EXPECT_TRUE(result.value.complete()); - EXPECT_TRUE(result.value.fields().empty()); - - // Verify that took effect - EXPECT_EQ(get_updates_table_value(), value(std::int32_t(200))); - - // Set field_int to something different - result = net->execute_statement(stmt.value, makevalues(250, "f0")); - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); - EXPECT_TRUE(result.value.complete()); - EXPECT_TRUE(result.value.fields().empty()); - - // Verify that took effect - EXPECT_EQ(get_updates_table_value(), value(std::int32_t(250))); - - // Close the statement - auto close_result = net->close_statement(stmt.value); - close_result.validate_no_error(); -} - -TEST_P(PreparedStatementLifecycleTest, MultipleStatements) -{ - auto* net = GetParam().net; - - // Prepare an update - auto stmt_update = net->prepare_statement( - conn, - "UPDATE updates_table SET field_int = ? WHERE field_varchar = ?" - ); - stmt_update.validate_no_error(); - - // Prepare a select - auto stmt_select = net->prepare_statement( - conn, - "SELECT field_int FROM updates_table WHERE field_varchar = ?" - ); - stmt_select.validate_no_error(); - - // They have different IDs - EXPECT_NE(stmt_update.value.id(), stmt_select.value.id()); - - // Execute update - auto update_result = net->execute_statement(stmt_update.value, makevalues(210, "f0")); - update_result.validate_no_error(); - EXPECT_TRUE(update_result.value.complete()); - - // Execute select - auto select_result = net->execute_statement(stmt_select.value, makevalues("f0")); - select_result.validate_no_error(); - auto rows = net->fetch_all(select_result.value); - rows.validate_no_error(); - EXPECT_EQ(rows.value.size(), 1); - EXPECT_EQ(static_cast(rows.value[0]), makerow(210)); - - // Execute update again - update_result = net->execute_statement(stmt_update.value, makevalues(220, "f0")); - update_result.validate_no_error(); - EXPECT_TRUE(update_result.value.complete()); - - // Update no longer needed, close it - auto close_result = net->close_statement(stmt_update.value); - close_result.validate_no_error(); - - // Execute select again - select_result = net->execute_statement(stmt_select.value, makevalues("f0")); - select_result.validate_no_error(); - rows = net->fetch_all(select_result.value); - rows.validate_no_error(); - EXPECT_EQ(rows.value.size(), 1); - EXPECT_EQ(static_cast(rows.value[0]), makerow(220)); - - // Close select - close_result = net->close_statement(stmt_select.value); - close_result.validate_no_error(); -} - -TEST_P(PreparedStatementLifecycleTest, InsertWithNullValues) -{ - auto* net = GetParam().net; - - // Statement to perform the updates - auto stmt = net->prepare_statement( - conn, - "UPDATE updates_table SET field_int = ? WHERE field_varchar = 'fnull'" - ); - stmt.validate_no_error(); - - // Set the value we will be updating to something non-NULL - auto result = net->execute_statement(stmt.value, makevalues(42)); - result.validate_no_error(); - - // Verify it took effect - ASSERT_EQ(get_updates_table_value("fnull"), value(std::int32_t(42))); - - // Update the value to NULL - result = net->execute_statement(stmt.value, makevalues(nullptr)); - result.validate_no_error(); - - // Verify it took effect - ASSERT_EQ(get_updates_table_value("fnull"), value(nullptr)); - - // Close statement - auto close_result = net->close_statement(stmt.value); - close_result.validate_no_error(); -} - -MYSQL_NETWORK_TEST_SUITE(PreparedStatementLifecycleTest); } diff --git a/test/integration/query.cpp b/test/integration/query.cpp index 93c3f5ea..5fd4d72f 100644 --- a/test/integration/query.cpp +++ b/test/integration/query.cpp @@ -23,74 +23,85 @@ using boost::mysql::errc; namespace { -struct QueryTest : public NetworkTest +template +struct QueryTest : public NetworkTest { - auto do_query(std::string_view sql) { return GetParam().net->query(conn, sql); } + auto do_query(std::string_view sql) + { + return this->GetParam().net->query(this->conn, sql); + } + + void InsertQueryOk() + { + const char* sql = "INSERT INTO inserts_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')"; + auto result = do_query(sql); + result.validate_no_error(); + EXPECT_TRUE(result.value.fields().empty()); + EXPECT_TRUE(result.value.valid()); + EXPECT_TRUE(result.value.complete()); + EXPECT_EQ(result.value.affected_rows(), 1); + EXPECT_EQ(result.value.warning_count(), 0); + EXPECT_GT(result.value.last_insert_id(), 0); + EXPECT_EQ(result.value.info(), ""); + } + + void InsertQueryFailed() + { + const char* sql = "INSERT INTO bad_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')"; + auto result = do_query(sql); + result.validate_error(errc::no_such_table, {"table", "doesn't exist", "bad_table"}); + EXPECT_FALSE(result.value.valid()); + } + + void UpdateQueryOk() + { + const char* sql = "UPDATE updates_table SET field_int = field_int+1"; + auto result = do_query(sql); + result.validate_no_error(); + EXPECT_TRUE(result.value.fields().empty()); + EXPECT_TRUE(result.value.valid()); + EXPECT_TRUE(result.value.complete()); + EXPECT_EQ(result.value.affected_rows(), 2); + EXPECT_EQ(result.value.warning_count(), 0); + EXPECT_EQ(result.value.last_insert_id(), 0); + EXPECT_THAT(std::string(result.value.info()), HasSubstr("Rows matched")); + } + + void SelectOk() + { + auto result = do_query("SELECT * FROM empty_table"); + result.validate_no_error(); + EXPECT_TRUE(result.value.valid()); + EXPECT_FALSE(result.value.complete()); + this->validate_2fields_meta(result.value, "empty_table"); + } + + void SelectQueryFailed() + { + auto result = do_query("SELECT field_varchar, field_bad FROM one_row_table"); + result.validate_error(errc::bad_field_error, {"unknown column", "field_bad"}); + EXPECT_FALSE(result.value.valid()); + } + + // Some system-level query tests (TODO: this does not feel right here) + void QueryAndFetch_AliasedTableAndField_MetadataCorrect() + { + auto result = do_query("SELECT field_varchar AS field_alias FROM empty_table table_alias"); + meta_validator validator ("table_alias", "empty_table", "field_alias", + "field_varchar", field_type::varchar); + result.validate_no_error(); + validate_meta(result.value.fields(), {validator}); + } }; -TEST_P(QueryTest, InsertQueryOk) -{ - const char* sql = "INSERT INTO inserts_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')"; - auto result = do_query(sql); - result.validate_no_error(); - EXPECT_TRUE(result.value.fields().empty()); - EXPECT_TRUE(result.value.valid()); - EXPECT_TRUE(result.value.complete()); - EXPECT_EQ(result.value.affected_rows(), 1); - EXPECT_EQ(result.value.warning_count(), 0); - EXPECT_GT(result.value.last_insert_id(), 0); - EXPECT_EQ(result.value.info(), ""); -} +MYSQL_NETWORK_TEST_SUITE2(QueryTest); -TEST_P(QueryTest, InsertQueryFailed) -{ - const char* sql = "INSERT INTO bad_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')"; - auto result = do_query(sql); - result.validate_error(errc::no_such_table, {"table", "doesn't exist", "bad_table"}); - EXPECT_FALSE(result.value.valid()); -} +MYSQL_NETWORK_TEST(QueryTest, InsertQueryOk) +MYSQL_NETWORK_TEST(QueryTest, InsertQueryFailed) +MYSQL_NETWORK_TEST(QueryTest, UpdateQueryOk) +MYSQL_NETWORK_TEST(QueryTest, SelectOk) +MYSQL_NETWORK_TEST(QueryTest, SelectQueryFailed) +MYSQL_NETWORK_TEST(QueryTest, QueryAndFetch_AliasedTableAndField_MetadataCorrect) -TEST_P(QueryTest, UpdateQueryOk) -{ - const char* sql = "UPDATE updates_table SET field_int = field_int+1"; - auto result = do_query(sql); - result.validate_no_error(); - EXPECT_TRUE(result.value.fields().empty()); - EXPECT_TRUE(result.value.valid()); - EXPECT_TRUE(result.value.complete()); - EXPECT_EQ(result.value.affected_rows(), 2); - EXPECT_EQ(result.value.warning_count(), 0); - EXPECT_EQ(result.value.last_insert_id(), 0); - EXPECT_THAT(std::string(result.value.info()), HasSubstr("Rows matched")); -} - -TEST_P(QueryTest, SelectOk) -{ - auto result = do_query("SELECT * FROM empty_table"); - result.validate_no_error(); - EXPECT_TRUE(result.value.valid()); - EXPECT_FALSE(result.value.complete()); - validate_2fields_meta(result.value, "empty_table"); -} - -TEST_P(QueryTest, SelectQueryFailed) -{ - auto result = do_query("SELECT field_varchar, field_bad FROM one_row_table"); - result.validate_error(errc::bad_field_error, {"unknown column", "field_bad"}); - EXPECT_FALSE(result.value.valid()); -} - - -// Some system-level query tests (TODO: this does not feel right here) -TEST_P(QueryTest, QueryAndFetch_AliasedTableAndField_MetadataCorrect) -{ - auto result = do_query("SELECT field_varchar AS field_alias FROM empty_table table_alias"); - meta_validator validator ("table_alias", "empty_table", "field_alias", - "field_varchar", field_type::varchar); - result.validate_no_error(); - validate_meta(result.value.fields(), {validator}); -} - -MYSQL_NETWORK_TEST_SUITE(QueryTest); } // anon namespace diff --git a/test/integration/resultset.cpp b/test/integration/resultset.cpp index 80f14cda..be89115c 100644 --- a/test/integration/resultset.cpp +++ b/test/integration/resultset.cpp @@ -14,285 +14,318 @@ using boost::mysql::field_metadata; using boost::mysql::field_type; using boost::mysql::error_code; using boost::mysql::error_info; -using boost::mysql::tcp_resultset; -using boost::mysql::tcp_connection; using boost::mysql::ssl_mode; +using boost::mysql::connection; +using boost::mysql::resultset; +using boost::mysql::prepared_statement; namespace net = boost::asio; namespace { +template class resultset_generator { public: virtual ~resultset_generator() {} virtual const char* name() const = 0; - virtual tcp_resultset generate(tcp_connection&, std::string_view) = 0; + virtual resultset generate(connection&, std::string_view) = 0; }; -struct resultset_testcase : named_param, network_testcase +template +struct resultset_testcase : named_param, + network_testcase { - resultset_generator* gen; + resultset_generator* gen; - resultset_testcase(network_testcase base, resultset_generator* gen): - network_testcase(base), gen(gen) {} + resultset_testcase(network_testcase base, resultset_generator* gen): + network_testcase(base), gen(gen) {} }; -std::ostream& operator<<(std::ostream& os, const resultset_testcase& v) +template +std::ostream& operator<<(std::ostream& os, const resultset_testcase& v) { return os << v.gen->name() << '.' << v.net->name() << '.' << to_string(v.ssl); } -struct ResultsetTest : public IntegTestAfterHandshake, - public testing::WithParamInterface +template +struct ResultsetTest : public IntegTestAfterHandshake, + public testing::WithParamInterface> { - ResultsetTest(): IntegTestAfterHandshake(GetParam().ssl) {} - auto do_generate(std::string_view query) { return GetParam().gen->generate(conn, query); } - auto do_fetch_one(tcp_resultset& r) { return GetParam().net->fetch_one(r); } - auto do_fetch_many(tcp_resultset& r, std::size_t count) { return GetParam().net->fetch_many(r, count); } - auto do_fetch_all(tcp_resultset& r) { return GetParam().net->fetch_all(r); } + ResultsetTest(): IntegTestAfterHandshake(this->GetParam().ssl) {} + auto do_generate(std::string_view query) { return this->GetParam().gen->generate(this->conn, query); } + auto do_fetch_one(resultset& r) { return this->GetParam().net->fetch_one(r); } + auto do_fetch_many(resultset& r, std::size_t count) { return this->GetParam().net->fetch_many(r, count); } + auto do_fetch_all(resultset& r) { return this->GetParam().net->fetch_all(r); } + + // FetchOne + void FetchOne_NoResults() + { + auto result = do_generate("SELECT * FROM empty_table"); + EXPECT_TRUE(result.valid()); + EXPECT_FALSE(result.complete()); + EXPECT_EQ(result.fields().size(), 2); + + // Already in the end of the resultset, we receive the EOF + auto row_result = do_fetch_one(result); + row_result.validate_no_error(); + EXPECT_EQ(row_result.value, nullptr); + this->validate_eof(result); + + // Fetching again just returns null + row_result = do_fetch_one(result); + row_result.validate_no_error(); + EXPECT_EQ(row_result.value, nullptr); + this->validate_eof(result); + } + + void FetchOne_OneRow() + { + auto result = do_generate("SELECT * FROM one_row_table"); + EXPECT_TRUE(result.valid()); + EXPECT_FALSE(result.complete()); + EXPECT_EQ(result.fields().size(), 2); + + // Fetch only row + auto row_result = do_fetch_one(result); + row_result.validate_no_error(); + ASSERT_NE(row_result.value, nullptr); + this->validate_2fields_meta(result, "one_row_table"); + EXPECT_EQ(row_result.value->values(), makevalues(1, "f0")); + EXPECT_FALSE(result.complete()); + + // Fetch next: end of resultset + row_result = do_fetch_one(result); + row_result.validate_no_error(); + ASSERT_EQ(row_result.value, nullptr); + this->validate_eof(result); + } + + void FetchOne_TwoRows() + { + auto result = do_generate("SELECT * FROM two_rows_table"); + EXPECT_TRUE(result.valid()); + EXPECT_FALSE(result.complete()); + EXPECT_EQ(result.fields().size(), 2); + + // Fetch first row + auto row_result = do_fetch_one(result); + row_result.validate_no_error(); + ASSERT_NE(row_result.value, nullptr); + this->validate_2fields_meta(result, "two_rows_table"); + EXPECT_EQ(row_result.value->values(), makevalues(1, "f0")); + EXPECT_FALSE(result.complete()); + + // Fetch next row + row_result = do_fetch_one(result); + row_result.validate_no_error(); + ASSERT_NE(row_result.value, nullptr); + this->validate_2fields_meta(result, "two_rows_table"); + EXPECT_EQ(row_result.value->values(), makevalues(2, "f1")); + EXPECT_FALSE(result.complete()); + + // Fetch next: end of resultset + row_result = do_fetch_one(result); + row_result.validate_no_error(); + ASSERT_EQ(row_result.value, nullptr); + this->validate_eof(result); + } + + // There seems to be no real case where fetch can fail (other than net fails) + + // FetchMany + void FetchMany_NoResults() + { + auto result = do_generate("SELECT * FROM empty_table"); + + // Fetch many, but there are no results + auto rows_result = do_fetch_many(result, 10); + rows_result.validate_no_error(); + EXPECT_TRUE(rows_result.value.empty()); + EXPECT_TRUE(result.complete()); + this->validate_eof(result); + + // Fetch again, should return OK and empty + rows_result = do_fetch_many(result, 10); + rows_result.validate_no_error(); + EXPECT_TRUE(rows_result.value.empty()); + EXPECT_TRUE(result.complete()); + this->validate_eof(result); + } + + void FetchMany_MoreRowsThanCount() + { + auto result = do_generate("SELECT * FROM three_rows_table"); + + // Fetch 2, one remaining + auto rows_result = do_fetch_many(result, 2); + rows_result.validate_no_error(); + EXPECT_FALSE(result.complete()); + EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1"))); + + // Fetch another two (completes the resultset) + rows_result = do_fetch_many(result, 2); + rows_result.validate_no_error(); + EXPECT_TRUE(result.complete()); + this->validate_eof(result); + EXPECT_EQ(rows_result.value, (makerows(2, 3, "f2"))); + } + + void FetchMany_LessRowsThanCount() + { + auto result = do_generate("SELECT * FROM two_rows_table"); + + // Fetch 3, resultset exhausted + auto rows_result = do_fetch_many(result, 3); + rows_result.validate_no_error(); + EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1"))); + this->validate_eof(result); + } + + void FetchMany_SameRowsAsCount() + { + auto result = do_generate("SELECT * FROM two_rows_table"); + + // Fetch 2, 0 remaining but resultset not exhausted + auto rows_result = do_fetch_many(result, 2); + rows_result.validate_no_error(); + EXPECT_FALSE(result.complete()); + EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1"))); + + // Fetch again, exhausts the resultset + rows_result = do_fetch_many(result, 2); + rows_result.validate_no_error(); + EXPECT_EQ(rows_result.value.size(), 0); + this->validate_eof(result); + } + + void FetchMany_CountEqualsOne() + { + auto result = do_generate("SELECT * FROM one_row_table"); + + // Fetch 1, 1 remaining + auto rows_result = do_fetch_many(result, 1); + rows_result.validate_no_error(); + EXPECT_FALSE(result.complete()); + EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0"))); + } + + // FetchAll + void FetchAll_NoResults() + { + auto result = do_generate("SELECT * FROM empty_table"); + + // Fetch many, but there are no results + auto rows_result = do_fetch_all(result); + rows_result.validate_no_error(); + EXPECT_TRUE(rows_result.value.empty()); + EXPECT_TRUE(result.complete()); + + // Fetch again, should return OK and empty + rows_result = do_fetch_all(result); + rows_result.validate_no_error(); + EXPECT_TRUE(rows_result.value.empty()); + this->validate_eof(result); + } + + void FetchAll_OneRow() + { + auto result = do_generate("SELECT * FROM one_row_table"); + + auto rows_result = do_fetch_all(result); + rows_result.validate_no_error(); + EXPECT_TRUE(result.complete()); + EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0"))); + } + + void FetchAll_SeveralRows() + { + auto result = do_generate("SELECT * FROM two_rows_table"); + + auto rows_result = do_fetch_all(result); + rows_result.validate_no_error(); + this->validate_eof(result); + EXPECT_TRUE(result.complete()); + EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1"))); + } }; -// FetchOne -TEST_P(ResultsetTest, FetchOne_NoResults) -{ - auto result = do_generate("SELECT * FROM empty_table"); - EXPECT_TRUE(result.valid()); - EXPECT_FALSE(result.complete()); - EXPECT_EQ(result.fields().size(), 2); +using ResultsetTestTCP = ResultsetTest; +#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS +using ResultsetTestUNIX = ResultsetTest; +#endif - // Already in the end of the resultset, we receive the EOF - auto row_result = do_fetch_one(result); - row_result.validate_no_error(); - EXPECT_EQ(row_result.value, nullptr); - validate_eof(result); - - // Fetching again just returns null - row_result = do_fetch_one(result); - row_result.validate_no_error(); - EXPECT_EQ(row_result.value, nullptr); - validate_eof(result); -} - -TEST_P(ResultsetTest, FetchOne_OneRow) -{ - auto result = do_generate("SELECT * FROM one_row_table"); - EXPECT_TRUE(result.valid()); - EXPECT_FALSE(result.complete()); - EXPECT_EQ(result.fields().size(), 2); - - // Fetch only row - auto row_result = do_fetch_one(result); - row_result.validate_no_error(); - ASSERT_NE(row_result.value, nullptr); - validate_2fields_meta(result, "one_row_table"); - EXPECT_EQ(row_result.value->values(), makevalues(1, "f0")); - EXPECT_FALSE(result.complete()); - - // Fetch next: end of resultset - row_result = do_fetch_one(result); - row_result.validate_no_error(); - ASSERT_EQ(row_result.value, nullptr); - validate_eof(result); -} - -TEST_P(ResultsetTest, FetchOne_TwoRows) -{ - auto result = do_generate("SELECT * FROM two_rows_table"); - EXPECT_TRUE(result.valid()); - EXPECT_FALSE(result.complete()); - EXPECT_EQ(result.fields().size(), 2); - - // Fetch first row - auto row_result = do_fetch_one(result); - row_result.validate_no_error(); - ASSERT_NE(row_result.value, nullptr); - validate_2fields_meta(result, "two_rows_table"); - EXPECT_EQ(row_result.value->values(), makevalues(1, "f0")); - EXPECT_FALSE(result.complete()); - - // Fetch next row - row_result = do_fetch_one(result); - row_result.validate_no_error(); - ASSERT_NE(row_result.value, nullptr); - validate_2fields_meta(result, "two_rows_table"); - EXPECT_EQ(row_result.value->values(), makevalues(2, "f1")); - EXPECT_FALSE(result.complete()); - - // Fetch next: end of resultset - row_result = do_fetch_one(result); - row_result.validate_no_error(); - ASSERT_EQ(row_result.value, nullptr); - validate_eof(result); -} - -// There seems to be no real case where fetch can fail (other than net fails) - -// FetchMany -TEST_P(ResultsetTest, FetchMany_NoResults) -{ - auto result = do_generate("SELECT * FROM empty_table"); - - // Fetch many, but there are no results - auto rows_result = do_fetch_many(result, 10); - rows_result.validate_no_error(); - EXPECT_TRUE(rows_result.value.empty()); - EXPECT_TRUE(result.complete()); - validate_eof(result); - - // Fetch again, should return OK and empty - rows_result = do_fetch_many(result, 10); - rows_result.validate_no_error(); - EXPECT_TRUE(rows_result.value.empty()); - EXPECT_TRUE(result.complete()); - validate_eof(result); -} - -TEST_P(ResultsetTest, FetchMany_MoreRowsThanCount) -{ - auto result = do_generate("SELECT * FROM three_rows_table"); - - // Fetch 2, one remaining - auto rows_result = do_fetch_many(result, 2); - rows_result.validate_no_error(); - EXPECT_FALSE(result.complete()); - EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1"))); - - // Fetch another two (completes the resultset) - rows_result = do_fetch_many(result, 2); - rows_result.validate_no_error(); - EXPECT_TRUE(result.complete()); - validate_eof(result); - EXPECT_EQ(rows_result.value, (makerows(2, 3, "f2"))); -} - -TEST_P(ResultsetTest, FetchMany_LessRowsThanCount) -{ - auto result = do_generate("SELECT * FROM two_rows_table"); - - // Fetch 3, resultset exhausted - auto rows_result = do_fetch_many(result, 3); - rows_result.validate_no_error(); - EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1"))); - validate_eof(result); -} - -TEST_P(ResultsetTest, FetchMany_SameRowsAsCount) -{ - auto result = do_generate("SELECT * FROM two_rows_table"); - - // Fetch 2, 0 remaining but resultset not exhausted - auto rows_result = do_fetch_many(result, 2); - rows_result.validate_no_error(); - EXPECT_FALSE(result.complete()); - EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1"))); - - // Fetch again, exhausts the resultset - rows_result = do_fetch_many(result, 2); - rows_result.validate_no_error(); - EXPECT_EQ(rows_result.value.size(), 0); - validate_eof(result); -} - -TEST_P(ResultsetTest, FetchMany_CountEqualsOne) -{ - auto result = do_generate("SELECT * FROM one_row_table"); - - // Fetch 1, 1 remaining - auto rows_result = do_fetch_many(result, 1); - rows_result.validate_no_error(); - EXPECT_FALSE(result.complete()); - EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0"))); -} - -// FetchAll -TEST_P(ResultsetTest, FetchAll_NoResults) -{ - auto result = do_generate("SELECT * FROM empty_table"); - - // Fetch many, but there are no results - auto rows_result = do_fetch_all(result); - rows_result.validate_no_error(); - EXPECT_TRUE(rows_result.value.empty()); - EXPECT_TRUE(result.complete()); - - // Fetch again, should return OK and empty - rows_result = do_fetch_all(result); - rows_result.validate_no_error(); - EXPECT_TRUE(rows_result.value.empty()); - validate_eof(result); -} - -TEST_P(ResultsetTest, FetchAll_OneRow) -{ - auto result = do_generate("SELECT * FROM one_row_table"); - - auto rows_result = do_fetch_all(result); - rows_result.validate_no_error(); - EXPECT_TRUE(result.complete()); - EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0"))); -} - -TEST_P(ResultsetTest, FetchAll_SeveralRows) -{ - auto result = do_generate("SELECT * FROM two_rows_table"); - - auto rows_result = do_fetch_all(result); - rows_result.validate_no_error(); - validate_eof(result); - EXPECT_TRUE(result.complete()); - EXPECT_EQ(rows_result.value, (makerows(2, 1, "f0", 2, "f1"))); -} +MYSQL_NETWORK_TEST(ResultsetTest, FetchOne_NoResults) +MYSQL_NETWORK_TEST(ResultsetTest, FetchOne_OneRow) +MYSQL_NETWORK_TEST(ResultsetTest, FetchOne_TwoRows) +MYSQL_NETWORK_TEST(ResultsetTest, FetchMany_NoResults) +MYSQL_NETWORK_TEST(ResultsetTest, FetchMany_MoreRowsThanCount) +MYSQL_NETWORK_TEST(ResultsetTest, FetchMany_LessRowsThanCount) +MYSQL_NETWORK_TEST(ResultsetTest, FetchMany_SameRowsAsCount) +MYSQL_NETWORK_TEST(ResultsetTest, FetchMany_CountEqualsOne) +MYSQL_NETWORK_TEST(ResultsetTest, FetchAll_NoResults) +MYSQL_NETWORK_TEST(ResultsetTest, FetchAll_OneRow) +MYSQL_NETWORK_TEST(ResultsetTest, FetchAll_SeveralRows) // Instantiate the test suites -class text_resultset_generator : public resultset_generator +template +class text_resultset_generator : public resultset_generator { public: const char* name() const override { return "text"; } - tcp_resultset generate(tcp_connection& conn, std::string_view query) override + resultset generate(connection& conn, std::string_view query) override { return conn.query(query); } }; -class binary_resultset_generator : public resultset_generator +template +class binary_resultset_generator : public resultset_generator { public: const char* name() const override { return "binary"; } - tcp_resultset generate(tcp_connection& conn, std::string_view query) override + resultset generate(connection& conn, std::string_view query) override { return conn.prepare_statement(query).execute(boost::mysql::no_statement_params); } }; -text_resultset_generator text_obj; -binary_resultset_generator binary_obj; +template text_resultset_generator text_obj; +template binary_resultset_generator binary_obj; -resultset_generator* all_resultset_generators [] = { - &text_obj, - &binary_obj -}; -std::vector make_all_resultset_testcases() +template +std::vector> make_all_resultset_testcases() { - std::vector res; + resultset_generator* all_resultset_generators [] = { + &text_obj, + &binary_obj + }; + + std::vector> res; for (auto* gen: all_resultset_generators) { - for (auto base: make_all_network_testcases()) + for (auto base: make_all_network_testcases()) { - res.push_back(resultset_testcase(base, gen)); + res.push_back(resultset_testcase(base, gen)); } } return res; } -INSTANTIATE_TEST_SUITE_P(Default, ResultsetTest, testing::ValuesIn( - make_all_resultset_testcases() +INSTANTIATE_TEST_SUITE_P(Default, ResultsetTestTCP, testing::ValuesIn( + make_all_resultset_testcases() ), test_name_generator); +#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS +INSTANTIATE_TEST_SUITE_P(Default, ResultsetTestUNIX, testing::ValuesIn( + make_all_resultset_testcases() +), test_name_generator); +#endif + }