-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[take over] riotnode: node abstraction package #13612
Closed
leandrolanzieri
wants to merge
9
commits into
RIOT-OS:master
from
leandrolanzieri:pr/riotnode_cleanup_squashed
Closed
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
d669948
riotnode: add an empty package directory for 'riotnode'
cladmi a40f35e
riotnode: add node implementation and tests
cladmi 1bcfacb
riotnode: Replace pexpect exception value with pattern
cladmi 3648088
riotnode: Remove exception ctx from inside pexpect implementation
cladmi ab730b0
riotnode/utils: add function to handle subprocesses
cladmi 3213d72
riotnode/tests: add a test for term sigkill
cladmi 526607f
riotnode/node: ensure children processes are cleaned up
cladmi a284c40
riotnode/node.py: use pexpect.close for killing process
cladmi 174f9b9
riotnode/node: remove _kill_term
cladmi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[run] | ||
omit = riotnode/tests/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 :) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Use the current setup.py for requirements | ||
. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,205 @@ | ||||||
"""RIOTNode abstraction. | ||||||
|
||||||
Define class to abstract a node over the RIOT build system. | ||||||
""" | ||||||
|
||||||
import os | ||||||
import time | ||||||
import logging | ||||||
import subprocess | ||||||
import contextlib | ||||||
|
||||||
import pexpect | ||||||
|
||||||
from . import utils | ||||||
|
||||||
DEVNULL = open(os.devnull, 'w') | ||||||
|
||||||
|
||||||
class TermSpawn(pexpect.spawn): | ||||||
"""Subclass to adapt the behaviour to our need. | ||||||
|
||||||
* change default `__init__` values | ||||||
* disable local 'echo' to not match send messages | ||||||
* 'utf-8/replace' by default | ||||||
* 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 | ||||||
command, timeout=10, echo=False, | ||||||
encoding='utf-8', codec_errors='replace', **kwargs): | ||||||
super().__init__(command, timeout=timeout, echo=echo, | ||||||
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 | ||||||
* Remove exception context | ||||||
""" | ||||||
exc.pexpect_value = exc.value | ||||||
exc.value = pattern | ||||||
|
||||||
# Remove exception context | ||||||
exc.__cause__ = None | ||||||
exc.__traceback__ = None | ||||||
return exc | ||||||
|
||||||
|
||||||
class RIOTNode(): | ||||||
"""Class abstracting a RIOTNode in an application. | ||||||
|
||||||
This should abstract the build system integration. | ||||||
|
||||||
:param application_directory: relative directory to the application. | ||||||
:param env: dictionary of environment variables that should be used. | ||||||
These overwrites values coming from `os.environ` and can help | ||||||
define factories where environment comes from a file or if the | ||||||
script is not executed from the build system context. | ||||||
|
||||||
Environment variable configuration | ||||||
|
||||||
:environment BOARD: current RIOT board type. | ||||||
:environment RIOT_TERM_START_DELAY: delay before `make term` is said to be | ||||||
ready after calling. | ||||||
""" | ||||||
|
||||||
TERM_SPAWN_CLASS = TermSpawn | ||||||
TERM_STARTED_DELAY = int(os.environ.get('RIOT_TERM_START_DELAY') or 3) | ||||||
|
||||||
MAKE_ARGS = () | ||||||
RESET_TARGETS = ('reset',) | ||||||
|
||||||
def __init__(self, application_directory='.', env=None): | ||||||
self._application_directory = application_directory | ||||||
|
||||||
# TODO I am not satisfied by this, but would require changing all the | ||||||
# environment handling, just put a note until I can fix it. | ||||||
# I still want to show a PR before this | ||||||
# I would prefer getting either no environment == os.environ or the | ||||||
# full environment to use. | ||||||
# It should also change the `TERM_STARTED_DELAY` thing. | ||||||
self.env = os.environ.copy() | ||||||
self.env.update(env or {}) | ||||||
|
||||||
self.term = None # type: pexpect.spawn | ||||||
|
||||||
self.logger = logging.getLogger(__name__) | ||||||
|
||||||
@property | ||||||
def application_directory(self): | ||||||
"""Absolute path to the current directory.""" | ||||||
return os.path.abspath(self._application_directory) | ||||||
|
||||||
def board(self): | ||||||
"""Return board type.""" | ||||||
return self.env['BOARD'] | ||||||
|
||||||
def reset(self): | ||||||
"""Reset current node.""" | ||||||
# Ignoring 'reset' return value was taken from `testrunner`. | ||||||
# For me it should not be done for all boards as it should be an error. | ||||||
# I would rather fix it in the build system or have a per board | ||||||
# configuration. | ||||||
|
||||||
# Make reset yields error on some boards even if successful | ||||||
# Ignore printed errors and returncode | ||||||
self.make_run(self.RESET_TARGETS, stdout=DEVNULL, stderr=DEVNULL) | ||||||
|
||||||
@contextlib.contextmanager | ||||||
def run_term(self, reset=True, **startkwargs): | ||||||
"""Terminal context manager.""" | ||||||
try: | ||||||
self.start_term(**startkwargs) | ||||||
if reset: | ||||||
self.reset() | ||||||
yield self.term | ||||||
finally: | ||||||
self.stop_term() | ||||||
|
||||||
def start_term(self, **spawnkwargs): | ||||||
"""Start the terminal. | ||||||
|
||||||
The function is blocking until it is ready. | ||||||
It waits some time until the terminal is ready and resets the node. | ||||||
""" | ||||||
self.stop_term() | ||||||
|
||||||
term_cmd = self.make_command(['term']) | ||||||
self.term = self.TERM_SPAWN_CLASS(term_cmd[0], args=term_cmd[1:], | ||||||
env=self.env, **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.""" | ||||||
with utils.ensure_all_subprocesses_stopped(self._term_pid(), | ||||||
self.logger): | ||||||
self._safe_term_close() | ||||||
|
||||||
def _safe_term_close(self): | ||||||
"""Safe 'term.close'. | ||||||
|
||||||
Handles possible exceptions. | ||||||
""" | ||||||
try: | ||||||
self.term.close() | ||||||
except AttributeError: | ||||||
# Not initialized | ||||||
pass | ||||||
except ProcessLookupError: | ||||||
self.logger.warning('Process already stopped') | ||||||
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 make_run(self, targets, *runargs, **runkwargs): | ||||||
"""Call make `targets` for current RIOTNode context. | ||||||
|
||||||
It is using `subprocess.run` internally. | ||||||
|
||||||
:param targets: make targets | ||||||
:param *runargs: args passed to subprocess.run | ||||||
:param *runkwargs: kwargs passed to subprocess.run | ||||||
:return: subprocess.CompletedProcess object | ||||||
""" | ||||||
command = self.make_command(targets) | ||||||
return subprocess.run(command, env=self.env, *runargs, **runkwargs) | ||||||
|
||||||
def make_command(self, targets): | ||||||
"""Make command for current RIOTNode context. | ||||||
|
||||||
:return: list of command arguments (for example for subprocess) | ||||||
""" | ||||||
command = ['make'] | ||||||
command.extend(self.MAKE_ARGS) | ||||||
if self._application_directory != '.': | ||||||
dir_cmd = '--no-print-directory', '-C', self.application_directory | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
command.extend(dir_cmd) | ||||||
command.extend(targets) | ||||||
return command |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""riotnode.tests directory.""" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add a setter function for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussing it offline with @cladmi, it makes sense to not be able to set it since some application have different requirements for the terminal
baudrate
use ofethos
, etc.. so it makes sense to not be able to change the application directory.