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
291 lines
10 KiB
Python
291 lines
10 KiB
Python
"""
|
|
High-value edge case tests for benchmarks/metrics/calculation.py.
|
|
|
|
Complements test_metrics_calculation.py with additional edge cases:
|
|
- JSONL with blank lines and whitespace
|
|
- Mixed graded/ungraded results
|
|
- Confidence with falsy values (0, empty string)
|
|
- calculate_combined_score with partial metrics
|
|
- calculate_resource_metrics with extreme values
|
|
"""
|
|
|
|
import json
|
|
|
|
|
|
from local_deep_research.benchmarks.metrics.calculation import (
|
|
calculate_combined_score,
|
|
calculate_metrics,
|
|
calculate_resource_metrics,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# calculate_metrics edge cases
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCalculateMetricsEdgeCases:
|
|
"""Additional edge case tests for calculate_metrics."""
|
|
|
|
def test_blank_lines_in_jsonl_skipped(self, tmp_path):
|
|
"""Blank lines between JSON entries are ignored."""
|
|
results_file = tmp_path / "results.jsonl"
|
|
results_file.write_text(
|
|
json.dumps({"is_correct": True}) + "\n"
|
|
"\n"
|
|
" \n" + json.dumps({"is_correct": False}) + "\n"
|
|
)
|
|
metrics = calculate_metrics(str(results_file))
|
|
assert metrics["total_examples"] == 2
|
|
assert metrics["graded_examples"] == 2
|
|
|
|
def test_ungraded_results_not_counted_for_accuracy(self, tmp_path):
|
|
"""Results without 'is_correct' are excluded from accuracy calculation."""
|
|
results_file = tmp_path / "results.jsonl"
|
|
results = [
|
|
{"is_correct": True},
|
|
{"question": "What is X?"}, # ungraded
|
|
{"is_correct": False},
|
|
]
|
|
with open(results_file, "w") as f:
|
|
for r in results:
|
|
f.write(json.dumps(r) + "\n")
|
|
|
|
metrics = calculate_metrics(str(results_file))
|
|
assert metrics["total_examples"] == 3
|
|
assert metrics["graded_examples"] == 2
|
|
assert metrics["accuracy"] == 0.5
|
|
|
|
def test_confidence_zero_string_is_included(self, tmp_path):
|
|
"""String '0' is truthy so it passes the 'if r.get(confidence)' check.
|
|
int('0') = 0, so both values are included: (0 + 80) / 2 = 40."""
|
|
results_file = tmp_path / "results.jsonl"
|
|
results = [
|
|
{"confidence": "0"},
|
|
{"confidence": "80"},
|
|
]
|
|
with open(results_file, "w") as f:
|
|
for r in results:
|
|
f.write(json.dumps(r) + "\n")
|
|
|
|
metrics = calculate_metrics(str(results_file))
|
|
# "0" is truthy string, int("0")=0, so (0+80)/2 = 40
|
|
assert metrics["average_confidence"] == 40
|
|
|
|
def test_confidence_empty_string_skipped(self, tmp_path):
|
|
"""Empty string confidence is falsy and skipped."""
|
|
results_file = tmp_path / "results.jsonl"
|
|
results = [
|
|
{"confidence": ""},
|
|
{"confidence": "90"},
|
|
]
|
|
with open(results_file, "w") as f:
|
|
for r in results:
|
|
f.write(json.dumps(r) + "\n")
|
|
|
|
metrics = calculate_metrics(str(results_file))
|
|
assert metrics["average_confidence"] == 90
|
|
|
|
def test_processing_time_only_from_entries_that_have_it(self, tmp_path):
|
|
results_file = tmp_path / "results.jsonl"
|
|
results = [
|
|
{"processing_time": 2.0},
|
|
{"question": "no time"},
|
|
{"processing_time": 4.0},
|
|
]
|
|
with open(results_file, "w") as f:
|
|
for r in results:
|
|
f.write(json.dumps(r) + "\n")
|
|
|
|
metrics = calculate_metrics(str(results_file))
|
|
assert metrics["average_processing_time"] == 3.0
|
|
|
|
def test_all_results_are_errors(self, tmp_path):
|
|
results_file = tmp_path / "results.jsonl"
|
|
results = [
|
|
{"error": "timeout"},
|
|
{"error": "connection failed"},
|
|
]
|
|
with open(results_file, "w") as f:
|
|
for r in results:
|
|
f.write(json.dumps(r) + "\n")
|
|
|
|
metrics = calculate_metrics(str(results_file))
|
|
assert metrics["error_count"] == 2
|
|
assert metrics["error_rate"] == 1.0
|
|
assert metrics["graded_examples"] == 0
|
|
assert metrics["accuracy"] == 0
|
|
|
|
def test_category_with_zero_correct(self, tmp_path):
|
|
results_file = tmp_path / "results.jsonl"
|
|
results = [
|
|
{"is_correct": False, "category": "hard"},
|
|
{"is_correct": False, "category": "hard"},
|
|
]
|
|
with open(results_file, "w") as f:
|
|
for r in results:
|
|
f.write(json.dumps(r) + "\n")
|
|
|
|
metrics = calculate_metrics(str(results_file))
|
|
assert metrics["categories"]["hard"]["accuracy"] == 0.0
|
|
|
|
def test_single_result(self, tmp_path):
|
|
results_file = tmp_path / "results.jsonl"
|
|
results_file.write_text(
|
|
json.dumps({"is_correct": True, "confidence": "95"}) + "\n"
|
|
)
|
|
|
|
metrics = calculate_metrics(str(results_file))
|
|
assert metrics["total_examples"] == 1
|
|
assert metrics["accuracy"] == 1.0
|
|
assert metrics["average_confidence"] == 95
|
|
|
|
def test_is_correct_false_counted_in_graded(self, tmp_path):
|
|
results_file = tmp_path / "results.jsonl"
|
|
results = [{"is_correct": False}]
|
|
with open(results_file, "w") as f:
|
|
for r in results:
|
|
f.write(json.dumps(r) + "\n")
|
|
|
|
metrics = calculate_metrics(str(results_file))
|
|
assert metrics["graded_examples"] == 1
|
|
assert metrics["correct"] == 0
|
|
assert metrics["accuracy"] == 0.0
|
|
|
|
def test_timestamp_present_in_metrics(self, tmp_path):
|
|
results_file = tmp_path / "results.jsonl"
|
|
results_file.write_text(json.dumps({"is_correct": True}) + "\n")
|
|
|
|
metrics = calculate_metrics(str(results_file))
|
|
assert "timestamp" in metrics
|
|
assert "T" in metrics["timestamp"] # ISO format
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# calculate_combined_score edge cases
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCalculateCombinedScoreEdgeCases:
|
|
"""Additional edge cases for combined score calculation."""
|
|
|
|
def test_empty_metrics_dict(self):
|
|
score = calculate_combined_score({})
|
|
assert score == 0.0
|
|
|
|
def test_only_quality_metric(self):
|
|
metrics = {"quality": {"quality_score": 0.9}}
|
|
score = calculate_combined_score(metrics)
|
|
# Only quality contributes: 0.9 * (0.6/1.0) = 0.54
|
|
expected = 0.9 * 0.6
|
|
assert abs(score - expected) < 0.001
|
|
|
|
def test_only_speed_metric(self):
|
|
metrics = {"speed": {"speed_score": 0.8}}
|
|
score = calculate_combined_score(metrics)
|
|
expected = 0.8 * 0.3
|
|
assert abs(score - expected) < 0.001
|
|
|
|
def test_only_resource_metric(self):
|
|
metrics = {"resource": {"resource_score": 0.5}}
|
|
score = calculate_combined_score(metrics)
|
|
expected = 0.5 * 0.1
|
|
assert abs(score - expected) < 0.001
|
|
|
|
def test_unrecognized_metric_keys_ignored(self):
|
|
metrics = {
|
|
"quality": {"quality_score": 1.0},
|
|
"custom_metric": {"score": 0.5},
|
|
}
|
|
score = calculate_combined_score(metrics)
|
|
# custom_metric has no weight, so ignored
|
|
expected = 1.0 * 0.6
|
|
assert abs(score - expected) < 0.001
|
|
|
|
def test_all_scores_zero(self):
|
|
metrics = {
|
|
"quality": {"quality_score": 0.0},
|
|
"speed": {"speed_score": 0.0},
|
|
"resource": {"resource_score": 0.0},
|
|
}
|
|
assert calculate_combined_score(metrics) == 0.0
|
|
|
|
def test_negative_weights_still_normalize(self):
|
|
"""Negative weights are technically allowed by the function."""
|
|
metrics = {
|
|
"quality": {"quality_score": 1.0},
|
|
"speed": {"speed_score": 1.0},
|
|
}
|
|
weights = {"quality": 1.0, "speed": -0.5, "resource": 0.0}
|
|
score = calculate_combined_score(metrics, weights)
|
|
# total_weight = 0.5, norm: quality=2.0, speed=-1.0
|
|
# score = 1.0 * 2.0 + 1.0 * (-1.0) = 1.0
|
|
assert abs(score - 1.0) < 0.001
|
|
|
|
def test_missing_score_key_defaults_to_zero(self):
|
|
metrics = {
|
|
"quality": {}, # no quality_score key
|
|
}
|
|
score = calculate_combined_score(metrics)
|
|
assert score == 0.0
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# calculate_resource_metrics edge cases
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCalculateResourceMetricsEdgeCases:
|
|
"""Additional edge cases for resource metrics."""
|
|
|
|
def test_very_high_complexity(self):
|
|
config = {
|
|
"iterations": 10,
|
|
"questions_per_iteration": 10,
|
|
"max_results": 200,
|
|
}
|
|
metrics = calculate_resource_metrics(config)
|
|
assert (
|
|
metrics["resource_score"] < 0.1
|
|
) # Very high complexity -> low score
|
|
|
|
def test_minimal_complexity(self):
|
|
config = {
|
|
"iterations": 1,
|
|
"questions_per_iteration": 1,
|
|
"max_results": 10,
|
|
}
|
|
metrics = calculate_resource_metrics(config)
|
|
assert (
|
|
metrics["resource_score"] > 0.8
|
|
) # Very low complexity -> high score
|
|
|
|
def test_zero_iterations(self):
|
|
config = {"iterations": 0}
|
|
metrics = calculate_resource_metrics(config)
|
|
assert metrics["estimated_complexity"] == 0
|
|
assert metrics["resource_score"] == 1.0 # 1/(1+0) = 1.0
|
|
|
|
def test_complexity_formula(self):
|
|
"""Verify the exact formula: iterations * questions * (max_results/50)."""
|
|
config = {
|
|
"iterations": 3,
|
|
"questions_per_iteration": 4,
|
|
"max_results": 100,
|
|
}
|
|
metrics = calculate_resource_metrics(config)
|
|
expected_complexity = 3 * 4 * (100 / 50)
|
|
assert metrics["estimated_complexity"] == expected_complexity
|
|
|
|
def test_resource_score_formula(self):
|
|
"""Verify: resource_score = 1/(1 + complexity/4)."""
|
|
config = {
|
|
"iterations": 2,
|
|
"questions_per_iteration": 2,
|
|
"max_results": 50,
|
|
}
|
|
metrics = calculate_resource_metrics(config)
|
|
# complexity = 2 * 2 * (50/50) = 4.0
|
|
expected_score = 1.0 / (1.0 + 4.0 / 4.0)
|
|
assert abs(metrics["resource_score"] - expected_score) < 0.001
|