From 2169e61968562aba57791acea7190e70e5285c9f Mon Sep 17 00:00:00 2001 From: Metin Kaya Date: Mon, 5 Feb 2024 10:11:35 +0000 Subject: [PATCH 1/3] utils/android: Add debug log about connection settings While we are there, also fix a trivial pylint issue regarding string format. Signed-off-by: Metin Kaya --- devlib/utils/android.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/devlib/utils/android.py b/devlib/utils/android.py index 36f064a61..6f2eb87ae 100755 --- a/devlib/utils/android.py +++ b/devlib/utils/android.py @@ -294,6 +294,10 @@ def __init__( total_transfer_timeout=total_transfer_timeout, transfer_poll_period=transfer_poll_period, ) + + self.logger.debug('server=%s port=%s device=%s as_root=%s', + adb_server, adb_port, device, adb_as_root) + self.timeout = timeout if timeout is not None else self.default_timeout if device is None: device = adb_get_device(timeout=timeout, adb_server=adb_server, adb_port=adb_port) @@ -545,9 +549,9 @@ def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS, adb_server=None, ad break time.sleep(10) else: # did not connect to the device - message = 'Could not connect to {}'.format(device or 'a device') + message = f'Could not connect to {device or "a device"} at {adb_server}:{adb_port}' if output: - message += '; got: "{}"'.format(output) + message += f'; got: {output}' raise HostError(message) From fb8887f8ca66ed0488b2df608f6ecc1035cd9c27 Mon Sep 17 00:00:00 2001 From: Metin Kaya Date: Mon, 5 Feb 2024 10:17:31 +0000 Subject: [PATCH 2/3] utils/misc: Move load_struct_from_yaml() from WA to devlib This is copied from WA (workload-automation/wa/utils/misc.py). Hence, published another PR [1] removes the implementation from WA. OTOH, this patch uses ``ruamel`` instead of ``yaml`` because of the latter's design issues. And also this patch fixes pylint issues in ``load_struct_from_yaml()``. [1] https://github.com/ARM-software/workload-automation/pull/1248 Signed-off-by: Metin Kaya --- devlib/utils/misc.py | 25 +++++++++++++++++++++++++ setup.py | 1 + 2 files changed, 26 insertions(+) diff --git a/devlib/utils/misc.py b/devlib/utils/misc.py index 365788545..e1e9dfa18 100644 --- a/devlib/utils/misc.py +++ b/devlib/utils/misc.py @@ -23,6 +23,7 @@ from itertools import groupby from operator import itemgetter from weakref import WeakSet +from ruamel.yaml import YAML import ctypes import logging @@ -590,6 +591,30 @@ def __str__(self): return message.format(self.filepath, self.lineno, self.message) +def load_struct_from_yaml(filepath): + """ + Parses a config structure from a YAML file. + The structure should be composed of basic Python types. + + :param filepath: Input file which contains YAML data. + :type filepath: str + + :raises LoadSyntaxError: if there is a syntax error in YAML data. + + :return: A dictionary which contains parsed YAML data + :rtype: Dict + """ + + try: + yaml = YAML(typ='safe', pure=True) + with open(filepath, 'r', encoding='utf-8') as file_handler: + return yaml.load(file_handler) + except yaml.YAMLError as ex: + message = ex.message if hasattr(ex, 'message') else '' + lineno = ex.problem_mark.line if hasattr(ex, 'problem_mark') else None + raise LoadSyntaxError(message, filepath=filepath, lineno=lineno) from ex + + RAND_MOD_NAME_LEN = 30 BAD_CHARS = string.punctuation + string.whitespace TRANS_TABLE = str.maketrans(BAD_CHARS, '_' * len(BAD_CHARS)) diff --git a/setup.py b/setup.py index 06b20952c..69acc9892 100644 --- a/setup.py +++ b/setup.py @@ -105,6 +105,7 @@ def _load_path(filepath): 'lxml', # More robust xml parsing 'nest_asyncio', # Allows running nested asyncio loops 'future', # for the "past" Python package + 'ruamel.yaml >= 0.15.72', # YAML formatted config parsing ], extras_require={ 'daq': ['daqpower>=2'], From 6217c77fe56bc21569c823056041cff19f8821fe Mon Sep 17 00:00:00 2001 From: Metin Kaya Date: Mon, 5 Feb 2024 08:36:19 +0000 Subject: [PATCH 3/3] tests/test_target: Read target connection settings from a YAML file This will be useful in automating CI tests without modifying the source code. Replace unittest with pytest in order to make parameter passing to test functions easier. Move target configuration reading and generating target object outside of the test function. Because we will run the test function for new targets and may want to add new test functions. While we are here, also fix pylint issues. Signed-off-by: Metin Kaya --- setup.py | 1 + tests/target_configs.yaml | 5 +++ tests/test_target.py | 68 +++++++++++++++++++++++++++------------ 3 files changed, 54 insertions(+), 20 deletions(-) create mode 100644 tests/target_configs.yaml diff --git a/setup.py b/setup.py index 69acc9892..e8b7d0fbe 100644 --- a/setup.py +++ b/setup.py @@ -102,6 +102,7 @@ def _load_path(filepath): 'wrapt', # Basic for construction of decorator functions 'numpy', 'pandas', + 'pytest', 'lxml', # More robust xml parsing 'nest_asyncio', # Allows running nested asyncio loops 'future', # for the "past" Python package diff --git a/tests/target_configs.yaml b/tests/target_configs.yaml new file mode 100644 index 000000000..47e00ec4e --- /dev/null +++ b/tests/target_configs.yaml @@ -0,0 +1,5 @@ +LocalLinuxTarget: + entry-0: + connection_settings: + unrooted: True + diff --git a/tests/test_target.py b/tests/test_target.py index 230aa35e6..0fa3fbf43 100644 --- a/tests/test_target.py +++ b/tests/test_target.py @@ -14,35 +14,63 @@ # limitations under the License. # +"""Module for testing targets.""" + import os import shutil import tempfile -from unittest import TestCase +from pprint import pp +import pytest from devlib import LocalLinuxTarget +from devlib.utils.misc import load_struct_from_yaml + + +def build_targets(): + """Read targets from a YAML formatted config file""" + + config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'target_configs.yaml') + + target_configs = load_struct_from_yaml(config_file) + if target_configs is None: + raise ValueError(f'{config_file} looks empty!') + + targets = [] + + if target_configs.get('LocalLinuxTarget') is not None: + print('> LocalLinux targets:') + for entry in target_configs['LocalLinuxTarget'].values(): + pp(entry) + ll_target = LocalLinuxTarget(connection_settings=entry['connection_settings']) + targets.append(ll_target) + + return targets + +@pytest.mark.parametrize("target", build_targets()) +def test_read_multiline_values(target): + """ + Test Target.read_tree_values_flat() -class TestReadTreeValues(TestCase): + :param target: Type of target per :class:`Target` based classes. + :type target: Target + """ - def test_read_multiline_values(self): - data = { - 'test1': '1', - 'test2': '2\n\n', - 'test3': '3\n\n4\n\n', - } + data = { + 'test1': '1', + 'test2': '2\n\n', + 'test3': '3\n\n4\n\n', + } - tempdir = tempfile.mkdtemp(prefix='devlib-test-') - for key, value in data.items(): - path = os.path.join(tempdir, key) - with open(path, 'w') as wfh: - wfh.write(value) + tempdir = tempfile.mkdtemp(prefix='devlib-test-') + for key, value in data.items(): + path = os.path.join(tempdir, key) + with open(path, 'w', encoding='utf-8') as wfh: + wfh.write(value) - t = LocalLinuxTarget(connection_settings={'unrooted': True}) - raw_result = t.read_tree_values_flat(tempdir) - result = {os.path.basename(k): v for k, v in raw_result.items()} + raw_result = target.read_tree_values_flat(tempdir) + result = {os.path.basename(k): v for k, v in raw_result.items()} - shutil.rmtree(tempdir) + shutil.rmtree(tempdir) - self.assertEqual({k: v.strip() - for k, v in data.items()}, - result) + assert {k: v.strip() for k, v in data.items()} == result