Files
wehub-resource-sync 5a558eb09e
Backend Tests / discover-tests (push) Waiting to run
Backend Tests / ${{ matrix.name }} (push) Blocked by required conditions
Build and Publish SDK / build-and-publish (push) Waiting to run
Build Opik Docker Images / set-version (push) Waiting to run
Build Opik Docker Images / build-backend (push) Blocked by required conditions
Build Opik Docker Images / build-sandbox-executor-python (push) Blocked by required conditions
Build Opik Docker Images / build-python-backend (push) Blocked by required conditions
Build Opik Docker Images / build-frontend (push) Blocked by required conditions
Build Opik Docker Images / create-git-tag (push) Blocked by required conditions
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Waiting to run
Docs - Publish / run (push) Waiting to run
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Waiting to run
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Blocked by required conditions
Frontend Unit Tests / Test on Node 20 (push) Waiting to run
Guardrails E2E Tests / Select Python version matrix (push) Waiting to run
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Blocked by required conditions
Guardrails E2E Tests / 📢 Slack Notification (push) Blocked by required conditions
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Waiting to run
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Blocked by required conditions
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Waiting to run
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Waiting to run
Lint Opik Helm Chart / unittest-helm-chart (push) Waiting to run
Lint Opik Helm Chart / render-equality (push) Waiting to run
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Waiting to run
Python BE E2E Tests / Python BE E2E (push) Waiting to run
Python Backend Tests / run-python-backend-tests (push) Waiting to run
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Waiting to run
Release Drafter / update_release_draft (push) Waiting to run
SDK E2E Libraries Integration Tests / Check Secrets (push) Waiting to run
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Blocked by required conditions
SDK E2E Libraries Integration Tests / build-opik (push) Blocked by required conditions
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Blocked by required conditions
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Waiting to run
TypeScript SDK Build & Publish / build-and-publish (push) Waiting to run
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 13:25:44 +08:00

665 lines
22 KiB
Python

