"""High-value tests for benchmarks/datasets/base.py. Covers BenchmarkDataset ABC enforcement, DatasetRegistry CRUD, load() with different file formats, sampling, and get_example bounds. """ import json import pytest from local_deep_research.benchmarks.datasets.base import ( BenchmarkDataset, DatasetRegistry, ) class ConcreteBenchmarkDataset(BenchmarkDataset): """Minimal concrete implementation for testing.""" @classmethod def get_dataset_info(cls): return { "id": "test-dataset", "name": "Test", "description": "For testing", } @classmethod def get_default_dataset_path(cls): return "/tmp/test_dataset.json" def process_example(self, example): return example class TestBenchmarkDatasetABC: """Test abstract base class enforcement.""" def test_cannot_instantiate_directly(self): """BenchmarkDataset cannot be instantiated.""" with pytest.raises(TypeError): BenchmarkDataset() def test_incomplete_subclass_raises(self): """Subclass missing abstract methods raises TypeError.""" class Incomplete(BenchmarkDataset): pass with pytest.raises(TypeError): Incomplete() def test_complete_subclass_instantiable(self): """Complete subclass can be instantiated.""" ds = ConcreteBenchmarkDataset() assert isinstance(ds, BenchmarkDataset) class TestBenchmarkDatasetInit: """Test BenchmarkDataset initialization.""" def test_default_path_from_class_method(self): """Uses get_default_dataset_path when no path provided.""" ds = ConcreteBenchmarkDataset() assert ds.dataset_path == "/tmp/test_dataset.json" def test_custom_path_overrides_default(self): """Custom path overrides the default.""" ds = ConcreteBenchmarkDataset(dataset_path="/custom/path.json") assert ds.dataset_path == "/custom/path.json" def test_not_loaded_initially(self): """Dataset is not loaded on init.""" ds = ConcreteBenchmarkDataset() assert ds._is_loaded is False assert ds.examples == [] def test_default_seed(self): """Default seed is 42.""" ds = ConcreteBenchmarkDataset() assert ds.seed == 42 def test_custom_num_examples(self): """num_examples is stored correctly.""" ds = ConcreteBenchmarkDataset(num_examples=10) assert ds.num_examples == 10 class TestBenchmarkDatasetLoad: """Test load() method.""" def test_load_json_file(self, tmp_path): """Loads examples from a JSON file.""" data = [ {"problem": "Q1", "answer": "A1"}, {"problem": "Q2", "answer": "A2"}, ] f = tmp_path / "data.json" f.write_text(json.dumps(data)) ds = ConcreteBenchmarkDataset(dataset_path=str(f)) examples = ds.load() assert len(examples) == 2 assert ds._is_loaded is True def test_load_jsonl_file(self, tmp_path): """Loads examples from a JSONL file.""" lines = ['{"problem": "Q1", "answer": "A1"}\n', '{"problem": "Q2"}\n'] f = tmp_path / "data.jsonl" f.write_text("".join(lines)) ds = ConcreteBenchmarkDataset(dataset_path=str(f)) examples = ds.load() assert len(examples) == 2 def test_load_csv_file(self, tmp_path): """Loads examples from a CSV file.""" f = tmp_path / "data.csv" f.write_text("problem,answer\nQ1,A1\nQ2,A2\n") ds = ConcreteBenchmarkDataset(dataset_path=str(f)) examples = ds.load() assert len(examples) == 2 def test_unsupported_format_raises(self, tmp_path): """Unsupported file format raises ValueError.""" f = tmp_path / "data.xml" f.write_text("") ds = ConcreteBenchmarkDataset(dataset_path=str(f)) with pytest.raises(ValueError, match="Unsupported file format"): ds.load() def test_load_returns_cached_on_second_call(self, tmp_path): """Second call to load() returns cached results.""" f = tmp_path / "data.json" f.write_text(json.dumps([{"q": "1"}])) ds = ConcreteBenchmarkDataset(dataset_path=str(f)) first = ds.load() second = ds.load() assert first is second def test_sampling_with_num_examples(self, tmp_path): """load() samples when num_examples < total examples.""" data = [{"q": str(i)} for i in range(100)] f = tmp_path / "data.json" f.write_text(json.dumps(data)) ds = ConcreteBenchmarkDataset(dataset_path=str(f), num_examples=10) examples = ds.load() assert len(examples) == 10 def test_sampling_deterministic_with_seed(self, tmp_path): """Same seed produces same sample.""" data = [{"q": str(i)} for i in range(100)] f = tmp_path / "data.json" f.write_text(json.dumps(data)) ds1 = ConcreteBenchmarkDataset( dataset_path=str(f), num_examples=5, seed=42 ) ds2 = ConcreteBenchmarkDataset( dataset_path=str(f), num_examples=5, seed=42 ) assert ds1.load() == ds2.load() def test_process_example_error_counted(self, tmp_path): """Errors during process_example are counted, not raised.""" data = [{"q": "1"}, {"q": "2"}, {"q": "3"}] f = tmp_path / "data.json" f.write_text(json.dumps(data)) class FailingDataset(ConcreteBenchmarkDataset): def process_example(self, example): if example["q"] == "2": raise RuntimeError("bad example") return example ds = FailingDataset(dataset_path=str(f)) examples = ds.load() assert len(examples) == 2 # 3 total - 1 failed class TestBenchmarkDatasetGetters: """Test get_example, get_question, get_answer.""" def test_get_example_valid_index(self, tmp_path): """get_example returns the correct example.""" f = tmp_path / "data.json" f.write_text(json.dumps([{"q": "A"}, {"q": "B"}])) ds = ConcreteBenchmarkDataset(dataset_path=str(f)) assert ds.get_example(1)["q"] == "B" def test_get_example_out_of_range(self, tmp_path): """get_example raises IndexError for invalid index.""" f = tmp_path / "data.json" f.write_text(json.dumps([{"q": "A"}])) ds = ConcreteBenchmarkDataset(dataset_path=str(f)) with pytest.raises(IndexError): ds.get_example(5) def test_get_example_negative_index_raises(self, tmp_path): """Negative index raises IndexError.""" f = tmp_path / "data.json" f.write_text(json.dumps([{"q": "A"}])) ds = ConcreteBenchmarkDataset(dataset_path=str(f)) with pytest.raises(IndexError): ds.get_example(-1) def test_get_question_returns_problem_field(self): """get_question extracts 'problem' field.""" ds = ConcreteBenchmarkDataset() assert ds.get_question({"problem": "What is X?"}) == "What is X?" def test_get_question_missing_field_returns_empty(self): """Missing 'problem' field returns empty string.""" ds = ConcreteBenchmarkDataset() assert ds.get_question({}) == "" def test_get_answer_prefers_correct_answer(self): """get_answer prefers 'correct_answer' over 'answer'.""" ds = ConcreteBenchmarkDataset() example = {"correct_answer": "CA", "answer": "A"} assert ds.get_answer(example) == "CA" def test_get_answer_falls_back_to_answer(self): """get_answer falls back to 'answer' field.""" ds = ConcreteBenchmarkDataset() assert ds.get_answer({"answer": "A"}) == "A" class TestDatasetRegistry: """Test DatasetRegistry registration and retrieval.""" def setup_method(self): """Clear registry before each test.""" DatasetRegistry._registry = {} def test_register_stores_class(self): """register() stores the class by its ID.""" DatasetRegistry.register(ConcreteBenchmarkDataset) assert "test-dataset" in DatasetRegistry._registry def test_register_no_id_raises(self): """register() raises when dataset has no ID.""" class NoIdDataset(ConcreteBenchmarkDataset): @classmethod def get_dataset_info(cls): return {"name": "No ID"} with pytest.raises(ValueError, match="must have an ID"): DatasetRegistry.register(NoIdDataset) def test_get_dataset_class_unknown_raises(self): """get_dataset_class raises for unknown ID.""" with pytest.raises(ValueError, match="Unknown dataset"): DatasetRegistry.get_dataset_class("nonexistent") def test_create_dataset_returns_instance(self): """create_dataset returns a BenchmarkDataset instance.""" DatasetRegistry.register(ConcreteBenchmarkDataset) ds = DatasetRegistry.create_dataset("test-dataset") assert isinstance(ds, BenchmarkDataset) def test_get_available_datasets(self): """get_available_datasets returns info for all registered datasets.""" DatasetRegistry.register(ConcreteBenchmarkDataset) available = DatasetRegistry.get_available_datasets() assert len(available) == 1 assert available[0]["id"] == "test-dataset"