From 7a73dfcc770ec4e06edb7f050d2a00ca0c7435bd Mon Sep 17 00:00:00 2001 From: Bastian Krause Date: Wed, 10 Aug 2022 14:14:05 +0200 Subject: [PATCH 1/2] crossbar: separate coordinator guest module arguments Python allows running a module via 'python -mmodule' (single argument). To be able to record the coordinator's coverage in a future commit, 'coverage run' is used instead of 'python'. The coverage tool is a little pickier than python and requires '-m' and the actual module name to be separate arguments. Since it will not hurt to have the arguments separated generally, split them. Signed-off-by: Bastian Krause --- .crossbar/config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.crossbar/config.yaml b/.crossbar/config.yaml index c172f940d..bc3ed9cf1 100644 --- a/.crossbar/config.yaml +++ b/.crossbar/config.yaml @@ -54,7 +54,8 @@ workers: type: guest executable: python3 arguments: - - -mlabgrid.remote.coordinator + - -m + - labgrid.remote.coordinator options: workdir: . env: From 33fa0ddb701e8a5f63280996e92cb98f23fea07d Mon Sep 17 00:00:00 2001 From: Bastian Krause Date: Wed, 10 Aug 2022 14:11:42 +0200 Subject: [PATCH 2/2] tests/conftest: record coordinator's coverage Recording the coordinator's coverage is useful to determine, which parts of the component are executed during tests. crossbar runs labgrid's coordinator component as a guest worker. Guest workers are spawned as subprocesses by crossbar. Therefore recording coverage of the coordinator component does not work out of the box. To allow recording coordinator coverage, set the coverage command line tool as the excutable in the crossbar configuration, whose 'run' subcommand works as a drop-in replacement for 'python'. This way the coverage of the guest worker is recorded separately. The '--parallel-mode' switch makes coverage add a unique suffix to the coverage data file. Set the location of the coverage data via '--data-file' to the root directory. pytest-cov automatically combines the coverage data in this directory. Since the crossbar process is forcefully terminated during the tests, the coverage data can not be written. Since the coverage tool registers an atexit SIGTERM handler to write its data, send crossbar SIGTERM, wait for any remaining output and the process to exit. Now the coverage data contains labgrid's coordinator component. Signed-off-by: Bastian Krause --- tests/conftest.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 546832dc3..d94d27aef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,12 @@ import logging +from signal import SIGTERM import sys import threading from importlib.util import find_spec import pytest import pexpect +import yaml from labgrid import Target from labgrid.driver import SerialDriver @@ -98,10 +100,33 @@ def serial_driver_no_name(target, serial_port, mocker): return s @pytest.fixture(scope='function') -def crossbar(tmpdir, pytestconfig): +def crossbar_config(tmpdir, pytestconfig): + crossbar_config = '.crossbar/config.yaml' + + pytestconfig.rootdir.join(crossbar_config).copy(tmpdir.mkdir('.crossbar')) + + # crossbar runs labgrid's coordinator component as a guest, record its coverage + if pytestconfig.pluginmanager.get_plugin('pytest_cov'): + with open(tmpdir.join(crossbar_config), 'r+') as stream: + conf = yaml.safe_load(stream) + + for worker in conf['workers']: + if worker['type'] == 'guest': + worker['executable'] = 'coverage' + worker['arguments'].insert(0, 'run') + worker['arguments'].insert(1, '--parallel-mode') + # pytest-cov combines coverage files in root dir automatically, so copy it there + coverage_data = pytestconfig.rootdir.join('.coverage') + worker['arguments'].insert(2, f'--data-file={coverage_data}') + + stream.seek(0) + yaml.safe_dump(conf, stream) + +@pytest.fixture(scope='function') +def crossbar(tmpdir, crossbar_config): if not find_spec('crossbar'): pytest.skip("crossbar not found") - pytestconfig.rootdir.join('.crossbar/config.yaml').copy(tmpdir.mkdir('.crossbar')) + spawn = pexpect.spawn( 'crossbar start --color false --logformat none', logfile=Prefixer(sys.stdout.buffer, 'crossbar'), @@ -116,9 +141,14 @@ def crossbar(tmpdir, pytestconfig): reader = threading.Thread(target=keep_reading, name='crossbar-reader', args=(spawn,), daemon=True) reader.start() yield spawn + + # let coverage write its data: + # https://coverage.readthedocs.io/en/latest/subprocess.html#process-termination print("stopping crossbar") - spawn.close(force=True) - assert not spawn.isalive() + spawn.kill(SIGTERM) + spawn.expect(pexpect.EOF) + spawn.wait() + reader.join() @pytest.fixture(scope='function')