mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Additional testing for devtools client/server
This commit is contained in:
24
tests/conftest.py
Normal file
24
tests/conftest.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import pytest
|
||||
|
||||
from textual.devtools import _make_devtools_aiohttp_app
|
||||
from textual.devtools_client import DevtoolsClient
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def server(aiohttp_server, unused_tcp_port):
|
||||
app = _make_devtools_aiohttp_app(
|
||||
size_change_poll_delay_secs=0.001,
|
||||
)
|
||||
server = await aiohttp_server(app, port=unused_tcp_port)
|
||||
yield server
|
||||
await server.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def devtools(aiohttp_client, server):
|
||||
client = await aiohttp_client(server)
|
||||
devtools = DevtoolsClient(address=client.host, port=client.port)
|
||||
await devtools.connect()
|
||||
yield devtools
|
||||
await devtools.disconnect()
|
||||
await client.close()
|
||||
88
tests/test_devtools.py
Normal file
88
tests/test_devtools.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
import time_machine
|
||||
from rich.align import Align
|
||||
from rich.console import Console
|
||||
from rich.segment import Segment
|
||||
|
||||
from tests.utilities.render import wait_for_predicate
|
||||
from textual.devtools import DevtoolsLogMessage, DevtoolsInternalMessage
|
||||
|
||||
TIMESTAMP = 1649166819
|
||||
WIDTH = 40
|
||||
# The string "Hello, world!" is encoded in the payload below
|
||||
EXAMPLE_LOG = {
|
||||
"type": "client_log",
|
||||
"payload": {
|
||||
"encoded_segments": "gASVQgAAAAAAAABdlCiMDHJpY2guc2VnbWVudJSMB1NlZ"
|
||||
"21lbnSUk5SMDUhlbGxvLCB3b3JsZCGUTk6HlIGUaAOMAQqUTk6HlIGUZS4=",
|
||||
"line_number": 123,
|
||||
"path": "abc/hello.py",
|
||||
"timestamp": TIMESTAMP,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def console():
|
||||
return Console(width=WIDTH)
|
||||
|
||||
|
||||
@time_machine.travel(TIMESTAMP)
|
||||
def test_log_message_render(console):
|
||||
message = DevtoolsLogMessage(
|
||||
[Segment("content")],
|
||||
path="abc/hello.py",
|
||||
line_number=123,
|
||||
unix_timestamp=TIMESTAMP,
|
||||
)
|
||||
table = next(iter(message.__rich_console__(console, console.options)))
|
||||
|
||||
assert len(table.rows) == 1
|
||||
|
||||
columns = list(table.columns)
|
||||
left_cells = list(columns[0].cells)
|
||||
left = left_cells[0]
|
||||
right_cells = list(columns[1].cells)
|
||||
right: Align = right_cells[0]
|
||||
|
||||
assert left == " [#888177]15:53:39 [dim]BST[/]"
|
||||
assert right.align == "right"
|
||||
assert "hello.py:123" in right.renderable
|
||||
|
||||
|
||||
def test_internal_message_render(console):
|
||||
message = DevtoolsInternalMessage("hello")
|
||||
rule = next(iter(message.__rich_console__(console, console.options)))
|
||||
assert rule.title == "hello"
|
||||
assert rule.characters == "─"
|
||||
|
||||
|
||||
async def test_devtools_valid_client_log(devtools):
|
||||
await devtools.websocket.send_json(EXAMPLE_LOG)
|
||||
assert devtools.is_connected
|
||||
|
||||
|
||||
async def test_devtools_string_not_json_message(devtools):
|
||||
await devtools.websocket.send_str("ABCDEFG")
|
||||
assert devtools.is_connected
|
||||
|
||||
|
||||
async def test_devtools_invalid_json_message(devtools):
|
||||
await devtools.websocket.send_json({"invalid": "json"})
|
||||
assert devtools.is_connected
|
||||
|
||||
|
||||
async def test_devtools_spillover_message(devtools):
|
||||
await devtools.websocket.send_json(
|
||||
{"type": "client_spillover", "payload": {"spillover": 123}}
|
||||
)
|
||||
assert devtools.is_connected
|
||||
|
||||
|
||||
async def test_devtools_console_size_change(server, devtools):
|
||||
# Update the width of the console on the server-side
|
||||
server.app["console"].width = 124
|
||||
# Wait for the client side to update the console on their end
|
||||
await wait_for_predicate(lambda: devtools.console.width == 124)
|
||||
@@ -9,29 +9,12 @@ from aiohttp.web_ws import WebSocketResponse
|
||||
from rich.console import ConsoleDimensions
|
||||
from rich.panel import Panel
|
||||
|
||||
from textual.devtools import make_aiohttp_app
|
||||
from tests.utilities.render import wait_for_predicate
|
||||
from textual.devtools_client import DevtoolsClient
|
||||
|
||||
TIMESTAMP = 1649166819
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def server(aiohttp_server, unused_tcp_port):
|
||||
server = await aiohttp_server(make_aiohttp_app(), port=unused_tcp_port)
|
||||
yield server
|
||||
await server.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def devtools(aiohttp_client, server):
|
||||
client = await aiohttp_client(server)
|
||||
devtools = DevtoolsClient(address=client.host, port=client.port)
|
||||
await devtools.connect()
|
||||
yield devtools
|
||||
await devtools.disconnect()
|
||||
await client.close()
|
||||
|
||||
|
||||
def test_devtools_client_initialize_defaults():
|
||||
devtools = DevtoolsClient()
|
||||
assert devtools.url == "ws://127.0.0.1:8081"
|
||||
@@ -50,7 +33,7 @@ async def test_devtools_log_places_encodes_and_queues_message(devtools):
|
||||
assert queued_log_json == {
|
||||
"payload": {
|
||||
"encoded_segments": "gASVQgAAAAAAAABdlCiMDHJpY2guc2VnbWVudJSMB1NlZ"
|
||||
"21lbnSUk5SMDUhlbGxvLCB3b3JsZCGUTk6HlIGUaAOMAQqUTk6HlIGUZS4=",
|
||||
"21lbnSUk5SMDUhlbGxvLCB3b3JsZCGUTk6HlIGUaAOMAQqUTk6HlIGUZS4=",
|
||||
"line_number": 0,
|
||||
"path": "",
|
||||
"timestamp": TIMESTAMP,
|
||||
@@ -99,12 +82,17 @@ async def test_devtools_log_spillover(devtools):
|
||||
|
||||
# Ensure we're informing the server of spillover rate-limiting
|
||||
spillover_message = await devtools.log_queue.get()
|
||||
assert json.loads(spillover_message) == {"type": "client_spillover", "payload": {"spillover": 2}}
|
||||
assert json.loads(spillover_message) == {
|
||||
"type": "client_spillover",
|
||||
"payload": {"spillover": 2},
|
||||
}
|
||||
|
||||
|
||||
async def test_devtools_client_update_console_dimensions(devtools, server):
|
||||
server_websocket: WebSocketResponse = next(iter(server.app["websockets"]))
|
||||
# Send new server information from the server via the websocket
|
||||
"""Sending new server info through websocket from server to client should (eventually)
|
||||
result in the dimensions of the devtools client console being updated to match.
|
||||
"""
|
||||
server_to_client: WebSocketResponse = next(iter(server.app["websockets"]))
|
||||
server_info = {
|
||||
"type": "server_info",
|
||||
"payload": {
|
||||
@@ -112,13 +100,7 @@ async def test_devtools_client_update_console_dimensions(devtools, server):
|
||||
"height": 456,
|
||||
},
|
||||
}
|
||||
await server_websocket.send_json(server_info)
|
||||
timer = 0
|
||||
poll_period = .1
|
||||
while True:
|
||||
if timer > 3:
|
||||
pytest.fail("The devtools client dimensions did not update")
|
||||
if devtools.console.size == ConsoleDimensions(123, 456):
|
||||
break
|
||||
await asyncio.sleep(.1)
|
||||
timer += poll_period
|
||||
await server_to_client.send_json(server_info)
|
||||
await wait_for_predicate(
|
||||
lambda: devtools.console.size == ConsoleDimensions(123, 456)
|
||||
)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import asyncio
|
||||
import io
|
||||
import re
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
from rich.console import Console, RenderableType
|
||||
|
||||
|
||||
re_link_ids = re.compile(r"id=[\d\.\-]*?;.*?\x1b")
|
||||
|
||||
|
||||
@@ -23,3 +25,32 @@ def render(renderable: RenderableType, no_wrap: bool = False) -> str:
|
||||
console.print(renderable, no_wrap=no_wrap, end="")
|
||||
output = replace_link_ids(capture.get())
|
||||
return output
|
||||
|
||||
|
||||
async def wait_for_predicate(
|
||||
predicate: Callable[[], bool],
|
||||
timeout_secs: float = 2,
|
||||
poll_delay_secs: float = 0.001,
|
||||
) -> None:
|
||||
"""Wait for the given predicate to become True by evaluating it every `poll_delay_secs`
|
||||
seconds. Fail the pytest test if the predicate does not become True after `timeout_secs`
|
||||
seconds.
|
||||
|
||||
Args:
|
||||
predicate (Callable[[], bool]): The predicate function which will be called repeatedly.
|
||||
timeout_secs (float): If the predicate doesn't evaluate to True after this number of
|
||||
seconds, the test will fail.
|
||||
poll_delay_secs (float): The number of seconds to wait between each call to the
|
||||
predicate function.
|
||||
"""
|
||||
time_taken = 0
|
||||
while True:
|
||||
result = predicate()
|
||||
if result:
|
||||
return
|
||||
await asyncio.sleep(poll_delay_secs)
|
||||
time_taken += poll_delay_secs
|
||||
if time_taken > timeout_secs:
|
||||
pytest.fail(
|
||||
f"Predicate {predicate} did not return True after {timeout_secs} seconds."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user