fix(code): parse-probe tree-sitter availability in code_handler (#1231) (#1300)

## 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:
Shengbo_Wang 2026-07-10 03:06:29 +08:00 committed by GitHub
parent 85804043ff
commit 1de35e775f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 65 additions and 2 deletions

View file

@ -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

View file

@ -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):