Spaces:
Build error
Build error
Download tests/test_search_compressor.py from minhtudragon/headroom_3: direct link, hf CLI and curl.
- Browser
- Download file 26.5 kB
-
https://huggingface.co/spaces/minhtudragon/headroom_3/resolve/69d6091f618f55f6340d4819dfbeaf1285b75fba/tests/test_search_compressor.py
- Command line
-
hf download hf://spaces/minhtudragon/headroom_3@69d6091f618f55f6340d4819dfbeaf1285b75fba/tests/test_search_compressor.py
-
curl -L -o test_search_compressor.py https://huggingface.co/spaces/minhtudragon/headroom_3/resolve/69d6091f618f55f6340d4819dfbeaf1285b75fba/tests/test_search_compressor.py
26.5 kB
| """Comprehensive tests for search_compressor.py. | |
| Tests cover: | |
| 1. grep/ripgrep output parsing | |
| 2. File grouping | |
| 3. Match selection and scoring | |
| 4. Edge cases | |
| """ | |
| from headroom.transforms.search_compressor import ( | |
| FileMatches, | |
| SearchCompressionResult, | |
| SearchCompressor, | |
| SearchCompressorConfig, | |
| SearchMatch, | |
| ) | |
| class TestGrepOutputParsing: | |
| """Tests for parsing grep/ripgrep style output.""" | |
| def test_parse_standard_grep_format(self): | |
| """Standard grep -n format is parsed correctly.""" | |
| content = """src/main.py:42:def process_data(items): | |
| src/main.py:43: \"\"\"Process items.\"\"\" | |
| src/utils.py:15:def validate(data): | |
| """ | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| assert "src/main.py" in file_matches | |
| assert "src/utils.py" in file_matches | |
| assert len(file_matches["src/main.py"].matches) == 2 | |
| assert len(file_matches["src/utils.py"].matches) == 1 | |
| def test_parse_ripgrep_context_format(self): | |
| """Ripgrep with context (- separator) is parsed.""" | |
| content = """src/main.py-40-some context before | |
| src/main.py:42:def process_data(items): | |
| src/main.py-43-some context after | |
| """ | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| assert "src/main.py" in file_matches | |
| # All three lines should be parsed (both : and - separators) | |
| assert len(file_matches["src/main.py"].matches) == 3 | |
| def test_parse_with_colons_in_content(self): | |
| """Content containing colons is parsed correctly.""" | |
| content = """src/config.py:10:DATABASE_URL = "postgres://user:pass@host:5432/db" | |
| src/config.py:20:REDIS_URL = "redis://localhost:6379" | |
| """ | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| assert "src/config.py" in file_matches | |
| matches = file_matches["src/config.py"].matches | |
| # Content after the second colon should be preserved | |
| assert "postgres://user:pass@host:5432/db" in matches[0].content | |
| def test_parse_windows_paths(self): | |
| """Windows-style paths are handled.""" | |
| content = """C:\\Users\\dev\\src\\main.py:10:def main(): | |
| C:\\Users\\dev\\src\\utils.py:20:def helper(): | |
| """ | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| # Windows paths may not parse correctly due to : in path | |
| # This tests current behavior | |
| assert len(file_matches) >= 0 # Just ensure no crash | |
| def test_parse_empty_content(self): | |
| """Empty input returns empty result.""" | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results("") | |
| assert file_matches == {} | |
| def test_parse_whitespace_only(self): | |
| """Whitespace-only input returns empty result.""" | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(" \n\n \n") | |
| assert file_matches == {} | |
| def test_parse_non_grep_content(self): | |
| """Non-grep content returns empty result.""" | |
| content = """This is just regular text | |
| without any grep-style formatting | |
| just normal lines here""" | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| assert file_matches == {} | |
| def test_parse_mixed_valid_invalid(self): | |
| """Mixed valid and invalid lines parse valid ones.""" | |
| content = """src/main.py:10:valid line | |
| this is not a grep line | |
| src/utils.py:20:another valid line | |
| more random text | |
| """ | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| assert "src/main.py" in file_matches | |
| assert "src/utils.py" in file_matches | |
| assert len(file_matches) == 2 | |
| class TestFileGrouping: | |
| """Tests for grouping matches by file.""" | |
| def test_matches_grouped_by_file(self): | |
| """Matches are correctly grouped by filename.""" | |
| content = """a.py:1:line 1 | |
| b.py:2:line 2 | |
| a.py:3:line 3 | |
| c.py:4:line 4 | |
| b.py:5:line 5 | |
| a.py:6:line 6 | |
| """ | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| assert len(file_matches) == 3 | |
| assert len(file_matches["a.py"].matches) == 3 | |
| assert len(file_matches["b.py"].matches) == 2 | |
| assert len(file_matches["c.py"].matches) == 1 | |
| def test_file_matches_first_property(self): | |
| """FileMatches.first returns first match.""" | |
| fm = FileMatches( | |
| file="test.py", | |
| matches=[ | |
| SearchMatch(file="test.py", line_number=10, content="first"), | |
| SearchMatch(file="test.py", line_number=20, content="second"), | |
| ], | |
| ) | |
| assert fm.first is not None | |
| assert fm.first.line_number == 10 | |
| assert fm.first.content == "first" | |
| def test_file_matches_last_property(self): | |
| """FileMatches.last returns last match.""" | |
| fm = FileMatches( | |
| file="test.py", | |
| matches=[ | |
| SearchMatch(file="test.py", line_number=10, content="first"), | |
| SearchMatch(file="test.py", line_number=20, content="last"), | |
| ], | |
| ) | |
| assert fm.last is not None | |
| assert fm.last.line_number == 20 | |
| assert fm.last.content == "last" | |
| def test_file_matches_empty(self): | |
| """FileMatches with no matches handles first/last.""" | |
| fm = FileMatches(file="test.py", matches=[]) | |
| assert fm.first is None | |
| assert fm.last is None | |
| class TestMatchScoring: | |
| """Tests for match relevance scoring.""" | |
| def test_score_context_word_overlap(self): | |
| """Matches containing context words get higher scores.""" | |
| content = """src/main.py:10:def process_data(): | |
| src/main.py:20:def calculate_result(): | |
| src/main.py:30:def handle_error(): | |
| """ | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| compressor._score_matches(file_matches, context="error handling") | |
| matches = file_matches["src/main.py"].matches | |
| error_match = next(m for m in matches if "error" in m.content) | |
| data_match = next(m for m in matches if "data" in m.content) | |
| # Error match should score higher with "error" context | |
| assert error_match.score > data_match.score | |
| def test_score_error_patterns_boosted(self): | |
| """Error/exception patterns get boosted scores.""" | |
| content = """src/main.py:10:def normal_function(): | |
| src/main.py:20:raise ValueError("error occurred") | |
| src/main.py:30:# TODO: fix this | |
| """ | |
| compressor = SearchCompressor(config=SearchCompressorConfig(boost_errors=True)) | |
| file_matches = compressor._parse_search_results(content) | |
| compressor._score_matches(file_matches, context="") | |
| matches = file_matches["src/main.py"].matches | |
| error_match = next(m for m in matches if "error" in m.content.lower()) | |
| normal_match = next(m for m in matches if "normal" in m.content) | |
| assert error_match.score > normal_match.score | |
| def test_score_warning_patterns(self): | |
| """Warning patterns get boosted scores.""" | |
| content = """src/main.py:10:def normal(): | |
| src/main.py:20:# WARNING: deprecated | |
| """ | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| compressor._score_matches(file_matches, context="") | |
| matches = file_matches["src/main.py"].matches | |
| warning_match = next(m for m in matches if "WARNING" in m.content) | |
| normal_match = next(m for m in matches if "normal" in m.content) | |
| assert warning_match.score > normal_match.score | |
| def test_score_todo_patterns(self): | |
| """TODO/FIXME patterns get boosted scores.""" | |
| content = """src/main.py:10:def normal(): | |
| src/main.py:20:# FIXME: this needs work | |
| src/main.py:30:# TODO: implement later | |
| """ | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| compressor._score_matches(file_matches, context="") | |
| matches = file_matches["src/main.py"].matches | |
| fixme_match = next(m for m in matches if "FIXME" in m.content) | |
| normal_match = next(m for m in matches if "normal" in m.content) | |
| assert fixme_match.score > normal_match.score | |
| def test_score_context_keywords_config(self): | |
| """context_keywords configuration boosts matching lines.""" | |
| content = """src/main.py:10:def auth_handler(): | |
| src/main.py:20:def data_processor(): | |
| """ | |
| config = SearchCompressorConfig(context_keywords=["auth", "security"]) | |
| compressor = SearchCompressor(config=config) | |
| file_matches = compressor._parse_search_results(content) | |
| compressor._score_matches(file_matches, context="") | |
| matches = file_matches["src/main.py"].matches | |
| auth_match = next(m for m in matches if "auth" in m.content) | |
| data_match = next(m for m in matches if "data" in m.content) | |
| assert auth_match.score > data_match.score | |
| def test_score_capped_at_one(self): | |
| """Scores are capped at 1.0.""" | |
| content = """src/main.py:10:ERROR FATAL exception fail warning TODO FIXME | |
| """ | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| compressor._score_matches(file_matches, context="error fatal exception") | |
| match = file_matches["src/main.py"].matches[0] | |
| assert match.score <= 1.0 | |
| class TestMatchSelection: | |
| """Tests for selecting which matches to keep.""" | |
| def test_keeps_first_and_last_by_default(self): | |
| """First and last matches are kept by default.""" | |
| content = "\n".join([f"src/file.py:{i}:line {i}" for i in range(1, 101)]) | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| always_keep_first=True, | |
| always_keep_last=True, | |
| max_matches_per_file=5, | |
| ) | |
| ) | |
| result = compressor.compress(content) | |
| assert "src/file.py:1:line 1" in result.compressed | |
| assert "src/file.py:100:line 100" in result.compressed | |
| def test_respects_max_matches_per_file(self): | |
| """max_matches_per_file limits matches per file.""" | |
| content = "\n".join([f"src/file.py:{i}:line {i}" for i in range(1, 51)]) | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| max_matches_per_file=3, | |
| enable_ccr=False, | |
| ) | |
| ) | |
| result = compressor.compress(content) | |
| # Should have at most 3 matches + summary | |
| file_lines = [ | |
| line for line in result.compressed.split("\n") if line.startswith("src/file.py:") | |
| ] | |
| assert len(file_lines) <= 3 | |
| def test_respects_max_total_matches(self): | |
| """max_total_matches limits total output.""" | |
| # Create matches across many files | |
| lines = [] | |
| for f in range(20): | |
| for i in range(10): | |
| lines.append(f"src/file{f}.py:{i}:line content") | |
| content = "\n".join(lines) | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| max_total_matches=15, | |
| max_files=20, | |
| enable_ccr=False, | |
| ) | |
| ) | |
| result = compressor.compress(content) | |
| # Count actual match lines (not summaries) | |
| match_lines = [ | |
| line for line in result.compressed.split("\n") if line and not line.startswith("[") | |
| ] | |
| assert len(match_lines) <= 15 | |
| def test_respects_max_files(self): | |
| """max_files limits number of files in output.""" | |
| # Create matches in many files | |
| lines = [] | |
| for f in range(30): | |
| lines.append(f"src/file{f}.py:1:content") | |
| content = "\n".join(lines) | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| max_files=5, | |
| enable_ccr=False, | |
| ) | |
| ) | |
| result = compressor.compress(content) | |
| # Count unique files in output | |
| output_files = set() | |
| for line in result.compressed.split("\n"): | |
| if ":" in line and not line.startswith("["): | |
| parts = line.split(":") | |
| if len(parts) >= 2: | |
| output_files.add(parts[0]) | |
| assert len(output_files) <= 5 | |
| def test_high_scoring_files_selected_first(self): | |
| """Files with higher-scoring matches are selected first.""" | |
| content = """normal/file.py:1:regular content | |
| important/file.py:1:ERROR critical failure | |
| another/file.py:1:some code here | |
| """ | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| max_files=1, | |
| boost_errors=True, | |
| enable_ccr=False, | |
| ) | |
| ) | |
| result = compressor.compress(content) | |
| # File with ERROR should be selected | |
| assert "important/file.py" in result.compressed | |
| def test_output_sorted_by_line_number(self): | |
| """Matches in output are sorted by line number within file.""" | |
| content = """src/file.py:50:middle line | |
| src/file.py:10:first line | |
| src/file.py:90:last line | |
| """ | |
| compressor = SearchCompressor() | |
| result = compressor.compress(content) | |
| lines = result.compressed.split("\n") | |
| line_numbers = [] | |
| for line in lines: | |
| if line.startswith("src/file.py:"): | |
| parts = line.split(":") | |
| if len(parts) >= 2 and parts[1].isdigit(): | |
| line_numbers.append(int(parts[1])) | |
| assert line_numbers == sorted(line_numbers) | |
| class TestCompressionBehavior: | |
| """Tests for overall compression behavior.""" | |
| def test_small_results_unchanged(self): | |
| """Small results pass through unchanged.""" | |
| content = "src/file.py:1:def foo():\nsrc/file.py:2: pass" | |
| compressor = SearchCompressor() | |
| result = compressor.compress(content) | |
| assert result.compression_ratio == 1.0 | |
| assert result.compressed == content | |
| def test_empty_input_handled(self): | |
| """Empty input is handled gracefully.""" | |
| compressor = SearchCompressor() | |
| result = compressor.compress("") | |
| assert result.compressed == "" | |
| assert result.original_match_count == 0 | |
| assert result.compression_ratio == 1.0 | |
| def test_compression_adds_summary(self): | |
| """Compression adds summary for omitted matches.""" | |
| content = "\n".join([f"src/file.py:{i}:line {i}" for i in range(1, 51)]) | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| max_matches_per_file=3, | |
| enable_ccr=False, | |
| ) | |
| ) | |
| result = compressor.compress(content) | |
| # Should have summary about omitted matches | |
| assert "[... and" in result.compressed | |
| assert "more matches" in result.compressed | |
| def test_compression_ratio_calculated(self): | |
| """Compression ratio is calculated correctly.""" | |
| content = "\n".join([f"src/file.py:{i}:line {i}" for i in range(1, 101)]) | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| max_matches_per_file=5, | |
| enable_ccr=False, | |
| ) | |
| ) | |
| result = compressor.compress(content) | |
| # Ratio should be less than 1.0 for compression | |
| assert result.compression_ratio < 1.0 | |
| class TestSearchCompressionResult: | |
| """Tests for SearchCompressionResult dataclass.""" | |
| def test_tokens_saved_estimate(self): | |
| """Token savings estimation works correctly.""" | |
| original = "a" * 400 # ~100 tokens | |
| compressed = "b" * 40 # ~10 tokens | |
| result = SearchCompressionResult( | |
| compressed=compressed, | |
| original=original, | |
| original_match_count=100, | |
| compressed_match_count=10, | |
| files_affected=5, | |
| compression_ratio=0.1, | |
| ) | |
| # (400 - 40) / 4 = 90 tokens saved | |
| assert result.tokens_saved_estimate == 90 | |
| def test_matches_omitted_property(self): | |
| """matches_omitted property calculates correctly.""" | |
| result = SearchCompressionResult( | |
| compressed="test", | |
| original="original", | |
| original_match_count=100, | |
| compressed_match_count=15, | |
| files_affected=10, | |
| compression_ratio=0.15, | |
| ) | |
| assert result.matches_omitted == 85 | |
| def test_default_summaries_empty(self): | |
| """Default summaries is empty dict.""" | |
| result = SearchCompressionResult( | |
| compressed="test", | |
| original="original", | |
| original_match_count=1, | |
| compressed_match_count=1, | |
| files_affected=1, | |
| compression_ratio=1.0, | |
| ) | |
| assert result.summaries == {} | |
| class TestEdgeCases: | |
| """Tests for edge cases and boundary conditions.""" | |
| def test_single_match_passthrough(self): | |
| """Single match passes through unchanged.""" | |
| content = "src/file.py:10:single match" | |
| compressor = SearchCompressor() | |
| result = compressor.compress(content) | |
| assert result.compressed == content | |
| assert result.original_match_count == 1 | |
| assert result.compressed_match_count == 1 | |
| def test_unicode_content(self): | |
| """Unicode characters in content are handled.""" | |
| content = """src/main.py:10:msg = "こんにちは" | |
| src/main.py:20:emoji = "🎉" | |
| src/main.py:30:umlaut = "über" | |
| """ | |
| compressor = SearchCompressor() | |
| result = compressor.compress(content) | |
| assert "こんにちは" in result.compressed | |
| assert "🎉" in result.compressed | |
| assert "über" in result.compressed | |
| def test_very_long_lines(self): | |
| """Very long content lines are handled.""" | |
| long_content = "x" * 10000 | |
| content = f"src/file.py:1:{long_content}" | |
| compressor = SearchCompressor() | |
| result = compressor.compress(content) | |
| assert len(result.compressed) > 0 | |
| assert long_content in result.compressed | |
| def test_many_files_few_matches(self): | |
| """Many files with one match each are handled.""" | |
| lines = [f"src/file{i}.py:1:single match" for i in range(100)] | |
| content = "\n".join(lines) | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| max_files=10, | |
| enable_ccr=False, | |
| ) | |
| ) | |
| result = compressor.compress(content) | |
| assert result.files_affected == 100 | |
| # Output should be limited to max_files | |
| output_files = set() | |
| for line in result.compressed.split("\n"): | |
| if ":" in line and not line.startswith("["): | |
| parts = line.split(":") | |
| if len(parts) >= 2: | |
| output_files.add(parts[0]) | |
| assert len(output_files) <= 10 | |
| def test_special_characters_in_path(self): | |
| """Special characters in file paths are handled.""" | |
| content = """src/my-file.py:10:content | |
| src/my_file.py:20:content | |
| src/my.file.py:30:content | |
| src/file (1).py:40:content | |
| """ | |
| compressor = SearchCompressor() | |
| result = compressor.compress(content) | |
| assert "my-file.py" in result.compressed | |
| assert "my_file.py" in result.compressed | |
| def test_line_number_zero(self): | |
| """Line number 0 is handled (edge case).""" | |
| content = "src/file.py:0:line at position 0" | |
| compressor = SearchCompressor() | |
| result = compressor.compress(content) | |
| assert ":0:" in result.compressed | |
| def test_negative_line_number_skipped(self): | |
| """Negative line numbers don't match the pattern.""" | |
| content = "src/file.py:-1:invalid" | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| # Pattern requires \d+ which is positive integers only | |
| assert len(file_matches) == 0 | |
| class TestContextIntegration: | |
| """Tests for context-aware compression.""" | |
| def test_context_influences_selection(self): | |
| """Context string influences which matches are selected.""" | |
| lines = [] | |
| for i in range(50): | |
| lines.append(f"src/utils.py:{i}:def helper_{i}():") | |
| # Add some specific matches | |
| lines.append("src/auth.py:100:def authenticate_user():") | |
| lines.append("src/auth.py:200:def validate_token():") | |
| content = "\n".join(lines) | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| max_total_matches=5, | |
| context_keywords=["auth", "token", "validate"], | |
| enable_ccr=False, | |
| ) | |
| ) | |
| result = compressor.compress(content, context="find authentication code") | |
| # Auth-related matches should be included | |
| assert "authenticate" in result.compressed or "token" in result.compressed | |
| def test_short_context_words_ignored(self): | |
| """Context words <= 2 chars are ignored for scoring.""" | |
| content = """src/file.py:10:a = 1 | |
| src/file.py:20:do something important | |
| """ | |
| compressor = SearchCompressor() | |
| file_matches = compressor._parse_search_results(content) | |
| compressor._score_matches(file_matches, context="a") | |
| # Short context word "a" shouldn't cause errors or abnormal scoring | |
| matches = file_matches["src/file.py"].matches | |
| assert all(m.score <= 1.0 for m in matches) | |
| class TestOutputFormatting: | |
| """Tests for output format and structure.""" | |
| def test_output_maintains_grep_format(self): | |
| """Output maintains file:line:content format.""" | |
| content = """src/file.py:10:def foo(): | |
| src/file.py:20:def bar(): | |
| """ | |
| compressor = SearchCompressor() | |
| result = compressor.compress(content) | |
| for line in result.compressed.split("\n"): | |
| if line and not line.startswith("["): | |
| assert line.count(":") >= 2 | |
| parts = line.split(":", 2) | |
| assert parts[1].isdigit() | |
| def test_summaries_track_omitted_per_file(self): | |
| """Summaries dict tracks omissions per file.""" | |
| content = "\n".join([f"src/file.py:{i}:line {i}" for i in range(1, 51)]) | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| max_matches_per_file=3, | |
| enable_ccr=False, | |
| ) | |
| ) | |
| result = compressor.compress(content) | |
| assert "src/file.py" in result.summaries | |
| assert "more matches" in result.summaries["src/file.py"] | |
| def test_files_sorted_in_output(self): | |
| """Files are sorted alphabetically in output.""" | |
| content = """z_file.py:1:content | |
| a_file.py:1:content | |
| m_file.py:1:content | |
| """ | |
| compressor = SearchCompressor() | |
| result = compressor.compress(content) | |
| lines = [ | |
| line for line in result.compressed.split("\n") if line and not line.startswith("[") | |
| ] | |
| files = [line.split(":")[0] for line in lines] | |
| assert files == sorted(files) | |
| class TestSearchMatchDataclass: | |
| """Tests for SearchMatch dataclass.""" | |
| def test_default_score_zero(self): | |
| """Default score is 0.0.""" | |
| match = SearchMatch(file="test.py", line_number=1, content="test") | |
| assert match.score == 0.0 | |
| def test_match_attributes(self): | |
| """Match attributes are set correctly.""" | |
| match = SearchMatch( | |
| file="src/main.py", | |
| line_number=42, | |
| content="def process():", | |
| score=0.8, | |
| ) | |
| assert match.file == "src/main.py" | |
| assert match.line_number == 42 | |
| assert match.content == "def process():" | |
| assert match.score == 0.8 | |
| class TestConfigOptions: | |
| """Tests for configuration options.""" | |
| def test_disable_keep_first(self): | |
| """always_keep_first=False doesn't force first match.""" | |
| content = "\n".join([f"src/file.py:{i}:line {i}" for i in range(1, 51)]) | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| always_keep_first=False, | |
| always_keep_last=True, | |
| max_matches_per_file=2, | |
| enable_ccr=False, | |
| ) | |
| ) | |
| result = compressor.compress(content) | |
| # First line not guaranteed to be present | |
| # But last should be | |
| assert "src/file.py:50:line 50" in result.compressed | |
| def test_disable_keep_last(self): | |
| """always_keep_last=False doesn't force last match.""" | |
| content = "\n".join([f"src/file.py:{i}:line {i}" for i in range(1, 51)]) | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| always_keep_first=True, | |
| always_keep_last=False, | |
| max_matches_per_file=2, | |
| enable_ccr=False, | |
| ) | |
| ) | |
| result = compressor.compress(content) | |
| # First line should be present | |
| assert "src/file.py:1:line 1" in result.compressed | |
| def test_disable_error_boost(self): | |
| """boost_errors=False doesn't prioritize error patterns.""" | |
| content = """src/file.py:1:ERROR critical failure | |
| src/file.py:2:normal code line | |
| """ | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| boost_errors=False, | |
| ) | |
| ) | |
| file_matches = compressor._parse_search_results(content) | |
| compressor._score_matches(file_matches, context="") | |
| matches = file_matches["src/file.py"].matches | |
| # Without boost, both should have similar (low) scores | |
| error_match = next(m for m in matches if "ERROR" in m.content) | |
| assert error_match.score == 0.0 # No boost applied | |
| def test_min_matches_for_ccr(self): | |
| """min_matches_for_ccr threshold is respected.""" | |
| content = "\n".join([f"src/file.py:{i}:line {i}" for i in range(1, 6)]) | |
| # With threshold of 10, CCR should not activate for 5 matches | |
| compressor = SearchCompressor( | |
| config=SearchCompressorConfig( | |
| min_matches_for_ccr=10, | |
| enable_ccr=True, | |
| ) | |
| ) | |
| result = compressor.compress(content) | |
| assert result.cache_key is None | |