Skip to content

Commit

Permalink
Refactor tests to use dataclass for TestCase
Browse files Browse the repository at this point in the history
  • Loading branch information
aazuspan committed Aug 20, 2024
1 parent c67ed82 commit c89b292
Show file tree
Hide file tree
Showing 11 changed files with 417 additions and 376 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ A Language Server Protocol (LSP) server to provide language support for the [SPI

- **Diagnostics**: Reports the location of syntax errors and warnings.
- **Signature help**: Shows parameter hints as instructions are entered.
- **Hover**: Shows documentation and values on hover.
- **Hover**: Shows documentation and assigned values on hover.
- **Completion**: Provides suggestions for opcodes, labels, and variables.
- **Renaming**: Allows renaming labels and variables.
- **Renaming**: Renames matching labels or variables.
- **Go to definition**: Jumps to the definition of a label, memory address, or variable.
- **Semantic highlighting**: Color codes variables, constants, instructions, etc. based on program semantics.

------

Expand Down
11 changes: 9 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import TypedDict

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

Expand All @@ -28,10 +29,16 @@ async def client(request, lsp_client: LanguageClient):
await lsp_client.shutdown_session()


class TestCase(TypedDict):
@dataclass
class TestCase:
"""The inputs and outputs of a test case."""

__test__ = False

name: str
"""The name used to identify the test case."""


def parametrize_cases(test_cases: list[TestCase]):
"""A decorator to parametrize a test function with test cases."""
return pytest.mark.parametrize("test_case", test_cases, ids=lambda x: x.name)
87 changes: 45 additions & 42 deletions tests/server_tests/test_completion.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from __future__ import annotations

from dataclasses import dataclass

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

from ..conftest import PATCH_DIR, TestCase
from ..conftest import PATCH_DIR, TestCase, parametrize_cases


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

Expand All @@ -17,62 +20,62 @@ class CompletionTestCase(TestCase):
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": "(assembler)",
"kind": lsp.CompletionItemKind.Operator,
"doc_contains": "**`EQU`** allows one to define symbolic operands",
"uri": f"file:///{PATCH_DIR / 'Basic.spn'}",
},
TEST_CASES: list[CompletionTestCase] = [
CompletionTestCase(
name="variable",
label="APOUT",
detail="(variable) APOUT: Literal[33]",
kind=lsp.CompletionItemKind.Variable,
doc_contains=None,
uri=f"file:///{PATCH_DIR / 'Basic.spn'}",
),
CompletionTestCase(
name="constant",
label="REG0",
detail="(constant) REG0: Literal[32]",
kind=lsp.CompletionItemKind.Constant,
doc_contains=None,
uri=f"file:///{PATCH_DIR / 'Basic.spn'}",
),
CompletionTestCase(
name="multi-word opcode",
label="CHO RDA",
detail="(opcode)",
kind=lsp.CompletionItemKind.Function,
doc_contains="`CHO RDA, N, C, D`",
uri=f"file:///{PATCH_DIR / 'Basic.spn'}",
),
CompletionTestCase(
name="assembler",
label="EQU",
detail="(assembler)",
kind=lsp.CompletionItemKind.Operator,
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"])
@parametrize_cases(TEST_CASES)
@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"]),
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"]]
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)}."
), 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)
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)
68 changes: 33 additions & 35 deletions tests/server_tests/test_definition.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from __future__ import annotations

from dataclasses import dataclass

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

from ..conftest import PATCH_DIR, TestCase
from ..conftest import PATCH_DIR, TestCase, parametrize_cases


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

Expand All @@ -15,72 +18,67 @@ class DefinitionTestCase(TestCase):
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(
TEST_CASES: list[DefinitionTestCase] = [
DefinitionTestCase(
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(
),
DefinitionTestCase(
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(
),
DefinitionTestCase(
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(
),
DefinitionTestCase(
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"])
@parametrize_cases(TEST_CASES)
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"]),
position=test_case.referenced,
text_document=lsp.TextDocumentIdentifier(uri=test_case.uri),
)
)

assert result == test_case["defined"]
assert result == test_case.defined
Loading

0 comments on commit c89b292

Please sign in to comment.