Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

744 lines
26 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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