from pathlib import Path import pytest from evaluations.datasets.frames import ( FETCH_ATTEMPTS, build_frames_case, fetch_article, map_frames_document, map_frames_retrieval, normalize_wiki_url, parse_revid, parse_wiki_links, question_is_answerable, strip_navigation, ) from evaluations.datasets.hotpotqa import ( build_hotpotqa_case, extract_unique_documents, map_hotpotqa_document, map_hotpotqa_retrieval, ) from evaluations.datasets.open_rag_bench import ( build_orb_case, download_pdf, is_multimodal_query, map_orb_document, map_orb_retrieval, ) from evaluations.datasets.t2_ragbench import ( build_t2_case, download_t2_pdf, load_t2_corpus, map_t2_document, map_t2_retrieval, ) class TestHotpotQA: def test_map_document(self) -> None: doc = {"title": "Albert Einstein", "content": "Was a physicist."} payload = map_hotpotqa_document(doc) assert payload.uri == "Albert Einstein" assert payload.content == "Was a physicist." assert payload.title == "Albert Einstein" def test_map_retrieval(self) -> None: doc = { "question": "Who was Einstein?", "supporting_facts": {"title": ["Albert Einstein", "Physics"]}, } sample = map_hotpotqa_retrieval(doc) assert sample is not None assert sample.expected_uris == ("Albert Einstein", "Physics") def test_map_retrieval_deduplicates_titles(self) -> None: doc = { "question": "Q?", "supporting_facts": {"title": ["A", "B", "A"]}, } sample = map_hotpotqa_retrieval(doc) assert sample is not None assert sample.expected_uris == ("A", "B") def test_map_retrieval_no_titles(self) -> None: doc = {"question": "Q?", "supporting_facts": {"title": []}} assert map_hotpotqa_retrieval(doc) is None def test_build_case(self) -> None: doc = { "id": "abc123", "question": "What is X?", "answer": "X is Y.", "type": "comparison", "level": "hard", } case = build_hotpotqa_case(5, doc) assert case.name == "5_abc123" assert case.inputs == "What is X?" assert case.expected_output == "X is Y." assert case.metadata == { "question_id": "abc123", "type": "comparison", "level": "hard", "case_index": "5", } def test_extract_unique_documents(self) -> None: # Simulate a minimal dataset with context dataset = [ { "context": { "title": ["Doc A", "Doc B"], "sentences": [["Sentence 1."], ["Sentence 2.", " More."]], } }, { "context": { "title": ["Doc A", "Doc C"], "sentences": [["Dupe."], ["Sentence 3."]], } }, ] docs = extract_unique_documents(dataset) # type: ignore[arg-type] # ty: ignore[invalid-argument-type] assert len(docs) == 3 titles = [d["title"] for d in docs] assert titles == ["Doc A", "Doc B", "Doc C"] assert docs[1]["content"] == "Sentence 2. More." class TestOpenRAGBench: def test_map_document(self, tmp_path: Path) -> None: # Pre-create a cached PDF cache_dir = tmp_path / "pdfs" cache_dir.mkdir() pdf_path = cache_dir / "paper1.pdf" pdf_path.write_bytes(b"%PDF-fake") doc = {"paper_id": "paper1", "pdf_url": "https://example.com/paper1.pdf"} # Patch get_cache_dir to use our tmp_path from unittest.mock import patch with patch( "evaluations.datasets.open_rag_bench.get_cache_dir", return_value=cache_dir ): payload = map_orb_document(doc) assert payload is not None assert payload.uri == "paper1" assert payload.title == "paper1" assert payload.source_path == pdf_path assert payload.metadata == {"arxiv_id": "paper1"} def test_map_document_download_fails(self, tmp_path: Path) -> None: cache_dir = tmp_path / "pdfs" cache_dir.mkdir() doc = {"paper_id": "missing", "pdf_url": "https://example.com/missing.pdf"} from unittest.mock import patch with patch( "evaluations.datasets.open_rag_bench.get_cache_dir", return_value=cache_dir ): with patch( "evaluations.datasets.open_rag_bench.download_pdf", return_value=None ): payload = map_orb_document(doc) assert payload is None def test_map_retrieval(self) -> None: doc = { "query": "What is attention?", "doc_id": "1706.03762", "source": "text", } sample = map_orb_retrieval(doc) assert sample is not None assert sample.question == "What is attention?" assert sample.expected_uris == ("1706.03762",) assert sample.source_type == "text" def test_build_case(self) -> None: doc = { "query_id": "q_abcdef12", "query": "Explain transformers.", "answer": "Transformers are...", "type": "factual", "source": "text", } case = build_orb_case(1, doc) assert case.name == "1_q_abcdef" assert case.inputs == "Explain transformers." assert case.expected_output == "Transformers are..." assert case.metadata is not None assert case.metadata["query_id"] == "q_abcdef12" def test_download_pdf_uses_cache(self, tmp_path: Path) -> None: pdf_path = tmp_path / "cached.pdf" pdf_path.write_bytes(b"%PDF-cached") result = download_pdf("cached", "https://example.com/cached.pdf", tmp_path) assert result == pdf_path def test_is_multimodal_query(self) -> None: assert is_multimodal_query("image") is True assert is_multimodal_query("image_table") is True assert is_multimodal_query("text") is False class TestT2RAGBench: def _row(self, **overrides: object) -> dict[str, object]: row: dict[str, object] = { "id": "finqa_dev_0", "context_id": "finqa_dev_ctx_138", "subset": "FinQA", "split": "dev", "file_name": "pdf/V/2008/page_17.pdf", "question": "What was the average payment volume per transaction?", "program_answer": "127.4", "company_name": "Visa Inc.", "company_symbol": "V", "report_year": 2008, "company_sector": "Financials", } row.update(overrides) return row def test_map_document(self, tmp_path: Path) -> None: pdf_path = tmp_path / "page_17.pdf" pdf_path.write_bytes(b"%PDF-fake") from unittest.mock import patch with patch( "evaluations.datasets.t2_ragbench.download_t2_pdf", return_value=pdf_path, ) as download: payload = map_t2_document(self._row()) download.assert_called_once_with("FinQA", "dev", "pdf/V/2008/page_17.pdf") assert payload is not None assert payload.uri == "finqa_dev_ctx_138" assert payload.source_path == pdf_path assert payload.content is None assert payload.title == "Visa Inc. 2008" assert payload.metadata == { "file_name": "pdf/V/2008/page_17.pdf", "company_name": "Visa Inc.", "company_symbol": "V", "report_year": 2008, "company_sector": "Financials", } def test_map_document_title_falls_back_to_context_id(self, tmp_path: Path) -> None: pdf_path = tmp_path / "page.pdf" pdf_path.write_bytes(b"%PDF-fake") from unittest.mock import patch row = self._row(company_name=None, report_year=None) with patch( "evaluations.datasets.t2_ragbench.download_t2_pdf", return_value=pdf_path, ): payload = map_t2_document(row) assert payload is not None assert payload.title == "finqa_dev_ctx_138" assert payload.metadata is not None assert "company_name" not in payload.metadata assert "report_year" not in payload.metadata def test_map_retrieval(self) -> None: sample = map_t2_retrieval(self._row()) assert sample is not None assert sample.question == self._row()["question"] assert sample.expected_uris == ("finqa_dev_ctx_138",) def test_build_case(self) -> None: case = build_t2_case(3, self._row()) assert case.name == "3_finqa_dev_0" assert case.inputs == self._row()["question"] assert case.expected_output == "127.4" assert case.metadata is not None assert case.metadata["case_index"] == "3" assert case.metadata["context_id"] == "finqa_dev_ctx_138" def test_build_case_casts_numeric_answer(self) -> None: case = build_t2_case(0, self._row(program_answer=127.4)) assert case.expected_output == "127.4" def test_download_pdf_materializes_pdf_suffix(self, tmp_path: Path) -> None: # hf_hub_download can return a suffix-less blob path; the cached copy # must carry the .pdf extension the converter dispatches on. blob = tmp_path / "blobs" / "6aa49306deadbeef" blob.parent.mkdir() blob.write_bytes(b"%PDF-fake") cache = tmp_path / "cache" cache.mkdir() from unittest.mock import patch with ( patch("evaluations.datasets.t2_ragbench.get_cache_dir", return_value=cache), patch( "evaluations.datasets.t2_ragbench.hf_hub_download", return_value=str(blob), ), ): out = download_t2_pdf("FinQA", "dev", "pdf/V/2008/page_17.pdf") assert out.suffix == ".pdf" assert out.exists() assert out.read_bytes() == b"%PDF-fake" assert out.name == "FinQA_dev_pdf_V_2008_page_17.pdf" def test_pdf_repo_path_per_subset(self, tmp_path: Path) -> None: from unittest.mock import patch blob = tmp_path / "blob" blob.write_bytes(b"%PDF-fake") cache = tmp_path / "cache" cache.mkdir() cases = [ ( "FinQA", "dev", "pdf/V/2008/page_17.pdf", "data/FinQA/dev/pdf/V/2008/page_17.pdf", ), ("TAT-DQA", "dev", "raw/abc123.pdf", "data/TAT-DQA/dev/raw/abc123.pdf"), ] for subset, split, file_name, expected_repo_path in cases: with ( patch( "evaluations.datasets.t2_ragbench.get_cache_dir", return_value=cache, ), patch( "evaluations.datasets.t2_ragbench.hf_hub_download", return_value=str(blob), ) as dl, ): download_t2_pdf(subset, split, file_name) assert dl.call_args.args[1] == expected_repo_path def test_load_corpus_dedupes_by_context_id(self) -> None: from unittest.mock import patch rows = [ self._row(id="finqa_dev_0", context_id="ctx_a"), self._row(id="finqa_dev_1", context_id="ctx_a"), self._row(id="finqa_dev_2", context_id="ctx_b"), ] with patch("evaluations.datasets.t2_ragbench._load_rows", return_value=rows): corpus = load_t2_corpus("FinQA") assert len(corpus) == 2 assert {r["context_id"] for r in corpus} == {"ctx_a", "ctx_b"} class TestFrames: def test_parse_wiki_links_plain(self) -> None: raw = ( "['https://en.wikipedia.org/wiki/James_Buchanan', " "'https://en.wikipedia.org/wiki/Harriet_Lane']" ) assert parse_wiki_links(raw) == [ "https://en.wikipedia.org/wiki/James_Buchanan", "https://en.wikipedia.org/wiki/Harriet_Lane", ] def test_parse_wiki_links_splits_comma_joined_urls(self) -> None: raw = ( "['https://en.wikipedia.org/wiki/Tim_Salmon, " "https://en.wikipedia.org/wiki/Troy_Glaus, ']" ) assert parse_wiki_links(raw) == [ "https://en.wikipedia.org/wiki/Tim_Salmon", "https://en.wikipedia.org/wiki/Troy_Glaus", ] def test_parse_wiki_links_keeps_commas_inside_titles(self) -> None: raw = ( "['https://en.wikipedia.org/wiki/Lincoln,_Nebraska', " "'https://en.wikipedia.org/wiki/Key_West#:~:text=The%20southernmost," "apart%20at%20their%20closest%20points.']" ) assert parse_wiki_links(raw) == [ "https://en.wikipedia.org/wiki/Lincoln,_Nebraska", "https://en.wikipedia.org/wiki/Key_West#:~:text=The%20southernmost," "apart%20at%20their%20closest%20points.", ] def test_parse_wiki_links_strips_trailing_annotation(self) -> None: raw = "['https://en.wikipedia.org/wiki/Pok%C3%A9mon (NOT REQUIRED, BUT HELPFUL) ']" assert parse_wiki_links(raw) == ["https://en.wikipedia.org/wiki/Pok%C3%A9mon"] def test_normalize_strips_fragment_and_mobile_host(self) -> None: assert ( normalize_wiki_url("https://en.m.wikipedia.org/wiki/World_War_I#Aftermath") == "https://en.wikipedia.org/wiki/World_War_I" ) def test_normalize_decodes_and_canonicalizes_title(self) -> None: assert ( normalize_wiki_url("https://en.wikipedia.org/wiki/pain %26 Gain") == "https://en.wikipedia.org/wiki/Pain_&_Gain" ) def test_normalize_schemeless(self) -> None: assert ( normalize_wiki_url("en.wikipedia.org/wiki/Grazia_Deledda") == "https://en.wikipedia.org/wiki/Grazia_Deledda" ) def test_normalize_index_php_title(self) -> None: assert ( normalize_wiki_url( "https://en.wikipedia.org/w/index.php?title=Bronco&redirect=no" ) == "https://en.wikipedia.org/wiki/Bronco" ) def test_normalize_search_url(self) -> None: url = ( "https://en.wikipedia.org/w/index.php?search=Polytrichum+piliferum" "&title=Special:Search&profile=advanced&fulltext=1&ns0=1" ) assert ( normalize_wiki_url(url) == "https://en.wikipedia.org/wiki/Polytrichum_piliferum" ) def test_normalize_shortlink_passthrough(self) -> None: assert normalize_wiki_url("https://w.wiki/ASFv") == "https://w.wiki/ASFv" def test_normalize_rejects_non_article(self) -> None: assert normalize_wiki_url("") is None assert normalize_wiki_url("https://en.wikipedia.org/foo") is None def test_parse_revid(self) -> None: assert parse_revid('W/"1364811104/52cd04f4-864c-11f1"') == "1364811104" assert parse_revid('"1234/abc"') == "1234" assert parse_revid(None) is None assert parse_revid("") is None def test_strip_navigation_removes_navboxes_keeps_infobox(self) -> None: html = ( "" '
Born April 23, 1791
' "

