mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description `_check_tree_sitter()` in `headroom/compression/handlers/code_handler.py` only verified that `tree_sitter_language_pack` could be imported. When tree-sitter core and the language pack are built against different ABIs, the import succeeds but `parser.language = get_language(...)` raises at request time, silently falling back to the generic text compressor — with no warning, while the banner still reports code-aware as enabled. #1299 fixed the same class of bug in `transforms/code_compressor.py`. This PR is the defensive follow-up tracked by #1231: it applies the same parse probe to the compression **structure handler** so both code-aware paths are consistent. Closes #1231 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) ## Changes Made - Replace the import-only probe in `code_handler._check_tree_sitter()` with a real parse probe: construct a `Parser`, assign a Python language, and parse `b"x = 1\n"` — if any step fails, mark unavailable - Log a WARNING when import succeeds but parsing fails, so the downgrade is visible - Add `TestAvailabilityProbe` covering the simulated ABI mismatch (-> False) and healthy install (-> True) cases > Note: an earlier revision of this PR also touched `transforms/code_compressor.py`, but that fix landed independently via #1299. After rebasing onto current `main`, this PR is scoped to the remaining `code_handler.py` gap only. ## Testing - [x] Unit tests pass (`pytest tests/test_compression/test_code_handler.py` -> 22 passed, 8 skipped) - [x] Linting / formatting pass (`ruff check .`, `ruff format --check .`) - [x] New tests added for new functionality ### Real Behavior Proof - Setup: Windows 11 (GBK locale), Python 3.10, tree-sitter NOT installed - Before fix: `_check_tree_sitter()` returns `True` on a partial/ABI-mismatched install (import succeeds), then silently degrades to the text compressor at request time with no warning - After fix: the probe parses a trivial snippet; an ABI mismatch is caught at probe time, `_check_tree_sitter()` returns `False`, and a WARNING is logged. `TestAvailabilityProbe::test_abi_mismatch_returns_false` reproduces this with a fake Parser whose `language` setter raises. Signed-off-by: RTCartist <wangshengb@buaa.edu.cn>
This commit is contained in:
parent
85804043ff
commit
1de35e775f
2 changed files with 65 additions and 2 deletions
|
|
@ -33,15 +33,32 @@ _tree_sitter_local = threading.local()
|
|||
|
||||
|
||||
def _check_tree_sitter() -> bool:
|
||||
"""Check if tree-sitter is available."""
|
||||
"""Check if tree-sitter is available and can actually parse.
|
||||
|
||||
Constructs a parser and runs a minimal parse so that ABI mismatches
|
||||
between ``tree_sitter`` and ``tree_sitter_language_pack`` surface here
|
||||
instead of silently falling back to the text compressor at request time.
|
||||
"""
|
||||
global _tree_sitter_available
|
||||
if _tree_sitter_available is None:
|
||||
try:
|
||||
import tree_sitter_language_pack # noqa: F401
|
||||
from tree_sitter import Parser
|
||||
from tree_sitter_language_pack import get_language
|
||||
|
||||
parser = Parser()
|
||||
parser.language = get_language("python")
|
||||
tree = parser.parse(b"x = 1\n")
|
||||
if tree.root_node.child_count == 0:
|
||||
raise RuntimeError("tree-sitter parse returned empty tree")
|
||||
_tree_sitter_available = True
|
||||
except ImportError:
|
||||
_tree_sitter_available = False
|
||||
except Exception:
|
||||
logger.warning(
|
||||
"tree-sitter imported but failed to parse; "
|
||||
"code-aware compression disabled (ABI mismatch?)"
|
||||
)
|
||||
_tree_sitter_available = False
|
||||
return _tree_sitter_available
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
"""Tests for code structure handler."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from headroom.compression.handlers.code_handler import (
|
||||
CodeStructureHandler,
|
||||
_check_tree_sitter,
|
||||
is_tree_sitter_available,
|
||||
)
|
||||
|
||||
|
|
@ -131,6 +134,49 @@ class TestRegexFallbackLanguages:
|
|||
assert result.confidence == 0.7
|
||||
|
||||
|
||||
class TestAvailabilityProbe:
|
||||
"""_check_tree_sitter must exercise a real parse, not just an import."""
|
||||
|
||||
def test_abi_mismatch_returns_false(self):
|
||||
import types
|
||||
|
||||
import headroom.compression.handlers.code_handler as mod
|
||||
|
||||
mod._tree_sitter_available = None
|
||||
|
||||
fake_ts = types.ModuleType("tree_sitter")
|
||||
|
||||
class FakeParser:
|
||||
def __setattr__(self, name, value):
|
||||
if name == "language":
|
||||
raise RuntimeError("ABI mismatch")
|
||||
super().__setattr__(name, value)
|
||||
|
||||
fake_ts.Parser = FakeParser
|
||||
|
||||
fake_pack = types.ModuleType("tree_sitter_language_pack")
|
||||
fake_pack.get_language = lambda name: object()
|
||||
|
||||
with patch.dict(
|
||||
"sys.modules",
|
||||
{
|
||||
"tree_sitter": fake_ts,
|
||||
"tree_sitter_language_pack": fake_pack,
|
||||
},
|
||||
):
|
||||
result = _check_tree_sitter()
|
||||
assert result is False
|
||||
mod._tree_sitter_available = None
|
||||
|
||||
@requires_tree_sitter
|
||||
def test_healthy_install_returns_true(self):
|
||||
import headroom.compression.handlers.code_handler as mod
|
||||
|
||||
mod._tree_sitter_available = None
|
||||
assert _check_tree_sitter() is True
|
||||
mod._tree_sitter_available = None
|
||||
|
||||
|
||||
class TestEdgeCases:
|
||||
@pytest.fixture
|
||||
def handler(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue