Skip to content

Commit

Permalink
Converge unit tests for test_language_server and test_notebook_docume…
Browse files Browse the repository at this point in the history
…nt (#418)
  • Loading branch information
tkrabel-db authored Aug 17, 2023
1 parent d47dc3c commit ceb8af2
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 119 deletions.
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

0 comments on commit ceb8af2

Please sign in to comment.