fix: inttest runner crash and minio bucket-init proxy issue
- Remove --profiling-output arg from runner.yaml (plugin uses default path)
- Inline profiling hooks in root conftest.py with graceful fallback
- Replace mc-based bucket-init with Python urllib (no proxy interference)
- Add explicit ProxyHandler({}) to guarantee no proxy usage in bucket-init
This commit is contained in:
+66
-2
@@ -1,2 +1,66 @@
|
|||||||
# Root conftest — pytest_plugins must be declared at the top level.
|
# Root conftest — profiling hooks for integration tests.
|
||||||
pytest_plugins = ["tests.integration.conftest_profiling"]
|
#
|
||||||
|
# Registers --profiling-output CLI option and writes a JSON timing report
|
||||||
|
# after the test session completes. The profiler fixture itself is defined
|
||||||
|
# in tests/integration/conftest.py (always available as a fallback) and
|
||||||
|
# optionally overridden by this plugin when it loads successfully.
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
_profiler_instance = None
|
||||||
|
_DEFAULT_OUTPUT = "/tmp/profiling-report.json"
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser: pytest.Parser) -> None:
|
||||||
|
"""Add --profiling-output CLI flag."""
|
||||||
|
parser.addoption(
|
||||||
|
"--profiling-output",
|
||||||
|
action="store",
|
||||||
|
default=_DEFAULT_OUTPUT,
|
||||||
|
help=f"Path for the JSON profiling report (default: {_DEFAULT_OUTPUT})",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def profiler():
|
||||||
|
"""Session-scoped EndpointProfiler instance."""
|
||||||
|
global _profiler_instance # noqa: PLW0603
|
||||||
|
try:
|
||||||
|
from tests.integration.profiler import EndpointProfiler
|
||||||
|
_profiler_instance = EndpointProfiler()
|
||||||
|
except ImportError:
|
||||||
|
# If profiler module unavailable, return a no-op object
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
_profiler_instance = MagicMock()
|
||||||
|
return _profiler_instance
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
|
||||||
|
"""Write profiling JSON report after all tests complete."""
|
||||||
|
if _profiler_instance is None:
|
||||||
|
return
|
||||||
|
output_path = session.config.getoption("profiling_output", _DEFAULT_OUTPUT)
|
||||||
|
try:
|
||||||
|
if hasattr(_profiler_instance, "write_json"):
|
||||||
|
_profiler_instance.write_json(output_path)
|
||||||
|
except (OSError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_terminal_summary(
|
||||||
|
terminalreporter: pytest.TerminalReporter,
|
||||||
|
exitstatus: int,
|
||||||
|
config: pytest.Config,
|
||||||
|
) -> None:
|
||||||
|
"""Print profiling summary at end of test session."""
|
||||||
|
if _profiler_instance is None:
|
||||||
|
return
|
||||||
|
output_path = config.getoption("profiling_output", _DEFAULT_OUTPUT)
|
||||||
|
try:
|
||||||
|
if hasattr(_profiler_instance, "print_summary"):
|
||||||
|
terminalreporter.section("Profiling Summary")
|
||||||
|
_profiler_instance.print_summary()
|
||||||
|
terminalreporter.write_line(f"JSON report written to: {output_path}")
|
||||||
|
except (OSError, TypeError):
|
||||||
|
pass
|
||||||
|
|||||||
+45
-13
@@ -129,8 +129,8 @@ spec:
|
|||||||
type: RuntimeDefault
|
type: RuntimeDefault
|
||||||
restartPolicy: OnFailure
|
restartPolicy: OnFailure
|
||||||
containers:
|
containers:
|
||||||
- name: mc
|
- name: bucket-init
|
||||||
image: minio/mc:latest
|
image: registry.celestium.life/dockerhub-cache/library/python:3.12-slim
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
securityContext:
|
securityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
@@ -143,23 +143,55 @@ spec:
|
|||||||
limits:
|
limits:
|
||||||
cpu: 250m
|
cpu: 250m
|
||||||
memory: 128Mi
|
memory: 128Mi
|
||||||
command: ["/bin/sh", "-c"]
|
|
||||||
env:
|
env:
|
||||||
- name: HTTP_PROXY
|
- name: HTTP_PROXY
|
||||||
value: ""
|
value: ""
|
||||||
- name: HTTPS_PROXY
|
- name: HTTPS_PROXY
|
||||||
value: ""
|
value: ""
|
||||||
|
- name: http_proxy
|
||||||
|
value: ""
|
||||||
|
- name: https_proxy
|
||||||
|
value: ""
|
||||||
- name: NO_PROXY
|
- name: NO_PROXY
|
||||||
value: "minio,.local,10.0.0.0/8,192.168.0.0/16"
|
value: "*"
|
||||||
- name: no_proxy
|
- name: no_proxy
|
||||||
value: "minio,.local,10.0.0.0/8,192.168.0.0/16"
|
value: "*"
|
||||||
|
command: ["python", "-c"]
|
||||||
args:
|
args:
|
||||||
- |
|
- |
|
||||||
echo "Waiting for MinIO to be ready..."
|
import urllib.request, urllib.error, time
|
||||||
until mc alias set sandbox http://minio:9000 minioadmin minioadmin 2>/dev/null; do
|
|
||||||
echo "MinIO not ready, retrying in 2s..."
|
# Disable all proxy usage in urllib
|
||||||
sleep 2
|
no_proxy_handler = urllib.request.ProxyHandler({})
|
||||||
done
|
opener = urllib.request.build_opener(no_proxy_handler)
|
||||||
echo "MinIO is ready. Creating bucket..."
|
urllib.request.install_opener(opener)
|
||||||
mc mb --ignore-existing sandbox/stonks-normalized
|
|
||||||
echo "Bucket stonks-normalized created successfully."
|
endpoint = "http://minio:9000"
|
||||||
|
max_wait = 60
|
||||||
|
|
||||||
|
# Wait for MinIO readiness
|
||||||
|
print("Waiting for MinIO to be ready...")
|
||||||
|
start = time.time()
|
||||||
|
while time.time() - start < max_wait:
|
||||||
|
try:
|
||||||
|
r = urllib.request.urlopen(f"{endpoint}/minio/health/ready", timeout=3)
|
||||||
|
if r.status == 200:
|
||||||
|
print("MinIO is ready.")
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
time.sleep(2)
|
||||||
|
else:
|
||||||
|
raise SystemExit("MinIO did not become ready within 60s")
|
||||||
|
|
||||||
|
# Create bucket via S3 PUT request
|
||||||
|
bucket = "stonks-normalized"
|
||||||
|
req = urllib.request.Request(f"{endpoint}/{bucket}", method="PUT")
|
||||||
|
try:
|
||||||
|
urllib.request.urlopen(req, timeout=5)
|
||||||
|
print(f"Bucket '{bucket}' created successfully.")
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if e.code == 409:
|
||||||
|
print(f"Bucket '{bucket}' already exists.")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ spec:
|
|||||||
- "-v"
|
- "-v"
|
||||||
- "--tb=short"
|
- "--tb=short"
|
||||||
- "--junitxml=/tmp/results.xml"
|
- "--junitxml=/tmp/results.xml"
|
||||||
- "--profiling-output=/tmp/profiling-report.json"
|
|
||||||
securityContext:
|
securityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
readOnlyRootFilesystem: true
|
readOnlyRootFilesystem: true
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from typing import Any
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
import pytest_asyncio
|
||||||
|
|
||||||
from tests.integration.profiler import EndpointProfiler
|
from tests.integration.profiler import EndpointProfiler
|
||||||
from tests.integration.seed_sandbox import (
|
from tests.integration.seed_sandbox import (
|
||||||
@@ -34,9 +35,6 @@ from tests.integration.seed_sandbox import (
|
|||||||
SEED_VARIANT_IDS,
|
SEED_VARIANT_IDS,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Profiling plugin loaded via root conftest.py (pytest_plugins must be top-level)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# ProfiledAsyncClient — transparent timing wrapper
|
# ProfiledAsyncClient — transparent timing wrapper
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -129,7 +127,7 @@ def trading_api_url() -> str:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest_asyncio.fixture
|
||||||
async def query_client(
|
async def query_client(
|
||||||
query_api_url: str, profiler: EndpointProfiler,
|
query_api_url: str, profiler: EndpointProfiler,
|
||||||
) -> ProfiledAsyncClient:
|
) -> ProfiledAsyncClient:
|
||||||
@@ -138,7 +136,7 @@ async def query_client(
|
|||||||
yield ProfiledAsyncClient(client, profiler)
|
yield ProfiledAsyncClient(client, profiler)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest_asyncio.fixture
|
||||||
async def registry_client(
|
async def registry_client(
|
||||||
registry_api_url: str, profiler: EndpointProfiler,
|
registry_api_url: str, profiler: EndpointProfiler,
|
||||||
) -> ProfiledAsyncClient:
|
) -> ProfiledAsyncClient:
|
||||||
@@ -147,7 +145,7 @@ async def registry_client(
|
|||||||
yield ProfiledAsyncClient(client, profiler)
|
yield ProfiledAsyncClient(client, profiler)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest_asyncio.fixture
|
||||||
async def risk_client(
|
async def risk_client(
|
||||||
risk_api_url: str, profiler: EndpointProfiler,
|
risk_api_url: str, profiler: EndpointProfiler,
|
||||||
) -> ProfiledAsyncClient:
|
) -> ProfiledAsyncClient:
|
||||||
@@ -156,7 +154,7 @@ async def risk_client(
|
|||||||
yield ProfiledAsyncClient(client, profiler)
|
yield ProfiledAsyncClient(client, profiler)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest_asyncio.fixture
|
||||||
async def trading_client(
|
async def trading_client(
|
||||||
trading_api_url: str, profiler: EndpointProfiler,
|
trading_api_url: str, profiler: EndpointProfiler,
|
||||||
) -> ProfiledAsyncClient:
|
) -> ProfiledAsyncClient:
|
||||||
|
|||||||
@@ -16,27 +16,13 @@ import pytest
|
|||||||
|
|
||||||
from tests.integration.profiler import EndpointProfiler
|
from tests.integration.profiler import EndpointProfiler
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# CLI option — registered by root conftest.py to ensure availability
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
DEFAULT_PROFILING_OUTPUT = "/tmp/profiling-report.json"
|
DEFAULT_PROFILING_OUTPUT = "/tmp/profiling-report.json"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# CLI option
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser: pytest.Parser) -> None:
|
|
||||||
"""Add ``--profiling-output`` CLI flag to pytest."""
|
|
||||||
parser.addoption(
|
|
||||||
"--profiling-output",
|
|
||||||
action="store",
|
|
||||||
default=DEFAULT_PROFILING_OUTPUT,
|
|
||||||
help=(
|
|
||||||
"Path for the JSON profiling report "
|
|
||||||
f"(default: {DEFAULT_PROFILING_OUTPUT})"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Session-scoped profiler instance (shared across all tests)
|
# Session-scoped profiler instance (shared across all tests)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user