Skip to content

Commit

Permalink
Reorganize and refactor server tests
Browse files Browse the repository at this point in the history
  • Loading branch information
aazuspan committed Aug 15, 2024
1 parent 116efb8 commit dd00366
Show file tree
Hide file tree
Showing 12 changed files with 772 additions and 715 deletions.
449 changes: 19 additions & 430 deletions tests/conftest.py

Large diffs are not rendered by default.

Empty file added tests/server_tests/__init__.py
Empty file.
78 changes: 78 additions & 0 deletions tests/server_tests/test_completion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from __future__ import annotations

import lsprotocol.types as lsp
import pytest
from pytest_lsp import LanguageClient

from ..conftest import PATCH_DIR, TestCase


class CompletionTestCase(TestCase):
"""A dictionary to track an expected completion result."""

label: str
detail: str
kind: lsp.CompletionItemKind
doc_contains: str | None
uri: str


COMPLETIONS: list[CompletionTestCase] = [
{
"name": "APOUT",
"label": "APOUT",
"detail": "(variable) APOUT: Literal[33]",
"kind": lsp.CompletionItemKind.Variable,
"doc_contains": None,
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
"name": "REG0",
"label": "REG0",
"detail": "(constant) REG0: Literal[32]",
"kind": lsp.CompletionItemKind.Constant,
"doc_contains": None,
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
"name": "CHO RDA",
"label": "CHO RDA",
"detail": "(opcode)",
"kind": lsp.CompletionItemKind.Function,
"doc_contains": "`CHO RDA, N, C, D`",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
"name": "EQU",
"label": "EQU",
"detail": "(opcode)",
"kind": lsp.CompletionItemKind.Function,
"doc_contains": "**`EQU`** allows one to define symbolic operands",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
]


@pytest.mark.parametrize("test_case", COMPLETIONS, ids=lambda x: x["name"])
@pytest.mark.asyncio()
async def test_completions(test_case: CompletionTestCase, client: LanguageClient):
"""Test that expected completions are shown with details and documentation."""
results = await client.text_document_completion_async(
params=lsp.CompletionParams(
position=lsp.Position(line=0, character=0),
text_document=lsp.TextDocumentIdentifier(uri=test_case["uri"]),
)
)
assert results is not None, "Expected completions"

matches = [item for item in results.items if item.label == test_case["label"]]

assert (
len(matches) == 1
), f"Expected 1 matching label `{test_case['label']}, got {len(matches)}."
match = matches[0]

assert match.detail == test_case["detail"]
assert match.kind == test_case["kind"]
if test_case["doc_contains"] is not None:
assert test_case["doc_contains"] in str(match.documentation)
86 changes: 86 additions & 0 deletions tests/server_tests/test_definition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from __future__ import annotations

import lsprotocol.types as lsp
import pytest
from pytest_lsp import LanguageClient

from ..conftest import PATCH_DIR, TestCase


class DefinitionTestCase(TestCase):
"""A dictionary track where a symbol is referenced and defined."""

referenced: lsp.Position
defined: lsp.Location
uri: str


DEFINITIONS: list[DefinitionTestCase] = [
{
# Variable
"name": "apout",
"referenced": lsp.Position(line=57, character=7),
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
"defined": lsp.Location(
uri=f"file:///{PATCH_DIR / 'Basic.spn'}",
range=lsp.Range(
start=lsp.Position(line=23, character=4),
end=lsp.Position(line=23, character=9),
),
),
},
{
# Memory
"name": "lap2a",
"referenced": lsp.Position(line=72, character=7),
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
"defined": lsp.Location(
uri=f"file:///{PATCH_DIR / 'Basic.spn'}",
range=lsp.Range(
start=lsp.Position(line=16, character=4),
end=lsp.Position(line=16, character=9),
),
),
},
{
# Memory. Note that this has an address modifier, but still points to the
# original definition.
"name": "lap2a#",
"referenced": lsp.Position(line=71, character=7),
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
"defined": lsp.Location(
uri=f"file:///{PATCH_DIR / 'Basic.spn'}",
range=lsp.Range(
start=lsp.Position(line=16, character=4),
end=lsp.Position(line=16, character=9),
),
),
},
{
# Label
"name": "endclr",
"referenced": lsp.Position(line=37, character=9),
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
"defined": lsp.Location(
uri=f"file:///{PATCH_DIR / 'Basic.spn'}",
range=lsp.Range(
start=lsp.Position(line=41, character=0),
end=lsp.Position(line=41, character=6),
),
),
},
]


@pytest.mark.asyncio()
@pytest.mark.parametrize("test_case", DEFINITIONS, ids=lambda x: x["name"])
async def test_definition(test_case: DefinitionTestCase, client: LanguageClient):
"""Test that the definition location of different assignments is correct."""
result = await client.text_document_definition_async(
params=lsp.DefinitionParams(
position=test_case["referenced"],
text_document=lsp.TextDocumentIdentifier(uri=test_case["uri"]),
)
)

assert result == test_case["defined"]
70 changes: 70 additions & 0 deletions tests/server_tests/test_diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import lsprotocol.types as lsp
import pytest
from pytest_lsp import LanguageClient


@pytest.mark.asyncio()
async def test_diagnostic_parsing_errors(client: LanguageClient):
"""Test that parsing errors and warnings are correctly reported by the server."""
source_with_errors = """
; Undefined symbol a
SOF 0,a
; Label REG0 re-defined
REG0 EQU 4
; Register out of range
MULX 100
"""

# We need a URI to associate with the source, but it doesn't need to be a real file.
test_uri = "dummy_uri"
client.text_document_did_open(
lsp.DidOpenTextDocumentParams(
text_document=lsp.TextDocumentItem(
uri=test_uri,
language_id="spinasm",
version=1,
text=source_with_errors,
)
)
)

await client.wait_for_notification(lsp.TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS)

expected = [
lsp.Diagnostic(
range=lsp.Range(
start=lsp.Position(line=2, character=6),
end=lsp.Position(line=2, character=6),
),
message="Undefined label a",
severity=lsp.DiagnosticSeverity.Error,
source="SPINAsm",
),
lsp.Diagnostic(
range=lsp.Range(
start=lsp.Position(line=5, character=9),
end=lsp.Position(line=5, character=9),
),
message="Label REG0 re-defined",
severity=lsp.DiagnosticSeverity.Warning,
source="SPINAsm",
),
lsp.Diagnostic(
range=lsp.Range(
start=lsp.Position(line=8, character=0),
end=lsp.Position(line=8, character=0),
),
message="Register 0x64 out of range for MULX",
severity=lsp.DiagnosticSeverity.Error,
source="SPINAsm",
),
]

returned = client.diagnostics[test_uri]
extra = len(returned) - len(expected)
assert extra == 0, f"Expected {len(expected)} diagnostics, got {len(returned)}."

for i, diag in enumerate(expected):
assert diag == returned[i], f"Diagnostic {i} does not match expected"
93 changes: 93 additions & 0 deletions tests/server_tests/test_hover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from __future__ import annotations

import lsprotocol.types as lsp
import pytest
from pytest_lsp import LanguageClient

from ..conftest import PATCH_DIR, TestCase


class HoverTestCase(TestCase):
"""A dictionary to record hover information for a symbol."""

position: lsp.Position
contains: str | None
uri: str


HOVERS: list[HoverTestCase] = [
{
"name": "mem",
"position": lsp.Position(line=8, character=0),
"contains": "`MEM`",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
"name": "skp",
"position": lsp.Position(line=37, character=2),
"contains": "`SKP CMASK, N`",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
"name": "endclr",
"position": lsp.Position(line=37, character=13),
"contains": "(label) ENDCLR: Offset[4]",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
"name": "mono",
"position": lsp.Position(line=47, character=5),
"contains": "(variable) MONO: Literal[32]",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
"name": "reg0",
"position": lsp.Position(line=22, character=9),
"contains": "(constant) REG0: Literal[32]",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
"name": "lap2b#",
"position": lsp.Position(line=73, character=4),
"contains": "(variable) LAP2B#: Literal[9802]",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
# CHO RDA, hovering over CHO
"name": "CHO_rda",
"position": lsp.Position(line=85, character=0),
"contains": "`CHO RDA, N, C, D`",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
# CHO RDA, hovering over RDA
"name": "cho_RDA",
"position": lsp.Position(line=85, character=4),
"contains": "`CHO RDA, N, C, D`",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
# Hovering over an int, which should return no hover info
"name": "None",
"position": lsp.Position(line=8, character=8),
"contains": None,
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
]


@pytest.mark.parametrize("test_case", HOVERS, ids=lambda x: x["name"])
@pytest.mark.asyncio()
async def test_hover(test_case: dict, client: LanguageClient):
result = await client.text_document_hover_async(
params=lsp.CompletionParams(
position=test_case["position"],
text_document=lsp.TextDocumentIdentifier(uri=test_case["uri"]),
)
)

if test_case["contains"] is None:
assert result is None, "Expected no hover result"
else:
msg = f"Hover does not contain `{test_case['contains']}`"
assert test_case["contains"] in result.contents.value, msg
68 changes: 68 additions & 0 deletions tests/server_tests/test_prepare_rename.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from __future__ import annotations

import lsprotocol.types as lsp
import pytest
from pytest_lsp import LanguageClient

from ..conftest import PATCH_DIR, TestCase


class PrepareRenameTestCase(TestCase):
"""A dictionary to record prepare rename results for a symbol."""

position: lsp.Position
result: bool
message: str | None
uri: str


PREPARE_RENAMES: list[PrepareRenameTestCase] = [
{
"name": "mem",
"position": lsp.Position(line=8, character=0),
"result": None,
"message": "Can't rename non-user defined token MEM.",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
"name": "reg0",
"position": lsp.Position(line=22, character=10),
"result": None,
"message": "Can't rename non-user defined token REG0.",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
"name": "ap1",
"position": lsp.Position(line=8, character=4),
"result": lsp.PrepareRenameResult_Type2(default_behavior=True),
"message": None,
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
{
"name": "endclr",
"position": lsp.Position(line=37, character=10),
"result": lsp.PrepareRenameResult_Type2(default_behavior=True),
"message": None,
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
]


@pytest.mark.parametrize("test_case", PREPARE_RENAMES, ids=lambda x: x["name"])
@pytest.mark.asyncio()
async def test_prepare_rename(test_case: PrepareRenameTestCase, client: LanguageClient):
"""Test that prepare rename prevents renaming non-user defined tokens."""
result = await client.text_document_prepare_rename_async(
params=lsp.PrepareRenameParams(
position=test_case["position"],
text_document=lsp.TextDocumentIdentifier(uri=test_case["uri"]),
)
)

assert result == test_case["result"]

if test_case["message"]:
assert test_case["message"] in client.log_messages[0].message
assert client.log_messages[0].type == lsp.MessageType.Info
else:
assert not client.log_messages
Loading

0 comments on commit dd00366

Please sign in to comment.