## Summary - Add `ensure_pch()` helper to MasterServer that builds/reuses precompiled headers via stateless workers, with preamble hash-based staleness detection (xxh3_64bits) - Fix `BuildPCHParams` to carry `preamble_bound` so the stateless worker truncates content at the preamble boundary (fixes redefinition errors when PCH included full file) - Wire PCH into both `run_build_drain` (stateful compile path) and `forward_stateless` (completion/signatureHelp path) - Add PCH state cleanup on `didClose` and hash invalidation on `didSave` ## Test plan - [x] 398 unit tests pass (including 6 new PCH tests: PreambleHash x3, PCHWorker x2, BuildPCHRequest assertion) - [x] 5 new integration tests pass (`test_pch.py`: diagnostics on open, body edit recompile, no-include file, hover with PCH, completion with PCH) - [x] 21 existing integration tests pass unchanged - [x] Build succeeds with 0 errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Precompiled header (PCH) caching to speed compilations and reduce edit latency * Automatic attachment of cached PCH to compile requests, improving hover and completion responsiveness * Module-aware completions expanded to include available module artifacts from other files * **Bug Fixes** * PCH cache cleared on file close; saving now triggers broader PCH invalidation to prevent stale PCH use * **Tests** * Added unit and integration tests exercising PCH build, reuse, and editor interactions <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
105 lines
4.1 KiB
Python
105 lines
4.1 KiB
Python
"""Integration tests for PCH (precompiled header) functionality in MasterServer."""
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
from lsprotocol.types import (
|
|
CompletionParams,
|
|
DidChangeTextDocumentParams,
|
|
DidCloseTextDocumentParams,
|
|
HoverParams,
|
|
Position,
|
|
TextDocumentContentChangeWholeDocument,
|
|
TextDocumentIdentifier,
|
|
VersionedTextDocumentIdentifier,
|
|
)
|
|
|
|
|
|
def _doc(uri: str) -> TextDocumentIdentifier:
|
|
return TextDocumentIdentifier(uri=uri)
|
|
|
|
|
|
@pytest.mark.workspace("pch_test")
|
|
async def test_pch_diagnostics_on_open(client, workspace):
|
|
"""Opening a file with #include should trigger PCH build and return clean diagnostics."""
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
assert uri in client.diagnostics
|
|
# main.cpp is well-formed, so diagnostics list should be empty (no errors).
|
|
diags = client.diagnostics[uri]
|
|
assert len(diags) == 0, f"Expected no diagnostics, got: {diags}"
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("pch_test")
|
|
async def test_pch_body_edit_triggers_recompile(client, workspace):
|
|
"""Editing only the body (not the preamble) should trigger recompilation."""
|
|
uri, content = await client.open_and_wait(workspace / "main.cpp")
|
|
|
|
# Edit only the function body — preamble (#include "common.h") unchanged.
|
|
new_content = content.replace("return result;", "return result + 1;")
|
|
event = client.wait_for_diagnostics(uri)
|
|
client.text_document_did_change(
|
|
DidChangeTextDocumentParams(
|
|
text_document=VersionedTextDocumentIdentifier(uri=uri, version=1),
|
|
content_changes=[TextDocumentContentChangeWholeDocument(text=new_content)],
|
|
)
|
|
)
|
|
# The key assertion: recompilation completes (diagnostics event fires).
|
|
await asyncio.wait_for(event.wait(), timeout=30.0)
|
|
assert uri in client.diagnostics
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("pch_test")
|
|
async def test_no_pch_for_no_includes(client, workspace):
|
|
"""A file with no #include directives should compile without PCH."""
|
|
uri, _ = await client.open_and_wait(workspace / "no_includes.cpp")
|
|
assert uri in client.diagnostics
|
|
diags = client.diagnostics[uri]
|
|
assert len(diags) == 0, f"Expected no diagnostics, got: {diags}"
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("pch_test")
|
|
async def test_hover_on_local_symbol(client, workspace):
|
|
"""Hover on a locally defined symbol should work when PCH is active."""
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
|
|
# Hover over "add" on line 2 (0-indexed): "int add(int a, int b) {"
|
|
result = await client.text_document_hover_async(
|
|
HoverParams(text_document=_doc(uri), position=Position(line=2, character=4))
|
|
)
|
|
assert result is not None
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("pch_test")
|
|
async def test_completion_with_pch(client, workspace):
|
|
"""Completion should see symbols from PCH headers."""
|
|
uri, content = await client.open_and_wait(workspace / "main.cpp")
|
|
|
|
# Add a line that starts typing "Poi" to trigger completion for Point.
|
|
new_content = content + "\nPoi"
|
|
lines = new_content.split("\n")
|
|
last_line = len(lines) - 1
|
|
|
|
event = client.wait_for_diagnostics(uri)
|
|
client.text_document_did_change(
|
|
DidChangeTextDocumentParams(
|
|
text_document=VersionedTextDocumentIdentifier(uri=uri, version=1),
|
|
content_changes=[TextDocumentContentChangeWholeDocument(text=new_content)],
|
|
)
|
|
)
|
|
# Brief wait for the change to be processed.
|
|
await asyncio.sleep(1.0)
|
|
|
|
result = await client.text_document_completion_async(
|
|
CompletionParams(
|
|
text_document=_doc(uri),
|
|
position=Position(line=last_line, character=3),
|
|
)
|
|
)
|
|
# Completion should return results.
|
|
assert result is not None
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|