diff --git a/test/tap/noise/noise_admin_pinger.sh b/test/tap/noise/noise_admin_pinger.sh new file mode 100755 index 000000000..50e8df67a --- /dev/null +++ b/test/tap/noise/noise_admin_pinger.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +HOST=$1 +PORT=$2 +USER=$3 +PASS=$4 +INTERVAL=${5:-1.0} + +while true; do + mysql -h $HOST -P $PORT -u $USER -p$PASS -e "SELECT 1;" > /dev/null 2>&1 + sleep $INTERVAL +done diff --git a/test/tap/noise/noise_pgsql_poller.sh b/test/tap/noise/noise_pgsql_poller.sh new file mode 100755 index 000000000..ce9816c16 --- /dev/null +++ b/test/tap/noise/noise_pgsql_poller.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +HOST=$1 +PORT=$2 +USER=$3 +PASS=$4 +INTERVAL=${5:-0.5} + +export PGPASSWORD=$PASS + +while true; do + psql -h $HOST -p $PORT -U $USER -c "SELECT 1;" > /dev/null 2>&1 + sleep $INTERVAL +done diff --git a/test/tap/noise/noise_stats_poller.py b/test/tap/noise/noise_stats_poller.py new file mode 100755 index 000000000..69ac91900 --- /dev/null +++ b/test/tap/noise/noise_stats_poller.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +import mysql.connector +import time +import argparse +import sys +import signal + +def main(): + parser = argparse.ArgumentParser(description="Generate noise by polling stats tables") + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, default=6032) + parser.add_argument("--user", default="admin") + parser.add_argument("--password", default="admin") + parser.add_argument("--interval", type=float, default=0.5) + args = parser.parse_args() + + def signal_handler(sig, frame): + sys.exit(0) + + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGINT, signal_handler) + + try: + conn = mysql.connector.connect( + host=args.host, + port=args.port, + user=args.user, + password=args.password, + autocommit=True + ) + cursor = conn.cursor() + + while True: + cursor.execute("SELECT * FROM stats_mysql_query_digest") + cursor.fetchall() + cursor.execute("SELECT * FROM stats_mysql_connection_pool") + cursor.fetchall() + time.sleep(args.interval) + except Exception as e: + print(f"Error in noise poller: {e}", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/test/tap/tap/command_line.cpp b/test/tap/tap/command_line.cpp index 3c7094b7f..ff889f9a2 100644 --- a/test/tap/tap/command_line.cpp +++ b/test/tap/tap/command_line.cpp @@ -376,5 +376,12 @@ int CommandLine::getEnv() { } } + value = getenv("TAP_USE_NOISE"); + if (value) { + if (strcmp(value, "1") == 0 || strcasecmp(value, "true") == 0) { + this->use_noise = true; + } + } + return 0; } diff --git a/test/tap/tap/command_line.h b/test/tap/tap/command_line.h index 224fc3690..8b6a47ba1 100644 --- a/test/tap/tap/command_line.h +++ b/test/tap/tap/command_line.h @@ -12,6 +12,7 @@ class CommandLine { bool checksum = true; bool no_write = false; + bool use_noise = false; int silent = false; // unpriviliged test connection diff --git a/test/tap/tap/tap.cpp b/test/tap/tap/tap.cpp index 6ff9d960b..1f9b9a30a 100644 --- a/test/tap/tap/tap.cpp +++ b/test/tap/tap/tap.cpp @@ -350,10 +350,14 @@ todo_end() *g_test.todo = '\0'; } +extern "C" void stop_noise_tools(); + int exit_status() { char buff[60]; + stop_noise_tools(); + /* If there were no plan, we write one last instead. */ diff --git a/test/tap/tap/utils.cpp b/test/tap/tap/utils.cpp index bf9f3a13a..8633f18b6 100644 --- a/test/tap/tap/utils.cpp +++ b/test/tap/tap/utils.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -2435,3 +2436,63 @@ int run_q(MYSQL *mysql, const char *q) { MYSQL_QUERY_T(mysql,q); return 0; } + +static std::vector background_noise_pids; +static bool atexit_noise_registered = false; + +extern "C" void stop_noise_tools() { + for (pid_t pid : background_noise_pids) { + kill(pid, SIGTERM); + // Small wait and reap + usleep(100000); + int status; + if (waitpid(pid, &status, WNOHANG) == 0) { + kill(pid, SIGKILL); + waitpid(pid, &status, 0); + } + } + background_noise_pids.clear(); +} + +void spawn_noise(const CommandLine& cl, const std::string& tool_path, const std::vector& args) { + if (!cl.use_noise) { + return; + } + + if (!atexit_noise_registered) { + atexit(stop_noise_tools); + atexit_noise_registered = true; + } + + pid_t pid = fork(); + if (pid == 0) { + // Child + setpgid(0, 0); + int fd = open("/dev/null", O_RDWR); + if (fd != -1) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > 2) { + close(fd); + } + } + + std::vector c_args; + c_args.push_back(const_cast(tool_path.c_str())); + for (const auto& arg : args) { + c_args.push_back(const_cast(arg.c_str())); + } + c_args.push_back(nullptr); + + execvp(tool_path.c_str(), c_args.data()); + // If we are here, exec failed + fprintf(stderr, "Failed to exec noise tool: %s\n", tool_path.c_str()); + _exit(1); + } else if (pid > 0) { + background_noise_pids.push_back(pid); + diag("Spawned background noise tool '%s' with PID %d", tool_path.c_str(), pid); + } else { + fprintf(stderr, "Failed to fork for noise tool: %s\n", tool_path.c_str()); + } +} diff --git a/test/tap/tap/utils.h b/test/tap/tap/utils.h index 435b8f773..576a0b423 100644 --- a/test/tap/tap/utils.h +++ b/test/tap/tap/utils.h @@ -1063,4 +1063,18 @@ bool get_env_bool(const char* envname, bool envdefault); MYSQL* init_mysql_conn(char* host, int port, char* user, char* pass, bool ssl=false, bool cmp=false); int run_q(MYSQL *mysql, const char *q); +/** + * @brief Spawns a background noise tool if noise is enabled in CommandLine. + * @param cl The CommandLine object containing configuration. + * @param tool_path Path to the executable tool. + * @param args Vector of arguments to pass to the tool. + */ +void spawn_noise(const CommandLine& cl, const std::string& tool_path, const std::vector& args); + +/** + * @brief Stops all background noise tools spawned by spawn_noise. + * @details This is intended to be called at the end of a TAP test. + */ +extern "C" void stop_noise_tools(); + #endif // #define UTILS_H diff --git a/test/tap/tests/test_noise_injection-t.cpp b/test/tap/tests/test_noise_injection-t.cpp new file mode 100644 index 000000000..b879e9cb6 --- /dev/null +++ b/test/tap/tests/test_noise_injection-t.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +int main(int argc, char** argv) { + CommandLine cl; + if (cl.getEnv()) { + diag("Failed to get environment variables"); + return 1; + } + + // Force noise enabled for this test if environment variable is not set + // but CommandLine::getEnv() already read it. + // To properly test, we should run this with TAP_USE_NOISE=1 + + if (!cl.use_noise) { + skip_all("TAP_USE_NOISE is not enabled. Skip noise injection test."); + } + + plan(3); + + // Use a simple script that just sleeps or a standard one + // We'll use our new stats poller but with a long interval or just 'sleep 100' + spawn_noise(cl, "/bin/sleep", {"100"}); + + // We can't easily get the PID from here as it's hidden in utils.cpp + // but we can check if a sleep 100 process exists. + // However, multiple might exist. + + // Better way: use a specific noise tool that writes its PID to a file + std::string pid_file = "/tmp/proxysql_noise_test.pid"; + std::string cmd = "echo $$ > " + pid_file + " && exec sleep 100"; + spawn_noise(cl, "/bin/bash", {"-c", cmd}); + + sleep(1); // Give it time to start + + ok(access(pid_file.c_str(), F_OK) == 0, "Noise process started and created PID file"); + + // Read PID from file + FILE* f = fopen(pid_file.c_str(), "r"); + pid_t pid = 0; + if (f) { + if (fscanf(f, "%d", &pid) != 1) pid = 0; + fclose(f); + } + diag("Noise process PID: %d", pid); + + // Verify it is alive + ok(pid > 0 && kill(pid, 0) == 0, "Noise process is alive"); + + // We can manually call stop_noise_tools() to verify it works + stop_noise_tools(); + sleep(1); // Give it time to be killed + + ok(pid > 0 && kill(pid, 0) != 0, "Noise process was killed"); + + if (access(pid_file.c_str(), F_OK) == 0) { + unlink(pid_file.c_str()); + } + + return exit_status(); +}