"""Unit tests for run_tests() and the internal test suite evaluation pipeline."""
import threading
import unittest.mock as mock
from opik import url_helpers
from opik.api_objects import opik_client
from opik.api_objects.dataset import dataset_item
from opik.api_objects.dataset.test_suite import test_suite
from opik.evaluation import evaluator as evaluator_module
from ...testlib import ANY_BUT_NONE, SpanModel, assert_equal
from ...testlib.models import TraceModel
def _create_mock_dataset(name="test-dataset", items=None, execution_policy=None):
mock_dataset = mock.MagicMock()
mock_dataset.name = name
mock_dataset.id = "dataset-id-123"
mock_dataset.project_name = None
mock_dataset.dataset_items_count = len(items) if items else 0
mock_dataset.get_evaluators.return_value = []
mock_dataset.get_execution_policy.return_value = execution_policy or {
"runs_per_item": 1,
"pass_threshold": 1,
}
mock_dataset.__internal_api__stream_items_as_dataclasses__ = mock.MagicMock(
return_value=iter(items if items else [])
)
mock_dataset.client = None
return mock_dataset
def _create_suite(mock_dataset, client=None):
mock_dataset.client = client
return test_suite.TestSuite(
name=mock_dataset.name,
dataset_=mock_dataset,
client=client,
)
def test_run_tests__creates_experiment_with_evaluation_method_test_suite():
mock_dataset = _create_mock_dataset()
mock_experiment = mock.MagicMock()
mock_experiment.prompts = None
mock_experiment.id = "exp-123"
mock_experiment.name = "test-experiment"
mock_client = mock.MagicMock()
mock_client.create_experiment.return_value = mock_experiment
suite = _create_suite(mock_dataset, client=mock_client)
with mock.patch.object(
evaluator_module.url_helpers,
"get_experiment_url_by_id",
return_value="http://example.com/exp",
):
evaluator_module.run_tests(
test_suite=suite,
task=lambda item: {"input": item, "output": "response"},
experiment_name="test-experiment",
verbose=0,
)
mock_client.create_experiment.assert_called_once()
call_kwargs = mock_client.create_experiment.call_args[1]
# TODO: OPIK-5795 - migrate DB value from 'evaluation_suite' to 'test_suite'
assert call_kwargs["evaluation_method"] == "evaluation_suite"
def test_run_tests__passes_evaluation_method_not_dataset():
"""Verify it's specifically 'evaluation_suite', not 'dataset'."""
mock_dataset = _create_mock_dataset()
mock_experiment = mock.MagicMock()
mock_experiment.prompts = None
mock_experiment.id = "exp-456"
mock_experiment.name = "test-experiment-2"
mock_client = mock.MagicMock()
mock_client.create_experiment.return_value = mock_experiment
suite = _create_suite(mock_dataset, client=mock_client)
with mock.patch.object(
evaluator_module.url_helpers,
"get_experiment_url_by_id",
return_value="http://example.com/exp",
):
evaluator_module.run_tests(
test_suite=suite,
task=lambda item: {"input": item, "output": "response"},
experiment_name="test-experiment-2",
verbose=0,
)
call_kwargs = mock_client.create_experiment.call_args[1]
assert call_kwargs["evaluation_method"] != "dataset"
# TODO: OPIK-5795 - migrate DB value from 'evaluation_suite' to 'test_suite'
assert call_kwargs["evaluation_method"] == "evaluation_suite"
def _call_run_tests(items, client=None):
"""Helper that runs run_tests through the real streamer pipeline."""
mock_dataset = _create_mock_dataset(items=items)
mock_experiment = mock.Mock()
mock_experiment.prompts = None
mock_experiment.id = "exp-789"
mock_experiment.name = "source-test-experiment"
mock_create_experiment = mock.Mock(return_value=mock_experiment)
mock_get_url = mock.Mock(return_value="any_url")
suite = _create_suite(mock_dataset, client=client)
def simple_task(item):
return {"input": item, "output": "response"}
with mock.patch.object(
opik_client.Opik, "create_experiment", mock_create_experiment
):
with mock.patch.object(url_helpers, "get_experiment_url_by_id", mock_get_url):
evaluator_module.run_tests(
test_suite=suite,
task=simple_task,
experiment_name="source-test-experiment",
verbose=0,
worker_threads=1,
)
def test_run_tests__trace_tree_source_is_experiment(fake_backend):
"""run_tests produces traces with source='experiment'."""
items = [
dataset_item.DatasetItem(
id="item-1", input={"message": "hello"}, reference="hello"
),
]
_call_run_tests(items=items)
expected = TraceModel(
id=ANY_BUT_NONE,
name="evaluation_task",
input=ANY_BUT_NONE,
output=ANY_BUT_NONE,
start_time=ANY_BUT_NONE,
end_time=ANY_BUT_NONE,
last_updated_at=ANY_BUT_NONE,
source="experiment",
spans=[
SpanModel(
id=ANY_BUT_NONE,
name="simple_task",
type="general",
input=ANY_BUT_NONE,
output=ANY_BUT_NONE,
start_time=ANY_BUT_NONE,
end_time=ANY_BUT_NONE,
spans=[],
source="experiment",
),
SpanModel(
id=ANY_BUT_NONE,
name="metrics_calculation",
tags=["__opik_eval_internal__"],
type="general",
input=ANY_BUT_NONE,
output=ANY_BUT_NONE,
start_time=ANY_BUT_NONE,
end_time=ANY_BUT_NONE,
spans=[],
source="experiment",
),
],
)
assert len(fake_backend.trace_trees) == 1
assert_equal(expected, fake_backend.trace_trees[0])
def test_internal_run__with_optimization_id__trace_source_optimization(
fake_backend,
):
"""When optimization_id is set via internal API, traces carry source='optimization'."""
items = [
dataset_item.DatasetItem(
id="item-1", input={"message": "hello"}, reference="hello"
),
]
mock_dataset = _create_mock_dataset(items=items)
mock_experiment = mock.Mock()
mock_experiment.prompts = None
mock_experiment.id = "exp-789"
mock_experiment.name = "source-test-experiment"
mock_create_experiment = mock.Mock(return_value=mock_experiment)
mock_get_url = mock.Mock(return_value="any_url")
def optimization_task(item):
return {"input": item, "output": "response"}
with mock.patch.object(
opik_client.Opik, "create_experiment", mock_create_experiment
):
with mock.patch.object(url_helpers, "get_experiment_url_by_id", mock_get_url):
evaluator_module.__internal_api__run_test_suite__(
suite_dataset=mock_dataset,
task=optimization_task,
client=None,
experiment_name="source-test-experiment",
verbose=0,
task_threads=1,
optimization_id="opt-789",
)
expected = TraceModel(
id=ANY_BUT_NONE,
name="evaluation_task",
input=ANY_BUT_NONE,
output=ANY_BUT_NONE,
start_time=ANY_BUT_NONE,
end_time=ANY_BUT_NONE,
last_updated_at=ANY_BUT_NONE,
source="optimization",
spans=[
SpanModel(
id=ANY_BUT_NONE,
name="optimization_task",
type="general",
input=ANY_BUT_NONE,
output=ANY_BUT_NONE,
start_time=ANY_BUT_NONE,
end_time=ANY_BUT_NONE,
spans=[],
source="optimization",
),
SpanModel(
id=ANY_BUT_NONE,
name="metrics_calculation",
tags=["__opik_eval_internal__"],
type="general",
input=ANY_BUT_NONE,
output=ANY_BUT_NONE,
start_time=ANY_BUT_NONE,
end_time=ANY_BUT_NONE,
spans=[],
source="optimization",
),
],
)
assert len(fake_backend.trace_trees) == 1
assert_equal(expected, fake_backend.trace_trees[0])
def test_run_tests__explicit_client__used_for_experiment_creation():
"""When a suite has an explicit client, run_tests uses it."""
mock_dataset = _create_mock_dataset()
mock_experiment = mock.MagicMock()
mock_experiment.prompts = None
mock_experiment.id = "exp-explicit"
mock_experiment.name = "explicit-experiment"
explicit_client = mock.MagicMock(spec=opik_client.Opik)
explicit_client.create_experiment.return_value = mock_experiment
suite = _create_suite(mock_dataset, client=explicit_client)
with mock.patch.object(
evaluator_module.url_helpers,
"get_experiment_url_by_id",
return_value="http://example.com/exp",
):
evaluator_module.run_tests(
test_suite=suite,
task=lambda item: {"input": item, "output": "response"},
experiment_name="explicit-experiment",
verbose=0,
)
explicit_client.create_experiment.assert_called_once()
def test_run_tests__explicit_client__propagated_to_worker_threads(
fake_backend,
):
"""The suite's client is visible via get_global_client() inside worker threads."""
items = [
dataset_item.DatasetItem(
id="item-1", input={"message": "hello"}, reference="ref"
),
dataset_item.DatasetItem(
id="item-2", input={"message": "world"}, reference="ref"
),
]
mock_dataset = _create_mock_dataset(items=items)
mock_experiment = mock.Mock()
mock_experiment.prompts = None
mock_experiment.id = "exp-thread"
mock_experiment.name = "thread-test-experiment"
mock_create_experiment = mock.Mock(return_value=mock_experiment)
mock_get_url = mock.Mock(return_value="any_url")
clients_seen_in_threads = []
def task_that_captures_client(item):
client = opik_client.get_global_client()
clients_seen_in_threads.append((threading.current_thread().name, id(client)))
return {"input": item, "output": "response"}
suite = _create_suite(mock_dataset)
with mock.patch.object(
opik_client.Opik, "create_experiment", mock_create_experiment
):
with mock.patch.object(url_helpers, "get_experiment_url_by_id", mock_get_url):
evaluator_module.run_tests(
test_suite=suite,
task=task_that_captures_client,
experiment_name="thread-test-experiment",
verbose=0,
worker_threads=2,
)
assert len(clients_seen_in_threads) == 2
client_ids = {entry[1] for entry in clients_seen_in_threads}
assert len(client_ids) == 1, (
f"Worker threads should all see the same client instance, "
f"but saw {len(client_ids)} distinct clients"
)
# =============================================================================
# Execution policy — previously covered by e2e tests in
# tests/e2e/evaluation/test_test_suite.py, moved here because the behaviour is
# SDK-local (engine loops `runs_per_item` times; item-level policy overrides
# suite-level) and does not require a real backend.
# =============================================================================
def test_run_tests__runs_per_item__task_called_n_times():
"""Suite-level runs_per_item=2 causes the task to run twice per item."""
items = [dataset_item.DatasetItem(id="item-1", input={"q": "hi"})]
mock_dataset = _create_mock_dataset(
items=items,
execution_policy={"runs_per_item": 2, "pass_threshold": 1},
)
suite = _create_suite(mock_dataset)
call_count = 0
def task(item):
nonlocal call_count
call_count += 1
return {"input": item, "output": "ok"}
mock_experiment = mock.Mock(id="exp", name="exp")
mock_experiment.prompts = None
with (
mock.patch.object(
opik_client.Opik, "create_experiment", return_value=mock_experiment
),
mock.patch.object(
url_helpers, "get_experiment_url_by_id", return_value="any_url"
),
):
evaluator_module.run_tests(
test_suite=suite,
task=task,
experiment_name="exp",
verbose=0,
worker_threads=1,
)
assert call_count == 2
def test_run_tests__item_level_policy_overrides_suite_policy():
"""Per-item execution_policy wins over the suite-level default."""
items = [
dataset_item.DatasetItem(
id="item-1",
input={"q": "hi"},
execution_policy=dataset_item.ExecutionPolicyItem(
runs_per_item=3, pass_threshold=1
),
)
]
mock_dataset = _create_mock_dataset(
items=items,
execution_policy={"runs_per_item": 1, "pass_threshold": 1},
)
suite = _create_suite(mock_dataset)
call_count = 0
def task(item):
nonlocal call_count
call_count += 1
return {"input": item, "output": "ok"}
mock_experiment = mock.Mock(id="exp", name="exp")
mock_experiment.prompts = None
with (
mock.patch.object(
opik_client.Opik, "create_experiment", return_value=mock_experiment
),
mock.patch.object(
url_helpers, "get_experiment_url_by_id", return_value="any_url"
),
):
evaluator_module.run_tests(
test_suite=suite,
task=task,
experiment_name="exp",
verbose=0,
worker_threads=1,
)
assert call_count == 3
def test_run_tests__no_assertions__items_pass_with_single_run(fake_backend):
"""Default policy with no assertions: task runs once per item and all pass."""
items = [
dataset_item.DatasetItem(id="item-1", input={"q": "a"}),
dataset_item.DatasetItem(id="item-2", input={"q": "b"}),
]
mock_dataset = _create_mock_dataset(items=items)
suite = _create_suite(mock_dataset)
call_count = 0
lock = threading.Lock()
def task(item):
nonlocal call_count
with lock:
call_count += 1
return {"input": item, "output": "ok"}
mock_experiment = mock.Mock(id="exp", name="exp")
mock_experiment.prompts = None
with (
mock.patch.object(
opik_client.Opik, "create_experiment", return_value=mock_experiment
),
mock.patch.object(
url_helpers, "get_experiment_url_by_id", return_value="any_url"
),
):
result = evaluator_module.run_tests(
test_suite=suite,
task=task,
experiment_name="exp",
verbose=0,
worker_threads=1,
)
assert call_count == 2
assert result.items_total == 2
assert result.items_passed == 2
assert result.all_items_passed is True
for item_result in result.item_results.values():
assert item_result.runs_total == 1
assert item_result.pass_threshold == 1
assert len(fake_backend.trace_trees) == 2
def test_run_tests__worker_threads_1__task_runs_in_caller_thread():
"""worker_threads=1 must execute tasks in the caller thread (no extra worker thread)."""
items = [
dataset_item.DatasetItem(id="item-1", input={"q": "a"}),
dataset_item.DatasetItem(id="item-2", input={"q": "b"}),
dataset_item.DatasetItem(id="item-3", input={"q": "c"}),
]
mock_dataset = _create_mock_dataset(items=items)
suite = _create_suite(mock_dataset)
caller_thread_id = threading.get_ident()
thread_ids_during_task = []
def task(item):
thread_ids_during_task.append(threading.get_ident())
return {"input": item, "output": "ok"}
mock_experiment = mock.Mock(id="exp", name="exp")
mock_experiment.prompts = None
with (
mock.patch.object(
opik_client.Opik, "create_experiment", return_value=mock_experiment
),
mock.patch.object(
url_helpers, "get_experiment_url_by_id", return_value="any_url"
),
):
evaluator_module.run_tests(
test_suite=suite,
task=task,
experiment_name="exp",
verbose=0,
worker_threads=1,
)
assert len(thread_ids_during_task) == 3
assert all(tid == caller_thread_id for tid in thread_ids_during_task), (
f"With worker_threads=1, tasks must run in the caller thread "
f"(id={caller_thread_id}); saw {set(thread_ids_during_task)}"
)
def test_run_tests__worker_threads_1__no_thread_pool_executor_created():
"""worker_threads=1 must not instantiate a ThreadPoolExecutor."""
from opik.evaluation.engine import evaluation_tasks_executor
items = [dataset_item.DatasetItem(id="item-1", input={"q": "a"})]
mock_dataset = _create_mock_dataset(items=items)
suite = _create_suite(mock_dataset)
def task(item):
return {"input": item, "output": "ok"}
mock_experiment = mock.Mock(id="exp", name="exp")
mock_experiment.prompts = None
with (
mock.patch.object(
opik_client.Opik, "create_experiment", return_value=mock_experiment
),
mock.patch.object(
url_helpers, "get_experiment_url_by_id", return_value="any_url"
),
mock.patch.object(
evaluation_tasks_executor.futures,
"ThreadPoolExecutor",
wraps=evaluation_tasks_executor.futures.ThreadPoolExecutor,
) as pool_spy,
):
evaluator_module.run_tests(
test_suite=suite,
task=task,
experiment_name="exp",
verbose=0,
worker_threads=1,
)
assert pool_spy.call_count == 0, (
"worker_threads=1 should not create a ThreadPoolExecutor; "
f"got {pool_spy.call_count} calls"
)
def test_run_tests__worker_threads_2__task_runs_in_worker_threads():
"""worker_threads=2 must dispatch tasks to threads other than the caller."""
items = [
dataset_item.DatasetItem(id=f"item-{i}", input={"q": str(i)}) for i in range(4)
]
mock_dataset = _create_mock_dataset(items=items)
suite = _create_suite(mock_dataset)
caller_thread_id = threading.get_ident()
thread_ids_during_task = []
thread_ids_lock = threading.Lock()
def task(item):
with thread_ids_lock:
thread_ids_during_task.append(threading.get_ident())
return {"input": item, "output": "ok"}
mock_experiment = mock.Mock(id="exp", name="exp")
mock_experiment.prompts = None
with (
mock.patch.object(
opik_client.Opik, "create_experiment", return_value=mock_experiment
),
mock.patch.object(
url_helpers, "get_experiment_url_by_id", return_value="any_url"
),
):
evaluator_module.run_tests(
test_suite=suite,
task=task,
experiment_name="exp",
verbose=0,
worker_threads=2,
)
assert len(thread_ids_during_task) == 4
assert all(tid != caller_thread_id for tid in thread_ids_during_task), (
f"With worker_threads=2, tasks must run off the caller thread "
f"(id={caller_thread_id}); saw {set(thread_ids_during_task)}"
)
def test_run_tests__worker_threads_1__caller_context_client_restored():
"""worker_threads=1 must not leak the suite's client into the caller's context."""
items = [dataset_item.DatasetItem(id="item-1", input={"q": "a"})]
mock_dataset = _create_mock_dataset(items=items)
pre_existing_client = mock.MagicMock(spec=opik_client.Opik)
suite_client = mock.MagicMock(spec=opik_client.Opik)
mock_experiment = mock.Mock(id="exp", name="exp")
mock_experiment.prompts = None
suite_client.create_experiment.return_value = mock_experiment
suite = _create_suite(mock_dataset, client=suite_client)
token = opik_client._context_client_var.set(pre_existing_client)
try:
with mock.patch.object(
url_helpers, "get_experiment_url_by_id", return_value="any_url"
):
evaluator_module.run_tests(
test_suite=suite,
task=lambda item: {"input": item, "output": "ok"},
experiment_name="exp",
verbose=0,
worker_threads=1,
)
assert opik_client._context_client_var.get() is pre_existing_client, (
"Caller's context-local client must be restored after run_tests exits"
)
finally:
opik_client._context_client_var.reset(token)
def test_run_tests__worker_threads_1__task_exception_propagates():
"""worker_threads=1 sequential path still surfaces task exceptions."""
items = [dataset_item.DatasetItem(id="item-1", input={"q": "a"})]
mock_dataset = _create_mock_dataset(items=items)
suite = _create_suite(mock_dataset)
class BoomError(RuntimeError):
pass
def task(item):
raise BoomError("synchronous failure")
mock_experiment = mock.Mock(id="exp", name="exp")
mock_experiment.prompts = None
with (
mock.patch.object(
opik_client.Opik, "create_experiment", return_value=mock_experiment
),
mock.patch.object(
url_helpers, "get_experiment_url_by_id", return_value="any_url"
),
):
try:
evaluator_module.run_tests(
test_suite=suite,
task=task,
experiment_name="exp",
verbose=0,
worker_threads=1,
)
except BoomError:
return
raise AssertionError("BoomError should have propagated to the caller")