c6af9e284a
Tests / catch-all (windows-latest) (push) Has been cancelled
Tests / jvm (macos-latest) (push) Has been cancelled
Tests / jvm (ubuntu-latest) (push) Has been cancelled
Tests / jvm (windows-latest) (push) Has been cancelled
Tests / native (macos-latest) (push) Has been cancelled
Tests / native (ubuntu-latest) (push) Has been cancelled
Tests / native (windows-latest) (push) Has been cancelled
Tests / niche (ubuntu-latest) (push) Has been cancelled
Tests / other-langs (macos-latest) (push) Has been cancelled
Tests / other-langs (ubuntu-latest) (push) Has been cancelled
Tests / other-langs (windows-latest) (push) Has been cancelled
Tests / catch-all (macos-latest) (push) Has been cancelled
Tests / catch-all (ubuntu-latest) (push) Has been cancelled
Docs Build / build (push) Has been cancelled
Docs Build / deploy (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Build and Push Docker Images / build-and-push (push) Has been cancelled
116 lines
4.8 KiB
Python
116 lines
4.8 KiB
Python
import pytest
|
|
|
|
from solidlsp import SolidLanguageServer
|
|
from solidlsp.language_servers.solidity_language_server import SolidityLanguageServer
|
|
from solidlsp.ls_config import Language
|
|
from test.solidlsp.util.diagnostics import assert_file_diagnostics
|
|
|
|
|
|
class _NeverSetEvent:
|
|
"""Stand-in for :class:`threading.Event` whose wait never succeeds."""
|
|
|
|
def wait(self, timeout: float | None = None) -> bool:
|
|
return False
|
|
|
|
def clear(self) -> None:
|
|
return None
|
|
|
|
def set(self) -> None:
|
|
return None
|
|
|
|
|
|
class _AlwaysSignalledEvent:
|
|
"""Stand-in for :class:`threading.Event` whose wait always reports a completion; counts clears."""
|
|
|
|
def __init__(self) -> None:
|
|
self.cleared = 0
|
|
|
|
def wait(self, timeout: float | None = None) -> bool:
|
|
return True
|
|
|
|
def clear(self) -> None:
|
|
self.cleared += 1
|
|
|
|
def set(self) -> None:
|
|
return None
|
|
|
|
|
|
@pytest.mark.solidity
|
|
class TestSolidityDiagnostics:
|
|
@pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
|
|
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
|
|
assert_file_diagnostics(
|
|
language_server,
|
|
"contracts/DiagnosticsSample.sol",
|
|
(),
|
|
min_count=1,
|
|
)
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
|
|
def test_file_diagnostics_via_validation_completion(
|
|
self, language_server: SolidLanguageServer, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""Diagnostics must still arrive via the validation-completion fallback when the initial
|
|
publish wait does not observe them (the failure mode of cold/slow CI runners).
|
|
"""
|
|
assert isinstance(language_server, SolidityLanguageServer)
|
|
|
|
# force the fast path to miss so that only the validation-completion fallback can deliver
|
|
monkeypatch.setattr(language_server, "_wait_for_relevant_published_diagnostics", lambda **kwargs: None)
|
|
|
|
assert_file_diagnostics(
|
|
language_server,
|
|
"contracts/DiagnosticsSample.sol",
|
|
(),
|
|
min_count=1,
|
|
)
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
|
|
def test_file_diagnostics_without_validation_signal(
|
|
self, language_server: SolidLanguageServer, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""A server that never signals validation completion must yield an empty result within the
|
|
bound instead of hanging or raising.
|
|
"""
|
|
assert isinstance(language_server, SolidityLanguageServer)
|
|
|
|
# force the fast path to miss and make the completion signal unobtainable
|
|
monkeypatch.setattr(language_server, "_wait_for_relevant_published_diagnostics", lambda **kwargs: None)
|
|
monkeypatch.setattr(language_server, "_VALIDATION_COMPLETION_TIMEOUT", 0.2)
|
|
monkeypatch.setattr(language_server, "_validation_completed", _NeverSetEvent())
|
|
|
|
diagnostics = language_server.request_text_document_diagnostics("contracts/DiagnosticsSample.sol", min_severity=1)
|
|
assert diagnostics == []
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
|
|
def test_file_diagnostics_rearms_after_spurious_completion(
|
|
self, language_server: SolidLanguageServer, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""A completion signal belonging to another document (the payload carries no URI) must
|
|
re-arm the fallback wait instead of ending the request without diagnostics.
|
|
"""
|
|
assert isinstance(language_server, SolidityLanguageServer)
|
|
|
|
expected_diagnostic = {
|
|
"uri": "file:///spurious-completion-test",
|
|
"severity": 1,
|
|
"message": "delivered after re-arm",
|
|
"range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 1}},
|
|
"code": None,
|
|
}
|
|
# first wake is spurious (no publication for the request URI), second delivers
|
|
grace_reads: list[list | None] = [None, [expected_diagnostic]]
|
|
event = _AlwaysSignalledEvent()
|
|
|
|
# force the fast path to miss and script the fallback's wake/read sequence
|
|
monkeypatch.setattr(language_server, "_wait_for_relevant_published_diagnostics", lambda **kwargs: None)
|
|
monkeypatch.setattr(language_server, "_validation_completed", event)
|
|
monkeypatch.setattr(language_server, "_wait_for_published_diagnostics", lambda **kwargs: grace_reads.pop(0))
|
|
|
|
diagnostics = language_server.request_text_document_diagnostics("contracts/DiagnosticsSample.sol", min_severity=1)
|
|
|
|
# one clear at request start plus exactly one re-arm after the spurious wake
|
|
assert event.cleared == 2
|
|
assert not grace_reads # both scripted reads consumed
|
|
assert [diagnostic["message"] for diagnostic in diagnostics] == ["delivered after re-arm"]
|