"""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