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
430 lines
14 KiB
Python
430 lines
14 KiB
Python
"""Extended tests for benchmarks/metrics/calculation.py - covering edge cases
|
|
in calculate_metrics and calculate_combined_score."""
|
|
|
|
import json
|
|
import tempfile
|
|
|
|
import pytest
|
|
|
|
|
|
class TestCalculateMetricsEdgeCases:
|
|
"""Tests for calculate_metrics edge cases."""
|
|
|
|
def test_nonexistent_file_returns_error(self):
|
|
"""Non-existent file should return error dict."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
result = calculate_metrics("/nonexistent/path/results.jsonl")
|
|
|
|
assert "error" in result
|
|
|
|
def test_empty_file_returns_error(self):
|
|
"""Empty file should return error about no results."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
f.write("")
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert result["error"] == "No results found"
|
|
|
|
def test_malformed_json_lines_returns_error(self):
|
|
"""File with malformed JSON should return error."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
f.write("not valid json\n")
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert "error" in result
|
|
|
|
def test_file_with_only_whitespace_lines(self):
|
|
"""File with only whitespace lines should return no results error."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
f.write("\n\n\n")
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert result["error"] == "No results found"
|
|
|
|
def test_all_correct_results(self):
|
|
"""All correct results should give accuracy of 1.0."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
for i in range(5):
|
|
f.write(
|
|
json.dumps({"is_correct": True, "processing_time": 1.0})
|
|
+ "\n"
|
|
)
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert result["accuracy"] == 1.0
|
|
assert result["correct"] == 5
|
|
assert result["graded_examples"] == 5
|
|
|
|
def test_all_incorrect_results(self):
|
|
"""All incorrect results should give accuracy of 0.0."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
for i in range(3):
|
|
f.write(json.dumps({"is_correct": False}) + "\n")
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert result["accuracy"] == 0.0
|
|
assert result["correct"] == 0
|
|
|
|
def test_mixed_graded_and_ungraded(self):
|
|
"""Mixed graded and ungraded results should only use graded for accuracy."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
f.write(json.dumps({"is_correct": True}) + "\n")
|
|
f.write(json.dumps({"is_correct": False}) + "\n")
|
|
f.write(json.dumps({"answer": "no grading"}) + "\n") # Ungraded
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert result["total_examples"] == 3
|
|
assert result["graded_examples"] == 2
|
|
assert result["accuracy"] == 0.5
|
|
|
|
def test_results_with_errors(self):
|
|
"""Results with errors should be counted in error_rate."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
f.write(json.dumps({"is_correct": True}) + "\n")
|
|
f.write(json.dumps({"error": "timeout"}) + "\n")
|
|
f.write(json.dumps({"error": "API error"}) + "\n")
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert result["error_count"] == 2
|
|
assert result["error_rate"] == pytest.approx(2 / 3)
|
|
|
|
def test_processing_time_calculation(self):
|
|
"""Average processing time should be calculated correctly."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
f.write(
|
|
json.dumps({"processing_time": 10.0, "is_correct": True}) + "\n"
|
|
)
|
|
f.write(
|
|
json.dumps({"processing_time": 20.0, "is_correct": True}) + "\n"
|
|
)
|
|
f.write(
|
|
json.dumps({"processing_time": 30.0, "is_correct": True}) + "\n"
|
|
)
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert result["average_processing_time"] == pytest.approx(20.0)
|
|
|
|
def test_confidence_parsing(self):
|
|
"""Confidence values should be parsed and averaged."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
f.write(json.dumps({"confidence": "80", "is_correct": True}) + "\n")
|
|
f.write(json.dumps({"confidence": "60", "is_correct": True}) + "\n")
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert result["average_confidence"] == 70.0
|
|
|
|
def test_invalid_confidence_values_skipped(self):
|
|
"""Invalid confidence values should be skipped."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
f.write(
|
|
json.dumps({"confidence": "not_a_number", "is_correct": True})
|
|
+ "\n"
|
|
)
|
|
f.write(json.dumps({"confidence": "90", "is_correct": True}) + "\n")
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert result["average_confidence"] == 90.0
|
|
|
|
def test_per_category_metrics(self):
|
|
"""Per-category metrics should be calculated correctly."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
f.write(json.dumps({"is_correct": True, "category": "math"}) + "\n")
|
|
f.write(
|
|
json.dumps({"is_correct": False, "category": "math"}) + "\n"
|
|
)
|
|
f.write(
|
|
json.dumps({"is_correct": True, "category": "science"}) + "\n"
|
|
)
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert "categories" in result
|
|
assert result["categories"]["math"]["accuracy"] == 0.5
|
|
assert result["categories"]["math"]["total"] == 2
|
|
assert result["categories"]["science"]["accuracy"] == 1.0
|
|
|
|
def test_no_processing_times(self):
|
|
"""Results without processing_time should give avg_time of 0."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
f.write(json.dumps({"is_correct": True}) + "\n")
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert result["average_processing_time"] == 0
|
|
|
|
def test_no_graded_results_accuracy_zero(self):
|
|
"""Results with no is_correct field should give accuracy 0."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
f.write(json.dumps({"answer": "something"}) + "\n")
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert result["accuracy"] == 0
|
|
assert result["graded_examples"] == 0
|
|
|
|
def test_timestamp_included(self):
|
|
"""Result should include a timestamp."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_metrics,
|
|
)
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".jsonl", delete=False
|
|
) as f:
|
|
f.write(json.dumps({"is_correct": True}) + "\n")
|
|
f.flush()
|
|
|
|
result = calculate_metrics(f.name)
|
|
|
|
assert "timestamp" in result
|
|
assert isinstance(result["timestamp"], str)
|
|
|
|
|
|
class TestCalculateCombinedScoreEdgeCases:
|
|
"""Tests for calculate_combined_score edge cases."""
|
|
|
|
def test_default_weights(self):
|
|
"""Default weights should be quality=0.6, speed=0.3, resource=0.1."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_combined_score,
|
|
)
|
|
|
|
metrics = {
|
|
"quality": {"quality_score": 1.0},
|
|
"speed": {"speed_score": 1.0},
|
|
"resource": {"resource_score": 1.0},
|
|
}
|
|
|
|
score = calculate_combined_score(metrics)
|
|
assert score == pytest.approx(1.0)
|
|
|
|
def test_zero_weights_returns_zero(self):
|
|
"""All zero weights should return 0.0."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_combined_score,
|
|
)
|
|
|
|
metrics = {"quality": {"quality_score": 1.0}}
|
|
weights = {"quality": 0.0, "speed": 0.0, "resource": 0.0}
|
|
|
|
score = calculate_combined_score(metrics, weights)
|
|
assert score == 0.0
|
|
|
|
def test_missing_metric_categories(self):
|
|
"""Missing metric categories should be skipped."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_combined_score,
|
|
)
|
|
|
|
metrics = {
|
|
"quality": {"quality_score": 0.8},
|
|
# No speed or resource
|
|
}
|
|
|
|
score = calculate_combined_score(metrics)
|
|
# Only quality contributes: 0.8 * (0.6/1.0) = 0.48
|
|
assert score == pytest.approx(0.8 * 0.6)
|
|
|
|
def test_custom_weights(self):
|
|
"""Custom weights should be used instead of defaults."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_combined_score,
|
|
)
|
|
|
|
metrics = {
|
|
"quality": {"quality_score": 1.0},
|
|
"speed": {"speed_score": 0.5},
|
|
}
|
|
weights = {"quality": 1.0, "speed": 1.0}
|
|
|
|
score = calculate_combined_score(metrics, weights)
|
|
# Normalized: quality=0.5, speed=0.5
|
|
# Score: 1.0*0.5 + 0.5*0.5 = 0.75
|
|
assert score == pytest.approx(0.75)
|
|
|
|
def test_empty_metrics(self):
|
|
"""Empty metrics should return 0."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_combined_score,
|
|
)
|
|
|
|
score = calculate_combined_score({})
|
|
assert score == 0.0
|
|
|
|
def test_missing_score_keys_in_metrics(self):
|
|
"""Missing score keys within metric dicts should default to 0."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_combined_score,
|
|
)
|
|
|
|
metrics = {
|
|
"quality": {}, # No quality_score key
|
|
"speed": {"speed_score": 0.8},
|
|
}
|
|
|
|
score = calculate_combined_score(metrics)
|
|
# quality_score defaults to 0.0, speed_score=0.8
|
|
assert score == pytest.approx(0.0 * 0.6 + 0.8 * 0.3)
|
|
|
|
|
|
class TestCalculateResourceMetrics:
|
|
"""Tests for calculate_resource_metrics."""
|
|
|
|
def test_basic_resource_calculation(self):
|
|
"""Basic resource metrics with default config."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_resource_metrics,
|
|
)
|
|
|
|
config = {
|
|
"iterations": 2,
|
|
"questions_per_iteration": 2,
|
|
"max_results": 50,
|
|
}
|
|
result = calculate_resource_metrics(config)
|
|
|
|
assert "resource_score" in result
|
|
assert "estimated_complexity" in result
|
|
assert 0 <= result["resource_score"] <= 1
|
|
|
|
def test_higher_config_means_more_resources(self):
|
|
"""Higher config values should give lower resource score (more resources used)."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_resource_metrics,
|
|
)
|
|
|
|
low_config = {
|
|
"iterations": 1,
|
|
"questions_per_iteration": 1,
|
|
"max_results": 10,
|
|
}
|
|
high_config = {
|
|
"iterations": 10,
|
|
"questions_per_iteration": 10,
|
|
"max_results": 100,
|
|
}
|
|
|
|
low_result = calculate_resource_metrics(low_config)
|
|
high_result = calculate_resource_metrics(high_config)
|
|
|
|
assert low_result["resource_score"] > high_result["resource_score"]
|
|
assert (
|
|
low_result["estimated_complexity"]
|
|
< high_result["estimated_complexity"]
|
|
)
|
|
|
|
def test_default_values_used_when_missing(self):
|
|
"""Default values should be used when config keys are missing."""
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_resource_metrics,
|
|
)
|
|
|
|
result = calculate_resource_metrics({}) # Empty config
|
|
|
|
assert "resource_score" in result
|
|
assert result["estimated_complexity"] > 0
|