diff options
-rw-r--r-- | .travis.yml | 4 | ||||
-rw-r--r-- | README.rst | 4 | ||||
-rw-r--r-- | codecov.yml | 1 | ||||
-rw-r--r-- | paramiko/client.py | 1 | ||||
-rw-r--r-- | paramiko/common.py | 22 | ||||
-rw-r--r-- | paramiko/file.py | 6 | ||||
-rw-r--r-- | paramiko/hostkeys.py | 7 | ||||
-rw-r--r-- | paramiko/kex_gss.py | 2 | ||||
-rw-r--r-- | paramiko/primes.py | 1 | ||||
-rw-r--r-- | paramiko/py3compat.py | 11 | ||||
-rw-r--r-- | paramiko/resource.py | 71 | ||||
-rw-r--r-- | paramiko/sftp_client.py | 11 | ||||
-rw-r--r-- | paramiko/transport.py | 18 | ||||
-rw-r--r-- | paramiko/win_pageant.py | 2 | ||||
-rw-r--r-- | sites/www/changelog.rst | 27 | ||||
-rw-r--r-- | tests/__init__.py | 36 | ||||
-rw-r--r-- | tests/test_client.py | 15 | ||||
-rwxr-xr-x | tests/test_file.py | 57 | ||||
-rwxr-xr-x | tests/test_sftp.py | 30 | ||||
-rw-r--r-- | tests/test_transport.py | 69 |
20 files changed, 243 insertions, 152 deletions
diff --git a/.travis.yml b/.travis.yml index c8faf0a2..7ca8db09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ install: # Self-install for setup.py-driven deps - pip install -e . # Dev (doc/test running) requirements - - pip install coveralls # For coveralls.io specifically + - pip install codecov # For codecov specifically - pip install -r dev-requirements.txt script: # Main tests, w/ coverage! @@ -33,4 +33,4 @@ notifications: on_failure: change email: false after_success: - - coveralls + - codecov @@ -6,8 +6,8 @@ Paramiko .. image:: https://travis-ci.org/paramiko/paramiko.svg?branch=master :target: https://travis-ci.org/paramiko/paramiko -.. image:: https://coveralls.io/repos/paramiko/paramiko/badge.svg?branch=master&service=github - :target: https://coveralls.io/github/paramiko/paramiko?branch=master +.. image:: https://codecov.io/gh/paramiko/paramiko/branch/master/graph/badge.svg + :target: https://codecov.io/gh/paramiko/paramiko :Paramiko: Python SSH module :Copyright: Copyright (c) 2003-2009 Robey Pointer <robeypointer@gmail.com> diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..69cb7601 --- /dev/null +++ b/codecov.yml @@ -0,0 +1 @@ +comment: false diff --git a/paramiko/client.py b/paramiko/client.py index 55365705..32878c72 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -34,7 +34,6 @@ from paramiko.dsskey import DSSKey from paramiko.ecdsakey import ECDSAKey from paramiko.hostkeys import HostKeys from paramiko.py3compat import string_types -from paramiko.resource import ResourceManager from paramiko.rsakey import RSAKey from paramiko.ssh_exception import ( SSHException, BadHostKeyException, NoValidConnectionsError diff --git a/paramiko/common.py b/paramiko/common.py index 556f046a..0012372a 100644 --- a/paramiko/common.py +++ b/paramiko/common.py @@ -20,9 +20,7 @@ Common constants and global variables. """ import logging -from paramiko.py3compat import ( - byte_chr, PY2, bytes_types, string_types, b, long, -) +from paramiko.py3compat import byte_chr, PY2, bytes_types, text_type, long MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG, \ MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT = range(1, 7) @@ -163,14 +161,16 @@ else: def asbytes(s): - if not isinstance(s, bytes_types): - if isinstance(s, string_types): - s = b(s) - else: - try: - s = s.asbytes() - except Exception: - raise Exception('Unknown type') + """Coerce to bytes if possible or return unchanged.""" + if isinstance(s, bytes_types): + return s + if isinstance(s, text_type): + # Accept text and encode as utf-8 for compatibility only. + return s.encode("utf-8") + asbytes = getattr(s, "asbytes", None) + if asbytes is not None: + return asbytes() + # May be an object that implements the buffer api, let callers handle. return s diff --git a/paramiko/file.py b/paramiko/file.py index 5212091a..a1bdafbe 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -18,7 +18,7 @@ from paramiko.common import ( linefeed_byte_value, crlf, cr_byte, linefeed_byte, cr_byte_value, ) -from paramiko.py3compat import BytesIO, PY2, u, b, bytes_types +from paramiko.py3compat import BytesIO, PY2, u, bytes_types, text_type from paramiko.util import ClosingContextManager @@ -391,7 +391,9 @@ class BufferedFile (ClosingContextManager): :param data: ``str``/``bytes`` data to write """ - data = b(data) + if isinstance(data, text_type): + # Accept text and encode as utf-8 for compatibility only. + data = data.encode('utf-8') if self._closed: raise IOError('File is closed') if not (self._flags & self.FLAG_WRITE): diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 008ba592..c873f58b 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -20,17 +20,12 @@ import binascii import os +from collections import MutableMapping from hashlib import sha1 from hmac import HMAC from paramiko.py3compat import b, u, encodebytes, decodebytes -try: - from collections import MutableMapping -except ImportError: - # noinspection PyUnresolvedReferences - from UserDict import DictMixin as MutableMapping - from paramiko.dsskey import DSSKey from paramiko.rsakey import RSAKey from paramiko.util import get_logger, constant_time_bytes_eq diff --git a/paramiko/kex_gss.py b/paramiko/kex_gss.py index ba24c0a0..3406babb 100644 --- a/paramiko/kex_gss.py +++ b/paramiko/kex_gss.py @@ -40,7 +40,7 @@ This module provides GSS-API / SSPI Key Exchange as defined in :rfc:`4462`. import os from hashlib import sha1 -from paramiko.common import * # noqa +from paramiko.common import DEBUG, max_byte, zero_byte from paramiko import util from paramiko.message import Message from paramiko.py3compat import byte_chr, byte_mask, byte_ord diff --git a/paramiko/primes.py b/paramiko/primes.py index 48a34e53..65617914 100644 --- a/paramiko/primes.py +++ b/paramiko/primes.py @@ -25,7 +25,6 @@ import os from paramiko import util from paramiko.py3compat import byte_mask, long from paramiko.ssh_exception import SSHException -from paramiko.common import * # noqa def _roll_random(n): diff --git a/paramiko/py3compat.py b/paramiko/py3compat.py index 095b0d09..6703ace8 100644 --- a/paramiko/py3compat.py +++ b/paramiko/py3compat.py @@ -65,15 +65,8 @@ if PY2: return s - try: - import cStringIO - - StringIO = cStringIO.StringIO # NOQA - except ImportError: - import StringIO - - StringIO = StringIO.StringIO # NOQA - + import cStringIO + StringIO = cStringIO.StringIO BytesIO = StringIO diff --git a/paramiko/resource.py b/paramiko/resource.py deleted file mode 100644 index 5fed22ad..00000000 --- a/paramiko/resource.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> -# -# This file is part of paramiko. -# -# Paramiko is free software; you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation; either version 2.1 of the License, or (at your option) -# any later version. -# -# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Paramiko; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - -""" -Resource manager. -""" - -import weakref - - -class ResourceManager (object): - """ - A registry of objects and resources that should be closed when those - objects are deleted. - - This is meant to be a safer alternative to Python's ``__del__`` method, - which can cause reference cycles to never be collected. Objects registered - with the ResourceManager can be collected but still free resources when - they die. - - Resources are registered using `register`, and when an object is garbage - collected, each registered resource is closed by having its ``close()`` - method called. Multiple resources may be registered per object, but a - resource will only be closed once, even if multiple objects register it. - (The last object to register it wins.) - """ - - def __init__(self): - self._table = {} - - def register(self, obj, resource): - """ - Register a resource to be closed with an object is collected. - - When the given ``obj`` is garbage-collected by the Python interpreter, - the ``resource`` will be closed by having its ``close()`` method - called. Any exceptions are ignored. - - :param object obj: the object to track - :param object resource: - the resource to close when the object is collected - """ - def callback(ref): - try: - resource.close() - except: - pass - del self._table[id(resource)] - - # keep the weakref in a table so it sticks around long enough to get - # its callback called. :) - self._table[id(resource)] = weakref.ref(obj, callback) - - -# singleton -ResourceManager = ResourceManager() diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 12fccb2f..ee5ab073 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -28,9 +28,7 @@ from paramiko import util from paramiko.channel import Channel from paramiko.message import Message from paramiko.common import INFO, DEBUG, o777 -from paramiko.py3compat import ( - bytestring, b, u, long, string_types, bytes_types, -) +from paramiko.py3compat import bytestring, b, u, long from paramiko.sftp import ( BaseSFTP, CMD_OPENDIR, CMD_HANDLE, SFTPError, CMD_READDIR, CMD_NAME, CMD_CLOSE, SFTP_FLAG_READ, SFTP_FLAG_WRITE, SFTP_FLAG_CREATE, @@ -758,13 +756,12 @@ class SFTPClient(BaseSFTP, ClosingContextManager): msg.add_int64(item) elif isinstance(item, int): msg.add_int(item) - elif isinstance(item, (string_types, bytes_types)): - msg.add_string(item) elif isinstance(item, SFTPAttributes): item._pack(msg) else: - raise Exception( - 'unknown type for %r type %r' % (item, type(item))) + # For all other types, rely on as_string() to either coerce + # to bytes before writing or raise a suitable exception. + msg.add_string(item) num = self.request_number self._expecting[num] = fileobj self.request_number += 1 diff --git a/paramiko/transport.py b/paramiko/transport.py index f0952e1b..d26af833 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -107,9 +107,9 @@ class Transport(threading.Thread, ClosingContextManager): 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', - 'blowfish-cbc', 'aes192-cbc', 'aes256-cbc', + 'blowfish-cbc', '3des-cbc', ) _preferred_macs = ( @@ -277,7 +277,6 @@ class Transport(threading.Thread, ClosingContextManager): arguments. """ self.active = False - self._sshclient = None if isinstance(sock, string_types): # convert "host:port" into (host, port) @@ -311,14 +310,9 @@ class Transport(threading.Thread, ClosingContextManager): threading.Thread.__init__(self) self.setDaemon(True) self.sock = sock - # Python < 2.3 doesn't have the settimeout method - RogerB - try: - # we set the timeout so we can check self.active periodically to - # see if we should bail. socket.timeout exception is never - # propagated. - self.sock.settimeout(self._active_check_timeout) - except AttributeError: - pass + # we set the timeout so we can check self.active periodically to + # see if we should bail. socket.timeout exception is never propagated. + self.sock.settimeout(self._active_check_timeout) # negotiated crypto parameters self.packetizer = Packetizer(sock) @@ -651,9 +645,6 @@ class Transport(threading.Thread, ClosingContextManager): Transport._modulus_pack = None return False - def set_sshclient(self, sshclient): - self._sshclient = sshclient - def close(self): """ Close this session, and any open channels that are tied to it. @@ -664,7 +655,6 @@ class Transport(threading.Thread, ClosingContextManager): for chan in list(self._channels.values()): chan._unlink() self.sock.close() - self._sshclient = None def get_remote_server_key(self): """ diff --git a/paramiko/win_pageant.py b/paramiko/win_pageant.py index c8c2c7bc..fda3b9c1 100644 --- a/paramiko/win_pageant.py +++ b/paramiko/win_pageant.py @@ -25,7 +25,7 @@ import array import ctypes.wintypes import platform import struct -from paramiko.util import * # noqa +from paramiko.common import zero_byte from paramiko.py3compat import b try: diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 6ab67985..3b2a7500 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -3,9 +3,19 @@ Changelog ========= * :bug:`865` SSHClient requests the type of host key it has (e.g. from - known_hosts) and does not consider a different type to be a "Missing" - host key. This fixes a common case where an ecdsa key is in known_hosts and - the server also has an rsa host key. Thanks to Pierce Lopez. + known_hosts) and does not consider a different type to be a "Missing" host + key. This fixes a common case where an ECDSA key is in known_hosts and the + server also has an RSA host key. Thanks to Pierce Lopez. +* :support:`906 (1.18+)` Clean up a handful of outdated imports and related + tweaks. Thanks to Pierce Lopez. +* :bug:`984` Enhance default cipher preference order such that + ``aes(192|256)-cbc`` are preferred over ``blowfish-cbc``. Thanks to Alex + Gaynor. +* :bug:`971 (1.17+)` Allow any type implementing the buffer API to be used with + `BufferedFile <paramiko.file.BufferedFile>`, `Channel + <paramiko.channel.Channel>`, and `SFTPFile <paramiko.sftp_file.SFTPFile>`. + This resolves a regression introduced in 1.13 with the Python 3 porting + changes, when using types such as ``memoryview``. Credit: Martin Packman. * :bug:`741` (also :issue:`809`, :issue:`772`; all via :issue:`912`) Writing encrypted/password-protected private key files was silently broken since 2.0 due to an incorrect API call; this has been fixed. @@ -16,6 +26,9 @@ Changelog Thanks to ``@virlos`` for the original report, Chris Harris and ``@ibuler`` for initial draft PRs, and ``@jhgorrell`` for the final patch. +* :support:`956 (1.17+)` Switch code coverage service from coveralls.io to + codecov.io (& then disable the latter's auto-comments.) Thanks to Nikolai + Røed Kristiansen for the patch. * :bug:`983` Move ``sha1`` above the now-arguably-broken ``md5`` in the list of preferred MAC algorithms, as an incremental security improvement for users whose target systems offer both. Credit: Pierce Lopez. @@ -40,8 +53,12 @@ Changelog (i.e. passes the maintainer's preferred `flake8 <http://flake8.pycqa.org/>`_ configuration) and add a ``flake8`` step to the Travis config. Big thanks to Dorian Pula! -* :bug:`683` Make ``util.log_to_file`` append instead of replace. Thanks - to ``@vlcinsky`` for the report. +* :bug:`949 (1.17+)` SSHClient and Transport could cause a memory leak if + there's a connection problem or protocol error, even if ``Transport.close()`` + is called. Thanks Kyle Agronick for the discovery and investigation, and + Pierce Lopez for assistance. +* :bug:`683 (1.17+)` Make ``util.log_to_file`` append instead of replace. + Thanks to ``@vlcinsky`` for the report. * :release:`2.1.2 <2017-02-20>` * :release:`2.0.5 <2017-02-20>` * :release:`1.18.2 <2017-02-20>` diff --git a/tests/__init__.py b/tests/__init__.py index e69de29b..8878f14d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,36 @@ +# Copyright (C) 2017 Martin Packman <gzlist@googlemail.com> +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +"""Base classes and helpers for testing paramiko.""" + +import unittest + +from paramiko.py3compat import ( + builtins, + ) + + +def skipUnlessBuiltin(name): + """Skip decorated test if builtin name does not exist.""" + if getattr(builtins, name, None) is None: + skip = getattr(unittest, "skip", None) + if skip is None: + # Python 2.6 pseudo-skip + return lambda func: None + return skip("No builtin " + repr(name)) + return lambda func: func diff --git a/tests/test_client.py b/tests/test_client.py index 3adb9665..2288cf57 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -35,7 +35,7 @@ import time from tests.util import test_path import paramiko -from paramiko.common import PY2 +from paramiko.py3compat import PY2, b from paramiko.ssh_exception import SSHException @@ -296,13 +296,10 @@ class SSHClientTest (unittest.TestCase): verify that when an SSHClient is collected, its transport (and the transport's packetizer) is closed. """ - # Unclear why this is borked on Py3, but it is, and does not seem worth - # pursuing at the moment. Skipped on PyPy because it fails on travis - # for unknown reasons, works fine locally. - # XXX: It's the release of the references to e.g packetizer that fails - # in py3... - if not PY2 or platform.python_implementation() == "PyPy": + # Skipped on PyPy because it fails on travis for unknown reasons + if platform.python_implementation() == "PyPy": return + threading.Thread(target=self._run).start() self.tc = paramiko.SSHClient() @@ -320,8 +317,8 @@ class SSHClientTest (unittest.TestCase): del self.tc # force a collection to see whether the SSHClient object is deallocated - # correctly. 2 GCs are needed to make sure it's really collected on - # PyPy + # 2 GCs are needed on PyPy, time is needed for Python 3 + time.sleep(0.3) gc.collect() gc.collect() diff --git a/tests/test_file.py b/tests/test_file.py index 7fab6985..b33ecd51 100755 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -21,10 +21,14 @@ Some unit tests for the BufferedFile abstraction. """ import unittest -from paramiko.file import BufferedFile -from paramiko.common import linefeed_byte, crlf, cr_byte import sys +from paramiko.common import linefeed_byte, crlf, cr_byte +from paramiko.file import BufferedFile +from paramiko.py3compat import BytesIO + +from tests import skipUnlessBuiltin + class LoopbackFile (BufferedFile): """ @@ -33,19 +37,16 @@ class LoopbackFile (BufferedFile): def __init__(self, mode='r', bufsize=-1): BufferedFile.__init__(self) self._set_mode(mode, bufsize) - self.buffer = bytes() + self.buffer = BytesIO() + self.offset = 0 def _read(self, size): - if len(self.buffer) == 0: - return None - if size > len(self.buffer): - size = len(self.buffer) - data = self.buffer[:size] - self.buffer = self.buffer[size:] + data = self.buffer.getvalue()[self.offset:self.offset+size] + self.offset += len(data) return data def _write(self, data): - self.buffer += data + self.buffer.write(data) return len(data) @@ -187,6 +188,42 @@ class BufferedFileTest (unittest.TestCase): self.assertEqual(data, b'hello') f.close() + def test_write_bad_type(self): + with LoopbackFile('wb') as f: + self.assertRaises(TypeError, f.write, object()) + + def test_write_unicode_as_binary(self): + text = u"\xa7 why is writing text to a binary file allowed?\n" + with LoopbackFile('rb+') as f: + f.write(text) + self.assertEqual(f.read(), text.encode("utf-8")) + + @skipUnlessBuiltin('memoryview') + def test_write_bytearray(self): + with LoopbackFile('rb+') as f: + f.write(bytearray(12)) + self.assertEqual(f.read(), 12 * b"\0") + + @skipUnlessBuiltin('buffer') + def test_write_buffer(self): + data = 3 * b"pretend giant block of data\n" + offsets = range(0, len(data), 8) + with LoopbackFile('rb+') as f: + for offset in offsets: + f.write(buffer(data, offset, 8)) + self.assertEqual(f.read(), data) + + @skipUnlessBuiltin('memoryview') + def test_write_memoryview(self): + data = 3 * b"pretend giant block of data\n" + offsets = range(0, len(data), 8) + with LoopbackFile('rb+') as f: + view = memoryview(data) + for offset in offsets: + f.write(view[offset:offset+8]) + self.assertEqual(f.read(), data) + + if __name__ == '__main__': from unittest import main main() diff --git a/tests/test_sftp.py b/tests/test_sftp.py index d3064fff..98a9cebb 100755 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -35,6 +35,7 @@ from tempfile import mkstemp import paramiko from paramiko.py3compat import PY2, b, u, StringIO from paramiko.common import o777, o600, o666, o644 +from tests import skipUnlessBuiltin from tests.stub_sftp import StubServer, StubSFTPServer from tests.loop import LoopSocket from tests.util import test_path @@ -817,6 +818,35 @@ class SFTPTest (unittest.TestCase): sftp_attributes = SFTPAttributes() self.assertEqual(str(sftp_attributes), "?--------- 1 0 0 0 (unknown date) ?") + @skipUnlessBuiltin('buffer') + def test_write_buffer(self): + """Test write() using a buffer instance.""" + data = 3 * b'A potentially large block of data to chunk up.\n' + try: + with sftp.open('%s/write_buffer' % FOLDER, 'wb') as f: + for offset in range(0, len(data), 8): + f.write(buffer(data, offset, 8)) + + with sftp.open('%s/write_buffer' % FOLDER, 'rb') as f: + self.assertEqual(f.read(), data) + finally: + sftp.remove('%s/write_buffer' % FOLDER) + + @skipUnlessBuiltin('memoryview') + def test_write_memoryview(self): + """Test write() using a memoryview instance.""" + data = 3 * b'A potentially large block of data to chunk up.\n' + try: + with sftp.open('%s/write_memoryview' % FOLDER, 'wb') as f: + view = memoryview(data) + for offset in range(0, len(data), 8): + f.write(view[offset:offset+8]) + + with sftp.open('%s/write_memoryview' % FOLDER, 'rb') as f: + self.assertEqual(f.read(), data) + finally: + sftp.remove('%s/write_memoryview' % FOLDER) + if __name__ == '__main__': SFTPTest.init_loopback() diff --git a/tests/test_transport.py b/tests/test_transport.py index c426cef1..3e352919 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -43,6 +43,7 @@ from paramiko.common import ( ) from paramiko.py3compat import bytes from paramiko.message import Message +from tests import skipUnlessBuiltin from tests.loop import LoopSocket from tests.util import test_path @@ -858,3 +859,71 @@ class TransportTest(unittest.TestCase): self.assertEqual([chan], r) self.assertEqual([], w) self.assertEqual([], e) + + def test_channel_send_misc(self): + """ + verify behaviours sending various instances to a channel + """ + self.setup_test_server() + text = u"\xa7 slice me nicely" + with self.tc.open_session() as chan: + schan = self.ts.accept(1.0) + if schan is None: + self.fail("Test server transport failed to accept") + sfile = schan.makefile() + + # TypeError raised on non string or buffer type + self.assertRaises(TypeError, chan.send, object()) + self.assertRaises(TypeError, chan.sendall, object()) + + # sendall() accepts a unicode instance + chan.sendall(text) + expected = text.encode("utf-8") + self.assertEqual(sfile.read(len(expected)), expected) + + @skipUnlessBuiltin('buffer') + def test_channel_send_buffer(self): + """ + verify sending buffer instances to a channel + """ + self.setup_test_server() + data = 3 * b'some test data\n whole' + with self.tc.open_session() as chan: + schan = self.ts.accept(1.0) + if schan is None: + self.fail("Test server transport failed to accept") + sfile = schan.makefile() + + # send() accepts buffer instances + sent = 0 + while sent < len(data): + sent += chan.send(buffer(data, sent, 8)) + self.assertEqual(sfile.read(len(data)), data) + + # sendall() accepts a buffer instance + chan.sendall(buffer(data)) + self.assertEqual(sfile.read(len(data)), data) + + @skipUnlessBuiltin('memoryview') + def test_channel_send_memoryview(self): + """ + verify sending memoryview instances to a channel + """ + self.setup_test_server() + data = 3 * b'some test data\n whole' + with self.tc.open_session() as chan: + schan = self.ts.accept(1.0) + if schan is None: + self.fail("Test server transport failed to accept") + sfile = schan.makefile() + + # send() accepts memoryview slices + sent = 0 + view = memoryview(data) + while sent < len(view): + sent += chan.send(view[sent:sent+8]) + self.assertEqual(sfile.read(len(data)), data) + + # sendall() accepts a memoryview instance + chan.sendall(memoryview(data)) + self.assertEqual(sfile.read(len(data)), data) |