Skip to content

Converge unit tests for test_language_server and test_notebook_document #418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions test/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from io import StringIO
from unittest.mock import MagicMock

from test.test_utils import ClientServerPair

import pytest
from pylsp_jsonrpc.dispatchers import MethodDispatcher
from pylsp_jsonrpc.endpoint import Endpoint
Expand All @@ -22,6 +24,7 @@
def main():
print sys.stdin.read()
"""
CALL_TIMEOUT_IN_SECONDS = 30


class FakeEditorMethodsMixin:
Expand Down Expand Up @@ -163,3 +166,17 @@ def create_file(name, content):
return workspace

return fn


@pytest.fixture
def client_server_pair():
"""A fixture that sets up a client/server pair and shuts down the server"""
client_server_pair_obj = ClientServerPair()

yield (client_server_pair_obj.client, client_server_pair_obj.server)

shutdown_response = client_server_pair_obj.client._endpoint.request(
"shutdown"
).result(timeout=CALL_TIMEOUT_IN_SECONDS)
assert shutdown_response is None
client_server_pair_obj.client._endpoint.notify("exit")
101 changes: 26 additions & 75 deletions test/test_language_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,96 +3,40 @@

import os
import time
import multiprocessing
import sys
from threading import Thread

from test.test_utils import ClientServerPair

from flaky import flaky
from pylsp_jsonrpc.exceptions import JsonRpcMethodNotFound
import pytest

from pylsp.python_lsp import start_io_lang_server, PythonLSPServer

CALL_TIMEOUT = 10
RUNNING_IN_CI = bool(os.environ.get("CI"))


def start_client(client):
client.start()


class _ClientServer:
"""A class to setup a client/server pair"""

def __init__(self, check_parent_process=False):
# Client to Server pipe
csr, csw = os.pipe()
# Server to client pipe
scr, scw = os.pipe()

if os.name == "nt":
ParallelKind = Thread
else:
if sys.version_info[:2] >= (3, 8):
ParallelKind = multiprocessing.get_context("fork").Process
else:
ParallelKind = multiprocessing.Process

self.process = ParallelKind(
target=start_io_lang_server,
args=(
os.fdopen(csr, "rb"),
os.fdopen(scw, "wb"),
check_parent_process,
PythonLSPServer,
),
)
self.process.start()

self.client = PythonLSPServer(
os.fdopen(scr, "rb"), os.fdopen(csw, "wb"), start_io_lang_server
)
self.client_thread = Thread(target=start_client, args=[self.client])
self.client_thread.daemon = True
self.client_thread.start()


@pytest.fixture
def client_server():
"""A fixture that sets up a client/server pair and shuts down the server
This client/server pair does not support checking parent process aliveness
"""
client_server_pair = _ClientServer()

yield client_server_pair.client

shutdown_response = client_server_pair.client._endpoint.request("shutdown").result(
timeout=CALL_TIMEOUT
)
assert shutdown_response is None
client_server_pair.client._endpoint.notify("exit")
CALL_TIMEOUT_IN_SECONDS = 10


@pytest.fixture
def client_exited_server():
"""A fixture that sets up a client/server pair that support checking parent process aliveness
and assert the server has already exited
"""
client_server_pair = _ClientServer(True)
client_server_pair_obj = ClientServerPair(True, True)

# yield client_server_pair.client
yield client_server_pair
yield client_server_pair_obj

assert client_server_pair.process.is_alive() is False
assert client_server_pair_obj.server_process.is_alive() is False


@flaky(max_runs=10, min_passes=1)
@pytest.mark.skipif(sys.platform == "darwin", reason="Too flaky on Mac")
def test_initialize(client_server): # pylint: disable=redefined-outer-name
response = client_server._endpoint.request(
def test_initialize(client_server_pair):
client, _ = client_server_pair
response = client._endpoint.request(
"initialize",
{"rootPath": os.path.dirname(__file__), "initializationOptions": {}},
).result(timeout=CALL_TIMEOUT)
).result(timeout=CALL_TIMEOUT_IN_SECONDS)
assert "capabilities" in response


Expand All @@ -104,7 +48,10 @@ def test_exit_with_parent_process_died(
client_exited_server,
): # pylint: disable=redefined-outer-name
# language server should have already exited before responding
lsp_server, mock_process = client_exited_server.client, client_exited_server.process
lsp_server, mock_process = (
client_exited_server.client,
client_exited_server.server_process,
)
# with pytest.raises(Exception):
lsp_server._endpoint.request(
"initialize",
Expand All @@ -113,31 +60,35 @@ def test_exit_with_parent_process_died(
"rootPath": os.path.dirname(__file__),
"initializationOptions": {},
},
).result(timeout=CALL_TIMEOUT)
).result(timeout=CALL_TIMEOUT_IN_SECONDS)

mock_process.terminate()
time.sleep(CALL_TIMEOUT)
time.sleep(CALL_TIMEOUT_IN_SECONDS)
assert not client_exited_server.client_thread.is_alive()


@flaky(max_runs=10, min_passes=1)
@pytest.mark.skipif(sys.platform.startswith("linux"), reason="Fails on linux")
def test_not_exit_without_check_parent_process_flag(
client_server,
): # pylint: disable=redefined-outer-name
response = client_server._endpoint.request(
client_server_pair,
):
client, _ = client_server_pair
response = client._endpoint.request(
"initialize",
{
"processId": 1234,
"rootPath": os.path.dirname(__file__),
"initializationOptions": {},
},
).result(timeout=CALL_TIMEOUT)
).result(timeout=CALL_TIMEOUT_IN_SECONDS)
assert "capabilities" in response


@flaky(max_runs=10, min_passes=1)
@pytest.mark.skipif(RUNNING_IN_CI, reason="This test is hanging on CI")
def test_missing_message(client_server): # pylint: disable=redefined-outer-name
def test_missing_message(client_server_pair):
client, _ = client_server_pair
with pytest.raises(JsonRpcMethodNotFound):
client_server._endpoint.request("unknown_method").result(timeout=CALL_TIMEOUT)
client._endpoint.request("unknown_method").result(
timeout=CALL_TIMEOUT_IN_SECONDS
)
50 changes: 6 additions & 44 deletions test/test_notebook_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@

import os
import time
from threading import Thread
from unittest.mock import patch, call

from test.fixtures import CALL_TIMEOUT_IN_SECONDS

import pytest

from pylsp import IS_WIN
from pylsp.python_lsp import PythonLSPServer
from pylsp.lsp import NotebookCellKind

CALL_TIMEOUT_IN_SECONDS = 30


def wait_for_condition(condition, timeout=CALL_TIMEOUT_IN_SECONDS):
"""Wait for a condition to be true, or timeout."""
Expand All @@ -23,44 +21,8 @@ def wait_for_condition(condition, timeout=CALL_TIMEOUT_IN_SECONDS):
raise TimeoutError("Timeout waiting for condition")


def start(obj):
obj.start()


class ClientServerPair:
"""A class to setup a client/server pair"""

def __init__(self):
# Client to Server pipe
csr, csw = os.pipe()
# Server to client pipe
scr, scw = os.pipe()

self.server = PythonLSPServer(os.fdopen(csr, "rb"), os.fdopen(scw, "wb"))
self.server_thread = Thread(target=start, args=[self.server])
self.server_thread.start()

self.client = PythonLSPServer(os.fdopen(scr, "rb"), os.fdopen(csw, "wb"))
self.client_thread = Thread(target=start, args=[self.client])
self.client_thread.start()


@pytest.fixture
def client_server_pair():
"""A fixture that sets up a client/server pair and shuts down the server"""
client_server_pair_obj = ClientServerPair()

yield (client_server_pair_obj.client, client_server_pair_obj.server)

shutdown_response = client_server_pair_obj.client._endpoint.request(
"shutdown"
).result(timeout=CALL_TIMEOUT_IN_SECONDS)
assert shutdown_response is None
client_server_pair_obj.client._endpoint.notify("exit")


@pytest.mark.skipif(IS_WIN, reason="Flaky on Windows")
def test_initialize(client_server_pair): # pylint: disable=redefined-outer-name
def test_initialize(client_server_pair):
client, server = client_server_pair
response = client._endpoint.request(
"initialize",
Expand All @@ -77,7 +39,7 @@ def test_initialize(client_server_pair): # pylint: disable=redefined-outer-name
@pytest.mark.skipif(IS_WIN, reason="Flaky on Windows")
def test_notebook_document__did_open(
client_server_pair,
): # pylint: disable=redefined-outer-name
):
client, server = client_server_pair
client._endpoint.request(
"initialize",
Expand Down Expand Up @@ -241,7 +203,7 @@ def test_notebook_document__did_open(
@pytest.mark.skipif(IS_WIN, reason="Flaky on Windows")
def test_notebook_document__did_change(
client_server_pair,
): # pylint: disable=redefined-outer-name
):
client, server = client_server_pair
client._endpoint.request(
"initialize",
Expand Down Expand Up @@ -513,7 +475,7 @@ def test_notebook_document__did_change(
@pytest.mark.skipif(IS_WIN, reason="Flaky on Windows")
def test_notebook__did_close(
client_server_pair,
): # pylint: disable=redefined-outer-name
):
client, server = client_server_pair
client._endpoint.request(
"initialize",
Expand Down
55 changes: 55 additions & 0 deletions test/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,67 @@
# Copyright 2017-2020 Palantir Technologies, Inc.
# Copyright 2021- Python Language Server Contributors.

import multiprocessing
import os
import sys
from threading import Thread
import time
from unittest import mock

from flaky import flaky

from pylsp import _utils
from pylsp.python_lsp import PythonLSPServer, start_io_lang_server


def start(obj):
obj.start()


class ClientServerPair:
"""
A class to setup a client/server pair.

args:
start_server_in_process: if True, the server will be started in a process.
check_parent_process: if True, the server_process will check if the parent process is alive.
"""

def __init__(self, start_server_in_process=False, check_parent_process=False):
# Client to Server pipe
csr, csw = os.pipe()
# Server to client pipe
scr, scw = os.pipe()

if start_server_in_process:
ParallelKind = self._get_parallel_kind()
self.server_process = ParallelKind(
target=start_io_lang_server,
args=(
os.fdopen(csr, "rb"),
os.fdopen(scw, "wb"),
check_parent_process,
PythonLSPServer,
),
)
self.server_process.start()
else:
self.server = PythonLSPServer(os.fdopen(csr, "rb"), os.fdopen(scw, "wb"))
self.server_thread = Thread(target=start, args=[self.server])
self.server_thread.start()

self.client = PythonLSPServer(os.fdopen(scr, "rb"), os.fdopen(csw, "wb"))
self.client_thread = Thread(target=start, args=[self.client])
self.client_thread.start()

def _get_parallel_kind(self):
if os.name == "nt":
return Thread

if sys.version_info[:2] >= (3, 8):
return multiprocessing.get_context("fork").Process

return multiprocessing.Process


@flaky(max_runs=6, min_passes=1)
Expand Down