Repository: RIOT-OS/riotctrl (language: python)

Issue:
Add README.rst validation to the lint testenv
As described [here](https://packaging.python.org/guides/making-a-pypi-friendly-readme/#validating-restructuredtext-markup), using twine it's possible to validate the rST Markup language of the README.

This PR adds a new command that does that to the Tox lint testenv.

This is based on #25 
Improve Tox and Pylint configurations
This PR also fixes the lint check.


Discussion / hints:

Something seems wrong with the github actions as well.
Please rebase.
Should be better now
> Please rebase.

Something isn't right with the tests indeed... Will investigate, but in the meantime revert my version bump. Please rebase once more, once done
Reverted release. Please rebase to current master (where 41eff32fe0181159937372b3233dd is now removed).
I have another useful change in the pipe

But it can be its own PR (this is what pushed me to this one)
> I have another useful change in the pipe
> 
> But it can be its own PR (this is what pushed me to this one)

Welp, as long as I don't know what the change is, I can't really tell if it makes sense in this PR or not ;-).
> I can't really tell if it makes sense in this PR or not

I want to add a check on the README.rst (using twine) but I found an issue locally that I'm still investigating. So this change is not related to this PR, it just needs it.
And there's still something wrong with gh action, test-rapidjson is not launched as expected.
> > Please rebase.
> 
> Something isn't right with the tests indeed... Will investigate, but in the meantime revert my version bump. Please rebase once more, once done

Mhhh now the checks are working again. With the proposed 0.5.0, some tests where failing due to a File-Exists-Error. A repeat of the build fixed it, but I am not sure what might have caused it.
> And there's still something wrong with gh action, test-rapidjson is not launched as expected.

What do you mean? `test-rapidjson` IIRC does not run all tests on purpose IIRC.
See https://github.com/RIOT-OS/riotctrl/pull/8 (of which some stuff was later fixed in https://github.com/RIOT-OS/riotctrl/pull/14).
There was an issue in the tox configuration with one testenv called `test` and `another called test-rapidjson` because of the build matrix `{py3.5...py3.9}-{test,test-rapidjson}`. Somehow `test` was used instead of `test-rapidjson`. I renamed the latter `rapidjson` and now it works (at least locally).
> There was an issue in the tox configuration with one testenv called `test` and `another called test-rapidjson` because of the build matrix `{py3.5...py3.9}-{test,test-rapidjson}`. Somehow `test` was used instead of `test-rapidjson`. I renamed the latter `rapidjson` and now it works (at least locally).

Need to change the required checks then :-/. Can we keep the old name somehow?
> Need to change the required checks then :-/. Can we keep the old name somehow?

