/* * ai_http_stub.c * * Spawns tests/functionaltests/ai_http_stub.py as a child process for * functional tests that exercise the AI client's HTTP code path. The * stub listens on 127.0.0.1 on an OS-chosen port and prints "PORT=" * as its first line; we read it back and hand the port to the caller. */ #include "ai_http_stub.h" #include #include #include #include #include #include #include #include #define STUB_SCRIPT "../tests/functionaltests/ai_http_stub.py" #define READLINE_MAX 64 static pid_t stub_pid = 0; int ai_http_stub_start(const char* mode, const char* reply) { int pipefd[2]; if (pipe(pipefd) != 0) { return 0; } pid_t pid = fork(); if (pid < 0) { close(pipefd[0]); close(pipefd[1]); return 0; } if (pid == 0) { /* Child: redirect stdout to the pipe write end, then exec the stub. */ close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); if (mode) { setenv("AI_STUB_MODE", mode, 1); } if (reply) { setenv("AI_STUB_REPLY", reply, 1); } /* Argument "0" -> let the OS pick a free port. */ execlp("python3", "python3", STUB_SCRIPT, "0", (char*)NULL); /* Falls through on exec failure. */ _exit(127); } /* Parent: read "PORT=\n" from the child's stdout. */ close(pipefd[1]); char buf[READLINE_MAX] = { 0 }; ssize_t total = 0; while (total < (ssize_t)sizeof(buf) - 1) { ssize_t n = read(pipefd[0], buf + total, sizeof(buf) - 1 - total); if (n <= 0) { break; } total += n; if (memchr(buf, '\n', total)) { break; } } close(pipefd[0]); int port = 0; if (sscanf(buf, "PORT=%d", &port) != 1 || port <= 0) { kill(pid, SIGTERM); waitpid(pid, NULL, 0); return 0; } stub_pid = pid; return port; } void ai_http_stub_stop(void) { if (stub_pid <= 0) { return; } kill(stub_pid, SIGTERM); /* Give the stub a moment to exit cleanly, then reap. */ int status = 0; pid_t waited = waitpid(stub_pid, &status, 0); (void)waited; (void)status; stub_pid = 0; }