7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
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
744 lines
26 KiB
Python
744 lines
26 KiB
Python
"""
|
||
Coverage-focused tests for benchmarks/metrics/calculation.py.
|
||
|
||
Targets the ~37% uncovered code, specifically:
|
||
- evaluate_benchmark_quality (with mocked runner)
|
||
- measure_execution_time (with mocked SearchSystem)
|
||
- calculate_quality_metrics (delegates to evaluate_benchmark_quality)
|
||
- calculate_speed_metrics (delegates to measure_execution_time)
|
||
- calculate_resource_metrics edge cases not yet covered
|
||
- calculate_combined_score with weights that need normalization
|
||
- calculate_metrics: malformed JSON mixed with valid lines, integer confidence,
|
||
confidence as None, confidence as a list (TypeError path)
|
||
"""
|
||
|
||
import json
|
||
from pathlib import Path
|
||
from unittest.mock import MagicMock, patch
|
||
|
||
import pytest
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Helper to write JSONL files
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def _write_jsonl(path, records):
|
||
"""Write a list of dicts (or raw strings) to a JSONL file."""
|
||
with open(path, "w") as f:
|
||
for r in records:
|
||
if isinstance(r, str):
|
||
f.write(r + "\n")
|
||
else:
|
||
f.write(json.dumps(r) + "\n")
|
||
|
||
|
||
# ===========================================================================
|
||
# calculate_metrics – coverage-gap tests
|
||
# ===========================================================================
|
||
|
||
|
||
class TestCalculateMetricsCoverageGaps:
|
||
"""Tests targeting lines not yet exercised by existing suites."""
|
||
|
||
def test_malformed_json_among_valid_lines_returns_error(self, tmp_path):
|
||
"""A single malformed JSON line causes json.loads to raise,
|
||
which is caught by the broad except and returns an error dict."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_metrics,
|
||
)
|
||
|
||
results_file = tmp_path / "mixed.jsonl"
|
||
_write_jsonl(
|
||
results_file,
|
||
[
|
||
'{"is_correct": true}',
|
||
"NOT JSON AT ALL",
|
||
],
|
||
)
|
||
result = calculate_metrics(str(results_file))
|
||
# The function catches *any* exception while iterating and returns error
|
||
assert "error" in result
|
||
|
||
def test_confidence_as_integer_value(self, tmp_path):
|
||
"""Integer confidence (not string) is truthy when non-zero,
|
||
and int() on an int succeeds."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_metrics,
|
||
)
|
||
|
||
results_file = tmp_path / "int_conf.jsonl"
|
||
_write_jsonl(
|
||
results_file,
|
||
[
|
||
{"confidence": 75},
|
||
{"confidence": 25},
|
||
],
|
||
)
|
||
metrics = calculate_metrics(str(results_file))
|
||
assert metrics["average_confidence"] == 50
|
||
|
||
def test_confidence_none_is_skipped(self, tmp_path):
|
||
"""confidence=None is falsy, so it should be skipped."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_metrics,
|
||
)
|
||
|
||
results_file = tmp_path / "none_conf.jsonl"
|
||
_write_jsonl(
|
||
results_file,
|
||
[
|
||
{"confidence": None},
|
||
{"confidence": "60"},
|
||
],
|
||
)
|
||
metrics = calculate_metrics(str(results_file))
|
||
assert metrics["average_confidence"] == 60
|
||
|
||
def test_confidence_as_list_triggers_type_error(self, tmp_path):
|
||
"""confidence=[1,2] is truthy, but int([1,2]) raises TypeError,
|
||
which is caught and skipped."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_metrics,
|
||
)
|
||
|
||
results_file = tmp_path / "list_conf.jsonl"
|
||
_write_jsonl(
|
||
results_file,
|
||
[
|
||
{"confidence": [1, 2]},
|
||
{"confidence": "50"},
|
||
],
|
||
)
|
||
metrics = calculate_metrics(str(results_file))
|
||
assert metrics["average_confidence"] == 50
|
||
|
||
def test_confidence_as_float_string(self, tmp_path):
|
||
"""confidence='85.5' – int('85.5') raises ValueError, skipped."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_metrics,
|
||
)
|
||
|
||
results_file = tmp_path / "float_conf.jsonl"
|
||
_write_jsonl(
|
||
results_file,
|
||
[
|
||
{"confidence": "85.5"},
|
||
{"confidence": "100"},
|
||
],
|
||
)
|
||
metrics = calculate_metrics(str(results_file))
|
||
# "85.5" is skipped (ValueError on int()), only "100" counted
|
||
assert metrics["average_confidence"] == 100
|
||
|
||
def test_no_categories_key_absent_from_metrics(self, tmp_path):
|
||
"""When no result has 'category', metrics should not contain 'categories'."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_metrics,
|
||
)
|
||
|
||
results_file = tmp_path / "no_cat.jsonl"
|
||
_write_jsonl(
|
||
results_file,
|
||
[
|
||
{"is_correct": True},
|
||
{"is_correct": False},
|
||
],
|
||
)
|
||
metrics = calculate_metrics(str(results_file))
|
||
assert "categories" not in metrics
|
||
|
||
def test_processing_time_zero_included(self, tmp_path):
|
||
"""processing_time=0 is in the result (key exists), so it counts."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_metrics,
|
||
)
|
||
|
||
results_file = tmp_path / "zero_time.jsonl"
|
||
_write_jsonl(
|
||
results_file,
|
||
[
|
||
{"processing_time": 0},
|
||
{"processing_time": 10.0},
|
||
],
|
||
)
|
||
metrics = calculate_metrics(str(results_file))
|
||
assert metrics["average_processing_time"] == 5.0
|
||
|
||
def test_multiple_categories(self, tmp_path):
|
||
"""Three categories, each with different accuracy."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_metrics,
|
||
)
|
||
|
||
results_file = tmp_path / "multi_cat.jsonl"
|
||
_write_jsonl(
|
||
results_file,
|
||
[
|
||
{"is_correct": True, "category": "A"},
|
||
{"is_correct": True, "category": "A"},
|
||
{"is_correct": False, "category": "B"},
|
||
{"is_correct": True, "category": "C"},
|
||
{"is_correct": False, "category": "C"},
|
||
{"is_correct": False, "category": "C"},
|
||
],
|
||
)
|
||
metrics = calculate_metrics(str(results_file))
|
||
assert metrics["categories"]["A"]["accuracy"] == 1.0
|
||
assert metrics["categories"]["B"]["accuracy"] == 0.0
|
||
assert metrics["categories"]["C"]["accuracy"] == pytest.approx(1 / 3)
|
||
|
||
def test_large_number_of_results(self, tmp_path):
|
||
"""Ensure it handles a larger dataset correctly."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_metrics,
|
||
)
|
||
|
||
results_file = tmp_path / "large.jsonl"
|
||
records = [
|
||
{"is_correct": i % 2 == 0, "processing_time": float(i)}
|
||
for i in range(100)
|
||
]
|
||
_write_jsonl(results_file, records)
|
||
metrics = calculate_metrics(str(results_file))
|
||
assert metrics["total_examples"] == 100
|
||
assert metrics["graded_examples"] == 100
|
||
assert metrics["correct"] == 50
|
||
assert metrics["accuracy"] == 0.5
|
||
|
||
def test_error_field_with_is_correct(self, tmp_path):
|
||
"""A result can have both 'error' and 'is_correct'."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_metrics,
|
||
)
|
||
|
||
results_file = tmp_path / "both.jsonl"
|
||
_write_jsonl(
|
||
results_file,
|
||
[
|
||
{"is_correct": True, "error": "partial failure"},
|
||
],
|
||
)
|
||
metrics = calculate_metrics(str(results_file))
|
||
assert metrics["error_count"] == 1
|
||
assert metrics["graded_examples"] == 1
|
||
assert metrics["correct"] == 1
|
||
|
||
|
||
# ===========================================================================
|
||
# evaluate_benchmark_quality – mocked runner
|
||
# ===========================================================================
|
||
|
||
|
||
class TestEvaluateBenchmarkQuality:
|
||
"""Tests for evaluate_benchmark_quality with mocked run_simpleqa_benchmark."""
|
||
|
||
def test_returns_accuracy_and_quality_score(self, tmp_path):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
evaluate_benchmark_quality,
|
||
)
|
||
|
||
mock_results = {"metrics": {"accuracy": 0.75}}
|
||
with patch(
|
||
"local_deep_research.benchmarks.runners.run_simpleqa_benchmark",
|
||
return_value=mock_results,
|
||
) as mock_run:
|
||
# Provide an output_dir so no tempdir is created
|
||
result = evaluate_benchmark_quality(
|
||
system_config={"iterations": 1},
|
||
num_examples=5,
|
||
output_dir=str(tmp_path),
|
||
)
|
||
|
||
assert result["accuracy"] == 0.75
|
||
assert result["quality_score"] == 0.75
|
||
mock_run.assert_called_once()
|
||
|
||
def test_uses_temp_dir_when_output_dir_none(self):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
evaluate_benchmark_quality,
|
||
)
|
||
|
||
mock_results = {"metrics": {"accuracy": 0.5}}
|
||
with patch(
|
||
"local_deep_research.benchmarks.runners.run_simpleqa_benchmark",
|
||
return_value=mock_results,
|
||
):
|
||
result = evaluate_benchmark_quality(
|
||
system_config={},
|
||
num_examples=2,
|
||
output_dir=None,
|
||
)
|
||
|
||
assert result["accuracy"] == 0.5
|
||
|
||
def test_exception_returns_zero_scores(self, tmp_path):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
evaluate_benchmark_quality,
|
||
)
|
||
|
||
with patch(
|
||
"local_deep_research.benchmarks.runners.run_simpleqa_benchmark",
|
||
side_effect=RuntimeError("boom"),
|
||
):
|
||
result = evaluate_benchmark_quality(
|
||
system_config={},
|
||
num_examples=2,
|
||
output_dir=str(tmp_path),
|
||
)
|
||
|
||
assert result["accuracy"] == 0.0
|
||
assert result["quality_score"] == 0.0
|
||
assert "error" in result
|
||
|
||
def test_missing_metrics_key_defaults_to_zero(self, tmp_path):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
evaluate_benchmark_quality,
|
||
)
|
||
|
||
# run_simpleqa_benchmark returns dict without "metrics"
|
||
with patch(
|
||
"local_deep_research.benchmarks.runners.run_simpleqa_benchmark",
|
||
return_value={},
|
||
):
|
||
result = evaluate_benchmark_quality(
|
||
system_config={},
|
||
num_examples=2,
|
||
output_dir=str(tmp_path),
|
||
)
|
||
|
||
assert result["accuracy"] == 0.0
|
||
assert result["quality_score"] == 0.0
|
||
|
||
def test_config_values_passed_to_runner(self, tmp_path):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
evaluate_benchmark_quality,
|
||
)
|
||
|
||
with patch(
|
||
"local_deep_research.benchmarks.runners.run_simpleqa_benchmark",
|
||
return_value={"metrics": {"accuracy": 0.9}},
|
||
) as mock_run:
|
||
evaluate_benchmark_quality(
|
||
system_config={
|
||
"iterations": 5,
|
||
"questions_per_iteration": 3,
|
||
"search_strategy": "custom",
|
||
"search_tool": "google",
|
||
"model_name": "gpt-4",
|
||
"provider": "openai",
|
||
},
|
||
num_examples=10,
|
||
output_dir=str(tmp_path),
|
||
)
|
||
|
||
call_kwargs = mock_run.call_args[1]
|
||
assert call_kwargs["num_examples"] == 10
|
||
search_config = call_kwargs["search_config"]
|
||
assert search_config["iterations"] == 5
|
||
assert search_config["questions_per_iteration"] == 3
|
||
assert search_config["search_strategy"] == "custom"
|
||
assert search_config["search_tool"] == "google"
|
||
assert search_config["model_name"] == "gpt-4"
|
||
assert search_config["provider"] == "openai"
|
||
|
||
def test_temp_dir_cleaned_up_on_success(self):
|
||
"""When output_dir is None, a temp dir is created and cleaned up."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
evaluate_benchmark_quality,
|
||
)
|
||
|
||
created_dirs = []
|
||
|
||
original_mkdtemp = __import__("tempfile").mkdtemp
|
||
|
||
def tracking_mkdtemp(**kwargs):
|
||
d = original_mkdtemp(**kwargs)
|
||
created_dirs.append(d)
|
||
return d
|
||
|
||
with (
|
||
patch(
|
||
"local_deep_research.benchmarks.runners.run_simpleqa_benchmark",
|
||
return_value={"metrics": {"accuracy": 0.5}},
|
||
),
|
||
patch(
|
||
"tempfile.mkdtemp",
|
||
side_effect=tracking_mkdtemp,
|
||
),
|
||
):
|
||
evaluate_benchmark_quality(
|
||
system_config={},
|
||
num_examples=1,
|
||
output_dir=None,
|
||
)
|
||
|
||
assert len(created_dirs) == 1
|
||
# The temp dir should have been cleaned up
|
||
assert not Path(created_dirs[0]).exists()
|
||
|
||
|
||
# ===========================================================================
|
||
# measure_execution_time – mocked SearchSystem
|
||
# ===========================================================================
|
||
|
||
|
||
class TestMeasureExecutionTime:
|
||
"""Tests for measure_execution_time with mocked SearchSystem."""
|
||
|
||
def _patch_search_system(self, search_time=0.1):
|
||
"""Return a context manager that patches AdvancedSearchSystem and its deps."""
|
||
from contextlib import ExitStack
|
||
|
||
mock_system = MagicMock()
|
||
mock_system.search.return_value = "result"
|
||
|
||
mock_cls = MagicMock(return_value=mock_system)
|
||
|
||
class CombinedPatcher:
|
||
def __enter__(self2):
|
||
self2.stack = ExitStack().__enter__()
|
||
self2.stack.enter_context(
|
||
patch(
|
||
"local_deep_research.config.llm_config.get_llm",
|
||
return_value=MagicMock(),
|
||
)
|
||
)
|
||
self2.stack.enter_context(
|
||
patch(
|
||
"local_deep_research.config.search_config.get_search",
|
||
return_value=MagicMock(),
|
||
)
|
||
)
|
||
self2.stack.enter_context(
|
||
patch(
|
||
"local_deep_research.search_system.AdvancedSearchSystem",
|
||
mock_cls,
|
||
)
|
||
)
|
||
return self2
|
||
|
||
def __exit__(self2, *args):
|
||
self2.stack.__exit__(*args)
|
||
|
||
return (
|
||
CombinedPatcher(),
|
||
mock_cls,
|
||
mock_system,
|
||
)
|
||
|
||
def test_basic_speed_measurement(self):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
measure_execution_time,
|
||
)
|
||
|
||
patcher, mock_cls, mock_system = self._patch_search_system()
|
||
with patcher:
|
||
result = measure_execution_time(
|
||
system_config={"iterations": 1},
|
||
query="test",
|
||
num_runs=1,
|
||
)
|
||
|
||
assert "average_time" in result
|
||
assert "speed_score" in result
|
||
assert "min_time" in result
|
||
assert "max_time" in result
|
||
assert result["average_time"] >= 0
|
||
assert 0 < result["speed_score"] <= 1.0
|
||
mock_system.search.assert_called_once_with("test", full_response=False)
|
||
|
||
def test_multiple_runs_averaged(self):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
measure_execution_time,
|
||
)
|
||
|
||
patcher, mock_cls, mock_system = self._patch_search_system()
|
||
with patcher:
|
||
result = measure_execution_time(
|
||
system_config={},
|
||
query="multi",
|
||
num_runs=3,
|
||
)
|
||
|
||
assert mock_system.search.call_count == 3
|
||
assert result["average_time"] >= 0
|
||
assert result["min_time"] <= result["max_time"]
|
||
|
||
def test_search_tool_override(self):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
measure_execution_time,
|
||
)
|
||
|
||
config = {"iterations": 2}
|
||
patcher, mock_cls, mock_system = self._patch_search_system()
|
||
with patcher:
|
||
measure_execution_time(
|
||
system_config=config,
|
||
search_tool="duckduckgo",
|
||
num_runs=1,
|
||
)
|
||
|
||
# search_tool should have been set on the config
|
||
assert config["search_tool"] == "duckduckgo"
|
||
|
||
def test_exception_returns_zero_scores(self):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
measure_execution_time,
|
||
)
|
||
|
||
mock_system = MagicMock()
|
||
mock_system.search.side_effect = RuntimeError("connection failed")
|
||
mock_cls = MagicMock(return_value=mock_system)
|
||
|
||
patcher, _, _ = self._patch_search_system()
|
||
# Override the mock_cls with our exception-raising one
|
||
with patcher:
|
||
with patch(
|
||
"local_deep_research.search_system.AdvancedSearchSystem",
|
||
mock_cls,
|
||
):
|
||
result = measure_execution_time(
|
||
system_config={},
|
||
num_runs=1,
|
||
)
|
||
|
||
assert result["average_time"] == 0.0
|
||
assert result["speed_score"] == 0.0
|
||
assert "error" in result
|
||
|
||
def test_speed_score_formula(self):
|
||
"""Verify speed_score = 1/(1 + avg_time/30)."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
measure_execution_time,
|
||
)
|
||
|
||
patcher, mock_cls, mock_system = self._patch_search_system()
|
||
mock_system.search.return_value = "r"
|
||
|
||
with patcher:
|
||
result = measure_execution_time(
|
||
system_config={},
|
||
num_runs=1,
|
||
)
|
||
|
||
avg = result["average_time"]
|
||
expected_score = 1.0 / (1.0 + avg / 30.0)
|
||
assert result["speed_score"] == pytest.approx(expected_score, abs=0.01)
|
||
|
||
|
||
# ===========================================================================
|
||
# calculate_quality_metrics – delegates to evaluate_benchmark_quality
|
||
# ===========================================================================
|
||
|
||
|
||
class TestCalculateQualityMetrics:
|
||
"""Tests for calculate_quality_metrics."""
|
||
|
||
def test_returns_quality_and_accuracy(self, tmp_path):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_quality_metrics,
|
||
)
|
||
|
||
with patch(
|
||
"local_deep_research.benchmarks.metrics.calculation.evaluate_benchmark_quality",
|
||
return_value={"quality_score": 0.85, "accuracy": 0.85},
|
||
):
|
||
result = calculate_quality_metrics(
|
||
system_config={"iterations": 1},
|
||
num_examples=5,
|
||
output_dir=str(tmp_path),
|
||
)
|
||
|
||
assert result["quality_score"] == 0.85
|
||
assert result["accuracy"] == 0.85
|
||
|
||
def test_defaults_to_zero_on_missing_keys(self, tmp_path):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_quality_metrics,
|
||
)
|
||
|
||
with patch(
|
||
"local_deep_research.benchmarks.metrics.calculation.evaluate_benchmark_quality",
|
||
return_value={},
|
||
):
|
||
result = calculate_quality_metrics(
|
||
system_config={},
|
||
output_dir=str(tmp_path),
|
||
)
|
||
|
||
assert result["quality_score"] == 0.0
|
||
assert result["accuracy"] == 0.0
|
||
|
||
|
||
# ===========================================================================
|
||
# calculate_speed_metrics – delegates to measure_execution_time
|
||
# ===========================================================================
|
||
|
||
|
||
class TestCalculateSpeedMetrics:
|
||
"""Tests for calculate_speed_metrics."""
|
||
|
||
def test_returns_speed_score_and_time(self):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_speed_metrics,
|
||
)
|
||
|
||
with patch(
|
||
"local_deep_research.benchmarks.metrics.calculation.measure_execution_time",
|
||
return_value={"speed_score": 0.7, "average_time": 12.0},
|
||
):
|
||
result = calculate_speed_metrics(
|
||
system_config={},
|
||
query="hello",
|
||
num_runs=2,
|
||
)
|
||
|
||
assert result["speed_score"] == 0.7
|
||
assert result["average_time"] == 12.0
|
||
|
||
def test_defaults_to_zero_on_missing_keys(self):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_speed_metrics,
|
||
)
|
||
|
||
with patch(
|
||
"local_deep_research.benchmarks.metrics.calculation.measure_execution_time",
|
||
return_value={},
|
||
):
|
||
result = calculate_speed_metrics(system_config={})
|
||
|
||
assert result["speed_score"] == 0.0
|
||
assert result["average_time"] == 0.0
|
||
|
||
def test_passes_search_tool_and_query(self):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_speed_metrics,
|
||
)
|
||
|
||
with patch(
|
||
"local_deep_research.benchmarks.metrics.calculation.measure_execution_time",
|
||
return_value={"speed_score": 0.5, "average_time": 20.0},
|
||
) as mock_measure:
|
||
calculate_speed_metrics(
|
||
system_config={"iterations": 3},
|
||
query="deep query",
|
||
search_tool="brave",
|
||
num_runs=5,
|
||
)
|
||
|
||
mock_measure.assert_called_once_with(
|
||
system_config={"iterations": 3},
|
||
query="deep query",
|
||
search_tool="brave",
|
||
num_runs=5,
|
||
)
|
||
|
||
|
||
# ===========================================================================
|
||
# calculate_resource_metrics – additional coverage
|
||
# ===========================================================================
|
||
|
||
|
||
class TestCalculateResourceMetricsAdditional:
|
||
"""Additional resource metric tests for coverage gaps."""
|
||
|
||
def test_search_tool_parameter_ignored_in_heuristic(self):
|
||
"""search_tool and query params exist but don't affect the heuristic."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_resource_metrics,
|
||
)
|
||
|
||
config = {
|
||
"iterations": 2,
|
||
"questions_per_iteration": 2,
|
||
"max_results": 50,
|
||
}
|
||
r1 = calculate_resource_metrics(
|
||
config, query="query A", search_tool="brave"
|
||
)
|
||
r2 = calculate_resource_metrics(
|
||
config, query="query B", search_tool="google"
|
||
)
|
||
assert r1["resource_score"] == r2["resource_score"]
|
||
assert r1["estimated_complexity"] == r2["estimated_complexity"]
|
||
|
||
def test_fractional_max_results(self):
|
||
"""max_results can be a float; formula still works."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_resource_metrics,
|
||
)
|
||
|
||
config = {
|
||
"iterations": 1,
|
||
"questions_per_iteration": 1,
|
||
"max_results": 25,
|
||
}
|
||
metrics = calculate_resource_metrics(config)
|
||
expected_complexity = 1 * 1 * (25 / 50)
|
||
assert metrics["estimated_complexity"] == pytest.approx(
|
||
expected_complexity
|
||
)
|
||
|
||
|
||
# ===========================================================================
|
||
# calculate_combined_score – additional coverage
|
||
# ===========================================================================
|
||
|
||
|
||
class TestCalculateCombinedScoreAdditional:
|
||
"""Additional combined score tests for coverage gaps."""
|
||
|
||
def test_weights_with_only_some_matching_metrics(self):
|
||
"""Weights for categories not in metrics are harmless."""
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_combined_score,
|
||
)
|
||
|
||
metrics = {"quality": {"quality_score": 1.0}}
|
||
weights = {"quality": 0.5, "speed": 0.3, "resource": 0.2}
|
||
score = calculate_combined_score(metrics, weights)
|
||
# Only quality matches: 1.0 * (0.5/1.0) = 0.5
|
||
assert score == pytest.approx(0.5)
|
||
|
||
def test_all_weights_equal(self):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_combined_score,
|
||
)
|
||
|
||
metrics = {
|
||
"quality": {"quality_score": 0.9},
|
||
"speed": {"speed_score": 0.6},
|
||
"resource": {"resource_score": 0.3},
|
||
}
|
||
weights = {"quality": 1, "speed": 1, "resource": 1}
|
||
score = calculate_combined_score(metrics, weights)
|
||
expected = (0.9 + 0.6 + 0.3) / 3.0
|
||
assert score == pytest.approx(expected)
|
||
|
||
def test_very_large_weights_still_normalize(self):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_combined_score,
|
||
)
|
||
|
||
metrics = {
|
||
"quality": {"quality_score": 0.8},
|
||
"speed": {"speed_score": 0.4},
|
||
"resource": {"resource_score": 0.2},
|
||
}
|
||
weights = {"quality": 1000, "speed": 500, "resource": 500}
|
||
score = calculate_combined_score(metrics, weights)
|
||
# norm: quality=0.5, speed=0.25, resource=0.25
|
||
expected = 0.8 * 0.5 + 0.4 * 0.25 + 0.2 * 0.25
|
||
assert score == pytest.approx(expected)
|
||
|
||
def test_empty_weights_dict_returns_zero(self):
|
||
from local_deep_research.benchmarks.metrics.calculation import (
|
||
calculate_combined_score,
|
||
)
|
||
|
||
metrics = {"quality": {"quality_score": 1.0}}
|
||
score = calculate_combined_score(metrics, weights={})
|
||
assert score == 0.0
|