From d6699481e6baab1e42137a3d3894038ec81df21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Harter?= Date: Mon, 28 Jan 2019 16:44:07 +0100 Subject: [PATCH 1/9] riotnode: add an empty package directory for 'riotnode' It is a python directory to start developing the package. I include all files around for testing and integrating in RIOT. Test dependencies in tox are already ready for testing with pytest. * sitecustomize.py: allow implementing 'riotnode' in a subdirectory * setup.py implement a releasable package * requirements.txt: uses 'setup.py' could then be used by our standard way of installing * tox.ini: run the test suite with 'tox' * setup.cfg/.coveragerc: test suites configuration --- dist/pythonlibs/riotnode/.coveragerc | 2 + dist/pythonlibs/riotnode/.gitignore | 112 ++++++++++++++++++ dist/pythonlibs/riotnode/README.rst | 28 +++++ dist/pythonlibs/riotnode/requirements.txt | 2 + dist/pythonlibs/riotnode/riotnode/__init__.py | 11 ++ .../riotnode/riotnode/tests/__init__.py | 1 + .../riotnode/riotnode/tests/riotnode_test.py | 10 ++ dist/pythonlibs/riotnode/setup.cfg | 15 +++ dist/pythonlibs/riotnode/setup.py | 48 ++++++++ dist/pythonlibs/riotnode/tox.ini | 32 +++++ dist/pythonlibs/sitecustomize.py | 7 ++ 11 files changed, 268 insertions(+) create mode 100644 dist/pythonlibs/riotnode/.coveragerc create mode 100644 dist/pythonlibs/riotnode/.gitignore create mode 100644 dist/pythonlibs/riotnode/README.rst create mode 100644 dist/pythonlibs/riotnode/requirements.txt create mode 100644 dist/pythonlibs/riotnode/riotnode/__init__.py create mode 100644 dist/pythonlibs/riotnode/riotnode/tests/__init__.py create mode 100644 dist/pythonlibs/riotnode/riotnode/tests/riotnode_test.py create mode 100644 dist/pythonlibs/riotnode/setup.cfg create mode 100644 dist/pythonlibs/riotnode/setup.py create mode 100644 dist/pythonlibs/riotnode/tox.ini create mode 100644 dist/pythonlibs/sitecustomize.py diff --git a/dist/pythonlibs/riotnode/.coveragerc b/dist/pythonlibs/riotnode/.coveragerc new file mode 100644 index 000000000000..3a2d4abfb5a2 --- /dev/null +++ b/dist/pythonlibs/riotnode/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = riotnode/tests/* diff --git a/dist/pythonlibs/riotnode/.gitignore b/dist/pythonlibs/riotnode/.gitignore new file mode 100644 index 000000000000..6091f4ddf75f --- /dev/null +++ b/dist/pythonlibs/riotnode/.gitignore @@ -0,0 +1,112 @@ +# Manually added: +# All xml reports +*.xml + +#### joe made this: http://goel.io/joe + +#####=== Python ===##### + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/dist/pythonlibs/riotnode/README.rst b/dist/pythonlibs/riotnode/README.rst new file mode 100644 index 000000000000..983f65e3c2ee --- /dev/null +++ b/dist/pythonlibs/riotnode/README.rst @@ -0,0 +1,28 @@ +RIOT Node abstraction +===================== + +This provides python object abstraction of a node. +The first goal is to be the starting point for the serial abstraction and +build on top of that to provide higher level abstraction like over the shell. + +It could provide an RPC interface to a node in Python over the serial port +and maybe also over network. + +The goal is here to be test environment agnostic and be usable in any test +framework and also without it. + + +Testing +------- + +Run `tox` to run the whole test suite: + +:: + + tox + ... + ________________________________ summary ________________________________ + test: commands succeeded + lint: commands succeeded + flake8: commands succeeded + congratulations :) diff --git a/dist/pythonlibs/riotnode/requirements.txt b/dist/pythonlibs/riotnode/requirements.txt new file mode 100644 index 000000000000..479e00d0a874 --- /dev/null +++ b/dist/pythonlibs/riotnode/requirements.txt @@ -0,0 +1,2 @@ +# Use the current setup.py for requirements +. diff --git a/dist/pythonlibs/riotnode/riotnode/__init__.py b/dist/pythonlibs/riotnode/riotnode/__init__.py new file mode 100644 index 000000000000..5df249a04756 --- /dev/null +++ b/dist/pythonlibs/riotnode/riotnode/__init__.py @@ -0,0 +1,11 @@ +"""RIOT Node abstraction. + +This prodives python object abstraction of a node. +The first goal is to be the starting point for the serial abstraction and +build on top of that to provide higher level abstraction like over the shell. + +It could provide an RPC interface to a node in Python over the serial port +and maybe also over network. +""" + +__version__ = '0.1.0' diff --git a/dist/pythonlibs/riotnode/riotnode/tests/__init__.py b/dist/pythonlibs/riotnode/riotnode/tests/__init__.py new file mode 100644 index 000000000000..b32d7cf7e2d9 --- /dev/null +++ b/dist/pythonlibs/riotnode/riotnode/tests/__init__.py @@ -0,0 +1 @@ +"""riotnode.tests directory.""" diff --git a/dist/pythonlibs/riotnode/riotnode/tests/riotnode_test.py b/dist/pythonlibs/riotnode/riotnode/tests/riotnode_test.py new file mode 100644 index 000000000000..5ef2b2d13bb1 --- /dev/null +++ b/dist/pythonlibs/riotnode/riotnode/tests/riotnode_test.py @@ -0,0 +1,10 @@ +"""riotnode.__init__ tests""" +import riotnode + + +def test_version(): + """Test there is an `__version__` attriubte. + + Goal is to have a test to run the test environment. + """ + assert getattr(riotnode, '__version__', None) is not None diff --git a/dist/pythonlibs/riotnode/setup.cfg b/dist/pythonlibs/riotnode/setup.cfg new file mode 100644 index 000000000000..16d2ea34817e --- /dev/null +++ b/dist/pythonlibs/riotnode/setup.cfg @@ -0,0 +1,15 @@ +[tool:pytest] +addopts = -v --junit-xml=test-report.xml + --doctest-modules + --cov=riotnode --cov-branch + --cov-report=term --cov-report=xml --cov-report=html +testpaths = riotnode + +[lint] +lint-reports = no +lint-disable = locally-disabled,star-args +lint-msg-template = {path}:{line}: [{msg_id}({symbol}), {obj}] {msg} + +[flake8] +exclude = .tox,dist,doc,build,*.egg +max-complexity = 10 diff --git a/dist/pythonlibs/riotnode/setup.py b/dist/pythonlibs/riotnode/setup.py new file mode 100644 index 000000000000..4f5d81d2f66d --- /dev/null +++ b/dist/pythonlibs/riotnode/setup.py @@ -0,0 +1,48 @@ +#! /usr/bin/env python3 + +import os +from setuptools import setup, find_packages + +PACKAGE = 'riotnode' +LICENSE = 'LGPLv2.1' +URL = 'https://github.com/RIOT-OS/RIOT' + + +def get_version(package): + """ Extract package version without importing file + Importing cause issues with coverage, + (modules can be removed from sys.modules to prevent this) + Importing __init__.py triggers importing rest and then requests too + + Inspired from pep8 setup.py + """ + with open(os.path.join(package, '__init__.py')) as init_fd: + for line in init_fd: + if line.startswith('__version__'): + return eval(line.split('=')[-1]) # pylint:disable=eval-used + return None + + +setup( + name=PACKAGE, + version=get_version(PACKAGE), + description='RIOTNode python abstraction', + long_description=open('README.rst').read(), + author='Gaëtan Harter', + author_email='gaetan.harter@fu-berlin.de', + url=URL, + license=LICENSE, + download_url=URL, + packages=find_packages(), + classifiers=['Development Status :: 2 - Pre-Alpha', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Intended Audience :: End Users/Desktop', + 'Environment :: Console', + 'Topic :: Utilities', ], + install_requires=[], + python_requires='>=3.5', +) diff --git a/dist/pythonlibs/riotnode/tox.ini b/dist/pythonlibs/riotnode/tox.ini new file mode 100644 index 000000000000..9c1ea46b723b --- /dev/null +++ b/dist/pythonlibs/riotnode/tox.ini @@ -0,0 +1,32 @@ +[tox] +envlist = test,lint,flake8 + +[testenv] +basepython = python3 +passenv = RIOTBASE +setenv = + RIOTBASE = {toxinidir}/../../.. + package = riotnode +commands = + test: {[testenv:test]commands} + lint: {[testenv:lint]commands} + flake8: {[testenv:flake8]commands} + +[testenv:test] +deps = + pytest + pytest-cov +commands = + pytest + +[testenv:lint] +deps = + pylint + pytest +commands = + pylint {envsitepackagesdir}/{env:package} + +[testenv:flake8] +deps = flake8 +commands = + flake8 diff --git a/dist/pythonlibs/sitecustomize.py b/dist/pythonlibs/sitecustomize.py new file mode 100644 index 000000000000..cb020d758b47 --- /dev/null +++ b/dist/pythonlibs/sitecustomize.py @@ -0,0 +1,7 @@ +import os +import sys + +# Allow importing packages implemented in sub directories +# Prepend as the directory has the same name as the package + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'riotnode')) From a40f35e5a7d2c6c41de251157ea242b8ad7a65d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Harter?= Date: Tue, 5 Feb 2019 12:51:05 +0100 Subject: [PATCH 2/9] riotnode: add node implementation and tests The abstraction is a basic class to replicate behavior that was in `testrunner. Implementation was sometimes directly taken from `testrunner` without being technically justified to keep backward compatibility. This is described in `TODO.rst` and dedicated implementation. This also adds a test `node` implementation with a Makefile. It is currently an easy to use node as no output is lost and reset correctly resets. TODO: Maybe put this as a comment in the file When pexpect 'close' a 'ptyprocess' it starts by 'SIGHUP'. It is not handled by default to call 'atexit'. To not do a specific handling for 'SIGHUP' just forward all signals to the child. This wrapper should not do any specific things anyway. TODO: Split the 'safe_term_close' to utils with a test not using Make term and so directly only wanting 'sigkill' This should allow merging it before and using it in testrunner TODO: Settle the `env` handling. Get feedback on what to do here. Disable local echo otherwise pexpect matches sent characters. --- dist/pythonlibs/riotnode/TODO.rst | 24 +++ dist/pythonlibs/riotnode/out.pdf | Bin 0 -> 97967 bytes dist/pythonlibs/riotnode/riotnode/node.py | 175 ++++++++++++++++++ .../riotnode/riotnode/tests/node_test.py | 155 ++++++++++++++++ .../riotnode/tests/utils/application/Makefile | 16 ++ .../riotnode/tests/utils/application/echo.py | 16 ++ .../riotnode/tests/utils/application/hello.py | 17 ++ .../riotnode/tests/utils/application/node.py | 74 ++++++++ dist/pythonlibs/riotnode/setup.py | 2 +- dist/pythonlibs/riotnode/tox.ini | 1 + 10 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 dist/pythonlibs/riotnode/TODO.rst create mode 100644 dist/pythonlibs/riotnode/out.pdf create mode 100644 dist/pythonlibs/riotnode/riotnode/node.py create mode 100644 dist/pythonlibs/riotnode/riotnode/tests/node_test.py create mode 100644 dist/pythonlibs/riotnode/riotnode/tests/utils/application/Makefile create mode 100755 dist/pythonlibs/riotnode/riotnode/tests/utils/application/echo.py create mode 100755 dist/pythonlibs/riotnode/riotnode/tests/utils/application/hello.py create mode 100755 dist/pythonlibs/riotnode/riotnode/tests/utils/application/node.py diff --git a/dist/pythonlibs/riotnode/TODO.rst b/dist/pythonlibs/riotnode/TODO.rst new file mode 100644 index 000000000000..a5b775d39e68 --- /dev/null +++ b/dist/pythonlibs/riotnode/TODO.rst @@ -0,0 +1,24 @@ +TODO list +========= + +Some list of things I would like to do but not for first publication. + + +Legacy handling +--------------- + +Some handling was directly taken from ``testrunner``, without a justified/tested +reason. I just used it to not break existing setup for nothing. +I have more details in the code. + +* Ignoring reset return value and error message +* Use killpg(SIGKILL) to kill terminal + + +Testing +------- + +The current 'node' implementation is an ideal node where all output is captured +and reset directly resets. Having wilder implementations with output loss (maybe +as a deamon with a ``flash`` pre-requisite and sometime no ``reset`` would be +interesting. diff --git a/dist/pythonlibs/riotnode/out.pdf b/dist/pythonlibs/riotnode/out.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f75d9a10ba12fd2a638ed588d5f5dad46494e389 GIT binary patch literal 97967 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 qIdu8==%EzPw;8/dev/null || true + +term: + sh -c 'echo $$$$ > $(PIDFILE); exec $(NODE_WRAPPER) $(APPLICATION)' diff --git a/dist/pythonlibs/riotnode/riotnode/tests/utils/application/echo.py b/dist/pythonlibs/riotnode/riotnode/tests/utils/application/echo.py new file mode 100755 index 000000000000..6e716f3b8f91 --- /dev/null +++ b/dist/pythonlibs/riotnode/riotnode/tests/utils/application/echo.py @@ -0,0 +1,16 @@ +#! /usr/bin/env python3 +"""Firmware implementing echoing line inputs.""" + +import sys + + +def main(): + """Print some header and echo the output.""" + print('Starting RIOT node') + print('This example will echo') + while True: + print(input()) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/dist/pythonlibs/riotnode/riotnode/tests/utils/application/hello.py b/dist/pythonlibs/riotnode/riotnode/tests/utils/application/hello.py new file mode 100755 index 000000000000..5c6d09cabb2c --- /dev/null +++ b/dist/pythonlibs/riotnode/riotnode/tests/utils/application/hello.py @@ -0,0 +1,17 @@ +#! /usr/bin/env python3 +"""Firmware implementing a simple hello-world.""" + +import sys +import signal + + +def main(): + """Print some header and do nothing.""" + print('Starting RIOT node') + print('Hello World') + while True: + signal.pause() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/dist/pythonlibs/riotnode/riotnode/tests/utils/application/node.py b/dist/pythonlibs/riotnode/riotnode/tests/utils/application/node.py new file mode 100755 index 000000000000..a3a9abed5551 --- /dev/null +++ b/dist/pythonlibs/riotnode/riotnode/tests/utils/application/node.py @@ -0,0 +1,74 @@ +#! /usr/bin/env python3 +"""Wrap an application to behave like a board firmware. + ++ Start a command given as argument ++ Handle 'reset' the firmware when receiving `SIGUSR1` + + +Ideas for extensions: + +* resetting or not on reset +* See how to implement loosing some of the output on first startup +""" + + +import sys +import signal +import threading +import argparse +import subprocess + +PARSER = argparse.ArgumentParser() +PARSER.add_argument('argument', nargs='+', default=[]) + +# Signals sent by 'pexpect' + SIGTERM +FORWARDED_SIGNALS = (signal.SIGHUP, signal.SIGCONT, signal.SIGINT, + signal.SIGTERM) + + +def forward_signal(signum, proc): + """Forward signal to child.""" + if not proc.poll(): + proc.send_signal(signum) + + +def _run_cmd(args, termonsig=signal.SIGUSR1, **popenkwargs): + """Run a subprocess of `args`. + + It will be terminated on `termonsig` signal. + + :param args: command arguments + :param termonsig: terminate the process on `termonsig` signal + :param **popenkwargs: Popen kwargs + :return: True if process should be restarted + """ + restart_process = threading.Event() + proc = subprocess.Popen(args, **popenkwargs) + + # Forward cleanup processes to child + for sig in FORWARDED_SIGNALS: + signal.signal(sig, lambda signum, _: forward_signal(signum, proc)) + + # set 'termonsig' handler for reset + def _reset(*_): + """Terminate process and set the 'restart_process' flag.""" + restart_process.set() + proc.terminate() + signal.signal(termonsig, _reset) + + proc.wait() + return restart_process.is_set() + + +def main(): + """Run an application in a loop. + + On 'SIGUSR1' the application will be reset. + """ + args = PARSER.parse_args() + while _run_cmd(args.argument): + pass + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/dist/pythonlibs/riotnode/setup.py b/dist/pythonlibs/riotnode/setup.py index 4f5d81d2f66d..c3b945cd97c4 100644 --- a/dist/pythonlibs/riotnode/setup.py +++ b/dist/pythonlibs/riotnode/setup.py @@ -43,6 +43,6 @@ def get_version(package): 'Intended Audience :: End Users/Desktop', 'Environment :: Console', 'Topic :: Utilities', ], - install_requires=[], + install_requires=['pexpect'], python_requires='>=3.5', ) diff --git a/dist/pythonlibs/riotnode/tox.ini b/dist/pythonlibs/riotnode/tox.ini index 9c1ea46b723b..37670ebe1c11 100644 --- a/dist/pythonlibs/riotnode/tox.ini +++ b/dist/pythonlibs/riotnode/tox.ini @@ -25,6 +25,7 @@ deps = pytest commands = pylint {envsitepackagesdir}/{env:package} + # This does not check files in 'tests/utils/application' [testenv:flake8] deps = flake8 From 1bcfacb6b96b4128566c9886b573592523d4e8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Harter?= Date: Mon, 4 Feb 2019 16:35:36 +0100 Subject: [PATCH 3/9] riotnode: Replace pexpect exception value with pattern This allows finer analysis of exception as sometimes the calling line is only `expect(variable)` without knowing the value. --- dist/pythonlibs/riotnode/riotnode/node.py | 26 ++++++++++++++++++ .../riotnode/riotnode/tests/node_test.py | 27 +++++++++++++++++++ dist/pythonlibs/testrunner/__init__.py | 4 +-- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/dist/pythonlibs/riotnode/riotnode/node.py b/dist/pythonlibs/riotnode/riotnode/node.py index 9f6999303bd4..93bb1315c142 100644 --- a/dist/pythonlibs/riotnode/riotnode/node.py +++ b/dist/pythonlibs/riotnode/riotnode/node.py @@ -22,6 +22,8 @@ class TermSpawn(pexpect.spawn): * disable local 'echo' to not match send messages * 'utf-8/replace' by default * default timeout + * tweak exception: + * replace the value with the called pattern """ def __init__(self, # pylint:disable=too-many-arguments @@ -31,6 +33,30 @@ def __init__(self, # pylint:disable=too-many-arguments encoding=encoding, codec_errors=codec_errors, **kwargs) + def expect(self, pattern, *args, **kwargs): + # pylint:disable=arguments-differ + try: + return super().expect(pattern, *args, **kwargs) + except (pexpect.TIMEOUT, pexpect.EOF) as exc: + raise self._pexpect_exception(exc, pattern) + + def expect_exact(self, pattern, *args, **kwargs): + # pylint:disable=arguments-differ + try: + return super().expect_exact(pattern, *args, **kwargs) + except (pexpect.TIMEOUT, pexpect.EOF) as exc: + raise self._pexpect_exception(exc, pattern) + + @staticmethod + def _pexpect_exception(exc, pattern): + """Tweak pexpect exception. + + * Put the calling 'pattern' as value + """ + exc.pexpect_value = exc.value + exc.value = pattern + return exc + class RIOTNode(): """Class abstracting a RIOTNode in an application. diff --git a/dist/pythonlibs/riotnode/riotnode/tests/node_test.py b/dist/pythonlibs/riotnode/riotnode/tests/node_test.py index 8cf28fc40210..17993e56cf18 100644 --- a/dist/pythonlibs/riotnode/riotnode/tests/node_test.py +++ b/dist/pythonlibs/riotnode/riotnode/tests/node_test.py @@ -153,3 +153,30 @@ def test_expect_not_matching_stdin(app_pidfile_env): matched = child.expect_exact([pexpect.TIMEOUT, msg], timeout=1) assert matched == 0 # This would have matched with `node.run_term(echo=True)` + + +def test_expect_value(app_pidfile_env): + """Test that expect value is being changed to the pattern.""" + env = {'BOARD': 'board', 'APPLICATION': './echo.py'} + env.update(app_pidfile_env) + + node = riotnode.node.RIOTNode(APPLICATIONS_DIR, env) + node.TERM_STARTED_DELAY = 1 + + with node.run_term(logfile=sys.stdout) as child: + child.expect_exact('Starting RIOT node') + + # Exception is 'exc_info.value' and pattern is in 'exc.value' + child.sendline('lowercase') + with pytest.raises(pexpect.TIMEOUT) as exc_info: + child.expect('UPPERCASE', timeout=0.5) + assert str(exc_info.value) == 'UPPERCASE' + + # value updated and old value saved + assert exc_info.value.value == 'UPPERCASE' + assert exc_info.value.pexpect_value.startswith('Timeout exceeded.') + + child.sendline('lowercase') + with pytest.raises(pexpect.TIMEOUT) as exc_info: + child.expect_exact('UPPERCASE', timeout=0.5) + assert str(exc_info.value) == 'UPPERCASE' diff --git a/dist/pythonlibs/testrunner/__init__.py b/dist/pythonlibs/testrunner/__init__.py index 31fa260eb469..a6e80e5b9e9b 100755 --- a/dist/pythonlibs/testrunner/__init__.py +++ b/dist/pythonlibs/testrunner/__init__.py @@ -28,13 +28,13 @@ def run(testfunc, timeout=TIMEOUT, echo=True, traceback=False): try: testfunc(child) except pexpect.TIMEOUT: - trace = find_exc_origin(sys.exc_info()[2]) + trace = find_exc_origin(sys.exc_info()[2], pexpect_path=pexpect_path) print("Timeout in expect script at \"%s\" (%s:%d)" % trace) if traceback: print_tb(sys.exc_info()[2]) return 1 except pexpect.EOF: - trace = find_exc_origin(sys.exc_info()[2]) + trace = find_exc_origin(sys.exc_info()[2], pexpect_path=pexpect_path) print("Unexpected end of file in expect script at \"%s\" (%s:%d)" % trace) if traceback: From 3648088ac83b99f36360ecd64c0e63dd41e7f8e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Harter?= Date: Mon, 4 Feb 2019 16:35:36 +0100 Subject: [PATCH 4/9] riotnode: Remove exception ctx from inside pexpect implementation This allows having a nicely printed output on `pytest`. This could be moved outside of here too. --- dist/pythonlibs/riotnode/riotnode/node.py | 6 ++++++ dist/pythonlibs/riotnode/riotnode/tests/node_test.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/dist/pythonlibs/riotnode/riotnode/node.py b/dist/pythonlibs/riotnode/riotnode/node.py index 93bb1315c142..edebc00facc5 100644 --- a/dist/pythonlibs/riotnode/riotnode/node.py +++ b/dist/pythonlibs/riotnode/riotnode/node.py @@ -24,6 +24,7 @@ class TermSpawn(pexpect.spawn): * default timeout * tweak exception: * replace the value with the called pattern + * remove exception context from inside pexpect implementation """ def __init__(self, # pylint:disable=too-many-arguments @@ -52,9 +53,14 @@ def _pexpect_exception(exc, pattern): """Tweak pexpect exception. * Put the calling 'pattern' as value + * Remove exception context """ exc.pexpect_value = exc.value exc.value = pattern + + # Remove exception context + exc.__cause__ = None + exc.__traceback__ = None return exc diff --git a/dist/pythonlibs/riotnode/riotnode/tests/node_test.py b/dist/pythonlibs/riotnode/riotnode/tests/node_test.py index 17993e56cf18..b4ca9e74f9a1 100644 --- a/dist/pythonlibs/riotnode/riotnode/tests/node_test.py +++ b/dist/pythonlibs/riotnode/riotnode/tests/node_test.py @@ -176,6 +176,9 @@ def test_expect_value(app_pidfile_env): assert exc_info.value.value == 'UPPERCASE' assert exc_info.value.pexpect_value.startswith('Timeout exceeded.') + # check the context is removed (should be only 2 levels) + assert len(exc_info.traceback) == 2 + child.sendline('lowercase') with pytest.raises(pexpect.TIMEOUT) as exc_info: child.expect_exact('UPPERCASE', timeout=0.5) From ab730b0f2a8ed7950f59d1ec6ac9639ed25e6250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Harter?= Date: Tue, 19 Feb 2019 19:13:49 +0100 Subject: [PATCH 5/9] riotnode/utils: add function to handle subprocesses Add functions that handle killing all child processes using psutil. This allows killing child processes that may not have been correctly terminated by its parent. It should not be necessary in an ideal world, but it would allow detecting errors in the tests. --- .../riotnode/riotnode/tests/utils_test.py | 123 ++++++++++++++++++ dist/pythonlibs/riotnode/riotnode/utils.py | 69 ++++++++++ dist/pythonlibs/riotnode/setup.py | 2 +- 3 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 dist/pythonlibs/riotnode/riotnode/tests/utils_test.py create mode 100644 dist/pythonlibs/riotnode/riotnode/utils.py diff --git a/dist/pythonlibs/riotnode/riotnode/tests/utils_test.py b/dist/pythonlibs/riotnode/riotnode/tests/utils_test.py new file mode 100644 index 000000000000..9e627a3a6fe2 --- /dev/null +++ b/dist/pythonlibs/riotnode/riotnode/tests/utils_test.py @@ -0,0 +1,123 @@ +"""rionode.utils test module.""" + +import time +import multiprocessing +import logging + +import psutil +import pytest + +import riotnode.utils + + +def _process_spawn(num, queue): + """Function for a process which spawns `num` number of processes and reports + their PIDs back. + """ + procs = [] + procs_pids = [] + + for _ in range(num): + proc = multiprocessing.Process(target=_process_wait) + proc.start() + procs.append(proc) + procs_pids.append(proc.pid) + + queue.put(procs_pids) + + # wait for all + for proc in procs: + proc.join() + + +def _process_wait(): + """Function for a dummy process which does nothing.""" + while True: + time.sleep(1) + + +def test_ensuring_all_subproc_are_stopped(): + """Tests that all subprocesses stopped.""" + queue = multiprocessing.Queue() + spawner = multiprocessing.Process(target=_process_spawn, args=(2, queue)) + spawner.start() + procs_pids = queue.get() + + procs = [] + for pid in procs_pids: + procs.append(psutil.Process(pid)) + + # should raise an exception as processes are not stopped + with pytest.raises(RuntimeError): + with riotnode.utils.ensure_all_subprocesses_stopped(spawner.pid, + logging): + pass + + # now all processes should be stopped + running = False + for proc in procs: + if proc.is_running(): + running = True + proc.terminate() + + try: + parent = psutil.Process(spawner.pid) + if parent.is_running(): + running = True + parent.terminate() + except psutil.NoSuchProcess: + pass + + assert not running + + +def test_getting_pid_subprocesses(): + """Tests getting all subprocesses PIDs.""" + queue = multiprocessing.Queue() + spawner = multiprocessing.Process(target=_process_spawn, args=(2, queue)) + spawner.start() + procs_pids = queue.get() + + subprocesses = riotnode.utils.pid_subprocesses(spawner.pid) + subprocesses_pids = (s.pid for s in subprocesses) + + try: + # returns all subprocesses and the parent + assert len(subprocesses) == len(procs_pids) + 1 + assert spawner.pid in subprocesses_pids + for pid in procs_pids: + assert pid in subprocesses_pids + + assert riotnode.utils.pid_subprocesses(None) == [] + finally: + for proc in subprocesses: + proc.terminate() + + +def test_ensuring_all_procs_are_stopped(): + """Tests that all processes are stopped.""" + queue = multiprocessing.Queue() + spawner = multiprocessing.Process(target=_process_spawn, args=(2, queue)) + spawner.start() + procs_pids = queue.get() + + procs = [] + for pid in procs_pids: + procs.append(psutil.Process(pid)) + + # Should raise exception as processes are running + with pytest.raises(RuntimeError): + riotnode.utils.ensure_processes_stopped(procs, logging) + + # All processes should be stopped + running = False + for proc in procs: + if proc.is_running(): + running = True + proc.terminate() + + spawner.terminate() + assert not running + + # Now it should not raise exceptions + riotnode.utils.ensure_processes_stopped(procs, logging) diff --git a/dist/pythonlibs/riotnode/riotnode/utils.py b/dist/pythonlibs/riotnode/riotnode/utils.py new file mode 100644 index 000000000000..1e621a3feb62 --- /dev/null +++ b/dist/pythonlibs/riotnode/riotnode/utils.py @@ -0,0 +1,69 @@ +"""Some utilities functions""" + +import contextlib + +import psutil + + +@contextlib.contextmanager +def ensure_all_subprocesses_stopped(pid, logger, timeout=3): + """Ensure all subprocesses for 'pid' are correctly stopped. + + context manager. + """ + try: + processes = pid_subprocesses(pid) + yield + finally: + ensure_processes_stopped(processes, logger, timeout=timeout) + + +def pid_subprocesses(pid): + """Return subprocesses for pid including itself. + + If pid is None return nothing. + """ + if pid is None: + return [] + + try: + proc = psutil.Process(pid) + processes = [proc] + processes.extend(proc.children(recursive=True)) + return processes + except psutil.NoSuchProcess: + return [] + + +def ensure_processes_stopped(processes, logger, timeout=3): + """Ensure processes are correctly stopped and kill them if not. + + Raise a RuntimeError if it is not the case. + """ + # Use a list to call all of them + stopped_processes = [_ensure_process_stopped(p, logger, timeout) + for p in processes] + if not all(stopped_processes): + raise RuntimeError('Some term process where not stopped') + + +def _ensure_process_stopped(proc, logger, timeout=3): + """Ensure the given process has been stopped. + + If it has not, do a critical logging message and kill it. + :type proc: psutil.Process + :return: True if process was stopped + """ + if not proc.is_running(): + return True + logger.critical('process %u:%s was not stopped', proc.pid, proc.status()) + + proc.kill() + try: + proc.wait(timeout) + except psutil.TimeoutExpired: # pragma: no cover + # No sure how to cover this… + logger.critical( + 'process %u:%s is not killable', proc.pid, proc.status()) + + return False diff --git a/dist/pythonlibs/riotnode/setup.py b/dist/pythonlibs/riotnode/setup.py index c3b945cd97c4..7f5b5591ff9e 100644 --- a/dist/pythonlibs/riotnode/setup.py +++ b/dist/pythonlibs/riotnode/setup.py @@ -43,6 +43,6 @@ def get_version(package): 'Intended Audience :: End Users/Desktop', 'Environment :: Console', 'Topic :: Utilities', ], - install_requires=['pexpect'], + install_requires=['pexpect', 'psutil'], python_requires='>=3.5', ) From 3213d72225621454c897b38743eb540db9c894c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Harter?= Date: Tue, 19 Feb 2019 14:05:15 +0100 Subject: [PATCH 6/9] riotnode/tests: add a test for term sigkill This shows the current handling that could correctly terminate a subprocess that is not always killed. It is here as a regression test when removing the process group sigkill. --- .../riotnode/riotnode/tests/node_test.py | 20 +++++++++++++++++++ .../tests/utils/application/sigkill_script.py | 18 +++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100755 dist/pythonlibs/riotnode/riotnode/tests/utils/application/sigkill_script.py diff --git a/dist/pythonlibs/riotnode/riotnode/tests/node_test.py b/dist/pythonlibs/riotnode/riotnode/tests/node_test.py index b4ca9e74f9a1..f7da59044052 100644 --- a/dist/pythonlibs/riotnode/riotnode/tests/node_test.py +++ b/dist/pythonlibs/riotnode/riotnode/tests/node_test.py @@ -2,6 +2,7 @@ import os import sys +import signal import tempfile import pytest @@ -183,3 +184,22 @@ def test_expect_value(app_pidfile_env): with pytest.raises(pexpect.TIMEOUT) as exc_info: child.expect_exact('UPPERCASE', timeout=0.5) assert str(exc_info.value) == 'UPPERCASE' + + +def test_killing_a_broken_term(app_pidfile_env): + """Test killing a terminal that can only be killed with SIGKILL.""" + env = {'BOARD': 'board', 'APPLICATION': './sigkill_script.py'} + env.update(app_pidfile_env) + + node = riotnode.node.RIOTNode(APPLICATIONS_DIR, env) + node.TERM_STARTED_DELAY = 1 + + with node.run_term(logfile=sys.stdout) as child: + child.expect_exact('Kill me with SIGKILL!') + child.expect(r'My PID: (\d+)') + term_pid = int(child.match.group(1)) + + # Send a SIGKILL to the process, it should raise an error as it is stopped + # And if it was running, it will be cleaned + with pytest.raises(ProcessLookupError): + os.kill(term_pid, signal.SIGKILL) diff --git a/dist/pythonlibs/riotnode/riotnode/tests/utils/application/sigkill_script.py b/dist/pythonlibs/riotnode/riotnode/tests/utils/application/sigkill_script.py new file mode 100755 index 000000000000..5a2c036386da --- /dev/null +++ b/dist/pythonlibs/riotnode/riotnode/tests/utils/application/sigkill_script.py @@ -0,0 +1,18 @@ +#! /usr/bin/env python3 +"""Ignore SIGINT/SIGTERM/SIGHUP, kill with SIGKILL.""" +import os +import signal + + +def main(): + """Only kill this program with SIGKILL.""" + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) + signal.signal(signal.SIGHUP, signal.SIG_IGN) + print('Kill me with SIGKILL!') + print('My PID: %u' % os.getpid()) + signal.pause() + + +if __name__ == '__main__': + main() From 526607f783febd77c3901d4042452042c0b5e467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Harter?= Date: Tue, 19 Feb 2019 17:56:45 +0100 Subject: [PATCH 7/9] riotnode/node: ensure children processes are cleaned up In case all children are not correctly cleaned by '_safe_term_close', sigkill them anyway. This prepares for replacing the global kill with SIGKILL with a less violent method. Also, adds a test where the terminal program tries to do a cleanup. It is important that a terminal can clean itself correctly to close files, or remove created interfaces. --- dist/pythonlibs/riotnode/riotnode/node.py | 10 +++- .../riotnode/riotnode/tests/node_test.py | 49 +++++++++++++++++++ .../tests/utils/application/create_file.py | 36 ++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100755 dist/pythonlibs/riotnode/riotnode/tests/utils/application/create_file.py diff --git a/dist/pythonlibs/riotnode/riotnode/node.py b/dist/pythonlibs/riotnode/riotnode/node.py index edebc00facc5..50dcb4b417ae 100644 --- a/dist/pythonlibs/riotnode/riotnode/node.py +++ b/dist/pythonlibs/riotnode/riotnode/node.py @@ -12,6 +12,8 @@ import pexpect +from . import utils + DEVNULL = open(os.devnull, 'w') @@ -151,9 +153,15 @@ def start_term(self, **spawnkwargs): # on many platforms, the termprog needs a short while to be ready time.sleep(self.TERM_STARTED_DELAY) + def _term_pid(self): + """Terminal pid or None.""" + return getattr(self.term, 'pid', None) + def stop_term(self): """Stop the terminal.""" - self._safe_term_close() + with utils.ensure_all_subprocesses_stopped(self._term_pid(), + self.logger): + self._safe_term_close() def _safe_term_close(self): """Safe 'term.close'. diff --git a/dist/pythonlibs/riotnode/riotnode/tests/node_test.py b/dist/pythonlibs/riotnode/riotnode/tests/node_test.py index f7da59044052..f68e127b3157 100644 --- a/dist/pythonlibs/riotnode/riotnode/tests/node_test.py +++ b/dist/pythonlibs/riotnode/riotnode/tests/node_test.py @@ -186,6 +186,31 @@ def test_expect_value(app_pidfile_env): assert str(exc_info.value) == 'UPPERCASE' +def test_term_cleanup(app_pidfile_env): + """Test a terminal that does a cleanup after kill. + + The term process should be able to run its cleanup. + """ + # Always run as 'deleted=True' to deleted even on early exception + # File must exist at the end of the context manager + with tempfile.NamedTemporaryFile(delete=True) as tmpfile: + env = {'BOARD': 'board'} + env.update(app_pidfile_env) + env['APPLICATION'] = './create_file.py %s' % tmpfile.name + + node = riotnode.node.RIOTNode(APPLICATIONS_DIR, env) + node.TERM_STARTED_DELAY = 1 + with node.run_term(logfile=sys.stdout) as child: + child.expect_exact('Running') + # Ensure script is started correctly + content = open(tmpfile.name, 'r', encoding='utf-8').read() + assert content == 'Running\n' + + # File should not exist anymore so no error to create one + # File must exist to be cleaned by tempfile + open(tmpfile.name, 'x') + + def test_killing_a_broken_term(app_pidfile_env): """Test killing a terminal that can only be killed with SIGKILL.""" env = {'BOARD': 'board', 'APPLICATION': './sigkill_script.py'} @@ -203,3 +228,27 @@ def test_killing_a_broken_term(app_pidfile_env): # And if it was running, it will be cleaned with pytest.raises(ProcessLookupError): os.kill(term_pid, signal.SIGKILL) + + +def test_killing_a_broken_term_when_kill_is_sigterm(app_pidfile_env): + """Test killing a terminal that can only be killed with SIGKILL.""" + env = {'BOARD': 'board', 'APPLICATION': './sigkill_script.py'} + env.update(app_pidfile_env) + + node = riotnode.node.RIOTNode(APPLICATIONS_DIR, env) + node.TERM_STARTED_DELAY = 1 + node.TERM_STOP_SIGNAL = signal.SIGTERM + + with node.run_term(logfile=sys.stdout) as child: + child.expect_exact('Kill me with SIGKILL!') + child.expect(r'My PID: (\d+)') + term_pid = int(child.match.group(1)) + + msg = 'Some term process where not stopped' + with pytest.raises(RuntimeError, match=msg): + node.stop_term() + + # Send a SIGKILL to the process, it should raise an error as it is stopped + # And if it was running, it will be cleaned + with pytest.raises(ProcessLookupError): + os.kill(term_pid, signal.SIGKILL) diff --git a/dist/pythonlibs/riotnode/riotnode/tests/utils/application/create_file.py b/dist/pythonlibs/riotnode/riotnode/tests/utils/application/create_file.py new file mode 100755 index 000000000000..bd2e63cae5c5 --- /dev/null +++ b/dist/pythonlibs/riotnode/riotnode/tests/utils/application/create_file.py @@ -0,0 +1,36 @@ +#! /usr/bin/env python3 +"""Implement creating a file and deleting at exit + +This should show a terminal program doing a cleanup. +""" + +import os +import sys +import atexit +import signal +import argparse + +PARSER = argparse.ArgumentParser() +PARSER.add_argument('running_file') + + +def main(): + """Create a file and delete it after program exits.""" + args = PARSER.parse_args() + + # Trigger atexit on SIGHUP + signal.signal(signal.SIGHUP, (lambda *_: sys.exit(0))) + + # Delete file after program closes + # This should be the case if normally terminated + atexit.register(os.remove, args.running_file) + + with open(args.running_file, 'w', encoding='utf-8') as rfile: + rfile.write('Running\n') + print('Running') + while True: + signal.pause() + + +if __name__ == '__main__': + sys.exit(main()) From a284c40db467c88161c29694ca9923f18fedbff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Harter?= Date: Tue, 19 Feb 2019 18:02:17 +0100 Subject: [PATCH 8/9] riotnode/node.py: use pexpect.close for killing process The process should be closed alone with 'self.term.close'. Closing child processes must be done by the `make term` program. --- dist/pythonlibs/riotnode/riotnode/node.py | 10 ++++++---- .../riotnode/riotnode/tests/node_test.py | 20 ------------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/dist/pythonlibs/riotnode/riotnode/node.py b/dist/pythonlibs/riotnode/riotnode/node.py index 50dcb4b417ae..355498597b9b 100644 --- a/dist/pythonlibs/riotnode/riotnode/node.py +++ b/dist/pythonlibs/riotnode/riotnode/node.py @@ -169,14 +169,16 @@ def _safe_term_close(self): Handles possible exceptions. """ try: - self._kill_term() + self.term.close() except AttributeError: # Not initialized - return + pass except ProcessLookupError: self.logger.warning('Process already stopped') - - self.term.close() + except pexpect.ExceptionPexpect: + # Not sure how to cover this in a test + # 'make term' is not killed by 'term.close()' + self.logger.critical('Could not close make term') def _kill_term(self): """Kill the current terminal.""" diff --git a/dist/pythonlibs/riotnode/riotnode/tests/node_test.py b/dist/pythonlibs/riotnode/riotnode/tests/node_test.py index f68e127b3157..812a737967db 100644 --- a/dist/pythonlibs/riotnode/riotnode/tests/node_test.py +++ b/dist/pythonlibs/riotnode/riotnode/tests/node_test.py @@ -219,26 +219,6 @@ def test_killing_a_broken_term(app_pidfile_env): node = riotnode.node.RIOTNode(APPLICATIONS_DIR, env) node.TERM_STARTED_DELAY = 1 - with node.run_term(logfile=sys.stdout) as child: - child.expect_exact('Kill me with SIGKILL!') - child.expect(r'My PID: (\d+)') - term_pid = int(child.match.group(1)) - - # Send a SIGKILL to the process, it should raise an error as it is stopped - # And if it was running, it will be cleaned - with pytest.raises(ProcessLookupError): - os.kill(term_pid, signal.SIGKILL) - - -def test_killing_a_broken_term_when_kill_is_sigterm(app_pidfile_env): - """Test killing a terminal that can only be killed with SIGKILL.""" - env = {'BOARD': 'board', 'APPLICATION': './sigkill_script.py'} - env.update(app_pidfile_env) - - node = riotnode.node.RIOTNode(APPLICATIONS_DIR, env) - node.TERM_STARTED_DELAY = 1 - node.TERM_STOP_SIGNAL = signal.SIGTERM - with node.run_term(logfile=sys.stdout) as child: child.expect_exact('Kill me with SIGKILL!') child.expect(r'My PID: (\d+)') From 174f9b9714ae23752ead8a2a114d2eb26c87ac81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Harter?= Date: Tue, 19 Feb 2019 18:02:17 +0100 Subject: [PATCH 9/9] riotnode/node: remove _kill_term Remove now unused _kill_term. --- dist/pythonlibs/riotnode/riotnode/node.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/dist/pythonlibs/riotnode/riotnode/node.py b/dist/pythonlibs/riotnode/riotnode/node.py index 355498597b9b..1274f45399fb 100644 --- a/dist/pythonlibs/riotnode/riotnode/node.py +++ b/dist/pythonlibs/riotnode/riotnode/node.py @@ -8,7 +8,6 @@ import logging import subprocess import contextlib -import signal import pexpect @@ -85,7 +84,6 @@ class RIOTNode(): """ TERM_SPAWN_CLASS = TermSpawn - TERM_STOP_SIGNAL = signal.SIGKILL TERM_STARTED_DELAY = int(os.environ.get('RIOT_TERM_START_DELAY') or 3) MAKE_ARGS = () @@ -180,16 +178,6 @@ def _safe_term_close(self): # 'make term' is not killed by 'term.close()' self.logger.critical('Could not close make term') - def _kill_term(self): - """Kill the current terminal.""" - # killpg(SIGKILL) was taken from `testrunner`. - # I do not really like direct `SIGKILL` as it prevents script cleanup. - # I kept it as I do not want to break an edge case that rely on it. - - # Using 'killpg' shows that our shell script do not correctly kill - # programs they started. So this is more a hack than a real solution. - os.killpg(os.getpgid(self.term.pid), self.TERM_STOP_SIGNAL) - def make_run(self, targets, *runargs, **runkwargs): """Call make `targets` for current RIOTNode context.