Skip to content

Commit

Permalink
BUG FIX: now correctly handles condition when the user specifies usin… (
Browse files Browse the repository at this point in the history
#36)

* BUG FIX: now correctly handles condition when the user specifies using plots, but libraries are not installed gracefully (i.e. reports the error as a warning instead of halting).

* Actually remembered to bump the version this time.

* Added (interim) fixes to allow windows to run the tests. Added `cleanup` from pytest-cov for thread tests.

* Accidentally a python module dep
  • Loading branch information
peads authored Jul 21, 2024
1 parent 40e9724 commit 66900a5
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
cd $WD;
python -m pip install --upgrade pip
PIP_NO_BINARY="" pip install . --upgrade
pip install flake8 pytest --upgrade
pip install flake8 pytest pytest-cov --upgrade
chmod +x example.sh;
- name: Lint with flake8
run: |
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ sources = ["src"]

[project]
name = "sdrterm"
version = "0.3"
version = "0.3.1"
dependencies = [
'scipy',
'numpy>=1.26',
Expand Down
51 changes: 13 additions & 38 deletions src/misc/general_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from contextlib import closing
from os import name as osName
from signal import SIGTERM, SIGABRT, Signals, signal, getsignal, SIGINT
from os import name as osName, getpid
from signal import SIGTERM, Signals, signal, SIGINT
from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR, SHUT_RDWR
from sys import stderr
from types import FrameType
Expand Down Expand Up @@ -110,9 +110,9 @@ def findMtu(sock: socket) -> int:
return min((filtered_stats[k] for k in filtered_addrs.keys()))


def killChildren(pid, sig):
def killChildren(_, sig):
from psutil import Process, NoSuchProcess
parent = Process(pid)
parent = Process(getpid())
children = parent.children(recursive=True)
for child in children:
try:
Expand All @@ -122,56 +122,31 @@ def killChildren(pid, sig):
pass


def __iAlreadyAxedYouOnce(pid: int, handlers: dict) -> Callable[[int, FrameType | None], None]:
def handleSignal(sig: int, frame: FrameType) -> None:
tprint(f'Frame: {frame}')
vprint(f'pid {pid} caught: {Signals(sig).name}')
if sig in handlers.keys():
newHandler = handlers.pop(sig)
signal(sig, newHandler)
tprint(f'Reset signal handler from {handleSignal} back to {newHandler}')

isPosix = 'posix' in osName
killChildren(pid, sig if isPosix or sig != SIGINT else SIGTERM)
if isPosix:
from os import getpgid, killpg
pgid = getpgid(pid)
tprint(f'Re-throwing {Signals(sig).name} to pgid: {pgid}')
killpg(pgid, sig)

return handleSignal


def __extendSignalHandlers(pid: int, handlers: dict, handlePostSignal: Callable[[], None]) \
def __extendSignalHandlers(pid: int, sigs: tuple, handlePostSignal: Callable[[], None]) \
-> Callable[[int, FrameType | None], None]:
def handleSignal(sig: int, frame: FrameType) -> None:
tprint(f'Frame: {frame}')
vprint(f'pid {pid} caught: {Signals(sig).name}')
signal(sig, __iAlreadyAxedYouOnce(pid, handlers))
tprint(f'Handlers after processing: {handlers}')
eprint(f'pid {pid} caught: {Signals(sig).name}')
handlePostSignal()
tprint(f'Handlers after processing: {[signal(s, killChildren) for s in sigs]}')

return handleSignal


def setSignalHandlers(pid: int, func: Callable[[], None]):
signals = [SIGTERM, SIGABRT, SIGINT]
handlers = {}
signals = [SIGTERM, SIGINT]
if 'nt' in osName:
from signal import SIGBREAK
signals.append(SIGBREAK)
elif 'posix' in osName:
from signal import SIGQUIT, SIGTSTP, SIGHUP, SIGTTIN, SIGTTOU, SIGXCPU
signals.append(SIGQUIT)
signals.append(SIGHUP)
signals.append(SIGXCPU)
from signal import SIGABRT, SIGQUIT, SIGTSTP, SIGHUP, SIGTTIN, SIGTTOU, SIGXCPU
signals.extend([SIGQUIT, SIGABRT, SIGHUP, SIGXCPU])

# disallow backgrounding from keyboard, except--obviously--if the terminal implements it as SIGSTOP
def ignore(s: int, _):
tprint(f'Ignored signal {Signals(s).name}')
vprint(f'Ignored signal {Signals(s).name}')

[signal(x, ignore) for x in [SIGTSTP, SIGTTIN, SIGTTOU]]
[signal(x, ignore) for x in (SIGTSTP, SIGTTIN, SIGTTOU)]

for sig in signals:
handlers[sig] = getsignal(sig)
signal(sig, __extendSignalHandlers(pid, handlers, func))
signal(sig, __extendSignalHandlers(pid, tuple(signals), func))
14 changes: 8 additions & 6 deletions src/misc/io_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def selectPlotType(plotType: str):
return WaterfallPlot
except ModuleNotFoundError as e:
printException(e)
return None
raise ValueError(f'Invalid plot type {plotType}')


Expand Down Expand Up @@ -125,12 +126,13 @@ def _initializeOutputHandlers(cls,
for p in pl.split(','):
psplot = selectPlotType(p)
kwargs['bandwidth'] = cls.strct['processor'].bandwidth
buffer, proc = cls._initializeProcess(isDead,
psplot,
fs, name="Plotter-",
**kwargs)
processes.append(proc)
buffers.append(buffer)
if psplot is not None:
buffer, proc = cls._initializeProcess(isDead,
psplot,
fs, name="Plotter-",
**kwargs)
processes.append(proc)
buffers.append(buffer)

buffer, proc = cls._initializeProcess(isDead,
cls.strct['processor'],
Expand Down
9 changes: 9 additions & 0 deletions test/misc/file_util_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from numpy import dtype

from misc.file_util import checkWavHeader, DataType
from misc.mappable_enum import MappableEnum

builtinsOpen = builtins.open

Expand Down Expand Up @@ -303,7 +304,15 @@ def test_checkAlawHeader(alawHeader):


def test_enum():
assert not len(MappableEnum.tuples())
assert not len(MappableEnum.dict())
for x, y in zip(DataType, DataType.dict().items()):
k, v = y
assert x.name == k
assert x.value == v
print(f'Matched {k}: {v}')
for x, y in zip(DataType, DataType.tuples()):
k, v = y
assert x.name == k
assert x.value == v
print(f'Matched {k}: {v}')
69 changes: 46 additions & 23 deletions test/misc/general_util_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
from multiprocessing import get_context, Value, Event

import pytest
from pytest_cov.embed import cleanup

from misc.general_util import setSignalHandlers, traceOn


def fail(value: Value):
value.value = 1
def fail(isDead: Value):
isDead.value = 1
cleanup()


def target(pid: Value, event: Event, value: Value):
setSignalHandlers(os.getpid(), lambda: fail(value))
def target(pid: Value, event: Event, isDead: Value):
setSignalHandlers(os.getpid(), lambda: fail(isDead))
traceOn()
pid.value = os.getpid()
event.set()
while not value.value:
while not isDead.value:
pass


Expand All @@ -26,31 +28,52 @@ def context():
return ctx, ctx.Value('B', 0), ctx.Event(), ctx.Value('L', 0)


def test_general_util(context):
@pytest.fixture(scope='function')
def signals():
ret = [signal.SIGTERM, signal.SIGINT]
if 'posix' not in os.name:
ret.append(signal.SIGBREAK)
ignoredSignals = ()
else:
ret.extend([signal.SIGABRT, signal.SIGQUIT, signal.SIGHUP, signal.SIGXCPU])
ignoredSignals = (signal.SIGTSTP, signal.SIGTTIN, signal.SIGTTOU)
return ret, ignoredSignals

def test_general_util(context, signals):
print('\n')
ctx, value, event, pid = context
for x in [signal.SIGINT, signal.SIGQUIT, signal.SIGHUP, signal.SIGTERM, signal.SIGXCPU, signal.SIGABRT, ]:
thread = ctx.Process(target=target, args=(pid, event, value,))
ctx, isDead, event, pid = context
sigs, ignoredSignals = signals
for x in sigs:
isDead.value = 0
thread = ctx.Process(target=target, args=(pid, event, isDead))
thread.start()
event.wait()
print(f'Sent {signal.Signals(x).name} to {pid.value}')
[os.kill(pid.value, x) for x in (signal.SIGTSTP, signal.SIGTTIN, signal.SIGTTOU)]
print(f'Sending {signal.Signals(x).name} to {pid.value}')
[os.kill(pid.value, x) for x in ignoredSignals]
os.kill(pid.value, x)
[os.kill(pid.value, x) for x in (signal.SIGTSTP, signal.SIGTTIN, signal.SIGTTOU)]
thread.join()
assert value.value
if 'posix' in os.name:
assert isDead.value
else:
assert x == thread.exitcode
print(f'Sent {signal.Signals(x).name} to {pid.value}')
del thread
event.clear()
value.value = 0


def test_general_util2(context):
def test_general_util2(context, signals):
if 'posix' not in os.name:
return
traceOn()
print('\n')
value = context[1]
setSignalHandlers(os.getpid(), lambda: fail(value))
for x in [signal.SIGINT, signal.SIGQUIT, signal.SIGHUP, signal.SIGTERM, signal.SIGXCPU, signal.SIGABRT, ]:
[os.kill(os.getpid(), x) for x in (signal.SIGTSTP, signal.SIGTTIN, signal.SIGTTOU)]
os.kill(os.getpid(), x)
[os.kill(os.getpid(), x) for x in (signal.SIGTSTP, signal.SIGTTIN, signal.SIGTTOU)]
assert value.value
value.value = 0
ctx, isDead, event, pid = context
sigs, ignoredSignals = signals
for x in sigs:
setSignalHandlers(os.getpid(), lambda: fail(isDead))
isDead.value = 0
pid.value = os.getpid()
[os.kill(pid.value, x) for x in ignoredSignals]
print(f'Sending {signal.Signals(x).name} to {pid.value}')
os.kill(pid.value, x)
assert isDead.value
print(f'Sent {signal.Signals(x).name} to {pid.value}')
21 changes: 12 additions & 9 deletions test/misc/io_args_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import pytest

from misc.io_args import IOArgs, DemodulationChoices
from misc.io_args import IOArgs, DemodulationChoices, selectPlotType


def throwingFiles(*_, **__):
raise ModuleNotFoundError


@pytest.fixture(scope='function')
Expand Down Expand Up @@ -30,12 +34,11 @@ def test_ioargs(osEnv):
processes=[],
buffers=[],
**kwargs)
if osEnv is not None:
if osEnv is not None: # DO NOT REMOVE; it's to prevent github's containerized rigs from barfing during testing
os.environ['DISPLAY'] = osEnv
# TODO more tests
# os.environ.pop('DISPLAY')
# IOArgs._initializeOutputHandlers(fs=1024000,
# dm=DemodulationChoices.FM,
# processes=[],
# buffers=[],
# **kwargs)

import importlib.resources as rscs
ogFiles = rscs.files
rscs.files = throwingFiles
assert selectPlotType(DemodulationChoices.FM) is None
rscs.files = ogFiles

0 comments on commit 66900a5

Please sign in to comment.