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
360 lines
11 KiB
Python
360 lines
11 KiB
Python
"""
|
|
Performance tests for citation formatter to ensure it handles large documents efficiently.
|
|
"""
|
|
|
|
import time
|
|
import pytest
|
|
from local_deep_research.text_optimization.citation_formatter import (
|
|
CitationFormatter,
|
|
CitationMode,
|
|
)
|
|
|
|
|
|
class TestCitationPerformance:
|
|
"""Test performance of citation formatter with large documents."""
|
|
|
|
def test_many_citations_performance(self):
|
|
"""Test performance with many citations."""
|
|
# Generate content with 1000 citations
|
|
citations = []
|
|
sources = []
|
|
|
|
for i in range(1, 1001):
|
|
# Mix up citation patterns
|
|
if i % 3 == 0:
|
|
citations.append(f"Multiple [{i}] citations [{i}] here [{i}]")
|
|
else:
|
|
citations.append(f"Single citation [{i}]")
|
|
|
|
sources.append(f"""[{i}] Source {i} Title
|
|
URL: https://example{i % 100}.com/paper/{i}""")
|
|
|
|
content = f"""# Large Document Performance Test
|
|
|
|
{" ".join(citations)}
|
|
|
|
## Sources
|
|
|
|
{chr(10).join(sources)}"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
|
|
start_time = time.time()
|
|
result = formatter.format_document(content)
|
|
end_time = time.time()
|
|
|
|
processing_time = end_time - start_time
|
|
|
|
# Should complete in reasonable time (less than 5 seconds for 1000 citations)
|
|
assert processing_time < 5.0, (
|
|
f"Processing took {processing_time:.2f}s, which is too slow"
|
|
)
|
|
|
|
# Should have formatted all citations
|
|
for i in range(100):
|
|
assert f"[example{i}.com]" in result
|
|
|
|
def test_long_document_performance(self):
|
|
"""Test performance with very long documents."""
|
|
# Create a 100KB document
|
|
paragraphs = []
|
|
|
|
for i in range(200):
|
|
paragraph = f"""
|
|
This is paragraph {i} with some citations [{i % 50 + 1}] and more text to make
|
|
it longer. The citation [{i % 50 + 1}] appears multiple times in this paragraph.
|
|
Sometimes we reference [{(i + 1) % 50 + 1}] as well. This helps test performance
|
|
with realistic document sizes and citation patterns.
|
|
"""
|
|
paragraphs.append(paragraph)
|
|
|
|
# Add sources
|
|
sources = []
|
|
for i in range(1, 51):
|
|
sources.append(f"""[{i}] Academic Paper {i}
|
|
URL: https://journal{i % 10}.com/article/{i}""")
|
|
|
|
content = f"""# Very Long Document
|
|
|
|
{"".join(paragraphs)}
|
|
|
|
## Sources
|
|
|
|
{chr(10).join(sources)}"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_ID_HYPERLINKS)
|
|
|
|
start_time = time.time()
|
|
result = formatter.format_document(content)
|
|
end_time = time.time()
|
|
|
|
processing_time = end_time - start_time
|
|
|
|
# Should handle large documents efficiently
|
|
assert processing_time < 10.0, (
|
|
f"Processing took {processing_time:.2f}s for large document"
|
|
)
|
|
|
|
# Verify formatting worked
|
|
assert "[journal0.com" in result
|
|
assert "[journal9.com" in result
|
|
|
|
def test_deeply_nested_markdown_performance(self):
|
|
"""Test performance with deeply nested markdown structures."""
|
|
# Create nested lists with citations
|
|
nested_content = """# Document with Nested Structure
|
|
|
|
"""
|
|
|
|
for i in range(10):
|
|
nested_content += f"{' ' * i}- Level {i} item [{i + 1}]\n"
|
|
nested_content += (
|
|
f"{' ' * i} - Sub-item with citation [{i + 11}]\n"
|
|
)
|
|
nested_content += f"{' ' * i} - Deep sub-item [{i + 21}]\n"
|
|
|
|
# Add complex markdown
|
|
nested_content += """
|
|
| Header 1 [31] | Header 2 [32] | Header 3 [33] |
|
|
|---------------|---------------|---------------|
|
|
| Cell [34] | Cell [35] | Cell [36] |
|
|
| Cell [37] | Cell [38] | Cell [39] |
|
|
|
|
> Blockquote level 1 [40]
|
|
>> Blockquote level 2 [41]
|
|
>>> Blockquote level 3 [42]
|
|
|
|
"""
|
|
|
|
# Add sources
|
|
sources = []
|
|
for i in range(1, 43):
|
|
sources.append(
|
|
f"[{i}] Source {i}\nURL: https://site{i % 5}.com/{i}"
|
|
)
|
|
|
|
content = nested_content + "\n## Sources\n\n" + "\n\n".join(sources)
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
|
|
start_time = time.time()
|
|
result = formatter.format_document(content)
|
|
end_time = time.time()
|
|
|
|
processing_time = end_time - start_time
|
|
|
|
# Should handle complex structure efficiently
|
|
assert processing_time < 2.0, (
|
|
f"Processing complex structure took {processing_time:.2f}s"
|
|
)
|
|
|
|
# Verify citations in various contexts were formatted
|
|
assert "[site0.com]" in result
|
|
assert "[site4.com]" in result
|
|
|
|
def test_repeated_formatting_performance(self):
|
|
"""Test performance when formatting the same document multiple times."""
|
|
content = """# Document
|
|
|
|
Citations [1] and [2] and [3].
|
|
|
|
## Sources
|
|
|
|
[1] First
|
|
URL: https://first.com
|
|
|
|
[2] Second
|
|
URL: https://second.com
|
|
|
|
[3] Third
|
|
URL: https://third.com"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
|
|
# Format 100 times
|
|
start_time = time.time()
|
|
for _ in range(100):
|
|
formatter.format_document(content)
|
|
end_time = time.time()
|
|
|
|
total_time = end_time - start_time
|
|
avg_time = total_time / 100
|
|
|
|
# Average time should be very fast
|
|
assert avg_time < 0.01, (
|
|
f"Average formatting time {avg_time:.4f}s is too slow"
|
|
)
|
|
|
|
def test_citation_modes_performance_comparison(self):
|
|
"""Compare performance across different citation modes."""
|
|
# Create test document
|
|
citations = [f"Citation [{i}]" for i in range(1, 101)]
|
|
sources = [
|
|
f"[{i}] Source {i}\nURL: https://domain{i % 20}.com/{i}"
|
|
for i in range(1, 101)
|
|
]
|
|
|
|
content = f"""# Performance Test
|
|
|
|
{" ".join(citations)}
|
|
|
|
## Sources
|
|
|
|
{chr(10).join(sources)}"""
|
|
|
|
modes = [
|
|
CitationMode.NUMBER_HYPERLINKS,
|
|
CitationMode.DOMAIN_HYPERLINKS,
|
|
CitationMode.DOMAIN_ID_HYPERLINKS,
|
|
CitationMode.DOMAIN_ID_ALWAYS_HYPERLINKS,
|
|
CitationMode.NO_HYPERLINKS,
|
|
]
|
|
|
|
times = {}
|
|
|
|
for mode in modes:
|
|
formatter = CitationFormatter(mode=mode)
|
|
|
|
start_time = time.time()
|
|
formatter.format_document(content)
|
|
end_time = time.time()
|
|
|
|
times[mode.value] = end_time - start_time
|
|
|
|
# All modes should complete quickly
|
|
for mode_name, duration in times.items():
|
|
assert duration < 1.0, f"Mode {mode_name} took {duration:.3f}s"
|
|
|
|
# Performance shouldn't vary drastically between modes
|
|
# Exclude NO_HYPERLINKS from comparison as it does minimal processing
|
|
times_without_no_hyperlinks = {
|
|
k: v for k, v in times.items() if k != "no_hyperlinks"
|
|
}
|
|
|
|
if times_without_no_hyperlinks:
|
|
max_time = max(times_without_no_hyperlinks.values())
|
|
min_time = min(times_without_no_hyperlinks.values())
|
|
# Allow more variance as some modes do more work
|
|
if min_time > 0:
|
|
assert max_time / min_time < 10.0, (
|
|
f"Performance varies too much between modes (excluding no_hyperlinks): {times_without_no_hyperlinks}"
|
|
)
|
|
|
|
# Verify NO_HYPERLINKS is indeed the fastest
|
|
if "no_hyperlinks" in times:
|
|
no_hyperlinks_time = times["no_hyperlinks"]
|
|
other_times = [v for k, v in times.items() if k != "no_hyperlinks"]
|
|
if other_times:
|
|
# NO_HYPERLINKS should be faster or comparable to the fastest other mode
|
|
assert no_hyperlinks_time <= min(other_times) * 2, (
|
|
"NO_HYPERLINKS mode should be among the fastest modes"
|
|
)
|
|
|
|
def test_memory_efficiency_large_content(self):
|
|
"""Test that formatter doesn't use excessive memory."""
|
|
# Create a large document (10MB+)
|
|
large_text = "This is a test paragraph. " * 10000
|
|
|
|
content = f"""# Large Document
|
|
|
|
{large_text}
|
|
|
|
Citation at the end [1].
|
|
|
|
## Sources
|
|
|
|
[1] Only Source
|
|
URL: https://example.com/source"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
|
|
# Should handle large content without issues
|
|
try:
|
|
start_time = time.time()
|
|
result = formatter.format_document(content)
|
|
end_time = time.time()
|
|
|
|
# Should complete even with large content
|
|
assert "[example.com]" in result
|
|
assert end_time - start_time < 5.0
|
|
|
|
except MemoryError:
|
|
pytest.fail("Formatter used too much memory")
|
|
|
|
@pytest.mark.parametrize("num_sources", [10, 50, 100, 500])
|
|
def test_scaling_with_source_count(self, num_sources):
|
|
"""Test how performance scales with number of sources."""
|
|
# Create content with varying number of sources
|
|
citations = []
|
|
sources = []
|
|
|
|
# Each source cited 3 times
|
|
for i in range(1, num_sources + 1):
|
|
citations.extend([f"[{i}]", f"[{i}]", f"[{i}]"])
|
|
sources.append(f"[{i}] Source {i}\nURL: https://example.com/{i}")
|
|
|
|
content = f"""# Scaling Test
|
|
|
|
Citations: {" ".join(citations)}
|
|
|
|
## Sources
|
|
|
|
{chr(10).join(sources)}"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_ID_HYPERLINKS)
|
|
|
|
start_time = time.time()
|
|
formatter.format_document(content)
|
|
end_time = time.time()
|
|
|
|
processing_time = end_time - start_time
|
|
|
|
# Time should scale roughly linearly with source count
|
|
expected_max_time = num_sources * 0.01 # 10ms per source max
|
|
assert processing_time < expected_max_time, (
|
|
f"Processing {num_sources} sources took {processing_time:.3f}s, "
|
|
f"expected < {expected_max_time:.3f}s"
|
|
)
|
|
|
|
def test_regex_performance_edge_cases(self):
|
|
"""Test performance with content that might cause regex issues."""
|
|
# Create content with patterns that could cause backtracking
|
|
tricky_patterns = [
|
|
"[" * 100 + "1" + "]" * 100, # Many brackets
|
|
"[[[[1]]]]", # Nested brackets
|
|
"[1][2][3][4][5]" * 50, # Many consecutive citations
|
|
"[1] " + "." * 1000 + " [2]", # Long spans between citations
|
|
]
|
|
|
|
sources = """
|
|
[1] Source 1
|
|
URL: https://one.com
|
|
|
|
[2] Source 2
|
|
URL: https://two.com
|
|
|
|
[3] Source 3
|
|
URL: https://three.com
|
|
|
|
[4] Source 4
|
|
URL: https://four.com
|
|
|
|
[5] Source 5
|
|
URL: https://five.com"""
|
|
|
|
for pattern in tricky_patterns:
|
|
content = f"# Test\n\n{pattern}\n\n## Sources\n{sources}"
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
|
|
start_time = time.time()
|
|
try:
|
|
formatter.format_document(content)
|
|
end_time = time.time()
|
|
|
|
# Should complete quickly even with tricky patterns
|
|
assert end_time - start_time < 1.0
|
|
|
|
except Exception as e:
|
|
pytest.fail(f"Failed on pattern: {pattern[:50]}... Error: {e}")
|