fix: add pytest to build.sh, fix hanging tests, add request timeouts

This commit is contained in:
2026-07-17 09:18:08 +03:00
parent b445449542
commit e1aa542917
4 changed files with 60 additions and 31 deletions
+7 -1
View File
@@ -49,10 +49,16 @@ fi
echo "==> Build" echo "==> Build"
cmake --build "${BUILD_DIR}" -j$(nproc) cmake --build "${BUILD_DIR}" -j$(nproc)
echo "==> Run tests" echo "==> Run C++ tests"
rm -rf /tmp/pipeline-runner-test/ rm -rf /tmp/pipeline-runner-test/
"${BUILD_DIR}/test-pipeline-runner" \ "${BUILD_DIR}/test-pipeline-runner" \
--gtest_brief=1 \ --gtest_brief=1 \
--gtest_output=xml:"${BUILD_DIR}/test-results.xml" --gtest_output=xml:"${BUILD_DIR}/test-results.xml"
if [ "${FAKE}" = true ]; then
echo "==> Run Python tests"
timeout 60 python3 -m pytest "${SCRIPT_DIR}/tests" --tb=line -q \
--junitxml="${BUILD_DIR}/python-test-results.xml"
fi
echo "==> Done" echo "==> Done"
+6 -6
View File
@@ -82,16 +82,16 @@ class PipelineServer:
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
self.process.kill() self.process.kill()
self.process.wait(timeout=5) 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") @pytest.fixture(scope="session")
def pipeline_server(): def pipeline_server():
"""Session-scoped fixture that builds, starts, and stops the pipeline-runner server.""" """Session-scoped fixture that starts and stops the pipeline-runner server."""
project_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
build_script = os.path.join(project_dir, "build.sh")
subprocess.check_call([build_script, "--fake"], cwd=project_dir)
work_dir = tempfile.mkdtemp(prefix="pipeline-test-") work_dir = tempfile.mkdtemp(prefix="pipeline-test-")
port = get_free_port() port = get_free_port()
+23 -8
View File
@@ -3,8 +3,11 @@
import time import time
import uuid import uuid
import pytest
import requests import requests
REQUEST_TIMEOUT = 5
def _unique_id(): def _unique_id():
return str(uuid.uuid4()) return str(uuid.uuid4())
@@ -26,7 +29,9 @@ def test_full_pipeline_lifecycle(pipeline_server):
{"id": _unique_id(), "text": "echo step3", "title": "Step 3", "order": 2}, {"id": _unique_id(), "text": "echo step3", "title": "Step 3", "order": 2},
], ],
} }
resp = requests.post(f"{base}/api/pipelines", json=pipeline) resp = requests.post(
f"{base}/api/pipelines", json=pipeline, timeout=REQUEST_TIMEOUT
)
assert resp.status_code == 200 assert resp.status_code == 200
created = resp.json() created = resp.json()
assert created["id"] == pipeline_id assert created["id"] == pipeline_id
@@ -34,13 +39,15 @@ def test_full_pipeline_lifecycle(pipeline_server):
assert len(created["prompts"]) == 3 assert len(created["prompts"]) == 3
# --- List pipelines --- # --- List pipelines ---
resp = requests.get(f"{base}/api/pipelines") resp = requests.get(f"{base}/api/pipelines", timeout=REQUEST_TIMEOUT)
assert resp.status_code == 200 assert resp.status_code == 200
pipelines = resp.json() pipelines = resp.json()
assert any(p["id"] == pipeline_id for p in pipelines) assert any(p["id"] == pipeline_id for p in pipelines)
# --- Get single pipeline --- # --- Get single pipeline ---
resp = requests.get(f"{base}/api/pipelines/{pipeline_id}") resp = requests.get(
f"{base}/api/pipelines/{pipeline_id}", timeout=REQUEST_TIMEOUT
)
assert resp.status_code == 200 assert resp.status_code == 200
fetched = resp.json() fetched = resp.json()
assert fetched["id"] == pipeline_id assert fetched["id"] == pipeline_id
@@ -49,7 +56,9 @@ def test_full_pipeline_lifecycle(pipeline_server):
assert fetched["prompts"][0]["order"] == 0 assert fetched["prompts"][0]["order"] == 0
# --- Start a run --- # --- Start a run ---
resp = requests.post(f"{base}/api/runs", json={"pipeline_id": pipeline_id}) resp = requests.post(
f"{base}/api/runs", json={"pipeline_id": pipeline_id}, timeout=REQUEST_TIMEOUT
)
assert resp.status_code == 200 assert resp.status_code == 200
run_data = resp.json() run_data = resp.json()
run_id = run_data["run_id"] run_id = run_data["run_id"]
@@ -58,7 +67,9 @@ def test_full_pipeline_lifecycle(pipeline_server):
# --- Poll run status until completed --- # --- Poll run status until completed ---
for _ in range(60): for _ in range(60):
resp = requests.get(f"{base}/api/runs/{run_id}/status") resp = requests.get(
f"{base}/api/runs/{run_id}/status", timeout=REQUEST_TIMEOUT
)
assert resp.status_code == 200 assert resp.status_code == 200
status_data = resp.json() status_data = resp.json()
if status_data["status"] in ("completed", "error"): if status_data["status"] in ("completed", "error"):
@@ -71,15 +82,19 @@ def test_full_pipeline_lifecycle(pipeline_server):
assert len(status_data["steps"]) == 3 assert len(status_data["steps"]) == 3
# --- Get log --- # --- Get log ---
resp = requests.get(f"{base}/api/runs/{run_id}/log") resp = requests.get(f"{base}/api/runs/{run_id}/log", timeout=REQUEST_TIMEOUT)
assert resp.status_code == 200 assert resp.status_code == 200
assert len(resp.text) > 0 assert len(resp.text) > 0
# --- Delete pipeline --- # --- Delete pipeline ---
resp = requests.delete(f"{base}/api/pipelines/{pipeline_id}") resp = requests.delete(
f"{base}/api/pipelines/{pipeline_id}", timeout=REQUEST_TIMEOUT
)
assert resp.status_code == 200 assert resp.status_code == 200
assert resp.json()["deleted"] == pipeline_id assert resp.json()["deleted"] == pipeline_id
# --- Verify deletion --- # --- Verify deletion ---
resp = requests.get(f"{base}/api/pipelines/{pipeline_id}") resp = requests.get(
f"{base}/api/pipelines/{pipeline_id}", timeout=REQUEST_TIMEOUT
)
assert resp.status_code == 404 assert resp.status_code == 404
+24 -16
View File
@@ -1,16 +1,15 @@
"""Smoke tests for pipeline-runner REST API endpoints.""" """Smoke tests for pipeline-runner REST API endpoints."""
import json
import os import os
import shutil import shutil
import socket import socket
import subprocess
import tempfile import tempfile
import time
import pytest import pytest
import requests import requests
REQUEST_TIMEOUT = 5
test_get_endpoints = [ test_get_endpoints = [
("api/pipelines", 200, list), ("api/pipelines", 200, list),
@@ -21,7 +20,9 @@ test_get_endpoints = [
@pytest.mark.parametrize("endpoint,expected_status,expected_type", test_get_endpoints) @pytest.mark.parametrize("endpoint,expected_status,expected_type", test_get_endpoints)
def test_get_endpoints(pipeline_server, endpoint, expected_status, expected_type): def test_get_endpoints(pipeline_server, endpoint, expected_status, expected_type):
"""Check HTTP status code and response content-type for GET endpoints.""" """Check HTTP status code and response content-type for GET endpoints."""
response = requests.get(f"{pipeline_server.base_url}/{endpoint}") response = requests.get(
f"{pipeline_server.base_url}/{endpoint}", timeout=REQUEST_TIMEOUT
)
assert response.status_code == expected_status assert response.status_code == expected_status
assert "application/json" in response.headers.get("Content-Type", "") assert "application/json" in response.headers.get("Content-Type", "")
data = response.json() data = response.json()
@@ -30,7 +31,9 @@ def test_get_endpoints(pipeline_server, endpoint, expected_status, expected_type
def test_cors_headers(pipeline_server): def test_cors_headers(pipeline_server):
"""Check that CORS headers are present on responses.""" """Check that CORS headers are present on responses."""
response = requests.get(f"{pipeline_server.base_url}/api/pipelines") response = requests.get(
f"{pipeline_server.base_url}/api/pipelines", timeout=REQUEST_TIMEOUT
)
assert response.headers.get("Access-Control-Allow-Origin") == "*" assert response.headers.get("Access-Control-Allow-Origin") == "*"
@@ -45,10 +48,6 @@ def _get_free_port():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def static_file_server(): def static_file_server():
"""Fixture that starts a server with web_root configured for static file tests.""" """Fixture that starts a server with web_root configured for static file tests."""
project_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
build_script = os.path.join(project_dir, "build.sh")
subprocess.check_call([build_script, "--fake"], cwd=project_dir)
work_dir = tempfile.mkdtemp(prefix="pipeline-static-test-") work_dir = tempfile.mkdtemp(prefix="pipeline-static-test-")
web_root = os.path.join(work_dir, "web") web_root = os.path.join(work_dir, "web")
@@ -88,7 +87,7 @@ def static_file_server():
class TestStaticFiles: class TestStaticFiles:
def test_serve_html(self, static_file_server): def test_serve_html(self, static_file_server):
server, _ = static_file_server server, _ = static_file_server
resp = requests.get(f"{server.base_url}/index.html") resp = requests.get(f"{server.base_url}/index.html", timeout=REQUEST_TIMEOUT)
assert resp.status_code == 200 assert resp.status_code == 200
assert "text/html" in resp.headers.get("Content-Type", "") assert "text/html" in resp.headers.get("Content-Type", "")
assert "<h1>Hello Static</h1>" in resp.text assert "<h1>Hello Static</h1>" in resp.text
@@ -96,28 +95,32 @@ class TestStaticFiles:
def test_serve_css(self, static_file_server): def test_serve_css(self, static_file_server):
server, _ = static_file_server server, _ = static_file_server
resp = requests.get(f"{server.base_url}/style.css") resp = requests.get(f"{server.base_url}/style.css", timeout=REQUEST_TIMEOUT)
assert resp.status_code == 200 assert resp.status_code == 200
assert "text/css" in resp.headers.get("Content-Type", "") assert "text/css" in resp.headers.get("Content-Type", "")
assert resp.text == "body { color: red; }" assert resp.text == "body { color: red; }"
def test_serve_js(self, static_file_server): def test_serve_js(self, static_file_server):
server, _ = static_file_server server, _ = static_file_server
resp = requests.get(f"{server.base_url}/app.js") resp = requests.get(f"{server.base_url}/app.js", timeout=REQUEST_TIMEOUT)
assert resp.status_code == 200 assert resp.status_code == 200
assert "text/javascript" in resp.headers.get("Content-Type", "") assert "text/javascript" in resp.headers.get("Content-Type", "")
assert resp.text == "console.log('hello');" assert resp.text == "console.log('hello');"
def test_serve_nested_file(self, static_file_server): def test_serve_nested_file(self, static_file_server):
server, _ = static_file_server server, _ = static_file_server
resp = requests.get(f"{server.base_url}/subdir/nested.txt") resp = requests.get(
f"{server.base_url}/subdir/nested.txt", timeout=REQUEST_TIMEOUT
)
assert resp.status_code == 200 assert resp.status_code == 200
assert "text/plain" in resp.headers.get("Content-Type", "") assert "text/plain" in resp.headers.get("Content-Type", "")
assert resp.text == "nested file content" assert resp.text == "nested file content"
def test_etag_304(self, static_file_server): def test_etag_304(self, static_file_server):
server, _ = static_file_server server, _ = static_file_server
resp1 = requests.get(f"{server.base_url}/index.html") resp1 = requests.get(
f"{server.base_url}/index.html", timeout=REQUEST_TIMEOUT
)
assert resp1.status_code == 200 assert resp1.status_code == 200
etag = resp1.headers.get("ETag") etag = resp1.headers.get("ETag")
assert etag is not None assert etag is not None
@@ -125,17 +128,22 @@ class TestStaticFiles:
resp2 = requests.get( resp2 = requests.get(
f"{server.base_url}/index.html", f"{server.base_url}/index.html",
headers={"If-None-Match": etag}, headers={"If-None-Match": etag},
timeout=REQUEST_TIMEOUT,
) )
assert resp2.status_code == 304 assert resp2.status_code == 304
assert resp2.text == "" assert resp2.text == ""
def test_cache_control_header(self, static_file_server): def test_cache_control_header(self, static_file_server):
server, _ = static_file_server server, _ = static_file_server
resp = requests.get(f"{server.base_url}/index.html") resp = requests.get(
f"{server.base_url}/index.html", timeout=REQUEST_TIMEOUT
)
assert resp.status_code == 200 assert resp.status_code == 200
assert "max-age" in resp.headers.get("Cache-Control", "") assert "max-age" in resp.headers.get("Cache-Control", "")
def test_static_404(self, static_file_server): def test_static_404(self, static_file_server):
server, _ = static_file_server server, _ = static_file_server
resp = requests.get(f"{server.base_url}/nonexistent.html") resp = requests.get(
f"{server.base_url}/nonexistent.html", timeout=REQUEST_TIMEOUT
)
assert resp.status_code == 404 assert resp.status_code == 404