## Summary - **CMake-based CDB generation for module tests**: Replace hand-written compile_commands.json with CMakeLists.txt (CMake 3.28 `FILE_SET CXX_MODULES`) in all 26 `tests/data/modules/*/` directories. CDB is generated on-the-fly via `cmake -G Ninja` during test setup. - **`@pytest.mark.workspace()` decorator**: Introduce a marker + fixture pattern so tests declare their workspace via decorator and receive a resolved `workspace` path. The fixture auto-generates CDB when a CMakeLists.txt is present. - **`CliceClient` helper methods**: Add `initialize()`, `open()`, `wait_diagnostics()`, and `open_and_wait()` to reduce boilerplate across all test files. - **Use `asyncio_mode = "auto"`**: Switch from `@pytest_asyncio.fixture` + `@pytest.mark.asyncio` to `@pytest.fixture` + auto mode for proper Pylance type inference on fixtures. - **Test cleanup**: Remove redundant section separators and docstrings, delete `tests/pyproject.toml` (config moved to `pytest.ini`). - **Format task**: Add `.cppm` to `format-cpp` glob pattern. - **CI fix**: Disable `CMAKE_CXX_SCAN_FOR_MODULES` and prefer pixi clang++ to fix macOS CI where CMake rejects module scanning. ## Test plan - [x] All 26 module test directories have CMakeLists.txt with FILE_SET CXX_MODULES - [x] generate_cdb() produces valid compile_commands.json with module flags - [x] Integration tests pass locally - [ ] CI passes on all platforms (Linux, macOS, Windows) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Unified fixtures and client workflow: new init/open/wait helpers, workspace marker support, bounded diagnostics waiting, CMake-based compilation-database generation, and directory-backed temp-file workflows; enabled asyncio test mode. * **Chores** * Added many C++20 module test projects and test data; removed prior test pyproject in favor of pytest config; updated formatter to include .cppm files. * **Style** * Reformatted many module/source implementations to consistent multi-line function bodies. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
311 lines
11 KiB
Python
311 lines
11 KiB
Python
"""Integration tests for the clice MasterServer using pygls."""
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
from lsprotocol.types import (
|
|
CodeActionContext,
|
|
CodeActionParams,
|
|
CompletionParams,
|
|
DefinitionParams,
|
|
DidCloseTextDocumentParams,
|
|
DidChangeTextDocumentParams,
|
|
DidSaveTextDocumentParams,
|
|
DocumentLinkParams,
|
|
DocumentSymbolParams,
|
|
FoldingRangeParams,
|
|
HoverParams,
|
|
InlayHintParams,
|
|
Position,
|
|
Range,
|
|
SemanticTokensParams,
|
|
SignatureHelpParams,
|
|
TextDocumentContentChangeWholeDocument,
|
|
TextDocumentIdentifier,
|
|
VersionedTextDocumentIdentifier,
|
|
)
|
|
|
|
|
|
def _doc(uri: str) -> TextDocumentIdentifier:
|
|
return TextDocumentIdentifier(uri=uri)
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_server_info(client, workspace):
|
|
assert client.init_result.server_info.name == "clice"
|
|
assert client.init_result.server_info.version == "0.1.0"
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_capabilities(client, workspace):
|
|
caps = client.init_result.capabilities
|
|
assert caps.hover_provider is True
|
|
assert caps.completion_provider is not None
|
|
assert caps.definition_provider is True
|
|
assert caps.document_symbol_provider is True
|
|
assert caps.folding_range_provider is True
|
|
assert caps.inlay_hint_provider is True
|
|
assert caps.code_action_provider is True
|
|
assert caps.semantic_tokens_provider is not None
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_did_open_close_cycle(client, workspace):
|
|
uri, _ = client.open(workspace / "main.cpp")
|
|
await asyncio.sleep(0.5)
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_shutdown_exit(client, workspace):
|
|
await client.shutdown_async(None)
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_feature_requests_after_close(client, workspace):
|
|
uri, _ = client.open(workspace / "main.cpp")
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
result = await client.text_document_hover_async(
|
|
HoverParams(text_document=_doc(uri), position=Position(line=0, character=0))
|
|
)
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_incremental_change(client, workspace):
|
|
uri, content = client.open(workspace / "main.cpp")
|
|
for i in range(5):
|
|
content += f"\n// change {i}"
|
|
client.text_document_did_change(
|
|
DidChangeTextDocumentParams(
|
|
text_document=VersionedTextDocumentIdentifier(uri=uri, version=i + 1),
|
|
content_changes=[TextDocumentContentChangeWholeDocument(text=content)],
|
|
)
|
|
)
|
|
await asyncio.sleep(0.05)
|
|
await asyncio.sleep(1)
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_diagnostics_received(client, workspace):
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
assert uri in client.diagnostics
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_hover_before_compile(client, workspace):
|
|
uri, _ = client.open(workspace / "main.cpp")
|
|
result = await client.text_document_hover_async(
|
|
HoverParams(text_document=_doc(uri), position=Position(line=0, character=0))
|
|
)
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_completion_request(client, workspace):
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
result = await client.text_document_completion_async(
|
|
CompletionParams(
|
|
text_document=_doc(uri), position=Position(line=0, character=0)
|
|
)
|
|
)
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_signature_help_request(client, workspace):
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
result = await client.text_document_signature_help_async(
|
|
SignatureHelpParams(
|
|
text_document=_doc(uri), position=Position(line=0, character=0)
|
|
)
|
|
)
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_definition_request(client, workspace):
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
result = await client.text_document_definition_async(
|
|
DefinitionParams(
|
|
text_document=_doc(uri), position=Position(line=0, character=4)
|
|
)
|
|
)
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_document_symbol_request(client, workspace):
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
result = await client.text_document_document_symbol_async(
|
|
DocumentSymbolParams(text_document=_doc(uri))
|
|
)
|
|
assert result is not None
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_folding_range_request(client, workspace):
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
result = await client.text_document_folding_range_async(
|
|
FoldingRangeParams(text_document=_doc(uri))
|
|
)
|
|
assert result is not None
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_semantic_tokens_request(client, workspace):
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
result = await client.text_document_semantic_tokens_full_async(
|
|
SemanticTokensParams(text_document=_doc(uri))
|
|
)
|
|
assert result is not None
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_inlay_hint_request(client, workspace):
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
result = await client.text_document_inlay_hint_async(
|
|
InlayHintParams(
|
|
text_document=_doc(uri),
|
|
range=Range(
|
|
start=Position(line=0, character=0), end=Position(line=10, character=0)
|
|
),
|
|
)
|
|
)
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_code_action_request(client, workspace):
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
result = await client.text_document_code_action_async(
|
|
CodeActionParams(
|
|
text_document=_doc(uri),
|
|
range=Range(
|
|
start=Position(line=0, character=0), end=Position(line=0, character=10)
|
|
),
|
|
context=CodeActionContext(diagnostics=[]),
|
|
)
|
|
)
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_document_link_request(client, workspace):
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
result = await client.text_document_document_link_async(
|
|
DocumentLinkParams(text_document=_doc(uri))
|
|
)
|
|
assert result is not None
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_rapid_changes_stress(client, workspace):
|
|
uri, content = client.open(workspace / "main.cpp")
|
|
for i in range(20):
|
|
content += f"\n// stress change {i}\n"
|
|
client.text_document_did_change(
|
|
DidChangeTextDocumentParams(
|
|
text_document=VersionedTextDocumentIdentifier(uri=uri, version=i + 1),
|
|
content_changes=[TextDocumentContentChangeWholeDocument(text=content)],
|
|
)
|
|
)
|
|
await asyncio.sleep(2)
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_save_notification(client, workspace):
|
|
uri, _ = client.open(workspace / "main.cpp")
|
|
await asyncio.sleep(0.5)
|
|
client.text_document_did_save(DidSaveTextDocumentParams(text_document=_doc(uri)))
|
|
await asyncio.sleep(0.5)
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_hover_on_unknown_file(client, workspace):
|
|
result = await client.text_document_hover_async(
|
|
HoverParams(
|
|
text_document=_doc("file:///nonexistent/fake.cpp"),
|
|
position=Position(line=0, character=0),
|
|
)
|
|
)
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.workspace("hello_world")
|
|
async def test_all_features_after_compile_wait(client, workspace):
|
|
"""Exercise all feature requests after compilation completes."""
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
|
|
hover = await client.text_document_hover_async(
|
|
HoverParams(text_document=_doc(uri), position=Position(line=0, character=4))
|
|
)
|
|
assert hover is not None
|
|
|
|
completion = await client.text_document_completion_async(
|
|
CompletionParams(
|
|
text_document=_doc(uri), position=Position(line=5, character=18)
|
|
)
|
|
)
|
|
|
|
await client.text_document_signature_help_async(
|
|
SignatureHelpParams(
|
|
text_document=_doc(uri), position=Position(line=0, character=0)
|
|
)
|
|
)
|
|
|
|
await client.text_document_definition_async(
|
|
DefinitionParams(
|
|
text_document=_doc(uri), position=Position(line=0, character=4)
|
|
)
|
|
)
|
|
|
|
symbols = await client.text_document_document_symbol_async(
|
|
DocumentSymbolParams(text_document=_doc(uri))
|
|
)
|
|
assert symbols is not None
|
|
|
|
folding = await client.text_document_folding_range_async(
|
|
FoldingRangeParams(text_document=_doc(uri))
|
|
)
|
|
assert folding is not None
|
|
|
|
tokens = await client.text_document_semantic_tokens_full_async(
|
|
SemanticTokensParams(text_document=_doc(uri))
|
|
)
|
|
assert tokens is not None
|
|
|
|
links = await client.text_document_document_link_async(
|
|
DocumentLinkParams(text_document=_doc(uri))
|
|
)
|
|
assert links is not None
|
|
|
|
await client.text_document_code_action_async(
|
|
CodeActionParams(
|
|
text_document=_doc(uri),
|
|
range=Range(
|
|
start=Position(line=0, character=0), end=Position(line=0, character=10)
|
|
),
|
|
context=CodeActionContext(diagnostics=[]),
|
|
)
|
|
)
|
|
|
|
await client.text_document_inlay_hint_async(
|
|
InlayHintParams(
|
|
text_document=_doc(uri),
|
|
range=Range(
|
|
start=Position(line=0, character=0), end=Position(line=10, character=0)
|
|
),
|
|
)
|
|
)
|
|
|
|
client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
|