Added TAP test

pull/5431/head
Rahim Kanji 2 months ago
parent 742ff05ecc
commit ea619d7816

@ -244,6 +244,7 @@
"test_ssl_fast_forward-3_libmariadb-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ],
"test_ssl_fast_forward-3_libmysql-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ],
"test_ignore_min_gtid-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ],
"pgsql-transaction_state_comprehensive-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ],
"pgsql-query_digests_stages_test-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ],
"pgsql_admin_metacmds-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ],
"pgsql-monitor_ssl_connections_test-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ],

File diff suppressed because it is too large Load Diff

@ -9,6 +9,7 @@
#include <sstream>
#include <chrono>
#include <thread>
#include <sys/select.h>
#include "libpq-fe.h"
#include "command_line.h"
#include "tap.h"
@ -565,12 +566,501 @@ int main(int argc, char** argv) {
return true;
});
// ============================================================================
// Pipeline Mode Tests for SET Variable Tracking
// ============================================================================
// Test: BEGIN + SET + COMMIT in pipeline mode
add_test("BEGIN + SET + COMMIT in pipeline mode", [&]() {
auto conn = createNewConnection(ConnType::BACKEND, "", false);
if (!conn) return false;
// Get original value and choose DIFFERENT value
const auto original = getVariable(conn.get(), "datestyle");
const std::string new_value = (original.find("ISO") != std::string::npos) ?
"SQL, DMY" : "Postgres, MDY";
diag("BEGIN+SET+COMMIT: original='%s', will SET to='%s'",
original.c_str(), new_value.c_str());
// Enter pipeline mode
if (PQenterPipelineMode(conn.get()) != 1) {
diag("Failed to enter pipeline mode");
return false;
}
// Send BEGIN, SET with DIFFERENT value, COMMIT in pipeline
PQsendQueryParams(conn.get(), "BEGIN", 0, NULL, NULL, NULL, NULL, 0);
std::string set_query = "SET datestyle = '" + new_value + "'";
PQsendQueryParams(conn.get(), set_query.c_str(), 0, NULL, NULL, NULL, NULL, 0);
PQsendQueryParams(conn.get(), "COMMIT", 0, NULL, NULL, NULL, NULL, 0);
PQpipelineSync(conn.get());
PQflush(conn.get());
// Consume results
int count = 0;
int cmdCount = 0;
int sock = PQsocket(conn.get());
PGresult* res;
while (count < 4) { // 3 commands + 1 sync
if (PQconsumeInput(conn.get()) == 0) break;
while ((res = PQgetResult(conn.get())) != NULL) {
ExecStatusType status = PQresultStatus(res);
if (status == PGRES_COMMAND_OK) cmdCount++;
if (status == PGRES_PIPELINE_SYNC) {
PQclear(res);
count++;
break;
}
PQclear(res);
count++;
}
if (count >= 4) break;
// Only wait if libpq reports it's busy waiting for more data
if (!PQisBusy(conn.get())) continue;
fd_set input_mask;
FD_ZERO(&input_mask);
FD_SET(sock, &input_mask);
struct timeval timeout = {5, 0};
select(sock + 1, &input_mask, NULL, NULL, &timeout);
}
PQexitPipelineMode(conn.get());
// Verify SET persisted after COMMIT (value should be different from original)
const auto newVal = getVariable(conn.get(), "datestyle");
bool value_changed = (newVal.find(new_value) != std::string::npos) &&
(newVal != original);
// Cleanup - restore original
executeQuery(conn.get(), "SET datestyle = '" + original + "'");
return value_changed && cmdCount >= 3;
});
// Test: BEGIN + SET + ROLLBACK in pipeline mode
add_test("BEGIN + SET + ROLLBACK in pipeline mode", [&]() {
auto conn = createNewConnection(ConnType::BACKEND, "", false);
if (!conn) return false;
// Get original value and choose DIFFERENT value
const auto original = getVariable(conn.get(), "timezone");
const std::string new_value = (original.find("UTC") != std::string::npos) ?
"PST8PDT" : "UTC";
diag("BEGIN+SET+ROLLBACK: original='%s', will SET to='%s'",
original.c_str(), new_value.c_str());
// Enter pipeline mode
if (PQenterPipelineMode(conn.get()) != 1) {
diag("Failed to enter pipeline mode");
return false;
}
// Send BEGIN, SET with DIFFERENT value, ROLLBACK in pipeline
PQsendQueryParams(conn.get(), "BEGIN", 0, NULL, NULL, NULL, NULL, 0);
std::string set_query = "SET timezone = '" + new_value + "'";
PQsendQueryParams(conn.get(), set_query.c_str(), 0, NULL, NULL, NULL, NULL, 0);
PQsendQueryParams(conn.get(), "ROLLBACK", 0, NULL, NULL, NULL, NULL, 0);
PQpipelineSync(conn.get());
PQflush(conn.get());
// Consume results
int count = 0;
int cmdCount = 0;
int sock = PQsocket(conn.get());
PGresult* res;
while (count < 4) {
if (PQconsumeInput(conn.get()) == 0) break;
while ((res = PQgetResult(conn.get())) != NULL) {
ExecStatusType status = PQresultStatus(res);
if (status == PGRES_COMMAND_OK) cmdCount++;
if (status == PGRES_PIPELINE_SYNC) {
PQclear(res);
count++;
break;
}
PQclear(res);
count++;
}
if (count >= 4) break;
// Only wait if libpq reports it's busy waiting for more data
if (!PQisBusy(conn.get())) continue;
fd_set input_mask;
FD_ZERO(&input_mask);
FD_SET(sock, &input_mask);
struct timeval timeout = {5, 0};
select(sock + 1, &input_mask, NULL, NULL, &timeout);
}
PQexitPipelineMode(conn.get());
// Verify ROLLBACK reverted the SET (value should be back to original, not the new value)
const auto newVal = getVariable(conn.get(), "timezone");
bool reverted = (newVal == original) && (newVal != new_value);
return reverted && cmdCount >= 3;
});
// Test: Multiple variables in transaction in pipeline mode
add_test("Multiple variables in transaction in pipeline", [&]() {
auto conn = createNewConnection(ConnType::BACKEND, "", false);
if (!conn) return false;
// Get original values and choose DIFFERENT values
const auto orig_datestyle = getVariable(conn.get(), "datestyle");
const auto orig_timezone = getVariable(conn.get(), "timezone");
const auto orig_bytea = getVariable(conn.get(), "bytea_output");
// Choose DIFFERENT values from original
const std::string new_datestyle = (orig_datestyle.find("ISO") != std::string::npos) ?
"Postgres, MDY" : "ISO, DMY";
const std::string new_timezone = (orig_timezone.find("UTC") != std::string::npos) ?
"EST5EDT" : "UTC";
const std::string new_bytea = (orig_bytea == "hex") ? "escape" : "hex";
diag("Multi-var: datestyle orig='%s'->'%s', timezone orig='%s'->'%s', bytea orig='%s'->'%s'",
orig_datestyle.c_str(), new_datestyle.c_str(),
orig_timezone.c_str(), new_timezone.c_str(),
orig_bytea.c_str(), new_bytea.c_str());
// Enter pipeline mode
if (PQenterPipelineMode(conn.get()) != 1) {
diag("Failed to enter pipeline mode");
return false;
}
// Send BEGIN + multiple SETs (with DIFFERENT values) + COMMIT
PQsendQueryParams(conn.get(), "BEGIN", 0, NULL, NULL, NULL, NULL, 0);
std::string set_datestyle = "SET datestyle = '" + new_datestyle + "'";
std::string set_timezone = "SET timezone = '" + new_timezone + "'";
std::string set_bytea = "SET bytea_output = '" + new_bytea + "'";
PQsendQueryParams(conn.get(), set_datestyle.c_str(), 0, NULL, NULL, NULL, NULL, 0);
PQsendQueryParams(conn.get(), set_timezone.c_str(), 0, NULL, NULL, NULL, NULL, 0);
PQsendQueryParams(conn.get(), set_bytea.c_str(), 0, NULL, NULL, NULL, NULL, 0);
PQsendQueryParams(conn.get(), "COMMIT", 0, NULL, NULL, NULL, NULL, 0);
PQpipelineSync(conn.get());
PQflush(conn.get());
// Consume results
int count = 0;
int cmdCount = 0;
int sock = PQsocket(conn.get());
PGresult* res;
while (count < 6) { // 5 commands + 1 sync
if (PQconsumeInput(conn.get()) == 0) break;
while ((res = PQgetResult(conn.get())) != NULL) {
ExecStatusType status = PQresultStatus(res);
if (status == PGRES_COMMAND_OK) cmdCount++;
if (status == PGRES_PIPELINE_SYNC) {
PQclear(res);
count++;
break;
}
PQclear(res);
count++;
}
if (count >= 6) break;
// Only wait if libpq reports it's busy waiting for more data
if (!PQisBusy(conn.get())) continue;
fd_set input_mask;
FD_ZERO(&input_mask);
FD_SET(sock, &input_mask);
struct timeval timeout = {5, 0};
select(sock + 1, &input_mask, NULL, NULL, &timeout);
}
PQexitPipelineMode(conn.get());
// Verify all SETs persisted (values should be the new ones, different from original)
bool success = true;
const auto final_datestyle = getVariable(conn.get(), "datestyle");
const auto final_timezone = getVariable(conn.get(), "timezone");
const auto final_bytea = getVariable(conn.get(), "bytea_output");
success &= (final_datestyle.find(new_datestyle) != std::string::npos) && (final_datestyle != orig_datestyle);
success &= (final_timezone == new_timezone) && (final_timezone != orig_timezone);
success &= (final_bytea == new_bytea) && (final_bytea != orig_bytea);
diag("Final values: datestyle='%s' (changed:%s), timezone='%s' (changed:%s), bytea='%s' (changed:%s)",
final_datestyle.c_str(), (final_datestyle != orig_datestyle) ? "yes" : "no",
final_timezone.c_str(), (final_timezone != orig_timezone) ? "yes" : "no",
final_bytea.c_str(), (final_bytea != orig_bytea) ? "yes" : "no");
// Cleanup
executeQuery(conn.get(), "SET datestyle = '" + orig_datestyle + "'");
executeQuery(conn.get(), "SET timezone = '" + orig_timezone + "'");
executeQuery(conn.get(), "SET bytea_output = '" + orig_bytea + "'");
return success && cmdCount >= 5;
});
// Test: SAVEPOINT with SET in pipeline mode
add_test("SAVEPOINT with SET in pipeline mode", [&]() {
auto conn = createNewConnection(ConnType::BACKEND, "", false);
if (!conn) return false;
// Get original and choose DIFFERENT values for testing
const auto original = getVariable(conn.get(), "extra_float_digits");
// Choose values that are different from original AND different from each other
const std::string value1 = (original == "0") ? "2" : "0"; // First SET value
const std::string value2 = (value1 == "2") ? "3" : "2"; // Second SET value (different from value1)
diag("SAVEPOINT test: original='%s', value1='%s', value2='%s'",
original.c_str(), value1.c_str(), value2.c_str());
// Enter pipeline mode
if (PQenterPipelineMode(conn.get()) != 1) {
diag("Failed to enter pipeline mode");
return false;
}
// Send: BEGIN, SET (value1), SAVEPOINT, SET (value2), ROLLBACK TO SAVEPOINT, COMMIT
// After ROLLBACK TO SAVEPOINT, value should be value1
// After COMMIT, value should remain value1
PQsendQueryParams(conn.get(), "BEGIN", 0, NULL, NULL, NULL, NULL, 0);
std::string set1 = "SET extra_float_digits = " + value1;
std::string set2 = "SET extra_float_digits = " + value2;
PQsendQueryParams(conn.get(), set1.c_str(), 0, NULL, NULL, NULL, NULL, 0);
PQsendQueryParams(conn.get(), "SAVEPOINT sp1", 0, NULL, NULL, NULL, NULL, 0);
PQsendQueryParams(conn.get(), set2.c_str(), 0, NULL, NULL, NULL, NULL, 0);
PQsendQueryParams(conn.get(), "ROLLBACK TO SAVEPOINT sp1", 0, NULL, NULL, NULL, NULL, 0);
PQsendQueryParams(conn.get(), "COMMIT", 0, NULL, NULL, NULL, NULL, 0);
PQpipelineSync(conn.get());
PQflush(conn.get());
// Consume results
int count = 0;
int cmdCount = 0;
int sock = PQsocket(conn.get());
PGresult* res;
while (count < 7) { // 6 commands + 1 sync
if (PQconsumeInput(conn.get()) == 0) break;
while ((res = PQgetResult(conn.get())) != NULL) {
ExecStatusType status = PQresultStatus(res);
if (status == PGRES_COMMAND_OK) cmdCount++;
if (status == PGRES_PIPELINE_SYNC) {
PQclear(res);
count++;
break;
}
PQclear(res);
count++;
}
if (count >= 7) break;
// Only wait if libpq reports it's busy waiting for more data
if (!PQisBusy(conn.get())) continue;
fd_set input_mask;
FD_ZERO(&input_mask);
FD_SET(sock, &input_mask);
struct timeval timeout = {5, 0};
select(sock + 1, &input_mask, NULL, NULL, &timeout);
}
PQexitPipelineMode(conn.get());
// After ROLLBACK TO SAVEPOINT, value should be value1 (first SET), not value2 (second SET)
// After COMMIT, the first SET persists
const auto newVal = getVariable(conn.get(), "extra_float_digits");
bool success = (newVal == value1) && (newVal != original) && (newVal != value2);
diag("After savepoint rollback: value='%s' (expected='%s', original='%s', value2='%s')",
newVal.c_str(), value1.c_str(), original.c_str(), value2.c_str());
// Cleanup
executeQuery(conn.get(), "SET extra_float_digits = " + original);
return success && cmdCount >= 6;
});
// ============================================================================
// SET Failure Tests in Pipeline Mode
// ============================================================================
// Test: SET failure with invalid value in pipeline - verify error handling
add_test("SET failure - invalid value in pipeline", [&]() {
auto conn = createNewConnection(ConnType::BACKEND, "", false);
if (!conn) return false;
// Get original value
const auto original = getVariable(conn.get(), "datestyle");
// Enter pipeline mode
if (PQenterPipelineMode(conn.get()) != 1) {
diag("Failed to enter pipeline mode");
return false;
}
// Send SET with invalid value
PQsendQueryParams(conn.get(), "SET datestyle = 'INVALID_STYLE_VALUE'", 0, NULL, NULL, NULL, NULL, 0);
PQpipelineSync(conn.get());
PQflush(conn.get());
// Consume results
int count = 0;
bool got_error = false;
int sock = PQsocket(conn.get());
PGresult* res;
while (count < 2) {
if (PQconsumeInput(conn.get()) == 0) break;
while ((res = PQgetResult(conn.get())) != NULL) {
ExecStatusType status = PQresultStatus(res);
if (status == PGRES_FATAL_ERROR) {
got_error = true;
diag("Got expected error for invalid datestyle");
}
if (status == PGRES_PIPELINE_SYNC) {
PQclear(res);
count++;
break;
}
PQclear(res);
count++;
}
if (count >= 2) break;
// Only wait if libpq reports it's busy waiting for more data
if (!PQisBusy(conn.get())) continue;
fd_set input_mask;
FD_ZERO(&input_mask);
FD_SET(sock, &input_mask);
struct timeval timeout = {5, 0};
select(sock + 1, &input_mask, NULL, NULL, &timeout);
}
PQexitPipelineMode(conn.get());
// Verify value unchanged (still original)
const auto afterVal = getVariable(conn.get(), "datestyle");
bool unchanged = (afterVal == original);
diag("After invalid SET: value='%s', original='%s', unchanged=%s",
afterVal.c_str(), original.c_str(), unchanged ? "yes" : "no");
return got_error && unchanged;
});
// Test: SET failure - multiple SETs with one invalid, verify state consistency
add_test("SET failure - state consistency after partial failure", [&]() {
auto conn = createNewConnection(ConnType::BACKEND, "", false);
if (!conn) return false;
// Get original values and choose DIFFERENT valid values
const auto orig_datestyle = getVariable(conn.get(), "datestyle");
const auto orig_timezone = getVariable(conn.get(), "timezone");
const std::string new_datestyle = (orig_datestyle.find("ISO") != std::string::npos) ?
"Postgres, MDY" : "ISO, DMY";
const std::string new_timezone = (orig_timezone.find("UTC") != std::string::npos) ?
"PST8PDT" : "UTC";
diag("Partial failure test: datestyle orig='%s'->'%s', timezone orig='%s'->'%s'",
orig_datestyle.c_str(), new_datestyle.c_str(),
orig_timezone.c_str(), new_timezone.c_str());
// Enter pipeline mode
if (PQenterPipelineMode(conn.get()) != 1) {
diag("Failed to enter pipeline mode");
return false;
}
// Send: valid SET, invalid SET, valid SET
std::string set1 = "SET datestyle = '" + new_datestyle + "'";
std::string set3 = "SET timezone = '" + new_timezone + "'";
PQsendQueryParams(conn.get(), set1.c_str(), 0, NULL, NULL, NULL, NULL, 0);
PQsendQueryParams(conn.get(), "SET invalid_variable = 'value'", 0, NULL, NULL, NULL, NULL, 0);
PQsendQueryParams(conn.get(), set3.c_str(), 0, NULL, NULL, NULL, NULL, 0);
PQpipelineSync(conn.get());
PQflush(conn.get());
// Consume results
int count = 0;
int error_count = 0;
int success_count = 0;
int sock = PQsocket(conn.get());
PGresult* res;
while (count < 4) { // 3 commands + 1 sync
if (PQconsumeInput(conn.get()) == 0) break;
while ((res = PQgetResult(conn.get())) != NULL) {
ExecStatusType status = PQresultStatus(res);
if (status == PGRES_COMMAND_OK) {
success_count++;
} else if (status == PGRES_FATAL_ERROR) {
error_count++;
} else if (status == PGRES_PIPELINE_SYNC) {
PQclear(res);
count++;
break;
}
PQclear(res);
count++;
}
if (count >= 4) break;
// Only wait if libpq reports it's busy waiting for more data
if (!PQisBusy(conn.get())) continue;
fd_set input_mask;
FD_ZERO(&input_mask);
FD_SET(sock, &input_mask);
struct timeval timeout = {5, 0};
select(sock + 1, &input_mask, NULL, NULL, &timeout);
}
PQexitPipelineMode(conn.get());
// After error, connection should still be usable
const auto final_datestyle = getVariable(conn.get(), "datestyle");
const auto final_timezone = getVariable(conn.get(), "timezone");
// At least first SET should have succeeded, middle should fail
diag("Results: successes=%d, errors=%d, final datestyle='%s', final timezone='%s'",
success_count, error_count, final_datestyle.c_str(), final_timezone.c_str());
// Verify connection still works
PGresult* test_res = PQexec(conn.get(), "SELECT 1");
bool connection_ok = (PQresultStatus(test_res) == PGRES_TUPLES_OK);
PQclear(test_res);
// Cleanup
executeQuery(conn.get(), "SET datestyle = '" + orig_datestyle + "'");
executeQuery(conn.get(), "SET timezone = '" + orig_timezone + "'");
// Expect at least 1 error (the invalid variable), and connection should still work
return (error_count >= 1) && connection_ok;
});
int total_tests = 0;
total_tests = tests.size();
plan(total_tests);
run_tests();
return exit_status();
}

Loading…
Cancel
Save