## Summary - Extract shared test utilities into `tests/integration/utils/` (client, workspace, assertions, wait, cache) - Migrate 12 test files into categorized subdirectories: `lifecycle/`, `compilation/`, `features/`, `modules/`, `extensions/`, `stress/` - Merge `test_include_completion.py` + `test_import_completion.py` → `features/test_completion.py` - Remove stale directory-tree comments and section divider comments ## Test plan - [x] `pytest --collect-only` collects all 113 tests - [x] All test module imports verified - [x] `pixi run format` applied 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Reorganized integration suites: added new feature tests (completion, server) and removed older duplicated modules. * Centralized shared test utilities and assertion helpers for diagnostics, workspace operations, waiting/synchronization, and cache inspection. * **Chores / Refactor** * Standardized test client lifecycle and helper usage across suites for more reliable test flows. * Improved server session lifecycle handling for more predictable document/session resets during tests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
192 lines
6.9 KiB
Python
192 lines
6.9 KiB
Python
"""Integration tests for #include completion and import completion in clice."""
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
from lsprotocol.types import (
|
|
HoverParams,
|
|
Position,
|
|
TextDocumentIdentifier,
|
|
)
|
|
|
|
from tests.integration.utils import doc
|
|
from tests.integration.utils.workspace import did_change
|
|
|
|
|
|
@pytest.mark.workspace("include_completion")
|
|
async def test_include_completion_quoted(client, workspace):
|
|
"""Completion after #include " should list local headers."""
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
|
|
# Update content to trigger include completion for "my" prefix.
|
|
did_change(client, uri, 1, '#include "my')
|
|
|
|
result = await client.completion_at(uri, 0, 12) # After "my"
|
|
|
|
assert result is not None
|
|
items = result.items if hasattr(result, "items") else result
|
|
labels = [item.label for item in items]
|
|
assert "myheader.h" in labels
|
|
|
|
client.close(uri)
|
|
|
|
|
|
@pytest.mark.workspace("include_completion")
|
|
async def test_include_completion_subdirectory(client, workspace):
|
|
"""Completion for #include "subdir/ should list files in subdir."""
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
|
|
did_change(client, uri, 1, '#include "subdir/')
|
|
|
|
result = await client.completion_at(uri, 0, 17) # After "subdir/"
|
|
|
|
assert result is not None
|
|
items = result.items if hasattr(result, "items") else result
|
|
labels = [item.label for item in items]
|
|
assert "nested.h" in labels
|
|
|
|
client.close(uri)
|
|
|
|
|
|
@pytest.mark.workspace("include_completion")
|
|
async def test_include_completion_angle_bracket(client, workspace):
|
|
"""Completion after #include < should list system headers."""
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
|
|
did_change(client, uri, 1, "#include <cstd")
|
|
|
|
result = await client.completion_at(uri, 0, 14) # After "cstd"
|
|
|
|
assert result is not None
|
|
items = result.items if hasattr(result, "items") else result
|
|
labels = [item.label for item in items]
|
|
# Should contain at least some standard library headers starting with "cstd".
|
|
cstd_labels = [name for name in labels if name.startswith("cstd")]
|
|
assert len(cstd_labels) > 0, f"Expected cstd* headers, got: {labels}"
|
|
|
|
client.close(uri)
|
|
|
|
|
|
@pytest.mark.workspace("include_completion")
|
|
async def test_no_include_completion_on_regular_code(client, workspace):
|
|
"""Regular code should NOT trigger include completion (goes to worker)."""
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
|
|
did_change(client, uri, 1, "int x = ")
|
|
|
|
result = await client.completion_at(uri, 0, 8)
|
|
|
|
# Should return results from clang (keywords, etc.), not include paths.
|
|
# Verify none of the results look like header filenames.
|
|
assert result is not None
|
|
items = result.items if hasattr(result, "items") else result
|
|
labels = [item.label for item in items]
|
|
assert "myheader.h" not in labels
|
|
assert "nested.h" not in labels
|
|
|
|
client.close(uri)
|
|
|
|
|
|
@pytest.mark.workspace("include_completion")
|
|
async def test_include_completion_empty_prefix(client, workspace):
|
|
"""Completion after #include " with no prefix should list all local headers."""
|
|
uri, _ = await client.open_and_wait(workspace / "main.cpp")
|
|
|
|
did_change(client, uri, 1, '#include "')
|
|
|
|
result = await client.completion_at(uri, 0, 10) # Right after the quote
|
|
|
|
assert result is not None
|
|
items = result.items if hasattr(result, "items") else result
|
|
labels = [item.label for item in items]
|
|
# With empty prefix, should list available headers including myheader.h
|
|
# and the subdir/ directory entry.
|
|
assert "myheader.h" in labels
|
|
|
|
client.close(uri)
|
|
|
|
|
|
@pytest.mark.workspace("modules/chained_modules")
|
|
async def test_import_completion_basic(client, workspace):
|
|
"""Import completion should list known modules."""
|
|
# First open mod_a to ensure it's scanned and module A is registered.
|
|
await client.open_and_wait(workspace / "mod_a.cppm")
|
|
|
|
# Open mod_b and change its content to an incomplete import line.
|
|
uri_b, _ = client.open(workspace / "mod_b.cppm")
|
|
did_change(client, uri_b, 1, "import ")
|
|
|
|
result = await client.completion_at(uri_b, 0, 7)
|
|
|
|
assert result is not None
|
|
items = result.items if hasattr(result, "items") else result
|
|
labels = [item.label for item in items]
|
|
assert "A" in labels, f"Expected 'A' in completion labels, got: {labels}"
|
|
|
|
|
|
@pytest.mark.workspace("modules/chained_modules")
|
|
async def test_import_completion_with_prefix(client, workspace):
|
|
"""Import completion with prefix should filter to matching modules."""
|
|
# Open mod_a to register module A.
|
|
await client.open_and_wait(workspace / "mod_a.cppm")
|
|
|
|
# Open mod_b and type 'import A' (with prefix).
|
|
uri_b, _ = client.open(workspace / "mod_b.cppm")
|
|
did_change(client, uri_b, 1, "import A")
|
|
|
|
result = await client.completion_at(uri_b, 0, 8)
|
|
|
|
assert result is not None
|
|
items = result.items if hasattr(result, "items") else result
|
|
labels = [item.label for item in items]
|
|
assert "A" in labels, f"Expected 'A' in completion labels, got: {labels}"
|
|
|
|
|
|
@pytest.mark.workspace("modules/dotted_module_name")
|
|
async def test_import_completion_dotted_names(client, workspace):
|
|
"""Import completion should return dotted module names like my.app and my.io."""
|
|
# Open both module files to register them.
|
|
await client.open_and_wait(workspace / "io.cppm")
|
|
await client.open_and_wait(workspace / "app.cppm")
|
|
|
|
# Change app.cppm to an incomplete import with dotted prefix.
|
|
uri_app, _ = client.open(workspace / "app.cppm")
|
|
did_change(client, uri_app, 1, "import my.")
|
|
|
|
result = await client.completion_at(uri_app, 0, 10)
|
|
|
|
assert result is not None
|
|
items = result.items if hasattr(result, "items") else result
|
|
labels = [item.label for item in items]
|
|
assert "my.app" in labels or "my.io" in labels, (
|
|
f"Expected dotted module names in completion labels, got: {labels}"
|
|
)
|
|
|
|
|
|
@pytest.mark.workspace("modules/consumer_imports_module")
|
|
async def test_buffer_aware_module_deps(client, workspace):
|
|
"""Adding import in buffer (unsaved) should still build the needed PCM."""
|
|
# Open the module file first so it gets scanned.
|
|
await client.open_and_wait(workspace / "math.cppm")
|
|
|
|
# Open main.cpp with new content that imports Math (simulating unsaved edit).
|
|
uri, _ = client.open(workspace / "main.cpp")
|
|
did_change(client, uri, 1, "import Math;\nint x = add(1, 2);\n")
|
|
|
|
# Trigger compilation via hover (pull-based model).
|
|
event = client.wait_for_diagnostics(uri)
|
|
await client.text_document_hover_async(
|
|
HoverParams(
|
|
text_document=TextDocumentIdentifier(uri=uri),
|
|
position=Position(line=0, character=0),
|
|
)
|
|
)
|
|
|
|
# Wait for diagnostics.
|
|
await asyncio.wait_for(event.wait(), timeout=60.0)
|
|
|
|
diags = client.diagnostics.get(uri, [])
|
|
# Should have no errors if Math PCM was built successfully from buffer scan.
|
|
errors = [d for d in diags if d.severity == 1]
|
|
assert len(errors) == 0, f"Expected no errors, got: {errors}"
|