Use async readline with local telnetlib3
This commit is contained in:
parent
a83248b387
commit
04230eb152
4 changed files with 33 additions and 39 deletions
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
|||
description = "a telnet mud engine"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"telnetlib3",
|
||||
"telnetlib3 @ file:///home/jtm/src/telnetlib3",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
|
|
@ -19,6 +19,9 @@ dev = [
|
|||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/mudlib"]
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ async def shell(
|
|||
writer: telnetlib3.TelnetWriter | telnetlib3.TelnetWriterUnicode,
|
||||
) -> None:
|
||||
"""Shell callback that greets the player and echoes their input."""
|
||||
# Cast to unicode variants since we're using encoding="utf8"
|
||||
_reader = cast(telnetlib3.TelnetReaderUnicode, reader)
|
||||
_writer = cast(telnetlib3.TelnetWriterUnicode, writer)
|
||||
|
||||
_writer.write("Welcome to the MUD!\r\n")
|
||||
|
|
@ -25,8 +23,10 @@ async def shell(
|
|||
_writer.write("mud> ")
|
||||
await _writer.drain()
|
||||
|
||||
inp = await _reader.readline()
|
||||
if not inp:
|
||||
# readline_async reads char-by-char, handles echo via writer.echo(),
|
||||
# and supports backspace editing
|
||||
inp = await telnetlib3.readline_async(reader, writer)
|
||||
if inp is None:
|
||||
break
|
||||
|
||||
command = inp.strip()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Tests for the server module."""
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -9,84 +9,69 @@ from mudlib import server
|
|||
|
||||
|
||||
def test_port_constant():
|
||||
"""Test that PORT is defined correctly."""
|
||||
assert server.PORT == 6789
|
||||
assert isinstance(server.PORT, int)
|
||||
|
||||
|
||||
def test_shell_exists():
|
||||
"""Test that the shell callback exists and is callable."""
|
||||
assert callable(server.shell)
|
||||
assert asyncio.iscoroutinefunction(server.shell)
|
||||
|
||||
|
||||
def test_run_server_exists():
|
||||
"""Test that run_server exists and is callable."""
|
||||
assert callable(server.run_server)
|
||||
assert asyncio.iscoroutinefunction(server.run_server)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_shell_greets_and_echoes():
|
||||
"""Test that shell greets the player and echoes input."""
|
||||
reader = AsyncMock()
|
||||
writer = MagicMock()
|
||||
writer.is_closing.return_value = False
|
||||
writer.drain = AsyncMock()
|
||||
writer.close = MagicMock()
|
||||
|
||||
# Simulate user typing "hello" then "quit"
|
||||
reader.readline.side_effect = ["hello\r\n", "quit\r\n"]
|
||||
|
||||
readline = "mudlib.server.telnetlib3.readline_async"
|
||||
with patch(readline, new_callable=AsyncMock) as mock_readline:
|
||||
mock_readline.side_effect = ["hello", "quit"]
|
||||
await server.shell(reader, writer)
|
||||
|
||||
# Check that welcome message was written
|
||||
calls = [str(call) for call in writer.write.call_args_list]
|
||||
assert any("Welcome" in call for call in calls)
|
||||
|
||||
# Check that input was echoed
|
||||
assert any("hello" in call for call in calls)
|
||||
|
||||
# Check that goodbye was written
|
||||
assert any("Goodbye" in call for call in calls)
|
||||
|
||||
# Check that close was called
|
||||
writer.close.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_shell_handles_empty_input():
|
||||
"""Test that shell handles EOF gracefully."""
|
||||
async def test_shell_handles_eof():
|
||||
reader = AsyncMock()
|
||||
writer = MagicMock()
|
||||
writer.is_closing.return_value = False
|
||||
writer.drain = AsyncMock()
|
||||
writer.close = MagicMock()
|
||||
|
||||
# Simulate EOF
|
||||
reader.readline.return_value = ""
|
||||
|
||||
readline = "mudlib.server.telnetlib3.readline_async"
|
||||
with patch(readline, new_callable=AsyncMock) as mock_readline:
|
||||
mock_readline.return_value = None
|
||||
await server.shell(reader, writer)
|
||||
|
||||
# Should close cleanly
|
||||
writer.close.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_shell_handles_quit():
|
||||
"""Test that shell exits on quit command."""
|
||||
reader = AsyncMock()
|
||||
writer = MagicMock()
|
||||
writer.is_closing.return_value = False
|
||||
writer.drain = AsyncMock()
|
||||
writer.close = MagicMock()
|
||||
|
||||
reader.readline.return_value = "quit\r\n"
|
||||
|
||||
readline = "mudlib.server.telnetlib3.readline_async"
|
||||
with patch(readline, new_callable=AsyncMock) as mock_readline:
|
||||
mock_readline.return_value = "quit"
|
||||
await server.shell(reader, writer)
|
||||
|
||||
# Check that goodbye was written
|
||||
calls = [str(call) for call in writer.write.call_args_list]
|
||||
assert any("Goodbye" in call for call in calls)
|
||||
|
||||
writer.close.assert_called_once()
|
||||
|
|
|
|||
16
uv.lock
16
uv.lock
|
|
@ -37,7 +37,7 @@ dev = [
|
|||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "telnetlib3" }]
|
||||
requires-dist = [{ name = "telnetlib3", directory = "../../src/telnetlib3" }]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
|
|
@ -153,11 +153,17 @@ wheels = [
|
|||
[[package]]
|
||||
name = "telnetlib3"
|
||||
version = "2.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0d/2a/a9a7a4cb24626493806d95df0b19740d40a5583836ee070970489ca063b1/telnetlib3-2.2.0.tar.gz", hash = "sha256:85312604c9f52914938fe3697e5bbd219adab446af0df3045f21b07ba5417f73", size = 211769, upload-time = "2026-02-06T20:21:48.288Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/62/51a035c18305402ac5c5eefa88fdaf7e14eba6f09e3a17a218d72fe2c18b/telnetlib3-2.2.0-py3-none-any.whl", hash = "sha256:028648619e66d1a746791a3ef3e2a1cc7ffe4b78cf283c8028bef9e493f30554", size = 219273, upload-time = "2026-02-06T20:21:46.956Z" },
|
||||
source = { directory = "../../src/telnetlib3" }
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "prettytable", marker = "extra == 'extras'" },
|
||||
{ name = "sphinx", marker = "extra == 'docs'", specifier = ">3" },
|
||||
{ name = "sphinx-autodoc-typehints", marker = "extra == 'docs'" },
|
||||
{ name = "sphinx-rtd-theme", marker = "extra == 'docs'" },
|
||||
{ name = "ucs-detect", marker = "extra == 'extras'", specifier = ">=2" },
|
||||
]
|
||||
provides-extras = ["docs", "extras"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
|
|
|
|||
Loading…
Reference in a new issue