summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJeff Forcier <jeff@bitprophet.org>2023-03-10 13:55:53 -0500
committerJeff Forcier <jeff@bitprophet.org>2023-03-10 13:55:53 -0500
commitcbfdc10c8f94e7242420ad43cede3c96c33edc53 (patch)
treed6099c1a11c5964cfefecfc110d10718bf8f9a20
parent08eb98d63f5f03172ce4734096e7013a56c560ac (diff)
parente465da57a07072ccc9500c85cac1e86dda52d19b (diff)
Merge branch 'main' into 2013-int
-rw-r--r--.circleci/config.yml12
-rw-r--r--.codespellrc7
-rw-r--r--.readthedocs.yml2
-rw-r--r--NEWS2
-rw-r--r--dev-requirements.txt4
-rw-r--r--paramiko/agent.py18
-rw-r--r--paramiko/client.py21
-rw-r--r--paramiko/hostkeys.py6
-rw-r--r--paramiko/kex_gss.py4
-rw-r--r--paramiko/server.py15
-rw-r--r--paramiko/sftp_attr.py2
-rw-r--r--paramiko/sftp_client.py8
-rw-r--r--paramiko/sftp_server.py10
-rw-r--r--paramiko/sftp_si.py4
-rw-r--r--paramiko/transport.py10
-rw-r--r--sites/www/changelog.rst9
-rw-r--r--sites/www/conf.py14
-rw-r--r--tests/test_file.py5
-rw-r--r--tests/test_hostkeys.py18
-rw-r--r--tests/test_proxy.py16
-rw-r--r--tests/test_sftp.py8
-rw-r--r--tests/test_transport.py2
22 files changed, 138 insertions, 59 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 1a1a4076..f18b6ff9 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -42,6 +42,16 @@ jobs:
- run: inv test
- orb/debug
+ # TODO: move to orb, rub on other projects too
+ spellcheck:
+ executor:
+ name: orb/default
+ version: "3.6"
+ steps:
+ - orb/setup
+ - run: codespell
+ - orb/debug
+
workflows:
main:
@@ -51,6 +61,8 @@ workflows:
name: Lint
- orb/format:
name: Style check
+ - spellcheck:
+ name: Spellcheck
# Main test run, w/ coverage, and latest-supported cryptography
- orb/coverage:
name: Test
diff --git a/.codespellrc b/.codespellrc
new file mode 100644
index 00000000..f6935aa7
--- /dev/null
+++ b/.codespellrc
@@ -0,0 +1,7 @@
+[codespell]
+# Default ignores, plus built docs and static doc sources
+skip = venvs,.venv,.git,build,*.egg-info,*.lock,*.js,*.css,docs
+# Certain words AUTHOR feels strongly about, plus various proper names that are
+# close enough to real words that they anger codespell. (NOTE: for some reason
+# codespell wants the latter listed in all-lowercase...!)
+ignore-words-list = keypair,flage
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 1f6737f4..144cc4c4 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -1,7 +1,5 @@
version: 2
-formats: all
-
build:
# Specific version of RTD docker image that's new enough to have Rust around
# (so Cryptography can build from source if needed).
diff --git a/NEWS b/NEWS
index 761f8e48..3c9d9abb 100644
--- a/NEWS
+++ b/NEWS
@@ -165,7 +165,7 @@ v1.7.1 (Amy) 10jun07
* SFTPClient.listdir_attr() now preserves the 'longname' field [patch from
wesley augur]
* SFTPClient.get_channel() API added
- * SSHClient constuctor takes an optional 'timeout' parameter [patch from
+ * SSHClient constructor takes an optional 'timeout' parameter [patch from
james bardin]
v1.7 (zubat) 18feb07
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 8ed7eabc..646a4c5f 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -9,12 +9,14 @@ pytest-xdist>=3
flake8>=4,<5
# Formatting!
black>=22.8,<22.9
+# Spelling!
+codespell>=2.2,<2.3
# Coverage!
coverage>=6.2,<7
codecov==2.1.12
# Documentation tools
alabaster==0.7.13
-releases>=2.0
+releases>=2.1
# Debuggery
icecream>=2.1
# Self (sans GSS which is a pain to bother with most of the time)
diff --git a/paramiko/agent.py b/paramiko/agent.py
index c1a07390..1cc9075f 100644
--- a/paramiko/agent.py
+++ b/paramiko/agent.py
@@ -65,6 +65,9 @@ class AgentSSH:
no SSH agent was running (or it couldn't be contacted), an empty list
will be returned.
+ This method performs no IO, just returns the list of keys retreived
+ when the connection was made.
+
:return:
a tuple of `.AgentKey` objects representing keys available on the
SSH agent
@@ -277,6 +280,17 @@ class AgentClientProxy:
class AgentServerProxy(AgentSSH):
"""
+ Allows an SSH server to access a forwarded agent.
+
+ This also creates a unix domain socket on the system to allow external
+ programs to also access the agent. For this reason, you probably only want
+ to create one of these.
+
+ :meth:`connect` must be called before it is usable. This will also load the
+ list of keys the agent contains. You must also call :meth:`close` in
+ order to clean up the unix socket and the thread that maintains it.
+ (:class:`contextlib.closing` might be helpful to you.)
+
:param .Transport t: Transport used for SSH Agent communication forwarding
:raises: `.SSHException` -- mostly if we lost the agent
@@ -314,10 +328,10 @@ class AgentServerProxy(AgentSSH):
def get_env(self):
"""
- Helper for the environnement under unix
+ Helper for the environment under unix
:return:
- a dict containing the ``SSH_AUTH_SOCK`` environnement variables
+ a dict containing the ``SSH_AUTH_SOCK`` environment variables
"""
return {"SSH_AUTH_SOCK": self._get_filename()}
diff --git a/paramiko/client.py b/paramiko/client.py
index a9e4e233..1fe14b07 100644
--- a/paramiko/client.py
+++ b/paramiko/client.py
@@ -324,14 +324,19 @@ class SSHClient(ClosingContextManager):
`.Transport` instance to be used by this client. Defaults to
`.Transport.__init__`.
- :raises:
- `.BadHostKeyException` -- if the server's host key could not be
- verified
- :raises: `.AuthenticationException` -- if authentication failed
- :raises:
- `.SSHException` -- if there was any other error connecting or
- establishing an SSH session
- :raises socket.error: if a socket error occurred while connecting
+ :raises BadHostKeyException:
+ if the server's host key could not be verified.
+ :raises AuthenticationException: if authentication failed.
+ :raises socket.error:
+ if a socket error (other than connection-refused or
+ host-unreachable) occurred while connecting.
+ :raises NoValidConnectionsError:
+ if all valid connection targets for the requested hostname (eg IPv4
+ and IPv6) yielded connection-refused or host-unreachable socket
+ errors.
+ :raises SSHException:
+ if there was any other error connecting or establishing an SSH
+ session.
.. versionchanged:: 1.15
Added the ``banner_timeout``, ``gss_auth``, ``gss_kex``,
diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py
index b189aac6..bbfa5755 100644
--- a/paramiko/hostkeys.py
+++ b/paramiko/hostkeys.py
@@ -20,6 +20,7 @@
from base64 import encodebytes, decodebytes
import binascii
import os
+import re
from collections.abc import MutableMapping
from hashlib import sha1
@@ -328,7 +329,8 @@ class HostKeyEntry:
"""
Parses the given line of text to find the names for the host,
the type of key, and the key data. The line is expected to be in the
- format used by the OpenSSH known_hosts file.
+ format used by the OpenSSH known_hosts file. Fields are separated by a
+ single space or tab.
Lines are expected to not have leading or trailing whitespace.
We don't bother to check for comments or empty lines. All of
@@ -337,7 +339,7 @@ class HostKeyEntry:
:param str line: a line from an OpenSSH known_hosts file
"""
log = get_logger("paramiko.hostkeys")
- fields = line.split(" ")
+ fields = re.split(" |\t", line)
if len(fields) < 3:
# Bad number of fields
msg = "Not enough fields found in known_hosts in line {} ({!r})"
diff --git a/paramiko/kex_gss.py b/paramiko/kex_gss.py
index e3fbb36e..2a5f29e3 100644
--- a/paramiko/kex_gss.py
+++ b/paramiko/kex_gss.py
@@ -125,7 +125,7 @@ class KexGSSGroup1:
Parse the next packet.
:param ptype: The (string) type of the incoming packet
- :param `.Message` m: The paket content
+ :param `.Message` m: The packet content
"""
if self.transport.server_mode and (ptype == MSG_KEXGSS_INIT):
return self._parse_kexgss_init(m)
@@ -380,7 +380,7 @@ class KexGSSGex:
Parse the next packet.
:param ptype: The (string) type of the incoming packet
- :param `.Message` m: The paket content
+ :param `.Message` m: The packet content
"""
if ptype == MSG_KEXGSS_GROUPREQ:
return self._parse_kexgss_groupreq(m)
diff --git a/paramiko/server.py b/paramiko/server.py
index b68607e1..6b0bb0f6 100644
--- a/paramiko/server.py
+++ b/paramiko/server.py
@@ -255,7 +255,7 @@ class ServerInterface:
We don't check if the krb5 principal is allowed to log in on
the server, because there is no way to do that in python. So
if you develop your own SSH server with paramiko for a cetain
- plattform like Linux, you should call C{krb5_kuserok()} in
+ platform like Linux, you should call C{krb5_kuserok()} in
your local kerberos library to make sure that the
krb5_principal has an account on the server and is allowed to
log in as a user.
@@ -287,7 +287,7 @@ class ServerInterface:
We don't check if the krb5 principal is allowed to log in on
the server, because there is no way to do that in python. So
if you develop your own SSH server with paramiko for a cetain
- plattform like Linux, you should call C{krb5_kuserok()} in
+ platform like Linux, you should call C{krb5_kuserok()} in
your local kerberos library to make sure that the
krb5_principal has an account on the server and is allowed
to log in as a user.
@@ -454,10 +454,10 @@ class ServerInterface:
subsystem; ``False`` if that subsystem can't or won't be provided.
"""
transport = channel.get_transport()
- handler_class, larg, kwarg = transport._get_subsystem_handler(name)
+ handler_class, args, kwargs = transport._get_subsystem_handler(name)
if handler_class is None:
return False
- handler = handler_class(channel, name, self, *larg, **kwarg)
+ handler = handler_class(channel, name, self, *args, **kwargs)
handler.start()
return True
@@ -517,6 +517,9 @@ class ServerInterface:
:param .Channel channel: the `.Channel` the request arrived on
:return: ``True`` if the AgentForward was loaded; ``False`` if not
+
+ If ``True`` is returned, the server should create an
+ :class:`AgentServerProxy` to access the agent.
"""
return False
@@ -634,7 +637,7 @@ class InteractiveQuery:
class SubsystemHandler(threading.Thread):
"""
- Handler for a subsytem in server mode. If you create a subclass of this
+ Handler for a subsystem in server mode. If you create a subclass of this
class and pass it to `.Transport.set_subsystem_handler`, an object of this
class will be created for each request for this subsystem. Each new object
will be executed within its own new thread by calling `start_subsystem`.
@@ -642,7 +645,7 @@ class SubsystemHandler(threading.Thread):
For example, if you made a subclass ``MP3Handler`` and registered it as the
handler for subsystem ``"mp3"``, then whenever a client has successfully
- authenticated and requests subsytem ``"mp3"``, an object of class
+ authenticated and requests subsystem ``"mp3"``, an object of class
``MP3Handler`` will be created, and `start_subsystem` will be called on
it from a new thread.
"""
diff --git a/paramiko/sftp_attr.py b/paramiko/sftp_attr.py
index eb4dd900..18ffbf86 100644
--- a/paramiko/sftp_attr.py
+++ b/paramiko/sftp_attr.py
@@ -24,7 +24,7 @@ from paramiko.common import x80000000, o700, o70, xffffffff
class SFTPAttributes:
"""
Representation of the attributes of a file (or proxied file) for SFTP in
- client or server mode. It attemps to mirror the object returned by
+ client or server mode. It attempts to mirror the object returned by
`os.stat` as closely as possible, so it may have the following fields,
with the same meanings as those returned by an `os.stat` object:
diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py
index d91a3951..31ac1292 100644
--- a/paramiko/sftp_client.py
+++ b/paramiko/sftp_client.py
@@ -817,17 +817,17 @@ class SFTPClient(BaseSFTP, ClosingContextManager):
# ...internals...
- def _request(self, t, *arg):
- num = self._async_request(type(None), t, *arg)
+ def _request(self, t, *args):
+ num = self._async_request(type(None), t, *args)
return self._read_response(num)
- def _async_request(self, fileobj, t, *arg):
+ def _async_request(self, fileobj, t, *args):
# this method may be called from other threads (prefetch)
self._lock.acquire()
try:
msg = Message()
msg.add_int(self.request_number)
- for item in arg:
+ for item in args:
if isinstance(item, int64):
msg.add_int64(item)
elif isinstance(item, int):
diff --git a/paramiko/sftp_server.py b/paramiko/sftp_server.py
index 6cb7ec62..cd3910dc 100644
--- a/paramiko/sftp_server.py
+++ b/paramiko/sftp_server.py
@@ -98,7 +98,7 @@ class SFTPServer(BaseSFTP, SubsystemHandler):
name,
server,
sftp_si=SFTPServerInterface,
- *largs,
+ *args,
**kwargs
):
"""
@@ -124,7 +124,7 @@ class SFTPServer(BaseSFTP, SubsystemHandler):
# map of handle-string to SFTPHandle for files & folders:
self.file_table = {}
self.folder_table = {}
- self.server = sftp_si(server, *largs, **kwargs)
+ self.server = sftp_si(server, *args, **kwargs)
def _log(self, level, msg):
if issubclass(type(msg), list):
@@ -221,10 +221,10 @@ class SFTPServer(BaseSFTP, SubsystemHandler):
# ...internals...
- def _response(self, request_number, t, *arg):
+ def _response(self, request_number, t, *args):
msg = Message()
msg.add_int(request_number)
- for item in arg:
+ for item in args:
# NOTE: this is a very silly tiny class used for SFTPFile mostly
if isinstance(item, int64):
msg.add_int64(item)
@@ -259,7 +259,7 @@ class SFTPServer(BaseSFTP, SubsystemHandler):
desc = SFTP_DESC[code]
except IndexError:
desc = "Unknown"
- # some clients expect a "langauge" tag at the end
+ # some clients expect a "language" tag at the end
# (but don't mind it being blank)
self._response(request_number, CMD_STATUS, code, desc, "")
diff --git a/paramiko/sftp_si.py b/paramiko/sftp_si.py
index 26b0ac9b..72b5db94 100644
--- a/paramiko/sftp_si.py
+++ b/paramiko/sftp_si.py
@@ -40,7 +40,7 @@ class SFTPServerInterface:
clients & servers obey the requirement that paths be encoded in UTF-8.
"""
- def __init__(self, server, *largs, **kwargs):
+ def __init__(self, server, *args, **kwargs):
"""
Create a new SFTPServerInterface object. This method does nothing by
default and is meant to be overridden by subclasses.
@@ -48,7 +48,7 @@ class SFTPServerInterface:
:param .ServerInterface server:
the server object associated with this channel and SFTP subsystem
"""
- super().__init__(*largs, **kwargs)
+ super().__init__(*args, **kwargs)
def session_started(self):
"""
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 0c6c3ad1..98cdae03 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -1010,7 +1010,7 @@ class Transport(threading.Thread, ClosingContextManager):
:raises:
`.SSHException` -- if the request is rejected, the session ends
- prematurely or there is a timeout openning a channel
+ prematurely or there is a timeout opening a channel
.. versionchanged:: 1.15
Added the ``window_size`` and ``max_packet_size`` arguments.
@@ -1417,7 +1417,7 @@ class Transport(threading.Thread, ClosingContextManager):
finally:
self.lock.release()
- def set_subsystem_handler(self, name, handler, *larg, **kwarg):
+ def set_subsystem_handler(self, name, handler, *args, **kwargs):
"""
Set the handler class for a subsystem in server mode. If a request
for this subsystem is made on an open ssh channel later, this handler
@@ -1433,7 +1433,7 @@ class Transport(threading.Thread, ClosingContextManager):
"""
try:
self.lock.acquire()
- self.subsystem_table[name] = (handler, larg, kwarg)
+ self.subsystem_table[name] = (handler, args, kwargs)
finally:
self.lock.release()
@@ -1694,7 +1694,7 @@ class Transport(threading.Thread, ClosingContextManager):
def auth_interactive_dumb(self, username, handler=None, submethods=""):
"""
- Autenticate to the server interactively but dumber.
+ Authenticate to the server interactively but dumber.
Just print the prompt and / or instructions to stdout and send back
the response. This is good for situations where partial auth is
achieved by key and then the user has to enter a 2fac token.
@@ -2060,7 +2060,7 @@ class Transport(threading.Thread, ClosingContextManager):
reply.add_string("")
reply.add_string("en")
# NOTE: Post-open channel messages do not need checking; the above will
- # reject attemps to open channels, meaning that even if a malicious
+ # reject attempts to open channels, meaning that even if a malicious
# user tries to send a MSG_CHANNEL_REQUEST, it will simply fall under
# the logic that handles unknown channel IDs (as the channel list will
# be empty.)
diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst
index 32e81828..b13fe9e0 100644
--- a/sites/www/changelog.rst
+++ b/sites/www/changelog.rst
@@ -2,6 +2,13 @@
Changelog
=========
+- :feature:`2173` Accept single tabs as field separators (in addition to
+ single spaces) in `<paramiko.hostkeys.HostKeyEntry.from_line>` for parity
+ with OpenSSH's KnownHosts parser. Patched by Alex Chavkin.
+- :support:`2178 backported` Apply ``codespell`` to the codebase, which found a
+ lot of very old minor spelling mistakes in docstrings. Also modernize many
+ instances of ``*largs`` vs ``*args`` and ``**kwarg`` vs ``**kwargs``. Patch
+ courtesy of Yaroslav Halchenko, with review from Brian Skinn.
- :release:`3.0.0 <2023-01-20>`
- :bug:`2110 major` Remove some unnecessary ``__repr__`` calls when handling
bytes-vs-str conversions. This was apparently doing a lot of unintentional
@@ -1130,7 +1137,7 @@ Changelog
functionality to address hangs from dropped network connections and/or failed
handshakes. Credit to ``@vazir`` and ``@dacut`` for the original patches and
to Olle Lundberg for reimplementation.
-- :bug:`490` Skip invalid/unparseable lines in ``known_hosts`` files, instead
+- :bug:`490` Skip invalid/unparsable lines in ``known_hosts`` files, instead
of raising `~paramiko.ssh_exception.SSHException`. This brings Paramiko's
behavior more in line with OpenSSH, which silently ignores such input. Catch
& patch courtesy of Martin Topholm.
diff --git a/sites/www/conf.py b/sites/www/conf.py
index 00944871..179f0b7f 100644
--- a/sites/www/conf.py
+++ b/sites/www/conf.py
@@ -1,22 +1,26 @@
# Obtain shared config values
-import sys
+from pathlib import Path
import os
-from os.path import abspath, join, dirname
+import sys
-sys.path.append(abspath(join(dirname(__file__), "..")))
+updir = Path(__file__).parent.parent.resolve()
+sys.path.append(str(updir))
from shared_conf import *
# Releases changelog extension
extensions.append("releases")
releases_release_uri = "https://github.com/paramiko/paramiko/tree/%s"
releases_issue_uri = "https://github.com/paramiko/paramiko/issues/%s"
+releases_development_branch = "main"
+# Don't show unreleased_X.x sections up top for 1.x or 2.x anymore
+releases_supported_versions = [3]
# Default is 'local' building, but reference the public docs site when building
# under RTD.
-target = join(dirname(__file__), "..", "docs", "_build")
+target = updir / "docs" / "_build"
if os.environ.get("READTHEDOCS") == "True":
target = "http://docs.paramiko.org/en/latest/"
-intersphinx_mapping["docs"] = (target, None)
+intersphinx_mapping["docs"] = (str(target), None)
# Sister-site links to API docs
html_theme_options["extra_nav_links"] = {
diff --git a/tests/test_file.py b/tests/test_file.py
index 364bbce2..456c0388 100644
--- a/tests/test_file.py
+++ b/tests/test_file.py
@@ -127,8 +127,9 @@ class BufferedFileTest(unittest.TestCase):
f.write("Not\nquite\n512 bytes.\n")
self.assertEqual(f.read(1), b"")
f.flush()
- self.assertEqual(f.read(5), b"Not\nq")
- self.assertEqual(f.read(10), b"uite\n512 b")
+ self.assertEqual(f.read(6), b"Not\nqu")
+ self.assertEqual(f.read(4), b"ite\n")
+ self.assertEqual(f.read(5), b"512 b")
self.assertEqual(f.read(9), b"ytes.\n")
self.assertEqual(f.read(3), b"")
f.close()
diff --git a/tests/test_hostkeys.py b/tests/test_hostkeys.py
index ebcc40f5..bdda295a 100644
--- a/tests/test_hostkeys.py
+++ b/tests/test_hostkeys.py
@@ -38,6 +38,18 @@ BGQ3GQ/Fc7SX6gkpXkwcZryoi4kNFhHu5LvHcZPdxXV1D+uTMfGS1eyd2Yz/DoNWXNAl8TI0cAsW\
5ymME3bQ4J/k1IKxCtz/bAlAqFgKoc+EolMziDYqWIATtW0rYTJvzGAzTmMj80/QpsFH+Pc2M=
"""
+test_hosts_file_tabs = """\
+secure.example.com\tssh-rsa\tAAAAB3NzaC1yc2EAAAABIwAAAIEA1PD6U2/TVxET6lkpKhOk5r\
+9q/kAYG6sP9f5zuUYP8i7FOFp/6ncCEbbtg/lB+A3iidyxoSWl+9jtoyyDOOVX4UIDV9G11Ml8om3\
+D+jrpI9cycZHqilK0HmxDeCuxbwyMuaCygU9gS2qoRvNLWZk70OpIKSSpBo0Wl3/XUmz9uhc=
+happy.example.com\tssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA8bP1ZA7DCZDB9J0s50l31M\
+BGQ3GQ/Fc7SX6gkpXkwcZryoi4kNFhHu5LvHcZPdxXV1D+uTMfGS1eyd2Yz/DoNWXNAl8TI0cAsW\
+5ymME3bQ4J/k1IKxCtz/bAlAqFgKoc+EolMziDYqWIATtW0rYTJvzGAzTmMj80/QpsFH+Pc2M=
+doublespace.example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA1PD6U2/TVxET6lkp\
+KhOk5r9q/kAYG6sP9f5zuUYP8i7FOFp/6ncCEbbtg/lB+A3iidyxoSWl+9jtoyyDOOVX4UIDV9G11M\
+l8om3D+jrpI9cycZHqilK0HmxDeCuxbwyMuaCygU9gS2qoRvNLWZk70OpIKSSpBo0Wl3/XUmz8BtZ=
+"""
+
keyblob = b"""\
AAAAB3NzaC1yc2EAAAABIwAAAIEA8bP1ZA7DCZDB9J0s50l31MBGQ3GQ/Fc7SX6gkpXkwcZryoi4k\
NFhHu5LvHcZPdxXV1D+uTMfGS1eyd2Yz/DoNWXNAl8TI0cAsW5ymME3bQ4J/k1IKxCtz/bAlAqFgK\
@@ -147,3 +159,9 @@ class HostKeysTest(unittest.TestCase):
pass # Good
else:
assert False, "Key was not deleted from Entry on delitem!"
+
+
+class HostKeysTabsTest(HostKeysTest):
+ def setUp(self):
+ with open("hostfile.temp", "w") as f:
+ f.write(test_hosts_file_tabs)
diff --git a/tests/test_proxy.py b/tests/test_proxy.py
index 83bdc040..22c2c9c3 100644
--- a/tests/test_proxy.py
+++ b/tests/test_proxy.py
@@ -43,14 +43,20 @@ class TestProxyCommand:
stdout = Popen.return_value.stdout
select.return_value = [stdout], None, None
fileno = stdout.fileno.return_value
- # Intentionally returning <5 at a time sometimes
- os_read.side_effect = [b"was", b"te", b"of ti", b"me"]
+ # Force os.read to return smaller-than-requested chunks
+ os_read.side_effect = [b"was", b"t", b"e", b"of ti", b"me"]
proxy = ProxyCommand("hi")
+ # Ask for 5 bytes (ie b"waste")
data = proxy.recv(5)
+ # Ensure we got "waste" stitched together
assert data == b"waste"
+ # Ensure the calls happened in the sizes expected (starting with the
+ # initial "I want all 5 bytes", followed by "I want whatever I believe
+ # should be left after what I've already read", until done)
assert [x[0] for x in os_read.call_args_list] == [
- (fileno, 5),
- (fileno, 2),
+ (fileno, 5), # initial
+ (fileno, 2), # I got 3, want 2 more
+ (fileno, 1), # I've now got 4, want 1 more
]
@patch("paramiko.proxy.subprocess.Popen")
@@ -122,7 +128,7 @@ class TestProxyCommand:
select.return_value = [stdout], None, None
# Base case: None timeout means no timing out
os_read.return_value = b"meh"
- proxy = ProxyCommand("yello")
+ proxy = ProxyCommand("hello")
assert proxy.timeout is None
# Implicit 'no raise' check
assert proxy.recv(3) == b"meh"
diff --git a/tests/test_sftp.py b/tests/test_sftp.py
index 2cd68d94..be123de4 100644
--- a/tests/test_sftp.py
+++ b/tests/test_sftp.py
@@ -46,7 +46,7 @@ ARTICLE = """
Insulin sensitivity and liver insulin receptor structure in ducks from two
genera
-T. Constans, B. Chevalier, M. Derouet and J. Simon
+T. Constantine, B. Chevalier, M. Derouet and J. Simon
Station de Recherches Avicoles, Institut National de la Recherche Agronomique,
Nouzilly, France.
@@ -129,7 +129,7 @@ class TestSFTP:
try:
with sftp.open(sftp.FOLDER + "/duck.txt", "w") as f:
f.write(ARTICLE)
- assert sftp.stat(sftp.FOLDER + "/duck.txt").st_size == 1483
+ assert sftp.stat(sftp.FOLDER + "/duck.txt").st_size == 1486
finally:
sftp.remove(sftp.FOLDER + "/duck.txt")
@@ -140,7 +140,7 @@ class TestSFTP:
try:
with sftp.open(sftp.FOLDER + "/duck.txt", "w") as f:
f.write(ARTICLE)
- assert sftp.stat(sftp.FOLDER + "/duck.txt").st_size == 1483
+ assert sftp.stat(sftp.FOLDER + "/duck.txt").st_size == 1486
finally:
sftp.remove(sftp.FOLDER + "/duck.txt")
@@ -724,7 +724,7 @@ class TestSFTP:
def test_seek_append(self, sftp):
"""
- verify that seek does't affect writes during append.
+ verify that seek doesn't affect writes during append.
does not work except through paramiko. :( openssh fails.
"""
diff --git a/tests/test_transport.py b/tests/test_transport.py
index 4d28199a..4062d767 100644
--- a/tests/test_transport.py
+++ b/tests/test_transport.py
@@ -891,7 +891,7 @@ class TransportTest(unittest.TestCase):
@slow
def test_handshake_timeout(self):
"""
- verify that we can get a hanshake timeout.
+ verify that we can get a handshake timeout.
"""
# Tweak client Transport instance's Packetizer instance so
# its read_message() sleeps a bit. This helps prevent race conditions