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
272 lines
9.2 KiB
Python
272 lines
9.2 KiB
Python
"""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("<data/>")
|
|
|
|
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"
|