diff --git a/test/tap/tests/unit/Makefile b/test/tap/tests/unit/Makefile new file mode 100644 index 000000000..5e3d0dde2 --- /dev/null +++ b/test/tap/tests/unit/Makefile @@ -0,0 +1,263 @@ +#!/bin/make -f +# +# Makefile for ProxySQL unit tests. +# +# Unit tests link against libproxysql.a with stub globals (test_globals.o) +# instead of main.o, allowing individual components to be tested in +# isolation without a running ProxySQL daemon or backend servers. +# +# See: GitHub issue #5473 (Phase 2.1: Test Infrastructure Foundation) + + +PROXYSQL_PATH := $(shell while [ ! -f ./src/proxysql_global.cpp ]; do cd ..; done; pwd) + +include $(PROXYSQL_PATH)/include/makefiles_vars.mk +include $(PROXYSQL_PATH)/include/makefiles_paths.mk + + +# =========================================================================== +# Include directories — mirrors test/tap/tests/Makefile +# =========================================================================== + +IDIRS := -I$(TAP_IDIR) \ + -I$(RE2_IDIR) \ + -I$(PROXYSQL_IDIR) \ + -I$(JEMALLOC_IDIR) \ + -I$(LIBCONFIG_IDIR) \ + -I$(MARIADB_IDIR) \ + -I$(LIBDAEMON_IDIR) \ + -I$(MICROHTTPD_IDIR) \ + -I$(LIBHTTPSERVER_IDIR) \ + -I$(CURL_IDIR) -I$(EV_IDIR) \ + -I$(PROMETHEUS_IDIR) \ + -I$(DOTENV_DYN_IDIR) \ + -I$(SQLITE3_IDIR) \ + -I$(JSON_IDIR) \ + -I$(POSTGRESQL_IDIR) \ + -I$(LIBSCRAM_IDIR) \ + -I$(LIBUSUAL_IDIR) \ + -I$(SSL_IDIR) \ + -I$(ZSTD_IDIR) \ + -I$(PROXYSQL_PATH)/include \ + -I$(PROXYSQL_PATH)/test/tap/test_helpers + + +# =========================================================================== +# Library directories +# =========================================================================== + +LDIRS := -L$(TAP_LDIR) \ + -L$(RE2_LDIR) \ + -L$(PROXYSQL_LDIR) \ + -L$(JEMALLOC_LDIR) \ + -L$(LIBCONFIG_LDIR) \ + -L$(MARIADB_LDIR) \ + -L$(LIBDAEMON_LDIR) \ + -L$(MICROHTTPD_LDIR) \ + -L$(LIBHTTPSERVER_LDIR) \ + -L$(CURL_LDIR) -L$(EV_LDIR) \ + -L$(PROMETHEUS_LDIR) \ + -L$(DOTENV_DYN_LDIR) \ + -L$(PCRE_LDIR) \ + -L$(LIBINJECTION_LDIR) \ + -L$(POSTGRESQL_LDIR) \ + -L$(LIBSCRAM_LDIR) \ + -L$(LIBUSUAL_LDIR) \ + -L$(SSL_LDIR) + +ifeq ($(UNAME_S),Linux) + LDIRS += -L$(COREDUMPER_LDIR) +endif +ifeq ($(UNAME_S),Darwin) + IDIRS += -I/usr/local/include -I/opt/homebrew/include + LDIRS += -L/usr/local/lib -L/opt/homebrew/lib +endif + + +# =========================================================================== +# ClickHouse include/link paths (enabled by default) +# =========================================================================== + +CLICKHOUSE_CPP_PATH := $(DEPS_PATH)/clickhouse-cpp/clickhouse-cpp +CLICKHOUSE_CPP_IDIR := $(CLICKHOUSE_CPP_PATH) -I$(CLICKHOUSE_CPP_PATH)/contrib/absl +CLICKHOUSE_CPP_LDIR := $(CLICKHOUSE_CPP_PATH)/clickhouse +LZ4_LDIR := $(DEPS_PATH)/lz4/lz4/lib + +IDIRS += -I$(CLICKHOUSE_CPP_IDIR) + + +# =========================================================================== +# libproxysql.a — the core library under test +# =========================================================================== + +LIBPROXYSQLAR := $(PROXYSQL_LDIR)/libproxysql.a + + +# =========================================================================== +# Static libraries required at link time +# =========================================================================== + +STATIC_LIBS := $(CITYHASH_LDIR)/libcityhash.a \ + $(LZ4_LDIR)/liblz4.a \ + $(ZSTD_LDIR)/libzstd.a + +ifeq ($(PROXYSQLCLICKHOUSE),1) + STATIC_LIBS += $(CLICKHOUSE_CPP_LDIR)/libclickhouse-cpp-lib.a +endif + +ifeq ($(UNAME_S),Linux) + STATIC_LIBS += $(COREDUMPER_LDIR)/libcoredumper.a +endif + +ifeq ($(PROXYSQLGENAI),1) + STATIC_LIBS += $(SQLITE3_LDIR)/../libsqlite_rembed.a $(SQLITE3_LDIR)/vec.o +endif + + +# =========================================================================== +# Linker flags — platform-specific +# =========================================================================== + +ifeq ($(UNAME_S),Darwin) +# macOS: No -Bstatic/-Bdynamic; use explicit .a paths for static linking. +# libproxysql.a already bundles most deps on Darwin (see src/Makefile). +LIBPROXYSQLAR_FULL := $(LIBPROXYSQLAR) \ + $(JEMALLOC_LDIR)/libjemalloc.a \ + $(MICROHTTPD_LDIR)/libmicrohttpd.a \ + $(LIBHTTPSERVER_LDIR)/libhttpserver.a \ + $(PCRE_LDIR)/libpcre.a \ + $(PCRE_LDIR)/libpcrecpp.a \ + $(LIBDAEMON_LDIR)/libdaemon.a \ + $(LIBCONFIG_LDIR)/libconfig++.a \ + $(LIBCONFIG_LDIR)/libconfig.a \ + $(CURL_LDIR)/libcurl.a \ + $(SQLITE3_LDIR)/sqlite3.o \ + $(LIBINJECTION_LDIR)/libinjection.a \ + $(EV_LDIR)/libev.a \ + $(LIBSCRAM_LDIR)/libscram.a \ + $(LIBUSUAL_LDIR)/libusual.a \ + $(MARIADB_LDIR)/libmariadbclient.a \ + $(RE2_LDIR)/libre2.a \ + $(POSTGRESQL_PATH)/interfaces/libpq/libpq.a \ + $(POSTGRESQL_PATH)/common/libpgcommon.a \ + $(POSTGRESQL_PATH)/port/libpgport.a + +MYLIBS := -lssl -lcrypto -lpthread -lm -lz \ + -liconv -lgnutls -lprometheus-cpp-pull -lprometheus-cpp-core -luuid \ + -lzstd $(LWGCOV) +else +# Linux/FreeBSD: Use -Bstatic/-Bdynamic for controlled linking. +LIBPROXYSQLAR_FULL := $(LIBPROXYSQLAR) + +MYLIBS := -Wl,--export-dynamic -Wl,-Bdynamic -lgnutls -lcurl -lssl -lcrypto -luuid \ + -Wl,-Bstatic -lconfig -lproxysql -ldaemon -lconfig++ -lre2 -lpcrecpp -lpcre \ + -lmariadbclient -lhttpserver -lmicrohttpd -linjection -lev \ + -lprometheus-cpp-pull -lprometheus-cpp-core \ + -Wl,-Bstatic -lpq -lpgcommon -lpgport \ + -Wl,-Bdynamic -lpthread -lm -lz -lzstd -lrt -ldl \ + -lscram -lusual -Wl,--allow-multiple-definition \ + $(LWGCOV) +endif + +ifneq ($(NOJEMALLOC),1) +ifeq ($(UNAME_S),Linux) + MYLIBS += -Wl,-Bstatic -ljemalloc -Wl,-Bdynamic +endif +endif + + +# =========================================================================== +# Compiler flags +# =========================================================================== + +PSQLCH := +ifeq ($(PROXYSQLCLICKHOUSE),1) + PSQLCH := -DPROXYSQLCLICKHOUSE +endif +PSQLGA := +ifeq ($(PROXYSQLGENAI),1) + PSQLGA := -DPROXYSQLGENAI +endif +PSQL31 := +ifeq ($(PROXYSQL31),1) + PSQL31 := -DPROXYSQL31 +endif +PSQLFFTO := +ifeq ($(PROXYSQLFFTO),1) + PSQLFFTO := -DPROXYSQLFFTO +endif +PSQLTSDB := +ifeq ($(PROXYSQLTSDB),1) + PSQLTSDB := -DPROXYSQLTSDB +endif + +OPT := $(STDCPP) -O0 -ggdb $(PSQLCH) $(PSQLGA) $(PSQL31) $(PSQLFFTO) $(PSQLTSDB) \ + -DGITVERSION=\"$(GIT_VERSION)\" $(NOJEM) $(WGCOV) $(WASAN) \ + -Wl,--no-as-needed -Wl,-rpath,$(TAP_LDIR) + +ifeq ($(UNAME_S),Darwin) + OPT := $(STDCPP) -O0 -ggdb $(PSQLCH) $(PSQLGA) $(PSQL31) $(PSQLFFTO) $(PSQLTSDB) \ + -DGITVERSION=\"$(GIT_VERSION)\" $(NOJEM) $(WGCOV) $(WASAN) +endif + + +# =========================================================================== +# Test helper objects +# =========================================================================== + +TEST_HELPERS_DIR := $(PROXYSQL_PATH)/test/tap/test_helpers +ODIR := obj + +TEST_HELPERS_OBJ := $(ODIR)/test_globals.o $(ODIR)/test_init.o $(ODIR)/tap.o + +$(ODIR): + mkdir -p $(ODIR) + +# Compile tap.o directly from tap.cpp to avoid the full TAP build chain +# and its cpp-dotenv dependency (which doesn't build on macOS). +# Unit tests only need the core TAP functions: plan(), ok(), is(), etc. +TAP_SRC := $(TAP_PATH)/tap.cpp +$(ODIR)/tap.o: $(TAP_SRC) | $(ODIR) + $(CXX) -c -o $@ $< $(OPT) $(IDIRS) -w + +$(ODIR)/test_globals.o: $(TEST_HELPERS_DIR)/test_globals.cpp | $(ODIR) + $(CXX) -c -o $@ $< $(OPT) $(IDIRS) -Wall + +$(ODIR)/test_init.o: $(TEST_HELPERS_DIR)/test_init.cpp | $(ODIR) + $(CXX) -c -o $@ $< $(OPT) $(IDIRS) -Wall + + +# =========================================================================== +# Unit test targets +# =========================================================================== + +UNIT_TESTS := smoke_test-t + +.DEFAULT: default +.PHONY: default debug all + +default: all +debug: OPT += -DDEBUG +debug: all + +all: $(UNIT_TESTS) + +ALLOW_MULTI_DEF := +ifneq ($(UNAME_S),Darwin) + ALLOW_MULTI_DEF := -Wl,--allow-multiple-definition +endif + +smoke_test-t: smoke_test-t.cpp $(TEST_HELPERS_OBJ) $(LIBPROXYSQLAR) + $(CXX) $< $(TEST_HELPERS_OBJ) $(IDIRS) $(LDIRS) $(OPT) \ + $(LIBPROXYSQLAR_FULL) $(STATIC_LIBS) $(MYLIBS) \ + $(ALLOW_MULTI_DEF) -o $@ + + +# =========================================================================== +# Clean +# =========================================================================== + +.PHONY: clean +.SILENT: clean +clean: + rm -rf $(ODIR) $(UNIT_TESTS) diff --git a/test/tap/tests/unit/smoke_test-t.cpp b/test/tap/tests/unit/smoke_test-t.cpp new file mode 100644 index 000000000..5982b8c9d --- /dev/null +++ b/test/tap/tests/unit/smoke_test-t.cpp @@ -0,0 +1,111 @@ +/** + * @file smoke_test-t.cpp + * @brief Smoke test for the ProxySQL unit test harness. + * + * Validates that the test infrastructure (test_globals + test_init) + * works correctly by performing minimal operations on each supported + * component. This test must pass before any component-specific unit + * tests can be trusted. + * + * Test coverage: + * 1. test_init_minimal() — GloVars is usable + * 2. test_init_auth() — MySQL_Authentication add/lookup cycle + * 3. test_cleanup_*() — clean shutdown without leaks + * + * @see Phase 2.1 of the Unit Testing Framework (GitHub issue #5473) + */ + +#include "tap.h" +#include "test_globals.h" +#include "test_init.h" + +#include "proxysql.h" +#include "MySQL_Authentication.hpp" +#include "PgSQL_Authentication.h" + +// Extern declarations for Glo* pointers (defined in test_globals.cpp) +extern MySQL_Authentication *GloMyAuth; +extern PgSQL_Authentication *GloPgAuth; + +/** + * @brief Test that minimal initialization sets up GloVars correctly. + */ +static void test_minimal_init() { + int rc = test_init_minimal(); + ok(rc == 0, "test_init_minimal() returns 0"); + ok(GloVars.datadir != nullptr, "GloVars.datadir is set after init"); + ok(GloVars.global.nostart == true, "GloVars.global.nostart is true"); +} + +/** + * @brief Test MySQL_Authentication add/lookup/del cycle. + */ +static void test_mysql_auth_basic() { + int rc = test_init_auth(); + ok(rc == 0, "test_init_auth() returns 0"); + ok(GloMyAuth != nullptr, "GloMyAuth is initialized"); + ok(GloPgAuth != nullptr, "GloPgAuth is initialized"); + + // Add a frontend user + bool added = GloMyAuth->add( + (char *)"testuser", // username + (char *)"testpass", // password + USERNAME_FRONTEND, // user type + false, // use_ssl + 0, // default_hostgroup + (char *)"", // default_schema + false, // schema_locked + false, // transaction_persistent + false, // fast_forward + 100, // max_connections + (char *)"", // attributes + (char *)"" // comment + ); + ok(added == true, "GloMyAuth->add() succeeds for frontend user"); + + // Verify user exists + bool exists = GloMyAuth->exists((char *)"testuser"); + ok(exists == true, "GloMyAuth->exists() returns true for added user"); + + // Verify user does not exist + bool not_exists = GloMyAuth->exists((char *)"nonexistent"); + ok(not_exists == false, "GloMyAuth->exists() returns false for unknown user"); + + // Cleanup + test_cleanup_auth(); + ok(GloMyAuth == nullptr, "GloMyAuth is nullptr after cleanup"); + ok(GloPgAuth == nullptr, "GloPgAuth is nullptr after cleanup"); +} + +/** + * @brief Test idempotency of init/cleanup functions. + */ +static void test_idempotency() { + // Double init should be safe + int rc1 = test_init_minimal(); + int rc2 = test_init_minimal(); + ok(rc1 == 0 && rc2 == 0, "test_init_minimal() is idempotent"); + + int rc3 = test_init_auth(); + int rc4 = test_init_auth(); + ok(rc3 == 0 && rc4 == 0, "test_init_auth() is idempotent"); + + // Double cleanup should be safe + test_cleanup_auth(); + test_cleanup_auth(); // should not crash + ok(1, "test_cleanup_auth() double-call does not crash"); + + test_cleanup_minimal(); + test_cleanup_minimal(); // should not crash + ok(1, "test_cleanup_minimal() double-call does not crash"); +} + +int main() { + plan(15); + + test_minimal_init(); + test_mysql_auth_basic(); + test_idempotency(); + + return exit_status(); +}