From b2b8aca7c6c5a984516ee5d862ae9895babc585d Mon Sep 17 00:00:00 2001 From: Celes Renata Date: Sun, 19 Apr 2026 19:15:20 +0000 Subject: [PATCH] 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 --- conftest.py | 68 ++++++++++++++++++++++++- infra/inttest/minio.yaml | 58 ++++++++++++++++----- infra/inttest/runner.yaml | 1 - tests/integration/conftest.py | 12 ++--- tests/integration/conftest_profiling.py | 22 ++------ 5 files changed, 120 insertions(+), 41 deletions(-) diff --git a/conftest.py b/conftest.py index 9e9c117..515fc05 100644 --- a/conftest.py +++ b/conftest.py @@ -1,2 +1,66 @@ -# Root conftest — pytest_plugins must be declared at the top level. -pytest_plugins = ["tests.integration.conftest_profiling"] +# Root conftest — profiling hooks for integration tests. +# +# 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 diff --git a/infra/inttest/minio.yaml b/infra/inttest/minio.yaml index e0be7a1..32f7a0b 100644 --- a/infra/inttest/minio.yaml +++ b/infra/inttest/minio.yaml @@ -129,8 +129,8 @@ spec: type: RuntimeDefault restartPolicy: OnFailure containers: - - name: mc - image: minio/mc:latest + - name: bucket-init + image: registry.celestium.life/dockerhub-cache/library/python:3.12-slim imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false @@ -143,23 +143,55 @@ spec: limits: cpu: 250m memory: 128Mi - command: ["/bin/sh", "-c"] env: - name: HTTP_PROXY value: "" - name: HTTPS_PROXY value: "" + - name: http_proxy + value: "" + - name: https_proxy + value: "" - name: NO_PROXY - value: "minio,.local,10.0.0.0/8,192.168.0.0/16" + value: "*" - name: no_proxy - value: "minio,.local,10.0.0.0/8,192.168.0.0/16" + value: "*" + command: ["python", "-c"] args: - | - echo "Waiting for MinIO to be ready..." - until mc alias set sandbox http://minio:9000 minioadmin minioadmin 2>/dev/null; do - echo "MinIO not ready, retrying in 2s..." - sleep 2 - done - echo "MinIO is ready. Creating bucket..." - mc mb --ignore-existing sandbox/stonks-normalized - echo "Bucket stonks-normalized created successfully." + import urllib.request, urllib.error, time + + # Disable all proxy usage in urllib + no_proxy_handler = urllib.request.ProxyHandler({}) + opener = urllib.request.build_opener(no_proxy_handler) + urllib.request.install_opener(opener) + + 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 diff --git a/infra/inttest/runner.yaml b/infra/inttest/runner.yaml index 99a7c50..e4f1e5b 100644 --- a/infra/inttest/runner.yaml +++ b/infra/inttest/runner.yaml @@ -49,7 +49,6 @@ spec: - "-v" - "--tb=short" - "--junitxml=/tmp/results.xml" - - "--profiling-output=/tmp/profiling-report.json" securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index f499b0b..3ad81c2 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -16,6 +16,7 @@ from typing import Any import httpx import pytest +import pytest_asyncio from tests.integration.profiler import EndpointProfiler from tests.integration.seed_sandbox import ( @@ -34,9 +35,6 @@ from tests.integration.seed_sandbox import ( SEED_VARIANT_IDS, ) -# Profiling plugin loaded via root conftest.py (pytest_plugins must be top-level) - - # --------------------------------------------------------------------------- # ProfiledAsyncClient — transparent timing wrapper # --------------------------------------------------------------------------- @@ -129,7 +127,7 @@ def trading_api_url() -> str: # --------------------------------------------------------------------------- -@pytest.fixture +@pytest_asyncio.fixture async def query_client( query_api_url: str, profiler: EndpointProfiler, ) -> ProfiledAsyncClient: @@ -138,7 +136,7 @@ async def query_client( yield ProfiledAsyncClient(client, profiler) -@pytest.fixture +@pytest_asyncio.fixture async def registry_client( registry_api_url: str, profiler: EndpointProfiler, ) -> ProfiledAsyncClient: @@ -147,7 +145,7 @@ async def registry_client( yield ProfiledAsyncClient(client, profiler) -@pytest.fixture +@pytest_asyncio.fixture async def risk_client( risk_api_url: str, profiler: EndpointProfiler, ) -> ProfiledAsyncClient: @@ -156,7 +154,7 @@ async def risk_client( yield ProfiledAsyncClient(client, profiler) -@pytest.fixture +@pytest_asyncio.fixture async def trading_client( trading_api_url: str, profiler: EndpointProfiler, ) -> ProfiledAsyncClient: diff --git a/tests/integration/conftest_profiling.py b/tests/integration/conftest_profiling.py index ee1ef33..ce8e860 100644 --- a/tests/integration/conftest_profiling.py +++ b/tests/integration/conftest_profiling.py @@ -16,27 +16,13 @@ import pytest from tests.integration.profiler import EndpointProfiler +# --------------------------------------------------------------------------- +# CLI option — registered by root conftest.py to ensure availability +# --------------------------------------------------------------------------- + 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) # ---------------------------------------------------------------------------