/** * @file genai_module-t.cpp * @brief TAP test for the GenAI module * * This test verifies the functionality of the GenAI (Generative AI) module in ProxySQL. * It tests: * - LOAD/SAVE commands for GenAI variables across all variants * - Variable access (SET and SELECT) for genai-var1 and genai-var2 * - Variable persistence across storage layers (memory, disk, runtime) * - CHECKSUM commands for GenAI variables * - SHOW VARIABLES for GenAI module * * @date 2025-01-08 */ #include #include #include #include #include #include #include #include "mysql.h" #include "mysqld_error.h" #include "tap.h" #include "command_line.h" #include "utils.h" using std::string; /** * @brief Helper function to add LOAD/SAVE command variants for GenAI module * * This function generates all the standard LOAD/SAVE command variants that * ProxySQL supports for module variables. It follows the same pattern used * for other modules like MYSQL, PGSQL, etc. * * @param queries Vector to append the generated commands to */ void add_genai_load_save_commands(std::vector& queries) { // LOAD commands - Memory variants queries.push_back("LOAD GENAI VARIABLES TO MEMORY"); queries.push_back("LOAD GENAI VARIABLES TO MEM"); // LOAD from disk queries.push_back("LOAD GENAI VARIABLES FROM DISK"); // LOAD from memory queries.push_back("LOAD GENAI VARIABLES FROM MEMORY"); queries.push_back("LOAD GENAI VARIABLES FROM MEM"); // LOAD to runtime queries.push_back("LOAD GENAI VARIABLES TO RUNTIME"); queries.push_back("LOAD GENAI VARIABLES TO RUN"); // SAVE from memory queries.push_back("SAVE GENAI VARIABLES FROM MEMORY"); queries.push_back("SAVE GENAI VARIABLES FROM MEM"); // SAVE to disk queries.push_back("SAVE GENAI VARIABLES TO DISK"); // SAVE to memory queries.push_back("SAVE GENAI VARIABLES TO MEMORY"); queries.push_back("SAVE GENAI VARIABLES TO MEM"); // SAVE from runtime queries.push_back("SAVE GENAI VARIABLES FROM RUNTIME"); queries.push_back("SAVE GENAI VARIABLES FROM RUN"); } /** * @brief Get the value of a GenAI variable as a string * * @param admin MySQL connection to admin interface * @param var_name Variable name (without genai- prefix) * @return std::string The variable value, or empty string on error */ std::string get_genai_variable(MYSQL* admin, const std::string& var_name) { std::string query = "SELECT @@genai-" + var_name; if (mysql_query(admin, query.c_str()) != 0) { return ""; } MYSQL_RES* res = mysql_store_result(admin); if (!res) { return ""; } MYSQL_ROW row = mysql_fetch_row(res); std::string value = row && row[0] ? row[0] : ""; mysql_free_result(res); return value; } /** * @brief Test variable access operations (SET and SELECT) * * Tests setting and retrieving GenAI variables to ensure they work correctly. */ int test_variable_access(MYSQL* admin) { int test_num = 0; // Test 1: Get default value of genai-var1 std::string var1_default = get_genai_variable(admin, "var1"); ok(var1_default == "default_value_1", "Default value of genai-var1 is 'default_value_1', got '%s'", var1_default.c_str()); // Test 2: Get default value of genai-var2 std::string var2_default = get_genai_variable(admin, "var2"); ok(var2_default == "100", "Default value of genai-var2 is '100', got '%s'", var2_default.c_str()); // Test 3: Set genai-var1 to a new value MYSQL_QUERY(admin, "SET genai-var1='test_value_123'"); std::string var1_new = get_genai_variable(admin, "var1"); ok(var1_new == "test_value_123", "After SET, genai-var1 is 'test_value_123', got '%s'", var1_new.c_str()); // Test 4: Set genai-var2 to a new integer value MYSQL_QUERY(admin, "SET genai-var2=42"); std::string var2_new = get_genai_variable(admin, "var2"); ok(var2_new == "42", "After SET, genai-var2 is '42', got '%s'", var2_new.c_str()); // Test 5: Set genai-var1 with special characters MYSQL_QUERY(admin, "SET genai-var1='test with spaces'"); std::string var1_special = get_genai_variable(admin, "var1"); ok(var1_special == "test with spaces", "genai-var1 with spaces is 'test with spaces', got '%s'", var1_special.c_str()); // Test 6: Verify SHOW VARIABLES LIKE pattern MYSQL_QUERY(admin, "SHOW VARIABLES LIKE 'genai-%'"); MYSQL_RES* res = mysql_store_result(admin); int num_rows = mysql_num_rows(res); ok(num_rows == 2, "SHOW VARIABLES LIKE 'genai-%%' returns 2 rows, got %d", num_rows); mysql_free_result(res); // Test 7: Restore default values MYSQL_QUERY(admin, "SET genai-var1='default_value_1'"); MYSQL_QUERY(admin, "SET genai-var2=100"); ok(1, "Restored default values for genai-var1 and genai-var2"); return test_num; } /** * @brief Test variable persistence across storage layers * * Tests that variables are correctly copied between: * - Memory (main.global_variables) * - Disk (disk.global_variables) * - Runtime (GloGATH handler object) */ int test_variable_persistence(MYSQL* admin) { int test_num = 0; // Test 1: Set values and save to disk diag("Testing variable persistence: Set values, save to disk, modify, load from disk"); MYSQL_QUERY(admin, "SET genai-var1='disk_test_value'"); MYSQL_QUERY(admin, "SET genai-var2=999"); MYSQL_QUERY(admin, "SAVE GENAI VARIABLES TO DISK"); ok(1, "Set genai-var1='disk_test_value', genai-var2=999 and saved to disk"); // Test 2: Modify values in memory MYSQL_QUERY(admin, "SET genai-var1='memory_value'"); MYSQL_QUERY(admin, "SET genai-var2=111"); std::string var1_mem = get_genai_variable(admin, "var1"); std::string var2_mem = get_genai_variable(admin, "var2"); ok(var1_mem == "memory_value" && var2_mem == "111", "Modified in memory: genai-var1='memory_value', genai-var2='111'"); // Test 3: Load from disk and verify original values restored MYSQL_QUERY(admin, "LOAD GENAI VARIABLES FROM DISK"); std::string var1_disk = get_genai_variable(admin, "var1"); std::string var2_disk = get_genai_variable(admin, "var2"); ok(var1_disk == "disk_test_value" && var2_disk == "999", "After LOAD FROM DISK: genai-var1='disk_test_value', genai-var2='999'"); // Test 4: Save to memory and verify MYSQL_QUERY(admin, "SAVE GENAI VARIABLES TO MEMORY"); ok(1, "SAVE GENAI VARIABLES TO MEMORY executed"); // Test 5: Load from memory MYSQL_QUERY(admin, "LOAD GENAI VARIABLES FROM MEMORY"); ok(1, "LOAD GENAI VARIABLES FROM MEMORY executed"); // Test 6: Test SAVE from runtime MYSQL_QUERY(admin, "SAVE GENAI VARIABLES FROM RUNTIME"); ok(1, "SAVE GENAI VARIABLES FROM RUNTIME executed"); // Test 7: Test LOAD to runtime MYSQL_QUERY(admin, "LOAD GENAI VARIABLES TO RUNTIME"); ok(1, "LOAD GENAI VARIABLES TO RUNTIME executed"); // Test 8: Restore default values MYSQL_QUERY(admin, "SET genai-var1='default_value_1'"); MYSQL_QUERY(admin, "SET genai-var2=100"); MYSQL_QUERY(admin, "SAVE GENAI VARIABLES TO DISK"); ok(1, "Restored default values and saved to disk"); return test_num; } /** * @brief Test CHECKSUM commands for GenAI variables * * Tests all CHECKSUM variants to ensure they work correctly. */ int test_checksum_commands(MYSQL* admin) { int test_num = 0; // Test 1: CHECKSUM DISK GENAI VARIABLES diag("Testing CHECKSUM commands for GenAI variables"); int rc1 = mysql_query(admin, "CHECKSUM DISK GENAI VARIABLES"); ok(rc1 == 0, "CHECKSUM DISK GENAI VARIABLES"); if (rc1 == 0) { MYSQL_RES* res = mysql_store_result(admin); int num_rows = mysql_num_rows(res); ok(num_rows == 1, "CHECKSUM DISK GENAI VARIABLES returns 1 row"); mysql_free_result(res); } else { skip(1, "Skipping row count check due to error"); } // Test 2: CHECKSUM MEM GENAI VARIABLES int rc2 = mysql_query(admin, "CHECKSUM MEM GENAI VARIABLES"); ok(rc2 == 0, "CHECKSUM MEM GENAI VARIABLES"); if (rc2 == 0) { MYSQL_RES* res = mysql_store_result(admin); int num_rows = mysql_num_rows(res); ok(num_rows == 1, "CHECKSUM MEM GENAI VARIABLES returns 1 row"); mysql_free_result(res); } else { skip(1, "Skipping row count check due to error"); } // Test 3: CHECKSUM MEMORY GENAI VARIABLES (alias for MEM) int rc3 = mysql_query(admin, "CHECKSUM MEMORY GENAI VARIABLES"); ok(rc3 == 0, "CHECKSUM MEMORY GENAI VARIABLES"); if (rc3 == 0) { MYSQL_RES* res = mysql_store_result(admin); int num_rows = mysql_num_rows(res); ok(num_rows == 1, "CHECKSUM MEMORY GENAI VARIABLES returns 1 row"); mysql_free_result(res); } else { skip(1, "Skipping row count check due to error"); } // Test 4: CHECKSUM GENAI VARIABLES (defaults to DISK) int rc4 = mysql_query(admin, "CHECKSUM GENAI VARIABLES"); ok(rc4 == 0, "CHECKSUM GENAI VARIABLES"); if (rc4 == 0) { MYSQL_RES* res = mysql_store_result(admin); int num_rows = mysql_num_rows(res); ok(num_rows == 1, "CHECKSUM GENAI VARIABLES returns 1 row"); mysql_free_result(res); } else { skip(1, "Skipping row count check due to error"); } return test_num; } /** * @brief Main test function * * Orchestrates all GenAI module tests. */ int main() { CommandLine cl; if (cl.getEnv()) { diag("Failed to get the required environmental variables."); return EXIT_FAILURE; } // Initialize connection to admin interface MYSQL* admin = mysql_init(NULL); if (!admin) { fprintf(stderr, "File %s, line %d, Error: mysql_init failed\n", __FILE__, __LINE__); return EXIT_FAILURE; } if (!mysql_real_connect(admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin)); return EXIT_FAILURE; } diag("Connected to ProxySQL admin interface at %s:%d", cl.host, cl.admin_port); // Build the list of LOAD/SAVE commands to test std::vector queries; add_genai_load_save_commands(queries); // Each command test = 2 tests (execution + optional result check) // LOAD/SAVE commands: 14 commands // Variable access tests: 7 tests // Persistence tests: 8 tests // CHECKSUM tests: 8 tests (4 commands × 2) int num_load_save_tests = (int)queries.size() * 2; // Each command + result check int total_tests = num_load_save_tests + 7 + 8 + 8; plan(total_tests); int test_count = 0; // ============================================================================ // Part 1: Test LOAD/SAVE commands // ============================================================================ diag("=== Part 1: Testing LOAD/SAVE GENAI VARIABLES commands ==="); for (const auto& query : queries) { MYSQL* admin_local = mysql_init(NULL); if (!admin_local) { diag("Failed to initialize MySQL connection"); continue; } if (!mysql_real_connect(admin_local, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { diag("Failed to connect to admin interface"); mysql_close(admin_local); continue; } int rc = run_q(admin_local, query.c_str()); ok(rc == 0, "Command executed successfully: %s", query.c_str()); // For SELECT/SHOW/CHECKSUM style commands, verify result set if (strncasecmp(query.c_str(), "SELECT ", 7) == 0 || strncasecmp(query.c_str(), "SHOW ", 5) == 0 || strncasecmp(query.c_str(), "CHECKSUM ", 9) == 0) { MYSQL_RES* res = mysql_store_result(admin_local); unsigned long long num_rows = mysql_num_rows(res); ok(num_rows != 0, "Command returned rows: %s", query.c_str()); mysql_free_result(res); } else { // For non-query commands, just mark the test as passed ok(1, "Command completed: %s", query.c_str()); } mysql_close(admin_local); } // ============================================================================ // Part 2: Test variable access (SET and SELECT) // ============================================================================ diag("=== Part 2: Testing variable access (SET and SELECT) ==="); test_count += test_variable_access(admin); // ============================================================================ // Part 3: Test variable persistence across layers // ============================================================================ diag("=== Part 3: Testing variable persistence across storage layers ==="); test_count += test_variable_persistence(admin); // ============================================================================ // Part 4: Test CHECKSUM commands // ============================================================================ diag("=== Part 4: Testing CHECKSUM commands ==="); test_count += test_checksum_commands(admin); // ============================================================================ // Cleanup // ============================================================================ mysql_close(admin); diag("=== All GenAI module tests completed ==="); return exit_status(); }