"""Pytest fixture for managing the pipeline-runner server lifecycle during tests.""" import json import os import shutil import socket import subprocess import tempfile import time import pytest def get_free_port(): """Get a free port for the test server.""" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("", 0)) port = s.getsockname()[1] s.close() return port class PipelineServer: """Manages a pipeline-runner server instance for tests.""" def __init__(self, port, work_dir, verbose=False, web_root=None): self.port = port self.work_dir = work_dir self.verbose = verbose self.web_root = web_root test_dir = os.path.dirname(os.path.abspath(__file__)) project_root = os.path.dirname(test_dir) self.bin_path = os.path.join(project_root, "build", "pipeline-runner") config = { "port": port, "pipelines_dir": os.path.join(work_dir, "pipelines") + "/", "logs_dir": os.path.join(work_dir, "logs") + "/", } if web_root: config["web_root"] = web_root self.config_path = os.path.join(work_dir, "pipeline-runner.conf") with open(self.config_path, "w") as f: json.dump(config, f) self.process = None @property def base_url(self): return f"http://localhost:{self.port}" def start(self, timeout=30): """Start the server and wait until it responds.""" self.process = subprocess.Popen( [self.bin_path, self.config_path], cwd=self.work_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) deadline = time.time() + timeout while time.time() < deadline: try: resp = __import__("requests").get( f"{self.base_url}/api/pipelines", timeout=1 ) if resp.status_code == 200: return True except Exception: pass time.sleep(0.3) return False def stop(self): """Stop the server.""" if self.process: self.process.terminate() try: self.process.wait(timeout=10) except subprocess.TimeoutExpired: self.process.kill() self.process.wait(timeout=5) # Close pipes to prevent resource leaks and zombie processes if self.process.stdout: self.process.stdout.close() if self.process.stderr: self.process.stderr.close() @pytest.fixture(scope="session") def pipeline_server(): """Session-scoped fixture that starts and stops the pipeline-runner server.""" work_dir = tempfile.mkdtemp(prefix="pipeline-test-") port = get_free_port() server = PipelineServer(port, work_dir) if not server.start(): pytest.fail("Could not start pipeline-runner server") yield server server.stop() shutil.rmtree(work_dir, ignore_errors=True)