You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/test/tap/tests/unit/glovars_unit-t.cpp

453 lines
15 KiB

/**
* @file glovars_unit-t.cpp
* @brief Unit tests for ProxySQL_GlobalVariables (GloVars) and related types.
*
* Covers:
* - Constructor default values for GloVars fields
* - ProxySQL_Checksum_Value construction, set_checksum, destructor
* - generate_global_checksum() determinism and range
* - Command-line parsing via parse() / process_opts_pre() / process_opts_post()
* - install_signal_handler()
*
* Note: Tests avoid directly modifying GloVars.checksums_values members
* because the anonymous struct layout may differ between translation units
* when conditional features (GenAI, FFTO, etc.) add extra fields to the
* enclosing class. Local ProxySQL_Checksum_Value objects are used instead.
*
* @see lib/ProxySQL_GloVars.cpp
*/
#include "tap.h"
#include "test_globals.h"
#include "test_init.h"
#include "proxysql.h"
#include "proxysql_utils.h"
#include <cstring>
#include <string>
// ============================================================
// Constructor default values
// ============================================================
/**
* @brief Verify that GloVars has expected default values after construction.
*/
static void test_constructor_defaults() {
ok(GloVars.opt != nullptr,
"Constructor: opt (ezOptionParser) is allocated");
ok(GloVars.confFile != nullptr,
"Constructor: confFile is allocated");
ok(GloVars.epoch_version == 0,
"Constructor: epoch_version is 0");
ok(GloVars.global.sqlite3_server == false,
"Constructor: global.sqlite3_server is false");
ok(GloVars.global.data_packets_history_size == 0,
"Constructor: global.data_packets_history_size is 0");
ok(GloVars.global.gr_bootstrap_mode == 0,
"Constructor: global.gr_bootstrap_mode is 0");
ok(GloVars.global.gr_bootstrap_conf_base_port == 0,
"Constructor: global.gr_bootstrap_conf_base_port is 0");
ok(GloVars.global.gr_bootstrap_conf_use_sockets == false,
"Constructor: global.gr_bootstrap_conf_use_sockets is false");
ok(GloVars.global.gr_bootstrap_conf_skip_tcp == false,
"Constructor: global.gr_bootstrap_conf_skip_tcp is false");
ok(GloVars.global.ssl_keylog_enabled == false,
"Constructor: global.ssl_keylog_enabled is false");
ok(GloVars.global.tls_load_count == 0,
"Constructor: global.tls_load_count is 0");
ok(GloVars.global.tls_last_load_timestamp == 0,
"Constructor: global.tls_last_load_timestamp is 0");
ok(GloVars.global.tls_last_load_ok == false,
"Constructor: global.tls_last_load_ok is false");
ok(GloVars.execute_on_exit_failure == nullptr,
"Constructor: execute_on_exit_failure is nullptr");
ok(GloVars.ldap_auth_plugin == nullptr,
"Constructor: ldap_auth_plugin is nullptr");
ok(GloVars.web_interface_plugin == nullptr,
"Constructor: web_interface_plugin is nullptr");
ok(GloVars.sqlite3_plugin == nullptr,
"Constructor: sqlite3_plugin is nullptr");
ok(GloVars.statuses.stack_memory_mysql_threads == 0,
"Constructor: statuses.stack_memory_mysql_threads is 0");
ok(GloVars.statuses.stack_memory_admin_threads == 0,
"Constructor: statuses.stack_memory_admin_threads is 0");
ok(GloVars.statuses.stack_memory_cluster_threads == 0,
"Constructor: statuses.stack_memory_cluster_threads is 0");
ok(GloVars.cluster_sync_interfaces == false,
"Constructor: cluster_sync_interfaces is false");
ok(GloVars.configfile_open == false,
"Constructor: configfile_open is false");
ok(GloVars.__cmd_proxysql_initial == false,
"Constructor: __cmd_proxysql_initial is false");
ok(GloVars.__cmd_proxysql_reload == false,
"Constructor: __cmd_proxysql_reload is false");
}
// ============================================================
// ProxySQL_Checksum_Value
// ============================================================
static void test_checksum_value_defaults() {
ProxySQL_Checksum_Value cv;
ok(cv.version == 0, "Checksum_Value: default version is 0");
ok(cv.epoch == 0, "Checksum_Value: default epoch is 0");
ok(cv.in_shutdown == false, "Checksum_Value: default in_shutdown is false");
ok(cv.checksum != nullptr, "Checksum_Value: checksum buffer is allocated");
bool all_zero = true;
for (int i = 0; i < ProxySQL_Checksum_Value_LENGTH; i++) {
if (cv.checksum[i] != '\0') {
all_zero = false;
break;
}
}
ok(all_zero, "Checksum_Value: checksum buffer is zero-filled on construction");
}
static void test_checksum_value_set() {
ProxySQL_Checksum_Value cv;
char input[] = "0x1234567890ABCDE1";
cv.set_checksum(input);
ok(cv.checksum[0] == '0', "set_checksum: first char preserved");
ok(cv.checksum[1] == 'x', "set_checksum: second char preserved");
ok(cv.checksum[2] == '1', "set_checksum: third char preserved");
// Test with embedded NUL bytes at positions 2-17
ProxySQL_Checksum_Value cv2;
char input2[ProxySQL_Checksum_Value_LENGTH];
memset(input2, 0, sizeof(input2));
input2[0] = 'A';
input2[1] = 'B';
cv2.set_checksum(input2);
ok(cv2.checksum[0] == 'A', "set_checksum(zeros): pos 0 preserved");
ok(cv2.checksum[1] == 'B', "set_checksum(zeros): pos 1 preserved");
bool zeros_replaced = true;
for (int i = 2; i < 18; i++) {
if (cv2.checksum[i] != '0') {
zeros_replaced = false;
break;
}
}
ok(zeros_replaced, "set_checksum(zeros): positions 2-17 all replaced with '0'");
// Test with spaces in checksum
ProxySQL_Checksum_Value cv3;
char input3[ProxySQL_Checksum_Value_LENGTH];
memset(input3, ' ', sizeof(input3));
input3[0] = 'X';
input3[1] = 'Y';
cv3.set_checksum(input3);
ok(cv3.checksum[0] == 'X', "set_checksum(spaces): pos 0 preserved");
ok(cv3.checksum[1] == 'Y', "set_checksum(spaces): pos 1 preserved");
bool spaces_replaced = true;
for (int i = 2; i < 18; i++) {
if (cv3.checksum[i] != '0') {
spaces_replaced = false;
break;
}
}
ok(spaces_replaced, "set_checksum(spaces): positions 2-17 all replaced with '0'");
}
static void test_checksum_value_shutdown_flag() {
// Normal destruction (in_shutdown == false): no crash
{
ProxySQL_Checksum_Value cv;
cv.set_checksum((char *)"0xABCDEF123456789A");
}
ok(true, "Checksum_Value: normal destruction (in_shutdown=false) doesn't crash");
// Destruction with in_shutdown == true: checksum is NOT freed (intentional leak)
{
ProxySQL_Checksum_Value cv;
cv.in_shutdown = true;
cv.set_checksum((char *)"0xABCDEF123456789A");
}
ok(true, "Checksum_Value: shutdown destruction (in_shutdown=true) doesn't crash");
}
// ============================================================
// ProxySQL_Checksum_Value version/epoch interaction
// ============================================================
static void test_checksum_value_version_interaction() {
ProxySQL_Checksum_Value cv;
cv.set_checksum((char *)"0xDEADBEEF1234567A");
cv.version = 1;
ok(cv.version == 1, "Checksum_Value: version can be set to 1");
ok(cv.epoch == 0, "Checksum_Value: epoch stays 0 after set_checksum");
ok(cv.checksum[0] == '0', "Checksum_Value: checksum data preserved after version set");
cv.version = 42;
ok(cv.version == 42, "Checksum_Value: version can be updated to 42");
cv.epoch = 1234567890;
ok(cv.epoch == 1234567890, "Checksum_Value: epoch can be set");
// Setting version doesn't affect checksum content
ProxySQL_Checksum_Value cv2;
cv2.set_checksum((char *)"0xCAFEBABE1234567A");
char saved = cv2.checksum[5];
cv2.version = 999;
ok(cv2.checksum[5] == saved,
"Checksum_Value: setting version doesn't modify checksum content");
}
/**
* @brief Test multiple set_checksum calls overwrite previous values.
*/
static void test_checksum_value_overwrite() {
ProxySQL_Checksum_Value cv;
cv.set_checksum((char *)"0xAAAAAAAAAAAAAAAA");
ok(cv.checksum[2] == 'A', "set_checksum overwrite: first set stores 'A'");
cv.set_checksum((char *)"0xBBBBBBBBBBBBBBBB");
ok(cv.checksum[2] == 'B', "set_checksum overwrite: second set stores 'B'");
cv.set_checksum((char *)"0xCCCCCCCCCCCCCCCC");
ok(cv.checksum[2] == 'C', "set_checksum overwrite: third set stores 'C'");
}
// ============================================================
// generate_global_checksum()
// ============================================================
/**
* @brief Test generate_global_checksum() with default (zero) versions.
*
* All versions are 0 from construction, so no checksum data is hashed.
* The result should be deterministic and within LLONG_MAX range.
*/
static void test_generate_global_checksum() {
uint64_t h1 = GloVars.generate_global_checksum();
uint64_t h2 = GloVars.generate_global_checksum();
ok(h1 == h2,
"generate_global_checksum: deterministic when all versions are 0");
// The code does h1 = h1/2 to keep result within LLONG_MAX
ok(h1 <= (uint64_t)LLONG_MAX,
"generate_global_checksum: result <= LLONG_MAX (divided by 2)");
}
// ============================================================
// Command-line parsing
// ============================================================
/**
* @brief Test command-line parsing with multiple flags at once.
*/
static void test_parse_combined() {
bool orig_initial = GloVars.__cmd_proxysql_initial;
bool orig_reload = GloVars.__cmd_proxysql_reload;
bool orig_version_check = GloVars.global.version_check;
bool orig_sqlite3 = GloVars.global.sqlite3_server;
char *orig_datadir = GloVars.__cmd_proxysql_datadir;
char *orig_uri = GloVars.global.gr_bootstrap_uri;
int orig_mode = GloVars.global.gr_bootstrap_mode;
uint64_t orig_port = GloVars.global.gr_bootstrap_conf_base_port;
const char *argv[] = {
"proxysql",
"--initial",
"--reload",
"--no-version-check",
"--sqlite3-server",
"-D", "/tmp/unit_test_datadir",
"--bootstrap", "mysql://user:pass@host:3306",
"--conf-base-port", "6033",
};
int argc = sizeof(argv) / sizeof(argv[0]);
GloVars.parse(argc, argv);
GloVars.process_opts_pre();
ok(GloVars.__cmd_proxysql_initial == true,
"parse: --initial sets __cmd_proxysql_initial");
ok(GloVars.__cmd_proxysql_reload == true,
"parse: --reload sets __cmd_proxysql_reload");
ok(GloVars.global.version_check == false,
"parse: --no-version-check disables version_check");
ok(GloVars.global.sqlite3_server == true,
"parse: --sqlite3-server enables sqlite3_server");
ok(GloVars.__cmd_proxysql_datadir != nullptr,
"parse: -D sets __cmd_proxysql_datadir");
if (GloVars.__cmd_proxysql_datadir) {
ok(strcmp(GloVars.__cmd_proxysql_datadir, "/tmp/unit_test_datadir") == 0,
"parse: -D value is correct");
} else {
ok(false, "parse: -D value is correct (was null)");
}
ok(GloVars.global.gr_bootstrap_mode == 1,
"parse: --bootstrap sets gr_bootstrap_mode to 1");
ok(GloVars.global.gr_bootstrap_uri != nullptr,
"parse: --bootstrap sets gr_bootstrap_uri");
if (GloVars.global.gr_bootstrap_uri) {
ok(strcmp(GloVars.global.gr_bootstrap_uri, "mysql://user:pass@host:3306") == 0,
"parse: --bootstrap URI value is correct");
} else {
ok(false, "parse: --bootstrap URI value is correct (was null)");
}
ok(GloVars.global.gr_bootstrap_conf_base_port == 6033,
"parse: --conf-base-port sets gr_bootstrap_conf_base_port");
// Restore
GloVars.__cmd_proxysql_initial = orig_initial;
GloVars.__cmd_proxysql_reload = orig_reload;
GloVars.global.version_check = orig_version_check;
GloVars.global.sqlite3_server = orig_sqlite3;
if (GloVars.__cmd_proxysql_datadir != orig_datadir) {
free(GloVars.__cmd_proxysql_datadir);
GloVars.__cmd_proxysql_datadir = orig_datadir;
}
if (GloVars.global.gr_bootstrap_uri != orig_uri) {
free(GloVars.global.gr_bootstrap_uri);
GloVars.global.gr_bootstrap_uri = orig_uri;
}
GloVars.global.gr_bootstrap_mode = orig_mode;
GloVars.global.gr_bootstrap_conf_base_port = orig_port;
}
/**
* @brief Test process_opts_post() for -n, -f, -M flags.
*/
static void test_parse_post_opts() {
bool orig_nostart = GloVars.global.nostart;
int orig_cmd_nostart = GloVars.__cmd_proxysql_nostart;
bool orig_foreground = GloVars.global.foreground;
bool orig_my_monitor = GloVars.global.my_monitor;
bool orig_pg_monitor = GloVars.global.pg_monitor;
const char *argv[] = { "proxysql", "-n", "-f", "-M" };
int argc = sizeof(argv) / sizeof(argv[0]);
GloVars.parse(argc, argv);
GloVars.process_opts_post();
ok(GloVars.global.nostart == true,
"parse post: -n sets global.nostart");
ok(GloVars.__cmd_proxysql_nostart == 1,
"parse post: -n sets __cmd_proxysql_nostart to 1");
ok(GloVars.global.foreground == true,
"parse post: -f sets global.foreground");
ok(GloVars.global.my_monitor == false,
"parse post: -M disables my_monitor");
ok(GloVars.global.pg_monitor == false,
"parse post: -M disables pg_monitor");
// Restore
GloVars.global.nostart = orig_nostart;
GloVars.__cmd_proxysql_nostart = orig_cmd_nostart;
GloVars.global.foreground = orig_foreground;
GloVars.global.my_monitor = orig_my_monitor;
GloVars.global.pg_monitor = orig_pg_monitor;
}
// ============================================================
// Miscellaneous
// ============================================================
static void test_install_signal_handler() {
GloVars.install_signal_handler();
ok(true, "install_signal_handler: does not crash");
}
static void test_checksum_value_length() {
ok(ProxySQL_Checksum_Value_LENGTH == 20,
"ProxySQL_Checksum_Value_LENGTH is 20");
}
static void test_prometheus_registry() {
ok(GloVars.prometheus_registry != nullptr,
"Constructor: prometheus_registry is initialized");
}
static void test_set_thread_name_default() {
ok(GloVars.set_thread_name == true,
"Constructor: set_thread_name defaults to true");
}
/**
* @brief Test replace_checksum_zeros utility directly.
*/
static void test_replace_checksum_zeros() {
char buf[ProxySQL_Checksum_Value_LENGTH];
// All zeros
memset(buf, 0, sizeof(buf));
replace_checksum_zeros(buf);
ok(buf[0] == '\0', "replace_checksum_zeros: pos 0 not touched (null)");
ok(buf[1] == '\0', "replace_checksum_zeros: pos 1 not touched (null)");
bool inner_ok = true;
for (int i = 2; i < 18; i++) {
if (buf[i] != '0') { inner_ok = false; break; }
}
ok(inner_ok, "replace_checksum_zeros: positions 2-17 replaced with '0'");
ok(buf[18] == '\0', "replace_checksum_zeros: pos 18 not touched");
ok(buf[19] == '\0', "replace_checksum_zeros: pos 19 not touched");
// Mix of spaces and valid chars
memset(buf, 0, sizeof(buf));
buf[0] = 'H';
buf[1] = 'I';
buf[2] = ' ';
buf[3] = 'Z';
buf[17] = ' ';
replace_checksum_zeros(buf);
ok(buf[2] == '0', "replace_checksum_zeros: space at pos 2 replaced");
ok(buf[3] == 'Z', "replace_checksum_zeros: non-zero non-space preserved");
ok(buf[17] == '0', "replace_checksum_zeros: space at pos 17 replaced");
}
int main() {
plan(78);
test_init_minimal();
// Constructor defaults (24 tests)
test_constructor_defaults();
// Checksum value (16 tests)
test_checksum_value_defaults(); // 5
test_checksum_value_set(); // 9
test_checksum_value_shutdown_flag(); // 2
// Checksum value version/epoch (6 tests)
test_checksum_value_version_interaction();
// Checksum value overwrite (3 tests)
test_checksum_value_overwrite();
// Global checksum generation (2 tests)
test_generate_global_checksum();
// Miscellaneous (12 tests)
test_checksum_value_length(); // 1
test_prometheus_registry(); // 1
test_set_thread_name_default(); // 1
test_install_signal_handler(); // 1
test_replace_checksum_zeros(); // 8
// Command-line parsing (15 tests)
test_parse_combined(); // 10
test_parse_post_opts(); // 5
test_cleanup_minimal();
return exit_status();
}