-
Notifications
You must be signed in to change notification settings - Fork 193
Test both TCP modes #2866
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
base: main
Are you sure you want to change the base?
Test both TCP modes #2866
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,8 +1,7 @@ | ||||||
| """ | ||||||
| A simple test server for integration tests. | ||||||
|
|
||||||
| Only understands stdio. | ||||||
| Uses the asyncio module and mypy types, so you'll need a modern Python. | ||||||
| Can do JSON-RPC with stdio or TCP sockets as the transport. | ||||||
|
|
||||||
| To make this server reply to requests, send the $test/setResponse notification. | ||||||
|
|
||||||
|
|
@@ -18,7 +17,6 @@ | |||||
| Tests can await this request to make sure that they receive notification before code | ||||||
| resumes (since response to request will arrive after requested notification). | ||||||
|
|
||||||
| TODO: Untested on Windows. | ||||||
| """ | ||||||
| from __future__ import annotations | ||||||
|
|
||||||
|
|
@@ -38,7 +36,7 @@ | |||||
| import uuid | ||||||
|
|
||||||
| __package__ = "server" | ||||||
| __version__ = "1.0.0" | ||||||
| __version__ = "2.0.0" | ||||||
|
|
||||||
|
|
||||||
| if sys.version_info[:2] < (3, 6): | ||||||
|
|
@@ -484,24 +482,36 @@ def do_blocking_drain() -> None: | |||||
| # END: https://stackoverflow.com/a/52702646/990142 | ||||||
|
|
||||||
|
|
||||||
| async def main(tcp_port: int | None = None) -> bool: | ||||||
| async def main(tcp_port: int | None = None, mode: str | None = None) -> bool: | ||||||
| if tcp_port is not None: | ||||||
|
|
||||||
| class ClientConnectedCallback: | ||||||
| if mode is None or mode == "server": | ||||||
| print("running in TCP server mode", file=sys.stderr) | ||||||
|
|
||||||
| def __init__(self) -> None: | ||||||
| self.received_shutdown = False | ||||||
| class ClientConnectedCallback: | ||||||
|
|
||||||
| async def __call__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: | ||||||
| session = Session(reader, writer) | ||||||
| self.received_shutdown = await session.run_forever() | ||||||
| def __init__(self) -> None: | ||||||
| self.received_shutdown = False | ||||||
|
|
||||||
| async def __call__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: | ||||||
| session = Session(reader, writer) | ||||||
| self.received_shutdown = await session.run_forever() | ||||||
|
|
||||||
| callback = ClientConnectedCallback() | ||||||
| server = await asyncio.start_server(callback, port=tcp_port) | ||||||
| # NOTE: This is deliberately wrong -- we should stop serving once the exit notification is received. But, | ||||||
| # it's good to have this botched logic here to make sure that servers shutdown in the integration tests. | ||||||
| await server.serve_forever() | ||||||
| return callback.received_shutdown | ||||||
|
|
||||||
| if mode is not None and mode == "client": | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| print("running in TCP client mode", file=sys.stderr) | ||||||
| reader, writer = await asyncio.open_connection(host=None, port=tcp_port) | ||||||
| session = Session(reader, writer) | ||||||
| return await session.run_forever() | ||||||
|
|
||||||
| return False | ||||||
|
|
||||||
| callback = ClientConnectedCallback() | ||||||
| server = await asyncio.start_server(callback, port=tcp_port) | ||||||
| # NOTE: This is deliberately wrong -- we should stop serving once the exit notification is received. | ||||||
| # But, it's good to have this botched logic here to make sure that servers shutdown in the integration tests. | ||||||
| await server.serve_forever() | ||||||
| return callback.received_shutdown | ||||||
| reader, writer = await stdio() | ||||||
| session = Session(reader, writer) | ||||||
| return await session.run_forever() | ||||||
|
|
@@ -511,6 +521,7 @@ async def __call__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWri | |||||
| parser = ArgumentParser(prog=__package__, description=__doc__) | ||||||
| parser.add_argument("-v", "--version", action="store_true", help="print version and exit") | ||||||
| parser.add_argument("-p", "--tcp-port", type=int) | ||||||
| parser.add_argument("--mode", help="one of 'client' or 'server'", default="server") | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can validate the argument early and then also doesn't really need a redundant help:
Suggested change
|
||||||
| args = parser.parse_args() | ||||||
| if args.version: | ||||||
| print(__package__, __version__) | ||||||
|
|
@@ -519,7 +530,7 @@ async def __call__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWri | |||||
| asyncio.set_event_loop(loop) | ||||||
| shutdown_received = False | ||||||
| try: | ||||||
| shutdown_received = loop.run_until_complete(main(args.tcp_port)) | ||||||
| shutdown_received = loop.run_until_complete(main(args.tcp_port, args.mode)) | ||||||
| except KeyboardInterrupt: | ||||||
| pass | ||||||
| loop.run_until_complete(loop.shutdown_asyncgens()) | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -41,23 +41,42 @@ def result(self) -> Any: | |||||||||||||||||||||||||||||||
| return self.__result | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def make_stdio_test_config() -> ClientConfig: | ||||||||||||||||||||||||||||||||
| return ClientConfig( | ||||||||||||||||||||||||||||||||
| name="TEST", | ||||||||||||||||||||||||||||||||
| def make_stdio_test_config(name: str, init_options: dict[str, Any]) -> ClientConfig: | ||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe make
Suggested change
|
||||||||||||||||||||||||||||||||
| """Start the fake language server in STDIO mode.""" | ||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpicking but the function doesn't really start the server. It just creates a config for starting a server. |
||||||||||||||||||||||||||||||||
| config = ClientConfig( | ||||||||||||||||||||||||||||||||
| name=name, | ||||||||||||||||||||||||||||||||
| command=["python3", join("$packages", "LSP", "tests", "server.py")], | ||||||||||||||||||||||||||||||||
| selector="text.plain", | ||||||||||||||||||||||||||||||||
| enabled=True, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| config.initialization_options.assign(init_options) | ||||||||||||||||||||||||||||||||
| return config | ||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+53
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not pass init_options to the constructor and save lines?
Suggested change
Same below. |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def make_tcp_test_config() -> ClientConfig: | ||||||||||||||||||||||||||||||||
| return ClientConfig( | ||||||||||||||||||||||||||||||||
| name="TEST", | ||||||||||||||||||||||||||||||||
| command=["python3", join("$packages", "LSP", "tests", "server.py"), "--tcp-port", "$port"], | ||||||||||||||||||||||||||||||||
| def make_tcp_server_test_config(name: str, init_options: dict[str, Any]) -> ClientConfig: | ||||||||||||||||||||||||||||||||
| """Start the fake server in TCP mode, and make it act as the TCP server, awaiting a single client connection.""" | ||||||||||||||||||||||||||||||||
| config = ClientConfig( | ||||||||||||||||||||||||||||||||
| name=name, | ||||||||||||||||||||||||||||||||
| command=["python3", join("$packages", "LSP", "tests", "server.py"), "--tcp-port", "$port", "--mode=server"], | ||||||||||||||||||||||||||||||||
| selector="text.plain", | ||||||||||||||||||||||||||||||||
| tcp_port=0, # select a free one for me | ||||||||||||||||||||||||||||||||
| enabled=True, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| config.initialization_options.assign(init_options) | ||||||||||||||||||||||||||||||||
| return config | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def make_tcp_client_test_config(name: str, init_options: dict[str, Any]) -> ClientConfig: | ||||||||||||||||||||||||||||||||
| """Start the fake server in TCP mode, and make it act as the TCP client, where it connects to the LSP plugin.""" | ||||||||||||||||||||||||||||||||
| config = ClientConfig( | ||||||||||||||||||||||||||||||||
| name=name, | ||||||||||||||||||||||||||||||||
| command=["python3", join("$packages", "LSP", "tests", "server.py"), "--tcp-port", "$port", "--mode=client"], | ||||||||||||||||||||||||||||||||
| selector="text.plain", | ||||||||||||||||||||||||||||||||
| tcp_port=-1, # select a free one for me | ||||||||||||||||||||||||||||||||
| enabled=True, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| config.initialization_options.assign(init_options) | ||||||||||||||||||||||||||||||||
| return config | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def add_config(config: ClientConfig) -> None: | ||||||||||||||||||||||||||||||||
|
|
@@ -82,7 +101,7 @@ def expand(s: str, w: sublime.Window) -> str: | |||||||||||||||||||||||||||||||
| class TextDocumentTestCase(DeferrableTestCase): | ||||||||||||||||||||||||||||||||
| @classmethod | ||||||||||||||||||||||||||||||||
| def get_stdio_test_config(cls) -> ClientConfig: | ||||||||||||||||||||||||||||||||
| return make_stdio_test_config() | ||||||||||||||||||||||||||||||||
| return make_stdio_test_config("TEST", {}) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @classmethod | ||||||||||||||||||||||||||||||||
| def setUpClass(cls) -> Generator: | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you set type for
modetoLiteral['client', 'server'] | Noneso that it self-documents allowed values?