chore: import upstream snapshot with attribution
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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:44 +08:00
commit 5a558eb09e
11579 changed files with 1795921 additions and 0 deletions
@@ -0,0 +1,513 @@
"""Unit tests for test_suite.converters module."""
import json
import os
import tempfile
import pandas as pd
import pandas.testing
import pytest
from unittest import mock
from opik.api_objects.dataset import dataset_item
from opik.api_objects.dataset.test_suite import converters
# ---------------------------------------------------------------------------
# evaluators_to_assertions
# ---------------------------------------------------------------------------
def test_evaluators_to_assertions__multiple_evaluators__concatenates():
e1 = mock.MagicMock()
e1.assertions = ["A1"]
e2 = mock.MagicMock()
e2.assertions = ["A2", "A3"]
assert converters.evaluators_to_assertions([e1, e2]) == ["A1", "A2", "A3"]
def test_evaluators_to_assertions__empty_list__returns_empty():
assert converters.evaluators_to_assertions([]) == []
# ---------------------------------------------------------------------------
# version_evaluators_to_assertions
# ---------------------------------------------------------------------------
def test_version_evaluators_to_assertions__llm_judge__extracts_assertions():
from opik.evaluation.suite_evaluators import LLMJudge
judge = LLMJudge(assertions=["Response is accurate"], track=False)
config = judge.to_config().model_dump(by_alias=True)
evaluator_item = mock.MagicMock()
evaluator_item.type = "llm_judge"
evaluator_item.config = config
assert converters.version_evaluators_to_assertions([evaluator_item]) == [
"Response is accurate"
]
def test_version_evaluators_to_assertions__none__returns_empty():
assert converters.version_evaluators_to_assertions(None) == []
def test_version_evaluators_to_assertions__non_llm_judge__skipped():
evaluator_item = mock.MagicMock()
evaluator_item.type = "custom_scorer"
assert converters.version_evaluators_to_assertions([evaluator_item]) == []
# ---------------------------------------------------------------------------
# version_policy_to_execution_policy
# ---------------------------------------------------------------------------
def test_version_policy_to_execution_policy__converts():
policy = mock.MagicMock()
policy.runs_per_item = 5
policy.pass_threshold = 3
assert converters.version_policy_to_execution_policy(policy) == {
"runs_per_item": 5,
"pass_threshold": 3,
}
def test_version_policy_to_execution_policy__none__returns_default():
assert converters.version_policy_to_execution_policy(None) == {
"runs_per_item": 1,
"pass_threshold": 1,
}
# ---------------------------------------------------------------------------
# dataset_item_to_suite_item_dict (DatasetItem → TestSuiteItem)
# ---------------------------------------------------------------------------
def test_dataset_item_to_suite_item_dict__all_fields():
item = dataset_item.DatasetItem(
id="item-1",
description="Test item",
question="What is 2+2?",
execution_policy=dataset_item.ExecutionPolicyItem(
runs_per_item=3, pass_threshold=2
),
)
assert converters.dataset_item_to_suite_item_dict(item) == {
"id": "item-1",
"data": {"question": "What is 2+2?"},
"assertions": [],
"description": "Test item",
"execution_policy": {"runs_per_item": 3, "pass_threshold": 2},
}
def test_dataset_item_to_suite_item_dict__minimal():
item = dataset_item.DatasetItem(id="item-1", question="Hello")
result = converters.dataset_item_to_suite_item_dict(item)
assert result == {"id": "item-1", "data": {"question": "Hello"}, "assertions": []}
assert "description" not in result
assert "execution_policy" not in result
def test_dataset_item_to_suite_item_dict__with_evaluators__extracts_assertions():
from opik.evaluation.suite_evaluators import LLMJudge
judge = LLMJudge(assertions=["Is correct"], track=False)
config = judge.to_config().model_dump(by_alias=True)
item = dataset_item.DatasetItem(
id="item-2",
evaluators=[
dataset_item.EvaluatorItem(
name="llm_judge", type="llm_judge", config=config
),
],
question="Hello",
)
assert converters.dataset_item_to_suite_item_dict(item)["assertions"] == [
"Is correct"
]
# ---------------------------------------------------------------------------
# suite_item_dict_to_dataset_item (TestSuiteItem → DatasetItem)
# ---------------------------------------------------------------------------
def test_suite_item_dict_to_dataset_item__all_fields():
item = {
"id": "item-1",
"data": {"question": "Hello", "context": "test"},
"assertions": ["Is polite"],
"description": "A test case",
"execution_policy": {"runs_per_item": 5, "pass_threshold": 3},
}
ds_item = converters.suite_item_dict_to_dataset_item(item)
assert ds_item.id == "item-1"
assert ds_item.get_content() == {"question": "Hello", "context": "test"}
assert ds_item.description == "A test case"
assert ds_item.execution_policy is not None
assert ds_item.execution_policy.runs_per_item == 5
assert ds_item.execution_policy.pass_threshold == 3
assert ds_item.evaluators is not None
assert len(ds_item.evaluators) == 1
assert ds_item.evaluators[0].type == "llm_judge"
def test_suite_item_dict_to_dataset_item__minimal__generates_id():
ds_item = converters.suite_item_dict_to_dataset_item(
{"data": {"question": "Hello"}}
)
assert ds_item.id is not None
assert len(ds_item.id) > 0
assert ds_item.get_content() == {"question": "Hello"}
assert ds_item.evaluators is None
assert ds_item.execution_policy is None
# ---------------------------------------------------------------------------
# to_json / from_json
# ---------------------------------------------------------------------------
SAMPLE_ITEMS = [
{
"id": "item-1",
"data": {"question": "How do I get a refund?", "context": "Premium user"},
"assertions": ["Response is polite"],
"description": "Refund scenario",
"execution_policy": {"runs_per_item": 3, "pass_threshold": 2},
},
{
"id": "item-2",
"data": {"question": "Is my account hacked?"},
"assertions": [],
},
]
def test_to_json__happyflow():
EXPECTED = [
{
"id": "item-1",
"data": {"question": "How do I get a refund?", "context": "Premium user"},
"assertions": ["Response is polite"],
"description": "Refund scenario",
"execution_policy": {"runs_per_item": 3, "pass_threshold": 2},
},
{
"id": "item-2",
"data": {"question": "Is my account hacked?"},
"assertions": [],
},
]
assert json.loads(converters.to_json(SAMPLE_ITEMS)) == EXPECTED
def test_to_json__empty_list():
assert json.loads(converters.to_json([])) == []
def test_from_json__happyflow():
json_str = json.dumps(
[
{"data": {"question": "Hello"}, "assertions": ["Is polite"]},
{"data": {"question": "Bye"}},
]
)
EXPECTED = [
{"data": {"question": "Hello"}, "assertions": ["Is polite"]},
{"data": {"question": "Bye"}},
]
assert converters.from_json(json_str, {}, []) == EXPECTED
def test_from_json__with_keys_mapping():
json_str = json.dumps(
[
{"test_data": {"question": "Hello"}, "checks": ["Is polite"]},
]
)
EXPECTED = [{"data": {"question": "Hello"}, "assertions": ["Is polite"]}]
assert (
converters.from_json(
json_str, {"test_data": "data", "checks": "assertions"}, []
)
== EXPECTED
)
def test_from_json__with_ignore_keys():
json_str = json.dumps(
[
{"data": {"question": "Hello"}, "internal_note": "skip this"},
]
)
EXPECTED = [{"data": {"question": "Hello"}}]
assert converters.from_json(json_str, {}, ["internal_note"]) == EXPECTED
def test_from_json__non_array__raises_value_error():
json_str = json.dumps({"data": {"question": "Hello"}})
with pytest.raises(ValueError, match="must be an array"):
converters.from_json(json_str, {}, [])
# ---------------------------------------------------------------------------
# to_pandas / from_pandas
# ---------------------------------------------------------------------------
def test_to_pandas__happyflow():
EXPECTED = pd.DataFrame(
[
{
"id": "item-1",
"data": {
"question": "How do I get a refund?",
"context": "Premium user",
},
"assertions": ["Response is polite"],
"description": "Refund scenario",
"execution_policy": {"runs_per_item": 3, "pass_threshold": 2},
},
{
"id": "item-2",
"data": {"question": "Is my account hacked?"},
"assertions": [],
},
]
)
pandas.testing.assert_frame_equal(converters.to_pandas(SAMPLE_ITEMS), EXPECTED)
def test_to_pandas__empty_list():
assert len(converters.to_pandas([])) == 0
def test_from_pandas__happyflow():
dataframe = pd.DataFrame(
[
{"data": {"question": "Hello"}, "assertions": ["Is polite"]},
{"data": {"question": "Bye"}},
]
)
EXPECTED = [
{"data": {"question": "Hello"}, "assertions": ["Is polite"]},
{"data": {"question": "Bye"}},
]
assert converters.from_pandas(dataframe, {}, []) == EXPECTED
def test_from_pandas__with_keys_mapping():
dataframe = pd.DataFrame([{"test_data": {"question": "Hello"}}])
EXPECTED = [{"data": {"question": "Hello"}}]
assert converters.from_pandas(dataframe, {"test_data": "data"}, []) == EXPECTED
def test_from_pandas__nan_values__skipped():
dataframe = pd.DataFrame(
[
{"data": {"question": "Hello"}, "assertions": ["Is polite"]},
{"data": {"question": "Bye"}, "assertions": float("nan")},
]
)
result = converters.from_pandas(dataframe, {}, [])
assert result[0] == {"data": {"question": "Hello"}, "assertions": ["Is polite"]}
assert result[1] == {"data": {"question": "Bye"}}
# ---------------------------------------------------------------------------
# from_jsonl_file
# ---------------------------------------------------------------------------
def test_from_jsonl_file__happyflow():
jsonl_content = (
'{"data": {"question": "What is 2+2?"}, "assertions": ["Is correct"]}\n'
'{"data": {"question": "Capital of France?"}}\n'
)
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
f.write(jsonl_content)
path = f.name
try:
EXPECTED = [
{"data": {"question": "What is 2+2?"}, "assertions": ["Is correct"]},
{"data": {"question": "Capital of France?"}},
]
assert converters.from_jsonl_file(path, {}, []) == EXPECTED
finally:
os.unlink(path)
def test_from_jsonl_file__empty_lines__skipped():
jsonl_content = '{"data": {"question": "Q1"}}\n\n{"data": {"question": "Q2"}}\n\n'
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
f.write(jsonl_content)
path = f.name
try:
assert len(converters.from_jsonl_file(path, {}, [])) == 2
finally:
os.unlink(path)
def test_from_jsonl_file__with_keys_mapping():
jsonl_content = '{"test_data": {"question": "Hello"}}\n'
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
f.write(jsonl_content)
path = f.name
try:
EXPECTED = [{"data": {"question": "Hello"}}]
assert converters.from_jsonl_file(path, {"test_data": "data"}, []) == EXPECTED
finally:
os.unlink(path)
# ---------------------------------------------------------------------------
# Round-trip tests
# ---------------------------------------------------------------------------
def test_adapter_roundtrip__suite_to_dataset_to_suite__preserves_all_fields():
original = {
"id": "item-1",
"data": {"question": "Hello", "context": "Premium"},
"assertions": ["Is polite", "Is helpful"],
"description": "Test case",
"execution_policy": {"runs_per_item": 3, "pass_threshold": 2},
}
ds_item = converters.suite_item_dict_to_dataset_item(original)
recovered = converters.dataset_item_to_suite_item_dict(ds_item)
assert recovered["id"] == original["id"]
assert recovered["data"] == original["data"]
assert sorted(recovered["assertions"]) == sorted(original["assertions"])
assert recovered["description"] == original["description"]
assert recovered["execution_policy"] == original["execution_policy"]
def test_adapter_roundtrip__two_cycles__stable():
original = {
"id": "item-1",
"data": {"question": "Hello"},
"assertions": ["Is polite"],
"execution_policy": {"runs_per_item": 5, "pass_threshold": 3},
}
suite_item = original
for _ in range(2):
ds_item = converters.suite_item_dict_to_dataset_item(suite_item)
suite_item = converters.dataset_item_to_suite_item_dict(ds_item)
assert suite_item["data"] == original["data"]
assert suite_item["assertions"] == original["assertions"]
assert suite_item["execution_policy"] == original["execution_policy"]
def test_json_roundtrip__export_import_export__stable():
json_str_1 = converters.to_json(SAMPLE_ITEMS)
imported = converters.from_json(json_str_1, {}, [])
json_str_2 = converters.to_json(imported)
assert json.loads(json_str_1) == json.loads(json_str_2)
def test_json_roundtrip__import_export_import__stable():
json_str = json.dumps(
[
{
"data": {"question": "Hello"},
"assertions": ["Is polite"],
"description": "Test",
"execution_policy": {"runs_per_item": 3, "pass_threshold": 2},
},
]
)
items_1 = converters.from_json(json_str, {}, [])
exported = converters.to_json(items_1)
items_2 = converters.from_json(exported, {}, [])
assert items_1 == items_2
def test_pandas_roundtrip__export_import_export__stable():
df_1 = converters.to_pandas(SAMPLE_ITEMS)
imported = converters.from_pandas(df_1, {}, [])
df_2 = converters.to_pandas(imported)
pandas.testing.assert_frame_equal(df_1, df_2)
def test_full_roundtrip__json_through_adapters__two_cycles():
"""JSON → from_json → adapter → adapter → to_json, repeated twice."""
original_json = json.dumps(
[
{
"id": "item-1",
"data": {"question": "Refund?", "tier": "premium"},
"assertions": ["Is polite", "No hallucination"],
"description": "Refund scenario",
"execution_policy": {"runs_per_item": 3, "pass_threshold": 2},
},
]
)
current_json = original_json
for _ in range(2):
suite_items = converters.from_json(current_json, {}, [])
ds_items = [converters.suite_item_dict_to_dataset_item(i) for i in suite_items]
suite_items = [converters.dataset_item_to_suite_item_dict(i) for i in ds_items]
current_json = converters.to_json(suite_items)
EXPECTED = {
"id": "item-1",
"data": {"question": "Refund?", "tier": "premium"},
"assertions": ["Is polite", "No hallucination"],
"description": "Refund scenario",
"execution_policy": {"runs_per_item": 3, "pass_threshold": 2},
}
final = json.loads(current_json)
assert len(final) == 1
result = final[0]
assert result["id"] == EXPECTED["id"]
assert result["data"] == EXPECTED["data"]
assert sorted(result["assertions"]) == sorted(EXPECTED["assertions"])
assert result["description"] == EXPECTED["description"]
assert result["execution_policy"] == EXPECTED["execution_policy"]
@@ -0,0 +1,317 @@
"""Unit tests for test suite result file generation."""
import json
import os
from opik.api_objects.dataset.test_suite import (
suite_result_constructor,
types as suite_types,
)
from opik.api_objects.dataset.test_suite.report_processors import file_writer
from opik.api_objects.dataset import dataset_item
from opik.evaluation import evaluation_result, test_result, test_case
from opik.evaluation.metrics import score_result
def _make_test_result(
dataset_item_id: str,
trial_id: int,
scores: list[tuple[str, float]],
task_output: dict | None = None,
dataset_item_content: dict | None = None,
execution_policy: dict | None = None,
task_execution_time: float | None = None,
scoring_time: float | None = None,
) -> test_result.TestResult:
ds_item = None
if execution_policy is not None:
ds_item = dataset_item.DatasetItem(
id=dataset_item_id,
execution_policy=dataset_item.ExecutionPolicyItem(
runs_per_item=execution_policy.get("runs_per_item"),
pass_threshold=execution_policy.get("pass_threshold"),
),
)
return test_result.TestResult(
test_case=test_case.TestCase(
trace_id=f"trace-{dataset_item_id}-{trial_id}",
dataset_item_id=dataset_item_id,
task_output=task_output or {"input": "test", "output": "result"},
dataset_item_content=dataset_item_content or {"question": "What?"},
dataset_item=ds_item,
),
score_results=[
score_result.ScoreResult(name=name, value=value) for name, value in scores
],
trial_id=trial_id,
task_execution_time=task_execution_time,
scoring_time=scoring_time,
)
def _make_suite_result(
test_results_list: list[test_result.TestResult],
suite_name: str | None = None,
total_time: float | None = None,
) -> suite_types.TestSuiteResult:
eval_result = evaluation_result.EvaluationResult(
experiment_id="exp-123",
dataset_id="dataset-456",
experiment_name="my-experiment",
test_results=test_results_list,
experiment_url="http://example.com/experiment/exp-123",
trial_count=1,
)
return suite_result_constructor.build_suite_result(
eval_result,
suite_name=suite_name,
total_time=total_time,
)
class TestToReportDict:
def test_to_report_dict__single_item_with_mixed_scores__returns_correct_structure(
self,
):
test_results_list = [
_make_test_result(
dataset_item_id="item-1",
trial_id=0,
scores=[("Is polite", True), ("Is helpful", False)],
task_output={"input": "hi", "output": "hello"},
dataset_item_content={"question": "hi"},
execution_policy={"runs_per_item": 1, "pass_threshold": 1},
task_execution_time=1.234,
scoring_time=0.567,
)
]
suite_result = _make_suite_result(test_results_list, suite_name="My Suite")
result = suite_result.to_report_dict()
assert result["suite_passed"] is False
assert result["items_passed"] == 0
assert result["items_total"] == 1
assert result["pass_rate"] == 0.0
assert result["experiment_id"] == "exp-123"
assert result["experiment_name"] == "my-experiment"
assert result["experiment_url"] == "http://example.com/experiment/exp-123"
assert result["suite_name"] == "My Suite"
assert "generated_at" in result
assert len(result["items"]) == 1
item = result["items"][0]
assert item["dataset_item_id"] == "item-1"
assert item["passed"] is False
assert item["runs_passed"] == 0
assert item["execution_policy"] == {"runs_per_item": 1, "pass_threshold": 1}
assert len(item["runs"]) == 1
run = item["runs"][0]
assert run["trial_id"] == 0
assert run["passed"] is False
assert run["input"] == "hi"
assert run["output"] == "hello"
assert run["trace_id"] == "trace-item-1-0"
assert run["task_execution_time_seconds"] == 1.234
assert run["scoring_time_seconds"] == 0.567
assert len(run["assertions"]) == 2
assert run["assertions"][0]["name"] == "Is polite"
assert run["assertions"][0]["passed"] is True
assert run["assertions"][1]["name"] == "Is helpful"
assert run["assertions"][1]["passed"] is False
def test_to_report_dict__all_items_pass__suite_passed_true(self):
test_results_list = [
_make_test_result(
dataset_item_id="item-1",
trial_id=0,
scores=[("A1", True)],
execution_policy={"runs_per_item": 1, "pass_threshold": 1},
),
_make_test_result(
dataset_item_id="item-2",
trial_id=0,
scores=[("A1", True)],
execution_policy={"runs_per_item": 1, "pass_threshold": 1},
),
]
suite_result = _make_suite_result(test_results_list)
result = suite_result.to_report_dict()
assert result["suite_passed"] is True
assert result["items_passed"] == 2
assert result["pass_rate"] == 1.0
def test_to_report_dict__with_total_time__includes_rounded_value(self):
test_results_list = [
_make_test_result(
dataset_item_id="item-1",
trial_id=0,
scores=[("A1", True)],
execution_policy={"runs_per_item": 1, "pass_threshold": 1},
),
]
suite_result = _make_suite_result(test_results_list, total_time=12.3456)
result = suite_result.to_report_dict()
assert result["total_time_seconds"] == 12.346
def test_to_report_dict__scoring_failed__marks_assertion_failed_with_reason(self):
test_results_list = [
test_result.TestResult(
test_case=test_case.TestCase(
trace_id="trace-1",
dataset_item_id="item-1",
task_output={"output": "test"},
dataset_item_content={},
dataset_item=dataset_item.DatasetItem(
id="item-1",
execution_policy=dataset_item.ExecutionPolicyItem(
runs_per_item=1,
pass_threshold=1,
),
),
),
score_results=[
score_result.ScoreResult(
name="A1",
value=0,
scoring_failed=True,
reason="Model error",
),
],
trial_id=0,
)
]
suite_result = _make_suite_result(test_results_list)
result = suite_result.to_report_dict()
assertion = result["items"][0]["runs"][0]["assertions"][0]
assert assertion["passed"] is False
assert assertion["scoring_failed"] is True
assert assertion["reason"] == "Model error"
class TestSaveReport:
def test_save_report__valid_input__writes_json_file(self, tmp_path):
test_results_list = [
_make_test_result(
dataset_item_id="item-1",
trial_id=0,
scores=[("A1", True)],
execution_policy={"runs_per_item": 1, "pass_threshold": 1},
),
]
suite_result = _make_suite_result(
test_results_list, suite_name="Test Suite", total_time=5.0
)
output_path = str(tmp_path / "report.json")
result_path = file_writer.save_report(suite_result, output_path)
assert result_path == output_path
assert os.path.exists(output_path)
with open(output_path) as f:
data = json.load(f)
assert data["suite_name"] == "Test Suite"
assert data["suite_passed"] is True
assert len(data["items"]) == 1
def test_save_report__no_path__uses_experiment_name(self, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
test_results_list = [
_make_test_result(
dataset_item_id="item-1",
trial_id=0,
scores=[("A1", True)],
execution_policy={"runs_per_item": 1, "pass_threshold": 1},
),
]
suite_result = _make_suite_result(test_results_list)
result_path = file_writer.save_report(suite_result)
assert "my-experiment" in os.path.basename(result_path)
assert result_path.endswith(".json")
assert os.path.exists(result_path)
def test_save_report__nested_path__creates_parent_directories(self, tmp_path):
output_path = str(tmp_path / "nested" / "dir" / "report.json")
test_results_list = [
_make_test_result(
dataset_item_id="item-1",
trial_id=0,
scores=[("A1", True)],
execution_policy={"runs_per_item": 1, "pass_threshold": 1},
),
]
suite_result = _make_suite_result(test_results_list)
result_path = file_writer.save_report(suite_result, output_path)
assert os.path.exists(result_path)
class TestBuildDefaultReportPath:
def test_build_default_report_path__unsafe_characters__replaces_with_underscore(
self,
):
path = file_writer.build_default_report_path("my/suite:name")
assert os.path.basename(path) == "my_suite_name.json"
def test_build_default_report_path__safe_characters__keeps_unchanged(self):
path = file_writer.build_default_report_path("my-suite_v1.0")
assert os.path.basename(path) == "my-suite_v1.0.json"
def test_build_default_report_path__spaces__replaces_with_underscore(self):
path = file_writer.build_default_report_path("my suite")
assert os.path.basename(path) == "my_suite.json"
class TestTestSuiteResultMethods:
def test_to_dict__passing_suite__returns_dict_with_suite_passed_true(self):
test_results_list = [
_make_test_result(
dataset_item_id="item-1",
trial_id=0,
scores=[("A1", True)],
execution_policy={"runs_per_item": 1, "pass_threshold": 1},
),
]
suite_result = _make_suite_result(test_results_list)
result = suite_result.to_dict()
assert isinstance(result, dict)
assert result["suite_passed"] is True
assert "items" in result
def test_to_report_dict__save_to_file__produces_valid_json(self, tmp_path):
test_results_list = [
_make_test_result(
dataset_item_id="item-1",
trial_id=0,
scores=[("A1", True)],
execution_policy={"runs_per_item": 1, "pass_threshold": 1},
),
]
suite_result = _make_suite_result(test_results_list)
output_path = str(tmp_path / "result.json")
path = file_writer.save_report(suite_result, output_path)
assert os.path.exists(path)
with open(path) as f:
data = json.load(f)
assert data["suite_passed"] is True
File diff suppressed because it is too large Load Diff