I dropped the testenv matrix from gh actions (because there's one in tox) to make things simpler (and configurable just with code). I strongly prefer it this way.
> > Need to change the required checks then :-/. Can we keep the old name somehow?
> 
> I dropped the testenv matrix from gh actions (because there's one in tox) to make things simpler (and configurable just with code). I strongly prefer it this way.

Not sure I agree. This makes it harder to spot in which test suite the problem is.
> Not sure I agree. This makes it harder to spot in which test suite the problem is.

From a CI performance point of view, there are many benefits though: only 5 jobs launched instead of 10. And 15 when another item is added to the matrix (+ the settings maintenance on GitHub).
Tox can handle that matrix, so let's use it.

Fix patch:
diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml
index ca6cfa7..ff3e161 100644
--- a/.github/workflows/tox.yml
+++ b/.github/workflows/tox.yml
@@ -32,6 +32,9 @@ jobs:
       run: |
         pyenv="py$(echo "${{ matrix.python-version }}" | tr -d '.')"
         tox -e ${pyenv}-test,${pyenv}-rapidjson,flake8,lint
+    - name: Check code format with black
+      if: matrix.python-version == 3.9
+      run: tox -e black
     - name: Check package
       if: matrix.python-version == 3.9
       run: tox -e check_package
diff --git a/riotctrl/__init__.py b/riotctrl/__init__.py
index c6aa05c..4311317 100644
--- a/riotctrl/__init__.py
+++ b/riotctrl/__init__.py
@@ -8,4 +8,4 @@
 and maybe also over network.
 """
 
-__version__ = '0.5.0'
+__version__ = "0.5.0"
diff --git a/riotctrl/ctrl.py b/riotctrl/ctrl.py
index 949ab5c..22d47d1 100644
--- a/riotctrl/ctrl.py
+++ b/riotctrl/ctrl.py
@@ -14,7 +14,7 @@
 
 
 DEVNULL = subprocess.DEVNULL
-MAKE = os.environ.get('MAKE', 'make')
+MAKE = os.environ.get("MAKE", "make")
 
 
 class TermSpawn(pexpect.spawn):
@@ -29,12 +29,23 @@ class TermSpawn(pexpect.spawn):
       * 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 __init__(
+        self,
+        command,
+        timeout=10,
+        echo=False,
+        encoding="utf-8",
+        codec_errors="replace",
+        **kwargs
+    ):  # pylint:disable=too-many-arguments
+        super().__init__(
+            command,
+            timeout=timeout,
+            echo=echo,
+            encoding=encoding,
+            codec_errors=codec_errors,
+            **kwargs
+        )
 
     def expect(self, pattern, *args, **kwargs):
         # pylint:disable=signature-differs
@@ -66,7 +77,7 @@ def _pexpect_exception(exc, pattern):
         return exc
 
 
-class RIOTCtrl():
+class RIOTCtrl:
     """Class abstracting a RIOTctrl in an application.
 
     This should abstract the build system integration.
@@ -85,14 +96,14 @@ class RIOTCtrl():
     """
 
     TERM_SPAWN_CLASS = TermSpawn
-    TERM_STARTED_DELAY = int(os.environ.get('RIOT_TERM_START_DELAY') or 3)
+    TERM_STARTED_DELAY = int(os.environ.get("RIOT_TERM_START_DELAY") or 3)
 
     MAKE_ARGS = ()
-    FLASH_TARGETS = ('flash',)
-    RESET_TARGETS = ('reset',)
-    TERM_TARGETS = ('cleanterm',)
+    FLASH_TARGETS = ("flash",)
+    RESET_TARGETS = ("reset",)
+    TERM_TARGETS = ("cleanterm",)
 
-    def __init__(self, application_directory='.', env=None):
+    def __init__(self, application_directory=".", env=None):
         self._application_directory = application_directory
 
         self.env = os.environ.copy()
@@ -109,7 +120,7 @@ def application_directory(self):
 
     def board(self):
         """Return board type."""
-        return self.env['BOARD']
+        return self.env["BOARD"]
 
     def flash(self, *runargs, stdout=DEVNULL, stderr=DEVNULL, **runkwargs):
         """Flash application in ``ctrl.application_directory`` to ctrl.
@@ -122,8 +133,9 @@ def flash(self, *runargs, stdout=DEVNULL, stderr=DEVNULL, **runkwargs):
         :param *runkwargs: kwargs passed to subprocess.run
         :return: subprocess.CompletedProcess object
         """
-        self.make_run(self.FLASH_TARGETS, *runargs,
-                      stdout=stdout, stderr=stderr, **runkwargs)
+        self.make_run(
+            self.FLASH_TARGETS, *runargs, stdout=stdout, stderr=stderr, **runkwargs
+        )
 
     def reset(self):
         """Reset current ctrl."""
@@ -151,15 +163,16 @@ def start_term(self, **spawnkwargs):
         self.stop_term()
 
         term_cmd = self.make_command(self.TERM_TARGETS)
-        self.term = self.TERM_SPAWN_CLASS(term_cmd[0], args=term_cmd[1:],
-                                          env=self.env, **spawnkwargs)
+        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)
+        return getattr(self.term, "pid", None)
 
     def stop_term(self):
         """Safe 'term.close'.
@@ -172,11 +185,11 @@ def stop_term(self):
             # Not initialized
             pass
         except ProcessLookupError:
-            self.logger.warning('Process already stopped')
+            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')
+            self.logger.critical("Could not close make term")
 
     def make_run(self, targets, *runargs, **runkwargs):
         """Call make `targets` for current RIOTctrl context.
@@ -187,7 +200,7 @@ def make_run(self, targets, *runargs, **runkwargs):
         :param *runargs: args passed to subprocess.run
         :param *runkwargs: kwargs passed to subprocess.run
         :return: subprocess.CompletedProcess object
-            """
+        """
         command = self.make_command(targets)
         # pylint:disable=subprocess-run-check
         return subprocess.run(command, env=self.env, *runargs, **runkwargs)
@@ -199,8 +212,8 @@ def make_command(self, targets):
         """
         command = [MAKE]
         command.extend(self.MAKE_ARGS)
-        if self._application_directory != '.':
-            dir_cmd = '--no-print-directory', '-C', self._application_directory
+        if self._application_directory != ".":
+            dir_cmd = "--no-print-directory", "-C", self._application_directory
             command.extend(dir_cmd)
         command.extend(targets)
         return command
@@ -212,7 +225,7 @@ class RIOTCtrlFactoryBase(abc.ABC):
     """Abstract factory to create different RIOTCtrl."""
 
     @abc.abstractmethod
-    def get_ctrl(self, application_directory='.', env=None):
+    def get_ctrl(self, application_directory=".", env=None):
         """
         Returns a RIOTCtrl object of a class specified by the Factory
 
@@ -242,7 +255,7 @@ def __init__(self, board_cls=None):
         if board_cls is not None:
             self.board_cls.update(board_cls)
 
-    def get_ctrl(self, application_directory='.', env=None):
+    def get_ctrl(self, application_directory=".", env=None):
         """
         Returns a RIOTCtrl object of a class as specified in `board_cls` on
         initialization.
@@ -262,9 +275,9 @@ def get_ctrl(self, application_directory='.', env=None):
         the_env.update(os.environ)
         if env:
             the_env.update(env)
-        if 'BOARD' not in the_env or the_env['BOARD'] not in self.board_cls:
+        if "BOARD" not in the_env or the_env["BOARD"] not in self.board_cls:
             cls = self.DEFAULT_CLS
         else:
-            cls = self.board_cls[the_env['BOARD']]
+            cls = self.board_cls[the_env["BOARD"]]
         # cls does its own fetching of `os.environ` so only provide `env` here
         return cls(application_directory=application_directory, env=env)
diff --git a/riotctrl/shell/__init__.py b/riotctrl/shell/__init__.py
index 74a4fab..b633d62 100644
--- a/riotctrl/shell/__init__.py
+++ b/riotctrl/shell/__init__.py
@@ -23,16 +23,17 @@ def parse(self, cmd_output):
         """
 
 
-class ShellInteraction():
+class ShellInteraction:
     """
     Base class for shell interactions
 
     :param riotctrl: a RIOTCtrl object
     :param prompt: the prompt of the shell (default: '> ')
     """
-    PROMPT_TIMEOUT = .5
 
-    def __init__(self, riotctrl, prompt='> '):
+    PROMPT_TIMEOUT = 0.5
+
+    def __init__(self, riotctrl, prompt="> "):
         self.riotctrl = riotctrl
         self.prompt = prompt
         self.replwrap = None
@@ -51,9 +52,7 @@ def _start_replwrap(self):
             # previous command on some RIOT-supported boards such as
             # nucleo-f411re, b-l072z-lrwan1, and b-l475e-iot01a.
             try:
-                self.riotctrl.term.read_nonblocking(
-                    10000, timeout=self.PROMPT_TIMEOUT
-                )
+                self.riotctrl.term.read_nonblocking(10000, timeout=self.PROMPT_TIMEOUT)
             except pexpect.TIMEOUT:
                 pass
             # enforce prompt to be shown by sending newline
@@ -82,6 +81,7 @@ def check_term(func, reset=True, **startkwargs):
         """
         Decorator to ensure the terminal is running and stopped
         """
+
         def wrapper(self, *args, **kwargs):
             if self.riotctrl.term is None:
                 with self.riotctrl.run_term(reset, **startkwargs):
diff --git a/riotctrl/shell/json.py b/riotctrl/shell/json.py
index 05e8538..70ff270 100644
--- a/riotctrl/shell/json.py
+++ b/riotctrl/shell/json.py
@@ -4,6 +4,7 @@
 
 import json
 import logging
+
 try:
     import rapidjson
 except ImportError:
@@ -15,6 +16,7 @@
 # pylint: disable=too-few-public-methods
 class JSONShellInteractionParser(ShellInteractionParser):
     """Allows for parsing result strings of a ShellInteraction as JSON"""
+
     json_module = json
 
     def parse(self, cmd_output):
@@ -34,6 +36,7 @@ class RapidJSONShellInteractionParser(JSONShellInteractionParser):
 
     .. _rapidjson: https://rapidjson.org/
     """
+
     json_module = rapidjson
 
     def __init__(self, *args, **kwargs):
@@ -41,9 +44,11 @@ def __init__(self, *args, **kwargs):
         self._parser_args = {}
         self._parser_kwargs = {}
         if self.json_module is None:
-            self.logger.warning("%s initialized without rapidjson installed\n"
-                                "Please install with riotctrl[rapidjson]",
-                                type(self).__name__)
+            self.logger.warning(
+                "%s initialized without rapidjson installed\n"
+                "Please install with riotctrl[rapidjson]",
+                type(self).__name__,
+            )
         super().__init__(*args, **kwargs)
 
     def set_parser_args(self, *args, **kwargs):
@@ -65,5 +70,6 @@ def parse(self, cmd_output):
         :param cmd_output (str): Output of ShellInteraction::cmd(). Must be
                                  valid JSON
         """
-        return self.json_module.loads(cmd_output, *self._parser_args,
-                                      **self._parser_kwargs)
+        return self.json_module.loads(
+            cmd_output, *self._parser_args, **self._parser_kwargs
+        )
diff --git a/setup.cfg b/setup.cfg
index 4e0cc0e..d2b4c37 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -9,9 +9,15 @@ markers =
 
 [pylint]
 reports = no
+max-line-length = 88
 disable = locally-disabled,star-args,consider-using-f-string
 msg-template = {path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
 
 [flake8]
+max-line-length = 88
+extend-ignore = E203
 exclude = .tox,dist,doc,build,*.egg
 max-complexity = 10
+
+[tool.black]
+skip-string-normalization = true
diff --git a/setup.py b/setup.py
index b345976..849a581 100644
--- a/setup.py
+++ b/setup.py
@@ -3,54 +3,54 @@
 import os
 from setuptools import setup, find_packages
 
-PACKAGE = 'riotctrl'
-LICENSE = 'MIT'
-URL = 'https://github.com/RIOT-OS/riotctrl'
+PACKAGE = "riotctrl"
+LICENSE = "MIT"
+URL = "https://github.com/RIOT-OS/riotctrl"
 
 
 def get_version(package):
-    """ Extract package version without importing file
+    """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:
+    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
+            if line.startswith("__version__"):
+                return eval(line.split("=")[-1])  # pylint:disable=eval-used
     return None
 
 
 setup(
     name=PACKAGE,
     version=get_version(PACKAGE),
-    description='RIOT Ctrl - A RIOT node python abstraction',
-    long_description=open('README.rst').read(),
+    description="RIOT Ctrl - A RIOT node python abstraction",
+    long_description=open("README.rst").read(),
     long_description_content_type="text/x-rst",
-    author='Gaëtan Harter, Leandro Lanzieri, Martine S. Lenders',
-    author_email='gaetan.harter@fu-berlin.de, '
-                 'leandro.lanzieri@haw-hamburg.de, '
-                 'm.lenders@fu-berlin.de',
+    author="Gaëtan Harter, Leandro Lanzieri, Martine S. Lenders",
+    author_email="gaetan.harter@fu-berlin.de, "
+    "leandro.lanzieri@haw-hamburg.de, "
+    "m.lenders@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',
-                 'Programming Language :: Python :: 3.8',
-                 'Programming Language :: Python :: 3.9',
-                 'Intended Audience :: End Users/Desktop',
-                 'Environment :: Console',
-                 'Topic :: Utilities', ],
-    install_requires=['pexpect>=4.7', 'psutil'],
-    extras_require={
-        'rapidjson': ['python-rapidjson']
-    },
-    python_requires='>=3.5',
+    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",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Intended Audience :: End Users/Desktop",
+        "Environment :: Console",
+        "Topic :: Utilities",
+    ],
+    install_requires=["pexpect>=4.7", "psutil"],
+    extras_require={"rapidjson": ["python-rapidjson"]},
+    python_requires=">=3.5",
 )
diff --git a/tox.ini b/tox.ini
index d959eb7..4e298b6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = {py35,py36,py37,py38,py39}-{test,rapidjson},lint,flake8,check_package
+envlist = {py35,py36,py37,py38,py39}-{test,rapidjson},lint,flake8,check_package,black
 skip_missing_interpreters = true
 
 [testenv]
@@ -12,12 +12,14 @@ deps =
     rapidjson:  {[testenv:rapidjson]deps}
     lint:       {[testenv:lint]deps}
     flake8:     {[testenv:flake8]deps}
+    black:      {[testenv:black]deps}
     check_package:  {[testenv:check_package]deps}
 commands =
     test:       {[testenv:test]commands}
     rapidjson:  {[testenv:rapidjson]commands}
     lint:       {[testenv:lint]commands}
     flake8:     {[testenv:flake8]commands}
+    black:      {[testenv:black]commands}
     check_package:  {[testenv:check_package]commands}
 
 [testenv:test]
@@ -53,3 +55,8 @@ commands =
 deps = flake8
 commands =
     flake8
+
+[testenv:black]
+deps = black
+commands =
+    black --check --diff .


Test patch:
diff --git a/riotctrl/tests/ctrl_test.py b/riotctrl/tests/ctrl_test.py
index ce714cf..67e0344 100644
--- a/riotctrl/tests/ctrl_test.py
+++ b/riotctrl/tests/ctrl_test.py
@@ -10,42 +10,42 @@
 import riotctrl.ctrl
 
 CURDIR = os.path.dirname(__file__)
-APPLICATIONS_DIR = os.path.join(CURDIR, 'utils', 'application')
+APPLICATIONS_DIR = os.path.join(CURDIR, "utils", "application")
 
 
 def test_riotctrl_application_dir():
     """Test the creation of a riotctrl with an `application_dir`."""
-    appbase = os.path.abspath(os.environ['APPBASE'])
-    application = os.path.join(appbase, 'application')
-    board = 'native'
+    appbase = os.path.abspath(os.environ["APPBASE"])
+    application = os.path.join(appbase, "application")
+    board = "native"
 
-    env = {'BOARD': board}
+    env = {"BOARD": board}
     ctrl = riotctrl.ctrl.RIOTCtrl(application, env)
 
     assert ctrl.application_directory == application
     assert ctrl.board() == board
 
-    clean_cmd = ['make', '--no-print-directory', '-C', application, 'clean']
-    assert ctrl.make_command(['clean']) == clean_cmd
+    clean_cmd = ["make", "--no-print-directory", "-C", application, "clean"]
+    assert ctrl.make_command(["clean"]) == clean_cmd
 
 
 def test_riotctrl_curdir():
     """Test the creation of a riotctrl with current directory."""
-    appbase = os.path.abspath(os.environ['APPBASE'])
-    application = os.path.join(appbase, 'application')
-    board = 'native'
+    appbase = os.path.abspath(os.environ["APPBASE"])
+    application = os.path.join(appbase, "application")
+    board = "native"
 
     _curdir = os.getcwd()
     _environ = os.environ.copy()
     try:
-        os.environ['BOARD'] = board
+        os.environ["BOARD"] = board
         os.chdir(application)
 
         ctrl = riotctrl.ctrl.RIOTCtrl()
 
         assert ctrl.application_directory == application
         assert ctrl.board() == board
-        assert ctrl.make_command(['clean']) == ['make', 'clean']
+        assert ctrl.make_command(["clean"]) == ["make", "clean"]
     finally:
         os.chdir(_curdir)
         os.environ.clear()
@@ -65,73 +65,75 @@ def make_run_mock(self, *_args, **_kwargs):
         args.append(_args)
         kwargs.append(_kwargs)
 
-    monkeypatch.setattr(riotctrl.ctrl.RIOTCtrl, 'make_run', make_run_mock)
+    monkeypatch.setattr(riotctrl.ctrl.RIOTCtrl, "make_run", make_run_mock)
     ctrl = riotctrl.ctrl.RIOTCtrl(APPLICATIONS_DIR)
     ctrl.flash()
     assert len(args) == len(kwargs) == 1
     assert args[-1] == (riotctrl.ctrl.RIOTCtrl.FLASH_TARGETS,)
-    assert kwargs[-1] == {'stderr': riotctrl.ctrl.DEVNULL,
-                          'stdout': riotctrl.ctrl.DEVNULL}
+    assert kwargs[-1] == {
+        "stderr": riotctrl.ctrl.DEVNULL,
+        "stdout": riotctrl.ctrl.DEVNULL,
+    }
 
 
-@pytest.fixture(name='app_pidfile_env')
+@pytest.fixture(name="app_pidfile_env")
 def fixture_app_pidfile_env():
     """Environment to use application pidfile"""
     with tempfile.NamedTemporaryFile() as tmpfile:
-        yield {'PIDFILE': tmpfile.name}
+        yield {"PIDFILE": tmpfile.name}
 
 
 def test_running_echo_application(app_pidfile_env):
     """Test basic functionalities with the 'echo' application."""
-    env = {'BOARD': 'board', 'APPLICATION': './echo.py'}
+    env = {"BOARD": "board", "APPLICATION": "./echo.py"}
     env.update(app_pidfile_env)
 
     ctrl = riotctrl.ctrl.RIOTCtrl(APPLICATIONS_DIR, env)
     ctrl.TERM_STARTED_DELAY = 1
 
     with ctrl.run_term(logfile=sys.stdout) as child:
-        child.expect_exact('Starting RIOT Ctrl')
+        child.expect_exact("Starting RIOT Ctrl")
 
         # Test multiple echo
         for i in range(16):
-            child.sendline('Hello Test {}'.format(i))
-            child.expect(r'Hello Test (\d+)', timeout=1)
+            child.sendline("Hello Test {}".format(i))
+            child.expect(r"Hello Test (\d+)", timeout=1)
             num = int(child.match.group(1))
             assert i == num
 
 
 def test_running_term_with_reset(app_pidfile_env):
     """Test that ctrl resets on run_term."""
-    env = {'BOARD': 'board'}
+    env = {"BOARD": "board"}
     env.update(app_pidfile_env)
-    env['APPLICATION'] = './hello.py'
+    env["APPLICATION"] = "./hello.py"
 
     ctrl = riotctrl.ctrl.RIOTCtrl(APPLICATIONS_DIR, env)
     ctrl.TERM_STARTED_DELAY = 1
 
     with ctrl.run_term(logfile=sys.stdout) as child:
         # Firmware should have started twice
-        child.expect_exact('Starting RIOT Ctrl')
-        child.expect_exact('Hello World')
-        child.expect_exact('Starting RIOT Ctrl')
-        child.expect_exact('Hello World')
+        child.expect_exact("Starting RIOT Ctrl")
+        child.expect_exact("Hello World")
+        child.expect_exact("Starting RIOT Ctrl")
+        child.expect_exact("Hello World")
 
 
 def test_running_term_without_reset(app_pidfile_env):
     """Test not resetting ctrl on run_term."""
-    env = {'BOARD': 'board'}
+    env = {"BOARD": "board"}
     env.update(app_pidfile_env)
-    env['APPLICATION'] = './hello.py'
+    env["APPLICATION"] = "./hello.py"
 
     ctrl = riotctrl.ctrl.RIOTCtrl(APPLICATIONS_DIR, env)
     ctrl.TERM_STARTED_DELAY = 1
 
     with ctrl.run_term(reset=False, logfile=sys.stdout) as child:
-        child.expect_exact('Starting RIOT Ctrl')
-        child.expect_exact('Hello World')
+        child.expect_exact("Starting RIOT Ctrl")
+        child.expect_exact("Hello World")
         # Firmware should start only once
         with pytest.raises(pexpect.exceptions.TIMEOUT):
-            child.expect_exact('Starting RIOT Ctrl', timeout=1)
+            child.expect_exact("Starting RIOT Ctrl", timeout=1)
 
 
 def test_running_error_cases(app_pidfile_env):
@@ -141,34 +143,37 @@ def test_running_error_cases(app_pidfile_env):
     * stopping already stopped child
     """
     # Use only 'echo' as process to exit directly
-    env = {'BOARD': 'board',
-           'NODE_WRAPPER': 'echo', 'APPLICATION': 'Starting RIOT Ctrl'}
+    env = {
+        "BOARD": "board",
+        "NODE_WRAPPER": "echo",
+        "APPLICATION": "Starting RIOT Ctrl",
+    }
     env.update(app_pidfile_env)
 
     ctrl = riotctrl.ctrl.RIOTCtrl(APPLICATIONS_DIR, env)
     ctrl.TERM_STARTED_DELAY = 1
 
     with ctrl.run_term(logfile=sys.stdout) as child:
-        child.expect_exact('Starting RIOT Ctrl')
+        child.expect_exact("Starting RIOT Ctrl")
 
         # Term is already finished and expect should trigger EOF
         with pytest.raises(pexpect.EOF):
-            child.expect('this should eof')
+            child.expect("this should eof")
 
     # Exiting the context manager should not crash when ctrl is killed
 
 
 def test_expect_not_matching_stdin(app_pidfile_env):
     """Test that expect does not match stdin."""
-    env = {'BOARD': 'board', 'APPLICATION': './hello.py'}
+    env = {"BOARD": "board", "APPLICATION": "./hello.py"}
     env.update(app_pidfile_env)
 
     ctrl = riotctrl.ctrl.RIOTCtrl(APPLICATIONS_DIR, env)
     ctrl.TERM_STARTED_DELAY = 1
 
     with ctrl.run_term(logfile=sys.stdout) as child:
-        child.expect_exact('Starting RIOT Ctrl')
-        child.expect_exact('Hello World')
+        child.expect_exact("Starting RIOT Ctrl")
+        child.expect_exact("Hello World")
 
         msg = "This should not be matched as it is on stdin"
         child.sendline(msg)
@@ -179,32 +184,32 @@ def test_expect_not_matching_stdin(app_pidfile_env):
 
 def test_expect_value(app_pidfile_env):
     """Test that expect value is being changed to the pattern."""
-    env = {'BOARD': 'board', 'APPLICATION': './echo.py'}
+    env = {"BOARD": "board", "APPLICATION": "./echo.py"}
     env.update(app_pidfile_env)
 
     ctrl = riotctrl.ctrl.RIOTCtrl(APPLICATIONS_DIR, env)
     ctrl.TERM_STARTED_DELAY = 1
 
     with ctrl.run_term(logfile=sys.stdout) as child:
-        child.expect_exact('Starting RIOT Ctrl')
+        child.expect_exact("Starting RIOT Ctrl")
 
         # Exception is 'exc_info.value' and pattern is in 'exc.value'
-        child.sendline('lowercase')
+        child.sendline("lowercase")
         with pytest.raises(pexpect.TIMEOUT) as exc_info:
-            child.expect('UPPERCASE', timeout=0.5)
-        assert str(exc_info.value) == 'UPPERCASE'
+            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.')
+        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')
+        child.sendline("lowercase")
         with pytest.raises(pexpect.TIMEOUT) as exc_info:
-            child.expect_exact('UPPERCASE', timeout=0.5)
-        assert str(exc_info.value) == 'UPPERCASE'
+            child.expect_exact("UPPERCASE", timeout=0.5)
+        assert str(exc_info.value) == "UPPERCASE"
 
 
 def test_term_cleanup(app_pidfile_env):
@@ -215,21 +220,21 @@ def test_term_cleanup(app_pidfile_env):
     # 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 = {"BOARD": "board"}
         env.update(app_pidfile_env)
-        env['APPLICATION'] = './create_file.py %s' % tmpfile.name
+        env["APPLICATION"] = "./create_file.py %s" % tmpfile.name
 
         ctrl = riotctrl.ctrl.RIOTCtrl(APPLICATIONS_DIR, env)
         ctrl.TERM_STARTED_DELAY = 1
         with ctrl.run_term(logfile=sys.stdout) as child:
-            child.expect_exact('Running')
+            child.expect_exact("Running")
             # Ensure script is started correctly
-            with open(tmpfile.name, 'r', encoding='utf-8') as tempfile_r:
-                assert tempfile_r.read() == 'Running\n'
+            with open(tmpfile.name, "r", encoding="utf-8") as tempfile_r:
+                assert tempfile_r.read() == "Running\n"
 
         # File should not exist anymore so no error to create one
         # File must exist to be cleaned by tempfile
-        with open(tmpfile.name, 'x', encoding='utf-8'):
+        with open(tmpfile.name, "x", encoding="utf-8"):
             pass
 
 
@@ -255,10 +260,10 @@ def test_board_factory_wo_board():
 def test_w_board_in_default_board_cls():
     """Tests if riotctrl.ctrl.RIOTCtrlBoardFactory returns a class in the
     static mapping of the factory exists"""
-    env = {'BOARD': 'mock'}
-    riotctrl.ctrl.RIOTCtrlBoardFactory.BOARD_CLS = {'mock': CtrlMock1}
+    env = {"BOARD": "mock"}
+    riotctrl.ctrl.RIOTCtrlBoardFactory.BOARD_CLS = {"mock": CtrlMock1}
     factory = riotctrl.ctrl.RIOTCtrlBoardFactory()
-    assert 'mock' in factory.board_cls
+    assert "mock" in factory.board_cls
     ctrl = factory.get_ctrl(env=env)
     # pylint: disable=unidiomatic-typecheck
     # in this case we want to know the exact type
@@ -269,10 +274,10 @@ def test_w_board_in_not_default_board_cls():
     """Tests if riotctrl.ctrl.RIOTCtrlBoardFactory defaults to
     riotctrl.ctrl.RIOTCtrl if no mapping exists for a device with an existing
     mapping"""
-    env = {'BOARD': 'foobar'}
-    riotctrl.ctrl.RIOTCtrlBoardFactory.BOARD_CLS = {'mock': CtrlMock1}
+    env = {"BOARD": "foobar"}
+    riotctrl.ctrl.RIOTCtrlBoardFactory.BOARD_CLS = {"mock": CtrlMock1}
     factory = riotctrl.ctrl.RIOTCtrlBoardFactory()
-    assert 'mock' in factory.board_cls
+    assert "mock" in factory.board_cls
     ctrl = factory.get_ctrl(env=env)
     # pylint: disable=unidiomatic-typecheck
     # in this case we want to know the exact type
@@ -282,12 +287,10 @@ def test_w_board_in_not_default_board_cls():
 def test_w_board_custom_board_cls():
     """Tests if riotctrl.ctrl.RIOTCtrlBoardFactory returns a class in the
     dynamic mapping of the factory exists"""
-    env = {'BOARD': 'mock'}
-    riotctrl.ctrl.RIOTCtrlBoardFactory.BOARD_CLS = {'mock': CtrlMock1}
-    factory = riotctrl.ctrl.RIOTCtrlBoardFactory(
-        board_cls={'mock': CtrlMock2}
-    )
-    assert 'mock' in factory.board_cls
+    env = {"BOARD": "mock"}
+    riotctrl.ctrl.RIOTCtrlBoardFactory.BOARD_CLS = {"mock": CtrlMock1}
+    factory = riotctrl.ctrl.RIOTCtrlBoardFactory(board_cls={"mock": CtrlMock2})
+    assert "mock" in factory.board_cls
     ctrl = factory.get_ctrl(env=env)
     # pylint: disable=unidiomatic-typecheck
     # in this case we want to know the exact type
diff --git a/riotctrl/tests/riotctrl_test.py b/riotctrl/tests/riotctrl_test.py
index 8abb5d6..04c95eb 100644
--- a/riotctrl/tests/riotctrl_test.py
+++ b/riotctrl/tests/riotctrl_test.py
@@ -7,4 +7,4 @@ def test_version():
 
     Goal is to have a test to run the test environment.
     """
-    assert getattr(riotctrl, '__version__', None) is not None
+    assert getattr(riotctrl, "__version__", None) is not None
diff --git a/riotctrl/tests/shell_json_test.py b/riotctrl/tests/shell_json_test.py
index f0c5971..8a80fed 100644
--- a/riotctrl/tests/shell_json_test.py
+++ b/riotctrl/tests/shell_json_test.py
@@ -24,11 +24,12 @@ def test_json_shell_interaction_parser():
 def test_rapid_json_shell_interaction_parser_wo_rapidjson(caplog):
     """Test RapidJSONShellInteractionParser initialization without rapidjson
     installed"""
-    with caplog.at_level(logging.WARNING,
-                         logger='RapidJSONShellInteractionParser'):
+    with caplog.at_level(logging.WARNING, logger="RapidJSONShellInteractionParser"):
         parser = riotctrl.shell.json.RapidJSONShellInteractionParser()
-    assert "RapidJSONShellInteractionParser initialized without " \
+    assert (
+        "RapidJSONShellInteractionParser initialized without "
         "rapidjson installed" in caplog.text
+    )
     with pytest.raises(AttributeError):
         parser.parse('[{"test": [1234, {"obj": {"val": 3.14}}]}]')
 
@@ -41,8 +42,7 @@ def test_rapid_json_shell_interaction_parser_w_rapidjson(caplog):
     import rapidjson
 
     assert not caplog.text
-    with caplog.at_level(logging.WARNING,
-                         logger='RapidJSONShellInteractionParser'):
+    with caplog.at_level(logging.WARNING, logger="RapidJSONShellInteractionParser"):
         parser = riotctrl.shell.json.RapidJSONShellInteractionParser()
     assert not caplog.text
     res = parser.parse('[{"test": [1234, {"obj": {"val": 3.14}}]}]')
diff --git a/riotctrl/tests/shell_test.py b/riotctrl/tests/shell_test.py
index 71e4f60..e11ead5 100644
--- a/riotctrl/tests/shell_test.py
+++ b/riotctrl/tests/shell_test.py
@@ -11,24 +11,24 @@
 import riotctrl.shell
 
 CURDIR = os.path.dirname(__file__)
-APPLICATIONS_DIR = os.path.join(CURDIR, 'utils', 'application')
+APPLICATIONS_DIR = os.path.join(CURDIR, "utils", "application")
 
 
-@pytest.fixture(name='app_pidfile_env')
+@pytest.fixture(name="app_pidfile_env")
 def fixture_app_pidfile_env():
     """Environment to use application pidfile"""
     with tempfile.NamedTemporaryFile() as tmpfile:
-        yield {'PIDFILE': tmpfile.name}
+        yield {"PIDFILE": tmpfile.name}
 
 
 def init_ctrl(app_pidfile_env, skip_first_prompt=False, prompt="> "):
     """Initializes RIOTCtrl for ShellInteraction tests"""
     env = {
-        'QUIET': '1',   # pipe > in command interferes with test
-        'BOARD': 'board',
-        'APPLICATION': './shell.py' +
-                       (' 1' if skip_first_prompt else ' 0') +
-                       " --prompt=\"{}\"".format(prompt),
+        "QUIET": "1",  # pipe > in command interferes with test
+        "BOARD": "board",
+        "APPLICATION": "./shell.py"
+        + (" 1" if skip_first_prompt else " 0")
+        + ' --prompt="{}"'.format(prompt),
     }
     env.update(app_pidfile_env)
 
@@ -41,10 +41,10 @@ def test_shell_interaction_cmd(app_pidfile_env):
     ctrl = init_ctrl(app_pidfile_env)
     with ctrl.run_term(logfile=sys.stdout, reset=False):
         shell = riotctrl.shell.ShellInteraction(ctrl)
-        res = shell.cmd('foobar')
-        assert 'foobar' in res
-        res = shell.cmd('snafoo')
-        assert 'snafoo' in res
+        res = shell.cmd("foobar")
+        assert "foobar" in res
+        res = shell.cmd("snafoo")
+        assert "snafoo" in res
 
 
 def test_shell_interaction_cmd_first_prompt_missing(app_pidfile_env):
@@ -53,10 +53,10 @@ def test_shell_interaction_cmd_first_prompt_missing(app_pidfile_env):
     ctrl = init_ctrl(app_pidfile_env, skip_first_prompt=True)
     with ctrl.run_term(logfile=sys.stdout, reset=False):
         shell = riotctrl.shell.ShellInteraction(ctrl)
-        res = shell.cmd('foobar')
-        assert 'foobar' in res
-        res = shell.cmd('snafoo')
-        assert 'snafoo' in res
+        res = shell.cmd("foobar")
+        assert "foobar" in res
+        res = shell.cmd("snafoo")
+        assert "snafoo" in res
 
 
 def test_shell_interaction_cmd_different_prompt(app_pidfile_env):
@@ -66,8 +66,8 @@ def test_shell_interaction_cmd_different_prompt(app_pidfile_env):
     ctrl = init_ctrl(app_pidfile_env, prompt=prompt)
     with ctrl.run_term(logfile=sys.stdout, reset=False):
         shell = riotctrl.shell.ShellInteraction(ctrl, prompt=prompt)
-        res = shell.cmd('foobar')
-        assert 'foobar' in res
+        res = shell.cmd("foobar")
+        assert "foobar" in res
 
 
 def test_shell_interaction_cmd_prompt_timeout(app_pidfile_env):
@@ -81,7 +81,7 @@ def test_shell_interaction_cmd_prompt_timeout(app_pidfile_env):
         with pytest.raises(TIMEOUT):
             # Fast systems may occasionally reply in 0 secs
             for _ in range(5):
-                shell.cmd('foobar')
+                shell.cmd("foobar")
 
 
 def test_shell_interaction_cmd_reset_term(app_pidfile_env):
@@ -89,20 +89,21 @@ def test_shell_interaction_cmd_reset_term(app_pidfile_env):
     ctrl = init_ctrl(app_pidfile_env)
     ctrl.start_term()
     shell = riotctrl.shell.ShellInteraction(ctrl)
-    res = shell.cmd('foobar')
-    assert 'foobar' in res
-    ctrl.start_term()   # reset's term implicitly
-    res = shell.cmd('snafoo')
-    assert 'snafoo' in res
+    res = shell.cmd("foobar")
+    assert "foobar" in res
+    ctrl.start_term()  # reset's term implicitly
+    res = shell.cmd("snafoo")
+    assert "snafoo" in res
     ctrl.stop_term()
 
 
 class Snafoo(riotctrl.shell.ShellInteraction):
     """Test inheritance class to test check_term decorator"""
+
     @riotctrl.shell.ShellInteraction.check_term
     def snafoo(self):
         """snafoo pseudo command"""
-        return self.cmd('snafoo')
+        return self.cmd("snafoo")
 
 
 def test_shell_interaction_check_term(app_pidfile_env):
@@ -110,7 +111,7 @@ def test_shell_interaction_check_term(app_pidfile_env):
     ctrl = init_ctrl(app_pidfile_env, skip_first_prompt=True)
     shell = Snafoo(ctrl)
     res = shell.snafoo()
-    assert 'snafoo' in res
+    assert "snafoo" in res
 
 
 def test_shell_interaction_check_term_when_term_started(app_pidfile_env):
@@ -120,6 +121,6 @@ def test_shell_interaction_check_term_when_term_started(app_pidfile_env):
     shell.start_term()
     res = shell.snafoo()
     try:
-        assert 'snafoo' in res
+        assert "snafoo" in res
     finally:
         del shell
diff --git a/riotctrl/tests/utils/application/create_file.py b/riotctrl/tests/utils/application/create_file.py
index bd2e63c..30e2570 100755
--- a/riotctrl/tests/utils/application/create_file.py
+++ b/riotctrl/tests/utils/application/create_file.py
@@ -11,7 +11,7 @@
 import argparse
 
 PARSER = argparse.ArgumentParser()
-PARSER.add_argument('running_file')
+PARSER.add_argument("running_file")
 
 
 def main():
@@ -25,12 +25,12 @@ def main():
     # 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')
+    with open(args.running_file, "w", encoding="utf-8") as rfile:
+        rfile.write("Running\n")
+    print("Running")
     while True:
         signal.pause()
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/riotctrl/tests/utils/application/ctrl.py b/riotctrl/tests/utils/application/ctrl.py
index a3a9abe..b29d4b9 100755
--- a/riotctrl/tests/utils/application/ctrl.py
+++ b/riotctrl/tests/utils/application/ctrl.py
@@ -19,11 +19,10 @@
 import subprocess
 
 PARSER = argparse.ArgumentParser()
-PARSER.add_argument('argument', nargs='+', default=[])
+PARSER.add_argument("argument", nargs="+", default=[])
 
 # Signals sent by 'pexpect' + SIGTERM
-FORWARDED_SIGNALS = (signal.SIGHUP, signal.SIGCONT, signal.SIGINT,
-                     signal.SIGTERM)
+FORWARDED_SIGNALS = (signal.SIGHUP, signal.SIGCONT, signal.SIGINT, signal.SIGTERM)
 
 
 def forward_signal(signum, proc):
@@ -54,6 +53,7 @@ def _reset(*_):
         """Terminate process and set the 'restart_process' flag."""
         restart_process.set()
         proc.terminate()
+
     signal.signal(termonsig, _reset)
 
     proc.wait()
@@ -70,5 +70,5 @@ def main():
         pass
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/riotctrl/tests/utils/application/echo.py b/riotctrl/tests/utils/application/echo.py
index 872f7fc..1f40138 100755
--- a/riotctrl/tests/utils/application/echo.py
+++ b/riotctrl/tests/utils/application/echo.py
@@ -6,11 +6,11 @@
 
 def main():
     """Print some header and echo the output."""
-    print('Starting RIOT Ctrl')
-    print('This example will echo')
+    print("Starting RIOT Ctrl")
+    print("This example will echo")
     while True:
         print(input())
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/riotctrl/tests/utils/application/hello.py b/riotctrl/tests/utils/application/hello.py
index e3b459b..15b2086 100755
--- a/riotctrl/tests/utils/application/hello.py
+++ b/riotctrl/tests/utils/application/hello.py
@@ -7,11 +7,11 @@
 
 def main():
     """Print some header and do nothing."""
-    print('Starting RIOT Ctrl')
-    print('Hello World')
+    print("Starting RIOT Ctrl")
+    print("Hello World")
     while True:
         signal.pause()
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/riotctrl/tests/utils/application/shell.py b/riotctrl/tests/utils/application/shell.py
index 6da48ac..90a18e0 100755
--- a/riotctrl/tests/utils/application/shell.py
+++ b/riotctrl/tests/utils/application/shell.py
@@ -6,15 +6,15 @@
 
 
 PARSER = argparse.ArgumentParser()
-PARSER.add_argument('skip_first_prompt', type=bool, default=False, nargs='?')
-PARSER.add_argument('--prompt', type=str, default="> ")
+PARSER.add_argument("skip_first_prompt", type=bool, default=False, nargs="?")
+PARSER.add_argument("--prompt", type=str, default="> ")
 
 
 def main(skip_first_prompt=False, prompt="> "):
     """Print some header and echo the output."""
     if not skip_first_prompt:
-        print('Starting RIOT Ctrl')
-        print('This example shell will echo')
+        print("Starting RIOT Ctrl")
+        print("This example shell will echo")
     else:
         print(input())
         print()
@@ -23,6 +23,6 @@ def main(skip_first_prompt=False, prompt="> "):
         print()
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     args = PARSER.parse_args()
     sys.exit(main(**vars(args)))
diff --git a/riotctrl/tests/utils/application/sigkill_script.py b/riotctrl/tests/utils/application/sigkill_script.py
index 5a2c036..7bfb9d0 100755
--- a/riotctrl/tests/utils/application/sigkill_script.py
+++ b/riotctrl/tests/utils/application/sigkill_script.py
@@ -9,10 +9,10 @@ def main():
     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())
+    print("Kill me with SIGKILL!")
+    print("My PID: %u" % os.getpid())
     signal.pause()
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()