Add unit test harness: Glo* stubs and component init helpers

Introduces the test infrastructure foundation for Phase 2.1 (#5473)
of the unit testing framework (#5472). This enables unit test binaries
to link against libproxysql.a without main.cpp, allowing individual
components to be tested in isolation.

New files:
- test/tap/test_helpers/test_globals.h/.cpp: Stub definitions for all
  25 Glo* global pointers and the PROXYSQL_EXTERN-based variable
  definitions (GloVars, MyHGM, PgHGM, __thread vars, tracked vars).
  Also stubs symbols from src/ (proxy_tls, SQLite3_Server, binary_sha1)
  and TAP noise testing functions.

- test/tap/test_helpers/test_init.h/.cpp: Component initialization
  helpers (test_init_minimal, test_init_auth, test_init_query_cache,
  test_init_query_processor) with matching cleanup functions. All
  are idempotent and safe to call multiple times.
pull/5486/head
René Cannaò 2 months ago
parent 0e955cd4bc
commit 803eb27590

@ -0,0 +1,271 @@
/**
* @file test_globals.cpp
* @brief Stub definitions of ProxySQL global symbols for unit testing.
*
* This file serves as a replacement for both src/proxysql_global.cpp and
* the global variable definitions in src/main.cpp. It allows unit test
* binaries to link against libproxysql.a without pulling in main() or
* the full daemon initialization sequence.
*
* The PROXYSQL_EXTERN mechanism (defined in include/proxysql_structs.h)
* controls whether global variables are declared as 'extern' or defined.
* By defining PROXYSQL_EXTERN here, we cause the headers to emit actual
* definitions for:
* - GloVars (ProxySQL_GlobalVariables)
* - MyHGM, PgHGM (HostGroups Manager pointers)
* - glovars (global_variables struct)
* - All __thread per-thread variables (mysql_thread_*, pgsql_thread_*)
* - mysql_tracked_variables[], pgsql_tracked_variables[]
*
* Additionally, this file defines the Glo* pointers that are normally
* declared in main.cpp (GloMyQC, GloMyAuth, GloAdmin, GloMTH, etc.),
* all initialized to nullptr.
*
* @see test_globals.h for the public interface.
* @see Phase 2.1 of the Unit Testing Framework (GitHub issue #5473)
*/
// Define PROXYSQL_EXTERN before including headers to emit global
// variable definitions (same mechanism as src/proxysql_global.cpp).
#define PROXYSQL_EXTERN
#include "../deps/json/json.hpp"
using json = nlohmann::json;
#define PROXYJSON
#include <iostream>
#include <thread>
#include <atomic>
#include "btree_map.h"
#include "proxysql.h"
#include "cpp.h"
#include "ProxySQL_Statistics.hpp"
#include "MySQL_PreparedStatement.h"
#include "PgSQL_PreparedStatement.h"
#include "ProxySQL_Cluster.hpp"
#include "MySQL_Logger.hpp"
#include "PgSQL_Logger.hpp"
#ifdef PROXYSQLGENAI
#include "MCP_Thread.h"
#include "GenAI_Thread.h"
#include "AI_Features_Manager.h"
#endif /* PROXYSQLGENAI */
#include "SQLite3_Server.h"
#include "MySQL_Query_Processor.h"
#include "PgSQL_Query_Processor.h"
#include "MySQL_Authentication.hpp"
#include "PgSQL_Authentication.h"
#include "MySQL_LDAP_Authentication.hpp"
#include "MySQL_Query_Cache.h"
#include "PgSQL_Query_Cache.h"
#include "proxysql_restapi.h"
#include "Web_Interface.hpp"
#include "proxysql_utils.h"
#include "test_globals.h"
// ============================================================================
// Glo* pointer stubs — normally defined in src/main.cpp
//
// These are the global singleton pointers that most ProxySQL classes
// reference directly. For unit tests, they start as nullptr and are
// selectively initialized by the test_init_*() helpers.
// ============================================================================
MySQL_Query_Cache *GloMyQC = nullptr;
PgSQL_Query_Cache *GloPgQC = nullptr;
MySQL_Authentication *GloMyAuth = nullptr;
PgSQL_Authentication *GloPgAuth = nullptr;
MySQL_LDAP_Authentication *GloMyLdapAuth = nullptr;
#ifdef PROXYSQLCLICKHOUSE
ClickHouse_Authentication *GloClickHouseAuth = nullptr;
#endif /* PROXYSQLCLICKHOUSE */
MySQL_Query_Processor *GloMyQPro = nullptr;
PgSQL_Query_Processor *GloPgQPro = nullptr;
ProxySQL_Admin *GloAdmin = nullptr;
MySQL_Threads_Handler *GloMTH = nullptr;
PgSQL_Threads_Handler *GloPTH = nullptr;
#ifdef PROXYSQLGENAI
MCP_Threads_Handler *GloMCPH = nullptr;
GenAI_Threads_Handler *GloGATH = nullptr;
AI_Features_Manager *GloAI = nullptr;
#endif /* PROXYSQLGENAI */
Web_Interface *GloWebInterface = nullptr;
MySQL_STMT_Manager_v14 *GloMyStmt = nullptr;
PgSQL_STMT_Manager *GloPgStmt = nullptr;
MySQL_Monitor *GloMyMon = nullptr;
PgSQL_Monitor *GloPgMon = nullptr;
std::thread *MyMon_thread = nullptr;
MySQL_Logger *GloMyLogger = nullptr;
PgSQL_Logger *GloPgSQL_Logger = nullptr;
MySQL_Variables mysql_variables;
PgSQL_Variables pgsql_variables;
SQLite3_Server *GloSQLite3Server = nullptr;
#ifdef PROXYSQLCLICKHOUSE
ClickHouse_Server *GloClickHouseServer = nullptr;
#endif /* PROXYSQLCLICKHOUSE */
ProxySQL_Cluster *GloProxyCluster = nullptr;
ProxySQL_Statistics *GloProxyStats = nullptr;
// ============================================================================
// TAP noise testing stubs — normally defined in noise_utils.cpp.
// Unit tests do not use noise testing, so these are empty stubs to
// satisfy the extern references in tap.cpp.
// ============================================================================
std::vector<std::string> noise_failures;
std::mutex noise_failure_mutex;
// ============================================================================
// Other symbols from main.cpp
// ============================================================================
/// Atomic load counter used during daemon startup. Unused in tests.
std::atomic<int> load_{0};
/// File descriptors for the proxy listener sockets. Unused in tests.
int listen_fd = -1;
int socket_fd = -1;
/// SHA1 checksum of the binary. Unused in tests.
char *binary_sha1 = nullptr;
// ============================================================================
// Stub functions for symbols referenced by libproxysql.a that are
// normally defined in src/ object files (proxy_tls.o, SQLite3_Server.o).
// These are no-ops since unit tests don't exercise TLS bootstrap or
// the SQLite3 server module.
// ============================================================================
#include "SQLite3_Server.h"
int ProxySQL_create_or_load_TLS(bool, std::string &) { return 0; }
char *SQLite3_Server::get_variable(char *) { return nullptr; }
bool SQLite3_Server::has_variable(const char *) { return false; }
bool SQLite3_Server::set_variable(char *, char *) { return false; }
char **SQLite3_Server::get_variables_list() { return nullptr; }
void SQLite3_Server::wrlock() {}
void SQLite3_Server::wrunlock() {}
// ============================================================================
// TAP noise testing stubs — exit_status() in tap.cpp calls these.
// Normally defined in noise_utils.cpp (test/tap/tap/utils.cpp).
// ============================================================================
extern "C" void stop_noise_tools() {}
extern "C" int get_noise_tools_count() { return 0; }
/// jemalloc configuration string. Required at link time on non-FreeBSD.
#ifndef __FreeBSD__
const char *malloc_conf =
"xmalloc:true,lg_tcache_max:16,prof:false";
#endif
// ============================================================================
// ProxySQL_GlobalVariables SSL helpers (from src/proxysql_global.cpp)
//
// These methods access GloVars.global.ssl_ctx which will be nullptr
// in tests. The stubs return nullptr/noop to avoid crashes.
// ============================================================================
SSL_CTX *ProxySQL_GlobalVariables::get_SSL_ctx() {
std::lock_guard<std::mutex> lock(global.ssl_mutex);
return global.ssl_ctx;
}
SSL *ProxySQL_GlobalVariables::get_SSL_new() {
std::lock_guard<std::mutex> lock(global.ssl_mutex);
if (global.ssl_ctx == nullptr) return nullptr;
return SSL_new(global.ssl_ctx);
}
void ProxySQL_GlobalVariables::get_SSL_pem_mem(char **key, char **cert) {
std::lock_guard<std::mutex> lock(global.ssl_mutex);
if (global.ssl_key_pem_mem != nullptr)
*key = strdup(global.ssl_key_pem_mem);
else
*key = nullptr;
if (global.ssl_cert_pem_mem != nullptr)
*cert = strdup(global.ssl_cert_pem_mem);
else
*cert = nullptr;
}
// ============================================================================
// test_globals_init / test_globals_cleanup
// ============================================================================
/**
* @brief Sets up GloVars with minimal safe defaults for unit testing.
*
* Configures GloVars to use a temporary directory as datadir, disables
* SSL, monitoring, and other features that require infrastructure.
* This function is idempotent.
*
* @return 0 on success, non-zero on failure.
*/
int test_globals_init() {
// Set safe defaults for the global configuration
GloVars.global.nostart = true;
GloVars.global.foreground = true;
GloVars.global.gdbg = false;
GloVars.global.my_monitor = false;
GloVars.global.pg_monitor = false;
GloVars.global.version_check = false;
GloVars.global.sqlite3_server = false;
#ifdef PROXYSQLCLICKHOUSE
GloVars.global.clickhouse_server = false;
#endif
GloVars.global.ssl_keylog_enabled = false;
GloVars.global.gr_bootstrap_mode = 0;
GloVars.global.gr_bootstrap_uri = nullptr;
GloVars.global.data_packets_history_size = 0;
// SSL pointers — nullptr means no SSL
GloVars.global.ssl_ctx = nullptr;
GloVars.global.tmp_ssl_ctx = nullptr;
GloVars.global.ssl_key_pem_mem = nullptr;
GloVars.global.ssl_cert_pem_mem = nullptr;
// File paths — use a temp directory so tests don't touch real data.
// These are strdup'd so cleanup can safely free() them.
const char *tmpdir = getenv("TMPDIR");
if (tmpdir == nullptr) tmpdir = "/tmp";
char datadir_buf[256];
snprintf(datadir_buf, sizeof(datadir_buf), "%s/proxysql_unit_test_%d",
tmpdir, getpid());
if (GloVars.datadir == nullptr) {
GloVars.datadir = strdup(datadir_buf);
}
return 0;
}
/**
* @brief Frees resources allocated by test_globals_init().
*
* Safe to call multiple times or even if test_globals_init() was never called.
*/
void test_globals_cleanup() {
if (GloVars.datadir != nullptr) {
free(GloVars.datadir);
GloVars.datadir = nullptr;
}
}

@ -0,0 +1,46 @@
/**
* @file test_globals.h
* @brief Stub global definitions for ProxySQL unit tests.
*
* This header is the entry point for unit tests that need to link against
* libproxysql.a without the real main.cpp. It provides:
*
* 1. All Glo* pointer stubs (initialized to nullptr)
* 2. GloVars (ProxySQL_GlobalVariables) with safe defaults
* 3. All __thread variable definitions via the PROXYSQL_EXTERN mechanism
* 4. Other extern symbols normally provided by main.cpp
*
* Usage in test files:
* @code
* #include "test_globals.h"
* #include "test_init.h"
* // ... test code ...
* @endcode
*
* @note This file must NOT be included by production code.
* @see test_globals.cpp for the corresponding definitions.
* @see Phase 2.1 of the Unit Testing Framework (GitHub issue #5473)
*/
#ifndef TEST_GLOBALS_H
#define TEST_GLOBALS_H
/**
* @brief Initialize minimal global state required for unit tests.
*
* Sets up GloVars with safe defaults (tmpdir-based datadir, no SSL,
* no pidfile). Must be called before any component initialization.
*
* @return 0 on success, non-zero on failure.
*/
int test_globals_init();
/**
* @brief Clean up global state allocated by test_globals_init().
*
* Frees any memory allocated during initialization. Safe to call
* multiple times.
*/
void test_globals_cleanup();
#endif /* TEST_GLOBALS_H */

@ -0,0 +1,129 @@
/**
* @file test_init.cpp
* @brief Implementation of component initialization helpers for unit tests.
*
* Each test_init_*() function creates real instances of ProxySQL components,
* bypassing the full daemon startup sequence. Components are assigned to
* their respective Glo* global pointers so that internal cross-references
* work correctly.
*
* @see test_init.h for the public interface and usage examples.
* @see Phase 2.1 of the Unit Testing Framework (GitHub issue #5473)
*/
#include "proxysql.h"
#include "cpp.h"
#include "MySQL_Authentication.hpp"
#include "PgSQL_Authentication.h"
#include "MySQL_Query_Cache.h"
#include "PgSQL_Query_Cache.h"
#include "MySQL_Query_Processor.h"
#include "PgSQL_Query_Processor.h"
#include "test_globals.h"
#include "test_init.h"
// Extern declarations for Glo* pointers defined in test_globals.cpp.
// These are normally defined in main.cpp and have no header declarations.
extern MySQL_Authentication *GloMyAuth;
extern PgSQL_Authentication *GloPgAuth;
extern MySQL_Query_Cache *GloMyQC;
extern PgSQL_Query_Cache *GloPgQC;
extern MySQL_Query_Processor *GloMyQPro;
extern PgSQL_Query_Processor *GloPgQPro;
// ============================================================================
// Minimal initialization
// ============================================================================
int test_init_minimal() {
return test_globals_init();
}
void test_cleanup_minimal() {
test_globals_cleanup();
}
// ============================================================================
// Authentication
// ============================================================================
int test_init_auth() {
if (GloMyAuth != nullptr || GloPgAuth != nullptr) {
// Already initialized — idempotent
return 0;
}
GloMyAuth = new MySQL_Authentication();
GloPgAuth = new PgSQL_Authentication();
return 0;
}
void test_cleanup_auth() {
if (GloMyAuth != nullptr) {
delete GloMyAuth;
GloMyAuth = nullptr;
}
if (GloPgAuth != nullptr) {
delete GloPgAuth;
GloPgAuth = nullptr;
}
}
// ============================================================================
// Query Cache
// ============================================================================
int test_init_query_cache() {
if (GloMyQC != nullptr || GloPgQC != nullptr) {
return 0;
}
GloMyQC = new MySQL_Query_Cache();
GloPgQC = new PgSQL_Query_Cache();
// NOTE: We intentionally do NOT start the purge thread here.
// Unit tests should call purgeHash() explicitly for deterministic
// behavior.
return 0;
}
void test_cleanup_query_cache() {
if (GloMyQC != nullptr) {
delete GloMyQC;
GloMyQC = nullptr;
}
if (GloPgQC != nullptr) {
delete GloPgQC;
GloPgQC = nullptr;
}
}
// ============================================================================
// Query Processor
// ============================================================================
int test_init_query_processor() {
if (GloMyQPro != nullptr || GloPgQPro != nullptr) {
return 0;
}
GloMyQPro = new MySQL_Query_Processor();
GloPgQPro = new PgSQL_Query_Processor();
return 0;
}
void test_cleanup_query_processor() {
if (GloMyQPro != nullptr) {
delete GloMyQPro;
GloMyQPro = nullptr;
}
if (GloPgQPro != nullptr) {
delete GloPgQPro;
GloPgQPro = nullptr;
}
}

@ -0,0 +1,112 @@
/**
* @file test_init.h
* @brief Component initialization helpers for ProxySQL unit tests.
*
* Provides functions to selectively initialize individual ProxySQL
* components for isolated testing, without requiring the full daemon
* startup sequence. Each init function has a matching cleanup function
* that frees all resources.
*
* Typical usage:
* @code
* #include "test_globals.h"
* #include "test_init.h"
* #include "tap.h"
*
* int main() {
* plan(3);
* test_init_minimal();
* test_init_auth();
*
* // ... run tests against GloMyAuth ...
*
* test_cleanup_auth();
* test_cleanup_minimal();
* return exit_status();
* }
* @endcode
*
* @note All init functions are idempotent calling them multiple
* times is safe (subsequent calls are no-ops).
* @note Always call cleanup functions in reverse init order.
*
* @see test_globals.h for the global stub definitions.
* @see Phase 2.1 of the Unit Testing Framework (GitHub issue #5473)
*/
#ifndef TEST_INIT_H
#define TEST_INIT_H
/**
* @brief Initialize minimal global state required by all unit tests.
*
* Sets up GloVars with safe defaults. This is the foundation that all
* other test_init_* functions build upon. Must be called first.
*
* @return 0 on success, non-zero on failure.
*/
int test_init_minimal();
/**
* @brief Clean up resources allocated by test_init_minimal().
*/
void test_cleanup_minimal();
/**
* @brief Initialize MySQL and PostgreSQL Authentication components.
*
* Creates real MySQL_Authentication and PgSQL_Authentication objects
* (assigned to GloMyAuth and GloPgAuth). The auth stores are empty
* and ready for test data via add()/lookup()/del().
*
* @pre test_init_minimal() must have been called.
* @return 0 on success, non-zero on failure.
*/
int test_init_auth();
/**
* @brief Clean up resources allocated by test_init_auth().
*
* Destroys GloMyAuth and GloPgAuth, setting them back to nullptr.
*/
void test_cleanup_auth();
/**
* @brief Initialize MySQL and PostgreSQL Query Cache components.
*
* Creates real MySQL_Query_Cache and PgSQL_Query_Cache objects
* (assigned to GloMyQC and GloPgQC). The purge thread is NOT started;
* callers can invoke purgeHash() manually for deterministic testing.
*
* @pre test_init_minimal() must have been called.
* @return 0 on success, non-zero on failure.
*/
int test_init_query_cache();
/**
* @brief Clean up resources allocated by test_init_query_cache().
*
* Destroys GloMyQC and GloPgQC, setting them back to nullptr.
*/
void test_cleanup_query_cache();
/**
* @brief Initialize MySQL and PostgreSQL Query Processor components.
*
* Creates real MySQL_Query_Processor and PgSQL_Query_Processor objects
* (assigned to GloMyQPro and GloPgQPro) with empty rulesets. Rules
* can be added via new_query_rule() for testing.
*
* @pre test_init_minimal() must have been called.
* @return 0 on success, non-zero on failure.
*/
int test_init_query_processor();
/**
* @brief Clean up resources allocated by test_init_query_processor().
*
* Destroys GloMyQPro and GloPgQPro, setting them back to nullptr.
*/
void test_cleanup_query_processor();
#endif /* TEST_INIT_H */
Loading…
Cancel
Save