Some prose.

" '
v t e Presidents
' "" ) stripped = strip_navigation(html) assert "Born April 23, 1791" in stripped assert "Some prose." in stripped assert "v t e Presidents" not in stripped def test_map_retrieval_normalizes_and_dedupes(self) -> None: row = { "Prompt": "Who was the 15th president?", "wiki_links": ( "['https://en.wikipedia.org/wiki/James_Buchanan#Presidency', " "'https://en.m.wikipedia.org/wiki/James_Buchanan', " "'https://en.wikipedia.org/wiki/Harriet_Lane']" ), } sample = map_frames_retrieval(row) assert sample is not None assert sample.question == "Who was the 15th president?" assert sample.expected_uris == ( "https://en.wikipedia.org/wiki/James_Buchanan", "https://en.wikipedia.org/wiki/Harriet_Lane", ) def test_map_retrieval_empty_links(self) -> None: assert map_frames_retrieval({"Prompt": "Q", "wiki_links": "[]"}) is None def test_map_document_html_strips_navigation(self, tmp_path: Path) -> None: page = tmp_path / "article.html" page.write_text( "

Buchanan was a president.

" '
v t e spam
' ) row = { "uri": "https://en.wikipedia.org/wiki/James_Buchanan", "title": "James Buchanan", "path": str(page), "format": "html", "revid": "1364811104", "fetched_at": "2026-07-23", } payload = map_frames_document(row) assert payload.uri == "https://en.wikipedia.org/wiki/James_Buchanan" assert payload.title == "James Buchanan" assert payload.format == "html" assert "Buchanan was a president." in (payload.content or "") assert "v t e spam" not in (payload.content or "") assert payload.metadata == { "revid": "1364811104", "fetched_at": "2026-07-23", } def test_map_document_markdown_passthrough(self, tmp_path: Path) -> None: page = tmp_path / "category.md" page.write_text( "Pages in Category:Summer Olympics in London:\n- 1908 Summer Olympics\n" ) row = { "uri": "https://en.wikipedia.org/wiki/Category:Summer_Olympics_in_London", "title": "Category:Summer Olympics in London", "path": str(page), "format": "md", "revid": None, "fetched_at": "2026-07-23", } payload = map_frames_document(row) assert payload.format == "md" assert "1908 Summer Olympics" in (payload.content or "") assert payload.metadata == {"fetched_at": "2026-07-23"} def test_build_case(self) -> None: row = { "Prompt": "Who was the 15th president?", "Answer": "James Buchanan", "reasoning_types": "Multiple constraints | Temporal reasoning", } case = build_frames_case(3, row) assert case.name == "3" assert case.inputs == "Who was the 15th president?" assert case.expected_output == "James Buchanan" assert case.metadata == { "reasoning_types": "Multiple constraints | Temporal reasoning", "case_index": "3", } def test_fetch_article_cache_hit_needs_no_network(self, tmp_path: Path) -> None: uri = "https://en.wikipedia.org/wiki/James_Buchanan" from urllib.parse import quote base = quote(uri, safe="") (tmp_path / f"{base}.html").write_text("cached") (tmp_path / f"{base}.json").write_text( '{"uri": "https://en.wikipedia.org/wiki/James_Buchanan",' ' "title": "James Buchanan", "format": "html",' ' "revid": "123", "fetched_at": "2026-07-23"}' ) row = fetch_article(uri, tmp_path, client=None) assert row is not None assert row["uri"] == uri assert row["revid"] == "123" assert row["format"] == "html" assert Path(row["path"]).read_text().startswith("") def test_fetch_article_category_synthesizes_members(self, tmp_path: Path) -> None: class StubResponse: def __init__(self, payload: dict) -> None: self._payload = payload def raise_for_status(self) -> None: pass def json(self) -> dict: return self._payload class StubClient: def get(self, url: str, params: dict | None = None) -> StubResponse: assert params is not None assert params["list"] == "categorymembers" return StubResponse( { "query": { "categorymembers": [ {"title": "1908 Summer Olympics"}, {"title": "2012 Summer Olympics"}, ] } } ) uri = "https://en.wikipedia.org/wiki/Category:Summer_Olympics_in_London" row = fetch_article( uri, tmp_path, client=StubClient(), # ty: ignore[invalid-argument-type] ) assert row is not None assert row["format"] == "md" content = Path(row["path"]).read_text() assert "1908 Summer Olympics" in content assert "2012 Summer Olympics" in content def test_fetch_article_retries_transient_failures( self, tmp_path: Path, monkeypatch ) -> None: sleeps: list[float] = [] monkeypatch.setattr( "evaluations.datasets.frames.time.sleep", lambda s: sleeps.append(s) ) class FlakyResponse: text = "ok" headers = {"etag": 'W/"42/uuid"'} def raise_for_status(self) -> None: pass class FlakyClient: def __init__(self) -> None: self.calls = 0 def get(self, url: str, params: dict | None = None) -> FlakyResponse: self.calls += 1 if self.calls < 3: raise OSError("connection reset") return FlakyResponse() client = FlakyClient() row = fetch_article( "https://en.wikipedia.org/wiki/Capybara", tmp_path, client=client, # ty: ignore[invalid-argument-type] ) assert row is not None assert row["revid"] == "42" assert client.calls == 3 # One throttle sleep before fetching plus one backoff per failure. assert len(sleeps) == 3 def test_fetch_article_honors_retry_after_on_rate_limit( self, tmp_path: Path, monkeypatch ) -> None: import httpx sleeps: list[float] = [] monkeypatch.setattr( "evaluations.datasets.frames.time.sleep", lambda s: sleeps.append(s) ) request = httpx.Request("GET", "https://en.wikipedia.org/x") class OkResponse: text = "ok" headers = {"etag": 'W/"42/uuid"'} def raise_for_status(self) -> None: pass class RateLimitedClient: def __init__(self) -> None: self.calls = 0 def get(self, url: str, params: dict | None = None) -> OkResponse: self.calls += 1 if self.calls == 1: raise httpx.HTTPStatusError( "429 too many requests", request=request, response=httpx.Response( 429, headers={"retry-after": "13"}, request=request ), ) return OkResponse() row = fetch_article( "https://en.wikipedia.org/wiki/Capybara", tmp_path, client=RateLimitedClient(), # ty: ignore[invalid-argument-type] ) assert row is not None assert 13.0 in sleeps def test_fetch_article_gives_up_after_max_attempts( self, tmp_path: Path, monkeypatch ) -> None: monkeypatch.setattr("evaluations.datasets.frames.time.sleep", lambda s: None) class DeadClient: def __init__(self) -> None: self.calls = 0 def get(self, url: str, params: dict | None = None): self.calls += 1 raise OSError("connection reset") client = DeadClient() row = fetch_article( "https://en.wikipedia.org/wiki/Capybara", tmp_path, client=client, # ty: ignore[invalid-argument-type] ) assert row is None assert client.calls == FETCH_ATTEMPTS def test_load_corpus_raises_on_partial_fetch(self, monkeypatch) -> None: import evaluations.datasets.frames as frames monkeypatch.setattr(frames, "_cached_corpus", None) monkeypatch.setattr( frames, "load_frames_questions", lambda: [ { "wiki_links": "['https://en.wikipedia.org/wiki/A', " "'https://en.wikipedia.org/wiki/B']" } ], ) monkeypatch.setattr( frames, "fetch_article", lambda uri, cache_dir, client: None ) with pytest.raises(RuntimeError, match="0/2"): frames.load_frames_corpus() def test_question_with_deleted_article_is_excluded(self) -> None: gone = { "wiki_links": "['https://en.wikipedia.org/wiki/Jack_Vance_(tennis)', " "'https://en.wikipedia.org/wiki/Capybara']" } kept = {"wiki_links": "['https://en.wikipedia.org/wiki/Capybara']"} assert question_is_answerable(gone) is False assert question_is_answerable(kept) is True