Skip to content

Commit

Permalink
Merge pull request #751 from amoffat/develop
Browse files Browse the repository at this point in the history
Release 2.2.2
  • Loading branch information
amoffat authored Feb 24, 2025
2 parents b36a6c4 + 3a8f772 commit a834c0d
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 2.2.2 - 2/23/25

- Bugfix where it was impossible to use a signal as an `ok_code` [#699](https://github.com/amoffat/sh/issues/699)

## 2.2.1 - 1/9/25

- Bugfix where `async` and `return_cmd` does not raise exceptions [#746](https://github.com/amoffat/sh/pull/746)
Expand Down
10 changes: 10 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ To run a single test::

$> make test='FunctionalTests.test_background' test_one

Docs
----

To build the docs, make sure you've run ``poetry install`` to install the dev dependencies, then::

$> cd docs
$> make html

This will generate the docs in ``docs/build/html``. You can open the ``index.html`` file in your browser to view the docs.

Coverage
--------

Expand Down
5 changes: 5 additions & 0 deletions docs/source/sections/exit_codes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ exception raised in this situation is :ref:`signal_exc`, which subclasses
except sh.SignalException_SIGKILL:
print("killed")
This behavior could be blocked by appending the negative value of the signal to
:ref:`ok_code`. All signals that raises :ref:`signal_exc` are ``[SIGABRT,
SIGBUS, SIGFPE, SIGILL, SIGINT, SIGKILL, SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM,
SIGTERM]``.

.. note::

You can catch :ref:`signal_exc` by using either a number or a signal name.
Expand Down
14 changes: 14 additions & 0 deletions docs/source/sections/special_arguments.rst
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,20 @@ programs use exit codes other than 0 to indicate success.
import sh
sh.weird_program(_ok_code=[0,3,5])
If the process is killed by a signal, a :ref:`signal_exc` is raised by
default. This behavior could be blocked by appending a negative number to
:ref:`ok_code` that represents the signal.

.. code-block:: python
import sh
# the process won't raise SignalException if SIGINT, SIGKILL, or SIGTERM
# are sent to kill the process
p = sh.sleep(3, _bg=True, _ok_code=[0, -2, -9, -15])
# No exception will be raised here
p.kill()
.. seealso:: :ref:`exit_codes`

.. _new_session:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "sh"
version = "2.2.1"
version = "2.2.2"
description = "Python subprocess replacement"
authors = ["Andrew Moffat <[email protected]>"]
readme = "README.rst"
Expand Down
11 changes: 7 additions & 4 deletions sh.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
https://sh.readthedocs.io/en/latest/
https://github.com/amoffat/sh
"""

# ===============================================================================
# Copyright (C) 2011-2023 by Andrew Moffat
# Copyright (C) 2011-2025 by Andrew Moffat
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
Expand All @@ -24,10 +25,9 @@
# THE SOFTWARE.
# ===============================================================================
import asyncio
import platform
from collections import deque
from collections.abc import Mapping

import platform
from importlib import metadata

try:
Expand Down Expand Up @@ -1730,7 +1730,10 @@ def fn(chunk):
def get_exc_exit_code_would_raise(exit_code, ok_codes, sigpipe_ok):
exc = None
success = exit_code in ok_codes
bad_sig = -exit_code in SIGNALS_THAT_SHOULD_THROW_EXCEPTION
signals_that_should_throw_exception = [
sig for sig in SIGNALS_THAT_SHOULD_THROW_EXCEPTION if -sig not in ok_codes
]
bad_sig = -exit_code in signals_that_should_throw_exception

# if this is a piped command, SIGPIPE must be ignored by us and not raise an
# exception, since it's perfectly normal for the consumer of a process's
Expand Down
70 changes: 70 additions & 0 deletions tests/sh_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@
tempdir = Path(tempfile.gettempdir()).resolve()
IS_MACOS = platform.system() in ("AIX", "Darwin")

SIGNALS_THAT_SHOULD_THROW_EXCEPTION = [
signal.SIGABRT,
signal.SIGBUS,
signal.SIGFPE,
signal.SIGILL,
signal.SIGINT,
signal.SIGKILL,
signal.SIGPIPE,
signal.SIGQUIT,
signal.SIGSEGV,
signal.SIGTERM,
signal.SIGSYS,
]


def hash(a: str):
h = md5(a.encode("utf8") + RAND_BYTES)
Expand Down Expand Up @@ -87,6 +101,7 @@ def append_module_path(env, m):
append_module_path(baked_env, sh)
python = system_python.bake(_env=baked_env, _return_cmd=True)
pythons = python.bake(_return_cmd=False)
python_bg = system_python.bake(_env=baked_env, _bg=True)


def requires_progs(*progs):
Expand Down Expand Up @@ -3137,6 +3152,61 @@ def test_unchecked_pipeline_failure(self):
ErrorReturnCode_2, python, middleman_normal_pipe, consumer.name
)

def test_bad_sig_raise_exception(self):
# test all bad signal are correctly raised
py = create_tmp_test(
"""
import time
import sys
time.sleep(2)
sys.exit(1)
"""
)
for sig in SIGNALS_THAT_SHOULD_THROW_EXCEPTION:
if sig == signal.SIGPIPE:
continue
sig_exception_name = f"SignalException_{sig}"
sig_exception = getattr(sh, sig_exception_name)
try:
p = python_bg(py.name)
time.sleep(0.5)
p.signal(sig)
p.wait()
except sig_exception:
pass
else:
self.fail(f"{sig_exception_name} not raised")

def test_ok_code_ignores_bad_sig_exception(self):
# Test if I have [-sig] in _ok_code, the exception won't be raised
py = create_tmp_test(
"""
import time
import sys
time.sleep(2)
sys.exit(1)
"""
)
for sig in SIGNALS_THAT_SHOULD_THROW_EXCEPTION:
if sig == signal.SIGPIPE:
continue
sig_exception_name = f"SignalException_{sig}"
sig_exception = getattr(sh, sig_exception_name)
python_bg_no_sig_exception = python_bg.bake(_ok_code=[-sig])
try:
p = python_bg_no_sig_exception(py.name)
time.sleep(0.5)
p.signal(sig)
p.wait()
except sig_exception:
self.fail(
f"{sig_exception_name} should not be raised setting _ok_code."
)
else:
self.assertEqual(p.exit_code, -sig)


class MockTests(BaseTests):
def test_patch_command_cls(self):
Expand Down

0 comments on commit a834c0d

Please sign in to comment.