telnetlib3 5.0.0__tar.gz → 5.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/PKG-INFO +2 -2
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/history.rst +17 -4
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/pyproject.toml +1 -1
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/accessories.py +33 -1
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/client.py +5 -3
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/guard_shells.py +5 -1
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/server_shell.py +5 -1
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/stream_reader.py +85 -20
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_client_unit.py +74 -3
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_reader.py +123 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_server_shell_unit.py +22 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/.gitignore +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/CONTRIBUTING.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/LICENSE.txt +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/README.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/Makefile +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/accessories.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/client.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/client_base.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/client_shell.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/client_shell_win32.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/fingerprinting.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/guard_shells.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/mud.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/server.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/server_base.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/server_pty_shell.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/server_shell.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/session_context.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/slc.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/stream_reader.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/stream_writer.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/sync.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/telnetlib.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api/telopt.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/api.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/conf.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/contributing.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/guidebook.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/index.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/intro.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/make.bat +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/rfcs.rst +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/docs/sphinxext/github.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/requirements-analysis.txt +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/requirements-docs.txt +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/requirements-tests.txt +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/requirements.txt +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/.gitignore +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/__init__.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/_base.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/_paths.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/_session_context.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/_types.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/client_base.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/client_shell.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/client_shell_win32.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/encodings/__init__.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/encodings/atarist.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/encodings/atascii.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/encodings/big5bbs.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/encodings/petscii.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/fingerprinting.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/fingerprinting_display.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/mud.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/py.typed +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/relay_server.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/server.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/server_base.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/server_fingerprinting.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/server_pty_shell.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/slc.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/stream_writer.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/sync.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/telnetlib.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/telopt.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/accessories.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/conftest.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/pty_helper.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_accessories.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_accessories_extra.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_atascii_codec.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_benchmarks.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_big5bbs_codec.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_charset.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_client_shell.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_client_shell_win32.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_core.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_encoding.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_environ.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_fingerprinting.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_guard_integration.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_linemode.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_mccp.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_mud.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_mud_negotiation.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_naws.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_petscii_codec.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_pty_shell.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_relay_server.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_server.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_server_api.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_server_cli.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_server_fingerprinting.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_server_mud.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_shell.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_slc.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_status_logger.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_stream_reader_extra.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_stream_writer_full.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_sync.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_telnetlib.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_telnetlib_import.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_timeout.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_tls.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_tspeed.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_ttype.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_uvloop_integration.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_writer.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/telnetlib3/tests/test_xdisploc.py +0 -0
- {telnetlib3-5.0.0 → telnetlib3-5.0.2}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: telnetlib3
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.2
|
|
4
4
|
Summary: Python Telnet server and client CLI and Protocol library
|
|
5
5
|
Project-URL: Homepage, https://github.com/jquast/telnetlib3
|
|
6
6
|
Project-URL: Documentation, https://telnetlib3.readthedocs.io
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
History
|
|
2
2
|
=======
|
|
3
3
|
|
|
4
|
+
5.0.2
|
|
5
|
+
* bugfix: :meth:`~telnetlib3.stream_reader.TelnetReaderUnicode.read`,
|
|
6
|
+
:meth:`~telnetlib3.stream_reader.TelnetReaderUnicode.readline`, and
|
|
7
|
+
:meth:`~telnetlib3.stream_reader.TelnetReaderUnicode.readexactly` returned incorrect text when
|
|
8
|
+
a multibyte sequence ended on a packet boundary, and dropped text retained by the incremental
|
|
9
|
+
decoder at end of stream. ``readline()`` now finalizes a truncated character with the line it
|
|
10
|
+
belongs to, ``read(n)`` no longer returns more than *n* characters, and
|
|
11
|
+
``encoding_errors="strict"`` raises :exc:`UnicodeDecodeError` at EOF.
|
|
12
|
+
|
|
13
|
+
5.0.1
|
|
14
|
+
* bugfix: :func:`~telnetlib3.client.open_connection` raised ``AttributeError: 'NoneType' object
|
|
15
|
+
has no attribute 'isatty'`` in processes without a console, like ``pythonw.exe`` on Windows.
|
|
16
|
+
|
|
17
|
+
|
|
4
18
|
5.0.0
|
|
5
19
|
* changed: :meth:`~telnetlib3.stream_writer.TelnetWriter.handle_zmp` now receives ``command,
|
|
6
20
|
*args`` instead of one ``parts`` list; ``zmp_data`` moved to ``writer.ctx`` and is now a dict
|
|
@@ -16,18 +30,17 @@ History
|
|
|
16
30
|
``always_do`` / ``passive_do``) used by downstream clients such as telix to accept them. Note
|
|
17
31
|
that MXP's negotiation direction is server-sends-``DO`` (the LPMud family convention, e.g.
|
|
18
32
|
Discworld), so a client accepting MXP replies ``WILL`` to ``IAC DO MXP``.
|
|
33
|
+
* new: :meth:`~telnetlib3.stream_writer.TelnetWriter.add_will_callback` and
|
|
19
34
|
:meth:`~telnetlib3.stream_writer.TelnetWriter.remove_will_callback` for per-option callbacks
|
|
20
35
|
invoked after :meth:`~telnetlib3.stream_writer.TelnetWriter.handle_will` negotiation. Replaces
|
|
21
|
-
the previous
|
|
22
|
-
|
|
23
|
-
will-detection.
|
|
36
|
+
the previous closure-wrapping pattern in :class:`~telnetlib3.client.TelnetClient` for GMCP, ZMP,
|
|
37
|
+
and CHARSET will-detection.
|
|
24
38
|
* enhancement: ``telnetlib3-fingerprint`` now accepts all MUD protocol offers (ATCP, AARDWOLF,
|
|
25
39
|
MSP, MXP, MSDP, MSSP) to collect subnegotiation data.
|
|
26
40
|
* enhancement: sub-negotiation payloads are bounded to 1,000KB.
|
|
27
41
|
* enhancement: ``--loglevel=trace`` receive dumps show the decompressed telnet stream (MCCP2,
|
|
28
42
|
MCCP3) instead of raw compressed bytes.
|
|
29
43
|
* bugfix: ``IAC SB IAC SE`` (sub-negotiation with no option byte) should not raise ``IndexError``
|
|
30
|
-
* new: :meth:`~telnetlib3.stream_writer.TelnetWriter.add_will_callback` and
|
|
31
44
|
|
|
32
45
|
|
|
33
46
|
4.0.6
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "telnetlib3"
|
|
7
|
-
version = "5.0.
|
|
7
|
+
version = "5.0.2" # Keep in sync with telnetlib3/accessories.py::get_version !
|
|
8
8
|
description = " Python Telnet server and client CLI and Protocol library"
|
|
9
9
|
readme = "README.rst"
|
|
10
10
|
license = "ISC"
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
# std imports
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
6
8
|
import shlex
|
|
7
9
|
import asyncio
|
|
8
10
|
import logging
|
|
@@ -26,6 +28,7 @@ __all__ = (
|
|
|
26
28
|
"repr_mapping",
|
|
27
29
|
"function_lookup",
|
|
28
30
|
"make_reader_task",
|
|
31
|
+
"is_a_tty",
|
|
29
32
|
)
|
|
30
33
|
|
|
31
34
|
PATIENCE_MESSAGES = [
|
|
@@ -42,7 +45,7 @@ PATIENCE_MESSAGES = [
|
|
|
42
45
|
|
|
43
46
|
def get_version() -> str:
|
|
44
47
|
"""Return the current version of telnetlib3."""
|
|
45
|
-
return "5.0.
|
|
48
|
+
return "5.0.2" # keep in sync with pyproject.toml !
|
|
46
49
|
|
|
47
50
|
|
|
48
51
|
def encoding_from_lang(lang: str) -> Optional[str]:
|
|
@@ -172,3 +175,32 @@ def make_reader_task(
|
|
|
172
175
|
) -> "asyncio.Task[Any]":
|
|
173
176
|
"""Return asyncio task wrapping coroutine of reader.read(size)."""
|
|
174
177
|
return asyncio.ensure_future(reader.read(size))
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def is_a_tty() -> bool:
|
|
181
|
+
"""
|
|
182
|
+
Whether an interactive terminal is available on standard input.
|
|
183
|
+
|
|
184
|
+
This is a more conservative test than ``sys.stdin.isatty()``: the standard
|
|
185
|
+
streams may be ``None`` (running under ``pythonw.exe`` on Windows, or any
|
|
186
|
+
process launched without a console), replaced by an object without a
|
|
187
|
+
working :meth:`~io.IOBase.isatty` or :meth:`~io.IOBase.fileno` (test
|
|
188
|
+
harnesses, GUI frameworks), or detached. A real file descriptor is
|
|
189
|
+
required, because terminal features such as window size and raw mode are
|
|
190
|
+
performed against ``sys.stdin.fileno()``.
|
|
191
|
+
|
|
192
|
+
:returns: ``True`` only when stdin is a terminal backed by a file
|
|
193
|
+
descriptor.
|
|
194
|
+
"""
|
|
195
|
+
stdin = sys.stdin
|
|
196
|
+
if stdin is None:
|
|
197
|
+
return False
|
|
198
|
+
try:
|
|
199
|
+
if not stdin.isatty():
|
|
200
|
+
return False
|
|
201
|
+
return os.isatty(stdin.fileno())
|
|
202
|
+
except (AttributeError, ValueError, OSError):
|
|
203
|
+
# AttributeError: stream without isatty/fileno,
|
|
204
|
+
# ValueError: closed or detached stream,
|
|
205
|
+
# OSError: fileno() unsupported by the underlying object.
|
|
206
|
+
return False
|
|
@@ -577,7 +577,9 @@ async def open_connection(
|
|
|
577
577
|
:param port: Remote Internet host TCP port.
|
|
578
578
|
:param client_factory: Client connection class factory. When ``None``,
|
|
579
579
|
:class:`TelnetTerminalClient` is used when *stdin* is attached to a
|
|
580
|
-
terminal, :class:`TelnetClient` otherwise.
|
|
580
|
+
terminal, :class:`TelnetClient` otherwise. Processes without a
|
|
581
|
+
console, such as those launched by ``pythonw.exe`` on Windows, where
|
|
582
|
+
``sys.stdin`` is ``None``, always receive :class:`TelnetClient`.
|
|
581
583
|
:param family: Same meaning as
|
|
582
584
|
:meth:`asyncio.loop.create_connection`.
|
|
583
585
|
:param flags: Same meaning as
|
|
@@ -643,7 +645,7 @@ async def open_connection(
|
|
|
643
645
|
"""
|
|
644
646
|
if client_factory is None:
|
|
645
647
|
client_factory = TelnetClient
|
|
646
|
-
if
|
|
648
|
+
if accessories.is_a_tty():
|
|
647
649
|
client_factory = TelnetTerminalClient
|
|
648
650
|
|
|
649
651
|
def connection_factory() -> client_base.BaseClient:
|
|
@@ -724,7 +726,7 @@ async def run_client() -> None:
|
|
|
724
726
|
def _client_factory(**kwargs: Any) -> client_base.BaseClient:
|
|
725
727
|
client: TelnetClient
|
|
726
728
|
kwargs["gmcp_modules"] = gmcp_modules
|
|
727
|
-
if
|
|
729
|
+
if accessories.is_a_tty():
|
|
728
730
|
client = TelnetTerminalClient(**kwargs)
|
|
729
731
|
else:
|
|
730
732
|
client = TelnetClient(**kwargs)
|
|
@@ -110,7 +110,11 @@ async def _read_line_inner(reader: Union[TelnetReader, TelnetReaderUnicode], max
|
|
|
110
110
|
"""Inner loop for _read_line, separated for wait_for compatibility."""
|
|
111
111
|
buf = ""
|
|
112
112
|
while len(buf) < max_len:
|
|
113
|
-
|
|
113
|
+
try:
|
|
114
|
+
char = await reader.read(1)
|
|
115
|
+
except UnicodeDecodeError:
|
|
116
|
+
# An incomplete character at end of stream is end of input, as with EOF.
|
|
117
|
+
break
|
|
114
118
|
if not char:
|
|
115
119
|
break
|
|
116
120
|
if isinstance(char, bytes):
|
|
@@ -67,7 +67,11 @@ async def filter_ansi(
|
|
|
67
67
|
return reader.read(1)
|
|
68
68
|
|
|
69
69
|
async def _next() -> str:
|
|
70
|
-
|
|
70
|
+
try:
|
|
71
|
+
ch = await _rch()
|
|
72
|
+
except UnicodeDecodeError:
|
|
73
|
+
# An incomplete character at end of stream is end of input, as with EOF.
|
|
74
|
+
return ""
|
|
71
75
|
if not ch:
|
|
72
76
|
return ""
|
|
73
77
|
return ch.decode("latin-1") if binary else ch # type: ignore[return-value]
|
|
@@ -564,6 +564,11 @@ class TelnetReaderUnicode(TelnetReader):
|
|
|
564
564
|
#: practice, however.
|
|
565
565
|
_decoder = None
|
|
566
566
|
|
|
567
|
+
#: Characters finalized from bytes retained by :attr:`_decoder` at end of
|
|
568
|
+
#: stream, held back when :meth:`read` requested fewer. Only ever set once
|
|
569
|
+
#: :attr:`_buffer` is empty, so these are always the last of the stream.
|
|
570
|
+
_pending = ""
|
|
571
|
+
|
|
567
572
|
def __init__(
|
|
568
573
|
self,
|
|
569
574
|
fn_encoding: Callable[..., str],
|
|
@@ -584,8 +589,13 @@ class TelnetReaderUnicode(TelnetReader):
|
|
|
584
589
|
self.encoding_errors = encoding_errors
|
|
585
590
|
|
|
586
591
|
def decode(self, buf: bytes, final: bool = False) -> str:
|
|
587
|
-
"""
|
|
588
|
-
|
|
592
|
+
"""
|
|
593
|
+
Decode bytes ``buf`` using preferred encoding.
|
|
594
|
+
|
|
595
|
+
:param final: When True, bytes retained by the incremental decoder are decoded as the end
|
|
596
|
+
of the stream, applying ``encoding_errors`` to an incomplete character.
|
|
597
|
+
"""
|
|
598
|
+
if buf == b"" and (not final or self._decoder is None):
|
|
589
599
|
return "" # EOF
|
|
590
600
|
|
|
591
601
|
encoding = self.fn_encoding(incoming=True)
|
|
@@ -597,20 +607,53 @@ class TelnetReaderUnicode(TelnetReader):
|
|
|
597
607
|
|
|
598
608
|
return self._decoder.decode(buf, final)
|
|
599
609
|
|
|
610
|
+
def _finalize_decoder(self, n: int = -1) -> str:
|
|
611
|
+
"""
|
|
612
|
+
Decode and return bytes retained by the incremental decoder at end of stream.
|
|
613
|
+
|
|
614
|
+
Characters decoded beyond *n* are held in :attr:`_pending` for the next read, so that
|
|
615
|
+
:meth:`read` never returns more characters than requested.
|
|
616
|
+
"""
|
|
617
|
+
if self._decoder is None:
|
|
618
|
+
return ""
|
|
619
|
+
|
|
620
|
+
u_data = self.decode(b"", final=True)
|
|
621
|
+
if n < 0 or len(u_data) <= n:
|
|
622
|
+
return u_data
|
|
623
|
+
|
|
624
|
+
self._pending = u_data[n:]
|
|
625
|
+
return u_data[:n]
|
|
626
|
+
|
|
627
|
+
def at_eof(self) -> bool:
|
|
628
|
+
"""Return True if the buffer is empty, nothing is pending, and 'feed_eof' was called."""
|
|
629
|
+
return self._eof and not self._buffer and not self._pending
|
|
630
|
+
|
|
600
631
|
async def readline(self) -> str: # type: ignore[override]
|
|
601
632
|
"""
|
|
602
633
|
Read one line.
|
|
603
634
|
|
|
604
|
-
See ancestor method, :func:`~TelnetReader.readline` for details.
|
|
635
|
+
See ancestor method, :func:`~TelnetReader.readline` for details. When the returned bytes
|
|
636
|
+
are the last of the stream, an incomplete character retained by the incremental decoder is
|
|
637
|
+
finalized, applying ``encoding_errors``.
|
|
638
|
+
|
|
639
|
+
:raises UnicodeDecodeError: if ``encoding_errors`` is ``"strict"`` and the stream
|
|
640
|
+
ends with an incomplete character.
|
|
605
641
|
"""
|
|
642
|
+
if self._pending:
|
|
643
|
+
line, self._pending = self._pending, ""
|
|
644
|
+
return line
|
|
645
|
+
|
|
606
646
|
buf = await super().readline()
|
|
607
|
-
return self.decode(buf)
|
|
647
|
+
return self.decode(buf, final=self._eof and not self._buffer)
|
|
608
648
|
|
|
609
649
|
async def read(self, n: int = -1) -> str: # type: ignore[override]
|
|
610
650
|
"""
|
|
611
|
-
Read up to *n*
|
|
651
|
+
Read up to *n* characters.
|
|
612
652
|
|
|
613
|
-
If the EOF was received and
|
|
653
|
+
If the EOF was received and no characters remain, return an empty string. Bytes retained
|
|
654
|
+
by the incremental decoder are finalized at end of stream, applying
|
|
655
|
+
``encoding_errors`` to an incomplete character: ``"strict"`` raises
|
|
656
|
+
:exc:`UnicodeDecodeError`.
|
|
614
657
|
|
|
615
658
|
:param n: If *n* is not provided, or set to -1, read until EOF and return all characters as
|
|
616
659
|
one large string.
|
|
@@ -621,6 +664,11 @@ class TelnetReaderUnicode(TelnetReader):
|
|
|
621
664
|
if not n:
|
|
622
665
|
return ""
|
|
623
666
|
|
|
667
|
+
if self._pending and n > 0:
|
|
668
|
+
u_data = self._pending[:n]
|
|
669
|
+
self._pending = self._pending[n:]
|
|
670
|
+
return u_data
|
|
671
|
+
|
|
624
672
|
if n < 0:
|
|
625
673
|
# This used to just loop creating a new waiter hoping to
|
|
626
674
|
# collect everything in self._buffer, but that would
|
|
@@ -635,20 +683,35 @@ class TelnetReaderUnicode(TelnetReader):
|
|
|
635
683
|
blocks.append(block)
|
|
636
684
|
return "".join(blocks)
|
|
637
685
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
self.
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
u_data
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
686
|
+
while True:
|
|
687
|
+
if self._exception is not None:
|
|
688
|
+
raise self._exception
|
|
689
|
+
if not self._buffer and not self._eof:
|
|
690
|
+
await self._wait_for_data("read")
|
|
691
|
+
|
|
692
|
+
decoder = self._decoder
|
|
693
|
+
decoder_state = decoder.getstate() if decoder is not None else None
|
|
694
|
+
buf = self.decode(bytes(self._buffer))
|
|
695
|
+
if len(buf) <= n:
|
|
696
|
+
u_data = buf
|
|
697
|
+
self._buffer.clear()
|
|
698
|
+
else:
|
|
699
|
+
# The probe decoded bytes that remain buffered. Restore the
|
|
700
|
+
# decoder before consuming only the requested characters.
|
|
701
|
+
if self._decoder is decoder and decoder_state is not None:
|
|
702
|
+
self._decoder.setstate(decoder_state)
|
|
703
|
+
else:
|
|
704
|
+
self._decoder.reset()
|
|
705
|
+
u_data = ""
|
|
706
|
+
while n > len(u_data):
|
|
707
|
+
u_data += self.decode(bytes([self._buffer.pop(0)]))
|
|
708
|
+
|
|
709
|
+
self._maybe_resume_transport()
|
|
710
|
+
if u_data:
|
|
711
|
+
return u_data
|
|
712
|
+
if self._eof:
|
|
713
|
+
return self._finalize_decoder(n)
|
|
714
|
+
# A partial character is not EOF: wait for the rest of the character.
|
|
652
715
|
|
|
653
716
|
async def readexactly(self, n: int) -> str: # type: ignore[override]
|
|
654
717
|
"""
|
|
@@ -658,6 +721,8 @@ class TelnetReaderUnicode(TelnetReader):
|
|
|
658
721
|
reached before *n* can be read. The
|
|
659
722
|
:attr:`asyncio.IncompleteReadError.partial` attribute of the
|
|
660
723
|
exception contains the partial read characters.
|
|
724
|
+
:raises UnicodeDecodeError: if ``encoding_errors`` is ``"strict"`` and the stream
|
|
725
|
+
ends with an incomplete character.
|
|
661
726
|
"""
|
|
662
727
|
if self._exception is not None:
|
|
663
728
|
raise self._exception
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# std imports
|
|
2
|
+
import io
|
|
3
|
+
import os
|
|
2
4
|
import sys
|
|
3
5
|
import types
|
|
4
6
|
import asyncio
|
|
@@ -224,7 +226,7 @@ def test_transform_args_typescript():
|
|
|
224
226
|
|
|
225
227
|
@pytest.mark.asyncio
|
|
226
228
|
async def test_open_connection_default_factory(bind_host, unused_tcp_port, monkeypatch):
|
|
227
|
-
monkeypatch.setattr(
|
|
229
|
+
monkeypatch.setattr(cl.accessories, "is_a_tty", lambda: False)
|
|
228
230
|
|
|
229
231
|
async with create_server(host=bind_host, port=unused_tcp_port, connect_maxwait=0.5):
|
|
230
232
|
reader, writer = await cl.open_connection(
|
|
@@ -235,10 +237,9 @@ async def test_open_connection_default_factory(bind_host, unused_tcp_port, monke
|
|
|
235
237
|
writer.close()
|
|
236
238
|
|
|
237
239
|
|
|
238
|
-
@pytest.mark.skipif(sys.platform == "win32", reason="TTY factory not used on win32")
|
|
239
240
|
@pytest.mark.asyncio
|
|
240
241
|
async def test_open_connection_tty_factory(bind_host, unused_tcp_port, monkeypatch):
|
|
241
|
-
monkeypatch.setattr(
|
|
242
|
+
monkeypatch.setattr(cl.accessories, "is_a_tty", lambda: True)
|
|
242
243
|
|
|
243
244
|
async with create_server(host=bind_host, port=unused_tcp_port, connect_maxwait=0.5):
|
|
244
245
|
reader, writer = await cl.open_connection(
|
|
@@ -248,6 +249,76 @@ async def test_open_connection_tty_factory(bind_host, unused_tcp_port, monkeypat
|
|
|
248
249
|
writer.close()
|
|
249
250
|
|
|
250
251
|
|
|
252
|
+
@pytest.mark.asyncio
|
|
253
|
+
async def test_open_connection_without_stdin(bind_host, unused_tcp_port, monkeypatch):
|
|
254
|
+
"""Processes without a console, sys.stdin is None, such as pythonw.exe."""
|
|
255
|
+
monkeypatch.setattr(sys, "stdin", None)
|
|
256
|
+
|
|
257
|
+
async with create_server(host=bind_host, port=unused_tcp_port, connect_maxwait=0.5):
|
|
258
|
+
reader, writer = await cl.open_connection(
|
|
259
|
+
host=bind_host, port=unused_tcp_port, connect_maxwait=0.1, encoding=False
|
|
260
|
+
)
|
|
261
|
+
assert isinstance(writer.protocol, cl.TelnetClient)
|
|
262
|
+
assert not isinstance(writer.protocol, cl.TelnetTerminalClient)
|
|
263
|
+
writer.close()
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_is_a_tty_without_stdin(monkeypatch):
|
|
267
|
+
monkeypatch.setattr(sys, "stdin", None)
|
|
268
|
+
assert accessories.is_a_tty() is False
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def test_is_a_tty_stdin_without_isatty(monkeypatch):
|
|
272
|
+
monkeypatch.setattr(sys, "stdin", object())
|
|
273
|
+
assert accessories.is_a_tty() is False
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_is_a_tty_stdin_not_a_tty(monkeypatch):
|
|
277
|
+
monkeypatch.setattr(sys, "stdin", io.StringIO())
|
|
278
|
+
assert accessories.is_a_tty() is False
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def test_is_a_tty_stdin_detached(monkeypatch):
|
|
282
|
+
class _Detached:
|
|
283
|
+
def isatty(self):
|
|
284
|
+
return True
|
|
285
|
+
|
|
286
|
+
def fileno(self):
|
|
287
|
+
raise ValueError("underlying buffer has been detached")
|
|
288
|
+
|
|
289
|
+
monkeypatch.setattr(sys, "stdin", _Detached())
|
|
290
|
+
assert accessories.is_a_tty() is False
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def test_is_a_tty_stdin_unsupported_fileno(monkeypatch):
|
|
294
|
+
class _NoFileno:
|
|
295
|
+
def isatty(self):
|
|
296
|
+
return True
|
|
297
|
+
|
|
298
|
+
monkeypatch.setattr(sys, "stdin", _NoFileno())
|
|
299
|
+
assert accessories.is_a_tty() is False
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def test_is_a_tty_stdin_is_a_tty(monkeypatch):
|
|
303
|
+
class _Tty:
|
|
304
|
+
def isatty(self):
|
|
305
|
+
return True
|
|
306
|
+
|
|
307
|
+
def fileno(self):
|
|
308
|
+
# a file descriptor that os.isatty() agrees is a terminal
|
|
309
|
+
return _Tty.fd
|
|
310
|
+
|
|
311
|
+
pty = pytest.importorskip("pty")
|
|
312
|
+
master_fd, slave_fd = pty.openpty()
|
|
313
|
+
_Tty.fd = slave_fd
|
|
314
|
+
try:
|
|
315
|
+
monkeypatch.setattr(sys, "stdin", _Tty())
|
|
316
|
+
assert accessories.is_a_tty() is True
|
|
317
|
+
finally:
|
|
318
|
+
os.close(slave_fd)
|
|
319
|
+
os.close(master_fd)
|
|
320
|
+
|
|
321
|
+
|
|
251
322
|
def test_detect_syncterm_font_sets_force_binary():
|
|
252
323
|
client = BaseClient.__new__(BaseClient)
|
|
253
324
|
client.log = types.SimpleNamespace(debug=lambda *a, **kw: None, isEnabledFor=lambda _: False)
|
|
@@ -425,3 +425,126 @@ async def test_telnet_reader_readuntil_pattern_cancelled_error(bind_host, unused
|
|
|
425
425
|
client_reader.set_exception(asyncio.CancelledError())
|
|
426
426
|
with pytest.raises(asyncio.CancelledError):
|
|
427
427
|
await client_reader.readuntil_pattern(pattern)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@pytest.mark.parametrize("encoding", ["utf-8", "utf-16", "iso2022_jp"])
|
|
431
|
+
async def test_unicode_read_preserves_decoder_state(encoding):
|
|
432
|
+
"""A size probe must not decode still-buffered bytes twice."""
|
|
433
|
+
reader = telnetlib3.TelnetReaderUnicode(fn_encoding=lambda **_: encoding)
|
|
434
|
+
given = "abc€" if encoding != "iso2022_jp" else "abc日本語"
|
|
435
|
+
encoded = given.encode(encoding)
|
|
436
|
+
reader.feed_data(encoded[:-1])
|
|
437
|
+
assert await reader.read(1) == "a"
|
|
438
|
+
assert await reader.read(1) == "b"
|
|
439
|
+
reader.feed_data(encoded[-1:])
|
|
440
|
+
reader.feed_eof()
|
|
441
|
+
assert await reader.read() == given[2:]
|
|
442
|
+
assert await reader.read() == ""
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@pytest.mark.parametrize("method", ["read", "readexactly"])
|
|
446
|
+
async def test_unicode_read_waits_for_split_character(method):
|
|
447
|
+
"""A partial multibyte character must not look like end of stream."""
|
|
448
|
+
reader = telnetlib3.TelnetReaderUnicode(fn_encoding=lambda **_: "utf-8")
|
|
449
|
+
reader.feed_data(b"\xe2")
|
|
450
|
+
task = asyncio.create_task(getattr(reader, method)(1))
|
|
451
|
+
asyncio.get_running_loop().call_soon(reader.feed_data, b"\x82\xac")
|
|
452
|
+
assert await asyncio.wait_for(task, 1) == "€"
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
@pytest.mark.parametrize(
|
|
456
|
+
"method, args, data, errors, expected",
|
|
457
|
+
[
|
|
458
|
+
("read", (), b"ab\xe2", "replace", "ab�"),
|
|
459
|
+
("read", (), b"ab\xe2", "ignore", "ab"),
|
|
460
|
+
("read", (), b"ab\xe2", "strict", UnicodeDecodeError),
|
|
461
|
+
("readline", (), b"ab\xe2", "replace", "ab�"),
|
|
462
|
+
("readline", (), b"ab\xe2", "ignore", "ab"),
|
|
463
|
+
("readline", (), b"ab\xe2", "strict", UnicodeDecodeError),
|
|
464
|
+
("readexactly", (3,), b"ab\xe2", "replace", "ab�"),
|
|
465
|
+
("readexactly", (3,), b"ab\xe2", "ignore", asyncio.IncompleteReadError),
|
|
466
|
+
("readexactly", (1,), b"\xe2", "strict", UnicodeDecodeError),
|
|
467
|
+
("read", (), b"", "replace", ""),
|
|
468
|
+
],
|
|
469
|
+
)
|
|
470
|
+
async def test_unicode_finalizes_trailing_character(method, args, data, errors, expected):
|
|
471
|
+
"""The character that ends the stream is decoded once by the configured error policy."""
|
|
472
|
+
reader = telnetlib3.TelnetReaderUnicode(fn_encoding=lambda **_: "utf-8", encoding_errors=errors)
|
|
473
|
+
reader.feed_data(data)
|
|
474
|
+
reader.feed_eof()
|
|
475
|
+
if not isinstance(expected, str):
|
|
476
|
+
with pytest.raises(expected):
|
|
477
|
+
await getattr(reader, method)(*args)
|
|
478
|
+
return
|
|
479
|
+
assert await getattr(reader, method)(*args) == expected
|
|
480
|
+
assert await reader.read() == ""
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
async def test_unicode_readline_finalizes_only_last_line():
|
|
484
|
+
"""Only the last of the buffered lines is decoded as end of stream."""
|
|
485
|
+
reader = telnetlib3.TelnetReaderUnicode(fn_encoding=lambda **_: "utf-8")
|
|
486
|
+
reader.feed_data(b"one\ntwo\xe2")
|
|
487
|
+
reader.feed_eof()
|
|
488
|
+
assert [await reader.readline() for _ in range(3)] == ["one\n", "two�", ""]
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
async def test_unicode_read_split_character_over_connection(bind_host, unused_tcp_port):
|
|
492
|
+
"""Read-size probes preserve UTF-8 across separate Telnet network writes."""
|
|
493
|
+
|
|
494
|
+
async def shell(reader, writer):
|
|
495
|
+
writer.write(b"abc\xe2")
|
|
496
|
+
await writer.drain()
|
|
497
|
+
assert await reader.readexactly(1) == b"!"
|
|
498
|
+
writer.write(b"\x82\xac")
|
|
499
|
+
writer.close()
|
|
500
|
+
|
|
501
|
+
async with create_server(host=bind_host, port=unused_tcp_port, shell=shell, encoding=False):
|
|
502
|
+
async with open_connection(host=bind_host, port=unused_tcp_port, encoding="utf-8") as (
|
|
503
|
+
client_reader,
|
|
504
|
+
client_writer,
|
|
505
|
+
):
|
|
506
|
+
assert await asyncio.wait_for(client_reader.read(1), 1) == "a"
|
|
507
|
+
client_writer.write("!")
|
|
508
|
+
await client_writer.drain()
|
|
509
|
+
assert await asyncio.wait_for(client_reader.read(), 1) == "bc€"
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
async def test_unicode_read_partial_character_then_connection_error():
|
|
513
|
+
"""A reset immediately after a partial character must wake the read caller."""
|
|
514
|
+
reader = telnetlib3.TelnetReaderUnicode(fn_encoding=lambda **_: "utf-8")
|
|
515
|
+
task = asyncio.create_task(reader.read(1))
|
|
516
|
+
await asyncio.sleep(0)
|
|
517
|
+
assert reader._waiter is not None
|
|
518
|
+
reader.feed_data(b"\xe2")
|
|
519
|
+
reader.set_exception(ConnectionResetError("connection reset"))
|
|
520
|
+
with pytest.raises(ConnectionResetError, match="connection reset"):
|
|
521
|
+
await asyncio.wait_for(task, 1)
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
async def test_unicode_read_cancelled_partial_character():
|
|
525
|
+
"""Cancellation leaves the partial decoder state usable by the next reader."""
|
|
526
|
+
reader = telnetlib3.TelnetReaderUnicode(fn_encoding=lambda **_: "utf-8")
|
|
527
|
+
reader.feed_data(b"\xe2")
|
|
528
|
+
task = asyncio.create_task(reader.read(1))
|
|
529
|
+
await asyncio.sleep(0)
|
|
530
|
+
assert not task.done()
|
|
531
|
+
task.cancel()
|
|
532
|
+
with pytest.raises(asyncio.CancelledError):
|
|
533
|
+
await task
|
|
534
|
+
asyncio.get_running_loop().call_soon(reader.feed_data, b"\x82\xac")
|
|
535
|
+
assert await asyncio.wait_for(reader.read(1), 1) == "€"
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
@pytest.mark.parametrize("method, args", [("read", (1,)), ("readline", ())])
|
|
539
|
+
async def test_unicode_read_holds_back_finalized_characters(method, args):
|
|
540
|
+
"""A sized read holds back characters decoded past the request."""
|
|
541
|
+
reader = telnetlib3.TelnetReaderUnicode(
|
|
542
|
+
fn_encoding=lambda **_: "utf-8", encoding_errors="surrogateescape"
|
|
543
|
+
)
|
|
544
|
+
reader.feed_data(b"a\xe2\x82")
|
|
545
|
+
reader.feed_eof()
|
|
546
|
+
assert await reader.read(1) == "a"
|
|
547
|
+
assert await reader.read(1) == "\udce2"
|
|
548
|
+
assert not reader.at_eof()
|
|
549
|
+
assert await getattr(reader, method)(*args) == "\udc82"
|
|
550
|
+
assert reader.at_eof()
|
|
@@ -43,6 +43,8 @@ def _run_readline(sequence, max_visible_width=0):
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class MockReader:
|
|
46
|
+
"""Reader of one character per call; a ``None`` entry raises UnicodeDecodeError."""
|
|
47
|
+
|
|
46
48
|
is_binary_reader = False
|
|
47
49
|
|
|
48
50
|
def __init__(self, data):
|
|
@@ -54,6 +56,8 @@ class MockReader:
|
|
|
54
56
|
return ""
|
|
55
57
|
result = self._data[self._idx]
|
|
56
58
|
self._idx += 1
|
|
59
|
+
if result is None:
|
|
60
|
+
raise UnicodeDecodeError("utf-8", b"\xe2", 0, 1, "unexpected end of data")
|
|
57
61
|
return result
|
|
58
62
|
|
|
59
63
|
|
|
@@ -225,6 +229,11 @@ async def test_read_line_inner(input_data, max_len, expected):
|
|
|
225
229
|
assert await gs._read_line_inner(MockReader(list(input_data)), max_len) == expected
|
|
226
230
|
|
|
227
231
|
|
|
232
|
+
@pytest.mark.asyncio
|
|
233
|
+
async def test_read_line_inner_truncated_character_at_eof():
|
|
234
|
+
assert await gs._read_line_inner(MockReader(["h", "i", None]), 100) == "hi"
|
|
235
|
+
|
|
236
|
+
|
|
228
237
|
@pytest.mark.asyncio
|
|
229
238
|
async def test_read_line_with_timeout():
|
|
230
239
|
assert await gs._read_line(MockReader(list("hello\r")), timeout=5.0) == "hello"
|
|
@@ -710,6 +719,19 @@ async def test_filter_ansi_esc_then_eof():
|
|
|
710
719
|
assert not result
|
|
711
720
|
|
|
712
721
|
|
|
722
|
+
@pytest.mark.parametrize(
|
|
723
|
+
"input_chars,expected",
|
|
724
|
+
[
|
|
725
|
+
pytest.param(["a", None], "a", id="after_character"),
|
|
726
|
+
pytest.param([None], "", id="immediately"),
|
|
727
|
+
pytest.param(["\x1b", None], "", id="esc_then_eof"),
|
|
728
|
+
],
|
|
729
|
+
)
|
|
730
|
+
@pytest.mark.asyncio
|
|
731
|
+
async def test_filter_ansi_truncated_character_at_eof(input_chars, expected):
|
|
732
|
+
assert await ss.filter_ansi(MockReader(input_chars), MockWriter()) == expected
|
|
733
|
+
|
|
734
|
+
|
|
713
735
|
class MockBinaryReader:
|
|
714
736
|
is_binary_reader = True
|
|
715
737
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|