Files
comet-ml--opik/sdks/python/tests/unit/runner/test_pid_file.py
T
wehub-resource-sync 5a558eb09e
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
TypeScript SDK Library Integration Tests / Check Secrets (push) Has been cancelled
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Has been cancelled
SDK Library Integration Tests Runner / Check Secrets (push) Has been cancelled
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Has been cancelled
SDK Library Integration Tests Runner / Build (push) Has been cancelled
SDK Library Integration Tests Runner / openai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / llama_index_tests (push) Has been cancelled
SDK Library Integration Tests Runner / anthropic_tests (push) Has been cancelled
SDK Library Integration Tests Runner / mistral_tests (push) Has been cancelled
SDK Library Integration Tests Runner / groq_tests (push) Has been cancelled
SDK Library Integration Tests Runner / aisuite_tests (push) Has been cancelled
SDK Library Integration Tests Runner / haystack_tests (push) Has been cancelled
SDK Library Integration Tests Runner / dspy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v1_tests (push) Has been cancelled
SDK Library Integration Tests Runner / genai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Has been cancelled
SDK Library Integration Tests Runner / bedrock_tests (push) Has been cancelled
SDK Library Integration Tests Runner / litellm_tests (push) Has been cancelled
SDK Library Integration Tests Runner / harbor_tests (push) Has been cancelled
SDK Library Integration Tests Runner / Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / render-equality (push) Has been cancelled
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Has been cancelled
Python BE E2E Tests / Python BE E2E (push) Has been cancelled
Python Backend Tests / run-python-backend-tests (push) Has been cancelled
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
SDK E2E Libraries Integration Tests / Check Secrets (push) Has been cancelled
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Has been cancelled
SDK E2E Libraries Integration Tests / build-opik (push) Has been cancelled
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Has been cancelled
TypeScript SDK Build & Publish / build-and-publish (push) Has been cancelled
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Has been cancelled
Backend Tests / discover-tests (push) Has been cancelled
Backend Tests / ${{ matrix.name }} (push) Has been cancelled
Build and Publish SDK / build-and-publish (push) Has been cancelled
Build Opik Docker Images / set-version (push) Has been cancelled
Build Opik Docker Images / build-backend (push) Has been cancelled
Build Opik Docker Images / build-sandbox-executor-python (push) Has been cancelled
Build Opik Docker Images / build-python-backend (push) Has been cancelled
Build Opik Docker Images / build-frontend (push) Has been cancelled
Build Opik Docker Images / create-git-tag (push) Has been cancelled
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Has been cancelled
Docs - Publish / run (push) Has been cancelled
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Has been cancelled
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Has been cancelled
Frontend Unit Tests / Test on Node 20 (push) Has been cancelled
Guardrails E2E Tests / Select Python version matrix (push) Has been cancelled
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Guardrails E2E Tests / 📢 Slack Notification (push) Has been cancelled
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Has been cancelled
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Has been cancelled
Lint Opik Helm Chart / unittest-helm-chart (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:25:44 +08:00

249 lines
8.5 KiB
Python

"""Tests for opik.runner.pid_file."""
import json
import os
import signal
import subprocess
import sys
import time
from pathlib import Path
import pytest
from opik.runner import pid_file
@pytest.fixture
def tmp_runners_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
runners = tmp_path / "runners"
monkeypatch.setattr(pid_file, "_RUNNERS_DIR", runners)
return runners
class TestWrite:
def test_write__happyflow(self, tmp_runners_dir: Path) -> None:
path = pid_file.write(
runner_id="r-1",
runner_type="connect",
project_name="my-proj",
workspace="my-ws",
)
assert path is not None
assert path.parent == tmp_runners_dir
data = json.loads(path.read_text())
assert data["pid"] == os.getpid()
assert data["runner_id"] == "r-1"
assert data["runner_type"] == "connect"
assert data["project_name"] == "my-proj"
assert data["workspace"] == "my-ws"
assert isinstance(data["started_at"], float)
def test_write__runners_dir_missing__creates_dir(
self, tmp_runners_dir: Path
) -> None:
assert not tmp_runners_dir.exists()
pid_file.write(
runner_id="r-1",
runner_type="endpoint",
project_name="p",
workspace=None,
)
assert tmp_runners_dir.exists()
def test_write__file_exists__overwrites(self, tmp_runners_dir: Path) -> None:
pid_file.write(
runner_id="r-1", runner_type="connect", project_name="a", workspace=None
)
path = pid_file.write(
runner_id="r-1", runner_type="connect", project_name="b", workspace=None
)
assert path is not None
data = json.loads(path.read_text())
assert data["project_name"] == "b"
def test_write__oserror__returns_none(
self, tmp_runners_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
def boom(*_a, **_kw):
raise OSError("disk full")
monkeypatch.setattr(Path, "mkdir", lambda *_a, **_kw: boom())
result = pid_file.write(
runner_id="r-1", runner_type="connect", project_name="p", workspace=None
)
assert result is None
class TestRemove:
def test_remove__file_exists__deletes(self, tmp_runners_dir: Path) -> None:
pid_file.write(
runner_id="r-1", runner_type="connect", project_name="p", workspace=None
)
pid_file.remove(runner_type="connect", runner_id="r-1")
assert not (tmp_runners_dir / "connect-r-1.json").exists()
def test_remove__file_missing__noop(self, tmp_runners_dir: Path) -> None:
# Must not raise on a runner that was never written or already cleaned up.
pid_file.remove(runner_type="connect", runner_id="absent")
class TestIsPidAlive:
def test_is_pid_alive__current_process__returns_true(self) -> None:
assert pid_file.is_pid_alive(os.getpid())
def test_is_pid_alive__non_positive_pid__returns_false(self) -> None:
assert not pid_file.is_pid_alive(0)
assert not pid_file.is_pid_alive(-1)
def test_is_pid_alive__exited_subprocess__returns_false(self) -> None:
proc = subprocess.Popen([sys.executable, "-c", "pass"])
proc.wait(timeout=5)
# Spin briefly while the kernel reaps.
for _ in range(20):
if not pid_file.is_pid_alive(proc.pid):
break
time.sleep(0.05)
assert not pid_file.is_pid_alive(proc.pid)
class TestListAll:
def _write_raw(self, dir: Path, name: str, payload: dict) -> Path:
dir.mkdir(parents=True, exist_ok=True)
path = dir / name
path.write_text(json.dumps(payload))
return path
def test_list_all__empty_dir__returns_empty(self, tmp_runners_dir: Path) -> None:
assert pid_file.list_all() == []
def test_list_all__stale_pid__skipped(self, tmp_runners_dir: Path) -> None:
# PID 1 (init) is alive; a high-number PID is very unlikely to be alive.
self._write_raw(
tmp_runners_dir,
"connect-alive.json",
{
"pid": os.getpid(),
"runner_id": "alive",
"runner_type": "connect",
"project_name": "p",
"workspace": None,
"started_at": 1.0,
},
)
self._write_raw(
tmp_runners_dir,
"connect-dead.json",
{
"pid": 2_147_483_640,
"runner_id": "dead",
"runner_type": "connect",
"project_name": "p",
"workspace": None,
"started_at": 1.0,
},
)
ids = [r.runner_id for r in pid_file.list_all()]
assert ids == ["alive"]
def test_list_all__runner_type_filter__returns_matching_only(
self, tmp_runners_dir: Path
) -> None:
for runner_type in ("connect", "endpoint"):
self._write_raw(
tmp_runners_dir,
f"{runner_type}-1.json",
{
"pid": os.getpid(),
"runner_id": "1",
"runner_type": runner_type,
"project_name": "p",
"workspace": None,
"started_at": 1.0,
},
)
connect_only = pid_file.list_all(runner_type="connect")
assert [r.runner_type for r in connect_only] == ["connect"]
def test_list_all__malformed_files__skipped(self, tmp_runners_dir: Path) -> None:
tmp_runners_dir.mkdir(parents=True)
(tmp_runners_dir / "connect-bad.json").write_text("not json {")
(tmp_runners_dir / "connect-missing-fields.json").write_text(
json.dumps({"pid": 1})
)
assert pid_file.list_all() == []
def test_list_all__non_json_file__ignored(self, tmp_runners_dir: Path) -> None:
tmp_runners_dir.mkdir(parents=True)
(tmp_runners_dir / "README.txt").write_text("hi")
assert pid_file.list_all() == []
class TestPurgeStale:
def test_write__stale_entry_exists__purged(self, tmp_runners_dir: Path) -> None:
# Seed a stale file, then write a new one and confirm the stale was removed.
tmp_runners_dir.mkdir(parents=True)
stale = tmp_runners_dir / "connect-stale.json"
stale.write_text(
json.dumps(
{
"pid": 2_147_483_640,
"runner_id": "stale",
"runner_type": "connect",
"project_name": "p",
"workspace": None,
"started_at": 1.0,
}
)
)
pid_file.write(
runner_id="r-new",
runner_type="connect",
project_name="p",
workspace=None,
)
assert not stale.exists()
def test_write__malformed_entry_exists__purged(self, tmp_runners_dir: Path) -> None:
tmp_runners_dir.mkdir(parents=True)
bad = tmp_runners_dir / "connect-bad.json"
bad.write_text("not json")
pid_file.write(
runner_id="r-new",
runner_type="connect",
project_name="p",
workspace=None,
)
assert not bad.exists()
class TestSignalRoundtrip:
"""Spin up a sleeping subprocess, write a pid file for it, signal it via the
discovered pid, and check the file/process disappear. Guards the contract
that `opik <type> stop` relies on: list_all → os.kill → process exits."""
def test_sigterm_via_pid_file__happyflow(self, tmp_runners_dir: Path) -> None:
proc = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(60)"])
try:
tmp_runners_dir.mkdir(parents=True)
payload = {
"pid": proc.pid,
"runner_id": "r-1",
"runner_type": "connect",
"project_name": "p",
"workspace": None,
"started_at": 1.0,
}
(tmp_runners_dir / "connect-r-1.json").write_text(json.dumps(payload))
runners = pid_file.list_all(runner_type="connect")
assert len(runners) == 1
assert runners[0].pid == proc.pid
os.kill(runners[0].pid, signal.SIGTERM)
proc.wait(timeout=5)
assert proc.returncode is not None
finally:
if proc.poll() is None:
proc.kill()
proc.wait(timeout=5)