Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

1143 lines
40 KiB
Python

"""
Coverage tests for benchmarks/runners.py.
Targets uncovered paths: dataset fallback loading, example processing with
dataset class methods, legacy browsecomp field handling, quick_summary calls,
error handling, evaluation paths (human, automated, fallback), metrics/report
generation, progress callbacks at every stage, and convenience wrappers.
"""
import json
import tempfile
from pathlib import Path
from unittest.mock import Mock, patch
MODULE = "local_deep_research.benchmarks.runners"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_example(problem="Q1", answer="A1", example_id="ex_1", **extra):
d = {"id": example_id, "problem": problem, "answer": answer}
d.update(extra)
return d
def _quick_summary_response(summary="Response text", sources=None):
return {"summary": summary, "sources": sources or []}
def _setup_registry_with_fake_class(mock_registry, examples):
"""Set up mock registry so isinstance check fails (legacy path)."""
FakeClass = type("OtherClass", (), {})
mock_dataset = Mock()
mock_dataset.load.return_value = examples
mock_registry.create_dataset.return_value = mock_dataset
mock_registry.get_dataset_class.return_value = FakeClass
# ---------------------------------------------------------------------------
# format_query (small gap: default parameter)
# ---------------------------------------------------------------------------
class TestFormatQueryDefaults:
def test_default_dataset_type_is_simpleqa(self):
from local_deep_research.benchmarks.runners import format_query
assert format_query("hello") == "hello"
# ---------------------------------------------------------------------------
# run_benchmark - dataset loading
# ---------------------------------------------------------------------------
class TestDatasetLoadingFallback:
"""Cover lines 97-107: fallback to legacy load_dataset on registry error."""
@patch(f"{MODULE}.load_dataset")
@patch(f"{MODULE}.DatasetRegistry")
def test_fallback_to_legacy_load_dataset(self, mock_registry, mock_load):
from local_deep_research.benchmarks.runners import run_benchmark
mock_registry.create_dataset.side_effect = ValueError("no such dataset")
mock_load.return_value = []
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
)
mock_load.assert_called_once()
assert result["status"] == "complete_no_eval"
# ---------------------------------------------------------------------------
# run_benchmark - example processing with dataset class methods
# ---------------------------------------------------------------------------
class TestExampleProcessingDatasetClass:
"""Cover lines 146-155: using dataset_instance.get_question / get_answer."""
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_uses_dataset_class_methods(
self, mock_registry, mock_qs, mock_extract
):
from local_deep_research.benchmarks.runners import run_benchmark
FakeDatasetClass = type(
"FakeDataset",
(),
{
"load": lambda self: [
{"id": "1", "problem": "Q", "answer": "A"}
],
"get_question": lambda self, ex: "class_question",
"get_answer": lambda self, ex: "class_answer",
},
)
instance = FakeDatasetClass()
mock_registry.create_dataset.return_value = instance
mock_registry.get_dataset_class.return_value = FakeDatasetClass
mock_qs.return_value = _quick_summary_response()
mock_extract.return_value = {
"extracted_answer": "ans",
"confidence": 0.9,
}
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
)
assert result["total_examples"] == 1
with open(result["results_path"]) as f:
line = json.loads(f.readline())
assert line["correct_answer"] == "class_answer"
assert line["problem"] == "class_question"
# ---------------------------------------------------------------------------
# run_benchmark - legacy fallback for simpleqa and browsecomp
# ---------------------------------------------------------------------------
class TestLegacyFieldExtraction:
"""Cover lines 156-167: legacy approach for extracting question/answer."""
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_legacy_simpleqa_fields(self, mock_registry, mock_qs, mock_extract):
from local_deep_research.benchmarks.runners import run_benchmark
_setup_registry_with_fake_class(
mock_registry,
[{"id": "1", "problem": "legacy_q", "answer": "legacy_a"}],
)
mock_qs.return_value = _quick_summary_response()
mock_extract.return_value = {"extracted_answer": "x", "confidence": 0.5}
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
)
with open(result["results_path"]) as f:
line = json.loads(f.readline())
assert line["problem"] == "legacy_q"
assert line["correct_answer"] == "legacy_a"
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_legacy_browsecomp_correct_answer_field(
self, mock_registry, mock_qs, mock_extract
):
from local_deep_research.benchmarks.runners import run_benchmark
_setup_registry_with_fake_class(
mock_registry,
[{"id": "1", "problem": "bc_q", "correct_answer": "bc_correct"}],
)
mock_qs.return_value = _quick_summary_response()
mock_extract.return_value = {"extracted_answer": "x", "confidence": 0.5}
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="browsecomp",
output_dir=tmpdir,
run_evaluation=False,
)
with open(result["results_path"]) as f:
line = json.loads(f.readline())
assert line["correct_answer"] == "bc_correct"
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_legacy_browsecomp_fallback_to_answer_field(
self, mock_registry, mock_qs, mock_extract
):
"""Cover line 166-167: fallback to 'answer' when 'correct_answer' missing."""
from local_deep_research.benchmarks.runners import run_benchmark
_setup_registry_with_fake_class(
mock_registry,
[{"id": "1", "problem": "bc_q", "answer": "bc_fallback_answer"}],
)
mock_qs.return_value = _quick_summary_response()
mock_extract.return_value = {"extracted_answer": "x", "confidence": 0.5}
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="browsecomp",
output_dir=tmpdir,
run_evaluation=False,
)
with open(result["results_path"]) as f:
line = json.loads(f.readline())
assert line["correct_answer"] == "bc_fallback_answer"
# ---------------------------------------------------------------------------
# run_benchmark - successful example processing
# ---------------------------------------------------------------------------
class TestSuccessfulExampleProcessing:
"""Cover lines 188-245: quick_summary call, extract, write result."""
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_result_contains_expected_fields(
self, mock_registry, mock_qs, mock_extract
):
from local_deep_research.benchmarks.runners import run_benchmark
_setup_registry_with_fake_class(mock_registry, [_make_example()])
mock_qs.return_value = _quick_summary_response("my summary", ["src1"])
mock_extract.return_value = {
"extracted_answer": "extracted",
"confidence": 0.8,
}
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
search_config={
"iterations": 2,
"questions_per_iteration": 1,
"search_tool": "wiki",
},
)
with open(result["results_path"]) as f:
line = json.loads(f.readline())
assert line["response"] == "my summary"
assert line["extracted_answer"] == "extracted"
assert line["confidence"] == 0.8
assert line["sources"] == ["src1"]
assert "processing_time" in line
assert line["search_config"]["search_tool"] == "wiki"
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_quick_summary_called_with_search_config(
self, mock_registry, mock_qs, mock_extract
):
from local_deep_research.benchmarks.runners import run_benchmark
_setup_registry_with_fake_class(mock_registry, [_make_example()])
mock_qs.return_value = _quick_summary_response()
mock_extract.return_value = {"extracted_answer": "a", "confidence": 0.5}
cfg = {
"iterations": 7,
"questions_per_iteration": 5,
"search_tool": "google",
}
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
search_config=cfg,
)
mock_qs.assert_called_once()
_, kwargs = mock_qs.call_args
assert kwargs["iterations"] == 7
assert kwargs["questions_per_iteration"] == 5
assert kwargs["search_tool"] == "google"
# ---------------------------------------------------------------------------
# run_benchmark - error handling during processing
# ---------------------------------------------------------------------------
class TestExampleProcessingError:
"""Cover lines 247-280: error during quick_summary."""
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_error_result_written_on_exception(self, mock_registry, mock_qs):
from local_deep_research.benchmarks.runners import run_benchmark
_setup_registry_with_fake_class(mock_registry, [_make_example()])
mock_qs.side_effect = RuntimeError("search failed")
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
)
with open(result["results_path"]) as f:
line = json.loads(f.readline())
assert "error" in line
assert "search failed" in line["error"]
assert line["id"] == "ex_1"
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_error_with_progress_callback(self, mock_registry, mock_qs):
from local_deep_research.benchmarks.runners import run_benchmark
_setup_registry_with_fake_class(mock_registry, [_make_example()])
mock_qs.side_effect = RuntimeError("boom")
callback = Mock()
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
progress_callback=callback,
)
statuses = [
c[0][2]["status"] for c in callback.call_args_list if len(c[0]) >= 3
]
assert "error" in statuses
assert "started" in statuses
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_error_result_uses_fallback_id(self, mock_registry, mock_qs):
"""When example has no 'id', fallback to example_{i}."""
from local_deep_research.benchmarks.runners import run_benchmark
_setup_registry_with_fake_class(
mock_registry, [{"problem": "q", "answer": "a"}]
)
mock_qs.side_effect = RuntimeError("fail")
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
)
with open(result["results_path"]) as f:
line = json.loads(f.readline())
assert line["id"] == "example_0"
# ---------------------------------------------------------------------------
# run_benchmark - progress callbacks throughout
# ---------------------------------------------------------------------------
class TestProgressCallbacks:
"""Cover all progress_callback invocations."""
@patch(f"{MODULE}.generate_report")
@patch(f"{MODULE}.calculate_metrics")
@patch(f"{MODULE}.grade_results")
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_all_progress_stages_with_evaluation(
self,
mock_registry,
mock_qs,
mock_extract,
mock_grade,
mock_calc,
mock_report,
):
from local_deep_research.benchmarks.runners import run_benchmark
_setup_registry_with_fake_class(mock_registry, [_make_example()])
mock_qs.return_value = _quick_summary_response()
mock_extract.return_value = {"extracted_answer": "a", "confidence": 0.5}
mock_grade.return_value = []
mock_calc.return_value = {"accuracy": 0.5}
mock_report.return_value = "/report.md"
callback = Mock()
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
progress_callback=callback,
)
statuses = [c[0][2]["status"] for c in callback.call_args_list]
assert "started" in statuses
assert "processing" in statuses
assert "completed_example" in statuses
assert "evaluating" in statuses
assert "calculating_metrics" in statuses
assert "generating_report" in statuses
assert "complete" in statuses
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_progress_truncates_long_question(
self, mock_registry, mock_qs, mock_extract
):
from local_deep_research.benchmarks.runners import run_benchmark
_setup_registry_with_fake_class(
mock_registry, [{"id": "1", "problem": "x" * 100, "answer": "a"}]
)
mock_qs.return_value = _quick_summary_response()
mock_extract.return_value = {"extracted_answer": "a", "confidence": 0.5}
callback = Mock()
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
progress_callback=callback,
)
processing_calls = [
c
for c in callback.call_args_list
if len(c[0]) >= 3 and c[0][2].get("status") == "processing"
]
assert len(processing_calls) == 1
assert processing_calls[0][0][2]["question"].endswith("...")
@patch(f"{MODULE}.DatasetRegistry")
def test_no_eval_progress_callback(self, mock_registry):
from local_deep_research.benchmarks.runners import run_benchmark
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
callback = Mock()
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
progress_callback=callback,
)
statuses = [c[0][2]["status"] for c in callback.call_args_list]
assert "complete_no_eval" in statuses
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_progress_short_question_no_truncation(
self, mock_registry, mock_qs, mock_extract
):
from local_deep_research.benchmarks.runners import run_benchmark
short_q = "Short question"
_setup_registry_with_fake_class(
mock_registry, [{"id": "1", "problem": short_q, "answer": "a"}]
)
mock_qs.return_value = _quick_summary_response()
mock_extract.return_value = {"extracted_answer": "a", "confidence": 0.5}
callback = Mock()
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
progress_callback=callback,
)
processing_calls = [
c
for c in callback.call_args_list
if len(c[0]) >= 3 and c[0][2].get("status") == "processing"
]
assert processing_calls[0][0][2]["question"] == short_q
# ---------------------------------------------------------------------------
# run_benchmark - evaluation paths
# ---------------------------------------------------------------------------
class TestEvaluationPaths:
"""Cover lines 285-425: evaluation with grading, metrics, report."""
@patch(f"{MODULE}.generate_report")
@patch(f"{MODULE}.calculate_metrics")
@patch(f"{MODULE}.grade_results")
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_automated_evaluation_success(
self,
mock_registry,
mock_qs,
mock_extract,
mock_grade,
mock_calc,
mock_report,
):
from local_deep_research.benchmarks.runners import run_benchmark
_setup_registry_with_fake_class(mock_registry, [_make_example()])
mock_qs.return_value = _quick_summary_response()
mock_extract.return_value = {"extracted_answer": "a", "confidence": 0.9}
mock_grade.return_value = [{"grade": "correct"}]
mock_calc.return_value = {"accuracy": 1.0}
mock_report.return_value = "/tmp/report.md"
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
)
assert result["status"] == "complete"
assert result["accuracy"] == 1.0
assert "report_path" in result
assert "evaluation_path" in result
mock_grade.assert_called_once()
mock_calc.assert_called_once()
@patch(f"{MODULE}.generate_report")
@patch(f"{MODULE}.calculate_metrics")
@patch(f"{MODULE}.grade_results")
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_automated_evaluation_with_eval_config(
self,
mock_registry,
mock_qs,
mock_extract,
mock_grade,
mock_calc,
mock_report,
):
from local_deep_research.benchmarks.runners import run_benchmark
_setup_registry_with_fake_class(mock_registry, [_make_example()])
mock_qs.return_value = _quick_summary_response()
mock_extract.return_value = {"extracted_answer": "a", "confidence": 0.5}
mock_grade.return_value = []
mock_calc.return_value = {"accuracy": 0.0}
mock_report.return_value = "/report.md"
eval_config = {"model": "test-model"}
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
evaluation_config=eval_config,
)
_, kwargs = mock_grade.call_args
assert kwargs["evaluation_config"] == eval_config
@patch(f"{MODULE}.generate_report")
@patch(f"{MODULE}.calculate_metrics")
@patch(f"{MODULE}.DatasetRegistry")
def test_human_evaluation_path(self, mock_registry, mock_calc, mock_report):
from local_deep_research.benchmarks.runners import run_benchmark
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
mock_calc.return_value = {"accuracy": 0.5}
mock_report.return_value = "/report.md"
with (
patch(f"{MODULE}.grade_results") as mock_grade,
patch(
"local_deep_research.benchmarks.graders.human_evaluation"
) as mock_human,
):
mock_human.return_value = [{"grade": "correct"}]
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
human_evaluation=True,
)
mock_human.assert_called_once()
mock_grade.assert_not_called()
assert result["status"] == "complete"
@patch(f"{MODULE}.generate_report")
@patch(f"{MODULE}.calculate_metrics")
@patch(f"{MODULE}.grade_results")
@patch(f"{MODULE}.DatasetRegistry")
def test_evaluation_report_config_info(
self, mock_registry, mock_grade, mock_calc, mock_report
):
"""Cover the config_info dict passed to generate_report."""
from local_deep_research.benchmarks.runners import run_benchmark
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
mock_grade.return_value = []
mock_calc.return_value = {"accuracy": 0.0}
mock_report.return_value = "/report.md"
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
human_evaluation=False,
dataset_path="/custom/path.json",
search_config={
"iterations": 10,
"questions_per_iteration": 2,
"search_tool": "bing",
},
)
_, kwargs = mock_report.call_args
assert kwargs["dataset_name"] == "Simpleqa"
config = kwargs["config_info"]
assert config["Dataset"] == "/custom/path.json"
assert config["Iterations"] == 10
assert config["Search tool"] == "bing"
assert config["Evaluation method"] == "Automated"
@patch(f"{MODULE}.generate_report")
@patch(f"{MODULE}.calculate_metrics")
@patch(f"{MODULE}.DatasetRegistry")
def test_evaluation_report_human_method_label(
self, mock_registry, mock_calc, mock_report
):
from local_deep_research.benchmarks.runners import run_benchmark
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
mock_calc.return_value = {"accuracy": 0.0}
mock_report.return_value = "/report.md"
with patch(
"local_deep_research.benchmarks.graders.human_evaluation"
) as mock_human:
mock_human.return_value = []
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
human_evaluation=True,
)
_, kwargs = mock_report.call_args
assert kwargs["config_info"]["Evaluation method"] == "Human"
# ---------------------------------------------------------------------------
# run_benchmark - automated evaluation failure
# ---------------------------------------------------------------------------
class TestEvaluationFailure:
"""Cover lines 320-367: grade_results raises, fallback decision."""
@patch("builtins.input", return_value="n")
@patch("builtins.print")
@patch(f"{MODULE}.grade_results")
@patch(f"{MODULE}.DatasetRegistry")
def test_eval_failure_skip_human_fallback(
self, mock_registry, mock_grade, mock_print, mock_input
):
from local_deep_research.benchmarks.runners import run_benchmark
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
mock_grade.side_effect = RuntimeError("eval error")
with patch(
"local_deep_research.security.file_write_verifier.write_file_verified"
) as mock_write:
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
)
assert result["status"] == "evaluation_error"
assert "eval error" in result["evaluation_error"]
mock_write.assert_called_once()
@patch("builtins.input", return_value="y")
@patch("builtins.print")
@patch(f"{MODULE}.generate_report")
@patch(f"{MODULE}.calculate_metrics")
@patch(f"{MODULE}.grade_results")
@patch(f"{MODULE}.DatasetRegistry")
def test_eval_failure_accept_human_fallback(
self,
mock_registry,
mock_grade,
mock_calc,
mock_report,
mock_print,
mock_input,
):
from local_deep_research.benchmarks.runners import run_benchmark
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
mock_grade.side_effect = RuntimeError("eval error")
mock_calc.return_value = {"accuracy": 0.5}
mock_report.return_value = "/report.md"
with patch(
"local_deep_research.benchmarks.graders.human_evaluation"
) as mock_human:
mock_human.return_value = []
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
)
mock_human.assert_called_once()
assert result["status"] == "complete"
@patch("builtins.input", return_value="n")
@patch("builtins.print")
@patch(f"{MODULE}.grade_results")
@patch(f"{MODULE}.DatasetRegistry")
def test_eval_failure_with_progress_callback(
self, mock_registry, mock_grade, mock_print, mock_input
):
from local_deep_research.benchmarks.runners import run_benchmark
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
mock_grade.side_effect = RuntimeError("eval error")
callback = Mock()
with patch(
"local_deep_research.security.file_write_verifier.write_file_verified"
):
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
progress_callback=callback,
)
statuses = [
c[0][2]["status"] for c in callback.call_args_list if len(c[0]) >= 3
]
assert "evaluation_fallback" in statuses
# ---------------------------------------------------------------------------
# run_benchmark - grade_results progress_callback lambda
# ---------------------------------------------------------------------------
class TestGradeProgressCallback:
"""Cover lines 310-318: the lambda passed as progress_callback to grade_results."""
@patch(f"{MODULE}.generate_report")
@patch(f"{MODULE}.calculate_metrics")
@patch(f"{MODULE}.grade_results")
@patch(f"{MODULE}.DatasetRegistry")
def test_grade_results_progress_lambda_invoked(
self, mock_registry, mock_grade, mock_calc, mock_report
):
from local_deep_research.benchmarks.runners import run_benchmark
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
def fake_grade(**kwargs):
cb = kwargs.get("progress_callback")
if cb:
cb(0, 10, {"detail": "grading"})
cb(5, 10, {"detail": "grading"})
return []
mock_grade.side_effect = fake_grade
mock_calc.return_value = {"accuracy": 0.0}
mock_report.return_value = "/report.md"
outer_callback = Mock()
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
progress_callback=outer_callback,
)
evaluating_calls = [
c
for c in outer_callback.call_args_list
if len(c[0]) >= 3 and c[0][2].get("status") == "evaluating"
]
assert len(evaluating_calls) >= 2
@patch(f"{MODULE}.generate_report")
@patch(f"{MODULE}.calculate_metrics")
@patch(f"{MODULE}.grade_results")
@patch(f"{MODULE}.DatasetRegistry")
def test_grade_results_progress_lambda_without_outer_callback(
self, mock_registry, mock_grade, mock_calc, mock_report
):
"""When no outer callback, the lambda should still work (returns None)."""
from local_deep_research.benchmarks.runners import run_benchmark
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
captured_cb = {}
def fake_grade(**kwargs):
captured_cb["cb"] = kwargs.get("progress_callback")
return []
mock_grade.side_effect = fake_grade
mock_calc.return_value = {"accuracy": 0.0}
mock_report.return_value = "/report.md"
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
progress_callback=None,
)
cb = captured_cb["cb"]
assert cb is not None
result = cb(0, 10, {"x": 1})
assert result is None
# ---------------------------------------------------------------------------
# run_benchmark - no evaluation path
# ---------------------------------------------------------------------------
class TestNoEvaluation:
"""Cover lines 427-441: run_evaluation=False."""
@patch(f"{MODULE}.DatasetRegistry")
def test_no_eval_returns_correct_status(self, mock_registry):
from local_deep_research.benchmarks.runners import run_benchmark
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
)
assert result["status"] == "complete_no_eval"
assert "evaluation_path" not in result
assert "metrics" not in result
# ---------------------------------------------------------------------------
# run_benchmark - file cleanup of existing output files
# ---------------------------------------------------------------------------
class TestOutputFileCleanup:
"""Cover lines 122-125: unlinking existing output files."""
@patch(f"{MODULE}.DatasetRegistry")
@patch(f"{MODULE}.time")
def test_existing_files_are_removed(self, mock_time, mock_registry):
from local_deep_research.benchmarks.runners import run_benchmark
mock_time.strftime.return_value = "20260101_000000"
mock_time.time.return_value = 0
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
with tempfile.TemporaryDirectory() as tmpdir:
for suffix in ["_results.jsonl", "_evaluation.jsonl", "_report.md"]:
p = Path(tmpdir) / f"simpleqa_20260101_000000{suffix}"
p.write_text("old content")
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
)
results_file = (
Path(tmpdir) / "simpleqa_20260101_000000_results.jsonl"
)
assert not results_file.exists() or results_file.read_text() == ""
# ---------------------------------------------------------------------------
# Convenience wrapper functions
# ---------------------------------------------------------------------------
class TestConvenienceWrappers:
"""Cover run_simpleqa_benchmark, run_browsecomp_benchmark, run_xbench_deepsearch_benchmark."""
@patch(f"{MODULE}.run_benchmark")
def test_run_simpleqa_benchmark(self, mock_rb):
from local_deep_research.benchmarks.runners import (
run_simpleqa_benchmark,
)
mock_rb.return_value = {"status": "ok"}
result = run_simpleqa_benchmark(num_examples=50, output_dir="/tmp/test")
mock_rb.assert_called_once_with(
dataset_type="simpleqa", num_examples=50, output_dir="/tmp/test"
)
assert result == {"status": "ok"}
@patch(f"{MODULE}.run_benchmark")
def test_run_browsecomp_benchmark(self, mock_rb):
from local_deep_research.benchmarks.runners import (
run_browsecomp_benchmark,
)
mock_rb.return_value = {"status": "ok"}
result = run_browsecomp_benchmark(num_examples=25)
mock_rb.assert_called_once_with(
dataset_type="browsecomp", num_examples=25
)
assert result == {"status": "ok"}
@patch(f"{MODULE}.run_benchmark")
def test_run_xbench_deepsearch_benchmark(self, mock_rb):
from local_deep_research.benchmarks.runners import (
run_xbench_deepsearch_benchmark,
)
mock_rb.return_value = {"status": "ok"}
result = run_xbench_deepsearch_benchmark(num_examples=10, seed=99)
mock_rb.assert_called_once_with(
dataset_type="xbench_deepsearch", num_examples=10, seed=99
)
assert result == {"status": "ok"}
# ---------------------------------------------------------------------------
# run_benchmark - multiple examples
# ---------------------------------------------------------------------------
class TestMultipleExamples:
"""Cover loop iteration with multiple examples."""
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_multiple_examples_all_written(
self, mock_registry, mock_qs, mock_extract
):
from local_deep_research.benchmarks.runners import run_benchmark
examples = [
_make_example(f"Q{i}", f"A{i}", f"id_{i}") for i in range(3)
]
_setup_registry_with_fake_class(mock_registry, examples)
mock_qs.return_value = _quick_summary_response()
mock_extract.return_value = {"extracted_answer": "a", "confidence": 0.5}
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
)
assert result["total_examples"] == 3
with open(result["results_path"]) as f:
lines = f.readlines()
assert len(lines) == 3
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.DatasetRegistry")
def test_mix_of_success_and_error(
self, mock_registry, mock_qs, mock_extract
):
from local_deep_research.benchmarks.runners import run_benchmark
examples = [
_make_example(f"Q{i}", f"A{i}", f"id_{i}") for i in range(3)
]
_setup_registry_with_fake_class(mock_registry, examples)
mock_qs.side_effect = [
_quick_summary_response(),
RuntimeError("fail"),
_quick_summary_response(),
]
mock_extract.return_value = {"extracted_answer": "a", "confidence": 0.5}
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=False,
)
with open(result["results_path"]) as f:
lines = [json.loads(line) for line in f.readlines()]
assert len(lines) == 3
assert "error" not in lines[0]
assert "error" in lines[1]
assert "error" not in lines[2]
# ---------------------------------------------------------------------------
# run_benchmark - DEFAULT_DATASET_URLS fallback in report config
# ---------------------------------------------------------------------------
class TestDefaultDatasetUrlInReport:
"""Cover line 391: when dataset_path is None, use DEFAULT_DATASET_URLS."""
@patch(f"{MODULE}.generate_report")
@patch(f"{MODULE}.calculate_metrics")
@patch(f"{MODULE}.grade_results")
@patch(f"{MODULE}.DatasetRegistry")
def test_report_uses_default_url_when_no_path(
self, mock_registry, mock_grade, mock_calc, mock_report
):
from local_deep_research.benchmarks.runners import run_benchmark
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
mock_grade.return_value = []
mock_calc.return_value = {"accuracy": 0.0}
mock_report.return_value = "/report.md"
with tempfile.TemporaryDirectory() as tmpdir:
run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
dataset_path=None,
)
_, kwargs = mock_report.call_args
assert kwargs["config_info"]["Dataset"] is not None
# ---------------------------------------------------------------------------
# run_benchmark - metrics accuracy key missing
# ---------------------------------------------------------------------------
class TestMetricsAccuracyFallback:
"""Cover line 424: metrics.get('accuracy', 0) when key missing."""
@patch(f"{MODULE}.generate_report")
@patch(f"{MODULE}.calculate_metrics")
@patch(f"{MODULE}.grade_results")
@patch(f"{MODULE}.DatasetRegistry")
def test_accuracy_defaults_to_zero(
self, mock_registry, mock_grade, mock_calc, mock_report
):
from local_deep_research.benchmarks.runners import run_benchmark
mock_dataset = Mock()
mock_dataset.load.return_value = []
mock_registry.create_dataset.return_value = mock_dataset
mock_grade.return_value = []
mock_calc.return_value = {} # No accuracy key
mock_report.return_value = "/report.md"
with tempfile.TemporaryDirectory() as tmpdir:
result = run_benchmark(
dataset_type="simpleqa",
output_dir=tmpdir,
run_evaluation=True,
)
assert result["accuracy"] == 0