New Upstream Release - python-spur
Ready changes
Summary
Merged new upstream version: 0.3.23 (was: 0.3.22).
Resulting package
Built on 2023-03-23T21:57 (took 3m49s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases python3-spur
Lintian Result
Diff
diff --git a/CHANGES b/CHANGES
index c50e91e..2d18f59 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,15 @@
# CHANGES
+## 0.3.23
+
+* Raise minimum Python version to 3.6.
+
+* Handle FileNotFoundError when using subprocess on Python >= 3.8.
+
+## 0.3.22
+
+* Fix: default connect timeout was not being set correctly.
+
## 0.3.21
* Add close() method to shells, behaving the same as __exit__().
diff --git a/README.rst b/README.rst
index d9efa94..a66d89c 100644
--- a/README.rst
+++ b/README.rst
@@ -36,7 +36,7 @@ LocalShell
Takes no arguments:
-.. code-block:: sh
+.. code-block:: python
spur.LocalShell()
@@ -76,7 +76,7 @@ Optional arguments:
* ``missing_host_key`` -- by default, an error is raised when a host
key is missing. One of the following values can be used to change the
behaviour when a host key is missing:
-
+
- ``spur.ssh.MissingHostKey.raise_error`` -- raise an error
- ``spur.ssh.MissingHostKey.warn`` -- accept the host key and log a
warning
@@ -88,14 +88,14 @@ Optional arguments:
often found on embedded systems, try changing ``shell_type`` to a more
appropriate value, such as ``spur.ssh.ShellTypes.minimal``. The following
shell types are currently supported:
-
+
- ``spur.ssh.ShellTypes.sh`` -- the Bourne shell. Supports all features.
-
+
- ``spur.ssh.ShellTypes.minimal`` -- a minimal shell. Several features
are unsupported:
-
+
- Non-existent commands will not raise ``spur.NoSuchCommandError``.
-
+
- The following arguments to ``spawn`` and ``run`` are unsupported unless
set to their default values:
``cwd``, ``update_env``, and ``store_pid``.
@@ -103,7 +103,7 @@ Optional arguments:
* ``look_for_private_keys`` -- by default, Spur will search for discoverable
private key files in ``~/.ssh/``.
Set to ``False`` to disable this behaviour.
-
+
* ``load_system_host_keys`` -- by default, Spur will attempt to read host keys
from the user's known hosts file, as used by OpenSSH, and no exception will
be raised if the file can't be read.
@@ -213,6 +213,12 @@ assuming you already have an instance of ``SshShell``:
with open("/path/to/local", "wb") as local_file:
shutil.copyfileobj(remote_file, local_file)
+close()
+~~~~~~~
+
+Closes and the shell and releases any associated resources.
+``close()`` is called automatically when the shell is used as a context manager.
+
Process interface
-----------------
diff --git a/debian/changelog b/debian/changelog
index 3766063..a66f9a6 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+python-spur (0.3.23-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Thu, 23 Mar 2023 21:53:37 -0000
+
python-spur (0.3.21-3) unstable; urgency=medium
[ Debian Janitor ]
diff --git a/makefile b/makefile
index 75a5b9a..ee0f56a 100644
--- a/makefile
+++ b/makefile
@@ -1,29 +1,39 @@
-.PHONY: test upload clean bootstrap
+.PHONY: test
test:
- sh -c '. _virtualenv/bin/activate; nosetests -m'\''^$$'\'' `find tests -name '\''*.py'\''`'
-
-upload:
+ sh -c '. _virtualenv/bin/activate; py.test tests'
+
+.PHONY: upload
+
+upload: build-dist
tox
- _virtualenv/bin/python setup.py sdist bdist_wheel upload
+ _virtualenv/bin/twine upload dist/*
make clean
-
-register:
- python setup.py register
+
+.PHONY: build-dist
+
+build-dist:
+ rm -rf dist
+ _virtualenv/bin/pyproject-build
+
+.PHONY: clean
clean:
rm -f MANIFEST
rm -rf dist build
-
+
+.PHONY: bootstrap
+
bootstrap: _virtualenv
_virtualenv/bin/pip install -e .
-ifneq ($(wildcard test-requirements.txt),)
+ifneq ($(wildcard test-requirements.txt),)
_virtualenv/bin/pip install -r test-requirements.txt
endif
make clean
-_virtualenv:
+_virtualenv:
python3 -m venv _virtualenv
_virtualenv/bin/pip install --upgrade pip
_virtualenv/bin/pip install --upgrade setuptools
_virtualenv/bin/pip install --upgrade wheel
+ _virtualenv/bin/pip install --upgrade build twine
diff --git a/setup.py b/setup.py
index 9f17300..f48dadc 100644
--- a/setup.py
+++ b/setup.py
@@ -8,27 +8,29 @@ def read(fname):
setup(
name='spur',
- version='0.3.21',
+ version='0.3.23',
description='Run commands and manipulate files locally or over SSH using the same interface',
long_description=read("README.rst"),
author='Michael Williamson',
author_email='mike@zwobble.org',
- url='http://github.com/mwilliamson/spur.py',
+ url='https://github.com/mwilliamson/spur.py',
keywords="ssh shell subprocess process",
packages=['spur'],
- install_requires=["paramiko>=1.13.1,<3"],
+ install_requires=["paramiko>=1.13.1,<4"],
+ python_requires='>=3.6',
+ license="BSD-2-Clause",
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.4',
- 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
+ 'Programming Language :: Python :: 3.11',
'Topic :: Internet',
],
)
diff --git a/spur/local.py b/spur/local.py
index 0996ed3..502f547 100644
--- a/spur/local.py
+++ b/spur/local.py
@@ -71,6 +71,13 @@ class LocalShell(object):
bufsize=0,
**self._subprocess_args(command, *args, **kwargs)
)
+ except FileNotFoundError as error:
+ if cwd is not None and error.filename == cwd:
+ raise CouldNotChangeDirectoryError(cwd, error)
+ elif error.filename == command[0]:
+ raise NoSuchCommandError(command[0])
+ else:
+ raise
except OSError as error:
if cwd is not None and self._is_cannot_change_directory_oserror(error, cwd):
raise CouldNotChangeDirectoryError(cwd, error)
diff --git a/spur/ssh.py b/spur/ssh.py
index ac1f309..6ee0c24 100644
--- a/spur/ssh.py
+++ b/spur/ssh.py
@@ -129,6 +129,9 @@ class SshShell(object):
load_system_host_keys=True,
sock=None):
+ if connect_timeout is None:
+ connect_timeout = _ONE_MINUTE
+
if port is None:
port = 22
@@ -141,7 +144,7 @@ class SshShell(object):
self._password = password
self._private_key_file = private_key_file
self._client = None
- self._connect_timeout = connect_timeout if not None else _ONE_MINUTE
+ self._connect_timeout = connect_timeout
self._look_for_private_keys = look_for_private_keys
self._load_system_host_keys = load_system_host_keys
self._closed = False
diff --git a/test-requirements.txt b/test-requirements.txt
index 2270513..e079f8a 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1 +1 @@
-nose>=1.2.1,<2
+pytest
diff --git a/tests/assertions.py b/tests/assertions.py
new file mode 100644
index 0000000..b80cc2b
--- /dev/null
+++ b/tests/assertions.py
@@ -0,0 +1,13 @@
+import pytest
+
+
+def assert_equal(expected, actual):
+ assert expected == actual
+
+
+def assert_not_equal(expected, actual):
+ assert expected != actual
+
+
+def assert_raises(exception_type, func):
+ pytest.raises(exception_type, func)
diff --git a/tests/local_tests.py b/tests/local_tests.py
index 850a737..919b82b 100644
--- a/tests/local_tests.py
+++ b/tests/local_tests.py
@@ -1,7 +1,5 @@
import spur
-from nose.tools import istest
-
from .process_test_set import ProcessTestSet
from .open_test_set import OpenTestSet
@@ -9,13 +7,11 @@ from .open_test_set import OpenTestSet
class LocalTestMixin(object):
def create_shell(self):
return spur.LocalShell()
-
-@istest
+
class LocalOpenTests(OpenTestSet, LocalTestMixin):
pass
-@istest
class LocalProcessTests(ProcessTestSet, LocalTestMixin):
pass
diff --git a/tests/open_test_set.py b/tests/open_test_set.py
index ae3805a..81665e8 100644
--- a/tests/open_test_set.py
+++ b/tests/open_test_set.py
@@ -3,25 +3,24 @@ from __future__ import unicode_literals
import uuid
import functools
-from nose.tools import assert_equal, istest, nottest
+from .assertions import assert_equal
+
__all__ = ["OpenTestSet"]
-@nottest
-def test(test_func):
+def with_shell(test_func):
@functools.wraps(test_func)
- @istest
def run_test(self, *args, **kwargs):
with self.create_shell() as shell:
test_func(shell)
-
+
return run_test
class OpenTestSet(object):
- @test
- def can_write_to_files_opened_by_open(shell):
+ @with_shell
+ def test_can_write_to_files_opened_by_open(shell):
path = "/tmp/{0}".format(uuid.uuid4())
f = shell.open(path, "w")
try:
@@ -31,9 +30,9 @@ class OpenTestSet(object):
finally:
f.close()
shell.run(["rm", path])
-
- @test
- def can_read_files_opened_by_open(shell):
+
+ @with_shell
+ def test_can_read_files_opened_by_open(shell):
path = "/tmp/{0}".format(uuid.uuid4())
shell.run(["sh", "-c", "echo hello > '{0}'".format(path)])
f = shell.open(path)
@@ -42,16 +41,16 @@ class OpenTestSet(object):
finally:
f.close()
shell.run(["rm", path])
-
- @test
- def open_can_be_used_as_context_manager(shell):
+
+ @with_shell
+ def test_open_can_be_used_as_context_manager(shell):
path = "/tmp/{0}".format(uuid.uuid4())
shell.run(["sh", "-c", "echo hello > '{0}'".format(path)])
with shell.open(path) as f:
assert_equal("hello\n", f.read())
-
- @test
- def files_can_be_opened_in_binary_mode(shell):
+
+ @with_shell
+ def test_files_can_be_opened_in_binary_mode(shell):
path = "/tmp/{0}".format(uuid.uuid4())
shell.run(["sh", "-c", "echo hello > '{0}'".format(path)])
with shell.open(path, "rb") as f:
diff --git a/tests/process_test_set.py b/tests/process_test_set.py
index 5381100..2bfc9c8 100644
--- a/tests/process_test_set.py
+++ b/tests/process_test_set.py
@@ -7,18 +7,15 @@ import signal
import functools
import posixpath
-from nose.tools import istest, nottest, assert_equal, assert_not_equal, assert_raises, assert_true
-
import spur
+from .assertions import assert_equal, assert_not_equal, assert_raises
__all__ = ["ProcessTestSet"]
-@nottest
-def test(test_func):
+def with_shell(test_func):
@functools.wraps(test_func)
- @istest
def run_test(self, *args, **kwargs):
with self.create_shell() as shell:
test_func(shell)
@@ -27,63 +24,63 @@ def test(test_func):
class ProcessTestSet(object):
- @test
- def output_of_run_is_stored(shell):
+ @with_shell
+ def test_output_of_run_is_stored(shell):
result = shell.run(["echo", "hello"])
assert_equal(b"hello\n", result.output)
- @test
- def output_is_not_truncated_when_not_ending_in_a_newline(shell):
+ @with_shell
+ def test_output_is_not_truncated_when_not_ending_in_a_newline(shell):
result = shell.run(["echo", "-n", "hello"])
assert_equal(b"hello", result.output)
- @test
- def trailing_newlines_are_not_stripped_from_run_output(shell):
+ @with_shell
+ def test_trailing_newlines_are_not_stripped_from_run_output(shell):
result = shell.run(["echo", "\n\n"])
assert_equal(b"\n\n\n", result.output)
- @test
- def stderr_output_of_run_is_stored(shell):
+ @with_shell
+ def test_stderr_output_of_run_is_stored(shell):
result = shell.run(["sh", "-c", "echo hello 1>&2"])
assert_equal(b"hello\n", result.stderr_output)
- @test
- def output_bytes_are_decoded_if_encoding_is_set(shell):
+ @with_shell
+ def test_output_bytes_are_decoded_if_encoding_is_set(shell):
result = shell.run(["bash", "-c", r'echo -e "\u2603"'], encoding="utf8")
assert_equal(_u("☃\n"), result.output)
- @test
- def cwd_of_run_can_be_set(shell):
+ @with_shell
+ def test_cwd_of_run_can_be_set(shell):
result = shell.run(["pwd"], cwd="/")
assert_equal(b"/\n", result.output)
- @test
- def environment_variables_can_be_added_for_run(shell):
+ @with_shell
+ def test_environment_variables_can_be_added_for_run(shell):
result = shell.run(["sh", "-c", "echo $NAME"], update_env={"NAME": "Bob"})
assert_equal(b"Bob\n", result.output)
- @test
- def exception_is_raised_if_return_code_is_not_zero(shell):
+ @with_shell
+ def test_exception_is_raised_if_return_code_is_not_zero(shell):
assert_raises(spur.RunProcessError, lambda: shell.run(["false"]))
- @test
- def exception_has_output_from_command(shell):
+ @with_shell
+ def test_exception_has_output_from_command(shell):
try:
shell.run(["sh", "-c", "echo Hello world!; false"])
assert_true(False)
except spur.RunProcessError as error:
assert_equal(b"Hello world!\n", error.output)
- @test
- def exception_has_stderr_output_from_command(shell):
+ @with_shell
+ def test_exception_has_stderr_output_from_command(shell):
try:
shell.run(["sh", "-c", "echo Hello world! 1>&2; false"])
assert_true(False)
except spur.RunProcessError as error:
assert_equal(b"Hello world!\n", error.stderr_output)
- @test
- def exception_message_contains_return_code_and_all_output(shell):
+ @with_shell
+ def test_exception_message_contains_return_code_and_all_output(shell):
try:
shell.run(["sh", "-c", "echo starting; echo failed! 1>&2; exit 1"])
assert_true(False)
@@ -93,8 +90,8 @@ class ProcessTestSet(object):
error.args[0]
)
- @test
- def exception_message_contains_output_as_string_if_encoding_is_set(shell):
+ @with_shell
+ def test_exception_message_contains_output_as_string_if_encoding_is_set(shell):
try:
shell.run(["sh", "-c", "echo starting; echo failed! 1>&2; exit 1"], encoding="ascii")
assert_true(False)
@@ -104,8 +101,8 @@ class ProcessTestSet(object):
error.args[0]
)
- @test
- def exception_message_shows_unicode_bytes(shell):
+ @with_shell
+ def test_exception_message_shows_unicode_bytes(shell):
try:
shell.run(["sh", "-c", _u("echo ‽; exit 1")])
assert_true(False)
@@ -115,45 +112,45 @@ class ProcessTestSet(object):
error.args[0]
)
- @test
- def return_code_stored_if_errors_allowed(shell):
+ @with_shell
+ def test_return_code_stored_if_errors_allowed(shell):
result = shell.run(["sh", "-c", "exit 14"], allow_error=True)
assert_equal(14, result.return_code)
- @test
- def can_get_result_of_spawned_process(shell):
+ @with_shell
+ def test_can_get_result_of_spawned_process(shell):
process = shell.spawn(["echo", "hello"])
result = process.wait_for_result()
assert_equal(b"hello\n", result.output)
- @test
- def calling_wait_for_result_is_idempotent(shell):
+ @with_shell
+ def test_calling_wait_for_result_is_idempotent(shell):
process = shell.spawn(["echo", "hello"])
process.wait_for_result()
result = process.wait_for_result()
assert_equal(b"hello\n", result.output)
- @test
- def wait_for_result_raises_error_if_return_code_is_not_zero(shell):
+ @with_shell
+ def test_wait_for_result_raises_error_if_return_code_is_not_zero(shell):
process = shell.spawn(["false"])
assert_raises(spur.RunProcessError, process.wait_for_result)
- @test
- def can_write_to_stdin_of_spawned_processes(shell):
+ @with_shell
+ def test_can_write_to_stdin_of_spawned_processes(shell):
process = shell.spawn(["sh", "-c", "read value; echo $value"])
process.stdin_write(b"hello\n")
result = process.wait_for_result()
assert_equal(b"hello\n", result.output)
- @test
- def can_tell_if_spawned_process_is_running(shell):
+ @with_shell
+ def test_can_tell_if_spawned_process_is_running(shell):
process = shell.spawn(["sh", "-c", "echo after; read dont_care; echo after"])
assert_equal(True, process.is_running())
process.stdin_write(b"\n")
_wait_for_assertion(lambda: assert_equal(False, process.is_running()))
- @test
- def can_write_stdout_to_file_object_while_process_is_executing(shell):
+ @with_shell
+ def test_can_write_stdout_to_file_object_while_process_is_executing(shell):
output_file = io.BytesIO()
process = shell.spawn(
["sh", "-c", "echo hello; read dont_care;"],
@@ -164,8 +161,8 @@ class ProcessTestSet(object):
process.stdin_write(b"\n")
assert_equal(b"hello\n", process.wait_for_result().output)
- @test
- def can_write_stderr_to_file_object_while_process_is_executing(shell):
+ @with_shell
+ def test_can_write_stderr_to_file_object_while_process_is_executing(shell):
output_file = io.BytesIO()
process = shell.spawn(
["sh", "-c", "echo hello 1>&2; read dont_care;"],
@@ -176,8 +173,8 @@ class ProcessTestSet(object):
process.stdin_write(b"\n")
assert_equal(b"hello\n", process.wait_for_result().stderr_output)
- @test
- def when_encoding_is_set_then_stdout_is_decoded_before_writing_to_stdout_argument(shell):
+ @with_shell
+ def test_when_encoding_is_set_then_stdout_is_decoded_before_writing_to_stdout_argument(shell):
output_file = io.StringIO()
process = shell.spawn(
["bash", "-c", r'echo -e "\u2603"hello; read dont_care'],
@@ -189,27 +186,27 @@ class ProcessTestSet(object):
process.stdin_write(b"\n")
assert_equal(_u("☃hello\n"), process.wait_for_result().output)
- @test
- def can_get_process_id_of_process_if_store_pid_is_true(shell):
+ @with_shell
+ def test_can_get_process_id_of_process_if_store_pid_is_true(shell):
process = shell.spawn(["sh", "-c", "echo $$"], store_pid=True)
result = process.wait_for_result()
assert_equal(int(result.output.strip()), process.pid)
- @test
- def process_id_is_not_available_if_store_pid_is_not_set(shell):
+ @with_shell
+ def test_process_id_is_not_available_if_store_pid_is_not_set(shell):
process = shell.spawn(["sh", "-c", "echo $$"])
assert not hasattr(process, "pid")
- @test
- def can_send_signal_to_process_if_store_pid_is_set(shell):
+ @with_shell
+ def test_can_send_signal_to_process_if_store_pid_is_set(shell):
process = shell.spawn(["cat"], store_pid=True)
assert process.is_running()
process.send_signal(signal.SIGTERM)
_wait_for_assertion(lambda: assert_equal(False, process.is_running()))
- @test
- def spawning_non_existent_command_raises_specific_no_such_command_exception(shell):
+ @with_shell
+ def test_spawning_non_existent_command_raises_specific_no_such_command_exception(shell):
try:
shell.spawn(["bin/i-am-not-a-command"])
# Expected exception
@@ -219,8 +216,8 @@ class ProcessTestSet(object):
assert_equal("bin/i-am-not-a-command", error.command)
- @test
- def spawning_command_that_uses_path_env_variable_asks_if_command_is_installed(shell):
+ @with_shell
+ def test_spawning_command_that_uses_path_env_variable_asks_if_command_is_installed(shell):
try:
shell.spawn(["i-am-not-a-command"])
# Expected exception
@@ -234,8 +231,8 @@ class ProcessTestSet(object):
assert_equal("i-am-not-a-command", error.command)
- @test
- def using_non_existent_cwd_does_not_raise_no_such_command_error(shell):
+ @with_shell
+ def test_using_non_existent_cwd_does_not_raise_no_such_command_error(shell):
cwd = "/some/path/that/hopefully/doesnt/exists/ljaslkfjaslkfjas"
try:
shell.spawn(["echo", "1"], cwd=cwd)
@@ -245,41 +242,41 @@ class ProcessTestSet(object):
assert not isinstance(error, spur.NoSuchCommandError)
- @test
- def commands_are_run_without_pseudo_terminal_by_default(shell):
+ @with_shell
+ def test_commands_are_run_without_pseudo_terminal_by_default(shell):
result = shell.run(["bash", "-c", "[ -t 0 ]"], allow_error=True)
assert_not_equal(0, result.return_code)
- @test
- def command_can_be_explicitly_run_with_pseudo_terminal(shell):
+ @with_shell
+ def test_command_can_be_explicitly_run_with_pseudo_terminal(shell):
result = shell.run(["bash", "-c", "[ -t 0 ]"], allow_error=True, use_pty=True)
assert_equal(0, result.return_code)
- @test
- def output_is_captured_when_using_pty(shell):
+ @with_shell
+ def test_output_is_captured_when_using_pty(shell):
result = shell.run(["echo", "-n", "hello"], use_pty=True)
assert_equal(b"hello", result.output)
- @test
- def stderr_is_redirected_stdout_when_using_pty(shell):
+ @with_shell
+ def test_stderr_is_redirected_stdout_when_using_pty(shell):
result = shell.run(["sh", "-c", "echo -n hello 1>&2"], use_pty=True)
assert_equal(b"hello", result.output)
assert_equal(b"", result.stderr_output)
- @test
- def can_write_to_stdin_of_spawned_process_when_using_pty(shell):
+ @with_shell
+ def test_can_write_to_stdin_of_spawned_process_when_using_pty(shell):
process = shell.spawn(["sh", "-c", "read value; echo $value"], use_pty=True)
process.stdin_write(b"hello\n")
result = process.wait_for_result()
# Get the output twice since the pty echoes input
assert_equal(b"hello\r\nhello\r\n", result.output)
- @test
- def using_non_existent_cwd_raises_could_not_change_directory_error(shell):
+ @with_shell
+ def test_using_non_existent_cwd_raises_could_not_change_directory_error(shell):
cwd = "/some/silly/path"
try:
shell.spawn(["echo", "1"], cwd=cwd)
@@ -289,8 +286,8 @@ class ProcessTestSet(object):
assert_equal("Could not change directory to: {0}".format(cwd), error.args[0].split("\n")[0])
assert_equal(cwd, error.directory)
- @test
- def attempting_to_change_directory_without_permissions_raises_cannot_change_directory_error(shell):
+ @with_shell
+ def test_attempting_to_change_directory_without_permissions_raises_cannot_change_directory_error(shell):
with shell.temporary_dir() as temp_dir:
dir_without_execute_permissions = posixpath.join(temp_dir, "a")
shell.run(["mkdir", dir_without_execute_permissions])
@@ -302,8 +299,8 @@ class ProcessTestSet(object):
except spur.CouldNotChangeDirectoryError as error:
assert_equal(dir_without_execute_permissions, error.directory)
- @test
- def using_non_existent_cwd_and_command_raises_could_not_change_directory_error(shell):
+ @with_shell
+ def test_using_non_existent_cwd_and_command_raises_could_not_change_directory_error(shell):
try:
shell.spawn(["bin/i-am-not-a-command"], cwd="/some/silly/path")
# Expected exception
@@ -311,8 +308,8 @@ class ProcessTestSet(object):
except spur.CouldNotChangeDirectoryError as error:
assert_equal("/some/silly/path", error.directory)
- @test
- def using_non_existent_command_and_correct_cwd_raises_no_such_command_exception(shell):
+ @with_shell
+ def test_using_non_existent_command_and_correct_cwd_raises_no_such_command_exception(shell):
try:
shell.spawn(["bin/i-am-not-a-command"], cwd="/bin")
# Expected exception
@@ -320,8 +317,8 @@ class ProcessTestSet(object):
except spur.NoSuchCommandError as error:
assert_equal("bin/i-am-not-a-command", error.command)
- @test
- def can_find_command_in_cwd(shell):
+ @with_shell
+ def test_can_find_command_in_cwd(shell):
# TODO: the behaviour in subprocess seems to be inconsistent between
# both Python versions and platforms (Windows vs Unix)
# See:
@@ -330,8 +327,7 @@ class ProcessTestSet(object):
result = shell.run(["./ls"], cwd="/bin")
assert_equal(result.return_code, 0)
- @istest
- def shell_can_be_closed_using_close_method(self):
+ def test_shell_can_be_closed_using_close_method(self):
shell = self.create_shell()
try:
result = shell.run(["echo", "hello"])
diff --git a/tests/ssh_tests.py b/tests/ssh_tests.py
index a239434..105a486 100644
--- a/tests/ssh_tests.py
+++ b/tests/ssh_tests.py
@@ -3,11 +3,9 @@ from __future__ import unicode_literals
import io
import socket
-from nose.tools import istest, assert_raises, assert_equal
-from paramiko.util import retry_on_signal
-
import spur
import spur.ssh
+from .assertions import assert_equal, assert_raises
from .testing import create_ssh_shell, HOSTNAME, PORT, PASSWORD, USERNAME
from .process_test_set import ProcessTestSet
from .open_test_set import OpenTestSet
@@ -16,29 +14,25 @@ from .open_test_set import OpenTestSet
class SshTestMixin(object):
def create_shell(self):
return create_ssh_shell()
-
-@istest
+
class SshOpenTests(OpenTestSet, SshTestMixin):
pass
-@istest
class SshProcessTests(ProcessTestSet, SshTestMixin):
pass
-@istest
-def attempting_to_connect_to_wrong_port_raises_connection_error():
+def test_attempting_to_connect_to_wrong_port_raises_connection_error():
def try_connection():
shell = _create_shell_with_wrong_port()
shell.run(["echo", "hello"])
-
+
assert_raises(spur.ssh.ConnectionError, try_connection)
-@istest
-def connection_error_contains_original_error():
+def test_connection_error_contains_original_error():
try:
shell = _create_shell_with_wrong_port()
shell.run(["true"])
@@ -48,8 +42,7 @@ def connection_error_contains_original_error():
assert isinstance(error.original_error, IOError)
-@istest
-def connection_error_contains_traceback_for_original_error():
+def test_connection_error_contains_traceback_for_original_error():
try:
shell = _create_shell_with_wrong_port()
shell.run(["true"])
@@ -59,36 +52,31 @@ def connection_error_contains_traceback_for_original_error():
assert "Traceback (most recent call last):" in error.original_traceback
-@istest
-def missing_host_key_set_to_accept_allows_connection_with_missing_host_key():
+def test_missing_host_key_set_to_accept_allows_connection_with_missing_host_key():
with create_ssh_shell(missing_host_key=spur.ssh.MissingHostKey.accept) as shell:
shell.run(["true"])
-@istest
-def missing_host_key_set_to_warn_allows_connection_with_missing_host_key():
+def test_missing_host_key_set_to_warn_allows_connection_with_missing_host_key():
with create_ssh_shell(missing_host_key=spur.ssh.MissingHostKey.warn) as shell:
shell.run(["true"])
-@istest
-def missing_host_key_set_to_raise_error_raises_error_when_missing_host_key():
+def test_missing_host_key_set_to_raise_error_raises_error_when_missing_host_key():
with create_ssh_shell(missing_host_key=spur.ssh.MissingHostKey.raise_error) as shell:
assert_raises(spur.ssh.ConnectionError, lambda: shell.run(["true"]))
-
-@istest
-def trying_to_use_ssh_shell_after_exit_results_in_error():
+
+def test_trying_to_use_ssh_shell_after_exit_results_in_error():
with create_ssh_shell() as shell:
pass
-
+
assert_raises(Exception, lambda: shell.run(["true"]))
-@istest
-def an_open_socket_can_be_used_for_ssh_connection_with_sock_argument():
+def test_an_open_socket_can_be_used_for_ssh_connection_with_sock_argument():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- retry_on_signal(lambda: sock.connect((HOSTNAME, PORT)))
+ sock.connect((HOSTNAME, PORT))
with _create_shell_with_wrong_port(sock=sock) as shell:
result = shell.run(["echo", "hello"])
@@ -109,63 +97,56 @@ def _create_shell_with_wrong_port(**kwargs):
class MinimalSshTestMixin(object):
def create_shell(self):
return create_ssh_shell(shell_type=spur.ssh.ShellTypes.minimal)
-
-@istest
+
class MinimalSshOpenTests(OpenTestSet, MinimalSshTestMixin):
pass
-@istest
class MinimalSshProcessTests(ProcessTestSet, MinimalSshTestMixin):
- spawning_command_that_uses_path_env_variable_asks_if_command_is_installed = None
- spawning_non_existent_command_raises_specific_no_such_command_exception = None
-
- can_get_process_id_of_process_if_store_pid_is_true = None
- can_send_signal_to_process_if_store_pid_is_set = None
+ test_spawning_command_that_uses_path_env_variable_asks_if_command_is_installed = None
+ test_spawning_non_existent_command_raises_specific_no_such_command_exception = None
+
+ test_can_get_process_id_of_process_if_store_pid_is_true = None
+ test_can_send_signal_to_process_if_store_pid_is_set = None
# cwd is not supported when using a minimal shell
- using_non_existent_cwd_raises_could_not_change_directory_error = None
- attempting_to_change_directory_without_permissions_raises_cannot_change_directory_error = None
- using_non_existent_cwd_and_command_raises_could_not_change_directory_error = None
- using_non_existent_command_and_correct_cwd_raises_no_such_command_exception = None
- can_find_command_in_cwd = None
-
- @istest
- def cannot_store_pid(self):
+ test_using_non_existent_cwd_raises_could_not_change_directory_error = None
+ test_attempting_to_change_directory_without_permissions_raises_cannot_change_directory_error = None
+ test_using_non_existent_cwd_and_command_raises_could_not_change_directory_error = None
+ test_using_non_existent_command_and_correct_cwd_raises_no_such_command_exception = None
+ test_can_find_command_in_cwd = None
+
+ def test_cannot_store_pid(self):
self._assert_unsupported_feature(store_pid=True)
-
- cwd_of_run_can_be_set = None
-
- @istest
- def cannot_set_cwd(self):
+
+ test_cwd_of_run_can_be_set = None
+
+ def test_cannot_set_cwd(self):
self._assert_unsupported_feature(cwd="/")
-
- environment_variables_can_be_added_for_run = None
-
- @istest
- def update_env_can_be_empty(self):
+
+ test_environment_variables_can_be_added_for_run = None
+
+ def test_update_env_can_be_empty(self):
self._assert_supported_feature(update_env={})
-
- @istest
- def cannot_update_env(self):
+
+ def test_cannot_update_env(self):
self._assert_unsupported_feature(update_env={"x": "one"})
-
- @istest
- def cannot_set_new_process_group(self):
+
+ def test_cannot_set_new_process_group(self):
self._assert_unsupported_feature(new_process_group=True)
-
-
+
+
def _assert_supported_feature(self, **kwargs):
with self.create_shell() as shell:
result = shell.run(["echo", "hello"], **kwargs)
-
+
assert_equal(b"hello\n", result.output)
-
-
+
+
def _assert_unsupported_feature(self, **kwargs):
name, = kwargs.keys()
-
+
try:
with self.create_shell() as shell:
shell.run(["echo", "hello"], **kwargs)
@@ -175,18 +156,14 @@ class MinimalSshProcessTests(ProcessTestSet, MinimalSshTestMixin):
-@istest
class ReadInitializationLineTests(object):
- @istest
- def reading_initialization_line_returns_int_from_line_of_file(self):
+ def test_reading_initialization_line_returns_int_from_line_of_file(self):
assert_equal(42, spur.ssh._read_int_initialization_line(io.StringIO("42\n")))
-
- @istest
- def blank_lines_are_skipped(self):
+
+ def test_blank_lines_are_skipped(self):
assert_equal(42, spur.ssh._read_int_initialization_line(io.StringIO("\n \n\t\t\n42\n")))
-
- @istest
- def error_if_non_blank_line_is_not_integer(self):
+
+ def test_error_if_non_blank_line_is_not_integer(self):
try:
spur.ssh._read_int_initialization_line(io.StringIO("x\n"))
assert False, "Expected error"
diff --git a/tox.ini b/tox.ini
index 993feaf..ac1a169 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,12 +1,11 @@
[tox]
-envlist = py27,py34,py35,py36,py37,pypy
+envlist = py36,py37,py38,py39,py310,py311
[testenv]
changedir = {envtmpdir}
deps=-r{toxinidir}/test-requirements.txt
commands=
- nosetests {toxinidir}/tests
+ py.test {toxinidir}/tests
passenv=TEST_SSH_*
-[testenv:py32]
-deps=
- {[testenv]deps}
- paramiko<2
+[pytest]
+python_classes = *Tests
+python_files = *_tests.py
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/lib/python3/dist-packages/spur-0.3.23.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/spur-0.3.23.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/spur-0.3.23.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/spur-0.3.23.egg-info/top_level.txt
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/lib/python3/dist-packages/spur-0.3.21.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/spur-0.3.21.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/spur-0.3.21.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/spur-0.3.21.egg-info/top_level.txt
No differences were encountered in the control files