""" Tests for CompositeBenchmarkEvaluator class. Tests the composite benchmark evaluator that combines multiple benchmarks. """ import tempfile from unittest.mock import MagicMock, patch from local_deep_research.benchmarks.evaluators.composite import ( CompositeBenchmarkEvaluator, ) class TestCompositeBenchmarkEvaluatorInit: """Test initialization of CompositeBenchmarkEvaluator.""" def test_init_default_weights(self): """Test initialization with default weights.""" evaluator = CompositeBenchmarkEvaluator() assert "simpleqa" in evaluator.benchmark_weights assert evaluator.benchmark_weights["simpleqa"] == 1.0 def test_init_custom_weights(self): """Test initialization with custom weights.""" weights = {"simpleqa": 0.6, "browsecomp": 0.4} evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) assert evaluator.benchmark_weights == weights def test_init_normalizes_weights(self): """Test that initialization normalizes weights to sum to 1.0.""" weights = {"simpleqa": 2.0, "browsecomp": 2.0} evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) # Normalized weights should be 0.5 each assert evaluator.normalized_weights["simpleqa"] == 0.5 assert evaluator.normalized_weights["browsecomp"] == 0.5 def test_init_creates_evaluators(self): """Test that initialization creates evaluator instances.""" evaluator = CompositeBenchmarkEvaluator() assert "simpleqa" in evaluator.evaluators assert "browsecomp" in evaluator.evaluators def test_init_handles_zero_total_weight(self): """Test initialization handles zero total weight.""" weights = {"simpleqa": 0.0} evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) # Should fall back to default weights assert evaluator.normalized_weights == {"simpleqa": 1.0} def test_init_handles_negative_weight(self): """Test initialization handles negative total weight.""" weights = {"simpleqa": -1.0} evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) # Should fall back to default weights assert evaluator.normalized_weights == {"simpleqa": 1.0} class TestCompositeBenchmarkEvaluatorWeightNormalization: """Test weight normalization.""" def test_normalize_single_weight(self): """Test normalization with single weight.""" weights = {"simpleqa": 5.0} evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) assert evaluator.normalized_weights["simpleqa"] == 1.0 def test_normalize_multiple_weights(self): """Test normalization with multiple weights.""" weights = {"simpleqa": 3.0, "browsecomp": 1.0} evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) assert evaluator.normalized_weights["simpleqa"] == 0.75 assert evaluator.normalized_weights["browsecomp"] == 0.25 def test_normalize_equal_weights(self): """Test normalization with equal weights.""" weights = {"simpleqa": 1.0, "browsecomp": 1.0} evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) assert evaluator.normalized_weights["simpleqa"] == 0.5 assert evaluator.normalized_weights["browsecomp"] == 0.5 def test_normalize_unequal_weights(self): """Test normalization with unequal weights.""" weights = {"simpleqa": 0.7, "browsecomp": 0.3} evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) assert evaluator.normalized_weights["simpleqa"] == 0.7 assert evaluator.normalized_weights["browsecomp"] == 0.3 class TestCompositeBenchmarkEvaluate: """Test evaluate method.""" @patch( "local_deep_research.benchmarks.evaluators.composite.SimpleQAEvaluator" ) @patch( "local_deep_research.benchmarks.evaluators.composite.BrowseCompEvaluator" ) def test_evaluate_runs_benchmarks_with_weight( self, mock_browsecomp_cls, mock_simpleqa_cls ): """Test that evaluate runs benchmarks with positive weight.""" mock_simpleqa = MagicMock() mock_simpleqa.evaluate.return_value = {"quality_score": 0.8} mock_simpleqa_cls.return_value = mock_simpleqa mock_browsecomp = MagicMock() mock_browsecomp.evaluate.return_value = {"quality_score": 0.6} mock_browsecomp_cls.return_value = mock_browsecomp weights = {"simpleqa": 0.5, "browsecomp": 0.5} with tempfile.TemporaryDirectory() as tmpdir: evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) evaluator.evaluate( system_config={"key": "value"}, num_examples=10, output_dir=tmpdir, ) mock_simpleqa.evaluate.assert_called_once() mock_browsecomp.evaluate.assert_called_once() @patch( "local_deep_research.benchmarks.evaluators.composite.SimpleQAEvaluator" ) @patch( "local_deep_research.benchmarks.evaluators.composite.BrowseCompEvaluator" ) def test_evaluate_computes_weighted_score( self, mock_browsecomp_cls, mock_simpleqa_cls ): """Test that evaluate computes weighted combined score.""" mock_simpleqa = MagicMock() mock_simpleqa.evaluate.return_value = {"quality_score": 0.8} mock_simpleqa_cls.return_value = mock_simpleqa mock_browsecomp = MagicMock() mock_browsecomp.evaluate.return_value = {"quality_score": 0.4} mock_browsecomp_cls.return_value = mock_browsecomp weights = {"simpleqa": 0.6, "browsecomp": 0.4} with tempfile.TemporaryDirectory() as tmpdir: evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) result = evaluator.evaluate( system_config={}, num_examples=5, output_dir=tmpdir, ) # Expected: 0.8 * 0.6 + 0.4 * 0.4 = 0.48 + 0.16 = 0.64 assert abs(result["quality_score"] - 0.64) < 0.001 assert abs(result["combined_score"] - 0.64) < 0.001 @patch( "local_deep_research.benchmarks.evaluators.composite.SimpleQAEvaluator" ) @patch( "local_deep_research.benchmarks.evaluators.composite.BrowseCompEvaluator" ) def test_evaluate_returns_individual_results( self, mock_browsecomp_cls, mock_simpleqa_cls ): """Test that evaluate returns individual benchmark results.""" mock_simpleqa = MagicMock() mock_simpleqa.evaluate.return_value = { "quality_score": 0.9, "accuracy": 0.9, } mock_simpleqa_cls.return_value = mock_simpleqa mock_browsecomp = MagicMock() mock_browsecomp.evaluate.return_value = { "quality_score": 0.7, "accuracy": 0.7, } mock_browsecomp_cls.return_value = mock_browsecomp weights = {"simpleqa": 0.5, "browsecomp": 0.5} with tempfile.TemporaryDirectory() as tmpdir: evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) result = evaluator.evaluate( system_config={}, num_examples=5, output_dir=tmpdir, ) assert "benchmark_results" in result assert "simpleqa" in result["benchmark_results"] assert "browsecomp" in result["benchmark_results"] @patch( "local_deep_research.benchmarks.evaluators.composite.SimpleQAEvaluator" ) @patch( "local_deep_research.benchmarks.evaluators.composite.BrowseCompEvaluator" ) def test_evaluate_returns_weights_used( self, mock_browsecomp_cls, mock_simpleqa_cls ): """Test that evaluate returns the weights used.""" mock_simpleqa = MagicMock() mock_simpleqa.evaluate.return_value = {"quality_score": 0.5} mock_simpleqa_cls.return_value = mock_simpleqa mock_browsecomp = MagicMock() mock_browsecomp.evaluate.return_value = {"quality_score": 0.5} mock_browsecomp_cls.return_value = mock_browsecomp weights = {"simpleqa": 0.7, "browsecomp": 0.3} with tempfile.TemporaryDirectory() as tmpdir: evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) result = evaluator.evaluate( system_config={}, num_examples=5, output_dir=tmpdir, ) assert "benchmark_weights" in result assert result["benchmark_weights"]["simpleqa"] == 0.7 assert result["benchmark_weights"]["browsecomp"] == 0.3 class TestCompositeBenchmarkEvaluateErrors: """Test error handling in evaluate method.""" @patch( "local_deep_research.benchmarks.evaluators.composite.SimpleQAEvaluator" ) @patch( "local_deep_research.benchmarks.evaluators.composite.BrowseCompEvaluator" ) def test_evaluate_handles_evaluator_exception( self, mock_browsecomp_cls, mock_simpleqa_cls ): """Test that evaluate handles exceptions from individual evaluators.""" mock_simpleqa = MagicMock() mock_simpleqa.evaluate.side_effect = RuntimeError("SimpleQA failed") mock_simpleqa_cls.return_value = mock_simpleqa mock_browsecomp = MagicMock() mock_browsecomp.evaluate.return_value = {"quality_score": 0.6} mock_browsecomp_cls.return_value = mock_browsecomp weights = {"simpleqa": 0.5, "browsecomp": 0.5} with tempfile.TemporaryDirectory() as tmpdir: evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) result = evaluator.evaluate( system_config={}, num_examples=5, output_dir=tmpdir, ) # Should still have results assert "benchmark_results" in result assert "error" in result["benchmark_results"]["simpleqa"] assert ( result["benchmark_results"]["browsecomp"]["quality_score"] == 0.6 ) @patch( "local_deep_research.benchmarks.evaluators.composite.SimpleQAEvaluator" ) @patch( "local_deep_research.benchmarks.evaluators.composite.BrowseCompEvaluator" ) def test_evaluate_zero_score_on_error( self, mock_browsecomp_cls, mock_simpleqa_cls ): """Test that failed benchmark contributes zero to combined score.""" mock_simpleqa = MagicMock() mock_simpleqa.evaluate.side_effect = RuntimeError("Failed") mock_simpleqa_cls.return_value = mock_simpleqa mock_browsecomp = MagicMock() mock_browsecomp.evaluate.return_value = {"quality_score": 1.0} mock_browsecomp_cls.return_value = mock_browsecomp weights = {"simpleqa": 0.5, "browsecomp": 0.5} with tempfile.TemporaryDirectory() as tmpdir: evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) result = evaluator.evaluate( system_config={}, num_examples=5, output_dir=tmpdir, ) # Only browsecomp contributes: 1.0 * 0.5 = 0.5 assert result["quality_score"] == 0.5 class TestCompositeBenchmarkSingleEvaluator: """Test composite with single evaluator.""" @patch( "local_deep_research.benchmarks.evaluators.composite.SimpleQAEvaluator" ) @patch( "local_deep_research.benchmarks.evaluators.composite.BrowseCompEvaluator" ) def test_evaluate_single_benchmark( self, mock_browsecomp_cls, mock_simpleqa_cls ): """Test that composite can run with single benchmark.""" mock_simpleqa = MagicMock() mock_simpleqa.evaluate.return_value = {"quality_score": 0.75} mock_simpleqa_cls.return_value = mock_simpleqa mock_browsecomp = MagicMock() mock_browsecomp_cls.return_value = mock_browsecomp # Only simpleqa weights = {"simpleqa": 1.0} with tempfile.TemporaryDirectory() as tmpdir: evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) result = evaluator.evaluate( system_config={}, num_examples=5, output_dir=tmpdir, ) mock_simpleqa.evaluate.assert_called_once() mock_browsecomp.evaluate.assert_not_called() assert result["quality_score"] == 0.75 class TestCompositeBenchmarkMissingEvaluator: """Test handling of unknown benchmark names.""" @patch( "local_deep_research.benchmarks.evaluators.composite.SimpleQAEvaluator" ) @patch( "local_deep_research.benchmarks.evaluators.composite.BrowseCompEvaluator" ) def test_unknown_benchmark_ignored( self, mock_browsecomp_cls, mock_simpleqa_cls ): """Test that unknown benchmark names are ignored.""" mock_simpleqa = MagicMock() mock_simpleqa.evaluate.return_value = {"quality_score": 0.8} mock_simpleqa_cls.return_value = mock_simpleqa mock_browsecomp = MagicMock() mock_browsecomp_cls.return_value = mock_browsecomp # Include unknown benchmark weights = {"simpleqa": 0.5, "unknown_benchmark": 0.5} with tempfile.TemporaryDirectory() as tmpdir: evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) result = evaluator.evaluate( system_config={}, num_examples=5, output_dir=tmpdir, ) # Only simpleqa should run mock_simpleqa.evaluate.assert_called_once() # Score should reflect only simpleqa's contribution assert "benchmark_results" in result class TestCompositeBenchmarkZeroWeight: """Test handling of zero weights.""" @patch( "local_deep_research.benchmarks.evaluators.composite.SimpleQAEvaluator" ) @patch( "local_deep_research.benchmarks.evaluators.composite.BrowseCompEvaluator" ) def test_zero_weight_benchmark_not_run( self, mock_browsecomp_cls, mock_simpleqa_cls ): """Test that benchmark with zero weight is not run.""" mock_simpleqa = MagicMock() mock_simpleqa.evaluate.return_value = {"quality_score": 0.8} mock_simpleqa_cls.return_value = mock_simpleqa mock_browsecomp = MagicMock() mock_browsecomp_cls.return_value = mock_browsecomp # browsecomp has zero weight weights = {"simpleqa": 1.0, "browsecomp": 0.0} with tempfile.TemporaryDirectory() as tmpdir: evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) evaluator.evaluate( system_config={}, num_examples=5, output_dir=tmpdir, ) mock_simpleqa.evaluate.assert_called_once() mock_browsecomp.evaluate.assert_not_called() class TestCompositeBenchmarkPassesConfig: """Test that configuration is passed to evaluators.""" @patch( "local_deep_research.benchmarks.evaluators.composite.SimpleQAEvaluator" ) @patch( "local_deep_research.benchmarks.evaluators.composite.BrowseCompEvaluator" ) def test_passes_system_config(self, mock_browsecomp_cls, mock_simpleqa_cls): """Test that system_config is passed to evaluators.""" mock_simpleqa = MagicMock() mock_simpleqa.evaluate.return_value = {"quality_score": 0.5} mock_simpleqa_cls.return_value = mock_simpleqa mock_browsecomp = MagicMock() mock_browsecomp_cls.return_value = mock_browsecomp config = {"iterations": 5, "search_tool": "google"} weights = {"simpleqa": 1.0} with tempfile.TemporaryDirectory() as tmpdir: evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) evaluator.evaluate( system_config=config, num_examples=10, output_dir=tmpdir, ) call_kwargs = mock_simpleqa.evaluate.call_args[1] assert call_kwargs["system_config"] == config @patch( "local_deep_research.benchmarks.evaluators.composite.SimpleQAEvaluator" ) @patch( "local_deep_research.benchmarks.evaluators.composite.BrowseCompEvaluator" ) def test_passes_num_examples(self, mock_browsecomp_cls, mock_simpleqa_cls): """Test that num_examples is passed to evaluators.""" mock_simpleqa = MagicMock() mock_simpleqa.evaluate.return_value = {"quality_score": 0.5} mock_simpleqa_cls.return_value = mock_simpleqa mock_browsecomp = MagicMock() mock_browsecomp_cls.return_value = mock_browsecomp weights = {"simpleqa": 1.0} with tempfile.TemporaryDirectory() as tmpdir: evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) evaluator.evaluate( system_config={}, num_examples=25, output_dir=tmpdir, ) call_kwargs = mock_simpleqa.evaluate.call_args[1] assert call_kwargs["num_examples"] == 25 @patch( "local_deep_research.benchmarks.evaluators.composite.SimpleQAEvaluator" ) @patch( "local_deep_research.benchmarks.evaluators.composite.BrowseCompEvaluator" ) def test_passes_output_dir(self, mock_browsecomp_cls, mock_simpleqa_cls): """Test that output_dir is passed to evaluators.""" mock_simpleqa = MagicMock() mock_simpleqa.evaluate.return_value = {"quality_score": 0.5} mock_simpleqa_cls.return_value = mock_simpleqa mock_browsecomp = MagicMock() mock_browsecomp_cls.return_value = mock_browsecomp weights = {"simpleqa": 1.0} with tempfile.TemporaryDirectory() as tmpdir: evaluator = CompositeBenchmarkEvaluator(benchmark_weights=weights) evaluator.evaluate( system_config={}, num_examples=5, output_dir=tmpdir, ) call_kwargs = mock_simpleqa.evaluate.call_args[1] assert call_kwargs["output_dir"] == tmpdir